Browse Source

More work on Node::cloneNode

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
49742a5709
  1. 2
      lib/Attr.php
  2. 4
      lib/Document.php
  3. 8
      lib/Element.php
  4. 102
      lib/Node.php
  5. 77
      tests/cases/TestNode.php

2
lib/Attr.php

@ -24,7 +24,7 @@ class Attr extends Node {
return (!str_contains(needle: 'U', haystack: $name)) ? $name : $this->uncoerceName($name);
}
protected function __get_namespaceURI(): string {
protected function __get_namespaceURI(): ?string {
// PHP's DOM does this correctly already.
return $this->innerNode->namespaceURI;
}

4
lib/Document.php

@ -23,7 +23,7 @@ class Document extends Node {
protected DOMImplementation $_implementation;
protected string $_URL = '';
protected function __get_body(): Element {
protected function __get_body(): ?Element {
if ($this->documentElement === null || !$this->documentElement->hasChildNodes()) {
return null;
}
@ -126,7 +126,7 @@ class Document extends Node {
return $this->innerNode->getWrapperNode($attr);
}
public function createAttributeNS(string $namespace, string $qualifiedName): Attr {
public function createAttributeNS(?string $namespace, string $qualifiedName): Attr {
# The createAttributeNS(namespace, qualifiedName) method steps are:
#
# 1. Let namespace, prefix, and localName be the result of passing namespace and

8
lib/Element.php

@ -13,6 +13,10 @@ use MensBeam\HTML\Parser;
class Element extends Node {
use ChildNode, DocumentOrElement, ParentNode;
protected function __get_localName(): ?string {
return $this->innerNode->localName;
}
protected function __get_namespaceURI(): string {
// PHP's DOM uses null incorrectly for the HTML namespace, and if you attempt to
// use the HTML namespace anyway it has additional bugs we don't have to work
@ -23,6 +27,10 @@ class Element extends Node {
return (($doc instanceof Document && !$doc instanceof XMLDocument) && $namespace === null) ? Parser::HTML_NAMESPACE : $namespace;
}
protected function __get_prefix(): ?string {
return $this->innerNode->prefix;
}
protected function __construct(\DOMElement $element) {
parent::__construct($element);

102
lib/Node.php

@ -322,24 +322,110 @@ abstract class Node {
}
public function cloneNode(?bool $deep = false): Node {
// PHP's DOM does this correctly already.
$newInner = $this->innerNode->cloneNode($deep);
# The cloneNode(deep) method steps are:
# 1. If this is a shadow root, then throw a "NotSupportedError" DOMException.
// DEVIATION: There is no scripting in this implementation
# 2. Return a clone of this, with the clone children flag set if deep is true.
#
# To clone a node, with an optional document and clone children flag, run these steps:
// node is $this
# 1. If document is not given, let document be node’s node document.
// No need for this step. There will always be a provided document
# 2. If node is an element, then:
if ($this instanceof Element) {
# 1. Let copy be the result of creating an element, given document, node’s local
# name, node’s namespace, node’s namespace prefix, and node’s is value, with the
# synchronous custom elements flag unset.
# 2. For each attribute in node’s attribute list:
# 1. Let copyAttribute be a clone of attribute.
# 2. Append copyAttribute to copy.
// PHP's DOM can do this part correctly by shallow cloning.
$copy = $this->innerNode->ownerDocument->getWrapperNode($this->innerNode->cloneNode());
}
# 3. Otherwise, let copy be a node that implements the same interfaces as node, and
# fulfills these additional requirements, switching on the interface node
# implements:
#
# ↪ Document
# Set copy’s encoding, content type, URL, origin, type, and mode to those of node.
elseif ($this instanceof Document) {
$copy = $this->innerNode->getWrapperNode($this->innerNode->cloneNode());
// Documents have some userland properties to transfer
if ($this instanceof Document) {
$newDoc = $this->innerNode->getWrapperNode($newInner);
if ($this->characterSet !== 'UTF-8' || $this->compatMode !== 'CSS1Compat' || $this->contentType !== 'text/html' || $this->URL !== '') {
Reflection::setProtectedProperties($newDoc, [
Reflection::setProtectedProperties($copy, [
'_characterSet' => $this->characterSet,
'_compatMode' => $this->compatMode,
'_contentType' => $this->contentType,
'_URL' => $this->URL
]);
}
return $newDoc;
}
return $this->innerNode->ownerDocument->getWrapperNode($newInner);
# ↪ DocumentType
# Set copy’s name, public ID, and system ID to those of node.
elseif ($this instanceof DocumentType) {
// OPTIMIZATION: No need for the other steps as the DocumentType node is created
// using this document's implementation
return $this->ownerDocument->implementation->createDocumentType($this->name, $this->publicId, $this->systemId);
}
# ↪ Attr
# Set copy’s namespace, namespace prefix, local name, and value to those of node.
# ↪ Text
# ↪ Comment
# Set copy’s data to that of node.
# ↪ ProcessingInstruction
# Set copy’s target and data to those of node.
elseif ($this instanceof Attr || $this instanceof Text || $this instanceof Comment || $this instanceof ProcessingInstruction) {
// OPTIMIZATION: No need for the other steps as PHP's DOM handles this fine
return $this->innerNode->ownerDocument->getWrapperNode($this->innerNode->cloneNode());
}
# ↪ Otherwise
# Do nothing.
else {
$copy = $this->innerNode->ownerDocument->getWrapperNode($this->innerNode->cloneNode());
}
# 4. Set copy’s node document and document to copy, if copy is a document, and
# set copy’s node document to document otherwise.
// PHP's DOM does this already
if ($deep) {
# 5. Run any cloning steps defined for node in other applicable specifications
# and pass copy, node, document and the clone children flag if set, as
# parameters.
if ($this instanceof HTMLTemplateElement) {
# The cloning steps for a template element node being cloned to a copy copy must
# run the following steps:
#
# 1. If the clone children flag is not set in the calling clone algorithm, return.
// This is done with the if statements above.
# 2. Let copied contents be the result of cloning all the children of node's template
# contents, with document set to copy's template contents's node document, and
# with the clone children flag set.
# 3. Append copied contents to copy's template contents.
$copy->content = $this->content->cloneNode(true);
}
# 6. If the clone children flag is set, clone all the children of node and append
# them to copy, with document as specified and the clone children flag being
# set.
if ($this instanceof Document || $this instanceof DocumentFragment || $this instanceof Element) {
$childNodes = $this->childNodes;
foreach ($childNodes as $child) {
$copy->appendChild($child->cloneNode(true));
}
}
}
# 7. Return copy.
return $copy;
}
public function compareDocumentPosition(Node $other): int {

77
tests/cases/TestNode.php

@ -41,6 +41,7 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::isEqualNode
* @covers \MensBeam\HTML\DOM\ProcessingInstruction::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__construct
@ -56,10 +57,80 @@ class TestNode extends \PHPUnit\Framework\TestCase {
*/
public function testMethod_cloneNode() {
$d = new Document();
$d2 = new XMLDocument();
$doctype = $d->appendChild($d->implementation->createDocumentType('html', '', ''));
$attr = $d->createAttribute('href');
$attr->value = 'https://poop💩.poop';
$cdata = $d2->createCDATASection('ook');
$comment = $d->createComment('comment');
$element = $d->createElement('html');
$element->appendChild($d->createElement('body'));
$pi = $d->createProcessingInstruction('ook', 'eek');
$text = $d->createTextNode('ook');
$frag = $d->createDocumentFragment();
$frag->appendChild($d->createTextNode('ook'));
// Node::cloneNode on attribute node
$attrClone = $attr->cloneNode();
$this->assertNotSame($attrClone, $attr);
$this->assertTrue($attrClone->isEqualNode($attr));
// Node::cloneNode on CDATA section
$cdataClone = $cdata->cloneNode();
$this->assertNotSame($cdataClone, $cdata);
$this->assertTrue($cdataClone->isEqualNode($cdata));
// Node::cloneNode on comment
$commentClone = $comment->cloneNode();
$this->assertNotSame($commentClone, $comment);
$this->assertTrue($commentClone->isEqualNode($comment));
// Node::cloneNode on document
$dClone = $d->cloneNode(true);
$this->assertNotSame($dClone, $d);
// Children on documents aren't cloned
$this->assertFalse($dClone->isEqualNode($d));
// Node::cloneNode on doctype
$doctypeClone = $doctype->cloneNode();
$this->assertNotSame($doctypeClone, $doctype);
$this->assertTrue($doctypeClone->isEqualNode($doctype));
// Node::cloneNode on document fragment
$fragClone = $frag->cloneNode(true);
$this->assertNotSame($fragClone, $frag);
// Children on document fragments aren't cloned
$this->assertFalse($fragClone->isEqualNode($frag));
// Node::cloneNode on element
$elementClone = $element->cloneNode(true);
$this->assertNotSame($elementClone, $element);
// Children on documents aren't cloned
$this->assertTrue($elementClone->isEqualNode($element));
/*
// Node::nodeType on comment
$this->assertSame($d, $d->createComment('comment')->ownerDocument);
// Node::nodeType on document
$this->assertNull($d->ownerDocument);
// Node::nodeType on doctype
$this->assertSame($d, $d->implementation->createDocumentType('html', '', '')->ownerDocument);
// Node::nodeType on document fragment
$this->assertSame($d, $d->createDocumentFragment()->ownerDocument);
// Node::cloneNode on Document
$d2 = $d->cloneNode(true);
$this->assertSame(Document::class, $d2::class);
// Node::nodeType on element
$this->assertSame($d, $d->createElement('html')->ownerDocument);
// Node::nodeType on processing instruction
$this->assertSame($d, $d->createProcessingInstruction('ook', 'eek')->ownerDocument);
// Node::nodeType on text node
$this->assertSame($d, $d->createTextNode('ook')->ownerDocument);
*/
}

Loading…
Cancel
Save