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