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