encode = $encode; $this->tag->setEncoding($encode); // check children foreach ($this->children as $id => $child) { /** @var AbstractNode $node */ $node = $child['node']; $node->propagateEncoding($encode); } } /** * Checks if this node has children. * * @return bool */ public function hasChildren(): bool { return ! empty($this->children); } /** * Returns the child by id. * * @param int $id * @return AbstractNode * @throws ChildNotFoundException */ public function getChild(int $id): AbstractNode { if ( ! isset($this->children[$id])) { throw new ChildNotFoundException("Child '$id' not found in this node."); } return $this->children[$id]['node']; } /** * Returns a new array of child nodes * * @return array */ public function getChildren(): array { $nodes = []; try { $child = $this->firstChild(); do { $nodes[] = $child; $child = $this->nextChild($child->id()); } while ( ! is_null($child)); } catch (ChildNotFoundException $e) { // we are done looking for children } return $nodes; } /** * Counts children * * @return int */ public function countChildren(): int { return count($this->children); } /** * Adds a child node to this node and returns the id of the child for this * parent. * * @param AbstractNode $child * @param Int $before * @return bool * @throws CircularException */ public function addChild(AbstractNode $child, int $before = -1): bool { $key = null; // check integrity if ($this->isAncestor($child->id())) { throw new CircularException('Can not add child. It is my ancestor.'); } // check if child is itself if ($child->id() == $this->id) { throw new CircularException('Can not set itself as a child.'); } $next = null; if ($this->hasChildren()) { if (isset($this->children[$child->id()])) { // we already have this child return false; } if ($before >= 0) { if (!isset($this->children[$before])) { return false; } $key = $this->children[$before]['prev']; if($key){ $this->children[$key]['next'] = $child->id(); } $this->children[$before]['prev'] = $child->id(); $next = $before; } else { $sibling = $this->lastChild(); $key = $sibling->id(); $this->children[$key]['next'] = $child->id(); } } $keys = array_keys($this->children); $insert = [ 'node' => $child, 'next' => $next, 'prev' => $key, ]; $index = $key ? (array_search($key, $keys, true) + 1) : 0; array_splice($keys, $index, 0, $child->id()); $children = array_values($this->children); array_splice($children, $index, 0, [$insert]); // add the child $this->children = array_combine($keys, $children); // tell child I am the new parent $child->setParent($this); //clear any cache $this->clear(); return true; } /** * Insert element before child with provided id * * @param AbstractNode $child * @param int $id * @return bool */ public function insertBefore(AbstractNode $child, int $id): bool { return $this->addChild($child, $id); } /** * Insert element before after with provided id * * @param AbstractNode $child * @param int $id * @return bool */ public function insertAfter(AbstractNode $child, int $id): bool { if (!isset($this->children[$id])) { return false; } if ($this->children[$id]['next']) { return $this->addChild($child, $this->children[$id]['next']); } // clear cache $this->clear(); return $this->addChild($child); } /** * Removes the child by id. * * @param int $id * @return InnerNode * @chainable */ public function removeChild(int $id): InnerNode { if ( ! isset($this->children[$id])) { return $this; } // handle moving next and previous assignments. $next = $this->children[$id]['next']; $prev = $this->children[$id]['prev']; if ( ! is_null($next)) { $this->children[$next]['prev'] = $prev; } if ( ! is_null($prev)) { $this->children[$prev]['next'] = $next; } // remove the child unset($this->children[$id]); //clear any cache $this->clear(); return $this; } /** * Check if has next Child * * @param int $id * @return mixed */ public function hasNextChild(int $id) { $child= $this->getChild($id); return $this->children[$child->id()]['next']; } /** * Attempts to get the next child. * * @param int $id * @return AbstractNode * @uses $this->getChild() * @throws ChildNotFoundException */ public function nextChild(int $id): AbstractNode { $child = $this->getChild($id); $next = $this->children[$child->id()]['next']; if (is_null($next)) { throw new ChildNotFoundException("Child '$id' next not found in this node."); } return $this->getChild($next); } /** * Attempts to get the previous child. * * @param int $id * @return AbstractNode * @uses $this->getChild() * @throws ChildNotFoundException */ public function previousChild(int $id): AbstractNode { $child = $this->getchild($id); $next = $this->children[$child->id()]['prev']; if (is_null($next)) { throw new ChildNotFoundException("Child '$id' previous not found in this node."); } return $this->getChild($next); } /** * Checks if the given node id is a child of the * current node. * * @param int $id * @return bool */ public function isChild(int $id): bool { foreach ($this->children as $childId => $child) { if ($id == $childId) { return true; } } return false; } /** * Removes the child with id $childId and replace it with the new child * $newChild. * * @param int $childId * @param AbstractNode $newChild * @throws ChildNotFoundException * @return void */ public function replaceChild(int $childId, AbstractNode $newChild): void { $oldChild = $this->children[$childId]; $newChild->prev = $oldChild['prev']; $newChild->next = $oldChild['next']; $keys = array_keys($this->children); $index = array_search($childId, $keys, true); $keys[$index] = $newChild->id(); $this->children = array_combine($keys, $this->children); $this->children[$newChild->id()] = array( 'prev' => $oldChild['prev'], 'node' => $newChild, 'next' => $oldChild['next'] ); // chnge previous child id to new child if ($oldChild['prev'] && isset($this->children[$newChild->prev])) { $this->children[$oldChild['prev']]['next'] = $newChild->id(); } // change next child id to new child if ($oldChild['next'] && isset($this->children[$newChild->next])) { $this->children[$oldChild['next']]['prev'] = $newChild->id(); } // remove old child unset($this->children[$childId]); // clean out cache $this->clear(); } /** * Shortcut to return the first child. * * @return AbstractNode * @uses $this->getChild() * @throws ChildNotFoundException */ public function firstChild(): AbstractNode { if (count($this->children) == 0) { // no children throw new ChildNotFoundException("No children found in node."); } reset($this->children); $key = (int) key($this->children); return $this->getChild($key); } /** * Attempts to get the last child. * * @return AbstractNode * @uses $this->getChild() * @throws ChildNotFoundException */ public function lastChild(): AbstractNode { if (count($this->children) == 0) { // no children throw new ChildNotFoundException("No children found in node."); } end($this->children); $key = key($this->children); return $this->getChild($key); } /** * Checks if the given node id is a descendant of the * current node. * * @param int $id * @return bool */ public function isDescendant(int $id): bool { if ($this->isChild($id)) { return true; } foreach ($this->children as $childId => $child) { /** @var InnerNode $node */ $node = $child['node']; if ($node instanceof InnerNode && $node->hasChildren() && $node->isDescendant($id) ) { return true; } } return false; } /** * Sets the parent node. * * @param InnerNode $parent * @return AbstractNode * @throws CircularException * @chainable */ public function setParent(InnerNode $parent): AbstractNode { // check integrity if ($this->isDescendant($parent->id())) { throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.'); } // clear cache $this->clear(); return parent::setParent($parent); } }