Passed
Push — master ( f21bed...142c4d )
by Arthur
02:05
created

QueryFilters::prevAll()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 6
nop 1
dl 0
loc 19
rs 9.2222
c 0
b 0
f 0
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);
0 ignored issues
show
Deprecated Code introduced by
The function create_function() has been deprecated: 7.2 ( Ignorable by Annotation )

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

88
        $function = /** @scrutinizer ignore-deprecated */ create_function('$index, $item', $fn);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The function create_function() has been deprecated: 7.2 ( Ignorable by Annotation )

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

355
            $fn = /** @scrutinizer ignore-deprecated */ create_function('$index, &$item', $lambda);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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;
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