Changeset 355
- Timestamp:
- 03/17/08 16:52:29 (2 months ago)
- Files:
-
- trunk/src/ingy/pQuery/Changes (modified) (1 diff)
- trunk/src/ingy/pQuery/MANIFEST (modified) (1 diff)
- trunk/src/ingy/pQuery/lib/pQuery.pm (modified) (11 diffs)
- trunk/src/ingy/pQuery/lib/pQuery/DOM.pm (modified) (2 diffs)
- trunk/src/ingy/pQuery/t/document1.html (modified) (1 diff)
- trunk/src/ingy/pQuery/t/dom.t (modified) (1 diff)
- trunk/src/ingy/pQuery/t/each.t (modified) (1 diff)
- trunk/src/ingy/pQuery/t/end.t (modified) (1 diff)
- trunk/src/ingy/pQuery/t/find.t (modified) (1 diff)
- trunk/src/ingy/pQuery/t/selectors.t (added)
- trunk/src/ingy/pQuery/t/spreadily.html (added)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/src/ingy/pQuery/Changes
r354 r355 1 --- 2 version: 0.05 3 date: Mon Mar 17 18:30:04 CDT 2008 4 changes: 5 - Implement most selectors 6 1 7 --- 2 8 version: 0.04 trunk/src/ingy/pQuery/MANIFEST
r354 r355 31 31 t/ideas 32 32 t/misc.t 33 t/selectors.t 34 t/spreadily.html 33 35 t/TestpQuery.pm 34 36 t/text.t trunk/src/ingy/pQuery/lib/pQuery.pm
r354 r355 9 9 use base 'Exporter'; 10 10 11 our $VERSION = '0.0 4';11 our $VERSION = '0.05'; 12 12 13 13 our @EXPORT = qw(pQuery PQUERY); … … 91 91 my $html = do {local $/; <FILE>}; 92 92 close FILE; 93 $selector = [ pQuery::DOM->fromHTML($html)];93 $selector = [$document = pQuery::DOM->fromHTML($html)]; 94 94 } 95 95 else { 96 $context ||= $document; 96 97 return pQuery($context)->find($selector); 97 98 } … … 107 108 108 109 sub size { return $#{$_[0]} + 1 } 109 sub length { return $#{$_[0]} + 1 }110 110 111 111 sub get { … … 136 136 137 137 # Not needed in Perl 138 # sub _setArray { 139 # my ($this, $elems) = @_; 140 # @$this = @$elems; 141 # return $this; 142 # } 138 # sub _setArray {} 143 139 144 140 sub each { … … 149 145 } 150 146 151 sub index {152 my ($this, $elem) = @_;153 my $ret = -1;154 $this->each(sub {155 $ret = shift156 if (ref($_) && ref($elem)) ? ($_ == $elem) : ($_ eq $elem);157 });158 return $ret;159 }160 161 147 sub attr { # (name, value, type) 162 148 # TODO - Get/set a named attribute … … 178 164 179 165 $text =~ s/\s+/ /g; 180 $text =~ s/ \s$//;166 $text =~ s/^\s+|\s+$//g; 181 167 182 168 return $text; … … 216 202 } 217 203 218 # XXX - Not really ported yet.219 204 sub find { 220 my $this = shift; 221 my $selector = shift or return; 205 my ($this, $selector) = @_; 222 206 my $elems = []; 223 $this->each(sub { 224 _find_elems($_, $selector, $elems);225 });226 return $this->pushStack($elems);227 } 228 229 # sub find { 230 # my ($this, $selector) = @_; 231 # my $elems = pQuery::map($this, sub { 232 233 207 208 for (my $i = 0; $i < @$this; $i++) { 209 push @$elems, @{$this->_find($selector, $this->[$i])}; 210 } 211 212 return $this->pushStack( 213 $selector =~ /[^+>] [^+>]/ 214 ? $this->_unique($elems) 215 : $elems 216 ) 217 } 234 218 235 219 sub clone { # (events) … … 239 223 sub filter { # (selector) 240 224 # TODO - A kind of grep 241 }242 243 sub not { # (selector)244 # TODO - An anti-grep??245 225 } 246 226 … … 325 305 my $elems; 326 306 return $this->each(sub { 327 if ( !defined $elems) {307 if (not defined $elems) { 328 308 $elems = $args; 329 309 @$elems = reverse @$elems … … 343 323 # sub isXMLdoc {} 344 324 # sub globalEval {} 345 # sub nodeName {} 325 326 sub _nodeName { 327 my ($this, $elem, $name) = @_; 328 return $elem->nodeName && 329 uc($elem->nodeName) eq uc($name); 330 } 331 332 346 333 # sub cache {} 347 334 # sub data {} … … 355 342 # sub clean {} 356 343 # sub attr {} 357 # sub trim {} 344 345 sub _trim { 346 (my $string = $_[1]) =~ s/^\s+|\s+$//g; 347 return $string; 348 } 349 358 350 # sub makeArray {} 359 351 # sub inArray {} 360 # sub merge {} 361 # sub unique {} 362 # sub grep {} 352 353 sub _merge { 354 push @{$_[1]}, @{$_[2]}; 355 return $_[1]; 356 } 357 358 sub _unique { 359 my $seen = {}; 360 return [ grep {not $seen->{$_}++} @{$_[1]} ]; 361 } 362 363 sub _grep { 364 my ($this, $elems, $callback, $inv) = @_; 365 my $ret = []; 366 367 for (my ($i, $length) = (0, scalar(@$elems)); $i < $length; $i++) { 368 push @$ret, $elems->[$i] 369 if (not $inv and &$callback($elems->[$i], $i)) or 370 ($inv and not &$callback($elems->[$i], $i)); 371 } 372 373 return $ret; 374 } 375 363 376 # sub map {} 364 # sub unique {}365 377 366 378 ################################################################################ 367 379 # Selector functions 368 380 ################################################################################ 369 381 my $chars = '(?:[\w\x{128}-\x{FFFF}_-]|\\.)'; 382 my $quickChild = qr/^>\s*($chars+)/; 383 my $quickId = qr/^($chars+)(#)($chars+)/; 384 my $quickClass = qr/^(([#.]?)($chars*))/; 385 sub _find { 386 my ($this, $t, $context) = @_; 387 388 return [ $t ] 389 if ref($t); 390 391 return [] 392 unless ref($context) and 393 $context->can('nodeType') and 394 $context->nodeType == 1; 395 396 $context ||= $document or return []; 397 398 my ($ret, $done, $last, $nodeName) = ([$context], [], '', ''); 399 400 while ($t and $last ne $t) { 401 my $r = []; 402 $last = $t; 403 404 $t = $this->_trim($t); 405 406 my $foundToken = 0; 407 408 my $re = $quickChild; 409 410 if ($t =~ $re) { 411 $nodeName = uc($1); 412 for (my $i = 0; $ret->[$i]; $i++) { 413 for (my $c = $ret->[$i]; $c; $c = $c->nextSiblingRef) { 414 if ($c->nodeType == 1 and 415 ( 416 $nodeName eq "*" or 417 uc($c->nodeName) eq $nodeName 418 ) 419 ) { push @$r, $c } 420 } 421 } 422 } 423 else { 424 if ($t =~ s/^([>+~])\s*(\w*)//) { 425 $r = []; 426 427 my $merge = {}; 428 $nodeName = uc($2); 429 my $m = $1; 430 431 for (my ($j, $rl) = (0, scalar(@$ret)); $j < $rl; $j++) { 432 my $n = ($m eq "~" or $m eq "+") 433 ? $ret->[$j]->nextSiblingRef 434 : $ret->[$j]->firstChild; 435 for (; $n; $n = $n->nextSiblingRef) { 436 if ($n->nodeType == 1) { 437 my $id = jQuery->data($n); 438 last if ($m eq "~" and $merge->{$id}); 439 if (not $nodeName or 440 uc($n->nodeName) eq $nodeName 441 ) { 442 $merge->{$id} = 1 if $m eq "~"; 443 push @$r, $n; 444 } 445 last if $m eq "+"; 446 } 447 } 448 } 449 $ret = $r; 450 451 $t = $this.trim($t); 452 $foundToken = 1; 453 } 454 } 455 456 my $m; 457 if ($t and not $foundToken) { 458 if ($t =~ s/^,//) { 459 shift @$ret if $context == $ret->[0]; 460 461 $done = $this._merge($done, $ret); 462 463 $r = $ret = [$context]; 464 465 $t = " $t"; 466 } 467 else { 468 if ($t =~ s/$quickId//) { 469 $m = [0, $2, $3, $1]; 470 } 471 else { 472 if ($t =~ s/$quickClass//) { 473 $m = [$1, $2, $3]; 474 } 475 } 476 $m->[2] =~s/\\//g; 477 478 my $elem = $ret->[-1]; 479 480 my $oid; 481 if ($m->[1] eq "#" and 482 $elem and 483 $elem->can('getElementById') 484 ) { 485 $oid = $elem->getElementById($m->[2]); 486 $ret = $r = ( 487 $oid && 488 (not $m->[3] or $this->_nodeName($oid, $m->[3])) 489 ) ? [$oid] : []; 490 } 491 else { 492 for (my $i = 0; $ret->[$i]; $i++) { 493 my $tag = ($m->[1] eq "#" and $m->[3]) 494 ? $m->[3] 495 : ($m->[1] ne "" or $m->[0] eq "") 496 ? "*" 497 : $m->[2]; 498 $r = $this->_merge( 499 $r, 500 $ret->[$i]->getElementsByTagName($tag) 501 ); 502 } 503 504 $r = $this->_classFilter($r, $m->[2]) 505 if ($m->[1] eq "."); 506 507 if ($m->[1] eq "#") { 508 my $tmp = []; 509 510 for (my $i = 0; $r->[$i]; $i++) { 511 if ($r->[$i]->getAttribute("id") eq $m->[2]) { 512 $tmp = [ $r->[$i] ]; 513 last; 514 } 515 } 516 $r = $tmp; 517 } 518 519 $ret = $r; 520 } 521 } 522 } 523 524 if ($t) { 525 my $val = $this->_filter($t, $r); 526 $ret = $r = $val->{r}; 527 $t = $this->_trim($val->{t}); 528 } 529 } 530 # $ret = [] if $t; 531 die "selector error" if $t; 532 533 shift(@$ret) if $ret and @$ret and $context == $ret->[0]; 534 535 $done = $this->_merge($done, $ret); 536 537 return $done; 538 } 539 540 sub _classFilter { 541 my ($this, $r, $m, $not) = @_; 542 $m = " $m "; 543 my $tmp = []; 544 for (my $i = 0; $r->[$i]; $i++) { 545 my $pass = CORE::index((" " . $r->[$i]->className . " "), $m) >= 0; 546 push @$tmp, $r->[$i] 547 if not $not and $pass or $not and not $pass; 548 } 549 return $tmp; 550 } 551 552 # The regular expressions that power the parsing engine 553 my $parse = [ 554 # Match: [@value='test'], [@foo] 555 qr/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/, 556 557 # Match: :contains('foo') 558 qr/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/, 559 560 # Match: :even, :last-chlid, #id, .class 561 qr/^([:.#]*)($chars+)/, 562 ]; 563 564 my $expr = { 565 ":" => { 566 lt => sub { return $_[1] < $_[2][3] }, 567 gt => sub { return $_[1] > $_[2][3] }, 568 eq => sub { return $_[2][3] == $_[1] }, 569 first => sub { return $_[1] == 0 }, 570 last => sub { return $_[1] == $#{$_[3]} }, 571 even => sub { return $_[1] % 2 == 0 }, 572 odd => sub { return $_[1] % 2 }, 573 574 contains => sub { index(pQuery($_[0])->text, $_[2][3]) >= 0 }, 575 }, 576 }; 577 578 sub _filter { 579 my ($this, $t, $r, $not) = @_; 580 581 my $last = ''; 582 583 while ($t and $t ne $last) { 584 $last = $t; 585 586 my ($p, $m) = ($parse); 587 588 for (my $i = 0; $p->[$i]; $i++) { 589 my $re = $p->[$i]; 590 if ($t =~ s/$re//) { 591 $m = [0, $1, $2, $3, $4, $5]; 592 $m->[2] =~ s/\\//g; 593 last; 594 } 595 } 596 597 last 598 if not $m; 599 600 if ( $m->[1] eq ":" && $m->[2] eq "not") { 601 $r = ($m->[3] =~ $isSimple) 602 ? $this->_filter($m->[3], $r, 1)->{r} 603 : pQuery($r)->not($m->[3]); 604 } 605 elsif ($m->[1] eq ".") { 606 $r = $this->_classFilter($r, $m->[2], $not); 607 } 608 elsif ($m->[1] eq "[") { 609 my ($tmp, $type) = ([], $m->[3]); 610 611 for (my ($i, $rl) = (0, scalar(@$r)); $i < $rl; $i++) { 612 my ($a, $z) = ($r->[$i], $this->_props->[$m->[2]] || $m->[2]); 613 614 if (not defined $z or $m->[2] =~ /href|src|selected/) { 615 $z = $this->attr($a, $m->[2]) || ''; 616 } 617 618 if ( 619 ( 620 $type eq "" and $z or 621 $type eq "=" and $z eq $m->[5] or 622 $type eq "!=" and $z ne $m->[5] or 623 $type eq "^=" and not index($z, $m->[5]) or 624 $type eq '$=' and substr($z, (0-length($m->[5]))) or 625 ($type eq "*=" or $type eq "~=") and 626 index($z, $m->[5]) >= 0 627 ) ^ $not 628 ) { push @$tmp, $a } 629 } 630 631 $r = $tmp; 632 } 633 elsif ($m->[1] eq ":" && $m->[2] eq "nth-child") { 634 # XXX - Finish porting this! 635 } 636 else { 637 my $fn = $expr->{$m->[1]}; 638 if (ref($fn) eq "HASH") { 639 $fn = $fn->{ $m->[2] }; 640 } 641 # if ( typeof fn == "string" ) 642 # fn = eval("false||function(a,i){return " + fn + ";}"); 643 $fn = sub { 0 } 644 if ref($fn) ne 'CODE'; 645 $r = $this->_grep( 646 $r, 647 sub { 648 return &$fn($_[0], $_[1], $m, $r); 649 }, 650 $not 651 ); 652 } 653 } 654 return { r => $r, t => $t }; 655 } 656 657 ################################################################################ 658 # These methods need to go down here because they are Perl builtins. 659 ################################################################################ 660 sub length { return $#{$_[0]} + 1 } 661 662 sub index { 663 my ($this, $elem) = @_; 664 my $ret = -1; 665 $this->each(sub { 666 $ret = shift 667 if (ref($_) && ref($elem)) ? ($_ == $elem) : ($_ eq $elem); 668 }); 669 return $ret; 670 } 671 672 sub not { # (selector) 673 # TODO - An anti-grep?? 674 } 370 675 371 676 ################################################################################ trunk/src/ingy/pQuery/lib/pQuery/DOM.pm
r351 r355 159 159 my ($self, $tag) = @_; 160 160 my $found = []; 161 _find($self, $found, sub { $_->{_tag} eq $tag });162 return wantarray ? @$found : $found->[0];161 _find($self, $found, sub { $_->{_tag} eq $tag or $tag eq "*" }); 162 return $found; 163 163 } 164 164 … … 213 213 return $_[0]->setAttribute(class => $_[1]); 214 214 } 215 $_[0]->getAttribute("class"); 215 my $className = $_[0]->getAttribute("class"); 216 return defined $className 217 ? $className 218 : ''; 216 219 } 217 220 trunk/src/ingy/pQuery/t/document1.html
r328 r355 6 6 <h2>Welcome to the sample document</h2> 7 7 <p id="text" class="para"> 8 This is an example8 This is an <i>example</i> 9 9 paragraph. 10 10 </p> trunk/src/ingy/pQuery/t/dom.t
r354 r355 32 32 is $span->tagName, "SPAN", 'tagName works'; 33 33 34 my @spans = $dom->getElementsByTagName('span');35 is scalar(@ spans), 3, "Found 3 spans";36 is $spans [0]->innerHTML, "Foo", '1st value is correct';37 is $spans [1]->innerHTML, "Bar", '2nd value is correct';34 my $spans = $dom->getElementsByTagName('span'); 35 is scalar(@$spans), 3, "Found 3 spans"; 36 is $spans->[0]->innerHTML, "Foo", '1st value is correct'; 37 is $spans->[1]->innerHTML, "Bar", '2nd value is correct'; 38 38 39 39 $span->setAttribute('Foo', 'Bar'); trunk/src/ingy/pQuery/t/each.t
r354 r355 3 3 use pQuery; 4 4 5 open FILE, 't/document1.html' or die $!;6 my $html = do {local $/; <FILE>};7 close FILE;8 chomp $html;9 10 5 my $output = ''; 11 pQuery( $html)->find('li')->each(sub {6 pQuery('t/document1.html')->find('li')->each(sub { 12 7 my $i = shift; 13 8 my $text = pQuery($_)->text(); trunk/src/ingy/pQuery/t/end.t
r353 r355 1 use t::TestpQuery tests => 3;1 use t::TestpQuery tests => 2; 2 2 3 3 use pQuery; trunk/src/ingy/pQuery/t/find.t
r354 r355 1 use t::TestpQuery tests => 2;1 use t::TestpQuery tests => 6; 2 2 3 3 use pQuery; 4 4 5 open FILE, 't/document1.html' or die $!; 6 my $html = do {local $/; <FILE>}; 7 close FILE; 8 chomp $html; 5 my $pquery; 9 6 10 my $pquery = pQuery($html)->find('li');7 $pquery = pQuery('t/document1.html')->find('li'); 11 8 12 9 is scalar(@$pquery), 5, 'Found 5 LI elements'; 13 10 14 $pquery = pQuery( $html)->find('xxx');11 $pquery = pQuery('t/document1.html')->find('xxx'); 15 12 16 13 is scalar(@$pquery), 0, 'Found 0 XXX elements'; 14 15 $pquery = pQuery('t/document1.html'); 16 17 $pquery->find('#text')->each(sub { 18 is $_->nodeName, 'P', 'find by id works'; 19 }); 20 $pquery->find('.para')->each(sub { 21 is $_->nodeName, 'P', 'find by class works'; 22 }); 23 24 is $pquery->find('body p i')->text, 'example', 'multiple nested tags works'; 25 26 is $pquery->find('li:eq(4)')->text, 'three', ':eq works';
