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