Passed
Pull Request — latest (#3)
by Mark
35:03
created

Node::__clone()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
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 was originally 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 UnicornFail\Emoji\Node;
18
19
abstract class Node
20
{
21
    /**
22
     * @var int
23
     *
24
     * @psalm-readonly-allow-private-mutation
25
     */
26
    protected $depth = 0;
27
28
    /**
29
     * @var Node|null
30
     *
31
     * @psalm-readonly-allow-private-mutation
32
     */
33
    protected $parent;
34
35
    /**
36
     * @var Node|null
37
     *
38
     * @psalm-readonly-allow-private-mutation
39
     */
40
    protected $previous;
41
42
    /**
43
     * @var Node|null
44
     *
45
     * @psalm-readonly-allow-private-mutation
46
     */
47
    protected $next;
48
49
    /**
50
     * @var Node|null
51
     *
52
     * @psalm-readonly-allow-private-mutation
53
     */
54
    protected $firstChild;
55
56
    /**
57
     * @var Node|null
58
     *
59
     * @psalm-readonly-allow-private-mutation
60
     */
61
    protected $lastChild;
62
63
    public function previous(): ?Node
64
    {
65
        return $this->previous;
66
    }
67
68
    public function next(): ?Node
69
    {
70
        return $this->next;
71
    }
72
73
    public function parent(): ?Node
74
    {
75
        return $this->parent;
76
    }
77
78
    protected function setParent(?Node $node = null): void
79
    {
80
        $this->parent = $node;
81
        $this->depth  = $node === null
82
            ? 0
83
            : $node->depth + 1;
84
    }
85
86
    /**
87
     * Inserts the $sibling node after $this
88
     */
89
    public function insertAfter(Node $sibling): void
90
    {
91
        $sibling->detach();
92
        $sibling->next = $this->next;
93
94
        if ($sibling->next) {
95
            $sibling->next->previous = $sibling;
96
        }
97
98
        $sibling->previous = $this;
99
        $this->next        = $sibling;
100
        $sibling->setParent($this->parent);
101
102
        if (! $sibling->next && $sibling->parent) {
103
            $sibling->parent->lastChild = $sibling;
104
        }
105
    }
106
107
    /**
108
     * Inserts the $sibling node before $this
109
     */
110
    public function insertBefore(Node $sibling): void
111
    {
112
        $sibling->detach();
113
        $sibling->previous = $this->previous;
114
115
        if ($sibling->previous) {
116
            $sibling->previous->next = $sibling;
117
        }
118
119
        $sibling->next  = $this;
120
        $this->previous = $sibling;
121
        $sibling->setParent($this->parent);
122
123
        if (! $sibling->previous && $sibling->parent) {
124
            $sibling->parent->firstChild = $sibling;
125
        }
126
    }
127
128
    public function replaceWith(Node $replacement): void
129
    {
130
        $replacement->detach();
131
        $this->insertAfter($replacement);
132
        $this->detach();
133
    }
134
135
    public function detach(): void
136
    {
137
        if ($this->previous) {
138
            $this->previous->next = $this->next;
139
        } elseif ($this->parent) {
140
            $this->parent->firstChild = $this->next;
141
        }
142
143
        if ($this->next) {
144
            $this->next->previous = $this->previous;
145
        } elseif ($this->parent) {
146
            $this->parent->lastChild = $this->previous;
147
        }
148
149
        $this->parent   = null;
150
        $this->next     = null;
151
        $this->previous = null;
152
        $this->depth    = 0;
153
    }
154
155
    public function hasChildren(): bool
156
    {
157
        return $this->firstChild !== null;
158
    }
159
160
    public function firstChild(): ?Node
161
    {
162
        return $this->firstChild;
163
    }
164
165
    public function lastChild(): ?Node
166
    {
167
        return $this->lastChild;
168
    }
169
170
    /**
171
     * @return Node[]
172
     */
173
    public function children(): iterable
174
    {
175
        $children = [];
176
        for ($current = $this->firstChild; $current !== null; $current = $current->next) {
177
            $children[] = $current;
178
        }
179
180
        return $children;
181
    }
182
183
    public function appendChild(Node $child): void
184
    {
185
        if ($this->lastChild) {
186
            $this->lastChild->insertAfter($child);
187
        } else {
188
            $child->detach();
189
            $child->setParent($this);
190
            $this->lastChild = $this->firstChild = $child;
191
        }
192
    }
193
194
    /**
195
     * Adds $child as the very first child of $this
196
     */
197
    public function prependChild(Node $child): void
198
    {
199
        if ($this->firstChild) {
200
            $this->firstChild->insertBefore($child);
201
        } else {
202
            $child->detach();
203
            $child->setParent($this);
204
            $this->lastChild = $this->firstChild = $child;
205
        }
206
    }
207
208
    /**
209
     * Detaches all child nodes of given node
210
     */
211
    public function detachChildren(): void
212
    {
213
        foreach ($this->children() as $children) {
214
            $children->setParent(null);
215
        }
216
217
        $this->firstChild = $this->lastChild = null;
218
    }
219
220
    /**
221
     * Replace all children of given node with collection of another
222
     *
223
     * @param iterable<Node> $children
224
     */
225
    public function replaceChildren(iterable $children): void
226
    {
227
        $this->detachChildren();
228
        foreach ($children as $item) {
229
            $this->appendChild($item);
230
        }
231
    }
232
233
    public function getDepth(): int
234
    {
235
        return $this->depth;
236
    }
237
238
    public function walker(): NodeWalker
239
    {
240
        return new NodeWalker($this);
241
    }
242
243
    /**
244
     * Clone the current node and its children
245
     *
246
     * WARNING: This is a recursive function and should not be called on deeply-nested node trees!
247
     */
248
    public function __clone()
249
    {
250
        // Cloned nodes are detached from their parents, siblings, and children
251
        $this->parent   = null;
252
        $this->previous = null;
253
        $this->next     = null;
254
        // But save a copy of the children since we'll need that in a moment
255
        $children = $this->children();
256
        $this->detachChildren();
257
258
        // The original children get cloned and re-added
259
        foreach ($children as $child) {
260
            $this->appendChild(clone $child);
261
        }
262
    }
263
}
264