File Coverage

blib/lib/HTML/Element.pm
Criterion Covered Total %
statement 695 1061 65.5
branch 393 784 50.1
condition 210 426 49.3
subroutine 84 127 66.1
pod 89 90 98.8
total 1471 2488 59.1


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