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