]> iEval git - html-element-library.git/blame - lib/HTML/Element/Library.pm
data_map method
[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
3dad7198
TB
40sub 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
de64e3d9 62sub HTML::Element::passover {
63 my ($tree, $child_id) = @_;
64
3dad7198
TB
65 warn "ARGS: my ($tree, $child_id)" if $DEBUG;
66 warn $tree->as_HTML(undef, ' ') if $DEBUG;
de64e3d9 67
68 my $exodus = $tree->look_down(id => $child_id);
69
3dad7198
TB
70 warn "E: $exodus" if $DEBUG;
71
de64e3d9 72 my @s = HTML::Element::siblings($exodus);
73
de64e3d9 74 for my $s (@s) {
de64e3d9 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
67e78ff2 87sub HTML::Element::sibdex {
88
89 my $element = shift;
90 firstidx { $_ eq $element } $element->siblings
91
92}
93
94sub HTML::Element::addr { goto &HTML::Element::sibdex }
95
96sub HTML::Element::replace_content {
97 my $elem = shift;
98 $elem->delete_content;
99 $elem->push_content(@_);
100}
101
102sub 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
115sub HTML::Element::Library::super_literal {
116 my($text) = @_;
117
118 HTML::Element->new('~literal', text => $text);
119}
120
121
122sub 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
139sub HTML::Element::content_handler {
3c14ea1e 140 my ($tree, %content_hash) = @_;
141
142 for my $k (keys %content_hash) {
143 $tree->set_child_content(id => $k, $content_hash{$k});
144 }
67e78ff2 145
67e78ff2 146
147}
148
149
150sub make_counter {
151 my $i = 1;
152 sub {
153 shift() . ':' . $i++
154 }
155}
156
157
158sub 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
177sub 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});
3dad7198 217 warn "container: " . $container if $p{debug} ;
67e78ff2 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
263sub 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
290sub 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
306sub 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
349sub 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
427sub overwrite_action {
428 my ($mute_node, %X) = @_;
429
430 $mute_node->attr($X{local_attr}{name} => $X{local_attr}{value}{new});
431}
432
433
434sub HTML::Element::overwrite_attr {
435 my $tree = shift;
436
437 $tree->mute_elem(@_, \&overwrite_action);
438}
439
440
441
442sub 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
468sub 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
538sub 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
551sub 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
5f53bf21 585 #warn 1;
67e78ff2 586 $table->{table_node} = ref_or_ld( $tree, $p{table_ld} ) ;
5f53bf21 587 #warn 2;
67e78ff2 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};
5f53bf21 626 #warn 3.3;
67e78ff2 627
628 $p{td_proc}->($new_tr_node, $row);
629 push @table_rows, $new_tr_node;
630
5f53bf21 631 #warn 4.4;
67e78ff2 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
643sub 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
674sub HTML::Element::set_sibling_content {
675 my ($elt, $content) = @_;
676
677 $elt->parent->splice_content($elt->pindex + 1, 1, $content);
678
679}
680
681sub 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
6911;
692__END__
693# Below is stub documentation for your module. You'd better edit it!
694
695=head1 NAME
696
697HTML::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
706This method provides API calls for common actions on trees when using
707L<HTML::Tree>.
708
709=head1 METHODS
710
711The test suite contains examples of each of these methods in a
712file C<t/$method.t>
713
714=head2 Positional Querying Methods
715
716=head3 $elem->siblings
717
718Return a list of all nodes under the same parent.
719
720=head3 $elem->sibdex
721
722Return the index of C<$elem> into the array of siblings of which it is
723a part. L<HTML::ElementSuper> calls this method C<addr> but I don't think
724that is a descriptive name. And such naming is deceptively close to the
725C<address> function of C<HTML::Element>. HOWEVER, in the interest of
726backwards compatibility, both methods are available.
727
728=head3 $elem->addr
729
730Same as sibdex
731
732=head3 $elem->position()
733
734Returns the coordinates of this element in the tree it inhabits.
735This is accomplished by succesively calling addr() on ancestor
736elements until either a) an element that does not support these
737methods is found, or b) there are no more parents. The resulting
738list 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
744In L<HTML::Element>, Sean Burke discusses super-literals. They are
745text which does not get escaped. Great for includng Javascript in
746HTML. Also great for including foreign language into a document.
747
748So, you basically toss C<super_literal> your text and back comes
749your text wrapped in a C<~literal> element.
750
751One 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
757Replaces all of C<$elem>'s content with C<@new_elem>.
758
759=head3 $elem->wrap_content($wrapper_element)
760
761Wraps the existing content in the provided element. If the provided element
762happens 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
768After finding the node, it detaches the node's content and pushes $content as the node's content.
769
3c14ea1e 770=head3 $tree->content_handler(%id_content)
67e78ff2 771
772This is a convenience method. Because the look_down criteria will often simply be:
773
774 id => 'fixme'
775
776to find things like:
777
778 <a id=fixme href=http://www.somesite.org>replace_content</a>
779
780You can call this method to shorten your typing a bit. You can simply type
781
782 $elem->content_handler( fixme => 'new text' )
783
784Instead of typing:
785
786 $elem->set_child_content(sid => 'fixme', 'new text')
787
3c14ea1e 788PLEASE 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
67e78ff2 797=head3 $tree->highlander($subtree_span_id, $conditionals, @conditionals_args)
798
799This allows for "if-then-else" style processing. Highlander was a movie in
800which only one would survive. Well, in terms of a tree when looking at a
801structure that you want to process in C<if-then-else> style, only one child
802will 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
818We only want one child of the C<span> tag with id C<age_dialog> to remain
819based on the age of the person visiting the page.
820
821So, 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
837And there we have it. If the age is less than 10, then the node with
838id C<under10> remains. For age less than 18, the node with id C<under18>
839remains.
840Otherwise our "else" condition fires and the child with id C<welcome> remains.
841
4b02c173 842=head3 $tree->passover($id_of_element)
843
844In some cases, you know exactly which element should survive. In this case,
845you can simply call C<passover> to remove it's siblings. For the HTML
846above, you could delete C<under10> and C<welcome> by simply calling:
847
848 $tree->passover('under18');
849
67e78ff2 850=head3 $tree->highlander2($tree, $conditionals, @conditionals_args)
851
852Right around the same time that C<table2()> came into being, Seamstress
853began to tackle tougher and tougher processing problems. It became clear that
854a more powerful highlander was needed... one that not only snipped the tree
855of the nodes that should not survive, but one that allows for
856post-processing of the survivor node. And one that was more flexible with
857how to find the nodes to snip.
858
859Thus (drum roll) C<highlander2()>.
860
861So 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
877In this case, a branch survives, but it has dummy data in it. We must take
878the surviving segment of HTML and rewrite the age C<span> with the age.
879Here 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
907We pass it the tree (C<$if_then>), an arrayref of conditions
908(C<cond>) and an arrayref of arguments which are passed to the
909C<cond>s and to the replacement subs.
910
911The C<under10>, C<under18> and C<welcome> are id attributes in the
912tree of the siblings of which only one will survive. However,
913should you need to do
914more complex look-downs to find the survivor,
915then supply an array ref instead of a simple
916scalar:
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
940This method is designed for taking a tree and reworking a set of nodes in
941a stereotyped fashion. For instance let's say you have 3 remote image
942archives, but you don't want to put long URLs in your img src
943tags 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
949and 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
959and 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
967This is a generalization of C<overwrite_attr>. C<overwrite_attr>
968assumes the return value of the
969closure is supposed overwrite an attribute value and does it for you.
970C<mute_elem> is a more general function which does nothing but
971hand the closure the element and let it mutate it as it jolly well pleases :)
972
973In fact, here is the implementation of C<overwrite_attr>
974to 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
994This 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
1001We 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
1009To 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
1025C<iter()> was fine for awhile, but some things
1026(e.g. definition lists) need a more general function to make them easy to
1027do. Hence C<iter2()>. This function will be explained by example of unrolling
1028a simple definition list.
1029
1030So 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
1060And 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
1069Now, let's make this problem a bit harder to show off the power of C<iter2()>.
1070Let'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
1072for unrolling with our data set. Let's further assume that we want them to
1073remain in the final output.
1074
1075So now, the API to C<iter2()> will be discussed and we will explain how our
1076goal of getting our data into HTML fits into the API.
1077
1078=over 4
1079
1080=item * wrapper_ld
1081
1082This is how to look down and find the container of all the elements we will
1083be unrolling. The <dl> tag is the container for the dt and dd tags we will be
1084unrolling.
1085
1086If you pass an anonymous subroutine, then it is presumed that execution of
1087this subroutine will return the HTML::Element representing the container tag.
1088If you pass an array ref, then this will be dereferenced and passed to
1089C<HTML::Element::look_down()>.
1090
1091default value: C<< ['_tag' => 'dl'] >>
1092
1093Based on the mock HTML above, this default is fine for finding our container
1094tag. So let's move on.
1095
1096=item * wrapper_data
1097
1098This is an array reference of data that we will be putting into the container.
1099You must supply this. C<@items> above is our C<wrapper_data>.
1100
1101=item * wrapper_proc
1102
1103After we find the container via C<wrapper_ld>, we may want to pre-process
1104some aspect of this tree. In our case the first two sets of dt and dd need
1105to be removed, leaving the last dt and dd. So, we supply a C<wrapper_proc>
1106which will do this.
1107
1108default: undef
1109
1110=item * item_ld
1111
1112This anonymous subroutine returns an array ref of C<HTML::Element>s that will
1113be cloned and populated with item data
1114(item data is a "row" of C<wrapper_data>).
1115
1116default: returns an arrayref consisting of the dt and dd element inside the
1117container.
1118
1119=item * item_data
1120
1121This is a subroutine that takes C<wrapper_data> and retrieves one "row"
1122to be "pasted" into the array ref of C<HTML::Element>s found via C<item_ld>.
1123I hope that makes sense.
1124
1125default: shifts C<wrapper_data>.
1126
1127=item * item_proc
1128
1129This is a subroutine that takes the C<item_data> and the C<HTML::Element>s
1130found via C<item_ld> and produces an arrayref of C<HTML::Element>s which will
1131eventually be spliced into the container.
1132
1133Note that this subroutine MUST return the new items. This is done
1134So that more items than were passed in can be returned. This is
1135useful when, for example, you must return 2 dts for an input data item.
1136And when would you do this? When a single term has multiple spellings
1137for instance.
1138
1139default: expects C<item_data> to be an arrayref of two elements and
1140C<item_elems> to be an arrayref of two C<HTML::Element>s. It replaces the
1141content of the C<HTML::Element>s with the C<item_data>.
1142
1143=item * splice
1144
1145After building up an array of C<@item_elems>, the subroutine passed as
1146C<splice> will be given the parent container HTML::Element and the
1147C<@item_elems>. How the C<@item_elems> end up in the container is up to this
1148routine: it could put half of them in. It could unshift them or whatever.
1149
1150default: C<< $container->splice_content(0, 2, @item_elems) >>
1151In other words, kill the 2 sample elements with the newly generated
1152@item_elems
1153
1154=back
1155
1156So 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
1182The 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
1193Here'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
1208Matthew Sisk has a much more intuitive (imperative)
1209way to generate tables via his module
1210L<HTML::ElementTable|HTML::ElementTable>.
1211However, for those with callback fever, the following
1212method is available. First, we look at a nuts and bolts way to build a table
1213using only standard L<HTML::Tree> API calls. Then the C<table> method
1214available 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
1410This:
1411
1412 gi_tr => 'iterate',
1413
1414becomes this:
1415
1416 gi_tr => ['iterate1', 'iterate2']
1417
1418=head3 $tree->table2() : New API Call to Unroll a Table
1419
1420After 2 or 3 years with C<table()>, I began to develop
1421production websites with it and decided it needed a cleaner
1422interface, particularly in the area of handling the fact that
1423C<id> tags will be the same after cloning a table row.
1424
1425First, I will give a dry listing of the function's argument parameters.
1426This will not be educational most likely. A better way to understand how
1427to use the function is to read through the incremental unrolling of the
1428function's interface given in conversational style after the dry listing.
1429But take your pick. It's the same information given in two different
1430ways.
1431
1432=head4 Dry/technical parameter documentation
1433
1434C<< $tree->table2(%param) >> takes the following arguments:
1435
1436=over
1437
1438=item * C<< table_ld => $look_down >> : optional
1439
1440How to find the C<table> element in C<$tree>. If C<$look_down> is an
1441arrayref, then use C<look_down>. If it is a CODE ref, then call it,
1442passing it C<$tree>.
1443
1444Defaults to C<< ['_tag' => 'table'] >> if not passed in.
1445
1446=item * C<< table_data => $tabular_data >> : required
1447
1448The data to fill the table with. I<Must> be passed in.
1449
1450=item * C<< table_proc => $code_ref >> : not implemented
1451
1452A subroutine to do something to the table once it is found.
1453Not currently implemented. Not obviously necessary. Just
1454created because there is a C<tr_proc> and C<td_proc>.
1455
1456=item * C<< tr_ld => $look_down >> : optional
1457
1458Same as C<table_ld> but for finding the table row elements. Please note
1459that the C<tr_ld> is done on the table node that was found I<instead>
1460of the whole HTML tree. This makes sense. The C<tr>s that you want exist
1461below the table that was just found.
1462
1463Defaults to C<< ['_tag' => 'tr'] >> if not passed in.
1464
1465=item * C<< tr_data => $code_ref >> : optional
1466
1467How 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
1475Something to do to the table row we are about to add to the
1476table we are making. Defaults to a routine which makes the C<id>
1477attribute 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
1486This coderef will take the row of data and operate on the C<td> cells that
1487are children of the C<tr>. See C<t/table2.t> for several usage examples.
1488
1489Here'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
1503The first thing you need is a table. So we need a look down for that. If you
1504don't give one, it defaults to
1505
1506 ['_tag' => 'table']
1507
1508What good is a table to display in without data to display?!
1509So you must supply a scalar representing your tabular
1510data source. This scalar might be an array reference, a C<next>able iterator,
1511a DBI statement handle. Whatever it is, it can be iterated through to build
1512up rows of table data.
1513These two required fields (the way to find the table and the data to
1514display in the table) are C<table_ld> and C<table_data>
1515respectively. A little more on C<table_ld>. If this happens to be a CODE ref,
1516then execution
1517of the code ref is presumed to return the C<HTML::Element>
1518representing the table in the HTML tree.
1519
1520Next, we get the row or rows which serve as sample C<tr> elements by doing
1521a C<look_down> from the C<table_elem>. While normally one sample row
1522is enough to unroll a table, consider when you have alternating
1523table rows. This API call would need one of each row so that it can
1524cycle through the
1525sample rows as it loops through the data.
1526Alternatively, you could always just use one row and
1527make the necessary changes to the single C<tr> row by
1528mutating the element in C<tr_proc>,
1529discussed below. The default C<tr_ld> is
1530C<< ['_tag' => 'tr'] >> but you can overwrite it. Note well, if you overwrite
1531it with a subroutine, then it is expected that the subroutine will return
1532the C<HTML::Element>(s)
1533which are C<tr> element(s).
1534The reason a subroutine might be preferred is in the case
1535that the HTML designers gave you 8 sample C<tr> rows but only one
1536prototype row is needed.
1537So you can write a subroutine, to splice out the 7 rows you don't need
1538and leave the one sample
1539row remaining so that this API call can clone it and supply it to
1540the C<tr_proc> and C<td_proc> calls.
1541
1542Now, as we move through the table rows with table data,
1543we need to do two different things on
1544each table row:
1545
1546=over 4
1547
1548=item * get one row of data from the C<table_data> via C<tr_data>
1549
1550The default procedure assumes the C<table_data> is an array reference and
1551shifts a row off of it:
1552
1553 sub { my ($self, $data) = @_;
1554 shift(@{$data}) ;
1555 }
1556
1557Your 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
1561The 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
1569Now that we have our row of data, we call C<td_proc> so that it can
1570take the data and the C<td> cells in this C<tr> and process them.
1571This function I<must> be supplied.
1572
1573
1574=head3 Whither a Table with No Rows
1575
1576Often when a table has no rows, we want to display a message
1577indicating this to the view. Use conditional processing to decide what
1578to 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
1613A perl package for creating and manipulating HTML trees
1614
1615=item * L<HTML::ElementTable>
1616
1617An L<HTML::Tree> - based module which allows for manipulation of HTML
1618trees using cartesian coordinations.
1619
1620=item * L<HTML::Seamstress>
1621
1622An L<HTML::Tree> - based module inspired by
1623XMLC (L<http://xmlc.enhydra.org>), allowing for dynamic
1624HTML generation via tree rewriting.
1625
1626=head1 TODO
1627
1628=over
1629
1630=item * highlander2
1631
1632currently the API expects the subtrees to survive or be pruned to be
1633identified 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
1649but, it should be more flexible. the C<under10>, and C<under18> are
1650expected to be ids in the tree... but it is not hard to have a check to
1651see if this field is an array reference and if it, then to do a look
1652down 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
1674L<HTML::Seamstress>
1675
1676=head1 AUTHOR
1677
1678Terrence Brannon, E<lt>tbone@cpan.orgE<gt>
1679
1680Many thanks to BARBIE for his RT bug report.
1681
1682=head1 COPYRIGHT AND LICENSE
1683
1684Copyright (C) 2004 by Terrence Brannon
1685
1686This library is free software; you can redistribute it and/or modify
1687it under the same terms as Perl itself, either Perl version 5.8.4 or,
1688at your option, any later version of Perl 5 you may have available.
1689
1690
1691=cut
This page took 0.205652 seconds and 4 git commands to generate.