Passed
Pull Request — master (#26)
by
unknown
05:47
created

FluidContext::getText()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
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 1 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
                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
        public function getText($glue = PHP_EOL)
290
        {
291
                return implode($glue, $this->map(function ($i, $n) { return $n->textContent; }));
292
        }
293
294 1
        public function setText($text)
295
        {
296 1
                foreach ($this->nodes as $n) {
297
                        // Algorithm 1:
298 1
                        $n->nodeValue = $text;
299
300
                        // Algorithm 2:
301
                        // foreach ($n->childNodes as $c) {
302
                        //         $n->removeChild($c);
303
                        // }
304
                        // $n->appendChild(new \DOMText($text));
305
306
                        // Algorithm 3:
307
                        // foreach ($n->childNodes as $c) {
308
                        //         $n->replaceChild(new \DOMText($text), $c);
309
                        // }
310
                }
311
312 1
                return $this;
313
        }
314
315 1
        public function addText($text)
316
        {
317 1
                foreach ($this->nodes as $n) {
318 1
                        $n->appendChild(new \DOMText($text));
319
                }
320
321 1
                return $this;
322
        }
323
324 1
        public function setCdata($text)
325
        {
326 1
                foreach ($this->nodes as $n) {
327 1
                        $n->nodeValue = '';
328 1
                        $n->appendChild(new \DOMCDATASection($text));
329
                }
330
331 1
                return $this;
332
        }
333
334 1
        public function addCdata($text)
335
        {
336 1
                foreach ($this->nodes as $n) {
337 1
                        $n->appendChild(new \DOMCDATASection($text));
338
                }
339
340 1
                return $this;
341
        }
342
343 1
        public function setComment($text)
344
        {
345 1
                foreach ($this->nodes as $n) {
346 1
                        $n->nodeValue = '';
347 1
                        $n->appendChild(new \DOMComment($text));
348
                }
349
350 1
                return $this;
351
        }
352
353 1
        public function addComment($text)
354
        {
355 1
                foreach ($this->nodes as $n) {
356 1
                        $n->appendChild(new \DOMComment($text));
357
                }
358
359 1
                return $this;
360
        }
361
362 1
        public function remove(...$query)
363
        {
364
                // Arguments can be empty, a string or an array of strings.
365
366 1
                if (empty($query)) {
367
                        // The user has requested to remove the nodes of this context.
368 1
                        $targets = $this->nodes;
369
                } else {
370 1
                        $targets = $this->query(...$query);
371
                }
372
373 1
                foreach ($targets as $t) {
374 1
                        $t->parentNode->removeChild($t);
375
                }
376
377 1
                return $this;
378
        }
379
380 1
        public function dom()
381
        {
382 1
                return $this->document->dom;
383
        }
384
385
        // This method should be called 'array',
386
        // but for compatibility with PHP 5.6
387
        // it is shadowed by the __call() method.
388 1
        public function array_()
389
        {
390 1
                return $this->nodes;
391
        }
392
393 1
        public function __toString()
394
        {
395 1
                return $this->xml();
396
        }
397
398 1
        public function xml($strip = false)
399
        {
400 1
                return FluidHelper::domnodesToString($this->nodes);
401
        }
402
403 1
        public function html($strip = false)
404
        {
405 1
                return FluidHelper::domnodesToString($this->nodes, true);
406
        }
407
408 1
        protected function newContext(&$context)
409
        {
410 1
                return new FluidContext($this->document, $this->handler, $context);
411
        }
412
413 1
        protected function resolveQuery($query)
414
        {
415 1
                if ( $query === '.'
416 1
                     || $query[0] === '/'
417 1
                     || ( $query[0] === '.' && $query[1] === '/' )
418 1
                     || ( $query[0] === '.' && $query[1] === '.' ) ) {
419 1
                        return $query;
420
                }
421
422 1
                return CssTranslator::xpath($query);
423
        }
424
425 1
        protected function filterQueryResults(&$results)
426
        {
427 1
                $set = [];
428
429 1
                foreach ($results as $r) {
430 1
                        $found = false;
431
432 1
                        foreach ($set as $u) {
433 1
                                $found = ($r === $u) || $found;
434
                        }
435
436 1
                        if (! $found) {
437 1
                                $set[] = $r;
438
                        }
439
                }
440
441 1
                return $set;
442
        }
443
444 1
        protected function callfn($fn, $args)
445
        {
446 1
                if ($fn instanceof \Closure) {
447 1
                        $bind = \array_shift($args);
448
449 1
                        $fn = $fn->bindTo($bind);
450
451
                        // It is faster than \call_user_func.
452 1
                        return $fn(...$args);
453
                }
454
455 1
                return \call_user_func($fn, ...$args);
456
        }
457
}
458