File Coverage

blib/lib/HTML/TreeBuilder/LibXML/Node.pm
Criterion Covered Total %
statement 18 246 7.3
branch 0 116 0.0
condition 0 12 0.0
subroutine 6 51 11.7
pod 0 42 0.0
total 24 467 5.1


line stmt bran cond sub pod time code
1             package HTML::TreeBuilder::LibXML::Node;
2 21     21   89 use strict;
  21         21  
  21         510  
3 21     21   64 use warnings;
  21         29  
  21         349  
4 21     21   62 use Carp();
  21         27  
  21         15876  
5              
6             sub new {
7 0     0 0   my ($class, $node) = @_;
8 0 0         Carp::croak 'missing node' unless $node;
9 0           bless {node => $node}, $class;
10             }
11              
12             sub attr {
13 0     0 0   my ($self, $key, $value) = @_;
14 0 0 0       if (@_ == 3) {
    0          
15 0 0         if (defined $value) {
16 0           $self->{node}->setAttribute (lc $key, $value);
17             } else {
18 0           $self->{node}->removeAttribute(lc $key);
19             }
20             } elsif (@_ == 2 and lc $key eq 'text') {
21 0           return $self->{node}->textContent;
22             }
23 0           $self->{node}->getAttribute(lc $key);
24             }
25              
26             sub isTextNode {
27 0     0 0   my $self = shift;
28 0           $self->{node}->isa('XML::LibXML::Text');
29             }
30              
31             # The analog of HTML::TreeBuilder::XPath::getValue for comment nodes
32             *getValue = \&as_text;
33              
34             sub string_value {
35 0     0 0   $_[0]->{node}->textContent;
36             }
37              
38             sub as_text {
39 0     0 0   $_[0]->{node}->textContent;
40             }
41              
42             sub as_trimmed_text {
43 0     0 0   my $text = shift->as_text(@_);
44 0           $text =~ s/[\n\r\f\t ]+$//s;
45 0           $text =~ s/^[\n\r\f\t ]+//s;
46 0           $text =~ s/[\n\r\f\t ]+/ /g;
47 0           return $text;
48             }
49 0     0 0   sub as_text_trimmed { shift->as_trimmed_text(@_) } # alias
50              
51 0     0 0   sub objectify_text { }
52 0     0 0   sub deobjectify_text { }
53              
54             sub as_XML {
55 0     0 0   $_[0]->{node}->toString;
56             }
57              
58             sub as_HTML {
59 0 0   0 0   return $_[0]->{node}->toStringHTML if $_[0]->{node}->can('toStringHTML'); # best method, but only document nodes can toStringHTML()
60            
61             # second best is to call toStringC14N(1), which generates valid HTML (eg. no auto closed
),
62             # but dies on some cases with "Failed to convert doc to string in doc->toStringC14N" error.
63             # so we fallback to toString()
64             {
65 0           local $@; # protect existing $@
  0            
66 0           my $output = eval { $_[0]->{node}->toStringC14N(1) };
  0            
67 0 0         return $@ ? $_[0]->{node}->toString : $output;
68             }
69             }
70              
71             sub tag {
72 0     0 0   $_[0]->{node}->localname
73             }
74              
75             sub id {
76 0 0   0 0   if (@_==2) {
77             # setter
78 0 0         if (defined $_[1]) {
79 0           $_[0]->{node}->setAttribute('id', $_[1]);
80             } else {
81 0           $_[0]->{node}->removeAttribute('id');
82             }
83             } else {
84 0           $_[0]->{node}->getAttribute('id');
85             }
86             }
87              
88             # hack for Web::Scraper
89             sub isa {
90 0     0 0   my ($self, $klass) = @_;
91 0 0         $klass eq 'HTML::Element' ? 1 : UNIVERSAL::isa($self, $klass);
92             }
93              
94             sub exists {
95 0     0 0   my( $self , $xpath ) = @_;
96              
97 0 0         $self->_eof_or_die unless $self->{node};
98 0           my @nodes = $self->{node}->findnodes( $xpath );
99 0 0         return scalar( @nodes ) ? 1 : 0;
100             }
101              
102             sub find {
103 0     0 0   my( $self , $elem ) = @_;
104              
105 0 0         $self->_eof_or_die unless $self->{node};
106              
107 0           my @nodes = $self->{node}->getElementsByTagName( $elem );
108 0           @nodes = map { HTML::TreeBuilder::LibXML::Node->new( $_ ) } @nodes;
  0            
109              
110 0 0         wantarray ? @nodes : \@nodes;
111             }
112              
113             sub findnodes {
114 0     0 0   my ($self, $xpath) = @_;
115              
116 0 0         $self->_eof_or_die unless $self->{node};
117 0           my @nodes = $self->{node}->findnodes( $xpath );
118 0           @nodes = map { HTML::TreeBuilder::LibXML::Node->new($_) } @nodes;
  0            
119 0 0         wantarray ? @nodes : \@nodes;
120             }
121              
122             *findnodes_as_string = \&findvalue;
123             *findnodes_as_strings = \&findvalues;
124              
125             sub findnodes_filter {
126 0     0 0   my( $self , $xpath , $callback ) = @_;
127              
128 0 0 0       Carp::croak "Second argument must be coderef"
129             unless $callback and ref $callback eq 'CODE';
130              
131 0           my @nodes = $self->findnodes( $xpath );
132 0           @nodes = grep { $callback->($_) } @nodes;
  0            
133              
134 0 0         wantarray ? @nodes : \@nodes;
135             }
136              
137             sub findvalue {
138 0     0 0   my ($self, $xpath) = @_;
139              
140 0 0         $self->_eof_or_die unless $self->{node};
141 0           $self->{node}->findvalue( $xpath );
142             }
143              
144             sub findvalues {
145 0     0 0   my( $self , $xpath ) = @_;
146              
147 0 0         $self->_eof_or_die unless $self->{node};
148 0           my $nodes = $self->{node}->find( $xpath );
149 0           my @nodes = map { $_->textContent } $nodes->get_nodelist;
  0            
150 0 0         wantarray ? @nodes : \@nodes;
151             }
152              
153             sub clone {
154 0     0 0   my ($self, ) = @_;
155              
156 0           my $orignode = $self->{node};
157 0           my $origdoc = $orignode->ownerDocument;
158              
159 0           my $node = $orignode->cloneNode(1);
160              
161             # arguments can be undefined
162 0           my $doc = do {
163 21     21   87 no warnings;
  21         24  
  21         16770  
164 0           XML::LibXML::Document->new($origdoc->version, $origdoc->encoding);
165             };
166            
167 0 0         if ($node->isa('XML::LibXML::Dtd')) {
    0          
168 0           $doc->createInternalSubset( $node->getName, $node->publicId, $node->systemId );
169 0           $node = $doc->internalSubset;
170             } elsif ($node->isa('XML::LibXML::Element')) {
171 0           $doc->setDocumentElement($node);
172             } else {
173 0           $doc->adoptNode($node);
174             }
175            
176 0           my $cloned = __PACKAGE__->new($node);
177 0           return $cloned;
178             }
179              
180             sub clone_list {
181 0     0 0   my $class = shift;
182 0           my @clones = map { $_->clone } @_;
  0            
183 0           @clones;
184             }
185              
186             sub detach {
187 0     0 0   my $self = shift;
188 0           my $parent = $self->parent;
189             #$self->{node}->unbindNode();
190 0           my $doc = XML::LibXML->createDocument;
191 0           $doc->adoptNode($self->{node});
192 0           $doc->setDocumentElement($self->{node});
193 0           $parent;
194             }
195              
196             sub delete {
197 0     0 0   my $self = shift;
198 0           $self->{node}->unbindNode();
199             }
200              
201             sub delete_content {
202 0     0 0   my ($self) = @_;
203 0           $self->{node}->removeChildNodes;
204             }
205              
206             sub getFirstChild {
207 0     0 0   my $self = shift;
208 0           __PACKAGE__->new($self->{node}->getFirstChild);
209             }
210              
211             sub childNodes {
212 0     0 0   my $self = shift;
213              
214 0 0         $self->_eof_or_die unless $self->{node};
215 0           my @nodes = $self->{node}->childNodes;
216 0           @nodes = map { __PACKAGE__->new($_) } @nodes;
  0            
217 0 0         wantarray ? @nodes : \@nodes;
218             }
219              
220             sub content_list {
221 0     0 0   my ($self) = @_;
222 0           my @nodes = $self->childNodes;
223 0           @nodes;
224             }
225              
226             sub replace_with {
227 0     0 0   my $self = shift;
228            
229             # TODO handle @_ == 0
230            
231 0           my $node = $self->{node};
232 0           my $doc = $node->ownerDocument;
233 0           my $parent = $node->parentNode;
234 0 0         die "can't replace_with(), node has no parent!" unless $parent;
235            
236 0 0         my @nodes = map { ref $_ ? $_->{node} : $doc->createTextNode($_) } @_;
  0            
237            
238 0 0         if ($parent->isa('XML::LibXML::Document')) {
239             # can't call insertBefore() in a document node,
240             # so this is the best hack so far :[
241             # works only if $node is the last child
242 0 0         die "[not supported] calling replace_with() in a node that is child of a document node, and its not the last child."
243             unless $node->isSameNode($parent->lastChild);
244            
245 0           foreach (@nodes) {
246            
247 0 0         if ($_->isa('XML::LibXML::Dtd')) {
248 0           $parent->createInternalSubset($_->getName, $_->publicId, $_->systemId);
249 0           next;
250             }
251 0           $parent->adoptNode($_);
252 0           $node->addSibling($_);
253             }
254            
255             }
256             else {
257             $parent->insertBefore($_, $node)
258 0           for @nodes;
259             }
260            
261 0           $self->detach;
262 0           $self;
263             }
264              
265             sub push_content {
266 0     0 0   my $self = shift;
267            
268 0           my $node = $self->{node};
269 0 0         my $doc = $node->isa('XML::LibXML::Document') ? $node : $node->ownerDocument;
270 0 0         my @nodes = map { ref $_ ? $_->{node} : $doc->createTextNode($_) } @_;
  0            
271            
272             # thats because appendChild() is not supported on a Document node (as of XML::LibXML 2.0017)
273 0 0         if ($node->isa('XML::LibXML::Document')) {
274            
275 0           foreach (@nodes) {
276             #$node->adoptNode($_);
277 0 0         $node->hasChildNodes ? $node->lastChild->addSibling($_)
278             : $node->setDocumentElement($_);
279             }
280             }
281             else {
282 0           $node->appendChild($_) for @nodes;
283             }
284            
285 0           $self;
286             }
287              
288             sub unshift_content {
289 0     0 0   my $self = shift;
290            
291 0 0         return $self->push_content(@_)
292             unless $self->{node}->hasChildNodes;
293              
294 0           my $node = $self->{node};
295 0 0         my $doc = $node->isa('XML::LibXML::Document') ? $node : $node->ownerDocument;
296 0 0         my @nodes = map { ref $_ ? $_->{node} : $doc->createTextNode($_) } @_;
  0            
297            
298             # thats because insertBefore() is not supported on a Document node (as of XML::LibXML 2.0017)
299 0 0         if ($node->isa('XML::LibXML::Document')) {
300            
301 0           foreach (@nodes) {
302 0 0         $node->hasChildNodes ? $node->lastChild->addSibling($_)
303             : $node->setDocumentElement($_);
304             }
305            
306             # rotate
307 0           while (not $node->firstChild->isSameNode($nodes[0])) {
308 0           my $first_node = $node->firstChild;
309 0           $first_node->unbindNode;
310 0           $node->lastChild->addSibling($first_node);
311            
312             }
313             }
314             else {
315 0           my $first_child = $node->firstChild;
316 0           $node->insertBefore($_, $first_child) for @nodes;
317             }
318            
319 0           $self;
320             }
321              
322             sub left {
323 0     0 0   my $self = shift;
324              
325 0 0         $self->_eof_or_die unless $self->{node};
326 0           my $prev = $self->{node}->previousNonBlankSibling;
327 0 0         return $prev ? __PACKAGE__->new( $prev ) : undef;
328             }
329              
330             sub right {
331 0     0 0   my $self = shift;
332              
333 0 0         $self->_eof_or_die unless $self->{node};
334 0           my $next = $self->{node}->nextNonBlankSibling;
335 0 0         return $next ? __PACKAGE__->new( $next ) : undef;
336             }
337              
338             sub look_down {
339 0     0 0   my $self = shift;
340 0           my @args = @_;
341              
342 0 0         $self->_eof_or_die unless $self->{node};
343              
344 0           my @filter;
345 0           my $xpath = "//*"; # default
346 0           while (@args) {
347 0 0 0       if (ref $args[0] eq 'CODE') {
    0          
    0          
348 0           my $code = shift @args;
349 0           push @filter, $code;
350             } elsif (@args >= 2 && $args[0] eq '_tag') {
351 0           my($tag, $want_tag) = splice(@args, 0, 2);
352 0           $xpath = "//$want_tag";
353             } elsif (@args >= 2) {
354 0           my($attr, $stuff) = splice(@args, 0, 2);
355 0 0         if (ref $stuff eq 'Regexp') {
356 21     21   101 push @filter, sub { no warnings 'uninitialized'; $_[0]->attr($attr) =~ $stuff };
  21     0   25  
  21         1089  
  0            
  0            
357             } else {
358 21     21   250 push @filter, sub { no warnings 'uninitialized'; $_[0]->attr($attr) eq $stuff };
  21     0   29  
  21         11936  
  0            
  0            
359             }
360             } else {
361 0           Carp::carp("Don't know what to do with @args");
362 0           shift @args;
363             }
364             }
365              
366 0           $xpath =~ s/~text\b/text()/g;
367              
368 0           my @nodes = $self->findnodes($xpath);
369 0           my @wants = grep {
370 0           my $node = $_;
371 0           my $ok = 1;
372 0           for my $filter (@filter) {
373 0 0         $filter->($_) or $ok = 0;
374             }
375 0 0         $ok ? $node : ();
376             } @nodes;
377              
378 0 0         wantarray ? @wants : $wants[0];
379             }
380              
381             sub all_attr {
382 0     0 0   my $self = shift;
383 0           return map { $_->name => $_->value } $self->{node}->attributes;
  0            
384             }
385              
386             sub all_attr_names {
387 0     0 0   my $self = shift;
388 0           return map $_->name, $self->{node}->attributes;
389             }
390              
391 0     0 0   sub all_external_attr { shift->all_attr(@_) }
392 0     0 0   sub all_external_attr_names { shift->all_attr_names(@_) }
393              
394             sub _eof_or_die {
395 0     0     my $self = shift;
396 0 0         if (defined($self->{_content})) {
397 0           $self->eof;
398             } else {
399 0           Carp::croak "\$self is not loaded: $self"
400             }
401             }
402              
403              
404             sub matches {
405 0     0 0   my ($self, $xpath) = @_;
406            
407 0           foreach ($self->{node}->ownerDocument->findnodes($xpath)) {
408 0 0         return 1 if $_->isEqual($self->{node});
409             }
410            
411 0           return;
412             }
413              
414             sub parent {
415 0     0 0   my $self = shift;
416            
417 0 0         if (@_) {
418            
419             # unset
420 0 0         unless (defined $_[0]) {
421 0           $self->{node}->unbindNode;
422 0           return;
423             }
424            
425             # set
426 0 0         Carp::croak "an element can't be made its own parent"
427             if ref $_[0]->{node}->isSameNode($self->{node}); # sanity
428            
429 0           my $parent = $_[0]->{node};
430            
431 0 0         if ($_[0]->{node}->isa('XML::LibXML::Document')) {
432              
433 0 0         if ($parent->hasChildNodes) {
434 0           $parent->lastChild->addSibling($self->{node});
435             }
436             else {
437 0           $parent->adoptNode($self->{node});
438 0           $parent->setDocumentElement($self->{node});
439             }
440            
441             }
442             else {
443 0           $parent->appendChild($self->{node});
444             }
445            
446             }
447             else {
448             # get
449 0           my $parent = $self->{node}->parentNode;
450 0 0 0       return defined $parent && !$parent->isa('XML::LibXML::DocumentFragment')? ref($self)->new($parent) : undef;
451             }
452              
453             }
454              
455             sub postinsert {
456 0     0 0   my $self = shift;
457 0           my @nodes = map { $_->{node} } @_;
  0            
458 0           my $parent = $self->{node}->parentNode;
459            
460             $parent->insertAfter($_, $self->{node})
461 0           foreach reverse @nodes;
462            
463 0           $self;
464             }
465              
466             sub preinsert {
467 0     0 0   my $self = shift;
468 0           my @nodes = map { $_->{node} } @_;
  0            
469 0           my $parent = $self->{node}->parentNode;
470            
471             $parent->insertBefore($_, $self->{node})
472 0           foreach @nodes;
473            
474 0           $self;
475             }
476              
477              
478              
479             1;
480              
481             __END__