Completed
Push — master ( 24d3ae...1f5a99 )
by Daniele
02:46
created

FluidContext::handleInsertion()   F

Complexity

Conditions 40
Paths 404

Size

Total Lines 121
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 40
Metric Value
dl 0
loc 121
ccs 46
cts 46
cp 1
rs 3.3154
cc 40
eloc 46
nc 404
nop 5
crap 40

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
// Copyright (c) 2016, Daniele Orlando <fluidxml(at)danieleorlando.com>
4
// All rights reserved.
5
//
6
// Redistribution and use in source and binary forms, with or without modification,
7
// are permitted provided that the following conditions are met:
8
//
9
// 1. Redistributions of source code must retain the above copyright notice, this
10
//    list of conditions and the following disclaimer.
11
//
12
// 2. Redistributions in binary form must reproduce the above copyright notice,
13
//    this list of conditions and the following disclaimer in the documentation
14
//    and/or other materials provided with the distribution.
15
//
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
25
// OF THE POSSIBILITY OF SUCH DAMAGE.
26
27
/**
28
 * FluidXML is a PHP library, under the Servo PHP framework umbrella,
29
 * specifically designed to manipulate XML documents with a concise
30
 * and fluent interface.
31
 *
32
 * It leverages XPath and the fluent programming technique to be fun
33
 * and effective.
34
 *
35
 * @author Daniele Orlando <fluidxml(at)danieleorlando.com>
36
 *
37
 * @license BSD-2-Clause
38
 * @license https://opensource.org/licenses/BSD-2-Clause
39
 */
40
41
namespace FluidXml
42
{
43
44
use \FluidXml\Core\FluidInterface;
45
use \FluidXml\Core\FluidDocument;
46
use \FluidXml\Core\FluidContext;
47
use \FluidXml\Core\NewableTrait;
48
use \FluidXml\Core\ReservedCallTrait;
49
use \FluidXml\Core\ReservedCallStaticTrait;
50
51
/**
52
 * Constructs a new FluidXml instance.
53
 *
54
 * ```php
55
 * $xml = fluidxml();
56
 * // is the same of
57
 * $xml = new FluidXml();
58
 *
59
 * $xml = fluidxml([
60
 *
61
 *   'root'       => 'doc',
62
 *
63
 *   'version'    => '1.0',
64
 *
65
 *   'encoding'   => 'UTF-8',
66
 *
67
 *   'stylesheet' => null ]);
68
 * ```
69
 *
70
 * @param array $arguments Options that influence the construction of the XML document.
71
 *
72
 * @return FluidXml A new FluidXml instance.
73
 */
74
function fluidify(...$arguments)
75
{
76 1
        return FluidXml::load(...$arguments);
77
}
78
79
function fluidxml(...$arguments)
80
{
81 1
        return new FluidXml(...$arguments);
82
}
83
84
function fluidns(...$arguments)
85
{
86 1
        return new FluidNamespace(...$arguments);
87
}
88
89
function is_an_xml_string($string)
90
{
91
        // Removes any empty new line at the beginning,
92
        // otherwise the first character check may fail.
93 1
        $string = \ltrim($string);
94
95 1
        return $string[0] === '<';
96
}
97
98
function domdocument_to_string_without_headers(\DOMDocument $dom)
99
{
100 1
        return $dom->saveXML($dom->documentElement);
101
}
102
103
function domnodelist_to_string(\DOMNodeList $nodelist)
104
{
105 1
        $nodes = [];
106
107 1
        foreach ($nodelist as $n) {
108 1
                $nodes[] = $n;
109
        }
110
111 1
        return domnodes_to_string($nodes);
112
}
113
114
function domnodes_to_string(array $nodes)
115
{
116 1
        $dom = $nodes[0]->ownerDocument;
117 1
        $xml = '';
118
119 1
        foreach ($nodes as $n) {
120 1
                $xml .= $dom->saveXML($n) . PHP_EOL;
121
        }
122
123 1
        return \rtrim($xml);
124
}
125
126
function simplexml_to_string_without_headers(\SimpleXMLElement $element)
127
{
128 1
        $dom = \dom_import_simplexml($element);
129
130 1
        return $dom->ownerDocument->saveXML($dom);
131
}
132
133
class FluidXml implements FluidInterface
134
{
135
        use NewableTrait,
136
            ReservedCallTrait,          // For compatibility with PHP 5.6.
137
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
138
139
        const ROOT_NODE = 'doc';
140
141
        private $document;
142
143 1
        public static function load($document)
144
        {
145 1
                if (\is_string($document) && ! is_an_xml_string($document)) {
146
                        // Removes any empty new line at the beginning,
147
                        // otherwise the first character check fails.
148
149 1
                        $file        = $document;
150 1
                        $is_file     = \is_file($file);
151 1
                        $is_readable = \is_readable($file);
152
153 1
                        if ($is_file && $is_readable) {
154 1
                                $document = \file_get_contents($file);
155
                        }
156
157 1
                        if (! $is_file || ! $is_readable || ! $document) {
158 1
                                throw new \Exception("File '$file' not accessible.");
159
                        }
160
                }
161
162 1
                return (new FluidXml(['root' => null]))->appendChild($document);
163
        }
164
165 1
        public function __construct($root = null, $options = [])
166
        {
167 1
                $this->document = new FluidDocument();
168 1
                $doc = $this->document;
169
170 1
                $defaults = [ 'root'       => self::ROOT_NODE,
171 1
                              'version'    => '1.0',
172 1
                              'encoding'   => 'UTF-8',
173
                              'stylesheet' => null ];
174
175 1
                if (\is_string($root)) {
176
                        // The root option can be specified as first argument
177
                        // because it is the most common.
178 1
                        $defaults['root'] = $root;
179 1
                } else if (\is_array($root)) {
180
                        // If the first argument is an array, the user has skipped
181
                        // the root option and is passing a bunch of options all together.
182 1
                        $options = $root;
183
                }
184
185 1
                $opts = \array_merge($defaults, $options);
186
187 1
                $doc->dom = new \DOMDocument($opts['version'], $opts['encoding']);
188 1
                $doc->dom->formatOutput       = true;
189 1
                $doc->dom->preserveWhiteSpace = false;
190
191 1
                $doc->xpath = new \DOMXPath($doc->dom);
192
193 1
                if (! empty($opts['root'])) {
194 1
                        $this->appendSibling($opts['root']);
195
                }
196
197 1
                if (! empty($opts['stylesheet'])) {
198
                        $attrs = 'type="text/xsl" '
199 1
                               . "encoding=\"{$opts['encoding']}\" "
200 1
                               . 'indent="yes" '
201 1
                               . "href=\"{$opts['stylesheet']}\"";
202 1
                        $stylesheet = new \DOMProcessingInstruction('xml-stylesheet', $attrs);
203
204 1
                        $doc->dom->insertBefore($stylesheet, $doc->dom->documentElement);
205
                }
206 1
        }
207
208 1
        public function xml($strip = false)
209
        {
210 1
                if ($strip) {
211 1
                        return domdocument_to_string_without_headers($this->document->dom);
212
                }
213
214 1
                return $this->document->dom->saveXML();
215
        }
216
217 1
        public function dom()
218
        {
219 1
                return $this->document->dom;
220
        }
221
222 1
        public function namespaces()
223
        {
224 1
                return $this->document->namespaces;
225
        }
226
227
        // This method should be called 'namespace',
228
        // but for compatibility with PHP 5.6
229
        // it is shadowed by the __call() method.
230 1
        protected function namespace_(...$arguments)
231
        {
232 1
                $namespaces = [];
233
234 1
                if (\is_string($arguments[0])) {
235 1
                        $args = [ $arguments[0], $arguments[1] ];
236
237 1
                        if (isset($arguments[2])) {
238 1
                                $args[] = $arguments[2];
239
                        }
240
241 1
                        $namespaces[] = new FluidNamespace(...$args);
242 1
                } else if (\is_array($arguments[0])) {
243 1
                        $namespaces = $arguments[0];
244
                } else {
245 1
                        $namespaces = $arguments;
246
                }
247
248 1
                foreach ($namespaces as $n) {
249 1
                        $this->document->namespaces[$n->id()] = $n;
250 1
                        $this->document->xpath->registerNamespace($n->id(), $n->uri());
251
                }
252
253 1
                return $this;
254
        }
255
256 1
        public function query(...$xpath)
257
        {
258 1
                return $this->context()->query(...$xpath);
259
        }
260
261 1
        public function times($times, callable $fn = null)
262
        {
263 1
                return $this->context()->times($times, $fn);
264
        }
265
266 1
        public function each(callable $fn)
267
        {
268 1
                return $this->context()->each($fn);
269
        }
270
271 1
        public function appendChild($child, ...$optionals)
272
        {
273
                // If the user has requested ['root' => null] at construction time
274
                // 'context()' promotes DOMDocument as root node.
275 1
                $context     = $this->context();
276 1
                $new_context = $context->appendChild($child, ...$optionals);
277
278 1
                return $this->chooseContext($context, $new_context);
279
        }
280
281
        // Alias of appendChild().
282 1
        public function add($child, ...$optionals)
283
        {
284 1
                return $this->appendChild($child, ...$optionals);
285
        }
286
287 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...
288
        {
289 1
                if ($this->document->dom->documentElement === null) {
290
                        // If the document doesn't have at least one root node,
291
                        // the sibling creation fails. In this case we replace
292
                        // the sibling creation with the creation of a generic node.
293 1
                        return $this->appendChild($sibling, ...$optionals);
294
                }
295
296 1
                $context     = $this->context();
297 1
                $new_context = $context->prependSibling($sibling, ...$optionals);
298
299 1
                return $this->chooseContext($context, $new_context);
300
        }
301
302
        // Alias of prependSibling().
303 1
        public function prepend($sibling, ...$optionals)
304
        {
305 1
                return $this->prependSibling($sibling, ...$optionals);
306
        }
307
308
        // Alias of prependSibling().
309 1
        public function insertSiblingBefore($sibling, ...$optionals)
310
        {
311 1
                return $this->prependSibling($sibling, ...$optionals);
312
        }
313
314 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...
315
        {
316 1
                if ($this->document->dom->documentElement === null) {
317
                        // If the document doesn't have at least one root node,
318
                        // the sibling creation fails. In this case we replace
319
                        // the sibling creation with the creation of a generic node.
320 1
                        return $this->appendChild($sibling, ...$optionals);
321
                }
322
323 1
                $context     = $this->context();
324 1
                $new_context = $context->appendSibling($sibling, ...$optionals);
325
326 1
                return $this->chooseContext($context, $new_context);
327
        }
328
329
        // Alias of appendSibling().
330 1
        public function append($sibling, ...$optionals)
331
        {
332 1
                return $this->appendSibling($sibling, ...$optionals);
333
        }
334
335
        // Alias of appendSibling().
336 1
        public function insertSiblingAfter($sibling, ...$optionals)
337
        {
338 1
                return $this->appendSibling($sibling, ...$optionals);
339
        }
340
341 1
        public function setAttribute(...$arguments)
342
        {
343 1
                $this->context()->setAttribute(...$arguments);
344
345 1
                return $this;
346
        }
347
348
        // Alias of setAttribute().
349 1
        public function attr(...$arguments)
350
        {
351 1
                return $this->setAttribute(...$arguments);
352
        }
353
354 1
        public function appendText($text)
355
        {
356 1
                $this->context()->appendText($text);
357
358 1
                return $this;
359
        }
360
361 1
        public function appendCdata($text)
362
        {
363 1
                $this->context()->appendCdata($text);
364
365 1
                return $this;
366
        }
367
368 1
        public function setText($text)
369
        {
370 1
                $this->context()->setText($text);
371
372 1
                return $this;
373
        }
374
375
        // Alias of setText().
376 1
        public function text($text)
377
        {
378 1
                return $this->setText($text);
379
        }
380
381 1
        public function setCdata($text)
382
        {
383 1
                $this->context()->setCdata($text);
384
385 1
                return $this;
386
        }
387
388
        // Alias of setCdata().
389 1
        public function cdata($text)
390
        {
391 1
                return $this->setCdata($text);
392
        }
393
394 1
        public function remove(...$xpath)
395
        {
396 1
                $this->context()->remove(...$xpath);
397
398 1
                return $this;
399
        }
400
401
        private $context;
402
        private $contextEl;
403
404 1
        protected function context()
405
        {
406 1
                if ($this->document->dom->documentElement === null) {
407
                        // If the user has requested ['root' => null] at construction time
408
                        // the 'documentElement' property is null because we have not created
409
                        // a root node yet. Whether there is not a root node, the DOMDocument
410
                        // is promoted as root node.
411 1
                        if ($this->context === null) {
412 1
                                $this->context = new FluidContext($this->document, $this->document->dom);
413
                        }
414
415 1
                        return $this->context;
416
                }
417
418 1
                if ($this->contextEl !== $this->document->dom->documentElement) {
419
                        // The user can prepend a root node to the current root node.
420
                        // In this case we have to update the context with the new first root node.
421 1
                        $this->context   = new FluidContext($this->document, $this->document->dom->documentElement);
422 1
                        $this->contextEl = $this->document->dom->documentElement;
423
                }
424
425 1
                return $this->context;
426
        }
427
428 1
        protected function chooseContext($help_context, $new_context)
429
        {
430
                // If the two contextes are diffent, the user has requested
431
                // a switch of the context and we have to return it.
432 1
                if ($help_context !== $new_context) {
433 1
                        return $new_context;
434
                }
435
436 1
                return $this;
437
        }
438
}
439
440
class FluidNamespace
441
{
442
        const ID   = 'id'  ;
443
        const URI  = 'uri' ;
444
        const MODE = 'mode';
445
446
        const MODE_IMPLICIT = 0;
447
        const MODE_EXPLICIT = 1;
448
449
        private $config = [ self::ID   => '',
450
                            self::URI  => '',
451
                            self::MODE => self::MODE_EXPLICIT ];
452
453 1
        public function __construct($id, $uri, $mode = 1)
454
        {
455 1
                if (\is_array($id)) {
456 1
                        $args = $id;
457 1
                        $id   = $args[self::ID];
458 1
                        $uri  = $args[self::URI];
459
460 1
                        if (isset($args[self::MODE])) {
461 1
                                $mode = $args[self::MODE];
462
                        }
463
                }
464
465 1
                $this->config[self::ID]   = $id;
466 1
                $this->config[self::URI]  = $uri;
467 1
                $this->config[self::MODE] = $mode;
468 1
        }
469
470 1
        public function id()
471
        {
472 1
                return $this->config[self::ID];
473
        }
474
475 1
        public function uri()
476
        {
477 1
                return $this->config[self::URI];
478
        }
479
480 1
        public function mode()
481
        {
482 1
                return $this->config[self::MODE];
483
        }
484
485 1
        public function querify($xpath)
486
        {
487 1
                $id = $this->id();
488
489 1
                if ($id) {
490 1
                        $id .= ':';
491
                }
492
493
                // An XPath query may not start with a slash ('/').
494
                // Relative queries are an example '../target".
495 1
                $new_xpath = '';
496
497 1
                $nodes = \explode('/', $xpath);
498
499 1
                foreach ($nodes as $node) {
500
                        // An XPath query may have multiple slashes ('/')
501
                        // example: //target
502 1
                        if ($node) {
503 1
                                $new_xpath .= "{$id}{$node}";
504
                        }
505
506 1
                        $new_xpath .= '/';
507
                }
508
509
                // Removes the last appended slash.
510 1
                return \substr($new_xpath, 0, -1);
511
        }
512
}
513
514
} // END OF NAMESPACE FluidXml
515
516
namespace FluidXml\Core
517
{
518
519
use \FluidXml\FluidXml;
0 ignored issues
show
Coding Style introduced by
USE declarations must go after the first namespace declaration
Loading history...
520
use \FluidXml\FluidNamespace;
0 ignored issues
show
Coding Style introduced by
USE declarations must go after the first namespace declaration
Loading history...
521
522
use function \FluidXml\is_an_xml_string;
0 ignored issues
show
Coding Style introduced by
USE declarations must go after the first namespace declaration
Loading history...
523
use function \FluidXml\domnodes_to_string;
0 ignored issues
show
Coding Style introduced by
USE declarations must go after the first namespace declaration
Loading history...
524
525
interface FluidInterface
526
{
527
        /**
528
         * Executes an XPath query.
529
         *
530
         * ```php
531
         * $xml = fluidxml();
532
533
         * $xml->query("/doc/book[@id='123']");
534
         *
535
         * // Relative queries are valid.
536
         * $xml->query("/doc")->query("book[@id='123']");
537
         * ```
538
         *
539
         * @param string $xpath The XPath to execute.
540
         *
541
         * @return FluidContext The context associated to the DOMNodeList.
542
         */
543
        public function query(...$xpath);
544
        public function times($times, callable $fn = null);
545
        public function each(callable $fn);
546
547
        /**
548
         * Append a new node as child of the current context.
549
         *
550
         * ```php
551
         * $xml = fluidxml();
552
553
         * $xml->appendChild('title', 'The Theory Of Everything');
554
         * $xml->appendChild([ 'author' => 'S. Hawking' ]);
555
         *
556
         * $xml->appendChild('chapters', true)->appendChild('chapter', ['id'=> 1]);
557
         *
558
         * ```
559
         *
560
         * @param string|array $child The child/children to add.
561
         * @param string $value The child text content.
0 ignored issues
show
Bug introduced by
There is no parameter named $value. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
562
         * @param bool $switchContext Whether to return the current context
0 ignored issues
show
Bug introduced by
There is no parameter named $switchContext. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
563
         *                            or the context of the created node.
564
         *
565
         * @return FluidContext The context associated to the DOMNodeList.
566
         */
567
        public function appendChild($child, ...$optionals);
568
        public function prependSibling($sibling, ...$optionals);
569
        public function appendSibling($sibling, ...$optionals);
570
        public function setAttribute(...$arguments);
571
        public function setText($text);
572
        public function appendText($text);
573
        public function setCdata($text);
574
        public function appendCdata($text);
575
        public function remove(...$xpath);
576
        public function xml($strip = false);
577
        // Aliases:
578
        public function add($child, ...$optionals);
579
        public function prepend($sibling, ...$optionals);
580
        public function insertSiblingBefore($sibling, ...$optionals);
581
        public function append($sibling, ...$optionals);
582
        public function insertSiblingAfter($sibling, ...$optionals);
583
        public function attr(...$arguments);
584
        public function text($text);
585
}
586
587
trait ReservedCallTrait
588
{
589 1
        public function __call($method, $arguments)
590
        {
591 1
                $m = "{$method}_";
592
593 1
                if (\method_exists($this, $m)) {
594 1
                        return $this->$m(...$arguments);
595
                }
596
597
                throw new \Exception("Method '$method' not found.");
598
        }
599
}
600
601
trait ReservedCallStaticTrait
602
{
603 1
        public static function __callStatic($method, $arguments)
604
        {
605 1
                $m = "{$method}_";
606
607 1
                if (\method_exists(static::class, $m)) {
608 1
                        return static::$m(...$arguments);
609
                }
610
611
                throw new \Exception("Method '$method' not found.");
612
        }
613
}
614
615
trait NewableTrait
616
{
617
        // This method should be called 'new',
618
        // but for compatibility with PHP 5.6
619
        // it is shadowed by the __callStatic() method.
620 1
        public static function new_(...$arguments)
621
        {
622 1
                return new static(...$arguments);
623
        }
624
}
625
626
class FluidDocument
627
{
628
        public $dom;
629
        public $xpath;
630
        public $namespaces = [];
631
}
632
633
class FluidRepeater
634
{
635
        private $document;
636
        private $context;
637
        private $times;
638
639 1
        public function __construct($document, $context, $times)
640
        {
641 1
                $this->document = $document;
642 1
                $this->context  = $context;
643 1
                $this->times    = $times;
644 1
        }
645
646 1
        public function __call($method, $arguments)
647
        {
648 1
                $nodes = [];
649 1
                $new_context = $this->context;
650
651 1
                for ($i = 0, $l = $this->times; $i < $l; ++$i) {
652 1
                        $new_context = $this->context->$method(...$arguments);
653 1
                        $nodes       = \array_merge($nodes, $new_context->asArray());
654
                }
655
656 1
                if ($new_context !== $this->context) {
657 1
                        return new FluidContext($this->document, $nodes);
658
                }
659
660 1
                return $this->context;
661
        }
662
}
663
664
class FluidContext implements FluidInterface, \ArrayAccess, \Iterator
665
{
666
        use NewableTrait,
667
            ReservedCallTrait,          // For compatibility with PHP 5.6.
668
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
669
670
        private $document;
671
        private $nodes = [];
672
        private $seek = 0;
673
674 1
        public function __construct($document, $context)
675
        {
676 1
                $this->document = $document;
677
678 1
                if (! \is_array($context) && ! $context instanceof \Traversable) {
679
                        // DOMDocument, DOMElement and DOMNode are not iterable.
680
                        // DOMNodeList and FluidContext are iterable.
681 1
                        $context = [ $context ];
682
                }
683
684 1
                foreach ($context as $n) {
685 1
                        if (! $n instanceof \DOMNode) {
686
                                throw new \Exception('Node type not recognized.');
687
                        }
688
689 1
                        $this->nodes[] = $n;
690
                }
691 1
        }
692
693 1
        public function asArray()
694
        {
695 1
                return $this->nodes;
696
        }
697
698
        // \ArrayAccess interface.
699 1
        public function offsetSet($offset, $value)
700
        {
701
                // if (\is_null($offset)) {
702
                //         $this->nodes[] = $value;
703
                // } else {
704
                //         $this->nodes[$offset] = $value;
705
                // }
706 1
                throw new \Exception('Setting a context element is not allowed.');
707
        }
708
709
        // \ArrayAccess interface.
710 1
        public function offsetExists($offset)
711
        {
712 1
                return isset($this->nodes[$offset]);
713
        }
714
715
        // \ArrayAccess interface.
716 1
        public function offsetUnset($offset)
717
        {
718
                // unset($this->nodes[$offset]);
719 1
                \array_splice($this->nodes, $offset, 1);
720 1
        }
721
722
        // \ArrayAccess interface.
723 1
        public function offsetGet($offset)
724
        {
725 1
                if (isset($this->nodes[$offset])) {
726 1
                        return $this->nodes[$offset];
727
                }
728
729 1
                return null;
730
        }
731
732
        // \Iterator interface.
733 1
        public function rewind()
734
        {
735 1
                $this->seek = 0;
736 1
        }
737
738
        // \Iterator interface.
739 1
        public function current()
740
        {
741 1
                return $this->nodes[$this->seek];
742
        }
743
744
        // \Iterator interface.
745 1
        public function key()
746
        {
747 1
                return $this->seek;
748
        }
749
750
        // \Iterator interface.
751 1
        public function next()
752
        {
753 1
                ++$this->seek;
754 1
        }
755
756
        // \Iterator interface.
757 1
        public function valid()
758
        {
759 1
                return isset($this->nodes[$this->seek]);
760
        }
761
762 1
        public function length()
763
        {
764 1
                return \count($this->nodes);
765
        }
766
767 1
        public function query(...$xpath)
768
        {
769 1
                $xpaths = $xpath;
770
771 1
                if (\is_array($xpath[0])) {
772 1
                        $xpaths = $xpath[0];
773
                }
774
775 1
                $results = [];
776
777 1
                foreach ($this->nodes as $n) {
778 1
                        foreach ($xpaths as $x) {
779
                                // Returns a DOMNodeList.
780 1
                                $res = $this->document->xpath->query($x, $n);
781
782
                                // Algorithm 1:
783 1
                                $results = \array_merge($results, \iterator_to_array($res));
784
785
                                // Algorithm 2:
786
                                // foreach ($res as $r) {
787
                                //         $results[] = $r;
788
                                // }
789
790
                                // Algorithm 3:
791
                                // for ($i = 0, $l = $res->length; $i < $l; ++$i) {
792
                                //         $results[] = $res->item($i);
793
                                // }
794
                        }
795
                }
796
797
                // Performing over multiple sibling nodes a query that ascends
798
                // the xpath, relative (../..) or absolute (//), returns identical
799
                // matching results that must be collapsed in an unique result
800
                // otherwise a subsequent operation is performed multiple times.
801 1
                $unique_results = [];
802 1
                foreach ($results as $r) {
803 1
                        $found = false;
804
805 1
                        foreach ($unique_results as $u) {
806 1
                                if ($r === $u) {
807 1
                                        $found = true;
808
                                }
809
                        }
810
811 1
                        if (! $found) {
812 1
                                $unique_results[] = $r;
813
                        }
814
                }
815
816 1
                return $this->newContext($unique_results);
817
        }
818
819 1
        public function times($times, callable $fn = null)
820
        {
821 1
                if ($fn === null) {
822 1
                        return new FluidRepeater($this->document, $this, $times);
823
                }
824
825 1
                for ($i = 0; $i < $times; ++$i) {
826 1
                        $args = [$this, $i];
827
828 1
                        if ($fn instanceof \Closure) {
829 1
                                $fn = $fn->bindTo($this);
830
831 1
                                \array_shift($args);
832
                        }
833
834 1
                        \call_user_func($fn, ...$args);
835
                }
836
837 1
                return $this;
838
        }
839
840 1
        public function each(callable $fn)
841
        {
842 1
                foreach ($this->nodes as $i => $n) {
843 1
                        $cx   = $this->newContext($n);
844 1
                        $args = [$cx, $i, $n];
845
846 1
                        if ($fn instanceof \Closure) {
847 1
                                $fn = $fn->bindTo($cx);
848
849 1
                                \array_shift($args);
850
                        }
851
852 1
                        \call_user_func($fn, ...$args);
853
                }
854
855 1
                return $this;
856
        }
857
858
        // appendChild($child, $value?, $attributes? = [], $switchContext? = false)
859 1
        public function appendChild($child, ...$optionals)
860
        {
861
                return $this->insertElement($child, $optionals, function($parent, $element) {
862 1
                        return $parent->appendChild($element);
863 1
                });
864
        }
865
866
        // Alias of appendChild().
867 1
        public function add($child, ...$optionals)
868
        {
869 1
                return $this->appendChild($child, ...$optionals);
870
        }
871
872 1
        public function prependSibling($sibling, ...$optionals)
873
        {
874
                return $this->insertElement($sibling, $optionals, function($sibling, $element) {
875 1
                        return $sibling->parentNode->insertBefore($element, $sibling);
876 1
                });
877
        }
878
879
        // Alias of prependSibling().
880 1
        public function prepend($sibling, ...$optionals)
881
        {
882 1
                return $this->prependSibling($sibling, ...$optionals);
883
        }
884
885
        // Alias of prependSibling().
886 1
        public function insertSiblingBefore($sibling, ...$optionals)
887
        {
888 1
                return $this->prependSibling($sibling, ...$optionals);
889
        }
890
891
        public function appendSibling($sibling, ...$optionals)
892
        {
893 1
                return $this->insertElement($sibling, $optionals, function($sibling, $element) {
894
                        // If ->nextSibling is null, $element is simply appended as last sibling.
895 1
                        return $sibling->parentNode->insertBefore($element, $sibling->nextSibling);
896 1
                });
897
        }
898
899
        // Alias of appendSibling().
900 1
        public function append($sibling, ...$optionals)
901
        {
902 1
                return $this->appendSibling($sibling, ...$optionals);
903
        }
904
905
        // Alias of appendSibling().
906 1
        public function insertSiblingAfter($sibling, ...$optionals)
907
        {
908 1
                return $this->appendSibling($sibling, ...$optionals);
909
        }
910
911
        // Arguments can be in the form of:
912
        // setAttribute($name, $value)
913
        // setAttribute(['name' => 'value', ...])
914 1
        public function setAttribute(...$arguments)
915
        {
916
                // Default case is:
917
                // [ 'name' => 'value', ... ]
918 1
                $attrs = $arguments[0];
919
920
                // If the first argument is not an array,
921
                // the user has passed two arguments:
922
                // 1. is the attribute name
923
                // 2. is the attribute value
924 1
                if (! \is_array($arguments[0])) {
925 1
                        $attrs = [$arguments[0] => $arguments[1]];
926
                }
927
928 1
                foreach ($this->nodes as $n) {
929 1
                        foreach ($attrs as $k => $v) {
930
                                // Algorithm 1:
931 1
                                $n->setAttribute($k, $v);
932
933
                                // Algorithm 2:
934
                                // $n->setAttributeNode(new \DOMAttr($k, $v));
935
936
                                // Algorithm 3:
937
                                // $n->appendChild(new \DOMAttr($k, $v));
938
939
                                // Algorithm 2 and 3 have a different behaviour
940
                                // from Algorithm 1.
941
                                // The attribute is still created or setted, but
942
                                // changing the value of an existing attribute
943
                                // changes even the order of that attribute
944
                                // in the attribute list.
945
                        }
946
                }
947
948 1
                return $this;
949
        }
950
951
        // Alias of setAttribute().
952 1
        public function attr(...$arguments)
953
        {
954 1
                return $this->setAttribute(...$arguments);
955
        }
956
957 1
        public function appendText($text)
958
        {
959 1
                foreach ($this->nodes as $n) {
960 1
                        $n->appendChild(new \DOMText($text));
961
                }
962
963 1
                return $this;
964
        }
965
966 1
        public function appendCdata($text)
967
        {
968 1
                foreach ($this->nodes as $n) {
969 1
                        $n->appendChild(new \DOMCDATASection($text));
970
                }
971
972 1
                return $this;
973
        }
974
975 1
        public function setText($text)
976
        {
977 1
                foreach ($this->nodes as $n) {
978
                        // Algorithm 1:
979 1
                        $n->nodeValue = $text;
980
981
                        // Algorithm 2:
982
                        // foreach ($n->childNodes as $c) {
983
                        //         $n->removeChild($c);
984
                        // }
985
                        // $n->appendChild(new \DOMText($text));
986
987
                        // Algorithm 3:
988
                        // foreach ($n->childNodes as $c) {
989
                        //         $n->replaceChild(new \DOMText($text), $c);
990
                        // }
991
                }
992
993 1
                return $this;
994
        }
995
996
        // Alias of setText().
997 1
        public function text($text)
998
        {
999 1
                return $this->setText($text);
1000
        }
1001
1002 1
        public function setCdata($text)
1003
        {
1004 1
                foreach ($this->nodes as $n) {
1005 1
                        $n->nodeValue = '';
1006 1
                        $n->appendChild(new \DOMCDATASection($text));
1007
                }
1008
1009 1
                return $this;
1010
        }
1011
1012
        // Alias of setCdata().
1013 1
        public function cdata($text)
1014
        {
1015 1
                return $this->setCdata($text);
1016
        }
1017
1018 1
        public function remove(...$xpath)
1019
        {
1020
                // Arguments can be empty, a string or an array of strings.
1021
1022 1
                if (empty($xpath)) {
1023
                        // The user has requested to remove the nodes of this context.
1024 1
                        $targets = $this->nodes;
1025
                } else {
1026 1
                        $targets = $this->query(...$xpath);
1027
                }
1028
1029 1
                foreach ($targets as $t) {
1030 1
                        $t->parentNode->removeChild($t);
1031
                }
1032
1033 1
                return $this;
1034
        }
1035
1036 1
        public function xml($strip = false)
1037
        {
1038 1
                return domnodes_to_string($this->nodes);
1039
        }
1040
1041 1
        protected function newContext($context)
1042
        {
1043 1
                return new FluidContext($this->document, $context);
1044
        }
1045
1046 1
        protected function handleOptionals($element, &$optionals)
1047
        {
1048 1
                if (! \is_array($element)) {
1049 1
                        $element = [ $element ];
1050
                }
1051
1052 1
                $switch_context = false;
1053 1
                $attributes     = [];
1054
1055 1
                foreach ($optionals as $opt) {
1056 1
                        if (\is_array($opt)) {
1057 1
                                $attributes = $opt;
1058
1059 1
                        } else if (\is_bool($opt)) {
1060 1
                                $switch_context = $opt;
1061
1062 1
                        } else if (\is_string($opt)) {
1063 1
                                $e = \array_pop($element);
1064
1065 1
                                $element[$e] = $opt;
1066
1067
                        } else {
1068 1
                                throw new \Exception("Optional argument '$opt' not recognized.");
1069
                        }
1070
                }
1071
1072 1
                return [ $element, $attributes, $switch_context ];
1073
        }
1074
1075 1
        protected function insertElement($element, &$optionals, $fn)
1076
        {
1077 1
                list($element, $attributes, $switch_context) = $this->handleOptionals($element, $optionals);
1078
1079 1
                $nodes = [];
1080
1081 1
                foreach ($this->nodes as $n) {
1082 1
                        foreach ($element as $k => $v) {
1083
                                // I give up, it's a too complex job for only one method like me.
1084 1
                                $cx    = $this->handleInsertion($n, $k, $v, $fn, $optionals);
1085
1086 1
                                $nodes = \array_merge($nodes, $cx);
1087
                        }
1088
                }
1089
1090 1
                $new_context = $this->newContext($nodes);
1091
1092
                // Setting the attributes is an help that the appendChild method
1093
                // offers to the user and is the same of:
1094
                // 1. appending a child switching the context
1095
                // 2. setting the attributes over the new context.
1096 1
                if (! empty($attributes)) {
1097 1
                        $new_context->setAttribute($attributes);
1098
                }
1099
1100 1
                if ($switch_context) {
1101 1
                        return $new_context;
1102
                }
1103
1104 1
                return $this;
1105
        }
1106
1107 1
        protected function handleInsertion($parent, $k, $v, $fn, &$optionals)
1108
        {
1109
                // This is an highly optimized method.
1110
                // Good code design would split this method in many different handlers
1111
                // each one with its own checks. But it is too much expensive in terms
1112
                // of performances for a core method like this, so this implementation
1113
                // is prefered to collapse many identical checks to one.
1114
1115
                //////////////////////
1116
                // Key is a string. //
1117
                //////////////////////
1118
1119
                ///////////////////////////////////////////////////////
1120 1
                $k_is_string    = \is_string($k);
1121 1
                $v_is_string    = \is_string($v);
1122 1
                $v_is_xml       = $v_is_string && is_an_xml_string($v);
1123 1
                $k_is_special   = $k_is_string && $k[0] === '@';
1124 1
                $k_isnt_special = ! $k_is_special;
1125 1
                $v_isnt_string  = ! $v_is_string;
1126 1
                $v_isnt_xml     = ! $v_is_xml;
1127
                ///////////////////////////////////////////////////////
1128
1129 1
                if ($k_is_string && $k_isnt_special && $v_is_string && $v_isnt_xml) {
1130 1
                        return $this->insertStringString($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertStringString() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1131
                }
1132
1133 1
                if ($k_is_string && $k_isnt_special && $v_is_string && $v_is_xml) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1134
                        // TODO
1135
                }
1136
1137
                //////////////////////////////////////////////
1138 1
                $k_is_special_c = $k_is_special && $k === '@';
1139
                //////////////////////////////////////////////
1140
1141 1
                if ($k_is_special_c && $v_is_string) {
1142 1
                        return $this->insertSpecialContent($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertSpecialContent() has too many arguments starting with $fn.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1143
                }
1144
1145
                /////////////////////////////////////////////////////
1146 1
                $k_is_special_a = $k_is_special && ! $k_is_special_c;
1147
                /////////////////////////////////////////////////////
1148
1149 1
                if ($k_is_special_a && $v_is_string) {
1150 1
                        return $this->insertSpecialAttribute($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertSpecialAttribute() has too many arguments starting with $fn.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1151
                }
1152
1153 1
                if ($k_is_string && $v_isnt_string) {
1154 1
                        return $this->insertStringMixed($parent, $k, $v, $fn, $optionals);
1155
                }
1156
1157
                ////////////////////////
1158
                // Key is an integer. //
1159
                ////////////////////////
1160
1161
                ////////////////////////////////
1162 1
                $k_is_integer = \is_integer($k);
1163 1
                $v_is_array   = \is_array($v);
1164
                ////////////////////////////////
1165
1166 1
                if ($k_is_integer && $v_is_array) {
1167 1
                        return $this->insertIntegerArray($parent, $k, $v, $fn, $optionals);
1168
                }
1169
1170 1
                if ($k_is_integer && $v_is_string && $v_isnt_xml) {
1171 1
                        return $this->insertIntegerString($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerString() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1172
                }
1173
1174 1
                if ($k_is_integer && $v_is_string && $v_is_xml) {
1175 1
                        return $this->insertIntegerXml($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerXml() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1176
                }
1177
1178
                //////////////////////////////////////////
1179 1
                $v_is_domdoc = $v instanceof \DOMDocument;
1180
                //////////////////////////////////////////
1181
1182 1
                if ($k_is_integer && $v_is_domdoc) {
1183 1
                        return $this->insertIntegerDomdocument($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerDomdocument() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1184
                }
1185
1186
                ///////////////////////////////////////////////
1187 1
                $v_is_domnodelist = $v instanceof \DOMNodeList;
1188
                ///////////////////////////////////////////////
1189
1190 1
                if ($k_is_integer && $v_is_domnodelist) {
1191 1
                        return $this->insertIntegerDomnodelist($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerDomnodelist() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1192
                }
1193
1194
                ///////////////////////////////////////
1195 1
                $v_is_domnode = $v instanceof \DOMNode;
1196
                ///////////////////////////////////////
1197
1198 1
                if ($k_is_integer && ! $v_is_domdoc && $v_is_domnode) {
1199 1
                        return $this->insertIntegerDomnode($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerDomnode() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1200
                }
1201
1202
                //////////////////////////////////////////////////
1203 1
                $v_is_simplexml = $v instanceof \SimpleXMLElement;
1204
                //////////////////////////////////////////////////
1205
1206 1
                if ($k_is_integer && $v_is_simplexml) {
1207 1
                        return $this->insertIntegerSimplexml($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerSimplexml() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1208
                }
1209
1210
                ////////////////////////////////////////
1211 1
                $v_is_fluidxml = $v instanceof FluidXml;
1212
                ////////////////////////////////////////
1213
1214 1
                if ($k_is_integer && $v_is_fluidxml) {
1215 1
                        return $this->insertIntegerFluidxml($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerFluidxml() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1216
                }
1217
1218
                ///////////////////////////////////////////
1219 1
                $v_is_fluidcx = $v instanceof FluidContext;
1220
                ///////////////////////////////////////////
1221
1222 1
                if ($k_is_integer && $v_is_fluidcx) {
1223 1
                        return $this->insertIntegerFluidcontext($parent, $k, $v, $fn, $optionals);
1 ignored issue
show
Unused Code introduced by
The call to FluidContext::insertIntegerFluidcontext() has too many arguments starting with $optionals.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1224
                }
1225
1226 1
                throw new \Exception('XML document not supported.');
1227
        }
1228
1229 1
        protected function createElement($name, $value = null)
1230
        {
1231
                // The DOMElement instance must be different for every node,
1232
                // otherwise only one element is attached to the DOM.
1233
1234 1
                $id  = null;
1235 1
                $uri = null;
1236
1237
                // The node name can contain the namespace id prefix.
1238
                // Example: xsl:template
1239 1
                $colon_pos = \strpos($name, ':');
1240
1241 1
                if ($colon_pos !== false) {
1242 1
                        $id   = \substr($name, 0, $colon_pos);
1243 1
                        $name = \substr($name, $colon_pos + 1);
1244
                }
1245
1246 1
                if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1247 1
                        $ns  = $this->document->namespaces[$id];
1248 1
                        $uri = $ns->uri();
1249
1250 1
                        if ($ns->mode() === FluidNamespace::MODE_EXPLICIT) {
1251 1
                                $name = "{$id}:{$name}";
1252
                        }
1253
                }
1254
1255
                // Algorithm 1:
1256 1
                $el = new \DOMElement($name, $value, $uri);
1257
1258
                // Algorithm 2:
1259
                // $el = $dom->createElement($name, $value);
1260
1261 1
                return $el;
1262
        }
1263
1264 1
        protected function attachNodes($parent, $nodes, $fn)
1265
        {
1266 1
                if (! \is_array($nodes) && ! $nodes instanceof \Traversable) {
1267 1
                        $nodes = [ $nodes ];
1268
                }
1269
1270 1
                $context = [];
1271
1272 1
                foreach ($nodes as $el) {
1273 1
                        $el        = $this->document->dom->importNode($el, true);
1274 1
                        $context[] = $fn( $parent, $el);
1275
                }
1276
1277 1
                return $context;
1278
        }
1279
1280 1
        protected function insertSpecialContent($parent, $k, $v)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1281
        {
1282
                // The user has passed an element text content:
1283
                // [ '@' => 'Element content.' ]
1284
1285
                // Algorithm 1:
1286 1
                $this->newContext($parent)->appendText($v);
1287
1288
                // Algorithm 2:
1289
                // $this->setText($v);
1290
1291
                // The user can specify multiple '@' special elements
1292
                // so Algorithm 1 is the right choice.
1293
1294 1
                return [];
1295
        }
1296
1297 1
        protected function insertSpecialAttribute($parent, $k, $v)
1298
        {
1299
                // The user has passed an attribute name and an attribute value:
1300
                // [ '@attribute' => 'Attribute content' ]
1301
1302 1
                $attr = \substr($k, 1);
1303 1
                $this->newContext($parent)->setAttribute($attr, $v);
1304
1305 1
                return [];
1306
        }
1307
1308 1 View Code Duplication
        protected function insertStringString($parent, $k, $v, $fn)
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...
1309
        {
1310
                // The user has passed an element name and an element value:
1311
                // [ 'element' => 'Element content' ]
1312
1313 1
                $el = $this->createElement($k, $v);
1314 1
                $el = $fn($parent, $el);
1315
1316 1
                return [ $el ];
1317
        }
1318
1319 1 View Code Duplication
        protected function insertStringMixed($parent, $k, $v, $fn, &$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...
1320
        {
1321
                // The user has passed one of these two cases:
1322
                // - [ 'element' => [...] ]
1323
                // - [ 'element' => DOMNode|SimpleXMLElement|FluidXml ]
1324
1325 1
                $el = $this->createElement($k);
1326 1
                $el = $fn($parent, $el);
1327
1328
                // The new children elements must be created in the order
1329
                // they are supplied, so 'appendChild' is the perfect operation.
1330 1
                $this->newContext($el)->appendChild($v, ...$optionals);
1331
1332 1
                return [ $el ];
1333
        }
1334
1335 1
        protected function insertIntegerArray($parent, $k, $v, $fn, &$optionals)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1336
        {
1337
                // The user has passed a wrapper array:
1338
                // [ [...], ... ]
1339
1340 1
                $context = [];
1341
1342 1
                foreach ($v as $kk => $vv) {
1343 1
                        $cx = $this->handleInsertion($parent, $kk, $vv, $fn, $optionals);
1344
1345 1
                        $context = \array_merge($context, $cx);
1346
                }
1347
1348 1
                return $context;
1349
        }
1350
1351 1
        protected function insertIntegerString($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1352
        {
1353
                // The user has passed a node name without a node value:
1354
                // [ 'element', ... ]
1355
1356 1
                $el = $this->createElement($v);
1357 1
                $el = $fn($parent, $el);
1358
1359 1
                return [ $el ];
1360
        }
1361
1362 1
        protected function insertIntegerXml($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1363
        {
1364
                // The user has passed an XML document instance:
1365
                // [ '<tag></tag>', DOMNode, SimpleXMLElement, FluidXml ]
1366
1367 1
                $wrapper = new \DOMDocument();
1368 1
                $wrapper->formatOutput       = true;
1369 1
                $wrapper->preserveWhiteSpace = false;
1370
1371 1
                $v = \ltrim($v);
1372
1373 1
                if ($v[1] === '?') {
1374 1
                        $wrapper->loadXML($v);
1375 1
                        $nodes = $wrapper->childNodes;
1376
                } else {
1377
                        // A way to import strings with multiple root nodes.
1378 1
                        $wrapper->loadXML("<root>$v</root>");
1379
1380
                        // Algorithm 1:
1381 1
                        $nodes = $wrapper->documentElement->childNodes;
1382
1383
                        // Algorithm 2:
1384
                        // $dom_xp = new \DOMXPath($dom);
1385
                        // $nodes = $dom_xp->query('/root/*');
1386
                }
1387
1388 1
                return $this->attachNodes($parent, $nodes, $fn);
1389
        }
1390
1391 1
        protected function insertIntegerDomdocument($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1392
        {
1393
                // A DOMDocument can have multiple root nodes.
1394
1395
                // Algorithm 1:
1396 1
                return $this->attachNodes($parent, $v->childNodes, $fn);
1397
1398
                // Algorithm 2:
1399
                // return $this->attachNodes($parent, $v->documentElement, $fn);
1400
        }
1401
1402 1
        protected function insertIntegerDomnodelist($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1403
        {
1404 1
                return $this->attachNodes($parent, $v, $fn);
1405
        }
1406
1407 1
        protected function insertIntegerDomnode($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1408
        {
1409 1
                return $this->attachNodes($parent, $v, $fn);
1410
        }
1411
1412 1
        protected function insertIntegerSimplexml($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1413
        {
1414 1
                return $this->attachNodes($parent, \dom_import_simplexml($v), $fn);
1415
        }
1416
1417 1
        protected function insertIntegerFluidxml($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1418
        {
1419 1
                return $this->attachNodes($parent, $v->dom()->documentElement, $fn);
1420
        }
1421
1422 1
        protected function insertIntegerFluidcontext($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1423
        {
1424 1
                return $this->attachNodes($parent, $v->asArray(), $fn);
1425
        }
1426
}
1427
1428
} // END OF NAMESPACE FluidXml\Core
1429