File Coverage

blib/lib/HTML/Element.pm
Criterion Covered Total %
statement 703 1066 65.9
branch 396 784 50.5
condition 212 435 48.7
subroutine 86 128 67.1
pod 89 90 98.8
total 1486 2503 59.3


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