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