PluginProbe
Page Builder: Pagelayer – Drag and Drop website builder / 1.0.4
Page Builder: Pagelayer – Drag and Drop website builder v1.0.4
2.2.1 2.2.0 2.1.9 2.1.8 2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 trunk 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 1.0.0 1.0.2 1.0.3 1.0.4 1.0.5 All 129 releases
pagelayer / lib / pquery / gan_node_html.php

gan_node_html.php in Page Builder: Pagelayer – Drag and Drop website builder 1.0.4, at lib/pquery/gan_node_html.php

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