Advertisement
seriy-coder

phpQuery

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