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).
// DEVIATION: The name is trimmed because PHP's DOM does not
// accept the empty string as a DOCTYPE name
$name = trim($node->childNodes->item(0)->name, ' ');
$name = trim($currentNode->name, ' ');
$s .= "<!DOCTYPE $name>";
}
}

32
lib/traits/ChildNode.php

@ -12,6 +12,38 @@ namespace MensBeam\HTML\DOM;
trait ChildNode {
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) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);

33
lib/traits/Node.php

@ -41,4 +41,37 @@ trait Node {
}
})->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
// 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);
@ -122,7 +121,7 @@ trait ParentNode {
# 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 ChildNode trait.
// errors in Node trait.
# 2. If node is a host-including inclusive ancestor of parent, then throw a
# "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
# host-including inclusive ancestor of B’s root’s host.
if ($node->parentNode !== null) {
if ($this->isSameNode($node) || $this->moonwalk(function($n) use($node) {
if ($n->isSameNode($node)) {
return true;
}
})->current() !== null) {
if ($this->parentNode !== null && ($this->isSameNode($node) || $this->moonwalk(function($n) use($node) {
return ($n->isSameNode($node));
})->current() !== null)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} else {
$parentRoot = $this->getRootNode();
$parentRootHost = $parentRoot->host;
if ($parentRoot instanceof DocumentFragment && $parentRootHost !== null && ($host->isSameNode($node) || $host->moonwalk(function($n) use($node) {
if ($n->isSameNode($node)) {
return true;
if ($parentRoot instanceof DocumentFragment) {
$parentRootHost = $parentRoot->host;
if ($parentRootHost !== null && ($parentRootHost->isSameNode($node) || $parentRootHost->moonwalk(function($n) use ($node) {
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
# 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 ChildNode trait
if (($node instanceof Text && $this instanceof Document) || ($node instanceof \DOMDocumentType && !$this instanceof Document)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 6. If parent is a document, and any of the statements below, switched on node,
# are true, then throw a "HierarchyRequestError" DOMException.
# 6. If parent is a document, and any of the statements below, switched on the
# interface node implements, are true, then throw a "HierarchyRequestError".
if ($this instanceof Document) {
# DocumentFragment node
# 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
# child, child is a doctype, or child is non-null and a doctype is following
# child.
if ($node instanceof \DOMDocumentType) {
if ($node->childNodes->length > 1 || $node->firstChild instanceof Text) {
if ($node instanceof DocumentFragment) {
$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);
} else {
if ($node->firstChild instanceof \DOMDocumentType) {
} elseif ($nodeChildElementCount === 1) {
if ($this->children->length > 0 || $child instanceof \DOMDocumentType) {
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) {
$n = $child;
while ($n = $n->nextSibling) {
@ -237,7 +233,7 @@ trait ParentNode {
if ($child !== null) {
$n = $child;
while ($n = $n->prevSibling) {
while ($n = $n->previousSibling) {
if ($n instanceof Element) {
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
* PHP's DOM handles natively in PHP8
*/
trait ParentNode {
trait ParentNodePolyfill {
protected function __get_childElementCount(): int {
# The childElementCount getter steps are to return the number of children of
# this that are elements.

28
lib/traits/Walk.php

@ -58,20 +58,24 @@ trait Walk {
if (!$backwards) {
$node = $node->firstChild;
do {
$next = $node->nextSibling;
if ($filter === null || $filter($node) === true) {
yield $node;
}
} while ($node = $next);
if ($node !== null) {
do {
$next = $node->nextSibling;
if ($filter === null || $filter($node) === true) {
yield $node;
}
} while ($node = $next);
}
} else {
$node = $node->lastChild;
do {
$next = $node->previousSibling;
if ($filter === null || $filter($node) === true) {
yield $node;
}
} while ($node = $next);
if ($node !== null) {
do {
$next = $node->previousSibling;
if ($filter === null || $filter($node) === true) {
yield $node;
}
} 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">
<file>cases/TestDocument.php</file>
<file>cases/TestElement.php</file>
<file>cases/TestParentNode.php</file>
</testsuite>
<testsuite name="Serializer">
<file>cases/Serializer/TestSerializer.php</file>

1158
yarn.lock

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