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