Browse Source

Many updates, tests fail

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
d9ea147932
  1. 4
      README.md
  2. 84
      lib/ChildNode.php
  3. 21
      lib/Document.php
  4. 2
      lib/DocumentFragment.php
  5. 25
      lib/DocumentOrElement.php
  6. 35
      lib/Element.php
  7. 4
      lib/NamedNodeMap.php
  8. 134
      lib/Node.php
  9. 27
      lib/NonElementParentNode.php
  10. 113
      tests/cases/TestNode.php

4
README.md

@ -65,6 +65,6 @@ The primary aim of this library is accuracy. However, due either to limitations
3. Per the specification an actual HTML document cannot be created outside of the parser itself unless created via `DOMImplementation::createHTMLDocument`. Also, per the spec `DOMImplementation` cannot be instantiated via its constructor. This would require in this library's use case first creating a document then creating an HTML document via its implementation. This is impractical, so in this library (like PHP DOM itself) a `DOMImplementation` can be instantiated independent of a document.
4. The specification shows `Document` as being able to be instantated through its constructor and shows `XMLDocument` as inheriting from `Document`. In browsers `XMLDocument` cannot be instantiated through its constructor. We will follow the specification here and allow it.
5. CDATA section nodes, text nodes, and document fragments per the specification can be instantiated by their constructors independent of the `Document::createCDATASectionNode`, `Document::createTextNode`, and `Document::createDocumentFragment` methods respectively. This is not possible currently with this library and probably never will be due to the difficulty of implementing it and the awkwardness of their being different from every other node type in this respect.
6. This implementation will not implement the `NodeIterator` and `TreeWalker` APIs. They are horribly conceived and impractical APIs that few people actually use because it's literally easier to write recursive loops to walk through the DOM than it is to use those APIs. They have instead been replaced with the `ChildNode::moonwalk`, `ParentNode::walk`, `ChildNode::walkFollowing`, and `ChildNode::walkPreceding` generators.
7. All of the `Range` APIs will also not be implemented due to the sheer complexity of creating them in userland and how it adds undue difficulty to node manipulation in the "core" DOM. Numerous operations reference in excrutiating detail what to do with Ranges when manipulating nodes and would have to be added here to be compliant or mostly so.
6. This implementation will not implement the `NodeIterator` and `TreeWalker` APIs. They are horribly conceived and impractical APIs that few people actually use because it's literally easier to write recursive loops to walk through the DOM than it is to use those APIs. They have instead been replaced with the `ParentNode::walk` generator.
7. All of the `Range` APIs will also not be implemented due to the sheer complexity of creating them in userland and how it adds undue difficulty to node manipulation in the "core" DOM. Numerous operations reference in excrutiating detail what to do with Ranges when manipulating nodes and would have to be added here to be compliant or mostly so -- slowing everything else down in the process on an already front-heavy library.
8. Aside from `HTMLElement`, `HTMLTemplateElement`, `MathMLElement`, and `SVGElement` none of the specific derived element classes (such as `HTMLAnchorElement` or `SVGSVGElement`) are implemented. The focus on this library will be on the core DOM before moving onto those. They may or may not be implemented in the future.

84
lib/ChildNode.php

@ -14,88 +14,4 @@ use MensBeam\HTML\DOM\InnerNode\{
trait ChildNode {
/**
* Generator which walks backwards through the DOM from the node the method is
* being run on. Nonstandard.
*
* @param ?\Closure $filter An optional callback function used to filter; if not provided the generator will
* just yield every node.
* @param bool $includeReferenceNode An optional boolean flag which if true includes the reference node ($this) in
* the iteration.
*/
public function moonwalk(?\Closure $filter = null, bool $includeReferenceNode = false): \Generator {
$node = $this->getInnerNode($this)->parentNode;
if ($node !== null) {
$doc = (!$node instanceof InnerDocument) ? $node->ownerDocument : $node;
do {
$next = $node->parentNode;
$nodeToFilter = $doc->getWrapperNode($node);
$result = ($filter === null) ? true : $filter($nodeToFilter);
if ($result === true) {
yield $nodeToFilter;
}
} while ($node = $next);
}
}
/**
* Generator which walks forwards through an element's siblings. Nonstandard.
*
* @param ?\Closure $filter An optional callback function used to filter; if not provided the generator will
* just yield every node.
* @param bool $includeReferenceNode An optional boolean flag which if true includes the reference node ($this) in
* the iteration.
*/
public function walkFollowing(?\Closure $filter = null, bool $includeReferenceNode = false): \Generator {
$node = $this->innerNode;
if (!$includeReferenceNode) {
$node = $node->nextSibling;
}
if ($node !== null) {
$doc = (!$node instanceof InnerDocument) ? $node->ownerDocument : $node;
do {
$next = $node->nextSibling;
$wrapperNode = $doc->getWrapperNode($node);
$result = ($filter === null) ? true : $filter($wrapperNode);
if ($result === true) {
yield $wrapperNode;
}
} while ($node = $next);
}
}
/**
* Generator which walks backwards through an element's siblings. Nonstandard.
*
* @param ?\Closure $filter An optional callback function used to filter; if not provided the generator will
* just yield every node.
* @param bool $includeReferenceNode An optional boolean flag which if true includes the reference node ($this) in
* the iteration.
*/
public function walkPreceding(?\Closure $filter = null, bool $includeReferenceNode = false): \Generator {
$node = $this->innerNode;
if (!$includeReferenceNode) {
$node = $node->previousSibling;
}
if ($node !== null) {
$doc = (!$node instanceof InnerDocument) ? $node->ownerDocument : $node;
do {
$next = $node->previousSibling;
$wrapperNode = $doc->getWrapperNode($node);
$result = ($filter === null) ? true : $filter($wrapperNode);
if ($result === true) {
yield $wrapperNode;
}
} while ($node = $next);
}
}
}

21
lib/Document.php

@ -19,7 +19,7 @@ use MensBeam\HTML\Parser\{
class Document extends Node {
use DocumentOrElement, ParentNode;
use DocumentOrElement, NonElementParentNode, ParentNode;
protected string $_characterSet = 'UTF-8';
protected string $_compatMode = 'CSS1Compat';
@ -28,17 +28,22 @@ class Document extends Node {
protected string $_URL = '';
protected function __get_body(): ?Element {
if ($this->documentElement === null || !$this->documentElement->hasChildNodes()) {
$documentElement = $this->innerNode->documentElement;
if ($documentElement === null || !$documentElement->hasChildNodes()) {
return null;
}
# The body element of a document is the first of the html element's children
# that is either a body element or a frameset element, or null if there is no
# such element.
return $this->documentElement->firstChild->walkFollowing(function($n) {
$name = strtolower($n->nodeName);
return ($n instanceof Element && $n->namespaceURI === Parser::HTML_NAMESPACE && ($name === 'body' || $name === 'frameset'));
}, true)->current();
$n = $documentElement->firstChild;
do {
if ($n instanceof \DOMElement && $n->namespaceURI === null && ($n->nodeName === 'body' || $n->nodeName === 'frameset')) {
return $n->ownerDocument->getWrapperNode($n);
}
} while ($n = $n->nextSibling);
return null;
}
protected function __get_charset(): string {
@ -83,7 +88,7 @@ class Document extends Node {
$this->_implementation = new DOMImplementation($this);
if ($source !== null) {
$this->importHTML($source, $charset ?? 'windows-1252');
$this->loadHTML($source, $charset ?? 'windows-1252');
} elseif ($charset !== 'UTF-8') {
$this->_characterSet = Charset::fromCharset((string)$charset) ?? 'UTF-8';
}
@ -263,7 +268,7 @@ class Document extends Node {
$source = $source->document;
$childNodes = $source->childNodes;
foreach ($source->childNodes as $child) {
$this->appendChild($this->importNode($child, true));
$this->innerNode->appendChild($this->cloneInnerNode($child, $this->innerNode, true));
}
}

2
lib/DocumentFragment.php

@ -10,7 +10,7 @@ namespace MensBeam\HTML\DOM;
class DocumentFragment extends Node {
use ParentNode;
use NonElementParentNode, ParentNode;
protected ?\WeakReference $host = null;

25
lib/DocumentOrElement.php

@ -7,8 +7,11 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\InnerNode\Document as InnerDocument,
MensBeam\HTML\Parser;
use MensBeam\HTML\DOM\InnerNode\{
Document as InnerDocument,
Reflection
};
use MensBeam\HTML\Parser;
/**
@ -17,6 +20,24 @@ use MensBeam\HTML\DOM\InnerNode\Document as InnerDocument,
* both the Document and Element interfaces.
*/
trait DocumentOrElement {
public function getElementsByTagName(string $qualifiedName): HTMLCollection {
// HTMLCollections cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\HTMLCollection', (!$this instanceof Document) ? $this->innerNode->ownerDocument : $this->innerNode, $this->innerNode->getElementsByTagNameNS(null, $qualifiedName));
}
public function getElementsByTagNameNS(?string $namespace = null, string $localName): HTMLCollection {
// If an HTML document and the namespace is the HTML namespace change it to null
// before running internally because HTML nodes are stored with null namespaces
// because of bugs in PHP DOM.
if ($namespace === Parser::HTML_NAMESPACE && (($this instanceof Document && !$this instanceof XMLDocument) || !$this->ownerDocument instanceof XMLDocument)) {
$namespace = null;
}
// HTMLCollections cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\HTMLCollection', (!$this instanceof Document) ? $this->innerNode->ownerDocument : $this->innerNode, $this->innerNode->getElementsByTagNameNS($namespace, $localName));
}
protected function validateAndExtract(string $qualifiedName, ?string $namespace = null): array {
# To validate and extract a namespace and qualifiedName, run these steps:
# 1. If namespace is the empty string, set it to null.

35
lib/Element.php

@ -45,6 +45,41 @@ class Element extends Node {
parent::__construct($element);
}
public function getAttributeNode(string $qualifiedName): ?Attr {
# The getAttributeNode(qualifiedName) method steps are to return the result of
# getting an attribute given qualifiedName and this.
#
# To get an attribute by name given a qualifiedName and element element, run
# these steps:
#
# 1. If element is in the HTML namespace and its node document is an HTML document,
# then set qualifiedName to qualifiedName in ASCII lowercase.
// Document will always be an HTML document
if (!$this instanceof XMLDocument && $this->namespaceURI === Parser::HTML_NAMESPACE) {
$qualifiedName = strtolower($qualifiedName);
}
# 2. Return the first attribute in element’s attribute list whose qualified name is
# qualifiedName; otherwise null.
// Going to try to handle this by getting the PHP DOM to do the heavy lifting
// when we can because it's faster.
$attr = $this->innerNode->getAttributeNode($qualifiedName);
if ($attr === false) {
// Replace any offending characters with "UHHHHHH" where H are the uppercase
// hexadecimal digits of the character's code point
$qualifiedName = $this->coerceName($qualifiedName);
$attributes = $this->innerNode->attributes;
foreach ($attributes as $a) {
if ($a->nodeName === $qualifiedName) {
return $this->innerNode->ownerDocument->getWrapperNode($a);
}
}
return null;
}
return ($attr !== false) ? $this->innerNode->ownerDocument->getWrapperNode($attr) : null;
}
public function hasAttribute(string $qualifiedName): bool {
# The hasAttribute(qualifiedName) method steps are:

4
lib/NamedNodeMap.php

@ -15,10 +15,10 @@ class NamedNodeMap extends Collection {
protected Element $element;
protected function __construct(Element $element, InnerDocument $innerDocument, \DOMNamedNodeMap $namedNodeMap) {
protected function __construct(Element $element, InnerDocument $innerDocument, ?\DOMNamedNodeMap $namedNodeMap) {
$this->element = $element;
$this->innerDocument = $innerDocument;
$this->innerCollection = $namedNodeMap;
$this->innerCollection = $namedNodeMap ?? new \DOMNamedNodeMap();
}

134
lib/Node.php

@ -325,6 +325,8 @@ abstract class Node {
# 2. Let node1 be other and node2 be this.
$node1 = $other;
$node2 = $this;
$innerNode1 = $this->getInnerNode($other);
$innerNode2 = $this->innerNode;
# 3. Let attr1 and attr2 be null.
$attr1 = $attr2 = null;
@ -332,20 +334,22 @@ abstract class Node {
# 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’s
# element.
if ($node1 instanceof Attr) {
$attr1 = $node1;
$attr1 = $innerNode1;
$node1 = $attr1->ownerElement;
}
# 5. If node2 is an attribute, then:
if ($node2 instanceof Attr) {
# 1. Set attr2 to node2 and node2 to attr2’s element.
$attr2 = $node2;
$attr2 = $innerNode2;
$node2 = $attr2->ownerElement;
# 2. If attr1 and node1 are non-null, and node2 is node1, then:
if ($attr1 !== null && $node1 !== null && $node2 === $node1) {
# 1. For each attr in node2’s attribute list:
foreach ($node2->attributes as $attr) {
$attributes = $innerNode2->attributes;
die(var_export($attributes));
foreach ($attributes as $attr) {
# 1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
if ($attr === $attr1) {
return Node::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + Node::DOCUMENT_POSITION_PRECEDING;
@ -376,12 +380,12 @@ abstract class Node {
return Node::DOCUMENT_POSITION_DISCONNECTED + Node::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + ((self::$rand === 0) ? Node::DOCUMENT_POSITION_PRECEDING : Node::DOCUMENT_POSITION_FOLLOWING);
}
$n = $node1;
$n = $innerNode1;
while ($n = $n->parentNode) {
$root1 = $n;
}
$n = $node2;
$n = $innerNode2;
while ($n = $n->parentNode) {
$root2 = $n;
}
@ -393,22 +397,23 @@ abstract class Node {
# 7. If node1 is an ancestor of node2 and attr1 is null, or node1 is node2 and attr2
# is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINS to
# DOCUMENT_POSITION_PRECEDING.
if (($node1 === $node2 && $attr2 !== null) || ($attr1 === null && $node2->contains($node1))) {
if (($node1 === $node2 && $attr2 !== null) || ($attr1 === null && $this->containsInner($innerNode1, $innerNode2))) {
return Node::DOCUMENT_POSITION_CONTAINS + Node::DOCUMENT_POSITION_PRECEDING;
}
# 8. If node1 is a descendant of node2 and attr2 is null, or node1 is node2 and attr1
# is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINED_BY to
# DOCUMENT_POSITION_FOLLOWING.
if (($node1 === $node2 && $attr1 !== null) || ($attr2 === null && $node2->contains($node1))) {
if (($node1 === $node2 && $attr1 !== null) || ($attr2 === null && $this->containsInner($innerNode2, $innerNode1))) {
return Node::DOCUMENT_POSITION_CONTAINED_BY + Node::DOCUMENT_POSITION_FOLLOWING;
}
# 9. If node1 is preceding node2, then return DOCUMENT_POSITION_PRECEDING.
if ($node2->walkPreceding(function($n) use($node1) {
return ($n === $node1);
})->current() !== null) {
return Node::DOCUMENT_POSITION_PRECEDING;
$n = $innerNode2;
while ($n = $n->previousSibling) {
if ($n === $innerNode1) {
return Node::DOCUMENT_POSITION_PRECEDING;
}
}
# 10. Return DOCUMENT_POSITION_FOLLOWING.
@ -416,11 +421,7 @@ abstract class Node {
}
public function contains(?Node $other): bool {
# The contains(other) method steps are to return true if other is an inclusive
# descendant of this; otherwise false (including when other is null).
return ($other->moonWalk(function($n) use($other) {
return ($n === $other);
})->current() !== null);
return $this->containsInner($this->innerNode, $this->getInnerNode($other));
}
public function getRootNode(): ?Node {
@ -605,27 +606,27 @@ abstract class Node {
# child that is not child or a doctype is following child.
if ($node instanceof DocumentFragment) {
$nodeChildElementCount = $node->childElementCount;
if ($nodeChildElementCount > 1 || $node->firstChild->walkFollowing(function($n) {
return ($n instanceof Text);
}, true)->current() !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
if ($nodeChildElementCount > 1) {
$n = $this->getInnerNode($node)->firstChild;
do {
if ($n instanceof \DOMText) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
} while ($n = $n->nextSibling);
} elseif ($nodeChildElementCount === 1) {
$beforeChild = true;
if ($node->firstChild->walkFollowing(function($n) use(&$beforeChild, $child) {
if (!$beforeChild && $n instanceof DocumentType) {
return true;
$n = $this->getInnerNode($node)->firstChild;
do {
if (!$beforeChild && $n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($n instanceof Element && $n !== $child) {
return true;
if ($n instanceof \DOMElement && $n !== $child) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} elseif ($n === $child) {
$beforeChild = false;
}
return false;
}, true)->current() !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
} while ($n = $n->nextSibling);
}
}
@ -634,21 +635,18 @@ abstract class Node {
# child.
elseif ($node instanceof Element) {
$beforeChild = true;
if ($node->firstChild->walkFollowing(function($n) use(&$beforeChild, $child) {
if (!$beforeChild && $n instanceof DocumentType) {
return true;
$n = $this->getInnerNode($node)->firstChild;
do {
if (!$beforeChild && $n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($n instanceof Element && $n !== $child) {
return true;
if ($n instanceof \DOMElement && $n !== $child) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} elseif ($n === $child) {
$beforeChild = false;
}
return false;
}, true)->current() !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
} while ($n = $n->nextSibling);
}
# ↪ DocumentType
@ -656,21 +654,18 @@ abstract class Node {
# child.
elseif ($node instanceof DocumentType) {
$beforeChild = true;
if ($node->firstChild->walkFollowing(function($n) use(&$beforeChild, $child) {
if ($beforeChild && $n instanceof Element) {
return true;
$n = $this->getInnerNode($node)->firstChild;
do {
if (!$beforeChild && $n instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($n instanceof DocumentType && $n !== $child) {
return true;
if ($n instanceof \DOMDocumentType && $n !== $child) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} elseif ($n === $child) {
$beforeChild = false;
}
return false;
}, true)->current() !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
} while ($n = $n->nextSibling);
}
}
@ -698,8 +693,15 @@ abstract class Node {
# 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, so it will be
// handled instead in the "Otherwise" section of step #3.
if ($node instanceof \DOMElement) {
$copy = ($import) ? $document->importNode($node) : $node->cloneNode();
// PHP DOM doesn't import id attributes where
// NonElementParentNode::getElementById can see them, so let's fix that.
if ($id = $copy->getAttributeNode('id')) {
$copy->setIdAttributeNode($id, true);
}
}
# 3. Otherwise, let copy be a node that implements the same interfaces as node, and
# fulfills these additional requirements, switching on the interface node
@ -707,7 +709,7 @@ abstract class Node {
#
# ↪ Document
# Set copy’s encoding, content type, URL, origin, type, and mode to those of node.
if ($node instanceof \DOMDocumentType) {
elseif ($node instanceof \DOMDocumentType) {
// OPTIMIZATION: No need for the other steps as the DocumentType node is created
// using this document's implementation
return $document->implementation->createDocumentType($node->name, $node->publicId, $node->systemId);
@ -821,6 +823,17 @@ abstract class Node {
$innerNode = $this->getInnerNode($node);
$innerDocument = $this->getInnerNode($document);
if ($node instanceof Element) {
$copy = ($import) ? $innerDocument->importNode($innerNode) : $innerNode->cloneNode();
$copyWrapper = $innerDocument->getWrapperNode($copy);
// PHP DOM doesn't import id attributes where
// NonElementParentNode::getElementById can see them, so let's fix that.
if ($id = $copy->getAttributeNode('id')) {
$copy->setIdAttributeNode($id, true);
}
}
# ↪ DocumentType
# Set copy’s name, public ID, and system ID to those of node.
if ($node instanceof DocumentType) {
@ -886,8 +899,21 @@ abstract class Node {
return $copyWrapper;
}
protected function containsInner(\DOMNode $node, \DOMNode $other): bool {
# The contains(other) method steps are to return true if other is an inclusive
# descendant of this; otherwise false (including when other is null).
$n = $other;
while ($n = $n->parentNode) {
if ($n === $node) {
return true;
}
}
return false;
}
protected function getInnerNode(?Node $node = null): \DOMNode {
if ($node === null) {
if ($node === null || $node === $this) {
return $this->innerNode;
}

27
lib/NonElementParentNode.php

@ -0,0 +1,27 @@
<?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;
use MensBeam\HTML\DOM\InnerNode\{
Document as InnerDocument,
Reflection
};
use MensBeam\HTML\Parser;
trait NonElementParentNode {
public function getElementById(string $elementId): ?Element {
$document = (!$this instanceof Element) ? $this->innerNode : $this->innerNode->ownerDocument;
$innerElement = $this->innerNode->getElementById($elementId);
if ($innerElement === null) {
return null;
}
return $document->getWrapperNode($innerElement);
}
}

113
tests/cases/TestNode.php

@ -18,6 +18,118 @@ use MensBeam\HTML\Parser;
/** @covers \MensBeam\HTML\DOM\Node */
class TestNode extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Node::compareDocumentPosition
*
* @covers \MensBeam\HTML\DOM\Attr::__construct
* @covers \MensBeam\HTML\DOM\Collection::count
* @covers \MensBeam\HTML\DOM\Collection::current
* @covers \MensBeam\HTML\DOM\Collection::__get_length
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Collection::key
* @covers \MensBeam\HTML\DOM\Collection::next
* @covers \MensBeam\HTML\DOM\NamedNodeMap::offsetGet
* @covers \MensBeam\HTML\DOM\Collection::offsetExists
* @covers \MensBeam\HTML\DOM\Collection::rewind
* @covers \MensBeam\HTML\DOM\Collection::valid
* @covers \MensBeam\HTML\DOM\Comment::__construct
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::createAttribute
* @covers \MensBeam\HTML\DOM\Document::__createAttribute
* @covers \MensBeam\HTML\DOM\Document::createComment
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::createProcessingInstruction
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::importNode
* @covers \MensBeam\HTML\DOM\Document::loadHTML
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DocumentType::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct
* @covers \MensBeam\HTML\DOM\NamedNodeMap::current
* @covers \MensBeam\HTML\DOM\NamedNodeMap::item
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::containsInner
* @covers \MensBeam\HTML\DOM\ProcessingInstruction::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::setProtectedProperties
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::get
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::has
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::key
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::set
*/
public function testMethod_compareDocumentPosition(): void {
$d = new Document('<!DOCTYPE html><html><body><header><h1>Ook</h1></header><main><h2 id="eek" class="ack">Eek</h2><p>Ook <a href="ook">eek</a>, ook?</p></main><footer></footer></body></html>');
$body = $d->body;
$main = $d->getElementsByTagName('main')[0];
$footer = $d->getElementsByTagName('footer')[0];
$eek = $d->getElementById('eek');
$h2Id = $eek->getAttributeNode('id');
$h2Class = $eek->getAttributeNode('class');
$aHref = $d->getElementsByTagName('a')[0]->getAttributeNode('href');
// Compare main element to body element
$compareMainToBody = $main->compareDocumentPosition($body);
$this->assertEquals(10, $compareMainToBody);
// Compare body element to main element
$compareBodyToMain = $body->compareDocumentPosition($main);
$this->assertEquals(20, $compareBodyToMain);
// Compare footer element to main element
$compareFooterToMain = $footer->compareDocumentPosition($main);
$this->assertEquals(2, $compareFooterToMain);
// Compare main element to footer element
$compareMainToFooter = $main->compareDocumentPosition($footer);
$this->assertEquals(4, $compareMainToFooter);
// Compare h2 element id attribute to a element href attribute
$compareH2IdToAHref = $h2Id->compareDocumentPosition($aHref);
$this->assertEquals(4, $compareH2IdToAHref);
// Compare h2 element id attribute to a h2 element class attribute
$compareH2IdToH2Class = $h2Id->compareDocumentPosition($h2Class);
$this->assertEquals(36, $compareH2IdToH2Class);
/*
$compareH2IdToH2Class = $h2Id->compareDocumentPosition($h2Class);
$this->assertEquals(36, $compareH2IdToH2Class);
$compareH2ClassToH2Id = $h2Class->compareDocumentPosition($h2Id);
$this->assertEquals(34, $compareH2ClassToH2Id);
$this->assertEquals(0, $m->compareDocumentPosition($m));
$this->assertGreaterThan(0, $compareMainToBody & Document::DOCUMENT_POSITION_CONTAINS);
$this->assertGreaterThan(0, $compareMainToBody & Document::DOCUMENT_POSITION_PRECEDING);
$this->assertEquals(0, $compareMainToBody & Document::DOCUMENT_POSITION_FOLLOWING);
$this->assertGreaterThan(0, $compareBodyToMain & Document::DOCUMENT_POSITION_CONTAINED_BY);
$this->assertGreaterThan(0, $compareBodyToMain & Document::DOCUMENT_POSITION_FOLLOWING);
$this->assertEquals(0, $compareBodyToMain & Document::DOCUMENT_POSITION_PRECEDING);
$this->assertGreaterThan(0, $compareFooterToMain & Document::DOCUMENT_POSITION_PRECEDING);
$this->assertGreaterThan(0, $compareMainToFooter & Document::DOCUMENT_POSITION_FOLLOWING);
$this->assertGreaterThan(0, $compareH2IdToAHref & Document::DOCUMENT_POSITION_FOLLOWING);
$this->assertGreaterThan(0, $compareH2IdToH2Class & Document::DOCUMENT_POSITION_FOLLOWING);
$this->assertGreaterThan(0, $compareH2ClassToH2Id & Document::DOCUMENT_POSITION_PRECEDING);
$m->parentNode->removeChild($m);
$compareDetachedMainToFooter = $m->compareDocumentPosition($f);
$this->assertEquals($compareDetachedMainToFooter, $m->compareDocumentPosition($f));
$this->assertGreaterThanOrEqual(35, $compareDetachedMainToFooter);
$this->assertLessThanOrEqual(37, $compareDetachedMainToFooter);
$this->assertNotEquals(36, $compareDetachedMainToFooter);*/
}
/**
* @covers \MensBeam\HTML\DOM\Node::cloneNode
* @covers \MensBeam\HTML\DOM\Node::cloneInnerNode
@ -227,7 +339,6 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Node::__get_isConnected
* @covers \MensBeam\HTML\DOM\Node::getRootNode
*
* @covers \MensBeam\HTML\DOM\ChildNode::moonwalk
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct

Loading…
Cancel
Save