Passed
Push — master ( 3fd8db...26be1e )
by Daniele
02:31
created

FluidContext::cdata()   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 1
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 NewableTrait,
11
            FluidSaveTrait,
12
            ReservedCallTrait,          // For compatibility with PHP 5.6.
13
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
14
15
        private $document;
16
        private $handler;
17
        private $nodes = [];
18
        private $seek = 0;
19
20 1
        public function __construct($document, $handler, $context)
21
        {
22 1
                $this->document = $document;
23 1
                $this->handler  = $handler;
24
25 1
                if (! \is_array($context) && ! $context instanceof \Traversable) {
26
                        // DOMDocument, DOMElement and DOMNode are not iterable.
27
                        // DOMNodeList and FluidContext are iterable.
28 1
                        $context = [ $context ];
29
                }
30
31 1
                foreach ($context as $n) {
32 1
                        if (! $n instanceof \DOMNode) {
33 1
                                throw new \Exception('Node type not recognized.');
34
                        }
35
36 1
                        $this->nodes[] = $n;
37
                }
38 1
        }
39
40
        // \ArrayAccess interface.
41 1
        public function offsetSet($offset, $value)
42
        {
43
                // if (\is_null($offset)) {
44
                //         $this->nodes[] = $value;
45
                // } else {
46
                //         $this->nodes[$offset] = $value;
47
                // }
48 1
                throw new \Exception('Setting a context element is not allowed.');
49
        }
50
51
        // \ArrayAccess interface.
52 1
        public function offsetExists($offset)
53
        {
54 1
                return isset($this->nodes[$offset]);
55
        }
56
57
        // \ArrayAccess interface.
58 1
        public function offsetUnset($offset)
59
        {
60
                // unset($this->nodes[$offset]);
61 1
                \array_splice($this->nodes, $offset, 1);
62 1
        }
63
64
        // \ArrayAccess interface.
65 1
        public function offsetGet($offset)
66
        {
67 1
                if (isset($this->nodes[$offset])) {
68 1
                        return $this->nodes[$offset];
69
                }
70
71 1
                return null;
72
        }
73
74
        // \Iterator interface.
75 1
        public function rewind()
76
        {
77 1
                $this->seek = 0;
78 1
        }
79
80
        // \Iterator interface.
81 1
        public function current()
82
        {
83 1
                return $this->nodes[$this->seek];
84
        }
85
86
        // \Iterator interface.
87 1
        public function key()
88
        {
89 1
                return $this->seek;
90
        }
91
92
        // \Iterator interface.
93 1
        public function next()
94
        {
95 1
                ++$this->seek;
96 1
        }
97
98
        // \Iterator interface.
99 1
        public function valid()
100
        {
101 1
                return isset($this->nodes[$this->seek]);
102
        }
103
104 1
        public function length()
105
        {
106 1
                return \count($this->nodes);
107
        }
108
109
        // Alias of ->length().
110 1
        public function size()
111
        {
112 1
                return $this->length();
113
        }
114
115 1
        public function query(...$query)
116
        {
117 1
                if (\is_array($query[0])) {
118 1
                        $query = $query[0];
119
                }
120
121 1
                $results = [];
122
123 1
                $xp = $this->document->xpath;
124
125 1
                foreach ($this->nodes as $n) {
126 1
                        foreach ($query as $q) {
127 1
                                $q = $this->resolveQuery($q);
128
129
                                // Returns a DOMNodeList.
130 1
                                $res = $xp->query($q, $n);
131
132
                                // Algorithm 1:
133
                                // $results = \array_merge($results, \iterator_to_array($res));
134
135
                                // Algorithm 2:
136
                                // It is faster than \iterator_to_array and a lot faster
137
                                // than \iterator_to_array + \array_merge.
138 1
                                foreach ($res as $r) {
139 1
                                        $results[] = $r;
140
                                }
141
142
                                // Algorithm 3:
143
                                // for ($i = 0, $l = $res->length; $i < $l; ++$i) {
144
                                //         $results[] = $res->item($i);
145
                                // }
146
                        }
147
                }
148
149
                // Performing over multiple sibling nodes a query that ascends
150
                // the xpath, relative (../..) or absolute (//), returns identical
151
                // matching results that must be collapsed in an unique result
152
                // otherwise a subsequent operation is performed multiple times.
153 1
                $results = $this->filterQueryResults($results);
154
155 1
                return $this->newContext($results);
156
        }
157
158 1
        public function __invoke(...$query)
159
        {
160 1
                return $this->query(...$query);
161
        }
162
163 1
        public function times($times, callable $fn = null)
164
        {
165 1
                if ($fn === null) {
166 1
                        return new FluidRepeater($this->document, $this->handler, $this, $times);
167
                }
168
169 1
                for ($i = 0; $i < $times; ++$i) {
170 1
                        $this->callfn($fn, [$this, $i]);
171
                }
172
173 1
                return $this;
174
        }
175
176 1
        public function each(callable $fn)
177
        {
178 1
                foreach ($this->nodes as $i => $n) {
179 1
                        $cx = $this->newContext($n);
180
181 1
                        $this->callfn($fn, [$cx, $i, $n]);
182
                }
183
184 1
                return $this;
185
        }
186
187 1
        public function filter(callable $fn)
188
        {
189 1
                $nodes = [];
190
191 1
                foreach ($this->nodes as $i => $n) {
192 1
                        $cx = $this->newContext($n);
193
194 1
                        $ret = $this->callfn($fn, [$cx, $i, $n]);
195
196 1
                        if ($ret !== false) {
197 1
                                $nodes[] = $n;
198
                        }
199
                }
200
201 1
                return $this->newContext($nodes);
202
        }
203
204
        // addChild($child, $value?, $attributes? = [], $switchContext? = false)
205 1
        public function addChild($child, ...$optionals)
206
        {
207
                return $this->handler->insertElement($this->nodes, $child, $optionals, function ($parent, $element) {
208 1
                        return $parent->appendChild($element);
209 1
                }, $this);
210
        }
211
212
        // Alias of ->addChild().
213 1
        public function add($child, ...$optionals)
214
        {
215 1
                return $this->addChild($child, ...$optionals);
216
        }
217
218 1
        public function prependSibling($sibling, ...$optionals)
219
        {
220
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
221 1
                        return $sibling->parentNode->insertBefore($element, $sibling);
222 1
                }, $this);
223
        }
224
225
        // Alias of ->prependSibling().
226 1
        public function prepend($sibling, ...$optionals)
227
        {
228 1
                return $this->prependSibling($sibling, ...$optionals);
229
        }
230
231
        public function appendSibling($sibling, ...$optionals)
232
        {
233 1
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
234
                        // If ->nextSibling is null, $element is simply appended as last sibling.
235 1
                        return $sibling->parentNode->insertBefore($element, $sibling->nextSibling);
236 1
                }, $this);
237
        }
238
239
        // Alias of ->appendSibling().
240 1
        public function append($sibling, ...$optionals)
241
        {
242 1
                return $this->appendSibling($sibling, ...$optionals);
243
        }
244
245
        // Arguments can be in the form of:
246
        // setAttribute($name, $value)
247
        // setAttribute(['name' => 'value', ...])
248 1
        public function setAttribute(...$arguments)
249
        {
250
                // Default case is:
251
                // [ 'name' => 'value', ... ]
252 1
                $attrs = $arguments[0];
253
254
                // If the first argument is not an array,
255
                // the user has passed two arguments:
256
                // 1. is the attribute name
257
                // 2. is the attribute value
258 1
                if (! \is_array($arguments[0])) {
259 1
                        $val = isset($arguments[1]) ? $arguments[1]: '';
260 1
                        $attrs = [$arguments[0] => $val];
261
                }
262
263 1
                foreach ($this->nodes as $n) {
264 1
                        foreach ($attrs as $k => $v) {
265 1
                                if (\is_integer($k)) {
266 1
                                        $k = $v;
267 1
                                        $v = null;
268
                                }
269
270
                                // Algorithm 1:
271 1
                                $n->setAttribute($k, $v);
272
273
                                // Algorithm 2:
274
                                // $n->setAttributeNode(new \DOMAttr($k, $v));
275
276
                                // Algorithm 3:
277
                                // $n->appendChild(new \DOMAttr($k, $v));
278
279
                                // Algorithm 2 and 3 have a different behaviour
280
                                // from Algorithm 1.
281
                                // The attribute is still created or setted, but
282
                                // changing the value of an existing attribute
283
                                // changes even the order of that attribute
284
                                // in the attribute list.
285
                        }
286
                }
287
288 1
                return $this;
289
        }
290
291
        // Alias of ->setAttribute().
292 1
        public function attr(...$arguments)
293
        {
294 1
                return $this->setAttribute(...$arguments);
295
        }
296
297 1
        public function setText($text)
298
        {
299 1
                foreach ($this->nodes as $n) {
300
                        // Algorithm 1:
301 1
                        $n->nodeValue = $text;
302
303
                        // Algorithm 2:
304
                        // foreach ($n->childNodes as $c) {
305
                        //         $n->removeChild($c);
306
                        // }
307
                        // $n->appendChild(new \DOMText($text));
308
309
                        // Algorithm 3:
310
                        // foreach ($n->childNodes as $c) {
311
                        //         $n->replaceChild(new \DOMText($text), $c);
312
                        // }
313
                }
314
315 1
                return $this;
316
        }
317
318
        // Alias of ->setText().
319 1
        public function text($text)
320
        {
321 1
                return $this->setText($text);
322
        }
323
324 1
        public function addText($text)
325
        {
326 1
                foreach ($this->nodes as $n) {
327 1
                        $n->appendChild(new \DOMText($text));
328
                }
329
330 1
                return $this;
331
        }
332
333 1
        public function setCdata($text)
334
        {
335 1
                foreach ($this->nodes as $n) {
336 1
                        $n->nodeValue = '';
337 1
                        $n->appendChild(new \DOMCDATASection($text));
338
                }
339
340 1
                return $this;
341
        }
342
343
        // Alias of ->setCdata().
344 1
        public function cdata($text)
345
        {
346 1
                return $this->setCdata($text);
347
        }
348
349 1
        public function addCdata($text)
350
        {
351 1
                foreach ($this->nodes as $n) {
352 1
                        $n->appendChild(new \DOMCDATASection($text));
353
                }
354
355 1
                return $this;
356
        }
357
358 1
        public function setComment($text)
359
        {
360 1
                foreach ($this->nodes as $n) {
361 1
                        $n->nodeValue = '';
362 1
                        $n->appendChild(new \DOMComment($text));
363
                }
364
365 1
                return $this;
366
        }
367
368
        // Alias of ->setComment().
369 1
        public function comment($text)
370
        {
371 1
                return $this->setComment($text);
372
        }
373
374 1
        public function addComment($text)
375
        {
376 1
                foreach ($this->nodes as $n) {
377 1
                        $n->appendChild(new \DOMComment($text));
378
                }
379
380 1
                return $this;
381
        }
382
383 1
        public function remove(...$query)
384
        {
385
                // Arguments can be empty, a string or an array of strings.
386
387 1
                if (empty($query)) {
388
                        // The user has requested to remove the nodes of this context.
389 1
                        $targets = $this->nodes;
390
                } else {
391 1
                        $targets = $this->query(...$query);
392
                }
393
394 1
                foreach ($targets as $t) {
395 1
                        $t->parentNode->removeChild($t);
396
                }
397
398 1
                return $this;
399
        }
400
401 1
        public function dom()
402
        {
403 1
                return $this->document->dom;
404
        }
405
406
        // This method should be called 'array',
407
        // but for compatibility with PHP 5.6
408
        // it is shadowed by the __call() method.
409 1
        public function array_()
410
        {
411 1
                return $this->nodes;
412
        }
413
414 1
        public function __toString()
415
        {
416 1
                return $this->xml();
417
        }
418
419 1
        public function xml($strip = false)
420
        {
421 1
                return FluidHelper::domnodesToString($this->nodes);
422
        }
423
424 1
        public function html($strip = false)
425
        {
426 1
                return FluidHelper::domnodesToString($this->nodes, true);
427
        }
428
429 1
        protected function newContext(&$context)
430
        {
431 1
                return new FluidContext($this->document, $this->handler, $context);
432
        }
433
434 1
        protected function resolveQuery($query)
435
        {
436 1
                if ( $query === '.'
437 1
                     || $query[0] === '/'
438 1
                     || ( $query[0] === '.' && $query[1] === '/' )
439 1
                     || ( $query[0] === '.' && $query[1] === '.' ) ) {
440 1
                        return $query;
441
                }
442
443 1
                return CssTranslator::xpath($query);
444
        }
445
446 1
        protected function filterQueryResults(&$results)
447
        {
448 1
                $set = [];
449
450 1
                foreach ($results as $r) {
451 1
                        $found = false;
452
453 1
                        foreach ($set as $u) {
454 1
                                $found = ($r === $u) || $found;
455
                        }
456
457 1
                        if (! $found) {
458 1
                                $set[] = $r;
459
                        }
460
                }
461
462 1
                return $set;
463
        }
464
465 1
        protected function callfn($fn, $args)
466
        {
467 1
                if ($fn instanceof \Closure) {
468 1
                        $bind = \array_shift($args);
469
470 1
                        $fn = $fn->bindTo($bind);
471
472
                        // It is faster than \call_user_func.
473 1
                        return $fn(...$args);
474
                }
475
476 1
                return \call_user_func($fn, ...$args);
477
        }
478
}
479