PageRenderTime 64ms CodeModel.GetById 2ms app.highlight 55ms RepoModel.GetById 1ms app.codeStats 1ms

/sites/all/modules/contrib/querypath/QueryPath/CssEventHandler.php

https://bitbucket.org/antisocnet/drupal
PHP | 1095 lines | 981 code | 114 blank | 0 comment | 65 complexity | 4eb47cda81bf199a0bc82b305ed8d2da MD5 | raw file
   1<?php
   2
   3
   4
   5require_once 'CssParser.php';
   6
   7
   8class QueryPathCssEventHandler implements CssEventHandler {
   9  protected $dom = NULL; 
  10  protected $matches = NULL; 
  11  protected $alreadyMatched = NULL; 
  12  protected $findAnyElement = TRUE;
  13  
  14  
  15  
  16  public function __construct($dom) {
  17    $this->alreadyMatched = new SplObjectStorage();
  18    $matches = new SplObjectStorage();
  19    
  20    
  21    if (is_array($dom) || $dom instanceof SplObjectStorage) {
  22      
  23      foreach($dom as $item) {
  24        if ($item instanceof DOMNode && $item->nodeType == XML_ELEMENT_NODE) {
  25          
  26          $matches->attach($item);
  27        }
  28      }
  29      
  30      if ($matches->count() > 0) {
  31        $matches->rewind();
  32        $this->dom = $matches->current();
  33      }
  34      else {
  35        
  36        $this->dom = NULL;
  37      }
  38      $this->matches = $matches;
  39    }
  40    
  41    elseif ($dom instanceof DOMDocument) {
  42      $this->dom = $dom->documentElement;
  43      $matches->attach($dom->documentElement);
  44    }
  45    
  46    elseif ($dom instanceof DOMElement) {
  47      $this->dom = $dom;
  48      $matches->attach($dom);
  49    }
  50    
  51    elseif ($dom instanceof DOMNodeList) {
  52      $a = array(); 
  53      foreach ($dom as $item) {
  54        if ($item->nodeType == XML_ELEMENT_NODE) {
  55          $matches->attach($item);
  56          $a[] = $item; 
  57        }
  58      }
  59      $this->dom = $a;
  60    }
  61    
  62    
  63    else {
  64      throw new Exception("Unhandled type: " . get_class($dom));
  65    }
  66    $this->matches = $matches;
  67  }
  68  
  69  
  70  public function find($filter) {
  71    $parser = new CssParser($filter, $this);
  72    $parser->parse();
  73    return $this;
  74  }
  75  
  76  
  77  public function getMatches() {
  78    
  79    $result = new SplObjectStorage();
  80    foreach($this->alreadyMatched as $m) $result->attach($m);
  81    foreach($this->matches as $m) $result->attach($m);
  82    return $result;
  83  }
  84  
  85  
  86  public function elementID($id) {
  87    $found = new SplObjectStorage();
  88    $matches = $this->candidateList();
  89    foreach ($matches as $item) {
  90      
  91      if ($item->hasAttribute('id') && $item->getAttribute('id') === $id) {
  92        $found->attach($item);
  93        break;
  94      }
  95    }
  96    $this->matches = $found;
  97    $this->findAnyElement = FALSE;
  98  }
  99  
 100  
 101  public function element($name) {
 102    $matches = $this->candidateList();
 103    $this->findAnyElement = FALSE;
 104    $found = new SplObjectStorage();
 105    foreach ($matches as $item) {
 106      
 107      
 108      
 109      if ($item->tagName == $name) {
 110        $found->attach($item);
 111      }
 112      
 113      
 114      
 115    }
 116    
 117    $this->matches = $found;
 118  }
 119  
 120  
 121  public function elementNS($lname, $namespace = NULL) {
 122    $this->findAnyElement = FALSE;
 123    $found = new SplObjectStorage();
 124    $matches = $this->candidateList();
 125    foreach ($matches as $item) {
 126      
 127      
 128      
 129      
 130      
 131      
 132      
 133      $nsuri = $this->dom->lookupNamespaceURI($namespace);
 134      
 135      
 136      
 137      
 138      
 139      
 140      if ($item instanceof DOMNode 
 141          && $item->namespaceURI == $nsuri 
 142          && $lname == $item->localName) {
 143        $found->attach($item);
 144      }
 145      
 146      if (!empty($nsuri)) {
 147        $nl = $item->getElementsByTagNameNS($nsuri, $lname);
 148        
 149        
 150        if (!empty($nl)) $this->attachNodeList($nl, $found);
 151      }
 152      else {
 153        
 154        $nl = $item->getElementsByTagName($lname);
 155        $tagname = $namespace . ':' . $lname;
 156        $nsmatches = array();
 157        foreach ($nl as $node) {
 158          if ($node->tagName == $tagname) {
 159            
 160            $found->attach($node);
 161          }
 162        }
 163        
 164        
 165      }
 166    }
 167    $this->matches = $found;
 168  }
 169  
 170  public function anyElement() {
 171    $found = new SplObjectStorage();
 172    
 173    $matches = $this->candidateList();
 174    foreach ($matches as $item) {
 175      $found->attach($item); 
 176      
 177      
 178      
 179      
 180    }
 181    
 182    $this->matches = $found;
 183    $this->findAnyElement = FALSE;
 184  }
 185  public function anyElementInNS($ns) {
 186    
 187    $nsuri = $this->dom->lookupNamespaceURI($ns);
 188    $found = new SplObjectStorage();
 189    if (!empty($nsuri)) {
 190      $matches = $this->candidateList();
 191      foreach ($matches as $item) {
 192        if ($item instanceOf DOMNode && $nsuri == $item->namespaceURI) {
 193          $found->attach($item);
 194        }
 195      }
 196    }
 197    $this->matches = $found;
 198    $this->findAnyElement = FALSE;
 199  }
 200  public function elementClass($name) {
 201    
 202    $found = new SplObjectStorage();
 203    $matches = $this->candidateList();
 204    foreach ($matches as $item) {
 205      if ($item->hasAttribute('class')) {
 206        $classes = explode(' ', $item->getAttribute('class'));
 207        if (in_array($name, $classes)) $found->attach($item);
 208      }
 209    }
 210    
 211    $this->matches = $found;
 212    $this->findAnyElement = FALSE;
 213  }
 214  
 215  public function attribute($name, $value = NULL, $operation = CssEventHandler::isExactly) {
 216    $found = new SplObjectStorage();
 217    $matches = $this->candidateList();
 218    foreach ($matches as $item) {
 219      if ($item->hasAttribute($name)) {
 220        if (isset($value)) {
 221          
 222          if($this->attrValMatches($value, $item->getAttribute($name), $operation)) {
 223            $found->attach($item);
 224          }
 225        }
 226        else {
 227          
 228          $found->attach($item);
 229        }
 230      }
 231    }
 232    $this->matches = $found; 
 233    $this->findAnyElement = FALSE;
 234  }
 235
 236  
 237  protected function searchForAttr($name, $value = NULL) {
 238    $found = new SplObjectStorage();
 239    $matches = $this->candidateList();
 240    foreach ($matches as $candidate) {
 241      if ($candidate->hasAttribute($name)) {
 242        
 243        if (isset($value) && $value == $candidate->getAttribute($name)) {
 244          $found->attach($candidate);
 245        }
 246        
 247        else {
 248          $found->attach($candidate);
 249        }
 250      }
 251    }
 252    
 253    $this->matches = $found;
 254  }
 255  
 256  public function attributeNS($lname, $ns, $value = NULL, $operation = CssEventHandler::isExactly) {
 257    $matches = $this->candidateList();
 258    $found = new SplObjectStorage();
 259    if (count($matches) == 0) {
 260      $this->matches = $found;
 261      return;
 262    }
 263    
 264    
 265    
 266    $matches->rewind();
 267    $e = $matches->current();
 268    $uri = $e->lookupNamespaceURI($ns);
 269    
 270    foreach ($matches as $item) {
 271      
 272      
 273      
 274      if ($item->hasAttributeNS($uri, $lname)) {
 275        if (isset($value)) {
 276          if ($this->attrValMatches($value, $item->getAttributeNS($uri, $lname), $operation)) {
 277            $found->attach($item);
 278          }
 279        }
 280        else {
 281          $found->attach($item);
 282        }
 283      }
 284    }
 285    $this->matches = $found;
 286    $this->findAnyElement = FALSE;
 287  }
 288  
 289  
 290  public function pseudoClass($name, $value = NULL) {
 291    $name = strtolower($name);
 292    
 293    switch($name) {
 294      case 'visited':
 295      case 'hover':
 296      case 'active':
 297      case 'focus':
 298      case 'animated': 
 299      case 'visible':
 300      case 'hidden':
 301        
 302      case 'target':
 303        
 304        $this->matches = new SplObjectStorage();
 305        break;
 306      case 'indeterminate':
 307        
 308        
 309        throw new NotImplementedException(":indeterminate is not implemented.");
 310        break;
 311      case 'lang':
 312        
 313        if (!isset($value)) {
 314          throw new NotImplementedException("No handler for lang pseudoclass without value.");
 315        }
 316        $this->lang($value);
 317        break;
 318      case 'link':
 319        $this->searchForAttr('href');
 320        break;
 321      case 'root':
 322        $found = new SplObjectStorage();
 323        if (empty($this->dom)) {
 324          $this->matches = $found;
 325        }
 326        elseif (is_array($this->dom)) {
 327          $found->attach($this->dom[0]->ownerDocument->documentElement);
 328          $this->matches = $found;
 329        }
 330        elseif ($this->dom instanceof DOMNode) {
 331          $found->attach($this->dom->ownerDocument->documentElement);
 332          $this->matches = $found;
 333        }
 334        elseif ($this->dom instanceof DOMNodeList && $this->dom->length > 0) {
 335          $found->attach($this->dom->item(0)->ownerDocument->documentElement);
 336          $this->matches = $found;
 337        }
 338        else {
 339          
 340          $found->attach($this->dom);
 341          $this->matches = $found;
 342        }
 343        break;
 344      
 345      
 346      
 347      case 'x-root':
 348      case 'x-reset':
 349        $this->matches = new SplObjectStorage();
 350        $this->matches->attach($this->dom);
 351        break;        
 352      
 353      
 354      
 355      case 'even':
 356        $this->nthChild(2, 0);
 357        break;
 358      case 'odd':
 359        $this->nthChild(2, 1);
 360        break;
 361      
 362      
 363      case 'nth-child':
 364        list($aVal, $bVal) = $this->parseAnB($value);
 365        $this->nthChild($aVal, $bVal);
 366        break;
 367      case 'nth-last-child':
 368        list($aVal, $bVal) = $this->parseAnB($value);
 369        $this->nthLastChild($aVal, $bVal);
 370        break;
 371      case 'nth-of-type':
 372        list($aVal, $bVal) = $this->parseAnB($value);
 373        $this->nthOfTypeChild($aVal, $bVal);
 374        break;
 375      case 'nth-last-of-type':
 376        list($aVal, $bVal) = $this->parseAnB($value);
 377        $this->nthLastOfTypeChild($aVal, $bVal);
 378        break;
 379      case 'first-child':
 380        $this->nthChild(0, 1);
 381        break;
 382      case 'last-child':
 383        $this->nthLastChild(0, 1);
 384        break;
 385      case 'first-of-type':
 386        $this->firstOfType();
 387        break;
 388      case 'last-of-type':
 389        $this->lastOfType();
 390        break;
 391      case 'only-child':
 392        $this->onlyChild();
 393        break;
 394      case 'only-of-type':
 395        $this->onlyOfType();
 396        break;
 397      case 'empty':
 398        $this->emptyElement();
 399        break;  
 400      case 'not':
 401        if (empty($value)) {
 402          throw new CssParseException(":not() requires a value.");
 403        }
 404        $this->not($value);
 405        break;
 406      
 407      case 'lt':
 408      case 'gt':
 409      case 'nth':
 410      case 'eq':
 411      case 'first':
 412      case 'last':
 413      
 414      
 415        $this->getByPosition($name, $value);  
 416        break;
 417      case 'parent':
 418        $matches = $this->candidateList();
 419        $found = new SplObjectStorage();
 420        foreach ($matches as $match) {
 421          if (!empty($match->firstChild)) {
 422            $found->attach($match);
 423          }
 424        }
 425        $this->matches = $found;
 426        break;
 427      
 428      case 'enabled':  
 429      case 'disabled':  
 430      case 'checked':  
 431        $this->attribute($name);
 432        break;
 433      case 'text':
 434      case 'radio':
 435      case 'checkbox':
 436      case 'file':
 437      case 'password':
 438      case 'submit':
 439      case 'image':
 440      case 'reset':
 441      case 'button':
 442      case 'submit':
 443        $this->attribute('type', $name);
 444        break;
 445
 446      case 'header':
 447        $matches = $this->candidateList();
 448        $found = new SplObjectStorage();
 449        foreach ($matches as $item) {
 450          $tag = $item->tagName;
 451          $f = strtolower(substr($tag, 0, 1));
 452          if ($f == 'h' && strlen($tag) == 2 && ctype_digit(substr($tag, 1, 1))) {
 453            $found->attach($item);
 454          }
 455        }
 456        $this->matches = $found;
 457        break;
 458      case 'has':
 459        $this->has($value);
 460        break;
 461      
 462      
 463      case 'contains':
 464        $value = $this->removeQuotes($value);
 465    
 466        $matches = $this->candidateList();
 467        $found = new SplObjectStorage();
 468        foreach ($matches as $item) {
 469          if (strpos($item->textContent, $value) !== FALSE) {
 470            $found->attach($item);
 471          }
 472        }
 473        $this->matches = $found;
 474        break;
 475        
 476      
 477      case 'contains-exactly':
 478        $value = $this->removeQuotes($value);
 479      
 480        $matches = $this->candidateList();
 481        $found = new SplObjectStorage();
 482        foreach ($matches as $item) {
 483          if ($item->textContent == $value) {
 484            $found->attach($item);
 485          }
 486        }
 487        $this->matches = $found;
 488        break;
 489      default:
 490        throw new CssParseException("Unknown Pseudo-Class: " . $name);
 491    }
 492    $this->findAnyElement = FALSE;
 493  }
 494  
 495  
 496  private function removeQuotes($str) {
 497    $f = substr($str, 0, 1);
 498    $l = substr($str, -1);
 499    if ($f === $l && ($f == '"' || $f == "'")) {
 500      $str = substr($str, 1, -1);
 501    }
 502    return $str;
 503  }
 504  
 505  
 506  private function getByPosition($operator, $pos) {
 507    $matches = $this->candidateList();
 508    $found = new SplObjectStorage();
 509    if ($matches->count() == 0) {
 510      return;
 511    }
 512    
 513    switch ($operator) {
 514      case 'nth':
 515      case 'eq':
 516        if ($matches->count() >= $pos) {
 517          
 518          foreach ($matches as $match) {
 519            
 520            if ($matches->key() + 1 == $pos) {
 521              $found->attach($match);
 522              break;
 523            }
 524          }
 525        }
 526        break;
 527      case 'first':
 528        if ($matches->count() > 0) {
 529          $matches->rewind(); 
 530          $found->attach($matches->current());
 531        }
 532        break;
 533      case 'last':
 534        if ($matches->count() > 0) {
 535          
 536          
 537          foreach ($matches as $item) {};
 538         
 539          $found->attach($item);
 540        }
 541        break;
 542      
 543      
 544      
 545      
 546      
 547      
 548      
 549      
 550      
 551      
 552      
 553      
 554      
 555      
 556      case 'lt':
 557        $i = 0;
 558        foreach ($matches as $item) {
 559          if (++$i < $pos) {
 560            $found->attach($item);
 561          }
 562        }
 563        break;
 564      case 'gt':
 565        $i = 0;
 566        foreach ($matches as $item) {
 567          if (++$i > $pos) {
 568            $found->attach($item);
 569          }
 570        }
 571        break;
 572    }
 573    
 574    $this->matches = $found;
 575  }
 576  
 577  
 578  protected function parseAnB($rule) {
 579    if ($rule == 'even') {
 580      return array(2, 0);
 581    }
 582    elseif ($rule == 'odd') {
 583      return array(2, 1);
 584    }
 585    elseif ($rule == 'n') {
 586      return array(1, 0);
 587    }
 588    elseif (is_numeric($rule)) {
 589      return array(0, (int)$rule);
 590    }
 591    
 592    $rule = explode('n', $rule);
 593    if (count($rule) == 0) {
 594      throw new CssParseException("nth-child value is invalid.");
 595    }
 596    $aVal = (int)trim($rule[0]);
 597    $bVal = !empty($rule[1]) ? (int)trim($rule[1]) : 0;
 598    return array($aVal, $bVal);
 599  }
 600  
 601  
 602  protected function nthChild($groupSize, $elementInGroup, $lastChild = FALSE) {
 603    
 604    
 605    
 606    $parents = new SplObjectStorage();
 607    $matches = new SplObjectStorage();
 608    
 609    $i = 0;
 610    foreach ($this->matches as $item) {
 611      $parent = $item->parentNode;
 612      
 613      
 614      
 615      
 616      if (!$parents->contains($parent)) {
 617        
 618        $c = 0;
 619        foreach ($parent->childNodes as $child) {
 620          
 621          
 622          
 623          
 624          
 625          if ($child->nodeType == XML_ELEMENT_NODE && ($this->findAnyElement || $child->tagName == $item->tagName)) {
 626            
 627            $child->nodeIndex = ++$c;
 628          }
 629        }
 630        
 631        $parent->numElements = $c;
 632        $parents->attach($parent);
 633      }
 634      
 635      
 636      
 637      if ($lastChild) {
 638        $indexToMatch = $item->parentNode->numElements  - $item->nodeIndex + 1;
 639      }
 640      
 641      else {
 642        $indexToMatch = $item->nodeIndex;
 643      }
 644      
 645      
 646      if ($groupSize == 0) {
 647        if ($indexToMatch == $elementInGroup) 
 648          $matches->attach($item);
 649      }
 650      
 651      
 652      else {
 653        if (($indexToMatch - $elementInGroup) % $groupSize == 0 
 654            && ($indexToMatch - $elementInGroup) / $groupSize >= 0) {
 655          $matches->attach($item);
 656        }
 657      }
 658      
 659      
 660      ++$i;
 661    }
 662    $this->matches = $matches;
 663  }
 664  
 665  
 666  
 667  
 668  protected function nthLastChild($groupSize, $elementInGroup) {
 669    
 670    $this->nthChild($groupSize, $elementInGroup, TRUE);
 671  }
 672  
 673  
 674  
 675   
 676  
 677  
 678  protected function nthOfTypeChild($groupSize, $elementInGroup, $lastChild) {
 679    
 680    
 681    
 682    $parents = new SplObjectStorage();
 683    $matches = new SplObjectStorage();
 684    
 685    $i = 0;
 686    foreach ($this->matches as $item) {
 687      $parent = $item->parentNode;
 688      
 689      
 690      
 691      
 692      if (!$parents->contains($parent)) {
 693        
 694        $c = 0;
 695        foreach ($parent->childNodes as $child) {
 696          
 697          
 698          if ($child->nodeType == XML_ELEMENT_NODE && $child->tagName == $item->tagName) {
 699            
 700            $child->nodeIndex = ++$c;
 701          }
 702        }
 703        
 704        $parent->numElements = $c;
 705        $parents->attach($parent);
 706      }
 707      
 708      
 709      
 710      if ($lastChild) {
 711        $indexToMatch = $item->parentNode->numElements  - $item->nodeIndex + 1;
 712      }
 713      
 714      else {
 715        $indexToMatch = $item->nodeIndex;
 716      }
 717      
 718      
 719      if ($groupSize == 0) {
 720        if ($indexToMatch == $elementInGroup) 
 721          $matches->attach($item);
 722      }
 723      
 724      
 725      else {
 726        if (($indexToMatch - $elementInGroup) % $groupSize == 0 
 727            && ($indexToMatch - $elementInGroup) / $groupSize >= 0) {
 728          $matches->attach($item);
 729        }
 730      }
 731      
 732      
 733      ++$i;
 734    }
 735    $this->matches = $matches;
 736  }
 737  
 738  
 739  protected function nthLastOfTypeChild($groupSize, $elementInGroup) {
 740    $this->nthOfTypeChild($groupSize, $elementInGroup, TRUE);    
 741  }
 742  
 743  
 744  protected function lang($value) {
 745    
 746    
 747    
 748    $operator = (strpos($value, '-') !== FALSE) ? self::isExactly : self::containsWithHyphen;
 749    
 750    $orig = $this->matches;
 751    $origDepth = $this->findAnyElement;
 752    
 753    
 754    $this->attribute('lang', $value, $operator);
 755    $lang = $this->matches; 
 756    
 757    
 758    $this->matches = $orig;
 759    $this->findAnyElement = $origDepth;
 760    
 761    
 762    $this->attributeNS('lang', 'xml', $value, $operator);
 763    
 764    
 765    
 766    
 767    
 768    
 769    foreach ($this->matches as $added) $lang->attach($added);
 770    $this->matches = $lang;
 771  }
 772  
 773  
 774  protected function not($filter) {
 775    $matches = $this->candidateList();
 776    
 777    $found = new SplObjectStorage();
 778    foreach ($matches as $item) {
 779      $handler = new QueryPathCssEventHandler($item);
 780      $not_these = $handler->find($filter)->getMatches();
 781      if ($not_these->count() == 0) {
 782        $found->attach($item);
 783      }
 784    }
 785    
 786    
 787    $this->matches = $found;    
 788  }
 789  
 790  
 791  public function has($filter) {
 792    $matches = $this->candidateList();
 793    
 794    $found = new SplObjectStorage();
 795    foreach ($matches as $item) {
 796      $handler = new QueryPathCssEventHandler($item);
 797      $these = $handler->find($filter)->getMatches();
 798      if (count($these) > 0) {
 799        $found->attach($item);
 800      }      
 801    }
 802    $this->matches = $found;
 803    return $this;
 804  }
 805  
 806  
 807  protected function firstOfType() {
 808    $matches = $this->candidateList();
 809    $found = new SplObjectStorage();
 810    foreach ($matches as $item) {
 811      $type = $item->tagName;
 812      $parent = $item->parentNode;
 813      foreach ($parent->childNodes as $kid) {
 814        if ($kid->nodeType == XML_ELEMENT_NODE && $kid->tagName == $type) {
 815          if (!$found->contains($kid)) {
 816            $found->attach($kid);
 817          }
 818          break;
 819        }
 820      }
 821    }
 822    $this->matches = $found;
 823  }
 824  
 825  
 826  protected function lastOfType() {
 827    $matches = $this->candidateList();
 828    $found = new SplObjectStorage();
 829    foreach ($matches as $item) {
 830      $type = $item->tagName;
 831      $parent = $item->parentNode;
 832      for ($i = $parent->childNodes->length - 1; $i >= 0; --$i) {
 833        $kid = $parent->childNodes->item($i);
 834        if ($kid->nodeType == XML_ELEMENT_NODE && $kid->tagName == $type) {
 835          if (!$found->contains($kid)) {
 836            $found->attach($kid);
 837          }
 838          break;
 839        }
 840      }
 841    }
 842    $this->matches = $found;
 843  }
 844  
 845  
 846  protected function onlyChild() {
 847    $matches = $this->candidateList();
 848    $found = new SplObjectStorage();
 849    foreach($matches as $item) {
 850      $parent = $item->parentNode;
 851      $kids = array();
 852      foreach($parent->childNodes as $kid) {
 853        if ($kid->nodeType == XML_ELEMENT_NODE) {
 854          $kids[] = $kid;
 855        }
 856      }
 857      
 858      
 859      if (count($kids) == 1 && $kids[0] === $item) {
 860        $found->attach($kids[0]);
 861      }
 862    }
 863    $this->matches = $found;
 864  }
 865  
 866  
 867  protected function emptyElement() {
 868    $found = new SplObjectStorage();
 869    $matches = $this->candidateList();
 870    foreach ($matches as $item) {
 871      $empty = TRUE;
 872      foreach($item->childNodes as $kid) {
 873        
 874        
 875        if ($kid->nodeType == XML_ELEMENT_NODE || $kid->nodeType == XML_TEXT_NODE) {
 876          $empty = FALSE;
 877          break;
 878        }
 879      }
 880      if ($empty) {
 881        $found->attach($item);
 882      }
 883    }
 884    $this->matches = $found;
 885  }
 886  
 887  
 888  protected function onlyOfType() {
 889    $matches = $this->candidateList();
 890    $found = new SplObjectStorage();
 891    foreach ($matches as $item) {
 892      if (!$item->parentNode) {
 893        $this->matches = new SplObjectStorage();
 894      }
 895      $parent = $item->parentNode;
 896      $onlyOfType = TRUE;
 897      
 898      
 899      foreach($parent->childNodes as $kid) {
 900        if ($kid->nodeType == XML_ELEMENT_NODE 
 901            && $kid->tagName == $item->tagName 
 902            && $kid !== $item) {
 903          
 904          $onlyOfType = FALSE;
 905          break;
 906        }
 907      }
 908      
 909      
 910      if ($onlyOfType) $found->attach($item);
 911    }
 912    $this->matches = $found;
 913  }
 914  
 915  
 916  protected function attrValMatches($needle, $haystack, $operation) {
 917    
 918    if (strlen($haystack) < strlen($needle)) return FALSE;
 919    
 920    
 921    
 922    
 923    
 924    switch ($operation) {
 925      case CssEventHandler::isExactly:
 926        return $needle == $haystack;
 927      case CssEventHandler::containsWithSpace:
 928        return in_array($needle, explode(' ', $haystack));
 929      case CssEventHandler::containsWithHyphen:
 930        return in_array($needle, explode('-', $haystack));
 931      case CssEventHandler::containsInString:
 932        return strpos($haystack, $needle) !== FALSE;
 933      case CssEventHandler::beginsWith:
 934        return strpos($haystack, $needle) === 0;
 935      case CssEventHandler::endsWith:
 936        
 937        return preg_match('/' . $needle . '$/', $haystack) == 1;
 938    }
 939    return FALSE; 
 940  }
 941  
 942  
 943  public function pseudoElement($name) {
 944    
 945    switch ($name) {
 946      
 947      
 948      case 'first-line':
 949        $matches = $this->candidateList();
 950        $found = new SplObjectStorage();
 951        $o = new stdClass();
 952        foreach ($matches as $item) {
 953          $str = $item->textContent;
 954          $lines = explode("\n", $str);
 955          if (!empty($lines)) {
 956            $line = trim($lines[0]);
 957            if (!empty($line))
 958              $o->textContent = $line;
 959              $found->attach($o);
 960          }
 961        }
 962        $this->matches = $found;
 963        break;
 964      
 965      
 966      case 'first-letter':
 967        $matches = $this->candidateList();
 968        $found = new SplObjectStorage();
 969        $o = new stdClass();
 970        foreach ($matches as $item) {
 971          $str = $item->textContent;
 972          if (!empty($str)) {
 973            $str = substr($str,0, 1);
 974            $o->textContent = $str;
 975            $found->attach($o);
 976          }
 977        }
 978        $this->matches = $found;
 979        break;
 980      case 'before':
 981      case 'after':
 982        
 983        
 984      case 'selection':
 985        
 986        throw new NotImplementedException("The $name pseudo-element is not implemented.");
 987        break;
 988    }
 989    $this->findAnyElement = FALSE;  
 990  }
 991  public function directDescendant() {
 992    $this->findAnyElement = FALSE;
 993        
 994    $kids = new SplObjectStorage();
 995    foreach ($this->matches as $item) {
 996      $kidsNL = $item->childNodes;
 997      foreach ($kidsNL as $kidNode) {
 998        if ($kidNode->nodeType == XML_ELEMENT_NODE) {
 999          $kids->attach($kidNode);
1000        }
1001      }
1002    }
1003    $this->matches = $kids;
1004  }
1005  
1006  public function adjacent() {
1007    $this->findAnyElement = FALSE;
1008    
1009    
1010    $found = new SplObjectStorage();
1011    foreach ($this->matches as $item) {
1012      if (isset($item->nextSibling) && $item->nextSibling->nodeType === XML_ELEMENT_NODE) {
1013        $found->attach($item->nextSibling);
1014      }
1015    }
1016    $this->matches = $found;
1017  }
1018  
1019  public function anotherSelector() {
1020    $this->findAnyElement = FALSE;
1021    
1022    if ($this->matches->count() > 0) {
1023      
1024      foreach ($this->matches as $item) $this->alreadyMatched->attach($item);
1025    }
1026    
1027    
1028    $this->findAnyElement = TRUE; 
1029    $this->matches = new SplObjectStorage();
1030    $this->matches->attach($this->dom);
1031  }
1032  
1033  
1034  public function sibling() {
1035    $this->findAnyElement = FALSE;
1036    
1037    
1038    if ($this->matches->count() > 0) {
1039      $sibs = new SplObjectStorage();
1040      foreach ($this->matches as $item) {
1041        
1042        while ($item->nextSibling != NULL) {
1043          $item = $item->nextSibling;
1044          if ($item->nodeType === XML_ELEMENT_NODE) $sibs->attach($item);
1045        }
1046      }
1047      $this->matches = $sibs;
1048    }
1049  }
1050  
1051  
1052  public function anyDescendant() {
1053    
1054    $found = new SplObjectStorage();
1055    foreach ($this->matches as $item) {
1056      $kids = $item->getElementsByTagName('*');
1057      
1058      $this->attachNodeList($kids, $found);
1059    }
1060    $this->matches = $found;
1061    
1062    
1063    $this->findAnyElement = TRUE;
1064  }
1065  
1066  
1067  private function candidateList() {
1068    if ($this->findAnyElement) {
1069      return $this->getAllCandidates($this->matches);
1070    }
1071    return $this->matches;
1072  }
1073  
1074  
1075  private function getAllCandidates($elements) {
1076    $found = new SplObjectStorage();
1077    foreach ($elements as $item) {
1078      $found->attach($item); 
1079      $nl = $item->getElementsByTagName('*');
1080      
1081      $this->attachNodeList($nl, $found);
1082    }
1083    return $found;
1084  }
1085  
1086  
1087  
1088  public function attachNodeList(DOMNodeList $nodeList, SplObjectStorage $splos) {
1089    foreach ($nodeList as $item) $splos->attach($item);
1090  }
1091  
1092}
1093
1094
1095class NotImplementedException extends Exception {}