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