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