Passed
Push — master ( d5cade...3c3d9c )
by Daniele
52s
created

FluidContext::map()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 5
Ratio 41.67 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 5
loc 12
ccs 6
cts 6
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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 View Code Duplication
                foreach ($this->nodes as $i => $n) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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 map(callable $fn)
183
        {
184 1
                $result = [];
185
186 1 View Code Duplication
                foreach ($this->nodes as $i => $n) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
187 1
                        $cx = $this->newContext($n);
188
189 1
                        $result[] = $this->callfn($fn, [$cx, $i, $n]);
190
                }
191
192 1
                return $result;
193
        }
194
195 1
        public function filter(callable $fn)
196
        {
197 1
                $nodes = [];
198
199 1
                foreach ($this->nodes as $i => $n) {
200 1
                        $cx = $this->newContext($n);
201
202 1
                        $ret = $this->callfn($fn, [$cx, $i, $n]);
203
204 1
                        if ($ret !== false) {
205 1
                                $nodes[] = $n;
206
                        }
207
                }
208
209 1
                return $this->newContext($nodes);
210
        }
211
212
        // addChild($child, $value?, $attributes? = [], $switchContext? = false)
213 1
        public function addChild($child, ...$optionals)
214
        {
215
                return $this->handler->insertElement($this->nodes, $child, $optionals, function ($parent, $element) {
216 1
                        return $parent->appendChild($element);
217 1
                }, $this);
218
        }
219
220 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...
221
        {
222
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
223 1
                        if ($sibling->parentNode === null) {
224
                                // If the node doesn't have at least one parent node,
225
                                // the sibling creation fails. In this case we replace
226
                                // the sibling creation with the creation of a child node.
227
                                // Useful when dealing with a DOMDocument with a null
228
                                // documentElement property.
229 1
                                return $sibling->appendChild($element);
230
                        }
231 1
                        return $sibling->parentNode->insertBefore($element, $sibling);
232 1
                }, $this);
233
        }
234
235 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...
236
        {
237 1
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
238 1
                        if ($sibling->parentNode === null) {
239
                                // If the node doesn't have at least one parent node,
240
                                // the sibling creation fails. In this case we replace
241
                                // the sibling creation with the creation of a child node.
242
                                // Useful when dealing with a DOMDocument with a null
243
                                // documentElement property.
244 1
                                return $sibling->appendChild($element);
245
                        }
246
                        // If ->nextSibling is null, $element is simply appended as last sibling.
247 1
                        return $sibling->parentNode->insertBefore($element, $sibling->nextSibling);
248 1
                }, $this);
249
        }
250
251
        // setAttribute($name, $value)
252
        // setAttribute(['name' => 'value', ...])
253 1
        public function setAttribute($name, $value = null)
254
        {
255 1
                if (\is_array($name)) {
256 1
                        $attrs = $name;
257
                } else {
258 1
                        $attrs = [ $name => $value ];
259
                }
260
261 1
                foreach ($this->nodes as $n) {
262 1
                        foreach ($attrs as $k => $v) {
263 1
                                if (\is_integer($k)) {
264 1
                                        $k = $v;
265 1
                                        $v = null;
266
                                }
267
268
                                // Algorithm 1:
269 1
                                $n->setAttribute($k, $v);
270
271
                                // Algorithm 2:
272
                                // $n->setAttributeNode(new \DOMAttr($k, $v));
273
274
                                // Algorithm 3:
275
                                // $n->appendChild(new \DOMAttr($k, $v));
276
277
                                // Algorithm 2 and 3 have a different behaviour
278
                                // from Algorithm 1.
279
                                // The attribute is still created or setted, but
280
                                // changing the value of an existing attribute
281
                                // changes even the order of that attribute
282
                                // in the attribute list.
283
                        }
284
                }
285
286 1
                return $this;
287
        }
288
289 1
        public function setText($text)
290
        {
291 1
                foreach ($this->nodes as $n) {
292
                        // Algorithm 1:
293 1
                        $n->nodeValue = $text;
294
295
                        // Algorithm 2:
296
                        // foreach ($n->childNodes as $c) {
297
                        //         $n->removeChild($c);
298
                        // }
299
                        // $n->appendChild(new \DOMText($text));
300
301
                        // Algorithm 3:
302
                        // foreach ($n->childNodes as $c) {
303
                        //         $n->replaceChild(new \DOMText($text), $c);
304
                        // }
305
                }
306
307 1
                return $this;
308
        }
309
310 1
        public function addText($text)
311
        {
312 1
                foreach ($this->nodes as $n) {
313 1
                        $n->appendChild(new \DOMText($text));
314
                }
315
316 1
                return $this;
317
        }
318
319 1
        public function setCdata($text)
320
        {
321 1
                foreach ($this->nodes as $n) {
322 1
                        $n->nodeValue = '';
323 1
                        $n->appendChild(new \DOMCDATASection($text));
324
                }
325
326 1
                return $this;
327
        }
328
329 1
        public function addCdata($text)
330
        {
331 1
                foreach ($this->nodes as $n) {
332 1
                        $n->appendChild(new \DOMCDATASection($text));
333
                }
334
335 1
                return $this;
336
        }
337
338 1
        public function setComment($text)
339
        {
340 1
                foreach ($this->nodes as $n) {
341 1
                        $n->nodeValue = '';
342 1
                        $n->appendChild(new \DOMComment($text));
343
                }
344
345 1
                return $this;
346
        }
347
348 1
        public function addComment($text)
349
        {
350 1
                foreach ($this->nodes as $n) {
351 1
                        $n->appendChild(new \DOMComment($text));
352
                }
353
354 1
                return $this;
355
        }
356
357 1
        public function remove(...$query)
358
        {
359
                // Arguments can be empty, a string or an array of strings.
360
361 1
                if (empty($query)) {
362
                        // The user has requested to remove the nodes of this context.
363 1
                        $targets = $this->nodes;
364
                } else {
365 1
                        $targets = $this->query(...$query);
366
                }
367
368 1
                foreach ($targets as $t) {
369 1
                        $t->parentNode->removeChild($t);
370
                }
371
372 1
                return $this;
373
        }
374
375 1
        public function dom()
376
        {
377 1
                return $this->document->dom;
378
        }
379
380
        // This method should be called 'array',
381
        // but for compatibility with PHP 5.6
382
        // it is shadowed by the __call() method.
383 1
        public function array_()
384
        {
385 1
                return $this->nodes;
386
        }
387
388 1
        public function __toString()
389
        {
390 1
                return $this->xml();
391
        }
392
393 1
        public function xml($strip = false)
394
        {
395 1
                return FluidHelper::domnodesToString($this->nodes);
396
        }
397
398 1
        public function html($strip = false)
399
        {
400 1
                return FluidHelper::domnodesToString($this->nodes, true);
401
        }
402
403 1
        protected function newContext(&$context)
404
        {
405 1
                return new FluidContext($this->document, $this->handler, $context);
406
        }
407
408 1
        protected function resolveQuery($query)
409
        {
410 1
                if ( $query === '.'
411 1
                     || $query[0] === '/'
412 1
                     || ( $query[0] === '.' && $query[1] === '/' )
413 1
                     || ( $query[0] === '.' && $query[1] === '.' ) ) {
414 1
                        return $query;
415
                }
416
417 1
                return CssTranslator::xpath($query);
418
        }
419
420 1
        protected function filterQueryResults(&$results)
421
        {
422 1
                $set = [];
423
424 1
                foreach ($results as $r) {
425 1
                        $found = false;
426
427 1
                        foreach ($set as $u) {
428 1
                                $found = ($r === $u) || $found;
429
                        }
430
431 1
                        if (! $found) {
432 1
                                $set[] = $r;
433
                        }
434
                }
435
436 1
                return $set;
437
        }
438
439 1
        protected function callfn($fn, $args)
440
        {
441 1
                if ($fn instanceof \Closure) {
442 1
                        $bind = \array_shift($args);
443
444 1
                        $fn = $fn->bindTo($bind);
445
446
                        // It is faster than \call_user_func.
447 1
                        return $fn(...$args);
448
                }
449
450 1
                return \call_user_func($fn, ...$args);
451
        }
452
}
453