File Coverage

blib/lib/Web/Query.pm
Criterion Covered Total %
statement 427 431 99.0
branch 126 138 91.3
condition 36 83 43.3
subroutine 75 75 100.0
pod 47 49 95.9
total 711 776 91.6


line stmt bran cond sub pod time code
1             package Web::Query;
2             our $AUTHORITY = 'cpan:TOKUHIROM';
3             # ABSTRACT: Yet another scraping library like jQuery
4             $Web::Query::VERSION = '0.39';
5 46     46   3474439 use strict;
  46         401  
  46         1376  
6 46     46   245 use warnings;
  46         94  
  46         1106  
7 46     46   1233 use 5.008001;
  46         166  
8 46     46   19641 use parent qw/Exporter/;
  46         12744  
  46         255  
9 46     46   27343 use HTML::TreeBuilder::XPath;
  46         3069708  
  46         404  
10 46     46   32264 use LWP::UserAgent;
  46         2051192  
  46         2147  
11 46     46   23392 use HTML::Selector::XPath 0.20 qw/selector_to_xpath/;
  46         125334  
  46         3187  
12 46     46   386 use Scalar::Util qw/blessed refaddr/;
  46         121  
  46         2793  
13 46     46   316 use HTML::Entities qw/encode_entities/;
  46         129  
  46         3236  
14              
15 46     46   344 use List::Util 1.44 qw/ reduce uniq /;
  46         725  
  46         3159  
16 46     46   341 use Scalar::Util qw/ refaddr /;
  46         146  
  46         86639  
17              
18             our @EXPORT = qw/wq/;
19              
20             our $RESPONSE;
21              
22 90     90 1 130719 sub wq { Web::Query->new(@_) }
23              
24             our $UserAgent = LWP::UserAgent->new();
25              
26             sub __ua {
27 8   33 8   37     $UserAgent ||= LWP::UserAgent->new( agent => __PACKAGE__ . "/" . __PACKAGE__->VERSION );
28 8         44     $UserAgent;
29             }
30              
31             sub _build_tree {
32 122     122   274     my( $self, $options ) = @_;
33              
34                 my $no_space_compacting = ref $self ? $self->{no_space_compacting}
35 122 100       518     : ref $options eq 'HASH' ? $options->{no_space_compacting} : 0;
    100          
36              
37 122         859     my $tree = HTML::TreeBuilder::XPath->new(
38                     no_space_compacting => $no_space_compacting
39                 );
40 122         35772     $tree->ignore_unknown(0);
41 122         3140     $tree->store_comments(1);
42 122         1122     $tree;
43             }
44              
45             sub new {
46 477     477 1 68868     my ($class, $stuff, $options) = @_;
47              
48 477 100       1232     my $self = $class->_resolve_new($stuff,$options)
49                     or return undef;
50              
51 474 100       2211     $self->{indent} = $options->{indent} if $options->{indent};
52              
53 474         902     $self->{no_space_compacting} = $options->{no_space_compacting};
54              
55 474         1614     return $self;
56             }
57              
58             sub _resolve_new {
59 477     477   1042     my( $class, $stuff, $options) = @_;
60              
61 477 100       1198     return $class->new_from_element([],undef,$options) unless defined $stuff;
62              
63 475 100       1582     if (blessed $stuff) {
64 268 100       1057         return $class->new_from_element([$stuff],undef,$options)
65                         if $stuff->isa('HTML::Element');
66              
67 119 100       376         return $class->new_from_url($stuff->as_string,$options)
68                         if $stuff->isa('URI');
69              
70 117 50       363         return $class->new_from_element($stuff->{trees}, undef, $options)
71                         if $stuff->isa($class);
72              
73 0         0         die "Unknown source type: $stuff";
74                 }
75              
76 207 100       574     return $class->new_from_element($stuff,undef,$options) if ref $stuff eq 'ARRAY';
77              
78 205 100       793     return $class->new_from_url($stuff,$options) if $stuff =~ m{^(?:https?|file)://};
79              
80 199 100       1511     return $class->new_from_html($stuff,$options) if $stuff =~ /<.*?>/;
81              
82 18 50 33     552     return $class->new_from_file($stuff,$options) if $stuff !~ /\n/ && -f $stuff;
83              
84 0         0     die "Unknown source type: $stuff";
85             }
86              
87             sub new_from_url {
88 8     8 1 36     my ($class, $url,$options) = @_;
89              
90 8         37     $RESPONSE = __ua()->get($url);
91              
92 8 100       72563     return undef unless $RESPONSE->is_success;
93              
94 5         75     return $class->new_from_html($RESPONSE->decoded_content,$options);
95             }
96              
97             sub new_from_file {
98 18     18 1 63     my ($class, $fname, $options) = @_;
99 18         57     my $tree = $class->_build_tree($options);
100 18         68     $tree->parse_file($fname);
101 18         29595     my $self = $class->new_from_element([$tree->disembowel],undef,$options);
102 18         74     $self->{need_delete}++;
103 18         93     return $self;
104             }
105              
106             sub new_from_html {
107 218     218 1 46873     my ($class, $html,$options) = @_;
108 218         661     my $tree = $class->_build_tree($options);
109 218         1290     $tree->parse_content($html);
110                 my $self = $class->new_from_element([
111                         map {
112 218 100       173681                 ref $_ ? $_ : bless { _content => $_ }, 'HTML::TreeBuilder::XPath::TextNode'
  237         18118  
113                         } $tree->disembowel
114                 ],undef,$options);
115 218         934     $self->{need_delete}++;
116 218         2112     return $self;
117             }
118              
119             sub new_from_element {
120 1268     1268 1 3925     my $self_or_class = shift;
121              
122 1268 100       3032     my $trees = ref $_[0] eq 'ARRAY' ? $_[0] : [$_[0]];
123              
124 1268   66     9572     return bless { trees => [ @$trees ], before => $_[1] },
125                     ref $self_or_class || $self_or_class;
126             }
127              
128             sub end {
129 108     108 1 362     my $self = shift;
130 108         263     return $self->{before};
131             }
132              
133             sub size {
134 142     142 1 7320     my $self = shift;
135 142         198     return scalar(@{$self->{trees}});
  142         551  
136             }
137              
138             sub parent {
139 4     4 1 8     my $self = shift;
140              
141 4         10     my @new = map { $_->parent } @{$self->{trees}};
  4         13  
  4         8  
142              
143 4   33     85     return (ref $self || $self)->new_from_element(\@new, $self);
144             }
145              
146             sub first {
147 12     12 1 38     my $self = shift;
148 12         42     return $self->eq(0);
149             }
150              
151             sub last {
152 6     6 1 27     my $self = shift;
153 6         17     return $self->eq(-1);
154             }
155              
156             sub get {
157 89     89 1 214     my ($self, $index) = @_;
158 89         274     return $self->{trees}[$index];
159             }
160              
161             sub eq {
162 26     26 0 59     my ($self, $index) = @_;
163 26   33     145     return (ref $self || $self)->new_from_element([$self->{trees}[$index] || ()], $self);
      66        
164             }
165              
166             sub find {
167 295     295 1 26566     my ($self, $selector) = @_;
168                 
169 295 100       1236     my $xpath = ref $selector ? $$selector : selector_to_xpath($selector, root => './');
170 295         30370     my @new = map { eval{ $_->findnodes($xpath) } } @{$self->{trees}};
  311         28210  
  311         1182  
  295         824  
171                 
172 295   33     525985     return (ref $self || $self)->new_from_element(\@new, $self);
173             }
174              
175             sub contents {
176 20     20 1 70     my ($self, $selector) = @_;
177              
178 20         29     my @new = map { $_->content_list } @{$self->{trees}};
  28         290  
  20         45  
179              
180 20 100       1917     if ($selector) {
181 2 50       21         my $xpath = ref $selector ? $$selector : selector_to_xpath($selector);
182 2         280         @new = grep { $_->matches($xpath) } @new;
  6         2934  
183                 }
184              
185 20   33     524     return (ref $self || $self)->new_from_element(\@new, $self);
186             }
187              
188             sub as_html {
189 105     105 1 6053     my $self = shift;
190 105         250     my %args = @_;
191              
192                 my @html = map {
193                     ref $_ ? ( $_->isa('HTML::TreeBuilder::XPath::TextNode') || $_->isa('HTML::TreeBuilder::XPath::CommentNode' ) )
194                                     ? $_->getValue
195                                     : $_->as_HTML( q{&<>'"}, $self->{indent}, {} )
196 148 100 66     9957                : $_
    100          
197 105         167     } @{$self->{trees}};
  105         257  
198              
199 105 100       27808     return join $args{join}, @html if defined $args{join};
200              
201 85 100       688     return wantarray ? @html : $html[0];
202             }
203              
204             sub html {
205 48     48 1 165     my $self = shift;
206              
207 48 100       125     if (@_) {
208                     map {
209 4         20             $_->delete_content;
210 4         59             my $tree = $self->_build_tree;
211                         
212 4         25             $tree->parse_content($_[0]);
213 4         1364             $_->push_content($tree->disembowel);
214 4         8         } @{$self->{trees}};
  4         15  
215 4         510         return $self;
216                 }
217              
218 44         74     my @html;
219 44         73     for my $t ( @{$self->{trees}} ) {
  44         111  
220                     push @html, join '', map {
221                         ref $_ ? $_->as_HTML( q{&<>'"}, $self->{indent}, {})
222 66 100       6782                    : encode_entities($_)
223 44         74         } eval { $t->content_list };
  44         136  
224                 }
225                 
226 44 100       12214     return wantarray ? @html : $html[0];
227             }
228              
229             sub text {
230 88     88 1 2188     my $self = shift;
231              
232 88 100       210     if (@_) {
233 2         4         map { $_->delete_content; $_->push_content($_[0]) } @{$self->{trees}};
  2         8  
  2         28  
  2         5  
234 2         61         return $self;
235                 }
236              
237                 my @html = map {
238 86 100       376         ref $_ ? $_->as_text : $_
239 86         131     } @{$self->{trees}};
  86         181  
240 86 100       3133     return wantarray ? @html : $html[0];
241             }
242              
243             sub attr {
244 100     100 1 11936     my $self = shift;
245              
246 100 100       252     if ( @_ == 1 ) { # getter
247                     return wantarray
248 16         155             ? map { $_->attr(@_) } @{$self->{trees}}
  10         26  
249 66 100       153             : eval { $self->{trees}[0]->attr(@_) }
  56         197  
250                         ;
251                 }
252              
253 34         124     while( my( $attr, $value ) = splice @_, 0, 2 ) {
254 36 100       156         my $code = ref $value eq 'CODE' ? $value : undef;
255              
256 36         49         for my $t ( @{$self->{trees}} ) {
  36         80  
257 42 100       214             if ( $code ) {
258 46     46   417                 no warnings 'uninitialized';
  46         126  
  46         54115  
259 4         16                 my $orig = $_ = $t->attr($attr);
260 4         63                 $code->();
261 4 100       23                 next if $orig eq $_;
262 2         4                 $value = $_;
263                         }
264 40         110             $t->attr($attr => $value);
265                     }
266                 }
267              
268 34         1810     return $self;
269             }
270              
271             sub id {
272 20     20 1 40     my $self = shift;
273              
274 20 100       43     if ( @_ ) { # setter
275 8         17         my $new_id = shift;
276              
277 8 50       23         return $self if $self->size == 0;
278              
279                     return $self->each(sub{
280 6     6   19             $_->attr( id => $new_id->(@_) )
281 8 100       45         }) if ref $new_id eq 'CODE';
282              
283 6 100       14         if ( $self->size == 1 ) {
284 2         9             $self->attr( id => $new_id );
285                     }
286                     else {
287 4         16             return $self->first->attr( id => $new_id );
288                     }
289                 }
290                 else { # getter
291              
292             # the eval is there in case there is no tree
293                     return wantarray
294 8         65             ? map { $_->attr('id') } @{$self->{trees}}
  4         13  
295 12 100       26             : eval { $self->{trees}[0]->attr('id') }
  8         25  
296                         ;
297                 }
298             }
299              
300             sub name {
301 12     12 1 23     my $self = shift;
302 12         26     $self->attr( 'name', @_ );
303             }
304              
305             sub data {
306 4     4 1 8     my $self = shift;
307 4         8     my $name = shift;
308 4         19     $self->attr( join( '-', 'data', $name ), @_ );
309             }
310              
311             sub tagname {
312 12     12 1 42     my $self = shift;
313 12 100       91     my @retval = map { $_ eq '~comment' ? '#comment' : $_ }
314 12 100       58                  map { ref $_ eq 'HTML::TreeBuilder::XPath::TextNode' ? '#text'
    50          
    100          
315                                  : ref $_ eq 'HTML::TreeBuilder::XPath::CommentNode' ? '#comment'
316                                  : ref $_ ? $_->tag(@_)
317                                  : '#text'
318                                  ;
319 12         21                 } @{$self->{trees}};
  12         24  
320 12 50       50     return wantarray ? @retval : $retval[0];
321             }
322              
323             sub each {
324 51     51 1 698     my ($self, $code) = @_;
325 51         85     my $i = 0;
326              
327             # make a copy such that if we modify the list via 'delete',
328             # it won't change from under our feet (see t/each-and-delete.t
329             # for a case where it can)
330 51         70     my @trees = @{ $self->{trees} };
  51         123  
331 51         127     for my $tree ( @trees ) {
332 140   33     545         local $_ = (ref $self || $self)->new_from_element([$tree], $self);
333 140         443         $code->($i++, $_);
334                 }
335 51         205     return $self;
336             }
337              
338             sub map {
339 87     87 1 221     my ($self, $code) = @_;
340 87         139     my $i = 0;
341                 return +[map {
342 125         185         my $tree = $_;
343 125   33     410         local $_ = (ref $self || $self)->new($tree);
344 125         294         $code->($i++, $_);
345 87         136     } @{$self->{trees}}];
  87         201  
346             }   
347              
348             sub filter {
349 64     64 1 3051     my $self = shift;
350              
351 64         87     my @new;
352              
353 64 100       161     if (ref($_[0]) eq 'CODE') {
354 48         74         my $code = $_[0];
355 48         70         my $i = 0;
356                     @new = grep {
357 78         116             my $tree = $_;
358 78   33     258             local $_ = (ref $self || $self)->new_from_element($tree);
359 78         192             $code->($i++, $_);
360 48         1122         } @{$self->{trees}};
  48         107  
361                 }
362                 else {
363 16 50       1085         my $xpath = ref $_[0] ? ${$_[0]} : selector_to_xpath($_[0]);
  0         0  
364 16         1611         @new = grep { eval { $_->matches($xpath) } } @{$self->{trees}};
  28         21502  
  28         106  
  16         45  
365                 }
366              
367 64   33     8986     return (ref $self || $self)->new_from_element(\@new, $self);
368             }
369              
370             sub _is_same_node {
371 22     22   84     refaddr($_[1]) == refaddr($_[2]);
372             }
373              
374             sub remove {
375 30     30 1 59     my $self = shift;
376 30         77     my $before = $self->end;
377                 
378 30         80     while (defined $before) {
379 28         61         @{$before->{trees}} = grep {
380 40         60             my $el = $_;
381 40         51             not grep { $self->_is_same_node( $el, $_ ) } @{$self->{trees}};
  44         81  
  40         62  
382 28         39         } @{$before->{trees}};
  28         55  
383              
384 28         53         $before = $before->end;
385                 }
386                 
387 30         44     $_->delete for @{$self->{trees}};
  30         106  
388 30         1817     @{$self->{trees}} = ();
  30         71  
389                 
390 30         100     $self;
391             }
392              
393             sub replace_with {
394 10     10 1 23     my ( $self, $replacement ) = @_;
395              
396 10         15     my $i = 0;
397 10         16     for my $node ( @{ $self->{trees} } ) {
  10         23  
398 18         121         my $rep = $replacement;
399              
400 18 100       42         if ( ref $rep eq 'CODE' ) {
401 8   33     27             local $_ = (ref $self || $self)->new($node);
402 8         22             $rep = $rep->( $i++ => $_ );
403                     }
404              
405 18 100 33     65         $rep = (ref $self || $self)->new_from_html( $rep )
406                         unless ref $rep;
407              
408              
409                         
410 18         111         my $r = $rep->{trees}->[0];
411 46     46   696         { no warnings;
  46         227  
  46         55845  
  18         1060  
412 18 100       66             $r = $r->clone if ref $r;
413                     }
414 18 100 66     625         $r->parent( $node->parent ) if ref $r and $node->parent;
415              
416 18         999         $node->replace_with( $r );
417                 }
418              
419 10 100 33     311     $replacement->remove if ref $replacement eq (ref $self || $self);
420              
421 10         34     return $self;
422             }
423              
424             sub append {
425 2     2 1 6     my ($self, $stuff) = @_;
426 2   33     9     $stuff = (ref $self || $self)->new($stuff);
427              
428 2         13     foreach my $t (@{$self->{trees}}) {
  2         11  
429 4         209         $t->push_content($_) for ref($t)->clone_list(@{$stuff->{trees}});
  4         16  
430                 }
431              
432 2         131     $self;
433             }
434              
435             sub prepend {
436 2     2 1 6     my ($self, $stuff) = @_;
437 2   33     9     $stuff = (ref $self || $self)->new($stuff);
438                 
439 2         5     foreach my $t (@{$self->{trees}}) {
  2         8  
440 4         216         $t->unshift_content($_) for ref($t)->clone_list(@{$stuff->{trees}});
  4         15  
441                 }
442                 
443 2         133     $self;
444             }
445              
446              
447             sub before {
448 2     2 1 7     my ($self, $stuff) = @_;
449 2   33     9     $stuff = (ref $self || $self)->new($stuff);
450                     
451 2         6     foreach my $t (@{$self->{trees}}) {
  2         6  
452 4         244         $t->preinsert(ref($t)->clone_list(@{$stuff->{trees}}));
  4         19  
453                 }
454                 
455 2         154     $self;
456             }
457              
458              
459             sub after {
460 2     2 1 6     my ($self, $stuff) = @_;
461 2   33     8     $stuff = (ref $self || $self)->new($stuff);
462                     
463 2         4     foreach my $t (@{$self->{trees}}) {
  2         5  
464 4         252         $t->postinsert(ref($t)->clone_list(@{$stuff->{trees}}));
  4         16  
465                 }
466                 
467 2         158     $self;
468             }
469              
470              
471             sub insert_before {
472 2     2 1 5     my ($self, $target) = @_;
473                     
474 2         3     foreach my $t (@{$target->{trees}}) {
  2         6  
475 4         234         $t->preinsert(ref($t)->clone_list(@{$self->{trees}}));
  4         14  
476                 }
477                 
478 2         145     $self;
479             }
480              
481             sub insert_after {
482 2     2 1 12     my ($self, $target) = @_;
483                     
484 2         4     foreach my $t (@{$target->{trees}}) {
  2         6  
485 4         251         $t->postinsert(ref($t)->clone_list(@{$self->{trees}}));
  4         13  
486                 }
487                 
488 2         153     $self;
489             }
490              
491             sub detach {
492 2     2 1 6     my ($self) = @_;
493 2         4     $_->detach for @{$self->{trees}};
  2         12  
494 2         221     $self;
495             }
496              
497             sub add_class {
498 16     16 1 37     my ($self, $class) = @_;
499                         
500 16         33     for (my $i = 0; $i < @{$self->{trees}}; $i++) {
  36         535  
501 20         35         my $t = $self->{trees}->[$i];
502 20   100     49         my $current_class = $t->attr('class') || '';
503                     
504 20 100       315         my $classes = ref $class eq 'CODE' ? $class->($i, $current_class, $t) : $class;
505 20         101         my @classes = split /\s+/, $classes;
506                     
507 20         41         foreach (@classes) {
508 32 100       537             $current_class .= " $_" unless $current_class =~ /(?:^|\s)$_(?:\s|$)/;
509                     }
510                                     
511 20         149         $current_class =~ s/(?:^\s*|\s*$)//g;
512 20         49         $current_class =~ s/\s\s+/ /g;
513                     
514 20         52         $t->attr('class', $current_class);
515                 }
516                 
517 16         44     $self;
518             }
519              
520              
521             sub remove_class {
522 16     16 1 36     my ($self, $class) = @_;
523              
524 16         33     for (my $i = 0; $i < @{$self->{trees}}; $i++) {
  36         474  
525 20         32         my $t = $self->{trees}->[$i];
526 20         49         my $current_class = $t->attr('class');
527 20 50       284         next unless defined $current_class;
528                     
529 20 100       58         my $classes = ref $class eq 'CODE' ? $class->($i, $current_class, $t) : $class;
530 20         78         my @remove_classes = split /\s+/, $classes;
531                     my @final = grep {
532 20         90             my $existing_class = $_;
  42         55  
533 42         51             not grep { $existing_class eq $_} @remove_classes;
  66         142  
534                     } split /\s+/, $current_class;
535                     
536 20         64         $t->attr('class', join ' ', @final);
537                 }
538                 
539 16         54     $self;
540                 
541             }
542              
543             sub toggle_class {
544 6     6 1 2692     my $self = shift;
545                 
546 6         34     my @classes = uniq @_;
547              
548                 $self->each(sub{
549 18     18   35         for my $class ( @classes ) {
550 24 100       46             my $method = $_->has_class($class) ? 'remove_class' : 'add_class';
551 24         1612             $_->$method($class);
552                     }
553 6         45     });
554             }
555              
556             sub has_class {
557 56     56 1 158     my ($self, $class) = @_;
558                 
559 56         70     foreach my $t (@{$self->{trees}}) {
  56         110  
560 46     46   423         no warnings 'uninitialized';
  46         111  
  46         58754  
561 58 100       209         return 1 if $t->attr('class') =~ /(?:^|\s)$class(?:\s|$)/;
562                 }
563                 
564 28         597     return undef;
565             }
566              
567             sub clone {
568 2     2 1 6     my ($self) = @_;
569 2         4     my @clones = map { $_->clone } @{$self->{trees}};
  2         19  
  2         4  
570 2   33     200     return (ref $self || $self)->new_from_element(\@clones);
571             }
572              
573             sub add {
574 77     77 1 170     my ($self, @stuff) = @_;
575 77         104     my @nodes;
576                 
577             # add(selector, context)
578 77 100 100     600     if (@stuff == 2 && !ref $stuff[0] && $stuff[1]->isa('HTML::Element')) {
      66        
579 2 50       17         my $xpath = ref $stuff[0] ? ${$stuff[0]} : selector_to_xpath($stuff[0]);
  0         0  
580 2         215         push @nodes, $stuff[1]->findnodes( $xpath, root => './');
581                 }
582                 else {
583             # handle any combination of html string, element object and web::query object
584                     push @nodes, map {
585 77 100       175             $self->{need_delete} = 1 if $_->{need_delete};
586 77         123             delete $_->{need_delete};
587 77         96             @{$_->{trees}};
  77         178  
588 75   33     135         } map { (ref $self || $self)->new($_) } @stuff;
  77         256  
589                 }
590              
591 77         7175     my %ids = map { $self->_node_id($_) => 1 } @{ $self->{trees} };
  120         256  
  77         182  
592              
593                 $self->new_from_element( [
594 77         152         @{$self->{trees}}, grep { ! $ids{ $self->_node_id($_) } } @nodes
  77         143  
  95         186  
595                 ], $self );
596             }
597              
598             sub _node_id {
599 76     76   117     my( undef, $node ) = @_;
600 76         1133     refaddr $node;
601             }
602              
603             sub prev {
604 1     1 1 3     my $self = shift;
605 1         2     my @new;
606 1         2     for my $tree (@{$self->{trees}}) {
  1         3  
607 2         51         push @new, $tree->getPreviousSibling;
608                 }
609 1   33     35     return (ref $self || $self)->new_from_element(\@new, $self);
610             }
611              
612             sub next {
613 22     22 1 35     my $self = shift;
614              
615 22         30     my @new = grep { $_ } map { $_->getNextSibling } @{ $self->{trees} };
  29         786  
  29         303  
  22         38  
616              
617 22   33     84     return (ref $self || $self)->new_from_element(\@new, $self);
618             }
619              
620             sub match {
621 73     73 1 124     my( $self, $selector ) = @_;
622              
623 73 50       210     my $xpath = ref $selector ? $$selector : selector_to_xpath($selector);
624              
625                 my $results = $self->map(sub{
626 81     81   148             my(undef,$e) = @_;
627 81 50       168         return 0 unless ref $e; # it's a string
628 81         167         return !!$e->get(0)->matches($xpath);
629 73         6347     });
630              
631 73 100       472     return wantarray ? @$results : $results->[0];
632             }
633              
634             sub not {
635 38     38 1 75     my( $self, $selector ) = @_;
636              
637 38         66     my $class = ref $self;
638              
639 38 50       115     my $xpath = ref $selector ? $$selector : selector_to_xpath($selector);
640 38     46   3297     $self->filter(sub { ! grep { $_->matches($xpath) } grep { ref $_ } $class->new($_)->{trees}[0] } );
  46         106  
  46         139  
  46         96  
641             }
642              
643             sub and_back {
644 2     2 1 32     my $self = shift;
645                 
646 2         13     $self->add( $self->end );
647             }
648              
649             sub next_until {
650 10     10 1 24     my( $self, $selector ) = @_;
651              
652 10         16     my $class = ref $self;
653 10         20     my $collection = $class->new_from_element([],$self);
654              
655 10         45     my $next = $self->next->not($selector);
656 10         62     while( $next->size ) {
657 26         57        $collection = $collection->add($next);
658 26         68        $next = $next->next->not( $selector );
659                 }
660              
661             # hide the loop from the outside world
662 10         25     $collection->{before} = $self;
663              
664 10         18     return $collection;
665             }
666              
667             sub split {
668 6     6 0 21     my( $self, $selector, %args ) = @_;
669              
670 6         9     my @current;
671                 my @list;
672              
673                 $self->contents->each(sub{
674 69     69   120             my(undef,$e)=@_;
675              
676 69 100       126             if( $e->match($selector) ) {
677 18         41                 push @list, [ @current ];
678 18         63                 @current = ( $e );
679                         }
680                         else {
681 51 100       102                 if ( $current[1] ) {
682 30         80                     $current[1] = $current[1]->add($e);
683                             }
684                             else {
685 21         53                     $current[1] = $e;
686                             }
687                         }
688 6         17     });
689 6         44     push @list, [ @current ];
690              
691 6 100       17     if( $args{skip_leading} ) {
692 2         5         @list = grep { $_->[0] } @list;
  8         17  
693                 }
694              
695 6 100       15     unless ( $args{pairs} ) {
696 2     5   7         @list = map { reduce { $a->add($b) } grep { $_ } @$_ } @list;
  8         29  
  5         12  
  15         53  
697                 }
698              
699 6         21     return @list;
700             }
701              
702             sub last_response {
703 4     4 1 3418     return $RESPONSE;
704             }
705              
706             sub DESTROY {
707 1269 100   1269   282917     return unless $_[0]->{need_delete};
708              
709             # avoid memory leaks
710 237         430     local $@;
711 237         384     eval { $_->delete } for @{$_[0]->{trees}};
  237         770  
  254         1424  
712             }
713              
714             1;
715              
716             __END__
717            
718             =pod
719            
720             =encoding UTF-8
721            
722             =head1 NAME
723            
724             Web::Query - Yet another scraping library like jQuery
725            
726             =head1 VERSION
727            
728             version 0.39
729            
730             =head1 SYNOPSIS
731            
732             use Web::Query;
733            
734             wq('http://www.w3.org/TR/html401/')
735             ->find('div.head dt')
736             ->each(sub {
737             my $i = shift;
738             printf("%d %s\n", $i+1, $_->text);
739             });
740            
741             =head1 DESCRIPTION
742            
743             Web::Query is a yet another scraping framework, have a jQuery like interface.
744            
745             Yes, I know Ingy's L<pQuery>. But it's just a alpha quality. It doesn't works.
746             Web::Query built at top of the CPAN modules, L<HTML::TreeBuilder::XPath>, L<LWP::UserAgent>, and L<HTML::Selector::XPath>.
747            
748             So, this module uses L<HTML::Selector::XPath> and only supports the CSS 3
749             selector supported by that module.
750             Web::Query doesn't support jQuery's extended queries(yet?). If a selector is
751             passed as a scalar ref, it'll be taken as a straight XPath expression.
752            
753             $wq( '<div><p>hello</p><p>there</p></div>' )->find( 'p' ); # css selector
754             $wq( '<div><p>hello</p><p>there</p></div>' )->find( \'/div/p' ); # xpath selector
755            
756             B<THIS LIBRARY IS UNDER DEVELOPMENT. ANY API MAY CHANGE WITHOUT NOTICE>.
757            
758             =for stopwords prev
759            
760             =head1 FUNCTIONS
761            
762             =over 4
763            
764             =item C<< wq($stuff) >>
765            
766             This is a shortcut for C<< Web::Query->new($stuff) >>. This function is exported by default.
767            
768             =back
769            
770             =head1 METHODS
771            
772             =head2 CONSTRUCTORS
773            
774             =over 4
775            
776             =item my $q = Web::Query->new($stuff, \%options )
777            
778             Create new instance of Web::Query. You can make the instance from URL(http, https, file scheme), HTML in string, URL in string, L<URI> object, C<undef>, and either one
779             L<HTML::Element> object or an array ref of them.
780            
781             # all valid creators
782             $q = Web::Query->new( 'http://techblog.babyl.ca' );
783             $q = Web::Query->new( '<p>foo</p>' );
784             $q = Web::Query->new( undef );
785            
786             This method throw the exception on unknown $stuff.
787            
788             This method returns undefined value on non-successful response with URL.
789            
790             Currently, the only two valid options are I<indent>, which will be used as
791             the indentation string if the object is printed, and I<no_space_compacting>,
792             which will prevent the compaction of whitespace characters in text blocks.
793            
794             =item my $q = Web::Query->new_from_element($element: HTML::Element)
795            
796             Create new instance of Web::Query from instance of L<HTML::Element>.
797            
798             =item C<< my $q = Web::Query->new_from_html($html: Str) >>
799            
800             Create new instance of Web::Query from HTML.
801            
802             =item my $q = Web::Query->new_from_url($url: Str)
803            
804             Create new instance of Web::Query from URL.
805            
806             If the response is not success(It means /^20[0-9]$/), this method returns undefined value.
807            
808             You can get a last result of response, use the C<< $Web::Query::RESPONSE >>.
809            
810             Here is a best practical code:
811            
812             my $url = 'http://example.com/';
813             my $q = Web::Query->new_from_url($url)
814             or die "Cannot get a resource from $url: " . Web::Query->last_response()->status_line;
815            
816             =item my $q = Web::Query->new_from_file($file_name: Str)
817            
818             Create new instance of Web::Query from file name.
819            
820             =back
821            
822             =head2 TRAVERSING
823            
824             =head3 add
825            
826             Returns a new object augmented with the new element(s).
827            
828             =over 4
829            
830             =item add($html)
831            
832             An HTML fragment to add to the set of matched elements.
833            
834             =item add(@elements)
835            
836             One or more @elements to add to the set of matched elements.
837            
838             @elements that already are part of the set are not added a second time.
839            
840             my $group = $wq->find('#foo'); # collection has 1 element
841             $group = $group->add( '#bar', $wq ); # 2 elements
842             $group->add( '#foo', $wq ); # still 2 elements
843            
844             =item add($wq)
845            
846             An existing Web::Query object to add to the set of matched elements.
847            
848             =item add($selector, $context)
849            
850             $selector is a string representing a selector expression to find additional elements to add to the set of matched elements.
851            
852             $context is the point in the document at which the selector should begin matching
853            
854             =back
855            
856             =head3 contents
857            
858             Get the immediate children of each element in the set of matched elements, including text and comment nodes.
859            
860             =head3 each
861            
862             Visit each nodes. C<< $i >> is a counter value, 0 origin. C<< $elem >> is iteration item.
863             C<< $_ >> is localized by C<< $elem >>.
864            
865             $q->each(sub { my ($i, $elem) = @_; ... })
866            
867             =head3 end
868            
869             Back to the before context like jQuery.
870            
871             =head3 filter
872            
873             Reduce the elements to those that pass the function's test.
874            
875             $q->filter(sub { my ($i, $elem) = @_; ... })
876            
877             =head3 find
878            
879             Get the descendants of each element in the current set of matched elements, filtered by a selector.
880            
881             my $q2 = $q->find($selector); # $selector is a CSS3 selector.
882            
883             B<NOTE> If you want to match the element itself, use L</filter>.
884            
885             B<INCOMPATIBLE CHANGE>
886             From v0.14 to v0.19 (inclusive) find() also matched the element itself, which is not jQuery compatible.
887             You can achieve that result using C<filter()>, C<add()> and C<find()>:
888            
889             my $wq = wq('<div class="foo"><p class="foo">bar</p></div>'); # needed because we don't have a global document like jQuery does
890             print $wq->filter('.foo')->add($wq->find('.foo'))->as_html; # <div class="foo"><p class="foo">bar</p></div><p class="foo">bar</p>
891            
892             =head3 first
893            
894             Return the first matching element.
895            
896             This method constructs a new Web::Query object from the first matching element.
897            
898             =head3 last
899            
900             Return the last matching element.
901            
902             This method constructs a new Web::Query object from the last matching element.
903            
904             =head3 match($selector)
905            
906             Returns a boolean indicating if the elements match the C<$selector>.
907            
908             In scalar context returns only the boolean for the first element.
909            
910             For the reverse of C<not()>, see C<filter()>.
911            
912             =head3 not($selector)
913            
914             Returns all the elements not matching the C<$selector>.
915            
916             # $do_for_love will be every thing, except #that
917             my $do_for_love = $wq->find('thing')->not('#that');
918            
919             =head3 and_back
920            
921             Add the previous set of elements to the current one.
922            
923             # get the h1 plus everything until the next h1
924             $wq->find('h1')->next_until('h1')->and_back;
925            
926             =head3 map
927            
928             Creates a new array with the results of calling a provided function on every element.
929            
930             $q->map(sub { my ($i, $elem) = @_; ... })
931            
932             =head3 parent
933            
934             Get the parent of each element in the current set of matched elements.
935            
936             =head3 prev
937            
938             Get the previous node of each element in the current set of matched elements.
939            
940             my $prev = $q->prev;
941            
942             =head3 next
943            
944             Get the next node of each element in the current set of matched elements.
945            
946             my $next = $q->next;
947            
948             =head3 next_until( $selector )
949            
950             Get all subsequent siblings, up to (but not including) the next node matched C<$selector>.
951            
952             =head2 MANIPULATION
953            
954             =head3 add_class
955            
956             Adds the specified class(es) to each of the set of matched elements.
957            
958             # add class 'foo' to <p> elements
959             wq('<div><p>foo</p><p>bar</p></div>')->find('p')->add_class('foo');
960            
961             =head3 toggle_class( @classes )
962            
963             Toggles the given class or classes on each of the element. I.e., if the element had the class, it'll be removed,
964             and if it hadn't, it'll be added.
965            
966             Classes are toggled once, no matter how many times they appear in the argument list.
967            
968             $q->toggle_class( 'foo', 'foo', 'bar' );
969            
970             # equivalent to
971            
972             $q->toggle_class('foo')->toggle_class('bar');
973            
974             # and not
975            
976             $q->toggle_class('foo')->toggle_class('foo')->toggle_class('bar');
977            
978             =head3 after
979            
980             Insert content, specified by the parameter, after each element in the set of matched elements.
981            
982             wq('<div><p>foo</p></div>')->find('p')
983             ->after('<b>bar</b>')
984             ->end
985             ->as_html; # <div><p>foo</p><b>bar</b></div>
986            
987             The content can be anything accepted by L</new>.
988            
989             =head3 append
990            
991             Insert content, specified by the parameter, to the end of each element in the set of matched elements.
992            
993             wq('<div></div>')->append('<p>foo</p>')->as_html; # <div><p>foo</p></div>
994            
995             The content can be anything accepted by L</new>.
996            
997             =head3 as_html
998            
999             Returns the string representations of either the first or all elements,
1000             depending if called in list or scalar context.
1001            
1002             If given an argument C<join>, the string representations of the elements
1003             will be concatenated with the given string.
1004            
1005             wq( '<div><p>foo</p><p>bar</p></div>' )
1006             ->find('p')
1007             ->as_html( join => '!' );
1008             # <p>foo</p>!<p>bar</p>
1009            
1010             =head3 C< attr >
1011            
1012             Get/set attribute values.
1013            
1014             In getter mode, it'll return either the values of the attribute
1015             for all elements of the set, or only the first one depending of the calling context.
1016            
1017             my @values = $q->attr('style'); # style of all elements
1018             my $first_value = $q->attr('style'); # style of first element
1019            
1020             In setter mode, it'll set attributes value for all elements, and return back
1021             the original object for easy chaining.
1022            
1023             $q->attr( 'alt' => 'a picture' )->find( ... );
1024            
1025             # can pass more than 1 element too
1026             $q->attr( alt => 'a picture', src => 'file:///...' );
1027            
1028             The value passed for an attribute can be a code ref. In that case,
1029             the code will be called with C<$_> set to the current attribute value.
1030             If the code modifies C<$_>, the attribute will be updated with the new value.
1031            
1032             $q->attr( alt => sub { $_ ||= 'A picture' } );
1033            
1034             =head3 C< id >
1035            
1036             Get/set the elements's id attribute.
1037            
1038             In getter mode, it behaves just like C<attr()>.
1039            
1040             In setter mode, it behaves like C<attr()>, but with the following exceptions.
1041            
1042             If the attribute value is a scalar, it'll be only assigned to
1043             the first element of the set (as ids are supposed to be unique), and the returned object will only contain
1044             that first element.
1045            
1046             my $first_element = $q->id('the_one');
1047            
1048             It's possible to set the ids of all the elements by passing a sub to C<id()>. The sub is given the same arguments as for
1049             C<each()>, and its return value is taken to be the new id of the elements.
1050            
1051             $q->id( sub { my $i = shift; 'foo_' . $i } );
1052            
1053             =head3 C< name >
1054            
1055             Get/set the elements's 'name' attribute.
1056            
1057             my $name = $q->name; # equivalent to $q->attr( 'name' );
1058            
1059             $q->name( 'foo' ); # equivalent to $q->attr( name => 'foo' );
1060            
1061             =head3 C< data >
1062            
1063             Get/set the elements's 'data-*name*' attributes.
1064            
1065             my $data = $q->data('foo'); # equivalent to $q->attr( 'data-foo' );
1066            
1067             $q->data( 'foo' => 'bar' ); # equivalent to $q->attr( 'data-foo' => 'bar' );
1068            
1069             =head3 tagname
1070            
1071             Get/Set the tag name of elements.
1072            
1073             my $name = $q->tagname;
1074            
1075             $q->tagname($new_name);
1076            
1077             =head3 before
1078            
1079             Insert content, specified by the parameter, before each element in the set of matched elements.
1080            
1081             wq('<div><p>foo</p></div>')->find('p')
1082             ->before('<b>bar</b>')
1083             ->end
1084             ->as_html; # <div><b>bar</b><p>foo</p></div>
1085            
1086             The content can be anything accepted by L</new>.
1087            
1088             =head3 clone
1089            
1090             Create a deep copy of the set of matched elements.
1091            
1092             =head3 detach
1093            
1094             Remove the set of matched elements from the DOM.
1095            
1096             =head3 has_class
1097            
1098             Determine whether any of the matched elements are assigned the given class.
1099            
1100             =head3 C< html >
1101            
1102             Get/Set the innerHTML.
1103            
1104             my @html = $q->html();
1105            
1106             my $html = $q->html(); # 1st matching element only
1107            
1108             $q->html('<p>foo</p>');
1109            
1110             =head3 insert_before
1111            
1112             Insert every element in the set of matched elements before the target.
1113            
1114             =head3 insert_after
1115            
1116             Insert every element in the set of matched elements after the target.
1117            
1118             =head3 C< prepend >
1119            
1120             Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.
1121            
1122             =head3 remove
1123            
1124             Delete the elements associated with the object from the DOM.
1125            
1126             # remove all <blink> tags from the document
1127             $q->find('blink')->remove;
1128            
1129             =head3 remove_class
1130            
1131             Remove a single class, multiple classes, or all classes from each element in the set of matched elements.
1132            
1133             =head3 replace_with
1134            
1135             Replace the elements of the object with the provided replacement.
1136             The replacement can be a string, a C<Web::Query> object or an
1137             anonymous function. The anonymous function is passed the index of the current
1138             node and the node itself (with is also localized as C<$_>).
1139            
1140             my $q = wq( '<p><b>Abra</b><i>cada</i><u>bra</u></p>' );
1141            
1142             $q->find('b')->replace_with('<a>Ocus</a>);
1143             # <p><a>Ocus</a><i>cada</i><u>bra</u></p>
1144            
1145             $q->find('u')->replace_with($q->find('b'));
1146             # <p><i>cada</i><b>Abra</b></p>
1147            
1148             $q->find('i')->replace_with(sub{
1149             my $name = $_->text;
1150             return "<$name></$name>";
1151             });
1152             # <p><b>Abra</b><cada></cada><u>bra</u></p>
1153            
1154             =head3 size
1155            
1156             Return the number of elements in the Web::Query object.
1157            
1158             wq('<div><p>foo</p><p>bar</p></div>')->find('p')->size; # 2
1159            
1160             =head3 text
1161            
1162             Get/Set the text.
1163            
1164             my @text = $q->text();
1165            
1166             my $text = $q->text(); # 1st matching element only
1167            
1168             $q->text('text');
1169            
1170             If called in a scalar context, only return the string representation
1171             of the first element
1172            
1173             =head2 OTHERS
1174            
1175             =over 4
1176            
1177             =item Web::Query->last_response()
1178            
1179             Returns last HTTP response status that generated by C<new_from_url()>.
1180            
1181             =back
1182            
1183             =head1 HOW DO I CUSTOMIZE USER AGENT?
1184            
1185             You can specify your own instance of L<LWP::UserAgent>.
1186            
1187             $Web::Query::UserAgent = LWP::UserAgent->new( agent => 'Mozilla/5.0' );
1188            
1189             =head1 FAQ AND TROUBLESHOOTING
1190            
1191             =head2 How to find XML processing instructions in a document?
1192            
1193             It's possible with L<Web::Query::LibXML> and by using an xpath expression
1194             with C<find()>:
1195            
1196             # find <?xml-stylesheet ... ?>
1197             $q->find(\"//processing-instruction('xml-stylesheet')");
1198            
1199             However, note that the support for processing instructions
1200             in L<HTML::TreeBuilder::LibXML::Node> is sketchy, so there
1201             are methods like C<attr()> that won't work.
1202            
1203             =head2 Can't get the content of script elements
1204            
1205             The <script> tag is treated differently by L<HTML::TreeBuilder>, the
1206             parser used by Web::Query. To retrieve the content, you can use either
1207             the method C<html()> (with the caveat that the content will be escaped),
1208             or use L<Web::Query::LibXML>, which parse the 'script' element differently.
1209            
1210             my $node = "<script>var x = '<p>foo</p>';</script>";
1211            
1212             say Web::Query::wq( $node )->text;
1213             # nothing is printed!
1214            
1215             say Web::Query::wq( $node )->html;
1216             # var x = &#39;&lt;p&gt;foo&lt;/p&gt;&#39;;
1217            
1218             say Web::Query::LibXML::wq( $node )->text;
1219             # var x = '<p>foo</p>';
1220            
1221             say Web::Query::LibXML::wq( $node )->html;
1222             # var x = '&lt;p&gt;foo&lt;/p&gt;';
1223            
1224             =head1 INCOMPATIBLE CHANGES
1225            
1226             =over 4
1227            
1228             =item 0.10
1229            
1230             new_from_url() is no longer throws exception on bad response from HTTP server.
1231            
1232             =back
1233            
1234             =head1 AUTHOR
1235            
1236             Tokuhiro Matsuno E<lt>tokuhirom AAJKLFJEF@ GMAIL COME<gt>
1237            
1238             =head1 SEE ALSO
1239            
1240             =over
1241            
1242             =item L<pQuery>
1243            
1244             =item L<XML::LibXML::jQuery>
1245            
1246             =back
1247            
1248             =head1 LICENSE
1249            
1250             Copyright (C) Tokuhiro Matsuno
1251            
1252             This library is free software; you can redistribute it and/or modify
1253             it under the same terms as Perl itself.
1254            
1255             =head1 BUGS
1256            
1257             Please report any bugs or feature requests on the bugtracker website
1258             https://github.com/tokuhirom/Web-Query/issues
1259            
1260             When submitting a bug or request, please include a test-file or a
1261             patch to an existing test-file that illustrates the bug or desired
1262             feature.
1263            
1264             =cut
1265