[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * phpQuery is a server-side, chainable, CSS3 selector driven 4 * Document Object Model (DOM) API based on jQuery JavaScript Library. 5 * 6 * @version 0.9.5 7 * @link http://code.google.com/p/phpquery/ 8 * @link http://phpquery-library.blogspot.com/ 9 * @link http://jquery.com/ 10 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 11 * @license http://www.opensource.org/licenses/mit-license.php MIT License 12 * @package phpQuery 13 * @deprecated 2022-10-19 14 */ 15 16 // class names for instanceof 17 // TODO move them as class constants into phpQuery 18 define('DOMDOCUMENT', 'DOMDocument'); 19 define('DOMELEMENT', 'DOMElement'); 20 define('DOMNODELIST', 'DOMNodeList'); 21 define('DOMNODE', 'DOMNode'); 22 23 /** 24 * DOMEvent class. 25 * 26 * Based on 27 * @link http://developer.mozilla.org/En/DOM:event 28 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 29 * @package phpQuery 30 * @todo implement ArrayAccess ? 31 */ 32 class DOMEvent { 33 /** 34 * Returns a boolean indicating whether the event bubbles up through the DOM or not. 35 * 36 * @var unknown_type 37 */ 38 public $bubbles = true; 39 /** 40 * Returns a boolean indicating whether the event is cancelable. 41 * 42 * @var unknown_type 43 */ 44 public $cancelable = true; 45 /** 46 * Returns a reference to the currently registered target for the event. 47 * 48 * @var unknown_type 49 */ 50 public $currentTarget; 51 /** 52 * Returns detail about the event, depending on the type of event. 53 * 54 * @var unknown_type 55 * @link http://developer.mozilla.org/en/DOM/event.detail 56 */ 57 public $detail; // ??? 58 /** 59 * Used to indicate which phase of the event flow is currently being evaluated. 60 * 61 * NOT IMPLEMENTED 62 * 63 * @var unknown_type 64 * @link http://developer.mozilla.org/en/DOM/event.eventPhase 65 */ 66 public $eventPhase; // ??? 67 /** 68 * The explicit original target of the event (Mozilla-specific). 69 * 70 * NOT IMPLEMENTED 71 * 72 * @var unknown_type 73 */ 74 public $explicitOriginalTarget; // moz only 75 /** 76 * The original target of the event, before any retargetings (Mozilla-specific). 77 * 78 * NOT IMPLEMENTED 79 * 80 * @var unknown_type 81 */ 82 public $originalTarget; // moz only 83 /** 84 * Identifies a secondary target for the event. 85 * 86 * @var unknown_type 87 */ 88 public $relatedTarget; 89 /** 90 * Returns a reference to the target to which the event was originally dispatched. 91 * 92 * @var unknown_type 93 */ 94 public $target; 95 /** 96 * Returns the time that the event was created. 97 * 98 * @var unknown_type 99 */ 100 public $timeStamp; 101 /** 102 * Returns the name of the event (case-insensitive). 103 */ 104 public $type; 105 public $runDefault = true; 106 public $data = null; 107 public function __construct($data) { 108 foreach($data as $k => $v) { 109 $this->$k = $v; 110 } 111 if (! $this->timeStamp) 112 $this->timeStamp = time(); 113 } 114 /** 115 * Cancels the event (if it is cancelable). 116 * 117 */ 118 public function preventDefault() { 119 $this->runDefault = false; 120 } 121 /** 122 * Stops the propagation of events further along in the DOM. 123 * 124 */ 125 public function stopPropagation() { 126 $this->bubbles = false; 127 } 128 } 129 130 131 /** 132 * DOMDocumentWrapper class simplifies work with DOMDocument. 133 * 134 * Know bug: 135 * - in XHTML fragments, <br /> changes to <br clear="none" /> 136 * 137 * @todo check XML catalogs compatibility 138 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 139 * @package phpQuery 140 */ 141 class DOMDocumentWrapper { 142 /** 143 * @var DOMDocument 144 */ 145 public $document; 146 public $id; 147 /** 148 * @todo Rewrite as method and quess if null. 149 * @var unknown_type 150 */ 151 public $contentType = ''; 152 public $xpath; 153 public $uuid = 0; 154 public $data = array(); 155 public $dataNodes = array(); 156 public $events = array(); 157 public $eventsNodes = array(); 158 public $eventsGlobal = array(); 159 /** 160 * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 161 * @var unknown_type 162 */ 163 public $frames = array(); 164 /** 165 * Document root, by default equals to document itself. 166 * Used by documentFragments. 167 * 168 * @var DOMNode 169 */ 170 public $root; 171 public $isDocumentFragment; 172 public $isXML = false; 173 public $isXHTML = false; 174 public $isHTML = false; 175 public $charset; 176 public function __construct($markup = null, $contentType = null, $newDocumentID = null) { 177 if (isset($markup)) 178 $this->load($markup, $contentType, $newDocumentID); 179 $this->id = $newDocumentID 180 ? $newDocumentID 181 : md5(microtime()); 182 } 183 public function load($markup, $contentType = null, $newDocumentID = null) { 184 // phpQuery::$documents[$id] = $this; 185 $this->contentType = strtolower($contentType); 186 if ($markup instanceof DOMDOCUMENT) { 187 $this->document = $markup; 188 $this->root = $this->document; 189 $this->charset = $this->document->encoding; 190 // TODO isDocumentFragment 191 } else { 192 $loaded = $this->loadMarkup($markup); 193 } 194 if ($loaded) { 195 // $this->document->formatOutput = true; 196 $this->document->preserveWhiteSpace = true; 197 $this->xpath = new DOMXPath($this->document); 198 $this->afterMarkupLoad(); 199 return true; 200 // remember last loaded document 201 // return phpQuery::selectDocument($id); 202 } 203 return false; 204 } 205 protected function afterMarkupLoad() { 206 if ($this->isXHTML) { 207 $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); 208 } 209 } 210 protected function loadMarkup($markup) { 211 $loaded = false; 212 if ($this->contentType) { 213 self::debug("Load markup for content type {$this->contentType}"); 214 // content determined by contentType 215 list($contentType, $charset) = $this->contentTypeToArray($this->contentType); 216 switch($contentType) { 217 case 'text/html': 218 phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); 219 $loaded = $this->loadMarkupHTML($markup, $charset); 220 break; 221 case 'text/xml': 222 case 'application/xhtml+xml': 223 phpQuery::debug("Loading XML, content type '{$this->contentType}'"); 224 $loaded = $this->loadMarkupXML($markup, $charset); 225 break; 226 default: 227 // for feeds or anything that sometimes doesn't use text/xml 228 if (strpos('xml', $this->contentType) !== false) { 229 phpQuery::debug("Loading XML, content type '{$this->contentType}'"); 230 $loaded = $this->loadMarkupXML($markup, $charset); 231 } else 232 phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); 233 } 234 } else { 235 // content type autodetection 236 if ($this->isXML($markup)) { 237 phpQuery::debug("Loading XML, isXML() == true"); 238 $loaded = $this->loadMarkupXML($markup); 239 if (! $loaded && $this->isXHTML) { 240 phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); 241 $loaded = $this->loadMarkupHTML($markup); 242 } 243 } else { 244 phpQuery::debug("Loading HTML, isXML() == false"); 245 $loaded = $this->loadMarkupHTML($markup); 246 } 247 } 248 return $loaded; 249 } 250 protected function loadMarkupReset() { 251 $this->isXML = $this->isXHTML = $this->isHTML = false; 252 } 253 protected function documentCreate($charset, $version = '1.0') { 254 if (! $version) 255 $version = '1.0'; 256 $this->document = new DOMDocument($version, $charset); 257 $this->charset = $this->document->encoding; 258 // $this->document->encoding = $charset; 259 $this->document->formatOutput = true; 260 $this->document->preserveWhiteSpace = true; 261 } 262 protected function loadMarkupHTML($markup, $requestedCharset = null) { 263 if (phpQuery::$debug) 264 phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250)); 265 $this->loadMarkupReset(); 266 $this->isHTML = true; 267 if (!isset($this->isDocumentFragment)) 268 $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); 269 $charset = null; 270 $documentCharset = $this->charsetFromHTML($markup); 271 $addDocumentCharset = false; 272 if ($documentCharset) { 273 $charset = $documentCharset; 274 $markup = $this->charsetFixHTML($markup); 275 } else if ($requestedCharset) { 276 $charset = $requestedCharset; 277 } 278 if (! $charset) 279 $charset = phpQuery::$defaultCharset; 280 // HTTP 1.1 says that the default charset is ISO-8859-1 281 // @see http://www.w3.org/International/O-HTTP-charset 282 if (! $documentCharset) { 283 $documentCharset = 'ISO-8859-1'; 284 $addDocumentCharset = true; 285 } 286 // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' 287 // Worse, some pages can have mixed encodings... we'll try not to worry about that 288 $requestedCharset = strtoupper($requestedCharset); 289 $documentCharset = strtoupper($documentCharset); 290 phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); 291 if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { 292 phpQuery::debug("CHARSET CONVERT"); 293 // Document Encoding Conversion 294 // http://code.google.com/p/phpquery/issues/detail?id=86 295 if (function_exists('mb_detect_encoding')) { 296 $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); 297 $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); 298 if (! $docEncoding) 299 $docEncoding = $documentCharset; // ok trust the document 300 phpQuery::debug("DETECTED '$docEncoding'"); 301 // Detected does not match what document says... 302 if ($docEncoding !== $documentCharset) { 303 // Tricky.. 304 } 305 if ($docEncoding !== $requestedCharset) { 306 phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); 307 $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); 308 $markup = $this->charsetAppendToHTML($markup, $requestedCharset); 309 $charset = $requestedCharset; 310 } 311 } else { 312 phpQuery::debug("TODO: charset conversion without mbstring..."); 313 } 314 } 315 $return = false; 316 if ($this->isDocumentFragment) { 317 phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); 318 $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); 319 } else { 320 if ($addDocumentCharset) { 321 phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); 322 $markup = $this->charsetAppendToHTML($markup, $charset); 323 } 324 phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); 325 $this->documentCreate($charset); 326 $return = phpQuery::$debug === 2 327 ? $this->document->loadHTML($markup) 328 : @$this->document->loadHTML($markup); 329 if ($return) 330 $this->root = $this->document; 331 } 332 if ($return && ! $this->contentType) 333 $this->contentType = 'text/html'; 334 return $return; 335 } 336 protected function loadMarkupXML($markup, $requestedCharset = null) { 337 if (phpQuery::$debug) 338 phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250)); 339 $this->loadMarkupReset(); 340 $this->isXML = true; 341 // check agains XHTML in contentType or markup 342 $isContentTypeXHTML = $this->isXHTML(); 343 $isMarkupXHTML = $this->isXHTML($markup); 344 if ($isContentTypeXHTML || $isMarkupXHTML) { 345 self::debug('Full markup load (XML), XHTML detected'); 346 $this->isXHTML = true; 347 } 348 // determine document fragment 349 if (! isset($this->isDocumentFragment)) 350 $this->isDocumentFragment = $this->isXHTML 351 ? self::isDocumentFragmentXHTML($markup) 352 : self::isDocumentFragmentXML($markup); 353 // this charset will be used 354 $charset = null; 355 // charset from XML declaration @var string 356 $documentCharset = $this->charsetFromXML($markup); 357 if (! $documentCharset) { 358 if ($this->isXHTML) { 359 // this is XHTML, try to get charset from content-type meta header 360 $documentCharset = $this->charsetFromHTML($markup); 361 if ($documentCharset) { 362 phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); 363 $this->charsetAppendToXML($markup, $documentCharset); 364 $charset = $documentCharset; 365 } 366 } 367 if (! $documentCharset) { 368 // if still no document charset... 369 $charset = $requestedCharset; 370 } 371 } else if ($requestedCharset) { 372 $charset = $requestedCharset; 373 } 374 if (! $charset) { 375 $charset = phpQuery::$defaultCharset; 376 } 377 if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { 378 // TODO place for charset conversion 379 // $charset = $requestedCharset; 380 } 381 $return = false; 382 if ($this->isDocumentFragment) { 383 phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); 384 $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); 385 } else { 386 // FIXME ??? 387 if ($isContentTypeXHTML && ! $isMarkupXHTML) 388 if (! $documentCharset) { 389 phpQuery::debug("Full markup load (XML), appending charset '$charset'"); 390 $markup = $this->charsetAppendToXML($markup, $charset); 391 } 392 // see http://php.net/manual/en/book.dom.php#78929 393 // LIBXML_DTDLOAD (>= PHP 5.1) 394 // does XML ctalogues works with LIBXML_NONET 395 // $this->document->resolveExternals = true; 396 // TODO test LIBXML_COMPACT for performance improvement 397 // create document 398 $this->documentCreate($charset); 399 if (phpversion() < 5.1) { 400 $this->document->resolveExternals = true; 401 $return = phpQuery::$debug === 2 402 ? $this->document->loadXML($markup) 403 : @$this->document->loadXML($markup); 404 } else { 405 /** @link http://php.net/manual/en/libxml.constants.php */ 406 $libxmlStatic = phpQuery::$debug === 2 407 ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET 408 : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR; 409 $return = $this->document->loadXML($markup, $libxmlStatic); 410 // if (! $return) 411 // $return = $this->document->loadHTML($markup); 412 } 413 if ($return) 414 $this->root = $this->document; 415 } 416 if ($return) { 417 if (! $this->contentType) { 418 if ($this->isXHTML) 419 $this->contentType = 'application/xhtml+xml'; 420 else 421 $this->contentType = 'text/xml'; 422 } 423 return $return; 424 } else { 425 throw new Exception("Error loading XML markup"); 426 } 427 } 428 protected function isXHTML($markup = null) { 429 if (! isset($markup)) { 430 return strpos($this->contentType, 'xhtml') !== false; 431 } 432 // XXX ok ? 433 return strpos($markup, "<!DOCTYPE html") !== false; 434 // return stripos($doctype, 'xhtml') !== false; 435 // $doctype = isset($dom->doctype) && is_object($dom->doctype) 436 // ? $dom->doctype->publicId 437 // : self::$defaultDoctype; 438 } 439 protected function isXML($markup) { 440 // return strpos($markup, '<?xml') !== false && stripos($markup, 'xhtml') === false; 441 return strpos(substr($markup, 0, 100), '<'.'?xml') !== false; 442 } 443 protected function contentTypeToArray($contentType) { 444 $matches = explode(';', trim(strtolower($contentType))); 445 if (isset($matches[1])) { 446 $matches[1] = explode('=', $matches[1]); 447 // strip 'charset=' 448 $matches[1] = isset($matches[1][1]) && trim($matches[1][1]) 449 ? $matches[1][1] 450 : $matches[1][0]; 451 } else 452 $matches[1] = null; 453 return $matches; 454 } 455 /** 456 * 457 * @param $markup 458 * @return array contentType, charset 459 */ 460 protected function contentTypeFromHTML($markup) { 461 $matches = array(); 462 // find meta tag 463 preg_match('@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', 464 $markup, $matches 465 ); 466 if (! isset($matches[0])) 467 return array(null, null); 468 // get attr 'content' 469 preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); 470 if (! isset($matches[0])) 471 return array(null, null); 472 return $this->contentTypeToArray($matches[2]); 473 } 474 protected function charsetFromHTML($markup) { 475 $contentType = $this->contentTypeFromHTML($markup); 476 return $contentType[1]; 477 } 478 protected function charsetFromXML($markup) { 479 $matches; 480 // find declaration 481 preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', 482 $markup, $matches 483 ); 484 return isset($matches[2]) 485 ? strtolower($matches[2]) 486 : null; 487 } 488 /** 489 * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. 490 * 491 * @link http://code.google.com/p/phpquery/issues/detail?id=80 492 * @param $html 493 */ 494 protected function charsetFixHTML($markup) { 495 $matches = array(); 496 // find meta tag 497 preg_match('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', 498 $markup, $matches, PREG_OFFSET_CAPTURE 499 ); 500 if (! isset($matches[0])) 501 return; 502 $metaContentType = $matches[0][0]; 503 $markup = substr($markup, 0, $matches[0][1]) 504 .substr($markup, $matches[0][1]+strlen($metaContentType)); 505 $headStart = stripos($markup, '<head>'); 506 $markup = substr($markup, 0, $headStart+6).$metaContentType 507 .substr($markup, $headStart+6); 508 return $markup; 509 } 510 protected function charsetAppendToHTML($html, $charset, $xhtml = false) { 511 // remove existing meta[type=content-type] 512 $html = preg_replace('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); 513 $meta = '<meta http-equiv="Content-Type" content="text/html;charset=' 514 .$charset.'" ' 515 .($xhtml ? '/' : '') 516 .'>'; 517 if (strpos($html, '<head') === false) { 518 if (strpos($hltml, '<html') === false) { 519 return $meta.$html; 520 } else { 521 return preg_replace( 522 '@<html(.*?)(?(?<!\?)>)@s', 523 "<html\\1><head>{$meta}</head>", 524 $html 525 ); 526 } 527 } else { 528 return preg_replace( 529 '@<head(.*?)(?(?<!\?)>)@s', 530 '<head\\1>'.$meta, 531 $html 532 ); 533 } 534 } 535 protected function charsetAppendToXML($markup, $charset) { 536 $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>'; 537 return $declaration.$markup; 538 } 539 public static function isDocumentFragmentHTML($markup) { 540 return stripos($markup, '<html') === false && stripos($markup, '<!doctype') === false; 541 } 542 public static function isDocumentFragmentXML($markup) { 543 return stripos($markup, '<'.'?xml') === false; 544 } 545 public static function isDocumentFragmentXHTML($markup) { 546 return self::isDocumentFragmentHTML($markup); 547 } 548 public function importAttr($value) { 549 // TODO 550 } 551 /** 552 * 553 * @param $source 554 * @param $target 555 * @param $sourceCharset 556 * @return array Array of imported nodes. 557 */ 558 public function import($source, $sourceCharset = null) { 559 // TODO charset conversions 560 $return = array(); 561 if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) 562 $source = array($source); 563 // if (is_array($source)) { 564 // foreach($source as $node) { 565 // if (is_string($node)) { 566 // // string markup 567 // $fake = $this->documentFragmentCreate($node, $sourceCharset); 568 // if ($fake === false) 569 // throw new Exception("Error loading documentFragment markup"); 570 // else 571 // $return = array_merge($return, 572 // $this->import($fake->root->childNodes) 573 // ); 574 // } else { 575 // $return[] = $this->document->importNode($node, true); 576 // } 577 // } 578 // return $return; 579 // } else { 580 // // string markup 581 // $fake = $this->documentFragmentCreate($source, $sourceCharset); 582 // if ($fake === false) 583 // throw new Exception("Error loading documentFragment markup"); 584 // else 585 // return $this->import($fake->root->childNodes); 586 // } 587 if (is_array($source) || $source instanceof DOMNODELIST) { 588 // dom nodes 589 self::debug('Importing nodes to document'); 590 foreach($source as $node) 591 $return[] = $this->document->importNode($node, true); 592 } else { 593 // string markup 594 $fake = $this->documentFragmentCreate($source, $sourceCharset); 595 if ($fake === false) 596 throw new Exception("Error loading documentFragment markup"); 597 else 598 return $this->import($fake->root->childNodes); 599 } 600 return $return; 601 } 602 /** 603 * Creates new document fragment. 604 * 605 * @param $source 606 * @return DOMDocumentWrapper 607 */ 608 protected function documentFragmentCreate($source, $charset = null) { 609 $fake = new DOMDocumentWrapper(); 610 $fake->contentType = $this->contentType; 611 $fake->isXML = $this->isXML; 612 $fake->isHTML = $this->isHTML; 613 $fake->isXHTML = $this->isXHTML; 614 $fake->root = $fake->document; 615 if (! $charset) 616 $charset = $this->charset; 617 // $fake->documentCreate($this->charset); 618 if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) 619 $source = array($source); 620 if (is_array($source) || $source instanceof DOMNODELIST) { 621 // dom nodes 622 // load fake document 623 if (! $this->documentFragmentLoadMarkup($fake, $charset)) 624 return false; 625 $nodes = $fake->import($source); 626 foreach($nodes as $node) 627 $fake->root->appendChild($node); 628 } else { 629 // string markup 630 $this->documentFragmentLoadMarkup($fake, $charset, $source); 631 } 632 return $fake; 633 } 634 /** 635 * 636 * @param $document DOMDocumentWrapper 637 * @param $markup 638 * @return $document 639 */ 640 private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) { 641 // TODO error handling 642 // TODO copy doctype 643 // tempolary turn off 644 $fragment->isDocumentFragment = false; 645 if ($fragment->isXML) { 646 if ($fragment->isXHTML) { 647 // add FAKE element to set default namespace 648 $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?>' 649 .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' 650 .'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' 651 .'<fake xmlns="http://www.w3.org/1999/xhtml">'.$markup.'</fake>'); 652 $fragment->root = $fragment->document->firstChild->nextSibling; 653 } else { 654 $fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?><fake>'.$markup.'</fake>'); 655 $fragment->root = $fragment->document->firstChild; 656 } 657 } else { 658 $markup2 = phpQuery::$defaultDoctype.'<html><head><meta http-equiv="Content-Type" content="text/html;charset=' 659 .$charset.'"></head>'; 660 $noBody = strpos($markup, '<body') === false; 661 if ($noBody) 662 $markup2 .= '<body>'; 663 $markup2 .= $markup; 664 if ($noBody) 665 $markup2 .= '</body>'; 666 $markup2 .= '</html>'; 667 $fragment->loadMarkupHTML($markup2); 668 // TODO resolv body tag merging issue 669 $fragment->root = $noBody 670 ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling 671 : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; 672 } 673 if (! $fragment->root) 674 return false; 675 $fragment->isDocumentFragment = true; 676 return true; 677 } 678 protected function documentFragmentToMarkup($fragment) { 679 phpQuery::debug('documentFragmentToMarkup'); 680 $tmp = $fragment->isDocumentFragment; 681 $fragment->isDocumentFragment = false; 682 $markup = $fragment->markup(); 683 if ($fragment->isXML) { 684 $markup = substr($markup, 0, strrpos($markup, '</fake>')); 685 if ($fragment->isXHTML) { 686 $markup = substr($markup, strpos($markup, '<fake')+43); 687 } else { 688 $markup = substr($markup, strpos($markup, '<fake>')+6); 689 } 690 } else { 691 $markup = substr($markup, strpos($markup, '<body>')+6); 692 $markup = substr($markup, 0, strrpos($markup, '</body>')); 693 } 694 $fragment->isDocumentFragment = $tmp; 695 if (phpQuery::$debug) 696 phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150)); 697 return $markup; 698 } 699 /** 700 * Return document markup, starting with optional $nodes as root. 701 * 702 * @param $nodes DOMNode|DOMNodeList 703 * @return string 704 */ 705 public function markup($nodes = null, $innerMarkup = false) { 706 if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) 707 $nodes = null; 708 if (isset($nodes)) { 709 $markup = ''; 710 if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) ) 711 $nodes = array($nodes); 712 if ($this->isDocumentFragment && ! $innerMarkup) 713 foreach($nodes as $i => $node) 714 if ($node->isSameNode($this->root)) { 715 // var_dump($node); 716 $nodes = array_slice($nodes, 0, $i) 717 + phpQuery::DOMNodeListToArray($node->childNodes) 718 + array_slice($nodes, $i+1); 719 } 720 if ($this->isXML && ! $innerMarkup) { 721 self::debug("Getting outerXML with charset '{$this->charset}'"); 722 // we need outerXML, so we can benefit from 723 // $node param support in saveXML() 724 foreach($nodes as $node) 725 $markup .= $this->document->saveXML($node); 726 } else { 727 $loop = array(); 728 if ($innerMarkup) 729 foreach($nodes as $node) { 730 if ($node->childNodes) 731 foreach($node->childNodes as $child) 732 $loop[] = $child; 733 else 734 $loop[] = $node; 735 } 736 else 737 $loop = $nodes; 738 self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment"); 739 $fake = $this->documentFragmentCreate($loop); 740 $markup = $this->documentFragmentToMarkup($fake); 741 } 742 if ($this->isXHTML) { 743 self::debug("Fixing XHTML"); 744 $markup = self::markupFixXHTML($markup); 745 } 746 self::debug("Markup: ".substr($markup, 0, 250)); 747 return $markup; 748 } else { 749 if ($this->isDocumentFragment) { 750 // documentFragment, html only... 751 self::debug("Getting markup, DocumentFragment detected"); 752 // return $this->markup( 753 //// $this->document->getElementsByTagName('body')->item(0) 754 // $this->document->root, true 755 // ); 756 $markup = $this->documentFragmentToMarkup($this); 757 // no need for markupFixXHTML, as it's done thought markup($nodes) method 758 return $markup; 759 } else { 760 self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'"); 761 $markup = $this->isXML 762 ? $this->document->saveXML() 763 : $this->document->saveHTML(); 764 if ($this->isXHTML) { 765 self::debug("Fixing XHTML"); 766 $markup = self::markupFixXHTML($markup); 767 } 768 self::debug("Markup: ".substr($markup, 0, 250)); 769 return $markup; 770 } 771 } 772 } 773 protected static function markupFixXHTML($markup) { 774 $markup = self::expandEmptyTag('script', $markup); 775 $markup = self::expandEmptyTag('select', $markup); 776 $markup = self::expandEmptyTag('textarea', $markup); 777 return $markup; 778 } 779 public static function debug($text) { 780 phpQuery::debug($text); 781 } 782 /** 783 * expandEmptyTag 784 * 785 * @param $tag 786 * @param $xml 787 * @return unknown_type 788 * @author mjaque at ilkebenson dot com 789 * @link http://php.net/manual/en/domdocument.savehtml.php#81256 790 */ 791 public static function expandEmptyTag($tag, $xml){ 792 $indice = 0; 793 while ($indice< strlen($xml)){ 794 $pos = strpos($xml, "<$tag ", $indice); 795 if ($pos){ 796 $posCierre = strpos($xml, ">", $pos); 797 if ($xml[$posCierre-1] == "/"){ 798 $xml = substr_replace($xml, "></$tag>", $posCierre-1, 2); 799 } 800 $indice = $posCierre; 801 } 802 else break; 803 } 804 return $xml; 805 } 806 } 807 808 /** 809 * Event handling class. 810 * 811 * @author Tobiasz Cudnik 812 * @package phpQuery 813 * @static 814 */ 815 abstract class phpQueryEvents { 816 /** 817 * Trigger a type of event on every matched element. 818 * 819 * @param DOMNode|phpQueryObject|string $document 820 * @param unknown_type $type 821 * @param unknown_type $data 822 * 823 * @TODO exclusive events (with !) 824 * @TODO global events (test) 825 * @TODO support more than event in $type (space-separated) 826 */ 827 public static function trigger($document, $type, $data = array(), $node = null) { 828 // trigger: function(type, data, elem, donative, extra) { 829 $documentID = phpQuery::getDocumentID($document); 830 $namespace = null; 831 if (strpos($type, '.') !== false) 832 list($name, $namespace) = explode('.', $type); 833 else 834 $name = $type; 835 if (! $node) { 836 if (self::issetGlobal($documentID, $type)) { 837 $pq = phpQuery::getDocument($documentID); 838 // TODO check add($pq->document) 839 $pq->find('*')->add($pq->document) 840 ->trigger($type, $data); 841 } 842 } else { 843 if (isset($data[0]) && $data[0] instanceof DOMEvent) { 844 $event = $data[0]; 845 $event->relatedTarget = $event->target; 846 $event->target = $node; 847 $data = array_slice($data, 1); 848 } else { 849 $event = new DOMEvent(array( 850 'type' => $type, 851 'target' => $node, 852 'timeStamp' => time(), 853 )); 854 } 855 $i = 0; 856 while($node) { 857 // TODO whois 858 phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on " 859 ."node \n");//.phpQueryObject::whois($node)."\n"); 860 $event->currentTarget = $node; 861 $eventNode = self::getNode($documentID, $node); 862 if (isset($eventNode->eventHandlers)) { 863 foreach($eventNode->eventHandlers as $eventType => $handlers) { 864 $eventNamespace = null; 865 if (strpos($type, '.') !== false) 866 list($eventName, $eventNamespace) = explode('.', $eventType); 867 else 868 $eventName = $eventType; 869 if ($name != $eventName) 870 continue; 871 if ($namespace && $eventNamespace && $namespace != $eventNamespace) 872 continue; 873 foreach($handlers as $handler) { 874 phpQuery::debug("Calling event handler\n"); 875 $event->data = $handler['data'] 876 ? $handler['data'] 877 : null; 878 $params = array_merge(array($event), $data); 879 $return = phpQuery::callbackRun($handler['callback'], $params); 880 if ($return === false) { 881 $event->bubbles = false; 882 } 883 } 884 } 885 } 886 // to bubble or not to bubble... 887 if (! $event->bubbles) 888 break; 889 $node = $node->parentNode; 890 $i++; 891 } 892 } 893 } 894 /** 895 * Binds a handler to one or more events (like click) for each matched element. 896 * Can also bind custom events. 897 * 898 * @param DOMNode|phpQueryObject|string $document 899 * @param unknown_type $type 900 * @param unknown_type $data Optional 901 * @param unknown_type $callback 902 * 903 * @TODO support '!' (exclusive) events 904 * @TODO support more than event in $type (space-separated) 905 * @TODO support binding to global events 906 */ 907 public static function add($document, $node, $type, $data, $callback = null) { 908 phpQuery::debug("Binding '$type' event"); 909 $documentID = phpQuery::getDocumentID($document); 910 // if (is_null($callback) && is_callable($data)) { 911 // $callback = $data; 912 // $data = null; 913 // } 914 $eventNode = self::getNode($documentID, $node); 915 if (! $eventNode) 916 $eventNode = self::setNode($documentID, $node); 917 if (!isset($eventNode->eventHandlers[$type])) 918 $eventNode->eventHandlers[$type] = array(); 919 $eventNode->eventHandlers[$type][] = array( 920 'callback' => $callback, 921 'data' => $data, 922 ); 923 } 924 /** 925 * Enter description here... 926 * 927 * @param DOMNode|phpQueryObject|string $document 928 * @param unknown_type $type 929 * @param unknown_type $callback 930 * 931 * @TODO namespace events 932 * @TODO support more than event in $type (space-separated) 933 */ 934 public static function remove($document, $node, $type = null, $callback = null) { 935 $documentID = phpQuery::getDocumentID($document); 936 $eventNode = self::getNode($documentID, $node); 937 if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { 938 if ($callback) { 939 foreach($eventNode->eventHandlers[$type] as $k => $handler) 940 if ($handler['callback'] == $callback) 941 unset($eventNode->eventHandlers[$type][$k]); 942 } else { 943 unset($eventNode->eventHandlers[$type]); 944 } 945 } 946 } 947 protected static function getNode($documentID, $node) { 948 foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { 949 if ($node->isSameNode($eventNode)) 950 return $eventNode; 951 } 952 } 953 protected static function setNode($documentID, $node) { 954 phpQuery::$documents[$documentID]->eventsNodes[] = $node; 955 return phpQuery::$documents[$documentID]->eventsNodes[ 956 count(phpQuery::$documents[$documentID]->eventsNodes)-1 957 ]; 958 } 959 protected static function issetGlobal($documentID, $type) { 960 return isset(phpQuery::$documents[$documentID]) 961 ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) 962 : false; 963 } 964 } 965 966 967 interface ICallbackNamed { 968 function hasName(); 969 function getName(); 970 } 971 /** 972 * Callback class introduces currying-like pattern. 973 * 974 * Example: 975 * function foo($param1, $param2, $param3) { 976 * var_dump($param1, $param2, $param3); 977 * } 978 * $fooCurried = new Callback('foo', 979 * 'param1 is now statically set', 980 * new CallbackParam, new CallbackParam 981 * ); 982 * phpQuery::callbackRun($fooCurried, 983 * array('param2 value', 'param3 value' 984 * ); 985 * 986 * Callback class is supported in all phpQuery methods which accepts callbacks. 987 * 988 * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures 989 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 990 * 991 * @TODO??? return fake forwarding function created via create_function 992 * @TODO honor paramStructure 993 */ 994 class Callback 995 implements ICallbackNamed { 996 public $callback = null; 997 public $params = null; 998 protected $name; 999 public function __construct($callback, $param1 = null, $param2 = null, 1000 $param3 = null) { 1001 $params = func_get_args(); 1002 $params = array_slice($params, 1); 1003 if ($callback instanceof Callback) { 1004 // TODO implement recurention 1005 } else { 1006 $this->callback = $callback; 1007 $this->params = $params; 1008 } 1009 } 1010 public function getName() { 1011 return 'Callback: '.$this->name; 1012 } 1013 public function hasName() { 1014 return isset($this->name) && $this->name; 1015 } 1016 public function setName($name) { 1017 $this->name = $name; 1018 return $this; 1019 } 1020 // TODO test me 1021 // public function addParams() { 1022 // $params = func_get_args(); 1023 // return new Callback($this->callback, $this->params+$params); 1024 // } 1025 } 1026 1027 /** 1028 * Callback type which on execution returns reference passed during creation. 1029 * 1030 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 1031 */ 1032 class CallbackReturnReference extends Callback 1033 implements ICallbackNamed { 1034 protected $reference; 1035 public function __construct(&$reference, $name = null){ 1036 $this->reference =& $reference; 1037 $this->callback = array($this, 'callback'); 1038 } 1039 public function callback() { 1040 return $this->reference; 1041 } 1042 public function getName() { 1043 return 'Callback: '.$this->name; 1044 } 1045 public function hasName() { 1046 return isset($this->name) && $this->name; 1047 } 1048 } 1049 /** 1050 * Callback type which on execution returns value passed during creation. 1051 * 1052 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 1053 */ 1054 class CallbackReturnValue extends Callback 1055 implements ICallbackNamed { 1056 protected $value; 1057 protected $name; 1058 public function __construct($value, $name = null){ 1059 $this->value =& $value; 1060 $this->name = $name; 1061 $this->callback = array($this, 'callback'); 1062 } 1063 public function callback() { 1064 return $this->value; 1065 } 1066 public function __toString() { 1067 return $this->getName(); 1068 } 1069 public function getName() { 1070 return 'Callback: '.$this->name; 1071 } 1072 public function hasName() { 1073 return isset($this->name) && $this->name; 1074 } 1075 } 1076 /** 1077 * CallbackParameterToReference can be used when we don't really want a callback, 1078 * only parameter passed to it. CallbackParameterToReference takes first 1079 * parameter's value and passes it to reference. 1080 * 1081 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 1082 */ 1083 class CallbackParameterToReference extends Callback { 1084 /** 1085 * @param $reference 1086 * @TODO implement $paramIndex; 1087 * param index choose which callback param will be passed to reference 1088 */ 1089 public function __construct(&$reference){ 1090 $this->callback =& $reference; 1091 } 1092 } 1093 //class CallbackReference extends Callback { 1094 // /** 1095 // * 1096 // * @param $reference 1097 // * @param $paramIndex 1098 // * @todo implement $paramIndex; param index choose which callback param will be passed to reference 1099 // */ 1100 // public function __construct(&$reference, $name = null){ 1101 // $this->callback =& $reference; 1102 // } 1103 //} 1104 class CallbackParam {} 1105 1106 /** 1107 * Class representing phpQuery objects. 1108 * 1109 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 1110 * @package phpQuery 1111 * @method phpQueryObject clone() clone() 1112 * @method phpQueryObject empty() empty() 1113 * @method phpQueryObject next() next($selector = null) 1114 * @method phpQueryObject prev() prev($selector = null) 1115 * @property Int $length 1116 */ 1117 class phpQueryObject 1118 implements Iterator, Countable, ArrayAccess { 1119 public $documentID = null; 1120 /** 1121 * DOMDocument class. 1122 * 1123 * @var DOMDocument 1124 */ 1125 public $document = null; 1126 public $charset = null; 1127 /** 1128 * 1129 * @var DOMDocumentWrapper 1130 */ 1131 public $documentWrapper = null; 1132 /** 1133 * XPath interface. 1134 * 1135 * @var DOMXPath 1136 */ 1137 public $xpath = null; 1138 /** 1139 * Stack of selected elements. 1140 * @TODO refactor to ->nodes 1141 * @var array 1142 */ 1143 public $elements = array(); 1144 /** 1145 * @access private 1146 */ 1147 protected $elementsBackup = array(); 1148 /** 1149 * @access private 1150 */ 1151 protected $previous = null; 1152 /** 1153 * @access private 1154 * @TODO deprecate 1155 */ 1156 protected $root = array(); 1157 /** 1158 * Indicated if doument is just a fragment (no <html> tag). 1159 * 1160 * Every document is realy a full document, so even documentFragments can 1161 * be queried against <html>, but getDocument(id)->htmlOuter() will return 1162 * only contents of <body>. 1163 * 1164 * @var bool 1165 */ 1166 public $documentFragment = true; 1167 /** 1168 * Iterator interface helper 1169 * @access private 1170 */ 1171 protected $elementsInterator = array(); 1172 /** 1173 * Iterator interface helper 1174 * @access private 1175 */ 1176 protected $valid = false; 1177 /** 1178 * Iterator interface helper 1179 * @access private 1180 */ 1181 protected $current = null; 1182 /** 1183 * Enter description here... 1184 * 1185 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1186 */ 1187 public function __construct($documentID) { 1188 dbg_deprecated(\DOMWrap\Document::class); 1189 1190 // if ($documentID instanceof self) 1191 // var_dump($documentID->getDocumentID()); 1192 $id = $documentID instanceof self 1193 ? $documentID->getDocumentID() 1194 : $documentID; 1195 // var_dump($id); 1196 if (! isset(phpQuery::$documents[$id] )) { 1197 // var_dump(phpQuery::$documents); 1198 throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); 1199 } 1200 $this->documentID = $id; 1201 $this->documentWrapper =& phpQuery::$documents[$id]; 1202 $this->document =& $this->documentWrapper->document; 1203 $this->xpath =& $this->documentWrapper->xpath; 1204 $this->charset =& $this->documentWrapper->charset; 1205 $this->documentFragment =& $this->documentWrapper->isDocumentFragment; 1206 // TODO check $this->DOM->documentElement; 1207 // $this->root = $this->document->documentElement; 1208 $this->root =& $this->documentWrapper->root; 1209 // $this->toRoot(); 1210 $this->elements = array($this->root); 1211 } 1212 /** 1213 * 1214 * @access private 1215 * @param $attr 1216 * @return unknown_type 1217 */ 1218 public function __get($attr) { 1219 switch($attr) { 1220 // FIXME doesnt work at all ? 1221 case 'length': 1222 return $this->size(); 1223 break; 1224 default: 1225 return $this->$attr; 1226 } 1227 } 1228 /** 1229 * Saves actual object to $var by reference. 1230 * Useful when need to break chain. 1231 * @param phpQueryObject $var 1232 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1233 */ 1234 public function toReference(&$var) { 1235 return $var = $this; 1236 } 1237 public function documentFragment($state = null) { 1238 if ($state) { 1239 phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; 1240 return $this; 1241 } 1242 return $this->documentFragment; 1243 } 1244 /** 1245 * @access private 1246 * @TODO documentWrapper 1247 */ 1248 protected function isRoot( $node) { 1249 // return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; 1250 return $node instanceof DOMDOCUMENT 1251 || ($node instanceof DOMELEMENT && $node->tagName == 'html') 1252 || $this->root->isSameNode($node); 1253 } 1254 /** 1255 * @access private 1256 */ 1257 protected function stackIsRoot() { 1258 return $this->size() == 1 && $this->isRoot($this->elements[0]); 1259 } 1260 /** 1261 * Enter description here... 1262 * NON JQUERY METHOD 1263 * 1264 * Watch out, it doesn't creates new instance, can be reverted with end(). 1265 * 1266 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1267 */ 1268 public function toRoot() { 1269 $this->elements = array($this->root); 1270 return $this; 1271 // return $this->newInstance(array($this->root)); 1272 } 1273 /** 1274 * Saves object's DocumentID to $var by reference. 1275 * <code> 1276 * $myDocumentId; 1277 * phpQuery::newDocument('<div/>') 1278 * ->getDocumentIDRef($myDocumentId) 1279 * ->find('div')->... 1280 * </code> 1281 * 1282 * @param unknown_type $domId 1283 * @see phpQuery::newDocument 1284 * @see phpQuery::newDocumentFile 1285 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1286 */ 1287 public function getDocumentIDRef(&$documentID) { 1288 $documentID = $this->getDocumentID(); 1289 return $this; 1290 } 1291 /** 1292 * Returns object with stack set to document root. 1293 * 1294 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1295 */ 1296 public function getDocument() { 1297 return phpQuery::getDocument($this->getDocumentID()); 1298 } 1299 /** 1300 * 1301 * @return DOMDocument 1302 */ 1303 public function getDOMDocument() { 1304 return $this->document; 1305 } 1306 /** 1307 * Get object's Document ID. 1308 * 1309 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1310 */ 1311 public function getDocumentID() { 1312 return $this->documentID; 1313 } 1314 /** 1315 * Unloads whole document from memory. 1316 * CAUTION! None further operations will be possible on this document. 1317 * All objects refering to it will be useless. 1318 * 1319 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1320 */ 1321 public function unloadDocument() { 1322 phpQuery::unloadDocuments($this->getDocumentID()); 1323 } 1324 public function isHTML() { 1325 return $this->documentWrapper->isHTML; 1326 } 1327 public function isXHTML() { 1328 return $this->documentWrapper->isXHTML; 1329 } 1330 public function isXML() { 1331 return $this->documentWrapper->isXML; 1332 } 1333 /** 1334 * Enter description here... 1335 * 1336 * @link http://docs.jquery.com/Ajax/serialize 1337 * @return string 1338 */ 1339 public function serialize() { 1340 return phpQuery::param($this->serializeArray()); 1341 } 1342 /** 1343 * Enter description here... 1344 * 1345 * @link http://docs.jquery.com/Ajax/serializeArray 1346 * @return array 1347 */ 1348 public function serializeArray($submit = null) { 1349 $source = $this->filter('form, input, select, textarea') 1350 ->find('input, select, textarea') 1351 ->andSelf() 1352 ->not('form'); 1353 $return = array(); 1354 // $source->dumpDie(); 1355 foreach($source as $input) { 1356 $input = phpQuery::pq($input); 1357 if ($input->is('[disabled]')) 1358 continue; 1359 if (!$input->is('[name]')) 1360 continue; 1361 if ($input->is('[type=checkbox]') && !$input->is('[checked]')) 1362 continue; 1363 // jquery diff 1364 if ($submit && $input->is('[type=submit]')) { 1365 if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit)) 1366 continue; 1367 else if (is_string($submit) && $input->attr('name') != $submit) 1368 continue; 1369 } 1370 $return[] = array( 1371 'name' => $input->attr('name'), 1372 'value' => $input->val(), 1373 ); 1374 } 1375 return $return; 1376 } 1377 /** 1378 * @access private 1379 */ 1380 protected function debug($in) { 1381 if (! phpQuery::$debug ) 1382 return; 1383 print('<pre>'); 1384 print_r($in); 1385 // file debug 1386 // file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND); 1387 // quite handy debug trace 1388 // if ( is_array($in)) 1389 // print_r(array_slice(debug_backtrace(), 3)); 1390 print("</pre>\n"); 1391 } 1392 /** 1393 * @access private 1394 */ 1395 protected function isRegexp($pattern) { 1396 return in_array( 1397 $pattern[ mb_strlen($pattern)-1 ], 1398 array('^','*','$') 1399 ); 1400 } 1401 /** 1402 * Determines if $char is really a char. 1403 * 1404 * @param string $char 1405 * @return bool 1406 * @todo rewrite me to charcode range ! ;) 1407 * @access private 1408 */ 1409 protected function isChar($char) { 1410 return extension_loaded('mbstring') && phpQuery::$mbstringSupport 1411 ? mb_eregi('\w', $char) 1412 : preg_match('@\w@', $char); 1413 } 1414 /** 1415 * @access private 1416 */ 1417 protected function parseSelector($query) { 1418 // clean spaces 1419 // TODO include this inside parsing ? 1420 $query = trim( 1421 preg_replace('@\s+@', ' ', 1422 preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) 1423 ) 1424 ); 1425 $queries = array(array()); 1426 if (! $query) 1427 return $queries; 1428 $return =& $queries[0]; 1429 $specialChars = array('>',' '); 1430 // $specialCharsMapping = array('/' => '>'); 1431 $specialCharsMapping = array(); 1432 $strlen = mb_strlen($query); 1433 $classChars = array('.', '-'); 1434 $pseudoChars = array('-'); 1435 $tagChars = array('*', '|', '-'); 1436 // split multibyte string 1437 // http://code.google.com/p/phpquery/issues/detail?id=76 1438 $_query = array(); 1439 for ($i=0; $i<$strlen; $i++) 1440 $_query[] = mb_substr($query, $i, 1); 1441 $query = $_query; 1442 // it works, but i dont like it... 1443 $i = 0; 1444 while( $i < $strlen) { 1445 $c = $query[$i]; 1446 $tmp = ''; 1447 // TAG 1448 if ($this->isChar($c) || in_array($c, $tagChars)) { 1449 while(isset($query[$i]) 1450 && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) { 1451 $tmp .= $query[$i]; 1452 $i++; 1453 } 1454 $return[] = $tmp; 1455 // IDs 1456 } else if ( $c == '#') { 1457 $i++; 1458 while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { 1459 $tmp .= $query[$i]; 1460 $i++; 1461 } 1462 $return[] = '#'.$tmp; 1463 // SPECIAL CHARS 1464 } else if (in_array($c, $specialChars)) { 1465 $return[] = $c; 1466 $i++; 1467 // MAPPED SPECIAL MULTICHARS 1468 // } else if ( $c.$query[$i+1] == '//') { 1469 // $return[] = ' '; 1470 // $i = $i+2; 1471 // MAPPED SPECIAL CHARS 1472 } else if ( isset($specialCharsMapping[$c])) { 1473 $return[] = $specialCharsMapping[$c]; 1474 $i++; 1475 // COMMA 1476 } else if ( $c == ',') { 1477 $queries[] = array(); 1478 $return =& $queries[ count($queries)-1 ]; 1479 $i++; 1480 while( isset($query[$i]) && $query[$i] == ' ') 1481 $i++; 1482 // CLASSES 1483 } else if ($c == '.') { 1484 while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { 1485 $tmp .= $query[$i]; 1486 $i++; 1487 } 1488 $return[] = $tmp; 1489 // ~ General Sibling Selector 1490 } else if ($c == '~') { 1491 $spaceAllowed = true; 1492 $tmp .= $query[$i++]; 1493 while( isset($query[$i]) 1494 && ($this->isChar($query[$i]) 1495 || in_array($query[$i], $classChars) 1496 || $query[$i] == '*' 1497 || ($query[$i] == ' ' && $spaceAllowed) 1498 )) { 1499 if ($query[$i] != ' ') 1500 $spaceAllowed = false; 1501 $tmp .= $query[$i]; 1502 $i++; 1503 } 1504 $return[] = $tmp; 1505 // + Adjacent sibling selectors 1506 } else if ($c == '+') { 1507 $spaceAllowed = true; 1508 $tmp .= $query[$i++]; 1509 while( isset($query[$i]) 1510 && ($this->isChar($query[$i]) 1511 || in_array($query[$i], $classChars) 1512 || $query[$i] == '*' 1513 || ($spaceAllowed && $query[$i] == ' ') 1514 )) { 1515 if ($query[$i] != ' ') 1516 $spaceAllowed = false; 1517 $tmp .= $query[$i]; 1518 $i++; 1519 } 1520 $return[] = $tmp; 1521 // ATTRS 1522 } else if ($c == '[') { 1523 $stack = 1; 1524 $tmp .= $c; 1525 while( isset($query[++$i])) { 1526 $tmp .= $query[$i]; 1527 if ( $query[$i] == '[') { 1528 $stack++; 1529 } else if ( $query[$i] == ']') { 1530 $stack--; 1531 if (! $stack ) 1532 break; 1533 } 1534 } 1535 $return[] = $tmp; 1536 $i++; 1537 // PSEUDO CLASSES 1538 } else if ($c == ':') { 1539 $stack = 1; 1540 $tmp .= $query[$i++]; 1541 while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { 1542 $tmp .= $query[$i]; 1543 $i++; 1544 } 1545 // with arguments ? 1546 if ( isset($query[$i]) && $query[$i] == '(') { 1547 $tmp .= $query[$i]; 1548 $stack = 1; 1549 while( isset($query[++$i])) { 1550 $tmp .= $query[$i]; 1551 if ( $query[$i] == '(') { 1552 $stack++; 1553 } else if ( $query[$i] == ')') { 1554 $stack--; 1555 if (! $stack ) 1556 break; 1557 } 1558 } 1559 $return[] = $tmp; 1560 $i++; 1561 } else { 1562 $return[] = $tmp; 1563 } 1564 } else { 1565 $i++; 1566 } 1567 } 1568 foreach($queries as $k => $q) { 1569 if (isset($q[0])) { 1570 if (isset($q[0][0]) && $q[0][0] == ':') 1571 array_unshift($queries[$k], '*'); 1572 if ($q[0] != '>') 1573 array_unshift($queries[$k], ' '); 1574 } 1575 } 1576 return $queries; 1577 } 1578 /** 1579 * Return matched DOM nodes. 1580 * 1581 * @param int $index 1582 * @return array|DOMElement Single DOMElement or array of DOMElement. 1583 */ 1584 public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { 1585 $return = isset($index) 1586 ? (isset($this->elements[$index]) ? $this->elements[$index] : null) 1587 : $this->elements; 1588 // pass thou callbacks 1589 $args = func_get_args(); 1590 $args = array_slice($args, 1); 1591 foreach($args as $callback) { 1592 if (is_array($return)) 1593 foreach($return as $k => $v) 1594 $return[$k] = phpQuery::callbackRun($callback, array($v)); 1595 else 1596 $return = phpQuery::callbackRun($callback, array($return)); 1597 } 1598 return $return; 1599 } 1600 /** 1601 * Return matched DOM nodes. 1602 * jQuery difference. 1603 * 1604 * @param int $index 1605 * @return array|string Returns string if $index != null 1606 * @todo implement callbacks 1607 * @todo return only arrays ? 1608 * @todo maybe other name... 1609 */ 1610 public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { 1611 if ($index) 1612 $return = $this->eq($index)->text(); 1613 else { 1614 $return = array(); 1615 for($i = 0; $i < $this->size(); $i++) { 1616 $return[] = $this->eq($i)->text(); 1617 } 1618 } 1619 // pass thou callbacks 1620 $args = func_get_args(); 1621 $args = array_slice($args, 1); 1622 foreach($args as $callback) { 1623 $return = phpQuery::callbackRun($callback, array($return)); 1624 } 1625 return $return; 1626 } 1627 /** 1628 * Return matched DOM nodes. 1629 * jQuery difference. 1630 * 1631 * @param int $index 1632 * @return array|string Returns string if $index != null 1633 * @todo implement callbacks 1634 * @todo return only arrays ? 1635 * @todo maybe other name... 1636 */ 1637 public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { 1638 if ($index) 1639 $return = $this->eq($index)->text(); 1640 else { 1641 $return = array(); 1642 for($i = 0; $i < $this->size(); $i++) { 1643 $return[] = $this->eq($i)->text(); 1644 } 1645 // pass thou callbacks 1646 $args = func_get_args(); 1647 $args = array_slice($args, 1); 1648 } 1649 foreach($args as $callback) { 1650 if (is_array($return)) 1651 foreach($return as $k => $v) 1652 $return[$k] = phpQuery::callbackRun($callback, array($v)); 1653 else 1654 $return = phpQuery::callbackRun($callback, array($return)); 1655 } 1656 return $return; 1657 } 1658 /** 1659 * Returns new instance of actual class. 1660 * 1661 * @param array $newStack Optional. Will replace old stack with new and move old one to history.c 1662 */ 1663 public function newInstance($newStack = null) { 1664 $class = get_class($this); 1665 // support inheritance by passing old object to overloaded constructor 1666 $new = $class != 'phpQuery' 1667 ? new $class($this, $this->getDocumentID()) 1668 : new phpQueryObject($this->getDocumentID()); 1669 $new->previous = $this; 1670 if (is_null($newStack)) { 1671 $new->elements = $this->elements; 1672 if ($this->elementsBackup) 1673 $this->elements = $this->elementsBackup; 1674 } else if (is_string($newStack)) { 1675 $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack(); 1676 } else { 1677 $new->elements = $newStack; 1678 } 1679 return $new; 1680 } 1681 /** 1682 * Enter description here... 1683 * 1684 * In the future, when PHP will support XLS 2.0, then we would do that this way: 1685 * contains(tokenize(@class, '\s'), "something") 1686 * @param unknown_type $class 1687 * @param unknown_type $node 1688 * @return boolean 1689 * @access private 1690 */ 1691 protected function matchClasses($class, $node) { 1692 // multi-class 1693 if ( mb_strpos($class, '.', 1)) { 1694 $classes = explode('.', substr($class, 1)); 1695 $classesCount = count( $classes ); 1696 $nodeClasses = explode(' ', $node->getAttribute('class') ); 1697 $nodeClassesCount = count( $nodeClasses ); 1698 if ( $classesCount > $nodeClassesCount ) 1699 return false; 1700 $diff = count( 1701 array_diff( 1702 $classes, 1703 $nodeClasses 1704 ) 1705 ); 1706 if (! $diff ) 1707 return true; 1708 // single-class 1709 } else { 1710 return in_array( 1711 // strip leading dot from class name 1712 substr($class, 1), 1713 // get classes for element as array 1714 explode(' ', $node->getAttribute('class') ) 1715 ); 1716 } 1717 } 1718 /** 1719 * @access private 1720 */ 1721 protected function runQuery($XQuery, $selector = null, $compare = null) { 1722 if ($compare && ! method_exists($this, $compare)) 1723 return false; 1724 $stack = array(); 1725 if (! $this->elements) 1726 $this->debug('Stack empty, skipping...'); 1727 // var_dump($this->elements[0]->nodeType); 1728 // element, document 1729 foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) { 1730 $detachAfter = false; 1731 // to work on detached nodes we need temporary place them somewhere 1732 // thats because context xpath queries sucks ;] 1733 $testNode = $stackNode; 1734 while ($testNode) { 1735 if (! $testNode->parentNode && ! $this->isRoot($testNode)) { 1736 $this->root->appendChild($testNode); 1737 $detachAfter = $testNode; 1738 break; 1739 } 1740 $testNode = isset($testNode->parentNode) 1741 ? $testNode->parentNode 1742 : null; 1743 } 1744 // XXX tmp ? 1745 $xpath = $this->documentWrapper->isXHTML 1746 ? $this->getNodeXpath($stackNode, 'html') 1747 : $this->getNodeXpath($stackNode); 1748 // FIXME pseudoclasses-only query, support XML 1749 $query = $XQuery == '//' && $xpath == '/html[1]' 1750 ? '//*' 1751 : $xpath.$XQuery; 1752 $this->debug("XPATH: {$query}"); 1753 // run query, get elements 1754 $nodes = $this->xpath->query($query); 1755 $this->debug("QUERY FETCHED"); 1756 if (! $nodes->length ) 1757 $this->debug('Nothing found'); 1758 $debug = array(); 1759 foreach($nodes as $node) { 1760 $matched = false; 1761 if ( $compare) { 1762 phpQuery::$debug ? 1763 $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") 1764 : null; 1765 $phpQueryDebug = phpQuery::$debug; 1766 phpQuery::$debug = false; 1767 // TODO ??? use phpQuery::callbackRun() 1768 if (call_user_func_array(array($this, $compare), array($selector, $node))) 1769 $matched = true; 1770 phpQuery::$debug = $phpQueryDebug; 1771 } else { 1772 $matched = true; 1773 } 1774 if ( $matched) { 1775 if (phpQuery::$debug) 1776 $debug[] = $this->whois( $node ); 1777 $stack[] = $node; 1778 } 1779 } 1780 if (phpQuery::$debug) { 1781 $this->debug("Matched ".count($debug).": ".implode(', ', $debug)); 1782 } 1783 if ($detachAfter) 1784 $this->root->removeChild($detachAfter); 1785 } 1786 $this->elements = $stack; 1787 } 1788 /** 1789 * Enter description here... 1790 * 1791 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1792 */ 1793 public function find($selectors, $context = null, $noHistory = false) { 1794 if (!$noHistory) 1795 // backup last stack /for end()/ 1796 $this->elementsBackup = $this->elements; 1797 // allow to define context 1798 // TODO combine code below with phpQuery::pq() context guessing code 1799 // as generic function 1800 if ($context) { 1801 if (! is_array($context) && $context instanceof DOMELEMENT) 1802 $this->elements = array($context); 1803 else if (is_array($context)) { 1804 $this->elements = array(); 1805 foreach ($context as $c) 1806 if ($c instanceof DOMELEMENT) 1807 $this->elements[] = $c; 1808 } else if ( $context instanceof self ) 1809 $this->elements = $context->elements; 1810 } 1811 $queries = $this->parseSelector($selectors); 1812 $this->debug(array('FIND', $selectors, $queries)); 1813 $XQuery = ''; 1814 // remember stack state because of multi-queries 1815 $oldStack = $this->elements; 1816 // here we will be keeping found elements 1817 $stack = array(); 1818 foreach($queries as $selector) { 1819 $this->elements = $oldStack; 1820 $delimiterBefore = false; 1821 foreach($selector as $s) { 1822 // TAG 1823 $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport 1824 ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*' 1825 : preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; 1826 if ($isTag) { 1827 if ($this->isXML()) { 1828 // namespace support 1829 if (mb_strpos($s, '|') !== false) { 1830 $ns = $tag = null; 1831 list($ns, $tag) = explode('|', $s); 1832 $XQuery .= "$ns:$tag"; 1833 } else if ($s == '*') { 1834 $XQuery .= "*"; 1835 } else { 1836 $XQuery .= "*[local-name()='$s']"; 1837 } 1838 } else { 1839 $XQuery .= $s; 1840 } 1841 // ID 1842 } else if ($s[0] == '#') { 1843 if ($delimiterBefore) 1844 $XQuery .= '*'; 1845 $XQuery .= "[@id='".substr($s, 1)."']"; 1846 // ATTRIBUTES 1847 } else if ($s[0] == '[') { 1848 if ($delimiterBefore) 1849 $XQuery .= '*'; 1850 // strip side brackets 1851 $attr = trim($s, ']['); 1852 $execute = false; 1853 // attr with specifed value 1854 if (mb_strpos($s, '=')) { 1855 $value = null; 1856 list($attr, $value) = explode('=', $attr); 1857 $value = trim($value, "'\""); 1858 if ($this->isRegexp($attr)) { 1859 // cut regexp character 1860 $attr = substr($attr, 0, -1); 1861 $execute = true; 1862 $XQuery .= "[@{$attr}]"; 1863 } else { 1864 $XQuery .= "[@{$attr}='{$value}']"; 1865 } 1866 // attr without specified value 1867 } else { 1868 $XQuery .= "[@{$attr}]"; 1869 } 1870 if ($execute) { 1871 $this->runQuery($XQuery, $s, 'is'); 1872 $XQuery = ''; 1873 if (! $this->length()) 1874 break; 1875 } 1876 // CLASSES 1877 } else if ($s[0] == '.') { 1878 // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); 1879 // thx wizDom ;) 1880 if ($delimiterBefore) 1881 $XQuery .= '*'; 1882 $XQuery .= '[@class]'; 1883 $this->runQuery($XQuery, $s, 'matchClasses'); 1884 $XQuery = ''; 1885 if (! $this->length() ) 1886 break; 1887 // ~ General Sibling Selector 1888 } else if ($s[0] == '~') { 1889 $this->runQuery($XQuery); 1890 $XQuery = ''; 1891 $this->elements = $this 1892 ->siblings( 1893 substr($s, 1) 1894 )->elements; 1895 if (! $this->length() ) 1896 break; 1897 // + Adjacent sibling selectors 1898 } else if ($s[0] == '+') { 1899 // TODO /following-sibling:: 1900 $this->runQuery($XQuery); 1901 $XQuery = ''; 1902 $subSelector = substr($s, 1); 1903 $subElements = $this->elements; 1904 $this->elements = array(); 1905 foreach($subElements as $node) { 1906 // search first DOMElement sibling 1907 $test = $node->nextSibling; 1908 while($test && ! ($test instanceof DOMELEMENT)) 1909 $test = $test->nextSibling; 1910 if ($test && $this->is($subSelector, $test)) 1911 $this->elements[] = $test; 1912 } 1913 if (! $this->length() ) 1914 break; 1915 // PSEUDO CLASSES 1916 } else if ($s[0] == ':') { 1917 // TODO optimization for :first :last 1918 if ($XQuery) { 1919 $this->runQuery($XQuery); 1920 $XQuery = ''; 1921 } 1922 if (! $this->length()) 1923 break; 1924 $this->pseudoClasses($s); 1925 if (! $this->length()) 1926 break; 1927 // DIRECT DESCENDANDS 1928 } else if ($s == '>') { 1929 $XQuery .= '/'; 1930 $delimiterBefore = 2; 1931 // ALL DESCENDANDS 1932 } else if ($s == ' ') { 1933 $XQuery .= '//'; 1934 $delimiterBefore = 2; 1935 // ERRORS 1936 } else { 1937 phpQuery::debug("Unrecognized token '$s'"); 1938 } 1939 $delimiterBefore = $delimiterBefore === 2; 1940 } 1941 // run query if any 1942 if ($XQuery && $XQuery != '//') { 1943 $this->runQuery($XQuery); 1944 $XQuery = ''; 1945 } 1946 foreach($this->elements as $node) 1947 if (! $this->elementsContainsNode($node, $stack)) 1948 $stack[] = $node; 1949 } 1950 $this->elements = $stack; 1951 return $this->newInstance(); 1952 } 1953 /** 1954 * @todo create API for classes with pseudoselectors 1955 * @access private 1956 */ 1957 protected function pseudoClasses($class) { 1958 // TODO clean args parsing ? 1959 $class = ltrim($class, ':'); 1960 $haveArgs = mb_strpos($class, '('); 1961 if ($haveArgs !== false) { 1962 $args = substr($class, $haveArgs+1, -1); 1963 $class = substr($class, 0, $haveArgs); 1964 } 1965 switch($class) { 1966 case 'even': 1967 case 'odd': 1968 $stack = array(); 1969 foreach($this->elements as $i => $node) { 1970 if ($class == 'even' && ($i%2) == 0) 1971 $stack[] = $node; 1972 else if ( $class == 'odd' && $i % 2 ) 1973 $stack[] = $node; 1974 } 1975 $this->elements = $stack; 1976 break; 1977 case 'eq': 1978 $k = intval($args); 1979 $this->elements = isset( $this->elements[$k] ) 1980 ? array( $this->elements[$k] ) 1981 : array(); 1982 break; 1983 case 'gt': 1984 $this->elements = array_slice($this->elements, $args+1); 1985 break; 1986 case 'lt': 1987 $this->elements = array_slice($this->elements, 0, $args+1); 1988 break; 1989 case 'first': 1990 if (isset($this->elements[0])) 1991 $this->elements = array($this->elements[0]); 1992 break; 1993 case 'last': 1994 if ($this->elements) 1995 $this->elements = array($this->elements[count($this->elements)-1]); 1996 break; 1997 /*case 'parent': 1998 $stack = array(); 1999 foreach($this->elements as $node) { 2000 if ( $node->childNodes->length ) 2001 $stack[] = $node; 2002 } 2003 $this->elements = $stack; 2004 break;*/ 2005 case 'contains': 2006 $text = trim($args, "\"'"); 2007 $stack = array(); 2008 foreach($this->elements as $node) { 2009 if (mb_stripos($node->textContent, $text) === false) 2010 continue; 2011 $stack[] = $node; 2012 } 2013 $this->elements = $stack; 2014 break; 2015 case 'not': 2016 $selector = self::unQuote($args); 2017 $this->elements = $this->not($selector)->stack(); 2018 break; 2019 case 'slice': 2020 // TODO jQuery difference ? 2021 $args = explode(',', 2022 str_replace(', ', ',', trim($args, "\"'")) 2023 ); 2024 $start = $args[0]; 2025 $end = isset($args[1]) 2026 ? $args[1] 2027 : null; 2028 if ($end > 0) 2029 $end = $end-$start; 2030 $this->elements = array_slice($this->elements, $start, $end); 2031 break; 2032 case 'has': 2033 $selector = trim($args, "\"'"); 2034 $stack = array(); 2035 foreach($this->stack(1) as $el) { 2036 if ($this->find($selector, $el, true)->length) 2037 $stack[] = $el; 2038 } 2039 $this->elements = $stack; 2040 break; 2041 case 'submit': 2042 case 'reset': 2043 $this->elements = phpQuery::merge( 2044 $this->map(array($this, 'is'), 2045 "input[type=$class]", new CallbackParam() 2046 ), 2047 $this->map(array($this, 'is'), 2048 "button[type=$class]", new CallbackParam() 2049 ) 2050 ); 2051 break; 2052 // $stack = array(); 2053 // foreach($this->elements as $node) 2054 // if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) 2055 // $stack[] = $el; 2056 // $this->elements = $stack; 2057 case 'input': 2058 $this->elements = $this->map( 2059 array($this, 'is'), 2060 'input', new CallbackParam() 2061 )->elements; 2062 break; 2063 case 'password': 2064 case 'checkbox': 2065 case 'radio': 2066 case 'hidden': 2067 case 'image': 2068 case 'file': 2069 $this->elements = $this->map( 2070 array($this, 'is'), 2071 "input[type=$class]", new CallbackParam() 2072 )->elements; 2073 break; 2074 case 'parent': 2075 $this->elements = $this->map( 2076 function ($node) { 2077 return $node instanceof DOMELEMENT && $node->childNodes->length 2078 ? $node : null; 2079 } 2080 )->elements; 2081 break; 2082 case 'empty': 2083 $this->elements = $this->map( 2084 function ($node) { 2085 return $node instanceof DOMELEMENT && $node->childNodes->length 2086 ? null : $node; 2087 } 2088 )->elements; 2089 break; 2090 case 'disabled': 2091 case 'selected': 2092 case 'checked': 2093 $this->elements = $this->map( 2094 array($this, 'is'), 2095 "[$class]", new CallbackParam() 2096 )->elements; 2097 break; 2098 case 'enabled': 2099 $this->elements = $this->map( 2100 function ($node) { 2101 return pq($node)->not(":disabled") ? $node : null; 2102 } 2103 )->elements; 2104 break; 2105 case 'header': 2106 $this->elements = $this->map( 2107 function ($node) { 2108 $isHeader = isset($node->tagName) && in_array($node->tagName, array( 2109 "h1", "h2", "h3", "h4", "h5", "h6", "h7" 2110 )); 2111 return $isHeader 2112 ? $node 2113 : null; 2114 } 2115 )->elements; 2116 // $this->elements = $this->map( 2117 // create_function('$node', '$node = pq($node); 2118 // return $node->is("h1") 2119 // || $node->is("h2") 2120 // || $node->is("h3") 2121 // || $node->is("h4") 2122 // || $node->is("h5") 2123 // || $node->is("h6") 2124 // || $node->is("h7") 2125 // ? $node 2126 // : null;') 2127 // )->elements; 2128 break; 2129 case 'only-child': 2130 $this->elements = $this->map( 2131 function ($node) { 2132 return pq($node)->siblings()->size() == 0 ? $node : null; 2133 } 2134 )->elements; 2135 break; 2136 case 'first-child': 2137 $this->elements = $this->map( 2138 function ($node) { 2139 return pq($node)->prevAll()->size() == 0 ? $node : null; 2140 } 2141 )->elements; 2142 break; 2143 case 'last-child': 2144 $this->elements = $this->map( 2145 function ($node) { 2146 return pq($node)->nextAll()->size() == 0 ? $node : null; 2147 } 2148 )->elements; 2149 break; 2150 case 'nth-child': 2151 $param = trim($args, "\"'"); 2152 if (! $param) 2153 break; 2154 // nth-child(n+b) to nth-child(1n+b) 2155 if ($param[0] == 'n') 2156 $param = '1'.$param; 2157 // :nth-child(index/even/odd/equation) 2158 if ($param == 'even' || $param == 'odd') 2159 $mapped = $this->map( 2160 function ($node, $param) { 2161 $index = pq($node)->prevAll()->size() + 1; 2162 if ($param == "even" && ($index % 2) == 0) 2163 return $node; 2164 else if ($param == "odd" && $index % 2 == 1) 2165 return $node; 2166 else 2167 return null; 2168 }, new CallbackParam(), $param 2169 ); 2170 else if (mb_strlen($param) > 1 && $param[1] == 'n') 2171 // an+b 2172 $mapped = $this->map( 2173 function ($node, $param) { 2174 $prevs = pq($node)->prevAll()->size(); 2175 $index = 1 + $prevs; 2176 $b = mb_strlen($param) > 3 2177 ? $param[3] 2178 : 0; 2179 $a = $param[0]; 2180 if ($b && $param[2] == "-") 2181 $b = -$b; 2182 if ($a > 0) { 2183 return ($index - $b) % $a == 0 2184 ? $node 2185 : null; 2186 phpQuery::debug($a . "*" . floor($index / $a) . "+$b-1 == " . ($a * floor($index / $a) + $b - 1) . " ?= $prevs"); 2187 return $a * floor($index / $a) + $b - 1 == $prevs 2188 ? $node 2189 : null; 2190 } else if ($a == 0) { 2191 return $index == $b 2192 ? $node 2193 : null; 2194 } else { 2195 // negative value 2196 return $index <= $b 2197 ? $node 2198 : null; 2199 } 2200 // if (! $b) 2201 // return $index%$a == 0 2202 // ? $node 2203 // : null; 2204 // else 2205 // return ($index-$b)%$a == 0 2206 // ? $node 2207 // : null; 2208 }, 2209 new CallbackParam(), $param 2210 ); 2211 else 2212 // index 2213 $mapped = $this->map( 2214 function ($node, $index) { 2215 $prevs = pq($node)->prevAll()->size(); 2216 if ($prevs && $prevs == $index - 1) 2217 return $node; 2218 else if (!$prevs && $index == 1) 2219 return $node; 2220 else 2221 return null; 2222 }, 2223 new CallbackParam(), $param 2224 ); 2225 $this->elements = $mapped->elements; 2226 break; 2227 default: 2228 $this->debug("Unknown pseudoclass '{$class}', skipping..."); 2229 } 2230 } 2231 /** 2232 * @access private 2233 */ 2234 protected function pseudoClassParam($paramsString) { 2235 // TODO; 2236 } 2237 /** 2238 * Enter description here... 2239 * 2240 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2241 */ 2242 public function is($selector, $nodes = null) { 2243 phpQuery::debug(array("Is:", $selector)); 2244 if (! $selector) 2245 return false; 2246 $oldStack = $this->elements; 2247 $returnArray = false; 2248 if ($nodes && is_array($nodes)) { 2249 $this->elements = $nodes; 2250 } else if ($nodes) 2251 $this->elements = array($nodes); 2252 $this->filter($selector, true); 2253 $stack = $this->elements; 2254 $this->elements = $oldStack; 2255 if ($nodes) 2256 return $stack ? $stack : null; 2257 return (bool)count($stack); 2258 } 2259 /** 2260 * Enter description here... 2261 * jQuery difference. 2262 * 2263 * Callback: 2264 * - $index int 2265 * - $node DOMNode 2266 * 2267 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2268 * @link http://docs.jquery.com/Traversing/filter 2269 */ 2270 public function filterCallback($callback, $_skipHistory = false) { 2271 if (! $_skipHistory) { 2272 $this->elementsBackup = $this->elements; 2273 $this->debug("Filtering by callback"); 2274 } 2275 $newStack = array(); 2276 foreach($this->elements as $index => $node) { 2277 $result = phpQuery::callbackRun($callback, array($index, $node)); 2278 if (is_null($result) || (! is_null($result) && $result)) 2279 $newStack[] = $node; 2280 } 2281 $this->elements = $newStack; 2282 return $_skipHistory 2283 ? $this 2284 : $this->newInstance(); 2285 } 2286 /** 2287 * Enter description here... 2288 * 2289 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2290 * @link http://docs.jquery.com/Traversing/filter 2291 */ 2292 public function filter($selectors, $_skipHistory = false) { 2293 if ($selectors instanceof Callback OR $selectors instanceof Closure) 2294 return $this->filterCallback($selectors, $_skipHistory); 2295 if (! $_skipHistory) 2296 $this->elementsBackup = $this->elements; 2297 $notSimpleSelector = array(' ', '>', '~', '+', '/'); 2298 if (! is_array($selectors)) 2299 $selectors = $this->parseSelector($selectors); 2300 if (! $_skipHistory) 2301 $this->debug(array("Filtering:", $selectors)); 2302 $finalStack = array(); 2303 foreach($selectors as $selector) { 2304 $stack = array(); 2305 if (! $selector) 2306 break; 2307 // avoid first space or / 2308 if (in_array($selector[0], $notSimpleSelector)) 2309 $selector = array_slice($selector, 1); 2310 // PER NODE selector chunks 2311 foreach($this->stack() as $node) { 2312 $break = false; 2313 foreach($selector as $s) { 2314 if (!($node instanceof DOMELEMENT)) { 2315 // all besides DOMElement 2316 if ( $s[0] == '[') { 2317 $attr = trim($s, '[]'); 2318 if ( mb_strpos($attr, '=')) { 2319 list( $attr, $val ) = explode('=', $attr); 2320 if ($attr == 'nodeType' && $node->nodeType != $val) 2321 $break = true; 2322 } 2323 } else 2324 $break = true; 2325 } else { 2326 // DOMElement only 2327 // ID 2328 if ( $s[0] == '#') { 2329 if ( $node->getAttribute('id') != substr($s, 1) ) 2330 $break = true; 2331 // CLASSES 2332 } else if ( $s[0] == '.') { 2333 if (! $this->matchClasses( $s, $node ) ) 2334 $break = true; 2335 // ATTRS 2336 } else if ( $s[0] == '[') { 2337 // strip side brackets 2338 $attr = trim($s, '[]'); 2339 if (mb_strpos($attr, '=')) { 2340 list($attr, $val) = explode('=', $attr); 2341 $val = self::unQuote($val); 2342 if ($attr == 'nodeType') { 2343 if ($val != $node->nodeType) 2344 $break = true; 2345 } else if ($this->isRegexp($attr)) { 2346 $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport 2347 ? quotemeta(trim($val, '"\'')) 2348 : preg_quote(trim($val, '"\''), '@'); 2349 // switch last character 2350 switch( substr($attr, -1)) { 2351 // quotemeta used insted of preg_quote 2352 // http://code.google.com/p/phpquery/issues/detail?id=76 2353 case '^': 2354 $pattern = '^'.$val; 2355 break; 2356 case '*': 2357 $pattern = '.*'.$val.'.*'; 2358 break; 2359 case '$': 2360 $pattern = '.*'.$val.'$'; 2361 break; 2362 } 2363 // cut last character 2364 $attr = substr($attr, 0, -1); 2365 $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport 2366 ? mb_ereg_match($pattern, $node->getAttribute($attr)) 2367 : preg_match("@{$pattern}@", $node->getAttribute($attr)); 2368 if (! $isMatch) 2369 $break = true; 2370 } else if ($node->getAttribute($attr) != $val) 2371 $break = true; 2372 } else if (! $node->hasAttribute($attr)) 2373 $break = true; 2374 // PSEUDO CLASSES 2375 } else if ( $s[0] == ':') { 2376 // skip 2377 // TAG 2378 } else if (trim($s)) { 2379 if ($s != '*') { 2380 // TODO namespaces 2381 if (isset($node->tagName)) { 2382 if ($node->tagName != $s) 2383 $break = true; 2384 } else if ($s == 'html' && ! $this->isRoot($node)) 2385 $break = true; 2386 } 2387 // AVOID NON-SIMPLE SELECTORS 2388 } else if (in_array($s, $notSimpleSelector)) { 2389 $break = true; 2390 $this->debug(array('Skipping non simple selector', $selector)); 2391 } 2392 } 2393 if ($break) 2394 break; 2395 } 2396 // if element passed all chunks of selector - add it to new stack 2397 if (! $break ) 2398 $stack[] = $node; 2399 } 2400 $tmpStack = $this->elements; 2401 $this->elements = $stack; 2402 // PER ALL NODES selector chunks 2403 foreach($selector as $s) 2404 // PSEUDO CLASSES 2405 if ($s[0] == ':') 2406 $this->pseudoClasses($s); 2407 foreach($this->elements as $node) 2408 // XXX it should be merged without duplicates 2409 // but jQuery doesnt do that 2410 $finalStack[] = $node; 2411 $this->elements = $tmpStack; 2412 } 2413 $this->elements = $finalStack; 2414 if ($_skipHistory) { 2415 return $this; 2416 } else { 2417 $this->debug("Stack length after filter(): ".count($finalStack)); 2418 return $this->newInstance(); 2419 } 2420 } 2421 /** 2422 * 2423 * @param $value 2424 * @return unknown_type 2425 * @TODO implement in all methods using passed parameters 2426 */ 2427 protected static function unQuote($value) { 2428 return $value[0] == '\'' || $value[0] == '"' 2429 ? substr($value, 1, -1) 2430 : $value; 2431 } 2432 /** 2433 * Enter description here... 2434 * 2435 * @link http://docs.jquery.com/Ajax/load 2436 * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2437 * @todo Support $selector 2438 */ 2439 public function load($url, $data = null, $callback = null) { 2440 if ($data && ! is_array($data)) { 2441 $callback = $data; 2442 $data = null; 2443 } 2444 if (mb_strpos($url, ' ') !== false) { 2445 $matches = null; 2446 if (extension_loaded('mbstring') && phpQuery::$mbstringSupport) 2447 mb_ereg('^([^ ]+) (.*)$', $url, $matches); 2448 else 2449 preg_match('^([^ ]+) (.*)$', $url, $matches); 2450 $url = $matches[1]; 2451 $selector = $matches[2]; 2452 // FIXME this sucks, pass as callback param 2453 $this->_loadSelector = $selector; 2454 } 2455 $ajax = array( 2456 'url' => $url, 2457 'type' => $data ? 'POST' : 'GET', 2458 'data' => $data, 2459 'complete' => $callback, 2460 'success' => array($this, 'loadSuccess') 2461 ); 2462 phpQuery::ajax($ajax); 2463 return $this; 2464 } 2465 /** 2466 * @access private 2467 * @param $html 2468 * @return unknown_type 2469 */ 2470 public function loadSuccess($html) { 2471 if ($this->_loadSelector) { 2472 $html = phpQuery::newDocument($html)->find($this->_loadSelector); 2473 unset($this->_loadSelector); 2474 } 2475 foreach($this->stack(1) as $node) { 2476 phpQuery::pq($node, $this->getDocumentID()) 2477 ->markup($html); 2478 } 2479 } 2480 /** 2481 * Enter description here... 2482 * 2483 * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2484 * @todo 2485 */ 2486 public function css() { 2487 // TODO 2488 return $this; 2489 } 2490 /** 2491 * @todo 2492 * 2493 */ 2494 public function show(){ 2495 // TODO 2496 return $this; 2497 } 2498 /** 2499 * @todo 2500 * 2501 */ 2502 public function hide(){ 2503 // TODO 2504 return $this; 2505 } 2506 /** 2507 * Trigger a type of event on every matched element. 2508 * 2509 * @param unknown_type $type 2510 * @param unknown_type $data 2511 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2512 * @TODO support more than event in $type (space-separated) 2513 */ 2514 public function trigger($type, $data = array()) { 2515 foreach($this->elements as $node) 2516 phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node); 2517 return $this; 2518 } 2519 /** 2520 * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions. 2521 * 2522 * @param unknown_type $type 2523 * @param unknown_type $data 2524 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2525 * @TODO 2526 */ 2527 public function triggerHandler($type, $data = array()) { 2528 // TODO; 2529 } 2530 /** 2531 * Binds a handler to one or more events (like click) for each matched element. 2532 * Can also bind custom events. 2533 * 2534 * @param unknown_type $type 2535 * @param unknown_type $data Optional 2536 * @param unknown_type $callback 2537 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2538 * @TODO support '!' (exclusive) events 2539 * @TODO support more than event in $type (space-separated) 2540 */ 2541 public function bind($type, $data, $callback = null) { 2542 // TODO check if $data is callable, not using is_callable 2543 if (! isset($callback)) { 2544 $callback = $data; 2545 $data = null; 2546 } 2547 foreach($this->elements as $node) 2548 phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback); 2549 return $this; 2550 } 2551 /** 2552 * Enter description here... 2553 * 2554 * @param unknown_type $type 2555 * @param unknown_type $callback 2556 * @return unknown 2557 * @TODO namespace events 2558 * @TODO support more than event in $type (space-separated) 2559 */ 2560 public function unbind($type = null, $callback = null) { 2561 foreach($this->elements as $node) 2562 phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback); 2563 return $this; 2564 } 2565 /** 2566 * Enter description here... 2567 * 2568 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2569 */ 2570 public function change($callback = null) { 2571 if ($callback) 2572 return $this->bind('change', $callback); 2573 return $this->trigger('change'); 2574 } 2575 /** 2576 * Enter description here... 2577 * 2578 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2579 */ 2580 public function submit($callback = null) { 2581 if ($callback) 2582 return $this->bind('submit', $callback); 2583 return $this->trigger('submit'); 2584 } 2585 /** 2586 * Enter description here... 2587 * 2588 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2589 */ 2590 public function click($callback = null) { 2591 if ($callback) 2592 return $this->bind('click', $callback); 2593 return $this->trigger('click'); 2594 } 2595 /** 2596 * Enter description here... 2597 * 2598 * @param String|phpQuery 2599 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2600 */ 2601 public function wrapAllOld($wrapper) { 2602 $wrapper = pq($wrapper)->_clone(); 2603 if (! $wrapper->length() || ! $this->length() ) 2604 return $this; 2605 $wrapper->insertBefore($this->elements[0]); 2606 $deepest = $wrapper->elements[0]; 2607 while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) 2608 $deepest = $deepest->firstChild; 2609 pq($deepest)->append($this); 2610 return $this; 2611 } 2612 /** 2613 * Enter description here... 2614 * 2615 * TODO testme... 2616 * @param String|phpQuery 2617 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2618 */ 2619 public function wrapAll($wrapper) { 2620 if (! $this->length()) 2621 return $this; 2622 return phpQuery::pq($wrapper, $this->getDocumentID()) 2623 ->clone() 2624 ->insertBefore($this->get(0)) 2625 ->map(array($this, '___wrapAllCallback')) 2626 ->append($this); 2627 } 2628 /** 2629 * 2630 * @param $node 2631 * @return unknown_type 2632 * @access private 2633 */ 2634 public function ___wrapAllCallback($node) { 2635 $deepest = $node; 2636 while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) 2637 $deepest = $deepest->firstChild; 2638 return $deepest; 2639 } 2640 /** 2641 * Enter description here... 2642 * NON JQUERY METHOD 2643 * 2644 * @param String|phpQuery 2645 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2646 */ 2647 public function wrapAllPHP($codeBefore, $codeAfter) { 2648 return $this 2649 ->slice(0, 1) 2650 ->beforePHP($codeBefore) 2651 ->end() 2652 ->slice(-1) 2653 ->afterPHP($codeAfter) 2654 ->end(); 2655 } 2656 /** 2657 * Enter description here... 2658 * 2659 * @param String|phpQuery 2660 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2661 */ 2662 public function wrap($wrapper) { 2663 foreach($this->stack() as $node) 2664 phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper); 2665 return $this; 2666 } 2667 /** 2668 * Enter description here... 2669 * 2670 * @param String|phpQuery 2671 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2672 */ 2673 public function wrapPHP($codeBefore, $codeAfter) { 2674 foreach($this->stack() as $node) 2675 phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter); 2676 return $this; 2677 } 2678 /** 2679 * Enter description here... 2680 * 2681 * @param String|phpQuery 2682 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2683 */ 2684 public function wrapInner($wrapper) { 2685 foreach($this->stack() as $node) 2686 phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper); 2687 return $this; 2688 } 2689 /** 2690 * Enter description here... 2691 * 2692 * @param String|phpQuery 2693 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2694 */ 2695 public function wrapInnerPHP($codeBefore, $codeAfter) { 2696 foreach($this->stack(1) as $node) 2697 phpQuery::pq($node, $this->getDocumentID())->contents() 2698 ->wrapAllPHP($codeBefore, $codeAfter); 2699 return $this; 2700 } 2701 /** 2702 * Enter description here... 2703 * 2704 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2705 * @testme Support for text nodes 2706 */ 2707 public function contents() { 2708 $stack = array(); 2709 foreach($this->stack(1) as $el) { 2710 // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56 2711 // if (! isset($el->childNodes)) 2712 // continue; 2713 foreach($el->childNodes as $node) { 2714 $stack[] = $node; 2715 } 2716 } 2717 return $this->newInstance($stack); 2718 } 2719 /** 2720 * Enter description here... 2721 * 2722 * jQuery difference. 2723 * 2724 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2725 */ 2726 public function contentsUnwrap() { 2727 foreach($this->stack(1) as $node) { 2728 if (! $node->parentNode ) 2729 continue; 2730 $childNodes = array(); 2731 // any modification in DOM tree breaks childNodes iteration, so cache them first 2732 foreach($node->childNodes as $chNode ) 2733 $childNodes[] = $chNode; 2734 foreach($childNodes as $chNode ) 2735 // $node->parentNode->appendChild($chNode); 2736 $node->parentNode->insertBefore($chNode, $node); 2737 $node->parentNode->removeChild($node); 2738 } 2739 return $this; 2740 } 2741 /** 2742 * Enter description here... 2743 * 2744 * jQuery difference. 2745 */ 2746 public function switchWith($markup) { 2747 $markup = pq($markup, $this->getDocumentID()); 2748 $content = null; 2749 foreach($this->stack(1) as $node) { 2750 pq($node) 2751 ->contents()->toReference($content)->end() 2752 ->replaceWith($markup->clone()->append($content)); 2753 } 2754 return $this; 2755 } 2756 /** 2757 * Enter description here... 2758 * 2759 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2760 */ 2761 public function eq($num) { 2762 $oldStack = $this->elements; 2763 $this->elementsBackup = $this->elements; 2764 $this->elements = array(); 2765 if ( isset($oldStack[$num]) ) 2766 $this->elements[] = $oldStack[$num]; 2767 return $this->newInstance(); 2768 } 2769 /** 2770 * Enter description here... 2771 * 2772 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2773 */ 2774 public function size() { 2775 return count($this->elements); 2776 } 2777 /** 2778 * Enter description here... 2779 * 2780 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2781 * @deprecated Use length as attribute 2782 */ 2783 public function length() { 2784 return $this->size(); 2785 } 2786 public function count() { 2787 return $this->size(); 2788 } 2789 /** 2790 * Enter description here... 2791 * 2792 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2793 * @todo $level 2794 */ 2795 public function end($level = 1) { 2796 // $this->elements = array_pop( $this->history ); 2797 // return $this; 2798 // $this->previous->DOM = $this->DOM; 2799 // $this->previous->XPath = $this->XPath; 2800 return $this->previous 2801 ? $this->previous 2802 : $this; 2803 } 2804 /** 2805 * Enter description here... 2806 * Normal use ->clone() . 2807 * 2808 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2809 * @access private 2810 */ 2811 public function _clone() { 2812 $newStack = array(); 2813 //pr(array('copy... ', $this->whois())); 2814 //$this->dumpHistory('copy'); 2815 $this->elementsBackup = $this->elements; 2816 foreach($this->elements as $node) { 2817 $newStack[] = $node->cloneNode(true); 2818 } 2819 $this->elements = $newStack; 2820 return $this->newInstance(); 2821 } 2822 /** 2823 * Enter description here... 2824 * 2825 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2826 */ 2827 public function replaceWithPHP($code) { 2828 return $this->replaceWith(phpQuery::php($code)); 2829 } 2830 /** 2831 * Enter description here... 2832 * 2833 * @param String|phpQuery $content 2834 * @link http://docs.jquery.com/Manipulation/replaceWith#content 2835 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2836 */ 2837 public function replaceWith($content) { 2838 return $this->after($content)->remove(); 2839 } 2840 /** 2841 * Enter description here... 2842 * 2843 * @param String $selector 2844 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2845 * @todo this works ? 2846 */ 2847 public function replaceAll($selector) { 2848 foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node) 2849 phpQuery::pq($node, $this->getDocumentID()) 2850 ->after($this->_clone()) 2851 ->remove(); 2852 return $this; 2853 } 2854 /** 2855 * Enter description here... 2856 * 2857 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2858 */ 2859 public function remove($selector = null) { 2860 $loop = $selector 2861 ? $this->filter($selector)->elements 2862 : $this->elements; 2863 foreach($loop as $node) { 2864 if (! $node->parentNode ) 2865 continue; 2866 if (isset($node->tagName)) 2867 $this->debug("Removing '{$node->tagName}'"); 2868 $node->parentNode->removeChild($node); 2869 // Mutation event 2870 $event = new DOMEvent(array( 2871 'target' => $node, 2872 'type' => 'DOMNodeRemoved' 2873 )); 2874 phpQueryEvents::trigger($this->getDocumentID(), 2875 $event->type, array($event), $node 2876 ); 2877 } 2878 return $this; 2879 } 2880 protected function markupEvents($newMarkup, $oldMarkup, $node) { 2881 if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) { 2882 $event = new DOMEvent(array( 2883 'target' => $node, 2884 'type' => 'change' 2885 )); 2886 phpQueryEvents::trigger($this->getDocumentID(), 2887 $event->type, array($event), $node 2888 ); 2889 } 2890 } 2891 /** 2892 * jQuey difference 2893 * 2894 * @param $markup 2895 * @return unknown_type 2896 * @TODO trigger change event for textarea 2897 */ 2898 public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) { 2899 $args = func_get_args(); 2900 if ($this->documentWrapper->isXML) 2901 return call_user_func_array(array($this, 'xml'), $args); 2902 else 2903 return call_user_func_array(array($this, 'html'), $args); 2904 } 2905 /** 2906 * jQuey difference 2907 * 2908 * @param $markup 2909 * @return unknown_type 2910 */ 2911 public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) { 2912 $args = func_get_args(); 2913 if ($this->documentWrapper->isXML) 2914 return call_user_func_array(array($this, 'xmlOuter'), $args); 2915 else 2916 return call_user_func_array(array($this, 'htmlOuter'), $args); 2917 } 2918 /** 2919 * Enter description here... 2920 * 2921 * @param unknown_type $html 2922 * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2923 * @TODO force html result 2924 */ 2925 public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) { 2926 if (isset($html)) { 2927 // INSERT 2928 $nodes = $this->documentWrapper->import($html); 2929 $this->empty(); 2930 foreach($this->stack(1) as $alreadyAdded => $node) { 2931 // for now, limit events for textarea 2932 if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') 2933 $oldHtml = pq($node, $this->getDocumentID())->markup(); 2934 foreach($nodes as $newNode) { 2935 $node->appendChild($alreadyAdded 2936 ? $newNode->cloneNode(true) 2937 : $newNode 2938 ); 2939 } 2940 // for now, limit events for textarea 2941 if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') 2942 $this->markupEvents($html, $oldHtml, $node); 2943 } 2944 return $this; 2945 } else { 2946 // FETCH 2947 $return = $this->documentWrapper->markup($this->elements, true); 2948 $args = func_get_args(); 2949 foreach(array_slice($args, 1) as $callback) { 2950 $return = phpQuery::callbackRun($callback, array($return)); 2951 } 2952 return $return; 2953 } 2954 } 2955 /** 2956 * @TODO force xml result 2957 */ 2958 public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) { 2959 $args = func_get_args(); 2960 return call_user_func_array(array($this, 'html'), $args); 2961 } 2962 /** 2963 * Enter description here... 2964 * @TODO force html result 2965 * 2966 * @return String 2967 */ 2968 public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { 2969 $markup = $this->documentWrapper->markup($this->elements); 2970 // pass thou callbacks 2971 $args = func_get_args(); 2972 foreach($args as $callback) { 2973 $markup = phpQuery::callbackRun($callback, array($markup)); 2974 } 2975 return $markup; 2976 } 2977 /** 2978 * @TODO force xml result 2979 */ 2980 public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { 2981 $args = func_get_args(); 2982 return call_user_func_array(array($this, 'htmlOuter'), $args); 2983 } 2984 public function __toString() { 2985 return $this->markupOuter(); 2986 } 2987 /** 2988 * Just like html(), but returns markup with VALID (dangerous) PHP tags. 2989 * 2990 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2991 * @todo support returning markup with PHP tags when called without param 2992 */ 2993 public function php($code = null) { 2994 return $this->markupPHP($code); 2995 } 2996 /** 2997 * Enter description here... 2998 * 2999 * @param $code 3000 * @return unknown_type 3001 */ 3002 public function markupPHP($code = null) { 3003 return isset($code) 3004 ? $this->markup(phpQuery::php($code)) 3005 : phpQuery::markupToPHP($this->markup()); 3006 } 3007 /** 3008 * Enter description here... 3009 * 3010 * @param $code 3011 * @return unknown_type 3012 */ 3013 public function markupOuterPHP() { 3014 return phpQuery::markupToPHP($this->markupOuter()); 3015 } 3016 /** 3017 * Enter description here... 3018 * 3019 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3020 */ 3021 public function children($selector = null) { 3022 $stack = array(); 3023 foreach($this->stack(1) as $node) { 3024 // foreach($node->getElementsByTagName('*') as $newNode) { 3025 foreach($node->childNodes as $newNode) { 3026 if ($newNode->nodeType != 1) 3027 continue; 3028 if ($selector && ! $this->is($selector, $newNode)) 3029 continue; 3030 if ($this->elementsContainsNode($newNode, $stack)) 3031 continue; 3032 $stack[] = $newNode; 3033 } 3034 } 3035 $this->elementsBackup = $this->elements; 3036 $this->elements = $stack; 3037 return $this->newInstance(); 3038 } 3039 /** 3040 * Enter description here... 3041 * 3042 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3043 */ 3044 public function ancestors($selector = null) { 3045 return $this->children( $selector ); 3046 } 3047 /** 3048 * Enter description here... 3049 * 3050 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3051 */ 3052 public function append( $content) { 3053 return $this->insert($content, __FUNCTION__); 3054 } 3055 /** 3056 * Enter description here... 3057 * 3058 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3059 */ 3060 public function appendPHP( $content) { 3061 return $this->insert("<php><!-- {$content} --></php>", 'append'); 3062 } 3063 /** 3064 * Enter description here... 3065 * 3066 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3067 */ 3068 public function appendTo( $seletor) { 3069 return $this->insert($seletor, __FUNCTION__); 3070 } 3071 /** 3072 * Enter description here... 3073 * 3074 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3075 */ 3076 public function prepend( $content) { 3077 return $this->insert($content, __FUNCTION__); 3078 } 3079 /** 3080 * Enter description here... 3081 * 3082 * @todo accept many arguments, which are joined, arrays maybe also 3083 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3084 */ 3085 public function prependPHP( $content) { 3086 return $this->insert("<php><!-- {$content} --></php>", 'prepend'); 3087 } 3088 /** 3089 * Enter description here... 3090 * 3091 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3092 */ 3093 public function prependTo( $seletor) { 3094 return $this->insert($seletor, __FUNCTION__); 3095 } 3096 /** 3097 * Enter description here... 3098 * 3099 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3100 */ 3101 public function before($content) { 3102 return $this->insert($content, __FUNCTION__); 3103 } 3104 /** 3105 * Enter description here... 3106 * 3107 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3108 */ 3109 public function beforePHP( $content) { 3110 return $this->insert("<php><!-- {$content} --></php>", 'before'); 3111 } 3112 /** 3113 * Enter description here... 3114 * 3115 * @param String|phpQuery 3116 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3117 */ 3118 public function insertBefore( $seletor) { 3119 return $this->insert($seletor, __FUNCTION__); 3120 } 3121 /** 3122 * Enter description here... 3123 * 3124 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3125 */ 3126 public function after( $content) { 3127 return $this->insert($content, __FUNCTION__); 3128 } 3129 /** 3130 * Enter description here... 3131 * 3132 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3133 */ 3134 public function afterPHP( $content) { 3135 return $this->insert("<php><!-- {$content} --></php>", 'after'); 3136 } 3137 /** 3138 * Enter description here... 3139 * 3140 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3141 */ 3142 public function insertAfter( $seletor) { 3143 return $this->insert($seletor, __FUNCTION__); 3144 } 3145 /** 3146 * Internal insert method. Don't use it. 3147 * 3148 * @param unknown_type $target 3149 * @param unknown_type $type 3150 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3151 * @access private 3152 */ 3153 public function insert($target, $type) { 3154 $this->debug("Inserting data with '{$type}'"); 3155 $to = false; 3156 switch( $type) { 3157 case 'appendTo': 3158 case 'prependTo': 3159 case 'insertBefore': 3160 case 'insertAfter': 3161 $to = true; 3162 } 3163 switch(gettype($target)) { 3164 case 'string': 3165 $insertFrom = $insertTo = array(); 3166 if ($to) { 3167 // INSERT TO 3168 $insertFrom = $this->elements; 3169 if (phpQuery::isMarkup($target)) { 3170 // $target is new markup, import it 3171 $insertTo = $this->documentWrapper->import($target); 3172 // insert into selected element 3173 } else { 3174 // $tagret is a selector 3175 $thisStack = $this->elements; 3176 $this->toRoot(); 3177 $insertTo = $this->find($target)->elements; 3178 $this->elements = $thisStack; 3179 } 3180 } else { 3181 // INSERT FROM 3182 $insertTo = $this->elements; 3183 $insertFrom = $this->documentWrapper->import($target); 3184 } 3185 break; 3186 case 'object': 3187 $insertFrom = $insertTo = array(); 3188 // phpQuery 3189 if ($target instanceof self) { 3190 if ($to) { 3191 $insertTo = $target->elements; 3192 if ($this->documentFragment && $this->stackIsRoot()) 3193 // get all body children 3194 // $loop = $this->find('body > *')->elements; 3195 // TODO test it, test it hard... 3196 // $loop = $this->newInstance($this->root)->find('> *')->elements; 3197 $loop = $this->root->childNodes; 3198 else 3199 $loop = $this->elements; 3200 // import nodes if needed 3201 $insertFrom = $this->getDocumentID() == $target->getDocumentID() 3202 ? $loop 3203 : $target->documentWrapper->import($loop); 3204 } else { 3205 $insertTo = $this->elements; 3206 if ( $target->documentFragment && $target->stackIsRoot() ) 3207 // get all body children 3208 // $loop = $target->find('body > *')->elements; 3209 $loop = $target->root->childNodes; 3210 else 3211 $loop = $target->elements; 3212 // import nodes if needed 3213 $insertFrom = $this->getDocumentID() == $target->getDocumentID() 3214 ? $loop 3215 : $this->documentWrapper->import($loop); 3216 } 3217 // DOMNODE 3218 } elseif ($target instanceof DOMNODE) { 3219 // import node if needed 3220 // if ( $target->ownerDocument != $this->DOM ) 3221 // $target = $this->DOM->importNode($target, true); 3222 if ( $to) { 3223 $insertTo = array($target); 3224 if ($this->documentFragment && $this->stackIsRoot()) 3225 // get all body children 3226 $loop = $this->root->childNodes; 3227 // $loop = $this->find('body > *')->elements; 3228 else 3229 $loop = $this->elements; 3230 foreach($loop as $fromNode) 3231 // import nodes if needed 3232 $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument) 3233 ? $target->ownerDocument->importNode($fromNode, true) 3234 : $fromNode; 3235 } else { 3236 // import node if needed 3237 if (! $target->ownerDocument->isSameNode($this->document)) 3238 $target = $this->document->importNode($target, true); 3239 $insertTo = $this->elements; 3240 $insertFrom[] = $target; 3241 } 3242 } 3243 break; 3244 } 3245 phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes"); 3246 foreach($insertTo as $insertNumber => $toNode) { 3247 // we need static relative elements in some cases 3248 switch( $type) { 3249 case 'prependTo': 3250 case 'prepend': 3251 $firstChild = $toNode->firstChild; 3252 break; 3253 case 'insertAfter': 3254 case 'after': 3255 $nextSibling = $toNode->nextSibling; 3256 break; 3257 } 3258 foreach($insertFrom as $fromNode) { 3259 // clone if inserted already before 3260 $insert = $insertNumber 3261 ? $fromNode->cloneNode(true) 3262 : $fromNode; 3263 switch($type) { 3264 case 'appendTo': 3265 case 'append': 3266 // $toNode->insertBefore( 3267 // $fromNode, 3268 // $toNode->lastChild->nextSibling 3269 // ); 3270 $toNode->appendChild($insert); 3271 $eventTarget = $insert; 3272 break; 3273 case 'prependTo': 3274 case 'prepend': 3275 $toNode->insertBefore( 3276 $insert, 3277 $firstChild 3278 ); 3279 break; 3280 case 'insertBefore': 3281 case 'before': 3282 if (! $toNode->parentNode) 3283 throw new Exception("No parentNode, can't do {$type}()"); 3284 else 3285 $toNode->parentNode->insertBefore( 3286 $insert, 3287 $toNode 3288 ); 3289 break; 3290 case 'insertAfter': 3291 case 'after': 3292 if (! $toNode->parentNode) 3293 throw new Exception("No parentNode, can't do {$type}()"); 3294 else 3295 $toNode->parentNode->insertBefore( 3296 $insert, 3297 $nextSibling 3298 ); 3299 break; 3300 } 3301 // Mutation event 3302 $event = new DOMEvent(array( 3303 'target' => $insert, 3304 'type' => 'DOMNodeInserted' 3305 )); 3306 phpQueryEvents::trigger($this->getDocumentID(), 3307 $event->type, array($event), $insert 3308 ); 3309 } 3310 } 3311 return $this; 3312 } 3313 /** 3314 * Enter description here... 3315 * 3316 * @return Int 3317 */ 3318 public function index($subject) { 3319 $index = -1; 3320 $subject = $subject instanceof phpQueryObject 3321 ? $subject->elements[0] 3322 : $subject; 3323 foreach($this->newInstance() as $k => $node) { 3324 if ($node->isSameNode($subject)) 3325 $index = $k; 3326 } 3327 return $index; 3328 } 3329 /** 3330 * Enter description here... 3331 * 3332 * @param unknown_type $start 3333 * @param unknown_type $end 3334 * 3335 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3336 * @testme 3337 */ 3338 public function slice($start, $end = null) { 3339 // $last = count($this->elements)-1; 3340 // $end = $end 3341 // ? min($end, $last) 3342 // : $last; 3343 // if ($start < 0) 3344 // $start = $last+$start; 3345 // if ($start > $last) 3346 // return array(); 3347 if ($end > 0) 3348 $end = $end-$start; 3349 return $this->newInstance( 3350 array_slice($this->elements, $start, $end) 3351 ); 3352 } 3353 /** 3354 * Enter description here... 3355 * 3356 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3357 */ 3358 public function reverse() { 3359 $this->elementsBackup = $this->elements; 3360 $this->elements = array_reverse($this->elements); 3361 return $this->newInstance(); 3362 } 3363 /** 3364 * Return joined text content. 3365 * @return String 3366 */ 3367 public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) { 3368 if (isset($text)) 3369 return $this->html(htmlspecialchars($text)); 3370 $args = func_get_args(); 3371 $args = array_slice($args, 1); 3372 $return = ''; 3373 foreach($this->elements as $node) { 3374 $text = $node->textContent; 3375 if (count($this->elements) > 1 && $text) 3376 $text .= "\n"; 3377 foreach($args as $callback) { 3378 $text = phpQuery::callbackRun($callback, array($text)); 3379 } 3380 $return .= $text; 3381 } 3382 return $return; 3383 } 3384 /** 3385 * Enter description here... 3386 * 3387 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3388 */ 3389 public function plugin($class, $file = null) { 3390 phpQuery::plugin($class, $file); 3391 return $this; 3392 } 3393 /** 3394 * Deprecated, use $pq->plugin() instead. 3395 * 3396 * @deprecated 3397 * @param $class 3398 * @param $file 3399 * @return unknown_type 3400 */ 3401 public static function extend($class, $file = null) { 3402 return $this->plugin($class, $file); 3403 } 3404 /** 3405 * 3406 * @access private 3407 * @param $method 3408 * @param $args 3409 * @return unknown_type 3410 */ 3411 public function __call($method, $args) { 3412 $aliasMethods = array('clone', 'empty'); 3413 if (isset(phpQuery::$extendMethods[$method])) { 3414 array_unshift($args, $this); 3415 return phpQuery::callbackRun( 3416 phpQuery::$extendMethods[$method], $args 3417 ); 3418 } else if (isset(phpQuery::$pluginsMethods[$method])) { 3419 array_unshift($args, $this); 3420 $class = phpQuery::$pluginsMethods[$method]; 3421 $realClass = "phpQueryObjectPlugin_$class"; 3422 $return = call_user_func_array( 3423 array($realClass, $method), 3424 $args 3425 ); 3426 // XXX deprecate ? 3427 return is_null($return) 3428 ? $this 3429 : $return; 3430 } else if (in_array($method, $aliasMethods)) { 3431 return call_user_func_array(array($this, '_'.$method), $args); 3432 } else 3433 throw new Exception("Method '{$method}' doesnt exist"); 3434 } 3435 /** 3436 * Safe rename of next(). 3437 * 3438 * Use it ONLY when need to call next() on an iterated object (in same time). 3439 * Normaly there is no need to do such thing ;) 3440 * 3441 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3442 * @access private 3443 */ 3444 public function _next($selector = null) { 3445 return $this->newInstance( 3446 $this->getElementSiblings('nextSibling', $selector, true) 3447 ); 3448 } 3449 /** 3450 * Use prev() and next(). 3451 * 3452 * @deprecated 3453 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3454 * @access private 3455 */ 3456 public function _prev($selector = null) { 3457 return $this->prev($selector); 3458 } 3459 /** 3460 * Enter description here... 3461 * 3462 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3463 */ 3464 public function prev($selector = null) { 3465 return $this->newInstance( 3466 $this->getElementSiblings('previousSibling', $selector, true) 3467 ); 3468 } 3469 /** 3470 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3471 * @todo 3472 */ 3473 public function prevAll($selector = null) { 3474 return $this->newInstance( 3475 $this->getElementSiblings('previousSibling', $selector) 3476 ); 3477 } 3478 /** 3479 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3480 * @todo FIXME: returns source elements insted of next siblings 3481 */ 3482 public function nextAll($selector = null) { 3483 return $this->newInstance( 3484 $this->getElementSiblings('nextSibling', $selector) 3485 ); 3486 } 3487 /** 3488 * @access private 3489 */ 3490 protected function getElementSiblings($direction, $selector = null, $limitToOne = false) { 3491 $stack = array(); 3492 $count = 0; 3493 foreach($this->stack() as $node) { 3494 $test = $node; 3495 while( isset($test->{$direction}) && $test->{$direction}) { 3496 $test = $test->{$direction}; 3497 if (! $test instanceof DOMELEMENT) 3498 continue; 3499 $stack[] = $test; 3500 if ($limitToOne) 3501 break; 3502 } 3503 } 3504 if ($selector) { 3505 $stackOld = $this->elements; 3506 $this->elements = $stack; 3507 $stack = $this->filter($selector, true)->stack(); 3508 $this->elements = $stackOld; 3509 } 3510 return $stack; 3511 } 3512 /** 3513 * Enter description here... 3514 * 3515 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3516 */ 3517 public function siblings($selector = null) { 3518 $stack = array(); 3519 $siblings = array_merge( 3520 $this->getElementSiblings('previousSibling', $selector), 3521 $this->getElementSiblings('nextSibling', $selector) 3522 ); 3523 foreach($siblings as $node) { 3524 if (! $this->elementsContainsNode($node, $stack)) 3525 $stack[] = $node; 3526 } 3527 return $this->newInstance($stack); 3528 } 3529 /** 3530 * Enter description here... 3531 * 3532 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3533 */ 3534 public function not($selector = null) { 3535 if (is_string($selector)) 3536 phpQuery::debug(array('not', $selector)); 3537 else 3538 phpQuery::debug('not'); 3539 $stack = array(); 3540 if ($selector instanceof self || $selector instanceof DOMNODE) { 3541 foreach($this->stack() as $node) { 3542 if ($selector instanceof self) { 3543 $matchFound = false; 3544 foreach($selector->stack() as $notNode) { 3545 if ($notNode->isSameNode($node)) 3546 $matchFound = true; 3547 } 3548 if (! $matchFound) 3549 $stack[] = $node; 3550 } else if ($selector instanceof DOMNODE) { 3551 if (! $selector->isSameNode($node)) 3552 $stack[] = $node; 3553 } else { 3554 if (! $this->is($selector)) 3555 $stack[] = $node; 3556 } 3557 } 3558 } else { 3559 $orgStack = $this->stack(); 3560 $matched = $this->filter($selector, true)->stack(); 3561 // $matched = array(); 3562 // // simulate OR in filter() instead of AND 5y 3563 // foreach($this->parseSelector($selector) as $s) { 3564 // $matched = array_merge($matched, 3565 // $this->filter(array($s))->stack() 3566 // ); 3567 // } 3568 foreach($orgStack as $node) 3569 if (! $this->elementsContainsNode($node, $matched)) 3570 $stack[] = $node; 3571 } 3572 return $this->newInstance($stack); 3573 } 3574 /** 3575 * Enter description here... 3576 * 3577 * @param string|phpQueryObject 3578 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3579 */ 3580 public function add($selector = null) { 3581 if (! $selector) 3582 return $this; 3583 $stack = array(); 3584 $this->elementsBackup = $this->elements; 3585 $found = phpQuery::pq($selector, $this->getDocumentID()); 3586 $this->merge($found->elements); 3587 return $this->newInstance(); 3588 } 3589 /** 3590 * @access private 3591 */ 3592 protected function merge() { 3593 foreach(func_get_args() as $nodes) 3594 foreach($nodes as $newNode ) 3595 if (! $this->elementsContainsNode($newNode) ) 3596 $this->elements[] = $newNode; 3597 } 3598 /** 3599 * @access private 3600 * TODO refactor to stackContainsNode 3601 */ 3602 protected function elementsContainsNode($nodeToCheck, $elementsStack = null) { 3603 $loop = ! is_null($elementsStack) 3604 ? $elementsStack 3605 : $this->elements; 3606 foreach($loop as $node) { 3607 if ( $node->isSameNode( $nodeToCheck ) ) 3608 return true; 3609 } 3610 return false; 3611 } 3612 /** 3613 * Enter description here... 3614 * 3615 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3616 */ 3617 public function parent($selector = null) { 3618 $stack = array(); 3619 foreach($this->elements as $node ) 3620 if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) ) 3621 $stack[] = $node->parentNode; 3622 $this->elementsBackup = $this->elements; 3623 $this->elements = $stack; 3624 if ( $selector ) 3625 $this->filter($selector, true); 3626 return $this->newInstance(); 3627 } 3628 /** 3629 * Enter description here... 3630 * 3631 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3632 */ 3633 public function parents($selector = null) { 3634 $stack = array(); 3635 if (! $this->elements ) 3636 $this->debug('parents() - stack empty'); 3637 foreach($this->elements as $node) { 3638 $test = $node; 3639 while( $test->parentNode) { 3640 $test = $test->parentNode; 3641 if ($this->isRoot($test)) 3642 break; 3643 if (! $this->elementsContainsNode($test, $stack)) { 3644 $stack[] = $test; 3645 continue; 3646 } 3647 } 3648 } 3649 $this->elementsBackup = $this->elements; 3650 $this->elements = $stack; 3651 if ( $selector ) 3652 $this->filter($selector, true); 3653 return $this->newInstance(); 3654 } 3655 /** 3656 * Internal stack iterator. 3657 * 3658 * @access private 3659 */ 3660 public function stack($nodeTypes = null) { 3661 if (!isset($nodeTypes)) 3662 return $this->elements; 3663 if (!is_array($nodeTypes)) 3664 $nodeTypes = array($nodeTypes); 3665 $return = array(); 3666 foreach($this->elements as $node) { 3667 if (in_array($node->nodeType, $nodeTypes)) 3668 $return[] = $node; 3669 } 3670 return $return; 3671 } 3672 // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes 3673 protected function attrEvents($attr, $oldAttr, $oldValue, $node) { 3674 // skip events for XML documents 3675 if (! $this->isXHTML() && ! $this->isHTML()) 3676 return; 3677 $event = null; 3678 // identify 3679 $isInputValue = $node->tagName == 'input' 3680 && ( 3681 in_array($node->getAttribute('type'), 3682 array('text', 'password', 'hidden')) 3683 || !$node->getAttribute('type') 3684 ); 3685 $isRadio = $node->tagName == 'input' 3686 && $node->getAttribute('type') == 'radio'; 3687 $isCheckbox = $node->tagName == 'input' 3688 && $node->getAttribute('type') == 'checkbox'; 3689 $isOption = $node->tagName == 'option'; 3690 if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) { 3691 $event = new DOMEvent(array( 3692 'target' => $node, 3693 'type' => 'change' 3694 )); 3695 } else if (($isRadio || $isCheckbox) && $attr == 'checked' && ( 3696 // check 3697 (! $oldAttr && $node->hasAttribute($attr)) 3698 // un-check 3699 || (! $node->hasAttribute($attr) && $oldAttr) 3700 )) { 3701 $event = new DOMEvent(array( 3702 'target' => $node, 3703 'type' => 'change' 3704 )); 3705 } else if ($isOption && $node->parentNode && $attr == 'selected' && ( 3706 // select 3707 (! $oldAttr && $node->hasAttribute($attr)) 3708 // un-select 3709 || (! $node->hasAttribute($attr) && $oldAttr) 3710 )) { 3711 $event = new DOMEvent(array( 3712 'target' => $node->parentNode, 3713 'type' => 'change' 3714 )); 3715 } 3716 if ($event) { 3717 phpQueryEvents::trigger($this->getDocumentID(), 3718 $event->type, array($event), $node 3719 ); 3720 } 3721 } 3722 public function attr($attr = null, $value = null) { 3723 foreach($this->stack(1) as $node) { 3724 if (! is_null($value)) { 3725 $loop = $attr == '*' 3726 ? $this->getNodeAttrs($node) 3727 : array($attr); 3728 foreach($loop as $a) { 3729 $oldValue = $node->getAttribute($a); 3730 $oldAttr = $node->hasAttribute($a); 3731 // TODO raises an error when charset other than UTF-8 3732 // while document's charset is also not UTF-8 3733 @$node->setAttribute($a, $value); 3734 $this->attrEvents($a, $oldAttr, $oldValue, $node); 3735 } 3736 } else if ($attr == '*') { 3737 // jQuery difference 3738 $return = array(); 3739 foreach($node->attributes as $n => $v) 3740 $return[$n] = $v->value; 3741 return $return; 3742 } else 3743 return $node->hasAttribute($attr) 3744 ? $node->getAttribute($attr) 3745 : null; 3746 } 3747 return is_null($value) 3748 ? '' : $this; 3749 } 3750 /** 3751 * @access private 3752 */ 3753 protected function getNodeAttrs($node) { 3754 $return = array(); 3755 foreach($node->attributes as $n => $o) 3756 $return[] = $n; 3757 return $return; 3758 } 3759 /** 3760 * Enter description here... 3761 * 3762 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3763 * @todo check CDATA ??? 3764 */ 3765 public function attrPHP($attr, $code) { 3766 if (! is_null($code)) { 3767 $value = '<'.'?php '.$code.' ?'.'>'; 3768 // TODO tempolary solution 3769 // http://code.google.com/p/phpquery/issues/detail?id=17 3770 // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII') 3771 // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES'); 3772 } 3773 foreach($this->stack(1) as $node) { 3774 if (! is_null($code)) { 3775 // $attrNode = $this->DOM->createAttribute($attr); 3776 $node->setAttribute($attr, $value); 3777 // $attrNode->value = $value; 3778 // $node->appendChild($attrNode); 3779 } else if ( $attr == '*') { 3780 // jQuery diff 3781 $return = array(); 3782 foreach($node->attributes as $n => $v) 3783 $return[$n] = $v->value; 3784 return $return; 3785 } else 3786 return $node->getAttribute($attr); 3787 } 3788 return $this; 3789 } 3790 /** 3791 * Enter description here... 3792 * 3793 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3794 */ 3795 public function removeAttr($attr) { 3796 foreach($this->stack(1) as $node) { 3797 $loop = $attr == '*' 3798 ? $this->getNodeAttrs($node) 3799 : array($attr); 3800 foreach($loop as $a) { 3801 $oldValue = $node->getAttribute($a); 3802 $node->removeAttribute($a); 3803 $this->attrEvents($a, $oldValue, null, $node); 3804 } 3805 } 3806 return $this; 3807 } 3808 /** 3809 * Return form element value. 3810 * 3811 * @return String Fields value. 3812 */ 3813 public function val($val = null) { 3814 if (! isset($val)) { 3815 if ($this->eq(0)->is('select')) { 3816 $selected = $this->eq(0)->find('option[selected=selected]'); 3817 if ($selected->is('[value]')) 3818 return $selected->attr('value'); 3819 else 3820 return $selected->text(); 3821 } else if ($this->eq(0)->is('textarea')) 3822 return $this->eq(0)->markup(); 3823 else 3824 return $this->eq(0)->attr('value'); 3825 } else { 3826 $_val = null; 3827 foreach($this->stack(1) as $node) { 3828 $node = pq($node, $this->getDocumentID()); 3829 if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) { 3830 $isChecked = in_array($node->attr('value'), $val) 3831 || in_array($node->attr('name'), $val); 3832 if ($isChecked) 3833 $node->attr('checked', 'checked'); 3834 else 3835 $node->removeAttr('checked'); 3836 } else if ($node->get(0)->tagName == 'select') { 3837 if (! isset($_val)) { 3838 $_val = array(); 3839 if (! is_array($val)) 3840 $_val = array((string)$val); 3841 else 3842 foreach($val as $v) 3843 $_val[] = $v; 3844 } 3845 foreach($node['option']->stack(1) as $option) { 3846 $option = pq($option, $this->getDocumentID()); 3847 $selected = false; 3848 // XXX: workaround for string comparsion, see issue #96 3849 // http://code.google.com/p/phpquery/issues/detail?id=96 3850 $selected = is_null($option->attr('value')) 3851 ? in_array($option->markup(), $_val) 3852 : in_array($option->attr('value'), $_val); 3853 // $optionValue = $option->attr('value'); 3854 // $optionText = $option->text(); 3855 // $optionTextLenght = mb_strlen($optionText); 3856 // foreach($_val as $v) 3857 // if ($optionValue == $v) 3858 // $selected = true; 3859 // else if ($optionText == $v && $optionTextLenght == mb_strlen($v)) 3860 // $selected = true; 3861 if ($selected) 3862 $option->attr('selected', 'selected'); 3863 else 3864 $option->removeAttr('selected'); 3865 } 3866 } else if ($node->get(0)->tagName == 'textarea') 3867 $node->markup($val); 3868 else 3869 $node->attr('value', $val); 3870 } 3871 } 3872 return $this; 3873 } 3874 /** 3875 * Enter description here... 3876 * 3877 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3878 */ 3879 public function andSelf() { 3880 if ( $this->previous ) 3881 $this->elements = array_merge($this->elements, $this->previous->elements); 3882 return $this; 3883 } 3884 /** 3885 * Enter description here... 3886 * 3887 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3888 */ 3889 public function addClass( $className) { 3890 if (! $className) 3891 return $this; 3892 foreach($this->stack(1) as $node) { 3893 if (! $this->is(".$className", $node)) 3894 $node->setAttribute( 3895 'class', 3896 trim($node->getAttribute('class').' '.$className) 3897 ); 3898 } 3899 return $this; 3900 } 3901 /** 3902 * Enter description here... 3903 * 3904 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3905 */ 3906 public function addClassPHP( $className) { 3907 foreach($this->stack(1) as $node) { 3908 $classes = $node->getAttribute('class'); 3909 $newValue = $classes 3910 ? $classes.' <'.'?php '.$className.' ?'.'>' 3911 : '<'.'?php '.$className.' ?'.'>'; 3912 $node->setAttribute('class', $newValue); 3913 } 3914 return $this; 3915 } 3916 /** 3917 * Enter description here... 3918 * 3919 * @param string $className 3920 * @return bool 3921 */ 3922 public function hasClass($className) { 3923 foreach($this->stack(1) as $node) { 3924 if ( $this->is(".$className", $node)) 3925 return true; 3926 } 3927 return false; 3928 } 3929 /** 3930 * Enter description here... 3931 * 3932 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3933 */ 3934 public function removeClass($className) { 3935 foreach($this->stack(1) as $node) { 3936 $classes = explode( ' ', $node->getAttribute('class')); 3937 if ( in_array($className, $classes)) { 3938 $classes = array_diff($classes, array($className)); 3939 if ( $classes ) 3940 $node->setAttribute('class', implode(' ', $classes)); 3941 else 3942 $node->removeAttribute('class'); 3943 } 3944 } 3945 return $this; 3946 } 3947 /** 3948 * Enter description here... 3949 * 3950 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3951 */ 3952 public function toggleClass($className) { 3953 foreach($this->stack(1) as $node) { 3954 if ( $this->is( $node, '.'.$className )) 3955 $this->removeClass($className); 3956 else 3957 $this->addClass($className); 3958 } 3959 return $this; 3960 } 3961 /** 3962 * Proper name without underscore (just ->empty()) also works. 3963 * 3964 * Removes all child nodes from the set of matched elements. 3965 * 3966