Compare commits

...

2 Commits
master ... dev

  1. 2
      .gitignore
  2. 212
      README.md
  3. 7
      composer.json
  4. 1813
      composer.lock
  5. 11
      lib/Attr.php
  6. 4
      lib/DOMException/HierarchyRequestError.php
  7. 4
      lib/DOMException/InUseAttributeError.php
  8. 4
      lib/DOMException/InvalidCharacterError.php
  9. 6
      lib/DOMException/NamespaceError.php
  10. 4
      lib/DOMException/NoModificationAllowedError.php
  11. 4
      lib/DOMException/NotFoundError.php
  12. 4
      lib/DOMException/NotSupportedError.php
  13. 4
      lib/DOMException/SyntaxError.php
  14. 4
      lib/DOMException/WrongDocumentError.php
  15. 1
      lib/DOMImplementation.php
  16. 4
      lib/DOMTokenList.php
  17. 152
      lib/Document.php
  18. 6
      lib/DocumentOrElement.php
  19. 28
      lib/Element.php
  20. 4
      lib/HTMLElement.php
  21. 1
      lib/HTMLElement/HTMLPreElement.php
  22. 1
      lib/HTMLElement/HTMLUnknownElement.php
  23. 8
      lib/Inner/Document.php
  24. 11
      lib/InvalidArgumentException.php
  25. 1
      lib/NamedNodeMap.php
  26. 12
      lib/Node.php
  27. 3
      lib/ParentNode.php
  28. 1
      lib/UnknownException.php
  29. 3
      lib/XMLDocument.php
  30. 166
      lib/XPathEvaluate.php
  31. 19
      lib/XPathEvaluator.php
  32. 30
      lib/XPathEvaluatorBase.php
  33. 64
      lib/XPathExpression.php
  34. 24
      lib/XPathNSResolver.php
  35. 171
      lib/XPathResult.php
  36. 57
      test
  37. 28
      tests/bootstrap.php
  38. 148
      tests/cases/TestAttr.php
  39. 220
      tests/cases/TestCharacterData.php
  40. 146
      tests/cases/TestChildNode.php
  41. 94
      tests/cases/TestCollection.php
  42. 164
      tests/cases/TestDOMImplementation.php
  43. 578
      tests/cases/TestDOMTokenList.php
  44. 1363
      tests/cases/TestDocument.php
  45. 180
      tests/cases/TestDocumentOrElement.php
  46. 1473
      tests/cases/TestElement.php
  47. 35
      tests/cases/TestException.php
  48. 126
      tests/cases/TestHTMLCollection.php
  49. 693
      tests/cases/TestHTMLElement.php
  50. 29
      tests/cases/TestHTMLOrSVGElement.php
  51. 108
      tests/cases/TestInnerDocument.php
  52. 311
      tests/cases/TestNamedNodeMap.php
  53. 1889
      tests/cases/TestNode.php
  54. 34
      tests/cases/TestNonDocumentTypeChildNode.php
  55. 348
      tests/cases/TestParentNode.php
  56. 21
      tests/cases/TestProcessingInstruction.php
  57. 263
      tests/cases/TestSerializer.php
  58. 42
      tests/cases/TestText.php
  59. 32
      tests/cases/TestXMLDocument.php
  60. 199
      tests/cases/TestXPathEvaluate.php
  61. 24
      tests/cases/TestXPathEvaluator.php
  62. 159
      tests/cases/TestXPathExpression.php
  63. 149
      tests/cases/TestXPathResult.php
  64. 0
      tests/misc/test.html
  65. 49
      tests/phpunit.dist.xml
  66. 22
      tests/phpunit.xml

2
.gitignore

@ -5,6 +5,8 @@ node_modules
/test*.php
old
tests/cases/old
tests/coverage
tests/.phpunit.cache
# General
*.DS_Store

212
README.md

@ -7,6 +7,10 @@
[g]: https://php.net/manual/en/book.dom.php
[h]: https://www.php.net/manual/en/book.ctype.php
[i]: https://code.mensbeam.com/MensBeam/Lit
[j]: https://github.com/whatwg/dom/issues/67
[k]: https://webidl.spec.whatwg.org
[l]: https://github.com/qt4cg/qtspecs/issues/296
[m]: https://www.w3.org/TR/xpath-30/#id-basics
# HTML DOM #
@ -14,11 +18,21 @@ Modern DOM library written in PHP for HTML documents. This library is an attempt
## Requirements ##
* PHP 8.0.2 or newer with the following extensions:
* PHP 8.2.0 or newer with the following extensions:
- [dom][g] extension
- [ctype][h] extension (optional, used when parsing)
* Composer 2.0 or newer
## Changes in 2.0 ##
### DOMException classes ###
Prior to 2.0, `MensBeam\HTML\DOM\DOMException` would output a different error message depending on what code it was fed, equal to the [WebIDL spec][k]'s deprecated DOMException constants. While this is a much cleaner way of handling DOM exceptions, it's against the intention of the specification and against common PHP practices. Unfortunately, the spec has these exceptions named things like `HierarchyRequestError` which is problematic in PHP because it would be named something like `HierarchyRequestException` since there's a differentiation between an exception and an error in PHP. We have adhered to the spec here, but it's under another namespace level `MensBeam\HTML\DOM\DOMException` to make it a bit more clear that it's a `DOMException`. All of them are outlined below.
### XPath ###
XPath in the HTML spec is in flux; it has been [in discussion][j] for some time, but there has been some movement on it recently. In any case, XPath's JavaScript API is horrendously terrible to use; we now believe it was a mistake for us to replicate that in HTML-DOM, and any decision by the WHATWG on how XPath will be shoehorned onto HTML will likely maintain most of the ridiculousness of the current API for backwards-compatibility's sake. We don't need to do this. We will keep the `Document::evaluate` method, but every other XPath-related class and trait has been removed except for `XPathException`. Everything is explained below when outlining `MensBeam\HTML\DOM\Document` and in _Limitations & Differences from Specification_ below.
## Usage ##
Full documentation for most of the library shouldn't be necessary because it largely follows the specification, but because of how the library is to be used there are a few things that are glaringly different. These will be outlined below.
@ -49,7 +63,7 @@ There are limitations as to what is considered a named property. Refer to the [W
namespace MensBeam\HTML\DOM;
partial class Document extends Node implements \ArrayAccess {
use DocumentOrElement, NonElementParentNode, ParentNode, XPathEvaluatorBase;
use DocumentOrElement, NonElementParentNode, ParentNode;
public function __construct(
?string $source = null,
@ -58,10 +72,21 @@ partial class Document extends Node implements \ArrayAccess {
public function destroy(): void;
public function evaluate(
string $expression,
?Node $contextNode = null,
bool|callable $resolver = false,
int $type = XPathResult::ANY_TYPE
): XPathResult;
public function registerXPathFunctions(
string|array|null $restrict = null
): void;
public function registerXPathNamespaces(
array $lookup
): void;
public function serialize(
?Node $node = null,
array $config = []
@ -71,6 +96,10 @@ partial class Document extends Node implements \ArrayAccess {
?Node $node = null,
array $config = []
): string;
public function unregisterXPathNamespaces(
string ...$prefix
): void;
}
```
@ -120,12 +149,12 @@ Creates a new `MensBeam\HTML\DOM\Document` object.
Output:
```
gb18030
gbk
```
#### MensBeam\HTML\DOM\Document::destroy ####
Destroys references associated with the instance so it may be garbage collected by PHP. Because of the way PHP's garbage collection is and the poor state of the library PHP DOM is based off of, references must be kept in userland for every created document. Therefore, this method should unfortunately be manually called whenever the document is not needed anymore.
Destroys references associated with the instance so it may be garbage collected by PHP. Because of the way PHP's garbage collection is and the poor state of the library PHP DOM is based off of, references must be kept in userland for every created node. Therefore, this method should unfortunately be manually called whenever the document is not needed anymore.
##### Example #####
@ -137,6 +166,75 @@ $d->destroy();
unset($d);
```
#### MensBeam\HTML\DOM\Document::evaluate ####
Selects elements based on a supplied XPath expression.
* `expression`: The XPath expression to be evaluated
* `contextNode`: The context node where the query will start at, defaults to the document
##### Example #####
```php
namespace MensBeam\HTML\DOM;
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<h1>Ook</h1>
<div class="ook">ook</div>
<div class="ook">eek</div>
<div class="ook">ack</div>
<div>ugh</div>
</body>
</html>
HTML);
$results = $d->evaluate('//div[@class="ook"]/text()');
echo "Found " . count($results) . " text nodes with div element parents that have an \"ook\" class:\n";
foreach ($result as $node) {
echo $node->data . "\n";
}
```
Output:
```
Found 3 text nodes with div element parents that have an "ook" class:
ook
eek
ack
```
_NOTE_: XPath cannot select namespaced elements that don't contain prefixes, so they must be manually defined.
_NOTE_: While HTML-DOM supports non-ASCII characters in element names, XPath does not. Working around it is fairly easy. This is an edge case and unlikely to turn up when working with HTML documents, but we will document it here to be thorough:
```php
namespace MensBeam\HTML\DOM;
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<h1>Ook</h1>
<poo💩>ook</poo💩>
<div>ugh</div>
</body>
</html>
HTML);
$results = $d->evaluate('name(//pooU01F4A9)');
echo $results[0] . "\n";
```
Output:
```
poo💩
```
#### MensBeam\HTML\DOM\Document::registerXPathFunctions ####
Register PHP functions as XPath functions. Works like `\DOMXPath::registerPhpFunctions` except that the php namespace does not need to be registered.
@ -168,6 +266,44 @@ Found 2 nodes with classes starting with 'subtitle':
<p class="subtitle2">Ook?</p>
```
#### MensBeam\HTML\DOM\Document::registerXPathNamespaces ####
Register prefixes with their namespace URIs for use in XPath expressions.
* `lookup`: A lookup table with prefixes as keys and namespace URIs as values.
If `lookup` is invalid a `MensBeam\HTML\DOM\InvalidArgumentException` will be thrown.
##### Example #####
```php
namespace MensBeam\HTML\DOM;
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<h1>Ook</h1>
<svg role="img" viewBox="0 0 100 100"><title>:)</title><path d="M50,75c0,13.2-11.8,24-25,24S1,88.2,1,75s10.8-24,24-24c4.2,0,8.4,1.1,12,3.2l1.5,0.9V12.5C38.5,6.2,43.7,1,50,1c6.3,0,11.5,5.2,11.5,11.5v42.6l1.5-0.9c3.6-2.1,7.8-3.2,12-3.2c13.2,0,24,10.8,24,24S88.2,99,75,99S50,88.2,50,75z"></path></svg>
</body>
</html>
HTML);
$d->registerXPathNamespaces([
'svg' => Node::SVG_NAMESPACE,
'mathml' => Node::MATHML_NAMESPACE
]);
$result = $d->evaluate('count(//svg:svg/svg:path)');
echo "There is $result svg path element\n";
```
Output:
```
There is 1 svg path element
```
#### MensBeam\HTML\DOM\Document::serialize ####
Converts a node to a string.
@ -288,6 +424,46 @@ Converts a node to a string but only serializes the node's contents.
<p>Ook, eek? Ooooook. Ook.</p>
```
#### MensBeam\HTML\DOM\Document::unregisterXPathNamespaces ####
Unregister prefixes used in XPath expressions.
* `prefix`: The prefix to be unregistered.
##### Example #####
```php
namespace MensBeam\HTML\DOM;
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<h1>Ook</h1>
<svg role="img" viewBox="0 0 100 100"><title>:)</title><path d="M50,75c0,13.2-11.8,24-25,24S1,88.2,1,75s10.8-24,24-24c4.2,0,8.4,1.1,12,3.2l1.5,0.9V12.5C38.5,6.2,43.7,1,50,1c6.3,0,11.5,5.2,11.5,11.5v42.6l1.5-0.9c3.6-2.1,7.8-3.2,12-3.2c13.2,0,24,10.8,24,24S88.2,99,75,99S50,88.2,50,75z"></path></svg>
</body>
</html>
HTML);
$d->registerXPathNamespaces([
'svg' => Node::SVG_NAMESPACE,
'mathml' => Node::MATHML_NAMESPACE
]);
$result = $d->evaluate('count(//svg:svg/svg:path)');
echo "There is $result svg path element\n";
$d->unregisterXPathNamespace('svg');
$result = $d->evaluate('count(//svg:svg/svg:path)');
echo "There is $result svg path element\n";
```
Output:
```
There is 1 svg path element
There is 0 svg path element
```
### MensBeam\HTML\DOM\Node ###
Common namespace constants are provided in `MensBeam\HTML\DOM\Node` to make using namespaces with this library not so onerous. In addition, constants are provided here to be used with `MensBeam\HTML\DOM\ParentNode::walk`. `MensBeam\HTML\DOM\Node` also implements `\Stringable` which means that any node can be simply converted to a string to serialize it.
@ -321,7 +497,7 @@ partial abstract class Node implements \Stringable {
*innerNode*: A readonly property that returns the encapsulated inner element.
**WARNING**: Manipulating this node directly can result in unexpected behavior. This is available in the public API only so the class may be interfaced with other libraries which expect a \\DOMDocument object such as [MensBeam\\Lit][i].
**WARNING**: Manipulating this node directly can result in unexpected behavior. This is available in the public API only so this library may be interfaced with other libraries which expect a \\DOMDocument object such as [MensBeam\\Lit][i].
#### MensBeam\HTML\DOM\Node::getNodePath ####
@ -468,24 +644,24 @@ Returns the wrapper node that corresponds to the provided inner node. If one doe
## Limitations & Differences from Specification ##
The primary aim of this library is accuracy. However, due either to limitations imposed by PHP's DOM, by assumptions made by the specification that aren't applicable to a PHP library, or simply because of impracticality some changes have needed to be made. There appears to be a lot of deviations from the specification below, but this is simply an exhaustive list of details about the implementation with a few even explaining why we follow the specification instead of what browsers do.
The primary aim of this library is accuracy when possible and/or practical. However, due either to limitations imposed by PHP's DOM, by assumptions made by the specification that aren't applicable to a PHP library, or simply because of impracticality some changes have needed to be made. There appears to be a lot of deviations from the specification below, but this is simply an exhaustive list of details about the implementation with a few even explaining why we follow the specification instead of what browsers do.
1. Any mention of scripting or anything necessary because of scripting (such as the `ElementCreationOptions` options dictionary on `Document::createElement`) will not be implemented.
2. Due to a PHP bug which severely degrades performance with large documents and in consideration of existing PHP software and because of bizarre uncircumventable `xmlns` attribute bugs when the document is in the HTML namespace, HTML elements in HTML documents are placed in the null namespace internally rather than in the HTML namespace. However, externally they will be shown as having the HTML namespace. Even though null namespaced elements do not exist in the HTML specification one can create them using the DOM. However, in this implementation they will be treated as HTML namespaced elements due to the HTML namespace limitation.
2. Due to a PHP bug which severely degrades performance with large documents and in consideration of existing PHP software and because of bizarre uncircumventable `xmlns` attribute bugs when the document is in the HTML namespace, HTML elements in HTML documents are placed in the null namespace internally rather than in the HTML namespace. However, externally they will be shown as having the HTML namespace. Even though null namespaced elements do not exist in the HTML specification one can create them using the DOM. Those will be shown as having a `null` namespace. This might change in the future because of [discussion][l] around what the default namespace for HTML elements should be in context of interoperability with XPath.
3. In the [WHATWG HTML DOM extensions specification][f] `Document` has named properties. In JavaScript one accesses them through either property notation (`document.ook`) or array notation (`document['ook']`). In PHP this is impractical because there's a differentation between the two notations. Instead, all named properties need to be accessed via array notation (`$document['ook']`).
4. The specification is written entirely with browsers in mind and aren't concerned with the DOM's being used outside of the browser. In browser there is always a document created by parsing serialized markup, and the DOM spec always assumes such. This is impossible in the way this PHP library is intended to be used. The default when creating a new `Document` is to set its content type to "application/xml". This isn't ideal when creating an HTML document entirely through the DOM, so this implementation will instead default to "text/html" unless using `XMLDocument`.
5. Again, because the specification assumes the implementation will be a browser, processing instructions are supposed to be parsed as comments. While it makes sense for a browser, this is impractical for a DOM library used outside of the browser where one may want to manipulate them; this library will instead preserve them when parsing a document but will convert them to comments when using `Element::innerHTML`.
6. 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 the first document's implementation. This is impractical and stupid, so in this library (like PHP DOM itself) a `DOMImplementation` can be instantiated independent of a document.
7. The specification shows `Document` as being able to be instantiated 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.
8. 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.
9. As the DOM is presently specified, CDATA section nodes cannot be created on an HTML document. However, they can be created (and rightly so) on XML documents. The DOM, however, does not prohibit importing of CDATA section nodes into an HTML document and will be appended to the document as such. This appears to be a glaring omission by the maintainers of the specification. This library will allow importing of CDATA section nodes into HTML documents but will instead convert them to text nodes.
10. 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 and faster to write recursive loops to walk through the DOM than it is to use those APIs. Walking downward through the tree has been replaced with the `ParentNode::walk` generator, and walking through adjacent children and moonwalking up the DOM tree can be accomplished through simple while or do/while loops.
11. 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 extremely front-heavy library.
12. The `DOMParser` and `XMLSerializer` APIs will not be implemented because they are ridiculous and limited in their scope. For instance, `DOMParser::parseFromString` won't set a document's character set to anything but UTF-8. This library needs to be able to print to other encodings due to the nature of how it is used. `Document::__construct` will accept optional `$source` and `$charset` arguments, and there are both `Document::load` and `Document::loadFile` methods for loading DOM from a string or a file respectively.
13. Aside from `HTMLElement`, `HTMLPreElement`, `HTMLTemplateElement`, `HTMLUnknownElement`, `MathMLElement`, and `SVGElement` none of the specific derived element classes (such as `HTMLAnchorElement` or `SVGSVGElement`) are implemented. The ones listed before are required for the element interface algorithm. The focus on this library will be on the core DOM before moving onto those -- if ever.
14. This class is meant to be used with HTML, but it will work -MOSTLY- as needed work with XML. Loading of XML uses PHP DOM's XML parser which does not completely conform to the XML specification. Writing an actual conforming XML parser is outside of the scope of this library. One notable feature of this library which won't work per the XML specification are unicode characters in element names. XML allows for capital letters while HTML doesn't. This implementation's workaround (because PHP's DOM doesn't support unicode at all in element names) internally coerces all non-ascii characters to 'Uxxxx' which would be valid modern XML names. Something like a lookup table would be necessary for XML instead, but this isn't implemented and may not be because of complexity.
15. While there is implementation of much of the XPath extensions, there will only be support for XPath 1.0 because that is all PHP DOM's XPath supports.
16. This library's XPath API is -- like the rest of the library itself -- a wrapper that wraps PHP's implementation but instead works like the specification, so there is no need to manually register namespaces. Namespaces that are associated with prefixes will be looked up when evaluating the expression if a `XPathNSResolver` is specified. However, access to registering PHP functions for use within XPath isn't in the specification but is available through `Document::registerXPathFunctions` and `XPathEvaluator::registerXPathFunctions`.
17. `XPathEvaluatorBase::evaluate` has a `result` argument where one provides it with an existing result object to use. I can't find any usable documentation on what this is supposed to do, and the specifications on it are vague. So, at present it does nothing until what it needs to do can be deduced.
18. At present XPath expressions cannot select elements or attributes which use any valid non-ascii character. This is because those nodes are coerced internally to work within PHP's DOM which doesn't support those characters. This can be worked around by coercing names in XPath queries, but that can only be reliably accomplished through an XPath parser. Writing an entire XPath parser for what amounts to an edge case isn't desirable.
19. The XPath API itself is an ill-conceived API that is entirely impractical to use because doing anything with the `XPathResult` object is cumbersome and stupid. Per the specification one cannot iterate over the result even if the result type is an iterator type (why in the hell call it that, then?). One has to instead repeatedly call the `XPathResult::iterateNext()` method. This implementation will allow for treating `XPathResult` snapshot or iterator types as arrays.
9. As the DOM is presently specified, CDATA section nodes cannot be created on an HTML document. However, they can be created (and rightly so) on XML documents. The DOM, however, does not prohibit importing of CDATA section nodes into an HTML document and will be appended to the document as such. Seeing as this wouldn't be an issue in browser it has been overlooked by the specification authors. This library will allow importing of CDATA section nodes into HTML documents but will instead convert them to text nodes.
10. The DOM spec states clearly that when creating an element the localName should match the Name production; however, HTML is more restrictive in naming, only allowing an ASCII alpha character as the first character. All browsers prohibit creation of elements in HTML documents which have non-ASCII alpha characters as the first character, so we will do so here as well as it makes more sense. In addition, this library will throw a `NotSupportedError` `DOMException` when importing nodes with invalid HTML element names into HTML documents.
11. 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 and faster to write recursive loops to walk through the DOM than it is to use those APIs. Walking downward through the tree has been replaced with the `ParentNode::walk` generator, and walking through adjacent children and moonwalking up the DOM tree can be accomplished through simple while or do/while loops.
12. 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 extremely front-heavy library.
13. The `DOMParser` and `XMLSerializer` APIs will not be implemented because they are ridiculous and limited in their scope. For instance, `DOMParser::parseFromString` won't set a document's character set to anything but UTF-8. This library needs to be able to print to other encodings due to the nature of how it is used. `Document::__construct` will accept optional `$source` and `$charset` arguments, and there are both `Document::load` and `Document::loadFile` methods for loading DOM from a string or a file respectively.
14. Aside from `HTMLElement`, `HTMLPreElement`, `HTMLTemplateElement`, `HTMLUnknownElement`, `MathMLElement`, and `SVGElement` none of the specific derived element classes (such as `HTMLAnchorElement` or `SVGSVGElement`) are implemented. The ones listed before are required for the element interface algorithm. The focus on this library will be on the core DOM before moving onto those -- if ever.
15. This class is meant to be used with HTML, but it will work -MOSTLY- work as needed with XML when using `XMLDocument`. Loading of XML uses PHP DOM's XML parser which does not completely conform to the XML specification. Writing an actual conforming XML parser is outside of the scope of this library. One notable feature of this library which won't work per the XML specification are unicode characters in element names. XML allows for capital letters while HTML doesn't. This implementation's workaround (because PHP's DOM doesn't support unicode at all in element names) internally coerces all non-ASCII characters to 'Uxxxxxx' which would be valid modern XML names. Something like a lookup table would be necessary for XML instead, but this isn't implemented and may not be because of complexity.
16. There will only be support for XPath 1.0 in this implementation because that is all PHP DOM's XPath supports. Writing a custom XPath parser to support XPath 3 would be a huge undertaking and is outside of the scope of this library.
17. The DOM XPath specification itself is an ill-conceived API that is entirely impractical to use, and as a result in browser land isn't used much at all. Version 1.x of this library attempted to implement much of the XPath extensions to maintain conformity with the specification, but in practice using it compared to PHP DOM's implementation was a horrible experience. Therefore, all XPath-related classes and traits have been removed from the library except for `XPathException`. Evaluation of XPath expressions is now done entirely through `Document::evaluate`.
18. `Document::evaluate` as specified (as part of `XPathEvaluatorBase`) is as ill-conceived as the rest of the DOM XPath specification, so while this library keeps it around it behaves entirely like `\DOMXPath::evaluate`. Namespaces must be manually defined using `Document::registerXPathNamespaces`. It's just a much simpler and less bullshit way to use XPath, and the PHP authors were right in deviating. As with `\DOMXPath`, `Document::evaluate` will return an integer, float, or a `NodeList` depending on what the expression would return instead of `XPathResult` as defined in the specification.
19. Unlike node names and prefixes this implementation will NOT coerce non-ASCII characters in XPath expressions. As of this writing (December 2023) no browser supports unicode characters in XPath expression selectors, but the [XPath specification][m] states the expression should be a Unicode string and that node names used in selectors should follow the same conventions as XML. On the surface it seems it's as easy as coercing any non-ASCII characters in an expression, but internally attribute values aren't coerced. To accomplish coercion of selectors a superficial tokenization of the expression would be necessary to isolate selectors and string comparison on `name()`. We might consider doing this in the future, but it's not a high priority as this is an edge case and would rarely turn up in real world usage.

7
composer.json

@ -4,7 +4,6 @@
"psr-4": {
"MensBeam\\HTML\\DOM\\": [
"lib/",
"lib/DOMException",
"lib/Exception",
"lib/HTMLElement"
]
@ -24,8 +23,14 @@
}
],
"require": {
"php": "8.2.* || >8.3.0",
"mensbeam/html-parser": "^1.3",
"symfony/css-selector": "^6.3",
"mensbeam/getters-and-setters": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "^10.4",
"phake/phake": "^4.4",
"mikey179/vfsstream": "^1.6"
}
}

1813
composer.lock

File diff suppressed because it is too large

11
lib/Attr.php

@ -11,8 +11,6 @@ namespace MensBeam\HTML\DOM;
/** @property \DOMAttr $_innerNode */
class Attr extends Node {
protected \DOMAttr $_innerNode;
protected function __get_localName(): string {
// PHP's DOM does this correctly already.
// Need to uncoerce string if necessary.
@ -45,11 +43,12 @@ class Attr extends Node {
return $innerOwnerDocument->getWrapperNode($this->_innerNode->ownerElement);
}
protected function __get_prefix(): string {
// PHP's DOM does this correctly already.
// Need to uncoerce string if necessary.
protected function __get_prefix(): ?string {
// PHP's DOM returns an empty string instead of null prefix
$prefix = $this->_innerNode->prefix;
return (!str_contains(needle: 'U', haystack: $prefix)) ? $prefix : $this->uncoerceName($prefix);
$prefix = ($prefix !== '') ? $prefix : null;
// Need to uncoerce string if necessary.
return (!str_contains(needle: 'U', haystack: (string)$prefix)) ? $prefix : $this->uncoerceName($prefix);
}
protected function __get_specified(): bool {

4
lib/DOMException/HierarchyRequestError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class HierarchyRequestError extends DOMException {
public function __construct(?\Throwable $previous = null) {

4
lib/DOMException/InUseAttributeError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class InUseAttributeError extends DOMException {
public function __construct(?\Throwable $previous = null) {

4
lib/DOMException/InvalidCharacterError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class InvalidCharacterError extends DOMException {
public function __construct(?\Throwable $previous = null) {

6
lib/DOMException/NamespaceError.php

@ -6,10 +6,12 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class NamespaceError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The operation is not allowed by namespaces', 14, $previous);
parent::__construct('The operation is not allowed by the supplied namespace', 14, $previous);
}
}

4
lib/DOMException/NoModificationAllowedError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class NoModificationAllowedError extends DOMException {
public function __construct(?\Throwable $previous = null) {

4
lib/DOMException/NotFoundError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class NotFoundError extends DOMException {
public function __construct(?\Throwable $previous = null) {

4
lib/DOMException/NotSupportedError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class NotSupportedError extends DOMException {
public function __construct(?\Throwable $previous = null) {

4
lib/DOMException/SyntaxError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class SyntaxError extends DOMException {
public function __construct(?\Throwable $previous = null) {

4
lib/DOMException/WrongDocumentError.php

@ -6,7 +6,9 @@
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
namespace MensBeam\HTML\DOM\DOMException;
use MensBeam\HTML\DOM\DOMException;
class WrongDocumentError extends DOMException {
public function __construct(?\Throwable $previous = null) {

1
lib/DOMImplementation.php

@ -7,6 +7,7 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\InvalidCharacterError;
use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument,
Reflection

4
lib/DOMTokenList.php

@ -7,6 +7,10 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\{
InvalidCharacterError,
SyntaxError
};
use MensBeam\HTML\Parser\Data,
MensBeam\GettersAndSetters;

152
lib/Document.php

@ -7,12 +7,19 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\{
InvalidCharacterError,
NoModificationAllowedError,
NotSupportedError,
WrongDocumentError
};
use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument,
NodeCache,
Reflection
};
use MensBeam\HTML\Parser;
use MensBeam\HTML\Parser,
MensBeam\Intl\Encoding\UTF8;
use MensBeam\HTML\Parser\{
Charset,
Data,
@ -22,7 +29,7 @@ use MensBeam\HTML\Parser\{
/** @property InnerDocument $_innerNode */
class Document extends Node implements \ArrayAccess {
use DocumentOrElement, NonElementParentNode, ParentNode, XPathEvaluatorBase;
use DocumentOrElement, NonElementParentNode, ParentNode;
protected static ?NodeCache $cache = null;
@ -30,6 +37,7 @@ class Document extends Node implements \ArrayAccess {
protected string $_compatMode = 'CSS1Compat';
protected string $_contentType = 'text/html';
protected bool $designModeEnabled = false;
protected ?XPathException $__lastXPathException = null;
protected DOMImplementation $_implementation;
protected string $_URL = 'about:blank';
@ -264,7 +272,6 @@ class Document extends Node implements \ArrayAccess {
protected function __set_title(string $value): void {
# On setting, the steps corresponding to the first matching condition in the following list must be run:
#
# If the document element is an SVG svg element
$documentElement = $this->_innerNode->documentElement;
if ($documentElement === null) {
return;
@ -332,16 +339,16 @@ class Document extends Node implements \ArrayAccess {
elseif ($head !== null) {
# 1. Let element be the result of creating an element given the document
# element's node document, title, and the HTML namespace.
$element = $this->_innerNode->createElementNS(Node::SVG_NAMESPACE, 'title');
$element = $this->_innerNode->createElement('title');
# 2. Append element to the head element.
$head->appendChild($element);
}
# 4. String replace all with the given value within element.
// This is basically what textContent will do for us...
if ($element !== null) {
$element->textContent = $value;
}
# 4. String replace all with the given value within element.
// This is basically what textContent will do for us...
if ($element !== null) {
$element->textContent = $value;
}
}
@ -367,15 +374,12 @@ class Document extends Node implements \ArrayAccess {
// This cache is used to prevent "must not be accessed before initialization"
// errors because of PHP's garbage... garbage collection.
if (self::$cache === null) {
// Pcov for some reason doesn't mark this line as being covered when it clearly
// is...
self::$cache = new NodeCache(); //@codeCoverageIgnore
self::$cache = new NodeCache();
}
self::$cache->set($this, $this->_innerNode);
}
public function adoptNode(Node &$node): Node {
# The adoptNode(node) method steps are:
#
@ -539,7 +543,12 @@ class Document extends Node implements \ArrayAccess {
# 1. If localName does not match the Name production, then throw an
# "InvalidCharacterError" DOMException.
if (!preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $localName)) {
// DEVIATION: The DOM spec states clearly that the localName should match the
// Name production, but HTML is more restrictive in naming, only allowing A-Za-z
// as the first character. All browsers prohibit creation of elements in HTML
// documents which have non-ASCII alpha characters as the first character, so we
// will do so here as well.
if (!preg_match((!$this instanceof XMLDocument) ? InnerDocument::HTML_ELEMENT_NAME_REGEX : InnerDocument::NAME_PRODUCTION_REGEX, $localName)) {
throw new InvalidCharacterError();
}
@ -594,11 +603,25 @@ class Document extends Node implements \ArrayAccess {
// The element name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
$qualifiedName = $this->coerceName($prefix) . ':' . $this->coerceName($localName);
$element = $this->_innerNode->createElementNS($namespace, $qualifiedName);
$localName = $this->coerceName($localName);
$element = $this->_innerNode->createElementNS($namespace, ($prefix !== null) ? $this->coerceName($prefix) . ':' . $localName : $localName);
}
return $this->_innerNode->getWrapperNode($element);
$result = $this->_innerNode->getWrapperNode($element);
// Due to a PHP bug which severely degrades performance with large documents and
// in consideration of existing PHP software and because of bizarre
// uncircumventable `xmlns` attribute bugs when the document is in the HTML
// namespace, HTML elements in HTML documents are placed in the null namespace
// internally rather than in the HTML namespace. This presents a problem in this
// case where if an element is explicitly created with a null namespace then it
// should show null when using Element::namespaceURI. Element::isNullNamespace
// exists to rectify this.
if ($namespace === null) {
Reflection::setProtectedProperties($result, [
'isNullNamespace' => true
]);
}
return $result;
}
public function createProcessingInstruction(string $target, string $data): ProcessingInstruction {
@ -623,8 +646,35 @@ class Document extends Node implements \ArrayAccess {
self::$cache->delete($this->_innerNode);
}
public function getElementsByName(string $elementName): NodeList {
# The getElementsByName(elementName) method steps are to return a live NodeList
public function evaluate(string $expression, ?Node $contextNode = null): int|float|string|NodeList {
$contextNode = ($contextNode === null) ? $this : $contextNode;
$innerContextNode = $contextNode->innerNode;
$doc = ($innerContextNode instanceof \DOMDocument) ? $innerContextNode : $innerContextNode->ownerDocument;
// PHP's DOM XPath incorrectly issues warnings rather than exceptions when
// expressions are incorrect, so we must use a custom error handler here to
// "catch" it and throw an exception in its place.
set_error_handler(__CLASS__ . '::xpathErrorHandler');
$result = $doc->xpath->evaluate($expression, $innerContextNode);
restore_error_handler();
if ($this->__lastXPathException) {
$e = $this->__lastXPathException;
$this->__lastXPathException = null;
throw $e;
}
if ($result instanceof \DOMNodeList) {
// NodeLists cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\NodeList', $this->_innerNode, $result);
} elseif (is_string($result)) {
$result = $this->uncoerceName($result);
}
return $result;
}
public function getElementsByName(string $name): NodeList {
# The getElementsByName(name) method steps are to return a live NodeList
# containing all the HTML elements in that document that have a name attribute
# whose value is identical to the elementName argument, in tree order. When the
# method is invoked on a Document object again with the same argument, the user
@ -633,7 +683,7 @@ class Document extends Node implements \ArrayAccess {
// Because of how namespaces are handled internally they're null when a HTML document.
$namespace = (!$this instanceof XMLDocument) ? '' : Node::HTML_NAMESPACE;
// NodeLists cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\NodeList', $this->_innerNode, $this->_innerNode->xpath->query(".//*[namespace-uri()='$namespace' and @name='$elementName']"));
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\NodeList', $this->_innerNode, $this->_innerNode->xpath->query(".//*[namespace-uri()='$namespace' and @name='$name']"));
}
public function importNode(Node|\DOMNode $node, bool $deep = false): Node {
@ -642,7 +692,7 @@ class Document extends Node implements \ArrayAccess {
# 1. If node is a document or shadow root, then throw a "NotSupportedError"
# DOMException.
// Because this can import from PHP's DOM we must check for more stuff.
if ($node instanceof Document || $node instanceof \DOMDocument || ($node instanceof \DOMNode && $node->ownerDocument::class !== 'DOMDocument') || $node instanceof \DOMEntityReference) {
if ($node instanceof Document || $node instanceof \DOMDocument || ($node instanceof \DOMNode && ($node->ownerDocument === null || $node->ownerDocument::class !== 'DOMDocument')) || $node instanceof \DOMEntityReference) {
throw new NotSupportedError();
}
@ -661,11 +711,8 @@ class Document extends Node implements \ArrayAccess {
// specification, but since this library is not a browser it should be able to
// read and print processing instructions.
$config->processingInstructions = true;
if ($charset !== null) {
$config->encodingFallback = Charset::fromCharset($charset);
}
// The parser spec defaults to windows-1252, but the DOM spec defaults to UTF-8
$config->encodingFallback = Charset::fromCharset($charset ?? 'UTF-8');
$source = Parser::parseInto($source, $this->_innerNode, null, $config);
$this->_characterSet = $source->encoding;
$this->_compatMode = ($source->quirksMode === Parser::NO_QUIRKS_MODE || $source->quirksMode === Parser::LIMITED_QUIRKS_MODE) ? 'CSS1Compat' : 'BackCompat';
@ -674,6 +721,10 @@ class Document extends Node implements \ArrayAccess {
}
public function loadFile(string $filename, ?string $charset = null): void {
if ($this->hasChildNodes()) {
throw new NoModificationAllowedError();
}
$f = @fopen($filename, 'r');
if (!$f) {
throw new FileNotFoundException();
@ -714,7 +765,7 @@ class Document extends Node implements \ArrayAccess {
// Because PHP is dumb and won't let us implement ArrayAccess with a more
// specific type than its interface...
if (!is_string($offset)) {
trigger_error('Type error; ' . __CLASS__ . ' keys may only be strings', \E_USER_ERROR);
throw new InvalidArgumentException('Argument #1 ($offset) must be a string');
}
$namespace = (!$this instanceof XMLDocument) ? '' : Node::HTML_NAMESPACE;
@ -725,7 +776,7 @@ class Document extends Node implements \ArrayAccess {
// Because PHP is dumb and won't let us implement ArrayAccess with a more
// specific type than its interface...
if (!is_string($offset)) {
trigger_error('Type error; ' . __CLASS__ . ' keys may only be strings', \E_USER_ERROR);
throw new InvalidArgumentException('Argument #1 ($offset) must be a string');
}
// In JavaScript this part of the Document interface is implemented as
@ -797,15 +848,31 @@ class Document extends Node implements \ArrayAccess {
public function offsetSet(mixed $offset, mixed $value): void {
// The specification is vague as to what to do here. Browsers silently fail, so
// that's what we're going to do.
}
} //@codeCoverageIgnore
public function offsetUnset(mixed $offset): void {
// The specification is vague as to what to do here. Browsers silently fail, so
// that's what we're going to do.
}
} //@codeCoverageIgnore
public function registerXPathFunctions(string|array|null $restrict = null): void {
$this->xpathRegisterPhpFunctions($this, $restrict);
$xpath = $this->innerNode->xpath;
$xpath->registerNamespace('php', 'http://php.net/xpath');
$xpath->registerPhpFunctions($restrict);
}
public function registerXPathNamespaces(array $lookup): void {
foreach ($lookup as $prefix => $namespaceURI) {
if (is_string($prefix) && $prefix !== '' && is_string($namespaceURI)) {
continue;
}
throw new InvalidArgumentException('Argument #1 ($lookup) must be an array with a non-empty string key and string value');
}
foreach ($lookup as $prefix => $namespaceURI) {
$this->innerNode->xpath->registerNamespace($prefix, $namespaceURI);
}
}
public function serialize(?Node $node = null, array $config = []): string {
@ -826,6 +893,29 @@ class Document extends Node implements \ArrayAccess {
return Serializer::serializeInner($node->innerNode, $config);
}
public function unregisterXPathNamespaces(string ...$prefix): void {
foreach ($prefix as $p) {
$this->innerNode->xpath->registerNamespace($p, '');
}
}
/** @internal */
public function xpathErrorHandler(int $errno, string $errstr, string $errfile, int $errline) {
if ($this->__lastXPathException) {
return true; // @codeCoverageIgnore
}
$lowerErrstr = strtolower($errstr);
if (str_contains(needle: 'undefined namespace prefix', haystack: $lowerErrstr)) {
$this->__lastXPathException = new XPathException(XPathException::UNRESOLVABLE_NAMESPACE_PREFIX);
} elseif (str_contains(needle: 'invalid expression', haystack: $lowerErrstr)) {
$this->__lastXPathException = new XPathException(XPathException::INVALID_EXPRESSION);
}
return true;
}
public function __toString(): string {
return $this->serialize();

6
lib/DocumentOrElement.php

@ -7,6 +7,10 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\{
InvalidCharacterError,
NamespaceError
};
use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument,
Reflection
@ -188,7 +192,7 @@ trait DocumentOrElement {
($prefix !== null && $namespace === null) ||
($prefix === 'xml' && $namespace !== self::XML_NAMESPACE) ||
(($qualifiedName === 'xmlns' || $prefix === 'xmlns') && $namespace !== self::XMLNS_NAMESPACE) ||
($namespace === self::XMLNS_NAMESPACE && $qualifiedName !== 'xmlns' && $prefix !== 'xmlns')
($namespace === self::XMLNS_NAMESPACE && ($qualifiedName !== 'xmlns' && $prefix !== 'xmlns'))
) {
throw new NamespaceError();
}

28
lib/Element.php

@ -7,6 +7,14 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\{
InUseAttributeError,
InvalidCharacterError,
NoModificationAllowedError,
NotFoundError,
NotSupportedError,
SyntaxError
};
use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument,
Reflection
@ -20,6 +28,8 @@ use MensBeam\HTML\Parser,
class Element extends Node {
use ChildNode, DocumentOrElement, NonDocumentTypeChildNode, ParentNode;
protected bool $isNullNamespace = false;
protected function __get_attributes(): NamedNodeMap {
// NamedNodeMaps cannot be created from their constructors normally.
@ -110,8 +120,7 @@ class Element extends Node {
}
protected function __get_localName(): ?string {
// PHP's DOM does this correctly already.
return $this->_innerNode->localName;
return (!str_contains(needle: 'U', haystack: $this->_innerNode->localName)) ? $this->_innerNode->localName : $this->uncoerceName($this->_innerNode->localName);
}
protected function __get_namespaceURI(): ?string {
@ -120,7 +129,11 @@ class Element extends Node {
// around because of the wrapper classes; So, use the null namespace internally
// but print out the HTML namespace instead.
$namespace = $this->_innerNode->namespaceURI;
return (!$this->ownerDocument instanceof XMLDocument && $namespace === null) ? self::HTML_NAMESPACE : $namespace;
if ($this->ownerDocument instanceof XMLDocument) {
return $namespace;
}
return ($namespace === null && !$this->isNullNamespace) ? self::HTML_NAMESPACE : $namespace;
}
protected function __get_outerHTML(): string {
@ -174,7 +187,12 @@ class Element extends Node {
protected function __get_prefix(): ?string {
$prefix = $this->_innerNode->prefix;
return ($prefix !== '') ? $prefix : null;
if ($prefix !== null && $prefix !== '') {
return (!str_contains(needle: 'U', haystack: $this->_innerNode->prefix)) ? $this->_innerNode->prefix : $this->uncoerceName($this->_innerNode->prefix);
}
return null;
}
protected function __get_tagName(): string {
@ -250,7 +268,7 @@ class Element extends Node {
# 3. Return attr’s value.
// Uncoerce the value if necessary
return $attr->value;
return (!str_contains(needle: 'U', haystack: $attr->value)) ? $attr->value : $this->uncoerceName($attr->value);
}
public function getAttributeNames(): array {

4
lib/HTMLElement.php

@ -7,6 +7,10 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\{
NoModificationAllowedError,
SyntaxError
};
class HTMLElement extends Element {

1
lib/HTMLElement/HTMLPreElement.php

@ -7,7 +7,6 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\Reflection;
class HTMLPreElement extends HTMLElement {}

1
lib/HTMLElement/HTMLUnknownElement.php

@ -7,7 +7,6 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\Reflection;
class HTMLUnknownElement extends HTMLElement {}

8
lib/Inner/Document.php

@ -10,7 +10,8 @@ namespace MensBeam\HTML\DOM\Inner;
use MensBeam\GettersAndSetters;
use MensBeam\HTML\DOM\{
Document as WrapperDocument,
DOMException,
DOMException\NotSupportedError,
DOMException\WrongDocumentError,
Node as WrapperNode,
XMLDocument as WrapperXMLDocument
};
@ -22,6 +23,7 @@ class Document extends \DOMDocument {
// Used for validation. Not sure where to put them where they wouldn't be
// exposed unnecessarily to the public API.
public const NAME_PRODUCTION_REGEX = '/^[:A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}][:A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*$/Su';
public const HTML_ELEMENT_NAME_REGEX = '/^[A-Z_a-z][:A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*$/Su';
public const POTENTIAL_CUSTOM_ELEMENT_NAME_REGEX = '/^[a-z][a-z0-9-\._\x{B7}\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{203F}-\x{2040}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]*-[a-z0-9-\._\x{B7}\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{203F}-\x{2040}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]*$/Su';
public const QNAME_PRODUCTION_REGEX = '/^([A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}][A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*:)?[A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}][A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*$/Su';
@ -67,10 +69,6 @@ class Document extends \DOMDocument {
return $this->wrapperNode;
}
if ($node instanceof \DOMDocument) {
throw new NotSupportedError();
}
// There's no way to see whether unappended doctypes are owned by a document in
// PHP DOM because of a bug where unappended doctypes have a null owner
// document. This is worked around in the wrapper DOM but a problem here when

11
lib/InvalidArgumentException.php

@ -0,0 +1,11 @@
<?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 InvalidArgumentException extends \OutOfBoundsException {}

1
lib/NamedNodeMap.php

@ -7,6 +7,7 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\NotFoundError;
use MensBeam\HTML\DOM\Inner\Document as InnerDocument,
MensBeam\HTML\Parser\NameCoercion;

12
lib/Node.php

@ -7,6 +7,11 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\DOMException\{
HierarchyRequestError,
NotFoundError,
NotSupportedError
};
use MensBeam\GettersAndSetters,
MensBeam\HTML\Parser\NameCoercion;
use MensBeam\HTML\DOM\Inner\{
@ -460,7 +465,7 @@ abstract class Node implements \Stringable {
}
public function getNodePath(): ?string {
return $this->_innerNode->getNodePath();
return $this->uncoerceName($this->_innerNode->getNodePath());
}
public function getRootNode(): ?Node {
@ -765,6 +770,11 @@ abstract class Node implements \Stringable {
# 1. Let copyAttribute be a clone of attribute.
# 2. Append copyAttribute to copy.
if ($node instanceof \DOMElement) {
// This prohibits importing elements which have invalid HTML element names.
if ($import && !$document instanceof XMLDocument && preg_match('/^U[0-9A-F]{6}/', $node->localName, $m)) {
throw new NotSupportedError();
}
$copy = ($import) ? $document->importNode($node) : $node->cloneNode();
// PHP DOM doesn't import id attributes where

3
lib/ParentNode.php

@ -7,7 +7,8 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\Reflection;
use MensBeam\HTML\DOM\DOMException\SyntaxError,
MensBeam\HTML\DOM\Inner\Reflection;
use Symfony\Component\CssSelector\CssSelectorConverter,
Symfony\Component\CssSelector\Exception\SyntaxErrorException as SymfonySyntaxErrorException;

1
lib/UnknownException.php

@ -8,6 +8,7 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
/** @codeCoverageIgnore */
class UnknownException extends \LogicException {
public function __construct(int $code = 0, ?\Throwable $previous = null) {
parent::__construct('The program reached an invalid state; this error should be reported', $code, $previous);

3
lib/XMLDocument.php

@ -7,7 +7,8 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
use MensBeam\HTML\Parser\Charset;
use MensBeam\HTML\DOM\DOMException\NoModificationAllowedError,
MensBeam\HTML\Parser\Charset;
class XMLDocument extends Document {

166
lib/XPathEvaluate.php

@ -1,166 +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;
use MensBeam\HTML\DOM\Inner\Reflection;
trait XPathEvaluate {
private ?XPathException $__xpathException = null;
public function xpathErrorHandler(int $errno, string $errstr, string $errfile, int $errline) {
if ($this->__xpathException) {
return true;
}
$lowerErrstr = strtolower($errstr);
if (str_contains(needle: 'undefined namespace prefix', haystack: $lowerErrstr)) {
$this->__xpathException = new XPathException(XPathException::UNRESOLVABLE_NAMESPACE_PREFIX);
} elseif (str_contains(needle: 'invalid expression', haystack: $lowerErrstr)) {
$this->__xpathException = new XPathException(XPathException::INVALID_EXPRESSION);
}
return true;
} // @codeCoverageIgnore
protected function xpathEvaluate(string $expression, Node $contextNode, \Closure|XPathNSResolver|null $resolver = null, int $type = XPathResult::ANY_TYPE, ?XPathResult $result = null): XPathResult {
$innerContextNode = $contextNode->innerNode;
$doc = ($innerContextNode instanceof \DOMDocument) ? $innerContextNode : $innerContextNode->ownerDocument;
if ($resolver !== null && preg_match_all('/([A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]{1}[A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*):([A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]{1}[A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*)/u', $expression, $m, \PREG_SET_ORDER)) {
foreach ($m as $prefix) {
$prefix = $prefix[1];
if ($resolver instanceof XPathNSResolver) {
$namespace = $contextNode->lookupNamespaceURI($prefix);
} elseif ($namespace = $resolver($prefix)) {
$namespace = (string)$namespace;
}
if ($namespace !== null) {
$doc->xpath->registerNamespace($prefix, $namespace);
}
}
}
// PHP's DOM XPath incorrectly issues warnings rather than exceptions when
// expressions are incorrect, so we must use a custom error handler here to
// "catch" it and throw an exception in its place.
set_error_handler([ $this, 'xpathErrorHandler' ]);
$result = $doc->xpath->evaluate($expression, $innerContextNode);
restore_error_handler();
if ($this->__xpathException) {
$e = $this->__xpathException;
$this->__xpathException = null;
throw $e;
}
if ($type === XPathResult::ANY_TYPE) {
$typeOfResult = gettype($result);
if ($typeOfResult === 'object') {
$typeOfResult = $result::class;
}
switch ($typeOfResult) {
case 'integer':
case 'double':
$resultType = XPathResult::NUMBER_TYPE;
break;
case 'string':
$resultType = XPathResult::STRING_TYPE;
break;
case 'boolean':
$resultType = XPathResult::BOOLEAN_TYPE;
break;
case 'DOMNodeList':
$resultType = XPathResult::ORDERED_NODE_ITERATOR_TYPE;
break;
default:
throw new NotSupportedError();
}
} else {
switch ($type) {
case XPathResult::NUMBER_TYPE:
if ($result instanceof \DOMNodeList) {
throw new XPathException(XPathException::TYPE_ERROR);
}
$result = (float)$result;
break;
case XPathResult::STRING_TYPE:
if ($result instanceof \DOMNodeList) {
throw new XPathException(XPathException::TYPE_ERROR);
}
$result = (string)$result;
break;
case XPathResult::BOOLEAN_TYPE:
if ($result instanceof \DOMNodeList) {
$result = ($result->length > 0);
}
$result = (bool)$result;
break;
// In this implementation there's no difference between these because PHP's
// XPath DOM (ALMOST!) always returns in document order, and that cannot be
// changed.
case XPathResult::UNORDERED_NODE_ITERATOR_TYPE:
case XPathResult::ORDERED_NODE_ITERATOR_TYPE:
if (!$result instanceof \DOMNodeList) {
throw new XPathException(XPathException::TYPE_ERROR);
}
break;
// In this implementation there's no difference between these because PHP's
// XPath DOM (ALMOST!) always returns in document order, and that cannot be
// changed.
case XPathResult::UNORDERED_NODE_SNAPSHOT_TYPE:
case XPathResult::ORDERED_NODE_SNAPSHOT_TYPE:
if (!$result instanceof \DOMNodeList) {
throw new XPathException(XPathException::TYPE_ERROR);
}
$temp = [];
foreach ($result as $node) {
$temp[] = $node;
}
$result = $temp;
break;
// In this implementation there's no difference between these because PHP's
// XPath DOM (ALMOST!) always returns in document order, and that cannot be
// changed.
case XPathResult::ANY_UNORDERED_NODE_TYPE:
case XPathResult::FIRST_ORDERED_NODE_TYPE:
if (!$result instanceof \DOMNodeList) {
throw new XPathException(XPathException::TYPE_ERROR);
}
$result = $result->item(0);
break;
default: throw new NotSupportedError();
}
$resultType = $type;
}
// XPathResult cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\XPathResult', $resultType, ($result instanceof \DOMNodeList || is_array($result)) ? $result : [ $result ]);
}
protected function xpathRegisterPhpFunctions(Document $document, string|array|null $restrict = null): void {
$xpath = $document->innerNode->xpath;
$xpath->registerNamespace('php', 'http://php.net/xpath');
$xpath->registerPhpFunctions($restrict);
}
}

19
lib/XPathEvaluator.php

@ -1,19 +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 XPathEvaluator {
use XPathEvaluatorBase;
public function registerXPathFunctions(Document $document, string|array|null $restrict = null): void {
$this->xpathRegisterPhpFunctions($document, $restrict);
}
}

30
lib/XPathEvaluatorBase.php

@ -1,30 +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;
use MensBeam\HTML\DOM\Inner\Reflection;
trait XPathEvaluatorBase {
use XPathEvaluate;
public function createExpression(string $expression, ?XPathNSResolver $resolver = null): XPathExpression {
// XPathExpression cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\XPathExpression', $expression, $resolver);
}
public function createNSResolver(Node $nodeResolver): XPathNSResolver {
// XPathNSResolver cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\XPathNSResolver', $nodeResolver);
}
public function evaluate(string $expression, Node $contextNode, \Closure|XPathNSResolver|null $resolver = null, int $type = XPathResult::ANY_TYPE, ?XPathResult $result = null): XPathResult {
return $this->xpathEvaluate($expression, $contextNode, $resolver, $type, $result);
}
}

64
lib/XPathExpression.php

@ -1,64 +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;
use MensBeam\HTML\DOM\Inner\Reflection;
class XPathExpression {
use XPathEvaluate;
protected string $expression;
protected ?XPathNSResolver $resolver;
protected function __construct(string $expression, ?XPathNSResolver $resolver) {
if ($resolver !== null && preg_match_all('/([A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]{1}[A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*):([A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]{1}[A-Z_a-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}-\.0-9\x{B7}\x{0300}-\x{036F}\x{203F}-\x{2040}]*)/u', $expression, $m, \PREG_SET_ORDER)) {
// This part is especially nasty because of egregious use of reflection to get
// protected properties, but neither should be exposed publicly; this is a crazy
// polyfill hack that wouldn't normally be necessary otherwise.
$nodeResolver = Reflection::getProtectedProperty($resolver, 'nodeResolver');
$innerNodeResolver = $nodeResolver->innerNode;
$doc = ($innerNodeResolver instanceof \DOMDocument) ? $innerNodeResolver : $innerNodeResolver->ownerDocument;
foreach ($m as $prefix) {
$prefix = $prefix[1];
if ($namespace = $resolver->lookupNamespaceURI($prefix)) {
$doc->xpath->registerNamespace($prefix, $namespace);
}
}
set_error_handler([ $this, 'xpathErrorHandler' ]);
$doc->xpath->evaluate($expression);
restore_error_handler();
} else {
// Test the expression by attempting to run it on an empty document. PHP's DOM
// XPath incorrectly issues a warnings rather than exceptions when expressions
// are incorrect, so we must use a custom error handler here to "catch" it and
// throw an exception in its place.
set_error_handler([ $this, 'xpathErrorHandler' ]);
$xpath = new \DOMXPath(new \DOMDocument());
$xpath->evaluate($expression);
restore_error_handler();
}
if ($this->__xpathException) {
$e = $this->__xpathException;
$this->__xpathException = null;
throw $e;
}
$this->expression = $expression;
$this->resolver = $resolver;
}
public function evaluate(Node $contextNode, int $type = XPathResult::ANY_TYPE, ?XPathResult $result = null): XPathResult {
return $this->xpathEvaluate($this->expression, $contextNode, $this->resolver, $type, $result);
}
}

24
lib/XPathNSResolver.php

@ -1,24 +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 XPathNSResolver {
protected Node $nodeResolver;
protected function __construct(Node $nodeResolver) {
$this->nodeResolver = $nodeResolver;
}
public function lookupNamespaceURI(?string $prefix): ?string {
return $this->nodeResolver->lookupNamespaceURI($prefix);
}
}

171
lib/XPathResult.php

@ -1,171 +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;
use MensBeam\GettersAndSetters;
class XPathResult implements \ArrayAccess, \Countable, \Iterator {
use GettersAndSetters;
public const ANY_TYPE = 0;
public const NUMBER_TYPE = 1;
public const STRING_TYPE = 2;
public const BOOLEAN_TYPE = 3;
public const UNORDERED_NODE_ITERATOR_TYPE = 4;
public const ORDERED_NODE_ITERATOR_TYPE = 5;
public const UNORDERED_NODE_SNAPSHOT_TYPE = 6;
public const ORDERED_NODE_SNAPSHOT_TYPE = 7;
public const ANY_UNORDERED_NODE_TYPE = 8;
public const FIRST_ORDERED_NODE_TYPE = 9;
protected bool $_invalidIteratorState = false;
protected int $position = 0;
protected int $_resultType;
protected \DOMNodeList|array $storage;
protected function __get_booleanValue(): ?bool {
if ($this->_resultType !== self::BOOLEAN_TYPE) {
throw new XPathException(XPathException::TYPE_ERROR);
}
return $this->storage[0];
}
protected function __get_invalidIteratorState(): bool {
return $this->_invalidIteratorState;
}
protected function __get_numberValue(): ?float {
if ($this->_resultType !== self::NUMBER_TYPE) {
throw new XPathException(XPathException::TYPE_ERROR);
}
return $this->storage[0];
}
protected function __get_resultType(): int {
return $this->_resultType;
}
protected function __get_singleNodeValue(): ?Node {
if (!in_array($this->_resultType, [ self::ANY_UNORDERED_NODE_TYPE, self::FIRST_ORDERED_NODE_TYPE ])) {
throw new XPathException(XPathException::TYPE_ERROR);
}
$node = $this->storage[0];
return ($node !== null) ? $node->ownerDocument->getWrapperNode($node) : null;
}
protected function __get_snapshotLength(): int {
if (!in_array($this->_resultType, [ self::ORDERED_NODE_SNAPSHOT_TYPE, self::UNORDERED_NODE_SNAPSHOT_TYPE ])) {
throw new XPathException(XPathException::TYPE_ERROR);
}
return $this->count();
}
protected function __get_stringValue(): ?string {
if ($this->_resultType !== self::STRING_TYPE) {
throw new XPathException(XPathException::TYPE_ERROR);
}
return $this->storage[0];
}
protected function __construct(int $type, \DOMNodeList|array $object) {
$this->storage = $object;
$this->_resultType = $type;
}
public function count(): int {
$this->validateStorage();
return (is_array($this->storage)) ? count($this->storage) : $this->storage->length;
}
public function current(): ?Node {
$this->validateStorage();
$node = $this->storage[$this->position];
return $node->ownerDocument->getWrapperNode($node);
}
public function iterateNext(): ?Node {
if (!in_array($this->_resultType, [ self::ORDERED_NODE_ITERATOR_TYPE, self::UNORDERED_NODE_ITERATOR_TYPE ])) {
throw new XPathException(XPathException::TYPE_ERROR);
}
if ($this->position + 1 > $this->count()) {
return null;
}
$node = $this->storage[$this->position++];
return $node->ownerDocument->getWrapperNode($node);
}
public function key(): int {
$this->validateStorage();
return $this->position;
}
public function next(): void {
$this->validateStorage();
$this->position++;
}
public function rewind(): void {
$this->validateStorage();
$this->position = 0;
}
public function offsetExists($offset): bool {
$this->validateStorage();
return isset($this->storage[$offset]);
}
public function offsetGet($offset): ?Node {
$this->validateStorage();
$node = $this->storage[$offset];
return ($node !== null) ? $node->ownerDocument->getWrapperNode($node) : null;
}
public function offsetSet($offset, $value): void {
$this->validateStorage();
}
public function offsetUnset($offset): void {
$this->validateStorage();
}
public function snapshotItem(int $index): ?Node {
if (!in_array($this->_resultType, [ self::ORDERED_NODE_SNAPSHOT_TYPE, self::UNORDERED_NODE_SNAPSHOT_TYPE ])) {
throw new XPathException(XPathException::TYPE_ERROR);
}
if (!isset($this->storage[$index])) {
return null;
}
$node = $this->storage[$index];
return $node->ownerDocument->getWrapperNode($node);
}
public function valid(): bool {
$this->validateStorage();
return $this->offsetExists($this->position);
}
protected function validateStorage(): void {
if (!in_array($this->_resultType, [ self::ORDERED_NODE_ITERATOR_TYPE, self::UNORDERED_NODE_ITERATOR_TYPE, self::ORDERED_NODE_SNAPSHOT_TYPE, self::UNORDERED_NODE_SNAPSHOT_TYPE ])) {
throw new XPathException(XPathException::TYPE_ERROR);
}
}
}

57
test

@ -0,0 +1,57 @@
#!/usr/bin/env php
<?php
/**
* @license MIT
* Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details
*/
$dir = ini_get('extension_dir');
$php = escapeshellarg(\PHP_BINARY);
$code = escapeshellarg(__DIR__ . '/lib');
array_shift($argv);
foreach ($argv as $k => $v) {
if (in_array($v, ['--coverage', '--coverage-html'])) {
$argv[$k] = '--coverage-html tests/coverage';
}
}
$cmd = [
$php,
'-d opcache.enable_cli=0',
];
$pcovLoaded = extension_loaded('pcov');
$xdebugLoaded = extension_loaded('xdebug');
if (!$pcovLoaded && !$xdebugLoaded) {
$extDir = ini_get('extension_dir');
if (!extension_loaded('pcov') && file_exists("$extDir/pcov.so")) {
$cmd[] = '-d zend_extension=pcov.so';
$pcovLoaded = true;
} elseif (!extension_loaded('xdebug') && file_exists("$extDir/xdebug.so")) {
$cmd[] = '-d zend_extension=xdebug.so';
$xdebugLoaded = true;
}
}
if ($pcovLoaded) {
$cmd[] = '-d pcov.enabled=1';
$cmd[] = "-d pcov.directory=$code";
} elseif ($xdebugLoaded) {
$cmd[] = '-d xdebug.mode=coverage,develop,trace';
} else {
fwrite(\STDERR, "Either the pcov or xdebug extension is required to run tests.\n");
exit(1);
}
$cmd = implode(' ', [
...$cmd,
escapeshellarg(__DIR__ . '/vendor/bin/phpunit'),
'--configuration tests/phpunit.xml',
...$argv,
'--display-deprecations'
]);
passthru($cmd);

28
tests/bootstrap.php

@ -1,24 +1,18 @@
<?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;
namespace MensBeam\HTML\DOM\Test;
const NS_BASE = __NAMESPACE__."\\";
define(NS_BASE."BASE", dirname(__DIR__).DIRECTORY_SEPARATOR);
const DOCROOT = BASE."tests".DIRECTORY_SEPARATOR."docroot".DIRECTORY_SEPARATOR;
ini_set("memory_limit", "-1");
ini_set("zend.assertions", "1");
ini_set("assert.exception", "true");
ini_set('memory_limit', '2G');
ini_set('zend.assertions', '1');
ini_set('assert.exception', 'true');
error_reporting(\E_ALL);
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
define('CWD', dirname(__DIR__));
require_once CWD . '/vendor/autoload.php';
/*if (function_exists("xdebug_set_filter")) {
if (defined("XDEBUG_PATH_INCLUDE")) {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, XDEBUG_PATH_INCLUDE, [BASE."lib/"]);
if (extension_loaded('xdebug') && ini_get('xdebug.mode') !== 'off' && function_exists('xdebug_set_filter')) {
if (defined('XDEBUG_PATH_INCLUDE')) {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_INCLUDE, [ CWD . '/lib/' ]);
} else {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, XDEBUG_PATH_WHITELIST, [BASE."lib/"]);
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [ CWD . '/lib/' ]);
}
}*/
}

148
tests/cases/TestAttr.php

@ -1,148 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
Node
};
/** @covers \MensBeam\HTML\DOM\Attr */
class TestAttr extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Attr::__get_name
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_name(): void {
$d = new Document('<!DOCTYPE html><html><body ook="ook" poop💩="poop💩"><svg id="eek"></svg></body></html>', 'utf-8');
$body = $d->body;
$svg = $d->getElementById('eek');
$svg->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:xlink', Node::XLINK_NAMESPACE);
// HTML name
$this->assertSame('ook', $body->getAttributeNode('ook')->name);
// Coerced name
$this->assertSame('poop💩', $body->getAttributeNode('poop💩')->name);
// Foreign attribute name
$this->assertSame('xmlns:xlink', $svg->getAttributeNodeNS(Node::XMLNS_NAMESPACE, 'xlink')->name);
}
/**
* @covers \MensBeam\HTML\DOM\Attr::__get_prefix
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_prefix(): void {
$d = new Document('<!DOCTYPE html><html><body><svg id="eek"></svg></body></html>', 'utf-8');
$svg = $d->getElementById('eek');
$svg->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:xlink', Node::XLINK_NAMESPACE);
$svg->setAttributeNS('https://poop.poop', 'poop💩:poop💩', 'poop💩');
// Foreign attribute name
$this->assertSame('xmlns', $svg->getAttributeNodeNS(Node::XMLNS_NAMESPACE, 'xlink')->prefix);
$this->assertSame('poop💩', $svg->getAttributeNodeNS('https://poop.poop', 'poop💩')->prefix);
}
/**
* @covers \MensBeam\HTML\DOM\Attr::__get_specified
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_specified(): void {
$d = new Document('<!DOCTYPE html><html><body ook="ook"></body></html>', 'utf-8');
$this->assertTrue($d->body->getAttributeNode('ook')->specified);
}
}

220
tests/cases/TestCharacterData.php

@ -1,220 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
XMLDocument
};
/** @covers \MensBeam\HTML\DOM\CharacterData */
class TestCharacterData extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\CharacterData::appendData
*
* @covers \MensBeam\HTML\DOM\CharacterData::__get_length
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createCDATASection
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_appendData(): void {
$d = new Document();
$t = $d->createTextNode('ookeek');
$this->assertEquals(6, $t->length);
$t->appendData('💩');
$this->assertEquals(7, $t->length);
$d = new XMLDocument();
$t = $d->createCDATASection('ookeek');
$this->assertEquals(6, $t->length);
$t->appendData('💩');
$this->assertEquals(7, $t->length);
}
/**
* @covers \MensBeam\HTML\DOM\CharacterData::deleteData
*
* @covers \MensBeam\HTML\DOM\CharacterData::__get_data
* @covers \MensBeam\HTML\DOM\CharacterData::__set_data
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_deleteData(): void {
$d = new Document();
$t = $d->createTextNode('ook eek');
$t->deleteData(3, 1);
$this->assertSame('ookeek', $t->data);
$t->data = 'ook💩eek';
$t->deleteData(3, 1);
$this->assertSame('ookeek', $t->data);
}
/**
* @covers \MensBeam\HTML\DOM\CharacterData::insertData
*
* @covers \MensBeam\HTML\DOM\CharacterData::__get_data
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_insertData(): void {
$d = new Document();
$t = $d->createTextNode('ookeek');
$t->insertData(3, '💩');
$this->assertSame('ook💩eek', $t->data);
$t->insertData(3, '💩');
$this->assertSame('ook💩💩eek', $t->data);
}
/**
* @covers \MensBeam\HTML\DOM\CharacterData::replaceData
*
* @covers \MensBeam\HTML\DOM\CharacterData::__get_data
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_replaceData(): void {
$d = new Document();
$t = $d->createTextNode('ook💩💩eek');
$t->replaceData(3, 2, ' ');
$this->assertSame('ook eek', $t->data);
}
/**
* @covers \MensBeam\HTML\DOM\CharacterData::substringData
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_substringData(): void {
$d = new Document();
$t = $d->createTextNode('ook💩💩eek');
$this->assertSame('💩💩', $t->substringData(3, 2));
}
/**
* @covers \MensBeam\HTML\DOM\CharacterData::__get_data
* @covers \MensBeam\HTML\DOM\CharacterData::__set_data
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createCDATASection
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_data(): void {
$d = new Document();
$t = $d->createTextNode('ook');
// Getting is thoroughly tested, setting isn't.
$t->data = 'eek';
$this->assertSame('eek', $t->data);
$d = new XMLDocument();
$t = $d->createCDATASection('ook');
$t->data = 'eek';
$this->assertSame('eek', $t->data);
}
/**
* @covers \MensBeam\HTML\DOM\CharacterData::__get_length
*
* @covers \MensBeam\HTML\DOM\CharacterData::__get_data
* @covers \MensBeam\HTML\DOM\CharacterData::__set_data
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createCDATASection
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_length(): void {
$d = new Document();
$t = $d->createTextNode('ookeek');
$this->assertEquals(6, $t->length);
$t->data .= '💩';
$this->assertEquals(7, $t->length);
$d = new XMLDocument();
$t = $d->createCDATASection('ookeek');
$this->assertEquals(6, $t->length);
$t->data .= '💩';
$this->assertEquals(7, $t->length);
}
}

146
tests/cases/TestChildNode.php

@ -1,146 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
Node
};
/** @covers \MensBeam\HTML\DOM\ChildNode */
class TestChildNode extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\ChildNode::after
* @covers \MensBeam\HTML\DOM\ChildNode::before
* @covers \MensBeam\HTML\DOM\ChildNode::replaceWith
*
* @covers \MensBeam\HTML\DOM\Comment::__construct
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createComment
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::serialize
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_firstChild
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Node::__toString
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::containsInner
* @covers \MensBeam\HTML\DOM\Node::convertNodesToNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::insertBefore
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Node::replaceChild
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_after_before_replaceWith(): void {
$d = new Document();
$d->appendChild($d->createElement('html'));
$body = $d->documentElement->appendChild($d->createElement('body'));
$div = $body->appendChild($d->createElement('div'));
$o = $body->appendChild($d->createTextNode('ook'));
$div2 = $body->appendChild($d->createElement('div'));
// On node with parent
$div->after($d->createElement('span'), $o, 'eek');
$this->assertSame('<body><div></div><span></span>ookeek<div></div></body>', (string)$body);
$div->after($o);
$this->assertSame('<body><div></div>ook<span></span>eek<div></div></body>', (string)$body);
// On node with no parent
$c = $d->createComment('ook');
$this->assertNull($c->after($d->createTextNode('ook')));
// On node with parent
$br = $body->insertBefore($d->createElement('br'), $div);
$e = $d->createTextNode('eek');
$div->before($d->createElement('span'), $o, 'eek', $e, $br);
$this->assertSame('<body><span></span>ookeekeek<br><div></div><span></span>eek<div></div></body>', (string)$body);
$div->before($o);
$this->assertSame('<body><span></span>eekeek<br>ook<div></div><span></span>eek<div></div></body>', (string)$body);
// On node with no parent
$c = $d->createComment('ook');
$this->assertNull($c->before($d->createTextNode('ook')));
// On node with parent
$s = $d->createElement('span');
$br->replaceWith('ack', $o, $e, $s);
$this->assertSame('<body><span></span>eekackookeek<span></span><div></div><span></span>eek<div></div></body>', (string)$body);
$s->replaceWith($o);
$this->assertSame('<body><span></span>eekackeekook<div></div><span></span>eek<div></div></body>', (string)$body);
// On node with no parent
$c = $d->createComment('ook');
$this->assertNull($c->replaceWith($d->createTextNode('ook')));
// Parent within node
$o->replaceWith('poo', $o, $e);
$this->assertSame('<body><span></span>eekackpooookeek<div></div><span></span>eek<div></div></body>', (string)$body);
}
/**
* @covers \MensBeam\HTML\DOM\ChildNode::remove
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::removeChild
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_remove(): void {
$d = new Document('<!DOCTYPE html><html><body></body></html>');
$body = $d->body;
$body->remove();
$this->assertNull($d->body);
// Test removal of an element without a parent.
$body->remove();
}
}

94
tests/cases/TestCollection.php

@ -1,94 +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\TestCase;
use MensBeam\HTML\DOM\{
Attr,
Document,
HTMLElement
};
/** @covers \MensBeam\HTML\DOM\Collection */
class TestCollection extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Collection::current
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Collection::key
* @covers \MensBeam\HTML\DOM\Collection::next
* @covers \MensBeam\HTML\DOM\Collection::offsetExists
* @covers \MensBeam\HTML\DOM\Collection::rewind
* @covers \MensBeam\HTML\DOM\Collection::valid
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_childNodes
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testIteration(): void {
$d = new Document('<!DOCTYPE html><html><body><br><br><br><br><br></body></html>');
$body = $d->body;
$children = $body->childNodes;
foreach ($children as $key => $child) {
$this->assertTrue($child instanceof HTMLElement);
}
}
/**
* @covers \MensBeam\HTML\DOM\Collection::offsetGet
* @covers \MensBeam\HTML\DOM\Collection::offsetUnset
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_attributes
* @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct
* @covers \MensBeam\HTML\DOM\NamedNodeMap::item
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_offsetSet_offsetUnset(): void {
$d = new Document('<!DOCTYPE html><html><body a="ook" b="eek" c="ook" d="eek" e="ook"></body></html>');
$body = $d->body;
$attributes = $body->attributes;
$attributes[0] = 'eek';
$this->assertSame('ook', $attributes[0]->value);
unset($attributes[2]);
$this->assertSame('ook', $attributes[2]->value);
}
}

164
tests/cases/TestDOMImplementation.php

@ -1,164 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
DOMImplementation,
Node
};
/** @covers \MensBeam\HTML\DOM\DOMImplementation */
class TestDOMImplementation extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocument
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_contentType
* @covers \MensBeam\HTML\DOM\Document::__get_doctype
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DocumentType::__get_name
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_localName
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::__get_prefix
* @covers \MensBeam\HTML\DOM\Element::__get_tagName
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_nodeName
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\Inner\Reflection::setProtectedProperties
*/
public function testMethod_createDocument(): void {
$di = new DOMImplementation();
$d = $di->createDocument(null, 'ook', $di->createDocumentType('ook', 'eek', 'ack'));
$this->assertNull($d->documentElement->namespaceURI);
$this->assertSame('ook', $d->documentElement->tagName);
$this->assertSame('ook', $d->doctype->nodeName);
$this->assertSame('application/xml', $d->contentType);
$d = $di->createDocument(Node::HTML_NAMESPACE, 'html', $di->createDocumentType('html', '', ''));
$this->assertSame(Node::HTML_NAMESPACE, $d->documentElement->namespaceURI);
$this->assertSame('html', $d->documentElement->tagName);
$this->assertSame('html', $d->doctype->nodeName);
$this->assertSame('application/xhtml+xml', $d->contentType);
$d = $di->createDocument(Node::SVG_NAMESPACE, 'svg', null);
$this->assertSame(Node::SVG_NAMESPACE, $d->documentElement->namespaceURI);
$this->assertSame('svg', $d->documentElement->tagName);
$this->assertNull($d->doctype);
$this->assertSame('image/svg+xml', $d->contentType);
}
/**
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
*/
public function testMethod_createDocumentType__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::INVALID_CHARACTER);
$di = new DOMImplementation();
$di->createDocumentType('fail fail', 'fail', 'fail');
}
/**
* @covers \MensBeam\HTML\DOM\DOMImplementation::createHTMLDocument
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_contentType
* @covers \MensBeam\HTML\DOM\Document::__get_doctype
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::__get_implementation
* @covers \MensBeam\HTML\DOM\Document::__get_title
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DocumentType::__get_name
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_localName
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::__get_prefix
* @covers \MensBeam\HTML\DOM\Element::__get_tagName
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_nodeName
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::setProtectedProperties
*/
public function testMethod_createHTMLDocument(): void {
$di = new DOMImplementation();
$d = $di->createHTMLDocument('ook');
$this->assertSame(Node::HTML_NAMESPACE, $d->documentElement->namespaceURI);
$this->assertSame('html', $d->documentElement->tagName);
$this->assertSame('html', $d->doctype->nodeName);
$this->assertSame('text/html', $d->contentType);
$this->assertSame('ook', $d->title);
}
/**
* @covers \MensBeam\HTML\DOM\DOMImplementation::hasFeature
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
*/
public function testMethod_hasFeature(): void {
$di = new DOMImplementation();
$this->assertTrue($di->hasFeature());
}
}

578
tests/cases/TestDOMTokenList.php

@ -1,578 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
Node,
Text,
XMLDocument
};
/** @covers \MensBeam\HTML\DOM\DOMTokenList */
class TestDOMTokenList extends \PHPUnit\Framework\TestCase {
public function provideMethod_add_remove_replace_toggle___errors(): iterable {
return [
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->add('');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->remove('');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->replace('ack', '');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->toggle('');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->add('fail fail');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->remove('fail fail');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->replace('ack', 'fail fail');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->toggle('fail fail');
}, DOMException::INVALID_CHARACTER ]
];
}
/**
* @dataProvider provideMethod_add_remove_replace_toggle___errors
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::remove
* @covers \MensBeam\HTML\DOM\DOMTokenList::replace
* @covers \MensBeam\HTML\DOM\DOMTokenList::toggle
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_add_remove_replace_toggle___errors(\Closure $closure, int $errorCode): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::contains
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_contains() {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertTrue($e->classList->contains('ack'));
$this->assertFalse($e->classList->contains('fail'));
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::count
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_count(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$this->assertEquals(0, count($e->classList));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertEquals(4, count($e->classList));
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::item
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_length
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_item(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertNull($e->classList->item(42));
$this->assertEquals(4, $e->classList->length);
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::offsetSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::offsetUnset
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_length
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_offsetSet_offsetUnset(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$e->classList[3] = 'ook';
unset($e->classList[3]);
$this->assertEquals(4, $e->classList->length);
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::replace
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_value
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_replace(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->setAttribute('class', 'ook eek ack ookeek');
$this->assertTrue($e->classList->replace('ack', 'what'));
$this->assertSame('ook eek what ookeek', $e->classList->value);
$this->assertSame('ook eek what ookeek', $e->getAttribute('class'));
$this->assertFalse($e->classList->replace('fail', 'eekook'));
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::remove
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_length
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_value
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::item
* @covers \MensBeam\HTML\DOM\DOMTokenList::offsetGet
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_remove(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->remove('ook');
$e->setAttribute('class', 'ook eek ack ookeek');
$e->classList->remove('ack');
$this->assertSame('ook eek ookeek', $e->classList->value);
$this->assertSame('ook eek ookeek', $e->getAttribute('class'));
// It is wasteful to do it like this of course, but this is only for testing.
$classList = $e->classList;
while ($classList->length > 0) {
$classList->remove($classList[0]);
}
$this->assertEquals(0, $e->classList->length);
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::supports
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_supports(): void {
// PHPUnit is supposed to support expecting of errors, but it doesn't. So let's
// write a bunch of bullshit so we can catch and assert errors instead.
set_error_handler(function($errno) {
if ($errno === \E_USER_ERROR) {
$this->assertEquals(\E_USER_ERROR, $errno);
}
});
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$e->classList->supports('ack');
restore_error_handler();
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::toggle
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_value
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::remove
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_toggle(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->setAttribute('class', 'ook eek ack ookeek');
$this->assertFalse($e->classList->toggle('ack'));
$this->assertSame('ook eek ookeek', $e->classList->value);
$this->assertSame('ook eek ookeek', $e->getAttribute('class'));
$this->assertTrue($e->classList->toggle('ack'));
$this->assertSame('ook eek ookeek ack', $e->classList->value);
$this->assertSame('ook eek ookeek ack', $e->getAttribute('class'));
$this->assertTrue($e->classList->toggle('ack', true));
$this->assertSame('ook eek ookeek ack', $e->classList->value);
$this->assertSame('ook eek ookeek ack', $e->getAttribute('class'));
$this->assertFalse($e->classList->toggle('eekook', false));
$this->assertSame('ook eek ookeek ack', $e->classList->value);
$this->assertSame('ook eek ookeek ack', $e->getAttribute('class'));
$this->assertTrue($e->classList->toggle('eekook', true));
$this->assertSame('ook eek ookeek ack eekook', $e->classList->value);
$this->assertSame('ook eek ookeek ack eekook', $e->getAttribute('class'));
}
/**
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::current
* @covers \MensBeam\HTML\DOM\DOMTokenList::item
* @covers \MensBeam\HTML\DOM\DOMTokenList::key
* @covers \MensBeam\HTML\DOM\DOMTokenList::next
* @covers \MensBeam\HTML\DOM\DOMTokenList::offsetExists
* @covers \MensBeam\HTML\DOM\DOMTokenList::offsetGet
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::rewind
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\DOMTokenList::valid
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProcess_iteration(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
foreach ($e->classList as $key => $className) {
$this->assertSame($className, $e->classList[$key]);
// test offsetExists
$this->assertTrue(isset($e->classList[$key]));
}
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_value
* @covers \MensBeam\HTML\DOM\DOMTokenList::__set_value
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::item
* @covers \MensBeam\HTML\DOM\DOMTokenList::offsetGet
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_value(): void {
// Test it with and without an attached document element
$d = new Document();
$e = $d->createElement('html');
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertSame('ook eek ack ookeek', $e->classList->value);
$this->assertSame('ook eek ack ookeek', $e->getAttribute('class'));
$e->classList->value = 'omg wtf bbq lol zor bor xxx';
$this->assertSame('lol', $e->classList[3]);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->classList->value);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->getAttribute('class'));
$e->classList->value = '';
$this->assertSame('', $e->classList->value);
$this->assertSame('', $e->getAttribute('class'));
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertSame('ook eek ack ookeek', $e->classList->value);
$this->assertSame('ook eek ack ookeek', $e->getAttribute('class'));
$e->classList->value = 'omg wtf bbq lol zor bor xxx';
$this->assertSame('lol', $e->classList[3]);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->classList->value);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->getAttribute('class'));
$e->classList->value = '';
$this->assertSame('', $e->classList->value);
$this->assertSame('', $e->getAttribute('class'));
}
}

1363
tests/cases/TestDocument.php

File diff suppressed because it is too large

180
tests/cases/TestDocumentOrElement.php

@ -1,134 +1,78 @@
<?php
/**
* @license MIT
* Copyright 2017 Dustin Wilson, J. King, et al.
* Copyright 2022 Dustin Wilson, 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,
Node,
Text,
XMLDocument
namespace MensBeam\HTML\DOM\Test;
use MensBeam\HTML\DOM\Document;
use MensBeam\HTML\DOM\DOMException\{
InvalidCharacterError,
NamespaceError
};
use PHPUnit\Framework\{
TestCase,
Attributes\CoversClass,
Attributes\DataProvider
};
/** @covers \MensBeam\HTML\DOM\DocumentOrElement */
class TestDocumentOrElement extends \PHPUnit\Framework\TestCase {
public function testMethod_getElementsByClassName() {
$d = new Document('<!DOCTYPE html><html><body><div class="ook eek"><span class="ook eek"><i class="ookeek eek"></i></span></div></body></html>');
// Empty class
$ook = $d->getElementsByClassName('');
$this->assertEquals(0, $ook->length);
// Whitespace
$ook = $d->getElementsByClassName(' ');
$this->assertEquals(0, $ook->length);
// Document context node
$ook = $d->getElementsByClassName('ook');
$this->assertEquals(2, $ook->length);
// Document context node with additional whitespace
$ook = $d->getElementsByClassName(' ook ');
$this->assertEquals(2, $ook->length);
// Element context node
$div = $ook[0];
$ook = $div->getElementsByClassName('ook');
$this->assertSame('DIV', $div->nodeName);
$this->assertEquals(1, $ook->length);
$this->assertSame('SPAN', $ook[0]->nodeName);
// Multiple classes
$ook = $d->getElementsByClassName('ook eek');
$this->assertEquals(2, $ook->length);
}
public function testMethod_getElementsByTagName() {
$d = new Document('<!DOCTYPE html><html><body><div><div><span></span></div></div><div></div><span></span><span></span><svg></svg></body></html>');
// Document context
$div = $d->getElementsByTagName('div');
$this->assertEquals(3, $div->length);
// Element context
$div = $div[1];
$span = $div->getElementsByTagName('span');
$this->assertEquals(1, $span->length);
// Wildcard
$all = $d->getElementsByTagName('*');
$this->assertEquals(10, $all->length);
// XML Document
$d = new XMLDocument();
$ook = $d->appendChild($d->createElement('ook'));
for ($i = 0; $i < 9; $i++) {
$ook->appendChild($d->createElement('ook'));
}
$ook = $d->getElementsByTagName('ook');
$this->assertEquals(10, $ook->length);
}
public function testMethod_getElementsByTagNameNS() {
$d = new Document('<!DOCTYPE html><html><body><div><div><span></span></div></div><div></div><span></span><span></span><svg></svg></body></html>');
$svg = $d->body->lastChild;
$svg->appendChild($d->createElementNS(Node::SVG_NAMESPACE, 'div'));
// HTML namespace
$div = $d->getElementsByTagNameNS(Node::HTML_NAMESPACE, 'div');
$this->assertEquals(3, $div->length);
// Null namespace
$div = $d->getElementsByTagNameNS(null, 'div');
$this->assertEquals(0, $div->length);
// Empty string namespace
$div = $d->getElementsByTagNameNS('', 'div');
$this->assertEquals(0, $div->length);
// Wildcard namespace and local name
$all = $d->getElementsByTagNameNS('*', '*');
$this->assertEquals(11, $all->length);
// Wildcard namespace
$div = $d->getElementsByTagNameNS('*', 'div');
$this->assertEquals(4, $div->length);
// Wildcard local name
$svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, '*');
$this->assertEquals(2, $svg->length);
#[CoversClass('MensBeam\HTML\DOM\DocumentOrElement')]
#[CoversClass('MensBeam\HTML\DOM\Document')]
#[CoversClass('MensBeam\HTML\DOM\DOMException\InvalidCharacterError')]
#[CoversClass('MensBeam\HTML\DOM\DOMException\NamespaceError')]
class TestDocumentOrElement extends TestCase {
#[DataProvider('provideFatalErrors')]
public function testFatalErrors(string $throwableClassName, \Closure $closure): void {
$this->expectException($throwableClassName);
$d = new Document();
$closure($d);
$d->destroy();
}
public function provideMethod_validateAndExtract__errors(): iterable {
return [
[ function() {
$d = new Document();
$d->createElementNS(Node::HTML_NAMESPACE, 'this will fail');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$d->createAttributeNS(Node::HTML_NAMESPACE, 'xmlns');
}, DOMException::NAMESPACE_ERROR ]
public static function provideFatalErrors(): iterable {
$iterable = [
// Invalid attribute name
[
InvalidCharacterError::class,
function (Document $d): void {
$d->createAttributeNS('fail', ' ');
}
],
// Attribute: non-null prefix, null/empty namespace
[
NamespaceError::class,
function (Document $d): void {
$d->createAttributeNS('', 'fail:fail');
}
],
// Attribute: prefix is 'xml', namespace is not xml namespace
[
NamespaceError::class,
function (Document $d): void {
$d->createAttributeNS('fail', 'xml:fail');
}
],
// Attribute: qualified name is 'xmlns', namespace is not xmlns namespace
[
NamespaceError::class,
function (Document $d): void {
$d->createAttributeNS('fail', 'xmlns');
}
],
// Attribute: prefix is 'xmlns', namespace is not xmlns namespace
[
NamespaceError::class,
function (Document $d): void {
$d->createAttributeNS('fail', 'xmlns:fail');
}
]
];
}
/**
* @dataProvider provideMethod_validateAndExtract__errors
*/
public function testMethod_validateAndExtract__errors(\Closure $closure, int $errorCode): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
foreach ($iterable as $i) {
yield $i;
}
}
}

1473
tests/cases/TestElement.php

File diff suppressed because it is too large

35
tests/cases/TestException.php

@ -1,35 +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\TestCase;
use MensBeam\HTML\DOM\Exception;
/** @covers \MensBeam\HTML\DOM\Exception */
class TestDOMException extends \PHPUnit\Framework\TestCase {
public function provideConstructorFailures(): iterable {
return [
[ function() {
$d = new Exception(2112);
}, Exception::INVALID_CODE ],
[ function() {
throw new Exception(Exception::UNKNOWN_ERROR, 'FAIL');
}, Exception::INCORRECT_PARAMETERS_FOR_MESSAGE ]
];
}
/**
* @dataProvider provideConstructorFailures
* @covers \MensBeam\HTML\DOM\Exception::__construct
*/
public function testConstructorFailures(\Closure $closure, int $errorCode): void {
$this->expectException(Exception::class);
$this->expectExceptionCode($errorCode);
$closure();
}
}

126
tests/cases/TestHTMLCollection.php

@ -1,126 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
Element,
Node
};
/** @covers \MensBeam\HTML\DOM\HTMLCollection */
class TestHTMLCollection extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\HTMLCollection::current
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetExists
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::current
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Collection::key
* @covers \MensBeam\HTML\DOM\Collection::next
* @covers \MensBeam\HTML\DOM\Collection::rewind
* @covers \MensBeam\HTML\DOM\Collection::valid
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\ParentNode::__get_children
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testIteration(): void {
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<div id="ook">Ook</div>
<div id="eek">Eek</div>
<div id="ack">Ack</div>
<div name="ook">Ook</div>
<div name="poop💩">poop💩</div>
</body>
</html>
HTML, 'UTF-8');
$body = $d->body;
$children = $body->children;
foreach ($children as $key => $child) {
$this->assertTrue($child instanceof Element);
}
}
/**
* @covers \MensBeam\HTML\DOM\HTMLCollection::namedItem
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
*
* @covers \MensBeam\HTML\DOM\CharacterData::__get_data
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_firstChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\ParentNode::__get_children
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_namedItem_offsetGet(): void {
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<div id="ook">Ook</div>
<div id="eek">Eek</div>
<div id="ack">Ack</div>
<div name="ook">Ook</div>
<div name="poop💩">poop💩</div>
</body>
</html>
HTML, 'UTF-8');
$body = $d->body;
$children = $body->children;
$this->assertSame($children[0], $children['ook']);
$this->assertSame($children[0], $children->namedItem('ook'));
$this->assertSame('Ook', $children['ook']->firstChild->data);
$this->assertSame('poop💩', $children['poop💩']->firstChild->data);
$this->assertNull($children['fail']);
$this->assertNull($children->namedItem('fail'));
$this->assertNull($children->namedItem(''));
}
}

693
tests/cases/TestHTMLElement.php

@ -1,693 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException
};
/** @covers \MensBeam\HTML\DOM\HTMLElement */
class TestHTMLElement extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_accessKey
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_accessKey
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_accessKey(): void {
$d = new Document('<!DOCTYPE html><html><body><a id="ook" href="https://ook.com" accesskey="o"></a></body></html>');
$ook = $d->getElementById('ook');
$this->assertSame('o', $ook->accessKey);
$ook->accessKey = 'e';
$this->assertSame('e', $ook->accessKey);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_autocapitalize
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_autocapitalize
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::removeAttribute
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
* @covers \MensBeam\HTML\DOM\HTMLElement::autoCapitalizationHint
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_autocapitalize(): void {
$d = new Document('<!DOCTYPE html><html><body><form><input type="text" autocapitalize="on"></form></body></html>');
$ook = $d->getElementsByTagName('input')[0];
$this->assertSame('sentences', $ook->autocapitalize);
$ook->removeAttribute('autocapitalize');
$this->assertSame('', $ook->autocapitalize);
$ook->autocapitalize = 'words';
$this->assertSame('words', $ook->autocapitalize);
$ook->autocapitalize = 'off';
$this->assertSame('none', $ook->autocapitalize);
$form = $d->getElementsByTagName('form')[0];
$form->autocapitalize = 'bullshit';
$ook->removeAttribute('autocapitalize');
$this->assertSame('sentences', $ook->autocapitalize);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_contentEditable
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_contentEditable
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_isContentEditable
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_designMode
* @covers \MensBeam\HTML\DOM\Document::__set_designMode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::hasAttribute
* @covers \MensBeam\HTML\DOM\Element::removeAttribute
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_contentEditable_isContentEditable(): void {
$d = new Document('<!DOCTYPE html><html><body><div></div></body></html>');
$div = $d->getElementsByTagName('div')[0];
$this->assertSame('inherit', $div->contentEditable);
$this->assertFalse($div->isContentEditable);
$div->contentEditable = 'true';
$this->assertSame('true', $div->contentEditable);
$this->assertTrue($div->isContentEditable);
$div->contentEditable = 'false';
$this->assertSame('false', $div->contentEditable);
$this->assertFalse($div->isContentEditable);
$div->contentEditable = 'inherit';
$this->assertFalse($div->hasAttribute('contenteditable'));
$this->assertSame('inherit', $div->contentEditable);
$div->removeAttribute('contenteditable');
$d->body->contentEditable = 'true';
$this->assertSame('inherit', $div->contentEditable);
$this->assertTrue($div->isContentEditable);
$d->designMode = 'on';
$this->assertTrue($div->isContentEditable);
$d->body->contentEditable = 'false';
$d->designMode = 'off';
$this->assertFalse($div->isContentEditable);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_contentEditable
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_contentEditable__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::SYNTAX_ERROR);
$d = new Document('<!DOCTYPE html><html></html>');
$d->documentElement->contentEditable = 'fail';
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_dir
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_dir
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_dir(): void {
$d = new Document('<!DOCTYPE html><html dir="ltr"></html>');
$html = $d->documentElement;
$this->assertSame('ltr', $html->dir);
$html->dir = 'bullshit';
$this->assertSame('', $html->dir);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_draggable
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_draggable
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_localName
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::__get_prefix
* @covers \MensBeam\HTML\DOM\Element::__get_tagName
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::hasAttribute
* @covers \MensBeam\HTML\DOM\Element::removeAttribute
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_draggable(): void {
$d = new Document('<!DOCTYPE html><html><body><img src="ook.html"><object data="ook"></object><div></div></body></html>');
$img = $d->getElementsByTagName('img')[0];
$div = $d->getElementsByTagName('div')[0];
$object = $d->getElementsByTagName('object')[0];
$this->assertTrue($img->draggable);
$this->assertFalse($div->draggable);
$div->draggable = true;
$this->assertTrue($div->draggable);
$div->draggable = false;
$this->assertFalse($div->draggable);
$img->draggable = false;
$this->assertFalse($img->draggable);
$this->assertFalse($object->draggable);
$object->setAttribute('type', 'image/jpeg');
$this->assertTrue($object->draggable);
$object->removeAttribute('type');
$object->setAttribute('data', 'ook.png');
$this->assertTrue($object->draggable);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_enterKeyHint
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_enterKeyHint
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_enterKeyHint(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertSame('', $html->enterKeyHint);
$html->enterKeyHint = 'ook';
$this->assertSame('', $html->enterKeyHint);
$html->enterKeyHint = 'done';
$this->assertSame('done', $html->enterKeyHint);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_hidden
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_hidden
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::hasAttribute
* @covers \MensBeam\HTML\DOM\Element::removeAttribute
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_hidden(): void {
$d = new Document('<!DOCTYPE html><html><body><div></div></body></html>');
$div = $d->getElementsByTagName('div')[0];
$this->assertFalse($div->hidden);
$div->hidden = true;
$this->assertTrue($div->hidden);
$div->hidden = false;
$d->documentElement->hidden = true;
$this->assertTrue($div->hidden);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_lang
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_lang
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_lang(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertSame('', $html->lang);
$html->lang = 'en';
$this->assertSame('en', $html->lang);
$html->lang = 'ook';
$this->assertSame('ook', $html->lang);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_innerText
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_innerText
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_outerText
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_outerText
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::__get_length
* @covers \MensBeam\HTML\DOM\Collection::count
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Element::getRenderedTextFragment
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_childNodes
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Node::__get_textContent
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testProperty_innerText_outerText(): void {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->documentElement->appendChild($d->createElement('body'));
$body = $d->body;
$body->appendChild($d->createTextNode('ook '));
$s = $body->appendChild($d->createElement('span'));
$s->appendChild($d->createTextNode('ook'));
$body->appendChild($d->createTextNode(' eek'));
$this->assertSame('ook <span>ook</span> eek', $body->innerHTML);
$s->innerText = <<<TEXT
ook\r\n
eek ook
TEXT;
$this->assertSame('ook ookook eek ook eek', $body->innerText);
$this->assertSame('ook<br><br>ook eek ook', $s->innerHTML);
$s->outerText = 'ack';
$this->assertSame('ook ack eek', $body->outerText);
$this->assertEquals(1, $body->childNodes->length);
$s = $body->appendChild($d->createElement('span'));
$s->outerText = '';
$this->assertSame('ook ack eek', $body->outerText);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_inputMode
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_inputMode
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_inputMode(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertSame('', $html->inputMode);
$html->inputMode = 'ook';
$this->assertSame('', $html->inputMode);
$html->inputMode = 'tel';
$this->assertSame('tel', $html->inputMode);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_outerText
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_outerText__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::NO_MODIFICATION_ALLOWED);
$d = new Document();
$h = $d->createElement('html');
$h->outerText = 'fail';
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_spellcheck
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_spellcheck
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_spellcheck(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertFalse($html->spellcheck);
$html->spellcheck = true;
$this->assertTrue($html->spellcheck);
$html->spellcheck = false;
$this->assertFalse($html->spellcheck);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_title
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_title
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_title(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertSame('', $html->title);
$html->title = 'ook';
$this->assertSame('ook', $html->title);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_translate
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_translate
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::removeAttribute
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_translate(): void {
$d = new Document('<!DOCTYPE html><html><div><span>Ook</span></div></html>');
$html = $d->documentElement;
$span = $d->getElementsByTagName('span')[0];
$this->assertFalse($span->translate);
$span->translate = true;
$this->assertTrue($span->translate);
$span->removeAttribute('translate');
$html->translate = true;
$this->assertTrue($span->translate);
$span->translate = false;
$this->assertFalse($span->translate);
}
}

29
tests/cases/TestHTMLOrSVGElement.php

@ -1,29 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException
};
/** @covers \MensBeam\HTML\DOM\HTMLOrSVGElement */
class TestHTMLOrSVGElement extends \PHPUnit\Framework\TestCase {
public function testProperty_autofocus(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertFalse($html->autofocus);
$html->autofocus = true;
$this->assertTrue($html->autofocus);
$html->autofocus = false;
$this->assertFalse($html->autofocus);
$this->assertFalse($html->hasAttribute('autofocus'));
}
}

108
tests/cases/TestInnerDocument.php

@ -1,39 +1,42 @@
<?php
/**
* @license MIT
* Copyright 2017 Dustin Wilson, J. King, et al.
* Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM\TestCase;
namespace MensBeam\HTML\DOM\Test;
use MensBeam\HTML\DOM\{
Document,
DOMException,
HTMLElement,
HTMLPreElement,
HTMLUnknownElement
HTMLTemplateElement,
HTMLUnknownElement,
MathMLElement,
Node,
SVGElement
};
use MensBeam\HTML\DOM\DOMException\WrongDocumentError;
use PHPUnit\Framework\{
TestCase,
Attributes\CoversClass,
Attributes\DataProvider
};
// use org\bovigo\vfs\vfsStream;
/** @covers \MensBeam\HTML\DOM\Inner\Document */
class TestInnerDocument extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
#[CoversClass('MensBeam\HTML\DOM\Inner\Document')]
#[CoversClass('MensBeam\HTML\DOM\Document')]
#[CoversClass('MensBeam\HTML\DOM\HTMLElement')]
#[CoversClass('MensBeam\HTML\DOM\HTMLPreElement')]
#[CoversClass('MensBeam\HTML\DOM\HTMLTemplateElement')]
#[CoversClass('MensBeam\HTML\DOM\HTMLUnknownElement')]
#[CoversClass('MensBeam\HTML\DOM\MathMLElement')]
#[CoversClass('MensBeam\HTML\DOM\Node')]
#[CoversClass('MensBeam\HTML\DOM\SVGElement')]
#[CoversClass('MensBeam\HTML\DOM\DOMException\WrongDocumentError')]
class TestInnerDocument extends TestCase {
public function testMethod_getWrapperNode(): void {
// Everything tests this method thoroughly except some element interfaces.
$d = new Document();
@ -41,41 +44,42 @@ class TestInnerDocument extends \PHPUnit\Framework\TestCase {
$this->assertSame(HTMLElement::class, $d->createElement('noembed')::class);
$this->assertSame(HTMLPreElement::class, $d->createElement('xmp')::class);
$this->assertSame(HTMLPreElement::class, $d->createElement('pre')::class);
$this->assertSame(MathMLElement::class, $d->createElementNS(Node::MATHML_NAMESPACE, 'math')::class);
$this->assertSame(HTMLTemplateElement::class, $d->createElement('template')::class);
$this->assertSame(HTMLElement::class, $d->createElement('p-icon')::class);
$this->assertSame(SVGElement::class, $d->createElementNS(Node::SVG_NAMESPACE, 'svg')::class);
}
public function provideMethod_getWrapperNode__errors(): iterable {
return [
[ function () {
$d = new Document();
$d->innerNode->getWrapperNode(new \DOMDocument());
}, DOMException::NOT_SUPPORTED ],
[ function () {
$d = new Document();
$d2 = new Document();
$d->innerNode->getWrapperNode($d2->innerNode->createTextNode('fail'));
}, DOMException::WRONG_DOCUMENT ],
];
#[DataProvider('provideFatalErrors')]
public function testFatalErrors(string $throwableClassName, \Closure $closure): void {
$this->expectException($throwableClassName);
$closure();
}
/**
* @dataProvider provideMethod_getWrapperNode__errors
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
*/
public function testMethod_getWrapperNode__errors(\Closure $closure, int $errorCode): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
public static function provideFatalErrors(): iterable {
$iterable = [
// Attempting to get the wrapper node of another document
[
WrongDocumentError::class,
function (): void {
$d = new Document();
$d->innerNode->getWrapperNode(new \DOMDocument());
}
],
// Attempting to get the wrapper node of another document's node
[
WrongDocumentError::class,
function (): void {
$d = new Document();
$d2 = new Document();
$d->innerNode->getWrapperNode($d2->innerNode->createTextNode('fail'));
}
]
];
foreach ($iterable as $i) {
yield $i;
}
}
}

311
tests/cases/TestNamedNodeMap.php

@ -1,311 +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\TestCase;
use MensBeam\HTML\DOM\{
Attr,
Document,
DOMException,
Node,
XMLDocument
};
/** @covers \MensBeam\HTML\DOM\NamedNodeMap */
class TestNamedNodeMap extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\NamedNodeMap::current
* @covers \MensBeam\HTML\DOM\NamedNodeMap::item
* @covers \MensBeam\HTML\DOM\NamedNodeMap::offsetExists
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Collection::current
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Collection::key
* @covers \MensBeam\HTML\DOM\Collection::next
* @covers \MensBeam\HTML\DOM\Collection::rewind
* @covers \MensBeam\HTML\DOM\Collection::valid
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_attributes
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testIteration(): void {
$d = new Document('<!DOCTYPE html><html><body a="ook" b="eek" c="ook" d="eek" e="ook" poop💩="poop💩"></body></html>');
$body = $d->body;
$body->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:href', Node::HTML_NAMESPACE);
$body->setAttributeNS('https://poop💩.poop', 'poop💩:poop💩', 'poop💩');
$attributes = $body->attributes;
foreach ($attributes as $key => $attr) {
$this->assertTrue($attr instanceof Attr);
}
}
/**
* @covers \MensBeam\HTML\DOM\NamedNodeMap::getNamedItem
* @covers \MensBeam\HTML\DOM\NamedNodeMap::getNamedItemNS
* @covers \MensBeam\HTML\DOM\NamedNodeMap::offsetGet
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttribute
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::importNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_attributes
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_nodeName
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::cloneInnerNode
* @covers \MensBeam\HTML\DOM\Node::cloneWrapperNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_getNamedItem_getNamedItemNS_offsetGet(): void {
$d = new Document('<!DOCTYPE html><html><body a="ook" b="eek" c="ook" d="eek" e="ook" poop💩="poop💩"></body></html>', 'UTF-8');
$body = $d->body;
$body->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:href', Node::HTML_NAMESPACE);
$body->setAttributeNS('https://poop💩.poop', 'poop💩:poop💩', 'poop💩');
$attributes = $body->attributes;
$this->assertSame($attributes[0], $attributes['a']);
$this->assertSame('ook', $attributes['a']->value);
$this->assertSame('ook', $attributes->getNamedItem('a')->value);
$this->assertSame(Node::HTML_NAMESPACE, $attributes['xmlns:href']->value);
$this->assertSame('poop💩', $attributes['poop💩']->value);
$this->assertSame('poop💩', $attributes->getNamedItem('poop💩')->value);
$this->assertSame('poop💩', $attributes['poop💩:poop💩']->value);
$this->assertSame('poop💩', $attributes->getNamedItemNS('https://poop💩.poop', 'poop💩')->value);
// Testing an edge case with uppercased attributes in the specification...
$d2 = new XMLDocument();
$F = $d2->createAttribute('F');
$F->value = 'eek';
$F = $d->importNode($F);
$body->setAttributeNode($F);
$this->assertNull($attributes['F']);
$this->assertSame('F', $F->nodeName);
}
/**
* @covers \MensBeam\HTML\DOM\NamedNodeMap::removeNamedItem
* @covers \MensBeam\HTML\DOM\NamedNodeMap::removeNamedItemNS
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Collection::__get_length
* @covers \MensBeam\HTML\DOM\Collection::count
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_attributes
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::hasAttribute
* @covers \MensBeam\HTML\DOM\Element::hasAttributeNS
* @covers \MensBeam\HTML\DOM\Element::removeAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_removeNamedItem_removeNamedItemNS(): void {
$d = new Document('<!DOCTYPE html><html><body a="ook" b="eek" c="ook" d="eek" e="ook" poop💩="poop💩"></body></html>', 'UTF-8');
$body = $d->body;
$body->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:href', Node::HTML_NAMESPACE);
$body->setAttributeNS('https://poop💩.poop', 'poop💩:poop💩', 'poop💩');
$attributes = $body->attributes;
$this->assertEquals(8, $attributes->length);
$attributes->removeNamedItem('a');
$this->assertEquals(7, $attributes->length);
$this->assertFalse($body->hasAttribute('a'));
$attributes->removeNamedItem('poop💩');
$this->assertEquals(6, $attributes->length);
$this->assertFalse($body->hasAttribute('poop💩'));
$attributes->removeNamedItemNS('https://poop💩.poop', 'poop💩');
$this->assertEquals(5, $attributes->length);
$this->assertFalse($body->hasAttributeNS('https://poop💩.poop', 'poop💩'));
}
/**
* @covers \MensBeam\HTML\DOM\NamedNodeMap::removeNamedItem
* @covers \MensBeam\HTML\DOM\NamedNodeMap::removeNamedItemNS
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_attributes
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_removeNamedItem__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::NOT_FOUND);
$d = new Document('<!DOCTYPE html><html><body></body></html>', 'UTF-8');
$body = $d->body;
$body->attributes->removeNamedItem('fail');
}
/**
* @covers \MensBeam\HTML\DOM\NamedNodeMap::setNamedItem
* @covers \MensBeam\HTML\DOM\NamedNodeMap::setNamedItemNS
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttribute
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_attributes
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_setNamedItem_setNamedItemNS(): void {
$d = new Document('<!DOCTYPE html><html><body a="ook" b="eek" c="ook" d="eek" e="ook"></body></html>', 'UTF-8');
$body = $d->body;
$attributes = $body->attributes;
$poop = $d->createAttribute('poop💩');
$poop->value = 'poop💩';
$attributes->setNamedItem($poop);
$this->assertSame($attributes['poop💩'], $poop);
$x = $d->createAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:href');
$x->value = Node::HTML_NAMESPACE;
$attributes->setNamedItemNS($x);
$this->assertSame($attributes['xmlns:href'], $x);
}
}

1889
tests/cases/TestNode.php

File diff suppressed because it is too large

34
tests/cases/TestNonDocumentTypeChildNode.php

@ -1,34 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
Node
};
/** @covers \MensBeam\HTML\DOM\NonDocumentTypeChildNode */
class TestNonDocumentTypeChildNode extends \PHPUnit\Framework\TestCase {
public function testProperty_nextElementSibling_previousElementSibling(): void {
$d = new Document('<!DOCTYPE html><html><body></body></html>');
$body = $d->body;
$br = $body->appendChild($d->createElement('br'));
$body->appendChild($d->createTextNode('eek'));
$ook = $body->appendChild($d->createTextNode('ook'));
$br2 = $body->appendChild($d->createElement('br'));
$this->assertSame($br2, $br->nextElementSibling);
$this->assertNull($br2->nextElementSibling);
$this->assertSame($br, $ook->previousElementSibling);
$this->assertNull($br->previousElementSibling);
}
}

348
tests/cases/TestParentNode.php

@ -1,348 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
Node
};
/** @covers \MensBeam\HTML\DOM\ParentNode */
class TestParentNode extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\ParentNode::append
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::convertNodesToNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_append(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$eek = $d->getElementById('eek');
$eek->append('ook', $d->createElement('br'));
$eek->append('eek');
$this->assertSame('eekook<br>eek', $eek->innerHTML);
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::prepend
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_firstChild
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::convertNodesToNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::insertBefore
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_prepend(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$eek = $d->getElementById('eek');
$eek->prepend('ook', $d->createElement('br'));
$this->assertSame('ook<br>eek', $eek->innerHTML);
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::replaceChildren
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::convertNodesToNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_replaceChildren(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$eek = $d->getElementById('eek');
$eek->replaceChildren('ook', $d->createElement('br'));
$this->assertSame('ook<br>', $eek->innerHTML);
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::querySelector
* @covers \MensBeam\HTML\DOM\ParentNode::querySelectorAll
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::__get_length
* @covers \MensBeam\HTML\DOM\Collection::count
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Collection::offsetGet
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_localName
* @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::__get_prefix
* @covers \MensBeam\HTML\DOM\Element::__get_tagName
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\ParentNode::scopeMatchSelector
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_querySelector_querySelectorAll(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$div = $d->body->querySelector('div');
$this->assertSame('div', $div->tagName);
$this->assertNull($d->querySelector('body::before'));
$divs = $d->body->querySelectorAll('div');
$this->assertEquals(2, $divs->length);
$this->assertSame('eek', $divs[1]->getAttribute('id'));
$this->assertNull($d->querySelector('.ook'));
$this->assertEquals(0, $d->querySelectorAll('body::before')->length);
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::querySelector
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\ParentNode::scopeMatchSelector
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
*/
public function testMethod_querySelector__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::SYNTAX_ERROR);
$d = new Document();
$d->querySelector('fail?');
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::walk
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::setProtectedProperties
*/
public function testMethod_walk(): void {
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<div>ook</div>
<div>
<div><p>Eek</p></div>
<div>
<div><p>Ook</p></div>
</div>
</div>
</body>
</html>
HTML);
// Empty filter -- walk over all nodes
$w = $d->walk();
$this->assertSame($d->doctype, $w->current());
foreach ($w as $node);
// Simple accept on element and reject everything else filter
$w = $d->walk(function($n) {
return ($n instanceof Element) ? Node::WALK_ACCEPT : Node::WALK_REJECT;
});
$this->assertSame($d->documentElement, $w->current());
foreach ($w as $node);
// Accept element but ignore children, simple reject otherwise
$w = $d->walk(function($n) {
return ($n instanceof Element) ? Node::WALK_ACCEPT | Node::WALK_SKIP_CHILDREN : Node::WALK_REJECT;
});
$this->assertSame($d->documentElement, $w->current());
$this->assertNull($w->next());
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::walk
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_walk__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::SYNTAX_ERROR);
$d = new Document();
$d->appendChild($d->createElement('html'));
$w = $d->walk(function($n) {
return 2112;
});
$w->current();
}
public function testProperty_childElementCount(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$this->assertEquals(2, $d->body->childElementCount);
}
public function testProperty_children(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$this->assertEquals(2, $d->body->children->length);
}
public function testProperty_firstElementChild(): void {
$d = new Document('<!DOCTYPE html><html><body>ook<div id="ook">ook</div><div id="eek">eek</div></body></html>');
$body = $d->body;
$this->assertSame($d->getElementById('ook'), $body->firstElementChild);
$this->assertNull($d->getElementById('eek')->firstElementChild);
}
public function testProperty_lastElementChild(): void {
$d = new Document('<!DOCTYPE html><html><body>ook<div id="ook">ook</div><div id="eek">eek</div><div id="ack">ack</div></body></html>');
$body = $d->body;
$this->assertSame($d->getElementById('ack'), $body->lastElementChild);
$this->assertNull($d->getElementById('eek')->lastElementChild);
}
}

21
tests/cases/TestProcessingInstruction.php

@ -1,21 +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\TestCase;
use MensBeam\HTML\DOM\Document;
/** @covers \MensBeam\HTML\DOM\ProcessingInstruction */
class TestProcessingInstruction extends \PHPUnit\Framework\TestCase {
public function testProperty_target(): void {
$d = new Document();
$this->assertSame('ook', $d->createProcessingInstruction('ook', 'eek')->target);
$this->assertSame('poop💩', $d->createProcessingInstruction('poop💩', 'poop💩')->target);
}
}

263
tests/cases/TestSerializer.php

@ -1,263 +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\TestCase;
use MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\{
Document,
DocumentFragment,
Node
};
/** @covers \MensBeam\HTML\DOM\Serializer */
class TestSerializer extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Serializer::isPreformattedContent
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\Document::serialize
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\HTMLTemplateElement::__construct
* @covers \MensBeam\HTML\DOM\HTMLTemplateElement::__get_content
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Serializer::getTemplateContent
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\Inner\Reflection::setProtectedProperties
*/
public function testMethod_isPreformattedContent(): void {
$d = new Document('<pre><code></code></pre>');
$this->assertSame(<<<HTML
<html>
<head></head>
<body>
<pre><code></code></pre>
</body>
</html>
HTML, $d->serialize(null, [ 'reformatWhitespace' => true ]));
$frag = $d->createDocumentFragment();
$p = $frag->appendChild($d->createElement('pre'));
$t = $p->appendChild($d->createElement('template'));
$t->content->appendChild($d->createTextNode('ook'));
$t->content->appendChild($d->createElement('br'));
$this->assertSame('<pre><template>ook<br></template></pre>', $d->serialize($frag, [ 'reformatWhitespace' => true ]));
$div = $t->content->appendChild($d->createElement('div'));
$div->appendChild($d->createTextNode('ook'));
$this->assertSame('<pre><template>ook<br><div>ook</div></template></pre>', $d->serialize($frag, [ 'reformatWhitespace' => true ]));
}
/**
* @covers \MensBeam\HTML\DOM\Serializer::treatAsBlockWithTemplates
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\Document::serialize
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\HTMLTemplateElement::__construct
* @covers \MensBeam\HTML\DOM\HTMLTemplateElement::__get_content
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChildInner
* @covers \MensBeam\HTML\DOM\Node::cloneInnerNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Serializer::fragmentHasHost
* @covers \MensBeam\HTML\DOM\Serializer::getTemplateContent
* @covers \MensBeam\HTML\DOM\Serializer::isPreformattedContent
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\Inner\Reflection::setProtectedProperties
*/
public function testMethod_treatAsBlockWithTemplates(): void {
$d = new Document('<span>ook</span><template><div><p>ook</p></div></template>');
$this->assertSame(<<<HTML
<body>
<span>ook</span>
<template>
<div>
<p>ook</p>
</div>
</template>
</body>
HTML, $d->serialize($d->body, [ 'reformatWhitespace' => true ]));
}
public function provideMethod_treatForeignRootAsBlock(): iterable {
return [
[
function() {
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<span><template><svg><g><rect id="eek--a" width="5" height="5"/></g></svg><div>ook</div></template></span>
</body>
</html>
HTML, 'UTF-8');
return $d->serialize($d->getElementsByTagName('template')[0]->content->firstChild->firstChild, [ 'reformatWhitespace' => true ]);
},
<<<HTML
<g><rect id="eek--a" width="5" height="5"></rect></g>
HTML
],
[
function() {
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<svg role="img" viewBox="0 0 26 26"><title>Ook</title>
<rect id="eek--a" width="5" height="5"/></svg>
</body>
</html>
HTML, 'UTF-8');
return $d->serialize($d->body, [ 'reformatWhitespace' => true ]);
},
<<<HTML
<body><svg role="img" viewBox="0 0 26 26"><title>Ook</title> <rect id="eek--a" width="5" height="5"></rect></svg></body>
HTML
],
[
function() {
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<svg><g><g><rect id="eek--a" width="5" height="5"/></g></g></svg>
<div></div>
</body>
</html>
HTML, 'UTF-8');
$svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, 'svg')[0];
$g = $svg->firstChild->firstChild;
return $d->serialize($g, [ 'reformatWhitespace' => true ]);
},
<<<HTML
<g>
<rect id="eek--a" width="5" height="5"></rect>
</g>
HTML
],
];
}
/**
* @dataProvider provideMethod_treatForeignRootAsBlock
* @covers \MensBeam\HTML\DOM\Serializer::treatForeignRootAsBlock
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\Document::serialize
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagNameNS
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
* @covers \MensBeam\HTML\DOM\HTMLTemplateElement::__construct
* @covers \MensBeam\HTML\DOM\HTMLTemplateElement::__get_content
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_firstChild
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChildInner
* @covers \MensBeam\HTML\DOM\Node::cloneInnerNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Serializer::fragmentHasHost
* @covers \MensBeam\HTML\DOM\Serializer::isPreformattedContent
* @covers \MensBeam\HTML\DOM\Serializer::treatAsBlockWithTemplates
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\Inner\Reflection::setProtectedProperties
*/
public function testMethod_treatForeignRootAsBlock(\Closure $closure, string $expected): void {
$this->assertSame($expected, $closure());
}
}

42
tests/cases/TestText.php

@ -1,42 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException
};
/** @covers \MensBeam\HTML\DOM\Text */
class TestText extends \PHPUnit\Framework\TestCase {
public function testMethod_splitText(): void {
$d = new Document('<!DOCTYPE html><html><body>poop💩 ook eek</html>', 'UTF-8');
$body = $d->body;
$body->firstChild->splitText(5);
$this->assertSame('poop💩', $body->firstChild->data);
}
public function testMethod_splitText__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::INDEX_SIZE_ERROR);
$d = new Document('<!DOCTYPE html><html><body>poop💩 ook eek</html>', 'UTF-8');
$body = $d->body;
$body->firstChild->splitText(2112);
}
public function testProperty_wholeText(): void {
$d = new Document('<!DOCTYPE html><html><body>ook <strong>ack</strong> eek</body></html>');
$body = $d->body;
$body->removeChild($body->getElementsByTagName('strong')[0]);
$this->assertSame('ook eek', $body->firstChild->wholeText);
}
}

32
tests/cases/TestXMLDocument.php

@ -1,32 +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\TestCase;
use MensBeam\HTML\DOM\{
DOMException,
Element,
XMLDocument
};
/** @covers \MensBeam\HTML\DOM\XMLDocument */
class TestXMLDocument extends \PHPUnit\Framework\TestCase {
public function testMethod_load(): void {
$d = new XMLDocument('<ook><eek>Ook</eek></ook>');
$this->assertSame(Element::class, $d->firstChild::class);
}
public function testMethod_load__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::NO_MODIFICATION_ALLOWED);
$d = new XMLDocument('<ook><eek>Ook</eek></ook>');
$d->load('fail');
}
}

199
tests/cases/TestXPathEvaluate.php

@ -1,199 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Node,
XPathException,
XPathResult
};
/** @covers \MensBeam\HTML\DOM\XPathEvaluate */
class TestXPathEvaluate extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\XPathEvaluate::xpathEvaluate
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\Document::registerXPathFunctions
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::locateNamespace
* @covers \MensBeam\HTML\DOM\Node::lookupNamespaceURI
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\XPathEvaluate::xpathRegisterPhpFunctions
* @covers \MensBeam\HTML\DOM\XPathEvaluatorBase::createNSResolver
* @covers \MensBeam\HTML\DOM\XPathEvaluatorBase::evaluate
* @covers \MensBeam\HTML\DOM\XPathNSResolver::__construct
* @covers \MensBeam\HTML\DOM\XPathResult::__construct
* @covers \MensBeam\HTML\DOM\XPathResult::__get_booleanValue
* @covers \MensBeam\HTML\DOM\XPathResult::__get_numberValue
* @covers \MensBeam\HTML\DOM\XPathResult::__get_singleNodeValue
* @covers \MensBeam\HTML\DOM\XPathResult::__get_stringValue
* @covers \MensBeam\HTML\DOM\XPathResult::count
* @covers \MensBeam\HTML\DOM\XPathResult::validateStorage
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
function testMethod_xpathEvaluate(): void {
$d = new Document('<!DOCTYPE html><html><body><span><span>Ook</span></span><span></span></body></html>');
$d->registerXPathFunctions();
$result = $d->evaluate('.//span', $d->body);
$this->assertEquals(3, count($result));
$result = $d->evaluate('count(.//span)', $d->body, null, XPathResult::NUMBER_TYPE);
$this->assertEquals(3, $result->numberValue);
$result = $d->evaluate('count(.//span)', $d->body, null);
$this->assertEquals(3, $result->numberValue);
$result = $d->evaluate('name(.//span)', $d->body, null, XPathResult::STRING_TYPE);
$this->assertEquals('span', $result->stringValue);
$result = $d->evaluate('name(.//span)', $d->body, null);
$this->assertEquals('span', $result->stringValue);
$result = $d->evaluate('.//span', $d->body, null, XPathResult::BOOLEAN_TYPE);
$this->assertTrue($result->booleanValue);
$result = $d->evaluate('not(.//span)', $d->body, null, XPathResult::BOOLEAN_TYPE);
$this->assertFalse($result->booleanValue);
$result = $d->evaluate('not(.//span)', $d->body, null);
$this->assertFalse($result->booleanValue);
$result = $d->evaluate('.//span', $d->body, null, XPathResult::ORDERED_NODE_ITERATOR_TYPE);
$this->assertEquals(3, count($result));
$result = $d->evaluate('.//span', $d->body, null);
$this->assertEquals(3, count($result));
$result = $d->evaluate('.//span', $d->body, null, XPathResult::UNORDERED_NODE_SNAPSHOT_TYPE);
$this->assertEquals(3, count($result));
$result = $d->evaluate('.//span', $d->body, null, XPathResult::FIRST_ORDERED_NODE_TYPE);
$this->assertSame($d->body->firstChild, $result->singleNodeValue);
$d->documentElement->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:poop', 'https://poop.poop');
$poop = $d->body->appendChild($d->createElementNS('https://poop.poop', 'poop:poop'));
$this->assertSame($poop, $d->evaluate('//poop:poop', $d->body, $d->createNSResolver($d->body), XPathResult::FIRST_ORDERED_NODE_TYPE)->singleNodeValue);
$svg = $d->body->appendChild($d->createElementNS(Node::SVG_NAMESPACE, 'svg'));
$this->assertSame($svg, $d->evaluate('//svg:svg', $d->body, function(string $prefix): ?string {
return ($prefix === 'svg') ? Node::SVG_NAMESPACE : null;
}, XPathResult::FIRST_ORDERED_NODE_TYPE)->singleNodeValue);
}
function provideMethod_xpathEvaluate__errors(): iterable {
return [
[ function() {
$d = new Document();
$d->evaluate('fail?', $d);
},
XPathException::class,
XPathException::INVALID_EXPRESSION ],
[ function() {
$d = new Document();
$d->evaluate('//fail', $d, null, XPathResult::NUMBER_TYPE);
},
XPathException::class,
XPathException::TYPE_ERROR ],
[ function() {
$d = new Document();
$d->evaluate('//fail', $d, null, XPathResult::STRING_TYPE);
},
XPathException::class,
XPathException::TYPE_ERROR ],
[ function() {
$d = new Document();
$d->evaluate('count(//fail)', $d, null, XPathResult::UNORDERED_NODE_ITERATOR_TYPE);
},
XPathException::class,
XPathException::TYPE_ERROR ],
[ function() {
$d = new Document();
$d->evaluate('count(//fail)', $d, null, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE);
},
XPathException::class,
XPathException::TYPE_ERROR ],
[ function() {
$d = new Document();
$d->evaluate('count(//fail)', $d, null, XPathResult::ANY_UNORDERED_NODE_TYPE);
},
XPathException::class,
XPathException::TYPE_ERROR ],
[ function() {
$d = new Document();
$d->evaluate('//svg:svg', $d, null);
},
XPathException::class,
XPathException::UNRESOLVABLE_NAMESPACE_PREFIX ],
[ function() {
$d = new Document();
$d->evaluate('count(//fail)', $d, null, 2112);
},
DOMException::class,
DOMException::NOT_SUPPORTED ]
];
}
/**
* @dataProvider provideMethod_xpathEvaluate__errors
* @covers \MensBeam\HTML\DOM\XPathEvaluate::xpathEvaluate
*
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\XPathEvaluatorBase::evaluate
* @covers \MensBeam\HTML\DOM\XPathException::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
function testMethod_xpathEvaluate__errors(\Closure $closure, string $errorClass, int $errorCode): void {
$this->expectException($errorClass);
$this->expectExceptionCode($errorCode);
$closure();
}
}

24
tests/cases/TestXPathEvaluator.php

@ -1,24 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
XPathEvaluator
};
/** @covers \MensBeam\HTML\DOM\XPathEvaluator */
class TestXPathEvaluator extends \PHPUnit\Framework\TestCase {
function testMethod_registerXPathFunctions(): void {
$d = new Document();
$e = new XPathEvaluator();
$this->assertNull($e->registerXPathFunctions($d));
}
}

159
tests/cases/TestXPathExpression.php

@ -1,159 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
Node,
XPathException,
XPathExpression,
XPathResult
};
/** @covers \MensBeam\HTML\DOM\XPathExpression */
class TestXPathExpression extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\XPathExpression::__construct
*
* @covers \MensBeam\HTML\DOM\Attr::__get_localName
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_innerNode
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::locateNamespace
* @covers \MensBeam\HTML\DOM\Node::lookupNamespaceURI
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\XPathEvaluatorBase::createExpression
* @covers \MensBeam\HTML\DOM\XPathEvaluatorBase::createNSResolver
* @covers \MensBeam\HTML\DOM\XPathNSResolver::__construct
* @covers \MensBeam\HTML\DOM\XPathNSResolver::lookupNamespaceURI
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
function testMethod_constructor(): void {
$d = new Document('<!DOCTYPE><html></html>');
$d->documentElement->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:poop', 'https://poop.poop');
$poop = $d->body->appendChild($d->createElementNS('https://poop.poop', 'poop:poop'));
$this->assertSame(XPathExpression::class, $d->createExpression('//poop:poop', $d->createNSResolver($d))::class);
$this->assertSame(XPathExpression::class, $d->createExpression('//span')::class);
}
function provideMethod_constructor__errors(): iterable {
return [
[ function() {
$d = new Document();
$d->createExpression('//fail:fail');
},
XPathException::UNRESOLVABLE_NAMESPACE_PREFIX ],
[ function() {
$d = new Document();
$d->createExpression('//fail:fail', $d->createNSResolver($d));
},
XPathException::UNRESOLVABLE_NAMESPACE_PREFIX ],
[ function() {
$d = new Document();
$d->createExpression('fail?');
},
XPathException::INVALID_EXPRESSION ],
[ function() {
$d = new Document();
$d->createExpression('fail?', $d->createNSResolver($d));
},
XPathException::INVALID_EXPRESSION ],
];
}
/**
* @dataProvider provideMethod_constructor__errors
* @covers \MensBeam\HTML\DOM\XPathExpression::__construct
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\XPathEvaluate::xpathErrorHandler
* @covers \MensBeam\HTML\DOM\XPathEvaluatorBase::createExpression
* @covers \MensBeam\HTML\DOM\XPathException::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
function testMethod_constructor__errors(\Closure $closure, int $errorCode): void {
$this->expectException(XPathException::class);
$this->expectExceptionCode($errorCode);
$closure();
}
/**
* @covers \MensBeam\HTML\DOM\XPathExpression::evaluate
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\XPathEvaluate::xpathEvaluate
* @covers \MensBeam\HTML\DOM\XPathEvaluatorBase::createExpression
* @covers \MensBeam\HTML\DOM\XPathExpression::__construct
* @covers \MensBeam\HTML\DOM\XPathResult::__construct
* @covers \MensBeam\HTML\DOM\XPathResult::__get_booleanValue
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
function testMethod_evaluate(): void {
$d = new Document('<!DOCTYPE html><html><body><span><span>Ook</span></span><span></span></body></html>');
$e = $d->createExpression('//span');
$this->assertTrue($e->evaluate($d, XPathResult::BOOLEAN_TYPE)->booleanValue);
}
}

149
tests/cases/TestXPathResult.php

@ -1,149 +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\TestCase;
use MensBeam\HTML\DOM\{
Document,
HTMLElement,
Node,
XPathException,
XPathResult
};
/** @covers \MensBeam\HTML\DOM\XPathResult */
class TestXPathResult extends \PHPUnit\Framework\TestCase {
function testIteration(): void {
$d = new Document('<!DOCTYPE html><html><body><span><span>Ook</span></span><span></span></body></html>');
$result = $d->evaluate('.//span', $d);
foreach ($result as $k => $r) {
$this->assertSame(HTMLElement::class, $r::class);
$this->assertSame(HTMLElement::class, $result[$k]::class);
}
}
function testMethod_iterateNext(): void {
$d = new Document('<!DOCTYPE html><html><body><span></span></body></html>');
$result = $d->evaluate('.//span', $d);
$this->assertSame($d->getElementsByTagName('span')[0], $result->iterateNext());
$this->assertNull($result->iterateNext());
}
function testMethod_iterateNext__errors(): void {
$this->expectException(XPathException::class);
$this->expectExceptionCode(XPathException::TYPE_ERROR);
$d = new Document();
$d->evaluate('.//fail', $d, null, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE)->iterateNext();
}
function testMethod_snapshotItem(): void {
$d = new Document('<!DOCTYPE html><html><body><span></span></body></html>');
$this->assertSame($d->getElementsByTagName('span')[0], $d->evaluate('.//span', $d, null, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE)->snapshotItem(0));
$this->assertNull($d->evaluate('.//span', $d, null, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE)->snapshotItem(42));
}
function testMethod_snapshotItem__errors(): void {
$this->expectException(XPathException::class);
$this->expectExceptionCode(XPathException::TYPE_ERROR);
$d = new Document();
$d->evaluate('.//fail', $d)->snapshotItem(0);
}
function testMethod_validateStorage__errors(): void {
$this->expectException(XPathException::class);
$this->expectExceptionCode(XPathException::TYPE_ERROR);
$d = new Document();
$result = $d->evaluate('count(.//fail)', $d, null, XPathResult::NUMBER_TYPE);
$result[0];
}
function provideProperty__errors(): iterable {
return [
[ function() {
$d = new Document();
$result = $d->evaluate('name(//fail)', $d, null, XPathResult::NUMBER_TYPE);
$result->booleanValue;
} ],
[ function() {
$d = new Document();
$result = $d->evaluate('//fail', $d, null, XPathResult::BOOLEAN_TYPE);
$result->numberValue;
} ],
[ function() {
$d = new Document();
$result = $d->evaluate('//fail', $d, null, XPathResult::BOOLEAN_TYPE);
$result->singleNodeValue;
} ],
[ function() {
$d = new Document();
$result = $d->evaluate('//fail', $d, null, XPathResult::BOOLEAN_TYPE);
$result->stringValue;
} ]
];
}
/** @dataProvider provideProperty__errors */
function testProperty__errors(\Closure $closure): void {
$this->expectException(XPathException::class);
$this->expectExceptionCode(XPathException::TYPE_ERROR);
$closure();
}
function testProperty_invalidIteratorState(): void {
$d = new Document();
$this->assertFalse($d->evaluate('//span', $d)->invalidIteratorState);
}
function testProperty_offsetSet_offsetUnset(): void {
$d = new Document('<!DOCTYPE html><html><body><span><span>Ook</span></span><span></span></body></html>');
$result = $d->evaluate('.//span', $d);
$result[1] = 'ook';
$this->assertNotSame('ook', $result[1]);
unset($result[1]);
$this->assertSame(HTMLElement::class, $result[1]::class);
}
function testProperty_resultType(): void {
$d = new Document('<!DOCTYPE html><html><body><span><span>Ook</span></span><span></span></body></html>');
$this->assertEquals(XPathResult::ORDERED_NODE_ITERATOR_TYPE, $d->evaluate('.//span', $d->body, null, XPathResult::ORDERED_NODE_ITERATOR_TYPE)->resultType);
$this->assertEquals(XPathResult::ORDERED_NODE_ITERATOR_TYPE, $d->evaluate('.//span', $d->body)->resultType);
$this->assertEquals(XPathResult::NUMBER_TYPE, $d->evaluate('count(.//span)', $d->body, null, XPathResult::NUMBER_TYPE)->resultType);
$this->assertEquals(XPathResult::NUMBER_TYPE, $d->evaluate('count(.//span)', $d->body)->resultType);
$this->assertEquals(XPathResult::STRING_TYPE, $d->evaluate('name(.//span)', $d->body, null, XPathResult::STRING_TYPE)->resultType);
$this->assertEquals(XPathResult::STRING_TYPE, $d->evaluate('name(.//span)', $d->body)->resultType);
$this->assertEquals(XPathResult::BOOLEAN_TYPE, $d->evaluate('.//span', $d->body, null, XPathResult::BOOLEAN_TYPE)->resultType);
$this->assertEquals(XPathResult::BOOLEAN_TYPE, $d->evaluate('not(.//span)', $d->body, null, XPathResult::BOOLEAN_TYPE)->resultType);
$this->assertEquals(XPathResult::BOOLEAN_TYPE, $d->evaluate('not(.//span)', $d->body)->resultType);
$this->assertEquals(XPathResult::UNORDERED_NODE_SNAPSHOT_TYPE, $d->evaluate('.//span', $d->body, null, XPathResult::UNORDERED_NODE_SNAPSHOT_TYPE)->resultType);
$this->assertEquals(XPathResult::FIRST_ORDERED_NODE_TYPE, $d->evaluate('.//span', $d->body, null, XPathResult::FIRST_ORDERED_NODE_TYPE)->resultType);
}
function testProperty_snapshotLength(): void {
$d = new Document('<!DOCTYPE html><html><body><span><span>Ook</span></span><span></span></body></html>');
$this->assertEquals(3, $d->evaluate('.//span', $d, null, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE)->snapshotLength);
}
function testProperty_snapshotLength__errors(): void {
$this->expectException(XPathException::class);
$this->expectExceptionCode(XPathException::TYPE_ERROR);
$d = new Document();
$d->evaluate('.//fail', $d)->snapshotLength;
}
}

0
tests/test.html → tests/misc/test.html

49
tests/phpunit.dist.xml

@ -1,49 +0,0 @@
<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
colors="true"
bootstrap="bootstrap.php"
convertErrorsToExceptions="false"
convertNoticesToExceptions="false"
convertWarningsToExceptions="false"
beStrictAboutTestsThatDoNotTestAnything="true"
forceCoversAnnotation="true">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../lib</directory>
</include>
</coverage>
<testsuites>
<testsuite name="DOM">
<file>cases/TestAttr.php</file>
<file>cases/TestCharacterData.php</file>
<file>cases/TestChildNode.php</file>
<file>cases/TestCollection.php</file>
<file>cases/TestDocument.php</file>
<file>cases/TestDocumentOrElement.php</file>
<file>cases/TestDOMImplementation.php</file>
<file>cases/TestDOMTokenList.php</file>
<file>cases/TestElement.php</file>
<file>cases/TestException.php</file>
<file>cases/TestHTMLCollection.php</file>
<file>cases/TestHTMLElement.php</file>
<file>cases/TestHTMLOrSVGElement.php</file>
<file>cases/TestInnerDocument.php</file>
<file>cases/TestNamedNodeMap.php</file>
<file>cases/TestNode.php</file>
<file>cases/TestNonDocumentTypeChildNode.php</file>
<file>cases/TestParentNode.php</file>
<file>cases/TestProcessingInstruction.php</file>
<file>cases/TestText.php</file>
<file>cases/TestXMLDocument.php</file>
<file>cases/TestXPathEvaluate.php</file>
<file>cases/TestXPathEvaluator.php</file>
<file>cases/TestXPathExpression.php</file>
<file>cases/TestXPathResult.php</file>
</testsuite>
<testsuite name="Serializer">
<file>cases/TestSerializer.php</file>
</testsuite>
</testsuites>
</phpunit>

22
tests/phpunit.xml

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
bootstrap="bootstrap.php"
cacheDirectory=".phpunit.cache"
colors="true"
executionOrder="defects"
requireCoverageMetadata="true"
>
<testsuites>
<testsuite name="Main">
<directory prefix="Test" suffix=".php">./cases</directory>
</testsuite>
</testsuites>
<coverage/>
<source>
<include>
<directory suffix=".php">../lib</directory>
</include>
</source>
</phpunit>
Loading…
Cancel
Save