[ 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 * Example: 3967 * pq("p")._empty() 3968 * 3969 * HTML: 3970 * <p>Hello, <span>Person</span> <a href="#">and person</a></p> 3971 * 3972 * Result: 3973 * [ <p></p> ] 3974 * 3975 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3976 * @access private 3977 */ 3978 public function _empty() { 3979 foreach($this->stack(1) as $node) { 3980 // thx to 'dave at dgx dot cz' 3981 $node->nodeValue = ''; 3982 } 3983 return $this; 3984 } 3985 /** 3986 * Enter description here... 3987 * 3988 * @param array|string $callback Expects $node as first param, $index as second 3989 * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) 3990 * @param array $arg1 Will ba passed as third and futher args to callback. 3991 * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... 3992 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3993 */ 3994 public function each($callback, $param1 = null, $param2 = null, $param3 = null) { 3995 $paramStructure = null; 3996 if (func_num_args() > 1) { 3997 $paramStructure = func_get_args(); 3998 $paramStructure = array_slice($paramStructure, 1); 3999 } 4000 foreach($this->elements as $v) 4001 phpQuery::callbackRun($callback, array($v), $paramStructure); 4002 return $this; 4003 } 4004 /** 4005 * Run callback on actual object. 4006 * 4007 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4008 */ 4009 public function callback($callback, $param1 = null, $param2 = null, $param3 = null) { 4010 $params = func_get_args(); 4011 $params[0] = $this; 4012 phpQuery::callbackRun($callback, $params); 4013 return $this; 4014 } 4015 /** 4016 * Enter description here... 4017 * 4018 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4019 * @todo add $scope and $args as in each() ??? 4020 */ 4021 public function map($callback, $param1 = null, $param2 = null, $param3 = null) { 4022 // $stack = array(); 4023 //// foreach($this->newInstance() as $node) { 4024 // foreach($this->newInstance() as $node) { 4025 // $result = call_user_func($callback, $node); 4026 // if ($result) 4027 // $stack[] = $result; 4028 // } 4029 $params = func_get_args(); 4030 array_unshift($params, $this->elements); 4031 return $this->newInstance( 4032 call_user_func_array(array('phpQuery', 'map'), $params) 4033 // phpQuery::map($this->elements, $callback) 4034 ); 4035 } 4036 /** 4037 * Enter description here... 4038 * 4039 * @param <type> $key 4040 * @param <type> $value 4041 */ 4042 public function data($key, $value = null) { 4043 if (! isset($value)) { 4044 // TODO? implement specific jQuery behavior od returning parent values 4045 // is child which we look up doesn't exist 4046 return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID()); 4047 } else { 4048 foreach($this as $node) 4049 phpQuery::data($node, $key, $value, $this->getDocumentID()); 4050 return $this; 4051 } 4052 } 4053 /** 4054 * Enter description here... 4055 * 4056 * @param <type> $key 4057 */ 4058 public function removeData($key) { 4059 foreach($this as $node) 4060 phpQuery::removeData($node, $key, $this->getDocumentID()); 4061 return $this; 4062 } 4063 // INTERFACE IMPLEMENTATIONS 4064 4065 // ITERATOR INTERFACE 4066 /** 4067 * @access private 4068 */ 4069 public function rewind(){ 4070 $this->debug('iterating foreach'); 4071 // phpQuery::selectDocument($this->getDocumentID()); 4072 $this->elementsBackup = $this->elements; 4073 $this->elementsInterator = $this->elements; 4074 $this->valid = isset( $this->elements[0] ) 4075 ? 1 : 0; 4076 // $this->elements = $this->valid 4077 // ? array($this->elements[0]) 4078 // : array(); 4079 $this->current = 0; 4080 } 4081 /** 4082 * @access private 4083 */ 4084 public function current(){ 4085 return $this->elementsInterator[ $this->current ]; 4086 } 4087 /** 4088 * @access private 4089 */ 4090 public function key(){ 4091 return $this->current; 4092 } 4093 /** 4094 * Double-function method. 4095 * 4096 * First: main iterator interface method. 4097 * Second: Returning next sibling, alias for _next(). 4098 * 4099 * Proper functionality is choosed automagicaly. 4100 * 4101 * @see phpQueryObject::_next() 4102 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4103 */ 4104 public function next($cssSelector = null){ 4105 // if ($cssSelector || $this->valid) 4106 // return $this->_next($cssSelector); 4107 $this->valid = isset( $this->elementsInterator[ $this->current+1 ] ) 4108 ? true 4109 : false; 4110 if (! $this->valid && $this->elementsInterator) { 4111 $this->elementsInterator = null; 4112 } else if ($this->valid) { 4113 $this->current++; 4114 } else { 4115 return $this->_next($cssSelector); 4116 } 4117 } 4118 /** 4119 * @access private 4120 */ 4121 public function valid(){ 4122 return $this->valid; 4123 } 4124 // ITERATOR INTERFACE END 4125 // ARRAYACCESS INTERFACE 4126 /** 4127 * @access private 4128 */ 4129 public function offsetExists($offset) { 4130 return $this->find($offset)->size() > 0; 4131 } 4132 /** 4133 * @access private 4134 */ 4135 public function offsetGet($offset) { 4136 return $this->find($offset); 4137 } 4138 /** 4139 * @access private 4140 */ 4141 public function offsetSet($offset, $value) { 4142 // $this->find($offset)->replaceWith($value); 4143 $this->find($offset)->html($value); 4144 } 4145 /** 4146 * @access private 4147 */ 4148 public function offsetUnset($offset) { 4149 // empty 4150 throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML."); 4151 } 4152 // ARRAYACCESS INTERFACE END 4153 /** 4154 * Returns node's XPath. 4155 * 4156 * @param unknown_type $oneNode 4157 * @return string 4158 * @TODO use native getNodePath is avaible 4159 * @access private 4160 */ 4161 protected function getNodeXpath($oneNode = null, $namespace = null) { 4162 $return = array(); 4163 $loop = $oneNode 4164 ? array($oneNode) 4165 : $this->elements; 4166 // if ($namespace) 4167 // $namespace .= ':'; 4168 foreach($loop as $node) { 4169 if ($node instanceof DOMDOCUMENT) { 4170 $return[] = ''; 4171 continue; 4172 } 4173 $xpath = array(); 4174 while(! ($node instanceof DOMDOCUMENT)) { 4175 $i = 1; 4176 $sibling = $node; 4177 while($sibling->previousSibling) { 4178 $sibling = $sibling->previousSibling; 4179 $isElement = $sibling instanceof DOMELEMENT; 4180 if ($isElement && $sibling->tagName == $node->tagName) 4181 $i++; 4182 } 4183 $xpath[] = $this->isXML() 4184 ? "*[local-name()='{$node->tagName}'][{$i}]" 4185 : "{$node->tagName}[{$i}]"; 4186 $node = $node->parentNode; 4187 } 4188 $xpath = join('/', array_reverse($xpath)); 4189 $return[] = '/'.$xpath; 4190 } 4191 return $oneNode 4192 ? $return[0] 4193 : $return; 4194 } 4195 // HELPERS 4196 public function whois($oneNode = null) { 4197 $return = array(); 4198 $loop = $oneNode 4199 ? array( $oneNode ) 4200 : $this->elements; 4201 foreach($loop as $node) { 4202 if (isset($node->tagName)) { 4203 $tag = in_array($node->tagName, array('php', 'js')) 4204 ? strtoupper($node->tagName) 4205 : $node->tagName; 4206 $return[] = $tag 4207 .($node->getAttribute('id') 4208 ? '#'.$node->getAttribute('id'):'') 4209 .($node->getAttribute('class') 4210 ? '.'.join('.', explode(' ', $node->getAttribute('class'))):'') 4211 .($node->getAttribute('name') 4212 ? '[name="'.$node->getAttribute('name').'"]':'') 4213 .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false 4214 ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'') 4215 .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false 4216 ? '[value=PHP]':'') 4217 .($node->getAttribute('selected') 4218 ? '[selected]':'') 4219 .($node->getAttribute('checked') 4220 ? '[checked]':'') 4221 ; 4222 } else if ($node instanceof DOMTEXT) { 4223 if (trim($node->textContent)) 4224 $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15); 4225 } else { 4226 4227 } 4228 } 4229 return $oneNode && isset($return[0]) 4230 ? $return[0] 4231 : $return; 4232 } 4233 /** 4234 * Dump htmlOuter and preserve chain. Usefull for debugging. 4235 * 4236 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4237 * 4238 */ 4239 public function dump() { 4240 print 'DUMP #'.(phpQuery::$dumpCount++).' '; 4241 $debug = phpQuery::$debug; 4242 phpQuery::$debug = false; 4243 // print __FILE__.':'.__LINE__."\n"; 4244 var_dump($this->htmlOuter()); 4245 return $this; 4246 } 4247 public function dumpWhois() { 4248 print 'DUMP #'.(phpQuery::$dumpCount++).' '; 4249 $debug = phpQuery::$debug; 4250 phpQuery::$debug = false; 4251 // print __FILE__.':'.__LINE__."\n"; 4252 var_dump('whois', $this->whois()); 4253 phpQuery::$debug = $debug; 4254 return $this; 4255 } 4256 public function dumpLength() { 4257 print 'DUMP #'.(phpQuery::$dumpCount++).' '; 4258 $debug = phpQuery::$debug; 4259 phpQuery::$debug = false; 4260 // print __FILE__.':'.__LINE__."\n"; 4261 var_dump('length', $this->length()); 4262 phpQuery::$debug = $debug; 4263 return $this; 4264 } 4265 public function dumpTree($html = true, $title = true) { 4266 $output = $title 4267 ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : ''; 4268 $debug = phpQuery::$debug; 4269 phpQuery::$debug = false; 4270 foreach($this->stack() as $node) 4271 $output .= $this->dumpTreeInternal($node); 4272 phpQuery::$debug = $debug; 4273 print $html 4274 ? nl2br(str_replace(' ', ' ', $output)) 4275 : $output; 4276 return $this; 4277 } 4278 private function dumpTreeInternal($node, $intend = 0) { 4279 $whois = $this->whois($node); 4280 $return = ''; 4281 if ($whois) 4282 $return .= str_repeat(' - ', $intend).$whois."\n"; 4283 if (isset($node->childNodes)) 4284 foreach($node->childNodes as $chNode) 4285 $return .= $this->dumpTreeInternal($chNode, $intend+1); 4286 return $return; 4287 } 4288 /** 4289 * Dump htmlOuter and stop script execution. Usefull for debugging. 4290 * 4291 */ 4292 public function dumpDie() { 4293 print __FILE__.':'.__LINE__; 4294 var_dump($this->htmlOuter()); 4295 die(); 4296 } 4297 } 4298 4299 4300 // -- Multibyte Compatibility functions --------------------------------------- 4301 // http://svn.iphonewebdev.com/lace/lib/mb_compat.php 4302 4303 /** 4304 * mb_internal_encoding() 4305 * 4306 * Included for mbstring pseudo-compatability. 4307 */ 4308 if (!function_exists('mb_internal_encoding')) 4309 { 4310 function mb_internal_encoding($enc) {return true; } 4311 } 4312 4313 /** 4314 * mb_regex_encoding() 4315 * 4316 * Included for mbstring pseudo-compatability. 4317 */ 4318 if (!function_exists('mb_regex_encoding')) 4319 { 4320 function mb_regex_encoding($enc) {return true; } 4321 } 4322 4323 /** 4324 * mb_strlen() 4325 * 4326 * Included for mbstring pseudo-compatability. 4327 */ 4328 if (!function_exists('mb_strlen')) 4329 { 4330 function mb_strlen($str) 4331 { 4332 return strlen($str); 4333 } 4334 } 4335 4336 /** 4337 * mb_strpos() 4338 * 4339 * Included for mbstring pseudo-compatability. 4340 */ 4341 if (!function_exists('mb_strpos')) 4342 { 4343 function mb_strpos($haystack, $needle, $offset=0) 4344 { 4345 return strpos($haystack, $needle, $offset); 4346 } 4347 } 4348 /** 4349 * mb_stripos() 4350 * 4351 * Included for mbstring pseudo-compatability. 4352 */ 4353 if (!function_exists('mb_stripos')) 4354 { 4355 function mb_stripos($haystack, $needle, $offset=0) 4356 { 4357 return stripos($haystack, $needle, $offset); 4358 } 4359 } 4360 4361 /** 4362 * mb_substr() 4363 * 4364 * Included for mbstring pseudo-compatability. 4365 */ 4366 if (!function_exists('mb_substr')) 4367 { 4368 function mb_substr($str, $start, $length=0) 4369 { 4370 return substr($str, $start, $length); 4371 } 4372 } 4373 4374 /** 4375 * mb_substr_count() 4376 * 4377 * Included for mbstring pseudo-compatability. 4378 */ 4379 if (!function_exists('mb_substr_count')) 4380 { 4381 function mb_substr_count($haystack, $needle) 4382 { 4383 return substr_count($haystack, $needle); 4384 } 4385 } 4386 4387 4388 /** 4389 * Static namespace for phpQuery functions. 4390 * 4391 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 4392 * @package phpQuery 4393 */ 4394 abstract class phpQuery { 4395 /** 4396 * XXX: Workaround for mbstring problems 4397 * 4398 * @var bool 4399 */ 4400 public static $mbstringSupport = true; 4401 public static $debug = false; 4402 public static $documents = array(); 4403 public static $defaultDocumentID = null; 4404 // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'; 4405 /** 4406 * Applies only to HTML. 4407 * 4408 * @var unknown_type 4409 */ 4410 public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 4411 "http://www.w3.org/TR/html4/loose.dtd">'; 4412 public static $defaultCharset = 'UTF-8'; 4413 /** 4414 * Static namespace for plugins. 4415 * 4416 * @var object 4417 */ 4418 public static $plugins = array(); 4419 /** 4420 * List of loaded plugins. 4421 * 4422 * @var unknown_type 4423 */ 4424 public static $pluginsLoaded = array(); 4425 public static $pluginsMethods = array(); 4426 public static $pluginsStaticMethods = array(); 4427 public static $extendMethods = array(); 4428 /** 4429 * @TODO implement 4430 */ 4431 public static $extendStaticMethods = array(); 4432 /** 4433 * Hosts allowed for AJAX connections. 4434 * Dot '.' means $_SERVER['HTTP_HOST'] (if any). 4435 * 4436 * @var array 4437 */ 4438 public static $ajaxAllowedHosts = array( 4439 '.' 4440 ); 4441 /** 4442 * AJAX settings. 4443 * 4444 * @var array 4445 * XXX should it be static or not ? 4446 */ 4447 public static $ajaxSettings = array( 4448 'url' => '',//TODO 4449 'global' => true, 4450 'type' => "GET", 4451 'timeout' => null, 4452 'contentType' => "application/x-www-form-urlencoded", 4453 'processData' => true, 4454 // 'async' => true, 4455 'data' => null, 4456 'username' => null, 4457 'password' => null, 4458 'accepts' => array( 4459 'xml' => "application/xml, text/xml", 4460 'html' => "text/html", 4461 'script' => "text/javascript, application/javascript", 4462 'json' => "application/json, text/javascript", 4463 'text' => "text/plain", 4464 '_default' => "*/*" 4465 ) 4466 ); 4467 public static $lastModified = null; 4468 public static $active = 0; 4469 public static $dumpCount = 0; 4470 /** 4471 * Multi-purpose function. 4472 * Use pq() as shortcut. 4473 * 4474 * In below examples, $pq is any result of pq(); function. 4475 * 4476 * 1. Import markup into existing document (without any attaching): 4477 * - Import into selected document: 4478 * pq('<div/>') // DOESNT accept text nodes at beginning of input string ! 4479 * - Import into document with ID from $pq->getDocumentID(): 4480 * pq('<div/>', $pq->getDocumentID()) 4481 * - Import into same document as DOMNode belongs to: 4482 * pq('<div/>', DOMNode) 4483 * - Import into document from phpQuery object: 4484 * pq('<div/>', $pq) 4485 * 4486 * 2. Run query: 4487 * - Run query on last selected document: 4488 * pq('div.myClass') 4489 * - Run query on document with ID from $pq->getDocumentID(): 4490 * pq('div.myClass', $pq->getDocumentID()) 4491 * - Run query on same document as DOMNode belongs to and use node(s)as root for query: 4492 * pq('div.myClass', DOMNode) 4493 * - Run query on document from phpQuery object 4494 * and use object's stack as root node(s) for query: 4495 * pq('div.myClass', $pq) 4496 * 4497 * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes 4498 * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root) 4499 * 4500 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false 4501 * phpQuery object or false in case of error. 4502 */ 4503 public static function pq($arg1, $context = null) { 4504 if ($arg1 instanceof DOMNODE && ! isset($context)) { 4505 foreach(phpQuery::$documents as $documentWrapper) { 4506 $compare = $arg1 instanceof DOMDocument 4507 ? $arg1 : $arg1->ownerDocument; 4508 if ($documentWrapper->document->isSameNode($compare)) 4509 $context = $documentWrapper->id; 4510 } 4511 } 4512 if (! $context) { 4513 $domId = self::$defaultDocumentID; 4514 if (! $domId) 4515 throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first."); 4516 // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) 4517 } else if (is_object($context) && $context instanceof phpQueryObject) 4518 $domId = $context->getDocumentID(); 4519 else if ($context instanceof DOMDOCUMENT) { 4520 $domId = self::getDocumentID($context); 4521 if (! $domId) { 4522 //throw new Exception('Orphaned DOMDocument'); 4523 $domId = self::newDocument($context)->getDocumentID(); 4524 } 4525 } else if ($context instanceof DOMNODE) { 4526 $domId = self::getDocumentID($context); 4527 if (! $domId) { 4528 throw new Exception('Orphaned DOMNode'); 4529 // $domId = self::newDocument($context->ownerDocument); 4530 } 4531 } else 4532 $domId = $context; 4533 if ($arg1 instanceof phpQueryObject) { 4534 // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) { 4535 /** 4536 * Return $arg1 or import $arg1 stack if document differs: 4537 * pq(pq('<div/>')) 4538 */ 4539 if ($arg1->getDocumentID() == $domId) 4540 return $arg1; 4541 $class = get_class($arg1); 4542 // support inheritance by passing old object to overloaded constructor 4543 $phpQuery = $class != 'phpQuery' 4544 ? new $class($arg1, $domId) 4545 : new phpQueryObject($domId); 4546 $phpQuery->elements = array(); 4547 foreach($arg1->elements as $node) 4548 $phpQuery->elements[] = $phpQuery->document->importNode($node, true); 4549 return $phpQuery; 4550 } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) { 4551 /* 4552 * Wrap DOM nodes with phpQuery object, import into document when needed: 4553 * pq(array($domNode1, $domNode2)) 4554 */ 4555 $phpQuery = new phpQueryObject($domId); 4556 if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1)) 4557 $arg1 = array($arg1); 4558 $phpQuery->elements = array(); 4559 foreach($arg1 as $node) { 4560 $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT 4561 && ! $node->ownerDocument->isSameNode($phpQuery->document); 4562 $phpQuery->elements[] = $sameDocument 4563 ? $phpQuery->document->importNode($node, true) 4564 : $node; 4565 } 4566 return $phpQuery; 4567 } else if (self::isMarkup($arg1)) { 4568 /** 4569 * Import HTML: 4570 * pq('<div/>') 4571 */ 4572 $phpQuery = new phpQueryObject($domId); 4573 return $phpQuery->newInstance( 4574 $phpQuery->documentWrapper->import($arg1) 4575 ); 4576 } else { 4577 /** 4578 * Run CSS query: 4579 * pq('div.myClass') 4580 */ 4581 $phpQuery = new phpQueryObject($domId); 4582 // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) 4583 if ($context && $context instanceof phpQueryObject) 4584 $phpQuery->elements = $context->elements; 4585 else if ($context && $context instanceof DOMNODELIST) { 4586 $phpQuery->elements = array(); 4587 foreach($context as $node) 4588 $phpQuery->elements[] = $node; 4589 } else if ($context && $context instanceof DOMNODE) 4590 $phpQuery->elements = array($context); 4591 return $phpQuery->find($arg1); 4592 } 4593 } 4594 /** 4595 * Sets default document to $id. Document has to be loaded prior 4596 * to using this method. 4597 * $id can be retrived via getDocumentID() or getDocumentIDRef(). 4598 * 4599 * @param unknown_type $id 4600 */ 4601 public static function selectDocument($id) { 4602 $id = self::getDocumentID($id); 4603 self::debug("Selecting document '$id' as default one"); 4604 self::$defaultDocumentID = self::getDocumentID($id); 4605 } 4606 /** 4607 * Returns document with id $id or last used as phpQueryObject. 4608 * $id can be retrived via getDocumentID() or getDocumentIDRef(). 4609 * Chainable. 4610 * 4611 * @see phpQuery::selectDocument() 4612 * @param unknown_type $id 4613 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4614 */ 4615 public static function getDocument($id = null) { 4616 if ($id) 4617 phpQuery::selectDocument($id); 4618 else 4619 $id = phpQuery::$defaultDocumentID; 4620 return new phpQueryObject($id); 4621 } 4622 /** 4623 * Creates new document from markup. 4624 * Chainable. 4625 * 4626 * @deprecated Use \DOMWrap\Document instead 4627 * @param unknown_type $markup 4628 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4629 */ 4630 public static function newDocument($markup = null, $contentType = null) { 4631 if (! $markup) 4632 $markup = ''; 4633 $documentID = phpQuery::createDocumentWrapper($markup, $contentType); 4634 return new phpQueryObject($documentID); 4635 } 4636 /** 4637 * Creates new document from markup. 4638 * Chainable. 4639 * 4640 * @param unknown_type $markup 4641 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4642 */ 4643 public static function newDocumentHTML($markup = null, $charset = null) { 4644 $contentType = $charset 4645 ? ";charset=$charset" 4646 : ''; 4647 return self::newDocument($markup, "text/html{$contentType}"); 4648 } 4649 /** 4650 * Creates new document from markup. 4651 * Chainable. 4652 * 4653 * @param unknown_type $markup 4654 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4655 */ 4656 public static function newDocumentXML($markup = null, $charset = null) { 4657 $contentType = $charset 4658 ? ";charset=$charset" 4659 : ''; 4660 return self::newDocument($markup, "text/xml{$contentType}"); 4661 } 4662 /** 4663 * Creates new document from markup. 4664 * Chainable. 4665 * 4666 * @param unknown_type $markup 4667 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4668 */ 4669 public static function newDocumentXHTML($markup = null, $charset = null) { 4670 $contentType = $charset 4671 ? ";charset=$charset" 4672 : ''; 4673 return self::newDocument($markup, "application/xhtml+xml{$contentType}"); 4674 } 4675 /** 4676 * Creates new document from markup. 4677 * Chainable. 4678 * 4679 * @param unknown_type $markup 4680 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4681 */ 4682 public static function newDocumentPHP($markup = null, $contentType = "text/html") { 4683 // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function) 4684 $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset); 4685 return self::newDocument($markup, $contentType); 4686 } 4687 public static function phpToMarkup($php, $charset = 'utf-8') { 4688 $regexes = array( 4689 '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s', 4690 '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s', 4691 ); 4692 foreach($regexes as $regex) 4693 while (preg_match($regex, $php, $matches)) { 4694 $php = preg_replace_callback( 4695 $regex, 4696 // create_function('$m, $charset = "'.$charset.'"', 4697 // 'return $m[1].$m[2] 4698 // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) 4699 // .$m[5].$m[2];' 4700 // ), 4701 array('phpQuery', '_phpToMarkupCallback'), 4702 $php 4703 ); 4704 } 4705 $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s'; 4706 //preg_match_all($regex, $php, $matches); 4707 //var_dump($matches); 4708 $php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php); 4709 return $php; 4710 } 4711 public static function _phpToMarkupCallback($php, $charset = 'utf-8') { 4712 return $m[1].$m[2] 4713 .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) 4714 .$m[5].$m[2]; 4715 } 4716 public static function _markupToPHPCallback($m) { 4717 return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">"; 4718 } 4719 /** 4720 * Converts document markup containing PHP code generated by phpQuery::php() 4721 * into valid (executable) PHP code syntax. 4722 * 4723 * @param string|phpQueryObject $content 4724 * @return string PHP code. 4725 */ 4726 public static function markupToPHP($content) { 4727 if ($content instanceof phpQueryObject) 4728 $content = $content->markupOuter(); 4729 /* <php>...</php> to <?php...? > */ 4730 $content = preg_replace_callback( 4731 '@<php>\s*<!--(.*?)-->\s*</php>@s', 4732 // create_function('$m', 4733 // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";' 4734 // ), 4735 array('phpQuery', '_markupToPHPCallback'), 4736 $content 4737 ); 4738 /* <node attr='< ?php ? >'> extra space added to save highlighters */ 4739 $regexes = array( 4740 '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^\']*)\'@s', 4741 '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^"]*)"@s', 4742 ); 4743 foreach($regexes as $regex) 4744 while (preg_match($regex, $content)) 4745 $content = preg_replace_callback( 4746 $regex, 4747 function ($m) { 4748 return $m[1] . $m[2] . $m[3] . '<?php ' 4749 . str_replace( 4750 array("%20", "%3E", "%09", " ", "	", "%7B", "%24", "%7D", "%22", "%5B", "%5D"), 4751 array(" ", ">", " ", "\n", " ", "{", "$", "}", '"', "[", "]"), 4752 htmlspecialchars_decode($m[4]) 4753 ) 4754 . " ?>" . $m[5] . $m[2]; 4755 }, 4756 $content 4757 ); 4758 return $content; 4759 } 4760 /** 4761 * Creates new document from file $file. 4762 * Chainable. 4763 * 4764 * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources. 4765 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4766 */ 4767 public static function newDocumentFile($file, $contentType = null) { 4768 $documentID = self::createDocumentWrapper( 4769 file_get_contents($file), $contentType 4770 ); 4771 return new phpQueryObject($documentID); 4772 } 4773 /** 4774 * Creates new document from markup. 4775 * Chainable. 4776 * 4777 * @param unknown_type $markup 4778 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4779 */ 4780 public static function newDocumentFileHTML($file, $charset = null) { 4781 $contentType = $charset 4782 ? ";charset=$charset" 4783 : ''; 4784 return self::newDocumentFile($file, "text/html{$contentType}"); 4785 } 4786 /** 4787 * Creates new document from markup. 4788 * Chainable. 4789 * 4790 * @param unknown_type $markup 4791 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4792 */ 4793 public static function newDocumentFileXML($file, $charset = null) { 4794 $contentType = $charset 4795 ? ";charset=$charset" 4796 : ''; 4797 return self::newDocumentFile($file, "text/xml{$contentType}"); 4798 } 4799 /** 4800 * Creates new document from markup. 4801 * Chainable. 4802 * 4803 * @param unknown_type $markup 4804 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4805 */ 4806 public static function newDocumentFileXHTML($file, $charset = null) { 4807 $contentType = $charset 4808 ? ";charset=$charset" 4809 : ''; 4810 return self::newDocumentFile($file, "application/xhtml+xml{$contentType}"); 4811 } 4812 /** 4813 * Creates new document from markup. 4814 * Chainable. 4815 * 4816 * @param unknown_type $markup 4817 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4818 */ 4819 public static function newDocumentFilePHP($file, $contentType = null) { 4820 return self::newDocumentPHP(file_get_contents($file), $contentType); 4821 } 4822 /** 4823 * Reuses existing DOMDocument object. 4824 * Chainable. 4825 * 4826 * @param $document DOMDocument 4827 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4828 * @TODO support DOMDocument 4829 */ 4830 public static function loadDocument($document) { 4831 // TODO 4832 die('TODO loadDocument'); 4833 } 4834 /** 4835 * Enter description here... 4836 * 4837 * @param unknown_type $html 4838 * @param unknown_type $domId 4839 * @return unknown New DOM ID 4840 * @todo support PHP tags in input 4841 * @todo support passing DOMDocument object from self::loadDocument 4842 */ 4843 protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) { 4844 if (function_exists('domxml_open_mem')) 4845 throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled."); 4846 // $id = $documentID 4847 // ? $documentID 4848 // : md5(microtime()); 4849 $document = null; 4850 if ($html instanceof DOMDOCUMENT) { 4851 if (self::getDocumentID($html)) { 4852 // document already exists in phpQuery::$documents, make a copy 4853 $document = clone $html; 4854 } else { 4855 // new document, add it to phpQuery::$documents 4856 $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); 4857 } 4858 } else { 4859 $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); 4860 } 4861 // $wrapper->id = $id; 4862 // bind document 4863 phpQuery::$documents[$wrapper->id] = $wrapper; 4864 // remember last loaded document 4865 phpQuery::selectDocument($wrapper->id); 4866 return $wrapper->id; 4867 } 4868 /** 4869 * Extend class namespace. 4870 * 4871 * @param string|array $target 4872 * @param array $source 4873 * @TODO support string $source 4874 * @return unknown_type 4875 */ 4876 public static function extend($target, $source) { 4877 switch($target) { 4878 case 'phpQueryObject': 4879 $targetRef = &self::$extendMethods; 4880 $targetRef2 = &self::$pluginsMethods; 4881 break; 4882 case 'phpQuery': 4883 $targetRef = &self::$extendStaticMethods; 4884 $targetRef2 = &self::$pluginsStaticMethods; 4885 break; 4886 default: 4887 throw new Exception("Unsupported \$target type"); 4888 } 4889 if (is_string($source)) 4890 $source = array($source => $source); 4891 foreach($source as $method => $callback) { 4892 if (isset($targetRef[$method])) { 4893 // throw new Exception 4894 self::debug("Duplicate method '{$method}', can\'t extend '{$target}'"); 4895 continue; 4896 } 4897 if (isset($targetRef2[$method])) { 4898 // throw new Exception 4899 self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}'," 4900 ." can\'t extend '{$target}'"); 4901 continue; 4902 } 4903 $targetRef[$method] = $callback; 4904 } 4905 return true; 4906 } 4907 /** 4908 * Extend phpQuery with $class from $file. 4909 * 4910 * @param string $class Extending class name. Real class name can be prepended phpQuery_. 4911 * @param string $file Filename to include. Defaults to "{$class}.php". 4912 */ 4913 public static function plugin($class, $file = null) { 4914 // TODO $class checked agains phpQuery_$class 4915 // if (strpos($class, 'phpQuery') === 0) 4916 // $class = substr($class, 8); 4917 if (in_array($class, self::$pluginsLoaded)) 4918 return true; 4919 if (! $file) 4920 $file = $class.'.php'; 4921 $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class); 4922 $staticClassExists = class_exists('phpQueryPlugin_'.$class); 4923 if (! $objectClassExists && ! $staticClassExists) 4924 require_once($file); 4925 self::$pluginsLoaded[] = $class; 4926 // static methods 4927 if (class_exists('phpQueryPlugin_'.$class)) { 4928 $realClass = 'phpQueryPlugin_'.$class; 4929 $vars = get_class_vars($realClass); 4930 $loop = isset($vars['phpQueryMethods']) 4931 && ! is_null($vars['phpQueryMethods']) 4932 ? $vars['phpQueryMethods'] 4933 : get_class_methods($realClass); 4934 foreach($loop as $method) { 4935 if ($method == '__initialize') 4936 continue; 4937 if (! is_callable(array($realClass, $method))) 4938 continue; 4939 if (isset(self::$pluginsStaticMethods[$method])) { 4940 throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'"); 4941 return; 4942 } 4943 self::$pluginsStaticMethods[$method] = $class; 4944 } 4945 if (method_exists($realClass, '__initialize')) 4946 call_user_func_array(array($realClass, '__initialize'), array()); 4947 } 4948 // object methods 4949 if (class_exists('phpQueryObjectPlugin_'.$class)) { 4950 $realClass = 'phpQueryObjectPlugin_'.$class; 4951 $vars = get_class_vars($realClass); 4952 $loop = isset($vars['phpQueryMethods']) 4953 && ! is_null($vars['phpQueryMethods']) 4954 ? $vars['phpQueryMethods'] 4955 : get_class_methods($realClass); 4956 foreach($loop as $method) { 4957 if (! is_callable(array($realClass, $method))) 4958 continue; 4959 if (isset(self::$pluginsMethods[$method])) { 4960 throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'"); 4961 continue; 4962 } 4963 self::$pluginsMethods[$method] = $class; 4964 } 4965 } 4966 return true; 4967 } 4968 /** 4969 * Unloades all or specified document from memory. 4970 * 4971 * @param mixed $documentID @see phpQuery::getDocumentID() for supported types. 4972 */ 4973 public static function unloadDocuments($id = null) { 4974 if (isset($id)) { 4975 if ($id = self::getDocumentID($id)) 4976 unset(phpQuery::$documents[$id]); 4977 } else { 4978 foreach(phpQuery::$documents as $k => $v) { 4979 unset(phpQuery::$documents[$k]); 4980 } 4981 } 4982 } 4983 /** 4984 * Parses phpQuery object or HTML result against PHP tags and makes them active. 4985 * 4986 * @param phpQuery|string $content 4987 * @deprecated 4988 * @return string 4989 */ 4990 public static function unsafePHPTags($content) { 4991 return self::markupToPHP($content); 4992 } 4993 public static function DOMNodeListToArray($DOMNodeList) { 4994 $array = array(); 4995 if (! $DOMNodeList) 4996 return $array; 4997 foreach($DOMNodeList as $node) 4998 $array[] = $node; 4999 return $array; 5000 } 5001 /** 5002 * Checks if $input is HTML string, which has to start with '<'. 5003 * 5004 * @deprecated 5005 * @param String $input 5006 * @return Bool 5007 * @todo still used ? 5008 */ 5009 public static function isMarkup($input) { 5010 return ! is_array($input) && substr(trim($input), 0, 1) == '<'; 5011 } 5012 public static function debug($text) { 5013 if (self::$debug) 5014 print var_dump($text); 5015 } 5016 /** 5017 * Make an AJAX request. 5018 * 5019 * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions 5020 * Additional options are: 5021 * 'document' - document for global events, @see phpQuery::getDocumentID() 5022 * 'referer' - implemented 5023 * 'requested_with' - TODO; not implemented (X-Requested-With) 5024 * @return Zend_Http_Client 5025 * @link http://docs.jquery.com/Ajax/jQuery.ajax 5026 * 5027 * @TODO $options['cache'] 5028 * @TODO $options['processData'] 5029 * @TODO $options['xhr'] 5030 * @TODO $options['data'] as string 5031 * @TODO XHR interface 5032 */ 5033 public static function ajax($options = array(), $xhr = null) { 5034 $options = array_merge( 5035 self::$ajaxSettings, $options 5036 ); 5037 $documentID = isset($options['document']) 5038 ? self::getDocumentID($options['document']) 5039 : null; 5040 if ($xhr) { 5041 // reuse existing XHR object, but clean it up 5042 $client = $xhr; 5043 // $client->setParameterPost(null); 5044 // $client->setParameterGet(null); 5045 $client->setAuth(false); 5046 $client->setHeaders("If-Modified-Since", null); 5047 $client->setHeaders("Referer", null); 5048 $client->resetParameters(); 5049 } else { 5050 // create new XHR object 5051 require_once('Zend/Http/Client.php'); 5052 $client = new Zend_Http_Client(); 5053 $client->setCookieJar(); 5054 } 5055 if (isset($options['timeout'])) 5056 $client->setConfig(array( 5057 'timeout' => $options['timeout'], 5058 )); 5059 // 'maxredirects' => 0, 5060 foreach(self::$ajaxAllowedHosts as $k => $host) 5061 if ($host == '.' && isset($_SERVER['HTTP_HOST'])) 5062 self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST']; 5063 $host = parse_url($options['url'], PHP_URL_HOST); 5064 if (! in_array($host, self::$ajaxAllowedHosts)) { 5065 throw new Exception("Request not permitted, host '$host' not present in " 5066 ."phpQuery::\$ajaxAllowedHosts"); 5067 } 5068 // JSONP 5069 $jsre = "/=\\?(&|$)/"; 5070 if (isset($options['dataType']) && $options['dataType'] == 'jsonp') { 5071 $jsonpCallbackParam = $options['jsonp'] 5072 ? $options['jsonp'] : 'callback'; 5073 if (strtolower($options['type']) == 'get') { 5074 if (! preg_match($jsre, $options['url'])) { 5075 $sep = strpos($options['url'], '?') 5076 ? '&' : '?'; 5077 $options['url'] .= "$sep$jsonpCallbackParam=?"; 5078 } 5079 } else if ($options['data']) { 5080 $jsonp = false; 5081 foreach($options['data'] as $n => $v) { 5082 if ($v == '?') 5083 $jsonp = true; 5084 } 5085 if (! $jsonp) { 5086 $options['data'][$jsonpCallbackParam] = '?'; 5087 } 5088 } 5089 $options['dataType'] = 'json'; 5090 } 5091 if (isset($options['dataType']) && $options['dataType'] == 'json') { 5092 $jsonpCallback = 'json_'.md5(microtime()); 5093 $jsonpData = $jsonpUrl = false; 5094 if ($options['data']) { 5095 foreach($options['data'] as $n => $v) { 5096 if ($v == '?') 5097 $jsonpData = $n; 5098 } 5099 } 5100 if (preg_match($jsre, $options['url'])) 5101 $jsonpUrl = true; 5102 if ($jsonpData !== false || $jsonpUrl) { 5103 // remember callback name for httpData() 5104 $options['_jsonp'] = $jsonpCallback; 5105 if ($jsonpData !== false) 5106 $options['data'][$jsonpData] = $jsonpCallback; 5107 if ($jsonpUrl) 5108 $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']); 5109 } 5110 } 5111 $client->setUri($options['url']); 5112 $client->setMethod(strtoupper($options['type'])); 5113 if (isset($options['referer']) && $options['referer']) 5114 $client->setHeaders('Referer', $options['referer']); 5115 $client->setHeaders(array( 5116 // 'content-type' => $options['contentType'], 5117 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko' 5118 .'/2008122010 Firefox/3.0.5', 5119 // TODO custom charset 5120 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 5121 // 'Connection' => 'keep-alive', 5122 // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 5123 'Accept-Language' => 'en-us,en;q=0.5', 5124 )); 5125 if ($options['username']) 5126 $client->setAuth($options['username'], $options['password']); 5127 if (isset($options['ifModified']) && $options['ifModified']) 5128 $client->setHeaders("If-Modified-Since", 5129 self::$lastModified 5130 ? self::$lastModified 5131 : "Thu, 01 Jan 1970 00:00:00 GMT" 5132 ); 5133 $client->setHeaders("Accept", 5134 isset($options['dataType']) 5135 && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ]) 5136 ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*" 5137 : self::$ajaxSettings['accepts']['_default'] 5138 ); 5139 // TODO $options['processData'] 5140 if ($options['data'] instanceof phpQueryObject) { 5141 $serialized = $options['data']->serializeArray($options['data']); 5142 $options['data'] = array(); 5143 foreach($serialized as $r) 5144 $options['data'][ $r['name'] ] = $r['value']; 5145 } 5146 if (strtolower($options['type']) == 'get') { 5147 $client->setParameterGet($options['data']); 5148 } else if (strtolower($options['type']) == 'post') { 5149 $client->setEncType($options['contentType']); 5150 $client->setParameterPost($options['data']); 5151 } 5152 if (self::$active == 0 && $options['global']) 5153 phpQueryEvents::trigger($documentID, 'ajaxStart'); 5154 self::$active++; 5155 // beforeSend callback 5156 if (isset($options['beforeSend']) && $options['beforeSend']) 5157 phpQuery::callbackRun($options['beforeSend'], array($client)); 5158 // ajaxSend event 5159 if ($options['global']) 5160 phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options)); 5161 if (phpQuery::$debug) { 5162 self::debug("{$options['type']}: {$options['url']}\n"); 5163 self::debug("Options: <pre>".var_export($options, true)."</pre>\n"); 5164 // if ($client->getCookieJar()) 5165 // self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n"); 5166 } 5167 // request 5168 $response = $client->request(); 5169 if (phpQuery::$debug) { 5170 self::debug('Status: '.$response->getStatus().' / '.$response->getMessage()); 5171 self::debug($client->getLastRequest()); 5172 self::debug($response->getHeaders()); 5173 } 5174 if ($response->isSuccessful()) { 5175 // XXX tempolary 5176 self::$lastModified = $response->getHeader('Last-Modified'); 5177 $data = self::httpData($response->getBody(), $options['dataType'], $options); 5178 if (isset($options['success']) && $options['success']) 5179 phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options)); 5180 if ($options['global']) 5181 phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options)); 5182 } else { 5183 if (isset($options['error']) && $options['error']) 5184 phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage())); 5185 if ($options['global']) 5186 phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options)); 5187 } 5188 if (isset($options['complete']) && $options['complete']) 5189 phpQuery::callbackRun($options['complete'], array($client, $response->getStatus())); 5190 if ($options['global']) 5191 phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options)); 5192 if ($options['global'] && ! --self::$active) 5193 phpQueryEvents::trigger($documentID, 'ajaxStop'); 5194 return $client; 5195 // if (is_null($domId)) 5196 // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false; 5197 // return new phpQueryAjaxResponse($response, $domId); 5198 } 5199 protected static function httpData($data, $type, $options) { 5200 if (isset($options['dataFilter']) && $options['dataFilter']) 5201 $data = self::callbackRun($options['dataFilter'], array($data, $type)); 5202 if (is_string($data)) { 5203 if ($type == "json") { 5204 if (isset($options['_jsonp']) && $options['_jsonp']) { 5205 $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data); 5206 } 5207 $data = self::parseJSON($data); 5208 } 5209 } 5210 return $data; 5211 } 5212 /** 5213 * Enter description here... 5214 * 5215 * @param array|phpQuery $data 5216 * 5217 */ 5218 public static function param($data) { 5219 return http_build_query($data, null, '&'); 5220 } 5221 public static function get($url, $data = null, $callback = null, $type = null) { 5222 if (!is_array($data)) { 5223 $callback = $data; 5224 $data = null; 5225 } 5226 // TODO some array_values on this shit 5227 return phpQuery::ajax(array( 5228 'type' => 'GET', 5229 'url' => $url, 5230 'data' => $data, 5231 'success' => $callback, 5232 'dataType' => $type, 5233 )); 5234 } 5235 public static function post($url, $data = null, $callback = null, $type = null) { 5236 if (!is_array($data)) { 5237 $callback = $data; 5238 $data = null; 5239 } 5240 return phpQuery::ajax(array( 5241 'type' => 'POST', 5242 'url' => $url, 5243 'data' => $data, 5244 'success' => $callback, 5245 'dataType' => $type, 5246 )); 5247 } 5248 public static function getJSON($url, $data = null, $callback = null) { 5249 if (!is_array($data)) { 5250 $callback = $data; 5251 $data = null; 5252 } 5253 // TODO some array_values on this shit 5254 return phpQuery::ajax(array( 5255 'type' => 'GET', 5256 'url' => $url, 5257 'data' => $data, 5258 'success' => $callback, 5259 'dataType' => 'json', 5260 )); 5261 } 5262 public static function ajaxSetup($options) { 5263 self::$ajaxSettings = array_merge( 5264 self::$ajaxSettings, 5265 $options 5266 ); 5267 } 5268 public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) { 5269 $loop = is_array($host1) 5270 ? $host1 5271 : func_get_args(); 5272 foreach($loop as $host) { 5273 if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) { 5274 phpQuery::$ajaxAllowedHosts[] = $host; 5275 } 5276 } 5277 } 5278 public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) { 5279 $loop = is_array($url1) 5280 ? $url1 5281 : func_get_args(); 5282 foreach($loop as $url) 5283 phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST)); 5284 } 5285 /** 5286 * Returns JSON representation of $data. 5287 * 5288 * @static 5289 * @param mixed $data 5290 * @return string 5291 */ 5292 public static function toJSON($data) { 5293 if (function_exists('json_encode')) 5294 return json_encode($data); 5295 require_once('Zend/Json/Encoder.php'); 5296 return Zend_Json_Encoder::encode($data); 5297 } 5298 /** 5299 * Parses JSON into proper PHP type. 5300 * 5301 * @static 5302 * @param string $json 5303 * @return mixed 5304 */ 5305 public static function parseJSON($json) { 5306 if (function_exists('json_decode')) { 5307 $return = json_decode(trim($json), true); 5308 // json_decode and UTF8 issues 5309 if (isset($return)) 5310 return $return; 5311 } 5312 require_once('Zend/Json/Decoder.php'); 5313 return Zend_Json_Decoder::decode($json); 5314 } 5315 /** 5316 * Returns source's document ID. 5317 * 5318 * @param $source DOMNode|phpQueryObject 5319 * @return string 5320 */ 5321 public static function getDocumentID($source) { 5322 if ($source instanceof DOMDOCUMENT) { 5323 foreach(phpQuery::$documents as $id => $document) { 5324 if ($source->isSameNode($document->document)) 5325 return $id; 5326 } 5327 } else if ($source instanceof DOMNODE) { 5328 foreach(phpQuery::$documents as $id => $document) { 5329 if ($source->ownerDocument->isSameNode($document->document)) 5330 return $id; 5331 } 5332 } else if ($source instanceof phpQueryObject) 5333 return $source->getDocumentID(); 5334 else if (is_string($source) && isset(phpQuery::$documents[$source])) 5335 return $source; 5336 } 5337 /** 5338 * Get DOMDocument object related to $source. 5339 * Returns null if such document doesn't exist. 5340 * 5341 * @param $source DOMNode|phpQueryObject|string 5342 * @return string 5343 */ 5344 public static function getDOMDocument($source) { 5345 if ($source instanceof DOMDOCUMENT) 5346 return $source; 5347 $source = self::getDocumentID($source); 5348 return $source 5349 ? self::$documents[$id]['document'] 5350 : null; 5351 } 5352 5353 // UTILITIES 5354 // http://docs.jquery.com/Utilities 5355 5356 /** 5357 * 5358 * @return unknown_type 5359 * @link http://docs.jquery.com/Utilities/jQuery.makeArray 5360 */ 5361 public static function makeArray($obj) { 5362 $array = array(); 5363 if (is_object($object) && $object instanceof DOMNODELIST) { 5364 foreach($object as $value) 5365 $array[] = $value; 5366 } else if (is_object($object) && ! ($object instanceof Iterator)) { 5367 foreach(get_object_vars($object) as $name => $value) 5368 $array[0][$name] = $value; 5369 } else { 5370 foreach($object as $name => $value) 5371 $array[0][$name] = $value; 5372 } 5373 return $array; 5374 } 5375 public static function inArray($value, $array) { 5376 return in_array($value, $array); 5377 } 5378 /** 5379 * 5380 * @param $object 5381 * @param $callback 5382 * @return unknown_type 5383 * @link http://docs.jquery.com/Utilities/jQuery.each 5384 */ 5385 public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) { 5386 $paramStructure = null; 5387 if (func_num_args() > 2) { 5388 $paramStructure = func_get_args(); 5389 $paramStructure = array_slice($paramStructure, 2); 5390 } 5391 if (is_object($object) && ! ($object instanceof Iterator)) { 5392 foreach(get_object_vars($object) as $name => $value) 5393 phpQuery::callbackRun($callback, array($name, $value), $paramStructure); 5394 } else { 5395 foreach($object as $name => $value) 5396 phpQuery::callbackRun($callback, array($name, $value), $paramStructure); 5397 } 5398 } 5399 /** 5400 * 5401 * @link http://docs.jquery.com/Utilities/jQuery.map 5402 */ 5403 public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) { 5404 $result = array(); 5405 $paramStructure = null; 5406 if (func_num_args() > 2) { 5407 $paramStructure = func_get_args(); 5408 $paramStructure = array_slice($paramStructure, 2); 5409 } 5410 foreach($array as $v) { 5411 $vv = phpQuery::callbackRun($callback, array($v), $paramStructure); 5412 // $callbackArgs = $args; 5413 // foreach($args as $i => $arg) { 5414 // $callbackArgs[$i] = $arg instanceof CallbackParam 5415 // ? $v 5416 // : $arg; 5417 // } 5418 // $vv = call_user_func_array($callback, $callbackArgs); 5419 if (is_array($vv)) { 5420 foreach($vv as $vvv) 5421 $result[] = $vvv; 5422 } else if ($vv !== null) { 5423 $result[] = $vv; 5424 } 5425 } 5426 return $result; 5427 } 5428 /** 5429 * 5430 * @param $callback Callback 5431 * @param $params 5432 * @param $paramStructure 5433 * @return unknown_type 5434 */ 5435 public static function callbackRun($callback, $params = array(), $paramStructure = null) { 5436 if (! $callback) 5437 return; 5438 if ($callback instanceof CallbackParameterToReference) { 5439 // TODO support ParamStructure to select which $param push to reference 5440 if (isset($params[0])) 5441 $callback->callback = $params[0]; 5442 return true; 5443 } 5444 if ($callback instanceof Callback) { 5445 $paramStructure = $callback->params; 5446 $callback = $callback->callback; 5447 } 5448 if (! $paramStructure) 5449 return call_user_func_array($callback, $params); 5450 $p = 0; 5451 foreach($paramStructure as $i => $v) { 5452 $paramStructure[$i] = $v instanceof CallbackParam 5453 ? $params[$p++] 5454 : $v; 5455 } 5456 return call_user_func_array($callback, $paramStructure); 5457 } 5458 /** 5459 * Merge 2 phpQuery objects. 5460 * @param array $one 5461 * @param array $two 5462 * @protected 5463 * @todo node lists, phpQueryObject 5464 */ 5465 public static function merge($one, $two) { 5466 $elements = $one->elements; 5467 foreach($two->elements as $node) { 5468 $exists = false; 5469 foreach($elements as $node2) { 5470 if ($node2->isSameNode($node)) 5471 $exists = true; 5472 } 5473 if (! $exists) 5474 $elements[] = $node; 5475 } 5476 return $elements; 5477 // $one = $one->newInstance(); 5478 // $one->elements = $elements; 5479 // return $one; 5480 } 5481 /** 5482 * 5483 * @param $array 5484 * @param $callback 5485 * @param $invert 5486 * @return unknown_type 5487 * @link http://docs.jquery.com/Utilities/jQuery.grep 5488 */ 5489 public static function grep($array, $callback, $invert = false) { 5490 $result = array(); 5491 foreach($array as $k => $v) { 5492 $r = call_user_func_array($callback, array($v, $k)); 5493 if ($r === !(bool)$invert) 5494 $result[] = $v; 5495 } 5496 return $result; 5497 } 5498 public static function unique($array) { 5499 return array_unique($array); 5500 } 5501 /** 5502 * 5503 * @param $function 5504 * @return unknown_type 5505 * @TODO there are problems with non-static methods, second parameter pass it 5506 * but doesnt verify is method is really callable 5507 */ 5508 public static function isFunction($function) { 5509 return is_callable($function); 5510 } 5511 public static function trim($str) { 5512 return trim($str); 5513 } 5514 /* PLUGINS NAMESPACE */ 5515 /** 5516 * 5517 * @param $url 5518 * @param $callback 5519 * @param $param1 5520 * @param $param2 5521 * @param $param3 5522 * @return phpQueryObject 5523 */ 5524 public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) { 5525 if (self::plugin('WebBrowser')) { 5526 $params = func_get_args(); 5527 return self::callbackRun(array(self::$plugins, 'browserGet'), $params); 5528 } else { 5529 self::debug('WebBrowser plugin not available...'); 5530 } 5531 } 5532 /** 5533 * 5534 * @param $url 5535 * @param $data 5536 * @param $callback 5537 * @param $param1 5538 * @param $param2 5539 * @param $param3 5540 * @return phpQueryObject 5541 */ 5542 public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) { 5543 if (self::plugin('WebBrowser')) { 5544 $params = func_get_args(); 5545 return self::callbackRun(array(self::$plugins, 'browserPost'), $params); 5546 } else { 5547 self::debug('WebBrowser plugin not available...'); 5548 } 5549 } 5550 /** 5551 * 5552 * @param $ajaxSettings 5553 * @param $callback 5554 * @param $param1 5555 * @param $param2 5556 * @param $param3 5557 * @return phpQueryObject 5558 */ 5559 public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) { 5560 if (self::plugin('WebBrowser')) { 5561 $params = func_get_args(); 5562 return self::callbackRun(array(self::$plugins, 'browser'), $params); 5563 } else { 5564 self::debug('WebBrowser plugin not available...'); 5565 } 5566 } 5567 /** 5568 * 5569 * @param $code 5570 * @return string 5571 */ 5572 public static function php($code) { 5573 return self::code('php', $code); 5574 } 5575 /** 5576 * 5577 * @param $type 5578 * @param $code 5579 * @return string 5580 */ 5581 public static function code($type, $code) { 5582 return "<$type><!-- ".trim($code)." --></$type>"; 5583 } 5584 5585 public static function __callStatic($method, $params) { 5586 return call_user_func_array( 5587 array(phpQuery::$plugins, $method), 5588 $params 5589 ); 5590 } 5591 protected static function dataSetupNode($node, $documentID) { 5592 // search are return if alredy exists 5593 foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) { 5594 if ($node->isSameNode($dataNode)) 5595 return $dataNode; 5596 } 5597 // if doesn't, add it 5598 phpQuery::$documents[$documentID]->dataNodes[] = $node; 5599 return $node; 5600 } 5601 protected static function dataRemoveNode($node, $documentID) { 5602 // search are return if alredy exists 5603 foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) { 5604 if ($node->isSameNode($dataNode)) { 5605 unset(self::$documents[$documentID]->dataNodes[$k]); 5606 unset(self::$documents[$documentID]->data[ $dataNode->dataID ]); 5607 } 5608 } 5609 } 5610 public static function data($node, $name, $data, $documentID = null) { 5611 if (! $documentID) 5612 // TODO check if this works 5613 $documentID = self::getDocumentID($node); 5614 $document = phpQuery::$documents[$documentID]; 5615 $node = self::dataSetupNode($node, $documentID); 5616 if (! isset($node->dataID)) 5617 $node->dataID = ++phpQuery::$documents[$documentID]->uuid; 5618 $id = $node->dataID; 5619 if (! isset($document->data[$id])) 5620 $document->data[$id] = array(); 5621 if (! is_null($data)) 5622 $document->data[$id][$name] = $data; 5623 if ($name) { 5624 if (isset($document->data[$id][$name])) 5625 return $document->data[$id][$name]; 5626 } else 5627 return $id; 5628 } 5629 public static function removeData($node, $name, $documentID) { 5630 if (! $documentID) 5631 // TODO check if this works 5632 $documentID = self::getDocumentID($node); 5633 $document = phpQuery::$documents[$documentID]; 5634 $node = self::dataSetupNode($node, $documentID); 5635 $id = $node->dataID; 5636 if ($name) { 5637 if (isset($document->data[$id][$name])) 5638 unset($document->data[$id][$name]); 5639 $name = null; 5640 foreach($document->data[$id] as $name) 5641 break; 5642 if (! $name) 5643 self::removeData($node, $name, $documentID); 5644 } else { 5645 self::dataRemoveNode($node, $documentID); 5646 } 5647 } 5648 } 5649 /** 5650 * Plugins static namespace class. 5651 * 5652 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 5653 * @package phpQuery 5654 * @todo move plugin methods here (as statics) 5655 */ 5656 class phpQueryPlugins { 5657 public function __call($method, $args) { 5658 if (isset(phpQuery::$extendStaticMethods[$method])) { 5659 $return = call_user_func_array( 5660 phpQuery::$extendStaticMethods[$method], 5661 $args 5662 ); 5663 } else if (isset(phpQuery::$pluginsStaticMethods[$method])) { 5664 $class = phpQuery::$pluginsStaticMethods[$method]; 5665 $realClass = "phpQueryPlugin_$class"; 5666 $return = call_user_func_array( 5667 array($realClass, $method), 5668 $args 5669 ); 5670 return isset($return) 5671 ? $return 5672 : $this; 5673 } else 5674 throw new Exception("Method '{$method}' doesnt exist"); 5675 } 5676 } 5677 /** 5678 * Shortcut to phpQuery::pq($arg1, $context) 5679 * Chainable. 5680 * 5681 * @see phpQuery::pq() 5682 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5683 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> 5684 * @package phpQuery 5685 */ 5686 function pq($arg1, $context = null) { 5687 $args = func_get_args(); 5688 return call_user_func_array( 5689 array('phpQuery', 'pq'), 5690 $args 5691 ); 5692 } 5693 // add plugins dir and Zend framework to include path 5694 set_include_path( 5695 get_include_path() 5696 .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/' 5697 .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/' 5698 ); 5699 // why ? no __call nor __get for statics in php... 5700 // XXX __callStatic will be available in PHP 5.3 5701 phpQuery::$plugins = new phpQueryPlugins(); 5702 // include bootstrap file (personal library config) 5703 if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php')) 5704 require_once dirname(__FILE__).'/phpQuery/bootstrap.php';
title
Description
Body
title
Description
Body
title
Description
Body
title
Body