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( $