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