File Coverage

blib/lib/Web/Query.pm
Criterion Covered Total %
statement 430 434 99.0
branch 125 138 90.5
condition 37 83 44.5
subroutine 76 76 100.0
pod 47 49 95.9
total 715 780 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 = '1.00';
5 46     46   4020276 use strict;
  46         469  
  46         1406  
6 46     46   256 use warnings;
  46         106  
  46         1084  
7 46     46   1365 use 5.008001;
  46         149  
8 46     46   22930 use parent qw/Exporter/;
  46         13747  
  46         281  
9 46     46   29973 use HTML::TreeBuilder::XPath;
  46         3262223  
  46         432  
10 46     46   35961 use LWP::UserAgent;
  46         2452340  
  46         3252  
11 46     46   25219 use HTML::Selector::XPath 0.20 qw/selector_to_xpath/;
  46         157084  
  46         4143  
12 46     46   400 use Scalar::Util qw/blessed refaddr/;
  46         110  
  46         2705  
13 46     46   1385 use HTML::Entities qw/encode_entities/;
  46         125  
  46         3339  
14              
15 46     46   396 use List::Util 1.44 qw/ reduce uniq /;
  46         792  
  46         3171  
16 46     46   374 use Scalar::Util qw/ refaddr /;
  46         204  
  46         24981  
17              
18             our @EXPORT = qw/wq/;
19              
20             our $RESPONSE;
21              
22 90     90 1 140162 sub wq { Web::Query->new(@_) }
23              
24             our $UserAgent;
25              
26 8   66 8   65 sub __ua { $UserAgent ||= LWP::UserAgent->new }
27              
28             sub _build_tree {
29 122     122   284 my( $self, $options ) = @_;
30              
31             my $no_space_compacting = ref $self ? $self->{no_space_compacting}
32 122 100       451 : ref $options eq 'HASH' ? $options->{no_space_compacting} : 0;
    100          
33              
34 122         975 my $tree = HTML::TreeBuilder::XPath->new(
35             no_space_compacting => $no_space_compacting
36             );
37 122         38243 $tree->ignore_unknown(0);
38 122         1885 $tree->store_comments(1);
39 122         1100 $tree;
40             }
41              
42             sub new {
43 477     477 1 82766 my ($class, $stuff, $options) = @_;
44              
45 477 50       1369 my $self = $class->_resolve_new($stuff,$options)
46             or return undef;
47              
48 474 100       2285 $self->{indent} = $options->{indent} if $options->{indent};
49              
50 474         958 $self->{no_space_compacting} = $options->{no_space_compacting};
51              
52 474         1685 return $self;
53             }
54              
55             sub _resolve_new {
56 477     477   949 my( $class, $stuff, $options) = @_;
57              
58 477 100       1178 return $class->new_from_element([],undef,$options) unless defined $stuff;
59              
60 475 100       1630 if (blessed $stuff) {
61 268 100       1041 return $class->new_from_element([$stuff],undef,$options)
62             if $stuff->isa('HTML::Element');
63              
64 119 100       412 return $class->new_from_url($stuff->as_string,$options)
65             if $stuff->isa('URI');
66              
67 117 50       401 return $class->new_from_element($stuff->{trees}, undef, $options)
68             if $stuff->isa($class);
69              
70 0         0 die "Unknown source type: $stuff";
71             }
72              
73 207 100       583 return $class->new_from_element($stuff,undef,$options) if ref $stuff eq 'ARRAY';
74              
75 205 100       793 return $class->new_from_url($stuff,$options) if $stuff =~ m{^(?:https?|file)://};
76              
77 199 100       1562 return $class->new_from_html($stuff,$options) if $stuff =~ /<.*?>/;
78              
79 18 50 33     529 return $class->new_from_file($stuff,$options) if $stuff !~ /\n/ && -f $stuff;
80              
81 0         0 die "Unknown source type: $stuff";
82             }
83              
84             sub new_from_url {
85 8     8 1 35 my ($class, $url,$options) = @_;
86              
87 8         38 $RESPONSE = __ua()->get($url);
88              
89 46     46   494 no warnings 'uninitialized';
  46         385  
  46         68466  
90              
91 8 100       73825 unless( $RESPONSE->is_success ) {
92 3         47 die "failed to retrieve '$url', " . $RESPONSE->code. " "
93             . $RESPONSE->message."\n";
94             };
95              
96 5         79 return $class->new_from_html($RESPONSE->decoded_content,$options);
97             }
98              
99             sub new_from_file {
100 18     18 1 74 my ($class, $fname, $options) = @_;
101 18         52 my $tree = $class->_build_tree($options);
102 18         76 $tree->parse_file($fname);
103 18         28602 my $self = $class->new_from_element([$tree->disembowel],undef,$options);
104 18         114 $self->{need_delete}++;
105 18         93 return $self;
106             }
107              
108             sub new_from_html {
109 218     218 1 51834 my ($class, $html,$options) = @_;
110 218         657 my $tree = $class->_build_tree($options);
111 218         1550 $tree->parse_content($html);
112             my $self = $class->new_from_element([
113             map {
114 218 100       176555 ref $_ ? $_ : bless { _content => $_ }, 'HTML::TreeBuilder::XPath::TextNode'
  237         19073  
115             } $tree->disembowel
116             ],undef,$options);
117 218         967 $self->{need_delete}++;
118 218         2260 return $self;
119             }
120              
121             sub new_from_element {
122 1268     1268 1 3997 my $self_or_class = shift;
123              
124 1268 100       2959 my $trees = ref $_[0] eq 'ARRAY' ? $_[0] : [$_[0]];
125              
126 1268   66     9356 return bless { trees => [ @$trees ], before => $_[1] },
127             ref $self_or_class || $self_or_class;
128             }
129              
130             sub end {
131 108     108 1 418 my $self = shift;
132 108         295 return $self->{before};
133             }
134              
135             sub size {
136 142     142 1 7409 my $self = shift;
137 142         205 return scalar(@{$self->{trees}});
  142         591  
138             }
139              
140             sub parent {
141 4     4 1 10 my $self = shift;
142              
143 4         7 my @new = map { $_->parent } @{$self->{trees}};
  4         13  
  4         15  
144              
145 4   33     91 return (ref $self || $self)->new_from_element(\@new, $self);
146             }
147              
148             sub first {
149 12     12 1 38 my $self = shift;
150 12         49 return $self->eq(0);
151             }
152              
153             sub last {
154 6     6 1 48 my $self = shift;
155 6         20 return $self->eq(-1);
156             }
157              
158             sub get {
159 89     89 1 200 my ($self, $index) = @_;
160 89         263 return $self->{trees}[$index];
161             }
162              
163             sub eq {
164 26     26 0 67 my ($self, $index) = @_;
165 26   33     158 return (ref $self || $self)->new_from_element([$self->{trees}[$index] || ()], $self);
      66        
166             }
167              
168             sub find {
169 295     295 1 29396 my ($self, $selector) = @_;
170            
171 295 100       1330 my $xpath = ref $selector ? $$selector : selector_to_xpath($selector, root => './');
172 295         31530 my @new = map { eval{ $_->findnodes($xpath) } } @{$self->{trees}};
  311         28850  
  311         1193  
  295         792  
173            
174 295   33     533615 return (ref $self || $self)->new_from_element(\@new, $self);
175             }
176              
177             sub contents {
178 20     20 1 116 my ($self, $selector) = @_;
179              
180 20         30 my @new = map { $_->content_list } @{$self->{trees}};
  28         215  
  20         49  
181              
182 20 100       880 if ($selector) {
183 2 50       16 my $xpath = ref $selector ? $$selector : selector_to_xpath($selector);
184 2         279 @new = grep { $_->matches($xpath) } @new;
  6         3010  
185             }
186              
187 20   33     517 return (ref $self || $self)->new_from_element(\@new, $self);
188             }
189              
190             sub as_html {
191 105     105 1 6724 my $self = shift;
192 105         285 my %args = @_;
193              
194             my @html = map {
195             ref $_ ? ( $_->isa('HTML::TreeBuilder::XPath::TextNode') || $_->isa('HTML::TreeBuilder::XPath::CommentNode' ) )
196             ? $_->getValue
197             : $_->as_HTML( q{&<>'"}, $self->{indent}, {} )
198 148 100 66     9108 : $_
    100          
199 105         179 } @{$self->{trees}};
  105         247  
200              
201 105 100       29315 return join $args{join}, @html if defined $args{join};
202              
203 85 100       847 return wantarray ? @html : $html[0];
204             }
205              
206             sub html {
207 48     48 1 172 my $self = shift;
208              
209 48 100       122 if (@_) {
210             map {
211 4         22 $_->delete_content;
212 4         60 my $tree = $self->_build_tree;
213            
214 4         40 $tree->parse_content($_[0]);
215 4         1433 $_->push_content($tree->disembowel);
216 4         5 } @{$self->{trees}};
  4         15  
217 4         506 return $self;
218             }
219              
220 44         73 my @html;
221 44         68 for my $t ( @{$self->{trees}} ) {
  44         116  
222             push @html, join '', map {
223             ref $_ ? $_->as_HTML( q{&<>'"}, $self->{indent}, {})
224 66 100       6907 : encode_entities($_)
225 44         74 } eval { $t->content_list };
  44         141  
226             }
227            
228 44 100       12523 return wantarray ? @html : $html[0];
229             }
230              
231             sub text {
232 88     88 1 2231 my $self = shift;
233              
234 88 100       224 if (@_) {
235 2         4 map { $_->delete_content; $_->push_content($_[0]) } @{$self->{trees}};
  2         10  
  2         29  
  2         6  
236 2         87 return $self;
237             }
238              
239             my @html = map {
240 86 100       352 ref $_ ? $_->as_text : $_
241 86         144 } @{$self->{trees}};
  86         175  
242 86 100       2021 return wantarray ? @html : $html[0];
243             }
244              
245             sub attr {
246 100     100 1 12857 my $self = shift;
247              
248 100 100       257 if ( @_ == 1 ) { # getter
249             return wantarray
250 16         122 ? map { $_->attr(@_) } @{$self->{trees}}
  10         27  
251 66 100       171 : eval { $self->{trees}[0]->attr(@_) }
  56         215  
252             ;
253             }
254              
255 34         145 while( my( $attr, $value ) = splice @_, 0, 2 ) {
256 36 100       151 my $code = ref $value eq 'CODE' ? $value : undef;
257              
258 36         55 for my $t ( @{$self->{trees}} ) {
  36         82  
259 42 100       219 if ( $code ) {
260 46     46   431 no warnings 'uninitialized';
  46         351  
  46         56746  
261 4         17 my $orig = $_ = $t->attr($attr);
262 4         60 $code->();
263 4 100       22 next if $orig eq $_;
264 2         4 $value = $_;
265             }
266 40         115 $t->attr($attr => $value);
267             }
268             }
269              
270 34         929 return $self;
271             }
272              
273             sub id {
274 20     20 1 40 my $self = shift;
275              
276 20 100       45 if ( @_ ) { # setter
277 8         18 my $new_id = shift;
278              
279 8 50       19 return $self if $self->size == 0;
280              
281             return $self->each(sub{
282 6     6   20 $_->attr( id => $new_id->(@_) )
283 8 100       38 }) if ref $new_id eq 'CODE';
284              
285 6 100       11 if ( $self->size == 1 ) {
286 2         6 $self->attr( id => $new_id );
287             }
288             else {
289 4         15 return $self->first->attr( id => $new_id );
290             }
291             }
292             else { # getter
293              
294             # the eval is there in case there is no tree
295             return wantarray
296 8         66 ? map { $_->attr('id') } @{$self->{trees}}
  4         9  
297 12 100       30 : eval { $self->{trees}[0]->attr('id') }
  8         26  
298             ;
299             }
300             }
301              
302             sub name {
303 12     12 1 23 my $self = shift;
304 12         29 $self->attr( 'name', @_ );
305             }
306              
307             sub data {
308 4     4 1 7 my $self = shift;
309 4         8 my $name = shift;
310 4         19 $self->attr( join( '-', 'data', $name ), @_ );
311             }
312              
313             sub tagname {
314 12     12 1 47 my $self = shift;
315 12 100       93 my @retval = map { $_ eq '~comment' ? '#comment' : $_ }
316 12 100       60 map { ref $_ eq 'HTML::TreeBuilder::XPath::TextNode' ? '#text'
    50          
    100          
317             : ref $_ eq 'HTML::TreeBuilder::XPath::CommentNode' ? '#comment'
318             : ref $_ ? $_->tag(@_)
319             : '#text'
320             ;
321 12         16 } @{$self->{trees}};
  12         28  
322 12 50       55 return wantarray ? @retval : $retval[0];
323             }
324              
325             sub each {
326 51     51 1 861 my ($self, $code) = @_;
327 51         91 my $i = 0;
328              
329             # make a copy such that if we modify the list via 'delete',
330             # it won't change from under our feet (see t/each-and-delete.t
331             # for a case where it can)
332 51         83 my @trees = @{ $self->{trees} };
  51         140  
333 51         112 for my $tree ( @trees ) {
334 140   33     506 local $_ = (ref $self || $self)->new_from_element([$tree], $self);
335 140         378 $code->($i++, $_);
336             }
337 51         165 return $self;
338             }
339              
340             sub map {
341 87     87 1 258 my ($self, $code) = @_;
342 87         126 my $i = 0;
343             return +[map {
344 125         180 my $tree = $_;
345 125   33     387 local $_ = (ref $self || $self)->new($tree);
346 125         313 $code->($i++, $_);
347 87         110 } @{$self->{trees}}];
  87         204  
348             }
349              
350             sub filter {
351 64     64 1 2939 my $self = shift;
352              
353 64         94 my @new;
354              
355 64 100       161 if (ref($_[0]) eq 'CODE') {
356 48         71 my $code = $_[0];
357 48         72 my $i = 0;
358             @new = grep {
359 78         115 my $tree = $_;
360 78   33     295 local $_ = (ref $self || $self)->new_from_element($tree);
361 78         207 $code->($i++, $_);
362 48         68 } @{$self->{trees}};
  48         107  
363             }
364             else {
365 16 50       69 my $xpath = ref $_[0] ? ${$_[0]} : selector_to_xpath($_[0]);
  0         0  
366 16         1701 @new = grep { eval { $_->matches($xpath) } } @{$self->{trees}};
  28         21238  
  28         126  
  16         56  
367             }
368              
369 64   33     14921 return (ref $self || $self)->new_from_element(\@new, $self);
370             }
371              
372             sub _is_same_node {
373 22     22   83 refaddr($_[1]) == refaddr($_[2]);
374             }
375              
376             sub remove {
377 30     30 1 59 my $self = shift;
378 30         75 my $before = $self->end;
379            
380 30         79 while (defined $before) {
381 28         62 @{$before->{trees}} = grep {
382 40         56 my $el = $_;
383 40         59 not grep { $self->_is_same_node( $el, $_ ) } @{$self->{trees}};
  44         83  
  40         59  
384 28         38 } @{$before->{trees}};
  28         52  
385              
386 28         54 $before = $before->end;
387             }
388            
389 30         35 $_->delete for @{$self->{trees}};
  30         129  
390 30         952 @{$self->{trees}} = ();
  30         65  
391            
392 30         102 $self;
393             }
394              
395             sub replace_with {
396 10     10 1 24 my ( $self, $replacement ) = @_;
397              
398 10         14 my $i = 0;
399 10         12 for my $node ( @{ $self->{trees} } ) {
  10         27  
400 18         118 my $rep = $replacement;
401              
402 18 100       45 if ( ref $rep eq 'CODE' ) {
403 8   33     28 local $_ = (ref $self || $self)->new($node);
404 8         22 $rep = $rep->( $i++ => $_ );
405             }
406              
407 18 100 33     61 $rep = (ref $self || $self)->new_from_html( $rep )
408             unless ref $rep;
409              
410              
411            
412 18         126 my $r = $rep->{trees}->[0];
413 46     46   473 { no warnings;
  46         100  
  46         60294  
  18         23  
414 18 100       64 $r = $r->clone if ref $r;
415             }
416 18 100 66     591 $r->parent( $node->parent ) if ref $r and $node->parent;
417              
418 18         626 $node->replace_with( $r );
419             }
420              
421 10 100 33     303 $replacement->remove if ref $replacement eq (ref $self || $self);
422              
423 10         45 return $self;
424             }
425              
426             sub append {
427 2     2 1 7 my ($self, $stuff) = @_;
428 2   33     9 $stuff = (ref $self || $self)->new($stuff);
429              
430 2         5 foreach my $t (@{$self->{trees}}) {
  2         6  
431 4         282 $t->push_content($_) for ref($t)->clone_list(@{$stuff->{trees}});
  4         22  
432             }
433              
434 2         132 $self;
435             }
436              
437             sub prepend {
438 2     2 1 8 my ($self, $stuff) = @_;
439 2   33     10 $stuff = (ref $self || $self)->new($stuff);
440            
441 2         6 foreach my $t (@{$self->{trees}}) {
  2         7  
442 4         234 $t->unshift_content($_) for ref($t)->clone_list(@{$stuff->{trees}});
  4         17  
443             }
444            
445 2         144 $self;
446             }
447              
448              
449             sub before {
450 2     2 1 11 my ($self, $stuff) = @_;
451 2   33     13 $stuff = (ref $self || $self)->new($stuff);
452            
453 2         8 foreach my $t (@{$self->{trees}}) {
  2         21  
454 4         265 $t->preinsert(ref($t)->clone_list(@{$stuff->{trees}}));
  4         15  
455             }
456            
457 2         176 $self;
458             }
459              
460              
461             sub after {
462 2     2 1 14 my ($self, $stuff) = @_;
463 2   33     10 $stuff = (ref $self || $self)->new($stuff);
464            
465 2         13 foreach my $t (@{$self->{trees}}) {
  2         25  
466 4         257 $t->postinsert(ref($t)->clone_list(@{$stuff->{trees}}));
  4         15  
467             }
468            
469 2         156 $self;
470             }
471              
472              
473             sub insert_before {
474 2     2 1 12 my ($self, $target) = @_;
475            
476 2         4 foreach my $t (@{$target->{trees}}) {
  2         4  
477 4         248 $t->preinsert(ref($t)->clone_list(@{$self->{trees}}));
  4         13  
478             }
479            
480 2         148 $self;
481             }
482              
483             sub insert_after {
484 2     2 1 11 my ($self, $target) = @_;
485            
486 2         5 foreach my $t (@{$target->{trees}}) {
  2         6  
487 4         261 $t->postinsert(ref($t)->clone_list(@{$self->{trees}}));
  4         17  
488             }
489            
490 2         151 $self;
491             }
492              
493             sub detach {
494 2     2 1 8 my ($self) = @_;
495 2         3 $_->detach for @{$self->{trees}};
  2         17  
496 2         194 $self;
497             }
498              
499             sub add_class {
500 16     16 1 25 my ($self, $class) = @_;
501            
502 16         27 for (my $i = 0; $i < @{$self->{trees}}; $i++) {
  36         464  
503 20         36 my $t = $self->{trees}->[$i];
504 20   100     40 my $current_class = $t->attr('class') || '';
505            
506 20 100       295 my $classes = ref $class eq 'CODE' ? $class->($i, $current_class, $t) : $class;
507 20         91 my @classes = split /\s+/, $classes;
508            
509 20         41 foreach (@classes) {
510 32 100       501 $current_class .= " $_" unless $current_class =~ /(?:^|\s)$_(?:\s|$)/;
511             }
512            
513 20         146 $current_class =~ s/(?:^\s*|\s*$)//g;
514 20         46 $current_class =~ s/\s\s+/ /g;
515            
516 20         52 $t->attr('class', $current_class);
517             }
518            
519 16         40 $self;
520             }
521              
522              
523             sub remove_class {
524 16     16 1 44 my ($self, $class) = @_;
525              
526 16         31 for (my $i = 0; $i < @{$self->{trees}}; $i++) {
  36         464  
527 20         36 my $t = $self->{trees}->[$i];
528 20         47 my $current_class = $t->attr('class');
529 20 50       281 next unless defined $current_class;
530            
531 20 100       72 my $classes = ref $class eq 'CODE' ? $class->($i, $current_class, $t) : $class;
532 20         76 my @remove_classes = split /\s+/, $classes;
533             my @final = grep {
534 20         74 my $existing_class = $_;
  42         56  
535 42         51 not grep { $existing_class eq $_} @remove_classes;
  66         146  
536             } split /\s+/, $current_class;
537            
538 20         75 $t->attr('class', join ' ', @final);
539             }
540            
541 16         48 $self;
542            
543             }
544              
545             sub toggle_class {
546 6     6 1 2366 my $self = shift;
547            
548 6         30 my @classes = uniq @_;
549              
550             $self->each(sub{
551 18     18   30 for my $class ( @classes ) {
552 24 100       44 my $method = $_->has_class($class) ? 'remove_class' : 'add_class';
553 24         507 $_->$method($class);
554             }
555 6         50 });
556             }
557              
558             sub has_class {
559 56     56 1 177 my ($self, $class) = @_;
560            
561 56         66 foreach my $t (@{$self->{trees}}) {
  56         112  
562 46     46   410 no warnings 'uninitialized';
  46         90  
  46         66668  
563 58 100       242 return 1 if $t->attr('class') =~ /(?:^|\s)$class(?:\s|$)/;
564             }
565            
566 28         591 return undef;
567             }
568              
569             sub clone {
570 2     2 1 4 my ($self) = @_;
571 2         3 my @clones = map { $_->clone } @{$self->{trees}};
  2         8  
  2         6  
572 2   33     219 return (ref $self || $self)->new_from_element(\@clones);
573             }
574              
575             sub add {
576 77     77 1 173 my ($self, @stuff) = @_;
577 77         107 my @nodes;
578            
579             # add(selector, context)
580 77 100 100     255 if (@stuff == 2 && !ref $stuff[0] && $stuff[1]->isa('HTML::Element')) {
      66        
581 2 50       23 my $xpath = ref $stuff[0] ? ${$stuff[0]} : selector_to_xpath($stuff[0]);
  0         0  
582 2         181 push @nodes, $stuff[1]->findnodes( $xpath, root => './');
583             }
584             else {
585             # handle any combination of html string, element object and web::query object
586             push @nodes, map {
587 77 100       203 $self->{need_delete} = 1 if $_->{need_delete};
588 77         106 delete $_->{need_delete};
589 77         104 @{$_->{trees}};
  77         171  
590 75   33     148 } map { (ref $self || $self)->new($_) } @stuff;
  77         263  
591             }
592              
593 77         7080 my %ids = map { $self->_node_id($_) => 1 } @{ $self->{trees} };
  120         238  
  77         163  
594              
595             $self->new_from_element( [
596 77         149 @{$self->{trees}}, grep { ! $ids{ $self->_node_id($_) } } @nodes
  77         146  
  95         187  
597             ], $self );
598             }
599              
600             sub _node_id {
601 76     76   121 my( undef, $node ) = @_;
602 76         279 refaddr $node;
603             }
604              
605             sub prev {
606 1     1 1 2 my $self = shift;
607 1         2 my @new;
608 1         3 for my $tree (@{$self->{trees}}) {
  1         2  
609 2         64 push @new, $tree->getPreviousSibling;
610             }
611 1   33     33 return (ref $self || $self)->new_from_element(\@new, $self);
612             }
613              
614             sub next {
615 22     22 1 34 my $self = shift;
616              
617 22         37 my @new = grep { $_ } map { $_->getNextSibling } @{ $self->{trees} };
  29         878  
  29         317  
  22         37  
618              
619 22   33     92 return (ref $self || $self)->new_from_element(\@new, $self);
620             }
621              
622             sub match {
623 73     73 1 126 my( $self, $selector ) = @_;
624              
625 73 50       211 my $xpath = ref $selector ? $$selector : selector_to_xpath($selector);
626              
627             my $results = $self->map(sub{
628 81     81   138 my(undef,$e) = @_;
629 81 50       155 return 0 unless ref $e; # it's a string
630 81         197 return !!$e->get(0)->matches($xpath);
631 73         6175 });
632              
633 73 100       491 return wantarray ? @$results : $results->[0];
634             }
635              
636             sub not {
637 38     38 1 109 my( $self, $selector ) = @_;
638              
639 38         62 my $class = ref $self;
640              
641 38 50       115 my $xpath = ref $selector ? $$selector : selector_to_xpath($selector);
642 38     46   3216 $self->filter(sub { ! grep { $_->matches($xpath) } grep { ref $_ } $class->new($_)->{trees}[0] } );
  46         110  
  46         135  
  46         101  
643             }
644              
645             sub and_back {
646 2     2 1 24 my $self = shift;
647            
648 2         10 $self->add( $self->end );
649             }
650              
651             sub next_until {
652 10     10 1 27 my( $self, $selector ) = @_;
653              
654 10         13 my $class = ref $self;
655 10         22 my $collection = $class->new_from_element([],$self);
656              
657 10         31 my $next = $self->next->not($selector);
658 10         80 while( $next->size ) {
659 26         58 $collection = $collection->add($next);
660 26         89 $next = $next->next->not( $selector );
661             }
662              
663             # hide the loop from the outside world
664 10         31 $collection->{before} = $self;
665              
666 10         19 return $collection;
667             }
668              
669             sub split {
670 6     6 0 20 my( $self, $selector, %args ) = @_;
671              
672 6         11 my @current;
673             my @list;
674              
675             $self->contents->each(sub{
676 69     69   117 my(undef,$e)=@_;
677              
678 69 100       122 if( $e->match($selector) ) {
679 18         43 push @list, [ @current ];
680 18         52 @current = ( $e );
681             }
682             else {
683 51 100       120 if ( $current[1] ) {
684 30         92 $current[1] = $current[1]->add($e);
685             }
686             else {
687 21         47 $current[1] = $e;
688             }
689             }
690 6         23 });
691 6         50 push @list, [ @current ];
692              
693 6 100       36 if( $args{skip_leading} ) {
694 2         19 @list = grep { $_->[0] } @list;
  8         21  
695             }
696              
697 6 100       16 unless ( $args{pairs} ) {
698 2     5   6 @list = map { reduce { $a->add($b) } grep { $_ } @$_ } @list;
  8         29  
  5         12  
  15         52  
699             }
700              
701 6         40 return @list;
702             }
703              
704             sub last_response {
705 4     4 1 4188 return $RESPONSE;
706             }
707              
708             sub DESTROY {
709 1269 100   1269   304843 return unless $_[0]->{need_delete};
710              
711             # avoid memory leaks
712 237         439 local $@;
713 237         388 eval { $_->delete } for @{$_[0]->{trees}};
  237         803  
  254         1540  
714             }
715              
716             1;
717              
718             __END__