Selector   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 69
c 1
b 0
f 0
dl 0
loc 334
ccs 84
cts 84
cp 1
rs 9.44
wmc 37

19 Methods

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