hash_map method about to be public for real
[html-element-library.git] / lib / HTML / Element / Library.pm
1 package HTML::Element::Library;
2
3 use 5.006001;
4 use strict;
5 use warnings;
6
7
8 our $DEBUG = 0;
9 #our $DEBUG = 1;
10
11 use Array::Group qw(:all);
12 use Carp qw(confess);
13 use Data::Dumper;
14 use HTML::Element;
15 use List::Util qw(first);
16 use List::MoreUtils qw/:all/;
17 use Params::Validate qw(:all);
18 use Scalar::Listify;
19 #use Tie::Cycle;
20 use List::Rotation::Cycle;
21
22 our %EXPORT_TAGS = ( 'all' => [ qw() ] );
23 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24 our @EXPORT = qw();
25
26
27
28 our $VERSION = '3.53';
29
30
31 # Preloaded methods go here.
32
33 sub HTML::Element::siblings {
34 my $element = shift;
35 my $p = $element->parent;
36 return () unless $p;
37 $p->content_list;
38 }
39
40 sub HTML::Element::hash_map {
41 my $container = shift;
42
43 my %p = validate(@_, {
44 hash => { type => HASHREF },
45 to_attr => 1,
46 excluding => { type => ARRAYREF },
47 debug => { default => 0 },
48 });
49
50 my @same_as = $container->look_down('_attr' => $p{to_attr});
51
52 for my $same_as (@same_as) {
53 next if first { $same_as->attr($p{to_attr}) eq $_ } @{$p{excluding}} ;
54 my $hash_key = $same_as->attr($p{to_attr}) ;
55 $same_as->replace_content( $p{hash}->{$hash_key} ) ;
56 }
57
58 }
59
60
61 sub HTML::Element::passover {
62 my ($tree, $child_id) = @_;
63
64 warn "ARGS: my ($tree, $child_id)" if $DEBUG;
65 warn $tree->as_HTML(undef, ' ') if $DEBUG;
66
67 my $exodus = $tree->look_down(id => $child_id);
68
69 warn "E: $exodus" if $DEBUG;
70
71 my @s = HTML::Element::siblings($exodus);
72
73 for my $s (@s) {
74 next unless ref $s;
75 if ($s->attr('id') eq $child_id) {
76 ;
77 } else {
78 $s->delete;
79 }
80 }
81
82 return $exodus; # Goodbye Egypt! http://en.wikipedia.org/wiki/Passover
83
84 }
85
86 sub HTML::Element::sibdex {
87
88 my $element = shift;
89 firstidx { $_ eq $element } $element->siblings
90
91 }
92
93 sub HTML::Element::addr { goto &HTML::Element::sibdex }
94
95 sub HTML::Element::replace_content {
96 my $elem = shift;
97 $elem->delete_content;
98 $elem->push_content(@_);
99 }
100
101 sub HTML::Element::wrap_content {
102 my($self, $wrap) = @_;
103 my $content = $self->content;
104 if (ref $content) {
105 $wrap->push_content(@$content);
106 @$content = ($wrap);
107 }
108 else {
109 $self->push_content($wrap);
110 }
111 $wrap;
112 }
113
114 sub HTML::Element::Library::super_literal {
115 my($text) = @_;
116
117 HTML::Element->new('~literal', text => $text);
118 }
119
120
121 sub HTML::Element::position {
122 # Report coordinates by chasing addr's up the
123 # HTML::ElementSuper tree. We know we've reached
124 # the top when a) there is no parent, or b) the
125 # parent is some HTML::Element unable to report
126 # it's position.
127 my $p = shift;
128 my @pos;
129 while ($p) {
130 my $a = $p->addr;
131 unshift(@pos, $a) if defined $a;
132 $p = $p->parent;
133 }
134 @pos;
135 }
136
137
138 sub HTML::Element::content_handler {
139 my ($tree, %content_hash) = @_;
140
141 for my $k (keys %content_hash) {
142 $tree->set_child_content(id => $k, $content_hash{$k});
143 }
144
145
146 }
147
148
149 sub make_counter {
150 my $i = 1;
151 sub {
152 shift() . ':' . $i++
153 }
154 }
155
156
157 sub HTML::Element::iter {
158 my ($tree, $p, @data) = @_;
159
160 # warn 'P: ' , $p->attr('id') ;
161 # warn 'H: ' , $p->as_HTML;
162
163 # my $id_incr = make_counter;
164 my @item = map {
165 my $new_item = clone $p;
166 $new_item->replace_content($_);
167 # $new_item->attr('id', $id_incr->( $p->attr('id') ));
168 $new_item;
169 } @data;
170
171 $p->replace_with(@item);
172
173 }
174
175
176 sub HTML::Element::iter2 {
177
178 my $tree = shift;
179
180 #warn "INPUT TO TABLE2: ", Dumper \@_;
181
182 my %p = validate(
183 @_, {
184 wrapper_ld => { default => ['_tag' => 'dl'] },
185 wrapper_data => 1,
186 wrapper_proc => { default => undef },
187 item_ld => { default => sub {
188 my $tree = shift;
189 [
190 $tree->look_down('_tag' => 'dt'),
191 $tree->look_down('_tag' => 'dd')
192 ];
193 }
194 },
195 item_data => { default => sub { my ($wrapper_data) = @_;
196 shift(@{$wrapper_data}) ;
197 }},
198 item_proc => {
199 default => sub {
200 my ($item_elems, $item_data, $row_count) = @_;
201 $item_elems->[$_]->replace_content($item_data->[$_]) for (0,1) ;
202 $item_elems;
203 }},
204 splice => { default => sub {
205 my ($container, @item_elems) = @_;
206 $container->splice_content(0, 2, @item_elems);
207 }
208 },
209 debug => {default => 0}
210 }
211 );
212
213 warn "wrapper_data: " . Dumper $p{wrapper_data} if $p{debug} ;
214
215 my $container = ref_or_ld($tree, $p{wrapper_ld});
216 warn "container: " . $container if $p{debug} ;
217 warn "wrapper_(preproc): " . $container->as_HTML if $p{debug} ;
218 $p{wrapper_proc}->($container) if defined $p{wrapper_proc} ;
219 warn "wrapper_(postproc): " . $container->as_HTML if $p{debug} ;
220
221 my $_item_elems = $p{item_ld}->($container);
222
223
224
225 my $row_count;
226 my @item_elem;
227 {
228 my $item_data = $p{item_data}->($p{wrapper_data});
229 last unless defined $item_data;
230
231 warn Dumper("item_data", $item_data);
232
233
234 my $item_elems = [ map { $_->clone } @{$_item_elems} ] ;
235
236 if ($p{debug}) {
237 for (@{$item_elems}) {
238 warn "ITEM_ELEMS ", $_->as_HTML;
239 }
240 }
241
242 my $new_item_elems = $p{item_proc}->($item_elems, $item_data, ++$row_count);
243
244 if ($p{debug}) {
245 for (@{$new_item_elems}) {
246 warn "NEWITEM_ELEMS ", $_->as_HTML;
247 }
248 }
249
250
251 push @item_elem, @{$new_item_elems} ;
252
253 redo;
254 }
255
256 warn "pushing " . @item_elem . " elems " if $p{debug} ;
257
258 $p{splice}->($container, @item_elem);
259
260 }
261
262 sub HTML::Element::dual_iter {
263 my ($parent, $data) = @_;
264
265 my ($prototype_a, $prototype_b) = $parent->content_list;
266
267 # my $id_incr = make_counter;
268
269 my $i;
270
271 @$data %2 == 0 or
272 confess 'dataset does not contain an even number of members';
273
274 my @iterable_data = ngroup 2 => @$data;
275
276 my @item = map {
277 my ($new_a, $new_b) = map { clone $_ } ($prototype_a, $prototype_b) ;
278 $new_a->splice_content(0,1, $_->[0]);
279 $new_b->splice_content(0,1, $_->[1]);
280 #$_->attr('id', $id_incr->($_->attr('id'))) for ($new_a, $new_b) ;
281 ($new_a, $new_b)
282 } @iterable_data;
283
284 $parent->splice_content(0, 2, @item);
285
286 }
287
288
289 sub HTML::Element::set_child_content {
290 my $tree = shift;
291 my $content = pop;
292 my @look_down = @_;
293
294 my $content_tag = $tree->look_down(@look_down);
295
296 unless ($content_tag) {
297 warn "criteria [@look_down] not found";
298 return;
299 }
300
301 $content_tag->replace_content($content);
302
303 }
304
305 sub HTML::Element::highlander {
306 my ($tree, $local_root_id, $aref, @arg) = @_;
307
308 ref $aref eq 'ARRAY' or confess
309 "must supply array reference";
310
311 my @aref = @$aref;
312 @aref % 2 == 0 or confess
313 "supplied array ref must have an even number of entries";
314
315 warn __PACKAGE__ if $DEBUG;
316
317 my $survivor;
318 while (my ($id, $test) = splice @aref, 0, 2) {
319 warn $id if $DEBUG;
320 if ($test->(@arg)) {
321 $survivor = $id;
322 last;
323 }
324 }
325
326
327 my @id_survivor = (id => $survivor);
328 my $survivor_node = $tree->look_down(@id_survivor);
329 # warn $survivor;
330 # warn $local_root_id;
331 # warn $node;
332
333 warn "survivor: $survivor" if $DEBUG;
334 warn "tree: " . $tree->as_HTML if $DEBUG;
335
336 $survivor_node or die "search for @id_survivor failed in tree($tree): " . $tree->as_HTML;
337
338 my $survivor_node_parent = $survivor_node->parent;
339 $survivor_node = $survivor_node->clone;
340 $survivor_node_parent->replace_content($survivor_node);
341
342 warn "new tree: " . $tree->as_HTML if $DEBUG;
343
344 $survivor_node;
345 }
346
347
348 sub HTML::Element::highlander2 {
349 my $tree = shift;
350
351 my %p = validate(@_, {
352 cond => { type => ARRAYREF },
353 cond_arg => { type => ARRAYREF,
354 default => []
355 },
356 debug => { default => 0 }
357 }
358 );
359
360
361 my @cond = @{$p{cond}};
362 @cond % 2 == 0 or confess
363 "supplied array ref must have an even number of entries";
364
365 warn __PACKAGE__ if $p{debug};
366
367 my @cond_arg = @{$p{cond_arg}};
368
369 my $survivor; my $then;
370 while (my ($id, $if_then) = splice @cond, 0, 2) {
371
372 warn $id if $p{debug};
373 my ($if, $_then);
374
375 if (ref $if_then eq 'ARRAY') {
376 ($if, $_then) = @$if_then;
377 } else {
378 ($if, $_then) = ($if_then, sub {});
379 }
380
381 if ($if->(@cond_arg)) {
382 $survivor = $id;
383 $then = $_then;
384 last;
385 }
386
387 }
388
389 my @ld = (ref $survivor eq 'ARRAY')
390 ? @$survivor
391 : (id => $survivor)
392 ;
393
394 warn "survivor: ", $survivor if $p{debug};
395 warn "survivor_ld: ", Dumper \@ld if $p{debug};
396
397
398 my $survivor_node = $tree->look_down(@ld);
399
400 $survivor_node or confess
401 "search for @ld failed in tree($tree): " . $tree->as_HTML;
402
403 my $survivor_node_parent = $survivor_node->parent;
404 $survivor_node = $survivor_node->clone;
405 $survivor_node_parent->replace_content($survivor_node);
406
407
408 # **************** NEW FUNCTIONALITY *******************
409
410 # apply transforms on survivor node
411
412
413 warn "SURV::pre_trans " . $survivor_node->as_HTML if $p{debug};
414 $then->($survivor_node, @cond_arg);
415 warn "SURV::post_trans " . $survivor_node->as_HTML if $p{debug};
416
417 # **************** NEW FUNCTIONALITY *******************
418
419
420
421
422 $survivor_node;
423 }
424
425
426 sub overwrite_action {
427 my ($mute_node, %X) = @_;
428
429 $mute_node->attr($X{local_attr}{name} => $X{local_attr}{value}{new});
430 }
431
432
433 sub HTML::Element::overwrite_attr {
434 my $tree = shift;
435
436 $tree->mute_elem(@_, \&overwrite_action);
437 }
438
439
440
441 sub HTML::Element::mute_elem {
442 my ($tree, $mute_attr, $closures, $post_hook) = @_;
443
444 warn "my mute_node = $tree->look_down($mute_attr => qr/.*/) ;";
445 my @mute_node = $tree->look_down($mute_attr => qr/.*/) ;
446
447 for my $mute_node (@mute_node) {
448 my ($local_attr,$mute_key) = split /\s+/, $mute_node->attr($mute_attr);
449 my $local_attr_value_current = $mute_node->attr($local_attr);
450 my $local_attr_value_new = $closures->{$mute_key}->($tree, $mute_node, $local_attr_value_current);
451 $post_hook->(
452 $mute_node,
453 tree => $tree,
454 local_attr => {
455 name => $local_attr,
456 value => {
457 current => $local_attr_value_current,
458 new => $local_attr_value_new
459 }
460 }
461 ) if ($post_hook) ;
462 }
463 }
464
465
466
467 sub HTML::Element::table {
468
469 my ($s, %table) = @_;
470
471 my $table = {};
472
473 # use Data::Dumper; warn Dumper \%table;
474
475 # ++$DEBUG if $table{debug} ;
476
477
478 # Get the table element
479 $table->{table_node} = $s->look_down(id => $table{gi_table});
480 $table->{table_node} or confess
481 "table tag not found via (id => $table{gi_table}";
482
483 # Get the prototype tr element(s)
484 my @table_gi_tr = listify $table{gi_tr} ;
485 my @iter_node = map
486 {
487 my $tr = $table->{table_node}->look_down(id => $_);
488 $tr or confess "tr with id => $_ not found";
489 $tr;
490 } @table_gi_tr;
491
492 warn "found " . @iter_node . " iter nodes " if $DEBUG;
493 # tie my $iter_node, 'Tie::Cycle', \@iter_node;
494 my $iter_node = List::Rotation::Cycle->new(@iter_node);
495
496 # warn $iter_node;
497 warn Dumper ($iter_node, \@iter_node) if $DEBUG;
498
499 # $table->{content} = $table{content};
500 #$table->{parent} = $table->{table_node}->parent;
501
502
503 # $table->{table_node}->detach;
504 # $_->detach for @iter_node;
505
506 my @table_rows;
507
508 {
509 my $row = $table{tr_data}->($table, $table{table_data});
510 last unless defined $row;
511
512 # get a sample table row and clone it.
513 my $I = $iter_node->next;
514 warn "I: $I" if $DEBUG;
515 my $new_iter_node = $I->clone;
516
517
518 $table{td_data}->($new_iter_node, $row);
519 push @table_rows, $new_iter_node;
520
521 redo;
522 }
523
524 if (@table_rows) {
525
526 my $replace_with_elem = $s->look_down(id => shift @table_gi_tr) ;
527 for (@table_gi_tr) {
528 $s->look_down(id => $_)->detach;
529 }
530
531 $replace_with_elem->replace_with(@table_rows);
532
533 }
534
535 }
536
537 sub ref_or_ld {
538
539 my ($tree, $slot) = @_;
540
541 if (ref($slot) eq 'CODE') {
542 $slot->($tree);
543 } else {
544 $tree->look_down(@$slot);
545 }
546 }
547
548
549
550 sub HTML::Element::table2 {
551
552 my $tree = shift;
553
554
555
556 my %p = validate(
557 @_, {
558 table_ld => { default => ['_tag' => 'table'] },
559 table_data => 1,
560 table_proc => { default => undef },
561
562 tr_ld => { default => ['_tag' => 'tr'] },
563 tr_data => { default => sub { my ($self, $data) = @_;
564 shift(@{$data}) ;
565 }},
566 tr_base_id => { default => undef },
567 tr_proc => { default => sub {} },
568 td_proc => 1,
569 debug => {default => 0}
570 }
571 );
572
573 warn "INPUT TO TABLE2: ", Dumper \@_ if $p{debug};
574
575 warn "table_data: " . Dumper $p{table_data} if $p{debug} ;
576
577 my $table = {};
578
579 # use Data::Dumper; warn Dumper \%table;
580
581 # ++$DEBUG if $table{debug} ;
582
583 # Get the table element
584 #warn 1;
585 $table->{table_node} = ref_or_ld( $tree, $p{table_ld} ) ;
586 #warn 2;
587 $table->{table_node} or confess
588 "table tag not found via " . Dumper($p{table_ld}) ;
589
590 warn "table: " . $table->{table_node}->as_HTML if $p{debug};
591
592
593 # Get the prototype tr element(s)
594 my @proto_tr = ref_or_ld( $table->{table_node}, $p{tr_ld} ) ;
595
596 warn "found " . @proto_tr . " iter nodes " if $p{debug};
597
598 @proto_tr or return ;
599
600 if ($p{debug}) {
601 warn $_->as_HTML for @proto_tr;
602 }
603 my $proto_tr = List::Rotation::Cycle->new(@proto_tr);
604
605 my $tr_parent = $proto_tr[0]->parent;
606 warn "parent element of trs: " . $tr_parent->as_HTML if $p{debug};
607
608 my $row_count;
609
610 my @table_rows;
611
612 {
613 my $row = $p{tr_data}->($table, $p{table_data}, $row_count);
614 warn "data row: " . Dumper $row if $p{debug};
615 last unless defined $row;
616
617 # wont work: my $new_iter_node = $table->{iter_node}->clone;
618 my $new_tr_node = $proto_tr->next->clone;
619 warn "new_tr_node: $new_tr_node" if $p{debug};
620
621 $p{tr_proc}->($tree, $new_tr_node, $row, $p{tr_base_id}, ++$row_count)
622 if defined $p{tr_proc};
623
624 warn "data row redux: " . Dumper $row if $p{debug};
625 #warn 3.3;
626
627 $p{td_proc}->($new_tr_node, $row);
628 push @table_rows, $new_tr_node;
629
630 #warn 4.4;
631
632 redo;
633 }
634
635 $_->detach for @proto_tr;
636
637 $tr_parent->push_content(@table_rows) if (@table_rows) ;
638
639 }
640
641
642 sub HTML::Element::unroll_select {
643
644 my ($s, %select) = @_;
645
646 my $select = {};
647
648 my $select_node = $s->look_down(id => $select{select_label});
649
650 my $option = $select_node->look_down('_tag' => 'option');
651
652 # warn $option;
653
654
655 $option->detach;
656
657 while (my $row = $select{data_iter}->($select{data}))
658 {
659 # warn Dumper($row);
660 my $o = $option->clone;
661 $o->attr('value', $select{option_value}->($row));
662 $o->attr('SELECTED', 1) if ($select{option_selected}->($row)) ;
663
664 $o->replace_content($select{option_content}->($row));
665 $select_node->push_content($o);
666 }
667
668
669 }
670
671
672
673 sub HTML::Element::set_sibling_content {
674 my ($elt, $content) = @_;
675
676 $elt->parent->splice_content($elt->pindex + 1, 1, $content);
677
678 }
679
680 sub HTML::TreeBuilder::parse_string {
681 my ($package, $string) = @_;
682
683 my $h = HTML::TreeBuilder->new;
684 HTML::TreeBuilder->parse($string);
685
686 }
687
688
689
690 1;
691 __END__
692 # Below is stub documentation for your module. You'd better edit it!
693
694 =head1 NAME
695
696 HTML::Element::Library - HTML::Element convenience functions
697
698 =head1 SYNOPSIS
699
700 use HTML::Element::Library;
701 use HTML::TreeBuilder;
702
703 =head1 DESCRIPTION
704
705 This method provides API calls for common actions on trees when using
706 L<HTML::Tree>.
707
708 =head1 METHODS
709
710 The test suite contains examples of each of these methods in a
711 file C<t/$method.t>
712
713 =head2 Positional Querying Methods
714
715 =head3 $elem->siblings
716
717 Return a list of all nodes under the same parent.
718
719 =head3 $elem->sibdex
720
721 Return the index of C<$elem> into the array of siblings of which it is
722 a part. L<HTML::ElementSuper> calls this method C<addr> but I don't think
723 that is a descriptive name. And such naming is deceptively close to the
724 C<address> function of C<HTML::Element>. HOWEVER, in the interest of
725 backwards compatibility, both methods are available.
726
727 =head3 $elem->addr
728
729 Same as sibdex
730
731 =head3 $elem->position()
732
733 Returns the coordinates of this element in the tree it inhabits.
734 This is accomplished by succesively calling addr() on ancestor
735 elements until either a) an element that does not support these
736 methods is found, or b) there are no more parents. The resulting
737 list is the n-dimensional coordinates of the element in the tree.
738
739 =head2 Element Decoration Methods
740
741 =head3 HTML::Element::Library::super_literal($text)
742
743 In L<HTML::Element>, Sean Burke discusses super-literals. They are
744 text which does not get escaped. Great for includng Javascript in
745 HTML. Also great for including foreign language into a document.
746
747 So, you basically toss C<super_literal> your text and back comes
748 your text wrapped in a C<~literal> element.
749
750 One of these days, I'll around to writing a nice C<EXPORT> section.
751
752 =head2 Tree Rewriting Methods
753
754 =head3 $elem->hash_map(hash => \%h, to_attr => $attr, excluding => \@excluded)
755
756 This method is designed to take a hashref and populate a series of elements. For example:
757
758
759 <table>
760 <tr sclass="tr" class="alt" align="left" valign="top">
761 <td sid="people_id">1</td>
762 <td sid="phone">(877) 255-3239</td>
763 <td sid="password">*********</td>
764 </tr>
765 </table>
766
767 In the table above, there are several attributes named C<sid>. If we have a hashref whose keys are the same:
768
769 my %data = (people_id => 888, phone => '444-4444', password => 'dont-you-dare-render');
770
771 Then a single API call allows us to populate the HTML while excluding those ones we dont:
772
773 $tree->hash_map(hash => \%data, to_attr => 'sid', excluding => ['password']);
774
775 Of course, the other way to prevent rendering some of the hash mapping is to not give that element the attr
776 you plan to use for hash mapping.
777
778
779 =head3 $elem->replace_content(@new_elem)
780
781 Replaces all of C<$elem>'s content with C<@new_elem>.
782
783 =head3 $elem->wrap_content($wrapper_element)
784
785 Wraps the existing content in the provided element. If the provided element
786 happens to be a non-element, a push_content is performed instead.
787
788 =head3 $elem->set_child_content(@look_down, $content)
789
790 This method looks down $tree using the criteria specified in @look_down using the the HTML::Element look_down() method.
791
792 After finding the node, it detaches the node's content and pushes $content as the node's content.
793
794 =head3 $tree->content_handler(%id_content)
795
796 This is a convenience method. Because the look_down criteria will often simply be:
797
798 id => 'fixme'
799
800 to find things like:
801
802 <a id=fixme href=http://www.somesite.org>replace_content</a>
803
804 You can call this method to shorten your typing a bit. You can simply type
805
806 $elem->content_handler( fixme => 'new text' )
807
808 Instead of typing:
809
810 $elem->set_child_content(sid => 'fixme', 'new text')
811
812 PLEASE NOTE: you can pass a hash whose keys are C<id>s and whose values are the content you want there and it will perform the replacement on each hash member:
813
814 my %id_content = (name => "Terrence Brannon",
815 email => 'tbrannon@in.com',
816 balance => 666,
817 content => $main_content);
818
819 $tree->content_handler(%id_content);
820
821 =head3 $tree->highlander($subtree_span_id, $conditionals, @conditionals_args)
822
823 This allows for "if-then-else" style processing. Highlander was a movie in
824 which only one would survive. Well, in terms of a tree when looking at a
825 structure that you want to process in C<if-then-else> style, only one child
826 will survive. For example, given this HTML template:
827
828 <span klass="highlander" id="age_dialog">
829 <span id="under10">
830 Hello, does your mother know you're
831 using her AOL account?
832 </span>
833 <span id="under18">
834 Sorry, you're not old enough to enter
835 (and too dumb to lie about your age)
836 </span>
837 <span id="welcome">
838 Welcome
839 </span>
840 </span>
841
842 We only want one child of the C<span> tag with id C<age_dialog> to remain
843 based on the age of the person visiting the page.
844
845 So, let's setup a call that will prune the subtree as a function of age:
846
847 sub process_page {
848 my $age = shift;
849 my $tree = HTML::TreeBuilder->new_from_file('t/html/highlander.html');
850
851 $tree->highlander
852 (age_dialog =>
853 [
854 under10 => sub { $_[0] < 10} ,
855 under18 => sub { $_[0] < 18} ,
856 welcome => sub { 1 }
857 ],
858 $age
859 );
860
861 And there we have it. If the age is less than 10, then the node with
862 id C<under10> remains. For age less than 18, the node with id C<under18>
863 remains.
864 Otherwise our "else" condition fires and the child with id C<welcome> remains.
865
866 =head3 $tree->passover($id_of_element)
867
868 In some cases, you know exactly which element should survive. In this case,
869 you can simply call C<passover> to remove it's siblings. For the HTML
870 above, you could delete C<under10> and C<welcome> by simply calling:
871
872 $tree->passover('under18');
873
874 =head3 $tree->highlander2($tree, $conditionals, @conditionals_args)
875
876 Right around the same time that C<table2()> came into being, Seamstress
877 began to tackle tougher and tougher processing problems. It became clear that
878 a more powerful highlander was needed... one that not only snipped the tree
879 of the nodes that should not survive, but one that allows for
880 post-processing of the survivor node. And one that was more flexible with
881 how to find the nodes to snip.
882
883 Thus (drum roll) C<highlander2()>.
884
885 So let's look at our HTML which requires post-selection processing:
886
887 <span klass="highlander" id="age_dialog">
888 <span id="under10">
889 Hello, little <span id=age>AGE</span>-year old,
890 does your mother know you're using her AOL account?
891 </span>
892 <span id="under18">
893 Sorry, you're only <span id=age>AGE</span>
894 (and too dumb to lie about your age)
895 </span>
896 <span id="welcome">
897 Welcome, isn't it good to be <span id=age>AGE</span> years old?
898 </span>
899 </span>
900
901 In this case, a branch survives, but it has dummy data in it. We must take
902 the surviving segment of HTML and rewrite the age C<span> with the age.
903 Here is how we use C<highlander2()> to do so:
904
905 sub replace_age {
906 my $branch = shift;
907 my $age = shift;
908 $branch->look_down(id => 'age')->replace_content($age);
909 }
910
911 my $if_then = $tree->look_down(id => 'age_dialog');
912
913 $if_then->highlander2(
914 cond => [
915 under10 => [
916 sub { $_[0] < 10} ,
917 \&replace_age
918 ],
919 under18 => [
920 sub { $_[0] < 18} ,
921 \&replace_age
922 ],
923 welcome => [
924 sub { 1 },
925 \&replace_age
926 ]
927 ],
928 cond_arg => [ $age ]
929 );
930
931 We pass it the tree (C<$if_then>), an arrayref of conditions
932 (C<cond>) and an arrayref of arguments which are passed to the
933 C<cond>s and to the replacement subs.
934
935 The C<under10>, C<under18> and C<welcome> are id attributes in the
936 tree of the siblings of which only one will survive. However,
937 should you need to do
938 more complex look-downs to find the survivor,
939 then supply an array ref instead of a simple
940 scalar:
941
942
943 $if_then->highlander2(
944 cond => [
945 [class => 'r12'] => [
946 sub { $_[0] < 10} ,
947 \&replace_age
948 ],
949 [class => 'z22'] => [
950 sub { $_[0] < 18} ,
951 \&replace_age
952 ],
953 [class => 'w88'] => [
954 sub { 1 },
955 \&replace_age
956 ]
957 ],
958 cond_arg => [ $age ]
959 );
960
961
962 =head3 $tree->overwrite_attr($mutation_attr => $mutating_closures)
963
964 This method is designed for taking a tree and reworking a set of nodes in
965 a stereotyped fashion. For instance let's say you have 3 remote image
966 archives, but you don't want to put long URLs in your img src
967 tags for reasons of abstraction, re-use and brevity. So instead you do this:
968
969 <img src="/img/smiley-face.jpg" fixup="src lnc">
970 <img src="/img/hot-babe.jpg" fixup="src playboy">
971 <img src="/img/footer.jpg" fixup="src foobar">
972
973 and then when the tree of HTML is being processed, you make this call:
974
975 my %closures = (
976 lnc => sub { my ($tree, $mute_node, $attr_value)= @_; "http://lnc.usc.edu$attr_value" },
977 playboy => sub { my ($tree, $mute_node, $attr_value)= @_; "http://playboy.com$attr_value" }
978 foobar => sub { my ($tree, $mute_node, $attr_value)= @_; "http://foobar.info$attr_value" }
979 )
980
981 $tree->overwrite_attr(fixup => \%closures) ;
982
983 and the tags come out modified like so:
984
985 <img src="http://lnc.usc.edu/img/smiley-face.jpg" fixup="src lnc">
986 <img src="http://playboy.com/img/hot-babe.jpg" fixup="src playboy">
987 <img src="http://foobar.info/img/footer.jpg" fixup="src foobar">
988
989 =head3 $tree->mute_elem($mutation_attr => $mutating_closures, [ $post_hook ] )
990
991 This is a generalization of C<overwrite_attr>. C<overwrite_attr>
992 assumes the return value of the
993 closure is supposed overwrite an attribute value and does it for you.
994 C<mute_elem> is a more general function which does nothing but
995 hand the closure the element and let it mutate it as it jolly well pleases :)
996
997 In fact, here is the implementation of C<overwrite_attr>
998 to give you a taste of how C<mute_attr> is used:
999
1000 sub overwrite_action {
1001 my ($mute_node, %X) = @_;
1002
1003 $mute_node->attr($X{local_attr}{name} => $X{local_attr}{value}{new});
1004 }
1005
1006
1007 sub HTML::Element::overwrite_attr {
1008 my $tree = shift;
1009
1010 $tree->mute_elem(@_, \&overwrite_action);
1011 }
1012
1013
1014
1015
1016 =head2 Tree-Building Methods
1017
1018
1019
1020 =head3 Unrolling an array via a single sample element (<ul> container)
1021
1022 This is best described by example. Given this HTML:
1023
1024 <strong>Here are the things I need from the store:</strong>
1025 <ul>
1026 <li class="store_items">Sample item</li>
1027 </ul>
1028
1029 We can unroll it like so:
1030
1031 my $li = $tree->look_down(class => 'store_items');
1032
1033 my @items = qw(bread butter vodka);
1034
1035 $tree->iter($li => @items);
1036
1037 To produce this:
1038
1039
1040 <html>
1041 <head></head>
1042 <body>Here are the things I need from the store:
1043 <ul>
1044 <li class="store_items">bread</li>
1045 <li class="store_items">butter</li>
1046 <li class="store_items">vodka</li>
1047 </ul>
1048 </body>
1049 </html>
1050
1051 =head3 Unrolling an array via n sample elements (<dl> container)
1052
1053 C<iter()> was fine for awhile, but some things
1054 (e.g. definition lists) need a more general function to make them easy to
1055 do. Hence C<iter2()>. This function will be explained by example of unrolling
1056 a simple definition list.
1057
1058 So here's our mock-up HTML from the designer:
1059
1060 <dl class="dual_iter" id="service_plan">
1061 <dt>
1062 Artist
1063 </dt>
1064 <dd>
1065 A person who draws blood.
1066 </dd>
1067
1068 <dt>
1069 Musician
1070 </dt>
1071 <dd>
1072 A clone of Iggy Pop.
1073 </dd>
1074
1075 <dt>
1076 Poet
1077 </dt>
1078 <dd>
1079 A relative of Edgar Allan Poe.
1080 </dd>
1081
1082 <dt class="adstyle">sample header</dt>
1083 <dd class="adstyle2">sample data</dd>
1084
1085 </dl>
1086
1087
1088 And we want to unroll our data set:
1089
1090 my @items = (
1091 ['the pros' => 'never have to worry about service again'],
1092 ['the cons' => 'upfront extra charge on purchase'],
1093 ['our choice' => 'go with the extended service plan']
1094 );
1095
1096
1097 Now, let's make this problem a bit harder to show off the power of C<iter2()>.
1098 Let's assume that we want only the last <dt> and it's accompanying <dd>
1099 (the one with "sample data") to be used as the sample data
1100 for unrolling with our data set. Let's further assume that we want them to
1101 remain in the final output.
1102
1103 So now, the API to C<iter2()> will be discussed and we will explain how our
1104 goal of getting our data into HTML fits into the API.
1105
1106 =over 4
1107
1108 =item * wrapper_ld
1109
1110 This is how to look down and find the container of all the elements we will
1111 be unrolling. The <dl> tag is the container for the dt and dd tags we will be
1112 unrolling.
1113
1114 If you pass an anonymous subroutine, then it is presumed that execution of
1115 this subroutine will return the HTML::Element representing the container tag.
1116 If you pass an array ref, then this will be dereferenced and passed to
1117 C<HTML::Element::look_down()>.
1118
1119 default value: C<< ['_tag' => 'dl'] >>
1120
1121 Based on the mock HTML above, this default is fine for finding our container
1122 tag. So let's move on.
1123
1124 =item * wrapper_data
1125
1126 This is an array reference of data that we will be putting into the container.
1127 You must supply this. C<@items> above is our C<wrapper_data>.
1128
1129 =item * wrapper_proc
1130
1131 After we find the container via C<wrapper_ld>, we may want to pre-process
1132 some aspect of this tree. In our case the first two sets of dt and dd need
1133 to be removed, leaving the last dt and dd. So, we supply a C<wrapper_proc>
1134 which will do this.
1135
1136 default: undef
1137
1138 =item * item_ld
1139
1140 This anonymous subroutine returns an array ref of C<HTML::Element>s that will
1141 be cloned and populated with item data
1142 (item data is a "row" of C<wrapper_data>).
1143
1144 default: returns an arrayref consisting of the dt and dd element inside the
1145 container.
1146
1147 =item * item_data
1148
1149 This is a subroutine that takes C<wrapper_data> and retrieves one "row"
1150 to be "pasted" into the array ref of C<HTML::Element>s found via C<item_ld>.
1151 I hope that makes sense.
1152
1153 default: shifts C<wrapper_data>.
1154
1155 =item * item_proc
1156
1157 This is a subroutine that takes the C<item_data> and the C<HTML::Element>s
1158 found via C<item_ld> and produces an arrayref of C<HTML::Element>s which will
1159 eventually be spliced into the container.
1160
1161 Note that this subroutine MUST return the new items. This is done
1162 So that more items than were passed in can be returned. This is
1163 useful when, for example, you must return 2 dts for an input data item.
1164 And when would you do this? When a single term has multiple spellings
1165 for instance.
1166
1167 default: expects C<item_data> to be an arrayref of two elements and
1168 C<item_elems> to be an arrayref of two C<HTML::Element>s. It replaces the
1169 content of the C<HTML::Element>s with the C<item_data>.
1170
1171 =item * splice
1172
1173 After building up an array of C<@item_elems>, the subroutine passed as
1174 C<splice> will be given the parent container HTML::Element and the
1175 C<@item_elems>. How the C<@item_elems> end up in the container is up to this
1176 routine: it could put half of them in. It could unshift them or whatever.
1177
1178 default: C<< $container->splice_content(0, 2, @item_elems) >>
1179 In other words, kill the 2 sample elements with the newly generated
1180 @item_elems
1181
1182 =back
1183
1184 So now that we have documented the API, let's see the call we need:
1185
1186 $tree->iter2(
1187 # default wrapper_ld ok.
1188 wrapper_data => \@items,
1189 wrapper_proc => sub {
1190 my ($container) = @_;
1191
1192 # only keep the last 2 dts and dds
1193 my @content_list = $container->content_list;
1194 $container->splice_content(0, @content_list - 2);
1195 },
1196
1197 # default item_ld is fine.
1198 # default item_data is fine.
1199 # default item_proc is fine.
1200 splice => sub {
1201 my ($container, @item_elems) = @_;
1202 $container->unshift_content(@item_elems);
1203 },
1204 debug => 1,
1205 );
1206
1207
1208
1209
1210 =head3 Select Unrolling
1211
1212 The C<unroll_select> method has this API:
1213
1214 $tree->unroll_select(
1215 select_label => $id_label,
1216 option_value => $closure, # how to get option value from data row
1217 option_content => $closure, # how to get option content from data row
1218 option_selected => $closure, # boolean to decide if SELECTED
1219 data => $data # the data to be put into the SELECT
1220 data_iter => $closure # the thing that will get a row of data
1221 );
1222
1223 Here's an example:
1224
1225 $tree->unroll_select(
1226 select_label => 'clan_list',
1227 option_value => sub { my $row = shift; $row->clan_id },
1228 option_content => sub { my $row = shift; $row->clan_name },
1229 option_selected => sub { my $row = shift; $row->selected },
1230 data => \@query_results,
1231 data_iter => sub { my $data = shift; $data->next }
1232 )
1233
1234
1235
1236 =head2 Tree-Building Methods: Table Generation
1237
1238 Matthew Sisk has a much more intuitive (imperative)
1239 way to generate tables via his module
1240 L<HTML::ElementTable|HTML::ElementTable>.
1241 However, for those with callback fever, the following
1242 method is available. First, we look at a nuts and bolts way to build a table
1243 using only standard L<HTML::Tree> API calls. Then the C<table> method
1244 available here is discussed.
1245
1246 =head3 Sample Model
1247
1248 package Simple::Class;
1249
1250 use Set::Array;
1251
1252 my @name = qw(bob bill brian babette bobo bix);
1253 my @age = qw(99 12 44 52 12 43);
1254 my @weight = qw(99 52 80 124 120 230);
1255
1256
1257 sub new {
1258 my $this = shift;
1259 bless {}, ref($this) || $this;
1260 }
1261
1262 sub load_data {
1263 my @data;
1264
1265 for (0 .. 5) {
1266 push @data, {
1267 age => $age[rand $#age] + int rand 20,
1268 name => shift @name,
1269 weight => $weight[rand $#weight] + int rand 40
1270 }
1271 }
1272
1273 Set::Array->new(@data);
1274 }
1275
1276
1277 1;
1278
1279
1280 =head4 Sample Usage:
1281
1282 my $data = Simple::Class->load_data;
1283 ++$_->{age} for @$data
1284
1285 =head3 Inline Code to Unroll a Table
1286
1287 =head4 HTML
1288
1289 <html>
1290
1291 <table id="load_data">
1292
1293 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1294
1295 <tr id="iterate">
1296
1297 <td id="name"> NATURE BOY RIC FLAIR </td>
1298 <td id="age"> 35 </td>
1299 <td id="weight"> 220 </td>
1300
1301 </tr>
1302
1303 </table>
1304
1305 </html>
1306
1307
1308 =head4 The manual way (*NOT* recommended)
1309
1310 require 'simple-class.pl';
1311 use HTML::Seamstress;
1312
1313 # load the view
1314 my $seamstress = HTML::Seamstress->new_from_file('simple.html');
1315
1316 # load the model
1317 my $o = Simple::Class->new;
1318 my $data = $o->load_data;
1319
1320 # find the <table> and <tr>
1321 my $table_node = $seamstress->look_down('id', 'load_data');
1322 my $iter_node = $table_node->look_down('id', 'iterate');
1323 my $table_parent = $table_node->parent;
1324
1325
1326 # drop the sample <table> and <tr> from the HTML
1327 # only add them in if there is data in the model
1328 # this is achieved via the $add_table flag
1329
1330 $table_node->detach;
1331 $iter_node->detach;
1332 my $add_table;
1333
1334 # Get a row of model data
1335 while (my $row = shift @$data) {
1336
1337 # We got row data. Set the flag indicating ok to hook the table into the HTML
1338 ++$add_table;
1339
1340 # clone the sample <tr>
1341 my $new_iter_node = $iter_node->clone;
1342
1343 # find the tags labeled name age and weight and
1344 # set their content to the row data
1345 $new_iter_node->content_handler($_ => $row->{$_})
1346 for qw(name age weight);
1347
1348 $table_node->push_content($new_iter_node);
1349
1350 }
1351
1352 # reattach the table to the HTML tree if we loaded data into some table rows
1353
1354 $table_parent->push_content($table_node) if $add_table;
1355
1356 print $seamstress->as_HTML;
1357
1358
1359
1360 =head3 $tree->table() : API call to Unroll a Table
1361
1362 require 'simple-class.pl';
1363 use HTML::Seamstress;
1364
1365 # load the view
1366 my $seamstress = HTML::Seamstress->new_from_file('simple.html');
1367 # load the model
1368 my $o = Simple::Class->new;
1369
1370 $seamstress->table
1371 (
1372 # tell seamstress where to find the table, via the method call
1373 # ->look_down('id', $gi_table). Seamstress detaches the table from the
1374 # HTML tree automatically if no table rows can be built
1375
1376 gi_table => 'load_data',
1377
1378 # tell seamstress where to find the tr. This is a bit useless as
1379 # the <tr> usually can be found as the first child of the parent
1380
1381 gi_tr => 'iterate',
1382
1383 # the model data to be pushed into the table
1384
1385 table_data => $o->load_data,
1386
1387 # the way to take the model data and obtain one row
1388 # if the table data were a hashref, we would do:
1389 # my $key = (keys %$data)[0]; my $val = $data->{$key}; delete $data->{$key}
1390
1391 tr_data => sub { my ($self, $data) = @_;
1392 shift(@{$data}) ;
1393 },
1394
1395 # the way to take a row of data and fill the <td> tags
1396
1397 td_data => sub { my ($tr_node, $tr_data) = @_;
1398 $tr_node->content_handler($_ => $tr_data->{$_})
1399 for qw(name age weight) }
1400
1401 );
1402
1403
1404 print $seamstress->as_HTML;
1405
1406
1407
1408 =head4 Looping over Multiple Sample Rows
1409
1410 * HTML
1411
1412 <html>
1413
1414 <table id="load_data" CELLPADDING=8 BORDER=2>
1415
1416 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1417
1418 <tr id="iterate1" BGCOLOR="white" >
1419
1420 <td id="name"> NATURE BOY RIC FLAIR </td>
1421 <td id="age"> 35 </td>
1422 <td id="weight"> 220 </td>
1423
1424 </tr>
1425 <tr id="iterate2" BGCOLOR="#CCCC99">
1426
1427 <td id="name"> NATURE BOY RIC FLAIR </td>
1428 <td id="age"> 35 </td>
1429 <td id="weight"> 220 </td>
1430
1431 </tr>
1432
1433 </table>
1434
1435 </html>
1436
1437
1438 * Only one change to last API call.
1439
1440 This:
1441
1442 gi_tr => 'iterate',
1443
1444 becomes this:
1445
1446 gi_tr => ['iterate1', 'iterate2']
1447
1448 =head3 $tree->table2() : New API Call to Unroll a Table
1449
1450 After 2 or 3 years with C<table()>, I began to develop
1451 production websites with it and decided it needed a cleaner
1452 interface, particularly in the area of handling the fact that
1453 C<id> tags will be the same after cloning a table row.
1454
1455 First, I will give a dry listing of the function's argument parameters.
1456 This will not be educational most likely. A better way to understand how
1457 to use the function is to read through the incremental unrolling of the
1458 function's interface given in conversational style after the dry listing.
1459 But take your pick. It's the same information given in two different
1460 ways.
1461
1462 =head4 Dry/technical parameter documentation
1463
1464 C<< $tree->table2(%param) >> takes the following arguments:
1465
1466 =over
1467
1468 =item * C<< table_ld => $look_down >> : optional
1469
1470 How to find the C<table> element in C<$tree>. If C<$look_down> is an
1471 arrayref, then use C<look_down>. If it is a CODE ref, then call it,
1472 passing it C<$tree>.
1473
1474 Defaults to C<< ['_tag' => 'table'] >> if not passed in.
1475
1476 =item * C<< table_data => $tabular_data >> : required
1477
1478 The data to fill the table with. I<Must> be passed in.
1479
1480 =item * C<< table_proc => $code_ref >> : not implemented
1481
1482 A subroutine to do something to the table once it is found.
1483 Not currently implemented. Not obviously necessary. Just
1484 created because there is a C<tr_proc> and C<td_proc>.
1485
1486 =item * C<< tr_ld => $look_down >> : optional
1487
1488 Same as C<table_ld> but for finding the table row elements. Please note
1489 that the C<tr_ld> is done on the table node that was found I<instead>
1490 of the whole HTML tree. This makes sense. The C<tr>s that you want exist
1491 below the table that was just found.
1492
1493 Defaults to C<< ['_tag' => 'tr'] >> if not passed in.
1494
1495 =item * C<< tr_data => $code_ref >> : optional
1496
1497 How to take the C<table_data> and return a row. Defaults to:
1498
1499 sub { my ($self, $data) = @_;
1500 shift(@{$data}) ;
1501 }
1502
1503 =item * C<< tr_proc => $code_ref >> : optional
1504
1505 Something to do to the table row we are about to add to the
1506 table we are making. Defaults to a routine which makes the C<id>
1507 attribute unique:
1508
1509 sub {
1510 my ($self, $tr, $tr_data, $tr_base_id, $row_count) = @_;
1511 $tr->attr(id => sprintf "%s_%d", $tr_base_id, $row_count);
1512 }
1513
1514 =item * C<< td_proc => $code_ref >> : required
1515
1516 This coderef will take the row of data and operate on the C<td> cells that
1517 are children of the C<tr>. See C<t/table2.t> for several usage examples.
1518
1519 Here's a sample one:
1520
1521 sub {
1522 my ($tr, $data) = @_;
1523 my @td = $tr->look_down('_tag' => 'td');
1524 for my $i (0..$#td) {
1525 $td[$i]->splice_content(0, 1, $data->[$i]);
1526 }
1527 }
1528
1529 =cut
1530
1531 =head4 Conversational parameter documentation
1532
1533 The first thing you need is a table. So we need a look down for that. If you
1534 don't give one, it defaults to
1535
1536 ['_tag' => 'table']
1537
1538 What good is a table to display in without data to display?!
1539 So you must supply a scalar representing your tabular
1540 data source. This scalar might be an array reference, a C<next>able iterator,
1541 a DBI statement handle. Whatever it is, it can be iterated through to build
1542 up rows of table data.
1543 These two required fields (the way to find the table and the data to
1544 display in the table) are C<table_ld> and C<table_data>
1545 respectively. A little more on C<table_ld>. If this happens to be a CODE ref,
1546 then execution
1547 of the code ref is presumed to return the C<HTML::Element>
1548 representing the table in the HTML tree.
1549
1550 Next, we get the row or rows which serve as sample C<tr> elements by doing
1551 a C<look_down> from the C<table_elem>. While normally one sample row
1552 is enough to unroll a table, consider when you have alternating
1553 table rows. This API call would need one of each row so that it can
1554 cycle through the
1555 sample rows as it loops through the data.
1556 Alternatively, you could always just use one row and
1557 make the necessary changes to the single C<tr> row by
1558 mutating the element in C<tr_proc>,
1559 discussed below. The default C<tr_ld> is
1560 C<< ['_tag' => 'tr'] >> but you can overwrite it. Note well, if you overwrite
1561 it with a subroutine, then it is expected that the subroutine will return
1562 the C<HTML::Element>(s)
1563 which are C<tr> element(s).
1564 The reason a subroutine might be preferred is in the case
1565 that the HTML designers gave you 8 sample C<tr> rows but only one
1566 prototype row is needed.
1567 So you can write a subroutine, to splice out the 7 rows you don't need
1568 and leave the one sample
1569 row remaining so that this API call can clone it and supply it to
1570 the C<tr_proc> and C<td_proc> calls.
1571
1572 Now, as we move through the table rows with table data,
1573 we need to do two different things on
1574 each table row:
1575
1576 =over 4
1577
1578 =item * get one row of data from the C<table_data> via C<tr_data>
1579
1580 The default procedure assumes the C<table_data> is an array reference and
1581 shifts a row off of it:
1582
1583 sub { my ($self, $data) = @_;
1584 shift(@{$data}) ;
1585 }
1586
1587 Your function MUST return undef when there is no more rows to lay out.
1588
1589 =item * take the C<tr> element and mutate it via C<tr_proc>
1590
1591 The default procedure simply makes the id of the table row unique:
1592
1593 sub { my ($self, $tr, $tr_data, $row_count, $root_id) = @_;
1594 $tr->attr(id => sprintf "%s_%d", $root_id, $row_count);
1595 }
1596
1597 =back
1598
1599 Now that we have our row of data, we call C<td_proc> so that it can
1600 take the data and the C<td> cells in this C<tr> and process them.
1601 This function I<must> be supplied.
1602
1603
1604 =head3 Whither a Table with No Rows
1605
1606 Often when a table has no rows, we want to display a message
1607 indicating this to the view. Use conditional processing to decide what
1608 to display:
1609
1610 <span id=no_data>
1611 <table><tr><td>No Data is Good Data</td></tr></table>
1612 </span>
1613 <span id=load_data>
1614 <html>
1615
1616 <table id="load_data">
1617
1618 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1619
1620 <tr id="iterate">
1621
1622 <td id="name"> NATURE BOY RIC FLAIR </td>
1623 <td id="age"> 35 </td>
1624 <td id="weight"> 220 </td>
1625
1626 </tr>
1627
1628 </table>
1629
1630 </html>
1631
1632 </span>
1633
1634
1635
1636
1637 =head1 SEE ALSO
1638
1639 =over
1640
1641 =item * L<HTML::Tree>
1642
1643 A perl package for creating and manipulating HTML trees
1644
1645 =item * L<HTML::ElementTable>
1646
1647 An L<HTML::Tree> - based module which allows for manipulation of HTML
1648 trees using cartesian coordinations.
1649
1650 =item * L<HTML::Seamstress>
1651
1652 An L<HTML::Tree> - based module inspired by
1653 XMLC (L<http://xmlc.enhydra.org>), allowing for dynamic
1654 HTML generation via tree rewriting.
1655
1656 =head1 TODO
1657
1658 =over
1659
1660 =item * highlander2
1661
1662 currently the API expects the subtrees to survive or be pruned to be
1663 identified by id:
1664
1665 $if_then->highlander2([
1666 under10 => sub { $_[0] < 10} ,
1667 under18 => sub { $_[0] < 18} ,
1668 welcome => [
1669 sub { 1 },
1670 sub {
1671 my $branch = shift;
1672 $branch->look_down(id => 'age')->replace_content($age);
1673 }
1674 ]
1675 ],
1676 $age
1677 );
1678
1679 but, it should be more flexible. the C<under10>, and C<under18> are
1680 expected to be ids in the tree... but it is not hard to have a check to
1681 see if this field is an array reference and if it, then to do a look
1682 down instead:
1683
1684 $if_then->highlander2([
1685 [class => 'under10'] => sub { $_[0] < 10} ,
1686 [class => 'under18'] => sub { $_[0] < 18} ,
1687 [class => 'welcome'] => [
1688 sub { 1 },
1689 sub {
1690 my $branch = shift;
1691 $branch->look_down(id => 'age')->replace_content($age);
1692 }
1693 ]
1694 ],
1695 $age
1696 );
1697
1698
1699
1700 =cut
1701
1702 =head1 SEE ALSO
1703
1704 L<HTML::Seamstress>
1705
1706 =head1 AUTHOR
1707
1708 Terrence Brannon, E<lt>tbone@cpan.orgE<gt>
1709
1710 Many thanks to BARBIE for his RT bug report.
1711
1712 =head1 COPYRIGHT AND LICENSE
1713
1714 Copyright (C) 2004 by Terrence Brannon
1715
1716 This library is free software; you can redistribute it and/or modify
1717 it under the same terms as Perl itself, either Perl version 5.8.4 or,
1718 at your option, any later version of Perl 5 you may have available.
1719
1720
1721 =cut
This page took 0.101044 seconds and 4 git commands to generate.