File Coverage

blib/lib/pQuery.pm
Criterion Covered Total %
statement 237 343 69.1
branch 99 152 65.1
condition 77 131 58.7
subroutine 39 78 50.0
pod 12 40 30.0
total 464 744 62.3


line stmt bran cond sub pod time code
1 12     12   49655 use strict; use warnings;
  12     12   27  
  12         430  
  12         65  
  12         22  
  12         9519  
2             package pQuery;
3             our $VERSION = '0.23';
4              
5 12     12   15748 use pQuery::DOM;
  12         47  
  12         203  
6 12     12   607 use Carp;
  12         38  
  12         1296  
7              
8 12     12   154 use HTML::TreeBuilder();
  12         27  
  12         325  
9              
10 12     12   68 use base 'Exporter';
  12         29  
  12         18906  
11              
12             our $document;
13             *pQuery = \$document;
14              
15             our @EXPORT = qw(pQuery $pQuery PQUERY);
16              
17             my $my = {};
18             my $lwp_user_agent;
19             my $quickExpr = qr/^([^<]*<(.|\s)+>[^>]*)$|^#(\w+)$/;
20             my $isSimple = qr/^.[^:#\[\.]*$/;
21             my $dom_element_class = 'pQuery::DOM';
22              
23             sub pQuery {
24 266     266 1 6586 return 'pQuery'->new(@_);
25             }
26              
27             sub PQUERY {
28 0     0 1 0 return 'PQUERY'->new(@_);
29             }
30              
31             #------------------------------------------------------------------------------#
32             # New ideas / Playing around stuffs
33             #------------------------------------------------------------------------------#
34             sub url {
35 0     0 0 0 my $this = shift;
36 0 0       0 return $my->{$this}{url}
37             if $my->{$this}{url};
38 0         0 while ($this = $my->{$this}{prevObject}) {
39 0 0       0 return $my->{$this}{url}
40             if $my->{$this}{url};
41             }
42 0         0 return;
43             }
44              
45             #------------------------------------------------------------------------------#
46             # Truly ported from jQuery stuff
47             #------------------------------------------------------------------------------#
48             sub new {
49 266     266 0 356 my $class = shift;
50 266         564 my $this = bless [], $class;
51 266         844 $my->{$this} = {};
52 266         3322 return $this->_init(@_);
53             }
54              
55             sub _init {
56 266     266   432 my ($this, $selector, $context) = @_;
57              
58 266 100 100     1052 $selector ||= $document or return $this;
59              
60 261 100       930 if (ref($selector) eq $dom_element_class) {
    100          
61 141         244 @$this = $selector;
62 141         482 return $this;
63             }
64             elsif (not ref($selector)) {
65 62         695 my $match = ($selector =~ m/$quickExpr/o);
66              
67 62 100 66     373 if ($match and ($1 or not $context)) {
      66        
68 16 100       55 if ($1) {
69 15         888 my $html = $this->_clean($1);
70 15         114 $selector = [pQuery::DOM->fromHTML($html)];
71             # $selector = $this->_clean([$1], $context);
72             }
73             else {
74 1         6 my $elem = $document->getElementById($3);
75 1 50       6 if ($elem) {
76 1         4 @$this = $elem;
77 1         7 return $this;
78             }
79             else {
80 0         0 $selector = [];
81             }
82             }
83             }
84             else {
85 46 50       262 if ($selector =~ /^\s*(https?|file):/) {
    100          
86 0         0 $my->{$this}{url} = $selector;
87 0         0 return $document = $this->_new_from_url($selector);
88             }
89             elsif ($selector =~ /^\S+\.html?$/) {
90 10         47 $my->{$this}{file} = $selector;
91 10 50       1078 open FILE, $selector
92             or croak "Can't open file '$selector' for input:\n$!";
93 10         27 my $html = do {local $/; };
  10         50  
  10         539  
94 10         242 close FILE;
95 10         58 $html = $this->_clean($html);
96 10         87 $selector = [$document = pQuery::DOM->fromHTML($html)];
97             }
98             else {
99 36   33     137 $context ||= $document;
100 36         66 return pQuery($context)->find($selector);
101             }
102             }
103             }
104 83 50 33     1046 @$this = (ref($selector) eq 'ARRAY' or ref($selector) eq 'pQuery')
105             ? @$selector
106             : $selector;
107 83         341 return $this;
108             }
109              
110             sub _clean {
111 25     25   80 my ($this, $html) = @_;
112 25         130 $html =~ s/^\s*<\?xml\s.*?>\s*//s;
113 25         96 $html =~ s/^\s*\s*//s;
114 25         160 return $html;
115             }
116              
117 1     1 1 19 sub pquery { return $VERSION }
118              
119 18     18 1 37 sub size { return $#{$_[0]} + 1 }
  18         97  
120              
121             sub get {
122 5     5 1 578 my $this = shift;
123              
124             # Get could be for Ajax URL or Object Member
125 5 50 66     49 return $this->_web_get(@_)
126             if @_ and $_[0] !~ /^\d+$/;
127              
128             return @_
129 5 50       36 ? $this->[$_[0]]
    100          
130             : wantarray ? (@$this) : $this->[0];
131             }
132              
133             sub pushStack {
134 55     55 0 221 my ($this, $elems) = @_;
135 55         167 my $ret = pQuery($elems);
136 55         199 $ret->_prevObject($this);
137 55         308 return $ret;
138             }
139              
140             sub _prevObject {
141 57     57   95 my $this = shift;
142             return @_
143 57 100       309 ? ($my->{$this}{prevObject} = $_[0])
144             : $my->{$this}{prevObject};
145             }
146              
147             # Not needed in Perl
148             # sub _setArray {}
149              
150             sub each {
151 85     85 1 119 my ($this, $sub) = @_;
152 85         123 my $i = 0;
153 85         441 &$sub($i++) for @$this;
154 85         4041 return $this;
155             }
156              
157             sub attr { # (elem, name, value)
158              
159 1     1 0 5 my ($elem, $name, $value) = @_;
160              
161 1         2 my $node = $elem->[0];
162              
163             # don't set attributes on text and comment nodes
164 1 50 33     7 return undef if (!$node || $node->nodeType eq 3 || $node->nodeType eq 8);
      33        
165              
166 1 50       10 if ( defined $value ) {
167             # convert the value to a string
168 0         0 $node->setAttribute( $name, $value );
169             }
170              
171 1         6 return $node->getAttribute( $name );
172              
173             }
174              
175 0     0 0 0 sub css { # (key, value)
176             # TODO - Get/set a css attribute
177             }
178              
179             # TODO/XXX Made up. Not ported yet.
180             sub text {
181             # TODO - Get/set text value
182 76     76 0 95 my $this = shift;
183 76         87 my @text;
184              
185             $this->each(sub {
186 95     95   122 my $text = '';
187 95         193 _to_text($_, \$text);
188 95         293 $text =~ s/\s+/ /g;
189 95         459 $text =~ s/^\s+|\s+$//g;
190 95         259 push @text, $text;
191 76         293 });
192              
193 76 50       856 return wantarray ? @text : join(' ', grep /\S/, @text);
194             }
195              
196 0     0 0 0 sub wrapAll { # (html)
197             # TODO - Wrap element with HTML
198             }
199              
200 0     0 0 0 sub wrapInner { # (html)
201             # TODO - Wrap sub elements with HTML
202             }
203              
204 0     0 0 0 sub wrap { # (html)
205             # TODO - Wrap current objects with HTML
206             }
207              
208 0     0 0 0 sub append { # (@_)
209             # TODO - Append arguments to current objects
210             }
211              
212 0     0 0 0 sub prepend { # (@_)
213             # TODO - Prepend arguments to current objects
214             }
215              
216 0     0 0 0 sub before { # (@_)
217             # TODO - Insert arguments before current objects
218             }
219              
220 0     0 0 0 sub after { # (@_)
221             # TODO - Insert arguments after current objects
222             }
223              
224             sub end {
225 2     2 1 5 my $this = shift;
226 2   66     8 return $this->_prevObject || pQuery([]);
227             }
228              
229             sub find {
230 55     55 1 107 my ($this, $selector) = @_;
231 55         88 my $elems = [];
232              
233 55         199 for (my $i = 0; $i < @$this; $i++) {
234 57         86 push @$elems, @{$this->_find($selector, $this->[$i])};
  57         207  
235             }
236              
237 55 100       299 return $this->pushStack(
238             $selector =~ /[^+>] [^+>]/
239             ? $this->_unique($elems)
240             : $elems
241             )
242             }
243              
244 0     0 0 0 sub clone { # (events)
245             # TODO - Not sure if we need this one.
246             }
247              
248 0     0 0 0 sub filter { # (selector)
249             # TODO - A kind of grep
250             }
251              
252 0     0 0 0 sub add { # (selector)
253             # TODO - Some kind of merge
254             }
255              
256 0     0 0 0 sub is { # (selector)
257             # TODO - One element matches the selector
258             }
259              
260             sub hasClass {
261 0     0 0 0 my ($this, $selector) = @_;
262 0         0 $this.is(".$selector");
263             }
264              
265 0     0 0 0 sub val { # (value)
266             # TODO Get/set
267             }
268              
269             # XXX - Not really ported yet.
270             sub html {
271 8     8 1 16 my $this = shift;
272 8 100       34 return unless @$this;
273 7 50       30 if (@_) {
274 0         0 for (@$this) {
275 0 0       0 next unless ref($_);
276 0         0 $_->innerHTML(@_);
277             }
278 0         0 return $this;
279             }
280 7         44 return $this->[0]->innerHTML(@_);
281             }
282              
283             # Not a jQuery function.
284             sub toHtml {
285 4     4 1 9 my $this = shift;
286 4 100       15 return unless @$this;
287 3         17 return $this->[0]->toHTML;
288             }
289              
290             # TODO - Not tested
291             sub replaceWith { # (value)
292 0     0 0 0 my ($this, $value) = @_;
293 0         0 return $this->after($value)->remove;
294             }
295              
296             # TODO - Not tested
297             sub eq {
298 0     0 0 0 my ($this, $i) = @_;
299 0         0 return $this->pushStack($this->[$i]);
300             }
301              
302 0     0 0 0 sub slice { #(i, j)
303             # TODO - Behave like JS slice()
304             }
305              
306             sub map {
307 0     0 0 0 my ($this, $callback) = @_;
308             return $this->pushStack(__map($this, sub {
309 0     0   0 my ($elem, $i) = @_;
310 0         0 return $callback->($elem, $i, $elem);
311 0         0 }));
312             }
313              
314             # TODO - Not tested
315             sub andSelf {
316 0     0 0 0 my $this = shift;
317 0         0 return $this.add($this->prevObject);
318             }
319              
320 0     0 0 0 sub data { # (key, value)
321             # TODO - Not sure
322             }
323              
324 0     0 0 0 sub removeData { # (key)
325             # TODO - Not Sure
326             }
327              
328             sub domManip {
329 0     0 0 0 my ($this, $args, $table, $reverse, $callback) = @_;
330 0         0 my $elems;
331             return $this->each(sub {
332 0 0   0   0 if (not defined $elems) {
333 0         0 $elems = $args;
334 0 0       0 @$elems = reverse @$elems
335             if $reverse;
336             }
337             pQuery::each($elems, sub {
338 0         0 $callback->($this, $_);
339 0         0 });
340 0         0 });
341             }
342              
343             #------------------------------------------------------------------------------#
344             # "Class" methods
345             #------------------------------------------------------------------------------#
346             # sub noConflict {}
347             # sub isFunction {}
348             # sub isXMLdoc {}
349             # sub globalEval {}
350              
351             sub _nodeName {
352 3     3   8 my ($this, $elem, $name) = @_;
353 3   33     13 return $elem->nodeName &&
354             uc($elem->nodeName) eq uc($name);
355             }
356              
357              
358             # sub cache {}
359             # sub data {}
360             # sub removeData {}
361             # sub each {}
362             # sub prop {}
363             # sub className {}
364             # sub swap {}
365             # sub css {}
366             # sub curCSS {}
367             # sub clean {}
368             # sub attr {}
369              
370             sub _trim {
371 124     124   611 (my $string = $_[1]) =~ s/^\s+|\s+$//g;
372 124         445 return $string;
373             }
374              
375             # sub makeArray {}
376             # sub inArray {}
377              
378             sub _merge {
379 113     113   135 push @{$_[1]}, @{$_[2]};
  113         1073  
  113         560  
380 113         403 return $_[1];
381             }
382              
383             sub _unique {
384 9     9   19 my $seen = {};
385 9         16 return [ grep {not $seen->{$_}++} @{$_[1]} ];
  106         445  
  9         24  
386             }
387              
388             sub _grep {
389 28     28   43 my ($this, $elems, $callback, $inv) = @_;
390 28         45 my $ret = [];
391              
392 28         82 for (my ($i, $length) = (0, scalar(@$elems)); $i < $length; $i++) {
393 697 100 66     1718 push @$ret, $elems->[$i]
      33        
      66        
394             if (not $inv and &$callback($elems->[$i], $i)) or
395             ($inv and not &$callback($elems->[$i], $i));
396             }
397              
398 28         212 return $ret;
399             }
400              
401             # sub map {}
402              
403             #------------------------------------------------------------------------------#
404             # Selector functions
405             #------------------------------------------------------------------------------#
406             my $chars = qr/(?:[\w\x{128}-\x{FFFF}*_-]|\\.)/;
407             my $quickChild = qr/^>\s*($chars+)/;
408             my $quickId = qr/^($chars+)(#)($chars+)/;
409             my $quickClass = qr/^(([#.]?)($chars*))/;
410              
411             my $expr = {
412             # XXX Can't figure out how to create tests for these yet :(
413             "" => sub {
414             die 'pQuery selector error #1001. Please notify ingy@cpan.org';
415             },
416             "#" => sub {
417             die 'pQuery selector error #1002. Please notify ingy@cpan.org';
418             },
419             ":" => {
420             # Position Checks
421             lt => sub { return $_[1] < $_[2][3] },
422             gt => sub { return $_[1] > $_[2][3] },
423             nth => sub { return $_[2][3] == $_[1] },
424             eq => sub { return $_[2][3] == $_[1] },
425             first => sub { return $_[1] == 0 },
426             last => sub { return $_[1] == $#{$_[3]} },
427             even => sub { return $_[1] % 2 == 0 },
428             odd => sub { return $_[1] % 2 },
429              
430             # Child Checks
431             "first-child" => sub {
432             return $_[0]->parentNode->getElementsByTagName("*")->[0] == $_[0];
433             },
434             "last-child" => sub {
435             return pQuery->_nth(
436             $_[0]->parentNode->lastChildRef,
437             1,
438             "previousSiblingRef"
439             ) == $_[0];
440             },
441             "only-child" => sub {
442             return ! pQuery->_nth(
443             $_[0]->parentNode->lastChildRef,
444             2,
445             "previousSiblingRef"
446             );
447             },
448              
449             # Parent Checks
450             parent => sub { return $_[0]->firstChild ? 1 : 0 },
451             empty => sub { return $_[0]->firstChild ? 0 : 1 },
452              
453             # Text Check
454             contains => sub { return index(pQuery($_[0])->text, $_[2][3]) >= 0 },
455              
456             # XXX Finish porting these if it makes sense...
457             # // Visibility
458             # visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},
459             # hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},
460             #
461             # // Form attributes
462             # enabled: function(a){return !a.disabled;},
463             # disabled: function(a){return a.disabled;},
464             # checked: function(a){return a.checked;},
465             # selected: function(a){return a.selected||jQuery.attr(a,"selected");},
466             #
467             # // Form elements
468             # text: function(a){return "text"==a.type;},
469             # radio: function(a){return "radio"==a.type;},
470             # checkbox: function(a){return "checkbox"==a.type;},
471             # file: function(a){return "file"==a.type;},
472             # password: function(a){return "password"==a.type;},
473             # submit: function(a){return "submit"==a.type;},
474             # image: function(a){return "image"==a.type;},
475             # reset: function(a){return "reset"==a.type;},
476             # button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");},
477             # input: function(a){return /input|select|textarea|button/i.test(a.nodeName);},
478              
479              
480             # :has()
481             # XXX - The first form should work. Indicates that context is messed up.
482             # has => sub { return pQuery->find($_[2][3], $_[0])->length ? 1 : 0 },
483             has => sub { return pQuery($_[0])->find($_[2][3])->length ? 1 : 0 },
484              
485             # :header
486             header => sub { return $_[0]->nodeName =~ /^h[1-6]$/i },
487             },
488             };
489              
490             # The regular expressions that power the parsing engine
491             my $parse = [
492             # Match: [@value='test'], [@foo]
493             qr/^(\[)\s*\@?([\w-]+)\s*((?:[\!\*\$\^\~\|\=]?\=)?)\s*([\'\"]?)(.*?)(?:\4)\s*\]/,
494              
495             # Match: :contains('foo')
496             qr/^(:)([\w-]+)\(\"?\'?(.*?(\(.*?\))?[^(]*?)\"?\'?\)/,
497              
498             # Match: :even, :last-chlid, #id, .class
499             qr/^([:.#]*)($chars+)/,
500             ];
501              
502 0     0   0 sub _multiFilter {
503             # XXX - Port me.
504             }
505              
506             sub _find {
507 57     57   101 my ($this, $t, $context) = @_;
508              
509 57 50       156 return [ $t ]
510             if ref($t);
511              
512 57 50 66     718 return []
      66        
513             unless ref($context) and
514             $context->can('nodeType') and
515             $context->nodeType == 1;
516              
517 55 50 33     257 $context ||= $document or return [];
518              
519 55         180 my ($ret, $done, $last, $nodeName) = ([$context], [], '', '');
520              
521 55   66     304 while ($t and $last ne $t) {
522 70         104 my $r = [];
523 70         103 $last = $t;
524              
525 70         221 $t = $this->_trim($t);
526              
527 70         102 my $foundToken = 0;
528              
529 70 100       237 if ($t =~ s/$quickChild//o) {
530 5         11 $nodeName = uc($1);
531 5         15 for (my $i = 0; $ret->[$i]; $i++) {
532 13         39 for (my $c = $ret->[$i]->firstChildRef; $c; $c = $c->nextSiblingRef) {
533 77 100 66     172 if ($c->nodeType == 1 and
      33        
534             (
535             $nodeName eq "*" or
536             uc($c->nodeName) eq $nodeName
537             )
538 67         207 ) { push @$r, $c }
539             }
540             }
541 5         6 $ret = $r;
542 5         14 $t = $this->_trim($t);
543 5         8 $foundToken = 1;
544             }
545             else {
546 65 100       232 if ($t =~ s/^([>+~])\s*(\w*)//) {
547 3         6 $r = [];
548              
549 3         6 my $merge = {};
550 3         10 $nodeName = uc($2);
551 3         7 my $m = $1;
552              
553 3         12 for (my ($j, $rl) = (0, scalar(@$ret)); $j < $rl; $j++) {
554 11 50 66     71 my $n = ($m eq "~" or $m eq "+")
555             ? $ret->[$j]->nextSiblingRef
556             : $ret->[$j]->firstChildRef;
557 11         27 for (; $n; $n = $n->nextSiblingRef) {
558 17 50       46 if ($n->nodeType == 1) {
559 17         22 my $id = $n;
560 17 50 66     67 last if ($m eq "~" and $merge->{$id});
561 17 100 66     64 if (not $nodeName or
562             uc($n->nodeName) eq $nodeName
563             ) {
564 11 100       29 $merge->{$id} = 1 if $m eq "~";
565 11         21 push @$r, $n;
566             }
567 17 100       73 last if $m eq "+";
568             }
569             }
570             }
571 3         5 $ret = $r;
572              
573 3         9 $t = $this->_trim($t);
574 3         9 $foundToken = 1;
575             }
576             }
577              
578 70         82 my $m;
579 70 100 100     366 if ($t and not $foundToken) {
580 62 50       142 if ($t =~ s/^,//) {
581 0 0       0 shift @$ret if $context == $ret->[0];
582              
583 0         0 $done = $this._merge($done, $ret);
584              
585 0         0 $r = $ret = [$context];
586              
587 0         0 $t = " $t";
588             }
589             else {
590 62 100       208 if ($t =~ s/$quickId//o) {
591 3         14 $m = [0, $2, $3, $1];
592             }
593             else {
594 59 50       417 if ($t =~ s/$quickClass//o) {
595 59         285 $m = [$1, $2, $3];
596             }
597             }
598 62         189 $m->[2] =~s/\\//g;
599              
600 62         100 my $elem = $ret->[-1];
601              
602 62         85 my $oid;
603 62 100 66     310 if ($m->[1] eq "#" and
      66        
604             $elem and
605             $elem->can('getElementById')
606             ) {
607 4         24 $oid = $elem->getElementById($m->[2]);
608 4 50 33     54 $ret = $r = (
609             $oid &&
610             (not $m->[3] or $this->_nodeName($oid, $m->[3]))
611             ) ? [$oid] : [];
612             }
613             else {
614 58         191 for (my $i = 0; $ret->[$i]; $i++) {
615 58 100 33     442 my $tag = ($m->[1] eq "#" and $m->[3])
    50 100        
616             ? $m->[3]
617             : ($m->[1] ne "" or $m->[0] eq "")
618             ? "*"
619             : $m->[2];
620 58         240 $r = $this->_merge(
621             $r,
622             $ret->[$i]->getElementsByTagName($tag)
623             );
624             }
625              
626 58 100       182 $r = $this->_classFilter($r, $m->[2])
627             if ($m->[1] eq ".");
628              
629 58 50       161 if ($m->[1] eq "#") {
630 0         0 my $tmp = [];
631              
632 0         0 for (my $i = 0; $r->[$i]; $i++) {
633 0 0       0 if ($r->[$i]->getAttribute("id") eq $m->[2]) {
634 0         0 $tmp = [ $r->[$i] ];
635 0         0 last;
636             }
637             }
638 0         0 $r = $tmp;
639             }
640              
641 58         108 $ret = $r;
642             }
643             }
644             }
645              
646 70 100       264 if ($t) {
647 46         136 my $val = $this->_filter($t, $r);
648 46         97 $ret = $r = $val->{r};
649 46         226 $t = $this->_trim($val->{t});
650             }
651             }
652             # $ret = [] if $t;
653 55 50       147 die "selector error: $t" if $t;
654              
655 55 50 66     488 shift(@$ret) if $ret and @$ret and $context == $ret->[0];
      66        
656              
657 55         159 $done = $this->_merge($done, $ret);
658              
659 55         338 return $done;
660             }
661              
662             sub _classFilter {
663 2     2   8 my ($this, $r, $m, $not) = @_;
664 2         10 $m = " $m ";
665 2         5 my $tmp = [];
666 2         12 for (my $i = 0; $r->[$i]; $i++) {
667 89         220 my $pass = CORE::index((" " . $r->[$i]->className . " "), $m) >= 0;
668 89 100 66     585 push @$tmp, $r->[$i]
      33        
      66        
669             if not $not and $pass or $not and not $pass;
670             }
671 2         9 return $tmp;
672             }
673              
674             sub _filter {
675 46     46   82 my ($this, $t, $r, $not) = @_;
676              
677 46         64 my $last = '';
678              
679 46   66     219 while ($t and $t ne $last) {
680 57         79 $last = $t;
681              
682 57         82 my ($p, $m) = ($parse);
683              
684 57         170 for (my $i = 0; $p->[$i]; $i++) {
685 129         158 my $re = $p->[$i];
686 129 100       928 if ($t =~ s/$re//) {
687 42         201 $m = [0, $1, $2, $3, $4, $5];
688 42         85 $m->[2] =~ s/\\//g;
689 42         67 last;
690             }
691             }
692              
693             last
694 57 100       144 if not $m;
695              
696 42 50 66     358 if ( $m->[1] eq ":" && $m->[2] eq "not") {
    50 33        
    100          
    50          
697 0 0       0 $r = ($m->[3] =~ m/$isSimple/o)
698             ? $this->_filter($m->[3], $r, 1)->{r}
699             : pQuery($r)->not($m->[3]);
700             }
701             elsif ($m->[1] eq ".") {
702 0         0 $r = $this->_classFilter($r, $m->[2], $not);
703             }
704             elsif ($m->[1] eq "[") {
705 14         32 my ($tmp, $type) = ([], $m->[3]);
706              
707 14         41 for (my ($i, $rl) = (0, scalar(@$r)); $i < $rl; $i++) {
708 844         1065 my $a = $r->[$i];
709 844   66     1552 my $z = $a->{($this->_props->{$m->[2]} || $m->[2])};
710              
711 844 100 100     3815 if (not defined $z or $m->[2] =~ m/href|src|selected/) {
712 839         2207 $z = $a->attr($m->[2]);
713             }
714              
715 844 100 66     23065 if (
    50          
    100          
716             ((
717             # Selects elements that have the specified attribute.
718             ($type eq "" and defined $z) or
719             # Selects elements that have the specified attribute with
720             # a value exactly equal to a certain value.
721             ($type eq "=" and defined $z and $z eq $m->[5]) or
722             # Select elements that either don’t have the specified attribute,
723             # or do have the specified attribute but not with a certain value.
724             ($type eq "!=" and (not defined $z or $z ne $m->[5])) or
725             # Selects elements that have the specified attribute with a
726             # value beginning exactly with a given string.
727             ($type eq "^=" and defined $z and $z =~ /\A\Q$m->[5]\E/) or
728             # Selects elements that have the specified attribute with
729             # a value ending exactly with a given string (case sensitive)
730             ($type eq '$=' and defined $z and $z =~ /\Q$m->[5]\E\z/) or
731             # Selects elements that have the specified attribute with
732             # a value containing the given substring.
733             ($type eq "*=" and defined $z and $z =~ /\Q$m->[5]\E/) or
734             # Selects elements that have the specified attribute with
735             # a value containing a given word, delimited by spaces.
736             ($type eq "~=" and defined $z and $z =~ /(?:\W|\A)\Q$m->[5]\E(?:\W|\z)/) or
737             # Selects elements that have the specified attribute with
738             # a value either equal to a given string or starting with
739             # that string followed by a hyphen (-).
740             ($type eq "|=" and defined $z and $z =~ /\A\Q$m->[5]\E(?:-|\z)/)
741             ) ? 1 : 0) ^ ($not ? 1 : 0)
742 36         116 ) { push @$tmp, $a }
743             }
744              
745 14         70 $r = $tmp;
746             }
747             elsif ($m->[1] eq ":" && $m->[2] eq "nth-child") {
748             # XXX - Finish porting this. Not sure how useful it is though...
749             }
750             else {
751 28         61 my $fn = $expr->{$m->[1]};
752 28 50       70 if (ref($fn) eq "HASH") {
753 28         63 $fn = $fn->{ $m->[2] };
754             }
755             # if ( typeof fn == "string" )
756             # fn = eval("false||function(a,i){return " + fn + ";}");
757 0     0   0 $fn = sub { 0 }
758 28 50       151 if ref($fn) ne 'CODE';
759             $r = $this->_grep(
760             $r,
761             sub {
762 697     697   1392 return &$fn($_[0], $_[1], $m, $r);
763             },
764 28         152 $not
765             );
766             }
767             }
768 46         180 return { r => $r, t => $t };
769             }
770              
771 0     0   0 sub _dir {
772             # XXX - Port me.
773             }
774              
775             sub _nth {
776 46     46   60 my ($this, $cur, $result, $dir, $elem) = @_;
777 46   50     88 $result ||= 1;
778 46         42 my $num = 0;
779              
780 46         84 for (; $cur; $cur = $cur->$dir) {
781 46 100 33     139 last if (ref($cur) and $cur->nodeType == 1 and ++$num == $result);
      66        
782             }
783              
784 46         328 return $cur;
785             }
786              
787 0     0   0 sub _sibling {
788             # XXX - Port me.
789             }
790              
791             sub _props {
792             return {
793 844     844   11493 for => "htmlFor",
794             class => "className",
795             # float => styleFloat,
796             # cssFloat => styleFloat,
797             # styleFloat => styleFloat,
798             innerHTML => "innerHTML",
799             className => "className",
800             value => "value",
801             disabled => "disabled",
802             checked => "checked",
803             readonly => "readOnly",
804             selected => "selected",
805             maxlength => "maxLength",
806             selectedIndex => "selectedIndex",
807             defaultValue => "defaultValue",
808             tagName => "tagName",
809             nodeName => "nodeName"
810             };
811             }
812              
813             #------------------------------------------------------------------------------#
814             # These methods need to go down here because they are Perl builtins.
815             #------------------------------------------------------------------------------#
816 8     8 1 28 sub length { return $#{$_[0]} + 1 }
  8         118  
817              
818             sub index {
819 3     3 1 7 my ($this, $elem) = @_;
820 3         4 my $ret = -1;
821             $this->each(sub {
822 9 100 100 9   76 $ret = shift
    100          
823             if (ref($_) && ref($elem)) ? ($_ == $elem) : ($_ eq $elem);
824 3         21 });
825 3         21 return $ret;
826             }
827              
828 0     0 0 0 sub not { # (selector)
829             # TODO - An anti-grep??
830             }
831              
832             #------------------------------------------------------------------------------#
833             # Helper functions (not methods)
834             #------------------------------------------------------------------------------#
835             sub _new_from_url {
836 0     0   0 require Encode;
837 0         0 my $this = shift;
838 0         0 my $url = shift;
839 0         0 my $response = $this->_web_get($url);
840 0 0       0 return $this
841             unless $response->is_success;
842 0         0 my $html = Encode::decode_utf8($response->content);
843 0         0 @$this = pQuery::DOM->fromHTML($html);
844 0         0 return $this;
845             }
846              
847             sub _web_get {
848 0     0   0 my $this = shift;
849 0         0 my $url = shift;
850 0         0 require LWP::UserAgent;
851 0   0     0 $lwp_user_agent ||= LWP::UserAgent->new;
852              
853 0         0 my $request = HTTP::Request->new(GET => $url);
854 0         0 my $response = $lwp_user_agent->request($request);
855 0         0 return $response;
856             }
857              
858             sub _to_text {
859 230     230   273 my ($elem, $text) = @_;
860 230 100       383 if (ref $elem) {
861 112         118 for my $child (@{$elem->{_content}}) {
  112         231  
862 135         267 _to_text($child, $text);
863             }
864             }
865             else {
866 118         343 $$text .= $elem;
867             }
868             }
869              
870             sub _find_elems {
871 0     0   0 my ($elem, $selector, $elems) = @_;
872 0 0       0 return unless ref $elem;
873              
874 0 0       0 if ($selector =~ /^\w+$/) {
875 0 0       0 if ($elem->{_tag} eq $selector) {
876 0         0 push @$elems, $elem;
877             }
878             }
879              
880 0         0 for my $child (@{$elem->{_content}}) {
  0         0  
881 0         0 _find_elems($child, $selector, $elems);
882             }
883             }
884              
885 266     266   10808 sub DESTROY { delete $my->{$_[0]}; }
886              
887             #------------------------------------------------------------------------------#
888             # THE AMAZING PQUERY
889             #------------------------------------------------------------------------------#
890             package PQUERY;
891              
892             sub new {
893 0     0     my $class = shift;
894 0           my $this = bless [], $class;
895 0           @$this = map 'pQuery'->new($_), @_;
896 0           return $this;
897             }
898              
899             sub AUTOLOAD {
900 0     0     (my $method = $PQUERY::AUTOLOAD) =~ s/.*:://;
901 0           my $this = shift;
902 0           my @args = @_;
903             $this->EACH(sub {
904 0     0     my $i = shift;
905 0           $this->[$i] = $_->$method(@args);
906 0           });
907 0           return $this;
908             }
909              
910             sub EACH {
911 0     0     my ($this, $sub) = @_;
912 0           my $index = 0;
913 0           &$sub($index++) for @$this;
914 0           return $this;
915             }
916              
917 0     0     sub DESTROY {}
918              
919             1;