QueryFilters::nextAll()   A
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 11
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 19
rs 9.2222
1
<?php
2
3
namespace QueryPath\Helpers;
4
5
use QueryPath\CSS\DOMTraverser;
6
use QueryPath\CSS\ParseException;
7
use QueryPath\Exception;
8
use QueryPath\Query;
9
use QueryPath\QueryPath;
10
11
/**
12
 * Trait QueryFilters
13
 *
14
 * @package QueryPath\Helpers
15
 *
16
 * @property array matches
17
 */
18
trait QueryFilters
19
{
20
21
    /**
22
     * Filter a list down to only elements that match the selector.
23
     * Use this, for example, to find all elements with a class, or with
24
     * certain children.
25
     *
26
     * @param string $selector
27
     *   The selector to use as a filter.
28
     * @return Query The DOMQuery with non-matching items filtered out.*   The DOMQuery with non-matching items
29
     *               filtered out.
30
     * @see filterLambda()
31
     * @see filterCallback()
32
     * @see map()
33
     * @see find()
34
     * @see is()
35
     * @throws ParseException
36
     */
37
    public function filter($selector): Query
38
    {
39
        $found = new \SplObjectStorage();
40
        $tmp = new \SplObjectStorage();
41
42
        foreach ($this->matches as $m) {
43
            $tmp->attach($m);
44
            // Seems like this should be right... but it fails unit
45
            // tests. Need to compare to jQuery.
46
            // $query = new \QueryPath\CSS\DOMTraverser($tmp, TRUE, $m);
47
            $query = new DOMTraverser($tmp);
48
            $query->find($selector);
49
            if (count($query->matches())) {
50
                $found->attach($m);
51
            }
52
            $tmp->detach($m);
53
        }
54
55
        return $this->inst($found, NULL);
0 ignored issues
show
Bug introduced by
It seems like inst() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

55
        return $this->/** @scrutinizer ignore-call */ inst($found, NULL);
Loading history...
56
    }
57
58
    /**
59
     * Filter based on a lambda function.
60
     *
61
     * The function string will be executed as if it were the body of a
62
     * function. It is passed two arguments:
63
     * - $index: The index of the item.
64
     * - $item: The current Element.
65
     * If the function returns boolean FALSE, the item will be removed from
66
     * the list of elements. Otherwise it will be kept.
67
     *
68
     * Example:
69
     *
70
     * @code
71
     * qp('li')->filterLambda('qp($item)->attr("id") == "test"');
72
     * @endcode
73
     *
74
     * The above would filter down the list to only an item whose ID is
75
     * 'text'.
76
     *
77
     * @param string $fn
78
     *  Inline lambda function in a string.
79
     * @return \QueryPath\DOMQuery
80
     * @see filter()
81
     * @see map()
82
     * @see mapLambda()
83
     * @see filterCallback()
84
     * @throws ParseException
85
     */
86
    public function filterLambda($fn): Query
87
    {
88
        $function = create_function('$index, $item', $fn);
89
        $found = new \SplObjectStorage();
90
        $i = 0;
91
        foreach ($this->matches as $item) {
92
            if ($function($i++, $item) !== false) {
93
                $found->attach($item);
94
            }
95
        }
96
97
        return $this->inst($found, NULL);
98
    }
99
100
    /**
101
     * Use regular expressions to filter based on the text content of matched elements.
102
     *
103
     * Only items that match the given regular expression will be kept. All others will
104
     * be removed.
105
     *
106
     * The regular expression is run against the <i>text content</i> (the PCDATA) of the
107
     * elements. This is a way of filtering elements based on their content.
108
     *
109
     * Example:
110
     *
111
     * @code
112
     *  <?xml version="1.0"?>
113
     *  <div>Hello <i>World</i></div>
114
     * @endcode
115
     *
116
     * @code
117
     *  <?php
118
     *    // This will be 1.
119
     *    qp($xml, 'div')->filterPreg('/World/')->matches->count();
120
     *  ?>
121
     * @endcode
122
     *
123
     * The return value above will be 1 because the text content of @codeqp($xml, 'div')@endcode is
124
     * @codeHello World@endcode.
125
     *
126
     * Compare this to the behavior of the <em>:contains()</em> CSS3 pseudo-class.
127
     *
128
     * @param string $regex
129
     *  A regular expression.
130
     * @return \QueryPath\DOMQuery
131
     * @see       filter()
132
     * @see       filterCallback()
133
     * @see       preg_match()
134
     * @throws ParseException
135
     */
136
    public function filterPreg($regex): Query
137
    {
138
        $found = new \SplObjectStorage();
139
140
        foreach ($this->matches as $item) {
141
            if (preg_match($regex, $item->textContent) > 0) {
142
                $found->attach($item);
143
            }
144
        }
145
146
        return $this->inst($found, NULL);
147
    }
148
149
    /**
150
     * Filter based on a callback function.
151
     *
152
     * A callback may be any of the following:
153
     *  - a function: 'my_func'.
154
     *  - an object/method combo: $obj, 'myMethod'
155
     *  - a class/method combo: 'MyClass', 'myMethod'
156
     * Note that classes are passed in strings. Objects are not.
157
     *
158
     * Each callback is passed to arguments:
159
     *  - $index: The index position of the object in the array.
160
     *  - $item: The item to be operated upon.
161
     *
162
     * If the callback function returns FALSE, the item will be removed from the
163
     * set of matches. Otherwise the item will be considered a match and left alone.
164
     *
165
     * @param callback $callback .
166
     *                           A callback either as a string (function) or an array (object, method OR
167
     *                           classname, method).
168
     * @return \QueryPath\DOMQuery
169
     *                           Query path object augmented according to the function.
170
     * @see filter()
171
     * @see filterLambda()
172
     * @see map()
173
     * @see is()
174
     * @see find()
175
     * @throws ParseException
176
     * @throws Exception
177
     */
178
    public function filterCallback($callback): Query
179
    {
180
        $found = new \SplObjectStorage();
181
        $i = 0;
182
        if (is_callable($callback)) {
183
            foreach ($this->matches as $item) {
184
                if ($callback($i++, $item) !== false) {
185
                    $found->attach($item);
186
                }
187
            }
188
        } else {
189
            throw new Exception('The specified callback is not callable.');
190
        }
191
192
        return $this->inst($found, NULL);
193
    }
194
195
    /**
196
     * Run a function on each item in a set.
197
     *
198
     * The mapping callback can return anything. Whatever it returns will be
199
     * stored as a match in the set, though. This means that afer a map call,
200
     * there is no guarantee that the elements in the set will behave correctly
201
     * with other DOMQuery functions.
202
     *
203
     * Callback rules:
204
     * - If the callback returns NULL, the item will be removed from the array.
205
     * - If the callback returns an array, the entire array will be stored in
206
     *   the results.
207
     * - If the callback returns anything else, it will be appended to the array
208
     *   of matches.
209
     *
210
     * @param callback $callback
211
     *  The function or callback to use. The callback will be passed two params:
212
     *  - $index: The index position in the list of items wrapped by this object.
213
     *  - $item: The current item.
214
     *
215
     * @return \QueryPath\DOMQuery
216
     *  The DOMQuery object wrapping a list of whatever values were returned
217
     *  by each run of the callback.
218
     *
219
     * @see DOMQuery::get()
220
     * @see filter()
221
     * @see find()
222
     * @throws Exception
223
     * @throws ParseException
224
     */
225
    public function map($callback): Query
226
    {
227
        $found = new \SplObjectStorage();
228
229
        if (is_callable($callback)) {
230
            $i = 0;
231
            foreach ($this->matches as $item) {
232
                $c = call_user_func($callback, $i, $item);
233
                if (isset($c)) {
234
                    if (is_array($c) || $c instanceof \Iterable) {
235
                        foreach ($c as $retval) {
236
                            if (!is_object($retval)) {
237
                                $tmp = new \stdClass();
238
                                $tmp->textContent = $retval;
239
                                $retval = $tmp;
240
                            }
241
                            $found->attach($retval);
242
                        }
243
                    } else {
244
                        if (!is_object($c)) {
245
                            $tmp = new \stdClass();
246
                            $tmp->textContent = $c;
247
                            $c = $tmp;
248
                        }
249
                        $found->attach($c);
250
                    }
251
                }
252
                ++$i;
253
            }
254
        } else {
255
            throw new Exception('Callback is not callable.');
256
        }
257
258
        return $this->inst($found, NULL);
259
    }
260
261
    /**
262
     * Narrow the items in this object down to only a slice of the starting items.
263
     *
264
     * @param integer $start
265
     *  Where in the list of matches to begin the slice.
266
     * @param integer $length
267
     *  The number of items to include in the slice. If nothing is specified, the
268
     *  all remaining matches (from $start onward) will be included in the sliced
269
     *  list.
270
     * @return \QueryPath\DOMQuery
271
     * @see array_slice()
272
     * @throws ParseException
273
     */
274
    public function slice($start, $length = 0): Query
275
    {
276
        $end = $length;
277
        $found = new \SplObjectStorage();
278
        if ($start >= $this->count()) {
0 ignored issues
show
Bug introduced by
It seems like count() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

278
        if ($start >= $this->/** @scrutinizer ignore-call */ count()) {
Loading history...
279
            return $this->inst($found, NULL);
280
        }
281
282
        $i = $j = 0;
283
        foreach ($this->matches as $m) {
284
            if ($i >= $start) {
285
                if ($end > 0 && $j >= $end) {
286
                    break;
287
                }
288
                $found->attach($m);
289
                ++$j;
290
            }
291
            ++$i;
292
        }
293
294
        return $this->inst($found, NULL);
295
    }
296
297
    /**
298
     * Run a callback on each item in the list of items.
299
     *
300
     * Rules of the callback:
301
     * - A callback is passed two variables: $index and $item. (There is no
302
     *   special treatment of $this, as there is in jQuery.)
303
     *   - You will want to pass $item by reference if it is not an
304
     *     object (DOMNodes are all objects).
305
     * - A callback that returns FALSE will stop execution of the each() loop. This
306
     *   works like break in a standard loop.
307
     * - A TRUE return value from the callback is analogous to a continue statement.
308
     * - All other return values are ignored.
309
     *
310
     * @param callback $callback
311
     *  The callback to run.
312
     * @return \QueryPath\DOMQuery
313
     *  The DOMQuery.
314
     * @see eachLambda()
315
     * @see filter()
316
     * @see map()
317
     * @throws Exception
318
     */
319
    public function each($callback): Query
320
    {
321
        if (is_callable($callback)) {
322
            $i = 0;
323
            foreach ($this->matches as $item) {
324
                if (call_user_func($callback, $i, $item) === false) {
325
                    return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type QueryPath\Helpers\QueryFilters which is incompatible with the type-hinted return QueryPath\Query.
Loading history...
326
                }
327
                ++$i;
328
            }
329
        } else {
330
            throw new \QueryPath\Exception('Callback is not callable.');
331
        }
332
333
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type QueryPath\Helpers\QueryFilters which is incompatible with the type-hinted return QueryPath\Query.
Loading history...
334
    }
335
336
    /**
337
     * An each() iterator that takes a lambda function.
338
     *
339
     * @deprecated
340
     *   Since PHP 5.3 supports anonymous functions -- REAL Lambdas -- this
341
     *   method is not necessary and should be avoided.
342
     * @param string $lambda
343
     *  The lambda function. This will be passed ($index, &$item).
344
     * @return \QueryPath\DOMQuery
345
     *  The DOMQuery object.
346
     * @see each()
347
     * @see filterLambda()
348
     * @see filterCallback()
349
     * @see map()
350
     */
351
    public function eachLambda($lambda): Query
352
    {
353
        $index = 0;
354
        foreach ($this->matches as $item) {
355
            $fn = create_function('$index, &$item', $lambda);
356
            if ($fn($index, $item) === false) {
357
                return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type QueryPath\Helpers\QueryFilters which is incompatible with the type-hinted return QueryPath\Query.
Loading history...
358
            }
359
            ++$index;
360
        }
361
362
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type QueryPath\Helpers\QueryFilters which is incompatible with the type-hinted return QueryPath\Query.
Loading history...
363
    }
364
365
    /**
366
     * Get the even elements, so counter-intuitively 1, 3, 5, etc.
367
     *
368
     * @return \QueryPath\DOMQuery
369
     *  A DOMQuery wrapping all of the children.
370
     * @see    removeChildren()
371
     * @see    parent()
372
     * @see    parents()
373
     * @see    next()
374
     * @see    prev()
375
     * @since  2.1
376
     * @author eabrand
377
     * @throws ParseException
378
     */
379
    public function even(): Query
380
    {
381
        $found = new \SplObjectStorage();
382
        $even = false;
383
        foreach ($this->matches as $m) {
384
            if ($even && $m->nodeType === XML_ELEMENT_NODE) {
385
                $found->attach($m);
386
            }
387
            $even = $even ? false : true;
388
        }
389
390
        return $this->inst($found, NULL);
391
    }
392
393
    /**
394
     * Get the odd elements, so counter-intuitively 0, 2, 4, etc.
395
     *
396
     * @return \QueryPath\DOMQuery
397
     *  A DOMQuery wrapping all of the children.
398
     * @see    removeChildren()
399
     * @see    parent()
400
     * @see    parents()
401
     * @see    next()
402
     * @see    prev()
403
     * @since  2.1
404
     * @author eabrand
405
     * @throws ParseException
406
     */
407
    public function odd(): Query
408
    {
409
        $found = new \SplObjectStorage();
410
        $odd = true;
411
        foreach ($this->matches as $m) {
412
            if ($odd && $m->nodeType === XML_ELEMENT_NODE) {
413
                $found->attach($m);
414
            }
415
            $odd = $odd ? false : true;
416
        }
417
418
        return $this->inst($found, NULL);
419
    }
420
421
    /**
422
     * Get the first matching element.
423
     *
424
     *
425
     * @return \QueryPath\DOMQuery
426
     *  A DOMQuery wrapping all of the children.
427
     * @see    next()
428
     * @see    prev()
429
     * @since  2.1
430
     * @author eabrand
431
     * @throws ParseException
432
     */
433
    public function first(): Query
434
    {
435
        $found = new \SplObjectStorage();
436
        foreach ($this->matches as $m) {
437
            if ($m->nodeType === XML_ELEMENT_NODE) {
438
                $found->attach($m);
439
                break;
440
            }
441
        }
442
443
        return $this->inst($found, NULL);
444
    }
445
446
    /**
447
     * Get the first child of the matching element.
448
     *
449
     *
450
     * @return \QueryPath\DOMQuery
451
     *  A DOMQuery wrapping all of the children.
452
     * @see    next()
453
     * @see    prev()
454
     * @since  2.1
455
     * @author eabrand
456
     * @throws ParseException
457
     */
458
    public function firstChild(): Query
459
    {
460
        // Could possibly use $m->firstChild http://theserverpages.com/php/manual/en/ref.dom.php
461
        $found = new \SplObjectStorage();
462
        $flag = false;
463
        foreach ($this->matches as $m) {
464
            foreach ($m->childNodes as $c) {
465
                if ($c->nodeType === XML_ELEMENT_NODE) {
466
                    $found->attach($c);
467
                    $flag = true;
468
                    break;
469
                }
470
            }
471
            if ($flag) {
472
                break;
473
            }
474
        }
475
476
        return $this->inst($found, NULL);
477
    }
478
479
    /**
480
     * Get the last matching element.
481
     *
482
     *
483
     * @return \QueryPath\DOMQuery
484
     *  A DOMQuery wrapping all of the children.
485
     * @see    next()
486
     * @see    prev()
487
     * @since  2.1
488
     * @author eabrand
489
     * @throws ParseException
490
     */
491
    public function last(): Query
492
    {
493
        $found = new \SplObjectStorage();
494
        $item = NULL;
495
        foreach ($this->matches as $m) {
496
            if ($m->nodeType === XML_ELEMENT_NODE) {
497
                $item = $m;
498
            }
499
        }
500
        if ($item) {
501
            $found->attach($item);
502
        }
503
504
        return $this->inst($found, NULL);
505
    }
506
507
    /**
508
     * Get the last child of the matching element.
509
     *
510
     *
511
     * @return \QueryPath\DOMQuery
512
     *  A DOMQuery wrapping all of the children.
513
     * @see    next()
514
     * @see    prev()
515
     * @since  2.1
516
     * @author eabrand
517
     * @throws ParseException
518
     */
519
    public function lastChild(): Query
520
    {
521
        $found = new \SplObjectStorage();
522
        $item = NULL;
523
        foreach ($this->matches as $m) {
524
            foreach ($m->childNodes as $c) {
525
                if ($c->nodeType === XML_ELEMENT_NODE) {
526
                    $item = $c;
527
                }
528
            }
529
            if ($item) {
530
                $found->attach($item);
531
                $item = NULL;
532
            }
533
        }
534
535
        return $this->inst($found, NULL);
536
    }
537
538
    /**
539
     * Get all siblings after an element until the selector is reached.
540
     *
541
     * For each element in the DOMQuery, get all siblings that appear after
542
     * it. If a selector is passed in, then only siblings that match the
543
     * selector will be included.
544
     *
545
     * @param string $selector
546
     *  A valid CSS 3 selector.
547
     * @return \QueryPath\DOMQuery
548
     *  The DOMQuery object, now containing the matching siblings.
549
     * @see    next()
550
     * @see    prevAll()
551
     * @see    children()
552
     * @see    siblings()
553
     * @since  2.1
554
     * @author eabrand
555
     * @throws Exception
556
     */
557
    public function nextUntil($selector = NULL): Query
558
    {
559
        $found = new \SplObjectStorage();
560
        foreach ($this->matches as $m) {
561
            while (isset($m->nextSibling)) {
562
                $m = $m->nextSibling;
563
                if ($m->nodeType === XML_ELEMENT_NODE) {
564
                    if (NULL !== $selector && QueryPath::with($m, NULL, $this->options)->is($selector) > 0) {
565
                        break;
566
                    }
567
                    $found->attach($m);
568
                }
569
            }
570
        }
571
572
        return $this->inst($found, NULL);
573
    }
574
575
    /**
576
     * Get the previous siblings for each element in the DOMQuery
577
     * until the selector is reached.
578
     *
579
     * For each element in the DOMQuery, get all previous siblings. If a
580
     * selector is provided, only matching siblings will be retrieved.
581
     *
582
     * @param string $selector
583
     *  A valid CSS 3 selector.
584
     * @return \QueryPath\DOMQuery
585
     *  The DOMQuery object, now wrapping previous sibling elements.
586
     * @see    prev()
587
     * @see    nextAll()
588
     * @see    siblings()
589
     * @see    contents()
590
     * @see    children()
591
     * @since  2.1
592
     * @author eabrand
593
     * @throws Exception
594
     */
595
    public function prevUntil($selector = NULL): Query
596
    {
597
        $found = new \SplObjectStorage();
598
        foreach ($this->matches as $m) {
599
            while (isset($m->previousSibling)) {
600
                $m = $m->previousSibling;
601
                if ($m->nodeType === XML_ELEMENT_NODE) {
602
                    if (NULL !== $selector && QueryPath::with($m, NULL, $this->options)->is($selector)) {
603
                        break;
604
                    }
605
606
                    $found->attach($m);
607
                }
608
            }
609
        }
610
611
        return $this->inst($found, NULL);
612
    }
613
614
    /**
615
     * Get all ancestors of each element in the DOMQuery until the selector is reached.
616
     *
617
     * If a selector is present, only matching ancestors will be retrieved.
618
     *
619
     * @see    parent()
620
     * @param string $selector
621
     *  A valid CSS 3 Selector.
622
     * @return \QueryPath\DOMQuery
623
     *  A DOMNode object containing the matching ancestors.
624
     * @see    siblings()
625
     * @see    children()
626
     * @since  2.1
627
     * @author eabrand
628
     * @throws Exception
629
     */
630
    public function parentsUntil($selector = NULL): Query
631
    {
632
        $found = new \SplObjectStorage();
633
        foreach ($this->matches as $m) {
634
            while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
635
                $m = $m->parentNode;
636
                // Is there any case where parent node is not an element?
637
                if ($m->nodeType === XML_ELEMENT_NODE) {
638
                    if (!empty($selector)) {
639
                        if (QueryPath::with($m, NULL, $this->options)->is($selector) > 0) {
640
                            break;
641
                        }
642
                        $found->attach($m);
643
                    } else {
644
                        $found->attach($m);
645
                    }
646
                }
647
            }
648
        }
649
650
        return $this->inst($found, NULL);
651
    }
652
653
    /**
654
     * Reduce the matched set to just one.
655
     *
656
     * This will take a matched set and reduce it to just one item -- the item
657
     * at the index specified. This is a destructive operation, and can be undone
658
     * with {@link end()}.
659
     *
660
     * @param $index
661
     *  The index of the element to keep. The rest will be
662
     *  discarded.
663
     * @return \QueryPath\DOMQuery
664
     * @see get()
665
     * @see is()
666
     * @see end()
667
     * @throws ParseException
668
     */
669
    public function eq($index): Query
670
    {
671
        return $this->inst($this->getNthMatch($index), NULL);
0 ignored issues
show
Bug introduced by
It seems like getNthMatch() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

671
        return $this->inst($this->/** @scrutinizer ignore-call */ getNthMatch($index), NULL);
Loading history...
672
    }
673
674
    /**
675
     * Filter a list to contain only items that do NOT match.
676
     *
677
     * @param string $selector
678
     *  A selector to use as a negation filter. If the filter is matched, the
679
     *  element will be removed from the list.
680
     * @return \QueryPath\DOMQuery
681
     *  The DOMQuery object with matching items filtered out.
682
     * @see find()
683
     * @throws ParseException
684
     * @throws Exception
685
     */
686
    public function not($selector): Query
687
    {
688
        $found = new \SplObjectStorage();
689
        if ($selector instanceof \DOMElement) {
0 ignored issues
show
introduced by
$selector is never a sub-type of DOMElement.
Loading history...
690
            foreach ($this->matches as $m) {
691
                if ($m !== $selector) {
692
                    $found->attach($m);
693
                }
694
            }
695
        } elseif (is_array($selector)) {
0 ignored issues
show
introduced by
The condition is_array($selector) is always false.
Loading history...
696
            foreach ($this->matches as $m) {
697
                if (!in_array($m, $selector, true)) {
698
                    $found->attach($m);
699
                }
700
            }
701
        } elseif ($selector instanceof \SplObjectStorage) {
0 ignored issues
show
introduced by
$selector is never a sub-type of SplObjectStorage.
Loading history...
702
            foreach ($this->matches as $m) {
703
                if ($selector->contains($m)) {
704
                    $found->attach($m);
705
                }
706
            }
707
        } else {
708
            foreach ($this->matches as $m) {
709
                if (!QueryPath::with($m, NULL, $this->options)->is($selector)) {
710
                    $found->attach($m);
711
                }
712
            }
713
        }
714
715
        return $this->inst($found, NULL);
716
    }
717
718
    /**
719
     * Find the closest element matching the selector.
720
     *
721
     * This finds the closest match in the ancestry chain. It first checks the
722
     * present element. If the present element does not match, this traverses up
723
     * the ancestry chain (e.g. checks each parent) looking for an item that matches.
724
     *
725
     * It is provided for jQuery 1.3 compatibility.
726
     *
727
     * @param string $selector
728
     *  A CSS Selector to match.
729
     * @return \QueryPath\DOMQuery
730
     *  The set of matches.
731
     * @since 2.0
732
     * @throws Exception
733
     */
734
    public function closest($selector): Query
735
    {
736
        $found = new \SplObjectStorage();
737
        foreach ($this->matches as $m) {
738
739
            if (QueryPath::with($m, NULL, $this->options)->is($selector) > 0) {
740
                $found->attach($m);
741
            } else {
742
                while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
743
                    $m = $m->parentNode;
744
                    // Is there any case where parent node is not an element?
745
                    if ($m->nodeType === XML_ELEMENT_NODE && QueryPath::with($m, NULL,
746
                            $this->options)->is($selector) > 0) {
747
                        $found->attach($m);
748
                        break;
749
                    }
750
                }
751
            }
752
753
        }
754
755
        // XXX: Should this be an in-place modification?
756
        return $this->inst($found, NULL);
757
    }
758
759
    /**
760
     * Get the immediate parent of each element in the DOMQuery.
761
     *
762
     * If a selector is passed, this will return the nearest matching parent for
763
     * each element in the DOMQuery.
764
     *
765
     * @param string $selector
766
     *  A valid CSS3 selector.
767
     * @return \QueryPath\DOMQuery
768
     *  A DOMNode object wrapping the matching parents.
769
     * @see children()
770
     * @see siblings()
771
     * @see parents()
772
     * @throws Exception
773
     */
774
    public function parent($selector = NULL): Query
775
    {
776
        $found = new \SplObjectStorage();
777
        foreach ($this->matches as $m) {
778
            while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
779
                $m = $m->parentNode;
780
                // Is there any case where parent node is not an element?
781
                if ($m->nodeType === XML_ELEMENT_NODE) {
782
                    if (!empty($selector)) {
783
                        if (QueryPath::with($m, NULL, $this->options)->is($selector) > 0) {
784
                            $found->attach($m);
785
                            break;
786
                        }
787
                    } else {
788
                        $found->attach($m);
789
                        break;
790
                    }
791
                }
792
            }
793
        }
794
795
        return $this->inst($found, NULL);
796
    }
797
798
    /**
799
     * Get all ancestors of each element in the DOMQuery.
800
     *
801
     * If a selector is present, only matching ancestors will be retrieved.
802
     *
803
     * @see parent()
804
     * @param string $selector
805
     *  A valid CSS 3 Selector.
806
     * @return \QueryPath\DOMQuery
807
     *  A DOMNode object containing the matching ancestors.
808
     * @see siblings()
809
     * @see children()
810
     * @throws ParseException
811
     * @throws Exception
812
     */
813
    public function parents($selector = NULL): Query
814
    {
815
        $found = new \SplObjectStorage();
816
        foreach ($this->matches as $m) {
817
            while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) {
818
                $m = $m->parentNode;
819
                // Is there any case where parent node is not an element?
820
                if ($m->nodeType === XML_ELEMENT_NODE) {
821
                    if (!empty($selector)) {
822
                        if (QueryPath::with($m, NULL, $this->options)->is($selector) > 0) {
823
                            $found->attach($m);
824
                        }
825
                    } else {
826
                        $found->attach($m);
827
                    }
828
                }
829
            }
830
        }
831
832
        return $this->inst($found, NULL);
833
    }
834
835
    /**
836
     * Get the next sibling of each element in the DOMQuery.
837
     *
838
     * If a selector is provided, the next matching sibling will be returned.
839
     *
840
     * @param string $selector
841
     *  A CSS3 selector.
842
     * @return \QueryPath\DOMQuery
843
     *  The DOMQuery object.
844
     * @throws Exception
845
     * @throws ParseException
846
     * @see nextAll()
847
     * @see prev()
848
     * @see children()
849
     * @see contents()
850
     * @see parent()
851
     * @see parents()
852
     */
853
    public function next($selector = NULL): Query
854
    {
855
        $found = new \SplObjectStorage();
856
        foreach ($this->matches as $m) {
857
            while (isset($m->nextSibling)) {
858
                $m = $m->nextSibling;
859
                if ($m->nodeType === XML_ELEMENT_NODE) {
860
                    if (!empty($selector)) {
861
                        if (QueryPath::with($m, NULL, $this->options)->is($selector) > 0) {
862
                            $found->attach($m);
863
                            break;
864
                        }
865
                    } else {
866
                        $found->attach($m);
867
                        break;
868
                    }
869
                }
870
            }
871
        }
872
873
        return $this->inst($found, NULL);
874
    }
875
876
    /**
877
     * Get all siblings after an element.
878
     *
879
     * For each element in the DOMQuery, get all siblings that appear after
880
     * it. If a selector is passed in, then only siblings that match the
881
     * selector will be included.
882
     *
883
     * @param string $selector
884
     *  A valid CSS 3 selector.
885
     * @return \QueryPath\DOMQuery
886
     *  The DOMQuery object, now containing the matching siblings.
887
     * @throws Exception
888
     * @throws ParseException
889
     * @see next()
890
     * @see prevAll()
891
     * @see children()
892
     * @see siblings()
893
     */
894
    public function nextAll($selector = NULL): Query
895
    {
896
        $found = new \SplObjectStorage();
897
        foreach ($this->matches as $m) {
898
            while (isset($m->nextSibling)) {
899
                $m = $m->nextSibling;
900
                if ($m->nodeType === XML_ELEMENT_NODE) {
901
                    if (!empty($selector)) {
902
                        if (QueryPath::with($m, NULL, $this->options)->is($selector) > 0) {
903
                            $found->attach($m);
904
                        }
905
                    } else {
906
                        $found->attach($m);
907
                    }
908
                }
909
            }
910
        }
911
912
        return $this->inst($found, NULL);
913
    }
914
915
    /**
916
     * Get the next sibling before each element in the DOMQuery.
917
     *
918
     * For each element in the DOMQuery, this retrieves the previous sibling
919
     * (if any). If a selector is supplied, it retrieves the first matching
920
     * sibling (if any is found).
921
     *
922
     * @param string $selector
923
     *  A valid CSS 3 selector.
924
     * @return \QueryPath\DOMQuery
925
     *  A DOMNode object, now containing any previous siblings that have been
926
     *  found.
927
     * @throws Exception
928
     * @throws ParseException
929
     * @see prevAll()
930
     * @see next()
931
     * @see siblings()
932
     * @see children()
933
     */
934
    public function prev($selector = NULL): Query
935
    {
936
        $found = new \SplObjectStorage();
937
        foreach ($this->matches as $m) {
938
            while (isset($m->previousSibling)) {
939
                $m = $m->previousSibling;
940
                if ($m->nodeType === XML_ELEMENT_NODE) {
941
                    if (!empty($selector)) {
942
                        if (QueryPath::with($m, NULL, $this->options)->is($selector)) {
943
                            $found->attach($m);
944
                            break;
945
                        }
946
                    } else {
947
                        $found->attach($m);
948
                        break;
949
                    }
950
                }
951
            }
952
        }
953
954
        return $this->inst($found, NULL);
955
    }
956
957
    /**
958
     * Get the previous siblings for each element in the DOMQuery.
959
     *
960
     * For each element in the DOMQuery, get all previous siblings. If a
961
     * selector is provided, only matching siblings will be retrieved.
962
     *
963
     * @param string $selector
964
     *  A valid CSS 3 selector.
965
     * @return \QueryPath\DOMQuery
966
     *  The DOMQuery object, now wrapping previous sibling elements.
967
     * @see prev()
968
     * @see nextAll()
969
     * @see siblings()
970
     * @see contents()
971
     * @see children()
972
     * @throws ParseException
973
     * @throws \QueryPath\Exception
974
     */
975
    public function prevAll($selector = NULL): Query
976
    {
977
        $found = new \SplObjectStorage();
978
        foreach ($this->matches as $m) {
979
            while (isset($m->previousSibling)) {
980
                $m = $m->previousSibling;
981
                if ($m->nodeType === XML_ELEMENT_NODE) {
982
                    if (!empty($selector)) {
983
                        if (QueryPath::with($m, NULL, $this->options)->is($selector)) {
984
                            $found->attach($m);
985
                        }
986
                    } else {
987
                        $found->attach($m);
988
                    }
989
                }
990
            }
991
        }
992
993
        return $this->inst($found, NULL);
994
    }
995
996
    /**
997
     * Get the children of the elements in the DOMQuery object.
998
     *
999
     * If a selector is provided, the list of children will be filtered through
1000
     * the selector.
1001
     *
1002
     * @param string $selector
1003
     *  A valid selector.
1004
     * @return \QueryPath\DOMQuery
1005
     *  A DOMQuery wrapping all of the children.
1006
     * @see removeChildren()
1007
     * @see parent()
1008
     * @see parents()
1009
     * @see next()
1010
     * @see prev()
1011
     * @throws ParseException
1012
     */
1013
    public function children($selector = NULL): Query
1014
    {
1015
        $found = new \SplObjectStorage();
1016
        $filter = strlen($selector) > 0;
0 ignored issues
show
Bug introduced by
It seems like $selector can also be of type null; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1016
        $filter = strlen(/** @scrutinizer ignore-type */ $selector) > 0;
Loading history...
1017
1018
        if ($filter) {
1019
            $tmp = new \SplObjectStorage();
1020
        }
1021
        foreach ($this->matches as $m) {
1022
            foreach ($m->childNodes as $c) {
1023
                if ($c->nodeType === XML_ELEMENT_NODE) {
1024
                    // This is basically an optimized filter() just for children().
1025
                    if ($filter) {
1026
                        $tmp->attach($c);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tmp does not seem to be defined for all execution paths leading up to this point.
Loading history...
1027
                        $query = new DOMTraverser($tmp, true, $c);
1028
                        $query->find($selector);
1029
                        if (count($query->matches()) > 0) {
1030
                            $found->attach($c);
1031
                        }
1032
                        $tmp->detach($c);
1033
1034
                    } // No filter. Just attach it.
1035
                    else {
1036
                        $found->attach($c);
1037
                    }
1038
                }
1039
            }
1040
        }
1041
1042
        return $this->inst($found, NULL);
1043
    }
1044
1045
    /**
1046
     * Get all child nodes (not just elements) of all items in the matched set.
1047
     *
1048
     * It gets only the immediate children, not all nodes in the subtree.
1049
     *
1050
     * This does not process iframes. Xinclude processing is dependent on the
1051
     * DOM implementation and configuration.
1052
     *
1053
     * @return \QueryPath\DOMQuery
1054
     *  A DOMNode object wrapping all child nodes for all elements in the
1055
     *  DOMNode object.
1056
     * @see find()
1057
     * @see text()
1058
     * @see html()
1059
     * @see innerHTML()
1060
     * @see xml()
1061
     * @see innerXML()
1062
     * @throws ParseException
1063
     */
1064
    public function contents(): Query
1065
    {
1066
        $found = new \SplObjectStorage();
1067
        foreach ($this->matches as $m) {
1068
            if (empty($m->childNodes)) {
1069
                continue;
1070
            }
1071
            foreach ($m->childNodes as $c) {
1072
                $found->attach($c);
1073
            }
1074
        }
1075
1076
        return $this->inst($found, NULL);
1077
    }
1078
1079
    /**
1080
     * Get a list of siblings for elements currently wrapped by this object.
1081
     *
1082
     * This will compile a list of every sibling of every element in the
1083
     * current list of elements.
1084
     *
1085
     * Note that if two siblings are present in the DOMQuery object to begin with,
1086
     * then both will be returned in the matched set, since they are siblings of each
1087
     * other. In other words,if the matches contain a and b, and a and b are siblings of
1088
     * each other, than running siblings will return a set that contains
1089
     * both a and b.
1090
     *
1091
     * @param string $selector
1092
     *  If the optional selector is provided, siblings will be filtered through
1093
     *  this expression.
1094
     * @return \QueryPath\DOMQuery
1095
     *  The DOMQuery containing the matched siblings.
1096
     * @see contents()
1097
     * @see children()
1098
     * @see parent()
1099
     * @see parents()
1100
     * @throws ParseException
1101
     * @throws ParseException
1102
     */
1103
    public function siblings($selector = NULL): Query
1104
    {
1105
        $found = new \SplObjectStorage();
1106
        foreach ($this->matches as $m) {
1107
            $parent = $m->parentNode;
1108
            foreach ($parent->childNodes as $n) {
1109
                if ($n->nodeType === XML_ELEMENT_NODE && $n !== $m) {
1110
                    $found->attach($n);
1111
                }
1112
            }
1113
        }
1114
        if (empty($selector)) {
1115
            return $this->inst($found, NULL);
1116
        }
1117
1118
        return $this->inst($found, NULL)->filter($selector);
1119
    }
1120
}
1121