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