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

Resolver::relationResolve()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 8
eloc 13
c 1
b 1
f 0
nc 10
nop 2
dl 0
loc 26
rs 8.4444
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 Resolver
15
{
16
    /**
17
     * @var DOMDocument
18
     */
19
    protected $doc;
20
21
    /**
22
     * @var DOMNode[]
23
     */
24
    protected $nodes;
25
26
    /**
27
     * Selector constructor.
28
     *
29
     * @param DOMDocument                          $doc
30
     * @param DOMNode|DOMNode[]|DOMNodeList|static $nodes
31
     *
32
     * @return static
33
     */
34
    abstract public function __construct(DOMDocument $doc, $nodes);
35
36
    /**
37
     * Get the descendants of each matched node, filtered by a selector.
38
     *
39
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
40
     *
41
     * @return static
42
     */
43
    abstract public function find($selector);
44
45
    /**
46
     * Resolve DOMNode(s) to a static instance.
47
     *
48
     * @param DOMNode|DOMNode[]|DOMNodeList|static $nodes
49
     *
50
     * @return static
51
     */
52
    protected function resolve($nodes)
53
    {
54
        if ($nodes instanceof static) {
55
            return $nodes;
56
        }
57
58
        return new static($this->doc, $nodes);
59
    }
60
61
    /**
62
     * If the parameter is a css selector, get the descendants
63
     * of dom document filtered by the css selector.
64
     * If the parameter is selection, resolve that selection to static object.
65
     *
66
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
67
     *
68
     * @return static
69
     */
70
    protected function targetResolve($selector)
71
    {
72
        if (is_string($selector)) {
73
            return $this->resolve($this->doc)->find($selector);
74
        }
75
76
        return $this->resolve($selector);
77
    }
78
79
    /**
80
     * If the parameter is string, consider it as raw html,
81
     * then create document fragment for it.
82
     * If the parameter is selection, resolve that selection to static instance.
83
     *
84
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
85
     *
86
     * @return static
87
     */
88
    protected function contentResolve($content)
89
    {
90
        if (is_string($content)) {
91
            return $this->htmlResolve($content);
92
        }
93
94
        return $this->resolve($content);
95
    }
96
97
    /**
98
     * Resolve the html content to static instance.
99
     *
100
     * @param string $html
101
     *
102
     * @return static
103
     */
104
    protected function htmlResolve(string $html)
105
    {
106
        $frag = $this->doc->createDocumentFragment();
107
        $frag->appendXML($html);
108
109
        return $this->resolve($frag);
110
    }
111
112
    /**
113
     * Resolve the nodes under the relation to static instance.
114
     * up to but not including the node matched by the $until selector.
115
     *
116
     * @param string $relation
117
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $until
118
     *
119
     * @return static
120
     */
121
    protected function relationResolve(string $relation, ?string $until = null)
122
    {
123
        $untilNodes = !is_null($until)
124
            ? $this->targetResolve($until)->nodes
125
            : [];
126
127
        $nodes = [];
128
        foreach ($this->nodes as $node) {
129
            while (($node = $node->$relation)
130
                && $node->nodeType !== XML_DOCUMENT_NODE
131
            ) {
132
                if ($node->nodeType !== XML_ELEMENT_NODE) {
133
                    continue;
134
                }
135
136
                if (in_array($node, $untilNodes, true)) {
137
                    break;
138
                }
139
140
                if (!in_array($node, $nodes, true)) {
141
                    $nodes[] = $node;
142
                }
143
            }
144
        }
145
146
        return $this->resolve($nodes);
147
    }
148
149
    /**
150
     * Determine where the parameter of the closure should resolve to static,
151
     * or just leave it as DOMNode
152
     *
153
     * @param Closure $function
154
     * @param int     $index    Which parameter of the closure, starts with 0
155
     *
156
     * @return bool
157
     */
158
    protected function shouldResolve(Closure $function, $index = 0)
159
    {
160
        $reflection = new ReflectionFunction($function);
161
162
        $parameters = $reflection->getParameters();
163
        if (!empty($parameters) && array_key_exists($index, $parameters)) {
164
            $class = $parameters[$index]->getClass();
165
            if ($class && $class->isInstance($this)) {
166
                return true;
167
            }
168
        }
169
170
        return false;
171
    }
172
173
    /**
174
     * Resolve the xpath to static instance.
175
     *
176
     * @param string $xpath
177
     *
178
     * @return static
179
     */
180
    protected function xpathFind(string $xpath)
181
    {
182
        $nodes = [];
183
        foreach ($this->nodes as $node) {
184
            $nodes = array_merge($nodes, $this->xpathQuery($xpath, $node));
185
        }
186
187
        $nodes = Helper::strictArrayUnique($nodes);
188
189
        return $this->resolve($nodes);
190
    }
191
192
    /**
193
     * Query xpath to an array of DOMNode
194
     *
195
     * @param string       $xpath
196
     * @param DOMNode|null $node
197
     *
198
     * @return DOMNode[]
199
     */
200
    protected function xpathQuery(
201
        string $xpath,
202
        ?DOMNode $node = null
203
    ): array {
204
        $docXpath = new DOMXpath($this->doc);
205
        $nodeList = $docXpath->query($xpath, $node);
206
207
        if (!($nodeList instanceof DOMNodeList)) {
208
            return [];
209
        }
210
211
        return iterator_to_array($nodeList);
212
    }
213
}
214