'beforeend', 'appendsibling' => 'afterend', 'prepend' => 'afterbegin', 'prependsibling' => 'beforebegin' ]; if (preg_match('(^(append|prepend)xsl(\\w+?)(sibling|)$)', $name, $m)) { $localName = $m[2]; $where = $positions[$m[1] . $m[3]]; return $this->insertXslElement($where, $localName, $arguments); } if (preg_match('(^(append|prepend)element(sibling|)$)', $name, $m)) { $nodeName = $arguments[0]; $text = $arguments[1] ?? ''; $where = $positions[$m[1] . $m[2]]; return $this->insertElement($where, $nodeName, $text); } if (preg_match('(^(append|prepend)text(sibling|)$)', $name, $m)) { $text = $arguments[0]; $where = $positions[$m[1] . $m[2]]; return $this->insertText($where, $text); } throw new BadMethodCallException; } /** * Evaluate and return the result of a given XPath expression using this element as context node * * @param string $expr XPath expression * @return mixed */ public function evaluate(string $expr) { return $this->ownerDocument->evaluate($expr, $this); } /** * Evaluate and return the first element of a given XPath query using this element as context node * * @param string $expr XPath expression * @return DOMNode|null */ public function firstOf(string $expr): ?DOMNode { return $this->ownerDocument->firstOf($expr, $this); } /** * Insert given element relative to this element's position * * @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * @param DOMElement $element * @return self */ public function insertAdjacentElement(string $where, DOMElement $element): self { $this->insertAdjacentNode($where, $element); return $element; } /** * Insert given text relative to this element's position * * @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * @param string $text * @return void */ public function insertAdjacentText(string $where, string $text): void { $this->insertText($where, $text); } /** * Insert given XML relative to this element's position * * @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * @param string $xml * @return void */ public function insertAdjacentXML(string $where, string $xml): void { $fragment = $this->ownerDocument->createDocumentFragment(); $fragment->appendXML($this->addMissingNamespaceDeclarations($xml)); $this->insertAdjacentNode($where, $fragment); } /** * Evaluate and return the result of a given XPath query using this element as context node * * @param string $expr XPath expression * @return DOMNodeList */ public function query(string $expr): DOMNodeList { return $this->ownerDocument->query($expr, $this); } /** * Remove this element from the document * * @return void */ public function remove(): void { $this->parentOrThrow()->removeChild($this); } /** * Replace this element with given nodes/text * * @param DOMNode|string $nodes * @return void */ public function replaceWith(...$nodes): void { $parentNode = $this->parentOrThrow(new DOMException('No Modification Allowed Error', DOM_NO_MODIFICATION_ALLOWED_ERR)); foreach ($nodes as $node) { if (!($node instanceof DOMNode)) { $node = $this->ownerDocument->createTextNode((string) $node); } $parentNode->insertBefore($node, $this); } $parentNode->removeChild($this); } /** * Add namespace declarations that may be missing in given XML * * @param string $xml Original XML * @return string Modified XML */ protected function addMissingNamespaceDeclarations(string $xml): string { preg_match_all('(xmlns:\\K[-\\w]++(?==))', $xml, $m); $prefixes = array_flip($m[0]); return preg_replace_callback( '(<([-\\w]++):[^>]*?\\K\\s*/?>)', function ($m) use ($prefixes) { $return = $m[0]; $prefix = $m[1]; if (!isset($prefixes[$prefix])) { $nsURI = $this->lookupNamespaceURI($prefix); $return = ' xmlns:' . $prefix . '="' . htmlspecialchars($nsURI, ENT_XML1) . '"' . $return; } return $return; }, $xml ); } /** * Insert given node relative to this element's position * * @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * @param DOMNode $node * @return void */ protected function insertAdjacentNode(string $where, DOMNode $node): void { switch (strtolower($where)) { case 'afterbegin': $this->insertBefore($node, $this->firstChild); break; case 'afterend': if (isset($this->parentNode)) { $this->parentNode->insertBefore($node, $this->nextSibling); } break; case 'beforebegin': if (isset($this->parentNode)) { $this->parentNode->insertBefore($node, $this); } break; case 'beforeend': $this->appendChild($node); break; default: throw new DOMException("'$where' is not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'", DOM_SYNTAX_ERR); } } /** * Create and insert an element at given position * * @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * @param string $nodeName Element's nodeName * @param string $text Text content * @return self */ protected function insertElement(string $where, string $nodeName, string $text): self { $text = htmlspecialchars($text, ENT_NOQUOTES); $pos = strpos($nodeName, ':'); if ($pos === false) { $element = $this->ownerDocument->createElement($nodeName, $text); } else { $prefix = substr($nodeName, 0, $pos); $namespaceURI = $this->ownerDocument->lookupNamespaceURI($prefix); $element = $this->ownerDocument->createElementNS($namespaceURI, $nodeName, $text); } return $this->insertAdjacentElement($where, $element); } /** * Insert given text relative to this element's position * * @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * @param string $text * @return DOMText */ protected function insertText(string $where, string $text): DOMText { $node = $this->ownerDocument->createTextNode($text); $this->insertAdjacentNode($where, $node); return $node; } /** * Create and insert an XSL element at given position * * @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * @param string $localName Element's localName * @param array $arguments Arguments passed to the Document::create* function * @return self */ protected function insertXslElement(string $where, string $localName, array $arguments): self { $callback = [$this->ownerDocument, 'createXsl' . ucfirst($localName)]; if (!is_callable($callback)) { throw new BadMethodCallException; } $element = call_user_func_array($callback, $arguments); return $this->insertAdjacentElement($where, $element); } /** * Return this element's parent element if available, or throw an exception * * @param DOMException $previous Previous exception * @return DOMNode */ protected function parentOrThrow(DOMException $previous = null): DOMNode { if (isset($this->parentNode)) { return $this->parentNode; } throw new DOMException('Not Found Error', DOM_NOT_FOUND_ERR, $previous); } }