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