PageRenderTime 12ms CodeModel.GetById 3ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/antisocnet/drupal
PHP | 2253 lines | 1691 code | 543 blank | 19 comment | 295 complexity | 304126f4700613db5de52c4186d1c9f0 MD5 | raw file
   1<?php
   2
   3
   4
   5
   6
   7
   8
   9 
  10
  11define('ML_EXP','/^[^<]*(<(.|\s)+>)[^>]*$/');
  12
  13
  14require_once 'CssEventHandler.php';
  15
  16require_once 'QueryPathExtension.php';
  17
  18
  19function qp($document = NULL, $string = NULL, $options = array()) {
  20  
  21  $qpClass = isset($options['QueryPath_class']) ? $options['QueryPath_class'] : 'QueryPath';
  22  
  23  $qp = new $qpClass($document, $string, $options);
  24  return $qp;
  25}
  26
  27
  28function htmlqp($document = NULL, $selector = NULL, $options = array()) {
  29
  30  
  31  
  32  
  33  
  34  $options += array(
  35    'ignore_parser_warnings' => TRUE,
  36    'convert_to_encoding' => 'ISO-8859-1',
  37    'convert_from_encoding' => 'auto',
  38    
  39    'use_parser' => 'html',
  40    'strip_low_ascii' => TRUE,
  41  );
  42  return @qp($document, $selector, $options);
  43}
  44
  45
  46class QueryPath implements IteratorAggregate {
  47  
  48  
  49  const VERSION = '2.1.0';
  50  
  51  
  52  const HTML_STUB = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  53  <html lang="en">
  54  <head>
  55  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  56  <title>Untitled</title>
  57  </head>
  58  <body></body>
  59  </html>';
  60  
  61  
  62  const XHTML_STUB = '<?xml version="1.0"?>
  63  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  64  <html xmlns="http://www.w3.org/1999/xhtml">
  65  <head>
  66  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  67  <title>Untitled</title>
  68  </head>
  69  <body></body>
  70  </html>';
  71  
  72  
  73  const DEFAULT_PARSER_FLAGS = NULL;
  74  
  75  
  76  private $errTypes = 771; 
  77  
  78  
  79  protected $document = NULL;
  80  private $options = array(
  81    'parser_flags' => NULL,
  82    'omit_xml_declaration' => FALSE,
  83    'replace_entities' => FALSE,
  84    'exception_level' => 771, 
  85    'ignore_parser_warnings' => FALSE,
  86  );
  87  
  88  protected $matches = array();
  89  
  90  protected $last = array(); 
  91  private $ext = array(); 
  92  
  93  
  94  public $length = 0;
  95  
  96  
  97  public function __construct($document = NULL, $string = NULL, $options = array()) {
  98    $string = trim($string);
  99    $this->options = $options + QueryPathOptions::get() + $this->options;
 100    
 101    $parser_flags = isset($options['parser_flags']) ? $options['parser_flags'] : self::DEFAULT_PARSER_FLAGS;
 102    if (!empty($this->options['ignore_parser_warnings'])) {
 103      
 104      $this->errTypes = 257; 
 105    }
 106    elseif (isset($this->options['exception_level'])) {
 107      
 108      
 109      
 110      $this->errTypes = $this->options['exception_level'];
 111    }
 112    
 113    
 114    if (empty($document)) {
 115      $this->document = isset($this->options['encoding']) ? new DOMDocument('1.0', $this->options['encoding']) : new DOMDocument();
 116      $this->setMatches(new SplObjectStorage());
 117    }
 118    
 119    elseif (is_object($document)) {
 120      
 121      if ($document instanceof QueryPath) {
 122        $this->matches = $document->get(NULL, TRUE);
 123        if ($this->matches->count() > 0)
 124          $this->document = $this->getFirstMatch()->ownerDocument;
 125      }
 126      elseif ($document instanceof DOMDocument) {
 127        $this->document = $document;
 128        
 129        $this->setMatches($document->documentElement);
 130      }
 131      elseif ($document instanceof DOMNode) {
 132        $this->document = $document->ownerDocument;
 133        
 134        $this->setMatches($document);
 135      }
 136      elseif ($document instanceof SimpleXMLElement) {
 137        $import = dom_import_simplexml($document);
 138        $this->document = $import->ownerDocument;
 139        
 140        $this->setMatches($import);
 141      }
 142      elseif ($document instanceof SplObjectStorage) {
 143        if ($document->count() == 0) {
 144          throw new QueryPathException('Cannot initialize QueryPath from an empty SplObjectStore');
 145        }
 146        $this->matches = $document;
 147        $this->document = $this->getFirstMatch()->ownerDocument;
 148      }
 149      else {
 150        throw new QueryPathException('Unsupported class type: ' . get_class($document));
 151      }
 152    }
 153    elseif (is_array($document)) {
 154      
 155      if (!empty($document) && $document[0] instanceof DOMNode) {
 156        $found = new SplObjectStorage();
 157        foreach ($document as $item) $found->attach($item);
 158        
 159        $this->setMatches($found);
 160        $this->document = $this->getFirstMatch()->ownerDocument;
 161      }
 162    }
 163    elseif ($this->isXMLish($document)) {
 164      
 165      $this->document = $this->parseXMLString($document);
 166      $this->setMatches($this->document->documentElement);
 167    }
 168    else {
 169      
 170      
 171      $context = empty($options['context']) ? NULL : $options['context'];
 172      $this->document = $this->parseXMLFile($document, $parser_flags, $context);
 173      $this->setMatches($this->document->documentElement);
 174    }
 175    
 176    
 177    if (isset($string) && strlen($string) > 0) {
 178      $this->find($string);
 179    }
 180  }
 181  
 182  
 183  public static function encodeDataURL($data, $mime = 'application/octet-stream', $context = NULL) {
 184    if (is_resource($data)) {
 185      $data = stream_get_contents($data);
 186    }
 187    elseif (filter_var($data, FILTER_VALIDATE_URL)) {
 188      $data = file_get_contents($data, FALSE, $context);
 189    }
 190    
 191    $encoded = base64_encode($data);
 192    
 193    return 'data:' . $mime . ';base64,' . $encoded;
 194  }
 195  
 196  
 197  public function getOptions() {
 198    return $this->options;
 199  }
 200  
 201  
 202  public function top($selector = NULL) {
 203    $this->setMatches($this->document->documentElement);
 204    
 205    
 206    
 207    return !empty($selector) ? $this->find($selector) : $this;
 208  }
 209  
 210  
 211  public function find($selector) {
 212    
 213    
 214    
 215    $ids = array();
 216    
 217    $regex = '/^#([\w-]+)$|^\.([\w-]+)$/'; 
 218    
 219    if (preg_match($regex, $selector, $ids) === 1) {
 220      
 221      if (!empty($ids[1])) {
 222        $xpath = new DOMXPath($this->document);
 223        foreach ($this->matches as $item) {
 224          
 225          
 226          
 227          
 228          if ($item->isSameNode($this->document->documentElement) ) {
 229            $xpathQuery = "//*[@id='{$ids[1]}']";
 230          }
 231          
 232          else {
 233            $xpathQuery = ".//*[@id='{$ids[1]}']";
 234          }
 235          
 236          
 237          $nl = $xpath->query($xpathQuery, $item);
 238          if ($nl->length > 0) {
 239            $this->setMatches($nl->item(0));
 240            break;
 241          }
 242          else {
 243            
 244            $this->noMatches();
 245          }
 246        }
 247      }
 248      
 249      
 250      else {
 251        $xpath = new DOMXPath($this->document);
 252        $found = new SplObjectStorage();
 253        foreach ($this->matches as $item) {
 254          
 255          if ($item->isSameNode($this->document->documentElement) ) {
 256            $xpathQuery = "//*[@class]";
 257          }
 258          
 259          else {
 260            $xpathQuery = ".//*[@class]";
 261          }
 262          $nl = $xpath->query($xpathQuery, $item);
 263          for ($i = 0; $i < $nl->length; ++$i) {
 264            $vals = explode(' ', $nl->item($i)->getAttribute('class'));
 265            if (in_array($ids[2], $vals)) $found->attach($nl->item($i));
 266          }
 267        }
 268        $this->setMatches($found);
 269      }
 270      
 271      return $this;
 272    }
 273    
 274    
 275    $query = new QueryPathCssEventHandler($this->matches);
 276    $query->find($selector);
 277    
 278    $this->setMatches($query->getMatches());
 279    return $this;
 280  }
 281  
 282  
 283  public function xpath($query) {
 284    $xpath = new DOMXPath($this->document);
 285    $found = new SplObjectStorage();
 286    foreach ($this->matches as $item) {
 287      $nl = $xpath->query($query, $item);
 288      if ($nl->length > 0) {
 289        for ($i = 0; $i < $nl->length; ++$i) $found->attach($nl->item($i));
 290      }
 291    }
 292    $this->setMatches($found);
 293    return $this;
 294  }
 295  
 296  
 297  public function size() {
 298    return $this->matches->count();
 299  }
 300  
 301  
 302  public function get($index = NULL, $asObject = FALSE) {
 303    if (isset($index)) {
 304      return ($this->size() > $index) ? $this->getNthMatch($index) : NULL;
 305    }
 306    
 307    if (!$asObject) {
 308      $matches = array();
 309      foreach ($this->matches as $m) $matches[] = $m;
 310      return $matches;
 311    }
 312    return $this->matches;
 313  }
 314  
 315  public function toArray() {
 316    return $this->get();
 317  }
 318  
 319  public function attr($name = NULL, $value = NULL) {
 320    
 321    
 322    if (is_null($name)) {
 323      if ($this->matches->count() == 0) return NULL;
 324      $ele = $this->getFirstMatch();
 325      $buffer = array();
 326      
 327      
 328      
 329      foreach ($ele->attributes as $name => $attrNode) {
 330        $buffer[$name] = $attrNode->value;
 331      }
 332      return $buffer;
 333    }
 334    
 335    
 336    if (is_array($name)) {
 337      foreach ($name as $k => $v) {
 338        foreach ($this->matches as $m) $m->setAttribute($k, $v);
 339      }
 340      return $this;
 341    }
 342    
 343    if (isset($value)) {
 344      foreach ($this->matches as $m) $m->setAttribute($name, $value);
 345      return $this;
 346    }
 347    
 348    
 349    if ($this->matches->count() == 0) return NULL;
 350    
 351    
 352    if ($name == 'nodeType') {
 353      return $this->getFirstMatch()->nodeType;
 354    }
 355    
 356    
 357    return $this->getFirstMatch()->getAttribute($name);
 358  }
 359  
 360  public function hasAttr($attrName) {
 361    foreach ($this->matches as $match) {
 362      if (!$match->hasAttribute($attrName)) return FALSE;
 363    }
 364    return TRUE;
 365  }
 366  
 367  
 368  public function css($name = NULL, $value = '') {
 369    if (empty($name)) {
 370      return $this->attr('style');
 371    }
 372    
 373    
 374    $css = array();
 375    foreach ($this->matches as $match) {
 376      $style = $match->getAttribute('style');
 377      if (!empty($style)) {
 378        
 379        $style_array = explode(';', $style);
 380        foreach ($style_array as $item) {
 381          $item = trim($item);
 382          
 383          
 384          if (strlen($item) == 0) continue;
 385          
 386          list($css_att, $css_val) = explode(':',$item, 2);
 387          $css[$css_att] = trim($css_val);
 388        }
 389      }
 390    }
 391    
 392    if (is_array($name)) {
 393      
 394      $css = array_merge($css, $name);
 395    }
 396    else {
 397      $css[$name] = $value;
 398    }
 399    
 400    
 401    $format = '%s: %s;';
 402    $css_string = '';
 403    foreach ($css as $n => $v) {
 404      $css_string .= sprintf($format, $n, trim($v));
 405    }
 406    
 407    $this->attr('style', $css_string);
 408    return $this;
 409  }
 410  
 411  
 412  public function dataURL($attr, $data = NULL, $mime = 'application/octet-stream', $context = NULL) {
 413    if (is_null($data)) {
 414      
 415      $data = $this->attr($attr);
 416      if (empty($data) || is_array($data) || strpos($data, 'data:') !== 0) {
 417        return;
 418      }
 419      
 420      
 421      $regex = '/^data:([a-zA-Z0-9]+)\/([a-zA-Z0-9]+);base64,(.*)$/';
 422      $matches = array();
 423      preg_match($regex, $data, $matches);
 424      
 425      if (!empty($matches)) {
 426        $result = array(
 427          'mime' => $matches[1] . '/' . $matches[2],
 428          'data' => base64_decode($matches[3]),
 429        );
 430        return $result;
 431      }
 432    }
 433    else {
 434      
 435      $attVal = self::encodeDataURL($data, $mime, $context);
 436      
 437      return $this->attr($attr, $attVal);
 438      
 439    }
 440  }
 441  
 442
 443  
 444  
 445  public function removeAttr($name) {
 446    foreach ($this->matches as $m) {
 447      
 448        $m->removeAttribute($name);
 449    }
 450    return $this;
 451  }
 452  
 453  public function eq($index) {
 454    
 455    $this->setMatches($this->getNthMatch($index));
 456    return $this;
 457  }
 458  
 459  public function is($selector) {
 460    foreach ($this->matches as $m) {
 461      $q = new QueryPathCssEventHandler($m);
 462      if ($q->find($selector)->getMatches()->count()) {
 463        return TRUE;
 464      }
 465    }
 466    return FALSE;
 467  }
 468  
 469  public function filter($selector) {
 470    $found = new SplObjectStorage();
 471    foreach ($this->matches as $m) if (qp($m, NULL, $this->options)->is($selector)) $found->attach($m);
 472    $this->setMatches($found);
 473    return $this;
 474  }
 475  
 476  public function filterLambda($fn) {
 477    $function = create_function('$index, $item', $fn);
 478    $found = new SplObjectStorage();
 479    $i = 0;
 480    foreach ($this->matches as $item)
 481      if ($function($i++, $item) !== FALSE) $found->attach($item);
 482    
 483    $this->setMatches($found);
 484    return $this;
 485  }
 486  
 487  
 488  public function filterPreg($regex) {
 489    
 490    $found = new SplObjectStorage();
 491    
 492    foreach ($this->matches as $item) {
 493      if (preg_match($regex, $item->textContent) > 0) {
 494        $found->attach($item);
 495      }
 496    }
 497    $this->setMatches($found);
 498    
 499    return $this;
 500  }
 501  
 502  public function filterCallback($callback) {
 503    $found = new SplObjectStorage();
 504    $i = 0;
 505    if (is_callable($callback)) {
 506      foreach($this->matches as $item) 
 507        if (call_user_func($callback, $i++, $item) !== FALSE) $found->attach($item);
 508    }
 509    else {
 510      throw new QueryPathException('The specified callback is not callable.');
 511    }
 512    $this->setMatches($found);
 513    return $this;
 514  }
 515  
 516  public function not($selector) {
 517    $found = new SplObjectStorage();
 518    if ($selector instanceof DOMElement) {
 519      foreach ($this->matches as $m) if ($m !== $selector) $found->attach($m); 
 520    }
 521    elseif (is_array($selector)) {
 522      foreach ($this->matches as $m) {
 523        if (!in_array($m, $selector, TRUE)) $found->attach($m);
 524      }
 525    }
 526    elseif ($selector instanceof SplObjectStorage) {
 527      foreach ($this->matches as $m) if ($selector->contains($m)) $found->attach($m); 
 528    }
 529    else {
 530      foreach ($this->matches as $m) if (!qp($m, NULL, $this->options)->is($selector)) $found->attach($m);
 531    }
 532    $this->setMatches($found);
 533    return $this;
 534  }
 535  
 536  public function index($subject) {
 537    
 538    $i = 0;
 539    foreach ($this->matches as $m) {
 540      if ($m === $subject) {
 541        return $i;
 542      }
 543      ++$i;
 544    }
 545    return FALSE;
 546  }
 547  
 548  public function map($callback) {
 549    $found = new SplObjectStorage();
 550    
 551    if (is_callable($callback)) {
 552      $i = 0;
 553      foreach ($this->matches as $item) {
 554        $c = call_user_func($callback, $i, $item);
 555        if (isset($c)) {
 556          if (is_array($c) || $c instanceof Iterable) {
 557            foreach ($c as $retval) {
 558              if (!is_object($retval)) {
 559                $tmp = new stdClass();
 560                $tmp->textContent = $retval;
 561                $retval = $tmp;
 562              }
 563              $found->attach($retval);
 564            }
 565          }
 566          else {
 567            if (!is_object($c)) {
 568              $tmp = new stdClass();
 569              $tmp->textContent = $c;
 570              $c = $tmp;
 571            }
 572            $found->attach($c);
 573          }
 574        }
 575        ++$i;
 576      }
 577    }
 578    else {
 579      throw new QueryPathException('Callback is not callable.');
 580    }
 581    $this->setMatches($found, FALSE);
 582    return $this;
 583  }
 584  
 585  public function slice($start, $length = 0) {
 586    $end = $length;
 587    $found = new SplObjectStorage();
 588    if ($start >= $this->size()) {
 589      $this->setMatches($found);
 590      return $this;
 591    }
 592    
 593    $i = $j = 0;
 594    foreach ($this->matches as $m) {
 595      if ($i >= $start) {
 596        if ($end > 0 && $j >= $end) {
 597          break;
 598        }
 599        $found->attach($m);
 600        ++$j;
 601      }
 602      ++$i;
 603    }
 604    
 605    $this->setMatches($found);
 606    return $this;
 607  }
 608  
 609  public function each($callback) {
 610    if (is_callable($callback)) {
 611      $i = 0;
 612      foreach ($this->matches as $item) {
 613        if (call_user_func($callback, $i, $item) === FALSE) return $this;
 614        ++$i;
 615      }
 616    }
 617    else {
 618      throw new QueryPathException('Callback is not callable.');
 619    }
 620    return $this;
 621  }
 622  
 623  public function eachLambda($lambda) {
 624    $index = 0;
 625    foreach ($this->matches as $item) {
 626      $fn = create_function('$index, &$item', $lambda);
 627      if ($fn($index, $item) === FALSE) return $this;
 628      ++$index;
 629    }
 630    return $this;
 631  }
 632  
 633  public function append($data) {
 634    $data = $this->prepareInsert($data);
 635    if (isset($data)) {
 636      if (empty($this->document->documentElement) && $this->matches->count() == 0) {
 637        
 638        $this->document->appendChild($data);
 639        $found = new SplObjectStorage();
 640        $found->attach($this->document->documentElement);
 641        $this->setMatches($found);
 642      }
 643      else {
 644        
 645        
 646        foreach ($this->matches as $m) { 
 647          
 648          
 649          if ($data instanceof DOMDocumentFragment) {
 650            foreach ($data->childNodes as $n)
 651              $m->appendChild($n->cloneNode(TRUE));
 652          }
 653          else {
 654            
 655            $m->appendChild($data->cloneNode(TRUE));
 656          }
 657          
 658        }
 659      }
 660        
 661    }
 662    return $this;
 663  }
 664  
 665  public function appendTo(QueryPath $dest) {
 666    foreach ($this->matches as $m) $dest->append($m);
 667    return $this;
 668  }
 669  
 670  public function prepend($data) {
 671    $data = $this->prepareInsert($data);
 672    if (isset($data)) {
 673      foreach ($this->matches as $m) {
 674        $ins = $data->cloneNode(TRUE);
 675        if ($m->hasChildNodes())
 676          $m->insertBefore($ins, $m->childNodes->item(0));
 677        else
 678          $m->appendChild($ins);
 679      }
 680    }
 681    return $this;
 682  }
 683  
 684  public function prependTo(QueryPath $dest) {
 685    foreach ($this->matches as $m) $dest->prepend($m);
 686    return $this;
 687  }
 688
 689  
 690  public function before($data) {
 691    $data = $this->prepareInsert($data);
 692    foreach ($this->matches as $m) {
 693      $ins = $data->cloneNode(TRUE);
 694      $m->parentNode->insertBefore($ins, $m);
 695    }
 696    
 697    return $this;
 698  }
 699  
 700  public function insertBefore(QueryPath $dest) {
 701    foreach ($this->matches as $m) $dest->before($m);
 702    return $this;
 703  }
 704  
 705  public function insertAfter(QueryPath $dest) {
 706    foreach ($this->matches as $m) $dest->after($m);
 707    return $this;
 708  }
 709  
 710  public function after($data) {
 711    $data = $this->prepareInsert($data);
 712    foreach ($this->matches as $m) {
 713      $ins = $data->cloneNode(TRUE);
 714      if (isset($m->nextSibling)) 
 715        $m->parentNode->insertBefore($ins, $m->nextSibling);
 716      else
 717        $m->parentNode->appendChild($ins);
 718    }
 719    return $this;
 720  }
 721  
 722  public function replaceWith($new) {
 723    $data = $this->prepareInsert($new);
 724    $found = new SplObjectStorage();
 725    foreach ($this->matches as $m) {
 726      $parent = $m->parentNode;
 727      $parent->insertBefore($data->cloneNode(TRUE), $m);
 728      $found->attach($parent->removeChild($m));
 729    }
 730    $this->setMatches($found);
 731    return $this;
 732  }
 733  
 734  public function unwrap() {
 735    
 736    
 737    
 738    
 739    
 740    $parents = new SplObjectStorage();
 741    foreach ($this->matches as $m) {
 742      
 743      
 744      if ($m->isSameNode($m->ownerDocument->documentElement)) {
 745        throw new QueryPathException('Cannot unwrap the root element.');
 746      }
 747      
 748      
 749      $parent = $m->parentNode;
 750      $old = $parent->removeChild($m);
 751      $parent->parentNode->insertBefore($old, $parent);
 752      $parents->attach($parent);
 753    }
 754    
 755    
 756    
 757    foreach ($parents as $ele) {
 758      $ele->parentNode->removeChild($ele);
 759    }
 760    
 761    return $this;
 762  }
 763  
 764  public function wrap($markup) {
 765    $data = $this->prepareInsert($markup);
 766    
 767    
 768    if (empty($data)) {
 769      return $this;
 770    }
 771    
 772    foreach ($this->matches as $m) {
 773      $copy = $data->firstChild->cloneNode(TRUE);
 774      
 775      
 776      if ($copy->hasChildNodes()) {
 777        $deepest = $this->deepestNode($copy); 
 778        
 779        $bottom = $deepest[0];
 780      }
 781      else
 782        $bottom = $copy;
 783
 784      $parent = $m->parentNode;
 785      $parent->insertBefore($copy, $m);
 786      $m = $parent->removeChild($m);
 787      $bottom->appendChild($m);
 788      
 789    }
 790    return $this;  
 791  }
 792  
 793  public function wrapAll($markup) {
 794    if ($this->matches->count() == 0) return;
 795    
 796    $data = $this->prepareInsert($markup);
 797    
 798    if (empty($data)) {
 799      return $this;
 800    }
 801    
 802    if ($data->hasChildNodes()) {
 803      $deepest = $this->deepestNode($data); 
 804      
 805      $bottom = $deepest[0];
 806    }
 807    else
 808      $bottom = $data;
 809
 810    $first = $this->getFirstMatch();
 811    $parent = $first->parentNode;
 812    $parent->insertBefore($data, $first);
 813    foreach ($this->matches as $m) {
 814      $bottom->appendChild($m->parentNode->removeChild($m));
 815    }
 816    return $this;
 817  }
 818  
 819  public function wrapInner($markup) {
 820    $data = $this->prepareInsert($markup);
 821    
 822    
 823    if (empty($data)) return $this;
 824    
 825    if ($data->hasChildNodes()) {
 826      $deepest = $this->deepestNode($data); 
 827      
 828      $bottom = $deepest[0];
 829    }
 830    else
 831      $bottom = $data;
 832      
 833    foreach ($this->matches as $m) {
 834      if ($m->hasChildNodes()) {
 835        while($m->firstChild) {
 836          $kid = $m->removeChild($m->firstChild);
 837          $bottom->appendChild($kid);
 838        }
 839      }
 840      $m->appendChild($data);
 841    }
 842    return $this; 
 843  }
 844  
 845  public function deepest() {
 846    $deepest = 0;
 847    $winner = new SplObjectStorage();
 848    foreach ($this->matches as $m) {
 849      $local_deepest = 0;
 850      $local_ele = $this->deepestNode($m, 0, NULL, $local_deepest);
 851      
 852      
 853      if ($local_deepest > $deepest) {
 854        $winner = new SplObjectStorage();
 855        foreach ($local_ele as $lele) $winner->attach($lele);
 856        $deepest = $local_deepest;
 857      }
 858      
 859      elseif ($local_deepest == $deepest) {
 860        foreach ($local_ele as $lele)
 861          $winner->attach($lele);
 862      }
 863    }
 864    $this->setMatches($winner);
 865    return $this;
 866  }
 867  
 868  
 869  protected function deepestNode(DOMNode $ele, $depth = 0, $current = NULL, &$deepest = NULL) {
 870    
 871    if (!isset($current)) $current = array($ele);
 872    if (!isset($deepest)) $deepest = $depth;
 873    if ($ele->hasChildNodes()) {
 874      foreach ($ele->childNodes as $child) {
 875        if ($child->nodeType === XML_ELEMENT_NODE) {
 876          $current = $this->deepestNode($child, $depth + 1, $current, $deepest);
 877        }
 878      }
 879    }
 880    elseif ($depth > $deepest) {
 881      $current = array($ele);
 882      $deepest = $depth;
 883    }
 884    elseif ($depth === $deepest) {
 885      $current[] = $ele;
 886    }
 887    return $current;
 888  }
 889  
 890  /**
 891   * Prepare an item for insertion into a DOM.
 892   *
 893   * This handles a variety of boilerplate tasks that need doing before an 
 894   * indeterminate object can be inserted into a DOM tree.
 895   * - If item is a string, this is converted into a document fragment and returned.
 896   * - If item is a QueryPath, then the first item is retrieved and this call function
 897   *   is called recursivel.
 898   * - If the item is a DOMNode, it is imported into the current DOM if necessary.
 899   * - If the item is a SimpleXMLElement, it is converted into a DOM node and then
 900   *   imported.
 901   *
 902   * @param mixed $item
 903   *  Item to prepare for insert.
 904   * @return mixed
 905   *  Returns the prepared item.
 906   * @throws QueryPathException
 907   *  Thrown if the object passed in is not of a supprted object type.
 908   */
 909  protected function prepareInsert($item) {
 910    if(empty($item)) {
 911      return;
 912    }
 913    elseif (is_string($item)) {
 914      
 915      if ($this->options['replace_entities']) {
 916        $item = QueryPathEntities::replaceAllEntities($item);
 917      }
 918      
 919      $frag = $this->document->createDocumentFragment();
 920      try {
 921        set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);
 922        $frag->appendXML($item);
 923      }
 924      
 925      catch (Exception $e) {
 926        restore_error_handler();
 927        throw $e;
 928      }
 929      restore_error_handler();
 930      return $frag;
 931    }
 932    elseif ($item instanceof QueryPath) {
 933      if ($item->size() == 0) 
 934        return;
 935        
 936      return $this->prepareInsert($item->get(0));
 937    }
 938    elseif ($item instanceof DOMNode) {
 939      if ($item->ownerDocument !== $this->document) {
 940        
 941        $item = $this->document->importNode($item, TRUE);
 942      }
 943      return $item;
 944    }
 945    elseif ($item instanceof SimpleXMLElement) {
 946      $element = dom_import_simplexml($item);
 947      return $this->document->importNode($element, TRUE);
 948    }
 949    
 950    
 951    throw new QueryPathException("Cannot prepare item of unsupported type: " . gettype($item));
 952  }
 953  
 954  public function tag() {
 955    return ($this->size() > 0) ? $this->getFirstMatch()->tagName : '';
 956  }
 957  
 958  public function remove($selector = NULL) {
 959    
 960    if(!empty($selector))
 961      $this->find($selector);
 962    
 963    $found = new SplObjectStorage();
 964    foreach ($this->matches as $item) {
 965      
 966      
 967      $found->attach($item->parentNode->removeChild($item));
 968    }
 969    $this->setMatches($found);
 970    return $this;
 971  }
 972  
 973  public function replaceAll($selector, DOMDocument $document) {
 974    $replacement = $this->size() > 0 ? $this->getFirstMatch() : $this->document->createTextNode('');
 975    
 976    $c = new QueryPathCssEventHandler($document);
 977    $c->find($selector);
 978    $temp = $c->getMatches();
 979    foreach ($temp as $item) {
 980      $node = $replacement->cloneNode();
 981      $node = $document->importNode($node);
 982      $item->parentNode->replaceChild($node, $item);
 983    }
 984    return qp($document, NULL, $this->options);
 985  }
 986  
 987  public function add($selector) {
 988    
 989    
 990    $this->last = $this->matches;
 991    
 992    foreach (qp($this->document, $selector, $this->options)->get() as $item)
 993      $this->matches->attach($item);
 994    return $this;
 995  }
 996  
 997  public function end() {
 998    
 999    
1000    $this->matches = $this->last;
1001    $this->last = new SplObjectStorage();
1002    return $this;
1003  }
1004  
1005  public function andSelf() {
1006    
1007    $last = $this->matches;
1008    
1009    foreach ($this->last as $item) $this->matches->attach($item);
1010    
1011    $this->last = $last;
1012    return $this;
1013  }
1014  
1015  public function removeChildren() {
1016    foreach ($this->matches as $m) {
1017      while($kid = $m->firstChild) {
1018        $m->removeChild($kid);
1019      }
1020    }
1021    return $this;
1022  }
1023  
1024  public function children($selector = NULL) {
1025    $found = new SplObjectStorage();
1026    foreach ($this->matches as $m) {
1027      foreach($m->childNodes as $c) {
1028        if ($c->nodeType == XML_ELEMENT_NODE) $found->attach($c);
1029      }
1030    }
1031    if (empty($selector)) {
1032      $this->setMatches($found);
1033    }
1034    else {
1035      $this->matches = $found; 
1036      $this->filter($selector);
1037    }
1038    return $this;
1039  }
1040  
1041  public function contents() {
1042    $found = new SplObjectStorage();
1043    foreach ($this->matches as $m) {
1044      foreach ($m->childNodes as $c) {
1045        $found->attach($c);
1046      }
1047    }
1048    $this->setMatches($found);
1049    return $this;
1050  }
1051  
1052  public function siblings($selector = NULL) {
1053    $found = new SplObjectStorage();
1054    foreach ($this->matches as $m) {
1055      $parent = $m->parentNode;
1056      foreach ($parent->childNodes as $n) {
1057        if ($n->nodeType == XML_ELEMENT_NODE && $n !== $m) {
1058          $found->attach($n);
1059        }
1060      }
1061    }
1062    if (empty($selector)) {
1063      $this->setMatches($found);
1064    }
1065    else {
1066      $this->matches = $found; 
1067      $this->filter($selector);
1068    }
1069    return $this;
1070  }
1071  
1072  public function closest($selector) {
1073    $found = new SplObjectStorage();
1074    foreach ($this->matches as $m) {
1075      
1076      if (qp($m, NULL, $this->options)->is($selector) > 0) {
1077        $found->attach($m);
1078      }
1079      else {
1080        while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
1081          $m = $m->parentNode;
1082          
1083          if ($m->nodeType === XML_ELEMENT_NODE && qp($m, NULL, $this->options)->is($selector) > 0) {
1084            $found->attach($m);
1085            break;
1086          }
1087        }
1088      }
1089      
1090    }
1091    $this->setMatches($found);
1092    return $this;
1093  }
1094  
1095  public function parent($selector = NULL) {
1096    $found = new SplObjectStorage();
1097    foreach ($this->matches as $m) {
1098      while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
1099        $m = $m->parentNode;
1100        
1101        if ($m->nodeType === XML_ELEMENT_NODE) {
1102          if (!empty($selector)) {
1103            if (qp($m, NULL, $this->options)->is($selector) > 0) {
1104              $found->attach($m);
1105              break;
1106            }
1107          }
1108          else {
1109            $found->attach($m);
1110            break;
1111          }
1112        }
1113      }
1114    }
1115    $this->setMatches($found);
1116    return $this;
1117  }
1118  
1119  public function parents($selector = NULL) {
1120    $found = new SplObjectStorage();
1121    foreach ($this->matches as $m) {
1122      while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
1123        $m = $m->parentNode;
1124        
1125        if ($m->nodeType === XML_ELEMENT_NODE) {
1126          if (!empty($selector)) {
1127            if (qp($m, NULL, $this->options)->is($selector) > 0)
1128              $found->attach($m);
1129          }
1130          else 
1131            $found->attach($m);
1132        }
1133      }
1134    }
1135    $this->setMatches($found);
1136    return $this;
1137  }
1138  
1139  public function html($markup = NULL) {
1140    if (isset($markup)) {
1141      
1142      if ($this->options['replace_entities']) {
1143        $markup = QueryPathEntities::replaceAllEntities($markup);
1144      }
1145      
1146      
1147      
1148      $doc = $this->document->createDocumentFragment();
1149      $doc->appendXML($markup);
1150      $this->removeChildren();
1151      $this->append($doc);
1152      return $this;
1153    }
1154    $length = $this->size();
1155    if ($length == 0) {
1156      return NULL;
1157    }
1158    
1159    $first = $this->getFirstMatch();
1160
1161    
1162    if (!($first instanceof DOMNode)) {
1163      return NULL;
1164    }
1165    
1166    
1167    if(!$first->ownerDocument->documentElement) {
1168      return NULL;
1169    }
1170    
1171    if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) {
1172      return $this->document->saveHTML();
1173    }
1174    
1175    return $this->document->saveXML($first);
1176  }
1177  
1178  
1179  public function innerHTML() {
1180    return $this->innerXML();
1181  } 
1182  
1183  
1184  public function innerXHTML() {
1185    $length = $this->size();
1186    if ($length == 0) {
1187      return NULL;
1188    }
1189    
1190    $first = $this->getFirstMatch();
1191
1192    
1193    if (!($first instanceof DOMNode)) {
1194      return NULL;
1195    }
1196    elseif (!$first->hasChildNodes()) {
1197      return '';
1198    }
1199    
1200    $buffer = '';
1201    foreach ($first->childNodes as $child) {
1202      $buffer .= $this->document->saveXML($child, LIBXML_NOEMPTYTAG);
1203    }
1204    
1205    return $buffer;
1206  }
1207  
1208  
1209  public function innerXML() {
1210    $length = $this->size();
1211    if ($length == 0) {
1212      return NULL;
1213    }
1214    
1215    $first = $this->getFirstMatch();
1216
1217    
1218    if (!($first instanceof DOMNode)) {
1219      return NULL;
1220    }
1221    elseif (!$first->hasChildNodes()) {
1222      return '';
1223    }
1224    
1225    $buffer = '';
1226    foreach ($first->childNodes as $child) {
1227      $buffer .= $this->document->saveXML($child);
1228    }
1229    
1230    return $buffer;
1231  }
1232  
1233  
1234  public function textImplode($sep = ', ', $filterEmpties = TRUE) {
1235    $tmp = array(); 
1236    foreach ($this->matches as $m) {
1237      $txt = $m->textContent;
1238      $trimmed = trim($txt);
1239      
1240      if ($filterEmpties) {
1241        if (strlen($trimmed) > 0) $tmp[] = $txt;
1242      }
1243      
1244      else {
1245        $tmp[] = $txt;
1246      }
1247    }
1248    return implode($sep, $tmp);
1249  }
1250  
1251  public function text($text = NULL) {
1252    if (isset($text)) {
1253      $this->removeChildren();
1254      $textNode = $this->document->createTextNode($text);
1255      foreach ($this->matches as $m) $m->appendChild($textNode);
1256      return $this;
1257    }
1258    
1259    $buf = '';
1260    foreach ($this->matches as $m) $buf .= $m->textContent;
1261    return $buf;
1262  }
1263  
1264  public function textBefore($text = NULL) {
1265    if (isset($text)) {
1266      $textNode = $this->document->createTextNode($text);
1267      return $this->before($textNode);
1268    }
1269    $buffer = '';
1270    foreach ($this->matches as $m) {
1271      $p = $m;
1272      while (isset($p->previousSibling) && $p->previousSibling->nodeType == XML_TEXT_NODE) {
1273        $p = $p->previousSibling;
1274        $buffer .= $p->textContent;
1275      }
1276    }
1277    return $buffer;
1278  }
1279  
1280  public function textAfter($text = NULL) {
1281    if (isset($text)) {
1282      $textNode = $this->document->createTextNode($text);
1283      return $this->after($textNode);
1284    }
1285    $buffer = '';
1286    foreach ($this->matches as $m) {
1287      $n = $m;
1288      while (isset($n->nextSibling) && $n->nextSibling->nodeType == XML_TEXT_NODE) {
1289        $n = $n->nextSibling;
1290        $buffer .= $n->textContent;
1291      }
1292    }
1293    return $buffer;
1294  }
1295  
1296  
1297  public function val($value = NULL) {
1298    if (isset($value)) {
1299      $this->attr('value', $value);
1300      return $this;
1301    }
1302    return $this->attr('value');
1303  }
1304  
1305  public function xhtml($markup = NULL) {
1306    
1307    
1308    
1309    
1310   
1311    $omit_xml_decl = $this->options['omit_xml_declaration'];
1312    if ($markup === TRUE) {
1313      
1314      
1315      $omit_xml_decl = TRUE;
1316    }
1317    elseif (isset($markup)) {
1318      return $this->xml($markup);
1319    }
1320    
1321    $length = $this->size();
1322    if ($length == 0) {
1323      return NULL;
1324    }
1325    
1326    
1327    $first = $this->getFirstMatch();
1328    
1329    if (!($first instanceof DOMNode)) {
1330      return NULL;
1331    }
1332    
1333    if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) {
1334      
1335      return  ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement, LIBXML_NOEMPTYTAG) : $this->document->saveXML(NULL, LIBXML_NOEMPTYTAG));
1336    }
1337    return $this->document->saveXML($first, LIBXML_NOEMPTYTAG);
1338  }
1339  
1340  public function xml($markup = NULL) {
1341    $omit_xml_decl = $this->options['omit_xml_declaration'];
1342    if ($markup === TRUE) {
1343      
1344      
1345      $omit_xml_decl = TRUE;
1346    }
1347    elseif (isset($markup)) {
1348      if ($this->options['replace_entities']) {
1349        $markup = QueryPathEntities::replaceAllEntities($markup);
1350      }
1351      $doc = $this->document->createDocumentFragment();
1352      $doc->appendXML($markup);
1353      $this->removeChildren();
1354      $this->append($doc);
1355      return $this;
1356    }
1357    $length = $this->size();
1358    if ($length == 0) {
1359      return NULL;
1360    }
1361    
1362    $first = $this->getFirstMatch();
1363    
1364    
1365    if (!($first instanceof DOMNode)) {
1366      return NULL;
1367    }
1368    
1369    if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) {
1370      
1371      return  ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement) : $this->document->saveXML());
1372    }
1373    return $this->document->saveXML($first);
1374  }
1375  
1376  public function writeXML($path = NULL, $options = NULL) {
1377    if ($path == NULL) {
1378      print $this->document->saveXML(NULL, $options);
1379    }
1380    else {
1381      try {
1382        set_error_handler(array('QueryPathIOException', 'initializeFromError'));
1383        $this->document->save($path, $options);
1384      }
1385      catch (Exception $e) {
1386        restore_error_handler();
1387        throw $e;
1388      }
1389      restore_error_handler();
1390    }
1391    return $this;
1392  }
1393  
1394  public function writeHTML($path = NULL) {
1395    if ($path == NULL) {
1396      print $this->document->saveHTML();
1397    }
1398    else {
1399      try {
1400        set_error_handler(array('QueryPathParseException', 'initializeFromError'));
1401        $this->document->saveHTMLFile($path);
1402      }
1403      catch (Exception $e) {
1404        restore_error_handler();
1405        throw $e;
1406      }
1407      restore_error_handler();
1408    }
1409    return $this;
1410  }
1411  
1412  
1413  public function writeXHTML($path = NULL) {
1414    return $this->writeXML($path, LIBXML_NOEMPTYTAG);
1415    
1416  }
1417  
1418  public function next($selector = NULL) {
1419    $found = new SplObjectStorage();
1420    foreach ($this->matches as $m) {
1421      while (isset($m->nextSibling)) {
1422        $m = $m->nextSibling;
1423        if ($m->nodeType === XML_ELEMENT_NODE) {
1424          if (!empty($selector)) {
1425            if (qp($m, NULL, $this->options)->is($selector) > 0) {
1426              $found->attach($m);
1427              break;
1428            }
1429          }
1430          else {
1431            $found->attach($m);
1432            break;
1433          }
1434        }
1435      }
1436    }
1437    $this->setMatches($found);
1438    return $this;
1439  }
1440  
1441  public function nextAll($selector = NULL) {
1442    $found = new SplObjectStorage();
1443    foreach ($this->matches as $m) {
1444      while (isset($m->nextSibling)) {
1445        $m = $m->nextSibling;
1446        if ($m->nodeType === XML_ELEMENT_NODE) {
1447          if (!empty($selector)) {
1448            if (qp($m, NULL, $this->options)->is($selector) > 0) {
1449              $found->attach($m);
1450            }
1451          }
1452          else {
1453            $found->attach($m);
1454          }
1455        }
1456      }
1457    }
1458    $this->setMatches($found);
1459    return $this;
1460  }
1461  
1462  public function prev($selector = NULL) {
1463    $found = new SplObjectStorage();
1464    foreach ($this->matches as $m) {
1465      while (isset($m->previousSibling)) {
1466        $m = $m->previousSibling;
1467        if ($m->nodeType === XML_ELEMENT_NODE) {
1468          if (!empty($selector)) {
1469            if (qp($m, NULL, $this->options)->is($selector)) {
1470              $found->attach($m);
1471              break;
1472            }
1473          }
1474          else {
1475            $found->attach($m);
1476            break;
1477          }
1478        }
1479      }
1480    }
1481    $this->setMatches($found);
1482    return $this;
1483  }
1484  
1485  public function prevAll($selector = NULL) {
1486    $found = new SplObjectStorage();
1487    foreach ($this->matches as $m) {
1488      while (isset($m->previousSibling)) {
1489        $m = $m->previousSibling;
1490        if ($m->nodeType === XML_ELEMENT_NODE) {
1491          if (!empty($selector)) {
1492            if (qp($m, NULL, $this->options)->is($selector)) {
1493              $found->attach($m);
1494            }
1495          }
1496          else {
1497            $found->attach($m);
1498          }
1499        }
1500      }
1501    }
1502    $this->setMatches($found);
1503    return $this;
1504  }
1505  
1506  public function peers($selector = NULL) {
1507    $found = new SplObjectStorage();
1508    foreach ($this->matches as $m) {
1509      foreach ($m->parentNode->childNodes as $kid) {
1510        if ($kid->nodeType == XML_ELEMENT_NODE && $m !== $kid) {
1511          if (!empty($selector)) {
1512            if (qp($kid, NULL, $this->options)->is($selector)) {
1513              $found->attach($kid);
1514            }
1515          }
1516          else {
1517            $found->attach($kid);
1518          }
1519        }
1520      }
1521    }
1522    $this->setMatches($found);
1523    return $this;
1524  }
1525  
1526  public function addClass($class) {
1527    foreach ($this->matches as $m) {
1528      if ($m->hasAttribute('class')) {
1529        $val = $m->getAttribute('class');
1530        $m->setAttribute('class', $val . ' ' . $class);
1531      }
1532      else {
1533        $m->setAttribute('class', $class);
1534      }
1535    }
1536    return $this;
1537  }
1538  
1539  public function removeClass($class) {
1540    foreach ($this->matches as $m) {
1541      if ($m->hasAttribute('class')) {
1542        $vals = explode(' ', $m->getAttribute('class'));
1543        if (in_array($class, $vals)) {
1544          $buf = array();
1545          foreach ($vals as $v) {
1546            if ($v != $class) $buf[] = $v;
1547          }
1548          if (count($buf) == 0)
1549            $m->removeAttribute('class');
1550          else
1551            $m->setAttribute('class', implode(' ', $buf));
1552        }
1553      }
1554    }
1555    return $this;
1556  }
1557  
1558  public function hasClass($class) {
1559    foreach ($this->matches as $m) {
1560      if ($m->hasAttribute('class')) {
1561        $vals = explode(' ', $m->getAttribute('class'));
1562        if (in_array($class, $vals)) return TRUE;
1563      }
1564    }
1565    return FALSE;
1566  }
1567
1568  
1569  public function branch($selector = NULL) {
1570    $temp = qp($this->matches, NULL, $this->options);
1571    if (isset($selector)) $temp->find($selector);
1572    return $temp;
1573  }
1574  
1575  public function cloneAll() {
1576    $found = new SplObjectStorage();
1577    foreach ($this->matches as $m) $found->attach($m->cloneNode(TRUE));
1578    $this->setMatches($found, FALSE);
1579    return $this;
1580  }
1581  
1582  
1583  public function __clone() {
1584    
1585    
1586    
1587    $this->cloneAll();
1588  }  
1589
1590  
1591  public function detach($selector = NULL) {
1592
1593    if(!empty($selector))
1594    $this->find($selector);
1595
1596    $found = new SplObjectStorage();
1597    $this->last = $this->matches;
1598    foreach ($this->matches as $item) {
1599      
1600      
1601      $found->attach($item->parentNode->removeChild($item));
1602    }
1603    $this->setMatches($found);
1604    return $this;
1605  }
1606
1607  
1608  public function attach(QueryPath $dest) {
1609    foreach ($this->last as $m) $dest->append($m);
1610    return $this;
1611  }
1612  
1613  
1614  public function has($contained) {
1615    $found = new SplObjectStorage();
1616    
1617    
1618    $nodes = array();
1619    if (is_string($contained)) {
1620      
1621      $nodes = $this->branch($contained)->get();
1622    }
1623    elseif ($contained instanceof DOMNode) {
1624      
1625      $nodes = array($contained);
1626    }
1627    
1628    
1629    
1630    
1631    foreach ($nodes as $original_node) {
1632      $node = $original_node;
1633      while (!empty($node)) {
1634        if ($this->matches->contains($node)) {
1635          $found->attach($node);
1636        }
1637        $node = $node->parentNode;
1638      }
1639    }
1640    
1641    $this->setMatches($found);
1642    return $this;
1643  }
1644
1645  
1646  public function emptyElement() {
1647    $this->removeChildren();
1648    return $this;
1649  }
1650  
1651  
1652  public function even() {
1653    $found = new SplObjectStorage();
1654    $even = false;
1655    foreach ($this->matches as $m) {
1656      if ($even && $m->nodeType == XML_ELEMENT_NODE) $found->attach($m);
1657      $even = ($even) ? false : true;
1658    }
1659    $this->setMatches($found);
1660    $this->matches = $found; 
1661    return $this;
1662  }
1663
1664  
1665  public function odd() {
1666    $found = new SplObjectStorage();
1667    $odd = true;
1668    foreach ($this->matches as $m) {
1669      if ($odd && $m->nodeType == XML_ELEMENT_NODE) $found->attach($m);
1670      $odd = ($odd) ? false : true;
1671    }
1672    $this->setMatches($found);
1673    $this->matches = $found; 
1674    return $this;
1675  }
1676  
1677  
1678  public function first() {
1679    $found = new SplObjectStorage();
1680    foreach ($this->matches as $m) {
1681      if ($m->nodeType == XML_ELEMENT_NODE) {
1682        $found->attach($m);
1683        break;
1684      }
1685    }
1686    $this->setMatches($found);
1687    $this->matches = $found; 
1688    return $this;
1689  }
1690
1691  
1692  public function firstChild() {
1693    
1694    $found = new SplObjectStorage();
1695    $flag = false;
1696    foreach ($this->matches as $m) {
1697      foreach($m->childNodes as $c) {
1698        if ($c->nodeType == XML_ELEMENT_NODE) {
1699          $found->attach($c);
1700          $flag = true;
1701          break;
1702        }
1703      }
1704      if($flag) break;
1705    }
1706    $this->setMatches($found);
1707    $this->matches = $found; 
1708    return $this;
1709  }
1710
1711  
1712  public function last() {
1713    $found = new SplObjectStorage();
1714    $item = null;
1715    foreach ($this->matches as $m) {
1716      if ($m->nodeType == XML_ELEMENT_NODE) {
1717        $item = $m;
1718      }
1719    }
1720    if ($item) {
1721      $found->attach($item);
1722    }
1723    $this->setMatches($found);
1724    $this->matches = $found; 
1725    return $this;
1726  }
1727
1728  
1729  public function lastChild() {
1730    $found = new SplObjectStorage();
1731    $item = null;
1732    foreach ($this->matches as $m) {
1733      foreach($m->childNodes as $c) {
1734        if ($c->nodeType == XML_ELEMENT_NODE) {
1735          $item = $c;
1736        }
1737      }
1738      if ($item) {
1739        $found->attach($item);
1740        $item = null;
1741      }
1742    }
1743    $this->setMatches($found);
1744    $this->matches = $found; 
1745    return $this;
1746  }
1747
1748  
1749  public function nextUntil($selector = NULL) {
1750    $found = new SplObjectStorage();
1751    foreach ($this->matches as $m) {
1752      while (isset($m->nextSibling)) {
1753        $m = $m->nextSibling;
1754        if ($m->nodeType === XML_ELEMENT_NODE) {
1755          if (!empty($selector)) {
1756            if (qp($m, NULL, $this->options)->is($selector) > 0) {
1757              break;
1758            }
1759            else {
1760              $found->attach($m);
1761            }
1762          }
1763          else {
1764            $found->attach($m);
1765          }
1766        }
1767      }
1768    }
1769    $this->setMatches($found);
1770    return $this;
1771  } 
1772
1773  
1774  public function prevUntil($selector = NULL) {
1775    $found = new SplObjectStorage();
1776    foreach ($this->matches as $m) {
1777      while (isset($m->previousSibling)) {
1778        $m = $m->previousSibling;
1779        if ($m->nodeType === XML_ELEMENT_NODE) {
1780          if (!empty($selector) && qp($m, NULL, $this->options)->is($selector))
1781          break;
1782          else
1783          $found->attach($m);
1784        }
1785      }
1786    }
1787    $this->setMatches($found);
1788    return $this;
1789  }
1790  
1791  
1792  public function parentsUntil($selector = NULL) {
1793    $found = new SplObjectStorage();
1794    foreach ($this->matches as $m) {
1795      while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
1796        $m = $m->parentNode;
1797        
1798        if ($m->nodeType === XML_ELEMENT_NODE) {
1799          if (!empty($selector)) {
1800            if (qp($m, NULL, $this->options)->is($selector) > 0)
1801            break;
1802            else
1803            $found->attach($m);
1804          }
1805          else
1806          $found->attach($m);
1807        }
1808      }
1809    }
1810    $this->setMatches($found);
1811    return $this;
1812  }
1813  
1814  
1815  
1816  
1817  
1818  protected function isXMLish($string) {
1819    
1820    
1821    
1822    return (strpos($string, '<') !== FALSE && strpos($string, '>') !== FALSE);
1823    
1824  }
1825  
1826  private function parseXMLString($string, $flags = NULL) {
1827    
1828    $document = new DOMDocument('1.0');
1829    $lead = strtolower(substr($string, 0, 5)); 
1830    try {
1831      set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);
1832      
1833      if (isset($this->options['convert_to_encoding'])) {
1834        
1835        
1836        $from_enc = isset($this->options['convert_from_encoding']) ? $this->options['convert_from_encoding'] : 'auto';
1837        $to_enc = $this->options['convert_to_encoding'];
1838        
1839        if (function_exists('mb_convert_encoding')) {
1840          $string = mb_convert_encoding($string, $to_enc, $from_enc);
1841        }
1842        
1843      }
1844      
1845      
1846      
1847      if (!empty($this->options['strip_low_ascii'])) {
1848        $string = filter_var($string, FILTER_UNSAFE_RAW, FILTER_FLAG_ENCODE_LOW);
1849      }
1850      
1851      
1852      if (empty($this->options['use_parser'])) {
1853        $useParser = '';
1854      }
1855      else {
1856        $useParser = strtolower($this->options['use_parser']);
1857      }
1858      
1859      
1860      if ($useParser == 'html') {
1861        $document->loadHTML($string);
1862      }
1863      
1864      elseif ($lead == '<?xml' || $useParser == 'xml') {
1865        if ($this->options['replace_entities']) {
1866          $string = QueryPathEntities::replaceAllEntities($string);
1867        }
1868        $document->loadXML($string, $flags);
1869      }
1870      
1871      else {
1872        $document->loadHTML($string);
1873      }
1874    }
1875    
1876    catch (Exception $e) {
1877      restore_error_handler();
1878      throw $e;
1879    }
1880    restore_error_handler();
1881    
1882    if (empty($document)) {
1883      throw new QueryPathParseException('Unknown parser exception.');
1884    }
1885    return $document;
1886  }
1887  
1888  
1889  public function setMatches($matches, $unique = TRUE) {
1890    
1891    
1892    $this->last = $this->matches;
1893    
1894    
1895    if ($matches instanceof SplObjectStorage) {
1896      $this->matches = $matches;
1897    }
1898    
1899    elseif (is_array($matches)) {
1900      trigger_error('Legacy array detected.');
1901      $tmp = new SplObjectStorage();
1902      foreach ($matches as $m) $tmp->attach($m);
1903      $this->matches = $tmp;
1904    }
1905    
1906    
1907    else {
1908      $found = new SplObjectStorage();
1909      if (isset($matches)) $found->attach($matches);
1910      $this->matches = $found;
1911    }
1912    
1913    
1914    $this->length = $this->matches->count();
1915  }
1916  
1917  
1918  private function noMatches() {
1919    $this->setMatches(NULL);
1920  }
1921    
1922  
1923  private function getNthMatch($index) {
1924    if ($index > $this->matches->count() || $index < 0) return;
1925    
1926    $i = 0;
1927    foreach ($this->matches as $m) {
1928      if ($i++ == $index) return $m;
1929    }
1930  }
1931  
1932  
1933  private function getFirstMatch() {
1934    $this->matches->rewind();
1935    return $this->matches->current();
1936  }
1937  
1938  
1939   
1940  
1941  
1942  private function parseXMLFile($filename, $flags = NULL, $context = NULL) {
1943    
1944    
1945    
1946    if (!empty($context)) {
1947      try {
1948        set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);
1949        $contents = file_get_contents($filename, FALSE, $context);
1950        
1951      }
1952      
1953      
1954      catch(Exception $e) {
1955        restore_error_handler();
1956        throw $e;
1957      }
1958      restore_error_handler();
1959      
1960      if ($contents == FALSE) {
1961        throw new QueryPathParseException(sprintf('Contents of the file %s could not be retrieved.', $filename));
1962      }
1963      
1964      
1965      
1966      
1967      return $this->parseXMLString($contents, $flags);
1968    }
1969    
1970    $document = new DOMDocument();
1971    $lastDot = strrpos($filename, '.');
1972    
1973    $htmlExtensions = array(
1974      '.html' => 1,
1975      '.htm' => 1,
1976    );
1977    
1978    
1979    if (empty($this->options['use_parser'])) {
1980      $useParser = '';
1981    }
1982    else {
1983      $useParser = strtolower($this->options['use_parser']);
1984    }
1985    
1986    $ext = $lastDot !== FALSE ? strtolower(substr($filename, $lastDot)) : '';
1987    
1988    try {
1989      set_error_handler(array('QueryPathParseException', 'initializeFromError'), $this->errTypes);
1990      
1991      
1992      if ($useParser == 'xml') {
1993        $r = $document->load($filename, $flags);
1994      }
1995      
1996      elseif (isset($htmlExtensions[$ext]) || $useParser == 'html') {
1997        
1998        $r = $document->loadHTMLFile($filename);
1999      }
2000      
2001      else {
2002        $r = $document->load($filename, $flags);
2003      }
2004      
2005    }
2006    
2007    catch (Exception $e) {
2008      restore_error_handler();
2009      throw $e;
2010    }
2011    restore_error_handler();
2012    
2013    
2014    
2015    
2016    return $document;
2017  }
2018  
2019  
2020  public function __call($name, $arguments) {
2021    
2022    if (!QueryPathExtensionRegistry::$useRegistry) {
2023      throw new QueryPathException("No method named $name found (Extensions disabled).");      
2024    }
2025    
2026    
2027    
2028    
2029    
2030    
2031    
2032    
2033    
2034    
2035    
2036    if (empty($this->ext)) {
2037      
2038      $this->ext = QueryPathExtensionRegistry::getExtensions($this);
2039    }
2040    
2041    
2042    if (!empty($this->ext) && QueryPathExtensionRegistry::hasMethod($name)) {
2043      $owner = QueryPathExtensionRegistry::getMethodClass($name);
2044      $method = new ReflectionMethod($owner, $name);
2045      return $method->invokeArgs($this->ext[$owner], $arguments);
2046    }
2047    throw new QueryPathException("No method named $name found. Possibly missing an extension.");
2048  }
2049  
2050  
2051   
2052  
2053  
2054  public function getIterator() {
2055    $i = new QueryPathIterator($this->matches);
2056    $i->options = $this->options;
2057    return $i;
2058  }
2059}
2060
2061
2062class QueryPathEntities {
2063  
2064  
2065  
2066  protected static $regex = '/&([\w]+);|&#([\d]+);|&#(x[0-9a-fA-F]+);|(&)/m';
2067  
2068  
2069  public static function replaceAllEntities($string) {
2070    return preg_replace_callback(self::$regex, 'QueryPathEntities::doReplacement', $string);
2071  }
2072  
2073  
2074  protected static function doReplacement($matches) {
2075    
2076    
2077
2078    
2079    
2080    $count = count($matches);
2081    switch ($count) {
2082      case 2:
2083        
2084        return '&#' . self::replaceEntity($matches[1]) . ';';
2085      case 3:
2086      case 4:
2087        
2088        return '&#' . $matches[$count-1] . ';'; 
2089      case 5:
2090        
2091        return '&#38;';
2092    }
2093  }
2094  
2095  
2096  public static function replaceEntity($entity) {
2097    return self::$entity_array[$entity];
2098  }
2099  
2100  
2101  private static $entity_array = array(
2102	  'nbsp' => 160, 'iexcl' => 161, 'cent' => 162, 'pound' => 163, 
2103	  'curren' => 164, 'yen' => 165, 'brvbar' => 166, 'sect' => 167, 
2104	  'uml' => 168, 'copy' => 169, 'ordf' => 170, 'laquo' => 171, 
2105	  'not' => 172, 'shy' => 173, 'reg' => 174, 'macr' => 175, 'deg' => 176, 
2106	  'plusmn' => 177, 'sup2' => 178, 'sup3' => 179, 'acute' => 180, 
2107	  'micro' => 181, 'para' => 182, 'middot' => 183, 'cedil' => 184, 
2108	  'sup1' => 185, 'ordm' => 186, 'raquo' => 187, 'frac14' => 188, 
2109	  'frac12' => 189, 'frac34' => 190, 'iquest' => 191, 'Agrave' => 192, 
2110	  'Aacute' => 193, 'Acirc' => 194, 'Atilde' => 195, 'Auml' => 196, 
2111	  'Aring' => 197, 'AElig' => 198, 'Ccedil' => 199, 'Egrave' => 200, 
2112	  'Eacute' => 201, 'Ecirc' => 202, 'Euml' => 203, 'Igrave' => 204, 
2113	  'Iacute' => 205, 'Icirc' => 206, 'Iuml' => 207, 'ETH' => 208, 
2114	  'Ntilde' => 209, 'Ograve' => 210, 'Oacute' => 211, 'Ocirc' => 212, 
2115	  'Otilde' => 213, 'Ouml' => 214, 'times' => 215, 'Oslash' => 216, 
2116	  'Ugrave' => 217, 'Uacute' => 218, 'Ucirc' => 219, 'Uuml' => 220, 
2117	  'Yacute' => 221, 'THORN' => 222, 'szlig' => 223, 'agrave' => 224, 
2118	  'aacute' => 225, 'acirc' => 226, 'atilde' => 227, 'auml' => 228, 
2119	  'aring' => 229, 'aelig' => 230, 'ccedil' => 231, 'egrave' => 232, 
2120	  'eacute' => 233, 'ecirc' => 234, 'euml' => 235, 'igrave' => 236, 
2121	  'iacute' => 237, 'icirc' => 238, 'iuml' => 239, 'eth' => 240, 
2122	  'ntilde' => 241, 'ograve' => 242, 'oacute' => 243, 'ocirc' => 244, 
2123	  'otilde' => 245, 'ouml' => 246, 'divide' => 247, 'oslash' => 248, 
2124	  'ugrave' => 249, 'uacute' => 250, 'ucirc' => 251, 'uuml' => 252, 
2125	  'yacute' => 253, 'thorn' => 254, 'yuml' => 255, 'quot' => 34, 
2126	  'amp' => 38, 'lt' => 60, 'gt' => 62, 'apos' => 39, 'OElig' => 338, 
2127	  'oelig' => 339, 'Scaron' => 352, 'scaron' => 353, 'Yuml' => 376, 
2128	  'circ' => 710, 'tilde' => 732, 'ensp' => 8194, 'emsp' => 8195, 
2129	  'thinsp' => 8201, 'zwnj' => 8204, 'zwj' => 8205, 'lrm' => 8206, 
2130	  'rlm' => 8207, 'ndash' => 8211, 'mdash' => 8212, 'lsquo' => 8216, 
2131	  'rsquo' => 8217, 'sbquo' => 8218, 'ldquo' => 8220, 'rdquo' => 8221, 
2132	  'bdquo' => 8222, 'dagger' => 8224, 'Dagger' => 8225, 'permil' => 8240, 
2133	  'lsaquo' => 8249, 'rsaquo' => 8250, 'euro' => 8364, 'fnof' => 402, 
2134	  'Alpha' => 913, 'Beta' => 914, 'Gamma' => 915, 'Delta' => 916, 
2135	  'Epsilon' => 917, 'Zeta' => 918, 'Eta' => 919, 'Theta' => 920, 
2136	  'Iota' => 921, 'Kappa' => 922, 'Lambda' => 923, 'Mu' => 924, 'Nu' => 925, 
2137	  'Xi' => 926, 'Omicron' => 927, 'Pi' => 928, 'Rho' => 929, 'Sigma' => 931,
2138	  'Tau' => 932, 'Upsilon' => 933, 'Phi' => 934, 'Chi' => 935, 'Psi' => 936,
2139	  'Omega' => 937, 'alpha' => 945, 'beta' => 946, 'gamma' => 947, 
2140	  'delta' => 948, 'epsilon' => 949, 'zeta' => 950, 'eta' => 951, 
2141	  'theta' => 952, 'iota' => 953, 'kappa' => 954, 'lambda' => 955, 
2142	  'mu' => 956, 'nu' => 957, 'xi' => 958, 'omicron' => 959, 'pi' => 960, 
2143	  'rho' => 961, 'sigmaf' => 962, 'sigma' => 963, 'tau' => 964, 
2144	  'upsilon' => 965, 'phi' => 966, 'chi' => 967, 'psi' => 968, 
2145	  'omega' => 969, 'thetasym' => 977, 'upsih' => 978, 'piv' => 982, 
2146	  'bull' => 8226, 'hellip' => 8230, 'prime' => 8242, 'Prime' => 8243, 
2147	  'oline' => 8254, 'frasl' => 8260, 'weierp' => 8472, 'image' => 8465, 
2148	  'real' => 8476, 'trade' => 8482, 'alefsym' => 8501, 'larr' => 8592, 
2149	  'uarr' => 8593, 'rarr' => 8594, 'darr' => 8595, 'harr' => 8596, 
2150	  'crarr' => 8629, 'lArr' => 8656, 'uArr' => 8657, 'rArr' => 8658, 
2151	  'dArr' => 8659, 'hArr' => 8660, 'forall' => 8704, 'part' => 8706, 
2152	  'exist' => 8707, 'empty' => 8709, 'nabla' => 8711, 'isin' => 8712, 
2153	  'notin' => 8713, 'ni' => 8715, 'prod' => 8719, 'sum' => 8721, 
2154	  'minus' => 8722, 'lowast' => 8727, 'radic' => 8730, 'prop' => 8733, 
2155	  'infin' => 8734, 'ang' => 8736, 'and' => 8743, 'or' => 8744, 'cap' => 8745, 
2156	  'cup' => 8746, 'int' => 8747, 'there4' => 8756, 'sim' => 8764, 
2157	  'cong' => 8773, 'asymp' => 8776, 'ne' => 8800, 'equiv' => 8801, 
2158	  'le' => 8804, 'ge' => 8805, 'sub' => 8834, 'sup' => 8835, 'nsub' => 8836, 
2159	  'sube' => 8838, 'supe' => 8839, 'oplus' => 8853, 'otimes' => 8855, 
2160	  'perp' => 8869, 'sdot' => 8901, 'lceil' => 8968, 'rceil' => 8969, 
2161	  'lfloor' => 8970, 'rfloor' => 8971, 'lang' => 9001, 'rang' => 9002, 
2162	  'loz' => 9674, 'spades' => 9824, 'clubs' => 9827, 'hearts' => 9829, 
2163	  'diams' => 9830
2164	);
2165}
2166
2167
2168class QueryPathIterator extends IteratorIterator {
2169  public $options = array();
2170  private $qp = NULL;
2171  
2172  public function current() {
2173    if (!isset($this->qp)) {
2174      $this->qp = qp(parent::current(), NULL, $this->options);
2175    }
2176    else {
2177      $splos = new SplObjectStorage();
2178      $splos->attach(parent::current());
2179      $this->qp->setMatches($splos);
2180    }
2181    return $this->qp;
2182  }
2183}
2184
2185
2186class QueryPathOptions {
2187  
2188  
2189  static $options = array();
2190  
2191  
2192  static function set($array) {
2193    self::$options = $array;
2194  }
2195  
2196  
2197  static function get() {
2198    return self::$options;
2199  }
2200  
2201  
2202  static function merge($array) {
2203    self::$options = $array + self::$options;
2204  }
2205  
2206  
2207  static function has($key) {
2208    return array_key_exists($key, self::$options);
2209  }
2210  
2211}
2212
2213
2214class QueryPathException extends Exception {}
2215
2216
2217class QueryPathParseException extends QueryPathException {
2218  const ERR_MSG_FORMAT = 'Parse error in %s on line %d column %d: %s (%d)';
2219  const WARN_MSG_FORMAT = 'Parser warning in %s on line %d column %d: %s (%d)';
2220  
2221  public function __construct($msg = '', $code = 0, $file = NULL, $line = NULL) {
2222
2223    $msgs = array();
2224    foreach(libxml_get_errors() as $err) {
2225      $format = $err->level == LIBXML_ERR_WARNING ? self::WARN_MSG_FORMAT : self::ERR_MSG_FORMAT;
2226      $msgs[] = sprintf($format, $err->file, $err->line, $err->column, $err->message, $err->code);
2227    }
2228    $msg .= implode("\n", $msgs);
2229    
2230    if (isset($file)) {
2231      $msg .= ' (' . $file;
2232      if (isset($line)) $msg .= ': ' . $line;
2233      $msg .= ')';
2234    }
2235    
2236    parent::__construct($msg, $code);
2237  }
2238  
2239  public static function initializeFromError($code, $str, $file, $line, $cxt) {
2240    
2241    $class = __CLASS__;
2242    throw new $class($str, $code, $file, $line);
2243  }
2244}
2245
2246
2247class QueryPathIOException extends QueryPathParseException {
2248  public static function initializeFromError($code, $str, $file, $line, $cxt) {
2249    $class = __CLASS__;
2250    throw new $class($str, $code, $file, $line);
2251  }
2252  
2253}