Passed
Push — node-iterator ( da69c7...1da87a )
by Colin
40:48 queued 07:54
created

Node::__clone()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 13
rs 10
cc 2
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the league/commonmark package.
7
 *
8
 * (c) Colin O'Dell <[email protected]>
9
 *
10
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
11
 *  - (c) John MacFarlane
12
 *
13
 * For the full copyright and license information, please view the LICENSE
14
 * file that was distributed with this source code.
15
 */
16
17
namespace League\CommonMark\Node;
18
19
use Dflydev\DotAccessData\Data;
20
21
abstract class Node
22
{
23
    /** @psalm-readonly */
24
    public Data $data;
25
26
    /** @psalm-readonly-allow-private-mutation */
27
    protected int $depth = 0;
28
29
    /** @psalm-readonly-allow-private-mutation */
30
    protected ?Node $parent = null;
31
32
    /** @psalm-readonly-allow-private-mutation */
33
    protected ?Node $previous = null;
34
35
    /** @psalm-readonly-allow-private-mutation */
36
    protected ?Node $next = null;
37
38
    /** @psalm-readonly-allow-private-mutation */
39
    protected ?Node $firstChild = null;
40
41
    /** @psalm-readonly-allow-private-mutation */
42
    protected ?Node $lastChild = null;
43
44
    public function __construct()
45
    {
46
        $this->data = new Data([
47
            'attributes' => [],
48
        ]);
49
    }
50
51
    public function previous(): ?Node
52
    {
53
        return $this->previous;
54
    }
55
56
    public function next(): ?Node
57
    {
58
        return $this->next;
59
    }
60
61
    public function parent(): ?Node
62
    {
63
        return $this->parent;
64
    }
65
66
    protected function setParent(?Node $node = null): void
67
    {
68
        $this->parent = $node;
69
        $this->depth  = $node === null ? 0 : $node->depth + 1;
70
    }
71
72
    /**
73
     * Inserts the $sibling node after $this
74
     */
75
    public function insertAfter(Node $sibling): void
76
    {
77
        $sibling->detach();
78
        $sibling->next = $this->next;
79
80
        if ($sibling->next) {
81
            $sibling->next->previous = $sibling;
82
        }
83
84
        $sibling->previous = $this;
85
        $this->next        = $sibling;
86
        $sibling->setParent($this->parent);
87
88
        if (! $sibling->next && $sibling->parent) {
89
            $sibling->parent->lastChild = $sibling;
90
        }
91
    }
92
93
    /**
94
     * Inserts the $sibling node before $this
95
     */
96
    public function insertBefore(Node $sibling): void
97
    {
98
        $sibling->detach();
99
        $sibling->previous = $this->previous;
100
101
        if ($sibling->previous) {
102
            $sibling->previous->next = $sibling;
103
        }
104
105
        $sibling->next  = $this;
106
        $this->previous = $sibling;
107
        $sibling->setParent($this->parent);
108
109
        if (! $sibling->previous && $sibling->parent) {
110
            $sibling->parent->firstChild = $sibling;
111
        }
112
    }
113
114
    public function replaceWith(Node $replacement): void
115
    {
116
        $replacement->detach();
117
        $this->insertAfter($replacement);
118
        $this->detach();
119
    }
120
121
    public function detach(): void
122
    {
123
        if ($this->previous) {
124
            $this->previous->next = $this->next;
125
        } elseif ($this->parent) {
126
            $this->parent->firstChild = $this->next;
127
        }
128
129
        if ($this->next) {
130
            $this->next->previous = $this->previous;
131
        } elseif ($this->parent) {
132
            $this->parent->lastChild = $this->previous;
133
        }
134
135
        $this->parent   = null;
136
        $this->next     = null;
137
        $this->previous = null;
138
        $this->depth    = 0;
139
    }
140
141
    public function hasChildren(): bool
142
    {
143
        return $this->firstChild !== null;
144
    }
145
146
    public function firstChild(): ?Node
147
    {
148
        return $this->firstChild;
149
    }
150
151
    public function lastChild(): ?Node
152
    {
153
        return $this->lastChild;
154
    }
155
156
    /**
157
     * @return Node[]
158
     */
159
    public function children(): iterable
160
    {
161
        $children = [];
162
        for ($current = $this->firstChild; $current !== null; $current = $current->next) {
163
            $children[] = $current;
164
        }
165
166
        return $children;
167
    }
168
169
    public function appendChild(Node $child): void
170
    {
171
        if ($this->lastChild) {
172
            $this->lastChild->insertAfter($child);
173
        } else {
174
            $child->detach();
175
            $child->setParent($this);
176
            $this->lastChild = $this->firstChild = $child;
177
        }
178
    }
179
180
    /**
181
     * Adds $child as the very first child of $this
182
     */
183
    public function prependChild(Node $child): void
184
    {
185
        if ($this->firstChild) {
186
            $this->firstChild->insertBefore($child);
187
        } else {
188
            $child->detach();
189
            $child->setParent($this);
190
            $this->lastChild = $this->firstChild = $child;
191
        }
192
    }
193
194
    /**
195
     * Detaches all child nodes of given node
196
     */
197
    public function detachChildren(): void
198
    {
199
        foreach ($this->children() as $children) {
200
            $children->setParent(null);
201
        }
202
203
        $this->firstChild = $this->lastChild = null;
204
    }
205
206
    /**
207
     * Replace all children of given node with collection of another
208
     *
209
     * @param iterable<Node> $children
210
     */
211
    public function replaceChildren(iterable $children): void
212
    {
213
        $this->detachChildren();
214
        foreach ($children as $item) {
215
            $this->appendChild($item);
216
        }
217
    }
218
219
    public function getDepth(): int
220
    {
221
        return $this->depth;
222
    }
223
224
    public function walker(): NodeWalker
225
    {
226
        return new NodeWalker($this);
227
    }
228
229
    public function iterator(int $flags = 0): NodeIterator
230
    {
231
        return new NodeIterator($this, $flags);
232
    }
233
234
    /**
235
     * Clone the current node and its children
236
     *
237
     * WARNING: This is a recursive function and should not be called on deeply-nested node trees!
238
     */
239
    public function __clone()
240
    {
241
        // Cloned nodes are detached from their parents, siblings, and children
242
        $this->parent   = null;
243
        $this->previous = null;
244
        $this->next     = null;
245
        // But save a copy of the children since we'll need that in a moment
246
        $children = $this->children();
247
        $this->detachChildren();
248
249
        // The original children get cloned and re-added
250
        foreach ($children as $child) {
251
            $this->appendChild(clone $child);
252
        }
253
    }
254
255
    public static function assertInstanceOf(Node $node): void
256
    {
257
        if (! $node instanceof static) {
0 ignored issues
show
introduced by
$node is always a sub-type of static.
Loading history...
258
            throw new \InvalidArgumentException(\sprintf('Incompatible node type: expected %s, got %s', static::class, \get_class($node)));
259
        }
260
    }
261
}
262