Blame | Letzte Änderung | Log anzeigen | RSS feed
<?phpdeclare(strict_types=1);namespace PHPHtmlParser\Dom;use PHPHtmlParser\Exceptions\ChildNotFoundException;use PHPHtmlParser\Exceptions\CircularException;use PHPHtmlParser\Exceptions\LogicalException;use stringEncode\Encode;/*** Inner node of the html tree, might have children.** @package PHPHtmlParser\Dom*/abstract class InnerNode extends ArrayNode{/*** An array of all the children.** @var array*/protected $children = [];/*** Sets the encoding class to this node and propagates it* to all its children.** @param Encode $encode** @return void*/public function propagateEncoding(Encode $encode): void{$this->encode = $encode;$this->tag->setEncoding($encode);// check childrenforeach ($this->children as $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 = [];$childrenIds = [];try {$child = $this->firstChild();do {$nodes[] = $child;$childrenIds[] = $child->id;$child = $this->nextChild($child->id());if (in_array($child->id, $childrenIds, true)) {throw new CircularException('Circular sibling referance found. Child with id '.$child->id().' found twice.');}} while (true);} catch (ChildNotFoundException $e) {// we are done looking for childrenunset($e);}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 ChildNotFoundException* @throws CircularException*/public function addChild(AbstractNode $child, int $before = -1): bool{$key = null;// check integrityif ($this->isAncestor($child->id())) {throw new CircularException('Can not add child. It is my ancestor.');}// check if child is itselfif ($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 childreturn 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 ? (int) (array_search($key, $keys, true) + 1) : 0;array_splice($keys, $index, 0, (string) $child->id());$children = array_values($this->children);array_splice($children, $index, 0, [$insert]);// add the child$combination = array_combine($keys, $children);$this->children = $combination;// 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* @throws ChildNotFoundException* @throws CircularException*/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* @throws ChildNotFoundException* @throws CircularException*/public function insertAfter(AbstractNode $child, int $id): bool{if (!isset($this->children[$id])) {return false;}if (isset($this->children[$id]['next']) && is_int($this->children[$id]['next'])) {return $this->addChild($child, (int) $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 childunset($this->children[$id]);//clear any cache$this->clear();return $this;}/*** Check if has next Child** @param int $id** @return mixed* @throws ChildNotFoundException*/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* @throws ChildNotFoundException* @uses $this->getChild()*/public function nextChild(int $id): AbstractNode{$child = $this->getChild($id);$next = $this->children[$child->id()]['next'];if (is_null($next) || !is_int($next)) {throw new ChildNotFoundException("Child '$id' next sibling not found in this node.");}return $this->getChild($next);}/*** Attempts to get the previous child.** @param int $id** @return AbstractNode* @throws ChildNotFoundException* @uses $this->getChild()*/public function previousChild(int $id): AbstractNode{$child = $this->getchild($id);$next = $this->children[$child->id()]['prev'];if (is_null($next) || !is_int($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(array_keys($this->children) as $childId) {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** @return void*/public function replaceChild(int $childId, AbstractNode $newChild): void{$oldChild = $this->children[$childId];$newChild->prev = (int) $oldChild['prev'];$newChild->next = (int) $oldChild['next'];$keys = array_keys($this->children);$index = array_search($childId, $keys, true);$keys[$index] = $newChild->id();$combination = array_combine($keys, $this->children);$this->children = $combination;$this->children[$newChild->id()] = ['prev' => $oldChild['prev'],'node' => $newChild,'next' => $oldChild['next']];// change previous child id to new childif ($oldChild['prev'] && isset($this->children[$newChild->prev])) {$this->children[$oldChild['prev']]['next'] = $newChild->id();}// change next child id to new childif ($oldChild['next'] && isset($this->children[$newChild->next])) {$this->children[$oldChild['next']]['prev'] = $newChild->id();}// remove old childunset($this->children[$childId]);// clean out cache$this->clear();}/*** Shortcut to return the first child.** @return AbstractNode* @throws ChildNotFoundException* @uses $this->getChild()*/public function firstChild(): AbstractNode{if (count($this->children) == 0) {// no childrenthrow 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* @throws ChildNotFoundException* @uses $this->getChild()*/public function lastChild(): AbstractNode{if (count($this->children) == 0) {// no childrenthrow new ChildNotFoundException("No children found in node.");}end($this->children);$key = key($this->children);if (!is_int($key)) {throw new LogicalException("Children array contain child with a key that is not an int.");}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 $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 ChildNotFoundException* @throws CircularException*/public function setParent(InnerNode $parent): AbstractNode{// check integrityif ($this->isDescendant($parent->id())) {throw new CircularException('Can not add descendant "'. $parent->id() . '" as my parent.');}// clear cache$this->clear();return parent::setParent($parent);}}