Browse Source

Worked on pre insertion validity coverage and bugs in it

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
8888179bd8
  1. 2
      lib/Document.php
  2. 32
      lib/traits/ChildNode.php
  3. 33
      lib/traits/Node.php
  4. 85
      lib/traits/ParentNode.php
  5. 2
      lib/traits/ParentNodePolyfill.php
  6. 28
      lib/traits/Walk.php
  7. 104
      tests/cases/TestParentNode.php
  8. 1
      tests/phpunit.dist.xml
  9. 1158
      yarn.lock

2
lib/Document.php

@ -772,7 +772,7 @@ class Document extends \DOMDocument {
# GREATER-THAN SIGN). # GREATER-THAN SIGN).
// DEVIATION: The name is trimmed because PHP's DOM does not // DEVIATION: The name is trimmed because PHP's DOM does not
// accept the empty string as a DOCTYPE name // accept the empty string as a DOCTYPE name
$name = trim($node->childNodes->item(0)->name, ' '); $name = trim($currentNode->name, ' ');
$s .= "<!DOCTYPE $name>"; $s .= "<!DOCTYPE $name>";
} }
} }

32
lib/traits/ChildNode.php

@ -12,6 +12,38 @@ namespace MensBeam\HTML\DOM;
trait ChildNode { trait ChildNode {
use Node; use Node;
public function after(...$nodes): void {
# The after(nodes) method steps are:
#
# 1. Let parent be this’s parent.
$parent = $this->parentNode;
# 2. If parent is null, then return.
if ($parent === null) {
return;
}
# 3. Let viableNextSibling be this’s first following sibling not in nodes;
# otherwise null.
$n = $this;
$nextViableSibling = null;
while ($n = $n->followingSibling) {
foreach ($nodes as $nodeOrString) {
if ($nodeOrString instanceof \DOMNode && $nodeOrString->isSameNode($n->followingSibling)) {
continue;
}
}
$nextViableSibling = $n;
break;
}
# 4. Let node be the result of converting nodes into a node, given nodes and this’s
# node document.
$node = $this->convertNodesToNode($nodes);
# 5. Pre-insert node into parent before viableNextSibling.
$parent->insertBefore($node, $viableNextSibling);
}
public function appendChild($node) { public function appendChild($node) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);

33
lib/traits/Node.php

@ -41,4 +41,37 @@ trait Node {
} }
})->current(); })->current();
} }
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 $k => &$n) {
// Can't do union types until PHP 8... OTL
if (!$n instanceof \DOMNode && !is_string($n)) {
throw new Exception(Exception::ARGUMENT_TYPE_ERROR, $k, 'nodes', '\DOMNode|string', gettype($n));
}
if (is_string($n)) {
$n = $this->ownerDocument->createTextNode($n);
}
if ($node !== null) {
$node->appendChild($n);
} else {
$node = $n;
}
}
return $node;
}
} }

85
lib/traits/ParentNode.php

@ -21,7 +21,6 @@ trait ParentNode {
// almost identical; so, using that. PHP's DOM doesn't provide the end user any // 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 // way to create a \DOMNodeList from scratch, so going to cheat and use XPath to
// make one for us. // make one for us.
$isDocument = ($this instanceof Document); $isDocument = ($this instanceof Document);
$document = ($isDocument) ? $this : $this->ownerDocument; $document = ($isDocument) ? $this : $this->ownerDocument;
return $document->xpath->query('//*', (!$isDocument) ? $this : null); return $document->xpath->query('//*', (!$isDocument) ? $this : null);
@ -122,7 +121,7 @@ trait ParentNode {
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw # 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" DOMException. # a "HierarchyRequestError" DOMException.
// Not necessary because they've been disabled and return hierarchy request // Not necessary because they've been disabled and return hierarchy request
// errors in ChildNode trait. // errors in Node trait.
# 2. If node is a host-including inclusive ancestor of parent, then throw a # 2. If node is a host-including inclusive ancestor of parent, then throw a
# "HierarchyRequestError" DOMException. # "HierarchyRequestError" DOMException.
@ -131,21 +130,19 @@ trait ParentNode {
# A is an inclusive ancestor of B, or if B’s root has a non-null host and A is a # 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. # host-including inclusive ancestor of B’s root’s host.
if ($node->parentNode !== null) { if ($node->parentNode !== null) {
if ($this->isSameNode($node) || $this->moonwalk(function($n) use($node) { if ($this->parentNode !== null && ($this->isSameNode($node) || $this->moonwalk(function($n) use($node) {
if ($n->isSameNode($node)) { return ($n->isSameNode($node));
return true; })->current() !== null)) {
}
})->current() !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} else { } else {
$parentRoot = $this->getRootNode(); $parentRoot = $this->getRootNode();
$parentRootHost = $parentRoot->host; if ($parentRoot instanceof DocumentFragment) {
if ($parentRoot instanceof DocumentFragment && $parentRootHost !== null && ($host->isSameNode($node) || $host->moonwalk(function($n) use($node) { $parentRootHost = $parentRoot->host;
if ($n->isSameNode($node)) { if ($parentRootHost !== null && ($parentRootHost->isSameNode($node) || $parentRootHost->moonwalk(function($n) use ($node) {
return true; return ($n->isSameNode($node));
})->current() !== null)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} }
})->current() !== null)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} }
} }
} }
@ -166,31 +163,30 @@ trait ParentNode {
# 5. If either node is a Text node and parent is a document, or node is a # 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" # doctype and parent is not a document, then throw a "HierarchyRequestError"
# DOMException. # DOMException.
// Not necessary because they've been disabled and return hierarchy request if (($node instanceof Text && $this instanceof Document) || ($node instanceof \DOMDocumentType && !$this instanceof Document)) {
// errors in ChildNode trait throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 6. If parent is a document, and any of the statements below, switched on node, # 6. If parent is a document, and any of the statements below, switched on the
# are true, then throw a "HierarchyRequestError" DOMException. # interface node implements, are true, then throw a "HierarchyRequestError".
if ($this instanceof Document) { if ($this instanceof Document) {
# DocumentFragment node # DocumentFragment node
# If node has more than one element child or has a Text node child. # If node has more than one element child or has a Text node child.
# Otherwise, if node has one element child and either parent has an element # Otherwise, if node has one element child and either parent has an element
# child, child is a doctype, or child is non-null and a doctype is following # child, child is a doctype, or child is non-null and a doctype is following
# child. # child.
if ($node instanceof \DOMDocumentType) { if ($node instanceof DocumentFragment) {
if ($node->childNodes->length > 1 || $node->firstChild instanceof Text) { $nodeChildElementCount = $node->children->length;
die(var_export($node->children));
if ($nodeChildElementCount > 1 || $node->walkShallow(function($n) {
return ($n instanceof Text);
})->current() !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} else { } elseif ($nodeChildElementCount === 1) {
if ($node->firstChild instanceof \DOMDocumentType) { if ($this->children->length > 0 || $child instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} }
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
if ($child !== null) { if ($child !== null) {
$n = $child; $n = $child;
while ($n = $n->nextSibling) { while ($n = $n->nextSibling) {
@ -237,7 +233,7 @@ trait ParentNode {
if ($child !== null) { if ($child !== null) {
$n = $child; $n = $child;
while ($n = $n->prevSibling) { while ($n = $n->previousSibling) {
if ($n instanceof Element) { if ($n instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} }
@ -252,37 +248,4 @@ trait ParentNode {
} }
} }
} }
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;
}
}
return $node;
}
} }

2
lib/traits/ParentNodePolyfill.php

@ -14,7 +14,7 @@ if (version_compare(\PHP_VERSION, '8.0', '<')) {
* Used for PHP7 installations to polyfill getters, setters, and methods that * Used for PHP7 installations to polyfill getters, setters, and methods that
* PHP's DOM handles natively in PHP8 * PHP's DOM handles natively in PHP8
*/ */
trait ParentNode { trait ParentNodePolyfill {
protected function __get_childElementCount(): int { protected function __get_childElementCount(): int {
# The childElementCount getter steps are to return the number of children of # The childElementCount getter steps are to return the number of children of
# this that are elements. # this that are elements.

28
lib/traits/Walk.php

@ -58,20 +58,24 @@ trait Walk {
if (!$backwards) { if (!$backwards) {
$node = $node->firstChild; $node = $node->firstChild;
do { if ($node !== null) {
$next = $node->nextSibling; do {
if ($filter === null || $filter($node) === true) { $next = $node->nextSibling;
yield $node; if ($filter === null || $filter($node) === true) {
} yield $node;
} while ($node = $next); }
} while ($node = $next);
}
} else { } else {
$node = $node->lastChild; $node = $node->lastChild;
do { if ($node !== null) {
$next = $node->previousSibling; do {
if ($filter === null || $filter($node) === true) { $next = $node->previousSibling;
yield $node; if ($filter === null || $filter($node) === true) {
} yield $node;
} while ($node = $next); }
} while ($node = $next);
}
} }
} }
} }

104
tests/cases/TestParentNode.php

@ -0,0 +1,104 @@
<?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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
ElementMap,
Exception,
HTMLTemplateElement
};
use MensBeam\HTML\Parser,
org\bovigo\vfs\vfsStream;
/** @covers \MensBeam\HTML\DOM\ParentNode */
class TestParentNode extends \PHPUnit\Framework\TestCase {
public function providePreInsertionValidationFailures(): iterable {
return [
[ function() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$b = $d->documentElement->appendChild($d->createElement('body'));
$b->appendChild($d->documentElement);
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$t = $d->appendChild($d->createElement('template'));
$d->appendChild($t->content);
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$b = $d->documentElement->appendChild($d->createElement('body'));
$t = $b->appendChild($d->createElement('template'));
$t->content->appendChild($b);
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$b = $d->documentElement->appendChild($d->createElement('body'));
$d->insertBefore($d->createElement('fail'), $b);
}, DOMException::NOT_FOUND ],
[ function() {
$d = new Document();
$df = $d->createDocumentFragment();
$df->appendChild($d->createElement('html'));
$df->appendChild($d->createTextNode(' '));
$d->appendChild($df);
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$d->appendChild($d);
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$d->appendChild($d->implementation->createDocumentType('html'));
$d->appendChild($d->implementation->createDocumentType('html'));
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->appendChild($d->implementation->createDocumentType('html'));
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$c = $d->appendChild($d->createComment('ook'));
$d->insertBefore($d->implementation->createDocumentType('html'), $c);
}, DOMException::HIERARCHY_REQUEST_ERROR ],
[ function() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->documentElement->insertBefore($d->implementation->createDocumentType('html'));
}, DOMException::HIERARCHY_REQUEST_ERROR ],
/*[ function() {
$d = new Document();
$d->createElementNS(null, '<ook>');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$d->createElementNS(null, 'xmlns');
}, DOMException::NAMESPACE_ERROR ]*/
];
}
/**
* @dataProvider providePreInsertionValidationFailures
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::validateAndExtract
*/
public function testPreInsertionValidationFailures(\Closure $closure, int $errorCode): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
}
}

1
tests/phpunit.dist.xml

@ -18,6 +18,7 @@
<testsuite name="DOM"> <testsuite name="DOM">
<file>cases/TestDocument.php</file> <file>cases/TestDocument.php</file>
<file>cases/TestElement.php</file> <file>cases/TestElement.php</file>
<file>cases/TestParentNode.php</file>
</testsuite> </testsuite>
<testsuite name="Serializer"> <testsuite name="Serializer">
<file>cases/Serializer/TestSerializer.php</file> <file>cases/Serializer/TestSerializer.php</file>

1158
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save