File Coverage

blib/lib/HTML/Element.pm
Criterion Covered Total %
statement 646 1023 63.1
branch 354 752 47.0
condition 205 426 48.1
subroutine 73 118 61.8
pod 81 82 98.7
total 1359 2401 56.6


line stmt bran cond sub pod time code
1             package HTML::Element;
2              
3             # ABSTRACT: Class for objects that represent HTML elements
4              
5 23     23   243244 use strict;
  23         47  
  23         591  
6 23     23   108 use warnings;
  23         34  
  23         822  
7              
8             our $VERSION = '5.07'; # VERSION from OurPkgVersion
9              
10 23     23   212 use Carp ();
  23         46  
  23         269  
11 23     23   2250 use HTML::Entities ();
  23         34299  
  23         388  
12 23     23   2206 use HTML::Tagset ();
  23         7524  
  23         376  
13 23     23   2294 use integer; # vroom vroom!
  23         122  
  23         108  
14              
15             # This controls encoding entities on output.
16             # When set entities won't be re-encoded.
17             # Defaulting off because parser defaults to unencoding entities
18             our $encoded_content = 0;
19              
20 23     23   882 use vars qw($html_uc $Debug $ID_COUNTER $VERSION %list_type_to_sub);
  23         38  
  23         2431  
21              
22             # Set up support for weak references, if possible:
23             my $using_weaken;
24              
25             #=head1 CLASS METHODS
26              
27              
28             sub Use_Weak_Refs {
29 24     24 1 47 my $self_or_class = shift;
30              
31 24 50       137 if (@_) { # set
32 24         53 $using_weaken = !! shift; # Normalize boolean value
33 24 50 33     186 Carp::croak("The installed Scalar::Util lacks support for weak references")
34             if $using_weaken and not defined &Scalar::Util::weaken;
35              
36 23     23   128 no warnings 'redefine';
  23         74  
  23         2409  
37 24 50   0   102 *_weaken = $using_weaken ? \&Scalar::Util::weaken : sub ($) {};
38             } # end if setting value
39              
40 24         217595 return $using_weaken;
41             } # end Use_Weak_Refs
42              
43             BEGIN {
44             # Attempt to import weaken from Scalar::Util, but don't complain
45             # if we can't. Also, rename it to _weaken.
46 23     23   135 require Scalar::Util;
47              
48 23         123 __PACKAGE__->Use_Weak_Refs(defined &Scalar::Util::weaken);
49             }
50              
51             sub import {
52 21     21   884 my $class = shift;
53              
54 21         21890 for (@_) {
55 1 50       7 if (/^-(no_?)?weak$/) {
56 1         5 $class->Use_Weak_Refs(not $1);
57             } else {
58 0         0 Carp::croak("$_ is not exported by the $class module");
59             }
60             }
61             } # end import
62              
63              
64             $Debug = 0 unless defined $Debug;
65              
66             #=head1 SUBROUTINES
67              
68              
69             sub Version {
70 0     0 1 0 Carp::carp("Deprecated subroutine HTML::Element::Version called");
71 0         0 $VERSION;
72             }
73              
74             my $nillio = [];
75              
76             *HTML::Element::emptyElement = \%HTML::Tagset::emptyElement; # legacy
77             *HTML::Element::optionalEndTag = \%HTML::Tagset::optionalEndTag; # legacy
78             *HTML::Element::linkElements = \%HTML::Tagset::linkElements; # legacy
79             *HTML::Element::boolean_attr = \%HTML::Tagset::boolean_attr; # legacy
80             *HTML::Element::canTighten = \%HTML::Tagset::canTighten; # legacy
81              
82             # Constants for signalling back to the traverser:
83             my $travsignal_package = __PACKAGE__ . '::_travsignal';
84             my ( $ABORT, $PRUNE, $PRUNE_SOFTLY, $OK, $PRUNE_UP )
85             = map { my $x = $_; bless \$x, $travsignal_package; }
86             qw(
87             ABORT PRUNE PRUNE_SOFTLY OK PRUNE_UP
88             );
89              
90              
91             ## Comments from Father Chrysostomos RT #58880
92             ## The sole purpose for empty parentheses after a sub name is to make it
93             ## parse as a 0-ary (nihilary?) function. I.e., ABORT+1 should parse as
94             ## ABORT()+1, not ABORT(+1). The parentheses also tell perl that it can
95             ### be inlined.
96             ##Deparse is really useful for demonstrating this:
97             ##$ perl -MO=Deparse,-p -e 'sub ABORT {7} print ABORT+8'
98             # Vs
99             # perl -MO=Deparse,-p -e 'sub ABORT() {7} print ABORT+8'
100             #
101             # With the parentheses, it not only makes it parse as a term.
102             # It even resolves the constant at compile-time, making the code run faster.
103              
104             ## no critic
105 3     3 1 7 sub ABORT () {$ABORT}
106 0     0 1 0 sub PRUNE () {$PRUNE}
107 0     0 1 0 sub PRUNE_SOFTLY () {$PRUNE_SOFTLY}
108 0     0 1 0 sub OK () {$OK}
109 0     0 1 0 sub PRUNE_UP () {$PRUNE_UP}
110             ## use critic
111              
112             $html_uc = 0;
113              
114             # set to 1 if you want tag and attribute names from starttag and endtag
115             # to be uc'd
116              
117             # regexs for XML names
118             # http://www.w3.org/TR/2006/REC-xml11-20060816/NT-NameStartChar
119             my $START_CHAR
120             = qr/(?:\:|[A-Z]|_|[a-z]|[\x{C0}-\x{D6}]|[\x{D8}-\x{F6}]|[\x{F8}-\x{2FF}]|[\x{370}-\x{37D}]|[\x{37F}-\x{1FFF}]|[\x{200C}-\x{200D}]|[\x{2070}-\x{218F}]|[\x{2C00}-\x{2FEF}]|[\x{3001}-\x{D7FF}]|[\x{F900}-\x{FDCF}]|[\x{FDF0}-\x{FFFD}]|[\x{10000}-\x{EFFFF}])/;
121              
122             # http://www.w3.org/TR/2006/REC-xml11-20060816/#NT-NameChar
123             my $NAME_CHAR
124             = qr/(?:$START_CHAR|-|\.|[0-9]|\x{B7}|[\x{0300}-\x{036F}]|[\x{203F}-\x{2040}])/;
125              
126             # Elements that does not have corresponding end tags (i.e. are empty)
127              
128             #==========================================================================
129              
130             #=head1 BASIC METHODS
131              
132              
133             #
134             # An HTML::Element is represented by blessed hash reference, much like
135             # Tree::DAG_Node objects. Key-names not starting with '_' are reserved
136             # for the SGML attributes of the element.
137             # The following special keys are used:
138             #
139             # '_tag': The tag name (i.e., the generic identifier)
140             # '_parent': A reference to the HTML::Element above (when forming a tree)
141             # '_pos': The current position (a reference to a HTML::Element) is
142             # where inserts will be placed (look at the insert_element
143             # method) If not set, the implicit value is the object itself.
144             # '_content': A ref to an array of nodes under this.
145             # It might not be set.
146             #
147             # Example: Gisle's photo is represented like this:
148             #
149             # bless {
150             # _tag => 'img',
151             # src => 'gisle.jpg',
152             # alt => "Gisle's photo",
153             # }, 'HTML::Element';
154             #
155              
156             sub new {
157 2149     2149 1 6439 my $class = shift;
158 2149   33     4858 $class = ref($class) || $class;
159              
160 2149         2639 my $tag = shift;
161 2149 50 33     5620 Carp::croak("No tagname") unless defined $tag and length $tag;
162 2149 50       4786 Carp::croak "\"$tag\" isn't a good tag name!"
163             if $tag =~ m/[<>\/\x00-\x20]/; # minimal sanity, certainly!
164 2149         3905 my $self = bless { _tag => scalar( $class->_fold_case($tag) ) }, $class;
165 2149         3364 my ( $attr, $val );
166 2149         4916 while ( ( $attr, $val ) = splice( @_, 0, 2 ) ) {
167             ## RT #42209 why does this default to the attribute name and not remain unset or the empty string?
168 513 100       788 $val = $attr unless defined $val;
169 513         805 $self->{ $class->_fold_case($attr) } = $val;
170             }
171 2149 100       3573 if ( $tag eq 'html' ) {
172 310         602 $self->{'_pos'} = undef;
173             }
174 2149 50       3571 _weaken($self->{'_parent'}) if $self->{'_parent'};
175 2149         3812 return $self;
176             }
177              
178              
179             sub attr {
180 8     8 1 432 my $self = shift;
181 8         22 my $attr = scalar( $self->_fold_case(shift) );
182 8 100       16 if (@_) { # set
183 5 100       13 if ( defined $_[0] ) {
184 4         8 my $old = $self->{$attr};
185 4         9 $self->{$attr} = $_[0];
186 4         9 return $old;
187             }
188             else { # delete, actually
189 1         3 return delete $self->{$attr};
190             }
191             }
192             else { # get
193 3         17 return $self->{$attr};
194             }
195             }
196              
197              
198             sub tag {
199 1022     1022 1 1542 my $self = shift;
200 1022 50       1466 if (@_) { # set
201 0         0 $self->{'_tag'} = $self->_fold_case( $_[0] );
202             }
203             else { # get
204 1022         2041 $self->{'_tag'};
205             }
206             }
207              
208              
209             sub parent {
210 118     118 1 871 my $self = shift;
211 118 50       227 if (@_) { # set
212 0 0 0     0 Carp::croak "an element can't be made its own parent"
      0        
213             if defined $_[0] and ref $_[0] and $self eq $_[0]; # sanity
214 0         0 _weaken($self->{'_parent'} = $_[0]);
215             }
216             else {
217 118         440 $self->{'_parent'}; # get
218             }
219             }
220              
221              
222             sub content_list {
223             return wantarray
224 136 50       532 ? @{ shift->{'_content'} || return () }
225 1563 100   1563 1 5249 : scalar @{ shift->{'_content'} || return 0 };
  1427 100       4133  
226             }
227              
228              
229             # a read-only method! can't say $h->content( [] )!
230             sub content {
231 1     1 1 521 return shift->{'_content'};
232             }
233              
234              
235             sub content_array_ref {
236 0   0 0 1 0 return shift->{'_content'} ||= [];
237             }
238              
239              
240             sub content_refs_list {
241 0 0   0 1 0 return \( @{ shift->{'_content'} || return () } );
  0         0  
242             }
243              
244              
245             sub implicit {
246 0     0 1 0 return shift->attr( '_implicit', @_ );
247             }
248              
249              
250             sub pos {
251 0     0 1 0 my $self = shift;
252 0         0 my $pos = $self->{'_pos'};
253 0 0       0 if (@_) { # set
254 0         0 my $parm = shift;
255 0 0 0     0 if ( defined $parm and $parm ne $self ) {
256 0         0 $self->{'_pos'} = $parm; # means that element
257             }
258             else {
259 0         0 $self->{'_pos'} = undef; # means $self
260             }
261             }
262 0 0       0 return $pos if defined($pos);
263 0         0 return $self;
264             }
265              
266              
267             sub all_attr {
268 1     1 1 2 return %{ $_[0] };
  1         6  
269              
270             # Yes, trivial. But no other way for the user to do the same
271             # without breaking encapsulation.
272             # And if our object representation changes, this method's behavior
273             # should stay the same.
274             }
275              
276             sub all_attr_names {
277 590     590 1 620 return keys %{ $_[0] };
  590         1871  
278             }
279              
280              
281             sub all_external_attr {
282 3     3 1 5 my $self = $_[0];
283             return map( ( length($_) && substr( $_, 0, 1 ) eq '_' )
284             ? ()
285 3 100 66     40 : ( $_, $self->{$_} ),
286             keys %$self );
287             }
288              
289             sub all_external_attr_names {
290 0   0 0 1 0 return grep !( length($_) && substr( $_, 0, 1 ) eq '_' ), keys %{ $_[0] };
  0         0  
291             }
292              
293              
294             sub id {
295 0 0   0 1 0 if ( @_ == 1 ) {
    0          
296 0         0 return $_[0]{'id'};
297             }
298             elsif ( @_ == 2 ) {
299 0 0       0 if ( defined $_[1] ) {
300 0         0 return $_[0]{'id'} = $_[1];
301             }
302             else {
303 0         0 return delete $_[0]{'id'};
304             }
305             }
306             else {
307 0         0 Carp::croak '$node->id can\'t take ' . scalar(@_) . ' parameters!';
308             }
309             }
310              
311              
312             sub _gensym {
313 0 0   0   0 unless ( defined $ID_COUNTER ) {
314              
315             # start it out...
316 0         0 $ID_COUNTER = sprintf( '%04x', rand(0x1000) );
317 0         0 $ID_COUNTER =~ tr<0-9a-f>; # yes, skip letter "oh"
318 0         0 $ID_COUNTER .= '00000';
319             }
320 0         0 ++$ID_COUNTER;
321             }
322              
323             sub idf {
324 0     0 1 0 my $nparms = scalar @_;
325              
326 0 0       0 if ( $nparms == 1 ) {
327 0         0 my $x;
328 0 0 0     0 if ( defined( $x = $_[0]{'id'} ) and length $x ) {
329 0         0 return $x;
330             }
331             else {
332 0         0 return $_[0]{'id'} = _gensym();
333             }
334             }
335 0 0       0 if ( $nparms == 2 ) {
336 0 0       0 if ( defined $_[1] ) {
337 0         0 return $_[0]{'id'} = $_[1];
338             }
339             else {
340 0         0 return delete $_[0]{'id'};
341             }
342             }
343 0         0 Carp::croak '$node->idf can\'t take ' . scalar(@_) . ' parameters!';
344             }
345              
346              
347             sub push_content {
348 3832     3832 1 5529 my $self = shift;
349 3832 100       6031 return $self unless @_;
350              
351 3831   100     8580 my $content = ( $self->{'_content'} ||= [] );
352 3831         6136 for (@_) {
353 3838 100       7172 if ( ref($_) eq 'ARRAY' ) {
    100          
354              
355             # magically call new_from_lol
356 3         13 push @$content, $self->new_from_lol($_);
357 3         12 _weaken($content->[-1]->{'_parent'} = $self);
358             }
359             elsif ( ref($_) ) { # insert an element
360 1605 100       2729 $_->detach if $_->{'_parent'};
361 1605         4798 _weaken($_->{'_parent'} = $self);
362 1605         2954 push( @$content, $_ );
363             }
364             else { # insert text segment
365 2230 100 100     5437 if ( @$content && !ref $content->[-1] ) {
366              
367             # last content element is also text segment -- append
368 386         678 $content->[-1] .= $_;
369             }
370             else {
371 1844         3483 push( @$content, $_ );
372             }
373             }
374             }
375 3831         5567 return $self;
376             }
377              
378              
379             sub unshift_content {
380 2     2 1 16 my $self = shift;
381 2 50       6 return $self unless @_;
382              
383 2   50     7 my $content = ( $self->{'_content'} ||= [] );
384 2         5 for ( reverse @_ ) { # so they get added in the order specified
385 2 100       10 if ( ref($_) eq 'ARRAY' ) {
    50          
386              
387             # magically call new_from_lol
388 1         4 unshift @$content, $self->new_from_lol($_);
389 1         7 _weaken($content->[0]->{'_parent'} = $self);
390             }
391             elsif ( ref $_ ) { # insert an element
392 1 50       5 $_->detach if $_->{'_parent'};
393 1         5 _weaken($_->{'_parent'} = $self);
394 1         3 unshift( @$content, $_ );
395             }
396             else { # insert text segment
397 0 0 0     0 if ( @$content && !ref $content->[0] ) {
398              
399             # last content element is also text segment -- prepend
400 0         0 $content->[0] = $_ . $content->[0];
401             }
402             else {
403 0         0 unshift( @$content, $_ );
404             }
405             }
406             }
407 2         5 return $self;
408             }
409              
410             # Cf. splice ARRAY,OFFSET,LENGTH,LIST
411              
412              
413             sub splice_content {
414 2     2 1 18 my ( $self, $offset, $length, @to_add ) = @_;
415 2 50       9 Carp::croak "splice_content requires at least one argument"
416             if @_ < 2; # at least $h->splice_content($offset);
417              
418 2   50     7 my $content = ( $self->{'_content'} ||= [] );
419              
420             # prep the list
421              
422 2         3 my @out;
423 2 50       6 if ( @_ > 2 ) { # self, offset, length, ...
424 2         5 foreach my $n (@to_add) {
425 2 100       10 if ( ref($n) eq 'ARRAY' ) {
    50          
426 1         3 $n = $self->new_from_lol($n);
427 1         6 _weaken($n->{'_parent'} = $self);
428             }
429             elsif ( ref($n) ) {
430 1         41 $n->detach;
431 1         13 _weaken($n->{'_parent'} = $self);
432             }
433             }
434 2         9 @out = splice @$content, $offset, $length, @to_add;
435             }
436             else { # self, offset
437 0         0 @out = splice @$content, $offset;
438             }
439 2         6 foreach my $n (@out) {
440 4 50       11 $n->{'_parent'} = undef if ref $n;
441             }
442 2         9 return @out;
443             }
444              
445              
446             sub detach {
447 4     4 1 12 my $self = $_[0];
448 4 100       16 return undef unless ( my $parent = $self->{'_parent'} );
449 1         1 $self->{'_parent'} = undef;
450 1   50     3 my $cohort = $parent->{'_content'} || return $parent;
451 1   0     2 @$cohort = grep { not( ref($_) and $_ eq $self ) } @$cohort;
  0         0  
452              
453             # filter $self out, if parent has any evident content
454              
455 1         2 return $parent;
456             }
457              
458              
459             sub detach_content {
460 1   50 1 1 9 my $c = $_[0]->{'_content'} || return (); # in case of no content
461 1         3 for (@$c) {
462 5 100       11 $_->{'_parent'} = undef if ref $_;
463             }
464 1         4 return splice @$c;
465             }
466              
467              
468             sub replace_with {
469 4     4 1 12 my ( $self, @replacers ) = @_;
470             Carp::croak "the target node has no parent"
471 4 50       15 unless my ($parent) = $self->{'_parent'};
472              
473 4         9 my $parent_content = $parent->{'_content'};
474 4 50 33     23 Carp::croak "the target node's parent has no content!?"
475             unless $parent_content and @$parent_content;
476              
477 4         8 my $replacers_contains_self;
478 4         11 for (@replacers) {
479 8 50       47 if ( !ref $_ ) {
    100          
    50          
    100          
480              
481             # noop
482             }
483             elsif ( $_ eq $self ) {
484              
485             # noop, but check that it's there just once.
486 4 50       17 Carp::croak "Replacement list contains several copies of target!"
487             if $replacers_contains_self++;
488             }
489             elsif ( $_ eq $parent ) {
490 0         0 Carp::croak "Can't replace an item with its parent!";
491             }
492             elsif ( ref($_) eq 'ARRAY' ) {
493 2         6 $_ = $self->new_from_lol($_);
494 2         8 _weaken($_->{'_parent'} = $parent);
495             }
496             else {
497 2         12 $_->detach;
498 2         6 _weaken($_->{'_parent'} = $parent);
499              
500             # each of these are necessary
501             }
502             } # for @replacers
503 4 100 66     9 @$parent_content = map { ( ref($_) and $_ eq $self ) ? @replacers : $_ }
  28         98  
504             @$parent_content;
505              
506 4 50       9 $self->{'_parent'} = undef unless $replacers_contains_self;
507              
508             # if replacers does contain self, then the parent attribute is fine as-is
509              
510 4         11 return $self;
511             }
512              
513              
514             sub preinsert {
515 2     2 1 16 my $self = shift;
516 2 50       7 return $self unless @_;
517 2         10 return $self->replace_with( @_, $self );
518             }
519              
520              
521             sub postinsert {
522 2     2 1 11 my $self = shift;
523 2 50       7 return $self unless @_;
524 2         9 return $self->replace_with( $self, @_ );
525             }
526              
527              
528             sub replace_with_content {
529 10     10 1 12 my $self = $_[0];
530             Carp::croak "the target node has no parent"
531 10 50       19 unless my ($parent) = $self->{'_parent'};
532              
533 10         15 my $parent_content = $parent->{'_content'};
534 10 50 33     25 Carp::croak "the target node's parent has no content!?"
535             unless $parent_content and @$parent_content;
536              
537 10   50     32 my $content_r = $self->{'_content'} || [];
538 10 100 66     18 @$parent_content = map { ( ref($_) and $_ eq $self ) ? @$content_r : $_ }
  35         111  
539             @$parent_content;
540              
541 10         19 $self->{'_parent'} = undef; # detach $self from its parent
542              
543             # Update parentage link, removing from $self's content list
544 10 0       16 for ( splice @$content_r ) { _weaken($_->{'_parent'} = $parent) if ref $_ }
  0         0  
545              
546 10         16 return $self; # note: doesn't destroy it.
547             }
548              
549              
550             sub delete_content {
551 1156     1156 1 1166 for (
552             splice @{
553 1156 50       2358 delete( $_[0]->{'_content'} )
554              
555             # Deleting it here (while holding its value, for the moment)
556             # will keep calls to detach() from trying to uselessly filter
557             # the list (as they won't be able to see it once it's been
558             # deleted)
559             || return ( $_[0] ) # in case of no content
560             },
561             0
562              
563             # the splice is so we can null the array too, just in case
564             # something somewhere holds a ref to it
565             )
566             {
567 1972 100       3168 $_->delete if ref $_;
568             }
569 1156         1641 $_[0];
570             }
571              
572              
573             # two handy aliases
574 0     0 1 0 sub destroy { shift->delete(@_) }
575 0     0 1 0 sub destroy_content { shift->delete_content(@_) }
576              
577             sub delete {
578 1290     1290 1 1545 my $self = $_[0];
579             $self->delete_content # recurse down
580 1290 100 100     2086 if $self->{'_content'} && @{ $self->{'_content'} };
  1158         2953  
581              
582 1290 50 66     3292 $self->detach if $self->{'_parent'} and $self->{'_parent'}{'_content'};
583              
584             # not the typical case
585              
586 1290         2021 %$self = (); # null out the whole object on the way out
587 1290         1938 return;
588             }
589              
590              
591             sub clone {
592              
593             #print "Cloning $_[0]\n";
594 4     4 1 14 my $it = shift;
595 4 50       7 Carp::croak "clone() can be called only as an object method"
596             unless ref $it;
597 4 50       8 Carp::croak "clone() takes no arguments" if @_;
598              
599 4         24 my $new = bless {%$it}, ref($it); # COPY!!! HOOBOY!
600 4         11 delete @$new{ '_content', '_parent', '_pos', '_head', '_body' };
601              
602             # clone any contents
603 4 100 66     10 if ( $it->{'_content'} and @{ $it->{'_content'} } ) {
  3         7  
604             $new->{'_content'}
605 3         5 = [ ref($it)->clone_list( @{ $it->{'_content'} } ) ];
  3         14  
606 3         4 for ( @{ $new->{'_content'} } ) {
  3         6  
607 5 100       12 _weaken($_->{'_parent'} = $new) if ref $_;
608             }
609             }
610              
611 4         9 return $new;
612             }
613              
614              
615             sub clone_list {
616 3 50   3 1 7 Carp::croak "clone_list can be called only as a class method"
617             if ref shift @_;
618              
619             # all that does is get me here
620             return map {
621 3 100       5 ref($_)
  5         14  
622             ? $_->clone # copy by method
623             : $_ # copy by evaluation
624             } @_;
625             }
626              
627              
628             sub normalize_content {
629 1     1 1 411 my $start = $_[0];
630 1         2 my $c;
631             return
632 1 50 33     11 unless $c = $start->{'_content'} and ref $c and @$c; # nothing to do
      33        
633             # TODO: if we start having text elements, deal with catenating those too?
634 1         3 my @stretches = (undef); # start with a barrier
635              
636             # I suppose this could be rewritten to treat stretches as it goes, instead
637             # of at the end. But feh.
638              
639             # Scan:
640 1         6 for ( my $i = 0; $i < @$c; ++$i ) {
641 6 100 100     18 if ( defined $c->[$i] and ref $c->[$i] ) { # not a text segment
642 1 50       4 if ( $stretches[0] ) {
643              
644             # put in a barrier
645 1 50       3 if ( $stretches[0][1] == 1 ) {
646              
647             #print "Nixing stretch at ", $i-1, "\n";
648 0         0 undef $stretches[0]; # nix the previous one-node "stretch"
649             }
650             else {
651              
652             #print "End of stretch at ", $i-1, "\n";
653 1         4 unshift @stretches, undef;
654             }
655             }
656              
657             # else no need for a barrier
658             }
659             else { # text segment
660 5 100       9 $c->[$i] = '' unless defined $c->[$i];
661 5 100       9 if ( $stretches[0] ) {
662 3         5 ++$stretches[0][1]; # increase length
663             }
664             else {
665              
666             #print "New stretch at $i\n";
667 2         6 unshift @stretches, [ $i, 1 ]; # start and length
668             }
669             }
670             }
671              
672             # Now combine. Note that @stretches is in reverse order, so the indexes
673             # still make sense as we work our way thru (i.e., backwards thru $c).
674 1         2 foreach my $s (@stretches) {
675 4 100 66     13 if ( $s and $s->[1] > 1 ) {
676              
677             #print "Stretch at ", $s->[0], " for ", $s->[1], "\n";
678 2         9 $c->[ $s->[0] ]
679             .= join( '', splice( @$c, $s->[0] + 1, $s->[1] - 1 ) )
680              
681             # append the subsequent ones onto the first one.
682             }
683             }
684 1         4 return;
685             }
686              
687              
688             sub delete_ignorable_whitespace {
689              
690             # This doesn't delete all sorts of whitespace that won't actually
691             # be used in rendering, tho -- that's up to the rendering application.
692             # For example:
693             #
694             # [some whitespace]
695             #
696             # The WS between the two elements /will/ get used by the renderer.
697             # But here:
698             #
699             # [some whitespace]
700             #
701             # the WS between them won't be rendered in any way, presumably.
702              
703             #my $Debug = 4;
704 278 50   278 1 536 die "delete_ignorable_whitespace can be called only as an object method"
705             unless ref $_[0];
706              
707 278 50       485 print "About to tighten up...\n" if $Debug > 2;
708 278         512 my (@to_do) = ( $_[0] ); # Start off.
709 278         397 my ( $i, $sibs, $ptag, $this ); # scratch for the loop...
710 278         514 while (@to_do) {
711 1813 100 100     6562 if ( ( $ptag = ( $this = shift @to_do )->{'_tag'} ) eq 'pre'
      66        
712             or $ptag eq 'textarea'
713             or $HTML::Tagset::isCDATA_Parent{$ptag} )
714             {
715              
716             # block the traversal under those
717 2 50       4 print "Blocking traversal under $ptag\n" if $Debug;
718 2         4 next;
719             }
720 1811 100 66     4522 next unless ( $sibs = $this->{'_content'} and @$sibs );
721 1661         2881 for ( $i = $#$sibs; $i >= 0; --$i ) { # work backwards thru the list
722 3237 100       4875 if ( ref $sibs->[$i] ) {
723 1535         2098 unshift @to_do, $sibs->[$i];
724              
725             # yes, this happens in pre order -- we're going backwards
726             # thru this sibling list. I doubt it actually matters, tho.
727 1535         2698 next;
728             }
729 1702 100       4138 next if $sibs->[$i] =~ m<[^\n\r\f\t ]>s; # it's /all/ whitespace
730              
731             print "Under $ptag whose canTighten ",
732 510 50       707 "value is ", 0 + $HTML::Element::canTighten{$ptag}, ".\n"
733             if $Debug > 3;
734              
735             # It's all whitespace...
736              
737 510 100       929 if ( $i == 0 ) {
    100          
738 2 50       5 if ( @$sibs == 1 ) { # I'm an only child
739 2 50       5 next unless $HTML::Element::canTighten{$ptag}; # parent
740             }
741             else { # I'm leftmost of many
742             # if either my parent or sib are eligible, I'm good.
743             next
744             unless $HTML::Element::canTighten{$ptag} # parent
745             or (ref $sibs->[1]
746             and $HTML::Element::canTighten{ $sibs->[1]
747 0 0 0     0 {'_tag'} } # right sib
      0        
748             );
749             }
750             }
751             elsif ( $i == $#$sibs ) { # I'm rightmost of many
752             # if either my parent or sib are eligible, I'm good.
753             next
754             unless $HTML::Element::canTighten{$ptag} # parent
755             or (ref $sibs->[ $i - 1 ]
756             and $HTML::Element::canTighten{ $sibs->[ $i - 1 ]
757 346 0 0     686 {'_tag'} } # left sib
      33        
758             );
759             }
760             else { # I'm the piggy in the middle
761             # My parent doesn't matter -- it all depends on my sibs
762             next
763 162 50 33     346 unless ref $sibs->[ $i - 1 ]
764             or ref $sibs->[ $i + 1 ];
765              
766             # if NEITHER sib is a node, quit
767              
768             next if
769              
770             # bailout condition: if BOTH are INeligible nodes
771             # (as opposed to being text, or being eligible nodes)
772             ref $sibs->[ $i - 1 ]
773             and ref $sibs->[ $i + 1 ]
774             and !$HTML::Element::canTighten{ $sibs->[ $i - 1 ]
775             {'_tag'} } # left sib
776             and !$HTML::Element::canTighten{ $sibs->[ $i + 1 ]
777 162 50 33     1054 {'_tag'} } # right sib
      33        
      33        
778             ;
779             }
780              
781             # Unknown tags aren't in canTighten and so AREN'T subject to tightening
782              
783 348 50       557 print " delendum: child $i of $ptag\n" if $Debug > 3;
784 348         735 splice @$sibs, $i, 1;
785             }
786              
787             # end of the loop-over-children
788             }
789              
790             # end of the while loop.
791              
792 278         614 return;
793             }
794              
795              
796             sub insert_element {
797 1597     1597 1 2755 my ( $self, $tag, $implicit ) = @_;
798 1597 50       2565 return $self->pos() unless $tag; # noop if nothing to insert
799              
800 1597         1735 my $e;
801 1597 100       3838 if ( ref $tag ) {
802 989         1126 $e = $tag;
803 989         1577 $tag = $e->tag;
804             }
805             else { # just a tag name -- so make the element
806 608         1093 $e = $self->element_class->new($tag);
807 608 50       1184 ++( $self->{'_element_count'} ) if exists $self->{'_element_count'};
808              
809             # undocumented. see TreeBuilder.
810             }
811              
812 1597 100       2736 $e->{'_implicit'} = 1 if $implicit;
813              
814 1597         1931 my $pos = $self->{'_pos'};
815 1597 100       2497 $pos = $self unless defined $pos;
816              
817 1597         3021 $pos->push_content($e);
818              
819             $self->{'_pos'} = $pos = $e
820 1597 50 66     2459 unless $self->_empty_element_map->{$tag} || $e->{'_empty_element'};
821              
822 1597         3036 $pos;
823             }
824              
825             #==========================================================================
826             # Some things to override in XML::Element
827              
828             sub _empty_element_map {
829 2383     2383   6998 \%HTML::Element::emptyElement;
830             }
831              
832             sub _fold_case_LC {
833 2704 100   2704   4009 if (wantarray) {
834 15         19 shift;
835 15         53 map lc($_), @_;
836             }
837             else {
838 2689         8058 return lc( $_[1] );
839             }
840             }
841              
842             sub _fold_case_NOT {
843 0 0   0   0 if (wantarray) {
844 0         0 shift;
845 0         0 @_;
846             }
847             else {
848 0         0 return $_[1];
849             }
850             }
851              
852             *_fold_case = \&_fold_case_LC;
853              
854             #==========================================================================
855              
856             #=head1 DUMPING METHODS
857              
858              
859             sub dump {
860 0     0 1 0 my ( $self, $fh, $depth ) = @_;
861 0 0       0 $fh = *STDOUT{IO} unless defined $fh;
862 0 0       0 $depth = 0 unless defined $depth;
863             print $fh " " x $depth, $self->starttag, " \@", $self->address,
864 0 0       0 $self->{'_implicit'} ? " (IMPLICIT)\n" : "\n";
865 0         0 for ( @{ $self->{'_content'} } ) {
  0         0  
866 0 0       0 if ( ref $_ ) { # element
867 0         0 $_->dump( $fh, $depth + 1 ); # recurse
868             }
869             else { # text node
870 0         0 print $fh " " x ( $depth + 1 );
871 0 0 0     0 if ( length($_) > 65 or m<[\x00-\x1F]> ) {
872              
873             # it needs prettyin' up somehow or other
874 0 0       0 my $x
875             = ( length($_) <= 65 )
876             ? $_
877             : ( substr( $_, 0, 65 ) . '...' );
878 0         0 $x =~ s<([\x00-\x1F])>
  0         0  
879 0         0 <'\\x'.(unpack("H2",$1))>eg;
880             print $fh qq{"$x"\n};
881             }
882 0         0 else {
883             print $fh qq{"$_"\n};
884             }
885             }
886             }
887             }
888              
889              
890 262     262 1 7694 sub as_HTML {
891             my ( $self, $entities, $indent, $omissible_map ) = @_;
892              
893 262         397 #my $indent_on = defined($indent) && length($indent);
894             my @html = ();
895 262   100     966  
896 262         524 $omissible_map ||= \%HTML::Element::optionalEndTag;
897             my $empty_element_map = $self->_empty_element_map;
898 262         366  
899 262         378 my $last_tag_tightenable = 0;
900 262         329 my $this_tag_tightenable = 0;
901             my $nonindentable_ancestors = 0; # count of nonindentible tags over us.
902 262         410  
903             my ( $tag, $node, $start, $depth ); # per-iteration scratch
904 262 100 66     535  
905             if ( defined($indent) && length($indent) ) {
906             $self->traverse(
907 616     616   1165 sub {
908 616 100       958 ( $node, $start, $depth ) = @_;
909             if ( ref $node ) { # it's an element
910              
911 456 50       1258 # detect bogus classes. RT #35948, #61673
912             $node->can('starttag')
913             or Carp::confess( "Object of class "
914             . ref($node)
915             . " cannot be processed by HTML::Element" );
916 456         702  
917             $tag = $node->{'_tag'};
918 456 100 66     1224  
    100          
919 229 100 66     800 if ($start) { # on the way in
      100        
920             if (( $this_tag_tightenable
921             = $HTML::Element::canTighten{$tag}
922             )
923             and !$nonindentable_ancestors
924             and $last_tag_tightenable
925             )
926 164         417 {
927             push
928             @html,
929             "\n",
930             $indent x $depth,
931             $node->starttag($entities),
932             ;
933             }
934 65         136 else {
935             push( @html, $node->starttag($entities) );
936 229         307 }
937             $last_tag_tightenable = $this_tag_tightenable;
938              
939             ++$nonindentable_ancestors
940 229 100 100     872 if $tag eq 'pre' or $tag eq 'textarea'
      66        
941             or $HTML::Tagset::isCDATA_Parent{$tag};
942              
943             }
944             elsif (
945             not( $empty_element_map->{$tag}
946             or $omissible_map->{$tag} )
947             )
948             {
949              
950 203 100 100     708 # on the way out
      66        
951             if ( $tag eq 'pre' or $tag eq 'textarea'
952             or $HTML::Tagset::isCDATA_Parent{$tag} )
953 2         4 {
954             --$nonindentable_ancestors;
955 2         3 $last_tag_tightenable
956 2         4 = $HTML::Element::canTighten{$tag};
957             push @html, $node->endtag;
958              
959             }
960 201 100 66     657 else { # general case
      100        
961             if (( $this_tag_tightenable
962             = $HTML::Element::canTighten{$tag}
963             )
964             and !$nonindentable_ancestors
965             and $last_tag_tightenable
966             )
967 89         201 {
968             push
969             @html,
970             "\n",
971             $indent x $depth,
972             $node->endtag,
973             ;
974             }
975 112         207 else {
976             push @html, $node->endtag;
977 201         307 }
978             $last_tag_tightenable = $this_tag_tightenable;
979              
980             #print "$tag tightenable: $this_tag_tightenable\n";
981             }
982             }
983             }
984             else { # it's a text segment
985 160         206  
986             $last_tag_tightenable = 0; # I guess this is right
987             HTML::Entities::encode_entities( $node, $entities )
988              
989             # That does magic things if $entities is undef.
990             unless (
991             ( defined($entities) && !length($entities) )
992              
993 160 50 33     984 # If there's no entity to encode, don't call it
      33        
      33        
994             || $HTML::Tagset::isCDATA_Parent{ $_[3]{'_tag'} }
995              
996             # To keep from amp-escaping children of script et al.
997             # That doesn't deal with descendants; but then, CDATA
998             # parents shouldn't /have/ descendants other than a
999             # text children (or comments?)
1000             || $encoded_content
1001 160 50       2310 );
1002 0         0 if ($nonindentable_ancestors) {
1003             push @html, $node; # say no go
1004             }
1005 160 50       226 else {
1006 0         0 if ($last_tag_tightenable) {
1007             $node =~ s<[\n\r\f\t ]+>< >s;
1008              
1009 0         0 #$node =~ s< $><>s;
1010 0         0 $node =~ s<^ ><>s;
1011             push
1012             @html,
1013             "\n",
1014             $indent x $depth,
1015             $node,
1016              
1017             #Text::Wrap::wrap($indent x $depth, $indent x $depth, "\n" . $node)
1018             ;
1019             }
1020 160         268 else {
1021             push
1022             @html,
1023             $node,
1024              
1025             #Text::Wrap::wrap('', $indent x $depth, $node)
1026             ;
1027             }
1028             }
1029 616         826 }
1030             1; # keep traversing
1031 15         147 }
1032             ); # End of parms to traverse()
1033             }
1034             else { # no indenting -- much simpler code
1035             $self->traverse(
1036 3532     3532   5701 sub {
1037 3532 100       4914 ( $node, $start ) = @_;
1038             if ( ref $node ) {
1039              
1040 2385 50       4206 # detect bogus classes. RT #35948
1041             $node->isa( $self->element_class )
1042             or Carp::confess( "Object of class "
1043             . ref($node)
1044             . " cannot be processed by HTML::Element" );
1045 2385         3599  
1046 2385 100 100     5046 $tag = $node->{'_tag'};
    100          
1047 1195         1834 if ($start) { # on the way in
1048             push( @html, $node->starttag($entities) );
1049             }
1050             elsif (
1051             not( $empty_element_map->{$tag}
1052             or $omissible_map->{$tag} )
1053             )
1054             {
1055              
1056 1172         1783 # on the way out
1057             push( @html, $node->endtag );
1058             }
1059             }
1060             else {
1061              
1062             # simple text content
1063             HTML::Entities::encode_entities( $node, $entities )
1064              
1065             # That does magic things if $entities is undef.
1066             unless (
1067             ( defined($entities) && !length($entities) )
1068              
1069 1147 100 100     6048 # If there's no entity to encode, don't call it
      100        
      100        
1070             || $HTML::Tagset::isCDATA_Parent{ $_[3]{'_tag'} }
1071              
1072             # To keep from amp-escaping children of script et al.
1073             # That doesn't deal with descendants; but then, CDATA
1074             # parents shouldn't /have/ descendants other than a
1075             # text children (or comments?)
1076             || $encoded_content
1077 1147         13079 );
1078             push( @html, $node );
1079 3532         4967 }
1080             1; # keep traversing
1081 247         1442 }
1082             ); # End of parms to traverse()
1083             }
1084 262 100 100     2012  
1085 1         6 if ( $self->{_store_declarations} && defined $self->{_decl} ) {
1086             unshift @html, sprintf "\n", $self->{_decl}->{text};
1087             }
1088 262         1681  
1089             return join( '', @html );
1090             }
1091              
1092              
1093             sub as_text {
1094              
1095 11     11 1 641 # Yet another iteratively implemented traverser
1096 11   50     41 my ( $this, %options ) = @_;
1097 11         21 my $skip_dels = $options{'skip_dels'} || 0;
1098 11         16 my (@pile) = ($this);
1099 11         16 my $tag;
1100 11         23 my $text = '';
1101 27 50       59 while (@pile) {
    100          
1102             if ( !defined( $pile[0] ) ) { # undef!
1103             # no-op
1104             }
1105 12         32 elsif ( !ref( $pile[0] ) ) { # text bit! save it!
1106             $text .= shift @pile;
1107             }
1108 15 100       49 else { # it's a ref -- traverse under it
1109 15 50 33     87 unshift @pile, @{ $this->{'_content'} || $nillio }
      33        
      33        
1110             unless ( $tag = ( $this = shift @pile )->{'_tag'} ) eq 'style'
1111             or $tag eq 'script'
1112             or ( $skip_dels and $tag eq 'del' );
1113             }
1114 11         37 }
1115             return $text;
1116             }
1117              
1118             # extra_chars added for RT #26436
1119 7     7 1 19 sub as_trimmed_text {
1120 7         19 my ( $this, %options ) = @_;
1121             my $text = $this->as_text(%options);
1122 7 100       18 my $extra_chars = defined $options{'extra_chars'}
1123             ? $options{'extra_chars'} : '';
1124 7         194  
1125 7         119 $text =~ s/[\n\r\f\t$extra_chars ]+$//s;
1126 7         76 $text =~ s/^[\n\r\f\t$extra_chars ]+//s;
1127 7         40 $text =~ s/[\n\r\f\t$extra_chars ]+/ /g;
1128             return $text;
1129             }
1130 1     1 0 4  
1131             sub as_text_trimmed { shift->as_trimmed_text(@_) } # alias, because I forget
1132              
1133              
1134             # TODO: make it wrap, if not indent?
1135              
1136             sub as_XML {
1137              
1138 115     115 1 430 # based an as_HTML
1139             my ($self) = @_;
1140              
1141 115         178 #my $indent_on = defined($indent) && length($indent);
1142 115         200 my @xml = ();
1143             my $empty_element_map = $self->_empty_element_map;
1144 115         163  
1145             my ( $tag, $node, $start ); # per-iteration scratch
1146             $self->traverse(
1147 1374     1374   2006 sub {
1148 1374 100       1837 ( $node, $start ) = @_;
1149 1164         1578 if ( ref $node ) { # it's an element
1150 1164 100       1579 $tag = $node->{'_tag'};
1151             if ($start) { # on the way in
1152 589         888  
1153 3680 100 100     7076 foreach my $attr ( $node->all_attr_names() ) {
1154             Carp::croak(
1155             "$tag has an invalid attribute name '$attr'")
1156             unless ( $attr eq '/' || $self->_valid_name($attr) );
1157             }
1158 588 100 100     1335  
1159 12 100       48 if ( $empty_element_map->{$tag}
1160             and !@{ $node->{'_content'} || $nillio } )
1161 11         23 {
1162             push( @xml, $node->starttag_XML( undef, 1 ) );
1163             }
1164 577         1001 else {
1165             push( @xml, $node->starttag_XML(undef) );
1166             }
1167             }
1168 575 50 66     1010 else { # on the way out
1169 1 50       4 unless ( $empty_element_map->{$tag}
1170             and !@{ $node->{'_content'} || $nillio } )
1171 575         884 {
1172             push( @xml, $node->endtag_XML() );
1173             } # otherwise it will have been an <... /> tag.
1174             }
1175             }
1176 210         418 else { # it's just text
1177 210         271 _xml_escape($node);
1178             push( @xml, $node );
1179 1373         1894 }
1180             1; # keep traversing
1181 115         685 }
1182             );
1183 114         1068  
1184             join( '', @xml, "\n" );
1185             }
1186              
1187             sub _xml_escape {
1188              
1189             # DESTRUCTIVE (a.k.a. "in-place")
1190             # Five required escapes: http://www.w3.org/TR/2006/REC-xml11-20060816/#syntax
1191 255     255   13706 # We allow & if it's part of a valid escape already: http://www.w3.org/TR/2006/REC-xml11-20060816/#sec-references
1192             foreach my $x (@_) {
1193              
1194 255 100       395 # In strings with no encoded entities all & should be encoded.
1195 26         474 if ($encoded_content) {
1196             $x
1197             =~ s/&(?! # An ampersand that isn't followed by...
1198             (\#\d+; | # A hash mark, digits and semicolon, or
1199             \#x[\da-f]+; | # A hash mark, "x", hex digits and semicolon, or
1200             $START_CHAR$NAME_CHAR+; ) # A valid unicode entity name and semicolon
1201             )/&/gx; # Needs to be escaped to amp
1202             }
1203 229         377 else {
1204             $x =~ s/&/&/g;
1205             }
1206              
1207 255         328 # simple character escapes
1208 255         293 $x =~ s/
1209 255         325 $x =~ s/>/>/g;
1210 255         354 $x =~ s/"/"/g;
1211             $x =~ s/'/'/g;
1212 255         323 }
1213             return;
1214             }
1215              
1216              
1217             # NOTES:
1218             #
1219             # It's been suggested that attribute names be made :-keywords:
1220             # (:_tag "img" :border 0 :src "pie.png" :usemap "#main.map")
1221             # However, it seems that Scheme has no such data type as :-keywords.
1222             # So, for the moment at least, I tend toward simplicity, uniformity,
1223             # and universality, where everything a string or a list.
1224              
1225 1     1 1 3 sub as_Lisp_form {
1226             my @out;
1227              
1228 1         2 my $sub;
1229 1         2 my $depth = 0;
1230             my ( @list, $val );
1231 1     1   2 $sub = sub { # Recursor
1232 1         4 my $self = $_[0];
1233 1 50       4 @list = ( '_tag', $self->{'_tag'} );
1234             @list = () unless defined $list[-1]; # unlikely
1235 1         9  
1236             for ( sort keys %$self ) { # predictable ordering
1237 4 100 100     20 next
      100        
      66        
1238             if $_ eq '_content'
1239             or $_ eq '_tag'
1240             or $_ eq '_parent'
1241             or $_ eq '/';
1242              
1243             # Leave the other private attributes, I guess.
1244 1 50       5 push @list, $_, $val
1245             if defined( $val = $self->{$_} ); # and !ref $val;
1246             }
1247 1         2  
1248             for (@list) {
1249              
1250 4         6 # octal-escape it
  0         0  
1251 4         9 s<([^\x20\x21\x23\x27-\x5B\x5D-\x7E])>
1252             eg;
1253 1         6 $_ = qq{"$_"};
1254 1 50       2 }
  1 50       8  
1255 1         2 push @out, ( ' ' x $depth ) . '(' . join ' ', splice @list;
1256 1         2 if ( @{ $self->{'_content'} || $nillio } ) {
1257 1         3 $out[-1] .= " \"_content\" (\n";
  1         2  
1258 1 50       3 ++$depth;
1259             foreach my $c ( @{ $self->{'_content'} } ) {
1260             if ( ref($c) ) {
1261 0         0  
1262             # an element -- recurse
1263             $sub->($c);
1264             }
1265             else {
1266 1         2  
1267 1         3 # a text segment -- stick it in and octal-escape it
  0         0  
1268             push @out, $c;
1269             $out[-1] =~ s<([^\x20\x21\x23\x27-\x5B\x5D-\x7E])>
1270 1         2 eg;
1271 1         4  
1272             # And quote and indent it.
1273             $out[-1] .= "\"\n";
1274 1         2 $out[-1] = ( ' ' x $depth ) . '"' . $out[-1];
1275 1         3 }
1276             }
1277             --$depth;
1278             substr( $out[-1], -1 )
1279 0         0 = "))\n"; # end of _content and of the element
1280             }
1281 1         1 else {
1282 1         8 $out[-1] .= ")\n";
1283             }
1284 1         3 return;
1285 1         11 };
1286 1         5  
1287             $sub->( $_[0] );
1288             undef $sub;
1289             return join '', @out;
1290             }
1291 0     0 1 0  
1292 0 0       0  
1293             sub format {
1294 0         0 my ( $self, $formatter ) = @_;
1295 0         0 unless ( defined $formatter ) {
1296             # RECOMMEND PREREQ: HTML::FormatText
1297 0         0 require HTML::FormatText;
1298             $formatter = HTML::FormatText->new();
1299             }
1300             $formatter->format($self);
1301             }
1302 1425     1425 1 1986  
1303              
1304 1425         1865 sub starttag {
1305             my ( $self, $entities ) = @_;
1306 1425 50       2317  
1307 1425 50       1932 my $name = $self->{'_tag'};
1308 1425 50       1978  
1309             return $self->{'text'} if $name eq '~literal';
1310 1425 50       2017 return "{'text'} . ">" if $name eq '~declaration';
1311 0 0 0     0 return "{'text'} . ">" if $name eq '~pi';
1312              
1313             if ( $name eq '~comment' ) {
1314             if ( ref( $self->{'text'} || '' ) eq 'ARRAY' ) {
1315              
1316 0         0 # Does this ever get used? And is this right?
  0         0  
1317             return
1318             "
1319 0         0 . join( ' ', map( "--$_--", @{ $self->{'text'} } ) ) . ">";
1320             }
1321             else {
1322             return "";
1323 1425 50       2614 }
1324 1425         1626 }
1325 1425         5893  
1326 8127 100 66     24140 my $tag = $html_uc ? "<\U$name" : "<\L$name";
      100        
1327 487         700 my $val;
1328 487 50       700 for ( sort keys %$self ) { # predictable ordering
1329 487 50 100     1082 next if !length $_ or m/^_/s or $_ eq '/';
    50 66        
1330             $val = $self->{$_};
1331             next if !defined $val; # or ref $val;
1332             if ($_ eq $val && # if attribute is boolean, for this element
1333             exists( $HTML::Element::boolean_attr{$name} )
1334             && (ref( $HTML::Element::boolean_attr{$name} )
1335             ? $HTML::Element::boolean_attr{$name}{$_}
1336             : $HTML::Element::boolean_attr{$name} eq $_
1337 0 0       0 )
1338             )
1339             {
1340             $tag .= $html_uc ? " \U$_" : " \L$_";
1341 487 100 66     873 }
1342             else { # non-boolean attribute
1343              
1344 1         2 if ( ref $val eq 'HTML::Element'
1345             and $val->{_tag} eq '~literal' )
1346             {
1347 486 50 33     1643 $val = $val->{text};
      33        
1348             }
1349             else {
1350             HTML::Entities::encode_entities( $val, $entities )
1351             unless (
1352             defined($entities) && !length($entities)
1353             || $encoded_content
1354              
1355 487         4404 );
1356 487 50       1006 }
1357              
1358             $val = qq{"$val"};
1359 1425 100 100     2802 $tag .= $html_uc ? qq{ \U$_\E=$val} : qq{ \L$_\E=$val};
1360             }
1361             } # for keys
1362 7         27 if ( scalar $self->content_list == 0
1363             && $self->_empty_element_map->{ $self->tag } )
1364             {
1365 1418         3662 return $tag . " />";
1366             }
1367             else {
1368             return $tag . ">";
1369             }
1370             }
1371 588     588 1 860  
1372              
1373             sub starttag_XML {
1374             my ($self) = @_;
1375 588         757  
1376             # and a third parameter to signal emptiness?
1377 588 50       944  
1378 588 50       820 my $name = $self->{'_tag'};
1379 588 50       827  
1380             return $self->{'text'} if $name eq '~literal';
1381 588 100       833 return '{'text'} . '>' if $name eq '~declaration';
1382 1 50 50     7 return "{'text'} . "?>" if $name eq '~pi';
1383              
1384             if ( $name eq '~comment' ) {
1385 0         0 if ( ref( $self->{'text'} || '' ) eq 'ARRAY' ) {
  0         0  
1386              
1387             # Does this ever get used? And is this right?
1388 1         1 $name = join( ' ', @{ $self->{'text'} } );
1389             }
1390 1         3 else {
1391 1         3 $name = $self->{'text'};
1392             }
1393             $name =~ s/--/--/g; # can't have double --'s in XML comments
1394 587         776 return "";
1395 587         610 }
1396 587         2091  
1397 3675 100 66     10557 my $tag = "<$name";
      100        
1398             my $val;
1399             for ( sort keys %$self ) { # predictable ordering
1400             next if !length $_ or m/^_/s or $_ eq '/';
1401 19 50       49  
1402 19         33 # Hm -- what to do if val is undef?
1403 19         40 # I suppose that shouldn't ever happen.
1404             next if !defined( $val = $self->{$_} ); # or ref $val;
1405 587 100       1789 _xml_escape($val);
1406             $tag .= qq{ $_="$val"};
1407             }
1408             @_ == 3 ? "$tag />" : "$tag>";
1409             }
1410 1375 50   1375 1 3684  
1411              
1412             sub endtag {
1413             $html_uc ? "{'_tag'}>" : "{'_tag'}>";
1414 575     575 1 1255 }
1415              
1416             sub endtag_XML {
1417             "{'_tag'}>";
1418             }
1419              
1420             #==========================================================================
1421             # This, ladies and germs, is an iterative implementation of a
1422             # recursive algorithm. DON'T TRY THIS AT HOME.
1423             # Basically, the algorithm says:
1424             #
1425             # To traverse:
1426             # 1: pre-order visit this node
1427             # 2: traverse any children of this node
1428             # 3: post-order visit this node, unless it's a text segment,
1429             # or a prototypically empty node (like "br", etc.)
1430             # Add to that the consideration of the callbacks' return values,
1431             # so you can block visitation of the children, or siblings, or
1432             # abort the whole excursion, etc.
1433             #
1434             # So, why all this hassle with making the code iterative?
1435             # It makes for real speed, because it eliminates the whole
1436             # hassle of Perl having to allocate scratch space for each
1437             # instance of the recursive sub. Since the algorithm
1438             # is basically simple (and not all recursive ones are!) and
1439             # has few necessary lexicals (basically just the current node's
1440             # content list, and the current position in it), it was relatively
1441             # straightforward to store that information not as the frame
1442             # of a sub, but as a stack, i.e., a simple Perl array (well, two
1443             # of them, actually: one for content-listrefs, one for indexes of
1444             # current position in each of those).
1445              
1446 380     380 1 715 my $NIL = [];
1447              
1448 380 50       782 sub traverse {
1449             my ( $start, $callback, $ignore_text ) = @_;
1450              
1451 380 50 33     1195 Carp::croak "traverse can be called only as an object method"
1452             unless ref $start;
1453              
1454             Carp::croak('must provide a callback for traverse()!')
1455 380         544 unless defined $callback and ref $callback;
1456 380 100       968  
    50          
1457 377         557 # Elementary type-checking:
1458             my ( $c_pre, $c_post );
1459             if ( UNIVERSAL::isa( $callback, 'CODE' ) ) {
1460 3         7 $c_pre = $c_post = $callback;
1461 3 50 33     19 }
1462             elsif ( UNIVERSAL::isa( $callback, 'ARRAY' ) ) {
1463             ( $c_pre, $c_post ) = @$callback;
1464 3 50 33     11 Carp::croak(
1465             "pre-order callback \"$c_pre\" is true but not a coderef!")
1466             if $c_pre and not UNIVERSAL::isa( $c_pre, 'CODE' );
1467 3 50 33     12 Carp::croak(
1468             "pre-order callback \"$c_post\" is true but not a coderef!")
1469             if $c_post and not UNIVERSAL::isa( $c_post, 'CODE' );
1470             return $start unless $c_pre or $c_post;
1471              
1472 0 0       0 # otherwise there'd be nothing to actually do!
1473             }
1474             else {
1475             Carp::croak("$callback is not a known kind of reference")
1476 380         666 unless ref($callback);
1477             }
1478 380         891  
1479 380         634 my $empty_element_map = $start->_empty_element_map;
1480              
1481             my (@C) = [$start]; # a stack containing lists of children
1482             my (@I) = (-1); # initial value must be -1 for each list
1483             # a stack of indexes to current position in corresponding lists in @C
1484 380         578 # In each of these, 0 is the active point
1485              
1486             # scratch:
1487             my ($rv, # return value of callback
1488             $this, # current node
1489             $content_r, # child list of $this
1490 380         689 );
1491              
1492             # THE BIG LOOP
1493 5931 100 66     10115 while (@C) {
  5931         12138  
1494              
1495             # Move to next item in this frame
1496             if ( !defined( $I[0] ) or ++$I[0] >= @{ $C[0] } ) {
1497 2376 100 66     17410  
      100        
      66        
      66        
      33        
      33        
1498             # We either went off the end of this list, or aborted the list
1499             # So call the post-order callback:
1500             if ( $c_post
1501             and defined $I[0]
1502             and @C > 1
1503              
1504             # to keep the next line from autovivifying
1505             and defined( $this = $C[1][ $I[1] ] ) # sanity, and
1506             # suppress callbacks on exiting the fictional top frame
1507             and ref($this) # sanity
1508             and not(
1509             $this->{'_empty_element'}
1510             || ( $empty_element_map->{ $this->{'_tag'} || '' }
1511             && !@{ $this->{'_content'} } ) # RT #49932
1512 1992         2539 ) # things that don't get post-order callbacks
1513 1992         2230 )
1514             {
1515             shift @I;
1516 1992         3094 shift @C;
1517              
1518             #print "Post! at depth", scalar(@I), "\n";
1519             $rv = $c_post->(
1520              
1521             #map $_, # copy to avoid any messiness
1522             $this, # 0: this
1523             0, # 1: startflag (0 for post-order call)
1524 1992 50 33     5171 @I - 1, # 2: depth
1525 0         0 );
1526 0 0       0  
    0          
    0          
    0          
    0          
1527 0         0 if ( defined($rv) and ref($rv) eq $travsignal_package ) {
1528             $rv = $$rv; #deref
1529             if ( $rv eq 'ABORT' ) {
1530             last; # end of this excursion!
1531             }
1532             elsif ( $rv eq 'PRUNE' ) {
1533              
1534             # NOOP on post!!
1535             }
1536             elsif ( $rv eq 'PRUNE_SOFTLY' ) {
1537              
1538             # NOOP on post!!
1539             }
1540             elsif ( $rv eq 'OK' ) {
1541              
1542 0         0 # noop
1543             }
1544             elsif ( $rv eq 'PRUNE_UP' ) {
1545 0         0 $I[0] = undef;
1546             }
1547             else {
1548             die "Unknown travsignal $rv\n";
1549              
1550             # should never happen
1551             }
1552 384         489 }
1553 384         491 }
1554             else {
1555 2376         4463 shift @I;
1556             shift @C;
1557             }
1558 3555         5404 next;
1559             }
1560 3555 50       5237  
1561 3555 100 66     8143 $this = $C[0][ $I[0] ];
1562 2030         3449  
1563             if ($c_pre) {
1564             if ( defined $this and ref $this ) { # element
1565             $rv = $c_pre->(
1566              
1567             #map $_, # copy to avoid any messiness
1568             $this, # 0: this
1569             1, # 1: startflag (1 for pre-order call)
1570             @I - 1, # 2: depth
1571 1525 100       2224 );
1572 1517         2987 }
1573             else { # text segment
1574             next if $ignore_text;
1575             $rv = $c_pre->(
1576              
1577             #map $_, # copy to avoid any messiness
1578             $this, # 0: this
1579             1, # 1: startflag (1 for pre-order call)
1580             @I - 1, # 2: depth
1581             $C[1][ $I[1] ], # 3: parent
1582             # And there will always be a $C[1], since
1583             # we can't start traversing at a text node
1584 3546 50       6883 $I[0] # 4: index of self in parent's content list
    100          
1585 0         0 );
1586             }
1587             if ( not $rv ) { # returned false. Same as PRUNE.
1588 3         8 next; # prune
1589 3 50       9 }
    0          
    0          
    0          
    0          
1590 3         4 elsif ( ref($rv) eq $travsignal_package ) {
1591             $rv = $$rv; # deref
1592             if ( $rv eq 'ABORT' ) {
1593 0         0 last; # end of this excursion!
1594             }
1595             elsif ( $rv eq 'PRUNE' ) {
1596 0 0 0     0 next;
      0        
1597             }
1598             elsif ( $rv eq 'PRUNE_SOFTLY' ) {
1599             if (ref($this)
1600             and not( $this->{'_empty_element'}
1601             || $empty_element_map->{ $this->{'_tag'} || '' } )
1602             )
1603 0         0 {
1604 0         0  
1605             # push a dummy empty content list just to trigger a post callback
1606 0         0 unshift @I, -1;
1607             unshift @C, $NIL;
1608             }
1609             next;
1610             }
1611             elsif ( $rv eq 'OK' ) {
1612              
1613 0         0 # noop
1614 0         0 }
1615             elsif ( $rv eq 'PRUNE_UP' ) {
1616             $I[0] = undef;
1617             next;
1618              
1619             # equivalent of last'ing out of the current child list.
1620              
1621             # Used to have PRUNE_UP_SOFTLY and ABORT_SOFTLY here, but the code
1622             # for these was seriously upsetting, served no particularly clear
1623             # purpose, and could not, I think, be easily implemented with a
1624 0         0 # recursive routine. All bad things!
1625             }
1626             else {
1627             die "Unknown travsignal $rv\n";
1628              
1629             # should never happen
1630             }
1631             }
1632              
1633             # else fall thru to meaning same as \'OK'.
1634             }
1635              
1636 3543 100 100     11588 # end of pre-order calling
      100        
1637              
1638             # Now queue up content list for the current element...
1639             if (ref $this
1640             and not( # ...except for those which...
1641             not( $content_r = $this->{'_content'} and @$content_r )
1642              
1643             # ...have empty content lists...
1644             and $this->{'_empty_element'}
1645             || $empty_element_map->{ $this->{'_tag'} || '' }
1646              
1647             # ...and that don't get post-order callbacks
1648 2008         2928 )
1649 2008   66     4337 )
1650             {
1651             unshift @I, -1;
1652             unshift @C, $content_r || $NIL;
1653              
1654 379         719 #print $this->{'_tag'}, " ($this) adds content_r ", $C[0], "\n";
1655             }
1656             }
1657             return $start;
1658             }
1659 2993     2993 1 3905  
1660 2993 50       4454  
1661             sub is_inside {
1662 2993         3349 my $self = shift;
1663             return 0 unless @_; # if no items specified, I guess this is right.
1664              
1665 2993 100       4516 my $current = $self;
1666 807   66     2156 # the loop starts by looking at the given element
1667 997 100 66     3430  
1668 678         1529 if (scalar @_ == 1) {
1669             while ( defined $current and ref $current ) {
1670 488         1170 return 1 if $current eq $_[0] || $current->{'_tag'} eq $_[0];
1671             $current = $current->{'_parent'};
1672 2186         3076 }
  4440         8737  
1673 2186   66     6572 return 0;
1674 5472 50 33     15105 } else {
1675 5472         13428 my %elements = map { $_ => 1 } @_;
1676             while ( defined $current and ref $current ) {
1677             return 1 if $elements{$current} || $elements{ $current->{'_tag'} };
1678 2186         5419 $current = $current->{'_parent'};
1679             }
1680             }
1681             return 0;
1682             }
1683 1     1 1 321  
1684 1   33     7  
1685             sub is_empty {
1686             my $self = shift;
1687             !$self->{'_content'} || !@{ $self->{'_content'} };
1688             }
1689 3     3 1 6  
1690              
1691 3   100     13 sub pindex {
1692 2   50     6 my $self = shift;
1693 2         6  
1694 5 100 100     24 my $parent = $self->{'_parent'} || return undef;
1695             my $pc = $parent->{'_content'} || return undef;
1696 0         0 for ( my $i = 0; $i < @$pc; ++$i ) {
1697             return $i if ref $pc->[$i] and $pc->[$i] eq $self;
1698             }
1699             return undef; # we shouldn't ever get here
1700             }
1701              
1702             #--------------------------------------------------------------------------
1703 0 0   0 1 0  
1704              
1705 0   0     0 sub left {
1706             Carp::croak "left() is supposed to be an object method"
1707             unless ref $_[0];
1708 0 0       0 my $pc = ( $_[0]->{'_parent'} || return )->{'_content'}
1709 0 0       0 || die "parent is childless?";
1710              
1711 0 0       0 die "parent is childless" unless @$pc;
1712 0         0 return if @$pc == 1; # I'm an only child
1713 0         0  
1714 0 0 0     0 if (wantarray) {
1715 0         0 my @out;
1716             foreach my $j (@$pc) {
1717             return @out if ref $j and $j eq $_[0];
1718             push @out, $j;
1719 0         0 }
1720 0 0 0     0 }
    0          
1721             else {
1722             for ( my $i = 0; $i < @$pc; ++$i ) {
1723             return $i ? $pc->[ $i - 1 ] : undef
1724             if ref $pc->[$i] and $pc->[$i] eq $_[0];
1725 0         0 }
1726 0         0 }
1727              
1728             die "I'm not in my parent's content list?";
1729             return;
1730             }
1731 0 0   0 1 0  
1732              
1733 0   0     0 sub right {
1734             Carp::croak "right() is supposed to be an object method"
1735             unless ref $_[0];
1736 0 0       0 my $pc = ( $_[0]->{'_parent'} || return )->{'_content'}
1737 0 0       0 || die "parent is childless?";
1738              
1739 0 0       0 die "parent is childless" unless @$pc;
1740 0         0 return if @$pc == 1; # I'm an only child
1741 0         0  
1742 0 0       0 if (wantarray) {
1743 0         0 my ( @out, $seen );
1744             foreach my $j (@$pc) {
1745             if ($seen) {
1746 0 0 0     0 push @out, $j;
1747             }
1748             else {
1749 0 0       0 $seen = 1 if ref $j and $j eq $_[0];
1750 0         0 }
1751             }
1752             die "I'm not in my parent's content list?" unless $seen;
1753 0         0 return @out;
1754 0 0 0     0 }
    0          
1755             else {
1756             for ( my $i = 0; $i < @$pc; ++$i ) {
1757 0         0 return +( $i == $#$pc ) ? undef : $pc->[ $i + 1 ]
1758 0         0 if ref $pc->[$i] and $pc->[$i] eq $_[0];
1759             }
1760             die "I'm not in my parent's content list?";
1761             return;
1762             }
1763             }
1764              
1765             #--------------------------------------------------------------------------
1766 13 100   13 1 93  
1767 1   100     6  
1768             sub address {
1769             if ( @_ == 1 ) { # report-address form
1770             return join(
1771             '.',
1772             reverse( # so it starts at the top
1773             map( $_->pindex() || '0', # so that root's undef -> '0'
1774             $_[0], # self and...
1775             $_[0]->lineage )
1776             )
1777 12         49 );
1778 12         22 }
1779             else { # get-node-at-address
1780 12 50 33     60 my @stack = split( /\./, $_[1] );
1781 0         0 my $here;
1782 0         0  
1783             if ( @stack and !length $stack[0] ) { # relative addressing
1784             $here = $_[0];
1785 12 50       40 shift @stack;
1786 12         50 }
1787             else { # absolute addressing
1788             return undef unless 0 == shift @stack; # pop the initial 0-for-root
1789 12         31 $here = $_[0]->root;
1790             }
1791              
1792 20 50 33     54 while (@stack) {
  20         68  
1793             return undef
1794             unless $here->{'_content'}
1795 20         41 and @{ $here->{'_content'} } > $stack[0];
1796 20 50 66     82  
1797             # make sure the index isn't too high
1798             $here = $here->{'_content'}[ shift @stack ];
1799             return undef if @stack and not ref $here;
1800              
1801 12         35 # we hit a text node when we expected a non-terminal element node
1802             }
1803              
1804             return $here;
1805             }
1806             }
1807 0     0 1 0  
1808 0         0  
1809 0   0     0 sub depth {
1810 0         0 my $here = $_[0];
1811             my $depth = 0;
1812 0         0 while ( defined( $here = $here->{'_parent'} ) and ref($here) ) {
1813             ++$depth;
1814             }
1815             return $depth;
1816             }
1817 12     12 1 24  
1818 12   33     41  
1819 0         0 sub root {
1820             my $here = my $root = shift;
1821 12         23 while ( defined( $here = $here->{'_parent'} ) and ref($here) ) {
1822             $root = $here;
1823             }
1824             return $root;
1825             }
1826 1     1 1 2  
1827 1         3  
1828 1   66     8 sub lineage {
1829 2         7 my $here = shift;
1830             my @lineage;
1831 1         7 while ( defined( $here = $here->{'_parent'} ) and ref($here) ) {
1832             push @lineage, $here;
1833             }
1834             return @lineage;
1835             }
1836 0     0 1 0  
1837 0         0  
1838 0   0     0 sub lineage_tag_names {
1839 0         0 my $here = my $start = shift;
1840             my @lineage_names;
1841 0         0 while ( defined( $here = $here->{'_parent'} ) and ref($here) ) {
1842             push @lineage_names, $here->{'_tag'};
1843             }
1844             return @lineage_names;
1845 0     0 1 0 }
1846              
1847              
1848 0     0 1 0 sub descendents { shift->descendants(@_) }
1849 0 0       0  
1850 0         0 sub descendants {
1851             my $start = shift;
1852             if (wantarray) {
1853             my @descendants;
1854 0     0   0 $start->traverse(
1855 0         0 [ # pre-order sub only
1856             sub {
1857             push( @descendants, $_[0] );
1858 0         0 return 1;
1859             },
1860             undef # no post
1861 0         0 ],
1862 0         0 1, # ignore text
1863             );
1864             shift @descendants; # so $self doesn't appear in the list
1865 0         0 return @descendants;
1866             }
1867             else { # just returns a scalar
1868             my $descendants = -1; # to offset $self being counted
1869 0     0   0 $start->traverse(
1870 0         0 [ # pre-order sub only
1871             sub {
1872             ++$descendants;
1873 0         0 return 1;
1874             },
1875             undef # no post
1876 0         0 ],
1877             1, # ignore text
1878             );
1879             return $descendants;
1880             }
1881 1     1 1 881 }
1882              
1883              
1884             sub find { shift->find_by_tag_name(@_) }
1885              
1886 3     3 1 17 # yup, a handy alias
1887 3 50       11  
1888             sub find_by_tag_name {
1889 3 50       11 my (@pile) = shift(@_); # start out the to-do stack for the traverser
1890 3         14 Carp::croak "find_by_tag_name can be called only as an object method"
1891 3         8 unless ref $pile[0];
1892 3         9 return () unless @_;
1893 16         27 my (@tags) = $pile[0]->_fold_case(@_);
1894 16         23 my ( @matching, $this, $this_tag );
1895 16 100       31 while (@pile) {
1896 3 50       9 $this_tag = ( $this = shift @pile )->{'_tag'};
1897 0         0 foreach my $t (@tags) {
1898 0         0 if ( $t eq $this_tag ) {
1899             if (wantarray) {
1900             push @matching, $this;
1901 3         10 last;
1902             }
1903             else {
1904             return $this;
1905 13 100       15 }
  13         40  
1906             }
1907 0 0       0 }
1908 0         0 unshift @pile, grep ref($_), @{ $this->{'_content'} || next };
1909             }
1910             return @matching if wantarray;
1911             return;
1912             }
1913              
1914              
1915 3     3 1 972 sub find_by_attribute {
1916 3 50       13  
1917             # We could limit this to non-internal attributes, but hey.
1918 3         14 my ( $self, $attribute, $value ) = @_;
1919             Carp::croak "Attribute must be a defined value!"
1920 3         5 unless defined $attribute;
1921 3         7 $attribute = $self->_fold_case($attribute);
1922 3         5  
1923             my @matching;
1924             my $wantarray = wantarray;
1925             my $quit;
1926 17 100 66 17   45 $self->traverse(
1927             [ # pre-order only
1928             sub {
1929 3         7 if ( exists $_[0]{$attribute}
1930 3 50       15 and $_[0]{$attribute} eq $value )
1931             {
1932             push @matching, $_[0];
1933 14         22 return HTML::Element::ABORT
1934             unless $wantarray; # only take the first
1935             }
1936 3         36 1; # keep traversing
1937             },
1938             undef # no post
1939             ],
1940 3 50       17 1, # yes, ignore text nodes.
1941 0         0 );
1942              
1943             if ($wantarray) {
1944 3         10 return @matching;
1945             }
1946             else {
1947             return $matching[0];
1948             }
1949             }
1950              
1951             #--------------------------------------------------------------------------
1952 13 50   13 1 1162  
1953              
1954 13         19 sub look_down {
1955 13         32 ref( $_[0] ) or Carp::croak "look_down works only as an object method";
1956 19 50       45  
1957             my @criteria;
1958 19 100       34 for ( my $i = 1; $i < @_; ) {
1959 4 50       10 Carp::croak "Can't use undef as an attribute name"
1960             unless defined $_[$i];
1961 4         11 if ( ref $_[$i] ) {
1962             Carp::croak "A " . ref( $_[$i] ) . " value is not a criterion"
1963             unless ref $_[$i] eq 'CODE';
1964 15 50       30 push @criteria, $_[ $i++ ];
1965 15 100       44 }
    50          
1966             else {
1967             Carp::croak "param list to look_down ends in a key!" if $i == $#_;
1968             push @criteria, [
1969             scalar( $_[0]->_fold_case( $_[$i] ) ),
1970             defined( $_[ $i + 1 ] )
1971             ? ( ( ref $_[ $i + 1 ] ? $_[ $i + 1 ] : lc( $_[ $i + 1 ] ) ),
1972             ref( $_[ $i + 1 ] )
1973             )
1974              
1975 15         36 # yes, leave that LC!
1976             : undef
1977             ];
1978 13 50       25 $i += 2;
1979             }
1980 13         21 }
1981 13         17 Carp::croak "No criteria?" unless @criteria;
1982              
1983 13         29 my (@pile) = ( $_[0] );
1984             my ( @matching, $val, $this );
1985             Node:
1986 58         80 while ( defined( $this = shift @pile ) ) {
1987 66 100       99  
1988 9 100       15 # Yet another traverser implemented with merely iterative code.
1989             foreach my $c (@criteria) {
1990             if ( ref($c) eq 'CODE' ) {
1991             next Node unless $c->($this); # jump to the continue block
1992             }
1993 57 50 66     278 else { # it's an attr-value pair
    100          
1994             next Node # jump to the continue block
1995             if # two values are unequal if:
1996             ( defined( $val = $this->{ $c->[0] } ) )
1997             ? ( !defined $c->[ 1
1998             ] # actual is def, critval is undef => fail
1999             # allow regex matching
2000             # allow regex matching
2001             or (
2002             $c->[2] eq 'Regexp'
2003             ? $val !~ $c->[1]
2004             : ( ref $val ne $c->[2]
2005              
2006             # have unequal ref values => fail
2007             or lc($val) ne lc( $c->[1] )
2008              
2009             # have unequal lc string values => fail
2010             )
2011             )
2012             )
2013             : ( defined $c->[1]
2014             ) # actual is undef, critval is def => fail
2015             }
2016 13 100       51 }
2017 4         7  
2018             # We make it this far only if all the criteria passed.
2019             return $this unless wantarray;
2020 49 100       61 push @matching, $this;
  49         186  
2021             }
2022 4 100       21 continue {
2023 1         3 unshift @pile, grep ref($_), @{ $this->{'_content'} || $nillio };
2024             }
2025             return @matching if wantarray;
2026             return;
2027             }
2028 1 50   1 1 5  
2029              
2030 1         2 sub look_up {
2031 1         4 ref( $_[0] ) or Carp::croak "look_up works only as an object method";
2032 1 50       4  
2033             my @criteria;
2034 1 50       3 for ( my $i = 1; $i < @_; ) {
2035 0 0       0 Carp::croak "Can't use undef as an attribute name"
2036             unless defined $_[$i];
2037 0         0 if ( ref $_[$i] ) {
2038             Carp::croak "A " . ref( $_[$i] ) . " value is not a criterion"
2039             unless ref $_[$i] eq 'CODE';
2040 1 50       3 push @criteria, $_[ $i++ ];
2041 1 50       4 }
    50          
2042             else {
2043             Carp::croak "param list to look_up ends in a key!" if $i == $#_;
2044             push @criteria, [
2045             scalar( $_[0]->_fold_case( $_[$i] ) ),
2046             defined( $_[ $i + 1 ] )
2047             ? ( ( ref $_[ $i + 1 ] ? $_[ $i + 1 ] : lc( $_[ $i + 1 ] ) ),
2048             ref( $_[ $i + 1 ] )
2049 1         3 )
2050             : undef # Yes, leave that LC!
2051             ];
2052 1 50       3 $i += 2;
2053             }
2054 1         3 }
2055 1         1 Carp::croak "No criteria?" unless @criteria;
2056              
2057 1         3 my ( @matching, $val );
2058             my $this = $_[0];
2059             Node:
2060 2         3 while (1) {
2061 2 50       5  
2062 0 0       0 # You'll notice that the code here is almost the same as for look_down.
2063             foreach my $c (@criteria) {
2064             if ( ref($c) eq 'CODE' ) {
2065             next Node unless $c->($this); # jump to the continue block
2066             }
2067 2 50 66     20 else { # it's an attr-value pair
    100          
2068             next Node # jump to the continue block
2069             if # two values are unequal if:
2070             ( defined( $val = $this->{ $c->[0] } ) )
2071             ? ( !defined $c->[ 1
2072             ] # actual is def, critval is undef => fail
2073             or (
2074             $c->[2] eq 'Regexp'
2075             ? $val !~ $c->[1]
2076             : ( ref $val ne $c->[2]
2077              
2078             # have unequal ref values => fail
2079             or lc($val) ne $c->[1]
2080              
2081             # have unequal lc string values => fail
2082             )
2083             )
2084             )
2085             : ( defined $c->[1]
2086             ) # actual is undef, critval is def => fail
2087             }
2088 1 50       4 }
2089 0         0  
2090             # We make it this far only if all the criteria passed.
2091             return $this unless wantarray;
2092 1 50 33     7 push @matching, $this;
2093             }
2094             continue {
2095 0 0       0 last unless defined( $this = $this->{'_parent'} ) and ref $this;
2096 0         0 }
2097              
2098             return @matching if wantarray;
2099             return;
2100             }
2101              
2102             #--------------------------------------------------------------------------
2103 0 0   0 1 0  
2104 0         0  
2105 0 0       0 sub attr_get_i {
2106             if ( @_ > 2 ) {
2107 0         0 my $self = shift;
2108 0 0       0 Carp::croak "No attribute names can be undef!"
2109 0         0 if grep !defined($_), @_;
2110 0         0 my @attributes = $self->_fold_case(@_);
2111             if (wantarray) {
2112 0 0       0 my @out;
  0         0  
2113             foreach my $x ( $self, $self->lineage ) {
2114 0         0 push @out,
2115             map { exists( $x->{$_} ) ? $x->{$_} : () } @attributes;
2116             }
2117 0         0 return @out;
2118 0         0 }
2119             else {
2120 0 0       0 foreach my $x ( $self, $self->lineage ) {
2121             foreach my $attribute (@attributes) {
2122             return $x->{$attribute}
2123 0         0 if exists $x->{$attribute}; # found
2124             }
2125             }
2126             return; # never found
2127             }
2128             }
2129             else {
2130 0 0       0  
2131             # Single-attribute search. Simpler, most common, so optimize
2132 0         0 # for the most common case
2133 0         0 Carp::croak "Attribute name must be a defined value!"
2134 0 0       0 unless defined $_[1];
2135             my $self = $_[0];
2136 0 0       0 my $attribute = $self->_fold_case( $_[1] );
  0         0  
2137             if (wantarray) { # list context
2138             return
2139             map { exists( $_->{$attribute} ) ? $_->{$attribute} : () }
2140 0         0 $self, $self->lineage;
2141 0 0       0 }
2142             else { # scalar context
2143 0         0 foreach my $x ( $self, $self->lineage ) {
2144             return $x->{$attribute} if exists $x->{$attribute}; # found
2145             }
2146             return; # never found
2147             }
2148             }
2149             }
2150 0     0 1 0  
2151 0 0       0  
2152             sub tagname_map {
2153 0         0 my (@pile) = $_[0]; # start out the to-do stack for the traverser
2154 0         0 Carp::croak "find_by_tag_name can be called only as an object method"
2155             unless ref $pile[0];
2156 0 0       0 my ( %map, $this_tag, $this );
2157             while (@pile) {
2158 0   0     0 $this_tag = ''
  0         0  
2159             unless defined( $this_tag = ( $this = shift @pile )->{'_tag'} )
2160 0 0       0 ; # dance around the strange case of having an undef tagname.
  0         0  
2161             push @{ $map{$this_tag} ||= [] }, $this; # add to map
2162 0         0 unshift @pile, grep ref($_),
2163             @{ $this->{'_content'} || next }; # traverse
2164             }
2165             return \%map;
2166             }
2167 0     0 1 0  
2168              
2169 0         0 sub extract_links {
2170 0         0 my $start = shift;
2171 0         0  
2172             my %wantType;
2173 0         0 @wantType{ $start->_fold_case(@_) } = (1) x @_; # if there were any
2174             my $wantType = scalar(@_);
2175              
2176             my @links;
2177 0         0  
2178             # TODO: add xml:link?
2179              
2180 0     0   0 my ( $link_attrs, $tag, $self, $val ); # scratch for each iteration
2181             $start->traverse(
2182 0         0 [ sub { # pre-order call only
2183             $self = $_[0];
2184 0 0 0     0  
2185             $tag = $self->{'_tag'};
2186 0 0       0 return 1
2187             if $wantType && !$wantType{$tag}; # if we're selective
2188              
2189             if (defined(
2190             $link_attrs = $HTML::Element::linkElements{$tag}
2191             )
2192             )
2193             {
2194              
2195 0 0       0 # If this is a tag that has any link attributes,
2196 0 0       0 # look over possibly present link attributes,
2197 0         0 # saving the value, if found.
2198             for ( ref($link_attrs) ? @$link_attrs : $link_attrs ) {
2199             if ( defined( $val = $self->attr($_) ) ) {
2200             push( @links, [ $val, $self, $_, $tag ] );
2201 0         0 }
2202             }
2203             }
2204 0         0 1; # return true, so we keep recursing
2205             },
2206             undef
2207 0         0 ],
2208             1, # ignore text nodes
2209             );
2210             \@links;
2211             }
2212 0     0 1 0  
2213              
2214 0         0 sub simplify_pres {
2215             my $pre = 0;
2216              
2217 0 0   0   0 my $sub;
2218 0 0       0 my $line;
  0         0  
2219 0 0       0 $sub = sub {
    0          
2220 0         0 ++$pre if $_[0]->{'_tag'} eq 'pre';
2221             foreach my $it ( @{ $_[0]->{'_content'} || return } ) {
2222             if ( ref $it ) {
2223             $sub->($it); # recurse!
2224             }
2225             elsif ($pre) {
2226              
2227 0         0 #$it =~ s/(?:(?:\cm\cj*)|(?:\cj))/\n/g;
2228 0         0  
2229 0         0 $it = join "\n", map {
2230             ;
2231 0         0 $line = $_;
2232             while (
2233             $line
2234             =~ s/^([^\t]*)(\t+)/$1.(" " x ((length($2)<<3)-(length($1)&7)))/e
2235              
2236             # Sort of adapted from Text::Tabs -- yes, it's hardwired-in that
2237             # tabs are at every EIGHTH column.
2238 0         0 )
2239             {
2240             }
2241             $line;
2242             }
2243 0 0       0 split /(?:(?:\cm\cj*)|(?:\cj))/, $it, -1;
2244 0         0 }
2245 0         0 }
2246 0         0 --$pre if $_[0]->{'_tag'} eq 'pre';
2247             return;
2248 0         0 };
2249 0         0 $sub->( $_[0] );
2250              
2251             undef $sub;
2252             return;
2253             }
2254 25 50   25 1 407  
2255 25         58  
2256 25 50       56 sub same_as {
2257             die 'same_as() takes only one argument: $h->same_as($i)' unless @_ == 2;
2258 25 50 33     80 my ( $h, $i ) = @_[ 0, 1 ];
2259             die "same_as() can be called only as an object method" unless ref $h;
2260              
2261             return 0 unless defined $i and ref $i;
2262              
2263 25 100       72 # An element can't be same_as anything but another element!
2264             # They needn't be of the same class, tho.
2265              
2266             return 1 if $h eq $i;
2267              
2268             # special (if rare) case: anything is the same as... itself!
2269              
2270             # assumes that no content lists in/under $h or $i contain subsequent
2271             # text segments, like: ['foo', ' bar']
2272              
2273 23 100       57 # compare attributes now.
2274             #print "Comparing tags of $h and $i...\n";
2275              
2276             return 0 unless $h->{'_tag'} eq $i->{'_tag'};
2277              
2278             # only significant attribute whose name starts with "_"
2279              
2280             #print "Comparing attributes of $h and $i...\n";
2281             # Compare attributes, but only the real ones.
2282             {
2283              
2284             # Bear in mind that the average element has very few attributes,
2285             # and that element names are rather short.
2286 22         30 # (Values are a different story.)
2287 22 50       59  
  110         378  
2288             # XXX I would think that /^[^_]/ would be faster, at least easier to read.
2289 22 50       62 my @keys_h
  109         344  
2290             = sort grep { length $_ and substr( $_, 0, 1 ) ne '_' } keys %$h;
2291 22 100       64 my @keys_i
2292             = sort grep { length $_ and substr( $_, 0, 1 ) ne '_' } keys %$i;
2293              
2294 21         53 return 0 unless @keys_h == @keys_i;
2295              
2296             # different number of real attributes? they're different.
2297 8 50 33     88 for ( my $x = 0; $x < @keys_h; ++$x ) {
2298             return 0
2299             unless $keys_h[$x] eq $keys_i[$x] and # same key name
2300             $h->{ $keys_h[$x] } eq $i->{ $keys_h[$x] }; # same value
2301             # Should this test for definedness on values?
2302             # People shouldn't be putting undef in attribute values, I think.
2303             }
2304 21   100     58 }
2305 21   100     48  
2306             #print "Comparing children of $h and $i...\n";
2307 21 50       41 my $hcl = $h->{'_content'} || [];
2308             my $icl = $i->{'_content'} || [];
2309              
2310             return 0 unless @$hcl == @$icl;
2311 21 100       38  
2312             # different numbers of children? they're different.
2313              
2314 18         38 if (@$hcl) {
2315 30 100       59  
2316 18 50       42 # compare each of the children:
2317             for ( my $x = 0; $x < @$hcl; ++$x ) {
2318             if ( ref $hcl->[$x] ) {
2319             return 0 unless ref( $icl->[$x] );
2320 18 100       48  
2321             # an element can't be the same as a text segment
2322             # Both elements:
2323 12 50       23 return 0 unless $hcl->[$x]->same_as( $icl->[$x] ); # RECURSE!
2324             }
2325             else {
2326             return 0 if ref( $icl->[$x] );
2327 12 50       32  
2328             # a text segment can't be the same as an element
2329             # Both text segments:
2330             return 0 unless $hcl->[$x] eq $icl->[$x];
2331             }
2332 19         63 }
2333             }
2334              
2335             return 1; # passed all the tests!
2336             }
2337 29     29 1 4057  
2338 29   66     118  
2339             sub new_from_lol {
2340             my $class = shift;
2341 29         54 $class = ref($class) || $class;
2342              
2343 29         46 # calling as an object method is just the same as ref($h)->new_from_lol(...)
2344             my $lol = $_[1];
2345              
2346             my @ancestor_lols;
2347 29         51  
2348             # So we can make sure there's no cyclicities in this lol.
2349             # That would be perverse, but one never knows.
2350             my ( $sub, $k, $v, $node ); # last three are scratch values
2351 161     161   212 $sub = sub {
2352 161 50       280  
2353 161         211 #print "Building for $_[0]\n";
2354 161 50       486 my $lol = $_[0];
2355             return unless @$lol;
2356 161         215 my ( @attributes, @children );
2357             Carp::croak "Cyclicity detected in source LOL tree, around $lol?!?"
2358 161         220 if grep( $_ eq $lol, @ancestor_lols );
2359             push @ancestor_lols, $lol;
2360              
2361 161         316 my $tag_name = 'null';
2362 424 100       861  
    100          
    100          
    50          
2363             # Recursion in in here:
2364 132         313 for ( my $i = 0; $i < @$lol; ++$i ) { # Iterate over children
2365             if ( ref( $lol->[$i] ) eq 'ARRAY' )
2366             { # subtree: most common thing in loltree
2367 279 100       403 push @children, $sub->( $lol->[$i] );
2368 161         227 }
2369 161 50       436 elsif ( !ref( $lol->[$i] ) ) {
2370             if ( $i == 0 ) { # name
2371             $tag_name = $lol->[$i];
2372             Carp::croak "\"$tag_name\" isn't a good tag name!"
2373             if $tag_name =~ m/[<>\/\x00-\x20]/
2374 118         300 ; # minimal sanity, certainly!
2375             }
2376             else { # text segment child
2377             push @children, $lol->[$i];
2378 12         15 }
  12         22  
2379 12         15 }
  24         69  
2380 12 50 33     79 elsif ( ref( $lol->[$i] ) eq 'HASH' ) { # attribute hashref
      33        
      33        
2381             keys %{ $lol->[$i] }; # reset the each-counter, just in case
2382             while ( ( $k, $v ) = each %{ $lol->[$i] } ) {
2383             push @attributes, $class->_fold_case($k), $v
2384             if defined $v
2385             and $k ne '_name'
2386             and $k ne '_content'
2387             and $k ne '_parent';
2388              
2389             # enforce /some/ sanity!
2390 1 50       3 }
2391             }
2392 0         0 elsif ( UNIVERSAL::isa( $lol->[$i], __PACKAGE__ ) ) {
2393             if ( $lol->[$i]->{'_parent'} ) { # if claimed
2394             #print "About to clone ", $lol->[$i], "\n";
2395 1         2 push @children, $lol->[$i]->clone();
2396             }
2397 1         3 else {
2398             push @children, $lol->[$i]; # if unclaimed...
2399             #print "Claiming ", $lol->[$i], "\n";
2400             $lol->[$i]->{'_parent'} = 1; # claim it NOW
2401             # This WILL be replaced by the correct value once we actually
2402             # construct the parent, just after the end of this loop...
2403 0         0 }
2404             }
2405             else {
2406             Carp::croak "new_from_lol doesn't handle references of type "
2407             . ref( $lol->[$i] );
2408 161         204 }
2409 161         335 }
2410              
2411             pop @ancestor_lols;
2412             $node = $class->new($tag_name);
2413 161 100       291  
2414 160 100       290 #print "Children: @children\n";
2415              
2416             if ( $class eq __PACKAGE__ ) { # Special-case it, for speed:
2417 160 100       285 %$node = ( %$node, @attributes ) if @attributes;
2418 156         340  
2419 156         226 #print join(' ', $node, ' ' , map("<$_>", %$node), "\n");
2420 250 100       558 if (@children) {
2421             $node->{'_content'} = \@children;
2422             foreach my $c (@children) {
2423             _weaken($c->{'_parent'} = $node)
2424             if ref $c;
2425             }
2426             }
2427 1         2 }
  0         0  
2428             else { # Do it the clean way...
2429 1 50       16 #print "Done neatly\n";
  1 50       5  
  1         5  
2430             while (@attributes) { $node->attr( splice @attributes, 0, 2 ) }
2431             $node->push_content(
2432             map { _weaken($_->{'_parent'} = $node) if ref $_; $_ }
2433             @children )
2434 161         385 if @children;
2435 29         189 }
2436              
2437             return $node;
2438             };
2439 29 100       76  
2440 4 50       11 # End of sub definition.
  4         9  
2441              
2442             if (wantarray) {
2443             my (@nodes) = map { ; ( ref($_) eq 'ARRAY' ) ? $sub->($_) : $_ } @_;
2444 4         40 # Let text bits pass thru, I guess. This makes this act more like
2445             # unshift_content et al. Undocumented.
2446              
2447 4         10 undef $sub;
2448             # so it won't be in its own frame, so its refcount can hit 0
2449              
2450 25 50       71 return @nodes;
2451             }
2452 25 50       75 else {
2453             Carp::croak "new_from_lol in scalar context needs exactly one lol"
2454             unless @_ == 1;
2455 25         62 return $_[0] unless ref( $_[0] ) eq 'ARRAY';
2456 25         416 # used to be a fatal error. still undocumented tho.
2457              
2458             $node = $sub->( $_[0] );
2459 25         71 undef $sub;
2460             # so it won't be in its own frame, so its refcount can hit 0
2461              
2462             return $node;
2463             }
2464             }
2465 0     0 1 0  
2466              
2467 0         0 sub objectify_text {
2468 0         0 my (@stack) = ( $_[0] );
2469 0         0  
  0         0  
2470 0 0       0 my ($this);
2471 0         0 while (@stack) {
2472             foreach my $c ( @{ ( $this = shift @stack )->{'_content'} } ) {
2473             if ( ref($c) ) {
2474 0         0 unshift @stack, $c; # visit it later.
2475             }
2476             else {
2477             $c = $this->element_class->new(
2478             '~text',
2479             'text' => $c,
2480             '_parent' => $this
2481             );
2482 0         0 }
2483             }
2484             }
2485             return;
2486 0     0 1 0 }
2487 0         0  
2488             sub deobjectify_text {
2489 0 0       0 my (@stack) = ( $_[0] );
2490             my ($old_node);
2491 0 0       0  
2492 0         0 if ( $_[0]{'_tag'} eq '~text' ) { # special case
2493             # Puts the $old_node variable to a different purpose
2494             if ( $_[0]{'_parent'} ) {
2495 0         0 $_[0]->replace_with( $old_node = delete $_[0]{'text'} )->delete;
2496             }
2497             else { # well, that's that, then!
2498 0 0       0 $old_node = delete $_[0]{'text'};
2499 0         0 }
  0         0  
2500              
2501             if ( ref( $_[0] ) eq __PACKAGE__ ) { # common case
2502             %{ $_[0] } = (); # poof!
2503             }
2504 0         0 else {
2505 0         0  
2506             # play nice:
2507 0 0       0 delete $_[0]{'_parent'};
2508 0         0 $_[0]->delete;
2509             }
2510             return '' unless defined $old_node; # sanity!
2511 0         0 return $old_node;
2512 0         0 }
  0         0  
2513 0 0       0  
2514 0 0       0 while (@stack) {
2515 0         0 foreach my $c ( @{ ( shift @stack )->{'_content'} } ) {
2516 0 0       0 if ( ref($c) ) {
2517 0         0 if ( $c->{'_tag'} eq '~text' ) {
2518             $c = ( $old_node = $c )->{'text'};
2519             if ( ref($old_node) eq __PACKAGE__ ) { # common case
2520             %$old_node = (); # poof!
2521             }
2522 0         0 else {
2523 0         0  
2524             # play nice:
2525             delete $old_node->{'_parent'};
2526             $old_node->delete;
2527 0         0 }
2528             }
2529             else {
2530             unshift @stack, $c; # visit it later.
2531             }
2532             }
2533 0         0 }
2534             }
2535              
2536             return undef;
2537             }
2538              
2539              
2540             {
2541              
2542             # The next three subs are basically copied from Number::Latin,
2543             # based on a one-liner by Abigail. Yes, I could simply require that
2544 23     23   310 # module, and a Roman numeral module too, but really, HTML-Tree already
  23         61  
  23         148  
2545             # has enough dependecies as it is; and anyhow, I don't need the functions
2546             # that do latin2int or roman2int.
2547 0 0   0   0 no integer;
2548 0 0 0     0  
2549 0 0       0 sub _int2latin {
2550             return unless defined $_[0];
2551 0         0 return '0' if $_[0] < 1 and $_[0] > -1;
2552             return '-' . _i2l( abs int $_[0] )
2553             if $_[0] <= -1; # tolerate negatives
2554             return _i2l( int $_[0] );
2555             }
2556              
2557 0 0   0   0 sub _int2LATIN {
2558 0 0 0     0  
2559 0 0       0 # just the above plus uc
2560             return unless defined $_[0];
2561 0         0 return '0' if $_[0] < 1 and $_[0] > -1;
2562             return '-' . uc( _i2l( abs int $_[0] ) )
2563             if $_[0] <= -1; # tolerate negs
2564             return uc( _i2l( int $_[0] ) );
2565             }
2566              
2567 0   0 0   0 my @alpha = ( 'a' .. 'z' );
2568 0         0  
2569             sub _i2l { # the real work
2570             my $int = $_[0] || return "";
2571             _i2l( int( ( $int - 1 ) / 26 ) )
2572             . $alpha[ $int % 26 - 1 ]; # yes, recursive
2573             # Yes, 26 => is (26 % 26 - 1), which is -1 => Z!
2574             }
2575             }
2576              
2577             {
2578              
2579             # And now, some much less impressive Roman numerals code:
2580              
2581             my (@i) = ( '', qw(I II III IV V VI VII VIII IX) );
2582             my (@x) = ( '', qw(X XX XXX XL L LX LXX LXXX XC) );
2583             my (@c) = ( '', qw(C CC CCC CD D DC DCC DCCC CM) );
2584 0     0   0 my (@m) = ( '', qw(M MM MMM) );
2585 0 0 0     0  
2586             sub _int2ROMAN {
2587 0 0 0     0 my ( $i, $pref );
2588             return '0'
2589             if 0 == ( $i = int( $_[0] || 0 ) ); # zero is a special case
2590 0 0       0 return $i + 0 if $i <= -4000 or $i >= 4000;
2591 0         0  
2592 0         0 # Because over 3999 would require non-ASCII chars, like D-with-)-inside
2593             if ( $i < 0 ) { # grumble grumble tolerate negatives grumble
2594             $pref = '-';
2595 0         0 $i = abs($i);
2596             }
2597             else {
2598 0         0 $pref = ''; # normal case
2599 0 0       0 }
2600 0         0  
2601 0         0 my ( $x, $c, $m ) = ( 0, 0, 0 );
2602 0 0       0 if ( $i >= 10 ) {
2603 0         0 $x = $i / 10;
2604 0         0 $i %= 10;
2605 0 0       0 if ( $x >= 10 ) {
  0         0  
  0         0  
2606             $c = $x / 10;
2607             $x %= 10;
2608             if ( $c >= 10 ) { $m = $c / 10; $c %= 10; }
2609             }
2610             }
2611 0         0  
2612             #print "m$m c$c x$x i$i\n";
2613              
2614 0     0   0 return join( '', $pref, $m[$m], $c[$c], $x[$x], $i[$i] );
2615             }
2616              
2617 0     0   0 sub _int2roman { lc( _int2ROMAN( $_[0] ) ) }
2618             }
2619              
2620             sub _int2int { $_[0] } # dummy
2621              
2622             %list_type_to_sub = (
2623             'I' => \&_int2ROMAN,
2624             'i' => \&_int2roman,
2625             'A' => \&_int2LATIN,
2626             'a' => \&_int2latin,
2627             '1' => \&_int2int,
2628 0     0 1 0 );
2629 0         0  
2630 0         0 sub number_lists {
2631 0 0 0     0 my (@stack) = ( $_[0] );
    0 0        
2632             my ( $this, $tag, $counter, $numberer ); # scratch
2633             while (@stack) { # yup, pre-order-traverser idiom
2634             if ( ( $tag = ( $this = shift @stack )->{'_tag'} ) eq 'ol' ) {
2635 0 0 0     0  
2636             # Prep some things:
2637             $counter
2638             = ( ( $this->{'start'} || '' ) =~ m<^\s*(\d{1,7})\s*$>s )
2639 0   0     0 ? $1
2640             : 1;
2641             $numberer = $list_type_to_sub{ $this->{'type'} || '' }
2642 0 0       0 || $list_type_to_sub{'1'};
  0         0  
2643 0 0       0  
2644 0         0 # Immeditately iterate over all children
2645 0 0       0 foreach my $c ( @{ $this->{'_content'} || next } ) {
2646             next unless ref $c;
2647             unshift @stack, $c;
2648 0 0 0     0 if ( $c->{'_tag'} eq 'li' ) {
2649 0         0 $counter = $1
2650 0         0 if (
2651             ( $c->{'value'} || '' ) =~ m<^\s*(\d{1,7})\s*$>s );
2652             $c->{'_bullet'} = $numberer->($counter) . '.';
2653             ++$counter;
2654             }
2655             }
2656              
2657             }
2658 0 0       0 elsif ( $tag eq 'ul' or $tag eq 'dir' or $tag eq 'menu' ) {
  0         0  
2659 0 0       0  
2660 0         0 # Immeditately iterate over all children
2661 0 0       0 foreach my $c ( @{ $this->{'_content'} || next } ) {
2662             next unless ref $c;
2663             unshift @stack, $c;
2664             $c->{'_bullet'} = '*' if $c->{'_tag'} eq 'li';
2665             }
2666 0 0       0  
  0         0  
2667 0 0       0 }
2668             else {
2669             foreach my $c ( @{ $this->{'_content'} || next } ) {
2670             unshift @stack, $c if ref $c;
2671 0         0 }
2672             }
2673             }
2674             return;
2675             }
2676 0     0 1 0  
2677 0         0  
2678             sub has_insane_linkage {
2679             my @pile = ( $_[0] );
2680             my ( $c, $i, $p, $this ); # scratch
2681 0         0  
2682 0         0 # Another iterative traverser; this time much simpler because
2683 0         0 # only in pre-order:
2684 0   0     0 my %parent_of = ( $_[0], 'TOP-OF-SCAN' );
2685 0 0       0 while (@pile) {
2686             $this = shift @pile;
2687 0 0       0 $c = $this->{'_content'} || next;
2688 0         0 return ( $this, "_content attribute is true but nonref." )
2689 0 0       0 unless ref($c) eq 'ARRAY';
2690             next unless @$c;
2691 0 0       0 for ( $i = 0; $i < @$c; ++$i ) {
2692 0 0       0 return ( $this, "Child $i is undef" )
2693             unless defined $c->[$i];
2694             if ( ref( $c->[$i] ) ) {
2695             return ( $c->[$i], "appears in its own content list" )
2696 0 0       0 if $c->[$i] eq $this;
2697 0         0 return ( $c->[$i],
2698             "appears twice in the tree: once under $this, once under $parent_of{$c->[$i]}"
2699             ) if exists $parent_of{ $c->[$i] };
2700             $parent_of{ $c->[$i] } = '' . $this;
2701              
2702             # might as well just use the stringification of it.
2703 0 0       0  
2704 0 0       0 return ( $c->[$i],
2705             "_parent attribute is wrong (not defined)" )
2706 0 0       0 unless defined( $p = $c->[$i]{'_parent'} );
2707             return ( $c->[$i], "_parent attribute is wrong (nonref)" )
2708             unless ref($p);
2709             return ( $c->[$i],
2710             "_parent attribute is wrong (is $p; should be $this)" )
2711 0         0 unless $p eq $this;
2712             }
2713             }
2714             unshift @pile, grep ref($_), @$c;
2715 0         0  
2716             # queue up more things on the pile stack
2717             }
2718             return; #okay
2719 0     0   0 }
2720 0         0  
2721 0         0 sub _asserts_fail { # to be run on trusted documents only
2722 0         0 my (@pile) = ( $_[0] );
2723 0 0       0 my ( @errors, $this, $id, $assert, $parent, $rv );
2724 0   0     0 while (@pile) {
2725             $this = shift @pile;
2726 0 0       0 if ( defined( $assert = $this->{'assert'} ) ) {
2727             $id = ( $this->{'id'} ||= $this->address )
2728             ; # don't use '0' as an ID, okay?
2729             unless ( ref($assert) ) {
2730 0 0       0  
2731             package main;
2732             ## no critic
2733             $assert = $this->{'assert'} = (
2734             $assert =~ m/\bsub\b/
2735             ? eval($assert)
2736 0 0       0 : eval("sub { $assert\n}")
2737 0         0 );
2738             ## use critic
2739 0     0   0 if ($@) {
2740             push @errors,
2741             [ $this, "assertion at $id broke in eval: $@" ];
2742 0         0 $assert = $this->{'assert'} = sub { };
2743 0         0 }
2744 0         0 }
2745             $parent = $this->{'_parent'};
2746             $rv = undef;
2747             eval {
2748 0 0       0 $rv = $assert->(
2749             $this, $this->{'_tag'}, $this->{'_id'}, # 0,1,2
2750             $parent
2751             ? ( $parent, $parent->{'_tag'}, $parent->{'id'} )
2752 0 0       0 : () # 3,4,5
    0          
2753 0         0 );
2754             };
2755             if ($@) {
2756 0         0 push @errors, [ $this, "assertion at $id died: $@" ];
2757             }
2758             elsif ( !$rv ) {
2759             push @errors, [ $this, "assertion at $id failed" ];
2760             }
2761 0 0       0  
  0         0  
2762             # else OK
2763 0         0 }
2764             push @pile, grep ref($_), @{ $this->{'_content'} || next };
2765             }
2766             return @errors;
2767             }
2768              
2769             ## _valid_name
2770             # validate XML style attribute names
2771 3679     3679   4026 # http://www.w3.org/TR/2006/REC-xml11-20060816/#NT-Name
2772 3679 50       5111  
2773             sub _valid_name {
2774             my $self = shift;
2775 3679 100       20660 my $attr = shift
2776             or Carp::croak("sub valid_name requires an attribute name");
2777 3678         9936  
2778             return (0) unless ( $attr =~ /^$START_CHAR$NAME_CHAR+$/ );
2779              
2780             return (1);
2781             }
2782 224 50   224 1 981  
2783              
2784             sub element_class {
2785             $_[0]->{_element_class} || __PACKAGE__;
2786             }
2787              
2788             1;
2789              
2790              
2791             1;
2792              
2793             __END__