1 | <?php |
||
2 | /** |
||
3 | * @author Niels A.D. |
||
4 | * @author Todd Burry <[email protected]> |
||
5 | * @copyright 2010 Niels A.D., 2014 Todd Burry |
||
6 | * @license http://opensource.org/licenses/LGPL-2.1 LGPL-2.1 |
||
7 | * @package pQuery |
||
8 | */ |
||
9 | |||
10 | namespace pQuery; |
||
11 | |||
12 | /** |
||
13 | * Holds (x)html/xml tag information like tag name, attributes, |
||
14 | * parent, children, self close, etc. |
||
15 | * |
||
16 | */ |
||
17 | class DomNode implements IQuery { |
||
18 | |||
19 | /** |
||
20 | * Element Node, used for regular elements |
||
21 | */ |
||
22 | const NODE_ELEMENT = 0; |
||
23 | /** |
||
24 | * Text Node |
||
25 | */ |
||
26 | const NODE_TEXT = 1; |
||
27 | /** |
||
28 | * Comment Node |
||
29 | */ |
||
30 | const NODE_COMMENT = 2; |
||
31 | /** |
||
32 | * Conditional Node (<![if]> <![endif]) |
||
33 | */ |
||
34 | const NODE_CONDITIONAL = 3; |
||
35 | /** |
||
36 | * CDATA Node (<![CDATA[]]> |
||
37 | */ |
||
38 | const NODE_CDATA = 4; |
||
39 | /** |
||
40 | * Doctype Node |
||
41 | */ |
||
42 | const NODE_DOCTYPE = 5; |
||
43 | /** |
||
44 | * XML Node, used for tags that start with ?, like <?xml and <?php |
||
45 | */ |
||
46 | const NODE_XML = 6; |
||
47 | /** |
||
48 | * ASP Node |
||
49 | */ |
||
50 | const NODE_ASP = 7; |
||
51 | |||
52 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
53 | #static $NODE_TYPE = self::NODE_ELEMENT; |
||
54 | #php4e |
||
55 | #php5 |
||
56 | /** |
||
57 | * Node type of class |
||
58 | */ |
||
59 | const NODE_TYPE = self::NODE_ELEMENT; |
||
60 | #php5e |
||
61 | |||
62 | |||
63 | /** |
||
64 | * Name of the selector class |
||
65 | * @var string |
||
66 | * @see select() |
||
67 | */ |
||
68 | var $selectClass = 'pQuery\\HtmlSelector'; |
||
69 | /** |
||
70 | * Name of the parser class |
||
71 | * @var string |
||
72 | * @see setOuterText() |
||
73 | * @see setInnerText() |
||
74 | */ |
||
75 | var $parserClass = 'pQuery\\Html5Parser'; |
||
76 | |||
77 | /** |
||
78 | * Name of the class used for {@link addChild()} |
||
79 | * @var string |
||
80 | */ |
||
81 | var $childClass = __CLASS__; |
||
82 | /** |
||
83 | * Name of the class used for {@link addText()} |
||
84 | * @var string |
||
85 | */ |
||
86 | var $childClass_Text = 'pQuery\\TextNode'; |
||
87 | /** |
||
88 | * Name of the class used for {@link addComment()} |
||
89 | * @var string |
||
90 | */ |
||
91 | var $childClass_Comment = 'pQuery\\CommentNode'; |
||
92 | /** |
||
93 | * Name of the class used for {@link addContional()} |
||
94 | * @var string |
||
95 | */ |
||
96 | var $childClass_Conditional = 'pQuery\\ConditionalTagNode'; |
||
97 | /** |
||
98 | * Name of the class used for {@link addCDATA()} |
||
99 | * @var string |
||
100 | */ |
||
101 | var $childClass_CDATA = 'pQuery\\CdataNode'; |
||
102 | /** |
||
103 | * Name of the class used for {@link addDoctype()} |
||
104 | * @var string |
||
105 | */ |
||
106 | var $childClass_Doctype = 'pQuery\\DoctypeNode'; |
||
107 | /** |
||
108 | * Name of the class used for {@link addXML()} |
||
109 | * @var string |
||
110 | */ |
||
111 | var $childClass_XML = 'pQuery\\XmlNode'; |
||
112 | /** |
||
113 | * Name of the class used for {@link addASP()} |
||
114 | * @var string |
||
115 | */ |
||
116 | var $childClass_ASP = 'pQuery\\AspEmbeddedNode'; |
||
117 | |||
118 | /** |
||
119 | * Parent node, null if none |
||
120 | * @var DomNode |
||
121 | * @see changeParent() |
||
122 | */ |
||
123 | var $parent = null; |
||
124 | |||
125 | /** |
||
126 | * Attributes of node |
||
127 | * @var array |
||
128 | * @internal array('attribute' => 'value') |
||
129 | * @internal Public for faster access! |
||
130 | * @see getAttribute() |
||
131 | * @see setAttribute() |
||
132 | * @access private |
||
133 | */ |
||
134 | var $attributes = array(); |
||
135 | |||
136 | /** |
||
137 | * Namespace info for attributes |
||
138 | * @var array |
||
139 | * @internal array('tag' => array(array('ns', 'tag', 'ns:tag', index))) |
||
140 | * @internal Public for easy outside modifications! |
||
141 | * @see findAttribute() |
||
142 | * @access private |
||
143 | */ |
||
144 | var $attributes_ns = null; |
||
145 | |||
146 | /** |
||
147 | * Array of child nodes |
||
148 | * @var array |
||
149 | * @internal Public for faster access! |
||
150 | * @see childCount() |
||
151 | * @see getChild() |
||
152 | * @see addChild() |
||
153 | * @see deleteChild() |
||
154 | * @access private |
||
155 | */ |
||
156 | var $children = array(); |
||
157 | |||
158 | /** |
||
159 | * Full tag name (including namespace) |
||
160 | * @var string |
||
161 | * @see getTagName() |
||
162 | * @see getNamespace() |
||
163 | */ |
||
164 | var $tag = ''; |
||
165 | |||
166 | /** |
||
167 | * Namespace info for tag |
||
168 | * @var array |
||
169 | * @internal array('namespace', 'tag') |
||
170 | * @internal Public for easy outside modifications! |
||
171 | * @access private |
||
172 | */ |
||
173 | var $tag_ns = null; |
||
174 | |||
175 | /** |
||
176 | * Is node a self closing node? No closing tag if true. |
||
177 | * @var bool |
||
178 | */ |
||
179 | var $self_close = false; |
||
180 | |||
181 | /** |
||
182 | * If self close, then this will be used to close the tag |
||
183 | * @var string |
||
184 | * @see $self_close |
||
185 | */ |
||
186 | var $self_close_str = ' /'; |
||
187 | |||
188 | /** |
||
189 | * Use short tags for attributes? If true, then attributes |
||
190 | * with values equal to the attribute name will not output |
||
191 | * the value, e.g. selected="selected" will be selected. |
||
192 | * @var bool |
||
193 | */ |
||
194 | var $attribute_shorttag = true; |
||
195 | |||
196 | /** |
||
197 | * Function map used for the selector filter |
||
198 | * @var array |
||
199 | * @internal array('root' => 'filter_root') will cause the |
||
200 | * selector to call $this->filter_root at :root |
||
201 | * @access private |
||
202 | */ |
||
203 | var $filter_map = array( |
||
204 | 'root' => 'filter_root', |
||
205 | 'nth-child' => 'filter_nchild', |
||
206 | 'eq' => 'filter_nchild', //jquery (naming) compatibility |
||
207 | 'gt' => 'filter_gt', |
||
208 | 'lt' => 'filter_lt', |
||
209 | 'nth-last-child' => 'filter_nlastchild', |
||
210 | 'nth-of-type' => 'filter_ntype', |
||
211 | 'nth-last-of-type' => 'filter_nlastype', |
||
212 | 'odd' => 'filter_odd', |
||
213 | 'even' => 'filter_even', |
||
214 | 'every' => 'filter_every', |
||
215 | 'first-child' => 'filter_first', |
||
216 | 'last-child' => 'filter_last', |
||
217 | 'first-of-type' => 'filter_firsttype', |
||
218 | 'last-of-type' => 'filter_lasttype', |
||
219 | 'only-child' => 'filter_onlychild', |
||
220 | 'only-of-type' => 'filter_onlytype', |
||
221 | 'empty' => 'filter_empty', |
||
222 | 'not-empty' => 'filter_notempty', |
||
223 | 'has-text' => 'filter_hastext', |
||
224 | 'no-text' => 'filter_notext', |
||
225 | 'lang' => 'filter_lang', |
||
226 | 'contains' => 'filter_contains', |
||
227 | 'has' => 'filter_has', |
||
228 | 'not' => 'filter_not', |
||
229 | 'element' => 'filter_element', |
||
230 | 'text' => 'filter_text', |
||
231 | 'comment' => 'filter_comment', |
||
232 | 'checked' => 'filter_checked', |
||
233 | 'selected' => 'filter_selected', |
||
234 | ); |
||
235 | |||
236 | /** |
||
237 | * Class constructor |
||
238 | * @param string|array $tag Name of the tag, or array with taginfo (array( |
||
239 | * 'tag_name' => 'tag', |
||
240 | * 'self_close' => false, |
||
241 | * 'attributes' => array('attribute' => 'value'))) |
||
242 | * @param DomNode $parent Parent of node, null if none |
||
243 | */ |
||
244 | 37 | function __construct($tag, $parent) { |
|
245 | 37 | $this->parent = $parent; |
|
246 | |||
247 | 37 | if (is_string($tag)) { |
|
248 | 37 | $this->tag = $tag; |
|
249 | 37 | } else { |
|
250 | 37 | $this->tag = $tag['tag_name']; |
|
251 | 37 | $this->self_close = $tag['self_close']; |
|
252 | 37 | $this->attributes = $tag['attributes']; |
|
253 | } |
||
254 | 37 | } |
|
255 | |||
256 | #php4 PHP4 class constructor compatibility |
||
257 | #function DomNode($tag, $parent) {return $this->__construct($tag, $parent);} |
||
258 | #php4e |
||
259 | |||
260 | /** |
||
261 | * Class destructor |
||
262 | * @access private |
||
263 | */ |
||
264 | 15 | function __destruct() { |
|
265 | 15 | $this->delete(); |
|
266 | 15 | } |
|
267 | |||
268 | /** |
||
269 | * Class toString, outputs {@link $tag} |
||
270 | * @return string |
||
271 | * @access private |
||
272 | */ |
||
273 | function __toString() { |
||
274 | return (($this->tag === '~root~') ? $this->toString(true, true, 1) : $this->tag); |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Class magic get method, outputs {@link getAttribute()} |
||
279 | * @return string |
||
280 | * @access private |
||
281 | */ |
||
282 | 4 | function __get($attribute) { |
|
283 | 4 | return $this->getAttribute($attribute); |
|
284 | } |
||
285 | |||
286 | /** |
||
287 | * Class magic set method, performs {@link setAttribute()} |
||
288 | * @access private |
||
289 | */ |
||
290 | 4 | function __set($attribute, $value) { |
|
291 | 4 | $this->setAttribute($attribute, $value); |
|
292 | 4 | } |
|
293 | |||
294 | /** |
||
295 | * Class magic isset method, returns {@link hasAttribute()} |
||
296 | * @return bool |
||
297 | * @access private |
||
298 | */ |
||
299 | function __isset($attribute) { |
||
300 | return $this->hasAttribute($attribute); |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Class magic unset method, performs {@link deleteAttribute()} |
||
305 | * @access private |
||
306 | */ |
||
307 | 1 | function __unset($attribute) { |
|
308 | 1 | return $this->deleteAttribute($attribute); |
|
309 | } |
||
310 | |||
311 | /** |
||
312 | * Class magic invoke method, performs {@link query()}. |
||
313 | * @param string $query The css query to run on the nodes. |
||
314 | * @return \pQuery |
||
315 | */ |
||
316 | 3 | function __invoke($query = '*') { |
|
317 | 3 | return $this->query($query); |
|
318 | } |
||
319 | |||
320 | /** |
||
321 | * Returns place in document |
||
322 | * @return string |
||
323 | */ |
||
324 | function dumpLocation() { |
||
325 | return (($this->parent) ? (($p = $this->parent->dumpLocation()) ? $p.' > ' : '').$this->tag.'('.$this->typeIndex().')' : ''); |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Returns all the attributes and their values |
||
330 | * @return string |
||
331 | * @access private |
||
332 | */ |
||
333 | 20 | protected function toString_attributes() { |
|
334 | 20 | $s = ''; |
|
335 | 20 | foreach($this->attributes as $a => $v) { |
|
336 | 17 | $s .= ' '.$a; |
|
337 | 17 | if ((!$this->attribute_shorttag) || ($v !== $a)) { |
|
338 | 17 | $quote = (strpos($v, '"') === false) ? '"' : "'"; |
|
339 | 17 | $s .= '='.$quote.$v.$quote; |
|
340 | 17 | } |
|
341 | 20 | } |
|
342 | 20 | return $s; |
|
343 | } |
||
344 | |||
345 | /** |
||
346 | * Returns the content of the node (child tags and text) |
||
347 | * @param bool $attributes Print attributes of child tags |
||
348 | * @param bool|int $recursive How many sublevels of childtags to print. True for all. |
||
349 | * @param bool $content_only Only print text, false will print tags too. |
||
350 | * @return string |
||
351 | * @access private |
||
352 | */ |
||
353 | 23 | protected function toString_content($attributes = true, $recursive = true, $content_only = false) { |
|
354 | 23 | $s = ''; |
|
355 | 23 | foreach($this->children as $c) { |
|
356 | 23 | $s .= $c->toString($attributes, $recursive, $content_only); |
|
357 | 23 | } |
|
358 | 23 | return $s; |
|
359 | } |
||
360 | |||
361 | /** |
||
362 | * Returns the node as string |
||
363 | * @param bool $attributes Print attributes (of child tags) |
||
364 | * @param bool|int $recursive How many sub-levels of child tags to print. True for all. |
||
365 | * @param bool|int $content_only Only print text, false will print tags too. |
||
366 | * @return string |
||
367 | */ |
||
368 | 23 | function toString($attributes = true, $recursive = true, $content_only = false) { |
|
369 | 23 | if ($content_only) { |
|
370 | 23 | if (is_int($content_only)) { |
|
371 | 21 | --$content_only; |
|
372 | 21 | } |
|
373 | 23 | return $this->toString_content($attributes, $recursive, $content_only); |
|
374 | } |
||
375 | |||
376 | 20 | $s = '<'.$this->tag; |
|
377 | 20 | if ($attributes) { |
|
378 | 20 | $s .= $this->toString_attributes(); |
|
379 | 20 | } |
|
380 | 20 | if ($this->self_close) { |
|
381 | $s .= $this->self_close_str.'>'; |
||
382 | } else { |
||
383 | 20 | $s .= '>'; |
|
384 | 20 | if($recursive) { |
|
385 | 20 | $s .= $this->toString_content($attributes); |
|
386 | 20 | } |
|
387 | 20 | $s .= '</'.$this->tag.'>'; |
|
388 | } |
||
389 | 20 | return $s; |
|
390 | } |
||
391 | |||
392 | /** |
||
393 | * Similar to JavaScript outerText, will return full (html formatted) node |
||
394 | * @return string |
||
395 | */ |
||
396 | function getOuterText() { |
||
397 | return html_entity_decode($this->toString(), ENT_QUOTES); |
||
398 | } |
||
399 | |||
400 | /** |
||
401 | * Similar to JavaScript outerText, will replace node (and child nodes) with new text |
||
402 | * @param string $text |
||
403 | * @param HtmlParserBase $parser Null to auto create instance |
||
404 | * @return bool|array True on succeed, array with errors on failure |
||
405 | */ |
||
406 | function setOuterText($text, $parser = null) { |
||
407 | if (trim($text)) { |
||
408 | $index = $this->index(); |
||
409 | if ($parser === null) { |
||
410 | $parser = new $this->parserClass(); |
||
411 | } |
||
412 | $parser->setDoc($text); |
||
413 | $parser->parse_all(); |
||
414 | $parser->root->moveChildren($this->parent, $index); |
||
415 | } |
||
416 | $this->delete(); |
||
417 | return (($parser && $parser->errors) ? $parser->errors : true); |
||
418 | } |
||
419 | |||
420 | /** |
||
421 | * Return html code of node |
||
422 | * @internal jquery (naming) compatibility |
||
423 | * @param string|null $value The value to set or null to get the value. |
||
424 | * @see toString() |
||
425 | * @return string |
||
426 | */ |
||
427 | 20 | function html($value = null) { |
|
428 | 20 | if ($value !== null) { |
|
429 | 1 | $this->setInnerText($value); |
|
430 | 1 | } |
|
431 | 20 | return $this->getInnerText(); |
|
432 | } |
||
433 | |||
434 | /** |
||
435 | * Similar to JavaScript innerText, will return (html formatted) content |
||
436 | * @return string |
||
437 | */ |
||
438 | 21 | function getInnerText() { |
|
439 | 21 | return html_entity_decode($this->toString(true, true, 1), ENT_QUOTES); |
|
440 | } |
||
441 | |||
442 | /** |
||
443 | * Similar to JavaScript innerText, will replace child nodes with new text |
||
444 | * @param string $text |
||
445 | * @param HtmlParserBase $parser Null to auto create instance |
||
446 | * @return bool|array True on succeed, array with errors on failure |
||
447 | */ |
||
448 | 2 | function setInnerText($text, $parser = null) { |
|
449 | 2 | $this->clear(); |
|
450 | 2 | if (trim($text)) { |
|
451 | 2 | if ($parser === null) { |
|
452 | 2 | $parser = new $this->parserClass(); |
|
453 | 2 | } |
|
454 | 2 | $parser->root =& $this; |
|
455 | 2 | $parser->setDoc($text); |
|
456 | 2 | $parser->parse_all(); |
|
457 | 2 | } |
|
458 | 2 | return (($parser && $parser->errors) ? $parser->errors : true); |
|
459 | } |
||
460 | |||
461 | /** |
||
462 | * Similar to JavaScript plainText, will return text in node (and subnodes) |
||
463 | * @return string |
||
464 | */ |
||
465 | 2 | function getPlainText() { |
|
466 | 2 | return preg_replace('`\s+`', ' ', html_entity_decode($this->toString(true, true, true), ENT_QUOTES)); |
|
467 | } |
||
468 | |||
469 | /** |
||
470 | * Return plaintext taking document encoding into account |
||
471 | * @return string |
||
472 | */ |
||
473 | function getPlainTextUTF8() { |
||
474 | $txt = $this->toString(true, true, true); |
||
475 | $enc = $this->getEncoding(); |
||
476 | if ($enc !== false) { |
||
477 | $txt = mb_convert_encoding($txt, 'UTF-8', $enc); |
||
478 | } |
||
479 | return preg_replace('`\s+`', ' ', html_entity_decode($txt, ENT_QUOTES, 'UTF-8')); |
||
480 | } |
||
481 | |||
482 | /** |
||
483 | * Similar to JavaScript plainText, will replace child nodes with new text (literal) |
||
484 | * @param string $text |
||
485 | */ |
||
486 | 1 | function setPlainText($text) { |
|
487 | 1 | $this->clear(); |
|
488 | 1 | if (trim($text)) { |
|
489 | 1 | $this->addText(htmlentities($text, ENT_QUOTES)); |
|
490 | 1 | } |
|
491 | 1 | } |
|
492 | |||
493 | /** |
||
494 | * Delete node from parent and clear node |
||
495 | */ |
||
496 | 15 | function delete() { |
|
497 | 15 | if (($p = $this->parent) !== null) { |
|
498 | 4 | $this->parent = null; |
|
499 | 4 | $p->deleteChild($this); |
|
500 | 4 | } else { |
|
501 | 15 | $this->clear(); |
|
502 | } |
||
503 | 15 | } |
|
504 | |||
505 | /** |
||
506 | * Detach node from parent |
||
507 | * @param bool $move_children_up Only detach current node and replace it with child nodes |
||
508 | * @internal jquery (naming) compatibility |
||
509 | * @see delete() |
||
510 | */ |
||
511 | 1 | function detach($move_children_up = false) { |
|
512 | 1 | if (($p = $this->parent) !== null) { |
|
513 | 1 | $index = $this->index(); |
|
514 | 1 | $this->parent = null; |
|
515 | |||
516 | 1 | if ($move_children_up) { |
|
517 | 1 | $this->moveChildren($p, $index); |
|
518 | 1 | } |
|
519 | 1 | $p->deleteChild($this, true); |
|
520 | 1 | } |
|
521 | 1 | } |
|
522 | |||
523 | /** |
||
524 | * Deletes all child nodes from node |
||
525 | */ |
||
526 | 15 | function clear() { |
|
527 | 15 | foreach($this->children as $c) { |
|
528 | 8 | $c->parent = null; |
|
529 | 8 | $c->delete(); |
|
530 | 15 | } |
|
531 | 15 | $this->children = array(); |
|
532 | 15 | } |
|
533 | |||
534 | /** |
||
535 | * Get top parent |
||
536 | * @return DomNode Root, null if node has no parent |
||
537 | */ |
||
538 | function getRoot() { |
||
539 | $r = $this->parent; |
||
540 | $n = ($r === null) ? null : $r->parent; |
||
541 | while ($n !== null) { |
||
542 | $r = $n; |
||
543 | $n = $r->parent; |
||
544 | } |
||
545 | |||
546 | return $r; |
||
547 | } |
||
548 | |||
549 | /** |
||
550 | * Change parent |
||
551 | * @param null|DomNode $to New parent, null if none |
||
552 | * @param false|int $index Add child to parent if not present at index, false to not add, negative to count from end, null to append |
||
553 | */ |
||
554 | #php4 |
||
555 | #function changeParent($to, &$index) { |
||
556 | #php4e |
||
557 | #php5 |
||
558 | 10 | function changeParent($to, &$index = null) { |
|
559 | #php5e |
||
560 | 10 | if ($this->parent !== null) { |
|
561 | 10 | $this->parent->deleteChild($this, true); |
|
562 | 10 | } |
|
563 | 10 | $this->parent = $to; |
|
564 | 10 | if ($index !== false) { |
|
565 | 10 | $new_index = $this->index(); |
|
566 | 10 | if (!(is_int($new_index) && ($new_index >= 0))) { |
|
567 | 10 | $this->parent->addChild($this, $index); |
|
568 | 10 | } |
|
569 | 10 | } |
|
570 | 10 | } |
|
571 | |||
572 | /** |
||
573 | * Find out if node has (a certain) parent |
||
574 | * @param DomNode|string $tag Match against parent, string to match tag, object to fully match node, null to return if node has parent |
||
575 | * @param bool $recursive |
||
576 | * @return bool |
||
577 | */ |
||
578 | function hasParent($tag = null, $recursive = false) { |
||
579 | if ($this->parent !== null) { |
||
580 | if ($tag === null) { |
||
581 | return true; |
||
582 | } elseif (is_string($tag)) { |
||
583 | return (($this->parent->tag === $tag) || ($recursive && $this->parent->hasParent($tag))); |
||
584 | } elseif (is_object($tag)) { |
||
585 | return (($this->parent === $tag) || ($recursive && $this->parent->hasParent($tag))); |
||
586 | } |
||
587 | } |
||
588 | |||
589 | return false; |
||
590 | } |
||
591 | |||
592 | /** |
||
593 | * Find out if node is parent of a certain tag |
||
594 | * @param DomNode|string $tag Match against parent, string to match tag, object to fully match node |
||
595 | * @param bool $recursive |
||
596 | * @return bool |
||
597 | * @see hasParent() |
||
598 | */ |
||
599 | function isParent($tag, $recursive = false) { |
||
600 | return ($this->hasParent($tag, $recursive) === ($tag !== null)); |
||
601 | } |
||
602 | |||
603 | /** |
||
604 | * Find out if node is text |
||
605 | * @return bool |
||
606 | */ |
||
607 | function isText() { |
||
608 | return false; |
||
609 | } |
||
610 | |||
611 | /** |
||
612 | * Find out if node is comment |
||
613 | * @return bool |
||
614 | */ |
||
615 | function isComment() { |
||
616 | return false; |
||
617 | } |
||
618 | |||
619 | /** |
||
620 | * Find out if node is text or comment node |
||
621 | * @return bool |
||
622 | */ |
||
623 | function isTextOrComment() { |
||
624 | return false; |
||
625 | } |
||
626 | |||
627 | /** |
||
628 | * Move node to other node |
||
629 | * @param DomNode $to New parent, null if none |
||
630 | * @param int $new_index Add child to parent at index if not present, null to not add, negative to count from end |
||
631 | * @internal Performs {@link changeParent()} |
||
632 | */ |
||
633 | #php4 |
||
634 | #function move($to, &$new_index) { |
||
635 | #php4e |
||
636 | #php5 |
||
637 | function move($to, &$new_index = -1) { |
||
638 | #php5e |
||
639 | $this->changeParent($to, $new_index); |
||
640 | } |
||
641 | |||
642 | /** |
||
643 | * Move child nodes to other node |
||
644 | * @param DomNode $to New parent, null if none |
||
645 | * @param int $new_index Add child to new node at index if not present, null to not add, negative to count from end |
||
646 | * @param int $start Index from child node where to start wrapping, 0 for first element |
||
647 | * @param int $end Index from child node where to end wrapping, -1 for last element |
||
648 | */ |
||
649 | #php4 |
||
650 | #function moveChildren($to, &$new_index, $start = 0, $end = -1) { |
||
651 | #php4e |
||
652 | #php5 |
||
653 | 2 | function moveChildren($to, &$new_index = -1, $start = 0, $end = -1) { |
|
654 | #php5e |
||
655 | 2 | if ($end < 0) { |
|
656 | 1 | $end += count($this->children); |
|
657 | 1 | } |
|
658 | 2 | for ($i = $start; $i <= $end; $i++) { |
|
659 | 2 | $this->children[$start]->changeParent($to, $new_index); |
|
660 | 2 | } |
|
661 | 2 | } |
|
662 | |||
663 | /** |
||
664 | * Index of node in parent |
||
665 | * @param bool $count_all True to count all tags, false to ignore text and comments |
||
666 | * @return int -1 if not found |
||
667 | */ |
||
668 | 10 | function index($count_all = true) { |
|
669 | 10 | if (!$this->parent) { |
|
670 | return -1; |
||
671 | 10 | } elseif ($count_all) { |
|
672 | 10 | return $this->parent->findChild($this); |
|
673 | } else{ |
||
674 | $index = -1; |
||
675 | //foreach($this->parent->children as &$c) { |
||
676 | // if (!$c->isTextOrComment()) { |
||
677 | // ++$index; |
||
678 | // } |
||
679 | // if ($c === $this) { |
||
680 | // return $index; |
||
681 | // } |
||
682 | //} |
||
683 | |||
684 | foreach(array_keys($this->parent->children) as $k) { |
||
685 | if (!$this->parent->children[$k]->isTextOrComment()) { |
||
686 | ++$index; |
||
687 | } |
||
688 | if ($this->parent->children[$k] === $this) { |
||
689 | return $index; |
||
690 | } |
||
691 | } |
||
692 | return -1; |
||
693 | } |
||
694 | } |
||
695 | |||
696 | /** |
||
697 | * Change index of node in parent |
||
698 | * @param int $index New index |
||
699 | */ |
||
700 | function setIndex($index) { |
||
701 | if ($this->parent) { |
||
702 | if ($index > $this->index()) { |
||
703 | --$index; |
||
704 | } |
||
705 | $this->delete(); |
||
706 | $this->parent->addChild($this, $index); |
||
707 | } |
||
708 | } |
||
709 | |||
710 | /** |
||
711 | * Index of all similar nodes in parent |
||
712 | * @return int -1 if not found |
||
713 | */ |
||
714 | function typeIndex() { |
||
715 | if (!$this->parent) { |
||
716 | return -1; |
||
717 | } else { |
||
718 | $index = -1; |
||
719 | //foreach($this->parent->children as &$c) { |
||
720 | // if (strcasecmp($this->tag, $c->tag) === 0) { |
||
721 | // ++$index; |
||
722 | // } |
||
723 | // if ($c === $this) { |
||
724 | // return $index; |
||
725 | // } |
||
726 | //} |
||
727 | |||
728 | foreach(array_keys($this->parent->children) as $k) { |
||
729 | if (strcasecmp($this->tag, $this->parent->children[$k]->tag) === 0) { |
||
730 | ++$index; |
||
731 | } |
||
732 | if ($this->parent->children[$k] === $this) { |
||
733 | return $index; |
||
734 | } |
||
735 | } |
||
736 | return -1; |
||
737 | } |
||
738 | } |
||
739 | |||
740 | /** |
||
741 | * Calculate indent of node (number of parent tags - 1) |
||
742 | * @return int |
||
743 | */ |
||
744 | function indent() { |
||
745 | return (($this->parent) ? $this->parent->indent() + 1 : -1); |
||
746 | } |
||
747 | |||
748 | /** |
||
749 | * Get sibling node |
||
750 | * @param int $offset Offset from current node |
||
751 | * @return DomNode Null if not found |
||
752 | */ |
||
753 | function getSibling($offset = 1) { |
||
754 | $index = $this->index() + $offset; |
||
755 | if (($index >= 0) && ($index < $this->parent->childCount())) { |
||
756 | return $this->parent->getChild($index); |
||
757 | } else { |
||
758 | return null; |
||
759 | } |
||
760 | } |
||
761 | |||
762 | /** |
||
763 | * Get node next to current |
||
764 | * @param bool $skip_text_comments |
||
765 | * @return DomNode Null if not found |
||
766 | * @see getSibling() |
||
767 | * @see getPreviousSibling() |
||
768 | */ |
||
769 | function getNextSibling($skip_text_comments = true) { |
||
770 | $offset = 1; |
||
771 | while (($n = $this->getSibling($offset)) !== null) { |
||
772 | if ($skip_text_comments && ($n->tag[0] === '~')) { |
||
773 | ++$offset; |
||
774 | } else { |
||
775 | break; |
||
776 | } |
||
777 | } |
||
778 | |||
779 | return $n; |
||
780 | } |
||
781 | |||
782 | /** |
||
783 | * Get node previous to current |
||
784 | * @param bool $skip_text_comments |
||
785 | * @return DomNode Null if not found |
||
786 | * @see getSibling() |
||
787 | * @see getNextSibling() |
||
788 | */ |
||
789 | function getPreviousSibling($skip_text_comments = true) { |
||
790 | $offset = -1; |
||
791 | while (($n = $this->getSibling($offset)) !== null) { |
||
792 | if ($skip_text_comments && ($n->tag[0] === '~')) { |
||
793 | --$offset; |
||
794 | } else { |
||
795 | break; |
||
796 | } |
||
797 | } |
||
798 | |||
799 | return $n; |
||
800 | } |
||
801 | |||
802 | /** |
||
803 | * Get namespace of node |
||
804 | * @return string |
||
805 | * @see setNamespace() |
||
806 | */ |
||
807 | 2 | function getNamespace() { |
|
808 | 2 | if ($this->tag_ns === null) { |
|
809 | 2 | $a = explode(':', $this->tag, 2); |
|
810 | 2 | if (empty($a[1])) { |
|
811 | 2 | $this->tag_ns = array('', $a[0]); |
|
812 | 2 | } else { |
|
813 | $this->tag_ns = array($a[0], $a[1]); |
||
814 | } |
||
815 | 2 | } |
|
816 | |||
817 | 2 | return $this->tag_ns[0]; |
|
818 | } |
||
819 | |||
820 | /** |
||
821 | * Set namespace of node |
||
822 | * @param string $ns |
||
823 | * @see getNamespace() |
||
824 | */ |
||
825 | function setNamespace($ns) { |
||
826 | if ($this->getNamespace() !== $ns) { |
||
827 | $this->tag_ns[0] = $ns; |
||
828 | $this->tag = $ns.':'.$this->tag_ns[1]; |
||
829 | } |
||
830 | } |
||
831 | |||
832 | /** |
||
833 | * Get tagname of node (without namespace) |
||
834 | * @return string |
||
835 | * @see setTag() |
||
836 | */ |
||
837 | 2 | function getTag() { |
|
838 | 2 | if ($this->tag_ns === null) { |
|
839 | 2 | $this->getNamespace(); |
|
840 | 2 | } |
|
841 | |||
842 | 2 | return $this->tag_ns[1]; |
|
843 | } |
||
844 | |||
845 | /** |
||
846 | * Set tag (with or without namespace) |
||
847 | * @param string $tag |
||
848 | * @param bool $with_ns Does $tag include namespace? |
||
849 | * @see getTag() |
||
850 | */ |
||
851 | 2 | function setTag($tag, $with_ns = false) { |
|
852 | 2 | $with_ns = $with_ns || (strpos($tag, ':') !== false); |
|
853 | 2 | if ($with_ns) { |
|
854 | $this->tag = $tag; |
||
855 | $this->tag_ns = null; |
||
856 | 2 | } elseif ($this->getTag() !== $tag) { |
|
857 | 2 | $this->tag_ns[1] = $tag; |
|
858 | 2 | $this->tag = (($this->tag_ns[0]) ? $this->tag_ns[0].':' : '').$tag; |
|
859 | 2 | } |
|
860 | 2 | } |
|
861 | |||
862 | /** |
||
863 | * Try to determine the encoding of the current tag |
||
864 | * @return string|bool False if encoding could not be found |
||
865 | */ |
||
866 | function getEncoding() { |
||
867 | $root = $this->getRoot(); |
||
868 | if ($root !== null) { |
||
869 | if ($enc = $root->select('meta[charset]', 0, true, true)) { |
||
870 | return $enc->getAttribute("charset"); |
||
871 | } elseif ($enc = $root->select('"?xml"[encoding]', 0, true, true)) { |
||
872 | return $enc->getAttribute("encoding"); |
||
873 | } elseif ($enc = $root->select('meta[content*="charset="]', 0, true, true)) { |
||
874 | $enc = $enc->getAttribute("content"); |
||
875 | return substr($enc, strpos($enc, "charset=")+8); |
||
876 | } |
||
877 | } |
||
878 | |||
879 | return false; |
||
880 | } |
||
881 | |||
882 | /** |
||
883 | * Number of children in node |
||
884 | * @param bool $ignore_text_comments Ignore text/comments with calculation |
||
885 | * @return int |
||
886 | */ |
||
887 | 37 | function childCount($ignore_text_comments = false) { |
|
888 | 37 | if (!$ignore_text_comments) { |
|
889 | 37 | return count($this->children); |
|
890 | } else{ |
||
891 | $count = 0; |
||
892 | //foreach($this->children as &$c) { |
||
893 | // if (!$c->isTextOrComment()) { |
||
894 | // ++$count; |
||
895 | // } |
||
896 | //} |
||
897 | |||
898 | foreach(array_keys($this->children) as $k) { |
||
899 | if (!$this->children[$k]->isTextOrComment()) { |
||
900 | ++$count; |
||
901 | } |
||
902 | } |
||
903 | return $count; |
||
904 | } |
||
905 | } |
||
906 | |||
907 | /** |
||
908 | * Find node in children |
||
909 | * @param DomNode $child |
||
910 | * @return int False if not found |
||
911 | */ |
||
912 | 12 | function findChild($child) { |
|
913 | 12 | return array_search($child, $this->children, true); |
|
914 | } |
||
915 | |||
916 | /** |
||
917 | * Checks if node has another node as child |
||
918 | * @param DomNode $child |
||
919 | * @return bool |
||
920 | */ |
||
921 | function hasChild($child) { |
||
922 | return ((bool) findChild($child)); |
||
923 | } |
||
924 | |||
925 | /** |
||
926 | * Get childnode |
||
927 | * @param int|DomNode $child Index, negative to count from end |
||
928 | * @param bool $ignore_text_comments Ignore text/comments with index calculation |
||
929 | * @return DomNode |
||
930 | */ |
||
931 | function &getChild($child, $ignore_text_comments = false) { |
||
932 | if (!is_int($child)) { |
||
933 | $child = $this->findChild($child); |
||
934 | } elseif ($child < 0) { |
||
935 | $child += $this->childCount($ignore_text_comments); |
||
936 | } |
||
937 | |||
938 | if ($ignore_text_comments) { |
||
939 | $count = 0; |
||
940 | $last = null; |
||
941 | //foreach($this->children as &$c) { |
||
942 | // if (!$c->isTextOrComment()) { |
||
943 | // if ($count++ === $child) { |
||
944 | // return $c; |
||
945 | // } |
||
946 | // $last = $c; |
||
947 | // } |
||
948 | //} |
||
949 | |||
950 | foreach(array_keys($this->children) as $k) { |
||
951 | if (!$this->children[$k]->isTextOrComment()) { |
||
952 | if ($count++ === $child) { |
||
953 | return $this->children[$k]; |
||
954 | } |
||
955 | $last = $this->children[$k]; |
||
956 | } |
||
957 | } |
||
958 | return (($child > $count) ? $last : null); |
||
959 | } else { |
||
960 | return $this->children[$child]; |
||
961 | } |
||
962 | } |
||
963 | |||
964 | /** |
||
965 | * Add child node |
||
966 | * @param string|DomNode $tag Tag name or object |
||
967 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
968 | * @return DomNode Added node |
||
969 | */ |
||
970 | #php4 |
||
971 | #function &addChild($tag, &$offset) { |
||
972 | #php4e |
||
973 | #php5 |
||
974 | 37 | function &addChild($tag, &$offset = null) { |
|
975 | #php5e |
||
976 | 37 | if (is_array($tag)) { |
|
977 | 37 | $tag = new $this->childClass($tag, $this); |
|
978 | 37 | } elseif (is_string($tag)) { |
|
979 | 3 | $nodes = $this->createNodes($tag); |
|
980 | 3 | $tag = array_shift($nodes); |
|
981 | |||
982 | 3 | if ($tag && $tag->parent !== $this) { |
|
983 | 2 | $index = false; |
|
984 | 2 | $tag->changeParent($this, $index); |
|
985 | 2 | } |
|
986 | 37 | } elseif (is_object($tag) && $tag->parent !== $this) { |
|
987 | $index = false; //Needs to be passed by ref |
||
988 | $tag->changeParent($this, $index); |
||
989 | } |
||
990 | |||
991 | 37 | if (is_int($offset) && ($offset < count($this->children)) && ($offset !== -1)) { |
|
992 | 8 | if ($offset < 0) { |
|
993 | $offset += count($this->children); |
||
994 | } |
||
995 | 8 | array_splice($this->children, $offset++, 0, array(&$tag)); |
|
996 | 8 | } else { |
|
997 | 37 | $this->children[] =& $tag; |
|
998 | } |
||
999 | |||
1000 | 37 | return $tag; |
|
1001 | } |
||
1002 | |||
1003 | /** |
||
1004 | * First child node |
||
1005 | * @param bool $ignore_text_comments Ignore text/comments with index calculation |
||
1006 | * @return DomNode |
||
1007 | */ |
||
1008 | function &firstChild($ignore_text_comments = false) { |
||
1009 | return $this->getChild(0, $ignore_text_comments); |
||
1010 | } |
||
1011 | |||
1012 | /** |
||
1013 | * Last child node |
||
1014 | * @param bool $ignore_text_comments Ignore text/comments with index calculation |
||
1015 | * @return DomNode |
||
1016 | */ |
||
1017 | function &lastChild($ignore_text_comments = false) { |
||
1018 | return $this->getChild(-1, $ignore_text_comments); |
||
1019 | } |
||
1020 | |||
1021 | /** |
||
1022 | * Insert childnode |
||
1023 | * @param string|DomNode $tag Tagname or object |
||
1024 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1025 | * @return DomNode Added node |
||
1026 | * @see addChild(); |
||
1027 | */ |
||
1028 | function &insertChild($tag, $index) { |
||
1029 | return $this->addChild($tag, $index); |
||
1030 | } |
||
1031 | |||
1032 | /** |
||
1033 | * Add text node |
||
1034 | * @param string $text |
||
1035 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1036 | * @return DomNode Added node |
||
1037 | * @see addChild(); |
||
1038 | */ |
||
1039 | #php4 |
||
1040 | #function &addText($text, &$offset) { |
||
1041 | #php4e |
||
1042 | #php5 |
||
1043 | 37 | function &addText($text, &$offset = null) { |
|
1044 | #php5e |
||
1045 | 37 | return $this->addChild(new $this->childClass_Text($this, $text), $offset); |
|
1046 | } |
||
1047 | |||
1048 | /** |
||
1049 | * Add comment node |
||
1050 | * @param string $text |
||
1051 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1052 | * @return DomNode Added node |
||
1053 | * @see addChild(); |
||
1054 | */ |
||
1055 | #php4 |
||
1056 | #function &addComment($text, &$offset) { |
||
1057 | #php4e |
||
1058 | #php5 |
||
1059 | 9 | function &addComment($text, &$offset = null) { |
|
1060 | #php5e |
||
1061 | 9 | return $this->addChild(new $this->childClass_Comment($this, $text), $offset); |
|
1062 | } |
||
1063 | |||
1064 | /** |
||
1065 | * Add conditional node |
||
1066 | * @param string $condition |
||
1067 | * @param bool True for <!--[if, false for <![if |
||
1068 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1069 | * @return DomNode Added node |
||
1070 | * @see addChild(); |
||
1071 | */ |
||
1072 | #php4 |
||
1073 | #function &addConditional($condition, $hidden = true, &$offset) { |
||
1074 | #php4e |
||
1075 | #php5 |
||
1076 | function &addConditional($condition, $hidden = true, &$offset = null) { |
||
1077 | #php5e |
||
1078 | return $this->addChild(new $this->childClass_Conditional($this, $condition, $hidden), $offset); |
||
1079 | } |
||
1080 | |||
1081 | /** |
||
1082 | * Add CDATA node |
||
1083 | * @param string $text |
||
1084 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1085 | * @return DomNode Added node |
||
1086 | * @see addChild(); |
||
1087 | */ |
||
1088 | #php4 |
||
1089 | #function &addCDATA($text, &$offset) { |
||
1090 | #php4e |
||
1091 | #php5 |
||
1092 | function &addCDATA($text, &$offset = null) { |
||
1093 | #php5e |
||
1094 | return $this->addChild(new $this->childClass_CDATA($this, $text), $offset); |
||
1095 | } |
||
1096 | |||
1097 | /** |
||
1098 | * Add doctype node |
||
1099 | * @param string $dtd |
||
1100 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1101 | * @return DomNode Added node |
||
1102 | * @see addChild(); |
||
1103 | */ |
||
1104 | #php4 |
||
1105 | #function &addDoctype($dtd, &$offset) { |
||
1106 | #php4e |
||
1107 | #php5 |
||
1108 | 9 | function &addDoctype($dtd, &$offset = null) { |
|
1109 | #php5e |
||
1110 | 9 | return $this->addChild(new $this->childClass_Doctype($this, $dtd), $offset); |
|
1111 | } |
||
1112 | |||
1113 | /** |
||
1114 | * Add xml node |
||
1115 | * @param string $tag Tag name after "?", e.g. "php" or "xml" |
||
1116 | * @param string $text |
||
1117 | * @param array $attributes Array of attributes (array('attribute' => 'value')) |
||
1118 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1119 | * @return DomNode Added node |
||
1120 | * @see addChild(); |
||
1121 | */ |
||
1122 | #php4 |
||
1123 | #function &addXML($tag = 'xml', $text = '', $attributes = array(), &$offset) { |
||
1124 | #php4e |
||
1125 | #php5 |
||
1126 | function &addXML($tag = 'xml', $text = '', $attributes = array(), &$offset = null) { |
||
1127 | #php5e |
||
1128 | return $this->addChild(new $this->childClass_XML($this, $tag, $text, $attributes), $offset); |
||
1129 | } |
||
1130 | |||
1131 | /** |
||
1132 | * Add ASP node |
||
1133 | * @param string $tag Tag name after "%" |
||
1134 | * @param string $text |
||
1135 | * @param array $attributes Array of attributes (array('attribute' => 'value')) |
||
1136 | * @param int $offset Position to insert node, negative to count from end, null to append |
||
1137 | * @return DomNode Added node |
||
1138 | * @see addChild(); |
||
1139 | */ |
||
1140 | #php4 |
||
1141 | #function &addASP($tag = '', $text = '', $attributes = array(), &$offset) { |
||
1142 | #php4e |
||
1143 | #php5 |
||
1144 | function &addASP($tag = '', $text = '', $attributes = array(), &$offset = null) { |
||
1145 | #php5e |
||
1146 | return $this->addChild(new $this->childClass_ASP($this, $tag, $text, $attributes), $offset); |
||
1147 | } |
||
1148 | |||
1149 | /** |
||
1150 | * Delete a child node |
||
1151 | * @param int|DomNode $child Child(index) to delete, negative to count from end |
||
1152 | * @param bool $soft_delete False to call {@link delete()} from child |
||
1153 | */ |
||
1154 | 12 | function deleteChild($child, $soft_delete = false) { |
|
1155 | 12 | if (is_object($child)) { |
|
1156 | 12 | $child = $this->findChild($child); |
|
1157 | 12 | } elseif ($child < 0) { |
|
1158 | $child += count($this->children); |
||
1159 | } |
||
1160 | |||
1161 | 12 | if (!$soft_delete) { |
|
1162 | 4 | $this->children[$child]->delete(); |
|
1163 | 4 | } |
|
1164 | 12 | unset($this->children[$child]); |
|
1165 | |||
1166 | //Rebuild indices |
||
1167 | 12 | $tmp = array(); |
|
1168 | |||
1169 | //foreach($this->children as &$c) { |
||
1170 | // $tmp[] =& $c; |
||
1171 | //} |
||
1172 | 12 | foreach(array_keys($this->children) as $k) { |
|
1173 | 12 | $tmp[] =& $this->children[$k]; |
|
1174 | 12 | } |
|
1175 | 12 | $this->children = $tmp; |
|
1176 | 12 | } |
|
1177 | |||
1178 | /** |
||
1179 | * Wrap node |
||
1180 | * @param string|DomNode $node Wrapping node, string to create new element node |
||
1181 | * @param int $wrap_index Index to insert current node in wrapping node, -1 to append |
||
1182 | * @param int $node_index Index to insert wrapping node, null to keep at same position |
||
1183 | * @return DomNode Wrapping node |
||
1184 | */ |
||
1185 | 2 | function wrap($node, $wrap_index = -1, $node_index = null) { |
|
1186 | 2 | if ($node_index === null) { |
|
1187 | 2 | $node_index = $this->index(); |
|
1188 | 2 | } |
|
1189 | |||
1190 | 2 | if (!is_object($node)) { |
|
1191 | 2 | $node = $this->parent->addChild($node, $node_index); |
|
1192 | 2 | } elseif ($node->parent !== $this->parent) { |
|
1193 | $node->changeParent($this->parent, $node_index); |
||
1194 | } |
||
1195 | |||
1196 | 2 | $this->changeParent($node, $wrap_index); |
|
1197 | 2 | return $node; |
|
1198 | } |
||
1199 | |||
1200 | /** |
||
1201 | * Wrap child nodes |
||
1202 | * @param string|DomNode $node Wrapping node, string to create new element node |
||
1203 | * @param int $start Index from child node where to start wrapping, 0 for first element |
||
1204 | * @param int $end Index from child node where to end wrapping, -1 for last element |
||
1205 | * @param int $wrap_index Index to insert in wrapping node, -1 to append |
||
1206 | * @param int $node_index Index to insert current node, null to keep at same position |
||
1207 | * @return DomNode Wrapping node |
||
1208 | */ |
||
1209 | 1 | function wrapInner($node, $start = 0, $end = -1, $wrap_index = -1, $node_index = null) { |
|
1210 | 1 | if ($end < 0) { |
|
1211 | 1 | $end += count($this->children); |
|
1212 | 1 | } |
|
1213 | 1 | if ($node_index === null) { |
|
1214 | 1 | $node_index = $end + 1; |
|
1215 | 1 | } |
|
1216 | |||
1217 | 1 | if (!is_object($node)) { |
|
1218 | 1 | $node = $this->addChild($node, $node_index); |
|
1219 | 1 | } elseif ($node->parent !== $this) { |
|
1220 | $node->changeParent($this->parent, $node_index); |
||
1221 | } |
||
1222 | |||
1223 | 1 | $this->moveChildren($node, $wrap_index, $start, $end); |
|
1224 | 1 | return $node; |
|
1225 | } |
||
1226 | |||
1227 | /** |
||
1228 | * Number of attributes |
||
1229 | * @return int |
||
1230 | */ |
||
1231 | function attributeCount() { |
||
1232 | return count($this->attributes); |
||
1233 | } |
||
1234 | |||
1235 | /** |
||
1236 | * Find attribute using namespace, name or both |
||
1237 | * @param string|int $attr Negative int to count from end |
||
1238 | * @param string $compare "namespace", "name" or "total" |
||
1239 | * @param bool $case_sensitive Compare with case sensitivity |
||
1240 | * @return array array('ns', 'attr', 'ns:attr', index) |
||
1241 | * @access private |
||
1242 | */ |
||
1243 | 27 | protected function findAttribute($attr, $compare = 'total', $case_sensitive = false) { |
|
1244 | 27 | if (is_int($attr)) { |
|
1245 | if ($attr < 0) { |
||
1246 | $attr += count($this->attributes); |
||
1247 | } |
||
1248 | $keys = array_keys($this->attributes); |
||
1249 | return $this->findAttribute($keys[$attr], 'total', true); |
||
1250 | 27 | } else if ($compare === 'total') { |
|
1251 | 27 | $b = explode(':', $attr, 2); |
|
1252 | 27 | if ($case_sensitive) { |
|
1253 | $t =& $this->attributes; |
||
1254 | } else { |
||
1255 | 27 | $t = array_change_key_case($this->attributes); |
|
1256 | 27 | $attr = strtolower($attr); |
|
1257 | } |
||
1258 | |||
1259 | 27 | if (isset($t[$attr])) { |
|
1260 | 26 | $index = 0; |
|
1261 | 26 | foreach($this->attributes as $a => $v) { |
|
1262 | 26 | if (($v === $t[$attr]) && (strcasecmp($a, $attr) === 0)) { |
|
1263 | 26 | $attr = $a; |
|
1264 | 26 | $b = explode(':', $attr, 2); |
|
1265 | 26 | break; |
|
1266 | } |
||
1267 | 6 | ++$index; |
|
1268 | 26 | } |
|
1269 | |||
1270 | 26 | if (empty($b[1])) { |
|
1271 | 26 | return array(array('', $b[0], $attr, $index)); |
|
1272 | } else { |
||
1273 | return array(array($b[0], $b[1], $attr, $index)); |
||
1274 | } |
||
1275 | } else { |
||
1276 | 22 | return false; |
|
1277 | } |
||
1278 | } else { |
||
1279 | if ($this->attributes_ns === null) { |
||
1280 | $index = 0; |
||
1281 | foreach($this->attributes as $a => $v) { |
||
1282 | $b = explode(':', $a, 2); |
||
1283 | if (empty($b[1])) { |
||
1284 | $this->attributes_ns[$b[0]][] = array('', $b[0], $a, $index); |
||
1285 | } else { |
||
1286 | $this->attributes_ns[$b[1]][] = array($b[0], $b[1], $a, $index); |
||
1287 | } |
||
1288 | ++$index; |
||
1289 | } |
||
1290 | } |
||
1291 | |||
1292 | if ($case_sensitive) { |
||
1293 | $t =& $this->attributes_ns; |
||
1294 | } else { |
||
1295 | $t = array_change_key_case($this->attributes_ns); |
||
1296 | $attr = strtolower($attr); |
||
1297 | } |
||
1298 | |||
1299 | if ($compare === 'namespace') { |
||
1300 | $res = array(); |
||
1301 | foreach($t as $ar) { |
||
1302 | foreach($ar as $a) { |
||
1303 | if ($a[0] === $attr) { |
||
1304 | $res[] = $a; |
||
1305 | } |
||
1306 | } |
||
1307 | } |
||
1308 | return $res; |
||
1309 | } elseif ($compare === 'name') { |
||
1310 | return ((isset($t[$attr])) ? $t[$attr] : false); |
||
1311 | } else { |
||
1312 | trigger_error('Unknown comparison mode'); |
||
1313 | } |
||
1314 | } |
||
1315 | } |
||
1316 | |||
1317 | /** |
||
1318 | * Checks if node has attribute |
||
1319 | * @param string|int$attr Negative int to count from end |
||
1320 | * @param string $compare Find node using "namespace", "name" or "total" |
||
1321 | * @param bool $case_sensitive Compare with case sensitivity |
||
1322 | * @return bool |
||
1323 | */ |
||
1324 | function hasAttribute($attr, $compare = 'total', $case_sensitive = false) { |
||
1325 | return ((bool) $this->findAttribute($attr, $compare, $case_sensitive)); |
||
1326 | } |
||
1327 | |||
1328 | /** |
||
1329 | * Gets namespace of attribute(s) |
||
1330 | * @param string|int $attr Negative int to count from end |
||
1331 | * @param string $compare Find node using "namespace", "name" or "total" |
||
1332 | * @param bool $case_sensitive Compare with case sensitivity |
||
1333 | * @return string|array False if not found |
||
1334 | */ |
||
1335 | function getAttributeNS($attr, $compare = 'name', $case_sensitive = false) { |
||
1336 | $f = $this->findAttribute($attr, $compare, $case_sensitive); |
||
1337 | if (is_array($f) && $f) { |
||
1338 | if (count($f) === 1) { |
||
1339 | return $this->attributes[$f[0][0]]; |
||
1340 | } else { |
||
1341 | $res = array(); |
||
1342 | foreach($f as $a) { |
||
1343 | $res[] = $a[0]; |
||
1344 | } |
||
1345 | return $res; |
||
1346 | } |
||
1347 | } else { |
||
1348 | return false; |
||
1349 | } |
||
1350 | } |
||
1351 | |||
1352 | /** |
||
1353 | * Sets namespace of attribute(s) |
||
1354 | * @param string|int $attr Negative int to count from end |
||
1355 | * @param string $namespace |
||
1356 | * @param string $compare Find node using "namespace", "name" or "total" |
||
1357 | * @param bool $case_sensitive Compare with case sensitivity |
||
1358 | * @return bool |
||
1359 | */ |
||
1360 | function setAttributeNS($attr, $namespace, $compare = 'name', $case_sensitive = false) { |
||
1361 | $f = $this->findAttribute($attr, $compare, $case_sensitive); |
||
1362 | if (is_array($f) && $f) { |
||
1363 | if ($namespace) { |
||
1364 | $namespace .= ':'; |
||
1365 | } |
||
1366 | foreach($f as $a) { |
||
1367 | $val = $this->attributes[$a[2]]; |
||
1368 | unset($this->attributes[$a[2]]); |
||
1369 | $this->attributes[$namespace.$a[1]] = $val; |
||
1370 | } |
||
1371 | $this->attributes_ns = null; |
||
1372 | return true; |
||
1373 | } else { |
||
1374 | return false; |
||
1375 | } |
||
1376 | } |
||
1377 | |||
1378 | /** |
||
1379 | * Gets value(s) of attribute(s) |
||
1380 | * @param string|int $attr Negative int to count from end |
||
1381 | * @param string $compare Find node using "namespace", "name" or "total" |
||
1382 | * @param bool $case_sensitive Compare with case sensitivity |
||
1383 | * @return string|array |
||
1384 | */ |
||
1385 | 12 | function getAttribute($attr, $compare = 'total', $case_sensitive = false) { |
|
1386 | 12 | $f = $this->findAttribute($attr, $compare, $case_sensitive); |
|
1387 | 12 | if (is_array($f) && $f){ |
|
1388 | 12 | if (count($f) === 1) { |
|
1389 | 12 | return $this->attributes[$f[0][2]]; |
|
1390 | } else { |
||
1391 | $res = array(); |
||
1392 | foreach($f as $a) { |
||
1393 | $res[] = $this->attributes[$a[2]]; |
||
1394 | } |
||
1395 | return $res; |
||
1396 | } |
||
1397 | } else { |
||
1398 | 8 | return null; |
|
1399 | } |
||
1400 | } |
||
1401 | |||
1402 | /** |
||
1403 | * Sets value(s) of attribute(s) |
||
1404 | * @param string|int $attr Negative int to count from end |
||
1405 | * @param string $compare Find node using "namespace", "name" or "total" |
||
1406 | * @param bool $case_sensitive Compare with case sensitivity |
||
1407 | */ |
||
1408 | 9 | function setAttribute($attr, $val, $compare = 'total', $case_sensitive = false) { |
|
1409 | 9 | if ($val === null) { |
|
1410 | return $this->deleteAttribute($attr, $compare, $case_sensitive); |
||
1411 | } |
||
1412 | |||
1413 | 9 | $f = $this->findAttribute($attr, $compare, $case_sensitive); |
|
1414 | 9 | if (is_array($f) && $f) { |
|
1415 | 6 | foreach($f as $a) { |
|
1416 | 6 | $this->attributes[$a[2]] = (string) $val; |
|
1417 | 6 | } |
|
1418 | 6 | } else { |
|
1419 | 6 | $this->attributes[$attr] = (string) $val; |
|
1420 | } |
||
1421 | 9 | } |
|
1422 | |||
1423 | /** |
||
1424 | * Add new attribute |
||
1425 | * @param string $attr |
||
1426 | * @param string $val |
||
1427 | */ |
||
1428 | function addAttribute($attr, $val) { |
||
1429 | $this->setAttribute($attr, $val, 'total', true); |
||
1430 | } |
||
1431 | |||
1432 | /** |
||
1433 | * Delete attribute(s) |
||
1434 | * @param string|int $attr Negative int to count from end |
||
1435 | * @param string $compare Find node using "namespace", "name" or "total" |
||
1436 | * @param bool $case_sensitive Compare with case sensitivity |
||
1437 | */ |
||
1438 | 5 | function deleteAttribute($attr, $compare = 'total', $case_sensitive = false) { |
|
1439 | 5 | $f = $this->findAttribute($attr, $compare, $case_sensitive); |
|
1440 | 5 | if (is_array($f) && $f) { |
|
1441 | 5 | foreach($f as $a) { |
|
1442 | 5 | unset($this->attributes[$a[2]]); |
|
1443 | 5 | if ($this->attributes_ns !== null) { |
|
1444 | unset($this->attributes_ns[$a[1]]); |
||
1445 | } |
||
1446 | 5 | } |
|
1447 | 5 | } |
|
1448 | 5 | } |
|
1449 | |||
1450 | /** |
||
1451 | * Determine if node has a certain class |
||
1452 | * @param string $className |
||
1453 | * @return bool |
||
1454 | */ |
||
1455 | 2 | function hasClass($className) { |
|
1456 | 2 | return ($className && preg_match('`\b'.preg_quote($className).'\b`si', $this->class)); |
|
1457 | } |
||
1458 | |||
1459 | /** |
||
1460 | * Add new class(es) |
||
1461 | * @param string|array $className |
||
1462 | */ |
||
1463 | 4 | function addClass($className) { |
|
1464 | 4 | if (!is_array($className)) { |
|
1465 | 4 | $className = array($className); |
|
1466 | 4 | } |
|
1467 | 4 | $class = $this->class; |
|
1468 | 4 | foreach ($className as $c) { |
|
1469 | 4 | if (!(preg_match('`\b'.preg_quote($c).'\b`si', $class) > 0)) { |
|
1470 | 4 | $class .= ' '.$c; |
|
1471 | 4 | } |
|
1472 | 4 | } |
|
1473 | 4 | $this->class = trim($class); |
|
1474 | 4 | } |
|
1475 | |||
1476 | /** |
||
1477 | * Remove clas(ses) |
||
1478 | * @param string|array $className |
||
1479 | */ |
||
1480 | 2 | function removeClass($className) { |
|
1481 | 2 | if (!is_array($className)) { |
|
1482 | 2 | $className = array($className); |
|
1483 | 2 | } |
|
1484 | 2 | $class = $this->class; |
|
1485 | 2 | foreach ($className as $c) { |
|
1486 | 2 | $class = preg_replace('`\b'.preg_quote($c).'\b`si', '', $class); |
|
1487 | 2 | } |
|
1488 | 2 | if ($class) { |
|
1489 | 2 | $this->class = $class; |
|
1490 | 2 | } else { |
|
1491 | 1 | unset($this->class); |
|
1492 | } |
||
1493 | 2 | } |
|
1494 | |||
1495 | /** |
||
1496 | * Finds children using a callback function |
||
1497 | * @param callable $callback Function($node) that returns a bool |
||
1498 | * @param bool|int $recursive Check recursively |
||
1499 | * @param bool $check_self Include this node in search? |
||
1500 | * @return array |
||
1501 | */ |
||
1502 | function getChildrenByCallback($callback, $recursive = true, $check_self = false) { |
||
1503 | $count = $this->childCount(); |
||
1504 | if ($check_self && $callback($this)) { |
||
1505 | $res = array($this); |
||
1506 | } else { |
||
1507 | $res = array(); |
||
1508 | } |
||
1509 | |||
1510 | if ($count > 0) { |
||
1511 | if (is_int($recursive)) { |
||
1512 | $recursive = (($recursive > 1) ? $recursive - 1 : false); |
||
1513 | } |
||
1514 | |||
1515 | for ($i = 0; $i < $count; $i++) { |
||
1516 | if ($callback($this->children[$i])) { |
||
1517 | $res[] = $this->children[$i]; |
||
1518 | } |
||
1519 | if ($recursive) { |
||
1520 | $res = array_merge($res, $this->children[$i]->getChildrenByCallback($callback, $recursive)); |
||
1521 | } |
||
1522 | } |
||
1523 | } |
||
1524 | |||
1525 | return $res; |
||
1526 | } |
||
1527 | |||
1528 | /** |
||
1529 | * Finds children using the {$link match()} function |
||
1530 | * @param $conditions See {$link match()} |
||
1531 | * @param $custom_filters See {$link match()} |
||
1532 | * @param bool|int $recursive Check recursively |
||
1533 | * @param bool $check_self Include this node in search? |
||
1534 | * @return array |
||
1535 | */ |
||
1536 | 37 | function getChildrenByMatch($conditions, $recursive = true, $check_self = false, $custom_filters = array()) { |
|
1537 | 37 | $count = $this->childCount(); |
|
1538 | 37 | if ($check_self && $this->match($conditions, true, $custom_filters)) { |
|
1539 | $res = array($this); |
||
1540 | } else { |
||
1541 | 37 | $res = array(); |
|
1542 | } |
||
1543 | |||
1544 | 37 | if ($count > 0) { |
|
1545 | 37 | if (is_int($recursive)) { |
|
1546 | $recursive = (($recursive > 1) ? $recursive - 1 : false); |
||
1547 | } |
||
1548 | |||
1549 | 37 | for ($i = 0; $i < $count; $i++) { |
|
1550 | 37 | if ($this->children[$i]->match($conditions, true, $custom_filters)) { |
|
1551 | 37 | $res[] = $this->children[$i]; |
|
1552 | 37 | } |
|
1553 | 37 | if ($recursive) { |
|
1554 | 37 | $res = array_merge($res, $this->children[$i]->getChildrenByMatch($conditions, $recursive, false, $custom_filters)); |
|
1555 | 37 | } |
|
1556 | 37 | } |
|
1557 | 37 | } |
|
1558 | |||
1559 | 37 | return $res; |
|
1560 | } |
||
1561 | |||
1562 | /** |
||
1563 | * Checks if tag matches certain conditions |
||
1564 | * @param array $tags array('tag1', 'tag2') or array(array( |
||
1565 | * 'tag' => 'tag1', |
||
1566 | * 'operator' => 'or'/'and', |
||
1567 | * 'compare' => 'total'/'namespace'/'name', |
||
1568 | * 'case_sensitive' => true)) |
||
1569 | * @return bool |
||
1570 | * @internal Used by selector class |
||
1571 | * @see match() |
||
1572 | * @access private |
||
1573 | */ |
||
1574 | 23 | protected function match_tags($tags) { |
|
1575 | 23 | $res = false; |
|
1576 | |||
1577 | 23 | foreach($tags as $tag => $match) { |
|
1578 | 23 | if (!is_array($match)) { |
|
1579 | $match = array( |
||
1580 | 'match' => $match, |
||
1581 | 'operator' => 'or', |
||
1582 | 'compare' => 'total', |
||
1583 | 'case_sensitive' => false |
||
1584 | ); |
||
1585 | } else { |
||
1586 | 23 | if (is_int($tag)) { |
|
1587 | 22 | $tag = $match['tag']; |
|
1588 | 22 | } |
|
1589 | 23 | if (!isset($match['match'])) { |
|
1590 | $match['match'] = true; |
||
1591 | } |
||
1592 | 23 | if (!isset($match['operator'])) { |
|
1593 | 23 | $match['operator'] = 'or'; |
|
1594 | 23 | } |
|
1595 | 23 | if (!isset($match['compare'])) { |
|
1596 | 23 | $match['compare'] = 'total'; |
|
1597 | 23 | } |
|
1598 | 23 | if (!isset($match['case_sensitive'])) { |
|
1599 | 23 | $match['case_sensitive'] = false; |
|
1600 | 23 | } |
|
1601 | } |
||
1602 | |||
1603 | 23 | if (($match['operator'] === 'and') && (!$res)) { |
|
1604 | return false; |
||
1605 | 23 | } elseif (!($res && ($match['operator'] === 'or'))) { |
|
1606 | 23 | if ($match['compare'] === 'total') { |
|
1607 | 23 | $a = $this->tag; |
|
1608 | 23 | } elseif ($match['compare'] === 'namespace') { |
|
1609 | $a = $this->getNamespace(); |
||
1610 | } elseif ($match['compare'] === 'name') { |
||
1611 | $a = $this->getTag(); |
||
1612 | } |
||
1613 | |||
1614 | 23 | if ($match['case_sensitive']) { |
|
1615 | $res = (($a === $tag) === $match['match']); |
||
1616 | } else { |
||
1617 | 23 | $res = ((strcasecmp($a, $tag) === 0) === $match['match']); |
|
1618 | } |
||
1619 | 23 | } |
|
1620 | 23 | } |
|
1621 | |||
1622 | 23 | return $res; |
|
1623 | } |
||
1624 | |||
1625 | /** |
||
1626 | * Checks if attributes match certain conditions |
||
1627 | * @param array $attributes array('attr' => 'val') or array(array( |
||
1628 | * 'operator_value' => 'equals'/'='/'contains_regex'/etc |
||
1629 | * 'attribute' => 'attr', |
||
1630 | * 'value' => 'val', |
||
1631 | * 'match' => true, |
||
1632 | * 'operator_result' => 'or'/'and', |
||
1633 | * 'compare' => 'total'/'namespace'/'name', |
||
1634 | * 'case_sensitive' => true)) |
||
1635 | * @return bool |
||
1636 | * @internal Used by selector class |
||
1637 | * @see match() |
||
1638 | * @access private |
||
1639 | */ |
||
1640 | 19 | protected function match_attributes($attributes) { |
|
1641 | 19 | $res = false; |
|
1642 | |||
1643 | 19 | foreach($attributes as $attribute => $match) { |
|
1644 | 19 | if (!is_array($match)) { |
|
1645 | $match = array( |
||
1646 | 'operator_value' => 'equals', |
||
1647 | 'value' => $match, |
||
1648 | 'match' => true, |
||
1649 | 'operator_result' => 'or', |
||
1650 | 'compare' => 'total', |
||
1651 | 'case_sensitive' => false |
||
1652 | ); |
||
1653 | } else { |
||
1654 | 19 | if (is_int($attribute)) { |
|
1655 | 19 | $attribute = $match['attribute']; |
|
1656 | 19 | } |
|
1657 | 19 | if (!isset($match['match'])) { |
|
1658 | 13 | $match['match'] = true; |
|
1659 | 13 | } |
|
1660 | 19 | if (!isset($match['operator_result'])) { |
|
1661 | $match['operator_result'] = 'or'; |
||
1662 | } |
||
1663 | 19 | if (!isset($match['compare'])) { |
|
1664 | 13 | $match['compare'] = 'total'; |
|
1665 | 13 | } |
|
1666 | 19 | if (!isset($match['case_sensitive'])) { |
|
1667 | 19 | $match['case_sensitive'] = false; |
|
1668 | 19 | } |
|
1669 | } |
||
1670 | |||
1671 | 19 | if (is_string($match['value']) && (!$match['case_sensitive'])) { |
|
1672 | 19 | $match['value'] = strtolower($match['value']); |
|
1673 | 19 | } |
|
1674 | |||
1675 | 19 | if (($match['operator_result'] === 'and') && (!$res)) { |
|
1676 | return false; |
||
1677 | 19 | } elseif (!($res && ($match['operator_result'] === 'or'))) { |
|
1678 | 19 | $possibles = $this->findAttribute($attribute, $match['compare'], $match['case_sensitive']); |
|
1679 | |||
1680 | 19 | $has = (is_array($possibles) && $possibles); |
|
1681 | 19 | $res = (($match['value'] === $has) || (($match['match'] === false) && ($has === $match['match']))); |
|
1682 | |||
1683 | 19 | if ((!$res) && $has && is_string($match['value'])) { |
|
1684 | 19 | foreach($possibles as $a) { |
|
1685 | 19 | $val = $this->attributes[$a[2]]; |
|
1686 | 19 | if (is_string($val) && (!$match['case_sensitive'])) { |
|
1687 | 19 | $val = strtolower($val); |
|
1688 | 19 | } |
|
1689 | |||
1690 | 19 | switch($match['operator_value']) { |
|
1691 | 19 | case '%=': |
|
1692 | 19 | case 'contains_regex': |
|
1693 | $res = ((preg_match('`'.$match['value'].'`s', $val) > 0) === $match['match']); |
||
1694 | if ($res) break 1; else break 2; |
||
1695 | |||
1696 | 19 | case '|=': |
|
1697 | 19 | case 'contains_prefix': |
|
1698 | 1 | $res = ((preg_match('`\b'.preg_quote($match['value']).'[\-\s]`s', $val) > 0) === $match['match']); |
|
1699 | 1 | if ($res) break 1; else break 2; |
|
1700 | |||
1701 | 18 | case '~=': |
|
1702 | 18 | case 'contains_word': |
|
1703 | 12 | $res = ((preg_match('`\s'.preg_quote($match['value']).'\s`s', " $val ") > 0) === $match['match']); |
|
1704 | 12 | if ($res) break 1; else break 2; |
|
1705 | |||
1706 | 6 | case '*=': |
|
1707 | 6 | case 'contains': |
|
1708 | $res = ((strpos($val, $match['value']) !== false) === $match['match']); |
||
1709 | if ($res) break 1; else break 2; |
||
1710 | |||
1711 | 6 | case '$=': |
|
1712 | 6 | case 'ends_with': |
|
1713 | $res = ((substr($val, -strlen($match['value'])) === $match['value']) === $match['match']); |
||
1714 | if ($res) break 1; else break 2; |
||
1715 | |||
1716 | 6 | case '^=': |
|
1717 | 6 | case 'starts_with': |
|
1718 | $res = ((substr($val, 0, strlen($match['value'])) === $match['value']) === $match['match']); |
||
1719 | if ($res) break 1; else break 2; |
||
1720 | |||
1721 | 6 | case '!=': |
|
1722 | 6 | case 'not_equal': |
|
1723 | $res = (($val !== $match['value']) === $match['match']); |
||
1724 | if ($res) break 1; else break 2; |
||
1725 | |||
1726 | 6 | case '=': |
|
1727 | 6 | case 'equals': |
|
1728 | 6 | $res = (($val === $match['value']) === $match['match']); |
|
1729 | 6 | if ($res) break 1; else break 2; |
|
1730 | |||
1731 | case '>=': |
||
1732 | case 'bigger_than': |
||
1733 | $res = (($val >= $match['value']) === $match['match']); |
||
1734 | if ($res) break 1; else break 2; |
||
1735 | |||
1736 | case '<=': |
||
1737 | case 'smaller_than': |
||
1738 | $res = (($val >= $match['value']) === $match['match']); |
||
1739 | if ($res) break 1; else break 2; |
||
1740 | |||
1741 | default: |
||
1742 | trigger_error('Unknown operator "'.$match['operator_value'].'" to match attributes!'); |
||
1743 | return false; |
||
1744 | 19 | } |
|
1745 | 19 | } |
|
1746 | 19 | } |
|
1747 | 19 | } |
|
1748 | 19 | } |
|
1749 | |||
1750 | 19 | return $res; |
|
1751 | } |
||
1752 | |||
1753 | /** |
||
1754 | * Checks if node matches certain filters |
||
1755 | * @param array $tags array(array( |
||
1756 | * 'filter' => 'last-child', |
||
1757 | * 'params' => '123')) |
||
1758 | * @param array $custom_filters Custom map next to {@link $filter_map} |
||
1759 | * @return bool |
||
1760 | * @internal Used by selector class |
||
1761 | * @see match() |
||
1762 | * @access private |
||
1763 | */ |
||
1764 | 3 | protected function match_filters($conditions, $custom_filters = array()) { |
|
1765 | 3 | foreach($conditions as $c) { |
|
1766 | 3 | $c['filter'] = strtolower($c['filter']); |
|
1767 | 3 | if (isset($this->filter_map[$c['filter']])) { |
|
1768 | 3 | if (!$this->{$this->filter_map[$c['filter']]}($c['params'])) { |
|
1769 | 3 | return false; |
|
1770 | } |
||
1771 | 3 | } elseif (isset($custom_filters[$c['filter']])) { |
|
1772 | if (!call_user_func($custom_filters[$c['filter']], $this, $c['params'])) { |
||
1773 | return false; |
||
1774 | } |
||
1775 | } else { |
||
1776 | trigger_error('Unknown filter "'.$c['filter'].'"!'); |
||
1777 | return false; |
||
1778 | } |
||
1779 | 3 | } |
|
1780 | |||
1781 | 3 | return true; |
|
1782 | } |
||
1783 | |||
1784 | /** |
||
1785 | * Checks if node matches certain conditions |
||
1786 | * @param array $tags array('tags' => array(tag_conditions), 'attributes' => array(attr_conditions), 'filters' => array(filter_conditions)) |
||
1787 | * @param array $match Should conditions evaluate to true? |
||
1788 | * @param array $custom_filters Custom map next to {@link $filter_map} |
||
1789 | * @return bool |
||
1790 | * @internal Used by selector class |
||
1791 | * @see match_tags(); |
||
1792 | * @see match_attributes(); |
||
1793 | * @see match_filters(); |
||
1794 | * @access private |
||
1795 | */ |
||
1796 | 37 | function match($conditions, $match = true, $custom_filters = array()) { |
|
1797 | 37 | $t = isset($conditions['tags']); |
|
1798 | 37 | $a = isset($conditions['attributes']); |
|
1799 | 37 | $f = isset($conditions['filters']); |
|
1800 | |||
1801 | 37 | if (!($t || $a || $f)) { |
|
1802 | 37 | if (is_array($conditions) && $conditions) { |
|
1803 | 37 | foreach($conditions as $c) { |
|
1804 | 37 | if ($this->match($c, $match)) { |
|
1805 | 37 | return true; |
|
1806 | } |
||
1807 | 36 | } |
|
1808 | 36 | } |
|
1809 | |||
1810 | 36 | return false; |
|
1811 | } else { |
||
1812 | 37 | if (($t && (!$this->match_tags($conditions['tags']))) === $match) { |
|
1813 | 22 | return false; |
|
1814 | } |
||
1815 | |||
1816 | 37 | if (($a && (!$this->match_attributes($conditions['attributes']))) === $match) { |
|
1817 | 19 | return false; |
|
1818 | } |
||
1819 | |||
1820 | 37 | if (($f && (!$this->match_filters($conditions['filters'], $custom_filters))) === $match) { |
|
1821 | 3 | return false; |
|
1822 | } |
||
1823 | |||
1824 | 37 | return true; |
|
1825 | } |
||
1826 | } |
||
1827 | |||
1828 | /** |
||
1829 | * Finds children that match a certain attribute |
||
1830 | * @param string $attribute |
||
1831 | * @param string $value |
||
1832 | * @param string $mode Compare mode, "equals", "|=", "contains_regex", etc. |
||
1833 | * @param string $compare "total"/"namespace"/"name" |
||
1834 | * @param bool|int $recursive |
||
1835 | * @return array |
||
1836 | */ |
||
1837 | function getChildrenByAttribute($attribute, $value, $mode = 'equals', $compare = 'total', $recursive = true) { |
||
1838 | if ($this->childCount() < 1) { |
||
1839 | return array(); |
||
1840 | } |
||
1841 | |||
1842 | $mode = explode(' ', strtolower($mode)); |
||
1843 | $match = ((isset($mode[1]) && ($mode[1] === 'not')) ? 'false' : 'true'); |
||
1844 | |||
1845 | return $this->getChildrenByMatch( |
||
1846 | array( |
||
1847 | 'attributes' => array( |
||
1848 | $attribute => array( |
||
1849 | 'operator_value' => $mode[0], |
||
1850 | 'value' => $value, |
||
1851 | 'match' => $match, |
||
1852 | 'compare' => $compare |
||
1853 | ) |
||
1854 | ) |
||
1855 | ), |
||
1856 | $recursive |
||
1857 | ); |
||
1858 | } |
||
1859 | |||
1860 | /** |
||
1861 | * Finds children that match a certain tag |
||
1862 | * @param string $tag |
||
1863 | * @param string $compare "total"/"namespace"/"name" |
||
1864 | * @param bool|int $recursive |
||
1865 | * @return array |
||
1866 | */ |
||
1867 | function getChildrenByTag($tag, $compare = 'total', $recursive = true) { |
||
1868 | if ($this->childCount() < 1) { |
||
1869 | return array(); |
||
1870 | } |
||
1871 | |||
1872 | $tag = explode(' ', strtolower($tag)); |
||
1873 | $match = ((isset($tag[1]) && ($tag[1] === 'not')) ? 'false' : 'true'); |
||
1874 | |||
1875 | return $this->getChildrenByMatch( |
||
1876 | array( |
||
1877 | 'tags' => array( |
||
1878 | $tag[0] => array( |
||
1879 | 'match' => $match, |
||
1880 | 'compare' => $compare |
||
1881 | ) |
||
1882 | ) |
||
1883 | ), |
||
1884 | $recursive |
||
1885 | ); |
||
1886 | } |
||
1887 | |||
1888 | /** |
||
1889 | * Finds all children using ID attribute |
||
1890 | * @param string $id |
||
1891 | * @param bool|int $recursive |
||
1892 | * @return array |
||
1893 | */ |
||
1894 | function getChildrenByID($id, $recursive = true) { |
||
1895 | return $this->getChildrenByAttribute('id', $id, 'equals', 'total', $recursive); |
||
1896 | } |
||
1897 | |||
1898 | /** |
||
1899 | * Finds all children using class attribute |
||
1900 | * @param string $class |
||
1901 | * @param bool|int $recursive |
||
1902 | * @return array |
||
1903 | */ |
||
1904 | function getChildrenByClass($class, $recursive = true) { |
||
1905 | return $this->getChildrenByAttribute('class', $class, 'equals', 'total', $recursive); |
||
1906 | } |
||
1907 | |||
1908 | /** |
||
1909 | * Finds all children using name attribute |
||
1910 | * @param string $name |
||
1911 | * @param bool|int $recursive |
||
1912 | * @return array |
||
1913 | */ |
||
1914 | function getChildrenByName($name, $recursive = true) { |
||
1915 | return $this->getChildrenByAttribute('name', $name, 'equals', 'total', $recursive); |
||
1916 | } |
||
1917 | |||
1918 | /** |
||
1919 | * Performs a css query on the node. |
||
1920 | * @param string $query |
||
1921 | * @return IQuery Returns the matching nodes from the query. |
||
1922 | */ |
||
1923 | 36 | public function query($query = '*') { |
|
1924 | 36 | $select = $this->select($query); |
|
1925 | 36 | $result = new \pQuery((array)$select); |
|
1926 | 36 | return $result; |
|
1927 | } |
||
1928 | |||
1929 | /** |
||
1930 | * Performs css query on node |
||
1931 | * @param string $query |
||
1932 | * @param int|bool $index True to return node instead of array if only 1 match, |
||
1933 | * false to return array, int to return match at index, negative int to count from end |
||
1934 | * @param bool|int $recursive |
||
1935 | * @param bool $check_self Include this node in search or only search child nodes |
||
1936 | * @return DomNode[]|DomNode Returns an array of matching {@link DomNode} objects |
||
1937 | * or a single {@link DomNode} if `$index` is not false. |
||
1938 | */ |
||
1939 | 37 | function select($query = '*', $index = false, $recursive = true, $check_self = false) { |
|
1940 | 37 | $s = new $this->selectClass($this, $query, $check_self, $recursive); |
|
1941 | 37 | $res = $s->result; |
|
1942 | 37 | unset($s); |
|
1943 | 37 | if (is_array($res) && ($index === true) && (count($res) === 1)) { |
|
1944 | return $res[0]; |
||
1945 | 37 | } elseif (is_int($index) && is_array($res)) { |
|
1946 | if ($index < 0) { |
||
1947 | $index += count($res); |
||
1948 | } |
||
1949 | return ($index < count($res)) ? $res[$index] : null; |
||
1950 | } else { |
||
1951 | 37 | return $res; |
|
1952 | } |
||
1953 | } |
||
1954 | |||
1955 | /** |
||
1956 | * Checks if node matches css query filter ":root" |
||
1957 | * @return bool |
||
1958 | * @see match() |
||
1959 | * @access private |
||
1960 | */ |
||
1961 | protected function filter_root() { |
||
1962 | return (strtolower($this->tag) === 'html'); |
||
1963 | } |
||
1964 | |||
1965 | /** |
||
1966 | * Checks if node matches css query filter ":nth-child(n)" |
||
1967 | * @param string $n 1-based index |
||
1968 | * @return bool |
||
1969 | * @see match() |
||
1970 | * @access private |
||
1971 | */ |
||
1972 | protected function filter_nchild($n) { |
||
1973 | return ($this->index(false)+1 === (int) $n); |
||
1974 | } |
||
1975 | |||
1976 | /** |
||
1977 | * Checks if node matches css query filter ":gt(n)" |
||
1978 | * @param string $n 0-based index |
||
1979 | * @return bool |
||
1980 | * @see match() |
||
1981 | * @access private |
||
1982 | */ |
||
1983 | protected function filter_gt($n) { |
||
1984 | return ($this->index(false) > (int) $n); |
||
1985 | } |
||
1986 | |||
1987 | /** |
||
1988 | * Checks if node matches css query filter ":lt(n)" |
||
1989 | * @param string $n 0-based index |
||
1990 | * @return bool |
||
1991 | * @see match() |
||
1992 | * @access private |
||
1993 | */ |
||
1994 | protected function filter_lt($n) { |
||
1995 | return ($this->index(false) < (int) $n); |
||
1996 | } |
||
1997 | |||
1998 | /** |
||
1999 | * Checks if node matches css query filter ":nth-last-child(n)" |
||
2000 | * @param string $n 1-based index |
||
2001 | * @return bool |
||
2002 | * @see match() |
||
2003 | * @access private |
||
2004 | */ |
||
2005 | protected function filter_nlastchild($n) { |
||
2006 | if ($this->parent === null) { |
||
2007 | return false; |
||
2008 | } else { |
||
2009 | return ($this->parent->childCount(true) - $this->index(false) === (int) $n); |
||
2010 | } |
||
2011 | } |
||
2012 | |||
2013 | /** |
||
2014 | * Checks if node matches css query filter ":nth-of-type(n)" |
||
2015 | * @param string $n 1-based index |
||
2016 | * @return bool |
||
2017 | * @see match() |
||
2018 | * @access private |
||
2019 | */ |
||
2020 | protected function filter_ntype($n) { |
||
2021 | return ($this->typeIndex()+1 === (int) $n); |
||
2022 | } |
||
2023 | |||
2024 | /** |
||
2025 | * Checks if node matches css query filter ":nth-last-of-type(n)" |
||
2026 | * @param string $n 1-based index |
||
2027 | * @return bool |
||
2028 | * @see match() |
||
2029 | * @access private |
||
2030 | */ |
||
2031 | protected function filter_nlastype($n) { |
||
2032 | if ($this->parent === null) { |
||
2033 | return false; |
||
2034 | } else { |
||
2035 | return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) - $this->typeIndex() === (int) $n); |
||
2036 | } |
||
2037 | } |
||
2038 | |||
2039 | /** |
||
2040 | * Checks if node matches css query filter ":odd" |
||
2041 | * @return bool |
||
2042 | * @see match() |
||
2043 | * @access private |
||
2044 | */ |
||
2045 | protected function filter_odd() { |
||
2046 | return (($this->index(false) & 1) === 1); |
||
2047 | } |
||
2048 | |||
2049 | /** |
||
2050 | * Checks if node matches css query filter ":even" |
||
2051 | * @return bool |
||
2052 | * @see match() |
||
2053 | * @access private |
||
2054 | */ |
||
2055 | protected function filter_even() { |
||
2056 | return (($this->index(false) & 1) === 0); |
||
2057 | } |
||
2058 | |||
2059 | /** |
||
2060 | * Checks if node matches css query filter ":every(n)" |
||
2061 | * @return bool |
||
2062 | * @see match() |
||
2063 | * @access private |
||
2064 | */ |
||
2065 | protected function filter_every($n) { |
||
2066 | return (($this->index(false) % (int) $n) === 0); |
||
2067 | } |
||
2068 | |||
2069 | /** |
||
2070 | * Checks if node matches css query filter ":first" |
||
2071 | * @return bool |
||
2072 | * @see match() |
||
2073 | * @access private |
||
2074 | */ |
||
2075 | protected function filter_first() { |
||
2076 | return ($this->index(false) === 0); |
||
2077 | } |
||
2078 | |||
2079 | /** |
||
2080 | * Checks if node matches css query filter ":last" |
||
2081 | * @return bool |
||
2082 | * @see match() |
||
2083 | * @access private |
||
2084 | */ |
||
2085 | protected function filter_last() { |
||
2086 | if ($this->parent === null) { |
||
2087 | return false; |
||
2088 | } else { |
||
2089 | return ($this->parent->childCount(true) - 1 === $this->index(false)); |
||
2090 | } |
||
2091 | } |
||
2092 | |||
2093 | /** |
||
2094 | * Checks if node matches css query filter ":first-of-type" |
||
2095 | * @return bool |
||
2096 | * @see match() |
||
2097 | * @access private |
||
2098 | */ |
||
2099 | protected function filter_firsttype() { |
||
2100 | return ($this->typeIndex() === 0); |
||
2101 | } |
||
2102 | |||
2103 | /** |
||
2104 | * Checks if node matches css query filter ":last-of-type" |
||
2105 | * @return bool |
||
2106 | * @see match() |
||
2107 | * @access private |
||
2108 | */ |
||
2109 | protected function filter_lasttype() { |
||
2110 | if ($this->parent === null) { |
||
2111 | return false; |
||
2112 | } else { |
||
2113 | return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) - 1 === $this->typeIndex()); |
||
2114 | } |
||
2115 | } |
||
2116 | |||
2117 | /** |
||
2118 | * Checks if node matches css query filter ":only-child" |
||
2119 | * @return bool |
||
2120 | * @see match() |
||
2121 | * @access private |
||
2122 | */ |
||
2123 | protected function filter_onlychild() { |
||
2124 | if ($this->parent === null) { |
||
2125 | return false; |
||
2126 | } else { |
||
2127 | return ($this->parent->childCount(true) === 1); |
||
2128 | } |
||
2129 | } |
||
2130 | |||
2131 | /** |
||
2132 | * Checks if node matches css query filter ":only-of-type" |
||
2133 | * @return bool |
||
2134 | * @see match() |
||
2135 | * @access private |
||
2136 | */ |
||
2137 | protected function filter_onlytype() { |
||
2138 | if ($this->parent === null) { |
||
2139 | return false; |
||
2140 | } else { |
||
2141 | return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) === 1); |
||
2142 | } |
||
2143 | } |
||
2144 | |||
2145 | /** |
||
2146 | * Checks if node matches css query filter ":empty" |
||
2147 | * @return bool |
||
2148 | * @see match() |
||
2149 | * @access private |
||
2150 | */ |
||
2151 | protected function filter_empty() { |
||
2152 | return ($this->childCount() === 0); |
||
2153 | } |
||
2154 | |||
2155 | /** |
||
2156 | * Checks if node matches css query filter ":not-empty" |
||
2157 | * @return bool |
||
2158 | * @see match() |
||
2159 | * @access private |
||
2160 | */ |
||
2161 | protected function filter_notempty() { |
||
2162 | return ($this->childCount() !== 0); |
||
2163 | } |
||
2164 | |||
2165 | /** |
||
2166 | * Checks if node matches css query filter ":has-text" |
||
2167 | * @return bool |
||
2168 | * @see match() |
||
2169 | * @access private |
||
2170 | */ |
||
2171 | protected function filter_hastext() { |
||
2172 | return ($this->getPlainText() !== ''); |
||
2173 | } |
||
2174 | |||
2175 | /** |
||
2176 | * Checks if node matches css query filter ":no-text" |
||
2177 | * @return bool |
||
2178 | * @see match() |
||
2179 | * @access private |
||
2180 | */ |
||
2181 | protected function filter_notext() { |
||
2182 | return ($this->getPlainText() === ''); |
||
2183 | } |
||
2184 | |||
2185 | /** |
||
2186 | * Checks if node matches css query filter ":lang(s)" |
||
2187 | * @param string $lang |
||
2188 | * @return bool |
||
2189 | * @see match() |
||
2190 | * @access private |
||
2191 | */ |
||
2192 | protected function filter_lang($lang) { |
||
2193 | return ($this->lang === $lang); |
||
2194 | } |
||
2195 | |||
2196 | /** |
||
2197 | * Checks if node matches css query filter ":contains(s)" |
||
2198 | * @param string $text |
||
2199 | * @return bool |
||
2200 | * @see match() |
||
2201 | * @access private |
||
2202 | */ |
||
2203 | protected function filter_contains($text) { |
||
2204 | return (strpos($this->getPlainTextUTF8(), $text) !== false); |
||
2205 | } |
||
2206 | |||
2207 | /** |
||
2208 | * Checks if node matches css query filter ":has(s)" |
||
2209 | * @param string $selector |
||
2210 | * @return bool |
||
2211 | * @see match() |
||
2212 | * @access private |
||
2213 | */ |
||
2214 | protected function filter_has($selector) { |
||
2215 | $s = $this->select((string) $selector, false); |
||
2216 | return (is_array($s) && (count($s) > 0)); |
||
2217 | } |
||
2218 | |||
2219 | /** |
||
2220 | * Checks if node matches css query filter ":not(s)" |
||
2221 | * @param string $selector |
||
2222 | * @return bool |
||
2223 | * @see match() |
||
2224 | * @access private |
||
2225 | */ |
||
2226 | protected function filter_not($selector) { |
||
2227 | $s = $this->select((string) $selector, false, true, true); |
||
2228 | return ((!is_array($s)) || (array_search($this, $s, true) === false)); |
||
2229 | } |
||
2230 | |||
2231 | /** |
||
2232 | * Checks if node matches css query filter ":element" |
||
2233 | * @return bool |
||
2234 | * @see match() |
||
2235 | * @access private |
||
2236 | */ |
||
2237 | protected function filter_element() { |
||
2238 | return true; |
||
2239 | } |
||
2240 | |||
2241 | /** |
||
2242 | * Checks if node matches css query filter ":text" |
||
2243 | * @return bool |
||
2244 | * @see match() |
||
2245 | * @access private |
||
2246 | */ |
||
2247 | protected function filter_text() { |
||
2248 | return false; |
||
2249 | } |
||
2250 | |||
2251 | /** |
||
2252 | * Checks if a node matches css query filter ":checked" |
||
2253 | * @return bool |
||
2254 | * @see match() |
||
2255 | */ |
||
2256 | 1 | protected function filter_checked() { |
|
2257 | 1 | $attr = $this->getAttribute('checked'); |
|
2258 | 1 | if (is_array($attr)) |
|
2259 | 1 | $attr = reset($attr); |
|
2260 | 1 | return strcasecmp($attr, 'checked') === 0; |
|
2261 | } |
||
2262 | |||
2263 | /** |
||
2264 | * Checks if node matches css query filter ":comment" |
||
2265 | * @return bool |
||
2266 | * @see match() |
||
2267 | * @access private |
||
2268 | */ |
||
2269 | protected function filter_comment() { |
||
2270 | return false; |
||
2271 | } |
||
2272 | |||
2273 | /** |
||
2274 | * Checks if a node matches css query filter ":selected" |
||
2275 | * @return bool |
||
2276 | * @see match() |
||
2277 | */ |
||
2278 | 2 | protected function filter_selected() { |
|
2279 | 2 | $attr = $this->getAttribute('selected'); |
|
2280 | 2 | if (is_array($attr)) |
|
2281 | 2 | $attr = reset($attr); |
|
2282 | |||
2283 | 2 | return strcasecmp($attr, 'selected') === 0; |
|
2284 | } |
||
2285 | |||
2286 | 1 | public function after($content) { |
|
2287 | 1 | $offset = $this->index() + 1; |
|
2288 | 1 | $parent = $this->parent; |
|
2289 | 1 | $nodes = $this->createNodes($content); |
|
2290 | |||
2291 | 1 | foreach ($nodes as $node) { |
|
2292 | 1 | $node->changeParent($parent, $offset); |
|
2293 | 1 | } |
|
2294 | 1 | return $this; |
|
2295 | } |
||
2296 | |||
2297 | |||
2298 | /** |
||
2299 | * Create a {@link DomNode} from its string representation. |
||
2300 | * @param string|DomNode $content |
||
2301 | * @return DomNode |
||
2302 | */ |
||
2303 | 2 | protected function createNode($content) { |
|
2304 | 2 | $nodes = $this->createNodes($content); |
|
2305 | 2 | return reset($nodes); |
|
2306 | } |
||
2307 | |||
2308 | /** |
||
2309 | * Create an array of {@link DomNode} objects from their string representation. |
||
2310 | * @param string|DomNode $content |
||
2311 | * @return DomNode[] |
||
2312 | */ |
||
2313 | 9 | protected function createNodes($content) { |
|
2314 | 9 | if (is_string($content)) { |
|
2315 | 9 | if (strpos($content, ' ') === false) { |
|
2316 | 1 | $nodes = array(new $this->childClass($content, $this)); |
|
2317 | 1 | } else { |
|
2318 | 8 | $node = new $this->parserClass($content); |
|
2319 | 8 | $nodes = $node->root->children; |
|
2320 | } |
||
2321 | 9 | } else { |
|
2322 | $nodes = (array)$content; |
||
2323 | } |
||
2324 | 9 | return $nodes; |
|
2325 | } |
||
2326 | |||
2327 | 1 | public function append($content) { |
|
2328 | 1 | $nodes = $this->createNodes($content); |
|
2329 | 1 | foreach ($nodes as $node) { |
|
2330 | 1 | $node->changeParent($this); |
|
2331 | 1 | } |
|
2332 | 1 | return $this; |
|
2333 | } |
||
2334 | |||
2335 | 9 | public function attr($name, $value = null) { |
|
2336 | 9 | if ($value === null) |
|
2337 | 9 | return $this->getAttribute($name); |
|
2338 | |||
2339 | 4 | $this->setAttribute($name, $value); |
|
2340 | 4 | return $this; |
|
2341 | } |
||
2342 | |||
2343 | 1 | public function before($content) { |
|
2344 | 1 | $offset = $this->index(); |
|
2345 | 1 | $parent = $this->parent; |
|
2346 | 1 | $nodes = $this->createNodes($content); |
|
2347 | |||
2348 | 1 | foreach ($nodes as $node) { |
|
2349 | 1 | $node->changeParent($parent, $offset); |
|
2350 | 1 | } |
|
2351 | |||
2352 | 1 | return $this; |
|
2353 | } |
||
2354 | |||
2355 | public function count() { |
||
2356 | return 1; |
||
2357 | } |
||
2358 | |||
2359 | // public function css($name, $value = null) { |
||
2360 | // |
||
2361 | // } |
||
2362 | |||
2363 | 1 | public function prepend($content = null) { |
|
2364 | 1 | $offset = 0; |
|
2365 | 1 | $parent = $this; |
|
2366 | 1 | $nodes = $this->createNodes($content); |
|
2367 | |||
2368 | 1 | foreach ($nodes as $node) { |
|
2369 | 1 | $node->changeParent($parent, $offset); |
|
2370 | 1 | } |
|
2371 | |||
2372 | 1 | return $this; |
|
2373 | } |
||
2374 | |||
2375 | 4 | public function prop($name, $value = null) { |
|
2376 | 4 | switch (strtolower($name)) { |
|
2377 | 4 | case 'checked': |
|
2378 | 4 | case 'disabled': |
|
2379 | 4 | case 'selected': |
|
2380 | 3 | if ($value !== null) { |
|
2381 | 2 | if ($value) { |
|
2382 | 1 | $this->attr($name, $name); |
|
2383 | 1 | } else { |
|
2384 | 1 | $this->removeAttr($name); |
|
2385 | } |
||
2386 | 2 | return $this; |
|
2387 | } |
||
2388 | 3 | return $this->attr($name) == $name; |
|
2389 | 1 | case 'tagname': |
|
2390 | 1 | return $this->tagName($value); |
|
2391 | } |
||
2392 | // The property is not supported, degrade gracefully |
||
2393 | if ($value === null) |
||
2394 | return $this; |
||
2395 | else |
||
2396 | return null; |
||
2397 | } |
||
2398 | |||
2399 | 4 | public function remove($selector = null) { |
|
2400 | 4 | if ($selector == null) { |
|
2401 | 3 | $this->delete(); |
|
2402 | 3 | } else { |
|
2403 | 1 | $nodes = (array)$this->select($selector); |
|
2404 | 1 | foreach ($nodes as $node) { |
|
2405 | 1 | $node->delete(); |
|
2406 | 1 | } |
|
2407 | } |
||
2408 | 4 | } |
|
2409 | |||
2410 | 3 | public function removeAttr($name) { |
|
2411 | 3 | $this->deleteAttribute($name); |
|
2412 | |||
2413 | 3 | return $this; |
|
2414 | } |
||
2415 | |||
2416 | 2 | function replaceWith($content) { |
|
2417 | 2 | $node_index = $this->index(); |
|
2418 | |||
2419 | // Add the new node. |
||
2420 | 2 | $node = $this->createNode($content); |
|
2421 | 2 | $node->changeParent($this->parent, $node_index); |
|
2422 | |||
2423 | // Remove this node. |
||
2424 | 2 | $this->remove(); |
|
2425 | |||
2426 | 2 | return $node; |
|
2427 | } |
||
2428 | |||
2429 | /** |
||
2430 | * @param type $value |
||
2431 | * @return string|DomNode |
||
2432 | */ |
||
2433 | 2 | public function tagName($value = null) { |
|
2434 | 2 | if ($value !== null) { |
|
2435 | 2 | $this->setTag($value); |
|
2436 | 2 | return $this; |
|
2437 | } |
||
2438 | 1 | return $this->getTag(); |
|
2439 | } |
||
2440 | |||
2441 | 2 | public function text($value = null) { |
|
2442 | 2 | if ($value === null) |
|
2443 | 2 | return $this->getPlainText(); |
|
2444 | |||
2445 | 1 | $this->setPlainText($value); |
|
2446 | 1 | return $this; |
|
2447 | } |
||
2448 | |||
2449 | 1 | public function toggleClass($classname, $switch = null) { |
|
2450 | 1 | if ($switch === true) { |
|
2451 | 1 | $this->addClass($classname); |
|
2452 | 1 | } elseif ($switch === false) { |
|
2453 | 1 | $this->removeClass($classname); |
|
2454 | 1 | } else { |
|
2455 | 1 | if ($this->hasClass($classname)) |
|
2456 | 1 | $this->removeClass($classname); |
|
2457 | else |
||
2458 | 1 | $this->addClass($classname); |
|
2459 | } |
||
2460 | 1 | return $this; |
|
2461 | } |
||
2462 | |||
2463 | 1 | public function unwrap() { |
|
2464 | 1 | $this->parent->detach(true); |
|
2465 | 1 | return $this; |
|
2466 | } |
||
2467 | |||
2468 | 5 | public function val($value = null) { |
|
2469 | 5 | switch (strtolower($this->tag)) { |
|
2470 | 5 | case 'select': |
|
2471 | 2 | if ($value === null) { |
|
2472 | // Return the value of a selected child. |
||
2473 | 2 | return $this->query('option:selected')->attr('value'); |
|
2474 | } else { |
||
2475 | // Select the option with the right value and deselect the others. |
||
2476 | 1 | foreach ($this->query('option') as $option) { |
|
2477 | 1 | if ($option->attr('value') == $value) { |
|
2478 | 1 | $option->attr('selected', 'selected'); |
|
2479 | 1 | } else { |
|
2480 | 1 | $option->removeAttr('selected'); |
|
2481 | } |
||
2482 | 1 | } |
|
2483 | 1 | return $this; |
|
2484 | } |
||
2485 | 3 | case 'textarea': |
|
2486 | 1 | if ($value === null) { |
|
2487 | // Return the contents of the textarea. |
||
2488 | 1 | return $this->getInnerText(); |
|
2489 | } else { |
||
2490 | // Set the contents of the textarea. |
||
2491 | 1 | $this->setInnerText($value); |
|
2492 | 1 | return $this; |
|
2493 | } |
||
2494 | 2 | case 'input': |
|
2495 | 2 | switch (strtolower($this->getAttribute('type'))) { |
|
2496 | 2 | case 'checkbox': |
|
2497 | 1 | if ($value === null) |
|
2498 | 1 | return $this->prop('checked') ? $this->getAttribute('value') : null; |
|
2499 | else { |
||
2500 | 1 | if (!$value) { |
|
2501 | 1 | $this->deleteAttribute('checked'); |
|
2502 | 1 | } else { |
|
2503 | 1 | $this->setAttribute('value', $value); |
|
2504 | 1 | $this->setAttribute('checked', 'checked'); |
|
2505 | } |
||
2506 | 1 | return $this; |
|
2507 | } |
||
2508 | 1 | } |
|
2509 | 1 | } |
|
2510 | |||
2511 | // Other node types can just get/set the value attribute. |
||
2512 | 1 | if ($value !== null) { |
|
2513 | 1 | $this->setAttribute('value', $value); |
|
2514 | 1 | return $this; |
|
2515 | } |
||
2516 | 1 | return $this->getAttribute('value'); |
|
2517 | } |
||
2518 | |||
2519 | } |
||
2520 | |||
2521 | /** |
||
2522 | * Node subclass for text |
||
2523 | */ |
||
2524 | class TextNode extends DomNode { |
||
2525 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
2526 | #static $NODE_TYPE = self::NODE_TEXT; |
||
2527 | #php4e |
||
2528 | #php5 |
||
2529 | const NODE_TYPE = self::NODE_TEXT; |
||
2530 | #php5e |
||
2531 | var $tag = '~text~'; |
||
2532 | |||
2533 | /** |
||
2534 | * @var string |
||
2535 | */ |
||
2536 | var $text = ''; |
||
2537 | |||
2538 | /** |
||
2539 | * Class constructor |
||
2540 | * @param DomNode $parent |
||
2541 | * @param string $text |
||
2542 | */ |
||
2543 | 37 | function __construct($parent, $text = '') { |
|
2544 | 37 | $this->parent = $parent; |
|
2545 | 37 | $this->text = $text; |
|
2546 | 37 | } |
|
2547 | |||
2548 | #php4 PHP4 class constructor compatibility |
||
2549 | #function TextNode($parent, $text = '') {return $this->__construct($parent, $text);} |
||
2550 | #php4e |
||
2551 | |||
2552 | function isText() {return true;} |
||
2553 | function isTextOrComment() {return true;} |
||
2554 | protected function filter_element() {return false;} |
||
2555 | protected function filter_text() {return true;} |
||
2556 | function toString_attributes() {return '';} |
||
2557 | function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} |
||
2558 | function toString($attributes = true, $recursive = true, $content_only = false) {return $this->text;} |
||
2559 | |||
2560 | /** |
||
2561 | * {@inheritdoc} |
||
2562 | */ |
||
2563 | 1 | public function text($value = null) { |
|
2564 | 1 | if ($value !== null) { |
|
2565 | 1 | $this->text = $value; |
|
2566 | 1 | return $this; |
|
2567 | } |
||
2568 | 1 | return $this->text; |
|
2569 | } |
||
2570 | |||
2571 | /** |
||
2572 | * {@inheritdoc} |
||
2573 | */ |
||
2574 | 1 | public function html($value = null) { |
|
2575 | 1 | if ($value !== null) { |
|
2576 | $this->text = $value; |
||
2577 | return $this; |
||
2578 | } |
||
2579 | 1 | return $this->text; |
|
2580 | } |
||
2581 | } |
||
2582 | |||
2583 | /** |
||
2584 | * Node subclass for comments |
||
2585 | */ |
||
2586 | class CommentNode extends DomNode { |
||
2587 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
2588 | #static $NODE_TYPE = self::NODE_COMMENT; |
||
2589 | #php4e |
||
2590 | #php5 |
||
2591 | const NODE_TYPE = self::NODE_COMMENT; |
||
2592 | #php5e |
||
2593 | var $tag = '~comment~'; |
||
2594 | |||
2595 | /** |
||
2596 | * @var string |
||
2597 | */ |
||
2598 | var $text = ''; |
||
2599 | |||
2600 | /** |
||
2601 | * Class constructor |
||
2602 | * @param DomNode $parent |
||
2603 | * @param string $text |
||
2604 | */ |
||
2605 | 9 | function __construct($parent, $text = '') { |
|
2606 | 9 | $this->parent = $parent; |
|
2607 | 9 | $this->text = $text; |
|
2608 | 9 | } |
|
2609 | |||
2610 | #php4 PHP4 class constructor compatibility |
||
2611 | #function CommentNode($parent, $text = '') {return $this->__construct($parent, $text);} |
||
2612 | #php4e |
||
2613 | |||
2614 | function isComment() {return true;} |
||
2615 | function isTextOrComment() {return true;} |
||
2616 | protected function filter_element() {return false;} |
||
2617 | protected function filter_comment() {return true;} |
||
2618 | function toString_attributes() {return '';} |
||
2619 | function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} |
||
2620 | function toString($attributes = true, $recursive = true, $content_only = false) {return '<!--'.$this->text.'-->';} |
||
2621 | } |
||
2622 | |||
2623 | /** |
||
2624 | * Node subclass for conditional tags |
||
2625 | */ |
||
2626 | class ConditionalTagNode extends DomNode { |
||
2627 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
2628 | #static $NODE_TYPE = self::NODE_CONDITIONAL; |
||
2629 | #php4e |
||
2630 | #php5 |
||
2631 | const NODE_TYPE = self::NODE_CONDITIONAL; |
||
2632 | #php5e |
||
2633 | var $tag = '~conditional~'; |
||
2634 | |||
2635 | /** |
||
2636 | * @var string |
||
2637 | */ |
||
2638 | var $condition = ''; |
||
2639 | |||
2640 | /** |
||
2641 | * Class constructor |
||
2642 | * @param DomNode $parent |
||
2643 | * @param string $condition e.g. "if IE" |
||
2644 | * @param bool $hidden <!--[if if true, <![if if false |
||
2645 | */ |
||
2646 | function __construct($parent, $condition = '', $hidden = true) { |
||
2647 | $this->parent = $parent; |
||
2648 | $this->hidden = $hidden; |
||
2649 | $this->condition = $condition; |
||
2650 | } |
||
2651 | |||
2652 | #php4 PHP4 class constructor compatibility |
||
2653 | #function ConditionalTagNode($parent, $condition = '', $hidden = true) {return $this->__construct($parent, $condition, $hidden);} |
||
2654 | #php4e |
||
2655 | |||
2656 | protected function filter_element() {return false;} |
||
2657 | function toString_attributes() {return '';} |
||
2658 | function toString($attributes = true, $recursive = true, $content_only = false) { |
||
2659 | if ($content_only) { |
||
2660 | if (is_int($content_only)) { |
||
2661 | --$content_only; |
||
2662 | } |
||
2663 | return $this->toString_content($attributes, $recursive, $content_only); |
||
2664 | } |
||
2665 | |||
2666 | $s = '<!'.(($this->hidden) ? '--' : '').'['.$this->condition.']>'; |
||
2667 | if($recursive) { |
||
2668 | $s .= $this->toString_content($attributes); |
||
2669 | } |
||
2670 | $s .= '<![endif]'.(($this->hidden) ? '--' : '').'>'; |
||
2671 | return $s; |
||
2672 | } |
||
2673 | } |
||
2674 | |||
2675 | /** |
||
2676 | * Node subclass for CDATA tags |
||
2677 | */ |
||
2678 | class CdataNode extends DomNode { |
||
2679 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
2680 | #static $NODE_TYPE = self::NODE_CDATA; |
||
2681 | #php4e |
||
2682 | #php5 |
||
2683 | const NODE_TYPE = self::NODE_CDATA; |
||
2684 | #php5e |
||
2685 | var $tag = '~cdata~'; |
||
2686 | |||
2687 | /** |
||
2688 | * @var string |
||
2689 | */ |
||
2690 | var $text = ''; |
||
2691 | |||
2692 | /** |
||
2693 | * Class constructor |
||
2694 | * @param DomNode $parent |
||
2695 | * @param string $text |
||
2696 | */ |
||
2697 | function __construct($parent, $text = '') { |
||
2698 | $this->parent = $parent; |
||
2699 | $this->text = $text; |
||
2700 | } |
||
2701 | |||
2702 | #php4 PHP4 class constructor compatibility |
||
2703 | #function CdataNode($parent, $text = '') {return $this->__construct($parent, $text);} |
||
2704 | #php4e |
||
2705 | |||
2706 | protected function filter_element() {return false;} |
||
2707 | function toString_attributes() {return '';} |
||
2708 | function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} |
||
2709 | function toString($attributes = true, $recursive = true, $content_only = false) {return '<![CDATA['.$this->text.']]>';} |
||
2710 | } |
||
2711 | |||
2712 | /** |
||
2713 | * Node subclass for doctype tags |
||
2714 | */ |
||
2715 | class DoctypeNode extends DomNode { |
||
2716 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
2717 | #static $NODE_TYPE = self::NODE_DOCTYPE; |
||
2718 | #php4e |
||
2719 | #php5 |
||
2720 | const NODE_TYPE = self::NODE_DOCTYPE; |
||
2721 | #php5e |
||
2722 | var $tag = '!DOCTYPE'; |
||
2723 | |||
2724 | /** |
||
2725 | * @var string |
||
2726 | */ |
||
2727 | var $dtd = ''; |
||
2728 | |||
2729 | /** |
||
2730 | * Class constructor |
||
2731 | * @param DomNode $parent |
||
2732 | * @param string $dtd |
||
2733 | */ |
||
2734 | 9 | function __construct($parent, $dtd = '') { |
|
2735 | 9 | $this->parent = $parent; |
|
2736 | 9 | $this->dtd = $dtd; |
|
2737 | 9 | } |
|
2738 | |||
2739 | #php4 PHP4 class constructor compatibility |
||
2740 | #function DoctypeNode($parent, $dtd = '') {return $this->__construct($parent, $dtd);} |
||
2741 | #php4e |
||
2742 | |||
2743 | protected function filter_element() {return false;} |
||
2744 | function toString_attributes() {return '';} |
||
2745 | function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} |
||
2746 | function toString($attributes = true, $recursive = true, $content_only = false) {return '<'.$this->tag.' '.$this->dtd.'>';} |
||
2747 | } |
||
2748 | |||
2749 | /** |
||
2750 | * Node subclass for embedded tags like xml, php and asp |
||
2751 | */ |
||
2752 | class EmbeddedNode extends DomNode { |
||
2753 | |||
2754 | /** |
||
2755 | * @var string |
||
2756 | * @internal specific char for tags, like ? for php and % for asp |
||
2757 | * @access private |
||
2758 | */ |
||
2759 | var $tag_char = ''; |
||
2760 | |||
2761 | /** |
||
2762 | * @var string |
||
2763 | */ |
||
2764 | var $text = ''; |
||
2765 | |||
2766 | /** |
||
2767 | * Class constructor |
||
2768 | * @param DomNode $parent |
||
2769 | * @param string $tag_char {@link $tag_char} |
||
2770 | * @param string $tag {@link $tag} |
||
2771 | * @param string $text |
||
2772 | * @param array $attributes array('attr' => 'val') |
||
2773 | */ |
||
2774 | function __construct($parent, $tag_char = '', $tag = '', $text = '', $attributes = array()) { |
||
2775 | $this->parent = $parent; |
||
2776 | $this->tag_char = $tag_char; |
||
2777 | if ($tag[0] !== $this->tag_char) { |
||
2778 | $tag = $this->tag_char.$tag; |
||
2779 | } |
||
2780 | $this->tag = $tag; |
||
2781 | $this->text = $text; |
||
2782 | $this->attributes = $attributes; |
||
2783 | $this->self_close_str = $tag_char; |
||
2784 | } |
||
2785 | |||
2786 | #php4 PHP4 class constructor compatibility |
||
2787 | #function EmbeddedNode($parent, $tag_char = '', $tag = '', $text = '', $attributes = array()) {return $this->__construct($parent, $tag_char, $tag, $text, $attributes);} |
||
2788 | #php4e |
||
2789 | |||
2790 | protected function filter_element() {return false;} |
||
2791 | function toString($attributes = true, $recursive = true, $content_only = false) { |
||
2792 | $s = '<'.$this->tag; |
||
2793 | if ($attributes) { |
||
2794 | $s .= $this->toString_attributes(); |
||
2795 | } |
||
2796 | $s .= $this->text.$this->self_close_str.'>'; |
||
2797 | return $s; |
||
2798 | } |
||
2799 | } |
||
2800 | |||
2801 | /** |
||
2802 | * Node subclass for "?" tags, like php and xml |
||
2803 | */ |
||
2804 | class XmlNode extends EmbeddedNode { |
||
2805 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
2806 | #static $NODE_TYPE = self::NODE_XML; |
||
2807 | #php4e |
||
2808 | #php5 |
||
2809 | const NODE_TYPE = self::NODE_XML; |
||
2810 | #php5e |
||
2811 | |||
2812 | /** |
||
2813 | * Class constructor |
||
2814 | * @param DomNode $parent |
||
2815 | * @param string $tag {@link $tag} |
||
2816 | * @param string $text |
||
2817 | * @param array $attributes array('attr' => 'val') |
||
2818 | */ |
||
2819 | function __construct($parent, $tag = 'xml', $text = '', $attributes = array()) { |
||
2820 | return parent::__construct($parent, '?', $tag, $text, $attributes); |
||
2821 | } |
||
2822 | |||
2823 | #php4 PHP4 class constructor compatibility |
||
2824 | #function XmlNode($parent, $tag = 'xml', $text = '', $attributes = array()) {return $this->__construct($parent, $tag, $text, $attributes);} |
||
2825 | #php4e |
||
2826 | } |
||
2827 | |||
2828 | /** |
||
2829 | * Node subclass for asp tags |
||
2830 | */ |
||
2831 | class AspEmbeddedNode extends EmbeddedNode { |
||
2832 | #php4 Compatibility with PHP4, this gets changed to a regular var in release tool |
||
2833 | #static $NODE_TYPE = self::NODE_ASP; |
||
2834 | #php4e |
||
2835 | #php5 |
||
2836 | const NODE_TYPE = self::NODE_ASP; |
||
2837 | #php5e |
||
2838 | |||
2839 | /** |
||
2840 | * Class constructor |
||
2841 | * @param DomNode $parent |
||
2842 | * @param string $tag {@link $tag} |
||
2843 | * @param string $text |
||
2844 | * @param array $attributes array('attr' => 'val') |
||
2845 | */ |
||
2846 | function __construct($parent, $tag = '', $text = '', $attributes = array()) { |
||
2847 | return parent::__construct($parent, '%', $tag, $text, $attributes); |
||
2848 | } |
||
2849 | |||
2850 | #php4 PHP4 class constructor compatibility |
||
2851 | #function AspEmbeddedNode($parent, $tag = '', $text = '', $attributes = array()) {return $this->__construct($parent, $tag, $text, $attributes);} |
||
2852 | #php4e |
||
2853 | } |
||
2854 | |||
2855 | ?> |