Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.88% |
176 / 178 |
|
95.83% |
23 / 24 |
CRAP | |
0.00% |
0 / 1 |
Child | |
98.88% |
176 / 178 |
|
95.83% |
23 / 24 |
94 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
create | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parseString | |
100.00% |
58 / 58 |
|
100.00% |
1 / 1 |
26 | |||
parseFile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getNodeName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getNodeValue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getNodeContent | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getTextContent | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
setNodeName | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setNodeValue | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addNodeValue | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setAsCData | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isCData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setAttribute | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setAttributes | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
hasAttribute | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAttribute | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getAttributes | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
removeAttribute | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
isChildrenFirst | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setChildrenFirst | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
preserveWhiteSpace | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
render | |
96.43% |
54 / 56 |
|
0.00% |
0 / 1 |
35 | |||
__toString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Dom; |
15 | |
16 | /** |
17 | * Dom child class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Dom |
21 | * @author Nick Sagona, III <dev@nolainteractive.com> |
22 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
23 | * @license http://www.popphp.org/license New BSD License |
24 | * @version 3.3.0 |
25 | */ |
26 | class Child extends AbstractNode |
27 | { |
28 | |
29 | /** |
30 | * Child element node name |
31 | * @var string |
32 | */ |
33 | protected $nodeName = null; |
34 | |
35 | /** |
36 | * Child element node value |
37 | * @var string |
38 | */ |
39 | protected $nodeValue = null; |
40 | |
41 | /** |
42 | * Child element node value CDATA flag |
43 | * @var boolean |
44 | */ |
45 | protected $cData = false; |
46 | |
47 | /** |
48 | * Flag to render children before node value or not |
49 | * @var boolean |
50 | */ |
51 | protected $childrenFirst = false; |
52 | |
53 | /** |
54 | * Child element attributes |
55 | * @var array |
56 | */ |
57 | protected $attributes = []; |
58 | |
59 | /** |
60 | * Flag to preserve whitespace |
61 | * @var boolean |
62 | */ |
63 | protected $preserveWhiteSpace = true; |
64 | |
65 | /** |
66 | * Constructor |
67 | * |
68 | * Instantiate the DOM element object |
69 | * |
70 | * @param string $name |
71 | * @param string $value |
72 | * @param array $options |
73 | */ |
74 | public function __construct($name, $value = null, array $options = []) |
75 | { |
76 | $this->nodeName = $name; |
77 | $this->nodeValue = $value; |
78 | |
79 | if (isset($options['cData'])) { |
80 | $this->cData = (bool)$options['cData']; |
81 | } |
82 | if (isset($options['childrenFirst'])) { |
83 | $this->childrenFirst = (bool)$options['childrenFirst']; |
84 | } |
85 | if (isset($options['indent'])) { |
86 | $this->indent = (string)$options['indent']; |
87 | } |
88 | if (isset($options['attributes'])) { |
89 | $this->setAttributes($options['attributes']); |
90 | } |
91 | if (isset($options['whitespace'])) { |
92 | $this->preserveWhiteSpace($options['whitespace']); |
93 | } |
94 | } |
95 | |
96 | /** |
97 | * Static factory method to create a child object |
98 | * |
99 | * @param string $name |
100 | * @param string $value |
101 | * @param array $options |
102 | * @return Child |
103 | */ |
104 | public static function create($name, $value = null, array $options = []) |
105 | { |
106 | return new self($name, $value, $options); |
107 | } |
108 | |
109 | /** |
110 | * Static method to parse an XML/HTML string |
111 | * |
112 | * @param string $string |
113 | * @return Child|array |
114 | */ |
115 | public static function parseString($string) |
116 | { |
117 | $doc = new \DOMDocument(); |
118 | $doc->loadHTML($string); |
119 | |
120 | $dit = new \RecursiveIteratorIterator( |
121 | new DomIterator($doc), |
122 | \RecursiveIteratorIterator::SELF_FIRST |
123 | ); |
124 | |
125 | $parent = null; |
126 | $child = null; |
127 | $lastDepth = 0; |
128 | $endElement = null; |
129 | $partial = ((stripos($string, '<html') === false) || (stripos($string, '<body') === false)); |
130 | |
131 | foreach($dit as $node) { |
132 | if (($node->nodeType == XML_ELEMENT_NODE) || ($node->nodeType == XML_TEXT_NODE)) { |
133 | $attribs = []; |
134 | if (null !== $node->attributes) { |
135 | for ($i = 0; $i < $node->attributes->length; $i++) { |
136 | $name = $node->attributes->item($i)->name; |
137 | $attribs[$name] = $node->getAttribute($name); |
138 | } |
139 | } |
140 | if (null === $parent) { |
141 | $parent = new Child($node->nodeName); |
142 | } else { |
143 | if (($node->nodeType == XML_TEXT_NODE) && (null !== $child)) { |
144 | $nodeValue = trim($node->nodeValue); |
145 | if (!empty($nodeValue)) { |
146 | if (($endElement) && (null !== $child->getParent()) && (null !== $node->previousSibling)) { |
147 | $prev = $node->previousSibling->nodeName; |
148 | $par = $child->getParent(); |
149 | while ((null !== $par) && ($prev != $par->getNodeName())) { |
150 | $par = $par->getParent(); |
151 | } |
152 | if (null === $par) { |
153 | $par = $child->getParent(); |
154 | } else { |
155 | $par = $par->getParent(); |
156 | } |
157 | $par->addChild(new Child('#text', $nodeValue)); |
158 | } else { |
159 | $child->setNodeValue($nodeValue); |
160 | $endElement = true; |
161 | } |
162 | } |
163 | } else { |
164 | // down |
165 | if ($dit->getDepth() > $lastDepth) { |
166 | if (null !== $child) { |
167 | $parent = $child; |
168 | } |
169 | $child = new Child($node->nodeName); |
170 | $parent->addChild($child); |
171 | $endElement = false; |
172 | // up |
173 | } else if ($dit->getDepth() < $lastDepth) { |
174 | while ($parent->getNodeName() != $node->parentNode->nodeName) { |
175 | $parent = $parent->getParent(); |
176 | } |
177 | //$parent = $parent->getParent(); |
178 | $child = new Child($node->nodeName); |
179 | $parent->addChild($child); |
180 | $endElement = false; |
181 | // next (sibling) |
182 | } else if ($dit->getDepth() == $lastDepth) { |
183 | $child = new Child($node->nodeName); |
184 | $parent->addChild($child); |
185 | $endElement = false; |
186 | } |
187 | if (!empty($attribs)) { |
188 | $child->setAttributes($attribs); |
189 | } |
190 | $lastDepth = $dit->getDepth(); |
191 | } |
192 | } |
193 | } |
194 | } |
195 | while (null !== $parent->getParent()) { |
196 | $parent = $parent->getParent(); |
197 | } |
198 | |
199 | if ($partial) { |
200 | $parent = $parent->getChild(0); |
201 | if (strtolower($parent->getNodeName()) == 'body') { |
202 | $parent = $parent->getChildNodes(); |
203 | } |
204 | } |
205 | |
206 | return $parent; |
207 | } |
208 | |
209 | /** |
210 | * Static method to parse an XML/HTML string from a file |
211 | * |
212 | * @param string $file |
213 | * @throws Exception |
214 | * @return Child |
215 | */ |
216 | public static function parseFile($file) |
217 | { |
218 | if (!file_exists($file)) { |
219 | throw new Exception('Error: That file does not exist.'); |
220 | } |
221 | return self::parseString(file_get_contents($file)); |
222 | } |
223 | |
224 | /** |
225 | * Return the child node name |
226 | * |
227 | * @return string |
228 | */ |
229 | public function getNodeName() |
230 | { |
231 | return $this->nodeName; |
232 | } |
233 | |
234 | /** |
235 | * Return the child node value |
236 | * |
237 | * @return string |
238 | */ |
239 | public function getNodeValue() |
240 | { |
241 | return $this->nodeValue; |
242 | } |
243 | |
244 | /** |
245 | * Return the child node content, including tags, etc |
246 | * |
247 | * @param boolean $ignoreWhiteSpace |
248 | * @return string |
249 | */ |
250 | public function getNodeContent($ignoreWhiteSpace = false) |
251 | { |
252 | $content = $this->render(0, null, true); |
253 | if ($ignoreWhiteSpace) { |
254 | $content = preg_replace('/\s+/', ' ', str_replace(["\n", "\r", "\t"], ["", "", ""], trim($content))); |
255 | $content = preg_replace('/\s*\.\s*/', '. ', $content); |
256 | $content = preg_replace('/\s*\?\s*/', '? ', $content); |
257 | $content = preg_replace('/\s*\!\s*/', '! ', $content); |
258 | $content = preg_replace('/\s*,\s*/', ', ', $content); |
259 | $content = preg_replace('/\s*\:\s*/', ': ', $content); |
260 | $content = preg_replace('/\s*\;\s*/', '; ', $content); |
261 | } |
262 | return $content; |
263 | } |
264 | |
265 | /** |
266 | * Return the child node content, including tags, etc |
267 | * |
268 | * @param boolean $ignoreWhiteSpace |
269 | * @return string |
270 | */ |
271 | public function getTextContent($ignoreWhiteSpace = false) |
272 | { |
273 | $content = strip_tags($this->render(0, null, true)); |
274 | |
275 | if ($ignoreWhiteSpace) { |
276 | $content = preg_replace('/\s+/', ' ', str_replace(["\n", "\r", "\t"], ["", "", ""], trim($content))); |
277 | $content = preg_replace('/\s*\.\s*/', '. ', $content); |
278 | $content = preg_replace('/\s*\?\s*/', '? ', $content); |
279 | $content = preg_replace('/\s*\!\s*/', '! ', $content); |
280 | $content = preg_replace('/\s*,\s*/', ', ', $content); |
281 | $content = preg_replace('/\s*\:\s*/', ': ', $content); |
282 | $content = preg_replace('/\s*\;\s*/', '; ', $content); |
283 | } |
284 | return $content; |
285 | } |
286 | |
287 | /** |
288 | * Set the child node name |
289 | * |
290 | * @param string $name |
291 | * @return Child |
292 | */ |
293 | public function setNodeName($name) |
294 | { |
295 | $this->nodeName = $name; |
296 | return $this; |
297 | } |
298 | |
299 | /** |
300 | * Set the child node value |
301 | * |
302 | * @param string $value |
303 | * @return Child |
304 | */ |
305 | public function setNodeValue($value) |
306 | { |
307 | $this->nodeValue = $value; |
308 | return $this; |
309 | } |
310 | |
311 | /** |
312 | * Add to the child node value |
313 | * |
314 | * @param string $value |
315 | * @return Child |
316 | */ |
317 | public function addNodeValue($value) |
318 | { |
319 | $this->nodeValue .= $value; |
320 | return $this; |
321 | } |
322 | |
323 | /** |
324 | * Set the child node value as CDATA |
325 | * |
326 | * @param boolean $cData |
327 | * @return Child |
328 | */ |
329 | public function setAsCData($cData = true) |
330 | { |
331 | $this->cData = (bool)$cData; |
332 | return $this; |
333 | } |
334 | |
335 | /** |
336 | * Determine if the child node value is CDATA |
337 | * |
338 | * @return boolean |
339 | */ |
340 | public function isCData() |
341 | { |
342 | return $this->cData; |
343 | } |
344 | |
345 | /** |
346 | * Set an attribute for the child element object |
347 | * |
348 | * @param string $a |
349 | * @param string $v |
350 | * @return Child |
351 | */ |
352 | public function setAttribute($a, $v) |
353 | { |
354 | $this->attributes[$a] = $v; |
355 | return $this; |
356 | } |
357 | |
358 | /** |
359 | * Set an attribute or attributes for the child element object |
360 | * |
361 | * @param array $a |
362 | * @return Child |
363 | */ |
364 | public function setAttributes(array $a) |
365 | { |
366 | foreach ($a as $name => $value) { |
367 | $this->attributes[$name] = $value; |
368 | } |
369 | return $this; |
370 | } |
371 | |
372 | /** |
373 | * Determine if the child object has an attribute |
374 | * |
375 | * @param string $name |
376 | * @return boolean |
377 | */ |
378 | public function hasAttribute($name) |
379 | { |
380 | return (isset($this->attributes[$name])); |
381 | } |
382 | |
383 | /** |
384 | * Get the attribute of the child object |
385 | * |
386 | * @param string $name |
387 | * @return string |
388 | */ |
389 | public function getAttribute($name) |
390 | { |
391 | return (isset($this->attributes[$name])) ? $this->attributes[$name] : null; |
392 | } |
393 | |
394 | /** |
395 | * Get the attributes of the child object |
396 | * |
397 | * @return array |
398 | */ |
399 | public function getAttributes() |
400 | { |
401 | return $this->attributes; |
402 | } |
403 | |
404 | /** |
405 | * Remove an attribute from the child element object |
406 | * |
407 | * @param string $a |
408 | * @return Child |
409 | */ |
410 | public function removeAttribute($a) |
411 | { |
412 | if (isset($this->attributes[$a])) { |
413 | unset($this->attributes[$a]); |
414 | } |
415 | return $this; |
416 | } |
417 | |
418 | /** |
419 | * Determine if child nodes render first, before the node value |
420 | * |
421 | * @return boolean |
422 | */ |
423 | public function isChildrenFirst() |
424 | { |
425 | return $this->childrenFirst; |
426 | } |
427 | |
428 | /** |
429 | * Set whether child nodes render first, before the node value |
430 | * |
431 | * @param bool $first |
432 | * @return Child |
433 | */ |
434 | public function setChildrenFirst($first = true) |
435 | { |
436 | $this->childrenFirst = (bool)$first; |
437 | return $this; |
438 | } |
439 | |
440 | /** |
441 | * Set whether to preserve whitespace |
442 | * |
443 | * @param bool $preserve |
444 | * @return Child |
445 | */ |
446 | public function preserveWhiteSpace($preserve = true) |
447 | { |
448 | $this->preserveWhiteSpace = (bool)$preserve; |
449 | return $this; |
450 | } |
451 | |
452 | /** |
453 | * Render the child and its child nodes. |
454 | * |
455 | * @param int $depth |
456 | * @param string $indent |
457 | * @param boolean $inner |
458 | * @return mixed |
459 | */ |
460 | public function render($depth = 0, $indent = null, $inner = false) |
461 | { |
462 | // Initialize child object properties and variables. |
463 | $this->output = ''; |
464 | $this->indent = (null === $this->indent) ? str_repeat(' ', $depth) : $this->indent; |
465 | $attribs = ''; |
466 | $attribAry = []; |
467 | |
468 | if ($this->cData) { |
469 | $this->nodeValue = '<![CDATA[' . $this->nodeValue . ']]>'; |
470 | } |
471 | |
472 | // Format child attributes, if applicable. |
473 | if (count($this->attributes) > 0) { |
474 | foreach ($this->attributes as $key => $value) { |
475 | $attribAry[] = $key . "=\"" . $value . "\""; |
476 | } |
477 | $attribs = ' ' . implode(' ', $attribAry); |
478 | } |
479 | |
480 | // Initialize the node. |
481 | if ($this->nodeName == '#text') { |
482 | $this->output .= ((!$this->preserveWhiteSpace) ? |
483 | '' : "{$indent}{$this->indent}") . $this->nodeValue . ((!$this->preserveWhiteSpace) ? '' : "\n"); |
484 | } else { |
485 | if (!$inner) { |
486 | $this->output .= ((!$this->preserveWhiteSpace) ? |
487 | '' : "{$indent}{$this->indent}") . "<{$this->nodeName}{$attribs}"; |
488 | } |
489 | |
490 | if ((null === $indent) && (null !== $this->indent)) { |
491 | $indent = $this->indent; |
492 | $origIndent = $this->indent; |
493 | } else { |
494 | $origIndent = $indent . $this->indent; |
495 | } |
496 | |
497 | // If current child element has child nodes, format and render. |
498 | if (count($this->childNodes) > 0) { |
499 | if (!$inner) { |
500 | $this->output .= ">"; |
501 | if ($this->preserveWhiteSpace) { |
502 | $this->output .= "\n"; |
503 | } |
504 | } |
505 | $newDepth = $depth + 1; |
506 | |
507 | // Render node value before the child nodes. |
508 | if (!$this->childrenFirst) { |
509 | if (null !== $this->nodeValue) { |
510 | $this->output .= ((!$this->preserveWhiteSpace) ? |
511 | '' : str_repeat(' ', $newDepth) . "{$indent}") . "{$this->nodeValue}\n"; |
512 | } |
513 | foreach ($this->childNodes as $child) { |
514 | $this->output .= $child->render($newDepth, $indent); |
515 | } |
516 | if (!$inner) { |
517 | if (!$this->preserveWhiteSpace) { |
518 | $this->output .= "</{$this->nodeName}>"; |
519 | } else { |
520 | $this->output .= "{$origIndent}</{$this->nodeName}>\n"; |
521 | } |
522 | } |
523 | // Else, render child nodes first, then node value. |
524 | } else { |
525 | foreach ($this->childNodes as $child) { |
526 | $this->output .= $child->render($newDepth, $indent); |
527 | } |
528 | if (!$inner) { |
529 | if (null !== $this->nodeValue) { |
530 | $this->output .= ((!$this->preserveWhiteSpace) ? |
531 | '' : str_repeat(' ', $newDepth) . "{$indent}") . |
532 | "{$this->nodeValue}" . ((!$this->preserveWhiteSpace) ? |
533 | '' : "\n{$origIndent}") . "</{$this->nodeName}>" . (($this->preserveWhiteSpace) ? '' : "\n"); |
534 | } else { |
535 | $this->output .= ((!$this->preserveWhiteSpace) ? |
536 | '' : "{$origIndent}") . "</{$this->nodeName}>" . ((!$this->preserveWhiteSpace) ? '' : "\n"); |
537 | } |
538 | } |
539 | } |
540 | // Else, render the child node. |
541 | } else { |
542 | if (!$inner) { |
543 | if ((null !== $this->nodeValue) || ($this->nodeName == 'textarea')) { |
544 | $this->output .= ">"; |
545 | $this->output .= "{$this->nodeValue}</{$this->nodeName}>" . ((!$this->preserveWhiteSpace) ? '' : "\n"); |
546 | } else { |
547 | $this->output .= " />"; |
548 | if ($this->preserveWhiteSpace) { |
549 | $this->output .= "\n"; |
550 | } |
551 | } |
552 | } else if (!empty($this->nodeValue)) { |
553 | $this->output .= $this->nodeValue; |
554 | } |
555 | } |
556 | } |
557 | |
558 | return $this->output; |
559 | } |
560 | |
561 | /** |
562 | * Render Dom child object to string |
563 | * |
564 | * @return string |
565 | */ |
566 | public function __toString() |
567 | { |
568 | return $this->render(); |
569 | } |
570 | |
571 | } |