[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/_test/core/ -> phpQuery-onefile.php (source)

   1  <?php
   2  /**
   3   * phpQuery is a server-side, chainable, CSS3 selector driven
   4   * Document Object Model (DOM) API based on jQuery JavaScript Library.
   5   *
   6   * @version 0.9.5
   7   * @link http://code.google.com/p/phpquery/
   8   * @link http://phpquery-library.blogspot.com/
   9   * @link http://jquery.com/
  10   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  11   * @license http://www.opensource.org/licenses/mit-license.php MIT License
  12   * @package phpQuery
  13   * @deprecated 2022-10-19
  14   */
  15  
  16  // class names for instanceof
  17  // TODO move them as class constants into phpQuery
  18  define('DOMDOCUMENT', 'DOMDocument');
  19  define('DOMELEMENT', 'DOMElement');
  20  define('DOMNODELIST', 'DOMNodeList');
  21  define('DOMNODE', 'DOMNode');
  22  
  23  /**
  24   * DOMEvent class.
  25   *
  26   * Based on
  27   * @link http://developer.mozilla.org/En/DOM:event
  28   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
  29   * @package phpQuery
  30   * @todo implement ArrayAccess ?
  31   */
  32  class DOMEvent {
  33      /**
  34       * Returns a boolean indicating whether the event bubbles up through the DOM or not.
  35       *
  36       * @var unknown_type
  37       */
  38      public $bubbles = true;
  39      /**
  40       * Returns a boolean indicating whether the event is cancelable.
  41       *
  42       * @var unknown_type
  43       */
  44      public $cancelable = true;
  45      /**
  46       * Returns a reference to the currently registered target for the event.
  47       *
  48       * @var unknown_type
  49       */
  50      public $currentTarget;
  51      /**
  52       * Returns detail about the event, depending on the type of event.
  53       *
  54       * @var unknown_type
  55       * @link http://developer.mozilla.org/en/DOM/event.detail
  56       */
  57      public $detail; // ???
  58      /**
  59       * Used to indicate which phase of the event flow is currently being evaluated.
  60       *
  61       * NOT IMPLEMENTED
  62       *
  63       * @var unknown_type
  64       * @link http://developer.mozilla.org/en/DOM/event.eventPhase
  65       */
  66      public $eventPhase; // ???
  67      /**
  68       * The explicit original target of the event (Mozilla-specific).
  69       *
  70       * NOT IMPLEMENTED
  71       *
  72       * @var unknown_type
  73       */
  74      public $explicitOriginalTarget; // moz only
  75      /**
  76       * The original target of the event, before any retargetings (Mozilla-specific).
  77       *
  78       * NOT IMPLEMENTED
  79       *
  80       * @var unknown_type
  81       */
  82      public $originalTarget; // moz only
  83      /**
  84       * Identifies a secondary target for the event.
  85       *
  86       * @var unknown_type
  87       */
  88      public $relatedTarget;
  89      /**
  90       * Returns a reference to the target to which the event was originally dispatched.
  91       *
  92       * @var unknown_type
  93       */
  94      public $target;
  95      /**
  96       * Returns the time that the event was created.
  97       *
  98       * @var unknown_type
  99       */
 100      public $timeStamp;
 101      /**
 102       * Returns the name of the event (case-insensitive).
 103       */
 104      public $type;
 105      public $runDefault = true;
 106      public $data = null;
 107      public function __construct($data) {
 108          foreach($data as $k => $v) {
 109              $this->$k = $v;
 110          }
 111          if (! $this->timeStamp)
 112              $this->timeStamp = time();
 113      }
 114      /**
 115       * Cancels the event (if it is cancelable).
 116       *
 117       */
 118      public function preventDefault() {
 119          $this->runDefault = false;
 120      }
 121      /**
 122       * Stops the propagation of events further along in the DOM.
 123       *
 124       */
 125      public function stopPropagation() {
 126          $this->bubbles = false;
 127      }
 128  }
 129  
 130  
 131  /**
 132   * DOMDocumentWrapper class simplifies work with DOMDocument.
 133   *
 134   * Know bug:
 135   * - in XHTML fragments, <br /> changes to <br clear="none" />
 136   *
 137   * @todo check XML catalogs compatibility
 138   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
 139   * @package phpQuery
 140   */
 141  class DOMDocumentWrapper {
 142      /**
 143       * @var DOMDocument
 144       */
 145      public $document;
 146      public $id;
 147      /**
 148       * @todo Rewrite as method and quess if null.
 149       * @var unknown_type
 150       */
 151      public $contentType = '';
 152      public $xpath;
 153      public $uuid = 0;
 154      public $data = array();
 155      public $dataNodes = array();
 156      public $events = array();
 157      public $eventsNodes = array();
 158      public $eventsGlobal = array();
 159      /**
 160       * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28
 161       * @var unknown_type
 162       */
 163      public $frames = array();
 164      /**
 165       * Document root, by default equals to document itself.
 166       * Used by documentFragments.
 167       *
 168       * @var DOMNode
 169       */
 170      public $root;
 171      public $isDocumentFragment;
 172      public $isXML = false;
 173      public $isXHTML = false;
 174      public $isHTML = false;
 175      public $charset;
 176      public function __construct($markup = null, $contentType = null, $newDocumentID = null) {
 177          if (isset($markup))
 178              $this->load($markup, $contentType, $newDocumentID);
 179          $this->id = $newDocumentID
 180              ? $newDocumentID
 181              : md5(microtime());
 182      }
 183      public function load($markup, $contentType = null, $newDocumentID = null) {
 184  //      phpQuery::$documents[$id] = $this;
 185          $this->contentType = strtolower($contentType);
 186          if ($markup instanceof DOMDOCUMENT) {
 187              $this->document = $markup;
 188              $this->root = $this->document;
 189              $this->charset = $this->document->encoding;
 190              // TODO isDocumentFragment
 191          } else {
 192              $loaded = $this->loadMarkup($markup);
 193          }
 194          if ($loaded) {
 195  //          $this->document->formatOutput = true;
 196              $this->document->preserveWhiteSpace = true;
 197              $this->xpath = new DOMXPath($this->document);
 198              $this->afterMarkupLoad();
 199              return true;
 200              // remember last loaded document
 201  //          return phpQuery::selectDocument($id);
 202          }
 203          return false;
 204      }
 205      protected function afterMarkupLoad() {
 206          if ($this->isXHTML) {
 207              $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
 208          }
 209      }
 210      protected function loadMarkup($markup) {
 211          $loaded = false;
 212          if ($this->contentType) {
 213              self::debug("Load markup for content type {$this->contentType}");
 214              // content determined by contentType
 215              list($contentType, $charset) = $this->contentTypeToArray($this->contentType);
 216              switch($contentType) {
 217                  case 'text/html':
 218                      phpQuery::debug("Loading HTML, content type '{$this->contentType}'");
 219                      $loaded = $this->loadMarkupHTML($markup, $charset);
 220                  break;
 221                  case 'text/xml':
 222                  case 'application/xhtml+xml':
 223                      phpQuery::debug("Loading XML, content type '{$this->contentType}'");
 224                      $loaded = $this->loadMarkupXML($markup, $charset);
 225                  break;
 226                  default:
 227                      // for feeds or anything that sometimes doesn't use text/xml
 228                      if (strpos('xml', $this->contentType) !== false) {
 229                          phpQuery::debug("Loading XML, content type '{$this->contentType}'");
 230                          $loaded = $this->loadMarkupXML($markup, $charset);
 231                      } else
 232                          phpQuery::debug("Could not determine document type from content type '{$this->contentType}'");
 233              }
 234          } else {
 235              // content type autodetection
 236              if ($this->isXML($markup)) {
 237                  phpQuery::debug("Loading XML, isXML() == true");
 238                  $loaded = $this->loadMarkupXML($markup);
 239                  if (! $loaded && $this->isXHTML) {
 240                      phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true');
 241                      $loaded = $this->loadMarkupHTML($markup);
 242                  }
 243              } else {
 244                  phpQuery::debug("Loading HTML, isXML() == false");
 245                  $loaded = $this->loadMarkupHTML($markup);
 246              }
 247          }
 248          return $loaded;
 249      }
 250      protected function loadMarkupReset() {
 251          $this->isXML = $this->isXHTML = $this->isHTML = false;
 252      }
 253      protected function documentCreate($charset, $version = '1.0') {
 254          if (! $version)
 255              $version = '1.0';
 256          $this->document = new DOMDocument($version, $charset);
 257          $this->charset = $this->document->encoding;
 258  //      $this->document->encoding = $charset;
 259          $this->document->formatOutput = true;
 260          $this->document->preserveWhiteSpace = true;
 261      }
 262      protected function loadMarkupHTML($markup, $requestedCharset = null) {
 263          if (phpQuery::$debug)
 264              phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250));
 265          $this->loadMarkupReset();
 266          $this->isHTML = true;
 267          if (!isset($this->isDocumentFragment))
 268              $this->isDocumentFragment = self::isDocumentFragmentHTML($markup);
 269          $charset = null;
 270          $documentCharset = $this->charsetFromHTML($markup);
 271          $addDocumentCharset = false;
 272          if ($documentCharset) {
 273              $charset = $documentCharset;
 274              $markup = $this->charsetFixHTML($markup);
 275          } else if ($requestedCharset) {
 276              $charset = $requestedCharset;
 277          }
 278          if (! $charset)
 279              $charset = phpQuery::$defaultCharset;
 280          // HTTP 1.1 says that the default charset is ISO-8859-1
 281          // @see http://www.w3.org/International/O-HTTP-charset
 282          if (! $documentCharset) {
 283              $documentCharset = 'ISO-8859-1';
 284              $addDocumentCharset = true;
 285          }
 286          // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding'
 287          // Worse, some pages can have mixed encodings... we'll try not to worry about that
 288          $requestedCharset = strtoupper($requestedCharset);
 289          $documentCharset = strtoupper($documentCharset);
 290          phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset");
 291          if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) {
 292              phpQuery::debug("CHARSET CONVERT");
 293              // Document Encoding Conversion
 294              // http://code.google.com/p/phpquery/issues/detail?id=86
 295              if (function_exists('mb_detect_encoding')) {
 296                  $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO');
 297                  $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets));
 298                  if (! $docEncoding)
 299                      $docEncoding = $documentCharset; // ok trust the document
 300                  phpQuery::debug("DETECTED '$docEncoding'");
 301                  // Detected does not match what document says...
 302                  if ($docEncoding !== $documentCharset) {
 303                      // Tricky..
 304                  }
 305                  if ($docEncoding !== $requestedCharset) {
 306                      phpQuery::debug("CONVERT $docEncoding => $requestedCharset");
 307                      $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding);
 308                      $markup = $this->charsetAppendToHTML($markup, $requestedCharset);
 309                      $charset = $requestedCharset;
 310                  }
 311              } else {
 312                  phpQuery::debug("TODO: charset conversion without mbstring...");
 313              }
 314          }
 315          $return = false;
 316          if ($this->isDocumentFragment) {
 317              phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'");
 318              $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
 319          } else {
 320              if ($addDocumentCharset) {
 321                  phpQuery::debug("Full markup load (HTML), appending charset: '$charset'");
 322                  $markup = $this->charsetAppendToHTML($markup, $charset);
 323              }
 324              phpQuery::debug("Full markup load (HTML), documentCreate('$charset')");
 325              $this->documentCreate($charset);
 326              $return = phpQuery::$debug === 2
 327                  ? $this->document->loadHTML($markup)
 328                  : @$this->document->loadHTML($markup);
 329              if ($return)
 330                  $this->root = $this->document;
 331          }
 332          if ($return && ! $this->contentType)
 333              $this->contentType = 'text/html';
 334          return $return;
 335      }
 336      protected function loadMarkupXML($markup, $requestedCharset = null) {
 337          if (phpQuery::$debug)
 338              phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250));
 339          $this->loadMarkupReset();
 340          $this->isXML = true;
 341          // check agains XHTML in contentType or markup
 342          $isContentTypeXHTML = $this->isXHTML();
 343          $isMarkupXHTML = $this->isXHTML($markup);
 344          if ($isContentTypeXHTML || $isMarkupXHTML) {
 345              self::debug('Full markup load (XML), XHTML detected');
 346              $this->isXHTML = true;
 347          }
 348          // determine document fragment
 349          if (! isset($this->isDocumentFragment))
 350              $this->isDocumentFragment = $this->isXHTML
 351                  ? self::isDocumentFragmentXHTML($markup)
 352                  : self::isDocumentFragmentXML($markup);
 353          // this charset will be used
 354          $charset = null;
 355          // charset from XML declaration @var string
 356          $documentCharset = $this->charsetFromXML($markup);
 357          if (! $documentCharset) {
 358              if ($this->isXHTML) {
 359                  // this is XHTML, try to get charset from content-type meta header
 360                  $documentCharset = $this->charsetFromHTML($markup);
 361                  if ($documentCharset) {
 362                      phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'");
 363                      $this->charsetAppendToXML($markup, $documentCharset);
 364                      $charset = $documentCharset;
 365                  }
 366              }
 367              if (! $documentCharset) {
 368                  // if still no document charset...
 369                  $charset = $requestedCharset;
 370              }
 371          } else if ($requestedCharset) {
 372              $charset = $requestedCharset;
 373          }
 374          if (! $charset) {
 375              $charset = phpQuery::$defaultCharset;
 376          }
 377          if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) {
 378              // TODO place for charset conversion
 379  //          $charset = $requestedCharset;
 380          }
 381          $return = false;
 382          if ($this->isDocumentFragment) {
 383              phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'");
 384              $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
 385          } else {
 386              // FIXME ???
 387              if ($isContentTypeXHTML && ! $isMarkupXHTML)
 388              if (! $documentCharset) {
 389                  phpQuery::debug("Full markup load (XML), appending charset '$charset'");
 390                  $markup = $this->charsetAppendToXML($markup, $charset);
 391              }
 392              // see http://php.net/manual/en/book.dom.php#78929
 393              // LIBXML_DTDLOAD (>= PHP 5.1)
 394              // does XML ctalogues works with LIBXML_NONET
 395      //      $this->document->resolveExternals = true;
 396              // TODO test LIBXML_COMPACT for performance improvement
 397              // create document
 398              $this->documentCreate($charset);
 399              if (phpversion() < 5.1) {
 400                  $this->document->resolveExternals = true;
 401                  $return = phpQuery::$debug === 2
 402                      ? $this->document->loadXML($markup)
 403                      : @$this->document->loadXML($markup);
 404              } else {
 405                  /** @link http://php.net/manual/en/libxml.constants.php */
 406                  $libxmlStatic = phpQuery::$debug === 2
 407                      ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET
 408                      : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR;
 409                  $return = $this->document->loadXML($markup, $libxmlStatic);
 410  //              if (! $return)
 411  //                  $return = $this->document->loadHTML($markup);
 412              }
 413              if ($return)
 414                  $this->root = $this->document;
 415          }
 416          if ($return) {
 417              if (! $this->contentType) {
 418                  if ($this->isXHTML)
 419                      $this->contentType = 'application/xhtml+xml';
 420                  else
 421                      $this->contentType = 'text/xml';
 422              }
 423              return $return;
 424          } else {
 425              throw new Exception("Error loading XML markup");
 426          }
 427      }
 428      protected function isXHTML($markup = null) {
 429          if (! isset($markup)) {
 430              return strpos($this->contentType, 'xhtml') !== false;
 431          }
 432          // XXX ok ?
 433          return strpos($markup, "<!DOCTYPE html") !== false;
 434  //      return stripos($doctype, 'xhtml') !== false;
 435  //      $doctype = isset($dom->doctype) && is_object($dom->doctype)
 436  //          ? $dom->doctype->publicId
 437  //          : self::$defaultDoctype;
 438      }
 439      protected function isXML($markup) {
 440  //      return strpos($markup, '<?xml') !== false && stripos($markup, 'xhtml') === false;
 441          return strpos(substr($markup, 0, 100), '<'.'?xml') !== false;
 442      }
 443      protected function contentTypeToArray($contentType) {
 444          $matches = explode(';', trim(strtolower($contentType)));
 445          if (isset($matches[1])) {
 446              $matches[1] = explode('=', $matches[1]);
 447              // strip 'charset='
 448              $matches[1] = isset($matches[1][1]) && trim($matches[1][1])
 449                  ? $matches[1][1]
 450                  : $matches[1][0];
 451          } else
 452              $matches[1] = null;
 453          return $matches;
 454      }
 455      /**
 456       *
 457       * @param $markup
 458       * @return array contentType, charset
 459       */
 460      protected function contentTypeFromHTML($markup) {
 461          $matches = array();
 462          // find meta tag
 463          preg_match('@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
 464              $markup, $matches
 465          );
 466          if (! isset($matches[0]))
 467              return array(null, null);
 468          // get attr 'content'
 469          preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches);
 470          if (! isset($matches[0]))
 471              return array(null, null);
 472          return $this->contentTypeToArray($matches[2]);
 473      }
 474      protected function charsetFromHTML($markup) {
 475          $contentType = $this->contentTypeFromHTML($markup);
 476          return $contentType[1];
 477      }
 478      protected function charsetFromXML($markup) {
 479          $matches;
 480          // find declaration
 481          preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i',
 482              $markup, $matches
 483          );
 484          return isset($matches[2])
 485              ? strtolower($matches[2])
 486              : null;
 487      }
 488      /**
 489       * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug.
 490       *
 491       * @link http://code.google.com/p/phpquery/issues/detail?id=80
 492       * @param $html
 493       */
 494      protected function charsetFixHTML($markup) {
 495          $matches = array();
 496          // find meta tag
 497          preg_match('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
 498              $markup, $matches, PREG_OFFSET_CAPTURE
 499          );
 500          if (! isset($matches[0]))
 501              return;
 502          $metaContentType = $matches[0][0];
 503          $markup = substr($markup, 0, $matches[0][1])
 504              .substr($markup, $matches[0][1]+strlen($metaContentType));
 505          $headStart = stripos($markup, '<head>');
 506          $markup = substr($markup, 0, $headStart+6).$metaContentType
 507              .substr($markup, $headStart+6);
 508          return $markup;
 509      }
 510      protected function charsetAppendToHTML($html, $charset, $xhtml = false) {
 511          // remove existing meta[type=content-type]
 512          $html = preg_replace('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html);
 513          $meta = '<meta http-equiv="Content-Type" content="text/html;charset='
 514              .$charset.'" '
 515              .($xhtml ? '/' : '')
 516              .'>';
 517          if (strpos($html, '<head') === false) {
 518              if (strpos($hltml, '<html') === false) {
 519                  return $meta.$html;
 520              } else {
 521                  return preg_replace(
 522                      '@<html(.*?)(?(?<!\?)>)@s',
 523                      "<html\\1><head>{$meta}</head>",
 524                      $html
 525                  );
 526              }
 527          } else {
 528              return preg_replace(
 529                  '@<head(.*?)(?(?<!\?)>)@s',
 530                  '<head\\1>'.$meta,
 531                  $html
 532              );
 533          }
 534      }
 535      protected function charsetAppendToXML($markup, $charset) {
 536          $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>';
 537          return $declaration.$markup;
 538      }
 539      public static function isDocumentFragmentHTML($markup) {
 540          return stripos($markup, '<html') === false && stripos($markup, '<!doctype') === false;
 541      }
 542      public static function isDocumentFragmentXML($markup) {
 543          return stripos($markup, '<'.'?xml') === false;
 544      }
 545      public static function isDocumentFragmentXHTML($markup) {
 546          return self::isDocumentFragmentHTML($markup);
 547      }
 548      public function importAttr($value) {
 549          // TODO
 550      }
 551      /**
 552       *
 553       * @param $source
 554       * @param $target
 555       * @param $sourceCharset
 556       * @return array Array of imported nodes.
 557       */
 558      public function import($source, $sourceCharset = null) {
 559          // TODO charset conversions
 560          $return = array();
 561          if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
 562              $source = array($source);
 563  //      if (is_array($source)) {
 564  //          foreach($source as $node) {
 565  //              if (is_string($node)) {
 566  //                  // string markup
 567  //                  $fake = $this->documentFragmentCreate($node, $sourceCharset);
 568  //                  if ($fake === false)
 569  //                      throw new Exception("Error loading documentFragment markup");
 570  //                  else
 571  //                      $return = array_merge($return,
 572  //                          $this->import($fake->root->childNodes)
 573  //                      );
 574  //              } else {
 575  //                  $return[] = $this->document->importNode($node, true);
 576  //              }
 577  //          }
 578  //          return $return;
 579  //      } else {
 580  //          // string markup
 581  //          $fake = $this->documentFragmentCreate($source, $sourceCharset);
 582  //          if ($fake === false)
 583  //              throw new Exception("Error loading documentFragment markup");
 584  //          else
 585  //              return $this->import($fake->root->childNodes);
 586  //      }
 587          if (is_array($source) || $source instanceof DOMNODELIST) {
 588              // dom nodes
 589              self::debug('Importing nodes to document');
 590              foreach($source as $node)
 591                  $return[] = $this->document->importNode($node, true);
 592          } else {
 593              // string markup
 594              $fake = $this->documentFragmentCreate($source, $sourceCharset);
 595              if ($fake === false)
 596                  throw new Exception("Error loading documentFragment markup");
 597              else
 598                  return $this->import($fake->root->childNodes);
 599          }
 600          return $return;
 601      }
 602      /**
 603       * Creates new document fragment.
 604       *
 605       * @param $source
 606       * @return DOMDocumentWrapper
 607       */
 608      protected function documentFragmentCreate($source, $charset = null) {
 609          $fake = new DOMDocumentWrapper();
 610          $fake->contentType = $this->contentType;
 611          $fake->isXML = $this->isXML;
 612          $fake->isHTML = $this->isHTML;
 613          $fake->isXHTML = $this->isXHTML;
 614          $fake->root = $fake->document;
 615          if (! $charset)
 616              $charset = $this->charset;
 617  //  $fake->documentCreate($this->charset);
 618          if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
 619              $source = array($source);
 620          if (is_array($source) || $source instanceof DOMNODELIST) {
 621              // dom nodes
 622              // load fake document
 623              if (! $this->documentFragmentLoadMarkup($fake, $charset))
 624                  return false;
 625              $nodes = $fake->import($source);
 626              foreach($nodes as $node)
 627                  $fake->root->appendChild($node);
 628          } else {
 629              // string markup
 630              $this->documentFragmentLoadMarkup($fake, $charset, $source);
 631          }
 632          return $fake;
 633      }
 634      /**
 635       *
 636       * @param $document DOMDocumentWrapper
 637       * @param $markup
 638       * @return $document
 639       */
 640      private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) {
 641          // TODO error handling
 642          // TODO copy doctype
 643          // tempolary turn off
 644          $fragment->isDocumentFragment = false;
 645          if ($fragment->isXML) {
 646              if ($fragment->isXHTML) {
 647                  // add FAKE element to set default namespace
 648                  $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?>'
 649                      .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
 650                      .'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
 651                      .'<fake xmlns="http://www.w3.org/1999/xhtml">'.$markup.'</fake>');
 652                  $fragment->root = $fragment->document->firstChild->nextSibling;
 653              } else {
 654                  $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?><fake>'.$markup.'</fake>');
 655                  $fragment->root = $fragment->document->firstChild;
 656              }
 657          } else {
 658              $markup2 = phpQuery::$defaultDoctype.'<html><head><meta http-equiv="Content-Type" content="text/html;charset='
 659                  .$charset.'"></head>';
 660              $noBody = strpos($markup, '<body') === false;
 661              if ($noBody)
 662                  $markup2 .= '<body>';
 663              $markup2 .= $markup;
 664              if ($noBody)
 665                  $markup2 .= '</body>';
 666              $markup2 .= '</html>';
 667              $fragment->loadMarkupHTML($markup2);
 668              // TODO resolv body tag merging issue
 669              $fragment->root = $noBody
 670                  ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling
 671                  : $fragment->document->firstChild->nextSibling->firstChild->nextSibling;
 672          }
 673          if (! $fragment->root)
 674              return false;
 675          $fragment->isDocumentFragment = true;
 676          return true;
 677      }
 678      protected function documentFragmentToMarkup($fragment) {
 679          phpQuery::debug('documentFragmentToMarkup');
 680          $tmp = $fragment->isDocumentFragment;
 681          $fragment->isDocumentFragment = false;
 682          $markup = $fragment->markup();
 683          if ($fragment->isXML) {
 684              $markup = substr($markup, 0, strrpos($markup, '</fake>'));
 685              if ($fragment->isXHTML) {
 686                  $markup = substr($markup, strpos($markup, '<fake')+43);
 687              } else {
 688                  $markup = substr($markup, strpos($markup, '<fake>')+6);
 689              }
 690          } else {
 691                  $markup = substr($markup, strpos($markup, '<body>')+6);
 692                  $markup = substr($markup, 0, strrpos($markup, '</body>'));
 693          }
 694          $fragment->isDocumentFragment = $tmp;
 695          if (phpQuery::$debug)
 696              phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150));
 697          return $markup;
 698      }
 699      /**
 700       * Return document markup, starting with optional $nodes as root.
 701       *
 702       * @param $nodes    DOMNode|DOMNodeList
 703       * @return string
 704       */
 705      public function markup($nodes = null, $innerMarkup = false) {
 706          if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT)
 707              $nodes = null;
 708          if (isset($nodes)) {
 709              $markup = '';
 710              if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) )
 711                  $nodes = array($nodes);
 712              if ($this->isDocumentFragment && ! $innerMarkup)
 713                  foreach($nodes as $i => $node)
 714                      if ($node->isSameNode($this->root)) {
 715                      //  var_dump($node);
 716                          $nodes = array_slice($nodes, 0, $i)
 717                              + phpQuery::DOMNodeListToArray($node->childNodes)
 718                              + array_slice($nodes, $i+1);
 719                          }
 720              if ($this->isXML && ! $innerMarkup) {
 721                  self::debug("Getting outerXML with charset '{$this->charset}'");
 722                  // we need outerXML, so we can benefit from
 723                  // $node param support in saveXML()
 724                  foreach($nodes as $node)
 725                      $markup .= $this->document->saveXML($node);
 726              } else {
 727                  $loop = array();
 728                  if ($innerMarkup)
 729                      foreach($nodes as $node) {
 730                          if ($node->childNodes)
 731                              foreach($node->childNodes as $child)
 732                                  $loop[] = $child;
 733                          else
 734                              $loop[] = $node;
 735                      }
 736                  else
 737                      $loop = $nodes;
 738                  self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment");
 739                  $fake = $this->documentFragmentCreate($loop);
 740                  $markup = $this->documentFragmentToMarkup($fake);
 741              }
 742              if ($this->isXHTML) {
 743                  self::debug("Fixing XHTML");
 744                  $markup = self::markupFixXHTML($markup);
 745              }
 746              self::debug("Markup: ".substr($markup, 0, 250));
 747              return $markup;
 748          } else {
 749              if ($this->isDocumentFragment) {
 750                  // documentFragment, html only...
 751                  self::debug("Getting markup, DocumentFragment detected");
 752  //              return $this->markup(
 753  ////                    $this->document->getElementsByTagName('body')->item(0)
 754  //                  $this->document->root, true
 755  //              );
 756                  $markup = $this->documentFragmentToMarkup($this);
 757                  // no need for markupFixXHTML, as it's done thought markup($nodes) method
 758                  return $markup;
 759              } else {
 760                  self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'");
 761                  $markup = $this->isXML
 762                      ? $this->document->saveXML()
 763                      : $this->document->saveHTML();
 764                  if ($this->isXHTML) {
 765                      self::debug("Fixing XHTML");
 766                      $markup = self::markupFixXHTML($markup);
 767                  }
 768                  self::debug("Markup: ".substr($markup, 0, 250));
 769                  return $markup;
 770              }
 771          }
 772      }
 773      protected static function markupFixXHTML($markup) {
 774          $markup = self::expandEmptyTag('script', $markup);
 775          $markup = self::expandEmptyTag('select', $markup);
 776          $markup = self::expandEmptyTag('textarea', $markup);
 777          return $markup;
 778      }
 779      public static function debug($text) {
 780          phpQuery::debug($text);
 781      }
 782      /**
 783       * expandEmptyTag
 784       *
 785       * @param $tag
 786       * @param $xml
 787       * @return unknown_type
 788       * @author mjaque at ilkebenson dot com
 789       * @link http://php.net/manual/en/domdocument.savehtml.php#81256
 790       */
 791      public static function expandEmptyTag($tag, $xml){
 792          $indice = 0;
 793          while ($indice< strlen($xml)){
 794              $pos = strpos($xml, "<$tag ", $indice);
 795              if ($pos){
 796                  $posCierre = strpos($xml, ">", $pos);
 797                  if ($xml[$posCierre-1] == "/"){
 798                      $xml = substr_replace($xml, "></$tag>", $posCierre-1, 2);
 799                  }
 800                  $indice = $posCierre;
 801              }
 802              else break;
 803          }
 804          return $xml;
 805      }
 806  }
 807  
 808  /**
 809   * Event handling class.
 810   *
 811   * @author Tobiasz Cudnik
 812   * @package phpQuery
 813   * @static
 814   */
 815  abstract class phpQueryEvents {
 816      /**
 817       * Trigger a type of event on every matched element.
 818       *
 819       * @param DOMNode|phpQueryObject|string $document
 820       * @param unknown_type $type
 821       * @param unknown_type $data
 822       *
 823       * @TODO exclusive events (with !)
 824       * @TODO global events (test)
 825       * @TODO support more than event in $type (space-separated)
 826       */
 827      public static function trigger($document, $type, $data = array(), $node = null) {
 828          // trigger: function(type, data, elem, donative, extra) {
 829          $documentID = phpQuery::getDocumentID($document);
 830          $namespace = null;
 831          if (strpos($type, '.') !== false)
 832              list($name, $namespace) = explode('.', $type);
 833          else
 834              $name = $type;
 835          if (! $node) {
 836              if (self::issetGlobal($documentID, $type)) {
 837                  $pq = phpQuery::getDocument($documentID);
 838                  // TODO check add($pq->document)
 839                  $pq->find('*')->add($pq->document)
 840                      ->trigger($type, $data);
 841              }
 842          } else {
 843              if (isset($data[0]) && $data[0] instanceof DOMEvent) {
 844                  $event = $data[0];
 845                  $event->relatedTarget = $event->target;
 846                  $event->target = $node;
 847                  $data = array_slice($data, 1);
 848              } else {
 849                  $event = new DOMEvent(array(
 850                      'type' => $type,
 851                      'target' => $node,
 852                      'timeStamp' => time(),
 853                  ));
 854              }
 855              $i = 0;
 856              while($node) {
 857                  // TODO whois
 858                  phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on "
 859                      ."node \n");//.phpQueryObject::whois($node)."\n");
 860                  $event->currentTarget = $node;
 861                  $eventNode = self::getNode($documentID, $node);
 862                  if (isset($eventNode->eventHandlers)) {
 863                      foreach($eventNode->eventHandlers as $eventType => $handlers) {
 864                          $eventNamespace = null;
 865                          if (strpos($type, '.') !== false)
 866                              list($eventName, $eventNamespace) = explode('.', $eventType);
 867                          else
 868                              $eventName = $eventType;
 869                          if ($name != $eventName)
 870                              continue;
 871                          if ($namespace && $eventNamespace && $namespace != $eventNamespace)
 872                              continue;
 873                          foreach($handlers as $handler) {
 874                              phpQuery::debug("Calling event handler\n");
 875                              $event->data = $handler['data']
 876                                  ? $handler['data']
 877                                  : null;
 878                              $params = array_merge(array($event), $data);
 879                              $return = phpQuery::callbackRun($handler['callback'], $params);
 880                              if ($return === false) {
 881                                  $event->bubbles = false;
 882                              }
 883                          }
 884                      }
 885                  }
 886                  // to bubble or not to bubble...
 887                  if (! $event->bubbles)
 888                      break;
 889                  $node = $node->parentNode;
 890                  $i++;
 891              }
 892          }
 893      }
 894      /**
 895       * Binds a handler to one or more events (like click) for each matched element.
 896       * Can also bind custom events.
 897       *
 898       * @param DOMNode|phpQueryObject|string $document
 899       * @param unknown_type $type
 900       * @param unknown_type $data Optional
 901       * @param unknown_type $callback
 902       *
 903       * @TODO support '!' (exclusive) events
 904       * @TODO support more than event in $type (space-separated)
 905       * @TODO support binding to global events
 906       */
 907      public static function add($document, $node, $type, $data, $callback = null) {
 908          phpQuery::debug("Binding '$type' event");
 909          $documentID = phpQuery::getDocumentID($document);
 910  //      if (is_null($callback) && is_callable($data)) {
 911  //          $callback = $data;
 912  //          $data = null;
 913  //      }
 914          $eventNode = self::getNode($documentID, $node);
 915          if (! $eventNode)
 916              $eventNode = self::setNode($documentID, $node);
 917          if (!isset($eventNode->eventHandlers[$type]))
 918              $eventNode->eventHandlers[$type] = array();
 919          $eventNode->eventHandlers[$type][] = array(
 920              'callback' => $callback,
 921              'data' => $data,
 922          );
 923      }
 924      /**
 925       * Enter description here...
 926       *
 927       * @param DOMNode|phpQueryObject|string $document
 928       * @param unknown_type $type
 929       * @param unknown_type $callback
 930       *
 931       * @TODO namespace events
 932       * @TODO support more than event in $type (space-separated)
 933       */
 934      public static function remove($document, $node, $type = null, $callback = null) {
 935          $documentID = phpQuery::getDocumentID($document);
 936          $eventNode = self::getNode($documentID, $node);
 937          if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) {
 938              if ($callback) {
 939                  foreach($eventNode->eventHandlers[$type] as $k => $handler)
 940                      if ($handler['callback'] == $callback)
 941                          unset($eventNode->eventHandlers[$type][$k]);
 942              } else {
 943                  unset($eventNode->eventHandlers[$type]);
 944              }
 945          }
 946      }
 947      protected static function getNode($documentID, $node) {
 948          foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) {
 949              if ($node->isSameNode($eventNode))
 950                  return $eventNode;
 951          }
 952      }
 953      protected static function setNode($documentID, $node) {
 954          phpQuery::$documents[$documentID]->eventsNodes[] = $node;
 955          return phpQuery::$documents[$documentID]->eventsNodes[
 956              count(phpQuery::$documents[$documentID]->eventsNodes)-1
 957          ];
 958      }
 959      protected static function issetGlobal($documentID, $type) {
 960          return isset(phpQuery::$documents[$documentID])
 961              ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal)
 962              : false;
 963      }
 964  }
 965  
 966  
 967  interface ICallbackNamed {
 968      function hasName();
 969      function getName();
 970  }
 971  /**
 972   * Callback class introduces currying-like pattern.
 973   *
 974   * Example:
 975   * function foo($param1, $param2, $param3) {
 976   *   var_dump($param1, $param2, $param3);
 977   * }
 978   * $fooCurried = new Callback('foo',
 979   *   'param1 is now statically set',
 980   *   new CallbackParam, new CallbackParam
 981   * );
 982   * phpQuery::callbackRun($fooCurried,
 983   *  array('param2 value', 'param3 value'
 984   * );
 985   *
 986   * Callback class is supported in all phpQuery methods which accepts callbacks.
 987   *
 988   * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures
 989   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
 990   *
 991   * @TODO??? return fake forwarding function created via create_function
 992   * @TODO honor paramStructure
 993   */
 994  class Callback
 995      implements ICallbackNamed {
 996      public $callback = null;
 997      public $params = null;
 998      protected $name;
 999      public function __construct($callback, $param1 = null, $param2 = null,
1000              $param3 = null) {
1001          $params = func_get_args();
1002          $params = array_slice($params, 1);
1003          if ($callback instanceof Callback) {
1004              // TODO implement recurention
1005          } else {
1006              $this->callback = $callback;
1007              $this->params = $params;
1008          }
1009      }
1010      public function getName() {
1011          return 'Callback: '.$this->name;
1012      }
1013      public function hasName() {
1014          return isset($this->name) && $this->name;
1015      }
1016      public function setName($name) {
1017          $this->name = $name;
1018          return $this;
1019      }
1020      // TODO test me
1021  //  public function addParams() {
1022  //      $params = func_get_args();
1023  //      return new Callback($this->callback, $this->params+$params);
1024  //  }
1025  }
1026  
1027  /**
1028   * Callback type which on execution returns reference passed during creation.
1029   *
1030   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1031   */
1032  class CallbackReturnReference extends Callback
1033      implements ICallbackNamed {
1034      protected $reference;
1035      public function __construct(&$reference, $name = null){
1036          $this->reference =& $reference;
1037          $this->callback = array($this, 'callback');
1038      }
1039      public function callback() {
1040          return $this->reference;
1041      }
1042      public function getName() {
1043          return 'Callback: '.$this->name;
1044      }
1045      public function hasName() {
1046          return isset($this->name) && $this->name;
1047      }
1048  }
1049  /**
1050   * Callback type which on execution returns value passed during creation.
1051   *
1052   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1053   */
1054  class CallbackReturnValue extends Callback
1055      implements ICallbackNamed {
1056      protected $value;
1057      protected $name;
1058      public function __construct($value, $name = null){
1059          $this->value =& $value;
1060          $this->name = $name;
1061          $this->callback = array($this, 'callback');
1062      }
1063      public function callback() {
1064          return $this->value;
1065      }
1066      public function __toString() {
1067          return $this->getName();
1068      }
1069      public function getName() {
1070          return 'Callback: '.$this->name;
1071      }
1072      public function hasName() {
1073          return isset($this->name) && $this->name;
1074      }
1075  }
1076  /**
1077   * CallbackParameterToReference can be used when we don't really want a callback,
1078   * only parameter passed to it. CallbackParameterToReference takes first
1079   * parameter's value and passes it to reference.
1080   *
1081   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1082   */
1083  class CallbackParameterToReference extends Callback {
1084      /**
1085       * @param $reference
1086       * @TODO implement $paramIndex;
1087       * param index choose which callback param will be passed to reference
1088       */
1089      public function __construct(&$reference){
1090          $this->callback =& $reference;
1091      }
1092  }
1093  //class CallbackReference extends Callback {
1094  //  /**
1095  //   *
1096  //   * @param $reference
1097  //   * @param $paramIndex
1098  //   * @todo implement $paramIndex; param index choose which callback param will be passed to reference
1099  //   */
1100  //  public function __construct(&$reference, $name = null){
1101  //      $this->callback =& $reference;
1102  //  }
1103  //}
1104  class CallbackParam {}
1105  
1106  /**
1107   * Class representing phpQuery objects.
1108   *
1109   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
1110   * @package phpQuery
1111   * @method phpQueryObject clone() clone()
1112   * @method phpQueryObject empty() empty()
1113   * @method phpQueryObject next() next($selector = null)
1114   * @method phpQueryObject prev() prev($selector = null)
1115   * @property Int $length
1116   */
1117  class phpQueryObject
1118      implements Iterator, Countable, ArrayAccess {
1119      public $documentID = null;
1120      /**
1121       * DOMDocument class.
1122       *
1123       * @var DOMDocument
1124       */
1125      public $document = null;
1126      public $charset = null;
1127      /**
1128       *
1129       * @var DOMDocumentWrapper
1130       */
1131      public $documentWrapper = null;
1132      /**
1133       * XPath interface.
1134       *
1135       * @var DOMXPath
1136       */
1137      public $xpath = null;
1138      /**
1139       * Stack of selected elements.
1140       * @TODO refactor to ->nodes
1141       * @var array
1142       */
1143      public $elements = array();
1144      /**
1145       * @access private
1146       */
1147      protected $elementsBackup = array();
1148      /**
1149       * @access private
1150       */
1151      protected $previous = null;
1152      /**
1153       * @access private
1154       * @TODO deprecate
1155       */
1156      protected $root = array();
1157      /**
1158       * Indicated if doument is just a fragment (no <html> tag).
1159       *
1160       * Every document is realy a full document, so even documentFragments can
1161       * be queried against <html>, but getDocument(id)->htmlOuter() will return
1162       * only contents of <body>.
1163       *
1164       * @var bool
1165       */
1166      public $documentFragment = true;
1167      /**
1168       * Iterator interface helper
1169       * @access private
1170       */
1171      protected $elementsInterator = array();
1172      /**
1173       * Iterator interface helper
1174       * @access private
1175       */
1176      protected $valid = false;
1177      /**
1178       * Iterator interface helper
1179       * @access private
1180       */
1181      protected $current = null;
1182      /**
1183       * Enter description here...
1184       *
1185       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1186       */
1187      public function __construct($documentID) {
1188          dbg_deprecated(\DOMWrap\Document::class);
1189  
1190  //      if ($documentID instanceof self)
1191  //          var_dump($documentID->getDocumentID());
1192          $id = $documentID instanceof self
1193              ? $documentID->getDocumentID()
1194              : $documentID;
1195  //      var_dump($id);
1196          if (! isset(phpQuery::$documents[$id] )) {
1197  //          var_dump(phpQuery::$documents);
1198              throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first.");
1199          }
1200          $this->documentID = $id;
1201          $this->documentWrapper =& phpQuery::$documents[$id];
1202          $this->document =& $this->documentWrapper->document;
1203          $this->xpath =& $this->documentWrapper->xpath;
1204          $this->charset =& $this->documentWrapper->charset;
1205          $this->documentFragment =& $this->documentWrapper->isDocumentFragment;
1206          // TODO check $this->DOM->documentElement;
1207  //      $this->root = $this->document->documentElement;
1208          $this->root =& $this->documentWrapper->root;
1209  //      $this->toRoot();
1210          $this->elements = array($this->root);
1211      }
1212      /**
1213       *
1214       * @access private
1215       * @param $attr
1216       * @return unknown_type
1217       */
1218      public function __get($attr) {
1219          switch($attr) {
1220              // FIXME doesnt work at all ?
1221              case 'length':
1222                  return $this->size();
1223              break;
1224              default:
1225                  return $this->$attr;
1226          }
1227      }
1228      /**
1229       * Saves actual object to $var by reference.
1230       * Useful when need to break chain.
1231       * @param phpQueryObject $var
1232       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1233       */
1234      public function toReference(&$var) {
1235          return $var = $this;
1236      }
1237      public function documentFragment($state = null) {
1238          if ($state) {
1239              phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state;
1240              return $this;
1241          }
1242          return $this->documentFragment;
1243      }
1244      /**
1245     * @access private
1246     * @TODO documentWrapper
1247       */
1248      protected function isRoot( $node) {
1249  //      return $node instanceof DOMDOCUMENT || $node->tagName == 'html';
1250          return $node instanceof DOMDOCUMENT
1251              || ($node instanceof DOMELEMENT && $node->tagName == 'html')
1252              || $this->root->isSameNode($node);
1253      }
1254      /**
1255     * @access private
1256       */
1257      protected function stackIsRoot() {
1258          return $this->size() == 1 && $this->isRoot($this->elements[0]);
1259      }
1260      /**
1261       * Enter description here...
1262       * NON JQUERY METHOD
1263       *
1264       * Watch out, it doesn't creates new instance, can be reverted with end().
1265       *
1266       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1267       */
1268      public function toRoot() {
1269          $this->elements = array($this->root);
1270          return $this;
1271  //      return $this->newInstance(array($this->root));
1272      }
1273      /**
1274       * Saves object's DocumentID to $var by reference.
1275       * <code>
1276       * $myDocumentId;
1277       * phpQuery::newDocument('<div/>')
1278       *     ->getDocumentIDRef($myDocumentId)
1279       *     ->find('div')->...
1280       * </code>
1281       *
1282       * @param unknown_type $domId
1283       * @see phpQuery::newDocument
1284       * @see phpQuery::newDocumentFile
1285       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1286       */
1287      public function getDocumentIDRef(&$documentID) {
1288          $documentID = $this->getDocumentID();
1289          return $this;
1290      }
1291      /**
1292       * Returns object with stack set to document root.
1293       *
1294       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1295       */
1296      public function getDocument() {
1297          return phpQuery::getDocument($this->getDocumentID());
1298      }
1299      /**
1300       *
1301       * @return DOMDocument
1302       */
1303      public function getDOMDocument() {
1304          return $this->document;
1305      }
1306      /**
1307       * Get object's Document ID.
1308       *
1309       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1310       */
1311      public function getDocumentID() {
1312          return $this->documentID;
1313      }
1314      /**
1315       * Unloads whole document from memory.
1316       * CAUTION! None further operations will be possible on this document.
1317       * All objects refering to it will be useless.
1318       *
1319       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1320       */
1321      public function unloadDocument() {
1322          phpQuery::unloadDocuments($this->getDocumentID());
1323      }
1324      public function isHTML() {
1325          return $this->documentWrapper->isHTML;
1326      }
1327      public function isXHTML() {
1328          return $this->documentWrapper->isXHTML;
1329      }
1330      public function isXML() {
1331          return $this->documentWrapper->isXML;
1332      }
1333      /**
1334       * Enter description here...
1335       *
1336       * @link http://docs.jquery.com/Ajax/serialize
1337       * @return string
1338       */
1339      public function serialize() {
1340          return phpQuery::param($this->serializeArray());
1341      }
1342      /**
1343       * Enter description here...
1344       *
1345       * @link http://docs.jquery.com/Ajax/serializeArray
1346       * @return array
1347       */
1348      public function serializeArray($submit = null) {
1349          $source = $this->filter('form, input, select, textarea')
1350              ->find('input, select, textarea')
1351              ->andSelf()
1352              ->not('form');
1353          $return = array();
1354  //      $source->dumpDie();
1355          foreach($source as $input) {
1356              $input = phpQuery::pq($input);
1357              if ($input->is('[disabled]'))
1358                  continue;
1359              if (!$input->is('[name]'))
1360                  continue;
1361              if ($input->is('[type=checkbox]') && !$input->is('[checked]'))
1362                  continue;
1363              // jquery diff
1364              if ($submit && $input->is('[type=submit]')) {
1365                  if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit))
1366                      continue;
1367                  else if (is_string($submit) && $input->attr('name') != $submit)
1368                      continue;
1369              }
1370              $return[] = array(
1371                  'name' => $input->attr('name'),
1372                  'value' => $input->val(),
1373              );
1374          }
1375          return $return;
1376      }
1377      /**
1378       * @access private
1379       */
1380      protected function debug($in) {
1381          if (! phpQuery::$debug )
1382              return;
1383          print('<pre>');
1384          print_r($in);
1385          // file debug
1386  //      file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
1387          // quite handy debug trace
1388  //      if ( is_array($in))
1389  //          print_r(array_slice(debug_backtrace(), 3));
1390          print("</pre>\n");
1391      }
1392      /**
1393       * @access private
1394       */
1395      protected function isRegexp($pattern) {
1396          return in_array(
1397              $pattern[ mb_strlen($pattern)-1 ],
1398              array('^','*','$')
1399          );
1400      }
1401      /**
1402       * Determines if $char is really a char.
1403       *
1404       * @param string $char
1405       * @return bool
1406       * @todo rewrite me to charcode range ! ;)
1407       * @access private
1408       */
1409      protected function isChar($char) {
1410          return extension_loaded('mbstring') && phpQuery::$mbstringSupport
1411              ? mb_eregi('\w', $char)
1412              : preg_match('@\w@', $char);
1413      }
1414      /**
1415       * @access private
1416       */
1417      protected function parseSelector($query) {
1418          // clean spaces
1419          // TODO include this inside parsing ?
1420          $query = trim(
1421              preg_replace('@\s+@', ' ',
1422                  preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
1423              )
1424          );
1425          $queries = array(array());
1426          if (! $query)
1427              return $queries;
1428          $return =& $queries[0];
1429          $specialChars = array('>',' ');
1430  //      $specialCharsMapping = array('/' => '>');
1431          $specialCharsMapping = array();
1432          $strlen = mb_strlen($query);
1433          $classChars = array('.', '-');
1434          $pseudoChars = array('-');
1435          $tagChars = array('*', '|', '-');
1436          // split multibyte string
1437          // http://code.google.com/p/phpquery/issues/detail?id=76
1438          $_query = array();
1439          for ($i=0; $i<$strlen; $i++)
1440              $_query[] = mb_substr($query, $i, 1);
1441          $query = $_query;
1442          // it works, but i dont like it...
1443          $i = 0;
1444          while( $i < $strlen) {
1445              $c = $query[$i];
1446              $tmp = '';
1447              // TAG
1448              if ($this->isChar($c) || in_array($c, $tagChars)) {
1449                  while(isset($query[$i])
1450                      && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) {
1451                      $tmp .= $query[$i];
1452                      $i++;
1453                  }
1454                  $return[] = $tmp;
1455              // IDs
1456              } else if ( $c == '#') {
1457                  $i++;
1458                  while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) {
1459                      $tmp .= $query[$i];
1460                      $i++;
1461                  }
1462                  $return[] = '#'.$tmp;
1463              // SPECIAL CHARS
1464              } else if (in_array($c, $specialChars)) {
1465                  $return[] = $c;
1466                  $i++;
1467              // MAPPED SPECIAL MULTICHARS
1468  //          } else if ( $c.$query[$i+1] == '//') {
1469  //              $return[] = ' ';
1470  //              $i = $i+2;
1471              // MAPPED SPECIAL CHARS
1472              } else if ( isset($specialCharsMapping[$c])) {
1473                  $return[] = $specialCharsMapping[$c];
1474                  $i++;
1475              // COMMA
1476              } else if ( $c == ',') {
1477                  $queries[] = array();
1478                  $return =& $queries[ count($queries)-1 ];
1479                  $i++;
1480                  while( isset($query[$i]) && $query[$i] == ' ')
1481                      $i++;
1482              // CLASSES
1483              } else if ($c == '.') {
1484                  while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) {
1485                      $tmp .= $query[$i];
1486                      $i++;
1487                  }
1488                  $return[] = $tmp;
1489              // ~ General Sibling Selector
1490              } else if ($c == '~') {
1491                  $spaceAllowed = true;
1492                  $tmp .= $query[$i++];
1493                  while( isset($query[$i])
1494                      && ($this->isChar($query[$i])
1495                          || in_array($query[$i], $classChars)
1496                          || $query[$i] == '*'
1497                          || ($query[$i] == ' ' && $spaceAllowed)
1498                      )) {
1499                      if ($query[$i] != ' ')
1500                          $spaceAllowed = false;
1501                      $tmp .= $query[$i];
1502                      $i++;
1503                  }
1504                  $return[] = $tmp;
1505              // + Adjacent sibling selectors
1506              } else if ($c == '+') {
1507                  $spaceAllowed = true;
1508                  $tmp .= $query[$i++];
1509                  while( isset($query[$i])
1510                      && ($this->isChar($query[$i])
1511                          || in_array($query[$i], $classChars)
1512                          || $query[$i] == '*'
1513                          || ($spaceAllowed && $query[$i] == ' ')
1514                      )) {
1515                      if ($query[$i] != ' ')
1516                          $spaceAllowed = false;
1517                      $tmp .= $query[$i];
1518                      $i++;
1519                  }
1520                  $return[] = $tmp;
1521              // ATTRS
1522              } else if ($c == '[') {
1523                  $stack = 1;
1524                  $tmp .= $c;
1525                  while( isset($query[++$i])) {
1526                      $tmp .= $query[$i];
1527                      if ( $query[$i] == '[') {
1528                          $stack++;
1529                      } else if ( $query[$i] == ']') {
1530                          $stack--;
1531                          if (! $stack )
1532                              break;
1533                      }
1534                  }
1535                  $return[] = $tmp;
1536                  $i++;
1537              // PSEUDO CLASSES
1538              } else if ($c == ':') {
1539                  $stack = 1;
1540                  $tmp .= $query[$i++];
1541                  while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) {
1542                      $tmp .= $query[$i];
1543                      $i++;
1544                  }
1545                  // with arguments ?
1546                  if ( isset($query[$i]) && $query[$i] == '(') {
1547                      $tmp .= $query[$i];
1548                      $stack = 1;
1549                      while( isset($query[++$i])) {
1550                          $tmp .= $query[$i];
1551                          if ( $query[$i] == '(') {
1552                              $stack++;
1553                          } else if ( $query[$i] == ')') {
1554                              $stack--;
1555                              if (! $stack )
1556                                  break;
1557                          }
1558                      }
1559                      $return[] = $tmp;
1560                      $i++;
1561                  } else {
1562                      $return[] = $tmp;
1563                  }
1564              } else {
1565                  $i++;
1566              }
1567          }
1568          foreach($queries as $k => $q) {
1569              if (isset($q[0])) {
1570                  if (isset($q[0][0]) && $q[0][0] == ':')
1571                      array_unshift($queries[$k], '*');
1572                  if ($q[0] != '>')
1573                      array_unshift($queries[$k], ' ');
1574              }
1575          }
1576          return $queries;
1577      }
1578      /**
1579       * Return matched DOM nodes.
1580       *
1581       * @param int $index
1582       * @return array|DOMElement Single DOMElement or array of DOMElement.
1583       */
1584      public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1585          $return = isset($index)
1586              ? (isset($this->elements[$index]) ? $this->elements[$index] : null)
1587              : $this->elements;
1588          // pass thou callbacks
1589          $args = func_get_args();
1590          $args = array_slice($args, 1);
1591          foreach($args as $callback) {
1592              if (is_array($return))
1593                  foreach($return as $k => $v)
1594                      $return[$k] = phpQuery::callbackRun($callback, array($v));
1595              else
1596                  $return = phpQuery::callbackRun($callback, array($return));
1597          }
1598          return $return;
1599      }
1600      /**
1601       * Return matched DOM nodes.
1602       * jQuery difference.
1603       *
1604       * @param int $index
1605       * @return array|string Returns string if $index != null
1606       * @todo implement callbacks
1607       * @todo return only arrays ?
1608       * @todo maybe other name...
1609       */
1610      public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1611          if ($index)
1612              $return = $this->eq($index)->text();
1613          else {
1614              $return = array();
1615              for($i = 0; $i < $this->size(); $i++) {
1616                  $return[] = $this->eq($i)->text();
1617              }
1618          }
1619          // pass thou callbacks
1620          $args = func_get_args();
1621          $args = array_slice($args, 1);
1622          foreach($args as $callback) {
1623              $return = phpQuery::callbackRun($callback, array($return));
1624          }
1625          return $return;
1626      }
1627      /**
1628       * Return matched DOM nodes.
1629       * jQuery difference.
1630       *
1631       * @param int $index
1632       * @return array|string Returns string if $index != null
1633       * @todo implement callbacks
1634       * @todo return only arrays ?
1635       * @todo maybe other name...
1636       */
1637      public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
1638          if ($index)
1639              $return = $this->eq($index)->text();
1640          else {
1641              $return = array();
1642              for($i = 0; $i < $this->size(); $i++) {
1643                  $return[] = $this->eq($i)->text();
1644              }
1645              // pass thou callbacks
1646              $args = func_get_args();
1647              $args = array_slice($args, 1);
1648          }
1649          foreach($args as $callback) {
1650              if (is_array($return))
1651                  foreach($return as $k => $v)
1652                      $return[$k] = phpQuery::callbackRun($callback, array($v));
1653              else
1654                  $return = phpQuery::callbackRun($callback, array($return));
1655          }
1656          return $return;
1657      }
1658      /**
1659       * Returns new instance of actual class.
1660       *
1661       * @param array $newStack Optional. Will replace old stack with new and move old one to history.c
1662       */
1663      public function newInstance($newStack = null) {
1664          $class = get_class($this);
1665          // support inheritance by passing old object to overloaded constructor
1666          $new = $class != 'phpQuery'
1667              ? new $class($this, $this->getDocumentID())
1668              : new phpQueryObject($this->getDocumentID());
1669          $new->previous = $this;
1670          if (is_null($newStack)) {
1671              $new->elements = $this->elements;
1672              if ($this->elementsBackup)
1673                  $this->elements = $this->elementsBackup;
1674          } else if (is_string($newStack)) {
1675              $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack();
1676          } else {
1677              $new->elements = $newStack;
1678          }
1679          return $new;
1680      }
1681      /**
1682       * Enter description here...
1683       *
1684       * In the future, when PHP will support XLS 2.0, then we would do that this way:
1685       * contains(tokenize(@class, '\s'), "something")
1686       * @param unknown_type $class
1687       * @param unknown_type $node
1688       * @return boolean
1689       * @access private
1690       */
1691      protected function matchClasses($class, $node) {
1692          // multi-class
1693          if ( mb_strpos($class, '.', 1)) {
1694              $classes = explode('.', substr($class, 1));
1695              $classesCount = count( $classes );
1696              $nodeClasses = explode(' ', $node->getAttribute('class') );
1697              $nodeClassesCount = count( $nodeClasses );
1698              if ( $classesCount > $nodeClassesCount )
1699                  return false;
1700              $diff = count(
1701                  array_diff(
1702                      $classes,
1703                      $nodeClasses
1704                  )
1705              );
1706              if (! $diff )
1707                  return true;
1708          // single-class
1709          } else {
1710              return in_array(
1711                  // strip leading dot from class name
1712                  substr($class, 1),
1713                  // get classes for element as array
1714                  explode(' ', $node->getAttribute('class') )
1715              );
1716          }
1717      }
1718      /**
1719       * @access private
1720       */
1721      protected function runQuery($XQuery, $selector = null, $compare = null) {
1722          if ($compare && ! method_exists($this, $compare))
1723              return false;
1724          $stack = array();
1725          if (! $this->elements)
1726              $this->debug('Stack empty, skipping...');
1727  //      var_dump($this->elements[0]->nodeType);
1728          // element, document
1729          foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) {
1730              $detachAfter = false;
1731              // to work on detached nodes we need temporary place them somewhere
1732              // thats because context xpath queries sucks ;]
1733              $testNode = $stackNode;
1734              while ($testNode) {
1735                  if (! $testNode->parentNode && ! $this->isRoot($testNode)) {
1736                      $this->root->appendChild($testNode);
1737                      $detachAfter = $testNode;
1738                      break;
1739                  }
1740                  $testNode = isset($testNode->parentNode)
1741                      ? $testNode->parentNode
1742                      : null;
1743              }
1744              // XXX tmp ?
1745              $xpath = $this->documentWrapper->isXHTML
1746                  ? $this->getNodeXpath($stackNode, 'html')
1747                  : $this->getNodeXpath($stackNode);
1748              // FIXME pseudoclasses-only query, support XML
1749              $query = $XQuery == '//' && $xpath == '/html[1]'
1750                  ? '//*'
1751                  : $xpath.$XQuery;
1752              $this->debug("XPATH: {$query}");
1753              // run query, get elements
1754              $nodes = $this->xpath->query($query);
1755              $this->debug("QUERY FETCHED");
1756              if (! $nodes->length )
1757                  $this->debug('Nothing found');
1758              $debug = array();
1759              foreach($nodes as $node) {
1760                  $matched = false;
1761                  if ( $compare) {
1762                      phpQuery::$debug ?
1763                          $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()")
1764                          : null;
1765                      $phpQueryDebug = phpQuery::$debug;
1766                      phpQuery::$debug = false;
1767                      // TODO ??? use phpQuery::callbackRun()
1768                      if (call_user_func_array(array($this, $compare), array($selector, $node)))
1769                          $matched = true;
1770                      phpQuery::$debug = $phpQueryDebug;
1771                  } else {
1772                      $matched = true;
1773                  }
1774                  if ( $matched) {
1775                      if (phpQuery::$debug)
1776                          $debug[] = $this->whois( $node );
1777                      $stack[] = $node;
1778                  }
1779              }
1780              if (phpQuery::$debug) {
1781                  $this->debug("Matched ".count($debug).": ".implode(', ', $debug));
1782              }
1783              if ($detachAfter)
1784                  $this->root->removeChild($detachAfter);
1785          }
1786          $this->elements = $stack;
1787      }
1788      /**
1789       * Enter description here...
1790       *
1791       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
1792       */
1793      public function find($selectors, $context = null, $noHistory = false) {
1794          if (!$noHistory)
1795              // backup last stack /for end()/
1796              $this->elementsBackup = $this->elements;
1797          // allow to define context
1798          // TODO combine code below with phpQuery::pq() context guessing code
1799          //   as generic function
1800          if ($context) {
1801              if (! is_array($context) && $context instanceof DOMELEMENT)
1802                  $this->elements = array($context);
1803              else if (is_array($context)) {
1804                  $this->elements = array();
1805                  foreach ($context as $c)
1806                      if ($c instanceof DOMELEMENT)
1807                          $this->elements[] = $c;
1808              } else if ( $context instanceof self )
1809                  $this->elements = $context->elements;
1810          }
1811          $queries = $this->parseSelector($selectors);
1812          $this->debug(array('FIND', $selectors, $queries));
1813          $XQuery = '';
1814          // remember stack state because of multi-queries
1815          $oldStack = $this->elements;
1816          // here we will be keeping found elements
1817          $stack = array();
1818          foreach($queries as $selector) {
1819              $this->elements = $oldStack;
1820              $delimiterBefore = false;
1821              foreach($selector as $s) {
1822                  // TAG
1823                  $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport
1824                      ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*'
1825                      : preg_match('@^[\w|\||-]+$@', $s) || $s == '*';
1826                  if ($isTag) {
1827                      if ($this->isXML()) {
1828                          // namespace support
1829                          if (mb_strpos($s, '|') !== false) {
1830                              $ns = $tag = null;
1831                              list($ns, $tag) = explode('|', $s);
1832                              $XQuery .= "$ns:$tag";
1833                          } else if ($s == '*') {
1834                              $XQuery .= "*";
1835                          } else {
1836                              $XQuery .= "*[local-name()='$s']";
1837                          }
1838                      } else {
1839                          $XQuery .= $s;
1840                      }
1841                  // ID
1842                  } else if ($s[0] == '#') {
1843                      if ($delimiterBefore)
1844                          $XQuery .= '*';
1845                      $XQuery .= "[@id='".substr($s, 1)."']";
1846                  // ATTRIBUTES
1847                  } else if ($s[0] == '[') {
1848                      if ($delimiterBefore)
1849                          $XQuery .= '*';
1850                      // strip side brackets
1851                      $attr = trim($s, '][');
1852                      $execute = false;
1853                      // attr with specifed value
1854                      if (mb_strpos($s, '=')) {
1855                          $value = null;
1856                          list($attr, $value) = explode('=', $attr);
1857                          $value = trim($value, "'\"");
1858                          if ($this->isRegexp($attr)) {
1859                              // cut regexp character
1860                              $attr = substr($attr, 0, -1);
1861                              $execute = true;
1862                              $XQuery .= "[@{$attr}]";
1863                          } else {
1864                              $XQuery .= "[@{$attr}='{$value}']";
1865                          }
1866                      // attr without specified value
1867                      } else {
1868                          $XQuery .= "[@{$attr}]";
1869                      }
1870                      if ($execute) {
1871                          $this->runQuery($XQuery, $s, 'is');
1872                          $XQuery = '';
1873                          if (! $this->length())
1874                              break;
1875                      }
1876                  // CLASSES
1877                  } else if ($s[0] == '.') {
1878                      // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
1879                      // thx wizDom ;)
1880                      if ($delimiterBefore)
1881                          $XQuery .= '*';
1882                      $XQuery .= '[@class]';
1883                      $this->runQuery($XQuery, $s, 'matchClasses');
1884                      $XQuery = '';
1885                      if (! $this->length() )
1886                          break;
1887                  // ~ General Sibling Selector
1888                  } else if ($s[0] == '~') {
1889                      $this->runQuery($XQuery);
1890                      $XQuery = '';
1891                      $this->elements = $this
1892                          ->siblings(
1893                              substr($s, 1)
1894                          )->elements;
1895                      if (! $this->length() )
1896                          break;
1897                  // + Adjacent sibling selectors
1898                  } else if ($s[0] == '+') {
1899                      // TODO /following-sibling::
1900                      $this->runQuery($XQuery);
1901                      $XQuery = '';
1902                      $subSelector = substr($s, 1);
1903                      $subElements = $this->elements;
1904                      $this->elements = array();
1905                      foreach($subElements as $node) {
1906                          // search first DOMElement sibling
1907                          $test = $node->nextSibling;
1908                          while($test && ! ($test instanceof DOMELEMENT))
1909                              $test = $test->nextSibling;
1910                          if ($test && $this->is($subSelector, $test))
1911                              $this->elements[] = $test;
1912                      }
1913                      if (! $this->length() )
1914                          break;
1915                  // PSEUDO CLASSES
1916                  } else if ($s[0] == ':') {
1917                      // TODO optimization for :first :last
1918                      if ($XQuery) {
1919                          $this->runQuery($XQuery);
1920                          $XQuery = '';
1921                      }
1922                      if (! $this->length())
1923                          break;
1924                      $this->pseudoClasses($s);
1925                      if (! $this->length())
1926                          break;
1927                  // DIRECT DESCENDANDS
1928                  } else if ($s == '>') {
1929                      $XQuery .= '/';
1930                      $delimiterBefore = 2;
1931                  // ALL DESCENDANDS
1932                  } else if ($s == ' ') {
1933                      $XQuery .= '//';
1934                      $delimiterBefore = 2;
1935                  // ERRORS
1936                  } else {
1937                      phpQuery::debug("Unrecognized token '$s'");
1938                  }
1939                  $delimiterBefore = $delimiterBefore === 2;
1940              }
1941              // run query if any
1942              if ($XQuery && $XQuery != '//') {
1943                  $this->runQuery($XQuery);
1944                  $XQuery = '';
1945              }
1946              foreach($this->elements as $node)
1947                  if (! $this->elementsContainsNode($node, $stack))
1948                      $stack[] = $node;
1949          }
1950          $this->elements = $stack;
1951          return $this->newInstance();
1952      }
1953      /**
1954       * @todo create API for classes with pseudoselectors
1955       * @access private
1956       */
1957      protected function pseudoClasses($class) {
1958          // TODO clean args parsing ?
1959          $class = ltrim($class, ':');
1960          $haveArgs = mb_strpos($class, '(');
1961          if ($haveArgs !== false) {
1962              $args = substr($class, $haveArgs+1, -1);
1963              $class = substr($class, 0, $haveArgs);
1964          }
1965          switch($class) {
1966              case 'even':
1967              case 'odd':
1968                  $stack = array();
1969                  foreach($this->elements as $i => $node) {
1970                      if ($class == 'even' && ($i%2) == 0)
1971                          $stack[] = $node;
1972                      else if ( $class == 'odd' && $i % 2 )
1973                          $stack[] = $node;
1974                  }
1975                  $this->elements = $stack;
1976                  break;
1977              case 'eq':
1978                  $k = intval($args);
1979                  $this->elements = isset( $this->elements[$k] )
1980                      ? array( $this->elements[$k] )
1981                      : array();
1982                  break;
1983              case 'gt':
1984                  $this->elements = array_slice($this->elements, $args+1);
1985                  break;
1986              case 'lt':
1987                  $this->elements = array_slice($this->elements, 0, $args+1);
1988                  break;
1989              case 'first':
1990                  if (isset($this->elements[0]))
1991                      $this->elements = array($this->elements[0]);
1992                  break;
1993              case 'last':
1994                  if ($this->elements)
1995                      $this->elements = array($this->elements[count($this->elements)-1]);
1996                  break;
1997              /*case 'parent':
1998                  $stack = array();
1999                  foreach($this->elements as $node) {
2000                      if ( $node->childNodes->length )
2001                          $stack[] = $node;
2002                  }
2003                  $this->elements = $stack;
2004                  break;*/
2005              case 'contains':
2006                  $text = trim($args, "\"'");
2007                  $stack = array();
2008                  foreach($this->elements as $node) {
2009                      if (mb_stripos($node->textContent, $text) === false)
2010                          continue;
2011                      $stack[] = $node;
2012                  }
2013                  $this->elements = $stack;
2014                  break;
2015              case 'not':
2016                  $selector = self::unQuote($args);
2017                  $this->elements = $this->not($selector)->stack();
2018                  break;
2019              case 'slice':
2020                  // TODO jQuery difference ?
2021                  $args = explode(',',
2022                      str_replace(', ', ',', trim($args, "\"'"))
2023                  );
2024                  $start = $args[0];
2025                  $end = isset($args[1])
2026                      ? $args[1]
2027                      : null;
2028                  if ($end > 0)
2029                      $end = $end-$start;
2030                  $this->elements = array_slice($this->elements, $start, $end);
2031                  break;
2032              case 'has':
2033                  $selector = trim($args, "\"'");
2034                  $stack = array();
2035                  foreach($this->stack(1) as $el) {
2036                      if ($this->find($selector, $el, true)->length)
2037                          $stack[] = $el;
2038                  }
2039                  $this->elements = $stack;
2040                  break;
2041              case 'submit':
2042              case 'reset':
2043                  $this->elements = phpQuery::merge(
2044                      $this->map(array($this, 'is'),
2045                          "input[type=$class]", new CallbackParam()
2046                      ),
2047                      $this->map(array($this, 'is'),
2048                          "button[type=$class]", new CallbackParam()
2049                      )
2050                  );
2051              break;
2052  //              $stack = array();
2053  //              foreach($this->elements as $node)
2054  //                  if ($node->is('input[type=submit]') || $node->is('button[type=submit]'))
2055  //                      $stack[] = $el;
2056  //              $this->elements = $stack;
2057              case 'input':
2058                  $this->elements = $this->map(
2059                      array($this, 'is'),
2060                      'input', new CallbackParam()
2061                  )->elements;
2062              break;
2063              case 'password':
2064              case 'checkbox':
2065              case 'radio':
2066              case 'hidden':
2067              case 'image':
2068              case 'file':
2069                  $this->elements = $this->map(
2070                      array($this, 'is'),
2071                      "input[type=$class]", new CallbackParam()
2072                  )->elements;
2073              break;
2074              case 'parent':
2075                  $this->elements = $this->map(
2076                      function ($node) {
2077                          return $node instanceof DOMELEMENT && $node->childNodes->length
2078                              ? $node : null;
2079                      }
2080                  )->elements;
2081              break;
2082              case 'empty':
2083                  $this->elements = $this->map(
2084                      function ($node) {
2085                          return $node instanceof DOMELEMENT && $node->childNodes->length
2086                              ? null : $node;
2087                      }
2088                  )->elements;
2089              break;
2090              case 'disabled':
2091              case 'selected':
2092              case 'checked':
2093                  $this->elements = $this->map(
2094                      array($this, 'is'),
2095                      "[$class]", new CallbackParam()
2096                  )->elements;
2097              break;
2098              case 'enabled':
2099                  $this->elements = $this->map(
2100                      function ($node) {
2101                          return pq($node)->not(":disabled") ? $node : null;
2102                      }
2103                  )->elements;
2104              break;
2105              case 'header':
2106                  $this->elements = $this->map(
2107                      function ($node) {
2108                          $isHeader = isset($node->tagName) && in_array($node->tagName, array(
2109                                  "h1", "h2", "h3", "h4", "h5", "h6", "h7"
2110                              ));
2111                          return $isHeader
2112                              ? $node
2113                              : null;
2114                      }
2115                  )->elements;
2116  //              $this->elements = $this->map(
2117  //                  create_function('$node', '$node = pq($node);
2118  //                      return $node->is("h1")
2119  //                          || $node->is("h2")
2120  //                          || $node->is("h3")
2121  //                          || $node->is("h4")
2122  //                          || $node->is("h5")
2123  //                          || $node->is("h6")
2124  //                          || $node->is("h7")
2125  //                          ? $node
2126  //                          : null;')
2127  //              )->elements;
2128              break;
2129              case 'only-child':
2130                  $this->elements = $this->map(
2131                      function ($node) {
2132                          return pq($node)->siblings()->size() == 0 ? $node : null;
2133                      }
2134                  )->elements;
2135              break;
2136              case 'first-child':
2137                  $this->elements = $this->map(
2138                      function ($node) {
2139                          return pq($node)->prevAll()->size() == 0 ? $node : null;
2140                      }
2141                  )->elements;
2142              break;
2143              case 'last-child':
2144                  $this->elements = $this->map(
2145                      function ($node) {
2146                          return pq($node)->nextAll()->size() == 0 ? $node : null;
2147                      }
2148                  )->elements;
2149              break;
2150              case 'nth-child':
2151                  $param = trim($args, "\"'");
2152                  if (! $param)
2153                      break;
2154                      // nth-child(n+b) to nth-child(1n+b)
2155                  if ($param[0] == 'n')
2156                      $param = '1'.$param;
2157                  // :nth-child(index/even/odd/equation)
2158                  if ($param == 'even' || $param == 'odd')
2159                      $mapped = $this->map(
2160                          function ($node, $param) {
2161                              $index = pq($node)->prevAll()->size() + 1;
2162                              if ($param == "even" && ($index % 2) == 0)
2163                                  return $node;
2164                              else if ($param == "odd" && $index % 2 == 1)
2165                                  return $node;
2166                              else
2167                                  return null;
2168                          }, new CallbackParam(), $param
2169                      );
2170                  else if (mb_strlen($param) > 1 && $param[1] == 'n')
2171                      // an+b
2172                      $mapped = $this->map(
2173                          function ($node, $param) {
2174                              $prevs = pq($node)->prevAll()->size();
2175                              $index = 1 + $prevs;
2176                              $b = mb_strlen($param) > 3
2177                                  ? $param[3]
2178                                  : 0;
2179                              $a = $param[0];
2180                              if ($b && $param[2] == "-")
2181                                  $b = -$b;
2182                              if ($a > 0) {
2183                                  return ($index - $b) % $a == 0
2184                                      ? $node
2185                                      : null;
2186                                  phpQuery::debug($a . "*" . floor($index / $a) . "+$b-1 == " . ($a * floor($index / $a) + $b - 1) . " ?= $prevs");
2187                                  return $a * floor($index / $a) + $b - 1 == $prevs
2188                                      ? $node
2189                                      : null;
2190                              } else if ($a == 0) {
2191                                  return $index == $b
2192                                      ? $node
2193                                      : null;
2194                              } else {
2195                                  // negative value
2196                                  return $index <= $b
2197                                      ? $node
2198                                      : null;
2199                              }
2200  //                          if (! $b)
2201  //                              return $index%$a == 0
2202  //                                  ? $node
2203  //                                  : null;
2204  //                          else
2205  //                              return ($index-$b)%$a == 0
2206  //                                  ? $node
2207  //                                  : null;
2208                          },
2209                          new CallbackParam(), $param
2210                      );
2211                  else
2212                      // index
2213                      $mapped = $this->map(
2214                          function ($node, $index) {
2215                              $prevs = pq($node)->prevAll()->size();
2216                              if ($prevs && $prevs == $index - 1)
2217                                  return $node;
2218                              else if (!$prevs && $index == 1)
2219                                  return $node;
2220                              else
2221                                  return null;
2222                          },
2223                          new CallbackParam(), $param
2224                      );
2225                  $this->elements = $mapped->elements;
2226              break;
2227              default:
2228                  $this->debug("Unknown pseudoclass '{$class}', skipping...");
2229          }
2230      }
2231      /**
2232       * @access private
2233       */
2234      protected function pseudoClassParam($paramsString) {
2235          // TODO;
2236      }
2237      /**
2238       * Enter description here...
2239       *
2240       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2241       */
2242      public function is($selector, $nodes = null) {
2243          phpQuery::debug(array("Is:", $selector));
2244          if (! $selector)
2245              return false;
2246          $oldStack = $this->elements;
2247          $returnArray = false;
2248          if ($nodes && is_array($nodes)) {
2249              $this->elements = $nodes;
2250          } else if ($nodes)
2251              $this->elements = array($nodes);
2252          $this->filter($selector, true);
2253          $stack = $this->elements;
2254          $this->elements = $oldStack;
2255          if ($nodes)
2256              return $stack ? $stack : null;
2257          return (bool)count($stack);
2258      }
2259      /**
2260       * Enter description here...
2261       * jQuery difference.
2262       *
2263       * Callback:
2264       * - $index int
2265       * - $node DOMNode
2266       *
2267       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2268       * @link http://docs.jquery.com/Traversing/filter
2269       */
2270      public function filterCallback($callback, $_skipHistory = false) {
2271          if (! $_skipHistory) {
2272              $this->elementsBackup = $this->elements;
2273              $this->debug("Filtering by callback");
2274          }
2275          $newStack = array();
2276          foreach($this->elements as $index => $node) {
2277              $result = phpQuery::callbackRun($callback, array($index, $node));
2278              if (is_null($result) || (! is_null($result) && $result))
2279                  $newStack[] = $node;
2280          }
2281          $this->elements = $newStack;
2282          return $_skipHistory
2283              ? $this
2284              : $this->newInstance();
2285      }
2286      /**
2287       * Enter description here...
2288       *
2289       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2290       * @link http://docs.jquery.com/Traversing/filter
2291       */
2292      public function filter($selectors, $_skipHistory = false) {
2293          if ($selectors instanceof Callback OR $selectors instanceof Closure)
2294              return $this->filterCallback($selectors, $_skipHistory);
2295          if (! $_skipHistory)
2296              $this->elementsBackup = $this->elements;
2297          $notSimpleSelector = array(' ', '>', '~', '+', '/');
2298          if (! is_array($selectors))
2299              $selectors = $this->parseSelector($selectors);
2300          if (! $_skipHistory)
2301              $this->debug(array("Filtering:", $selectors));
2302          $finalStack = array();
2303          foreach($selectors as $selector) {
2304              $stack = array();
2305              if (! $selector)
2306                  break;
2307              // avoid first space or /
2308              if (in_array($selector[0], $notSimpleSelector))
2309                  $selector = array_slice($selector, 1);
2310              // PER NODE selector chunks
2311              foreach($this->stack() as $node) {
2312                  $break = false;
2313                  foreach($selector as $s) {
2314                      if (!($node instanceof DOMELEMENT)) {
2315                          // all besides DOMElement
2316                          if ( $s[0] == '[') {
2317                              $attr = trim($s, '[]');
2318                              if ( mb_strpos($attr, '=')) {
2319                                  list( $attr, $val ) = explode('=', $attr);
2320                                  if ($attr == 'nodeType' && $node->nodeType != $val)
2321                                      $break = true;
2322                              }
2323                          } else
2324                              $break = true;
2325                      } else {
2326                          // DOMElement only
2327                          // ID
2328                          if ( $s[0] == '#') {
2329                              if ( $node->getAttribute('id') != substr($s, 1) )
2330                                  $break = true;
2331                          // CLASSES
2332                          } else if ( $s[0] == '.') {
2333                              if (! $this->matchClasses( $s, $node ) )
2334                                  $break = true;
2335                          // ATTRS
2336                          } else if ( $s[0] == '[') {
2337                              // strip side brackets
2338                              $attr = trim($s, '[]');
2339                              if (mb_strpos($attr, '=')) {
2340                                  list($attr, $val) = explode('=', $attr);
2341                                  $val = self::unQuote($val);
2342                                  if ($attr == 'nodeType') {
2343                                      if ($val != $node->nodeType)
2344                                          $break = true;
2345                                  } else if ($this->isRegexp($attr)) {
2346                                      $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2347                                          ? quotemeta(trim($val, '"\''))
2348                                          : preg_quote(trim($val, '"\''), '@');
2349                                      // switch last character
2350                                      switch( substr($attr, -1)) {
2351                                          // quotemeta used insted of preg_quote
2352                                          // http://code.google.com/p/phpquery/issues/detail?id=76
2353                                          case '^':
2354                                              $pattern = '^'.$val;
2355                                              break;
2356                                          case '*':
2357                                              $pattern = '.*'.$val.'.*';
2358                                              break;
2359                                          case '$':
2360                                              $pattern = '.*'.$val.'$';
2361                                              break;
2362                                      }
2363                                      // cut last character
2364                                      $attr = substr($attr, 0, -1);
2365                                      $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
2366                                          ? mb_ereg_match($pattern, $node->getAttribute($attr))
2367                                          : preg_match("@{$pattern}@", $node->getAttribute($attr));
2368                                      if (! $isMatch)
2369                                          $break = true;
2370                                  } else if ($node->getAttribute($attr) != $val)
2371                                      $break = true;
2372                              } else if (! $node->hasAttribute($attr))
2373                                  $break = true;
2374                          // PSEUDO CLASSES
2375                          } else if ( $s[0] == ':') {
2376                              // skip
2377                          // TAG
2378                          } else if (trim($s)) {
2379                              if ($s != '*') {
2380                                  // TODO namespaces
2381                                  if (isset($node->tagName)) {
2382                                      if ($node->tagName != $s)
2383                                          $break = true;
2384                                  } else if ($s == 'html' && ! $this->isRoot($node))
2385                                      $break = true;
2386                              }
2387                          // AVOID NON-SIMPLE SELECTORS
2388                          } else if (in_array($s, $notSimpleSelector)) {
2389                              $break = true;
2390                              $this->debug(array('Skipping non simple selector', $selector));
2391                          }
2392                      }
2393                      if ($break)
2394                          break;
2395                  }
2396                  // if element passed all chunks of selector - add it to new stack
2397                  if (! $break )
2398                      $stack[] = $node;
2399              }
2400              $tmpStack = $this->elements;
2401              $this->elements = $stack;
2402              // PER ALL NODES selector chunks
2403              foreach($selector as $s)
2404                  // PSEUDO CLASSES
2405                  if ($s[0] == ':')
2406                      $this->pseudoClasses($s);
2407              foreach($this->elements as $node)
2408                  // XXX it should be merged without duplicates
2409                  // but jQuery doesnt do that
2410                  $finalStack[] = $node;
2411              $this->elements = $tmpStack;
2412          }
2413          $this->elements = $finalStack;
2414          if ($_skipHistory) {
2415              return $this;
2416          } else {
2417              $this->debug("Stack length after filter(): ".count($finalStack));
2418              return $this->newInstance();
2419          }
2420      }
2421      /**
2422       *
2423       * @param $value
2424       * @return unknown_type
2425       * @TODO implement in all methods using passed parameters
2426       */
2427      protected static function unQuote($value) {
2428          return $value[0] == '\'' || $value[0] == '"'
2429              ? substr($value, 1, -1)
2430              : $value;
2431      }
2432      /**
2433       * Enter description here...
2434       *
2435       * @link http://docs.jquery.com/Ajax/load
2436       * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2437       * @todo Support $selector
2438       */
2439      public function load($url, $data = null, $callback = null) {
2440          if ($data && ! is_array($data)) {
2441              $callback = $data;
2442              $data = null;
2443          }
2444          if (mb_strpos($url, ' ') !== false) {
2445              $matches = null;
2446              if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
2447                  mb_ereg('^([^ ]+) (.*)$', $url, $matches);
2448              else
2449                  preg_match('^([^ ]+) (.*)$', $url, $matches);
2450              $url = $matches[1];
2451              $selector = $matches[2];
2452              // FIXME this sucks, pass as callback param
2453              $this->_loadSelector = $selector;
2454          }
2455          $ajax = array(
2456              'url' => $url,
2457              'type' => $data ? 'POST' : 'GET',
2458              'data' => $data,
2459              'complete' => $callback,
2460              'success' => array($this, 'loadSuccess')
2461          );
2462          phpQuery::ajax($ajax);
2463          return $this;
2464      }
2465      /**
2466       * @access private
2467       * @param $html
2468       * @return unknown_type
2469       */
2470      public function loadSuccess($html) {
2471          if ($this->_loadSelector) {
2472              $html = phpQuery::newDocument($html)->find($this->_loadSelector);
2473              unset($this->_loadSelector);
2474          }
2475          foreach($this->stack(1) as $node) {
2476              phpQuery::pq($node, $this->getDocumentID())
2477                  ->markup($html);
2478          }
2479      }
2480      /**
2481       * Enter description here...
2482       *
2483       * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2484       * @todo
2485       */
2486      public function css() {
2487          // TODO
2488          return $this;
2489      }
2490      /**
2491       * @todo
2492       *
2493       */
2494      public function show(){
2495          // TODO
2496          return $this;
2497      }
2498      /**
2499       * @todo
2500       *
2501       */
2502      public function hide(){
2503          // TODO
2504          return $this;
2505      }
2506      /**
2507       * Trigger a type of event on every matched element.
2508       *
2509       * @param unknown_type $type
2510       * @param unknown_type $data
2511       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2512       * @TODO support more than event in $type (space-separated)
2513       */
2514      public function trigger($type, $data = array()) {
2515          foreach($this->elements as $node)
2516              phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
2517          return $this;
2518      }
2519      /**
2520       * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
2521       *
2522       * @param unknown_type $type
2523       * @param unknown_type $data
2524       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2525       * @TODO
2526       */
2527      public function triggerHandler($type, $data = array()) {
2528          // TODO;
2529      }
2530      /**
2531       * Binds a handler to one or more events (like click) for each matched element.
2532       * Can also bind custom events.
2533       *
2534       * @param unknown_type $type
2535       * @param unknown_type $data Optional
2536       * @param unknown_type $callback
2537       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2538       * @TODO support '!' (exclusive) events
2539       * @TODO support more than event in $type (space-separated)
2540       */
2541      public function bind($type, $data, $callback = null) {
2542          // TODO check if $data is callable, not using is_callable
2543          if (! isset($callback)) {
2544              $callback = $data;
2545              $data = null;
2546          }
2547          foreach($this->elements as $node)
2548              phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
2549          return $this;
2550      }
2551      /**
2552       * Enter description here...
2553       *
2554       * @param unknown_type $type
2555       * @param unknown_type $callback
2556       * @return unknown
2557       * @TODO namespace events
2558       * @TODO support more than event in $type (space-separated)
2559       */
2560      public function unbind($type = null, $callback = null) {
2561          foreach($this->elements as $node)
2562              phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
2563          return $this;
2564      }
2565      /**
2566       * Enter description here...
2567       *
2568       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2569       */
2570      public function change($callback = null) {
2571          if ($callback)
2572              return $this->bind('change', $callback);
2573          return $this->trigger('change');
2574      }
2575      /**
2576       * Enter description here...
2577       *
2578       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2579       */
2580      public function submit($callback = null) {
2581          if ($callback)
2582              return $this->bind('submit', $callback);
2583          return $this->trigger('submit');
2584      }
2585      /**
2586       * Enter description here...
2587       *
2588       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2589       */
2590      public function click($callback = null) {
2591          if ($callback)
2592              return $this->bind('click', $callback);
2593          return $this->trigger('click');
2594      }
2595      /**
2596       * Enter description here...
2597       *
2598       * @param String|phpQuery
2599       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2600       */
2601      public function wrapAllOld($wrapper) {
2602          $wrapper = pq($wrapper)->_clone();
2603          if (! $wrapper->length() || ! $this->length() )
2604              return $this;
2605          $wrapper->insertBefore($this->elements[0]);
2606          $deepest = $wrapper->elements[0];
2607          while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2608              $deepest = $deepest->firstChild;
2609          pq($deepest)->append($this);
2610          return $this;
2611      }
2612      /**
2613       * Enter description here...
2614       *
2615       * TODO testme...
2616       * @param String|phpQuery
2617       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2618       */
2619      public function wrapAll($wrapper) {
2620          if (! $this->length())
2621              return $this;
2622          return phpQuery::pq($wrapper, $this->getDocumentID())
2623              ->clone()
2624              ->insertBefore($this->get(0))
2625              ->map(array($this, '___wrapAllCallback'))
2626              ->append($this);
2627      }
2628    /**
2629     *
2630       * @param $node
2631       * @return unknown_type
2632       * @access private
2633     */
2634      public function ___wrapAllCallback($node) {
2635          $deepest = $node;
2636          while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
2637              $deepest = $deepest->firstChild;
2638          return $deepest;
2639      }
2640      /**
2641       * Enter description here...
2642       * NON JQUERY METHOD
2643       *
2644       * @param String|phpQuery
2645       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2646       */
2647      public function wrapAllPHP($codeBefore, $codeAfter) {
2648          return $this
2649              ->slice(0, 1)
2650                  ->beforePHP($codeBefore)
2651              ->end()
2652              ->slice(-1)
2653                  ->afterPHP($codeAfter)
2654              ->end();
2655      }
2656      /**
2657       * Enter description here...
2658       *
2659       * @param String|phpQuery
2660       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2661       */
2662      public function wrap($wrapper) {
2663          foreach($this->stack() as $node)
2664              phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
2665          return $this;
2666      }
2667      /**
2668       * Enter description here...
2669       *
2670       * @param String|phpQuery
2671       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2672       */
2673      public function wrapPHP($codeBefore, $codeAfter) {
2674          foreach($this->stack() as $node)
2675              phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
2676          return $this;
2677      }
2678      /**
2679       * Enter description here...
2680       *
2681       * @param String|phpQuery
2682       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2683       */
2684      public function wrapInner($wrapper) {
2685          foreach($this->stack() as $node)
2686              phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
2687          return $this;
2688      }
2689      /**
2690       * Enter description here...
2691       *
2692       * @param String|phpQuery
2693       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2694       */
2695      public function wrapInnerPHP($codeBefore, $codeAfter) {
2696          foreach($this->stack(1) as $node)
2697              phpQuery::pq($node, $this->getDocumentID())->contents()
2698                  ->wrapAllPHP($codeBefore, $codeAfter);
2699          return $this;
2700      }
2701      /**
2702       * Enter description here...
2703       *
2704       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2705       * @testme Support for text nodes
2706       */
2707      public function contents() {
2708          $stack = array();
2709          foreach($this->stack(1) as $el) {
2710              // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
2711  //          if (! isset($el->childNodes))
2712  //              continue;
2713              foreach($el->childNodes as $node) {
2714                  $stack[] = $node;
2715              }
2716          }
2717          return $this->newInstance($stack);
2718      }
2719      /**
2720       * Enter description here...
2721       *
2722       * jQuery difference.
2723       *
2724       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2725       */
2726      public function contentsUnwrap() {
2727          foreach($this->stack(1) as $node) {
2728              if (! $node->parentNode )
2729                  continue;
2730              $childNodes = array();
2731              // any modification in DOM tree breaks childNodes iteration, so cache them first
2732              foreach($node->childNodes as $chNode )
2733                  $childNodes[] = $chNode;
2734              foreach($childNodes as $chNode )
2735  //              $node->parentNode->appendChild($chNode);
2736                  $node->parentNode->insertBefore($chNode, $node);
2737              $node->parentNode->removeChild($node);
2738          }
2739          return $this;
2740      }
2741      /**
2742       * Enter description here...
2743       *
2744       * jQuery difference.
2745       */
2746      public function switchWith($markup) {
2747          $markup = pq($markup, $this->getDocumentID());
2748          $content = null;
2749          foreach($this->stack(1) as $node) {
2750              pq($node)
2751                  ->contents()->toReference($content)->end()
2752                  ->replaceWith($markup->clone()->append($content));
2753          }
2754          return $this;
2755      }
2756      /**
2757       * Enter description here...
2758       *
2759       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2760       */
2761      public function eq($num) {
2762          $oldStack = $this->elements;
2763          $this->elementsBackup = $this->elements;
2764          $this->elements = array();
2765          if ( isset($oldStack[$num]) )
2766              $this->elements[] = $oldStack[$num];
2767          return $this->newInstance();
2768      }
2769      /**
2770       * Enter description here...
2771       *
2772       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2773       */
2774      public function size() {
2775          return count($this->elements);
2776      }
2777      /**
2778       * Enter description here...
2779       *
2780       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2781       * @deprecated Use length as attribute
2782       */
2783      public function length() {
2784          return $this->size();
2785      }
2786      public function count() {
2787          return $this->size();
2788      }
2789      /**
2790       * Enter description here...
2791       *
2792       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2793       * @todo $level
2794       */
2795      public function end($level = 1) {
2796  //      $this->elements = array_pop( $this->history );
2797  //      return $this;
2798  //      $this->previous->DOM = $this->DOM;
2799  //      $this->previous->XPath = $this->XPath;
2800          return $this->previous
2801              ? $this->previous
2802              : $this;
2803      }
2804      /**
2805       * Enter description here...
2806       * Normal use ->clone() .
2807       *
2808       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2809       * @access private
2810       */
2811      public function _clone() {
2812          $newStack = array();
2813          //pr(array('copy... ', $this->whois()));
2814          //$this->dumpHistory('copy');
2815          $this->elementsBackup = $this->elements;
2816          foreach($this->elements as $node) {
2817              $newStack[] = $node->cloneNode(true);
2818          }
2819          $this->elements = $newStack;
2820          return $this->newInstance();
2821      }
2822      /**
2823       * Enter description here...
2824       *
2825       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2826       */
2827      public function replaceWithPHP($code) {
2828          return $this->replaceWith(phpQuery::php($code));
2829      }
2830      /**
2831       * Enter description here...
2832       *
2833       * @param String|phpQuery $content
2834       * @link http://docs.jquery.com/Manipulation/replaceWith#content
2835       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2836       */
2837      public function replaceWith($content) {
2838          return $this->after($content)->remove();
2839      }
2840      /**
2841       * Enter description here...
2842       *
2843       * @param String $selector
2844       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2845       * @todo this works ?
2846       */
2847      public function replaceAll($selector) {
2848          foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node)
2849              phpQuery::pq($node, $this->getDocumentID())
2850                  ->after($this->_clone())
2851                  ->remove();
2852          return $this;
2853      }
2854      /**
2855       * Enter description here...
2856       *
2857       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2858       */
2859      public function remove($selector = null) {
2860          $loop = $selector
2861              ? $this->filter($selector)->elements
2862              : $this->elements;
2863          foreach($loop as $node) {
2864              if (! $node->parentNode )
2865                  continue;
2866              if (isset($node->tagName))
2867                  $this->debug("Removing '{$node->tagName}'");
2868              $node->parentNode->removeChild($node);
2869              // Mutation event
2870              $event = new DOMEvent(array(
2871                  'target' => $node,
2872                  'type' => 'DOMNodeRemoved'
2873              ));
2874              phpQueryEvents::trigger($this->getDocumentID(),
2875                  $event->type, array($event), $node
2876              );
2877          }
2878          return $this;
2879      }
2880      protected function markupEvents($newMarkup, $oldMarkup, $node) {
2881          if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
2882              $event = new DOMEvent(array(
2883                  'target' => $node,
2884                  'type' => 'change'
2885              ));
2886              phpQueryEvents::trigger($this->getDocumentID(),
2887                  $event->type, array($event), $node
2888              );
2889          }
2890      }
2891      /**
2892       * jQuey difference
2893       *
2894       * @param $markup
2895       * @return unknown_type
2896       * @TODO trigger change event for textarea
2897       */
2898      public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2899          $args = func_get_args();
2900          if ($this->documentWrapper->isXML)
2901              return call_user_func_array(array($this, 'xml'), $args);
2902          else
2903              return call_user_func_array(array($this, 'html'), $args);
2904      }
2905      /**
2906       * jQuey difference
2907       *
2908       * @param $markup
2909       * @return unknown_type
2910       */
2911      public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2912          $args = func_get_args();
2913          if ($this->documentWrapper->isXML)
2914              return call_user_func_array(array($this, 'xmlOuter'), $args);
2915          else
2916              return call_user_func_array(array($this, 'htmlOuter'), $args);
2917      }
2918      /**
2919       * Enter description here...
2920       *
2921       * @param unknown_type $html
2922       * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2923       * @TODO force html result
2924       */
2925      public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2926          if (isset($html)) {
2927              // INSERT
2928              $nodes = $this->documentWrapper->import($html);
2929              $this->empty();
2930              foreach($this->stack(1) as $alreadyAdded => $node) {
2931                  // for now, limit events for textarea
2932                  if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2933                      $oldHtml = pq($node, $this->getDocumentID())->markup();
2934                  foreach($nodes as $newNode) {
2935                      $node->appendChild($alreadyAdded
2936                          ? $newNode->cloneNode(true)
2937                          : $newNode
2938                      );
2939                  }
2940                  // for now, limit events for textarea
2941                  if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
2942                      $this->markupEvents($html, $oldHtml, $node);
2943              }
2944              return $this;
2945          } else {
2946              // FETCH
2947              $return = $this->documentWrapper->markup($this->elements, true);
2948              $args = func_get_args();
2949              foreach(array_slice($args, 1) as $callback) {
2950                  $return = phpQuery::callbackRun($callback, array($return));
2951              }
2952              return $return;
2953          }
2954      }
2955      /**
2956       * @TODO force xml result
2957       */
2958      public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) {
2959          $args = func_get_args();
2960          return call_user_func_array(array($this, 'html'), $args);
2961      }
2962      /**
2963       * Enter description here...
2964       * @TODO force html result
2965       *
2966       * @return String
2967       */
2968      public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2969          $markup = $this->documentWrapper->markup($this->elements);
2970          // pass thou callbacks
2971          $args = func_get_args();
2972          foreach($args as $callback) {
2973              $markup = phpQuery::callbackRun($callback, array($markup));
2974          }
2975          return $markup;
2976      }
2977      /**
2978       * @TODO force xml result
2979       */
2980      public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
2981          $args = func_get_args();
2982          return call_user_func_array(array($this, 'htmlOuter'), $args);
2983      }
2984      public function __toString() {
2985          return $this->markupOuter();
2986      }
2987      /**
2988       * Just like html(), but returns markup with VALID (dangerous) PHP tags.
2989       *
2990       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
2991       * @todo support returning markup with PHP tags when called without param
2992       */
2993      public function php($code = null) {
2994          return $this->markupPHP($code);
2995      }
2996      /**
2997       * Enter description here...
2998       *
2999       * @param $code
3000       * @return unknown_type
3001       */
3002      public function markupPHP($code = null) {
3003          return isset($code)
3004              ? $this->markup(phpQuery::php($code))
3005              : phpQuery::markupToPHP($this->markup());
3006      }
3007      /**
3008       * Enter description here...
3009       *
3010       * @param $code
3011       * @return unknown_type
3012       */
3013      public function markupOuterPHP() {
3014          return phpQuery::markupToPHP($this->markupOuter());
3015      }
3016      /**
3017       * Enter description here...
3018       *
3019       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3020       */
3021      public function children($selector = null) {
3022          $stack = array();
3023          foreach($this->stack(1) as $node) {
3024  //          foreach($node->getElementsByTagName('*') as $newNode) {
3025              foreach($node->childNodes as $newNode) {
3026                  if ($newNode->nodeType != 1)
3027                      continue;
3028                  if ($selector && ! $this->is($selector, $newNode))
3029                      continue;
3030                  if ($this->elementsContainsNode($newNode, $stack))
3031                      continue;
3032                  $stack[] = $newNode;
3033              }
3034          }
3035          $this->elementsBackup = $this->elements;
3036          $this->elements = $stack;
3037          return $this->newInstance();
3038      }
3039      /**
3040       * Enter description here...
3041       *
3042       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3043       */
3044      public function ancestors($selector = null) {
3045          return $this->children( $selector );
3046      }
3047      /**
3048       * Enter description here...
3049       *
3050       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3051       */
3052      public function append( $content) {
3053          return $this->insert($content, __FUNCTION__);
3054      }
3055      /**
3056       * Enter description here...
3057       *
3058       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3059       */
3060      public function appendPHP( $content) {
3061          return $this->insert("<php><!-- {$content} --></php>", 'append');
3062      }
3063      /**
3064       * Enter description here...
3065       *
3066       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3067       */
3068      public function appendTo( $seletor) {
3069          return $this->insert($seletor, __FUNCTION__);
3070      }
3071      /**
3072       * Enter description here...
3073       *
3074       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3075       */
3076      public function prepend( $content) {
3077          return $this->insert($content, __FUNCTION__);
3078      }
3079      /**
3080       * Enter description here...
3081       *
3082       * @todo accept many arguments, which are joined, arrays maybe also
3083       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3084       */
3085      public function prependPHP( $content) {
3086          return $this->insert("<php><!-- {$content} --></php>", 'prepend');
3087      }
3088      /**
3089       * Enter description here...
3090       *
3091       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3092       */
3093      public function prependTo( $seletor) {
3094          return $this->insert($seletor, __FUNCTION__);
3095      }
3096      /**
3097       * Enter description here...
3098       *
3099       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3100       */
3101      public function before($content) {
3102          return $this->insert($content, __FUNCTION__);
3103      }
3104      /**
3105       * Enter description here...
3106       *
3107       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3108       */
3109      public function beforePHP( $content) {
3110          return $this->insert("<php><!-- {$content} --></php>", 'before');
3111      }
3112      /**
3113       * Enter description here...
3114       *
3115       * @param String|phpQuery
3116       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3117       */
3118      public function insertBefore( $seletor) {
3119          return $this->insert($seletor, __FUNCTION__);
3120      }
3121      /**
3122       * Enter description here...
3123       *
3124       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3125       */
3126      public function after( $content) {
3127          return $this->insert($content, __FUNCTION__);
3128      }
3129      /**
3130       * Enter description here...
3131       *
3132       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3133       */
3134      public function afterPHP( $content) {
3135          return $this->insert("<php><!-- {$content} --></php>", 'after');
3136      }
3137      /**
3138       * Enter description here...
3139       *
3140       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3141       */
3142      public function insertAfter( $seletor) {
3143          return $this->insert($seletor, __FUNCTION__);
3144      }
3145      /**
3146       * Internal insert method. Don't use it.
3147       *
3148       * @param unknown_type $target
3149       * @param unknown_type $type
3150       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3151       * @access private
3152       */
3153      public function insert($target, $type) {
3154          $this->debug("Inserting data with '{$type}'");
3155          $to = false;
3156          switch( $type) {
3157              case 'appendTo':
3158              case 'prependTo':
3159              case 'insertBefore':
3160              case 'insertAfter':
3161                  $to = true;
3162          }
3163          switch(gettype($target)) {
3164              case 'string':
3165                  $insertFrom = $insertTo = array();
3166                  if ($to) {
3167                      // INSERT TO
3168                      $insertFrom = $this->elements;
3169                      if (phpQuery::isMarkup($target)) {
3170                          // $target is new markup, import it
3171                          $insertTo = $this->documentWrapper->import($target);
3172                      // insert into selected element
3173                      } else {
3174                          // $tagret is a selector
3175                          $thisStack = $this->elements;
3176                          $this->toRoot();
3177                          $insertTo = $this->find($target)->elements;
3178                          $this->elements = $thisStack;
3179                      }
3180                  } else {
3181                      // INSERT FROM
3182                      $insertTo = $this->elements;
3183                      $insertFrom = $this->documentWrapper->import($target);
3184                  }
3185                  break;
3186              case 'object':
3187                  $insertFrom = $insertTo = array();
3188                  // phpQuery
3189                  if ($target instanceof self) {
3190                      if ($to) {
3191                          $insertTo = $target->elements;
3192                          if ($this->documentFragment && $this->stackIsRoot())
3193                              // get all body children
3194  //                          $loop = $this->find('body > *')->elements;
3195                              // TODO test it, test it hard...
3196  //                          $loop = $this->newInstance($this->root)->find('> *')->elements;
3197                              $loop = $this->root->childNodes;
3198                          else
3199                              $loop = $this->elements;
3200                          // import nodes if needed
3201                          $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3202                              ? $loop
3203                              : $target->documentWrapper->import($loop);
3204                      } else {
3205                          $insertTo = $this->elements;
3206                          if ( $target->documentFragment && $target->stackIsRoot() )
3207                              // get all body children
3208  //                          $loop = $target->find('body > *')->elements;
3209                              $loop = $target->root->childNodes;
3210                          else
3211                              $loop = $target->elements;
3212                          // import nodes if needed
3213                          $insertFrom = $this->getDocumentID() == $target->getDocumentID()
3214                              ? $loop
3215                              : $this->documentWrapper->import($loop);
3216                      }
3217                  // DOMNODE
3218                  } elseif ($target instanceof DOMNODE) {
3219                      // import node if needed
3220  //                  if ( $target->ownerDocument != $this->DOM )
3221  //                      $target = $this->DOM->importNode($target, true);
3222                      if ( $to) {
3223                          $insertTo = array($target);
3224                          if ($this->documentFragment && $this->stackIsRoot())
3225                              // get all body children
3226                              $loop = $this->root->childNodes;
3227  //                          $loop = $this->find('body > *')->elements;
3228                          else
3229                              $loop = $this->elements;
3230                          foreach($loop as $fromNode)
3231                              // import nodes if needed
3232                              $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument)
3233                                  ? $target->ownerDocument->importNode($fromNode, true)
3234                                  : $fromNode;
3235                      } else {
3236                          // import node if needed
3237                          if (! $target->ownerDocument->isSameNode($this->document))
3238                              $target = $this->document->importNode($target, true);
3239                          $insertTo = $this->elements;
3240                          $insertFrom[] = $target;
3241                      }
3242                  }
3243                  break;
3244          }
3245          phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes");
3246          foreach($insertTo as $insertNumber => $toNode) {
3247              // we need static relative elements in some cases
3248              switch( $type) {
3249                  case 'prependTo':
3250                  case 'prepend':
3251                      $firstChild = $toNode->firstChild;
3252                      break;
3253                  case 'insertAfter':
3254                  case 'after':
3255                      $nextSibling = $toNode->nextSibling;
3256                      break;
3257              }
3258              foreach($insertFrom as $fromNode) {
3259                  // clone if inserted already before
3260                  $insert = $insertNumber
3261                      ? $fromNode->cloneNode(true)
3262                      : $fromNode;
3263                  switch($type) {
3264                      case 'appendTo':
3265                      case 'append':
3266  //                      $toNode->insertBefore(
3267  //                          $fromNode,
3268  //                          $toNode->lastChild->nextSibling
3269  //                      );
3270                          $toNode->appendChild($insert);
3271                          $eventTarget = $insert;
3272                          break;
3273                      case 'prependTo':
3274                      case 'prepend':
3275                          $toNode->insertBefore(
3276                              $insert,
3277                              $firstChild
3278                          );
3279                          break;
3280                      case 'insertBefore':
3281                      case 'before':
3282                          if (! $toNode->parentNode)
3283                              throw new Exception("No parentNode, can't do {$type}()");
3284                          else
3285                              $toNode->parentNode->insertBefore(
3286                                  $insert,
3287                                  $toNode
3288                              );
3289                          break;
3290                      case 'insertAfter':
3291                      case 'after':
3292                          if (! $toNode->parentNode)
3293                              throw new Exception("No parentNode, can't do {$type}()");
3294                          else
3295                              $toNode->parentNode->insertBefore(
3296                                  $insert,
3297                                  $nextSibling
3298                              );
3299                          break;
3300                  }
3301                  // Mutation event
3302                  $event = new DOMEvent(array(
3303                      'target' => $insert,
3304                      'type' => 'DOMNodeInserted'
3305                  ));
3306                  phpQueryEvents::trigger($this->getDocumentID(),
3307                      $event->type, array($event), $insert
3308                  );
3309              }
3310          }
3311          return $this;
3312      }
3313      /**
3314       * Enter description here...
3315       *
3316       * @return Int
3317       */
3318      public function index($subject) {
3319          $index = -1;
3320          $subject = $subject instanceof phpQueryObject
3321              ? $subject->elements[0]
3322              : $subject;
3323          foreach($this->newInstance() as $k => $node) {
3324              if ($node->isSameNode($subject))
3325                  $index = $k;
3326          }
3327          return $index;
3328      }
3329      /**
3330       * Enter description here...
3331       *
3332       * @param unknown_type $start
3333       * @param unknown_type $end
3334       *
3335       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3336       * @testme
3337       */
3338      public function slice($start, $end = null) {
3339  //      $last = count($this->elements)-1;
3340  //      $end = $end
3341  //          ? min($end, $last)
3342  //          : $last;
3343  //      if ($start < 0)
3344  //          $start = $last+$start;
3345  //      if ($start > $last)
3346  //          return array();
3347          if ($end > 0)
3348              $end = $end-$start;
3349          return $this->newInstance(
3350              array_slice($this->elements, $start, $end)
3351          );
3352      }
3353      /**
3354       * Enter description here...
3355       *
3356       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3357       */
3358      public function reverse() {
3359          $this->elementsBackup = $this->elements;
3360          $this->elements = array_reverse($this->elements);
3361          return $this->newInstance();
3362      }
3363      /**
3364       * Return joined text content.
3365       * @return String
3366       */
3367      public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) {
3368          if (isset($text))
3369              return $this->html(htmlspecialchars($text));
3370          $args = func_get_args();
3371          $args = array_slice($args, 1);
3372          $return = '';
3373          foreach($this->elements as $node) {
3374              $text = $node->textContent;
3375              if (count($this->elements) > 1 && $text)
3376                  $text .= "\n";
3377              foreach($args as $callback) {
3378                  $text = phpQuery::callbackRun($callback, array($text));
3379              }
3380              $return .= $text;
3381          }
3382          return $return;
3383      }
3384      /**
3385       * Enter description here...
3386       *
3387       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3388       */
3389      public function plugin($class, $file = null) {
3390          phpQuery::plugin($class, $file);
3391          return $this;
3392      }
3393      /**
3394       * Deprecated, use $pq->plugin() instead.
3395       *
3396       * @deprecated
3397       * @param $class
3398       * @param $file
3399       * @return unknown_type
3400       */
3401      public static function extend($class, $file = null) {
3402          return $this->plugin($class, $file);
3403      }
3404      /**
3405       *
3406       * @access private
3407       * @param $method
3408       * @param $args
3409       * @return unknown_type
3410       */
3411      public function __call($method, $args) {
3412          $aliasMethods = array('clone', 'empty');
3413          if (isset(phpQuery::$extendMethods[$method])) {
3414              array_unshift($args, $this);
3415              return phpQuery::callbackRun(
3416                  phpQuery::$extendMethods[$method], $args
3417              );
3418          } else if (isset(phpQuery::$pluginsMethods[$method])) {
3419              array_unshift($args, $this);
3420              $class = phpQuery::$pluginsMethods[$method];
3421              $realClass = "phpQueryObjectPlugin_$class";
3422              $return = call_user_func_array(
3423                  array($realClass, $method),
3424                  $args
3425              );
3426              // XXX deprecate ?
3427              return is_null($return)
3428                  ? $this
3429                  : $return;
3430          } else if (in_array($method, $aliasMethods)) {
3431              return call_user_func_array(array($this, '_'.$method), $args);
3432          } else
3433              throw new Exception("Method '{$method}' doesnt exist");
3434      }
3435      /**
3436       * Safe rename of next().
3437       *
3438       * Use it ONLY when need to call next() on an iterated object (in same time).
3439       * Normaly there is no need to do such thing ;)
3440       *
3441       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3442       * @access private
3443       */
3444      public function _next($selector = null) {
3445          return $this->newInstance(
3446              $this->getElementSiblings('nextSibling', $selector, true)
3447          );
3448      }
3449      /**
3450       * Use prev() and next().
3451       *
3452       * @deprecated
3453       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3454       * @access private
3455       */
3456      public function _prev($selector = null) {
3457          return $this->prev($selector);
3458      }
3459      /**
3460       * Enter description here...
3461       *
3462       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3463       */
3464      public function prev($selector = null) {
3465          return $this->newInstance(
3466              $this->getElementSiblings('previousSibling', $selector, true)
3467          );
3468      }
3469      /**
3470       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3471       * @todo
3472       */
3473      public function prevAll($selector = null) {
3474          return $this->newInstance(
3475              $this->getElementSiblings('previousSibling', $selector)
3476          );
3477      }
3478      /**
3479       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3480       * @todo FIXME: returns source elements insted of next siblings
3481       */
3482      public function nextAll($selector = null) {
3483          return $this->newInstance(
3484              $this->getElementSiblings('nextSibling', $selector)
3485          );
3486      }
3487      /**
3488       * @access private
3489       */
3490      protected function getElementSiblings($direction, $selector = null, $limitToOne = false) {
3491          $stack = array();
3492          $count = 0;
3493          foreach($this->stack() as $node) {
3494              $test = $node;
3495              while( isset($test->{$direction}) && $test->{$direction}) {
3496                  $test = $test->{$direction};
3497                  if (! $test instanceof DOMELEMENT)
3498                      continue;
3499                  $stack[] = $test;
3500                  if ($limitToOne)
3501                      break;
3502              }
3503          }
3504          if ($selector) {
3505              $stackOld = $this->elements;
3506              $this->elements = $stack;
3507              $stack = $this->filter($selector, true)->stack();
3508              $this->elements = $stackOld;
3509          }
3510          return $stack;
3511      }
3512      /**
3513       * Enter description here...
3514       *
3515       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3516       */
3517      public function siblings($selector = null) {
3518          $stack = array();
3519          $siblings = array_merge(
3520              $this->getElementSiblings('previousSibling', $selector),
3521              $this->getElementSiblings('nextSibling', $selector)
3522          );
3523          foreach($siblings as $node) {
3524              if (! $this->elementsContainsNode($node, $stack))
3525                  $stack[] = $node;
3526          }
3527          return $this->newInstance($stack);
3528      }
3529      /**
3530       * Enter description here...
3531       *
3532       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3533       */
3534      public function not($selector = null) {
3535          if (is_string($selector))
3536              phpQuery::debug(array('not', $selector));
3537          else
3538              phpQuery::debug('not');
3539          $stack = array();
3540          if ($selector instanceof self || $selector instanceof DOMNODE) {
3541              foreach($this->stack() as $node) {
3542                  if ($selector instanceof self) {
3543                      $matchFound = false;
3544                      foreach($selector->stack() as $notNode) {
3545                          if ($notNode->isSameNode($node))
3546                              $matchFound = true;
3547                      }
3548                      if (! $matchFound)
3549                          $stack[] = $node;
3550                  } else if ($selector instanceof DOMNODE) {
3551                      if (! $selector->isSameNode($node))
3552                          $stack[] = $node;
3553                  } else {
3554                      if (! $this->is($selector))
3555                          $stack[] = $node;
3556                  }
3557              }
3558          } else {
3559              $orgStack = $this->stack();
3560              $matched = $this->filter($selector, true)->stack();
3561  //          $matched = array();
3562  //          // simulate OR in filter() instead of AND 5y
3563  //          foreach($this->parseSelector($selector) as $s) {
3564  //              $matched = array_merge($matched,
3565  //                  $this->filter(array($s))->stack()
3566  //              );
3567  //          }
3568              foreach($orgStack as $node)
3569                  if (! $this->elementsContainsNode($node, $matched))
3570                      $stack[] = $node;
3571          }
3572          return $this->newInstance($stack);
3573      }
3574      /**
3575       * Enter description here...
3576       *
3577       * @param string|phpQueryObject
3578       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3579       */
3580      public function add($selector = null) {
3581          if (! $selector)
3582              return $this;
3583          $stack = array();
3584          $this->elementsBackup = $this->elements;
3585          $found = phpQuery::pq($selector, $this->getDocumentID());
3586          $this->merge($found->elements);
3587          return $this->newInstance();
3588      }
3589      /**
3590       * @access private
3591       */
3592      protected function merge() {
3593          foreach(func_get_args() as $nodes)
3594              foreach($nodes as $newNode )
3595                  if (! $this->elementsContainsNode($newNode) )
3596                      $this->elements[] = $newNode;
3597      }
3598      /**
3599       * @access private
3600       * TODO refactor to stackContainsNode
3601       */
3602      protected function elementsContainsNode($nodeToCheck, $elementsStack = null) {
3603          $loop = ! is_null($elementsStack)
3604              ? $elementsStack
3605              : $this->elements;
3606          foreach($loop as $node) {
3607              if ( $node->isSameNode( $nodeToCheck ) )
3608                  return true;
3609          }
3610          return false;
3611      }
3612      /**
3613       * Enter description here...
3614       *
3615       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3616       */
3617      public function parent($selector = null) {
3618          $stack = array();
3619          foreach($this->elements as $node )
3620              if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) )
3621                  $stack[] = $node->parentNode;
3622          $this->elementsBackup = $this->elements;
3623          $this->elements = $stack;
3624          if ( $selector )
3625              $this->filter($selector, true);
3626          return $this->newInstance();
3627      }
3628      /**
3629       * Enter description here...
3630       *
3631       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3632       */
3633      public function parents($selector = null) {
3634          $stack = array();
3635          if (! $this->elements )
3636              $this->debug('parents() - stack empty');
3637          foreach($this->elements as $node) {
3638              $test = $node;
3639              while( $test->parentNode) {
3640                  $test = $test->parentNode;
3641                  if ($this->isRoot($test))
3642                      break;
3643                  if (! $this->elementsContainsNode($test, $stack)) {
3644                      $stack[] = $test;
3645                      continue;
3646                  }
3647              }
3648          }
3649          $this->elementsBackup = $this->elements;
3650          $this->elements = $stack;
3651          if ( $selector )
3652              $this->filter($selector, true);
3653          return $this->newInstance();
3654      }
3655      /**
3656       * Internal stack iterator.
3657       *
3658       * @access private
3659       */
3660      public function stack($nodeTypes = null) {
3661          if (!isset($nodeTypes))
3662              return $this->elements;
3663          if (!is_array($nodeTypes))
3664              $nodeTypes = array($nodeTypes);
3665          $return = array();
3666          foreach($this->elements as $node) {
3667              if (in_array($node->nodeType, $nodeTypes))
3668                  $return[] = $node;
3669          }
3670          return $return;
3671      }
3672      // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
3673      protected function attrEvents($attr, $oldAttr, $oldValue, $node) {
3674          // skip events for XML documents
3675          if (! $this->isXHTML() && ! $this->isHTML())
3676              return;
3677          $event = null;
3678          // identify
3679          $isInputValue = $node->tagName == 'input'
3680              && (
3681                  in_array($node->getAttribute('type'),
3682                      array('text', 'password', 'hidden'))
3683                  || !$node->getAttribute('type')
3684                   );
3685          $isRadio = $node->tagName == 'input'
3686              && $node->getAttribute('type') == 'radio';
3687          $isCheckbox = $node->tagName == 'input'
3688              && $node->getAttribute('type') == 'checkbox';
3689          $isOption = $node->tagName == 'option';
3690          if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
3691              $event = new DOMEvent(array(
3692                  'target' => $node,
3693                  'type' => 'change'
3694              ));
3695          } else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
3696                  // check
3697                  (! $oldAttr && $node->hasAttribute($attr))
3698                  // un-check
3699                  || (! $node->hasAttribute($attr) && $oldAttr)
3700              )) {
3701              $event = new DOMEvent(array(
3702                  'target' => $node,
3703                  'type' => 'change'
3704              ));
3705          } else if ($isOption && $node->parentNode && $attr == 'selected' && (
3706                  // select
3707                  (! $oldAttr && $node->hasAttribute($attr))
3708                  // un-select
3709                  || (! $node->hasAttribute($attr) && $oldAttr)
3710              )) {
3711              $event = new DOMEvent(array(
3712                  'target' => $node->parentNode,
3713                  'type' => 'change'
3714              ));
3715          }
3716          if ($event) {
3717              phpQueryEvents::trigger($this->getDocumentID(),
3718                  $event->type, array($event), $node
3719              );
3720          }
3721      }
3722      public function attr($attr = null, $value = null) {
3723          foreach($this->stack(1) as $node) {
3724              if (! is_null($value)) {
3725                  $loop = $attr == '*'
3726                      ? $this->getNodeAttrs($node)
3727                      : array($attr);
3728                  foreach($loop as $a) {
3729                      $oldValue = $node->getAttribute($a);
3730                      $oldAttr = $node->hasAttribute($a);
3731                      // TODO raises an error when charset other than UTF-8
3732                      // while document's charset is also not UTF-8
3733                      @$node->setAttribute($a, $value);
3734                      $this->attrEvents($a, $oldAttr, $oldValue, $node);
3735                  }
3736              } else if ($attr == '*') {
3737                  // jQuery difference
3738                  $return = array();
3739                  foreach($node->attributes as $n => $v)
3740                      $return[$n] = $v->value;
3741                  return $return;
3742              } else
3743                  return $node->hasAttribute($attr)
3744                      ? $node->getAttribute($attr)
3745                      : null;
3746          }
3747          return is_null($value)
3748              ? '' : $this;
3749      }
3750      /**
3751       * @access private
3752       */
3753      protected function getNodeAttrs($node) {
3754          $return = array();
3755          foreach($node->attributes as $n => $o)
3756              $return[] = $n;
3757          return $return;
3758      }
3759      /**
3760       * Enter description here...
3761       *
3762       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3763       * @todo check CDATA ???
3764       */
3765      public function attrPHP($attr, $code) {
3766          if (! is_null($code)) {
3767              $value = '<'.'?php '.$code.' ?'.'>';
3768              // TODO tempolary solution
3769              // http://code.google.com/p/phpquery/issues/detail?id=17
3770  //          if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
3771  //              $value  = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
3772          }
3773          foreach($this->stack(1) as $node) {
3774              if (! is_null($code)) {
3775  //              $attrNode = $this->DOM->createAttribute($attr);
3776                  $node->setAttribute($attr, $value);
3777  //              $attrNode->value = $value;
3778  //              $node->appendChild($attrNode);
3779              } else if ( $attr == '*') {
3780                  // jQuery diff
3781                  $return = array();
3782                  foreach($node->attributes as $n => $v)
3783                      $return[$n] = $v->value;
3784                  return $return;
3785              } else
3786                  return $node->getAttribute($attr);
3787          }
3788          return $this;
3789      }
3790      /**
3791       * Enter description here...
3792       *
3793       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3794       */
3795      public function removeAttr($attr) {
3796          foreach($this->stack(1) as $node) {
3797              $loop = $attr == '*'
3798                  ? $this->getNodeAttrs($node)
3799                  : array($attr);
3800              foreach($loop as $a) {
3801                  $oldValue = $node->getAttribute($a);
3802                  $node->removeAttribute($a);
3803                  $this->attrEvents($a, $oldValue, null, $node);
3804              }
3805          }
3806          return $this;
3807      }
3808      /**
3809       * Return form element value.
3810       *
3811       * @return String Fields value.
3812       */
3813      public function val($val = null) {
3814          if (! isset($val)) {
3815              if ($this->eq(0)->is('select')) {
3816                      $selected = $this->eq(0)->find('option[selected=selected]');
3817                      if ($selected->is('[value]'))
3818                          return $selected->attr('value');
3819                      else
3820                          return $selected->text();
3821              } else if ($this->eq(0)->is('textarea'))
3822                      return $this->eq(0)->markup();
3823                  else
3824                      return $this->eq(0)->attr('value');
3825          } else {
3826              $_val = null;
3827              foreach($this->stack(1) as $node) {
3828                  $node = pq($node, $this->getDocumentID());
3829                  if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
3830                      $isChecked = in_array($node->attr('value'), $val)
3831                              || in_array($node->attr('name'), $val);
3832                      if ($isChecked)
3833                          $node->attr('checked', 'checked');
3834                      else
3835                          $node->removeAttr('checked');
3836                  } else if ($node->get(0)->tagName == 'select') {
3837                      if (! isset($_val)) {
3838                          $_val = array();
3839                          if (! is_array($val))
3840                              $_val = array((string)$val);
3841                          else
3842                              foreach($val as $v)
3843                                  $_val[] = $v;
3844                      }
3845                      foreach($node['option']->stack(1) as $option) {
3846                          $option = pq($option, $this->getDocumentID());
3847                          $selected = false;
3848                          // XXX: workaround for string comparsion, see issue #96
3849                          // http://code.google.com/p/phpquery/issues/detail?id=96
3850                          $selected = is_null($option->attr('value'))
3851                              ? in_array($option->markup(), $_val)
3852                              : in_array($option->attr('value'), $_val);
3853  //                      $optionValue = $option->attr('value');
3854  //                      $optionText = $option->text();
3855  //                      $optionTextLenght = mb_strlen($optionText);
3856  //                      foreach($_val as $v)
3857  //                          if ($optionValue == $v)
3858  //                              $selected = true;
3859  //                          else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
3860  //                              $selected = true;
3861                          if ($selected)
3862                              $option->attr('selected', 'selected');
3863                          else
3864                              $option->removeAttr('selected');
3865                      }
3866                  } else if ($node->get(0)->tagName == 'textarea')
3867                      $node->markup($val);
3868                  else
3869                      $node->attr('value', $val);
3870              }
3871          }
3872          return $this;
3873      }
3874      /**
3875       * Enter description here...
3876       *
3877       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3878       */
3879      public function andSelf() {
3880          if ( $this->previous )
3881              $this->elements = array_merge($this->elements, $this->previous->elements);
3882          return $this;
3883      }
3884      /**
3885       * Enter description here...
3886       *
3887       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3888       */
3889      public function addClass( $className) {
3890          if (! $className)
3891              return $this;
3892          foreach($this->stack(1) as $node) {
3893              if (! $this->is(".$className", $node))
3894                  $node->setAttribute(
3895                      'class',
3896                      trim($node->getAttribute('class').' '.$className)
3897                  );
3898          }
3899          return $this;
3900      }
3901      /**
3902       * Enter description here...
3903       *
3904       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3905       */
3906      public function addClassPHP( $className) {
3907          foreach($this->stack(1) as $node) {
3908                  $classes = $node->getAttribute('class');
3909                  $newValue = $classes
3910                      ? $classes.' <'.'?php '.$className.' ?'.'>'
3911                      : '<'.'?php '.$className.' ?'.'>';
3912                  $node->setAttribute('class', $newValue);
3913          }
3914          return $this;
3915      }
3916      /**
3917       * Enter description here...
3918       *
3919       * @param   string  $className
3920       * @return  bool
3921       */
3922      public function hasClass($className) {
3923          foreach($this->stack(1) as $node) {
3924              if ( $this->is(".$className", $node))
3925                  return true;
3926          }
3927          return false;
3928      }
3929      /**
3930       * Enter description here...
3931       *
3932       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3933       */
3934      public function removeClass($className) {
3935          foreach($this->stack(1) as $node) {
3936              $classes = explode( ' ', $node->getAttribute('class'));
3937              if ( in_array($className, $classes)) {
3938                  $classes = array_diff($classes, array($className));
3939                  if ( $classes )
3940                      $node->setAttribute('class', implode(' ', $classes));
3941                  else
3942                      $node->removeAttribute('class');
3943              }
3944          }
3945          return $this;
3946      }
3947      /**
3948       * Enter description here...
3949       *
3950       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3951       */
3952      public function toggleClass($className) {
3953          foreach($this->stack(1) as $node) {
3954              if ( $this->is( $node, '.'.$className ))
3955                  $this->removeClass($className);
3956              else
3957                  $this->addClass($className);
3958          }
3959          return $this;
3960      }
3961      /**
3962       * Proper name without underscore (just ->empty()) also works.
3963       *
3964       * Removes all child nodes from the set of matched elements.
3965       *
3966       * Example:
3967       * pq("p")._empty()
3968       *
3969       * HTML:
3970       * <p>Hello, <span>Person</span> <a href="#">and person</a></p>
3971       *
3972       * Result:
3973       * [ <p></p> ]
3974       *
3975       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3976       * @access private
3977       */
3978      public function _empty() {
3979          foreach($this->stack(1) as $node) {
3980              // thx to 'dave at dgx dot cz'
3981              $node->nodeValue = '';
3982          }
3983          return $this;
3984      }
3985      /**
3986       * Enter description here...
3987       *
3988       * @param array|string $callback Expects $node as first param, $index as second
3989       * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
3990       * @param array $arg1 Will ba passed as third and futher args to callback.
3991       * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
3992       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
3993       */
3994      public function each($callback, $param1 = null, $param2 = null, $param3 = null) {
3995          $paramStructure = null;
3996          if (func_num_args() > 1) {
3997              $paramStructure = func_get_args();
3998              $paramStructure = array_slice($paramStructure, 1);
3999          }
4000          foreach($this->elements as $v)
4001              phpQuery::callbackRun($callback, array($v), $paramStructure);
4002          return $this;
4003      }
4004      /**
4005       * Run callback on actual object.
4006       *
4007       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4008       */
4009      public function callback($callback, $param1 = null, $param2 = null, $param3 = null) {
4010          $params = func_get_args();
4011          $params[0] = $this;
4012          phpQuery::callbackRun($callback, $params);
4013          return $this;
4014      }
4015      /**
4016       * Enter description here...
4017       *
4018       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4019       * @todo add $scope and $args as in each() ???
4020       */
4021      public function map($callback, $param1 = null, $param2 = null, $param3 = null) {
4022  //      $stack = array();
4023  ////        foreach($this->newInstance() as $node) {
4024  //      foreach($this->newInstance() as $node) {
4025  //          $result = call_user_func($callback, $node);
4026  //          if ($result)
4027  //              $stack[] = $result;
4028  //      }
4029          $params = func_get_args();
4030          array_unshift($params, $this->elements);
4031          return $this->newInstance(
4032              call_user_func_array(array('phpQuery', 'map'), $params)
4033  //          phpQuery::map($this->elements, $callback)
4034          );
4035      }
4036      /**
4037       * Enter description here...
4038       *
4039       * @param <type> $key
4040       * @param <type> $value
4041       */
4042      public function data($key, $value = null) {
4043          if (! isset($value)) {
4044              // TODO? implement specific jQuery behavior od returning parent values
4045              // is child which we look up doesn't exist
4046              return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
4047          } else {
4048              foreach($this as $node)
4049                  phpQuery::data($node, $key, $value, $this->getDocumentID());
4050              return $this;
4051          }
4052      }
4053      /**
4054       * Enter description here...
4055       *
4056       * @param <type> $key
4057       */
4058      public function removeData($key) {
4059          foreach($this as $node)
4060              phpQuery::removeData($node, $key, $this->getDocumentID());
4061          return $this;
4062      }
4063      // INTERFACE IMPLEMENTATIONS
4064  
4065      // ITERATOR INTERFACE
4066      /**
4067     * @access private
4068       */
4069      public function rewind(){
4070          $this->debug('iterating foreach');
4071  //      phpQuery::selectDocument($this->getDocumentID());
4072          $this->elementsBackup = $this->elements;
4073          $this->elementsInterator = $this->elements;
4074          $this->valid = isset( $this->elements[0] )
4075              ? 1 : 0;
4076  //      $this->elements = $this->valid
4077  //          ? array($this->elements[0])
4078  //          : array();
4079          $this->current = 0;
4080      }
4081      /**
4082     * @access private
4083       */
4084      public function current(){
4085          return $this->elementsInterator[ $this->current ];
4086      }
4087      /**
4088     * @access private
4089       */
4090      public function key(){
4091          return $this->current;
4092      }
4093      /**
4094       * Double-function method.
4095       *
4096       * First: main iterator interface method.
4097       * Second: Returning next sibling, alias for _next().
4098       *
4099       * Proper functionality is choosed automagicaly.
4100       *
4101       * @see phpQueryObject::_next()
4102       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4103       */
4104      public function next($cssSelector = null){
4105  //      if ($cssSelector || $this->valid)
4106  //          return $this->_next($cssSelector);
4107          $this->valid = isset( $this->elementsInterator[ $this->current+1 ] )
4108              ? true
4109              : false;
4110          if (! $this->valid && $this->elementsInterator) {
4111              $this->elementsInterator = null;
4112          } else if ($this->valid) {
4113              $this->current++;
4114          } else {
4115              return $this->_next($cssSelector);
4116          }
4117      }
4118      /**
4119     * @access private
4120       */
4121      public function valid(){
4122          return $this->valid;
4123      }
4124      // ITERATOR INTERFACE END
4125      // ARRAYACCESS INTERFACE
4126      /**
4127     * @access private
4128       */
4129      public function offsetExists($offset) {
4130          return $this->find($offset)->size() > 0;
4131      }
4132      /**
4133     * @access private
4134       */
4135      public function offsetGet($offset) {
4136          return $this->find($offset);
4137      }
4138      /**
4139     * @access private
4140       */
4141      public function offsetSet($offset, $value) {
4142  //      $this->find($offset)->replaceWith($value);
4143          $this->find($offset)->html($value);
4144      }
4145      /**
4146     * @access private
4147       */
4148      public function offsetUnset($offset) {
4149          // empty
4150          throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
4151      }
4152      // ARRAYACCESS INTERFACE END
4153      /**
4154       * Returns node's XPath.
4155       *
4156       * @param unknown_type $oneNode
4157       * @return string
4158       * @TODO use native getNodePath is avaible
4159       * @access private
4160       */
4161      protected function getNodeXpath($oneNode = null, $namespace = null) {
4162          $return = array();
4163          $loop = $oneNode
4164              ? array($oneNode)
4165              : $this->elements;
4166  //      if ($namespace)
4167  //          $namespace .= ':';
4168          foreach($loop as $node) {
4169              if ($node instanceof DOMDOCUMENT) {
4170                  $return[] = '';
4171                  continue;
4172              }
4173              $xpath = array();
4174              while(! ($node instanceof DOMDOCUMENT)) {
4175                  $i = 1;
4176                  $sibling = $node;
4177                  while($sibling->previousSibling) {
4178                      $sibling = $sibling->previousSibling;
4179                      $isElement = $sibling instanceof DOMELEMENT;
4180                      if ($isElement && $sibling->tagName == $node->tagName)
4181                          $i++;
4182                  }
4183                  $xpath[] = $this->isXML()
4184                      ? "*[local-name()='{$node->tagName}'][{$i}]"
4185                      : "{$node->tagName}[{$i}]";
4186                  $node = $node->parentNode;
4187              }
4188              $xpath = join('/', array_reverse($xpath));
4189              $return[] = '/'.$xpath;
4190          }
4191          return $oneNode
4192              ? $return[0]
4193              : $return;
4194      }
4195      // HELPERS
4196      public function whois($oneNode = null) {
4197          $return = array();
4198          $loop = $oneNode
4199              ? array( $oneNode )
4200              : $this->elements;
4201          foreach($loop as $node) {
4202              if (isset($node->tagName)) {
4203                  $tag = in_array($node->tagName, array('php', 'js'))
4204                      ? strtoupper($node->tagName)
4205                      : $node->tagName;
4206                  $return[] = $tag
4207                      .($node->getAttribute('id')
4208                          ? '#'.$node->getAttribute('id'):'')
4209                      .($node->getAttribute('class')
4210                          ? '.'.join('.', explode(' ', $node->getAttribute('class'))):'')
4211                      .($node->getAttribute('name')
4212                          ? '[name="'.$node->getAttribute('name').'"]':'')
4213                      .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false
4214                          ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'')
4215                      .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false
4216                          ? '[value=PHP]':'')
4217                      .($node->getAttribute('selected')
4218                          ? '[selected]':'')
4219                      .($node->getAttribute('checked')
4220                          ? '[checked]':'')
4221                  ;
4222              } else if ($node instanceof DOMTEXT) {
4223                  if (trim($node->textContent))
4224                      $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15);
4225              } else {
4226  
4227              }
4228          }
4229          return $oneNode && isset($return[0])
4230              ? $return[0]
4231              : $return;
4232      }
4233      /**
4234       * Dump htmlOuter and preserve chain. Usefull for debugging.
4235       *
4236       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4237       *
4238       */
4239      public function dump() {
4240          print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4241          $debug = phpQuery::$debug;
4242          phpQuery::$debug = false;
4243  //      print __FILE__.':'.__LINE__."\n";
4244          var_dump($this->htmlOuter());
4245          return $this;
4246      }
4247      public function dumpWhois() {
4248          print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4249          $debug = phpQuery::$debug;
4250          phpQuery::$debug = false;
4251  //      print __FILE__.':'.__LINE__."\n";
4252          var_dump('whois', $this->whois());
4253          phpQuery::$debug = $debug;
4254          return $this;
4255      }
4256      public function dumpLength() {
4257          print 'DUMP #'.(phpQuery::$dumpCount++).' ';
4258          $debug = phpQuery::$debug;
4259          phpQuery::$debug = false;
4260  //      print __FILE__.':'.__LINE__."\n";
4261          var_dump('length', $this->length());
4262          phpQuery::$debug = $debug;
4263          return $this;
4264      }
4265      public function dumpTree($html = true, $title = true) {
4266          $output = $title
4267              ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : '';
4268          $debug = phpQuery::$debug;
4269          phpQuery::$debug = false;
4270          foreach($this->stack() as $node)
4271              $output .= $this->dumpTreeInternal($node);
4272          phpQuery::$debug = $debug;
4273          print $html
4274              ? nl2br(str_replace(' ', '&nbsp;', $output))
4275              : $output;
4276          return $this;
4277      }
4278      private function dumpTreeInternal($node, $intend = 0) {
4279          $whois = $this->whois($node);
4280          $return = '';
4281          if ($whois)
4282              $return .= str_repeat(' - ', $intend).$whois."\n";
4283          if (isset($node->childNodes))
4284              foreach($node->childNodes as $chNode)
4285                  $return .= $this->dumpTreeInternal($chNode, $intend+1);
4286          return $return;
4287      }
4288      /**
4289       * Dump htmlOuter and stop script execution. Usefull for debugging.
4290       *
4291       */
4292      public function dumpDie() {
4293          print __FILE__.':'.__LINE__;
4294          var_dump($this->htmlOuter());
4295          die();
4296      }
4297  }
4298  
4299  
4300  // -- Multibyte Compatibility functions ---------------------------------------
4301  // http://svn.iphonewebdev.com/lace/lib/mb_compat.php
4302  
4303  /**
4304   *  mb_internal_encoding()
4305   *
4306   *  Included for mbstring pseudo-compatability.
4307   */
4308  if (!function_exists('mb_internal_encoding'))
4309  {
4310      function mb_internal_encoding($enc) {return true; }
4311  }
4312  
4313  /**
4314   *  mb_regex_encoding()
4315   *
4316   *  Included for mbstring pseudo-compatability.
4317   */
4318  if (!function_exists('mb_regex_encoding'))
4319  {
4320      function mb_regex_encoding($enc) {return true; }
4321  }
4322  
4323  /**
4324   *  mb_strlen()
4325   *
4326   *  Included for mbstring pseudo-compatability.
4327   */
4328  if (!function_exists('mb_strlen'))
4329  {
4330      function mb_strlen($str)
4331      {
4332          return strlen($str);
4333      }
4334  }
4335  
4336  /**
4337   *  mb_strpos()
4338   *
4339   *  Included for mbstring pseudo-compatability.
4340   */
4341  if (!function_exists('mb_strpos'))
4342  {
4343      function mb_strpos($haystack, $needle, $offset=0)
4344      {
4345          return strpos($haystack, $needle, $offset);
4346      }
4347  }
4348  /**
4349   *  mb_stripos()
4350   *
4351   *  Included for mbstring pseudo-compatability.
4352   */
4353  if (!function_exists('mb_stripos'))
4354  {
4355      function mb_stripos($haystack, $needle, $offset=0)
4356      {
4357          return stripos($haystack, $needle, $offset);
4358      }
4359  }
4360  
4361  /**
4362   *  mb_substr()
4363   *
4364   *  Included for mbstring pseudo-compatability.
4365   */
4366  if (!function_exists('mb_substr'))
4367  {
4368      function mb_substr($str, $start, $length=0)
4369      {
4370          return substr($str, $start, $length);
4371      }
4372  }
4373  
4374  /**
4375   *  mb_substr_count()
4376   *
4377   *  Included for mbstring pseudo-compatability.
4378   */
4379  if (!function_exists('mb_substr_count'))
4380  {
4381      function mb_substr_count($haystack, $needle)
4382      {
4383          return substr_count($haystack, $needle);
4384      }
4385  }
4386  
4387  
4388  /**
4389   * Static namespace for phpQuery functions.
4390   *
4391   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
4392   * @package phpQuery
4393   */
4394  abstract class phpQuery {
4395      /**
4396       * XXX: Workaround for mbstring problems
4397       *
4398       * @var bool
4399       */
4400      public static $mbstringSupport = true;
4401      public static $debug = false;
4402      public static $documents = array();
4403      public static $defaultDocumentID = null;
4404  //  public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
4405      /**
4406       * Applies only to HTML.
4407       *
4408       * @var unknown_type
4409       */
4410      public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4411  "http://www.w3.org/TR/html4/loose.dtd">';
4412      public static $defaultCharset = 'UTF-8';
4413      /**
4414       * Static namespace for plugins.
4415       *
4416       * @var object
4417       */
4418      public static $plugins = array();
4419      /**
4420       * List of loaded plugins.
4421       *
4422       * @var unknown_type
4423       */
4424      public static $pluginsLoaded = array();
4425      public static $pluginsMethods = array();
4426      public static $pluginsStaticMethods = array();
4427      public static $extendMethods = array();
4428      /**
4429       * @TODO implement
4430       */
4431      public static $extendStaticMethods = array();
4432      /**
4433       * Hosts allowed for AJAX connections.
4434       * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
4435       *
4436       * @var array
4437       */
4438      public static $ajaxAllowedHosts = array(
4439          '.'
4440      );
4441      /**
4442       * AJAX settings.
4443       *
4444       * @var array
4445       * XXX should it be static or not ?
4446       */
4447      public static $ajaxSettings = array(
4448          'url' => '',//TODO
4449          'global' => true,
4450          'type' => "GET",
4451          'timeout' => null,
4452          'contentType' => "application/x-www-form-urlencoded",
4453          'processData' => true,
4454  //      'async' => true,
4455          'data' => null,
4456          'username' => null,
4457          'password' => null,
4458          'accepts' => array(
4459              'xml' => "application/xml, text/xml",
4460              'html' => "text/html",
4461              'script' => "text/javascript, application/javascript",
4462              'json' => "application/json, text/javascript",
4463              'text' => "text/plain",
4464              '_default' => "*/*"
4465          )
4466      );
4467      public static $lastModified = null;
4468      public static $active = 0;
4469      public static $dumpCount = 0;
4470      /**
4471       * Multi-purpose function.
4472       * Use pq() as shortcut.
4473       *
4474       * In below examples, $pq is any result of pq(); function.
4475       *
4476       * 1. Import markup into existing document (without any attaching):
4477       * - Import into selected document:
4478       *   pq('<div/>')               // DOESNT accept text nodes at beginning of input string !
4479       * - Import into document with ID from $pq->getDocumentID():
4480       *   pq('<div/>', $pq->getDocumentID())
4481       * - Import into same document as DOMNode belongs to:
4482       *   pq('<div/>', DOMNode)
4483       * - Import into document from phpQuery object:
4484       *   pq('<div/>', $pq)
4485       *
4486       * 2. Run query:
4487       * - Run query on last selected document:
4488       *   pq('div.myClass')
4489       * - Run query on document with ID from $pq->getDocumentID():
4490       *   pq('div.myClass', $pq->getDocumentID())
4491       * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
4492       *   pq('div.myClass', DOMNode)
4493       * - Run query on document from phpQuery object
4494       *   and use object's stack as root node(s) for query:
4495       *   pq('div.myClass', $pq)
4496       *
4497       * @param string|DOMNode|DOMNodeList|array  $arg1   HTML markup, CSS Selector, DOMNode or array of DOMNodes
4498       * @param string|phpQueryObject|DOMNode $context    DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
4499       *
4500       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
4501     * phpQuery object or false in case of error.
4502       */
4503      public static function pq($arg1, $context = null) {
4504          if ($arg1 instanceof DOMNODE && ! isset($context)) {
4505              foreach(phpQuery::$documents as $documentWrapper) {
4506                  $compare = $arg1 instanceof DOMDocument
4507                      ? $arg1 : $arg1->ownerDocument;
4508                  if ($documentWrapper->document->isSameNode($compare))
4509                      $context = $documentWrapper->id;
4510              }
4511          }
4512          if (! $context) {
4513              $domId = self::$defaultDocumentID;
4514              if (! $domId)
4515                  throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
4516  //      } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4517          } else if (is_object($context) && $context instanceof phpQueryObject)
4518              $domId = $context->getDocumentID();
4519          else if ($context instanceof DOMDOCUMENT) {
4520              $domId = self::getDocumentID($context);
4521              if (! $domId) {
4522                  //throw new Exception('Orphaned DOMDocument');
4523                  $domId = self::newDocument($context)->getDocumentID();
4524              }
4525          } else if ($context instanceof DOMNODE) {
4526              $domId = self::getDocumentID($context);
4527              if (! $domId) {
4528                  throw new Exception('Orphaned DOMNode');
4529  //              $domId = self::newDocument($context->ownerDocument);
4530              }
4531          } else
4532              $domId = $context;
4533          if ($arg1 instanceof phpQueryObject) {
4534  //      if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
4535              /**
4536               * Return $arg1 or import $arg1 stack if document differs:
4537               * pq(pq('<div/>'))
4538               */
4539              if ($arg1->getDocumentID() == $domId)
4540                  return $arg1;
4541              $class = get_class($arg1);
4542              // support inheritance by passing old object to overloaded constructor
4543              $phpQuery = $class != 'phpQuery'
4544                  ? new $class($arg1, $domId)
4545                  : new phpQueryObject($domId);
4546              $phpQuery->elements = array();
4547              foreach($arg1->elements as $node)
4548                  $phpQuery->elements[] = $phpQuery->document->importNode($node, true);
4549              return $phpQuery;
4550          } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
4551              /*
4552               * Wrap DOM nodes with phpQuery object, import into document when needed:
4553               * pq(array($domNode1, $domNode2))
4554               */
4555              $phpQuery = new phpQueryObject($domId);
4556              if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1))
4557                  $arg1 = array($arg1);
4558              $phpQuery->elements = array();
4559              foreach($arg1 as $node) {
4560                  $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
4561                      && ! $node->ownerDocument->isSameNode($phpQuery->document);
4562                  $phpQuery->elements[] = $sameDocument
4563                      ? $phpQuery->document->importNode($node, true)
4564                      : $node;
4565              }
4566              return $phpQuery;
4567          } else if (self::isMarkup($arg1)) {
4568              /**
4569               * Import HTML:
4570               * pq('<div/>')
4571               */
4572              $phpQuery = new phpQueryObject($domId);
4573              return $phpQuery->newInstance(
4574                  $phpQuery->documentWrapper->import($arg1)
4575              );
4576          } else {
4577              /**
4578               * Run CSS query:
4579               * pq('div.myClass')
4580               */
4581              $phpQuery = new phpQueryObject($domId);
4582  //          if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
4583              if ($context && $context instanceof phpQueryObject)
4584                  $phpQuery->elements = $context->elements;
4585              else if ($context && $context instanceof DOMNODELIST) {
4586                  $phpQuery->elements = array();
4587                  foreach($context as $node)
4588                      $phpQuery->elements[] = $node;
4589              } else if ($context && $context instanceof DOMNODE)
4590                  $phpQuery->elements = array($context);
4591              return $phpQuery->find($arg1);
4592          }
4593      }
4594      /**
4595       * Sets default document to $id. Document has to be loaded prior
4596       * to using this method.
4597       * $id can be retrived via getDocumentID() or getDocumentIDRef().
4598       *
4599       * @param unknown_type $id
4600       */
4601      public static function selectDocument($id) {
4602          $id = self::getDocumentID($id);
4603          self::debug("Selecting document '$id' as default one");
4604          self::$defaultDocumentID = self::getDocumentID($id);
4605      }
4606      /**
4607       * Returns document with id $id or last used as phpQueryObject.
4608       * $id can be retrived via getDocumentID() or getDocumentIDRef().
4609       * Chainable.
4610       *
4611       * @see phpQuery::selectDocument()
4612       * @param unknown_type $id
4613       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4614       */
4615      public static function getDocument($id = null) {
4616          if ($id)
4617              phpQuery::selectDocument($id);
4618          else
4619              $id = phpQuery::$defaultDocumentID;
4620          return new phpQueryObject($id);
4621      }
4622      /**
4623       * Creates new document from markup.
4624       * Chainable.
4625       *
4626       * @deprecated Use \DOMWrap\Document instead
4627       * @param unknown_type $markup
4628       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4629       */
4630      public static function newDocument($markup = null, $contentType = null) {
4631          if (! $markup)
4632              $markup = '';
4633          $documentID = phpQuery::createDocumentWrapper($markup, $contentType);
4634          return new phpQueryObject($documentID);
4635      }
4636      /**
4637       * Creates new document from markup.
4638       * Chainable.
4639       *
4640       * @param unknown_type $markup
4641       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4642       */
4643      public static function newDocumentHTML($markup = null, $charset = null) {
4644          $contentType = $charset
4645              ? ";charset=$charset"
4646              : '';
4647          return self::newDocument($markup, "text/html{$contentType}");
4648      }
4649      /**
4650       * Creates new document from markup.
4651       * Chainable.
4652       *
4653       * @param unknown_type $markup
4654       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4655       */
4656      public static function newDocumentXML($markup = null, $charset = null) {
4657          $contentType = $charset
4658              ? ";charset=$charset"
4659              : '';
4660          return self::newDocument($markup, "text/xml{$contentType}");
4661      }
4662      /**
4663       * Creates new document from markup.
4664       * Chainable.
4665       *
4666       * @param unknown_type $markup
4667       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4668       */
4669      public static function newDocumentXHTML($markup = null, $charset = null) {
4670          $contentType = $charset
4671              ? ";charset=$charset"
4672              : '';
4673          return self::newDocument($markup, "application/xhtml+xml{$contentType}");
4674      }
4675      /**
4676       * Creates new document from markup.
4677       * Chainable.
4678       *
4679       * @param unknown_type $markup
4680       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4681       */
4682      public static function newDocumentPHP($markup = null, $contentType = "text/html") {
4683          // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
4684          $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
4685          return self::newDocument($markup, $contentType);
4686      }
4687      public static function phpToMarkup($php, $charset = 'utf-8') {
4688          $regexes = array(
4689              '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s',
4690              '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s',
4691          );
4692          foreach($regexes as $regex)
4693              while (preg_match($regex, $php, $matches)) {
4694                  $php = preg_replace_callback(
4695                      $regex,
4696  //                  create_function('$m, $charset = "'.$charset.'"',
4697  //                      'return $m[1].$m[2]
4698  //                          .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4699  //                          .$m[5].$m[2];'
4700  //                  ),
4701                      array('phpQuery', '_phpToMarkupCallback'),
4702                      $php
4703                  );
4704              }
4705          $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
4706  //preg_match_all($regex, $php, $matches);
4707  //var_dump($matches);
4708          $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
4709          return $php;
4710      }
4711      public static function _phpToMarkupCallback($php, $charset = 'utf-8') {
4712          return $m[1].$m[2]
4713              .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
4714              .$m[5].$m[2];
4715      }
4716      public static function _markupToPHPCallback($m) {
4717          return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">";
4718      }
4719      /**
4720       * Converts document markup containing PHP code generated by phpQuery::php()
4721       * into valid (executable) PHP code syntax.
4722       *
4723       * @param string|phpQueryObject $content
4724       * @return string PHP code.
4725       */
4726      public static function markupToPHP($content) {
4727          if ($content instanceof phpQueryObject)
4728              $content = $content->markupOuter();
4729          /* <php>...</php> to <?php...? > */
4730          $content = preg_replace_callback(
4731              '@<php>\s*<!--(.*?)-->\s*</php>@s',
4732  //          create_function('$m',
4733  //              'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
4734  //          ),
4735              array('phpQuery', '_markupToPHPCallback'),
4736              $content
4737          );
4738          /* <node attr='< ?php ? >'> extra space added to save highlighters */
4739          $regexes = array(
4740              '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
4741              '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
4742          );
4743          foreach($regexes as $regex)
4744              while (preg_match($regex, $content))
4745                  $content = preg_replace_callback(
4746                      $regex,
4747                      function ($m) {
4748                          return $m[1] . $m[2] . $m[3] . '<?php '
4749                              . str_replace(
4750                                  array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
4751                                  array(" ", ">", "   ", "\n", "  ", "{", "$", "}", '"', "[", "]"),
4752                                  htmlspecialchars_decode($m[4])
4753                              )
4754                              . " ?>" . $m[5] . $m[2];
4755                      },
4756                      $content
4757                  );
4758          return $content;
4759      }
4760      /**
4761       * Creates new document from file $file.
4762       * Chainable.
4763       *
4764       * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
4765       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4766       */
4767      public static function newDocumentFile($file, $contentType = null) {
4768          $documentID = self::createDocumentWrapper(
4769              file_get_contents($file), $contentType
4770          );
4771          return new phpQueryObject($documentID);
4772      }
4773      /**
4774       * Creates new document from markup.
4775       * Chainable.
4776       *
4777       * @param unknown_type $markup
4778       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4779       */
4780      public static function newDocumentFileHTML($file, $charset = null) {
4781          $contentType = $charset
4782              ? ";charset=$charset"
4783              : '';
4784          return self::newDocumentFile($file, "text/html{$contentType}");
4785      }
4786      /**
4787       * Creates new document from markup.
4788       * Chainable.
4789       *
4790       * @param unknown_type $markup
4791       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4792       */
4793      public static function newDocumentFileXML($file, $charset = null) {
4794          $contentType = $charset
4795              ? ";charset=$charset"
4796              : '';
4797          return self::newDocumentFile($file, "text/xml{$contentType}");
4798      }
4799      /**
4800       * Creates new document from markup.
4801       * Chainable.
4802       *
4803       * @param unknown_type $markup
4804       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4805       */
4806      public static function newDocumentFileXHTML($file, $charset = null) {
4807          $contentType = $charset
4808              ? ";charset=$charset"
4809              : '';
4810          return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
4811      }
4812      /**
4813       * Creates new document from markup.
4814       * Chainable.
4815       *
4816       * @param unknown_type $markup
4817       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4818       */
4819      public static function newDocumentFilePHP($file, $contentType = null) {
4820          return self::newDocumentPHP(file_get_contents($file), $contentType);
4821      }
4822      /**
4823       * Reuses existing DOMDocument object.
4824       * Chainable.
4825       *
4826       * @param $document DOMDocument
4827       * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
4828       * @TODO support DOMDocument
4829       */
4830      public static function loadDocument($document) {
4831          // TODO
4832          die('TODO loadDocument');
4833      }
4834      /**
4835       * Enter description here...
4836       *
4837       * @param unknown_type $html
4838       * @param unknown_type $domId
4839       * @return unknown New DOM ID
4840       * @todo support PHP tags in input
4841       * @todo support passing DOMDocument object from self::loadDocument
4842       */
4843      protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) {
4844          if (function_exists('domxml_open_mem'))
4845              throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
4846  //      $id = $documentID
4847  //          ? $documentID
4848  //          : md5(microtime());
4849          $document = null;
4850          if ($html instanceof DOMDOCUMENT) {
4851              if (self::getDocumentID($html)) {
4852                  // document already exists in phpQuery::$documents, make a copy
4853                  $document = clone $html;
4854              } else {
4855                  // new document, add it to phpQuery::$documents
4856                  $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4857              }
4858          } else {
4859              $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
4860          }
4861  //      $wrapper->id = $id;
4862          // bind document
4863          phpQuery::$documents[$wrapper->id] = $wrapper;
4864          // remember last loaded document
4865          phpQuery::selectDocument($wrapper->id);
4866          return $wrapper->id;
4867      }
4868      /**
4869       * Extend class namespace.
4870       *
4871       * @param string|array $target
4872       * @param array $source
4873       * @TODO support string $source
4874       * @return unknown_type
4875       */
4876      public static function extend($target, $source) {
4877          switch($target) {
4878              case 'phpQueryObject':
4879                  $targetRef = &self::$extendMethods;
4880                  $targetRef2 = &self::$pluginsMethods;
4881                  break;
4882              case 'phpQuery':
4883                  $targetRef = &self::$extendStaticMethods;
4884                  $targetRef2 = &self::$pluginsStaticMethods;
4885                  break;
4886              default:
4887                  throw new Exception("Unsupported \$target type");
4888          }
4889          if (is_string($source))
4890              $source = array($source => $source);
4891          foreach($source as $method => $callback) {
4892              if (isset($targetRef[$method])) {
4893  //              throw new Exception
4894                  self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
4895                  continue;
4896              }
4897              if (isset($targetRef2[$method])) {
4898  //              throw new Exception
4899                  self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
4900                      ." can\'t extend '{$target}'");
4901                  continue;
4902              }
4903              $targetRef[$method] = $callback;
4904          }
4905          return true;
4906      }
4907      /**
4908       * Extend phpQuery with $class from $file.
4909       *
4910       * @param string $class Extending class name. Real class name can be prepended phpQuery_.
4911       * @param string $file Filename to include. Defaults to "{$class}.php".
4912       */
4913      public static function plugin($class, $file = null) {
4914          // TODO $class checked agains phpQuery_$class
4915  //      if (strpos($class, 'phpQuery') === 0)
4916  //          $class = substr($class, 8);
4917          if (in_array($class, self::$pluginsLoaded))
4918              return true;
4919          if (! $file)
4920              $file = $class.'.php';
4921          $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class);
4922          $staticClassExists = class_exists('phpQueryPlugin_'.$class);
4923          if (! $objectClassExists && ! $staticClassExists)
4924              require_once($file);
4925          self::$pluginsLoaded[] = $class;
4926          // static methods
4927          if (class_exists('phpQueryPlugin_'.$class)) {
4928              $realClass = 'phpQueryPlugin_'.$class;
4929              $vars = get_class_vars($realClass);
4930              $loop = isset($vars['phpQueryMethods'])
4931                  && ! is_null($vars['phpQueryMethods'])
4932                  ? $vars['phpQueryMethods']
4933                  : get_class_methods($realClass);
4934              foreach($loop as $method) {
4935                  if ($method == '__initialize')
4936                      continue;
4937                  if (! is_callable(array($realClass, $method)))
4938                      continue;
4939                  if (isset(self::$pluginsStaticMethods[$method])) {
4940                      throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'");
4941                      return;
4942                  }
4943                  self::$pluginsStaticMethods[$method] = $class;
4944              }
4945              if (method_exists($realClass, '__initialize'))
4946                  call_user_func_array(array($realClass, '__initialize'), array());
4947          }
4948          // object methods
4949          if (class_exists('phpQueryObjectPlugin_'.$class)) {
4950              $realClass = 'phpQueryObjectPlugin_'.$class;
4951              $vars = get_class_vars($realClass);
4952              $loop = isset($vars['phpQueryMethods'])
4953                  && ! is_null($vars['phpQueryMethods'])
4954                  ? $vars['phpQueryMethods']
4955                  : get_class_methods($realClass);
4956              foreach($loop as $method) {
4957                  if (! is_callable(array($realClass, $method)))
4958                      continue;
4959                  if (isset(self::$pluginsMethods[$method])) {
4960                      throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'");
4961                      continue;
4962                  }
4963                  self::$pluginsMethods[$method] = $class;
4964              }
4965          }
4966          return true;
4967      }
4968      /**
4969       * Unloades all or specified document from memory.
4970       *
4971       * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
4972       */
4973      public static function unloadDocuments($id = null) {
4974          if (isset($id)) {
4975              if ($id = self::getDocumentID($id))
4976                  unset(phpQuery::$documents[$id]);
4977          } else {
4978              foreach(phpQuery::$documents as $k => $v) {
4979                  unset(phpQuery::$documents[$k]);
4980              }
4981          }
4982      }
4983      /**
4984       * Parses phpQuery object or HTML result against PHP tags and makes them active.
4985       *
4986       * @param phpQuery|string $content
4987       * @deprecated
4988       * @return string
4989       */
4990      public static function unsafePHPTags($content) {
4991          return self::markupToPHP($content);
4992      }
4993      public static function DOMNodeListToArray($DOMNodeList) {
4994          $array = array();
4995          if (! $DOMNodeList)
4996              return $array;
4997          foreach($DOMNodeList as $node)
4998              $array[] = $node;
4999          return $array;
5000      }
5001      /**
5002       * Checks if $input is HTML string, which has to start with '<'.
5003       *
5004       * @deprecated
5005       * @param String $input
5006       * @return Bool
5007       * @todo still used ?
5008       */
5009      public static function isMarkup($input) {
5010          return ! is_array($input) && substr(trim($input), 0, 1) == '<';
5011      }
5012      public static function debug($text) {
5013          if (self::$debug)
5014              print var_dump($text);
5015      }
5016      /**
5017       * Make an AJAX request.
5018       *
5019       * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
5020       * Additional options are:
5021       * 'document' - document for global events, @see phpQuery::getDocumentID()
5022       * 'referer' - implemented
5023       * 'requested_with' - TODO; not implemented (X-Requested-With)
5024       * @return Zend_Http_Client
5025       * @link http://docs.jquery.com/Ajax/jQuery.ajax
5026       *
5027       * @TODO $options['cache']
5028       * @TODO $options['processData']
5029       * @TODO $options['xhr']
5030       * @TODO $options['data'] as string
5031       * @TODO XHR interface
5032       */
5033      public static function ajax($options = array(), $xhr = null) {
5034          $options = array_merge(
5035              self::$ajaxSettings, $options
5036          );
5037          $documentID = isset($options['document'])
5038              ? self::getDocumentID($options['document'])
5039              : null;
5040          if ($xhr) {
5041              // reuse existing XHR object, but clean it up
5042              $client = $xhr;
5043  //          $client->setParameterPost(null);
5044  //          $client->setParameterGet(null);
5045              $client->setAuth(false);
5046              $client->setHeaders("If-Modified-Since", null);
5047              $client->setHeaders("Referer", null);
5048              $client->resetParameters();
5049          } else {
5050              // create new XHR object
5051              require_once('Zend/Http/Client.php');
5052              $client = new Zend_Http_Client();
5053              $client->setCookieJar();
5054          }
5055          if (isset($options['timeout']))
5056              $client->setConfig(array(
5057                  'timeout'      => $options['timeout'],
5058              ));
5059  //          'maxredirects' => 0,
5060          foreach(self::$ajaxAllowedHosts as $k => $host)
5061              if ($host == '.' && isset($_SERVER['HTTP_HOST']))
5062                  self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
5063          $host = parse_url($options['url'], PHP_URL_HOST);
5064          if (! in_array($host, self::$ajaxAllowedHosts)) {
5065              throw new Exception("Request not permitted, host '$host' not present in "
5066                  ."phpQuery::\$ajaxAllowedHosts");
5067          }
5068          // JSONP
5069          $jsre = "/=\\?(&|$)/";
5070          if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
5071              $jsonpCallbackParam = $options['jsonp']
5072                  ? $options['jsonp'] : 'callback';
5073              if (strtolower($options['type']) == 'get') {
5074                  if (! preg_match($jsre, $options['url'])) {
5075                      $sep = strpos($options['url'], '?')
5076                          ? '&' : '?';
5077                      $options['url'] .= "$sep$jsonpCallbackParam=?";
5078                  }
5079              } else if ($options['data']) {
5080                  $jsonp = false;
5081                  foreach($options['data'] as $n => $v) {
5082                      if ($v == '?')
5083                          $jsonp = true;
5084                  }
5085                  if (! $jsonp) {
5086                      $options['data'][$jsonpCallbackParam] = '?';
5087                  }
5088              }
5089              $options['dataType'] = 'json';
5090          }
5091          if (isset($options['dataType']) && $options['dataType'] == 'json') {
5092              $jsonpCallback = 'json_'.md5(microtime());
5093              $jsonpData = $jsonpUrl = false;
5094              if ($options['data']) {
5095                  foreach($options['data'] as $n => $v) {
5096                      if ($v == '?')
5097                          $jsonpData = $n;
5098                  }
5099              }
5100              if (preg_match($jsre, $options['url']))
5101                  $jsonpUrl = true;
5102              if ($jsonpData !== false || $jsonpUrl) {
5103                  // remember callback name for httpData()
5104                  $options['_jsonp'] = $jsonpCallback;
5105                  if ($jsonpData !== false)
5106                      $options['data'][$jsonpData] = $jsonpCallback;
5107                  if ($jsonpUrl)
5108                      $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
5109              }
5110          }
5111          $client->setUri($options['url']);
5112          $client->setMethod(strtoupper($options['type']));
5113          if (isset($options['referer']) && $options['referer'])
5114              $client->setHeaders('Referer', $options['referer']);
5115          $client->setHeaders(array(
5116  //          'content-type' => $options['contentType'],
5117              'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
5118                   .'/2008122010 Firefox/3.0.5',
5119              // TODO custom charset
5120              'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
5121  //          'Connection' => 'keep-alive',
5122  //          'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
5123              'Accept-Language' => 'en-us,en;q=0.5',
5124          ));
5125          if ($options['username'])
5126              $client->setAuth($options['username'], $options['password']);
5127          if (isset($options['ifModified']) && $options['ifModified'])
5128              $client->setHeaders("If-Modified-Since",
5129                  self::$lastModified
5130                      ? self::$lastModified
5131                      : "Thu, 01 Jan 1970 00:00:00 GMT"
5132              );
5133          $client->setHeaders("Accept",
5134              isset($options['dataType'])
5135              && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ])
5136                  ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*"
5137                  : self::$ajaxSettings['accepts']['_default']
5138          );
5139          // TODO $options['processData']
5140          if ($options['data'] instanceof phpQueryObject) {
5141              $serialized = $options['data']->serializeArray($options['data']);
5142              $options['data'] = array();
5143              foreach($serialized as $r)
5144                  $options['data'][ $r['name'] ] = $r['value'];
5145          }
5146          if (strtolower($options['type']) == 'get') {
5147              $client->setParameterGet($options['data']);
5148          } else if (strtolower($options['type']) == 'post') {
5149              $client->setEncType($options['contentType']);
5150              $client->setParameterPost($options['data']);
5151          }
5152          if (self::$active == 0 && $options['global'])
5153              phpQueryEvents::trigger($documentID, 'ajaxStart');
5154          self::$active++;
5155          // beforeSend callback
5156          if (isset($options['beforeSend']) && $options['beforeSend'])
5157              phpQuery::callbackRun($options['beforeSend'], array($client));
5158          // ajaxSend event
5159          if ($options['global'])
5160              phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
5161          if (phpQuery::$debug) {
5162              self::debug("{$options['type']}: {$options['url']}\n");
5163              self::debug("Options: <pre>".var_export($options, true)."</pre>\n");
5164  //          if ($client->getCookieJar())
5165  //              self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n");
5166          }
5167          // request
5168          $response = $client->request();
5169          if (phpQuery::$debug) {
5170              self::debug('Status: '.$response->getStatus().' / '.$response->getMessage());
5171              self::debug($client->getLastRequest());
5172              self::debug($response->getHeaders());
5173          }
5174          if ($response->isSuccessful()) {
5175              // XXX tempolary
5176              self::$lastModified = $response->getHeader('Last-Modified');
5177              $data = self::httpData($response->getBody(), $options['dataType'], $options);
5178              if (isset($options['success']) && $options['success'])
5179                  phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
5180              if ($options['global'])
5181                  phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
5182          } else {
5183              if (isset($options['error']) && $options['error'])
5184                  phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
5185              if ($options['global'])
5186                  phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options));
5187          }
5188          if (isset($options['complete']) && $options['complete'])
5189              phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
5190          if ($options['global'])
5191              phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
5192          if ($options['global'] && ! --self::$active)
5193              phpQueryEvents::trigger($documentID, 'ajaxStop');
5194          return $client;
5195  //      if (is_null($domId))
5196  //          $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
5197  //      return new phpQueryAjaxResponse($response, $domId);
5198      }
5199      protected static function httpData($data, $type, $options) {
5200          if (isset($options['dataFilter']) && $options['dataFilter'])
5201              $data = self::callbackRun($options['dataFilter'], array($data, $type));
5202          if (is_string($data)) {
5203              if ($type == "json") {
5204                  if (isset($options['_jsonp']) && $options['_jsonp']) {
5205                      $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
5206                  }
5207                  $data = self::parseJSON($data);
5208              }
5209          }
5210          return $data;
5211      }
5212      /**
5213       * Enter description here...
5214       *
5215       * @param array|phpQuery $data
5216       *
5217       */
5218      public static function param($data) {
5219          return http_build_query($data, null, '&');
5220      }
5221      public static function get($url, $data = null, $callback = null, $type = null) {
5222          if (!is_array($data)) {
5223              $callback = $data;
5224              $data = null;
5225          }
5226          // TODO some array_values on this shit
5227          return phpQuery::ajax(array(
5228              'type' => 'GET',
5229              'url' => $url,
5230              'data' => $data,
5231              'success' => $callback,
5232              'dataType' => $type,
5233          ));
5234      }
5235      public static function post($url, $data = null, $callback = null, $type = null) {
5236          if (!is_array($data)) {
5237              $callback = $data;
5238              $data = null;
5239          }
5240          return phpQuery::ajax(array(
5241              'type' => 'POST',
5242              'url' => $url,
5243              'data' => $data,
5244              'success' => $callback,
5245              'dataType' => $type,
5246          ));
5247      }
5248      public static function getJSON($url, $data = null, $callback = null) {
5249          if (!is_array($data)) {
5250              $callback = $data;
5251              $data = null;
5252          }
5253          // TODO some array_values on this shit
5254          return phpQuery::ajax(array(
5255              'type' => 'GET',
5256              'url' => $url,
5257              'data' => $data,
5258              'success' => $callback,
5259              'dataType' => 'json',
5260          ));
5261      }
5262      public static function ajaxSetup($options) {
5263          self::$ajaxSettings = array_merge(
5264              self::$ajaxSettings,
5265              $options
5266          );
5267      }
5268      public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) {
5269          $loop = is_array($host1)
5270              ? $host1
5271              : func_get_args();
5272          foreach($loop as $host) {
5273              if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) {
5274                  phpQuery::$ajaxAllowedHosts[] = $host;
5275              }
5276          }
5277      }
5278      public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) {
5279          $loop = is_array($url1)
5280              ? $url1
5281              : func_get_args();
5282          foreach($loop as $url)
5283              phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
5284      }
5285      /**
5286       * Returns JSON representation of $data.
5287       *
5288       * @static
5289       * @param mixed $data
5290       * @return string
5291       */
5292      public static function toJSON($data) {
5293          if (function_exists('json_encode'))
5294              return json_encode($data);
5295          require_once('Zend/Json/Encoder.php');
5296          return Zend_Json_Encoder::encode($data);
5297      }
5298      /**
5299       * Parses JSON into proper PHP type.
5300       *
5301       * @static
5302       * @param string $json
5303       * @return mixed
5304       */
5305      public static function parseJSON($json) {
5306          if (function_exists('json_decode')) {
5307              $return = json_decode(trim($json), true);
5308              // json_decode and UTF8 issues
5309              if (isset($return))
5310                  return $return;
5311          }
5312          require_once('Zend/Json/Decoder.php');
5313          return Zend_Json_Decoder::decode($json);
5314      }
5315      /**
5316       * Returns source's document ID.
5317       *
5318       * @param $source DOMNode|phpQueryObject
5319       * @return string
5320       */
5321      public static function getDocumentID($source) {
5322          if ($source instanceof DOMDOCUMENT) {
5323              foreach(phpQuery::$documents as $id => $document) {
5324                  if ($source->isSameNode($document->document))
5325                      return $id;
5326              }
5327          } else if ($source instanceof DOMNODE) {
5328              foreach(phpQuery::$documents as $id => $document) {
5329                  if ($source->ownerDocument->isSameNode($document->document))
5330                      return $id;
5331              }
5332          } else if ($source instanceof phpQueryObject)
5333              return $source->getDocumentID();
5334          else if (is_string($source) && isset(phpQuery::$documents[$source]))
5335              return $source;
5336      }
5337      /**
5338       * Get DOMDocument object related to $source.
5339       * Returns null if such document doesn't exist.
5340       *
5341       * @param $source DOMNode|phpQueryObject|string
5342       * @return string
5343       */
5344      public static function getDOMDocument($source) {
5345          if ($source instanceof DOMDOCUMENT)
5346              return $source;
5347          $source = self::getDocumentID($source);
5348          return $source
5349              ? self::$documents[$id]['document']
5350              : null;
5351      }
5352  
5353      // UTILITIES
5354      // http://docs.jquery.com/Utilities
5355  
5356      /**
5357       *
5358       * @return unknown_type
5359       * @link http://docs.jquery.com/Utilities/jQuery.makeArray
5360       */
5361      public static function makeArray($obj) {
5362          $array = array();
5363          if (is_object($object) && $object instanceof DOMNODELIST) {
5364              foreach($object as $value)
5365                  $array[] = $value;
5366          } else if (is_object($object) && ! ($object instanceof Iterator)) {
5367              foreach(get_object_vars($object) as $name => $value)
5368                  $array[0][$name] = $value;
5369          } else {
5370              foreach($object as $name => $value)
5371                  $array[0][$name] = $value;
5372          }
5373          return $array;
5374      }
5375      public static function inArray($value, $array) {
5376          return in_array($value, $array);
5377      }
5378      /**
5379       *
5380       * @param $object
5381       * @param $callback
5382       * @return unknown_type
5383       * @link http://docs.jquery.com/Utilities/jQuery.each
5384       */
5385      public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) {
5386          $paramStructure = null;
5387          if (func_num_args() > 2) {
5388              $paramStructure = func_get_args();
5389              $paramStructure = array_slice($paramStructure, 2);
5390          }
5391          if (is_object($object) && ! ($object instanceof Iterator)) {
5392              foreach(get_object_vars($object) as $name => $value)
5393                  phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5394          } else {
5395              foreach($object as $name => $value)
5396                  phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
5397          }
5398      }
5399      /**
5400       *
5401       * @link http://docs.jquery.com/Utilities/jQuery.map
5402       */
5403      public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) {
5404          $result = array();
5405          $paramStructure = null;
5406          if (func_num_args() > 2) {
5407              $paramStructure = func_get_args();
5408              $paramStructure = array_slice($paramStructure, 2);
5409          }
5410          foreach($array as $v) {
5411              $vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
5412  //          $callbackArgs = $args;
5413  //          foreach($args as $i => $arg) {
5414  //              $callbackArgs[$i] = $arg instanceof CallbackParam
5415  //                  ? $v
5416  //                  : $arg;
5417  //          }
5418  //          $vv = call_user_func_array($callback, $callbackArgs);
5419              if (is_array($vv))  {
5420                  foreach($vv as $vvv)
5421                      $result[] = $vvv;
5422              } else if ($vv !== null) {
5423                  $result[] = $vv;
5424              }
5425          }
5426          return $result;
5427      }
5428      /**
5429       *
5430       * @param $callback Callback
5431       * @param $params
5432       * @param $paramStructure
5433       * @return unknown_type
5434       */
5435      public static function callbackRun($callback, $params = array(), $paramStructure = null) {
5436          if (! $callback)
5437              return;
5438          if ($callback instanceof CallbackParameterToReference) {
5439              // TODO support ParamStructure to select which $param push to reference
5440              if (isset($params[0]))
5441                  $callback->callback = $params[0];
5442              return true;
5443          }
5444          if ($callback instanceof Callback) {
5445              $paramStructure = $callback->params;
5446              $callback = $callback->callback;
5447          }
5448          if (! $paramStructure)
5449              return call_user_func_array($callback, $params);
5450          $p = 0;
5451          foreach($paramStructure as $i => $v) {
5452              $paramStructure[$i] = $v instanceof CallbackParam
5453                  ? $params[$p++]
5454                  : $v;
5455          }
5456          return call_user_func_array($callback, $paramStructure);
5457      }
5458      /**
5459       * Merge 2 phpQuery objects.
5460       * @param array $one
5461       * @param array $two
5462       * @protected
5463       * @todo node lists, phpQueryObject
5464       */
5465      public static function merge($one, $two) {
5466          $elements = $one->elements;
5467          foreach($two->elements as $node) {
5468              $exists = false;
5469              foreach($elements as $node2) {
5470                  if ($node2->isSameNode($node))
5471                      $exists = true;
5472              }
5473              if (! $exists)
5474                  $elements[] = $node;
5475          }
5476          return $elements;
5477  //      $one = $one->newInstance();
5478  //      $one->elements = $elements;
5479  //      return $one;
5480      }
5481      /**
5482       *
5483       * @param $array
5484       * @param $callback
5485       * @param $invert
5486       * @return unknown_type
5487       * @link http://docs.jquery.com/Utilities/jQuery.grep
5488       */
5489      public static function grep($array, $callback, $invert = false) {
5490          $result = array();
5491          foreach($array as $k => $v) {
5492              $r = call_user_func_array($callback, array($v, $k));
5493              if ($r === !(bool)$invert)
5494                  $result[] = $v;
5495          }
5496          return $result;
5497      }
5498      public static function unique($array) {
5499          return array_unique($array);
5500      }
5501      /**
5502       *
5503       * @param $function
5504       * @return unknown_type
5505       * @TODO there are problems with non-static methods, second parameter pass it
5506       *  but doesnt verify is method is really callable
5507       */
5508      public static function isFunction($function) {
5509          return is_callable($function);
5510      }
5511      public static function trim($str) {
5512          return trim($str);
5513      }
5514      /* PLUGINS NAMESPACE */
5515      /**
5516       *
5517       * @param $url
5518       * @param $callback
5519       * @param $param1
5520       * @param $param2
5521       * @param $param3
5522       * @return phpQueryObject
5523       */
5524      public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) {
5525          if (self::plugin('WebBrowser')) {
5526              $params = func_get_args();
5527              return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
5528          } else {
5529              self::debug('WebBrowser plugin not available...');
5530          }
5531      }
5532      /**
5533       *
5534       * @param $url
5535       * @param $data
5536       * @param $callback
5537       * @param $param1
5538       * @param $param2
5539       * @param $param3
5540       * @return phpQueryObject
5541       */
5542      public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) {
5543          if (self::plugin('WebBrowser')) {
5544              $params = func_get_args();
5545              return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
5546          } else {
5547              self::debug('WebBrowser plugin not available...');
5548          }
5549      }
5550      /**
5551       *
5552       * @param $ajaxSettings
5553       * @param $callback
5554       * @param $param1
5555       * @param $param2
5556       * @param $param3
5557       * @return phpQueryObject
5558       */
5559      public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) {
5560          if (self::plugin('WebBrowser')) {
5561              $params = func_get_args();
5562              return self::callbackRun(array(self::$plugins, 'browser'), $params);
5563          } else {
5564              self::debug('WebBrowser plugin not available...');
5565          }
5566      }
5567      /**
5568       *
5569       * @param $code
5570       * @return string
5571       */
5572      public static function php($code) {
5573          return self::code('php', $code);
5574      }
5575      /**
5576       *
5577       * @param $type
5578       * @param $code
5579       * @return string
5580       */
5581      public static function code($type, $code) {
5582          return "<$type><!-- ".trim($code)." --></$type>";
5583      }
5584  
5585      public static function __callStatic($method, $params) {
5586          return call_user_func_array(
5587              array(phpQuery::$plugins, $method),
5588              $params
5589          );
5590      }
5591      protected static function dataSetupNode($node, $documentID) {
5592          // search are return if alredy exists
5593          foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
5594              if ($node->isSameNode($dataNode))
5595                  return $dataNode;
5596          }
5597          // if doesn't, add it
5598          phpQuery::$documents[$documentID]->dataNodes[] = $node;
5599          return $node;
5600      }
5601      protected static function dataRemoveNode($node, $documentID) {
5602          // search are return if alredy exists
5603          foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
5604              if ($node->isSameNode($dataNode)) {
5605                  unset(self::$documents[$documentID]->dataNodes[$k]);
5606                  unset(self::$documents[$documentID]->data[ $dataNode->dataID ]);
5607              }
5608          }
5609      }
5610      public static function data($node, $name, $data, $documentID = null) {
5611          if (! $documentID)
5612              // TODO check if this works
5613              $documentID = self::getDocumentID($node);
5614          $document = phpQuery::$documents[$documentID];
5615          $node = self::dataSetupNode($node, $documentID);
5616          if (! isset($node->dataID))
5617              $node->dataID = ++phpQuery::$documents[$documentID]->uuid;
5618          $id = $node->dataID;
5619          if (! isset($document->data[$id]))
5620              $document->data[$id] = array();
5621          if (! is_null($data))
5622              $document->data[$id][$name] = $data;
5623          if ($name) {
5624              if (isset($document->data[$id][$name]))
5625                  return $document->data[$id][$name];
5626          } else
5627              return $id;
5628      }
5629      public static function removeData($node, $name, $documentID) {
5630          if (! $documentID)
5631              // TODO check if this works
5632              $documentID = self::getDocumentID($node);
5633          $document = phpQuery::$documents[$documentID];
5634          $node = self::dataSetupNode($node, $documentID);
5635          $id = $node->dataID;
5636          if ($name) {
5637              if (isset($document->data[$id][$name]))
5638                  unset($document->data[$id][$name]);
5639              $name = null;
5640              foreach($document->data[$id] as $name)
5641                  break;
5642              if (! $name)
5643                  self::removeData($node, $name, $documentID);
5644          } else {
5645              self::dataRemoveNode($node, $documentID);
5646          }
5647      }
5648  }
5649  /**
5650   * Plugins static namespace class.
5651   *
5652   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5653   * @package phpQuery
5654   * @todo move plugin methods here (as statics)
5655   */
5656  class phpQueryPlugins {
5657      public function __call($method, $args) {
5658          if (isset(phpQuery::$extendStaticMethods[$method])) {
5659              $return = call_user_func_array(
5660                  phpQuery::$extendStaticMethods[$method],
5661                  $args
5662              );
5663          } else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
5664              $class = phpQuery::$pluginsStaticMethods[$method];
5665              $realClass = "phpQueryPlugin_$class";
5666              $return = call_user_func_array(
5667                  array($realClass, $method),
5668                  $args
5669              );
5670              return isset($return)
5671                  ? $return
5672                  : $this;
5673          } else
5674              throw new Exception("Method '{$method}' doesnt exist");
5675      }
5676  }
5677  /**
5678   * Shortcut to phpQuery::pq($arg1, $context)
5679   * Chainable.
5680   *
5681   * @see phpQuery::pq()
5682   * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
5683   * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
5684   * @package phpQuery
5685   */
5686  function pq($arg1, $context = null) {
5687      $args = func_get_args();
5688      return call_user_func_array(
5689          array('phpQuery', 'pq'),
5690          $args
5691      );
5692  }
5693  // add plugins dir and Zend framework to include path
5694  set_include_path(
5695      get_include_path()
5696          .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/'
5697          .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/'
5698  );
5699  // why ? no __call nor __get for statics in php...
5700  // XXX __callStatic will be available in PHP 5.3
5701  phpQuery::$plugins = new phpQueryPlugins();
5702  // include bootstrap file (personal library config)
5703  if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php'))
5704      require_once dirname(__FILE__).'/phpQuery/bootstrap.php';