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