Passed
Push — master ( c01fee...eeab5f )
by Daniele
02:33
created

FluidContext::size()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace FluidXml;
4
5
/**
6
 * @method array array()
7
 */
8
class FluidContext implements FluidInterface, \ArrayAccess, \Iterator
9
{
10
        use FluidAliasesTrait,
11
            FluidSaveTrait,
12
            NewableTrait,
13
            ReservedCallTrait,          // For compatibility with PHP 5.6.
14
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
15
16
        private $document;
17
        private $handler;
18
        private $nodes = [];
19
        private $seek = 0;
20
21 1
        public function __construct($document, $handler, $context)
22
        {
23 1
                $this->document = $document;
24 1
                $this->handler  = $handler;
25
26 1
                if (! \is_array($context) && ! $context instanceof \Traversable) {
27
                        // DOMDocument, DOMElement and DOMNode are not iterable.
28
                        // DOMNodeList and FluidContext are iterable.
29 1
                        $context = [ $context ];
30
                }
31
32 1
                foreach ($context as $n) {
33 1
                        if (! $n instanceof \DOMNode) {
34 1
                                throw new \Exception('Node type not recognized.');
35
                        }
36
37 1
                        $this->nodes[] = $n;
38
                }
39 1
        }
40
41
        // \ArrayAccess interface.
42 1
        public function offsetSet($offset, $value)
43
        {
44
                // if (\is_null($offset)) {
45
                //         $this->nodes[] = $value;
46
                // } else {
47
                //         $this->nodes[$offset] = $value;
48
                // }
49 1
                throw new \Exception('Setting a context element is not allowed.');
50
        }
51
52
        // \ArrayAccess interface.
53 1
        public function offsetExists($offset)
54
        {
55 1
                return isset($this->nodes[$offset]);
56
        }
57
58
        // \ArrayAccess interface.
59 1
        public function offsetUnset($offset)
60
        {
61
                // unset($this->nodes[$offset]);
62 1
                \array_splice($this->nodes, $offset, 1);
63 1
        }
64
65
        // \ArrayAccess interface.
66 1
        public function offsetGet($offset)
67
        {
68 1
                if (isset($this->nodes[$offset])) {
69 1
                        return $this->nodes[$offset];
70
                }
71
72 1
                return null;
73
        }
74
75
        // \Iterator interface.
76 1
        public function rewind()
77
        {
78 1
                $this->seek = 0;
79 1
        }
80
81
        // \Iterator interface.
82 1
        public function current()
83
        {
84 1
                return $this->nodes[$this->seek];
85
        }
86
87
        // \Iterator interface.
88 1
        public function key()
89
        {
90 1
                return $this->seek;
91
        }
92
93
        // \Iterator interface.
94 1
        public function next()
95
        {
96 1
                ++$this->seek;
97 1
        }
98
99
        // \Iterator interface.
100 1
        public function valid()
101
        {
102 1
                return isset($this->nodes[$this->seek]);
103
        }
104
105 1
        public function length()
106
        {
107 1
                return \count($this->nodes);
108
        }
109
110 1
        public function query(...$query)
111
        {
112 1
                if (\is_array($query[0])) {
113 1
                        $query = $query[0];
114
                }
115
116 1
                $results = [];
117
118 1
                $xp = $this->document->xpath;
119
120 1
                foreach ($this->nodes as $n) {
121 1
                        foreach ($query as $q) {
122 1
                                $q = $this->resolveQuery($q);
123
124
                                // Returns a DOMNodeList.
125 1
                                $res = $xp->query($q, $n);
126
127
                                // Algorithm 1:
128
                                // $results = \array_merge($results, \iterator_to_array($res));
129
130
                                // Algorithm 2:
131
                                // It is faster than \iterator_to_array and a lot faster
132
                                // than \iterator_to_array + \array_merge.
133 1
                                foreach ($res as $r) {
134 1
                                        $results[] = $r;
135
                                }
136
137
                                // Algorithm 3:
138
                                // for ($i = 0, $l = $res->length; $i < $l; ++$i) {
139
                                //         $results[] = $res->item($i);
140
                                // }
141
                        }
142
                }
143
144
                // Performing over multiple sibling nodes a query that ascends
145
                // the xpath, relative (../..) or absolute (//), returns identical
146
                // matching results that must be collapsed in an unique result
147
                // otherwise a subsequent operation is performed multiple times.
148 1
                $results = $this->filterQueryResults($results);
149
150 1
                return $this->newContext($results);
151
        }
152
153 1
        public function __invoke(...$query)
154
        {
155 1
                return $this->query(...$query);
156
        }
157
158 1
        public function times($times, callable $fn = null)
159
        {
160 1
                if ($fn === null) {
161 1
                        return new FluidRepeater($this->document, $this->handler, $this, $times);
162
                }
163
164 1
                for ($i = 0; $i < $times; ++$i) {
165 1
                        $this->callfn($fn, [$this, $i]);
166
                }
167
168 1
                return $this;
169
        }
170
171 1
        public function each(callable $fn)
172
        {
173 1
                foreach ($this->nodes as $i => $n) {
174 1
                        $cx = $this->newContext($n);
175
176 1
                        $this->callfn($fn, [$cx, $i, $n]);
177
                }
178
179 1
                return $this;
180
        }
181
182 1
        public function filter(callable $fn)
183
        {
184 1
                $nodes = [];
185
186 1
                foreach ($this->nodes as $i => $n) {
187 1
                        $cx = $this->newContext($n);
188
189 1
                        $ret = $this->callfn($fn, [$cx, $i, $n]);
190
191 1
                        if ($ret !== false) {
192 1
                                $nodes[] = $n;
193
                        }
194
                }
195
196 1
                return $this->newContext($nodes);
197
        }
198
199
        // addChild($child, $value?, $attributes? = [], $switchContext? = false)
200 1
        public function addChild($child, ...$optionals)
201
        {
202
                return $this->handler->insertElement($this->nodes, $child, $optionals, function ($parent, $element) {
203 1
                        return $parent->appendChild($element);
204 1
                }, $this);
205
        }
206
207 1 View Code Duplication
        public function prependSibling($sibling, ...$optionals)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
208
        {
209
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
210 1
                        if ($sibling->parentNode === null) {
211
                                // If the node doesn't have at least one parent node,
212
                                // the sibling creation fails. In this case we replace
213
                                // the sibling creation with the creation of a child node.
214
                                // Useful when dealing with a DOMDocument with a null
215
                                // documentElement property.
216 1
                                return $sibling->appendChild($element);
217
                        }
218 1
                        return $sibling->parentNode->insertBefore($element, $sibling);
219 1
                }, $this);
220
        }
221
222 View Code Duplication
        public function appendSibling($sibling, ...$optionals)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
        {
224 1
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
225 1
                        if ($sibling->parentNode === null) {
226
                                // If the node doesn't have at least one parent node,
227
                                // the sibling creation fails. In this case we replace
228
                                // the sibling creation with the creation of a child node.
229
                                // Useful when dealing with a DOMDocument with a null
230
                                // documentElement property.
231 1
                                return $sibling->appendChild($element);
232
                        }
233
                        // If ->nextSibling is null, $element is simply appended as last sibling.
234 1
                        return $sibling->parentNode->insertBefore($element, $sibling->nextSibling);
235 1
                }, $this);
236
        }
237
238
        // setAttribute($name, $value)
239
        // setAttribute(['name' => 'value', ...])
240 1
        public function setAttribute($name, $value = null)
241
        {
242 1
                $attrs = [ $name => $value ];
243
244 1
                if (\is_array($name)) {
245 1
                        $attrs = $name;
246
                }
247
248 1
                foreach ($this->nodes as $n) {
249 1
                        foreach ($attrs as $k => $v) {
250 1
                                if (\is_integer($k)) {
251 1
                                        $k = $v;
252 1
                                        $v = null;
253
                                }
254
255
                                // Algorithm 1:
256 1
                                $n->setAttribute($k, $v);
257
258
                                // Algorithm 2:
259
                                // $n->setAttributeNode(new \DOMAttr($k, $v));
260
261
                                // Algorithm 3:
262
                                // $n->appendChild(new \DOMAttr($k, $v));
263
264
                                // Algorithm 2 and 3 have a different behaviour
265
                                // from Algorithm 1.
266
                                // The attribute is still created or setted, but
267
                                // changing the value of an existing attribute
268
                                // changes even the order of that attribute
269
                                // in the attribute list.
270
                        }
271
                }
272
273 1
                return $this;
274
        }
275
276 1
        public function setText($text)
277
        {
278 1
                foreach ($this->nodes as $n) {
279
                        // Algorithm 1:
280 1
                        $n->nodeValue = $text;
281
282
                        // Algorithm 2:
283
                        // foreach ($n->childNodes as $c) {
284
                        //         $n->removeChild($c);
285
                        // }
286
                        // $n->appendChild(new \DOMText($text));
287
288
                        // Algorithm 3:
289
                        // foreach ($n->childNodes as $c) {
290
                        //         $n->replaceChild(new \DOMText($text), $c);
291
                        // }
292
                }
293
294 1
                return $this;
295
        }
296
297 1
        public function addText($text)
298
        {
299 1
                foreach ($this->nodes as $n) {
300 1
                        $n->appendChild(new \DOMText($text));
301
                }
302
303 1
                return $this;
304
        }
305
306 1
        public function setCdata($text)
307
        {
308 1
                foreach ($this->nodes as $n) {
309 1
                        $n->nodeValue = '';
310 1
                        $n->appendChild(new \DOMCDATASection($text));
311
                }
312
313 1
                return $this;
314
        }
315
316 1
        public function addCdata($text)
317
        {
318 1
                foreach ($this->nodes as $n) {
319 1
                        $n->appendChild(new \DOMCDATASection($text));
320
                }
321
322 1
                return $this;
323
        }
324
325 1
        public function setComment($text)
326
        {
327 1
                foreach ($this->nodes as $n) {
328 1
                        $n->nodeValue = '';
329 1
                        $n->appendChild(new \DOMComment($text));
330
                }
331
332 1
                return $this;
333
        }
334
335 1
        public function addComment($text)
336
        {
337 1
                foreach ($this->nodes as $n) {
338 1
                        $n->appendChild(new \DOMComment($text));
339
                }
340
341 1
                return $this;
342
        }
343
344 1
        public function remove(...$query)
345
        {
346
                // Arguments can be empty, a string or an array of strings.
347
348 1
                if (empty($query)) {
349
                        // The user has requested to remove the nodes of this context.
350 1
                        $targets = $this->nodes;
351
                } else {
352 1
                        $targets = $this->query(...$query);
353
                }
354
355 1
                foreach ($targets as $t) {
356 1
                        $t->parentNode->removeChild($t);
357
                }
358
359 1
                return $this;
360
        }
361
362 1
        public function dom()
363
        {
364 1
                return $this->document->dom;
365
        }
366
367
        // This method should be called 'array',
368
        // but for compatibility with PHP 5.6
369
        // it is shadowed by the __call() method.
370 1
        public function array_()
371
        {
372 1
                return $this->nodes;
373
        }
374
375 1
        public function __toString()
376
        {
377 1
                return $this->xml();
378
        }
379
380 1
        public function xml($strip = false)
381
        {
382 1
                return FluidHelper::domnodesToString($this->nodes);
383
        }
384
385 1
        public function html($strip = false)
386
        {
387 1
                return FluidHelper::domnodesToString($this->nodes, true);
388
        }
389
390 1
        protected function newContext(&$context)
391
        {
392 1
                return new FluidContext($this->document, $this->handler, $context);
393
        }
394
395 1
        protected function resolveQuery($query)
396
        {
397 1
                if ( $query === '.'
398 1
                     || $query[0] === '/'
399 1
                     || ( $query[0] === '.' && $query[1] === '/' )
400 1
                     || ( $query[0] === '.' && $query[1] === '.' ) ) {
401 1
                        return $query;
402
                }
403
404 1
                return CssTranslator::xpath($query);
405
        }
406
407 1
        protected function filterQueryResults(&$results)
408
        {
409 1
                $set = [];
410
411 1
                foreach ($results as $r) {
412 1
                        $found = false;
413
414 1
                        foreach ($set as $u) {
415 1
                                $found = ($r === $u) || $found;
416
                        }
417
418 1
                        if (! $found) {
419 1
                                $set[] = $r;
420
                        }
421
                }
422
423 1
                return $set;
424
        }
425
426 1
        protected function callfn($fn, $args)
427
        {
428 1
                if ($fn instanceof \Closure) {
429 1
                        $bind = \array_shift($args);
430
431 1
                        $fn = $fn->bindTo($bind);
432
433
                        // It is faster than \call_user_func.
434 1
                        return $fn(...$args);
435
                }
436
437 1
                return \call_user_func($fn, ...$args);
438
        }
439
}
440