Completed
Branch scrutinizer (a0bbb1)
by Thomas
10:52
created

Selector::relationResolve()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 14
c 1
b 0
f 0
nc 10
nop 2
dl 0
loc 26
rs 8.0555
1
<?php
2
3
namespace Sulao\HtmlQuery;
4
5
use Closure;
6
use DOMDocument, DOMNode, DOMNodeList, DOMXPath;
7
use ReflectionFunction;
8
9
/**
10
 * Trait Selector
11
 *
12
 * @package Sulao\HtmlQuery
13
 */
14
trait Selector
15
{
16
    use Resolver;
17
18
    /**
19
     * @var DOMDocument
20
     */
21
    protected $doc;
22
23
    /**
24
     * @var DOMNode[]
25
     */
26
    protected $nodes;
27
28
    /**
29
     *  Make the static object can be called as a function.
30
     *
31
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
32
     *
33
     * @return static|null
34
     */
35
    public function __invoke($selector)
36
    {
37
        if (is_string($selector)) {
38
            return $this->query($selector);
39
        }
40
41
        return $this->resolve($selector);
42
    }
43
44
    /**
45
     * If the parameter is raw html, then create document fragment for it,
46
     * If the parameter is a css selector, get the descendants
47
     * of each current node, filtered by a css selector.
48
     * If the parameter is selection, resolve that selection
49
     *
50
     * @param string $selector css selector or raw html
51
     *
52
     * @return static
53
     */
54
    public function query(string $selector)
55
    {
56
        if (Helper::isRawHtml($selector)) {
57
            return $this->htmlResolve($selector);
58
        }
59
60
        return $this->targetResolve($selector);
61
    }
62
63
    /**
64
     * Get the descendants of each matched node, filtered by a selector.
65
     *
66
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
67
     *
68
     * @return static
69
     */
70
    public function find($selector)
71
    {
72
        if (is_string($selector)) {
73
            $selection = $this->xpathFind(Helper::toXpath($selector));
74
75
            return Helper::isIdSelector($selector)
76
                ? $this->resolve($selection->nodes[0] ?? [])
77
                : $selection;
78
        }
79
80
        $descendants = $this->xpathFind('descendant::*');
81
82
        return $descendants->intersect($selector);
83
    }
84
85
    /**
86
     * Get the nodes in the current nodes filtered by a selector.
87
     *
88
     * @param string|Closure|DOMNode|DOMNode[]|DOMNodeList|static $selector
89
     *
90
     * @return static
91
     */
92
    public function filter($selector)
93
    {
94
        if (is_string($selector)) {
95
            $xpath = Helper::toXpath($selector, 'self::');
96
            return $this->xpathFind($xpath);
97
        } elseif ($selector instanceof Closure) {
98
            $resolve = $this->shouldResolve($selector, 1);
99
100
            $nodes = [];
101
            foreach ($this->nodes as $key => $node) {
102
                if ($selector($key, $resolve ? $this->resolve($node) : $node)) {
103
                    $nodes[] = $node;
104
                }
105
            }
106
107
            return $this->resolve($nodes);
108
        }
109
110
        return $this->intersect($selector);
111
    }
112
113
    /**
114
     * Get the parent of each node in the current nodes,
115
     * optionally filtered by a css selector.
116
     *
117
     * @param string|null $selector
118
     *
119
     * @return static
120
     */
121
    public function parent(?string $selector = null)
122
    {
123
        $selector = is_null($selector) ? '*' : $selector;
124
        $xpath = Helper::toXpath($selector, 'parent::');
125
126
        return $this->xpathFind($xpath);
127
    }
128
129
    /**
130
     * Get the ancestors of each node in the current nodes,
131
     * optionally filtered by a css selector.
132
     *
133
     * @param string|null $selector
134
     *
135
     * @return static
136
     */
137
    public function parents(?string $selector = null)
138
    {
139
        $selector = is_null($selector) ? '*' : $selector;
140
        $xpath = Helper::toXpath($selector, 'ancestor::');
141
142
        return $this->xpathFind($xpath);
143
    }
144
145
    /**
146
     * Get the ancestors of each node in the current nodes,
147
     * up to but not including the node matched by the selector.
148
     *
149
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
150
     *
151
     * @return static
152
     */
153
    public function parentsUntil($selector)
154
    {
155
        return $this->relationResolve('parentNode', $selector);
156
    }
157
158
    /**
159
     * Get the children of each node in the current nodes,
160
     * optionally filtered by a css selector.
161
     *
162
     * @param string|null $selector
163
     *
164
     * @return static
165
     */
166
    public function children(?string $selector = null)
167
    {
168
        $selector = is_null($selector) ? '*' : $selector;
169
        $xpath = Helper::toXpath($selector, 'child::');
170
171
        return $this->xpathFind($xpath);
172
    }
173
174
    /**
175
     * Get the siblings of each node in the current nodes,
176
     * optionally filtered by a css selector.
177
     *
178
     * @param string|null $selector
179
     *
180
     * @return static
181
     */
182
    public function siblings(?string $selector = null)
183
    {
184
        $xpath = is_null($selector) ? '*' : Helper::toXpath($selector, '');
185
        $xpath = "preceding-sibling::{$xpath}|following-sibling::{$xpath}";
186
187
        return $this->xpathFind($xpath);
188
    }
189
190
    /**
191
     * Get the immediately preceding sibling of each node in the current nodes,
192
     * optionally filtered by a css selector.
193
     *
194
     * @param string|null $selector
195
     *
196
     * @return static
197
     */
198
    public function prev(?string $selector = null)
199
    {
200
        $xpath = is_null($selector) ? '*' : Helper::toXpath($selector, '');
201
        $xpath = "preceding-sibling::{$xpath}[1]";
202
203
        return $this->xpathFind($xpath);
204
    }
205
206
    /**
207
     * Get all preceding siblings of each node in the current nodes,
208
     * optionally filtered by a css selector
209
     *
210
     * @param string|null $selector
211
     *
212
     * @return static
213
     */
214
    public function prevAll(?string $selector = null)
215
    {
216
        $xpath = is_null($selector) ? '*' : Helper::toXpath($selector, '');
217
        $xpath = "preceding-sibling::{$xpath}";
218
219
        return $this->xpathFind($xpath);
220
    }
221
222
    /**
223
     * Get all preceding siblings of each node
224
     * up to but not including the node matched by the selector.
225
     *
226
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
227
     *
228
     * @return static
229
     */
230
    public function prevUntil($selector)
231
    {
232
        return $this->relationResolve('previousSibling', $selector);
233
    }
234
235
    /**
236
     * Get the immediately following sibling of each node in the current nodes,
237
     * optionally filtered by a css selector.
238
     *
239
     * @param string|null $selector
240
     *
241
     * @return static
242
     */
243
    public function next(?string $selector = null)
244
    {
245
        $xpath = is_null($selector) ? '*' : Helper::toXpath($selector, '');
246
        $xpath = "following-sibling::{$xpath}[1]";
247
248
        return $this->xpathFind($xpath);
249
    }
250
251
    /**
252
     * Get all following siblings of each node in the current nodes,
253
     * optionally filtered by a selector.
254
     *
255
     * @param string|null $selector
256
     *
257
     * @return static
258
     */
259
    public function nextAll(?string $selector = null)
260
    {
261
        $xpath = is_null($selector) ? '*' : Helper::toXpath($selector, '');
262
        $xpath = "following-sibling::{$xpath}";
263
264
        return $this->xpathFind($xpath);
265
    }
266
267
    /**
268
     * Get all following siblings of each node
269
     * up to but not including the node matched by the selector.
270
     *
271
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
272
     *
273
     * @return static
274
     */
275
    public function nextUntil($selector)
276
    {
277
        return $this->relationResolve('nextSibling', $selector);
278
    }
279
280
    /**
281
     * Create a new static object with nodes added to the current nodes.
282
     *
283
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
284
     *
285
     * @return static
286
     */
287
    public function add($selector)
288
    {
289
        $nodes = $this->targetResolve($selector)->nodes;
290
291
        $nodes = array_merge($this->nodes, $nodes);
292
        $nodes = Helper::strictArrayUnique($nodes);
293
294
        return $this->resolve($nodes);
295
    }
296
297
    /**
298
     * Create a new static object with the intersected nodes
299
     * between current nodes and nodes of the selection.
300
     *
301
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
302
     *
303
     * @return static
304
     */
305
    public function intersect($selector)
306
    {
307
        $selection = $this->targetResolve($selector);
308
309
        return $this->resolve(
310
            Helper::strictArrayIntersect($this->nodes, $selection->nodes)
311
        );
312
    }
313
314
    /**
315
     * Remove nodes from the current nodes.
316
     *
317
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
318
     *
319
     * @return static
320
     */
321
    public function not($selector)
322
    {
323
        $nodes = $this->targetResolve($selector)->nodes;
324
        $nodes = Helper::strictArrayDiff($this->nodes, $nodes);
325
326
        return $this->resolve($nodes);
327
    }
328
329
    /**
330
     * Check the current matched nodes against a selector, node(s), or static
331
     * object and return true if at least one of nodes matches.
332
     *
333
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
334
     *
335
     * @return bool
336
     */
337
    public function is($selector): bool
338
    {
339
        if (count($this->nodes)) {
340
            return (bool) count($this->intersect($selector)->nodes);
341
        }
342
343
        return false;
344
    }
345
}
346