Completed
Push — master ( 1f5a99...3ca83b )
by Daniele
03:04
created

FluidContext::query()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 40
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5
Metric Value
dl 0
loc 40
ccs 12
cts 12
cp 1
rs 8.439
cc 5
eloc 12
nc 8
nop 1
crap 5
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\FluidInsertionHandler;
47
use \FluidXml\Core\FluidContext;
48
use \FluidXml\Core\NewableTrait;
49
use \FluidXml\Core\ReservedCallTrait;
50
use \FluidXml\Core\ReservedCallStaticTrait;
51
52
/**
53
 * Constructs a new FluidXml instance.
54
 *
55
 * ```php
56
 * $xml = fluidxml();
57
 * // is the same of
58
 * $xml = new FluidXml();
59
 *
60
 * $xml = fluidxml([
61
 *
62
 *   'root'       => 'doc',
63
 *
64
 *   'version'    => '1.0',
65
 *
66
 *   'encoding'   => 'UTF-8',
67
 *
68
 *   'stylesheet' => null ]);
69
 * ```
70
 *
71
 * @param array $arguments Options that influence the construction of the XML document.
72
 *
73
 * @return FluidXml A new FluidXml instance.
74
 */
75
function fluidify(...$arguments)
76
{
77 1
        return FluidXml::load(...$arguments);
78
}
79
80
function fluidxml(...$arguments)
81
{
82 1
        return new FluidXml(...$arguments);
83
}
84
85
function fluidns(...$arguments)
86
{
87 1
        return new FluidNamespace(...$arguments);
88
}
89
90
function is_an_xml_string($string)
91
{
92
        // Removes any empty new line at the beginning,
93
        // otherwise the first character check may fail.
94 1
        $string = \ltrim($string);
95
96 1
        return $string[0] === '<';
97
}
98
99
function domdocument_to_string_without_headers(\DOMDocument $dom)
100
{
101 1
        return $dom->saveXML($dom->documentElement);
102
}
103
104
function domnodelist_to_string(\DOMNodeList $nodelist)
105
{
106 1
        $nodes = [];
107
108 1
        foreach ($nodelist as $n) {
109 1
                $nodes[] = $n;
110
        }
111
112 1
        return domnodes_to_string($nodes);
113
}
114
115
function domnodes_to_string(array $nodes)
116
{
117 1
        $dom = $nodes[0]->ownerDocument;
118 1
        $xml = '';
119
120 1
        foreach ($nodes as $n) {
121 1
                $xml .= $dom->saveXML($n) . PHP_EOL;
122
        }
123
124 1
        return \rtrim($xml);
125
}
126
127
function simplexml_to_string_without_headers(\SimpleXMLElement $element)
128
{
129 1
        $dom = \dom_import_simplexml($element);
130
131 1
        return $dom->ownerDocument->saveXML($dom);
132
}
133
134
/**
135
 * @method FluidXml namespace(...$arguments)
136
 */
137
class FluidXml implements FluidInterface
138
{
139
        use NewableTrait,
140
            ReservedCallTrait,          // For compatibility with PHP 5.6.
141
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
142
143
        const ROOT_NODE = 'doc';
144
145
        private $document;
146
        private $handler;
147
148 1
        public static function load($document)
149
        {
150 1
                if (\is_string($document) && ! is_an_xml_string($document)) {
151
                        // Removes any empty new line at the beginning,
152
                        // otherwise the first character check fails.
153
154 1
                        $file        = $document;
155 1
                        $is_file     = \is_file($file);
156 1
                        $is_readable = \is_readable($file);
157
158 1
                        if ($is_file && $is_readable) {
159 1
                                $document = \file_get_contents($file);
160
                        }
161
162 1
                        if (! $is_file || ! $is_readable || ! $document) {
163 1
                                throw new \Exception("File '$file' not accessible.");
164
                        }
165
                }
166
167 1
                return (new FluidXml(['root' => null]))->appendChild($document);
168
        }
169
170 1
        public function __construct($root = null, $options = [])
171
        {
172 1
                $defaults = [ 'root'       => self::ROOT_NODE,
173 1
                              'version'    => '1.0',
174 1
                              'encoding'   => 'UTF-8',
175
                              'stylesheet' => null ];
176
177 1
                if (\is_string($root)) {
178
                        // The root option can be specified as first argument
179
                        // because it is the most common.
180 1
                        $defaults['root'] = $root;
181 1
                } else if (\is_array($root)) {
182
                        // If the first argument is an array, the user has skipped
183
                        // the root option and is passing a bunch of options all together.
184 1
                        $options = $root;
185
                }
186
187 1
                $opts = \array_merge($defaults, $options);
188
189 1
                $this->document = new FluidDocument();
190 1
                $doc            = $this->document;
191
192 1
                $doc->dom = new \DOMDocument($opts['version'], $opts['encoding']);
193 1
                $doc->dom->formatOutput       = true;
194 1
                $doc->dom->preserveWhiteSpace = false;
195
196 1
                $doc->xpath    = new \DOMXPath($doc->dom);
197
198 1
                $this->handler = new FluidInsertionHandler($doc);
199
200 1
                if (! empty($opts['root'])) {
201 1
                        $this->appendSibling($opts['root']);
202
                }
203
204 1
                if (! empty($opts['stylesheet'])) {
205
                        $attrs = 'type="text/xsl" '
206 1
                               . "encoding=\"{$opts['encoding']}\" "
207 1
                               . 'indent="yes" '
208 1
                               . "href=\"{$opts['stylesheet']}\"";
209 1
                        $stylesheet = new \DOMProcessingInstruction('xml-stylesheet', $attrs);
210
211 1
                        $doc->dom->insertBefore($stylesheet, $doc->dom->documentElement);
212
                }
213 1
        }
214
215 1
        public function xml($strip = false)
216
        {
217 1
                if ($strip) {
218 1
                        return domdocument_to_string_without_headers($this->document->dom);
219
                }
220
221 1
                return $this->document->dom->saveXML();
222
        }
223
224 1
        public function dom()
225
        {
226 1
                return $this->document->dom;
227
        }
228
229 1
        public function namespaces()
230
        {
231 1
                return $this->document->namespaces;
232
        }
233
234
        // This method should be called 'namespace',
235
        // but for compatibility with PHP 5.6
236
        // it is shadowed by the __call() method.
237 1
        protected function namespace_(...$arguments)
238
        {
239 1
                $namespaces = [];
240
241 1
                if (\is_string($arguments[0])) {
242 1
                        $args = [ $arguments[0], $arguments[1] ];
243
244 1
                        if (isset($arguments[2])) {
245 1
                                $args[] = $arguments[2];
246
                        }
247
248 1
                        $namespaces[] = new FluidNamespace(...$args);
249 1
                } else if (\is_array($arguments[0])) {
250 1
                        $namespaces = $arguments[0];
251
                } else {
252 1
                        $namespaces = $arguments;
253
                }
254
255 1
                foreach ($namespaces as $n) {
256 1
                        $this->document->namespaces[$n->id()] = $n;
257 1
                        $this->document->xpath->registerNamespace($n->id(), $n->uri());
258
                }
259
260 1
                return $this;
261
        }
262
263 1
        public function query(...$xpath)
264
        {
265 1
                return $this->context()->query(...$xpath);
266
        }
267
268 1
        public function times($times, callable $fn = null)
269
        {
270 1
                return $this->context()->times($times, $fn);
271
        }
272
273 1
        public function each(callable $fn)
274
        {
275 1
                return $this->context()->each($fn);
276
        }
277
278 1
        public function appendChild($child, ...$optionals)
279
        {
280
                // If the user has requested ['root' => null] at construction time
281
                // 'context()' promotes DOMDocument as root node.
282 1
                $context     = $this->context();
283 1
                $new_context = $context->appendChild($child, ...$optionals);
284
285 1
                return $this->chooseContext($context, $new_context);
286
        }
287
288
        // Alias of appendChild().
289 1
        public function add($child, ...$optionals)
290
        {
291 1
                return $this->appendChild($child, ...$optionals);
292
        }
293
294 1 View Code Duplication
        public function prependSibling($sibling, ...$optionals)
295
        {
296 1
                if ($this->document->dom->documentElement === null) {
297
                        // If the document doesn't have at least one root node,
298
                        // the sibling creation fails. In this case we replace
299
                        // the sibling creation with the creation of a generic node.
300 1
                        return $this->appendChild($sibling, ...$optionals);
301
                }
302
303 1
                $context     = $this->context();
304 1
                $new_context = $context->prependSibling($sibling, ...$optionals);
305
306 1
                return $this->chooseContext($context, $new_context);
307
        }
308
309
        // Alias of prependSibling().
310 1
        public function prepend($sibling, ...$optionals)
311
        {
312 1
                return $this->prependSibling($sibling, ...$optionals);
313
        }
314
315
        // Alias of prependSibling().
316 1
        public function insertSiblingBefore($sibling, ...$optionals)
317
        {
318 1
                return $this->prependSibling($sibling, ...$optionals);
319
        }
320
321 1 View Code Duplication
        public function appendSibling($sibling, ...$optionals)
322
        {
323 1
                if ($this->document->dom->documentElement === null) {
324
                        // If the document doesn't have at least one root node,
325
                        // the sibling creation fails. In this case we replace
326
                        // the sibling creation with the creation of a generic node.
327 1
                        return $this->appendChild($sibling, ...$optionals);
328
                }
329
330 1
                $context     = $this->context();
331 1
                $new_context = $context->appendSibling($sibling, ...$optionals);
332
333 1
                return $this->chooseContext($context, $new_context);
334
        }
335
336
        // Alias of appendSibling().
337 1
        public function append($sibling, ...$optionals)
338
        {
339 1
                return $this->appendSibling($sibling, ...$optionals);
340
        }
341
342
        // Alias of appendSibling().
343 1
        public function insertSiblingAfter($sibling, ...$optionals)
344
        {
345 1
                return $this->appendSibling($sibling, ...$optionals);
346
        }
347
348 1
        public function setAttribute(...$arguments)
349
        {
350 1
                $this->context()->setAttribute(...$arguments);
351
352 1
                return $this;
353
        }
354
355
        // Alias of setAttribute().
356 1
        public function attr(...$arguments)
357
        {
358 1
                return $this->setAttribute(...$arguments);
359
        }
360
361 1
        public function appendText($text)
362
        {
363 1
                $this->context()->appendText($text);
364
365 1
                return $this;
366
        }
367
368 1
        public function appendCdata($text)
369
        {
370 1
                $this->context()->appendCdata($text);
371
372 1
                return $this;
373
        }
374
375 1
        public function setText($text)
376
        {
377 1
                $this->context()->setText($text);
378
379 1
                return $this;
380
        }
381
382
        // Alias of setText().
383 1
        public function text($text)
384
        {
385 1
                return $this->setText($text);
386
        }
387
388 1
        public function setCdata($text)
389
        {
390 1
                $this->context()->setCdata($text);
391
392 1
                return $this;
393
        }
394
395
        // Alias of setCdata().
396 1
        public function cdata($text)
397
        {
398 1
                return $this->setCdata($text);
399
        }
400
401 1
        public function remove(...$xpath)
402
        {
403 1
                $this->context()->remove(...$xpath);
404
405 1
                return $this;
406
        }
407
408
        private $context;
409
        private $contextEl;
410
411 1
        protected function context()
412
        {
413 1
                if ($this->document->dom->documentElement === null) {
414
                        // If the user has requested ['root' => null] at construction time
415
                        // the 'documentElement' property is null because we have not created
416
                        // a root node yet. Whether there is not a root node, the DOMDocument
417
                        // is promoted as root node.
418 1
                        if ($this->context === null) {
419 1
                                $this->context = new FluidContext($this->document, $this->handler, $this->document->dom);
420
                        }
421
422 1
                        return $this->context;
423
                }
424
425 1
                if ($this->contextEl !== $this->document->dom->documentElement) {
426
                        // The user can prepend a root node to the current root node.
427
                        // In this case we have to update the context with the new first root node.
428 1
                        $this->context   = new FluidContext($this->document, $this->handler, $this->document->dom->documentElement);
429 1
                        $this->contextEl = $this->document->dom->documentElement;
430
                }
431
432 1
                return $this->context;
433
        }
434
435 1
        protected function chooseContext($help_context, $new_context)
436
        {
437
                // If the two contextes are diffent, the user has requested
438
                // a switch of the context and we have to return it.
439 1
                if ($help_context !== $new_context) {
440 1
                        return $new_context;
441
                }
442
443 1
                return $this;
444
        }
445
}
446
447
class FluidNamespace
448
{
449
        const ID   = 'id'  ;
450
        const URI  = 'uri' ;
451
        const MODE = 'mode';
452
453
        const MODE_IMPLICIT = 0;
454
        const MODE_EXPLICIT = 1;
455
456
        private $config = [ self::ID   => '',
457
                            self::URI  => '',
458
                            self::MODE => self::MODE_EXPLICIT ];
459
460 1
        public function __construct($id, $uri, $mode = 1)
461
        {
462 1
                if (\is_array($id)) {
463 1
                        $args = $id;
464 1
                        $id   = $args[self::ID];
465 1
                        $uri  = $args[self::URI];
466
467 1
                        if (isset($args[self::MODE])) {
468 1
                                $mode = $args[self::MODE];
469
                        }
470
                }
471
472 1
                $this->config[self::ID]   = $id;
473 1
                $this->config[self::URI]  = $uri;
474 1
                $this->config[self::MODE] = $mode;
475 1
        }
476
477 1
        public function id()
478
        {
479 1
                return $this->config[self::ID];
480
        }
481
482 1
        public function uri()
483
        {
484 1
                return $this->config[self::URI];
485
        }
486
487 1
        public function mode()
488
        {
489 1
                return $this->config[self::MODE];
490
        }
491
492 1
        public function querify($xpath)
493
        {
494 1
                $id = $this->id();
495
496 1
                if (! empty($id)) {
497 1
                        $id .= ':';
498
                }
499
500
                // An XPath query may not start with a slash ('/').
501
                // Relative queries are an example '../target".
502 1
                $new_xpath = '';
503
504 1
                $nodes = \explode('/', $xpath);
505
506 1
                foreach ($nodes as $node) {
507
                        // An XPath query may have multiple slashes ('/')
508
                        // example: //target
509 1
                        if ($node) {
510 1
                                $new_xpath .= "{$id}{$node}";
511
                        }
512
513 1
                        $new_xpath .= '/';
514
                }
515
516
                // Removes the last appended slash.
517 1
                return \substr($new_xpath, 0, -1);
518
        }
519
}
520
521
} // END OF NAMESPACE FluidXml
522
523
namespace FluidXml\Core
524
{
525
526
use \FluidXml\FluidXml;
527
use \FluidXml\FluidNamespace;
528
529
use function \FluidXml\is_an_xml_string;
530
use function \FluidXml\domnodes_to_string;
531
532
interface FluidInterface
533
{
534
        /**
535
         * Executes an XPath query.
536
         *
537
         * ```php
538
         * $xml = fluidxml();
539
540
         * $xml->query("/doc/book[@id='123']");
541
         *
542
         * // Relative queries are valid.
543
         * $xml->query("/doc")->query("book[@id='123']");
544
         * ```
545
         *
546
         * @param string $xpath The XPath to execute.
547
         *
548
         * @return FluidContext The context associated to the DOMNodeList.
549
         */
550
        public function query(...$xpath);
551
        public function times($times, callable $fn = null);
552
        public function each(callable $fn);
553
554
        /**
555
         * Append a new node as child of the current context.
556
         *
557
         * ```php
558
         * $xml = fluidxml();
559
560
         * $xml->appendChild('title', 'The Theory Of Everything');
561
         * $xml->appendChild([ 'author' => 'S. Hawking' ]);
562
         *
563
         * $xml->appendChild('chapters', true)->appendChild('chapter', ['id'=> 1]);
564
         *
565
         * ```
566
         *
567
         * @param string|array $child The child/children to add.
568
         * @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...
569
         * @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...
570
         *                            or the context of the created node.
571
         *
572
         * @return FluidContext The context associated to the DOMNodeList.
573
         */
574
        public function appendChild($child, ...$optionals);
575
        public function prependSibling($sibling, ...$optionals);
576
        public function appendSibling($sibling, ...$optionals);
577
        public function setAttribute(...$arguments);
578
        public function setText($text);
579
        public function appendText($text);
580
        public function setCdata($text);
581
        public function appendCdata($text);
582
        public function remove(...$xpath);
583
        public function xml($strip = false);
584
        // Aliases:
585
        public function add($child, ...$optionals);
586
        public function prepend($sibling, ...$optionals);
587
        public function insertSiblingBefore($sibling, ...$optionals);
588
        public function append($sibling, ...$optionals);
589
        public function insertSiblingAfter($sibling, ...$optionals);
590
        public function attr(...$arguments);
591
        public function text($text);
592
}
593
594
trait ReservedCallTrait
595
{
596 1
        public function __call($method, $arguments)
597
        {
598 1
                $m = "{$method}_";
599
600 1
                if (\method_exists($this, $m)) {
601 1
                        return $this->$m(...$arguments);
602
                }
603
604
                throw new \Exception("Method '$method' not found.");
605
        }
606
}
607
608
trait ReservedCallStaticTrait
609
{
610 1
        public static function __callStatic($method, $arguments)
611
        {
612 1
                $m = "{$method}_";
613
614 1
                if (\method_exists(static::class, $m)) {
615 1
                        return static::$m(...$arguments);
616
                }
617
618
                throw new \Exception("Method '$method' not found.");
619
        }
620
}
621
622
trait NewableTrait
623
{
624
        // This method should be called 'new',
625
        // but for compatibility with PHP 5.6
626
        // it is shadowed by the __callStatic() method.
627 1
        public static function new_(...$arguments)
628
        {
629 1
                return new static(...$arguments);
630
        }
631
}
632
633
class FluidDocument
634
{
635
        public $dom;
636
        public $xpath;
637
        public $namespaces = [];
638
        public $handler;
639
}
640
641
class FluidRepeater
642
{
643
        private $document;
644
        private $handler;
645
        private $context;
646
        private $times;
647
648 1
        public function __construct($document, $handler, $context, $times)
649
        {
650 1
                $this->document = $document;
651 1
                $this->handler  = $handler;
652 1
                $this->context  = $context;
653 1
                $this->times    = $times;
654 1
        }
655
656 1
        public function __call($method, $arguments)
657
        {
658 1
                $nodes = [];
659 1
                $new_context = $this->context;
660
661 1
                for ($i = 0, $l = $this->times; $i < $l; ++$i) {
662 1
                        $new_context = $this->context->$method(...$arguments);
663 1
                        $nodes       = \array_merge($nodes, $new_context->asArray());
664
                }
665
666 1
                if ($new_context !== $this->context) {
667 1
                        return new FluidContext($this->document, $this->handler, $nodes);
668
                }
669
670 1
                return $this->context;
671
        }
672
}
673
674
class FluidInsertionHandler
675
{
676
        private $document;
677
        private $dom;
678
        private $namespaces;
679
680 1
        public function __construct($document)
681
        {
682 1
                $this->document   = $document;
683 1
                $this->dom        = $document->dom;
684 1
                $this->namespaces =& $document->namespaces;
685 1
        }
686
687 1
        public function insertElement(&$nodes, $element, &$optionals, $fn, $orig_context)
688
        {
689 1
                list($element, $attributes, $switch_context) = $this->handleOptionals($element, $optionals);
690
691 1
                $new_nodes = [];
692
693 1
                foreach ($nodes as $n) {
694 1
                        foreach ($element as $k => $v) {
695
                                // I give up, it's a too complex job for only one method like me.
696 1
                                $cx        = $this->handleInsertion($n, $k, $v, $fn, $optionals);
697 1
                                $new_nodes = \array_merge($new_nodes, $cx);
698
                        }
699
                }
700
701 1
                $new_context = $this->newContext($new_nodes);
702
703
                // Setting the attributes is an help that the appendChild method
704
                // offers to the user and is the same of:
705
                // 1. appending a child switching the context
706
                // 2. setting the attributes over the new context.
707 1
                if (! empty($attributes)) {
708 1
                        $new_context->setAttribute($attributes);
709
                }
710
711 1
                if ($switch_context) {
712 1
                        return $new_context;
713
                }
714
715 1
                return $orig_context;
716
        }
717
718 1
        protected function newContext(&$context)
719
        {
720 1
                return new FluidContext($this->document, $this, $context);
721
        }
722
723 1
        protected function handleOptionals($element, &$optionals)
724
        {
725 1
                if (! \is_array($element)) {
726 1
                        $element = [ $element ];
727
                }
728
729 1
                $switch_context = false;
730 1
                $attributes     = [];
731
732 1
                foreach ($optionals as $opt) {
733 1
                        if (\is_array($opt)) {
734 1
                                $attributes = $opt;
735
736 1
                        } else if (\is_bool($opt)) {
737 1
                                $switch_context = $opt;
738
739 1
                        } else if (\is_string($opt)) {
740 1
                                $e = \array_pop($element);
741
742 1
                                $element[$e] = $opt;
743
744
                        } else {
745 1
                                throw new \Exception("Optional argument '$opt' not recognized.");
746
                        }
747
                }
748
749 1
                return [ $element, $attributes, $switch_context ];
750
        }
751
752
753 1
        protected function handleInsertion($parent, $k, $v, $fn, &$optionals)
754
        {
755
                // This is an highly optimized method.
756
                // Good code design would split this method in many different handlers
757
                // each one with its own checks. But it is too much expensive in terms
758
                // of performances for a core method like this, so this implementation
759
                // is prefered to collapse many identical checks to one.
760
761
                //////////////////////
762
                // Key is a string. //
763
                //////////////////////
764
765
                ///////////////////////////////////////////////////////
766 1
                $k_is_string    = \is_string($k);
767 1
                $v_is_string    = \is_string($v);
768 1
                $v_is_xml       = $v_is_string && is_an_xml_string($v);
769 1
                $k_is_special   = $k_is_string && $k[0] === '@';
770 1
                $k_isnt_special = ! $k_is_special;
771 1
                $v_isnt_string  = ! $v_is_string;
772 1
                $v_isnt_xml     = ! $v_is_xml;
773
                ///////////////////////////////////////////////////////
774
775 1
                if ($k_is_string && $k_isnt_special && $v_is_string && $v_isnt_xml) {
776 1
                        return $this->insertStringString($parent, $k, $v, $fn, $optionals);
777
                }
778
779 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...
780
                        // TODO
781
                }
782
783
                //////////////////////////////////////////////
784 1
                $k_is_special_c = $k_is_special && $k === '@';
785
                //////////////////////////////////////////////
786
787 1
                if ($k_is_special_c && $v_is_string) {
788 1
                        return $this->insertSpecialContent($parent, $k, $v, $fn, $optionals);
789
                }
790
791
                /////////////////////////////////////////////////////
792 1
                $k_is_special_a = $k_is_special && ! $k_is_special_c;
793
                /////////////////////////////////////////////////////
794
795 1
                if ($k_is_special_a && $v_is_string) {
796 1
                        return $this->insertSpecialAttribute($parent, $k, $v, $fn, $optionals);
797
                }
798
799 1
                if ($k_is_string && $v_isnt_string) {
800 1
                        return $this->insertStringMixed($parent, $k, $v, $fn, $optionals);
801
                }
802
803
                ////////////////////////
804
                // Key is an integer. //
805
                ////////////////////////
806
807
                ////////////////////////////////
808 1
                $k_is_integer = \is_integer($k);
809 1
                $v_is_array   = \is_array($v);
810
                ////////////////////////////////
811
812 1
                if ($k_is_integer && $v_is_array) {
813 1
                        return $this->insertIntegerArray($parent, $k, $v, $fn, $optionals);
814
                }
815
816 1
                if ($k_is_integer && $v_is_string && $v_isnt_xml) {
817 1
                        return $this->insertIntegerString($parent, $k, $v, $fn, $optionals);
818
                }
819
820 1
                if ($k_is_integer && $v_is_string && $v_is_xml) {
821 1
                        return $this->insertIntegerXml($parent, $k, $v, $fn, $optionals);
822
                }
823
824
                //////////////////////////////////////////
825 1
                $v_is_domdoc = $v instanceof \DOMDocument;
826
                //////////////////////////////////////////
827
828 1
                if ($k_is_integer && $v_is_domdoc) {
829 1
                        return $this->insertIntegerDomdocument($parent, $k, $v, $fn, $optionals);
830
                }
831
832
                ///////////////////////////////////////////////
833 1
                $v_is_domnodelist = $v instanceof \DOMNodeList;
834
                ///////////////////////////////////////////////
835
836 1
                if ($k_is_integer && $v_is_domnodelist) {
837 1
                        return $this->insertIntegerDomnodelist($parent, $k, $v, $fn, $optionals);
838
                }
839
840
                ///////////////////////////////////////
841 1
                $v_is_domnode = $v instanceof \DOMNode;
842
                ///////////////////////////////////////
843
844 1
                if ($k_is_integer && ! $v_is_domdoc && $v_is_domnode) {
845 1
                        return $this->insertIntegerDomnode($parent, $k, $v, $fn, $optionals);
846
                }
847
848
                //////////////////////////////////////////////////
849 1
                $v_is_simplexml = $v instanceof \SimpleXMLElement;
850
                //////////////////////////////////////////////////
851
852 1
                if ($k_is_integer && $v_is_simplexml) {
853 1
                        return $this->insertIntegerSimplexml($parent, $k, $v, $fn, $optionals);
854
                }
855
856
                ////////////////////////////////////////
857 1
                $v_is_fluidxml = $v instanceof FluidXml;
858
                ////////////////////////////////////////
859
860 1
                if ($k_is_integer && $v_is_fluidxml) {
861 1
                        return $this->insertIntegerFluidxml($parent, $k, $v, $fn, $optionals);
862
                }
863
864
                ///////////////////////////////////////////
865 1
                $v_is_fluidcx = $v instanceof FluidContext;
866
                ///////////////////////////////////////////
867
868 1
                if ($k_is_integer && $v_is_fluidcx) {
869 1
                        return $this->insertIntegerFluidcontext($parent, $k, $v, $fn, $optionals);
870
                }
871
872 1
                throw new \Exception('XML document not supported.');
873
        }
874
875 1
        protected function createElement($name, $value = null)
876
        {
877
                // The DOMElement instance must be different for every node,
878
                // otherwise only one element is attached to the DOM.
879
880 1
                $id  = null;
881 1
                $uri = null;
882
883
                // The node name can contain the namespace id prefix.
884
                // Example: xsl:template
885 1
                $colon_pos = \strpos($name, ':');
886
887 1
                if ($colon_pos !== false) {
888 1
                        $id   = \substr($name, 0, $colon_pos);
889 1
                        $name = \substr($name, $colon_pos + 1);
890
                }
891
892 1
                if ($id !== null) {
893 1
                        $ns  = $this->namespaces[$id];
894 1
                        $uri = $ns->uri();
895
896 1
                        if ($ns->mode() === FluidNamespace::MODE_EXPLICIT) {
897 1
                                $name = "{$id}:{$name}";
898
                        }
899
                }
900
901
                // Algorithm 1:
902 1
                $el = new \DOMElement($name, $value, $uri);
903
904
                // Algorithm 2:
905
                // $el = $dom->createElement($name, $value);
906
907 1
                return $el;
908
        }
909
910 1
        protected function attachNodes($parent, $nodes, $fn)
911
        {
912 1
                if (! \is_array($nodes) && ! $nodes instanceof \Traversable) {
913 1
                        $nodes = [ $nodes ];
914
                }
915
916 1
                $context = [];
917
918 1
                foreach ($nodes as $el) {
919 1
                        $el        = $this->dom->importNode($el, true);
920 1
                        $context[] = $fn($parent, $el);
921
                }
922
923 1
                return $context;
924
        }
925
926 1
        protected function insertSpecialContent($parent, $k, $v)
927
        {
928
                // The user has passed an element text content:
929
                // [ '@' => 'Element content.' ]
930
931
                // Algorithm 1:
932 1
                $this->newContext($parent)->appendText($v);
933
934
                // Algorithm 2:
935
                // $this->setText($v);
936
937
                // The user can specify multiple '@' special elements
938
                // so Algorithm 1 is the right choice.
939
940 1
                return [];
941
        }
942
943 1
        protected function insertSpecialAttribute($parent, $k, $v)
944
        {
945
                // The user has passed an attribute name and an attribute value:
946
                // [ '@attribute' => 'Attribute content' ]
947
948 1
                $attr = \substr($k, 1);
949 1
                $this->newContext($parent)->setAttribute($attr, $v);
950
951 1
                return [];
952
        }
953
954 1 View Code Duplication
        protected function insertStringString($parent, $k, $v, $fn)
955
        {
956
                // The user has passed an element name and an element value:
957
                // [ 'element' => 'Element content' ]
958
959 1
                $el = $this->createElement($k, $v);
960 1
                $el = $fn($parent, $el);
961
962 1
                return [ $el ];
963
        }
964
965 1 View Code Duplication
        protected function insertStringMixed($parent, $k, $v, $fn, &$optionals)
966
        {
967
                // The user has passed one of these two cases:
968
                // - [ 'element' => [...] ]
969
                // - [ 'element' => DOMNode|SimpleXMLElement|FluidXml ]
970
971 1
                $el = $this->createElement($k);
972 1
                $el = $fn($parent, $el);
973
974
                // The new children elements must be created in the order
975
                // they are supplied, so 'appendChild' is the perfect operation.
976 1
                $this->newContext($el)->appendChild($v, ...$optionals);
977
978 1
                return [ $el ];
979
        }
980
981 1
        protected function insertIntegerArray($parent, $k, $v, $fn, &$optionals)
982
        {
983
                // The user has passed a wrapper array:
984
                // [ [...], ... ]
985
986 1
                $context = [];
987
988 1
                foreach ($v as $kk => $vv) {
989 1
                        $cx = $this->handleInsertion($parent, $kk, $vv, $fn, $optionals);
990
991 1
                        $context = \array_merge($context, $cx);
992
                }
993
994 1
                return $context;
995
        }
996
997 1
        protected function insertIntegerString($parent, $k, $v, $fn)
998
        {
999
                // The user has passed a node name without a node value:
1000
                // [ 'element', ... ]
1001
1002 1
                $el = $this->createElement($v);
1003 1
                $el = $fn($parent, $el);
1004
1005 1
                return [ $el ];
1006
        }
1007
1008 1
        protected function insertIntegerXml($parent, $k, $v, $fn)
1009
        {
1010
                // The user has passed an XML document instance:
1011
                // [ '<tag></tag>', DOMNode, SimpleXMLElement, FluidXml ]
1012
1013 1
                $wrapper = new \DOMDocument();
1014 1
                $wrapper->formatOutput       = true;
1015 1
                $wrapper->preserveWhiteSpace = false;
1016
1017 1
                $v = \ltrim($v);
1018
1019 1
                if ($v[1] === '?') {
1020 1
                        $wrapper->loadXML($v);
1021 1
                        $nodes = $wrapper->childNodes;
1022
                } else {
1023
                        // A way to import strings with multiple root nodes.
1024 1
                        $wrapper->loadXML("<root>$v</root>");
1025
1026
                        // Algorithm 1:
1027 1
                        $nodes = $wrapper->documentElement->childNodes;
1028
1029
                        // Algorithm 2:
1030
                        // $dom_xp = new \DOMXPath($dom);
1031
                        // $nodes = $dom_xp->query('/root/*');
1032
                }
1033
1034 1
                return $this->attachNodes($parent, $nodes, $fn);
1035
        }
1036
1037 1
        protected function insertIntegerDomdocument($parent, $k, $v, $fn)
1038
        {
1039
                // A DOMDocument can have multiple root nodes.
1040
1041
                // Algorithm 1:
1042 1
                return $this->attachNodes($parent, $v->childNodes, $fn);
1043
1044
                // Algorithm 2:
1045
                // return $this->attachNodes($parent, $v->documentElement, $fn);
1046
        }
1047
1048 1
        protected function insertIntegerDomnodelist($parent, $k, $v, $fn)
1049
        {
1050 1
                return $this->attachNodes($parent, $v, $fn);
1051
        }
1052
1053 1
        protected function insertIntegerDomnode($parent, $k, $v, $fn)
1054
        {
1055 1
                return $this->attachNodes($parent, $v, $fn);
1056
        }
1057
1058 1
        protected function insertIntegerSimplexml($parent, $k, $v, $fn)
1059
        {
1060 1
                return $this->attachNodes($parent, \dom_import_simplexml($v), $fn);
1061
        }
1062
1063 1
        protected function insertIntegerFluidxml($parent, $k, $v, $fn)
1064
        {
1065 1
                return $this->attachNodes($parent, $v->dom()->documentElement, $fn);
1066
        }
1067
1068 1
        protected function insertIntegerFluidcontext($parent, $k, $v, $fn)
1069
        {
1070 1
                return $this->attachNodes($parent, $v->asArray(), $fn);
1071
        }
1072
}
1073
1074
class FluidContext implements FluidInterface, \ArrayAccess, \Iterator
1075
{
1076
        use NewableTrait,
1077
            ReservedCallTrait,          // For compatibility with PHP 5.6.
1078
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
1079
1080
        private $document;
1081
        private $handler;
1082
        private $nodes = [];
1083
        private $seek = 0;
1084
1085 1
        public function __construct($document, $handler, $context)
1086
        {
1087 1
                $this->document = $document;
1088 1
                $this->handler  = $handler;
1089
1090 1
                if (! \is_array($context) && ! $context instanceof \Traversable) {
1091
                        // DOMDocument, DOMElement and DOMNode are not iterable.
1092
                        // DOMNodeList and FluidContext are iterable.
1093 1
                        $context = [ $context ];
1094
                }
1095
1096 1
                foreach ($context as $n) {
1097 1
                        if (! $n instanceof \DOMNode) {
1098
                                throw new \Exception('Node type not recognized.');
1099
                        }
1100
1101 1
                        $this->nodes[] = $n;
1102
                }
1103 1
        }
1104
1105 1
        public function asArray()
1106
        {
1107 1
                return $this->nodes;
1108
        }
1109
1110
        // \ArrayAccess interface.
1111 1
        public function offsetSet($offset, $value)
1112
        {
1113
                // if (\is_null($offset)) {
1114
                //         $this->nodes[] = $value;
1115
                // } else {
1116
                //         $this->nodes[$offset] = $value;
1117
                // }
1118 1
                throw new \Exception('Setting a context element is not allowed.');
1119
        }
1120
1121
        // \ArrayAccess interface.
1122 1
        public function offsetExists($offset)
1123
        {
1124 1
                return isset($this->nodes[$offset]);
1125
        }
1126
1127
        // \ArrayAccess interface.
1128 1
        public function offsetUnset($offset)
1129
        {
1130
                // unset($this->nodes[$offset]);
1131 1
                \array_splice($this->nodes, $offset, 1);
1132 1
        }
1133
1134
        // \ArrayAccess interface.
1135 1
        public function offsetGet($offset)
1136
        {
1137 1
                if (isset($this->nodes[$offset])) {
1138 1
                        return $this->nodes[$offset];
1139
                }
1140
1141 1
                return null;
1142
        }
1143
1144
        // \Iterator interface.
1145 1
        public function rewind()
1146
        {
1147 1
                $this->seek = 0;
1148 1
        }
1149
1150
        // \Iterator interface.
1151 1
        public function current()
1152
        {
1153 1
                return $this->nodes[$this->seek];
1154
        }
1155
1156
        // \Iterator interface.
1157 1
        public function key()
1158
        {
1159 1
                return $this->seek;
1160
        }
1161
1162
        // \Iterator interface.
1163 1
        public function next()
1164
        {
1165 1
                ++$this->seek;
1166 1
        }
1167
1168
        // \Iterator interface.
1169 1
        public function valid()
1170
        {
1171 1
                return isset($this->nodes[$this->seek]);
1172
        }
1173
1174 1
        public function length()
1175
        {
1176 1
                return \count($this->nodes);
1177
        }
1178
1179 1
        public function query(...$xpath)
1180
        {
1181 1
                if (\is_array($xpath[0])) {
1182 1
                        $xpath = $xpath[0];
1183
                }
1184
1185 1
                $results = [];
1186
1187 1
                $xp = $this->document->xpath;
1188
1189 1
                foreach ($this->nodes as $n) {
1190 1
                        foreach ($xpath as $x) {
1191
                                // Returns a DOMNodeList.
1192 1
                                $res = $xp->query($x, $n);
1193
1194
                                // Algorithm 1:
1195
                                // $results = \array_merge($results, \iterator_to_array($res));
1196
1197
                                // Algorithm 2:
1198
                                // It is faster than \iterator_to_array and a lot faster
1199
                                // than \iterator_to_array + \array_merge.
1200 1
                                foreach ($res as $r) {
1201 1
                                        $results[] = $r;
1202
                                }
1203
1204
                                // Algorithm 3:
1205
                                // for ($i = 0, $l = $res->length; $i < $l; ++$i) {
1206
                                //         $results[] = $res->item($i);
1207
                                // }
1208
                        }
1209
                }
1210
1211
                // Performing over multiple sibling nodes a query that ascends
1212
                // the xpath, relative (../..) or absolute (//), returns identical
1213
                // matching results that must be collapsed in an unique result
1214
                // otherwise a subsequent operation is performed multiple times.
1215 1
                $results = $this->filterQueryResults($results);
1216
1217 1
                return $this->newContext($results);
1218
        }
1219
1220 1
        public function times($times, callable $fn = null)
1221
        {
1222 1
                if ($fn === null) {
1223 1
                        return new FluidRepeater($this->document, $this->handler, $this, $times);
1224
                }
1225
1226 1
                for ($i = 0; $i < $times; ++$i) {
1227 1
                        $args = [$this, $i];
1228
1229 1 View Code Duplication
                        if ($fn instanceof \Closure) {
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...
1230 1
                                $fn = $fn->bindTo($this);
1231
1232 1
                                \array_shift($args);
1233
1234
                                // It is faster than \call_user_func.
1235 1
                                $fn(...$args);
1236
                        } else {
1237 1
                                \call_user_func($fn, ...$args);
1238
                        }
1239
                }
1240
1241 1
                return $this;
1242
        }
1243
1244 1
        public function each(callable $fn)
1245
        {
1246 1
                foreach ($this->nodes as $i => $n) {
1247 1
                        $cx   = $this->newContext($n);
1248 1
                        $args = [$cx, $i, $n];
1249
1250 1 View Code Duplication
                        if ($fn instanceof \Closure) {
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...
1251 1
                                $fn = $fn->bindTo($cx);
1252
1253 1
                                \array_shift($args);
1254
1255
                                // It is faster than \call_user_func.
1256 1
                                $fn(...$args);
1257
                        } else {
1258 1
                                \call_user_func($fn, ...$args);
1259
                        }
1260
                }
1261
1262 1
                return $this;
1263
        }
1264
1265
        // appendChild($child, $value?, $attributes? = [], $switchContext? = false)
1266 1
        public function appendChild($child, ...$optionals)
1267
        {
1268
                return $this->handler->insertElement($this->nodes, $child, $optionals, function($parent, $element) {
1269 1
                        return $parent->appendChild($element);
1270 1
                }, $this);
1271
        }
1272
1273
        // Alias of appendChild().
1274 1
        public function add($child, ...$optionals)
1275
        {
1276 1
                return $this->appendChild($child, ...$optionals);
1277
        }
1278
1279 1
        public function prependSibling($sibling, ...$optionals)
1280
        {
1281
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function($sibling, $element) {
1282 1
                        return $sibling->parentNode->insertBefore($element, $sibling);
1283 1
                }, $this);
1284
        }
1285
1286
        // Alias of prependSibling().
1287 1
        public function prepend($sibling, ...$optionals)
1288
        {
1289 1
                return $this->prependSibling($sibling, ...$optionals);
1290
        }
1291
1292
        // Alias of prependSibling().
1293 1
        public function insertSiblingBefore($sibling, ...$optionals)
1294
        {
1295 1
                return $this->prependSibling($sibling, ...$optionals);
1296
        }
1297
1298
        public function appendSibling($sibling, ...$optionals)
1299
        {
1300 1
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function($sibling, $element) {
1301
                        // If ->nextSibling is null, $element is simply appended as last sibling.
1302 1
                        return $sibling->parentNode->insertBefore($element, $sibling->nextSibling);
1303 1
                }, $this);
1304
        }
1305
1306
        // Alias of appendSibling().
1307 1
        public function append($sibling, ...$optionals)
1308
        {
1309 1
                return $this->appendSibling($sibling, ...$optionals);
1310
        }
1311
1312
        // Alias of appendSibling().
1313 1
        public function insertSiblingAfter($sibling, ...$optionals)
1314
        {
1315 1
                return $this->appendSibling($sibling, ...$optionals);
1316
        }
1317
1318
        // Arguments can be in the form of:
1319
        // setAttribute($name, $value)
1320
        // setAttribute(['name' => 'value', ...])
1321 1
        public function setAttribute(...$arguments)
1322
        {
1323
                // Default case is:
1324
                // [ 'name' => 'value', ... ]
1325 1
                $attrs = $arguments[0];
1326
1327
                // If the first argument is not an array,
1328
                // the user has passed two arguments:
1329
                // 1. is the attribute name
1330
                // 2. is the attribute value
1331 1
                if (! \is_array($arguments[0])) {
1332 1
                        $attrs = [$arguments[0] => $arguments[1]];
1333
                }
1334
1335 1
                foreach ($this->nodes as $n) {
1336 1
                        foreach ($attrs as $k => $v) {
1337
                                // Algorithm 1:
1338 1
                                $n->setAttribute($k, $v);
1339
1340
                                // Algorithm 2:
1341
                                // $n->setAttributeNode(new \DOMAttr($k, $v));
1342
1343
                                // Algorithm 3:
1344
                                // $n->appendChild(new \DOMAttr($k, $v));
1345
1346
                                // Algorithm 2 and 3 have a different behaviour
1347
                                // from Algorithm 1.
1348
                                // The attribute is still created or setted, but
1349
                                // changing the value of an existing attribute
1350
                                // changes even the order of that attribute
1351
                                // in the attribute list.
1352
                        }
1353
                }
1354
1355 1
                return $this;
1356
        }
1357
1358
        // Alias of setAttribute().
1359 1
        public function attr(...$arguments)
1360
        {
1361 1
                return $this->setAttribute(...$arguments);
1362
        }
1363
1364 1
        public function appendText($text)
1365
        {
1366 1
                foreach ($this->nodes as $n) {
1367 1
                        $n->appendChild(new \DOMText($text));
1368
                }
1369
1370 1
                return $this;
1371
        }
1372
1373 1
        public function appendCdata($text)
1374
        {
1375 1
                foreach ($this->nodes as $n) {
1376 1
                        $n->appendChild(new \DOMCDATASection($text));
1377
                }
1378
1379 1
                return $this;
1380
        }
1381
1382 1
        public function setText($text)
1383
        {
1384 1
                foreach ($this->nodes as $n) {
1385
                        // Algorithm 1:
1386 1
                        $n->nodeValue = $text;
1387
1388
                        // Algorithm 2:
1389
                        // foreach ($n->childNodes as $c) {
1390
                        //         $n->removeChild($c);
1391
                        // }
1392
                        // $n->appendChild(new \DOMText($text));
1393
1394
                        // Algorithm 3:
1395
                        // foreach ($n->childNodes as $c) {
1396
                        //         $n->replaceChild(new \DOMText($text), $c);
1397
                        // }
1398
                }
1399
1400 1
                return $this;
1401
        }
1402
1403
        // Alias of setText().
1404 1
        public function text($text)
1405
        {
1406 1
                return $this->setText($text);
1407
        }
1408
1409 1
        public function setCdata($text)
1410
        {
1411 1
                foreach ($this->nodes as $n) {
1412 1
                        $n->nodeValue = '';
1413 1
                        $n->appendChild(new \DOMCDATASection($text));
1414
                }
1415
1416 1
                return $this;
1417
        }
1418
1419
        // Alias of setCdata().
1420 1
        public function cdata($text)
1421
        {
1422 1
                return $this->setCdata($text);
1423
        }
1424
1425 1
        public function remove(...$xpath)
1426
        {
1427
                // Arguments can be empty, a string or an array of strings.
1428
1429 1
                if (empty($xpath)) {
1430
                        // The user has requested to remove the nodes of this context.
1431 1
                        $targets = $this->nodes;
1432
                } else {
1433 1
                        $targets = $this->query(...$xpath);
1434
                }
1435
1436 1
                foreach ($targets as $t) {
1437 1
                        $t->parentNode->removeChild($t);
1438
                }
1439
1440 1
                return $this;
1441
        }
1442
1443 1
        public function xml($strip = false)
1444
        {
1445 1
                return domnodes_to_string($this->nodes);
1446
        }
1447
1448 1
        protected function newContext(&$context)
1449
        {
1450 1
                return new FluidContext($this->document, $this->handler, $context);
1451
        }
1452
1453 1
        protected function filterQueryResults(&$results)
1454
        {
1455 1
                $set = [];
1456
1457 1
                foreach ($results as $r) {
1458 1
                        $found = false;
1459
1460 1
                        foreach ($set as $u) {
1461 1
                                if ($r === $u) {
1462 1
                                        $found = true;
1463
                                }
1464
                        }
1465
1466 1
                        if (! $found) {
1467 1
                                $set[] = $r;
1468
                        }
1469
                }
1470
1471 1
                return $set;
1472
        }
1473
1474
}
1475
1476
} // END OF NAMESPACE FluidXml\Core
1477