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

Hello, Person and person

3970 | * 3971 | * Result: 3972 | * [

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