File Coverage

blib/lib/HTML/TreeBuilder/LibXML/Node.pm
Criterion Covered Total %
statement 15 242 6.2
branch 0 116 0.0
condition 0 12 0.0
subroutine 5 50 10.0
pod 0 42 0.0
total 20 462 4.3


line stmt bran cond sub pod time code
1             package HTML::TreeBuilder::LibXML::Node;
2 21     21   100 use strict;
  21         23  
  21         592  
3 21     21   78 use warnings;
  21         24  
  21         422  
4 21     21   74 use Carp();
  21         24  
  21         35785  
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 0           my $doc = XML::LibXML::Document->new($origdoc->version, $origdoc->encoding);
161            
162 0 0         if ($node->isa('XML::LibXML::Dtd')) {
    0          
163 0           $doc->createInternalSubset( $node->getName, $node->publicId, $node->systemId );
164 0           $node = $doc->internalSubset;
165             } elsif ($node->isa('XML::LibXML::Element')) {
166 0           $doc->setDocumentElement($node);
167             } else {
168 0           $doc->adoptNode($node);
169             }
170            
171 0           my $cloned = __PACKAGE__->new($node);
172 0           return $cloned;
173             }
174              
175             sub clone_list {
176 0     0 0   my $class = shift;
177 0           my @clones = map { $_->clone } @_;
  0            
178 0           @clones;
179             }
180              
181             sub detach {
182 0     0 0   my $self = shift;
183 0           my $parent = $self->parent;
184             #$self->{node}->unbindNode();
185 0           my $doc = XML::LibXML->createDocument;
186 0           $doc->adoptNode($self->{node});
187 0           $doc->setDocumentElement($self->{node});
188 0           $parent;
189             }
190              
191             sub delete {
192 0     0 0   my $self = shift;
193 0           $self->{node}->unbindNode();
194             }
195              
196             sub delete_content {
197 0     0 0   my ($self) = @_;
198 0           $self->{node}->removeChildNodes;
199             }
200              
201             sub getFirstChild {
202 0     0 0   my $self = shift;
203 0           __PACKAGE__->new($self->{node}->getFirstChild);
204             }
205              
206             sub childNodes {
207 0     0 0   my $self = shift;
208              
209 0 0         $self->_eof_or_die unless $self->{node};
210 0           my @nodes = $self->{node}->childNodes;
211 0           @nodes = map { __PACKAGE__->new($_) } @nodes;
  0            
212 0 0         wantarray ? @nodes : \@nodes;
213             }
214              
215             sub content_list {
216 0     0 0   my ($self) = @_;
217 0           my @nodes = $self->childNodes;
218 0           @nodes;
219             }
220              
221             sub replace_with {
222 0     0 0   my $self = shift;
223            
224             # TODO handle @_ == 0
225            
226 0           my $node = $self->{node};
227 0           my $doc = $node->ownerDocument;
228 0           my $parent = $node->parentNode;
229 0 0         die "can't replace_with(), node has no parent!" unless $parent;
230            
231 0 0         my @nodes = map { ref $_ ? $_->{node} : $doc->createTextNode($_) } @_;
  0            
232            
233 0 0         if ($parent->isa('XML::LibXML::Document')) {
234             # can't call insertBefore() in a document node,
235             # so this is the best hack so far :[
236             # works only if $node is the last child
237 0 0         die "[not supported] calling replace_with() in a node that is child of a document node, and its not the last child."
238             unless $node->isSameNode($parent->lastChild);
239            
240 0           foreach (@nodes) {
241            
242 0 0         if ($_->isa('XML::LibXML::Dtd')) {
243 0           $parent->createInternalSubset($_->getName, $_->publicId, $_->systemId);
244 0           next;
245             }
246 0           $parent->adoptNode($_);
247 0           $node->addSibling($_);
248             }
249            
250             }
251             else {
252             $parent->insertBefore($_, $node)
253 0           for @nodes;
254             }
255            
256 0           $self->detach;
257 0           $self;
258             }
259              
260             sub push_content {
261 0     0 0   my $self = shift;
262            
263 0           my $node = $self->{node};
264 0 0         my $doc = $node->isa('XML::LibXML::Document') ? $node : $node->ownerDocument;
265 0 0         my @nodes = map { ref $_ ? $_->{node} : $doc->createTextNode($_) } @_;
  0            
266            
267             # thats because appendChild() is not supported on a Document node (as of XML::LibXML 2.0017)
268 0 0         if ($node->isa('XML::LibXML::Document')) {
269            
270 0           foreach (@nodes) {
271             #$node->adoptNode($_);
272 0 0         $node->hasChildNodes ? $node->lastChild->addSibling($_)
273             : $node->setDocumentElement($_);
274             }
275             }
276             else {
277 0           $node->appendChild($_) for @nodes;
278             }
279            
280 0           $self;
281             }
282              
283             sub unshift_content {
284 0     0 0   my $self = shift;
285            
286 0 0         return $self->push_content(@_)
287             unless $self->{node}->hasChildNodes;
288              
289 0           my $node = $self->{node};
290 0 0         my $doc = $node->isa('XML::LibXML::Document') ? $node : $node->ownerDocument;
291 0 0         my @nodes = map { ref $_ ? $_->{node} : $doc->createTextNode($_) } @_;
  0            
292            
293             # thats because insertBefore() is not supported on a Document node (as of XML::LibXML 2.0017)
294 0 0         if ($node->isa('XML::LibXML::Document')) {
295            
296 0           foreach (@nodes) {
297 0 0         $node->hasChildNodes ? $node->lastChild->addSibling($_)
298             : $node->setDocumentElement($_);
299             }
300            
301             # rotate
302 0           while (not $node->firstChild->isSameNode($nodes[0])) {
303 0           my $first_node = $node->firstChild;
304 0           $first_node->unbindNode;
305 0           $node->lastChild->addSibling($first_node);
306            
307             }
308             }
309             else {
310 0           my $first_child = $node->firstChild;
311 0           $node->insertBefore($_, $first_child) for @nodes;
312             }
313            
314 0           $self;
315             }
316              
317             sub left {
318 0     0 0   my $self = shift;
319              
320 0 0         $self->_eof_or_die unless $self->{node};
321 0           my $prev = $self->{node}->previousNonBlankSibling;
322 0 0         return $prev ? __PACKAGE__->new( $prev ) : undef;
323             }
324              
325             sub right {
326 0     0 0   my $self = shift;
327              
328 0 0         $self->_eof_or_die unless $self->{node};
329 0           my $next = $self->{node}->nextNonBlankSibling;
330 0 0         return $next ? __PACKAGE__->new( $next ) : undef;
331             }
332              
333             sub look_down {
334 0     0 0   my $self = shift;
335 0           my @args = @_;
336              
337 0 0         $self->_eof_or_die unless $self->{node};
338              
339 0           my @filter;
340 0           my $xpath = "//*"; # default
341 0           while (@args) {
342 0 0 0       if (ref $args[0] eq 'CODE') {
    0          
    0          
343 0           my $code = shift @args;
344 0           push @filter, $code;
345             } elsif (@args >= 2 && $args[0] eq '_tag') {
346 0           my($tag, $want_tag) = splice(@args, 0, 2);
347 0           $xpath = "//$want_tag";
348             } elsif (@args >= 2) {
349 0           my($attr, $stuff) = splice(@args, 0, 2);
350 0 0         if (ref $stuff eq 'Regexp') {
351 21     21   115 push @filter, sub { no warnings 'uninitialized'; $_[0]->attr($attr) =~ $stuff };
  21     0   36  
  21         1223  
  0            
  0            
352             } else {
353 21     21   89 push @filter, sub { no warnings 'uninitialized'; $_[0]->attr($attr) eq $stuff };
  21     0   40  
  21         12945  
  0            
  0            
354             }
355             } else {
356 0           Carp::carp("Don't know what to do with @args");
357 0           shift @args;
358             }
359             }
360              
361 0           $xpath =~ s/~text\b/text()/g;
362              
363 0           my @nodes = $self->findnodes($xpath);
364 0           my @wants = grep {
365 0           my $node = $_;
366 0           my $ok = 1;
367 0           for my $filter (@filter) {
368 0 0         $filter->($_) or $ok = 0;
369             }
370 0 0         $ok ? $node : ();
371             } @nodes;
372              
373 0 0         wantarray ? @wants : $wants[0];
374             }
375              
376             sub all_attr {
377 0     0 0   my $self = shift;
378 0           return map { $_->name => $_->value } $self->{node}->attributes;
  0            
379             }
380              
381             sub all_attr_names {
382 0     0 0   my $self = shift;
383 0           return map $_->name, $self->{node}->attributes;
384             }
385              
386 0     0 0   sub all_external_attr { shift->all_attr(@_) }
387 0     0 0   sub all_external_attr_names { shift->all_attr_names(@_) }
388              
389             sub _eof_or_die {
390 0     0     my $self = shift;
391 0 0         if (defined($self->{_content})) {
392 0           $self->eof;
393             } else {
394 0           Carp::croak "\$self is not loaded: $self"
395             }
396             }
397              
398              
399             sub matches {
400 0     0 0   my ($self, $xpath) = @_;
401            
402 0           foreach ($self->{node}->ownerDocument->findnodes($xpath)) {
403 0 0         return 1 if $_->isEqual($self->{node});
404             }
405            
406 0           return;
407             }
408              
409             sub parent {
410 0     0 0   my $self = shift;
411            
412 0 0         if (@_) {
413            
414             # unset
415 0 0         unless (defined $_[0]) {
416 0           $self->{node}->unbindNode;
417 0           return;
418             }
419            
420             # set
421 0 0         Carp::croak "an element can't be made its own parent"
422             if ref $_[0]->{node}->isSameNode($self->{node}); # sanity
423            
424 0           my $parent = $_[0]->{node};
425            
426 0 0         if ($_[0]->{node}->isa('XML::LibXML::Document')) {
427              
428 0 0         if ($parent->hasChildNodes) {
429 0           $parent->lastChild->addSibling($self->{node});
430             }
431             else {
432 0           $parent->adoptNode($self->{node});
433 0           $parent->setDocumentElement($self->{node});
434             }
435            
436             }
437             else {
438 0           $parent->appendChild($self->{node});
439             }
440            
441             }
442             else {
443             # get
444 0           my $parent = $self->{node}->parentNode;
445 0 0 0       return defined $parent && !$parent->isa('XML::LibXML::DocumentFragment')? ref($self)->new($parent) : undef;
446             }
447              
448             }
449              
450             sub postinsert {
451 0     0 0   my $self = shift;
452 0           my @nodes = map { $_->{node} } @_;
  0            
453 0           my $parent = $self->{node}->parentNode;
454            
455             $parent->insertAfter($_, $self->{node})
456 0           foreach reverse @nodes;
457            
458 0           $self;
459             }
460              
461             sub preinsert {
462 0     0 0   my $self = shift;
463 0           my @nodes = map { $_->{node} } @_;
  0            
464 0           my $parent = $self->{node}->parentNode;
465            
466             $parent->insertBefore($_, $self->{node})
467 0           foreach @nodes;
468            
469 0           $self;
470             }
471              
472              
473              
474             1;
475              
476             __END__