Passed
Pull Request — master (#262)
by
unknown
44:48 queued 19:43
created

Element::getSelector()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 12
nc 4
nop 0
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 5
rs 9.5555
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace League\HTMLToMarkdown;
6
7
class Element implements ElementInterface
8
{
9
    /** @var \DOMNode */
10
    protected $node;
11
12
    /** @var ElementInterface|null */
13
    private $nextCached;
14
15
    /** @var \DOMNode|null */
16
    private $previousSiblingCached;
17
18
    public function __construct(\DOMNode $node)
19
    {
20
        $this->node = $node;
21
22 96
        $this->previousSiblingCached = $this->node->previousSibling;
23
    }
24 96
25 96
    public function isBlock(): bool
26 96
    {
27
        switch ($this->getTagName()) {
28
            case 'blockquote':
29
            case 'body':
30
            case 'div':
31 18
            case 'h1':
32
            case 'h2':
33 18
            case 'h3':
34 18
            case 'h4':
35 18
            case 'h5':
36 18
            case 'h6':
37 18
            case 'hr':
38 18
            case 'html':
39 18
            case 'li':
40 18
            case 'p':
41 18
            case 'ol':
42 18
            case 'ul':
43 18
                return true;
44 18
            default:
45 18
                return false;
46 17
        }
47 17
    }
48 17
49 3
    public function isText(): bool
50 10
    {
51 15
        return $this->getTagName() === '#text';
52 10
    }
53
54
    public function isWhitespace(): bool
55
    {
56
        return $this->getTagName() === '#text' && \trim($this->getValue()) === '';
57
    }
58 18
59
    public function getNode(): ?\DOMNode
60 18
    {
61
        return $this->node;
62
    }
63
    
64
    public function getTagName(): string
65
    {
66 9
        return $this->node->nodeName;
67
    }
68 9
69
    public function getValue(): string
70
    {
71
        return $this->node->nodeValue ?? '';
72
    }
73
74 96
    public function hasParent(): bool
75
    {
76 96
        return $this->node->parentNode !== null;
77
    }
78
79
    public function getParent(): ?ElementInterface
80
    {
81
        return $this->node->parentNode ? new self($this->node->parentNode) : null;
82 84
    }
83
84 84
    public function getNextSibling(): ?ElementInterface
85
    {
86
        return $this->node->nextSibling !== null ? new self($this->node->nextSibling) : null;
87
    }
88
89
    public function getPreviousSibling(): ?ElementInterface
90 81
    {
91
        return $this->previousSiblingCached !== null ? new self($this->previousSiblingCached) : null;
92 81
    }
93
94
    public function hasChildren(): bool
95
    {
96
        return $this->node->hasChildNodes();
97
    }
98 18
99
    /**
100 18
     * @return ElementInterface[]
101
     */
102
    public function getChildren(): array
103
    {
104
        $ret = [];
105
        foreach ($this->node->childNodes as $node) {
106 18
            /** @psalm-suppress RedundantCondition */
107
            \assert($node instanceof \DOMNode);
108 18
            $ret[] = new self($node);
109
        }
110
111
        return $ret;
112
    }
113
114 96
    public function getNext(): ?ElementInterface
115
    {
116 96
        if ($this->nextCached === null) {
117
            $nextNode = $this->getNextNode($this->node);
118
            if ($nextNode !== null) {
119
                $this->nextCached = new self($nextNode);
120
            }
121
        }
122 96
123
        return $this->nextCached;
124 96
    }
125
126 96
    private function getNextNode(\DOMNode $node, bool $checkChildren = true): ?\DOMNode
127 96
    {
128 64
        if ($checkChildren && $node->firstChild) {
129
            return $node->firstChild;
130 96
        }
131
132
        if ($node->nextSibling) {
133
            return $node->nextSibling;
134
        }
135
136 24
        if ($node->parentNode) {
137
            return $this->getNextNode($node->parentNode, false);
138 24
        }
139 24
140 24
        return null;
141 24
    }
142 16
143 16
    /**
144
     * @param string[]|string $tagNames
145 24
     */
146
    public function isDescendantOf($tagNames): bool
147
    {
148
        if (! \is_array($tagNames)) {
149
            $tagNames = [$tagNames];
150
        }
151
152
        for ($p = $this->node->parentNode; $p !== null; $p = $p->parentNode) {
153
            if (\in_array($p->nodeName, $tagNames, true)) {
154 24
                return true;
155
            }
156 24
        }
157
158
        return false;
159
    }
160 24
161 24
    public function setFinalMarkdown(string $markdown): void
162
    {
163
        if ($this->node->ownerDocument === null) {
164 6
            throw new \RuntimeException('Unowned node');
165 6
        }
166
167
        if ($this->node->parentNode === null) {
168
            throw new \RuntimeException('Cannot setFinalMarkdown() on a node without a parent');
169
        }
170
171
        $markdownNode = $this->node->ownerDocument->createTextNode($markdown);
172
        $this->node->parentNode->replaceChild($markdownNode, $this->node);
173
    }
174 96
175
    public function getChildrenAsString(): string
176 96
    {
177 6
        return $this->node->C14N();
178 4
    }
179
180 96
    public function getSiblingPosition(): int
181 96
    {
182 96
        $position = 0;
183
184
        $parent = $this->getParent();
185 96
        if ($parent === null) {
186 24
            return $position;
187
        }
188 64
189
        // Loop through all nodes and find the given $node
190
        foreach ($parent->getChildren() as $currentNode) {
191
            if (! $currentNode->isWhitespace()) {
192
                $position++;
193
            }
194
195
            // TODO: Need a less-buggy way of comparing these
196 96
            // Perhaps we can somehow ensure that we always have the exact same object and use === instead?
197
            if ($this->equals($currentNode)) {
198 96
                break;
199 96
            }
200 96
        }
201
202
        return $position;
203
    }
204
205 87
    public function getListItemLevel(): int
206
    {
207 87
        $level  = 0;
208
        $parent = $this->getParent();
209
210
        while ($parent !== null && $parent->hasParent()) {
211
            if ($parent->getTagName() === 'li') {
212
                $level++;
213 9
            }
214
215 9
            $parent = $parent->getParent();
216
        }
217
218 9
        return $level;
219 9
    }
220 9
221 6
    public function getAttribute(string $name): string
222
    {
223
        if ($this->node instanceof \DOMElement) {
224
            return $this->node->getAttribute($name);
225 9
        }
226 9
227
        return '';
228 6
    }
229
    
230 9
    public function getSelector(): string {
231
        $element = $this;
232
        if (!empty($element->getAttribute('id'))) {
233
            return '#' . $element->getAttribute('id');
234
        }
235
        $path = [];
236 12
        while ($element && $element->getTagName() !== 'body') {
237
            $part = $element->getTagName();
238 12
            $index = $element->getSiblingPosition();
239 12
            if ($index > 0) {
240
                $part .= ':nth-child(' . $index . ')';
241 12
            }
242 12
            array_unshift($path, $part);
243 6
            $element = $element->getParent();
244 4
        }
245 12
        return implode(' > ', $path);
246 8
    }
247
248 12
    public function equals(ElementInterface $element): bool
249
    {
250
        if ($element instanceof self) {
251
            return $element->node === $this->node;
252
        }
253
254
        return false;
255
    }
256
}
257