Completed
Push — develop ( 7b8839...dac3c0 )
by Peter
11:35
created

NodeList::getNodes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
/**
3
 * Webino (http://webino.sk)
4
 *
5
 * @link        https://github.com/webino/WebinoDraw for the canonical source repository
6
 * @copyright   Copyright (c) 2012-2017 Webino, s. r. o. (http://webino.sk)
7
 * @author      Peter Bačinský <[email protected]>
8
 * @license     BSD-3-Clause
9
 */
10
11
namespace WebinoDraw\Dom;
12
13
use ArrayObject;
14
use DOMNodeList;
15
use IteratorAggregate;
16
use IteratorIterator;
17
use WebinoDraw\Exception;
18
use WebinoDraw\View\Helper\EscapeHtmlTrait;
19
20
/**
21
 * Batch DOMElement manipulation
22
 */
23
class NodeList implements IteratorAggregate
24
{
25
    use EscapeHtmlTrait;
26
27
    /**
28
     * @var IteratorIterator
29
     */
30
    protected $nodes;
31
32
    /**
33
     * @var Locator
34
     */
35
    protected $locator;
36
37
    /**
38
     * @param Locator $locator
39
     * @param array|DOMNodeList $nodes DOMNodes in array or DOMNodeList
40
     */
41
    public function __construct(Locator $locator, $nodes = null)
42
    {
43
        $this->locator = $locator;
44
        empty($nodes) or $this->setNodes($nodes);
45
    }
46
47
    /**
48
     * @param array|DOMNodeList $nodes DOMNodes in array or DOMNodeList
49
     * @return $this
50
     */
51
    public function create($nodes)
52
    {
53
        return new self($this->locator, $nodes);
54
    }
55
56
    /**
57
     * @return IteratorIterator
58
     */
59
    public function getNodes()
60
    {
61
        if (null === $this->nodes) {
62
            $this->setNodes([]);
63
        }
64
        return $this->nodes;
65
    }
66
67
    /**
68
     * @param array|DOMNodeList|IteratorIterator $nodes
69
     * @return $this
70
     * @throws Exception\InvalidArgumentException
71
     */
72
    public function setNodes($nodes)
73
    {
74
        if ($nodes instanceof IteratorIterator) {
75
            $this->nodes = $nodes;
76
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
77
        } elseif (is_array($nodes)) {
78
            $this->nodes = new IteratorIterator(new ArrayObject($nodes));
79
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
80
        } elseif ($nodes instanceof DOMNodeList) {
81
            $this->nodes = new IteratorIterator($nodes);
82
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
83
        } else {
84
            throw new Exception\InvalidArgumentException('Expected nodes as array|DOMNodelist');
85
        }
86
87
        foreach ($this->nodes as $node) {
88
            // inject locator to node
89
            $node instanceof Locator\LocatorAwareInterface
90
                and $node->setLocator($this->locator);
91
        }
92
93
        return $this;
94
    }
95
96
    /**
97
     * @return \Traversable
98
     */
99
    public function getIterator()
100
    {
101
        return $this->getNodes()->getInnerIterator();
102
    }
103
104
    /**
105
     * Set nodes text value
106
     *
107
     * @param string $value
108
     * @param Callable $preSet Modify and return value, passed parameters $node, $value
109
     * @return $this
110
     */
111
    public function setValue($value, $preSet = null)
112
    {
113
        $escapeHtml = $this->getEscapeHtml();
114
        foreach ($this as $node) {
115
            $nodeValue = is_callable($preSet) ? $preSet($node, $value, $this) : $value;
116
            $node->nodeValue = $escapeHtml($nodeValue);
117
        }
118
119
        return $this;
120
    }
121
122
    /**
123
     * Set nodes html value
124
     *
125
     * @param string $xhtml
126
     * @param Callable $preSet Modify and return xhtml, passed parameters $node, $xhtml
127
     * @return $this
128
     */
129
    public function setHtml($xhtml, $preSet = null)
130
    {
131
        foreach ($this as $node) {
132
            $nodeXhtml = is_callable($preSet) ? $preSet($node, $xhtml, $this) : $xhtml;
133
            $node->nodeValue = '';
134
135
            if (empty($nodeXhtml) || !($node instanceof Element)) {
136
                continue;
137
            }
138
139
            $frag = $node->ownerDocument->createDocumentFragment();
140
            $frag->appendXml($nodeXhtml);
141
            $node->appendChild($frag);
142
        }
143
144
        return $this;
145
    }
146
147
    /**
148
     * Append XHTML to nodes
149
     *
150
     * @param string $xhtml Valid XHTML
151
     * @return $this Newly created nodes
152
     */
153
    public function appendHtml($xhtml)
154
    {
155
        $nodes = [];
156
        $childNode = null;
157
158
        foreach ($this as $node) {
159
            if (!($node instanceof Element)) {
160
                continue;
161
            }
162
163
            if (empty($childNode)) {
164
                $childNode = $node->ownerDocument->createDocumentFragment();
165
                $childNode->appendXml($xhtml);
166
            }
167
168
            $nodes[] = $node->appendChild(clone $childNode);
169
        }
170
171
        return $this->create($nodes);
172
    }
173
174
    /**
175
     * Set nodes attributes
176
     *
177
     * @param array $attribs Attributes to set
178
     * @param Callable $preSet Modify and return value, assed parameters $node, $value
179
     * @return $this
180
     */
181
    public function setAttribs(array $attribs, $preSet = null)
182
    {
183
        foreach ($this as $node) {
184
            if (!($node instanceof Element)) {
185
                continue;
186
            }
187
188
            // prepare callback
189
            $callback = $preSet
190
                ? function ($value) use ($node, $preSet) {
191
                    return $preSet($node, $value, $this);
192
                }
193
                : null;
194
195
            $node->setAttributes($attribs, $callback);
196
        }
197
198
        return $this;
199
    }
200
201
    /**
202
     * Replace node with XHTML code
203
     *
204
     * @param string $xhtml XHTML to replace node
205
     * @param Callable $preSet Modify and return xhtml, assed parameters $node, $xhtml
206
     * @return $this
207
     */
208
    public function replace($xhtml, $preSet = null)
209
    {
210
        $remove   = [];
211
        $nodeList = new ArrayObject;
212
213
        foreach ($this as $node) {
214
            if (!($node instanceof Element)) {
215
                continue;
216
            }
217
218
            $nodeXhtml = is_callable($preSet) ? $preSet($node, $xhtml, $this) : $xhtml;
219
            if (empty($nodeXhtml)) {
220
                $node->nodeValue = '';
221
                continue;
222
            }
223
224
            $frag = $node->ownerDocument->createDocumentFragment();
225
            $frag->appendXml($nodeXhtml);
226
227
            $nodeList[] = $node->parentNode->insertBefore($frag, $node);
228
            $remove[]   = $node;
229
        }
230
231
        foreach ($remove as $node) {
232
            $node->parentNode->removeChild($node);
233
        }
234
235
        $this->nodes = new IteratorIterator($nodeList);
236
        return $this;
237
    }
238
239
    /**
240
     * Remove target nodes
241
     *
242
     * @param string $locator CSS selector or XPath (xpath=)
243
     * @return $this
244
     */
245
    public function remove($locator = '.')
246
    {
247
        if (empty($locator)) {
248
            return $this;
249
        }
250
251
        $remove = [];
252
        $this->each(
253
            $locator,
254
            function (NodeList $nodes) use (&$remove) {
255
                $remove = array_merge($remove, (array) $nodes->getIterator());
256
            }
257
        );
258
259
        foreach ($remove as $node) {
260
            empty($node->parentNode)
261
                or $node->parentNode->removeChild($node);
262
        }
263
264
        return $this;
265
    }
266
267
    /**
268
     * Perform callback on each node that match the locator
269
     *
270
     * @param string $locator
271
     * @param Callable $callback The NodeList parameter is passed
272
     * @return $this
273
     * @throws Exception\RuntimeException
274
     */
275
    public function each($locator, $callback)
276
    {
277
        if (empty($locator)) {
278
            return $this;
279
        }
280
281
        $xpath = $this->locator->set($locator)->xpathMatchAny();
282
        foreach ($this as $node) {
283
            if (!($node->ownerDocument instanceof Document)) {
284
                throw new Exception\RuntimeException('Expects `' . Document::class . '` with xpath');
285
            }
286
287
            $nodes = $node->ownerDocument->getXpath()->query($xpath, $node);
288
            foreach ($nodes as $subNode) {
289
                $callback($this->create([$subNode]));
290
            }
291
        }
292
293
        return $this;
294
    }
295
296
    /**
297
     * Return nodes in array
298
     *
299
     * Error-free nodes
300
     * manipulation in a loop.
301
     *
302
     * @return array
303
     */
304
    public function toArray()
305
    {
306
        $return = [];
307
        foreach ($this as $node) {
308
            (null === $node) or $return[] = $node;
309
        }
310
        return $return;
311
    }
312
}
313