Browse Source

Cleaning up traits

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
fbd5c9ca59
  1. 0
      childNodes
  2. 2
      lib/AbstractDocument.php
  3. 2
      lib/Comment.php
  4. 2
      lib/DocumentFragment.php
  5. 6
      lib/Element.php
  6. 23
      lib/ElementSet.php
  7. 88
      lib/HTMLElement.php
  8. 2
      lib/HTMLTemplateElement.php
  9. 2
      lib/ProcessingInstruction.php
  10. 2
      lib/Text.php
  11. 6
      lib/traits/ChildNode.php
  12. 106
      lib/traits/ContainerNode.php
  13. 3
      lib/traits/DocumentOrElement.php
  14. 2
      lib/traits/Moonwalk.php
  15. 366
      lib/traits/ParentNode.php
  16. 77
      lib/traits/ParentNodePolyfill.php

0
childNodes

2
lib/AbstractDocument.php

@ -10,5 +10,5 @@ namespace MensBeam\HTML\DOM;
// Exists so Document can extend methods from its traits.
abstract class AbstractDocument extends \DOMDocument {
use ContainerNode, DocumentOrElement, EscapeString, MagicProperties, ParentNode, Walk;
use DocumentOrElement, EscapeString, MagicProperties, ParentNode, Walk;
}

2
lib/Comment.php

@ -9,5 +9,5 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class Comment extends \DOMComment {
use LeafNode, Moonwalk, ToString;
use ChildNode, Moonwalk, ToString;
}

2
lib/DocumentFragment.php

@ -9,7 +9,7 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class DocumentFragment extends \DOMDocumentFragment {
use ContainerNode, ParentNode, Walk;
use ParentNode, Walk;
public function __toString() {
return $this->ownerDocument->saveHTML($this);

6
lib/Element.php

@ -11,7 +11,7 @@ use MensBeam\HTML\Parser;
class Element extends \DOMElement {
use ContainerNode, DocumentOrElement, EscapeString, MagicProperties, Moonwalk, ParentNode, ToString, Walk;
use DocumentOrElement, EscapeString, MagicProperties, Moonwalk, ParentNode, ToString, Walk;
protected $_classList;
@ -52,7 +52,7 @@ class Element extends \DOMElement {
# 2. Let fragment be the result of invoking the fragment parsing algorithm with
# the new value as markup, and with context element.
$fragment = Parser::parseFragment($this, 0, $value, 'UTF-8');
$fragment = Parser::parseFragment($this, $this->ownerDocument->quirksMode, $value, 'UTF-8');
$fragment = $this->ownerDocument->importNode($fragment);
# 3. If the context object is a template element, then let context object be the
@ -170,7 +170,7 @@ class Element extends \DOMElement {
# 5. Let fragment be the result of invoking the fragment parsing algorithm with
# the new value as markup, and parent as the context element.
$fragment = Parser::parseFragment($parent, 0, $value, 'UTF-8');
$fragment = Parser::parseFragment($parent, $this->ownerDocument->quirksMode, $value, 'UTF-8');
$fragment = $this->ownerDocument->importNode($fragment);
# 6. Replace the context object with fragment within the context object's

23
lib/ElementMap.php → lib/ElementSet.php

@ -8,13 +8,23 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
// This is a write-only map of elements which need to be kept in memory; it
// This is a write-only set of elements which need to be kept in memory; it
// exists because values of properties on derived DOM classes are lost unless at
// least one PHP reference is kept for the element somewhere in userspace. This
// is that somewhere. It is at present only used for template elements.
class ElementMap {
class ElementSet {
protected static $_storage = [];
public static function add(Element $element) {
if (!self::has($element)) {
self::$_storage[] = $element;
return true;
}
return false;
}
public static function delete(Element $element) {
foreach (self::$_storage as $k => $v) {
if ($v->isSameNode($element)) {
@ -59,13 +69,4 @@ class ElementMap {
return false;
}
public static function set(Element $element) {
if (!self::has($element)) {
self::$_storage[] = $element;
return true;
}
return false;
}
}

88
lib/HTMLElement.php

@ -1,88 +0,0 @@
<?php
/**
* @license MIT
* Copyright 2017, Dustin Wilson, J. King et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class HTMLElement extends Element {
protected function __get_accessKey(): string {
# The accessKey IDL attribute must reflect the accesskey content attribute.
return $this->getAttribute('accesskey');
}
protected function __set_accessKey(string $value) {
return $this->setAttribute('accesskey', $value);
}
protected function __get_contentEditable(): string {
# The contentEditable IDL attribute, on getting, must return the string "true"
# if the content attribute is set to the true state, "false" if the content
# attribute is set to the false state, and "inherit" otherwise.
$result = $this->getAttribute('contenteditable');
switch ($value) {
case 'false':
case 'true':
return $result;
default:
return 'inherit';
}
}
protected function __set_contentEditable(string $value) {
# On setting, if the new value is an ASCII case-insensitive match for the
# string "inherit" then the content attribute must be removed, if the new value
# is an ASCII case-insensitive match for the string "true" then the content
# attribute must be set to the string "true", if the new value is an ASCII
# case-insensitive match for the string "false" then the content attribute must
# be set to the string "false", and otherwise the attribute setter must throw a
# "SyntaxError" DOMException.
switch ($value) {
case 'inherit'
$this->removeAttribute('contenteditable');
case 'false':
case 'true':
return $this->setAttribute('contenteditable', $value);
default:
throw new DOMException(DOMException::SYNTAX_ERROR);
}
}
protected function __get_isContentEditable(): bool {
# The isContentEditable IDL attribute, on getting, must return true if the
# element is either an editing host or editable, and false otherwise.
#
# An editing host is either an HTML element with its contenteditable attribute
# in the true state, or a child HTML element of a Document whose design mode
# enabled is true.
#
# Something is editable if it is a node; it is not an editing host; it does
# not have a contenteditable attribute set to the false state; its parent is an
# editing host or editable; and either it is an HTML element, or it is an svg or
# math element, or it is not an Element and its parent is an HTML element.
$contentEditable = $this->__get_contentEditable();
$designMode = ($this->ownerDocument->designMode === 'on');
if ($contentEditable === 'true' || $designMode) {
return true;
} elseif ($contentEditable !== 'false') {
// If the parent can be either an editing host or editable then all is needed
// is to see if there's an ancestor that's an editing host. Just seems absurd
// to word the specification like that. Since isContentEditable is a property
// of HTMLElement there's no need to check if it's an HTML element, svg, or
// non-element child of foreign content. There is also no need to check for
// design mode enabled on the document because it's checked above.
if ($this->moonwalk(function($n) {
if ($n instanceof HTMLElement && $n->contentEditable === 'true') {
return true;
}
})->current() !== null) {
return true;
}
}
return false;
}
}

2
lib/HTMLTemplateElement.php

@ -26,7 +26,7 @@ class HTMLTemplateElement extends Element {
$this->content = $this->ownerDocument->createDocumentFragment();
// Template elements need to have a reference kept in userland
ElementMap::set($this);
ElementSet::add($this);
}

2
lib/ProcessingInstruction.php

@ -9,5 +9,5 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class ProcessingInstruction extends \DOMProcessingInstruction {
use LeafNode, Moonwalk, ToString;
use ChildNode, Moonwalk, ToString;
}

2
lib/Text.php

@ -9,5 +9,5 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class Text extends \DOMText {
use LeafNode, Moonwalk, ToString;
use ChildNode, Moonwalk, ToString;
}

6
lib/traits/LeafNode.php → lib/traits/ChildNode.php

@ -8,10 +8,8 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
// Node in the DOM spec is dirty. Many nodes which inherit from it inherit
// methods it cannot use which all check for this and throw exceptions. This is
// for nodes which DO NOT have child nodes.
trait LeafNode {
trait ChildNode {
use Node;

106
lib/traits/ContainerNode.php

@ -1,106 +0,0 @@
<?php
/**
* @license MIT
* Copyright 2017, Dustin Wilson, J. King et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
// Node in the DOM spec is dirty. Many nodes which inherit from it inherit
// methods it cannot use which all check for this and throw exceptions. This is
// for nodes which DO have child nodes.
trait ContainerNode {
use Node;
public function appendChild($node) {
$this->preInsertionValidity($node);
$result = parent::appendChild($node);
if ($result !== false && $result instanceof TemplateElement) {
if ($result instanceof TemplateElement) {
ElementMap::set($result);
}
}
return $result;
}
public function insertBefore($node, $child = null) {
$this->preInsertionValidity($node, $child);
$result = parent::insertBefore($node, $child);
if ($result !== false) {
if ($result instanceof TemplateElement) {
ElementMap::set($result);
}
if ($child instanceof TemplateElement) {
ElementMap::delete($child);
}
}
return $result;
}
public function removeChild($child) {
$result = parent::removeChild($child);
if ($result !== false && $result instanceof TemplateElement) {
ElementMap::delete($child);
}
return $result;
}
public function replaceChild($node, $child) {
$result = parent::replaceChild($node, $child);
if ($result !== false) {
if ($result instanceof TemplateElement) {
ElementMap::set($child);
}
if ($child instanceof TemplateElement) {
ElementMap::delete($child);
}
}
return $result;
}
protected function preInsertionValidity(\DOMNode $node, ?\DOMNode $child = null) {
// "parent" in the spec comments below is $this
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
# 2. If node is a host-including inclusive ancestor of parent, then throw a
# "HierarchyRequestError" DOMException.
#
# An object A is a host-including inclusive ancestor of an object B, if either
# A is an inclusive ancestor of B, or if B’s root has a non-null host and A is a
# host-including inclusive ancestor of B’s root’s host.
// DEVIATION: The baseline for this library is PHP 7.1, and without
// WeakReferences we cannot add a host property to DocumentFragment to check
// against.
// This is handled just fine by PHP's DOM.
# 3. If child is non-null and its parent is not parent, then throw a
# "NotFoundError" DOMException.
// This is handled just fine by PHP's DOM.
# 4. If node is not a DocumentFragment, DocumentType, Element, Text,
# ProcessingInstruction, or Comment node, then throw a "HierarchyRequestError"
# DOMException.
if (!$node instanceof DocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof Element && !$node instanceof Text && !$node instanceof ProcessingInstruction && !$node instanceof Comment) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 5. If either node is a Text node and parent is a document, or node is a
# doctype and parent is not a document, then throw a "HierarchyRequestError"
# DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
# 6. If parent is a document, and any of the statements below, switched on node,
# are true, then throw a "HierarchyRequestError" DOMException.
// Handled by the Document class.
}
}

3
lib/traits/DocumentOrElement.php

@ -47,7 +47,8 @@ trait DocumentOrElement {
#
# The comparisons for the classes must be done in an ASCII case-insensitive manner
# if root’s node document’s mode is "quirks"; otherwise in an identical to manner.
// DEVIATION: Since we can't just create a \DOMNodeList we must instead query the document with XPath with the root element to get a list.
// DEVIATION: Since we can't just create a \DOMNodeList we must instead query
// the document with XPath with the root element to get a list.
$query = '//*';
foreach ($inputTokens as $token) {

2
lib/traits/Moonwalk.php

@ -46,7 +46,7 @@ trait Moonwalk {
// templates; if it is change node to the template element and reprocess. Magic!
// Can walk backwards THROUGH templates!
if ($node instanceof DocumentFragment) {
foreach (ElementMap::getIterator() as $element) {
foreach (ElementSet::getIterator() as $element) {
if ($element->ownerDocument->isSameNode($node->ownerDocument) && $element instanceof TemplateElement && $element->content->isSameNode($node)) {
$node = $element;
continue;

366
lib/traits/ParentNode.php

@ -8,240 +8,186 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
if (version_compare(\PHP_VERSION, '8.0', '>=')) {
# 4.2.6. Mixin ParentNode
trait ParentNode {
protected function __get_children(): \DOMNodeList {
# The children getter steps are to return an HTMLCollection collection rooted at
# this matching only element children.
// DEVIATION: HTMLCollection doesn't exist in PHP's DOM, and \DOMNodeList is
// almost identical; so, using that. PHP's DOM doesn't provide the end user any
// way to create a \DOMNodeList from scratch, so going to cheat and use XPath to
// make one for us.
$isDocument = ($this instanceof Document);
$document = ($isDocument) ? $this : $this->ownerDocument;
return $document->xpath->query('//*', (!$isDocument) ? $this : null);
}
# 4.2.6. Mixin ParentNode
trait ParentNode {
use Node, ParentNodePolyfill;
protected function __get_children(): \DOMNodeList {
# The children getter steps are to return an HTMLCollection collection rooted at
# this matching only element children.
// DEVIATION: HTMLCollection doesn't exist in PHP's DOM, and \DOMNodeList is
// almost identical; so, using that. PHP's DOM doesn't provide the end user any
// way to create a \DOMNodeList from scratch, so going to cheat and use XPath to
// make one for us.
$isDocument = ($this instanceof Document);
$document = ($isDocument) ? $this : $this->ownerDocument;
return $document->xpath->query('//*', (!$isDocument) ? $this : null);
}
public function replaceChildren(...$nodes) {
# The replaceChildren(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Ensure pre-insertion validity of node into this before null.
$this->preInsertionValidity($node);
# 3. Replace all with node within this.
#
# To replace all with a node within a parent, run these steps:
# 1. Let removedNodes be parent’s children.
$removedNodes = $this->childNodes;
# 2. Let addedNodes be the empty set.
$addedNodes = [];
# 3. If node is a DocumentFragment node, then set addedNodes to node’s children.
if ($node instanceof DocumentFragment) {
$addedNodes = $node->childNodes;
}
# 4. Otherwise, if node is non-null, set addedNodes to « node ».
elseif ($node !== null) {
$addedNodes = node;
}
# 5. Remove all parent’s children, in tree order, with the suppress observers
# flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
while ($this->hasChildNodes()) {
$this->removeChild($this->firstChild);
}
# 6. If node is non-null, then insert node into parent before null with the
# suppress observers flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
if ($node !== null) {
$this->appendChild($node);
public function appendChild($node) {
$this->preInsertionValidity($node);
$result = parent::appendChild($node);
if ($result !== false && $result instanceof TemplateElement) {
if ($result instanceof TemplateElement) {
ElementSet::add($result);
}
# 7. If either addedNodes or removedNodes is not empty, then queue a tree
# mutation record for parent with addedNodes, removedNodes, null, and null.
// DEVIATION: There is no scripting in this implementation
}
return $result;
}
private function convertNodesToNode(array $nodes): \DOMNode {
# To convert nodes into a node, given nodes and document, run these steps:
# 1. Let node be null.
# 2. Replace each string in nodes with a new Text node whose data is the string
# and node document is document.
# 3. If nodes contains one node, then set node to nodes[0].
# 4. Otherwise, set node to a new DocumentFragment node whose node document is
# document, and then append each node in nodes, if any, to it.
// The spec would have us iterate through the provided nodes and then iterate
// through them again to append. Let's optimize this a wee bit, shall we?
$document = ($this instanceof Document) ? $this : $this->ownerDocument;
$node = ($node->length > 1) ? $document->createDocumentFragment() : null;
foreach ($nodes as &$n) {
// Can't do union types until PHP 8... OTL
if (!$n instanceof \DOMNode && !is_string($n)) {
trigger_error(sprintf("Uncaught TypeError: %s::%s(): Argument #1 (\$%s) must be of type \DOMNode|string, %s given", __CLASS__, __METHOD__, 'nodes', gettype($n)));
}
if (is_string($n)) {
$n = $this->ownerDocument->createTextNode($n);
}
if ($node !== null) {
$node->appendChild($n);
} else {
$node = $n;
}
}
public function insertBefore($node, $child = null) {
$this->preInsertionValidity($node, $child);
return $node;
$result = parent::insertBefore($node, $child);
if ($result !== false) {
if ($result instanceof TemplateElement) {
ElementSet::add($result);
}
if ($child instanceof TemplateElement) {
ElementSet::delete($child);
}
}
return $result;
}
} else {
trait ParentNode {
protected function __get_childElementCount(): int {
# The childElementCount getter steps are to return the number of children of
# this that are elements.
$count = 0;
foreach ($this->childNodes as $child) {
if ($child instanceof Element) {
$count++;
}
}
return $count;
public function removeChild($child) {
$result = parent::removeChild($child);
if ($result !== false && $result instanceof TemplateElement) {
ElementSet::delete($child);
}
return $result;
}
protected function __get_children(): \DOMNodeList {
# The children getter steps are to return an HTMLCollection collection rooted at
# this matching only element children.
// DEVIATION: HTMLCollection doesn't exist in PHP's DOM, and \DOMNodeList is
// almost identical; so, using that. PHP's DOM doesn't provide the end user any
// way to create a \DOMNodeList from scratch, so going to cheat and use XPath to
// make one for us.
$isDocument = ($this instanceof Document);
$document = ($isDocument) ? $this : $this->ownerDocument;
return $document->xpath->query('//*', (!$isDocument) ? $this : null);
public function replaceChild($node, $child) {
$result = parent::replaceChild($node, $child);
if ($result !== false) {
if ($result instanceof TemplateElement) {
ElementSet::add($child);
}
if ($child instanceof TemplateElement) {
ElementSet::delete($child);
}
}
return $result;
}
protected function __get_firstElementChild(): Element {
# The firstElementChild getter steps are to return the first child that is an
# element; otherwise null.
foreach ($this->childNodes as $child) {
if ($child instanceof Element) {
return $child;
}
}
return null;
public function replaceChildren(...$nodes) {
# The replaceChildren(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Ensure pre-insertion validity of node into this before null.
$this->preInsertionValidity($node);
# 3. Replace all with node within this.
#
# To replace all with a node within a parent, run these steps:
# 1. Let removedNodes be parent’s children.
$removedNodes = $this->childNodes;
# 2. Let addedNodes be the empty set.
$addedNodes = [];
# 3. If node is a DocumentFragment node, then set addedNodes to node’s children.
if ($node instanceof DocumentFragment) {
$addedNodes = $node->childNodes;
}
# 4. Otherwise, if node is non-null, set addedNodes to « node ».
elseif ($node !== null) {
$addedNodes = node;
}
# 5. Remove all parent’s children, in tree order, with the suppress observers
# flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
while ($this->hasChildNodes()) {
$this->removeChild($this->firstChild);
}
# 6. If node is non-null, then insert node into parent before null with the
# suppress observers flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
if ($node !== null) {
$this->appendChild($node);
}
# 7. If either addedNodes or removedNodes is not empty, then queue a tree
# mutation record for parent with addedNodes, removedNodes, null, and null.
// DEVIATION: There is no scripting in this implementation
}
protected function __get_lastElementChild(): Element {
# The lastElementChild getter steps are to return the last child that is an
# element; otherwise null.
for ($i = $this->childNodes->length - 1; $i >= 0; $i--) {
$child = $this->childNodes->item($i);
if ($child instanceof Element) {
return $child;
}
}
return null;
protected function preInsertionValidity(\DOMNode $node, ?\DOMNode $child = null) {
// "parent" in the spec comments below is $this
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
# 2. If node is a host-including inclusive ancestor of parent, then throw a
# "HierarchyRequestError" DOMException.
#
# An object A is a host-including inclusive ancestor of an object B, if either
# A is an inclusive ancestor of B, or if B’s root has a non-null host and A is a
# host-including inclusive ancestor of B’s root’s host.
// DEVIATION: The baseline for this library is PHP 7.1, and without
// WeakReferences we cannot add a host property to DocumentFragment to check
// against.
// This is handled just fine by PHP's DOM.
# 3. If child is non-null and its parent is not parent, then throw a
# "NotFoundError" DOMException.
// This is handled just fine by PHP's DOM.
# 4. If node is not a DocumentFragment, DocumentType, Element, Text,
# ProcessingInstruction, or Comment node, then throw a "HierarchyRequestError"
# DOMException.
if (!$node instanceof DocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof Element && !$node instanceof Text && !$node instanceof ProcessingInstruction && !$node instanceof Comment) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 5. If either node is a Text node and parent is a document, or node is a
# doctype and parent is not a document, then throw a "HierarchyRequestError"
# DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
public function append(...$nodes): void {
# The append(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Append node to this.
$this->appendChild($node);
}
# 6. If parent is a document, and any of the statements below, switched on node,
# are true, then throw a "HierarchyRequestError" DOMException.
// Handled by the Document class.
}
public function prepend(...$nodes): void {
# The prepend(nodes) method steps are:
#
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Pre-insert node into this before this’s first child.
$this->insertBefore($node, $this->firstChild);
}
public function replaceChildren(...$nodes) {
# The replaceChildren(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Ensure pre-insertion validity of node into this before null.
$this->preInsertionValidity($node);
# 3. Replace all with node within this.
#
# To replace all with a node within a parent, run these steps:
# 1. Let removedNodes be parent’s children.
$removedNodes = $this->childNodes;
# 2. Let addedNodes be the empty set.
$addedNodes = [];
# 3. If node is a DocumentFragment node, then set addedNodes to node’s children.
if ($node instanceof DocumentFragment) {
$addedNodes = $node->childNodes;
private function convertNodesToNode(array $nodes): \DOMNode {
# To convert nodes into a node, given nodes and document, run these steps:
# 1. Let node be null.
# 2. Replace each string in nodes with a new Text node whose data is the string
# and node document is document.
# 3. If nodes contains one node, then set node to nodes[0].
# 4. Otherwise, set node to a new DocumentFragment node whose node document is
# document, and then append each node in nodes, if any, to it.
// The spec would have us iterate through the provided nodes and then iterate
// through them again to append. Let's optimize this a wee bit, shall we?
$document = ($this instanceof Document) ? $this : $this->ownerDocument;
$node = ($node->length > 1) ? $document->createDocumentFragment() : null;
foreach ($nodes as &$n) {
// Can't do union types until PHP 8... OTL
if (!$n instanceof \DOMNode && !is_string($n)) {
trigger_error(sprintf("Uncaught TypeError: %s::%s(): Argument #1 (\$%s) must be of type \DOMNode|string, %s given", __CLASS__, __METHOD__, 'nodes', gettype($n)));
}
# 4. Otherwise, if node is non-null, set addedNodes to « node ».
elseif ($node !== null) {
$addedNodes = node;
}
# 5. Remove all parent’s children, in tree order, with the suppress observers
# flag set.
// DEVIATION: There is no scripting in this implementation so cannnot set
// suppress observers flag.
while ($this->hasChildNodes()) {
$this->removeChild($this->firstChild);
if (is_string($n)) {
$n = $this->ownerDocument->createTextNode($n);
}
# 6. If node is non-null, then insert node into parent before null with the
# suppress observers flag set.
// DEVIATION: There is no scripting in this implementation so cannnot set
// suppress observers flag.
if ($node !== null) {
$this->appendChild($node);
$node->appendChild($n);
} else {
$node = $n;
}
# 7. If either addedNodes or removedNodes is not empty, then queue a tree
# mutation record for parent with addedNodes, removedNodes, null, and null.
// DEVIATION: There is no scripting in this implementation
}
private function convertNodesToNode(array $nodes): \DOMNode {
# To convert nodes into a node, given nodes and document, run these steps:
# 1. Let node be null.
# 2. Replace each string in nodes with a new Text node whose data is the string
# and node document is document.
# 3. If nodes contains one node, then set node to nodes[0].
# 4. Otherwise, set node to a new DocumentFragment node whose node document is
# document, and then append each node in nodes, if any, to it.
// The spec would have us iterate through the provided nodes and then iterate
// through them again to append. Let's optimize this a wee bit, shall we?
$document = ($this instanceof Document) ? $this : $this->ownerDocument;
$node = ($node->length > 1) ? $document->createDocumentFragment() : null;
foreach ($nodes as $n) {
// Can't do union types until PHP 8... OTL
if (!$n instanceof \DOMNode && !is_string($n)) {
$type = gettype($n);
if ($type === 'object') {
$type = get_class($n);
}
throw new Exception(Exception::ARGUMENT_TYPE_ERROR, 1, 'nodes', '[\DOMNode|string]', $n);
}
$nn = (!is_string($n)) ? $n : $this->ownerDocument->createTextNode($n);
if ($node !== null) {
$node->appendChild($nn);
} else {
$node = $nn;
}
}
return $node;
}
return $node;
}
}
}

77
lib/traits/ParentNodePolyfill.php

@ -0,0 +1,77 @@
<?php
/**
* @license MIT
* Copyright 2017, Dustin Wilson, J. King et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
if (version_compare(\PHP_VERSION, '8.0', '<')) {
/**
* Used for PHP7 installations to polyfill getters, setters, and methods that
* PHP's DOM handles natively in PHP8
*/
trait ParentNode {
protected function __get_childElementCount(): int {
# The childElementCount getter steps are to return the number of children of
# this that are elements.
$count = 0;
foreach ($this->childNodes as $child) {
if ($child instanceof Element) {
$count++;
}
}
return $count;
}
protected function __get_firstElementChild(): Element {
# The firstElementChild getter steps are to return the first child that is an
# element; otherwise null.
foreach ($this->childNodes as $child) {
if ($child instanceof Element) {
return $child;
}
}
return null;
}
protected function __get_lastElementChild(): Element {
# The lastElementChild getter steps are to return the last child that is an
# element; otherwise null.
for ($i = $this->childNodes->length - 1; $i >= 0; $i--) {
$child = $this->childNodes->item($i);
if ($child instanceof Element) {
return $child;
}
}
return null;
}
public function append(...$nodes): void {
# The append(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Append node to this.
$this->appendChild($node);
}
public function prepend(...$nodes): void {
# The prepend(nodes) method steps are:
#
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Pre-insert node into this before this’s first child.
$this->insertBefore($node, $this->firstChild);
}
}
} else {
trait ParentNodePolyfill {}
}
Loading…
Cancel
Save