Element::getPreviousSibling()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 2
rs 10
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 getTagName(): string
60 18
    {
61
        return $this->node->nodeName;
62
    }
63
64
    public function getValue(): string
65
    {
66 9
        return $this->node->nodeValue ?? '';
67
    }
68 9
69
    public function hasParent(): bool
70
    {
71
        return $this->node->parentNode !== null;
72
    }
73
74 96
    public function getParent(): ?ElementInterface
75
    {
76 96
        return $this->node->parentNode ? new self($this->node->parentNode) : null;
77
    }
78
79
    public function getNextSibling(): ?ElementInterface
80
    {
81
        return $this->node->nextSibling !== null ? new self($this->node->nextSibling) : null;
82 84
    }
83
84 84
    public function getPreviousSibling(): ?ElementInterface
85
    {
86
        return $this->previousSiblingCached !== null ? new self($this->previousSiblingCached) : null;
87
    }
88
89
    public function hasChildren(): bool
90 81
    {
91
        return $this->node->hasChildNodes();
92 81
    }
93
94
    /**
95
     * @return ElementInterface[]
96
     */
97
    public function getChildren(): array
98 18
    {
99
        $ret = [];
100 18
        foreach ($this->node->childNodes as $node) {
101
            /** @psalm-suppress RedundantCondition */
102
            \assert($node instanceof \DOMNode);
103
            $ret[] = new self($node);
104
        }
105
106 18
        return $ret;
107
    }
108 18
109
    public function getNext(): ?ElementInterface
110
    {
111
        if ($this->nextCached === null) {
112
            $nextNode = $this->getNextNode($this->node);
113
            if ($nextNode !== null) {
114 96
                $this->nextCached = new self($nextNode);
115
            }
116 96
        }
117
118
        return $this->nextCached;
119
    }
120
121
    private function getNextNode(\DOMNode $node, bool $checkChildren = true): ?\DOMNode
122 96
    {
123
        if ($checkChildren && $node->firstChild) {
124 96
            return $node->firstChild;
125
        }
126 96
127 96
        if ($node->nextSibling) {
128 64
            return $node->nextSibling;
129
        }
130 96
131
        if ($node->parentNode) {
132
            return $this->getNextNode($node->parentNode, false);
133
        }
134
135
        return null;
136 24
    }
137
138 24
    /**
139 24
     * @param string[]|string $tagNames
140 24
     */
141 24
    public function isDescendantOf($tagNames): bool
142 16
    {
143 16
        if (! \is_array($tagNames)) {
144
            $tagNames = [$tagNames];
145 24
        }
146
147
        for ($p = $this->node->parentNode; $p !== null; $p = $p->parentNode) {
148
            if (\in_array($p->nodeName, $tagNames, true)) {
149
                return true;
150
            }
151
        }
152
153
        return false;
154 24
    }
155
156 24
    public function setFinalMarkdown(string $markdown): void
157
    {
158
        if ($this->node->ownerDocument === null) {
159
            throw new \RuntimeException('Unowned node');
160 24
        }
161 24
162
        if ($this->node->parentNode === null) {
163
            throw new \RuntimeException('Cannot setFinalMarkdown() on a node without a parent');
164 6
        }
165 6
166
        $markdownNode = $this->node->ownerDocument->createTextNode($markdown);
167
        $this->node->parentNode->replaceChild($markdownNode, $this->node);
168
    }
169
170
    public function getChildrenAsString(): string
171
    {
172
        return $this->node->C14N();
173
    }
174 96
175
    public function getSiblingPosition(): int
176 96
    {
177 6
        $position = 0;
178 4
179
        $parent = $this->getParent();
180 96
        if ($parent === null) {
181 96
            return $position;
182 96
        }
183
184
        // Loop through all nodes and find the given $node
185 96
        foreach ($parent->getChildren() as $currentNode) {
186 24
            if (! $currentNode->isWhitespace()) {
187
                $position++;
188 64
            }
189
190
            // TODO: Need a less-buggy way of comparing these
191
            // Perhaps we can somehow ensure that we always have the exact same object and use === instead?
192
            if ($this->equals($currentNode)) {
193
                break;
194
            }
195
        }
196 96
197
        return $position;
198 96
    }
199 96
200 96
    public function getListItemLevel(): int
201
    {
202
        $level  = 0;
203
        $parent = $this->getParent();
204
205 87
        while ($parent !== null && $parent->hasParent()) {
206
            if ($parent->getTagName() === 'li') {
207 87
                $level++;
208
            }
209
210
            $parent = $parent->getParent();
211
        }
212
213 9
        return $level;
214
    }
215 9
216
    public function getAttribute(string $name): string
217
    {
218 9
        if ($this->node instanceof \DOMElement) {
219 9
            return $this->node->getAttribute($name);
220 9
        }
221 6
222
        return '';
223
    }
224
225 9
    public function equals(ElementInterface $element): bool
226 9
    {
227
        if ($element instanceof self) {
228 6
            return $element->node === $this->node;
229
        }
230 9
231
        return false;
232
    }
233
}
234