Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
95.83% covered (success)
95.83%
23 / 24
CRAP
98.89% covered (success)
98.89%
178 / 180
Child
0.00% covered (danger)
0.00%
0 / 1
95.83% covered (success)
95.83%
23 / 24
94
98.89% covered (success)
98.89%
178 / 180
 __construct
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
13 / 13
 create
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 parseString
100.00% covered (success)
100.00%
1 / 1
26
100.00% covered (success)
100.00%
59 / 59
 parseFile
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 getNodeName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getNodeValue
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getNodeContent
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
10 / 10
 getTextContent
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
10 / 10
 setNodeName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setNodeValue
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 addNodeValue
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setAsCData
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 isCData
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setAttribute
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setAttributes
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 hasAttribute
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getAttribute
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 getAttributes
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 removeAttribute
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 isChildrenFirst
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setChildrenFirst
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 preserveWhiteSpace
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 render
0.00% covered (danger)
0.00%
0 / 1
35
96.43% covered (success)
96.43%
54 / 56
 __toString
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
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-2021 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace 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-2021 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    3.3.0
25 */
26class 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}