Browse Source

Remove DOM features and related

ns
J. King 3 years ago
parent
commit
3c8cc21064
  1. 4
      RoboFile.php
  2. 13
      composer.json
  3. 2183
      composer.lock
  4. 15
      docs/config.json
  5. 1
      docs/en/010_About.md
  6. 8
      docs/en/020_Installation.md
  7. 63
      docs/en/030_Document_Object_Model/010_Comment.md
  8. 28
      docs/en/030_Document_Object_Model/010_Document/010_construct.md
  9. 13
      docs/en/030_Document_Object_Model/010_Document/020_createEntityReference.md
  10. 46
      docs/en/030_Document_Object_Model/010_Document/020_load.md
  11. 46
      docs/en/030_Document_Object_Model/010_Document/020_loadHTML.md
  12. 9
      docs/en/030_Document_Object_Model/010_Document/020_loadHTMLFile.md
  13. 13
      docs/en/030_Document_Object_Model/010_Document/020_loadXML.md
  14. 43
      docs/en/030_Document_Object_Model/010_Document/020_save.md
  15. 9
      docs/en/030_Document_Object_Model/010_Document/020_saveHTMLFile.md
  16. 13
      docs/en/030_Document_Object_Model/010_Document/020_saveXML.md
  17. 13
      docs/en/030_Document_Object_Model/010_Document/020_validate.md
  18. 13
      docs/en/030_Document_Object_Model/010_Document/020_xinclude.md
  19. 147
      docs/en/030_Document_Object_Model/010_Document/index.md
  20. 24
      docs/en/030_Document_Object_Model/010_Element/010_getAttribute.md
  21. 26
      docs/en/030_Document_Object_Model/010_Element/010_getAttributeNS.md
  22. 100
      docs/en/030_Document_Object_Model/010_Element/index.md
  23. 55
      docs/en/030_Document_Object_Model/ContainerNode/010_appendChild.md
  24. 40
      docs/en/030_Document_Object_Model/ContainerNode/010_insertBefore.md
  25. 14
      docs/en/030_Document_Object_Model/ContainerNode/index.md
  26. 13
      docs/en/030_Document_Object_Model/LeafNode/010_appendChild.md
  27. 13
      docs/en/030_Document_Object_Model/LeafNode/010_insertBefore.md
  28. 13
      docs/en/030_Document_Object_Model/LeafNode/010_removeChild.md
  29. 13
      docs/en/030_Document_Object_Model/LeafNode/010_replaceChild.md
  30. 16
      docs/en/030_Document_Object_Model/LeafNode/index.md
  31. 43
      docs/en/030_Document_Object_Model/Moonwalk/010_moonwalk.md
  32. 11
      docs/en/030_Document_Object_Model/Moonwalk/index.md
  33. 13
      docs/en/030_Document_Object_Model/Node/010_C14N.md
  34. 13
      docs/en/030_Document_Object_Model/Node/010_C14NFile.md
  35. 12
      docs/en/030_Document_Object_Model/Node/index.md
  36. 45
      docs/en/030_Document_Object_Model/Walk/010_walk.md
  37. 11
      docs/en/030_Document_Object_Model/Walk/index.md
  38. 1
      docs/en/030_Document_Object_Model/index.md
  39. 1
      docs/index.md
  40. 9
      docs/theme/php/config.json
  41. 2
      docs/theme/php/daux.min.js
  42. 2
      docs/theme/php/php.css
  43. 324
      docs/theme/src/php.scss
  44. 12
      lib/DOM/AbstractDocument.php
  45. 11
      lib/DOM/Comment.php
  46. 65
      lib/DOM/DOMException.php
  47. 755
      lib/DOM/Document.php
  48. 11
      lib/DOM/DocumentFragment.php
  49. 313
      lib/DOM/Element.php
  50. 70
      lib/DOM/ElementMap.php
  51. 11
      lib/DOM/ProcessingInstruction.php
  52. 29
      lib/DOM/TemplateElement.php
  53. 317
      lib/DOM/TokenList.php
  54. 104
      lib/DOM/traits/ContainerNode.php
  55. 57
      lib/DOM/traits/DocumentOrElement.php
  56. 56
      lib/DOM/traits/EscapeString.php
  57. 31
      lib/DOM/traits/LeafNode.php
  58. 64
      lib/DOM/traits/MagicProperties.php
  59. 40
      lib/DOM/traits/Moonwalk.php
  60. 27
      lib/DOM/traits/MoonwalkShallow.php
  61. 21
      lib/DOM/traits/Node.php
  62. 243
      lib/DOM/traits/ParentNode.php
  63. 15
      lib/DOM/traits/ToString.php
  64. 31
      lib/DOM/traits/Walk.php
  65. 24
      lib/DOM/traits/WalkShallow.php
  66. 31
      lib/Parser.php
  67. 4
      lib/Parser/ActiveFormattingElementsList.php
  68. 2
      lib/Parser/CharacterReference.php
  69. 2
      lib/Parser/Charset.php
  70. 4
      lib/Parser/Data.php
  71. 2
      lib/Parser/Exception.php
  72. 2
      lib/Parser/LoopException.php
  73. 2
      lib/Parser/NameCoercion.php
  74. 2
      lib/Parser/NotImplementedException.php
  75. 4
      lib/Parser/OpenElementsStack.php
  76. 10
      lib/Parser/Output.php
  77. 2
      lib/Parser/ParseError.php
  78. 2
      lib/Parser/ParseErrorDummy.php
  79. 2
      lib/Parser/ParseErrorEmitter.php
  80. 2
      lib/Parser/Stack.php
  81. 2
      lib/Parser/TemplateInsertionModesStack.php
  82. 2
      lib/Parser/Token.php
  83. 3
      lib/Parser/Tokenizer.php
  84. 8
      lib/Parser/TreeBuilder.php
  85. 2
      lib/Parser/ctype.php
  86. 17
      package.json
  87. 17
      postcss.config.js
  88. 2
      tests/bootstrap.php
  89. 8
      tests/cases/TestCharset.php
  90. 318
      tests/cases/TestDOM.php
  91. 138
      tests/cases/TestSerializer.php
  92. 49
      tests/cases/TestTokenizer.php
  93. 38
      tests/cases/TestTreeConstructor.php
  94. 32
      tests/cases/serializer/menbeam01.dat
  95. 34
      tests/cases/serializer/menbeam02.dat
  96. 913
      tests/cases/serializer/wpt01.dat
  97. 6
      tests/phpunit.dist.xml
  98. 1158
      yarn.lock

4
RoboFile.php

@ -204,7 +204,7 @@ class RoboFile extends \Robo\Tasks {
$template = <<<'FILE'
<?php
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
// This file is machine-generated
// DO NOT MODIFY
@ -301,6 +301,6 @@ FILE;
$list = implode(",", $list);
$template = str_replace('%C1_SUBSTITUTIONS%', $list, $template);
// output the file itself
file_put_contents(BASE."lib/CharacterReference.php", $template);
file_put_contents(BASE."lib/Parser/CharacterReference.php", $template);
}
}

13
composer.json

@ -1,5 +1,5 @@
{
"name": "mensbeam/html",
"name": "mensbeam/html-parser",
"description": "Parses modern HTML text into a PHP DOMDocument",
"type": "library",
"require": {
@ -31,13 +31,11 @@
"autoload": {
"psr-4": {
"MensBeam\\HTML\\": [
"lib/",
"lib/DOM",
"lib/DOM/traits"
"lib/"
]
},
"classmap": ["lib/Token.php"],
"files": ["lib/ctype.php"]
"classmap": ["lib/Parser/Token.php"],
"files": ["lib/Parser/ctype.php"]
},
"autoload-dev": {
"psr-4": {
@ -47,7 +45,6 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.3",
"masterminds/html5": "^2.7",
"daux/daux.io": "^0.16.0"
"masterminds/html5": "^2.7"
}
}

2183
composer.lock

File diff suppressed because it is too large

15
docs/config.json

@ -1,15 +0,0 @@
{
"title": "HTML",
"tagline": "Tools for parsing and printing HTML5 documents and fragments.",
"author": "Dustin Wilson",
"languages": {
"en": "English"
},
"themes_directory": "docs/theme",
"html": {
"theme":"php",
"float": false,
"toggle_code": false,
"search": false
}
}

1
docs/en/010_About.md

@ -1 +0,0 @@
HTML is a library which provides tools for parsing and printing of HTML5 documents and fragments. Unlike PHP's DOM and other similar libraries the goal of the project is to parse HTML as accurate to the specification as possible given the limitations of PHP's DOM and of the uses of the library. Therefore, there is no scripting in this implementation, and there likely never will be.

8
docs/en/020_Installation.md

@ -1,8 +0,0 @@
We try to make the installation of the MensBeam HTML library as easy and straightforward as possible.
## Requirements ##
HTML intentionally has few requirements. It only requires PHP 7.1.0 or later with the [dom](http://php.net/manual/en/book.dom.php) extension installed. It is recommended to install the [ctype](https://www.php.net/manual/en/book.ctype.php) extension for performance improvements, but it is not required.
TODO: Add Installation instructions once there are releases and a package is available on Packagist.

63
docs/en/030_Document_Object_Model/010_Comment.md

@ -1,63 +0,0 @@
---
title: Comment
---
# The Comment Class #
## Introduction ##
<div class="admonition info"><p><strong>Info</strong> Only new methods and methods which make outward-facing changes from <a href="https://www.php.net/manual/en/class.domcomment.php">\DOMComment</a> will be documented here, otherwise they will be linked back to PHP's documentation.</p></div>
## Class Synopsis ##
<pre><code class="php">MensBeam\HTML\Comment extends <a href="https://www.php.net/manual/en/class.domcomment.php">\DOMComment</a> {
use <a href="../LeafNode/index.html">LeafNode</a>, <a href="../Moonwalk/index.html">Moonwalk</a>;
/* Inherited properties */
public string <a href="https://www.php.net/manual/en/class.domcharacterdata.php#domcharacterdata.props.data">$data</a> ;
public readonly int <a href="https://www.php.net/manual/en/class.domcharacterdata.php#domcharacterdata.props.length">$length</a> ;
public readonly string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodename">$nodeName</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodevalue">$nodeValue</a> ;
public readonly int <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodetype">$nodeType</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.parentnode">$parentNode</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnodelist.php">\DOMNodeList</a> <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.childnodes">$childNodes</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.firstchild">$firstChild</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.lastchild">$lastChild</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.previoussibling">$previousSibling</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nextsibling">$nextSibling</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnamednodemap.php">\DOMNamedNodeMap</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.attributes">$attributes</a> ;
public readonly Document|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.ownerdocument">$ownerDocument</a> ;
public readonly string|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.namespaceuri">$namespaceURI</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.prefix">$prefix</a> ;
public readonly string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.localname">$localName</a> ;
public readonly string|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.baseuri">$baseURI</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.textcontent">$textContent</a> ;
/* Trait Methods */
public <a href="../LeafNode/appendChild.html">LeafNode::appendChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node ) : DOMException;
public <a href="../Node/C14N.html">Node::C14N</a> ( bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
public <a href="../Node/C14NFile.html">Node::C14NFile</a> ( string $uri , bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
public <a href="../LeafNode/insertBefore.html">LeafNode::insertBefore</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null $child = null ) : DOMException
public <a href="../Moonwalk/moonwalk.html">Moonwalk::moonwalk</a> ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a>|null $filter = null ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
public <a href="../LeafNode/removeChild.html">LeafNode::removeChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : DOMException
public <a href="../LeafNode/replaceChild.html">LeafNode::replaceChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : DOMException
/* Magic Methods */
public __toString() : string
/* Inherited Methods */
public <a href="https://www.php.net/manual/en/domcomment.construct.php">__construct</a> ( string $data = "" )
public <a href="https://www.php.net/manual/en/domnode.clonenode.php">\DOMNode::cloneNode</a> ( bool $deep = false ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domnode.getlineno.php">\DOMNode::getLineNo</a> ( ) : int
public <a href="https://www.php.net/manual/en/domnode.getnodepath.php">\DOMNode::getNodePath</a> ( ) : string|null
public <a href="https://www.php.net/manual/en/domnode.hasattributes.php">\DOMNode::hasAttributes</a> ( ) : bool
public <a href="https://www.php.net/manual/en/domnode.haschildnodes.php">\DOMNode::hasChildNodes</a> ( ) : bool
public <a href="https://www.php.net/manual/en/domnode.isdefaultnamespace.php">\DOMNode::isDefaultNamespace</a> ( string $namespace ) : bool
public <a href="https://www.php.net/manual/en/domnode.issamenode.php">\DOMNode::isSameNode</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $otherNode ) : bool
public <a href="https://www.php.net/manual/en/domnode.issupported.php">\DOMNode::isSupported</a> ( string $feature , string $version ) : bool
public <a href="https://www.php.net/manual/en/domnode.lookupnamespaceuri.php">\DOMNode::lookupNamespaceUri</a> ( string $prefix ) : string
public <a href="https://www.php.net/manual/en/domnode.lookupprefix.php">\DOMNode::lookupPrefix</a> ( string $namespace ) : string|null
public <a href="https://www.php.net/manual/en/domnode.normalize.php">\DOMNode::normalize</a> ( ) : void
}</code></pre>

28
docs/en/030_Document_Object_Model/010_Document/010_construct.md

@ -1,28 +0,0 @@
---
title: Document::__construct
---
Document::__construct — Creates a new Document object
## Description ##
```php
public Document::__construct ( )
```
Creates a new Document object.
## Examples ##
**Example \#1 Creating a new Document**
```php
<?php
namespace MensBeam\HTML;
$dom = new Document();
echo $dom;
?>
```

13
docs/en/030_Document_Object_Model/010_Document/020_createEntityReference.md

@ -1,13 +0,0 @@
---
title: Document::createEntityReference
---
Document::createEntityReference — **DISABLED**
## Description ##
```php
public Document::createEntityReference ( string $name ) : false
```
This function has been disabled and will always return `false`. Documented to show difference from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php). DOM4 does not have entity references or entity nodes.

46
docs/en/030_Document_Object_Model/010_Document/020_load.md

@ -1,46 +0,0 @@
---
title: Document::load
---
Document::load — Load HTML from a file
## Description ##
```php
public Document::load ( string $filename , null $options = null , string|null $encodingOrContentType = null ) : bool
```
Loads an HTML document from a file.
## Parameters ##
<dl>
<dt><code>filename</code></dt>
<dd>The path to the HTML document.</dd>
<dt><code>options</code></dt>
<dd>Always <code>null</code>. Was used for option constants in <a href="https://www.php.net/manual/en/class.domdocument.php"><code>\DOMDocument</code></a>.</dd>
<dt><code>encodingOrContentType</code></dt>
<dd>The encoding of the document that is being loaded. If not specified it will be determined automatically.</dd>
</dl>
## Return Values ##
Returns <code>true</code> on success or <code>false</code> on failure.
## Examples ##
**Example \#1 Creating a Document**
```php
<?php
namespace MensBeam\HTML;
$dom = new Document();
$dom->load('ook.html');
echo $dom;
?>
```

46
docs/en/030_Document_Object_Model/010_Document/020_loadHTML.md

@ -1,46 +0,0 @@
---
title: Document::loadHTML
---
Document::loadHTML — Load HTML from a string
## Description ##
```php
public Document::loadHTML ( string $source , null $options = null , string|null $encodingOrContentType = null ) : bool
```
The function parses the HTML contained in the string <var>source</var>.
## Parameters ##
<dl>
<dt><code>source</code></dt>
<dd>The HTML string.</dd>
<dt><code>options</code></dt>
<dd>Always <code>null</code>. Was used for option constants in <a href="https://www.php.net/manual/en/class.domdocument.php"><code>\DOMDocument</code></a>.</dd>
<dt><code>encodingOrContentType</code></dt>
<dd>The encoding of the document that is being loaded. If not specified it will be determined automatically.</dd>
</dl>
## Return Values ##
Returns <code>true</code> on success or <code>false</code> on failure.
## Examples ##
**Example \#1 Creating a Document**
```php
<?php
namespace MensBeam\HTML;
$dom = new Document();
$dom->loadHTML('<!DOCTYPE html><html><head><title>Ook!</title></head><body><h1>Eek</h1></body></html>');
echo $dom;
?>
```

9
docs/en/030_Document_Object_Model/010_Document/020_loadHTMLFile.md

@ -1,9 +0,0 @@
---
title: Document::loadHTMLFile
---
Document::loadHTMLFile — Alias of <a href="Document_load.html"><code>Document::load()</code></a>
## Description ##
This function is an alias of <a href="Document_load.html"><code>Document::load()</code></a>.

13
docs/en/030_Document_Object_Model/010_Document/020_loadXML.md

@ -1,13 +0,0 @@
---
title: Document::loadXML
---
Document::loadXML — **DISABLED**
## Description ##
```php
public Document::loadXML ( string $source , null $options = null ) : false
```
This function has been disabled and will always return `false`. Documented to show difference from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php).

43
docs/en/030_Document_Object_Model/010_Document/020_save.md

@ -1,43 +0,0 @@
---
title: Document::save
---
Document::save — Serializes the DOM tree into a file
## Description ##
```php
public Document::save ( string $filename , null $options = null ) : int|false
```
Creates an HTML document from the DOM representation.
## Parameters ##
<dl>
<dt><code>filename</code></dt>
<dd>The path to the saved HTML document</dd>
<dt><code>options</code></dt>
<dd>Always <code>null</code>. Was used for option constants in <a href="https://www.php.net/manual/en/class.domdocument.php"><code>\DOMDocument</code></a>.</dd>
</dl>
## Return Values ##
Returns the number of bytes written or <code>false</code> on failure.
## Examples ##
**Example \#1 Saving a DOM tree into a file**
```php
<?php
namespace MensBeam\HTML;
$dom = new Document();
$dom->loadHTML('<!DOCTYPE html><html><head><title>Ook!</title></head><body><h1>Eek</h1></body></html>');
echo 'Wrote: ' . $dom->save('/tmp/test.html') . ' bytes'; // Wrote: 85 bytes
?>
```

9
docs/en/030_Document_Object_Model/010_Document/020_saveHTMLFile.md

@ -1,9 +0,0 @@
---
title: Document::saveHTMLFile
---
Document::saveHTMLFile — Alias of <a href="Document_save.html"><code>Document::save()</code></a>
## Description ##
This function is an alias of <a href="Document_save.html"><code>Document::save()</code></a>.

13
docs/en/030_Document_Object_Model/010_Document/020_saveXML.md

@ -1,13 +0,0 @@
---
title: Document::saveXML
---
Document::saveXML — **DISABLED**
## Description ##
```php
public Document::saveXML ( DOMNode|null $node = null , null $options = null ) : false
```
This function has been disabled and will always return `false`. Documented to show difference from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php).

13
docs/en/030_Document_Object_Model/010_Document/020_validate.md

@ -1,13 +0,0 @@
---
title: Document::validate
---
Document::validate — **DISABLED**
## Description ##
```php
public Document::validate ( ) : true
```
This function has been disabled and will always return `true`. Documented to show difference from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php).

13
docs/en/030_Document_Object_Model/010_Document/020_xinclude.md

@ -1,13 +0,0 @@
---
title: Document::xinclude
---
Document::xinclude — **DISABLED**
## Description ##
```php
public Document::xinclude ( null $options = null ) : false
```
This function has been disabled and will always return `false`. Documented to show difference from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php).

147
docs/en/030_Document_Object_Model/010_Document/index.md

@ -1,147 +0,0 @@
---
title: Document
---
# The Document Class #
## Introduction ##
Represents an entire HTML document; serves as the root of the document tree. Unlike the PHP [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php) class in which it inherits from it cannot be used to represent an XML document. It is strictly used to represent HTML.
<div class="admonition"><p><strong>Note:</strong> Only new methods and methods which make outward-facing changes from <a href="https://www.php.net/manual/en/class.domdocument.php">\DOMDocument</a> will be documented here, otherwise they will be linked back to PHP's documentation.</p></div>
## Class Synopsis ##
<pre><code class="php">MensBeam\HTML\Document extends <a href="https://www.php.net/manual/en/class.domdocument.php">\DOMDocument</a> {
use <a href="../ContainerNode/index.html">ContainerNode</a>, <a href="../Walk/index.html">Walk</a>;
/* Constants */
public const NO_QUIRKS_MODE = 0 ;
public const QUIRKS_MODE = 1 ;
public const LIMITED_QUIRKS_MODE = 2 ;
/* Properties */
public <a href="../Element/index.html">Element</a>|null <a href="#document-props-body">$body</a> = null ;
public string|null <a href="#document-props-documentencoding">$documentEncoding</a> = null ;
public int <a href="#document-props-quirksmode">$quirksMode</a> = 0 ;
/* Inherited properties */
public readonly <a href="https://www.php.net/manual/en/class.domnamednodemap.php">\DOMNamedNodeMap</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.attributes">$attributes</a> ;
public readonly string|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.baseuri">$baseURI</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnodelist.php">\DOMNodeList</a> <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.childnodes">$childNodes</a> ;
public readonly DocumentType <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.doctype">$doctype</a> ;
public readonly Element <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.documentelement">$documentElement</a> ;
public string|null <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.documenturi">$documentURI</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.firstchild">$firstChild</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domimplementation.php">\DOMImplementation</a> <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.implementation">$implementation</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.lastchild">$lastChild</a> ;
public readonly string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.localname">$localName</a> ;
public readonly string|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.namespaceuri">$namespaceURI</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nextsibling">$nextSibling</a> ;
public readonly string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodename">$nodeName</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodevalue">$nodeValue</a> ;
public readonly int <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodetype">$nodeType</a> ;
public readonly Document|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.ownerdocument">$ownerDocument</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.parentnode">$parentNode</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.prefix">$prefix</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.previoussibling">$previousSibling</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.textcontent">$textContent</a> ;
/* Methods */
public <a href="construct.html">__construct</a> ( )
public <a href="createEntityReference.html">createEntityReference</a> ( string $name ) : false
public <a href="load.html">load</a> ( string $filename , null $options = null , string|null $encodingOrContentType = null ) : bool
public <a href="loadHTML.html">loadHTML</a> ( string $source , null $options = null , string|null $encodingOrContentType = null ) : bool
public <a href="loadHTMLFile.html">loadHTMLFile</a> ( string $filename , null $options = null , string|null $encodingOrContentType = null ) : bool
public <a href="loadHTML.html">loadXML</a> ( string $source , null $options = null ) : false
public <a href="save.html">save</a> ( string $filename , null $options = null ) : int|false
public <a href="saveHTMLFile.html">saveHTMLFile</a> ( string $filename , null $options = null ) : int|false
public <a href="saveXML.html">saveXML</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null $node = null , null $options = null ) : false
public <a href="validate.html">validate</a> ( ) : true
public <a href="xinclude.html">xinclude</a> ( null $options = null ) : false
/* Trait Methods */
public <a href="../ContainerNode/appendChild.html">ContainerNode::appendChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="../Node/C14N.html">Node::C14N</a> ( bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
public <a href="../Node/C14NFile.html">Node::C14NFile</a> ( string $uri , bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
public <a href="../ContainerNode/insertBefore.html">ContainerNode::insertBefore</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null $child = null ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="../Walk/walk.html">Walk::walk</a> ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a>|null $filter = null ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
/* Magic Methods */
public __toString() : string
/* Inherited methods */
public <a href="https://www.php.net/manual/en/domnode.clonenode.php">\DOMNode::cloneNode</a> ( bool $deep = false ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domdocument.createattribute.php">\DOMDocument::createAttribute</a> ( string $localName ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|false
public <a href="https://www.php.net/manual/en/domdocument.createattributens.php">\DOMDocument::createAttributeNS</a> ( string|null $namespace , string $qualifiedName ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|false
public <a href="https://www.php.net/manual/en/domdocument.createcdatasection.php">\DOMDocument::createCDATASection</a> ( string $data ) : <a href="https://www.php.net/manual/en/class.domcdatasection.php">\DOMCdataSection</a>|false
public <a href="https://www.php.net/manual/en/domdocument.createcomment.php">\DOMDocument::createComment</a> ( string $data ) : Comment|false
public <a href="https://www.php.net/manual/en/domdocument.createdocumentfragment.php">\DOMDocument::createDocumentFragment</a> ( ) : DocumentFragment|false
public <a href="https://www.php.net/manual/en/domdocument.createelement.php">\DOMDocument::createElement</a> ( string $localName , string $value = "" ) : Element|false
public <a href="https://www.php.net/manual/en/domdocument.createelementns.php">\DOMDocument::createElementNS</a> ( string|null $namespace , string $qualifiedName , string $value = "" ) : Element|false
public <a href="https://www.php.net/manual/en/domdocument.createprocessinginstruction.php">\DOMDocument::createProcessingInstruction</a> ( string $target , string $data = "" ) : ProcessingInstruction|false
public <a href="https://www.php.net/manual/en/domdocument.createtextnode.php"\DOMDocument::>\DOMDocument::createTextNode</a> ( string $data ) : Text|false
public <a href="https://www.php.net/manual/en/domdocument.getelementbyid.php">\DOMDocument::getElementById</a> ( string $elementId ) : Element|null
public <a href="https://www.php.net/manual/en/domdocument.getelementsbytagname.php">\DOMDocument:getElementsByTagName</a> ( string $qualifiedName ) : <a href="https://www.php.net/manual/en/class.domnodelist.php">\DOMNodeList</a>
public <a href="https://www.php.net/manual/en/domdocument.createelementsbytagnamens.php">getElementsByTagNameNS</a> ( string $namespace , string $localName ) : <a href="https://www.php.net/manual/en/class.domnodelist.php">\DOMNodeList</a>
public <a href="https://www.php.net/manual/en/domnode.getlineno.php">\DOMNode::getLineNo</a> ( ) : int
public <a href="https://www.php.net/manual/en/domnode.getnodepath.php">\DOMNode::getNodePath</a> ( ) : string|null
public <a href="https://www.php.net/manual/en/domnode.hasattributes.php">\DOMNode::hasAttributes</a> ( ) : bool
public <a href="https://www.php.net/manual/en/domnode.haschildnodes.php">\DOMNode::hasChildNodes</a> ( ) : bool
public <a href="https://www.php.net/manual/en/domdocument.importnode.php">\DOMDocument::importNode</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , bool $deep = false ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domnode.isdefaultnamespace.php">\DOMNode::isDefaultNamespace</a> ( string $namespace ) : bool
public <a href="https://www.php.net/manual/en/domnode.issamenode.php">\DOMNode::isSameNode</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $otherNode ) : bool
public <a href="https://www.php.net/manual/en/domnode.issupported.php">\DOMNode::isSupported</a> ( string $feature , string $version ) : bool
public <a href="https://www.php.net/manual/en/domnode.lookupnamespaceuri.php">\DOMNode::lookupNamespaceUri</a> ( string $prefix ) : string
public <a href="https://www.php.net/manual/en/domnode.lookupprefix.php">\DOMNode::lookupPrefix</a> ( string $namespace ) : string|null
public <a href="https://www.php.net/manual/en/domnode.normalize.php">\DOMNode::normalize</a> ( ) : void
public <a href="https://www.php.net/manual/en/domdocument.normalizedocument.php">\DOMDocument::normalizeDocument</a> ( ) : void
public <a href="https://www.php.net/manual/en/domdocument.registernodeclass.php">\DOMDocument::registerNodeClass</a> ( string $baseClass , string|null $extendedClass ) : bool
public <a href="https://www.php.net/manual/en/domdocument.relaxngvalidate.php">\DOMDocument::relaxNGValidate</a> ( string $filename ) : bool
public <a href="https://www.php.net/manual/en/domdocument.relaxngvalidatesource.php">\DOMDocument::relaxNGValidateSource</a> ( string $source ) : bool
public <a href="https://www.php.net/manual/en/domnode.removechild.php">\DOMNode::removeChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domnode.replacechild.php">\DOMNode::replaceChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domdocument.savehtml.php">\DOMDocument::saveHTML</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null $node = null ) : string|false
public <a href="https://www.php.net/manual/en/domdocument.schemavalidate.php">\DOMDocument::schemaValidate</a> ( string $filename , int $flags = 0 ) : bool
public <a href="https://www.php.net/manual/en/domdocument.schemavalidatesource.php">\DOMDocument::schemaValidateSource</a> ( string $source , int $flags = 0 ) : bool
}</code></pre>
## Constants ##
| Constant | Value | Description |
| ----------------------------------------------------- | ----- | ------------------------------------- |
| <var>MensBeam\HTML\Document::NO_QUIRKS_MODE</var> | 0 | Document not in quirks mode |
| <var>MensBeam\HTML\Document::QUIRKS_MODE</var> | 1 | Document is in quirks mode |
| <var>MensBeam\HTML\Document::LIMITEDQUIRKS_MODE</var> | 2 | Document is in limited quirks mode |
## Properties ##
<dl>
<dt id="document-props-body"><var>body</var></dt>
<dd>Represents the <code>body</code> or <code>frameset</code> node of the current document, or <code>null</code> if no such element exists.</dd>
<dt id="document-props-documentencoding"><var>documentEncoding</var></dt>
<dd>Encoding of the document, as specified when parsing or when determining encoding type. Use this instead of <a href="https://php.net/manual/en/class.domdocument.php#domdocument.props.encoding"><code>\DOMDocument::encoding</code></a>.</dd>
<dt id="document-props-quirksmode"><var>quirksMode</var></dt>
<dd>Used when parsing. Specifies which mode the document was parsed in. One of the <a href="#page_Constants">predefined quirks mode constants</a>.</dd>
</dl>
The following properties inherited from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php) have no effect in `Mensbeam\HTML\Document`, so therefore are not listed in the schema above:
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.actualencoding"><var>actualEncoding</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.config"><var>config</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.encoding"><var>encoding</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.formatoutput"><var>formatOutput</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.preservewhitespace"><var>preserveWhiteSpace</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.recover"><var>recover</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.resolveexternals"><var>resolveExternals</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.standalone"><var>standalone</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.stricterrorchecking"><var>strictErrorChecking</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.substituteentities"><var>substituteEntities</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.validateonparse"><var>validateOnParse</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.version"><var>version</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.xmlencoding"><var>xmlEncoding</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.xmlstandalone"><var>xmlStandalone</var></a>
* <a href="https://www.php.net/manual/en/class.domdocument.php#domdocument.props.xmlversion"><var>xmlVersion</var></a>

24
docs/en/030_Document_Object_Model/010_Element/010_getAttribute.md

@ -1,24 +0,0 @@
---
title: Element::getAttribute
---
Element::getAttribute — Returns value of attribute
## Description ##
```php
public Element::getAttribute ( string $qualifiedName ) : string|null
```
Gets the value of the attribute with name `qualifiedName` for the current node.
## Parameters ##
<dl>
<dt><code>qualifiedName</code></dt>
<dd>The name of the attribute.</dd>
</dl>
## Return Values ##
Returns a string on success or <code>null</code> if no attribute with the given `qualifiedName` is found. `\DOMElement::getAttribute` returns an empty string on failure which is incorrect in newer versions of the DOM.

26
docs/en/030_Document_Object_Model/010_Element/010_getAttributeNS.md

@ -1,26 +0,0 @@
---
title: Element::getAttributeNS
---
Element::getAttributeNS — Returns value of attribute
## Description ##
```php
public Element::getAttribute ( string|null $namespace , string $localName ) : string|null
```
Gets the value of the attribute in namespace `namespace` with local name `localName` for the current node.
## Parameters ##
<dl>
<dt><code>namespace</code></dt>
<dd>The namespace URI.</dd>
<dt><code>localName</code></dt>
<dd>The local name of the attribute.</dd>
</dl>
## Return Values ##
Returns a string on success or <code>null</code> if no attribute with the given `localName` and `namespace` is found. `\DOMElement::getAttribute` returns an empty string on failure which is incorrect in newer versions of the DOM.

100
docs/en/030_Document_Object_Model/010_Element/index.md

@ -1,100 +0,0 @@
---
title: Element
---
# The Element Class #
## Introduction ##
<div class="admonition"><p><strong>Note:</strong> Only new methods and methods which make outward-facing changes from <a href="https://www.php.net/manual/en/class.domelement.php">\DOMElement</a> will be documented here, otherwise they will be linked back to PHP's documentation.</p></div>
## Class Synopsis ##
<pre><code class="php">MensBeam\HTML\Element extends <a href="https://www.php.net/manual/en/class.domelement.php">\DOMElement</a> {
use <a href="../ContainerNode/index.html">ContainerNode</a>, <a href="../Moonwalk/index.html">Moonwalk</a>, <a href="../Walk/index.html">Walk</a>;
/* Properties */
public readonly NodeList|null <a href="#element-props-classlist">$classList</a> ;
public string <a href="#element-props-innerhtml">$innerHTML</a> ;
public string <a href="#element-props-outerhtml">$outerHTML</a> ;
/* Inherited properties */
public readonly string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodename">$nodeName</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodevalue">$nodeValue</a> ;
public readonly int <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nodetype">$nodeType</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.parentnode">$parentNode</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnodelist.php">\DOMNodeList</a> <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.childnodes">$childNodes</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.firstchild">$firstChild</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.lastchild">$lastChild</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.previoussibling">$previousSibling</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.nextsibling">$nextSibling</a> ;
public readonly <a href="https://www.php.net/manual/en/class.domnamednodemap.php">\DOMNamedNodeMap</a>|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.attributes">$attributes</a> ;
public readonly Document|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.ownerdocument">$ownerDocument</a> ;
public readonly string|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.namespaceuri">$namespaceURI</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.prefix">$prefix</a> ;
public readonly string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.localname">$localName</a> ;
public readonly string|null <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.baseuri">$baseURI</a> ;
public string <a href="https://www.php.net/manual/en/class.domnode.php#domnode.props.textcontent">$textContent</a> ;
/* Methods */
public <a href="getAttribute.html">getAttribute</a> ( string $qualifiedName ) : string|null
public <a href="getAttributeNS.html">getAttributeNS</a> ( string|null $namespace , string $localName ) : string|null
/* Trait Methods */
public <a href="../ContainerNode/appendChild.html">ContainerNode::appendChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="../Node/C14N.html">Node::C14N</a> ( bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
public <a href="../Node/C14NFile.html">Node::C14NFile</a> ( string $uri , bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
public <a href="../ContainerNode/insertBefore.html">ContainerNode::insertBefore</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null $child = null ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="../Moonwalk/moonwalk.html">Moonwalk::moonwalk</a> ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a>|null $filter = null ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
public <a href="../Walk/walk.html">Walk::walk</a> ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a>|null $filter = null ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
/* Magic Methods */
public __toString() : string
/* Inherited Methods */
public <a href="https://www.php.net/manual/en/domelement.construct.php">__construct</a> ( string $qualifiedName , string|null $value = null , string $namespace = "" )
public <a href="https://www.php.net/manual/en/domnode.clonenode.php">\DOMNode::cloneNode</a> ( bool $deep = false ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domelement.getattributenode.php">\DOMElement::getAttributeNode</a> ( string $qualifiedName ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|false
public <a href="https://www.php.net/manual/en/domelement.getattributenodens.php">\DOMElement::getAttributeNodeNS</a> ( string|null $namespace , string $localName ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|null
public <a href="https://www.php.net/manual/en/domelement.getelementsbytagname.php">\DOMElement::getElementsByTagName</a> ( string $qualifiedName ) : <a href="https://www.php.net/manual/en/class.domnodelist.php">\DOMNodeList</a>
public <a href="https://www.php.net/manual/en/domelement.getelementsbytagnamens.php">\DOMElement::getElementsByTagNameNS</a> ( string $namespace , string $localName ) : <a href="https://www.php.net/manual/en/class.domnodelist.php">\DOMNodeList</a>
public <a href="https://www.php.net/manual/en/domnode.getlineno.php">\DOMNode::getLineNo</a> ( ) : int
public <a href="https://www.php.net/manual/en/domnode.getnodepath.php">\DOMNode::getNodePath</a> ( ) : string|null
public <a href="https://www.php.net/manual/en/domelement.hasattribute.php">\DOMElement::hasAttribute</a> ( string $qualifiedName ) : bool
public <a href="https://www.php.net/manual/en/domelement.hasattributens.php">\DOMElement::hasAttributeNS</a> ( string|null $namespace , string $localName ) : bool
public <a href="https://www.php.net/manual/en/domnode.hasattributes.php">\DOMNode::hasAttributes</a> ( ) : bool
public <a href="https://www.php.net/manual/en/domnode.haschildnodes.php">\DOMNode::hasChildNodes</a> ( ) : bool
public <a href="https://www.php.net/manual/en/domnode.isdefaultnamespace.php">\DOMNode::isDefaultNamespace</a> ( string $namespace ) : bool
public <a href="https://www.php.net/manual/en/domnode.issamenode.php">\DOMNode::isSameNode</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $otherNode ) : bool
public <a href="https://www.php.net/manual/en/domnode.issupported.php">\DOMNode::isSupported</a> ( string $feature , string $version ) : bool
public <a href="https://www.php.net/manual/en/domnode.lookupnamespaceuri.php">\DOMNode::lookupNamespaceUri</a> ( string $prefix ) : string
public <a href="https://www.php.net/manual/en/domnode.lookupprefix.php">\DOMNode::lookupPrefix</a> ( string $namespace ) : string|null
public <a href="https://www.php.net/manual/en/domnode.normalize.php">\DOMNode::normalize</a> ( ) : void
public <a href="https://www.php.net/manual/en/domelement.removeattribute.php">\DOMElement::removeAttribute</a> ( string $qualifiedName ) : bool
public <a href="https://www.php.net/manual/en/domelement.removeattributenode.php">\DOMElement::removeAttributeNode</a> ( <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a> $attr ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|false
public <a href="https://www.php.net/manual/en/domelement.removeattributenodens.php">\DOMElement::removeAttributeNS</a> ( string|null $namespace , string $localName ) : void
public <a href="https://www.php.net/manual/en/domelement.setattribute.php">\DOMElement::setAttribute</a> ( string $qualifiedName , string $value ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|bool
public <a href="https://www.php.net/manual/en/domnode.removechild.php">\DOMNode::removeChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domnode.replacechild.php">\DOMNode::replaceChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="https://www.php.net/manual/en/domelement.setattributenode.php">\DOMElement::setAttributeNode</a> ( <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a> $attr ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|null|false
public <a href="https://www.php.net/manual/en/domelement.setattributenodens.php">\DOMElement::setAttributeNodeNS</a> ( <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a> $attr ) : <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a>|null|false
public <a href="https://www.php.net/manual/en/domelement.setattributens.php">\DOMElement::setAttributeNS</a> ( string|null $namespace , string $qualifiedName , string $value ) : void
public <a href="https://www.php.net/manual/en/domelement.setidattribute.php">\DOMElement::setIdAttribute</a> ( string $qualifiedName , bool $isId ) : void
public <a href="https://www.php.net/manual/en/domelement.setidattributenode.php">\DOMElement::setIdAttributeNode</a> ( <a href="https://www.php.net/manual/en/class.domattr.php">\DOMAttr</a> $attr , bool $isId ) : void
public <a href="https://www.php.net/manual/en/domelement.setidattributens.php">\DOMElement::setIdAttributeNS</a> ( string $namespace , string $qualifiedName , bool $isId ) : void
}</code></pre>
## Properties ##
<dl>
<dt id="element-props-classlist"><var>classList</var></dt>
<dd>A live <a href="../TokenList/TokenList.html">TokenList</a> collection of the class attributes of the element. This can then be used to manipulate the class list.</dd>
<dt id="element-props-innerhtml"><var>innerHTML</var></dt>
<dd>Gets or sets the HTML or XML markup contained within the element</dd>
<dt id="element-props-outerhtml"><var>outerHTML</var></dt>
<dd>Gets the serialized HTML fragment describing the element including its descendants. It can also be set to replace the element with nodes parsed from the given string.</dd>
</dl>

55
docs/en/030_Document_Object_Model/ContainerNode/010_appendChild.md

@ -1,55 +0,0 @@
---
title: ContainerNode::appendChild
---
ContainerNode::appendChild — Adds new child at the end of the children
## Description ##
```php
public ContainerNode::appendChild ( \DOMNode $node ) : \DOMNode|false
```
This function appends a child to an existing list of children or creates a new list of children. The child can be created with e.g. [`Document::createElement()`](https://www.php.net/manual/en/domdocument.createelement.php), [`Document::createTextNode()`](https://www.php.net/manual/en/domdocument.createtextnode.php) etc. or simply by using any other node.
When using an existing node it will be moved.
<div class="warning">
<p><strong>Warning</strong> Only the following element types may be appended to any node using <code>Node</code> and subject to hierarchy restrictions depending on the type of node being appended to:</p>
<ul>
<li><code>Comment</code></li>
<li><code>DocumentFragment</code></li>
<li><a href="https://www.php.net/manual/en/class.domdocumenttype.php"><code>\DOMDocumentType</code></a></li>
<li><code>Element</code></li>
<li><code>ProcessingInstruction</code></li>
<li><code>Text</code></li>
</ul>
<p>Note that <code>\DOMAttr</code> is missing from this list.</p>
</div>
## Parameters ##
<dl>
<dt><code>node</code></dt>
<dd>The new node.</dd>
</dl>
## Examples ##
**Example \#1 Adding a child to the body**
```php
<?php
namespace MensBeam\HTML;
$dom = new Document();
$dom->loadHTML('<!DOCTYPE html><html><head><title>Ook!</title></head><body></body></html>');
$node = $dom->createElement('br');
$dom->body->appendChild($node);
?>
```

40
docs/en/030_Document_Object_Model/ContainerNode/010_insertBefore.md

@ -1,40 +0,0 @@
---
title: ContainerNode::insertBefore
---
ContainerNode::insertBefore — Adds a new child before a reference node
## Description ##
```php
public ContainerNode::insertBefore ( \DOMNode $node , \DOMNode|null $child = null ) : \DOMNode|false
```
This function inserts a new node right before the reference node. If you plan to do further modifications on the appended child you must use the returned node.
When using an existing node it will be moved.
<div class="warning">
<p><strong>Warning</strong> Only the following element types may be appended to any node using <code>Node</code> and subject to hierarchy restrictions depending on the type of node being appended to:</p>
<ul>
<li><code>Comment</code></li>
<li><code>DocumentFragment</code></li>
<li><a href="https://www.php.net/manual/en/class.domdocumenttype.php"><code>\DOMDocumentType</code></a></li>
<li><code>Element</code></li>
<li><code>ProcessingInstruction</code></li>
<li><code>Text</code></li>
</ul>
<p>Note that <code>\DOMAttr</code> is missing from this list.</p>
</div>
## Parameters ##
<dl>
<dt><code>node</code></dt>
<dd>The new node.</dd>
<dt><code>child</code></dt>
<dd>The reference node. If not supplied, <code>node</code> is appended to the children.</dd>
</dl>

14
docs/en/030_Document_Object_Model/ContainerNode/index.md

@ -1,14 +0,0 @@
# The ContainerNode trait #
## Introduction ##
Allows the extended PHP DOM classes to simulate inheriting from a theoretical extended [\DOMNode](https://www.php.net/manual/en/class.domnode.php). This one implements improved DOM child insertion methods.
<pre><code class="php">trait MensBeam\HTML\ContainerNode {
use <a href="../Node/index.html">Node</a>;
public <a href="appendChild.html">appendChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
public <a href="insertBefore.html">insertBefore</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null $child = null ) : <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|false
}</code></pre>

13
docs/en/030_Document_Object_Model/LeafNode/010_appendChild.md

@ -1,13 +0,0 @@
---
title: LeafNode::appendChild
---
LeafNode::appendChild — **DISABLED**
## Description ##
```php
public LeafNode::appendChild ( \DOMNode $node ) : DOMException
```
Throws a `DOMException` upon use.

13
docs/en/030_Document_Object_Model/LeafNode/010_insertBefore.md

@ -1,13 +0,0 @@
---
title: LeafNode::insertBefore
---
LeafNode::insertBefore — **DISABLED**
## Description ##
```php
public LeafNode::insertBefore ( \DOMNode $node , \DOMNode|null $child = null ) : DOMException
```
Throws a `DOMException` upon use.

13
docs/en/030_Document_Object_Model/LeafNode/010_removeChild.md

@ -1,13 +0,0 @@
---
title: LeafNode::removeChild
---
LeafNode::removeChild — **DISABLED**
## Description ##
```php
public LeafNode::removeChild ( \DOMNode $node ) : DOMException
```
Throws a `DOMException` upon use.

13
docs/en/030_Document_Object_Model/LeafNode/010_replaceChild.md

@ -1,13 +0,0 @@
---
title: LeafNode::replaceChild
---
LeafNode::replaceChild — **DISABLED**
## Description ##
```php
public LeafNode::replaceChild ( \DOMNode $node , \DOMNode $child ) : DOMException
```
Throws a `DOMException` upon use.

16
docs/en/030_Document_Object_Model/LeafNode/index.md

@ -1,16 +0,0 @@
# The LeafNode trait #
## Introduction ##
Allows the extended PHP DOM classes to simulate inheriting from a theoretical extended [\DOMNode](https://www.php.net/manual/en/class.domnode.php). This one disables all DOM child insertion methods.
<pre><code class="php">trait MensBeam\HTML\LeafNode {
use <a href="../Node/index.html">Node</a>;
public <a href="appendChild.html">appendChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node ) : DOMException
public <a href="Node_insertBefore.html">insertBefore</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node , <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a>|null $child = null ) : DOMException
public <a href="removeChild.html">removeChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : DOMException
public <a href="replaceChild.html">replaceChild</a> ( <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $node, <a href="https://www.php.net/manual/en/class.domnode.php">\DOMNode</a> $child ) : DOMException
}</code></pre>

43
docs/en/030_Document_Object_Model/Moonwalk/010_moonwalk.md

@ -1,43 +0,0 @@
---
title: Moonwalk::moonwalk
---
Moonwalk::moonwalk — Output generator for walking up the DOM tree
## Description ##
<pre><code class="php">public Moonwalk::moonwalk ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a>|null $filter = null ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
</code></pre>
Non-standard. Creates a [`\Generator`](https://www.php.net/manual/en/class.generator.php) object for walking up the DOM tree. This is in lieu of recreating the awful [DOM TreeWalker API](https://developer.mozilla.org/en-US/docs/Web/API/Treewalker).
## Examples ##
**Example \#1 Print name of all ancestors of the H1 element**
```php
<?php
namespace MensBeam\HTML;
$dom = new Document();
$dom->loadHTML('<!DOCTYPE html><html><head><title>Ook!</title></head><body><h1>Eek</h1></body></html>');
$h1 = $dom->getElementsByTagName('h1')->item(0);
// All ancestors will be elements so there's no reason to have a filter.
$tree = $h1->moonwalk();
foreach ($tree as $t) {
echo "{$t->nodeName}\n";
}
?>
```
The above example will output something similar to:
```php
body
html
```

11
docs/en/030_Document_Object_Model/Moonwalk/index.md

@ -1,11 +0,0 @@
# The Moonwalk trait #
## Introduction ##
Allows the extended PHP DOM classes to Moonwalk up the DOM via a [`\Generator`](https://www.php.net/manual/en/class.generator.php). This is in lieu of recreating the awful [DOM TreeMoonwalker API](https://developer.mozilla.org/en-US/docs/Web/API/TreeMoonwalker).
<pre><code class="php">trait MensBeam\HTML\Moonwalk {
public <a href="Moonwalk.html">Moonwalk</a> ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a> $filter ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
}</code></pre>

13
docs/en/030_Document_Object_Model/Node/010_C14N.md

@ -1,13 +0,0 @@
---
title: Node::C14N
---
Node::C14N — **DISABLED**
## Description ##
```php
public Node::C14N ( bool $exclusive = false , bool $withComments = false , array|null $xpath = null , array|null $nsPrefixes = null ) : false
```
This function has been disabled and will always return `false`. `\DOMNode::C14N` is an extremely slow and inefficient method to serialize DOM and never should be used.

13
docs/en/030_Document_Object_Model/Node/010_C14NFile.md

@ -1,13 +0,0 @@
---
title: Node::C14NFile
---
Document::C14NFile — **DISABLED**
## Description ##
```php
public Node::C14NFile ( string $uri , bool $exclusive = false , bool $withComments = false , array|null $xpath = null , array|null $nsPrefixes = null ) : false
```
This function has been disabled and will always return `false`. `\DOMNode::C14NFile` is an extremely slow and inefficient method to serialize DOM and never should be used.

12
docs/en/030_Document_Object_Model/Node/index.md

@ -1,12 +0,0 @@
# The Node trait #
## Introduction ##
Allows the extended PHP DOM classes to simulate inheriting from a theoretical extended [\DOMNode](https://www.php.net/manual/en/class.domnode.php). It is used to disable [C14N](C14N.html) and [C14NFile](C14NFile.html).
<pre><code class="php">trait MensBeam\HTML\Node {
public <a href="C14N.html">C14N</a> ( bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
public <a href="C14NFile.html">C14NFile</a> ( string $uri , bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
}</code></pre>

45
docs/en/030_Document_Object_Model/Walk/010_walk.md

@ -1,45 +0,0 @@
---
title: Walk::walk
---
Walk::walk — Output generator for walking down the DOM tree
## Description ##
<pre><code class="php">public Walk::walk ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a>|null $filter = null ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
</code></pre>
Non-standard. Creates a [`\Generator`](https://www.php.net/manual/en/class.generator.php) object for walking down the DOM tree. This is in lieu of recreating the awful [DOM TreeWalker API](https://developer.mozilla.org/en-US/docs/Web/API/Treewalker).
## Examples ##
**Example \#1 Print name of every Element**
```php
<?php
namespace MensBeam\HTML;
$dom = new Document();
$dom->loadHTML('<!DOCTYPE html><html><head><title>Ook!</title></head><body><h1>Eek</h1></body></html>');
$tree = $dom->walk(function($node) {
return ($node instanceof Element);
});
foreach ($tree as $t) {
echo "{$t->nodeName}\n";
}
?>
```
The above example will output something similar to:
```php
html
head
title
body
h1
```

11
docs/en/030_Document_Object_Model/Walk/index.md

@ -1,11 +0,0 @@
# The Walk trait #
## Introduction ##
Allows the extended PHP DOM classes to walk down the DOM via a [`\Generator`](https://www.php.net/manual/en/class.generator.php). This is in lieu of recreating the awful [DOM TreeWalker API](https://developer.mozilla.org/en-US/docs/Web/API/Treewalker).
<pre><code class="php">trait MensBeam\HTML\Walk {
public <a href="walk.html">walk</a> ( <a href="https://www.php.net/manual/en/class.closure.php">\Closure</a> $filter ) : <a href="https://www.php.net/manual/en/class.generator.php">\Generator</a>
}</code></pre>

1
docs/en/030_Document_Object_Model/index.md

@ -1 +0,0 @@
The MensBeam HTML library works by parsing HTML strings into PHP's existing XML DOM. It, however, has to force the antiquated PHP DOM extension into working properly with modern HTML DOM by extending many of the node types. The documentation below follows PHP's doc style guide as closely as possible. Each class should be listed separately in the menu under this section.

1
docs/index.md

@ -1 +0,0 @@
Welcome to the user manual for HTML. It is included with each copy of the software, and is also [available online](https://mensbeam.com/html/en/). Please select a language above.

9
docs/theme/php/config.json

@ -1,9 +0,0 @@
{
"favicon": "<theme_url>favicon.png",
"js": [
"<theme_url>daux.min.js"
],
"css": [
"<theme_url>php.css"
]
}

2
docs/theme/php/daux.min.js

@ -1,2 +0,0 @@
var e=document.querySelectorAll(".s-content pre"),t=document.querySelector(".CodeToggler"),n="daux_code_blocks_hidden";function a(t){for(var a=0;a<e.length;a++)e[a].classList.toggle("Hidden",t);try{localStorage.setItem(n,t)}catch(e){}}t&&(e.length?function(){var e=t.querySelector(".CodeToggler__button--main");e.addEventListener("change",(function(e){a(!e.target.checked)}),!1);var r=!1;try{"false"===(r=localStorage.getItem(n))?r=!1:"true"===r&&(r=!0),r&&(a(!!r),e.checked=!r)}catch(e){}}():t.classList.add("Hidden"));var r=document.querySelector(".Collapsible__trigger");if(r){var o=document.querySelector(".Collapsible__content");r.addEventListener("click",(function(e){o.classList.contains("Collapsible__content--open")?(o.style.height=0,o.classList.remove("Collapsible__content--open"),r.setAttribute("aria-expanded","false")):(r.setAttribute("aria-expanded","true"),o.style.transitionDuration="150ms",o.style.height="".concat(o.scrollHeight,"px"),o.classList.add("Collapsible__content--open"))}))}var l=document.querySelectorAll("pre > code:not(.hljs)");if(l.length){var i=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.async=!0,c.src="".concat(window.base_url,"daux_libraries/highlight.pack.js"),c.onload=function(e){[].forEach.call(l,window.hljs.highlightBlock)},i.appendChild(c)}function s(e){var t=void 0!==e.preventDefault;t&&e.preventDefault();var n=function(e){for(var t=e;(t=t.parentNode)&&9!==t.nodeType;)if(1===t.nodeType&&t.classList.contains("Nav__item"))return t;throw new Error("Could not find a NavItem...")}(e.target),a=n.querySelector("ul.Nav");t&&n.classList.contains("Nav__item--open")?(a.style.height="".concat(a.scrollHeight,"px"),a.style.transitionDuration="150ms",a.style.height="0px",n.classList.remove("Nav__item--open")):t?(a.style.transitionDuration="150ms",a.addEventListener("transitionend",(function e(t){"0px"!==t.target.style.height&&(t.target.style.height="auto"),t.target.removeEventListener("transitionend",e)})),a.style.height="".concat(a.scrollHeight,"px"),n.classList.add("Nav__item--open")):a.style.height="auto"}for(var d,u=document.querySelectorAll(".Nav__item.has-children i.Nav__arrow"),h=u.length-1;h>=0;h--)(d=u[h]).addEventListener("click",s),d.parentNode.parentNode.classList.contains("Nav__item--open")&&s({target:d});var g=document.querySelectorAll(".Nav__item__link--nopage"),v=!0,p=!1,_=void 0;try{for(var y,m=g[Symbol.iterator]();!(v=(y=m.next()).done);v=!0){y.value.addEventListener("click",s)}}catch(e){p=!0,_=e}finally{try{v||null==m.return||m.return()}finally{if(p)throw _}}
//# sourceMappingURL=daux.min.js.map

2
docs/theme/php/php.css

File diff suppressed because one or more lines are too long

324
docs/theme/src/php.scss

@ -1,324 +0,0 @@
/* Daux imports; fonts are omitted */
@import "../../../vendor/daux/daux.io/src/css/theme_daux/vendor/normalize.scss";
@import "../../../vendor/daux/daux.io/src/css/theme_daux/_variables.scss";
@import "../../../vendor/daux/daux.io/src/css/theme_daux/_mixins.scss";
@import "../../../vendor/daux/daux.io/src/css/theme_daux/_structure.scss";
@import "../../../vendor/daux/daux.io/src/css/theme_daux/_typography.scss";
@import "../../../vendor/daux/daux.io/src/css/theme_daux/_components.scss";
@import "../../../vendor/daux/daux.io/src/css/theme_daux/_homepage.scss";
@import "../../../vendor/daux/daux.io/src/css/theme_daux/_print.scss" print;
/* Overrides */
:root {
--font-family-text: sans-serif;
--font-family-monospace: "Operator Mono SSm", "Operator Mono", monospace;
--font-family-heading: sans-serif;
--type-size-1: 1.75rem;
--type-size-2: 1.5rem;
--type-size-3: 1.25rem;
--type-size-4: 1.125rem;
--type-size-5: 1rem;
--type-size-6: 1rem;
--purple: #4f5b93;
--tyrian: #793862;
--light-purple: #8892bf;
--lighter-purple: #c4c9df;
--danger: #f4dfdf;
--page: #f2f2f2;
--text: #333;
--red: #e63c2f;
--blue: #15284b;
--light-blue: #93b7bb;
--beige: #e8d5d3;
--green: #2c9a42;
--dark-gray: color(var(--page) blend(var(--text) 75%));
--gray: color(var(--page) blend(var(--text) 50%));
--light-gray: color(var(--page) blend(var(--text) 25%));
--lighter-gray: color(var(--page) blend(var(--text) 12.5%));
--lightest-gray: color(#fff blend(var(--page) 75%));
--dark: var(--text);
--light: var(--light-purple);
--sidebar-background: var(--text);
--sidebar-link-active-background: var(--tyrian);
--sidebar-link-color: var(--page);
--sidebar-link-secondary-color: var(--page);
--sidebar-collapsible--hamburger-color: var(--beige);
--link-color: #369;
--brand-color: #fff;
--brand-background: var(--purple);
--code-tag-background-color: transparent;
--code-tag-border-radius: 0;
--code-tag-box-shadow: none;
--homepage-navbar-background: var(--red);
--hero-button-block-background: var(--beige);
--homepage-hero-background: #fff;
--content-floating-blocks-background: var(--blue);
}
body {
line-height: 1.618;
font-size: 16px;
color: var(--text) !important;
}
body, .Columns__right__content {
background-color: var(--page);
}
a.Link--external::after {
content: '';
}
.Page__header h1 {
font-size: var(--type-size-6);
border-bottom: 0;
margin-bottom: 0;
}
.s-content {
h1, h2, h3, h4, h5, h6 {
margin-bottom: 1.5rem;
}
h1 {
font-size: var(--type-size-1);
}
h2 {
font-size: var(--type-size-2);
}
h3 {
font-size: var(--type-size-3);
}
h4 {
font-size: var(--type-size-4);
}
h5 {
font-size: var(--type-size-5);
}
h6 {
font-size: var(--type-size-6);
}
code {
padding-top: 0;
padding-bottom: 0;
padding: 0;
border: 0;
margin: 0;
&::before, &::after {
display: none;
}
pre & {
display: inline;
}
}
table {
border-collapse: separate;
border-spacing: 2px;
border: 2px solid var(--gray);
thead, tbody {
background-color: #fff;
}
tr {
border-top: 0;
&:nth-child(2n) {
background-color: transparent;
td {
background-color: #fff;
}
}
}
th, td {
border: 0;
}
th {
background-color: var(--lighter-purple);
}
}
}
.s-content table, .Nav__item .Nav__item {
font-size: 1rem;
}
.Brand, h1, h2, h3, h4, h5, h6 {
font-weight: 600;
font-stretch: condensed;
}
h1, h2, h3, h4, h5, h6 {
color: var(--tyrian);
border-bottom: 1px dotted var(--text);
padding-bottom: 5px;
}
.Button {
border-radius: 0;
}
.HomepageButtons .Button--hero {
font-weight: normal;
font-size: var(--type-size-6);
}
.Page__header {
border-bottom: 0;
}
.Pager li > a {
border: 2px solid var(--lighter-gray);
border-radius: 0;
&:hover, &:focus {
background-color: var(--lighter-gray);
}
}
.Pager--prev a::before {
content: "\2190\00a0";
}
.Pager--next a::after {
content: "\00a0\2192";
}
.Navbar {
height: auto;
box-shadow: none;
.Brand {
float: none;
line-height: inherit;
height: auto;
}
}
.Homepage {
padding-top: 10px !important;
}
.Nav__item {
font-size: var(--type-size-6);
}
.Nav .Nav .Nav__item a {
padding-left: 35px;
}
.Nav__arrow:before {
margin: 0 0 0 -.25em;
top: auto;
bottom: calc(50% - 0.0625em);
width: 0.375em;
height: 0.375em;
transform-origin: center;
}
.Nav__arrow:before, .Nav .Nav .Nav__item a .Nav__arrow:before {
border-right-color: var(--page);
border-top-color: var(--page);
}
.admonition {
padding: 0.75rem;
margin: 1.5rem 0;
border: 1px solid var(--light-gray);
background-color: #fff;
p:last-child {
margin-bottom: 0;
}
.danger {
background-color: var(--danger);
border-color: color(var(--danger) blend(var(--text) 25%));
}
}
.hljs, .s-content pre {
background: var(--blue);
color: var(--beige);
}
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-comment, .hljs-quote {
color: #978e9c;
}
/* Green */
.hljs-keyword, .hljs-selector-tag, .hljs-addition {
color: #acb39a;
}
/* Cyan */
.hljs-number, .hljs-string, .hljs-meta .hljs-meta-string, .hljs-literal, .hljs-doctag, .hljs-regexp {
color: var(--light-blue);
}
/* Blue */
.hljs-title, .hljs-section, .hljs-name, .hljs-selector-id, .hljs-selector-class {
color: #82b7e5;
}
/* Yellow */
.hljs-attribute, .hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-class .hljs-title, .hljs-type {
color: #c5b031;
}
/* Orange */
.hljs-symbol, .hljs-bullet, .hljs-subst, .hljs-meta, .hljs-meta .hljs-keyword, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-link {
color: #ea8031;
}
/* Red */
.hljs-built_in, .hljs-deletion {
color: var(--red);
}
.hljs-formula {
background: #686986;
}
@media (--viewport-large) {
.Columns__left {
border: 0;
}
}

12
lib/DOM/AbstractDocument.php

@ -1,12 +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;
// Exists so Document can extend methods from its traits.
abstract class AbstractDocument extends \DOMDocument {
use ContainerNode, DocumentOrElement, EscapeString, MagicProperties, MoonwalkShallow, ParentNode, Walk, WalkShallow;
}

11
lib/DOM/Comment.php

@ -1,11 +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;
class Comment extends \DOMComment {
use LeafNode, Moonwalk, ToString;
}

65
lib/DOM/DOMException.php

@ -1,65 +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;
class DOMException extends \Exception {
// From PHP's DOMException; keeping error codes consistent
const HIERARCHY_REQUEST_ERROR = 3;
const WRONG_DOCUMENT = 4;
const INVALID_CHARACTER = 5;
const NO_MODIFICATION_ALLOWED = 7;
const NOT_FOUND = 8;
const SYNTAX_ERROR = 12;
const ARGUMENT_TYPE_ERROR = 100;
const OUTER_HTML_FAILED_NOPARENT = 101;
protected static $messages = [
3 => 'Hierarchy request error; supplied node is not allowed here',
4 => 'Supplied node does not belong to this document',
5 => 'Invalid character',
7 => 'Modification not allowed here',
8 => 'Not found error',
12 => 'Syntax error',
100 => 'Argument #%s (\$%s) must be of type %s, %s given',
101 => 'Failed to set the "outerHTML" property; the element does not have a parent node'
];
public function __construct(int $code, ...$args) {
if (!isset(self::$messages[$code])) {
throw new Exception(Exception::INVALID_CODE);
}
$message = self::$messages[$code];
$previous = null;
if ($args) {
// Grab a previous exception if there is one.
if ($args[0] instanceof \Throwable) {
$previous = array_shift($args);
} elseif (end($args) instanceof \Throwable) {
$previous = array_pop($args);
}
}
// Count the number of replacements needed in the message.
preg_match_all('/(\%(?:\d+\$)?s)/', $message, $matches);
$count = count($matches[1]);
// If the number of replacements don't match the arguments then oops.
if (count($args) !== $count) {
throw new Exception(Exception::INCORRECT_PARAMETERS_FOR_MESSAGE, $count);
}
if ($count > 0) {
// Go through each of the arguments and run sprintf on the strings.
$message = call_user_func_array('sprintf', array_merge([$message], $args));
}
parent::__construct($message, $code, $previous);
}
}

755
lib/DOM/Document.php

@ -1,755 +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;
class Document extends AbstractDocument {
protected ?Element $_body = null;
/** Nonstandard */
protected ?\DOMXPath $_xpath = null;
// List of elements that are treated as block elements for the purposes of
// output formatting when serializing
protected const BLOCK_ELEMENTS = [ 'address', 'article', 'aside', 'blockquote', 'base', 'body', 'details', 'dialog', 'dd', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'isindex', 'li', 'link', 'main', 'meta', 'nav', 'ol', 'p', 'picture', 'pre', 'section', 'script', 'source', 'style', 'table', 'template', 'td', 'tfoot', 'th', 'thead', 'title', 'tr', 'ul' ];
// List of h-elements used when determining extra spacing for the purposes of
// output formatting when serializing
protected const H_ELEMENTS = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
// List of preformatted elements where content is ignored for the purposes of
// output formatting when serializing
protected const PREFORMATTED_ELEMENTS = [ 'iframe', 'listing', 'noembed', 'noframes', 'noscript', 'plaintext', 'pre', 'style', 'script', 'textarea', 'title', 'xmp' ];
// List of elements which are self-closing; used when serializing
protected const VOID_ELEMENTS = [ 'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' ];
public function __get_body(): \DOMNode {
if ($this->documentElement === null || $this->documentElement->childNodes->length === 0) {
return null;
}
$body = null;
# The body element of a document is the first of the html element's children
# that is either a body element or a frameset element, or null if there is no
# such element.
$n = $this->documentElement->firstChild;
do {
if ($n instanceof Element && $n->namespaceURI === null && ($n->nodeName === 'body' || $n->nodeName === 'frameset')) {
$body = $n;
break;
}
} while ($n = $n->nextSibling);
if ($body !== null) {
// References are handled weirdly by PHP's DOM. Return a stored body element
// unless it is changed so operations (like classList) can be done without
// losing the reference.
if ($body !== $this->_body) {
$this->_body = $body;
}
return $this->_body;
}
$this->_body = null;
return null;
}
public function __set_body($value) {
# On setting, the following algorithm must be run:
#
# 1. If the new value is not a body or frameset element, then throw a
# "HierarchyRequestError" DOMException.
if (!$value instanceof Element || $value->namespaceURI !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($value->nodeName !== 'body' && $value->nodeName !== 'frameset') {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($this->_body !== null) {
# 2. Otherwise, if the new value is the same as the body element, return.
if ($value->isSameNode($this->_body)) {
return;
}
# 3. Otherwise, if the body element is not null, then replace the body element
# with the new value within the body element's parent and return.
$this->documentElement->replaceChild($value, $this->_body);
$this->_body = $value;
return;
}
# 4. Otherwise, if there is no document element, throw a "HierarchyRequestError"
# DOMException.
if ($this->documentElement === null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 5. Otherwise, the body element is null, but there's a document element. Append
# the new value to the document element.
$this->documentElement->appendChild($value);
$this->_body = $value;
}
public function __get_xpath(): \DOMXPath {
if ($this->_xpath === null) {
$this->_xpath = new \DOMXPath($this);
}
return $this->_xpath;
}
public function __construct($source = null, ?string $encodingOrContentType = null) {
// Because we cannot have union types until php 8... :)
if ($source !== null && !$source instanceof \DOMDocument && !is_string($source)) {
throw new DOMException(DOMException::ARGUMENT_TYPE_ERROR, 1, 'source', 'string|\DOMDocument', gettype($source));
} elseif ($source instanceof self) {
return $source;
}
parent::__construct();
$this->registerNodeClass('DOMDocument', '\MensBeam\HTML\Document');
$this->registerNodeClass('DOMComment', '\MensBeam\HTML\Comment');
$this->registerNodeClass('DOMDocumentFragment', '\MensBeam\HTML\DocumentFragment');
$this->registerNodeClass('DOMElement', '\MensBeam\HTML\Element');
$this->registerNodeClass('DOMProcessingInstruction', '\MensBeam\HTML\ProcessingInstruction');
$this->registerNodeClass('DOMText', '\MensBeam\HTML\Text');
if ($source !== null) {
if (is_string($source)) {
$source = Parser::parse($source, null, $encodingOrContentType);
}
foreach ($source->childNodes as $child) {
if (!$child instanceof \DOMDocumentType) {
$this->appendChild($this->importNode($child, true));
} else {
$this->appendChild($this->implementation->createDocumentType($child->name ?? ' ', $child->public ?? '', $child->system ?? ''));
}
}
}
}
public function createAttribute($name) {
return $this->createAttributeNS(null, $name);
}
public function createAttributeNS($namespaceURI, $qualifiedName) {
// Normalize the attribute name and namespace URI per modern DOM specifications.
if ($namespaceURI !== null) {
$namespaceURI = trim($namespaceURI);
}
$qualifiedName = trim($qualifiedName);
try {
return parent::createAttributeNS($namespaceURI, $qualifiedName);
} catch (\DOMException $e) {
// 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
$this->mangledAttributes = true;
if ($namespaceURI !== null) {
$qualifiedName = implode(":", array_map([$this, "coerceName"], explode(":", $qualifiedName, 2)));
} else {
$qualifiedName = $this->coerceName($qualifiedName);
}
return parent::createAttributeNS($namespaceURI, $qualifiedName);
}
}
public function createElement($name, $value = "") {
return $this->createElementNS(null, $name, $value);
}
public function createElementNS($namespaceURI, $qualifiedName, $value = "") {
// Normalize the element name and namespace URI per modern DOM specifications.
if ($namespaceURI !== null) {
$namespaceURI = trim($namespaceURI);
$namespaceURI = ($namespaceURI === Parser::HTML_NAMESPACE) ? null : $namespaceURI;
}
$qualifiedName = ($namespaceURI === null) ? strtolower(trim($qualifiedName)) : trim($qualifiedName);
try {
if ($qualifiedName !== 'template' || $namespaceURI !== null) {
$e = parent::createElementNS($namespaceURI, $qualifiedName, $value);
} else {
$e = new TemplateElement($this, $qualifiedName, $value);
// Template elements need to have a reference kept in userland
ElementMap::set($e);
$e->content = $this->createDocumentFragment();
}
return $e;
} catch (\DOMException $e) {
// 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
$this->mangledElements = true;
if ($namespaceURI !== null) {
$qualifiedName = implode(":", array_map([$this, "coerceName"], explode(":", $qualifiedName, 2)));
} else {
$qualifiedName = $this->coerceName($qualifiedName);
}
return parent::createElementNS($namespaceURI, $qualifiedName, $value);
}
}
public function createEntityReference($name): bool {
return false;
}
public function load($filename, $options = null, ?string $encodingOrContentType = null): bool {
$data = Parser::fetchFile($filename, $encodingOrContentType);
if (!$data) {
return false;
}
[$data, $encodingOrContentType] = $data;
Parser::parse($data, $this, $encodingOrContentType, null, (string)$filename);
return true;
}
public function loadHTML($source, $options = null, ?string $encodingOrContentType = null): bool {
if (!is_string($source)) {
throw new DOMException(DOMException::ARGUMENT_TYPE_ERROR, 1, 'source', 'string', gettype($source));
}
if (is_string($source)) {
$source = Parser::parse($source, null, $encodingOrContentType);
}
foreach ($source->childNodes as $child) {
if (!$child instanceof \DOMDocumentType) {
$this->appendChild($this->importNode($child, true));
} else {
$this->appendChild($this->implementation->createDocumentType($child->name ?? ' ', $child->public ?? '', $child->system ?? ''));
}
}
assert(is_string($source), new DOMException(DOMException::STRING_EXPECTED, 'source', gettype($source)));
Parser::parse($source, $this, $encodingOrContentType);
return true;
}
public function loadHTMLFile($filename, $options = null, ?string $encodingOrContentType = null): bool {
return $this->load($filename, $options, $encodingOrContentType);
}
public function loadXML($source, $options = null): bool {
return false;
}
public function save($filename, $options = null) {
return file_put_contents($filename, $this->serialize());
}
public function saveHTML(\DOMNode $node = null): string {
return $node->serialize($node);
}
public function saveHTMLFile($filename): int {
return $this->save($filename);
}
public function saveXML(?\DOMNode $node = null, $options = null): bool {
return false;
}
public function serialize(\DOMNode $node = null): string {
$node = $node ?? $this;
$formatOutput = $this->formatOutput;
if ($node !== $this) {
if (!$node->ownerDocument->isSameNode($this)) {
throw new DOMException(DOMException::WRONG_DOCUMENT);
}
// This method is used to serialize any node. If not a Document or a
// DocumentFragment or a DocumentType clone the node in a fragment and serialize
// that. Otherwise, if a DocumentFragment create a new Document with a clone of
// the DocumentFragment as its doctype and then serialize the new document.
if (!$node instanceof Document && !$node instanceof DocumentFragment) {
// If the node isn't an element disable output formatting
if ($formatOutput && !$node instanceof Element) {
$formatOutput = false;
}
if (!$node instanceof \DOMDocumentType) {
$frag = $this->createDocumentFragment();
$frag->appendChild($node->cloneNode(true));
$node = $frag;
} else {
$newDoc = new self();
$newDoc->appendChild($newDoc->implementation->createDocumentType($node->name, $node->publicId, $node->systemId));
$node = $newDoc;
}
}
} elseif ($formatOutput && $node instanceof DocumentFragment) {
// If node is a document fragment disable output formatting if the
// DocumentFragment doesn't have any Element children.
$formatOutput = ($node->childElementCount > 0);
}
return $this->serializeFragment($node, $formatOutput);
}
public function validate(): bool {
return true;
}
public function xinclude($options = null): bool {
return false;
}
protected function preInsertionValidity(\DOMNode $node, ?\DOMNode $child = null) {
parent::preInsertionValidity($node, $child);
# 6. If parent is a document, and any of the statements below, switched on node,
# are true, then throw a "HierarchyRequestError" DOMException.
#
# DocumentFragment node
# If node has more than one element child or has a Text node child.
# Otherwise, if node has one element child and either parent has an element
# child, child is a doctype, or child is non-null and a doctype is following
# child.
if ($node instanceof \DOMDocumentType) {
if ($node->childNodes->length > 1 || $node->firstChild instanceof Text) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} else {
if ($node->firstChild instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
if ($child !== null) {
$n = $child;
while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
}
}
# element
# parent has an element child, child is a doctype, or child is non-null and a
# doctype is following child.
elseif ($node instanceof Element) {
if ($child instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($child !== null) {
$n = $child;
while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
# doctype
# parent has a doctype child, child is non-null and an element is preceding
# child, or child is null and parent has an element child.
elseif ($node instanceof \DOMDocumentType) {
foreach ($this->childNodes as $c) {
if ($c instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
if ($child !== null) {
$n = $child;
while ($n = $n->prevSibling) {
if ($n instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
} else {
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
}
}
protected function serializeBlockElementFilter(\DOMNode $ignoredNode): \Closure {
$blockElementFilter = function($n) use ($ignoredNode) {
if (!$n->isSameNode($ignoredNode) && $n instanceof Element && $n->namespaceURI === null && (in_array($n->nodeName, self::BLOCK_ELEMENTS) || $n->walk(function($nn) {
if ($nn instanceof Element && $nn->namespaceURI === null && in_array($nn->nodeName, self::BLOCK_ELEMENTS)) {
return true;
}
})->current() !== null)) {
return true;
}
};
return $blockElementFilter;
}
protected function serializeFragment(\DOMNode $node, bool $formatOutput = false): string {
if ($formatOutput) {
// Stores the root foreign element when parsing its descendants
static $foreignElement = null;
// Flag used if the root foreign element above has block element siblings
static $foreignElementWithBlockElementSiblings = false;
// Stores the indention level
static $indent = 0;
// Stores the root preformatted element when parsing its descendants
static $preformattedElement = null;
// Stores the previous non text node name so it can be used to check for adding
// additional space.
static $previousNonTextNodeSiblingName = null;
}
# 13.3. Serializing HTML fragments
#
# 1. If the node serializes as void, then return the empty string.
if (in_array($node->nodeName, self::VOID_ELEMENTS)) {
return '';
}
# 2. Let s be a string, and initialize it to the empty string.
$s = '';
# 3. If the node is a template element, then let the node instead be the
# template element’s template contents (a DocumentFragment node).
if ($node instanceof TemplateElement) {
$node = $node->content;
}
$nodesLength = $node->childNodes->length;
# 4. For each child node of the node, in tree order, run the following steps:
## 1. Let current node be the child node being processed.
foreach ($node->childNodes as $currentNode) {
$foreign = ($currentNode->namespaceURI !== null);
if ($this->formatOutput) {
// Filter meant to be used with DOM walker generator methods which checks if
// elements are block or if elements are inline with block descendants
$blockElementFilter = self::serializeBlockElementFilter($currentNode->parentNode);
}
# 2. Append the appropriate string from the following list to s:
# If current node is an Element
if ($currentNode instanceof Element) {
# If current node is an element in the HTML namespace, the MathML namespace, or
# the SVG namespace, then let tagname be current node's local name. Otherwise,
# let tagname be current node's qualified name.
$tagName = (!$foreign || $currentNode->namespaceURI === Parser::MATHML_NAMESPACE || $currentNode->namespaceURI === Parser::SVG_NAMESPACE) ? $currentNode->localName : $currentNode->nodeName;
// Since tag names can contain characters that are invalid in PHP's XML DOM
// uncoerce the name when printing if necessary.
if (strpos($tagName, 'U') !== false) {
$tagName = $this->uncoerceName($tagName);
}
if ($formatOutput) {
$blockElementFilter = self::serializeBlockElementFilter($currentNode);
$hasChildNodes = ($currentNode->hasChildNodes());
$modify = false;
if (!$foreign) {
if ($hasChildNodes && $preformattedElement === null && in_array($tagName, self::PREFORMATTED_ELEMENTS)) {
$preformattedElement = $currentNode;
}
// If a block element, an inline element with block element siblings, or an
// inline element with block element descendants...
if (in_array($tagName, self::BLOCK_ELEMENTS) || $currentNode->parentNode->walkShallow($blockElementFilter)->current() !== null || ($hasChildNodes && $currentNode->walk($blockElementFilter)->current() !== null)) {
$modify = true;
}
} else {
// If a foreign element with block element siblings
if ($hasChildNodes && $foreignElement === null) {
$foreignElement = $currentNode;
if ($currentNode->parentNode->walkShallow($blockElementFilter)->current() !== null) {
$foreignElementWithBlockElementSiblings = true;
$modify = true;
}
}
// If a foreign element with a foreign element ancestor with block element
// siblings
elseif ($foreignElement !== null && $foreignElementWithBlockElementSiblings) {
$modify = true;
}
}
if ($modify) {
// If the previous non text node sibling doesn't have the same name as the
// current node and neither are h1-h6 elements then add an additional newline.
if ($previousNonTextNodeSiblingName !== null && $previousNonTextNodeSiblingName !== $tagName && !(in_array($previousNonTextNodeSiblingName, self::H_ELEMENTS) && in_array($tagName, self::H_ELEMENTS))) {
$s .= "\n";
}
$s .= "\n" . str_repeat(' ', $indent);
}
}
# Append a U+003C LESS-THAN SIGN character (<), followed by tagname.
$s .= "<$tagName";
# If current node's is value is not null, and the element does not have an is
# attribute in its attribute list, then append the string " is="", followed by
# current node's is value escaped as described below in attribute mode, followed
# by a U+0022 QUOTATION MARK character (").
// DEVIATION: There is no scripting support in this implementation.
# For each attribute that the element has, append a U+0020 SPACE character,
# the attribute’s serialized name as described below, a U+003D EQUALS SIGN
# character (=), a U+0022 QUOTATION MARK character ("), the attribute’s value,
# escaped as described below in attribute mode, and a second U+0022 QUOTATION
# MARK character (").
foreach ($currentNode->attributes as $attr) {
# An attribute’s serialized name for the purposes of the previous paragraph
# must be determined as follows:
switch ($attr->namespaceURI) {
# If the attribute has no namespace
case null:
# The attribute’s serialized name is the attribute’s local name.
$name = $attr->localName;
break;
# If the attribute is in the XML namespace
case Parser::XML_NAMESPACE:
# The attribute’s serialized name is the string "xml:" followed by the
# attribute’s local name.
$name = 'xml:' . $attr->localName;
break;
# If the attribute is in the XMLNS namespace...
case Parser::XMLNS_NAMESPACE:
# ...and the attribute’s local name is xmlns
if ($attr->localName === 'xmlns') {
# The attribute’s serialized name is the string "xmlns".
$name = 'xmlns';
}
# ... and the attribute’s local name is not xmlns
else {
# The attribute’s serialized name is the string "xmlns:" followed by the
# attribute’s local name.
$name = 'xmlns:' . $attr->localName;
}
break;
# If the attribute is in the XLink namespace
case Parser::XLINK_NAMESPACE:
# The attribute’s serialized name is the string "xlink:" followed by the
# attribute’s local name.
$name = 'xlink:' . $attr->localName;
break;
# If the attribute is in some other namespace
default:
# The attribute’s serialized name is the attribute’s qualified name.
$name = $attr->nodeName;
}
// undo any name mangling
if (strpos($name, 'U') !== false) {
$name = $this->uncoerceName($name);
}
$value = $this->escapeString($attr->value, true);
$s .= " $name=\"$value\"";
}
# While the exact order of attributes is UA-defined, and may depend on factors
# such as the order that the attributes were given in the original markup, the
# sort order must be stable, such that consecutive invocations of this
# algorithm serialize an element’s attributes in the same order.
// Okay.
// When formatting output set the previous non text node sibling name to the
// current node name so void elements and empty foreign elements will be
// recognized by their next sibling.
if ($formatOutput) {
$previousNonTextNodeSiblingName = $tagName;
}
# Append a U+003E GREATER-THAN SIGN character (>).
// DEVIATION: Printing XML-based content such as SVG as if it's HTML might be
// practical when a browser is serializing, but it's not in this library's
// usage. So, if the element is foreign and doesn't contain any children close
// the element instead and continue on to the next child node.
$hasChildNodes = $currentNode->hasChildNodes();
if (!$foreign || $hasChildNodes) {
$s .= '>';
} elseif (!$hasChildNodes) {
$s .= '/>';
continue;
}
# If current node serializes as void, then continue on to the next child node at
# this point.
if (in_array($currentNode->nodeName, self::VOID_ELEMENTS)) {
continue;
}
if ($formatOutput) {
// If formatting output set the previous non text node sibling to null before
// serializing children.
$previousNonTextNodeSiblingName = null;
// If formatting output and the element has already been modified increment the
// indention level
if ($modify) {
$indent++;
}
}
# Append the value of running the HTML fragment serialization algorithm on the
# current node element (thus recursing into this algorithm for that element),
# followed by a U+003C LESS-THAN SIGN character (<), a U+002F SOLIDUS character (/),
# tagname again, and finally a U+003E GREATER-THAN SIGN character (>).
$s .= $this->serializeFragment($currentNode, $formatOutput);
if ($formatOutput) {
if ($modify) {
// Decrement the indention level.
$indent--;
if ($preformattedElement === null) {
// If a foreign element with a foreign element ancestor with block element
// siblings and has at least one element child or any element with a block
// element descendant...
if (($foreign && $foreignElementWithBlockElementSiblings && $currentNode->firstElementChild !== null) || ($currentNode->walk($blockElementFilter)->current() !== null)) {
$s .= "\n" . str_repeat(' ', $indent);
}
}
}
if ($foreignElement !== null && $currentNode->isSameNode($foreignElement)) {
$foreignElement = null;
$foreignElementWithBlockElementSiblings = false;
} elseif ($preformattedElement !== null && $currentNode->isSameNode($preformattedElement)) {
$preformattedElement = null;
}
// Set the previous text node sibling name to the current node's name so it may
// be recognized by the following sibling.
$previousNonTextNodeSiblingName = $tagName;
}
$s .= "</$tagName>";
}
# If current node is a Text node
elseif ($currentNode instanceof Text) {
$text = $currentNode->data;
# If the parent of current node is a style, script, xmp, iframe, noembed,
# noframes, or plaintext element, or if the parent of current node is a noscript
# element and scripting is enabled for the node, then append the value of
# current node’s data IDL attribute literally.
if ($currentNode->parentNode->namespaceURI === null && in_array($currentNode->parentNode->nodeName, [ 'style', 'script', 'xmp', 'iframe', 'noembed', 'noframes', 'plaintext' ])) {
$s .= $text;
}
# Otherwise, append the value of current node’s data IDL attribute, escaped as
# described below.
else {
if ($formatOutput) {
if ($preformattedElement === null) {
// Condense spaces and tabs into a single space.
$text = preg_replace('/ +/', ' ', str_replace("\t", ' ', $text));
if ($foreignElementWithBlockElementSiblings || $currentNode->parentNode->walk($blockElementFilter)->current() !== null) {
// If the text node's data is made up of only whitespace characters continue
// onto the next node
if (strspn($text, Data::WHITESPACE) === strlen($text)) {
continue;
}
// Otherwise, remove newlines from the text node's data; if that causes the data
// to be empty then continue onto the next node.
$text = preg_replace('/[\n\x0C\x0D]+/', '', $text);
if ($text === '') {
continue;
}
}
}
}
$s .= $this->escapeString($text);
}
}
# If current node is a Comment
elseif ($currentNode instanceof Comment) {
if ($formatOutput) {
if ($preformattedElement === null && $foreignElementWithBlockElementSiblings || $currentNode->parentNode->walk($blockElementFilter)->current() !== null) {
// Add an additional newline if the previous sibling wasn't a comment.
if ($previousNonTextNodeSiblingName !== null && $previousNonTextNodeSiblingName !== $this->nodeName) {
$s .= "\n";
}
$s .= "\n" . str_repeat(' ', $indent);
}
$previousNonTextNodeSiblingName = $this->nodeName;
}
# Append the literal string "<!--" (U+003C LESS-THAN SIGN, U+0021 EXCLAMATION
# MARK, U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS), followed by the value of
# current node’s data IDL attribute, followed by the literal string "-->"
# (U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN).
$s .= "<!--{$currentNode->data}-->";
}
# If current node is a ProcessingInstruction
elseif ($currentNode instanceof ProcessingInstruction) {
if ($formatOutput) {
if ($preformattedElement === null && $foreignElementWithBlockElementSiblings || $currentNode->parentNode->walk($blockElementFilter)->current() !== null) {
// Add an additional newline if the previous sibling wasn't a processing
// instruction.
if ($previousNonTextNodeSiblingName !== null && $previousNonTextNodeSiblingName !== $this->nodeName) {
$s .= "\n";
}
$s .= "\n" . str_repeat(' ', $indent);
}
$previousNonTextNodeSiblingName = $this->nodeName;
}
# Append the literal string "<?" (U+003C LESS-THAN SIGN, U+003F QUESTION MARK),
# followed by the value of current node’s target IDL attribute, followed by a
# single U+0020 SPACE character, followed by the value of current node’s data
# IDL attribute, followed by a single U+003E GREATER-THAN SIGN character (>).
$s .= "<?{$currentNode->target} {$currentNode->data}>";
}
# If current node is a DocumentFragment
elseif ($currentNode instanceof \DOMDocumentType) {
# Append the literal string "<!DOCTYPE" (U+003C LESS-THAN SIGN, U+0021
# EXCLAMATION MARK, U+0044 LATIN CAPITAL LETTER D, U+004F LATIN CAPITAL LETTER
# O, U+0043 LATIN CAPITAL LETTER C, U+0054 LATIN CAPITAL LETTER T, U+0059
# LATIN CAPITAL LETTER Y, U+0050 LATIN CAPITAL LETTER P, U+0045 LATIN CAPITAL
# LETTER E), followed by a space (U+0020 SPACE), followed by the value of
# current node's name IDL attribute, followed by the literal string ">" (U+003E
# GREATER-THAN SIGN).
// DEVIATION: The name is trimmed because PHP's DOM does not
// accept the empty string as a DOCTYPE name
$name = trim($node->childNodes->item(0)->name, ' ');
$s .= "<!DOCTYPE $name>";
}
}
# 5. Return s.
return $s;
}
public function __toString() {
return $this->serialize();
}
}

11
lib/DOM/DocumentFragment.php

@ -1,11 +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;
class DocumentFragment extends \DOMDocumentFragment {
use ContainerNode, MoonwalkShallow, ParentNode, ToString, Walk, WalkShallow;
}

313
lib/DOM/Element.php

@ -1,313 +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;
class Element extends \DOMElement {
use ContainerNode, DocumentOrElement, EscapeString, MagicProperties, Moonwalk, MoonwalkShallow, ParentNode, ToString, Walk, WalkShallow;
protected $_classList;
public function __get_classList(): ?TokenList {
// MensBeam\HTML\TokenList uses WeakReference to prevent a circular reference,
// so it requires PHP 7.4 to work.
if (version_compare(\PHP_VERSION, '7.4.0', '>=')) {
// Only create the class list if it is actually used.
if ($this->_classList === null) {
$this->_classList = new TokenList($this, 'class');
}
return $this->_classList;
}
return null; // @codeCoverageIgnore
}
public function __get_innerHTML(): string {
### DOM Parsing Specification ###
# 2.3 The InnerHTML mixin
#
# On getting, return the result of invoking the fragment serializing algorithm
# on the context object providing true for the require well-formed flag (this
# might throw an exception instead of returning a string).
// DEVIATION: Parsing of XML documents will not be handled by this
// implementation, so there's no need for the well-formed flag.
return $this->ownerDocument->serialize($this);
}
public function __set_innerHTML(string $value) {
### DOM Parsing Specification ###
# 2.3 The InnerHTML mixin
#
# On setting, these steps must be run:
# 1. Let context element be the context object's host if the context object is a
# ShadowRoot object, or the context object otherwise.
// DEVIATION: There is no scripting in this implementation.
# 2. Let fragment be the result of invoking the fragment parsing algorithm with
# the new value as markup, and with context element.
$fragment = Parser::parseFragment($value, $this->ownerDocument, 'UTF-8', $this);
# 3. If the context object is a template element, then let context object be the
# template's template contents (a DocumentFragment).
if ($this->nodeName === 'template') {
$this->content = $fragment;
}
# 4. Replace all with fragment within the context object.
else {
# To replace all with a node within a parent, run these steps:
#
# 1. Let removedNodes be parent’s children.
// DEVIATION: removedNodes is used below for scripting. There is no scripting in
// this implementation.
# 2. Let addedNodes be parent’s children.
// DEVIATION: addedNodes is used below for scripting. There is no scripting in
// this implementation.
# 3. If node is a DocumentFragment node, then set addedNodes to node’s
# children.
// DEVIATION: Again, there is no scripting in this implementation.
# 4. Otherwise, if node is non-null, set addedNodes to « node ».
// DEVIATION: Yet again, there is no scripting in this implementation.
# 5. Remove all parent’s children, in tree order, with the suppress observers
# flag set.
// DEVIATION: There are no observers to suppress as there is no scripting in
// this implementation.
while ($this->hasChildNodes()) {
$this->removeChild($this->firstChild);
}
# 6. Otherwise, if node is non-null, set addedNodes to « node ».
# If node is non-null, then insert node into parent before null with the
# suppress observers flag set.
// DEVIATION: Yet again, there is no scripting in this implementation.
# 7. If either addedNodes or removedNodes is not empty, then queue a tree
# mutation record for parent with addedNodes, removedNodes, null, and null.
// DEVIATION: Normally the tree mutation record would do the actual replacement,
// but there is no scripting in this implementation. Going to simply append the
// fragment instead.
$this->appendChild($fragment);
}
}
public function __get_nextElementSibling(): Element {
# The nextElementSibling getter steps are to return the first following sibling
# that is an element; otherwise null.
if ($this->parentNode !== null) {
$start = false;
foreach ($this->parentNode->childNodes as $child) {
if (!$start) {
if ($child->isSameNode($this)) {
$start = true;
}
continue;
}
if (!$child instanceof Element) {
continue;
}
return $child;
}
}
return null;
}
public function __get_outerHTML(): string {
### DOM Parsing Specification ###
# 2.4 Extensions to the Element interface
# outerHTML
#
# On getting, return the result of invoking the fragment serializing algorithm
# on a fictional node whose only child is the context object providing true for
# the require well-formed flag (this might throw an exception instead of
# returning a string).
// DEVIATION: Parsing of XML documents will not be handled by this
// implementation, so there's no need for the well-formed flag.
return $this->__toString();
}
public function __set_outerHTML(string $value) {
### DOM Parsing Specification ###
# 2.4 Extensions to the Element interface
# outerHTML
#
# On setting, the following steps must be run:
# 1. Let parent be the context object's parent.
$parent = $this->parentNode;
# 2. If parent is null, terminate these steps. There would be no way to obtain a
# reference to the nodes created even if the remaining steps were run.
// The spec is unclear here as to what to do. What do you return? Most browsers
// throw an exception here, so that's what we're going to do.
if ($parent === null) {
throw new DOMException(DOMException::OUTER_HTML_FAILED_NOPARENT);
}
# 3. If parent is a Document, throw a "NoModificationAllowedError" DOMException.
elseif ($parent instanceof Document) {
throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED);
}
# 4. parent is a DocumentFragment, let parent be a new Element with:
# • body as its local name,
# • The HTML namespace as its namespace, and
# • The context object's node document as its node document.
elseif ($parent instanceof DocumentFragment) {
$parent = $this->ownerDocument->createElement('body');
}
# 5. Let fragment be the result of invoking the fragment parsing algorithm with
# the new value as markup, and parent as the context element.
$fragment = Parser::parseFragment($value, $this->ownerDocument, 'UTF-8', $parent);
# 6. Replace the context object with fragment within the context object's
# parent.
$this->parentNode->replaceChild($fragment, $this);
}
public function __get_previousElementSibling(): Element {
# The previousElementSibling getter steps are to return the first preceding
# sibling that is an element; otherwise null.
if ($this->parentNode !== null) {
foreach ($this->parentNode->childNodes as $child) {
if ($child->isSameNode($this)) {
return null;
}
if (!$child instanceof Element) {
continue;
}
return $child;
}
}
return null;
}
public function getAttribute($name) {
// Newer versions of the DOM spec have getAttribute return an empty string only
// when the attribute exists and is empty, otherwise null. This fixes that.
$value = parent::getAttribute($name);
if ($value === '' && !parent::hasAttribute($name)) {
// the PHP DOM does not acknowledge the presence of XMLNS-namespace attributes
foreach ($this->attributes as $a) {
if ($a->nodeName === $name) {
return $a->value;
}
}
return null;
}
return $value;
}
public function getAttributeNS($namespaceURI, $localName) {
// Newer versions of the DOM spec have getAttributeNS return an empty string
// only when the attribute exists and is empty, otherwise null. This fixes that.
$value = parent::getAttributeNS($namespaceURI, $localName);
if ($value === '' && !$this->hasAttributeNS($namespaceURI, $localName)) {
return null;
}
return $value;
}
public function hasAttribute($name) {
if (!parent::hasAttribute($name)) {
foreach ($this->attributes as $a) {
if ($a->nodeName === $name) {
return true;
}
}
return false;
}
return true;
}
public function setAttribute($name, $value) {
$this->setAttributeNS(null, $name, $value);
}
public function setAttributeNS($namespaceURI, $qualifiedName, $value) {
// Normalize the attribute name and namespace URI per modern DOM specifications.
if ($namespaceURI !== null) {
$namespaceURI = trim($namespaceURI);
}
$qualifiedName = trim($qualifiedName);
if ($namespaceURI === null && ($this->namespaceURI ?? Parser::HTML_NAMESPACE) === Parser::HTML_NAMESPACE && !$this->hasAttributeNS($namespaceURI, $qualifiedName)) {
$qualifiedName = trim(strtolower($qualifiedName));
}
// If setting a class attribute and classList has been invoked use classList to
// set it.
if ($qualifiedName === 'class' && $namespaceURI === null && $this->_classList !== null) {
$this->_classList->value = $value;
} elseif ($namespaceURI === Parser::XMLNS_NAMESPACE) {
// NOTE: We create attribute nodes so that xmlns attributes
// don't get lost; otherwise they cannot be serialized
$a = @$this->ownerDocument->createAttributeNS($namespaceURI, $qualifiedName);
if ($a === false) {
// The document element does not exist yet, so we need
// to insert this element into the document
$this->ownerDocument->appendChild($this);
$a = $this->ownerDocument->createAttributeNS($namespaceURI, $qualifiedName);
$this->ownerDocument->removeChild($this);
}
$a->value = $this->escapeString($value, true);
$this->setAttributeNodeNS($a);
} else {
try {
parent::setAttributeNS($namespaceURI, $qualifiedName, $value);
} catch (\DOMException $e) {
// The attribute name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
$this->ownerDocument->mangledAttributes = true;
if ($namespaceURI !== null) {
$qualifiedName = implode(":", array_map([$this, "coerceName"], explode(":", $qualifiedName, 2)));
} else {
$qualifiedName = $this->coerceName($qualifiedName);
}
parent::setAttributeNS($namespaceURI, $qualifiedName, $value);
}
if ($qualifiedName === "id" && $namespaceURI === null) {
$this->setIdAttribute($qualifiedName, true);
}
}
}
public function setAttributeNode(\DOMAttr $attribute) {
return $this->setAttributeNodeNS($attribute, null);
}
public function setAttributeNodeNS(\DOMAttr $attribute) {
$fixId = false;
if ($attribute->namespaceURI === null) {
if ($attribute->name === 'id') {
$fixId = true;
}
// If appending a class attribute node, and classList has been invoked set
// the class using classList instead of appending the attribute node. Will
// return the created node instead. TokenList appends an attribute node
// internally to set the class attribute, so to prevent an infinite call loop
// from occurring, a check between the normalized value and classList's
// serialized value is performed. The spec is vague on how this is supposed to
// be handled.
elseif ($this->_classList !== null && $attribute->name === 'class' && preg_replace(Data::WHITESPACE_REGEX, ' ', $attribute->value) !== $this->_classList->value) {
$this->_classList->value = $attribute->value;
return $this->getAttributeNode('class');
}
}
$result = parent::setAttributeNodeNS($attribute);
if ($fixId) {
$this->setIdAttribute($attribute->name, true);
}
return $result;
}
}

70
lib/DOM/ElementMap.php

@ -1,70 +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;
// This is a write-only map of elements which need to be kept in memory; it
// exists because values of properties on derived DOM classes are lost unless at
// least one PHP reference is kept for the element somewhere in userspace. This
// is that somewhere. It is at present only used for template elements.
class ElementMap {
protected static $_storage = [];
public static function delete(Element $element) {
foreach (self::$_storage as $k => $v) {
if ($v->isSameNode($element)) {
unset(self::$_storage[$k]);
self::$_storage = array_values(self::$_storage);
return true;
}
}
return false;
}
public static function destroy(Document $document) {
$changed = false;
foreach (self::$_storage as $k => $v) {
if ($v->ownerDocument->isSameNode($document)) {
unset(self::$_storage[$k]);
$changed = true;
}
}
if ($changed) {
self::$_storage = array_values(self::$_storage);
return true;
}
return false;
}
public static function getIterator(): \Traversable {
foreach (self::$_storage as $v) {
yield $v;
}
}
public static function has(Element $element) {
foreach (self::$_storage as $v) {
if ($v->isSameNode($element)) {
return true;
}
}
return false;
}
public static function set(Element $element) {
if (!self::has($element)) {
self::$_storage[] = $element;
return true;
}
return false;
}
}

11
lib/DOM/ProcessingInstruction.php

@ -1,11 +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;
class ProcessingInstruction extends \DOMProcessingInstruction {
use LeafNode, Moonwalk, ToString;
}

29
lib/DOM/TemplateElement.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;
/** Class specifically for template elements to handle its content property. */
class TemplateElement extends Element {
public $content = null;
public function __construct(Document $ownerDocument, string $qualifiedName, ?string $value = null, string $namespace = '') {
parent::__construct($qualifiedName, $value, $namespace);
// Elements that are created by their constructor in PHP aren't owned by any
// document and are readonly until owned by one. Temporarily append to a
// document fragment so the element will be owned by the supplied owner
// document.
$frag = $ownerDocument->createDocumentFragment();
$frag->appendChild($this);
$frag->removeChild($this);
unset($frag);
}
public function __destruct() {
ElementMap::delete($this);
}
}

317
lib/DOM/TokenList.php

@ -1,317 +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;
class TokenList implements \ArrayAccess, \Countable, \Iterator {
use MagicProperties;
protected $localName;
protected $element;
protected $_length = 0;
protected $position = 0;
# A DOMTokenList object has an associated token set (a set), which is initially
# empty.
protected $tokenSet = [];
private const ASCII_WHITESPACE_REGEX = '/[\t\n\x0c\r ]+/';
public function __get_length(): int {
return $this->_length;
}
public function __get_value(): string {
return $this->__toString();
}
public function __set_value(string $value) {
$this->tokenSet = $this->parseOrderedSet($value);
$this->_length = count($this->tokenSet);
}
public function __construct(\DOMElement $element, string $attributeLocalName) {
# A DOMTokenList object also has an associated element and an attribute’s local
# name.
# When a DOMTokenList object is created, then:
#
# 1. Let element be associated element.
// Using a weak reference here to prevent a circular reference.
$this->element = \WeakReference::create($element);
# 2. Let localName be associated attribute’s local name.
$this->localName = $attributeLocalName;
# 3. Let value be the result of getting an attribute value given element and
# localName.
$value = $element->getAttribute($attributeLocalName);
# 4. Run the attribute change steps for element, localName, value, value, and
# null.
$this->attributeChange($attributeLocalName, $value, $value);
}
public function add(...$tokens) {
# 1. For each token in tokens:
foreach ($tokens as $token) {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
}
# 2. For each token in tokens, append token to this’s token set.
foreach ($tokens as $token) {
if (!in_array($token, $this->tokenSet)) {
// The spec does not say to trim, but browsers do.
$this->tokenSet[] = trim($token);
$this->_length++;
}
}
# 3. Run the update steps.
$this->update();
}
public function contains(string $token): bool {
return (in_array($token, $this->tokenSet));
}
public function count(): int {
return $this->_length;
}
public function current() {
return $this->item($this->position);
}
public function item(int $index): string {
return $this->tokenSet[$index];
}
public function key() {
return $this->position;
}
public function next() {
++$this->position;
}
public function rewind() {
$this->position = 0;
}
public function offsetExists($offset) {
return $this->contains($offset);
}
public function offsetGet($offset): string {
return $this->item($offset);
}
public function offsetSet($offset, $value) {
$this->add($offset);
}
public function offsetUnset($offset) {
$this->remove($offset);
}
public function remove(...$tokens) {
# 1. For each token in tokens:
foreach ($tokens as $token) {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
}
# For each token in tokens, remove token from this’s token set.
$changed = false;
foreach ($tokens as $token) {
if (in_array($token, $this->tokenSet)) {
unset($this->tokenSet[$token]);
$this->_length--;
$changed = true;
}
}
if ($changed) {
$this->tokenSet = array_values($this->tokenSet);
}
# 3. Run the update steps.
$this->update();
}
public function replace(string $token, string $newToken): bool {
# 1. If either token or newToken is the empty string, then throw a "SyntaxError"
# DOMException.
if ($token === '' || $newToken === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If either token or newToken contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token) || preg_match(Data::WHITESPACE_REGEX, $newToken)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
// The spec does not say to trim, but browsers do.
$token = trim($token);
$newToken = trim($token);
# 3. If this’s token set does not contain token, then return false.
if (!isset($this->tokenSet[$token])) {
return false;
}
# 4. Replace token in this’s token set with newToken.
$index = array_search($token, $this->tokenSet);
$this->tokenSet[$index] = $newToken;
# 5. Run the update steps.
$this->update();
# 6. Return true.
return true;
}
public function supports(string $token): bool {
# 1. Let result be the return value of validation steps called with token.
# 2. Return result.
#
# A DOMTokenList object’s validation steps for a given token are:
#
# 1. If the associated attribute’s local name does not define supported tokens,
# throw a TypeError.
# 2. Let lowercase token be a copy of token, in ASCII lowercase.
# 3. If lowercase token is present in supported tokens, return true.
# 4. Return false.
// This class is presently only used for Element::classList, and it supports any
// valid class name as a token. So, there's nothing to do here at the moment.
// Just return true.
return true;
}
public function toggle(string $token, ?bool $force = false): bool {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
# 3. If this’s token set[token] exists, then:
if (isset($this->tokenSet[$token])) {
# 1. If force is either not given or is false, then remove token from this’s
# token set, run the update steps and return false.
if (!$force) {
$this->remove($token);
return false;
}
# 2. Return true.
return true;
}
# 4. Otherwise, if force not given or is true, append token to this’s token set,
# run the update steps, and return true.
else {
$this->add($token);
return true;
}
# 5. Return false.
return false;
}
public function valid() {
return array_key_exists($this->position, $this->tokenSet);
}
protected function attributeChange(string $localName, ?string $oldValue = null, ?string $value = null, ?string $namespace = null) {
# A DOMTokenList object has these attribute change steps for its associated
# element:
#
# 1. If localName is associated attribute’s local name, namespace is null, and
# value is null, then empty token set.
if ($localName !== $this->localName || $namespace !== null) {
return;
}
if ($value === null) {
$this->tokenSet = [];
$this->tokenKeys = [];
$this->_length = 0;
}
# 2. Otherwise, if localName is associated attribute’s local name, namespace is
# null, then set token set to value, parsed.
else {
$this->tokenSet = $this->parseOrderedSet($value);
$this->_length = count($this->tokenSet);
}
}
protected function parseOrderedSet(string $input) {
if ($input === '') {
return [];
}
# The ordered set parser takes a string input and then runs these steps:
#
# 1. Let inputTokens be the result of splitting input on ASCII whitespace.
// There isn't a Set object in php, so make sure all the tokens are unique.
$inputTokens = array_unique(preg_split(Data::WHITESPACE_REGEX, $input));
# 2. Let tokens be a new ordered set.
# 3. For each token in inputTokens, append token to tokens.
# 4. Return tokens.
// There isn't a Set object in php, so just return the uniqued input tokens.
return $inputTokens;
}
protected function update() {
# A DOMTokenList object’s update steps are:
#
# 1. If the associated element does not have an associated attribute and token
# set is empty, then return.
// Not sure what this is about. This class is constructed with a provided
// associated element and attribute; there is no need to do this.
# 2. Set an attribute value for the associated element using associated
# attribute’s local name and the result of running the ordered set serializer
# for token set.
$element = $this->element->get();
$class = $element->ownerDocument->createAttribute($this->localName);
$class->value = $this->__toString();
$element->setAttributeNode($class);
}
public function __toString(): string {
# The ordered set serializer takes a set and returns the concatenation of set
# using U+0020 SPACE.
return implode(' ', $this->tokenSet);
}
}

104
lib/DOM/traits/ContainerNode.php

@ -1,104 +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;
// Node in the DOM spec is dirty. Many nodes which inherit from it inherit
// methods it cannot use which all check for this and throw exceptions. This is
// for nodes which DO have child nodes.
trait ContainerNode {
use Node;
public function appendChild($node) {
$this->preInsertionValidity($node);
$result = parent::appendChild($node);
if ($result !== false && $result instanceof TemplateElement) {
if ($result instanceof TemplateElement) {
ElementMap::set($result);
}
}
return $result;
}
public function insertBefore($node, $child = null) {
$this->preInsertionValidity($node, $child);
$result = parent::insertBefore($node, $child);
if ($result !== false) {
if ($result instanceof TemplateElement) {
ElementMap::set($result);
}
if ($child instanceof TemplateElement) {
ElementMap::delete($child);
}
}
return $result;
}
public function removeChild($child) {
$result = parent::removeChild($child);
if ($result !== false && $result instanceof TemplateElement) {
ElementMap::delete($child);
}
return $result;
}
public function replaceChild($node, $child) {
$result = parent::replaceChild($node, $child);
if ($result !== false) {
if ($result instanceof TemplateElement) {
ElementMap::set($child);
}
if ($child instanceof TemplateElement) {
ElementMap::delete($child);
}
}
return $result;
}
protected function preInsertionValidity(\DOMNode $node, ?\DOMNode $child = null) {
// "parent" in the spec comments below is $this
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
# 2. If node is a host-including inclusive ancestor of parent, then throw a
# "HierarchyRequestError" DOMException.
#
# An object A is a host-including inclusive ancestor of an object B, if either
# A is an inclusive ancestor of B, or if B’s root has a non-null host and A is a
# host-including inclusive ancestor of B’s root’s host.
// DEVIATION: The baseline for this library is PHP 7.1, and without
// WeakReferences we cannot add a host property to DocumentFragment to check
// against.
// This is handled just fine by PHP's DOM.
# 3. If child is non-null and its parent is not parent, then throw a
# "NotFoundError" DOMException.
// This is handled just fine by PHP's DOM.
# 4. If node is not a DocumentFragment, DocumentType, Element, Text,
# ProcessingInstruction, or Comment node, then throw a "HierarchyRequestError"
# DOMException.
if (!$node instanceof DocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof Element && !$node instanceof Text && !$node instanceof ProcessingInstruction && !$node instanceof Comment) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 5. If either node is a Text node and parent is a document, or node is a
# doctype and parent is not a document, then throw a "HierarchyRequestError"
# DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
# 6. If parent is a document, and any of the statements below, switched on node,
# are true, then throw a "HierarchyRequestError" DOMException.
// Handled by the Document class.
}
}

57
lib/DOM/traits/DocumentOrElement.php

@ -1,57 +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;
// This exists because the DOM spec for some stupid reason doesn't give
// DocumentFragment some methods.
trait DocumentOrElement {
public function getElementsByClassName(string $classNames): \DOMNodeList {
# The list of elements with class names classNames for a node root is the
# HTMLCollection returned by the following algorithm:
// DEVIATION: There's no HTMLCollection. The result will be a DOMNodeList
// instead. It is, fortunately, almost exactly the same thing anyway.
# 1. Let classes be the result of running the ordered set parser on classNames.
#
## The ordered set parser takes a string input and then runs these steps:
##
## 1. Let inputTokens be the result of splitting input on ASCII whitespace.
// There isn't a Set object in php, so make sure all the tokens are unique.
$inputTokens = ($classNames !== '') ? array_unique(preg_split(Data::WHITESPACE_REGEX, $classNames)) : [];
$isDocument = ($this instanceof Document);
$document = ($isDocument) ? $this : $this->ownerDocument;
## 2. Let tokens be a new ordered set.
## 3. For each token in inputTokens, append token to tokens.
## 4. Return tokens.
// There isn't a Set object in php, so just use the uniqued input tokens.
# 2. If classes is the empty set, return an empty HTMLCollection.
// DEVIATION: We can't do that, so let's create a bogus Xpath query instead.
if ($inputTokens === []) {
$ook = $document->createElement('ook');
$query = $document->xpath->query('//eek', $ook);
unset($ook);
return $query;
}
# 3. Return a HTMLCollection rooted at root, whose filter matches descendant
# elements that have all their classes in classes.
#
# The comparisons for the classes must be done in an ASCII case-insensitive manner
# if root’s node document’s mode is "quirks"; otherwise in an identical to manner.
// DEVIATION: Since we can't just create a \DOMNodeList we must instead query the document with XPath with the root element to get a list.
$query = '//*';
foreach ($inputTokens as $token) {
$query .= "[@class=\"$token\"]";
}
return ($isDocument) ? $document->xpath->query($query) : $document->xpath->query($query, $this);
}
}

56
lib/DOM/traits/EscapeString.php

@ -1,56 +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;
use MensBeam\Intl\Encoding\UTF8;
trait EscapeString {
protected function escapeString(string $string, bool $attribute = false): string {
# Escaping a string (for the purposes of the algorithm above) consists of
# running the following steps:
# 1. Replace any occurrence of the "&" character by the string "&amp;".
# 2. Replace any occurrences of the U+00A0 NO-BREAK SPACE character by the
# string "&nbsp;".
$string = str_replace(['&', "\u{A0}"], ['&amp;', '&nbsp;'], $string);
# 3. If the algorithm was invoked in the attribute mode, replace any
# occurrences of the """ character by the string "&quot;".
# 4. If the algorithm was not invoked in the attribute mode, replace any
# occurrences of the "<" character by the string "&lt;", and any
# occurrences of the ">" character by the string "&gt;".
return ($attribute) ? str_replace('"', '&quot;', $string) : str_replace(['<', '>'], ['&lt;', '&gt;'], $string);
}
protected function coerceName(string $name): string {
// This matches the inverse of the production of NameChar in XML 1.0,
// with the added exclusion of ":" from allowed characters
// See https://www.w3.org/TR/REC-xml/#NT-NameStartChar
preg_match_all('/[^\-\.0-9\x{B7}\x{300}-\x{36F}\x{203F}-\x{2040}A-Za-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}]/u', $name, $m);
foreach (array_unique($m[0], \SORT_STRING) as $c) {
$o = (new UTF8($c))->nextCode();
$esc = "U".str_pad(strtoupper(dechex($o)), 6, "0", \STR_PAD_LEFT);
$name = str_replace($c, $esc, $name);
}
// Apply stricter rules to the first character
if (preg_match('/^[^A-Za-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}]/u', $name, $m)) {
$c = $m[0];
$o = (new UTF8($c))->nextCode();
$esc = "U".str_pad(strtoupper(dechex($o)), 6, "0", \STR_PAD_LEFT);
$name = $esc.substr($name, strlen($c));
}
return $name;
}
protected function uncoerceName(string $name): string {
preg_match_all('/U[0-9A-F]{6}/', $name, $m);
foreach (array_unique($m[0], \SORT_STRING) as $o) {
$c = UTF8::encode(hexdec(substr($o, 1)));
$name = str_replace($o, $c, $name);
}
return $name;
}
}

31
lib/DOM/traits/LeafNode.php

@ -1,31 +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;
// Node in the DOM spec is dirty. Many nodes which inherit from it inherit
// methods it cannot use which all check for this and throw exceptions. This is
// for nodes which DO NOT have child nodes.
trait LeafNode {
use Node;
public function appendChild($node) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
public function insertBefore($node, $child = null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
public function removeChild($child) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
public function replaceChild($node, $child) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}

64
lib/DOM/traits/MagicProperties.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;
/**
* Getters and setters in PHP sucks. Instead of having getter and setter
* function types for classes we instead have the __get and __set magic methods
* to handle all properties. Not only are they unwieldy to use when you have
* many properties they also become difficult to handle when inheriting where
* traits are involved. This trait attempts to create hackish getter and setter
* functions that can be extended by simple inheritance.
*/
trait MagicProperties {
public function __get(string $name) {
// If a getter method exists return it. Otherwise, trigger a property does not
// exist fatal error.
$methodName = $this->getMagicPropertyMethodName($name);
if (!method_exists($this, $methodName)) {
trigger_error("Property \"$name\" does not exist", \E_USER_ERROR);
}
return call_user_func([ $this, $methodName ]);
}
public function __isset(string $name): bool {
return (method_exists($this, $this->getMagicPropertyMethodName($name)));
}
public function __set(string $name, $value) {
// If a setter method exists return that.
$methodName = $this->getMagicPropertyMethodName($name, false);
if (method_exists($this, $methodName)) {
call_user_func([ $this, $methodName ], $value);
return;
}
// Otherwise, if a getter exists then trigger a readonly property fatal error.
// Finally, if a getter doesn't exist trigger a property does not exist fatal
// error.
if (method_exists($this, $this->getMagicPropertyMethodName($name))) {
trigger_error("Cannot write readonly property \"$name\"", \E_USER_ERROR);
} else {
trigger_error("Property \"$name\" does not exist", \E_USER_ERROR);
}
}
public function __unset(string $name) {
$methodName = $this->getMagicPropertyMethodName($name, false);
if (!method_exists($this, $methodName)) {
trigger_error("Cannot write readonly property \"$name\"", \E_USER_ERROR);
}
call_user_func([ $this, $methodName ], null);
}
private function getMagicPropertyMethodName(string $name, bool $get = true): string {
return "__" . (($get) ? 'get' : 'set') . "_{$name}";
}
}

40
lib/DOM/traits/Moonwalk.php

@ -1,40 +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;
trait Moonwalk {
/** Generator which walks up the DOM. Nonstandard. */
public function moonwalk(?\Closure $filter = null): \Generator {
return $this->moonwalkGenerator($this, $filter);
}
private function moonwalkGenerator(\DOMNode $node, ?\Closure $filter = null) {
do {
while (true) {
if ($filter === null || $filter($node)) {
yield $node;
}
// If node is an instance of DocumentFragment then it might be the content
// fragment of a template element, so iterate through all template elements
// stored in the element map and see if node is the fragment of one of the
// templates; if it is change node to the template element and reprocess. Magic!
// Can walk backwards THROUGH templates!
if ($node instanceof DocumentFragment) {
foreach (ElementMap::getIterator() as $element) {
if ($element->ownerDocument->isSameNode($node->ownerDocument) && $element instanceof TemplateElement && $element->content->isSameNode($node)) {
$node = $element;
continue;
}
}
}
break;
}
} while ($node = $node->parentNode);
}
}

27
lib/DOM/traits/MoonwalkShallow.php

@ -1,27 +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;
trait MoonwalkShallow {
/**
* Generator which just walks through a node's child nodes in reverse.
* Nonstandard.
*
* @param ?\Closure $filter - An optional closure to use to filter
*/
public function moonwalkShallow(?\Closure $filter = null): \Generator {
$node = (!$this instanceof TemplateElement) ? $this : $this->content;
$childNodesLength = $node->childNodes->length;
for ($childNodesLength = $node->childNodes->length, $i = $childNodesLength - 1; $i >= 0; $i--) {
$child = $node->childNodes[$i];
if ($filter === null || $filter($child)) {
yield $child;
}
}
}
}

21
lib/DOM/traits/Node.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;
// Extensions to PHP's DOM cannot inherit from an extended Node parent, so a
// trait is the next best thing...
trait Node {
// Disable C14N
public function C14N($exclusive = null, $with_comments = null, ?array $xpath = null, ?array $ns_prefixes = null): bool {
return false;
}
// Disable C14NFile
public function C14NFile($uri, $exclusive = null, $with_comments = null, ?array $xpath = null, ?array $ns_prefixes = null): bool {
return false;
}
}

243
lib/DOM/traits/ParentNode.php

@ -1,243 +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;
if (version_compare(\PHP_VERSION, '8.0', '>=')) {
# 4.2.6. Mixin ParentNode
trait ParentNode {
public function __get_children(): \DOMNodeList {
# The children getter steps are to return an HTMLCollection collection rooted at
# this matching only element children.
// DEVIATION: HTMLCollection doesn't exist in PHP's DOM, and \DOMNodeList is
// almost identical; so, using that. PHP's DOM doesn't provide the end user any
// way to create a \DOMNodeList from scratch, so going to cheat and use XPath to
// make one for us.
$isDocument = ($this instanceof Document);
$document = ($isDocument) ? $this : $this->ownerDocument;
return $document->xpath->query('//*', (!$isDocument) ? $this : null);
}
public function replaceChildren(...$nodes) {
# The replaceChildren(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Ensure pre-insertion validity of node into this before null.
$this->preInsertionValidity($node);
# 3. Replace all with node within this.
#
# To replace all with a node within a parent, run these steps:
# 1. Let removedNodes be parent’s children.
$removedNodes = $this->childNodes;
# 2. Let addedNodes be the empty set.
$addedNodes = [];
# 3. If node is a DocumentFragment node, then set addedNodes to node’s children.
if ($node instanceof DocumentFragment) {
$addedNodes = $node->childNodes;
}
# 4. Otherwise, if node is non-null, set addedNodes to « node ».
elseif ($node !== null) {
$addedNodes = node;
}
# 5. Remove all parent’s children, in tree order, with the suppress observers
# flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
while ($this->hasChildNodes()) {
$this->removeChild($this->firstChild);
}
# 6. If node is non-null, then insert node into parent before null with the
# suppress observers flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
if ($node !== null) {
$this->appendChild($node);
}
# 7. If either addedNodes or removedNodes is not empty, then queue a tree
# mutation record for parent with addedNodes, removedNodes, null, and null.
// DEVIATION: There is no scripting in this implementation
}
private function convertNodesToNode(array $nodes): \DOMNode {
# To convert nodes into a node, given nodes and document, run these steps:
# 1. Let node be null.
# 2. Replace each string in nodes with a new Text node whose data is the string
# and node document is document.
# 3. If nodes contains one node, then set node to nodes[0].
# 4. Otherwise, set node to a new DocumentFragment node whose node document is
# document, and then append each node in nodes, if any, to it.
// The spec would have us iterate through the provided nodes and then iterate
// through them again to append. Let's optimize this a wee bit, shall we?
$document = ($this instanceof Document) ? $this : $this->ownerDocument;
$node = ($node->length > 1) ? $document->createDocumentFragment() : null;
foreach ($nodes as &$n) {
// Can't do union types until PHP 8... OTL
if (!$n instanceof \DOMNode && !is_string($n)) {
trigger_error(sprintf("Uncaught TypeError: %s::%s(): Argument #1 (\$%s) must be of type \DOMNode|string, %s given", __CLASS__, __METHOD__, 'nodes', gettype($n)));
}
if (is_string($n)) {
$n = $this->ownerDocument->createTextNode($n);
}
if ($node !== null) {
$node->appendChild($n);
} else {
$node = $n;
}
}
return $node;
}
}
} else {
trait ParentNode {
public function __get_childElementCount(): int {
# The childElementCount getter steps are to return the number of children of
# this that are elements.
$count = 0;
foreach ($this->childNodes as $child) {
if ($child instanceof Element) {
$count++;
}
}
return $count;
}
public function __get_children(): \DOMNodeList {
# The children getter steps are to return an HTMLCollection collection rooted at
# this matching only element children.
// DEVIATION: HTMLCollection doesn't exist in PHP's DOM, and \DOMNodeList is
// almost identical; so, using that. PHP's DOM doesn't provide the end user any
// way to create a \DOMNodeList from scratch, so going to cheat and use XPath to
// make one for us.
$isDocument = ($this instanceof Document);
$document = ($isDocument) ? $this : $this->ownerDocument;
return $document->xpath->query('//*', (!$isDocument) ? $this : null);
}
public function __get_firstElementChild(): Element {
# The firstElementChild getter steps are to return the first child that is an
# element; otherwise null.
foreach ($this->childNodes as $child) {
if ($child instanceof Element) {
return $child;
}
}
return null;
}
public function __get_lastElementChild(): Element {
# The lastElementChild getter steps are to return the last child that is an
# element; otherwise null.
for ($i = $this->childNodes->length - 1; $i >= 0; $i--) {
$child = $this->childNodes->item($i);
if ($child instanceof Element) {
return $child;
}
}
return null;
}
public function append(...$nodes): void {
# The append(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Append node to this.
$this->appendChild($node);
}
public function prepend(...$nodes): void {
# The prepend(nodes) method steps are:
#
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Pre-insert node into this before this’s first child.
$this->insertBefore($node, $this->firstChild);
}
public function replaceChildren(...$nodes) {
# The replaceChildren(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 2. Ensure pre-insertion validity of node into this before null.
$this->preInsertionValidity($node);
# 3. Replace all with node within this.
#
# To replace all with a node within a parent, run these steps:
# 1. Let removedNodes be parent’s children.
$removedNodes = $this->childNodes;
# 2. Let addedNodes be the empty set.
$addedNodes = [];
# 3. If node is a DocumentFragment node, then set addedNodes to node’s children.
if ($node instanceof DocumentFragment) {
$addedNodes = $node->childNodes;
}
# 4. Otherwise, if node is non-null, set addedNodes to « node ».
elseif ($node !== null) {
$addedNodes = node;
}
# 5. Remove all parent’s children, in tree order, with the suppress observers
# flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
while ($this->hasChildNodes()) {
$this->removeChild($this->firstChild);
}
# 6. If node is non-null, then insert node into parent before null with the
# suppress observers flag set.
// DEVIATION: There is no scripting in this implementation, so cannnot set
// suppress observers flag.
if ($node !== null) {
$this->appendChild($node);
}
# 7. If either addedNodes or removedNodes is not empty, then queue a tree
# mutation record for parent with addedNodes, removedNodes, null, and null.
// DEVIATION: There is no scripting in this implementation
}
private function convertNodesToNode(array $nodes): \DOMNode {
# To convert nodes into a node, given nodes and document, run these steps:
# 1. Let node be null.
# 2. Replace each string in nodes with a new Text node whose data is the string
# and node document is document.
# 3. If nodes contains one node, then set node to nodes[0].
# 4. Otherwise, set node to a new DocumentFragment node whose node document is
# document, and then append each node in nodes, if any, to it.
// The spec would have us iterate through the provided nodes and then iterate
// through them again to append. Let's optimize this a wee bit, shall we?
$document = ($this instanceof Document) ? $this : $this->ownerDocument;
$node = ($node->length > 1) ? $document->createDocumentFragment() : null;
foreach ($nodes as &$n) {
// Can't do union types until PHP 8... OTL
if (!$n instanceof \DOMNode && !is_string($n)) {
trigger_error(sprintf("Uncaught TypeError: %s::%s(): Argument #1 (\$%s) must be of type \DOMNode|string, %s given", __CLASS__, __METHOD__, 'nodes', gettype($n)));
}
if (is_string($n)) {
$n = $this->ownerDocument->createTextNode($n);
}
if ($node !== null) {
$node->appendChild($n);
} else {
$node = $n;
}
}
return $node;
}
}
}

15
lib/DOM/traits/ToString.php

@ -1,15 +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;
trait ToString {
public function __toString(): string {
$frag = $this->ownerDocument->createDocumentFragment();
$frag->appendChild($this->cloneNode(true));
return $this->ownerDocument->serialize($frag);
}
}

31
lib/DOM/traits/Walk.php

@ -1,31 +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;
trait Walk {
/** Generator which walks down the DOM. Nonstandard. */
public function walk(?\Closure $filter = null): \Generator {
return $this->walkGenerator($this, $filter);
}
private function walkGenerator(\DOMNode $node, ?\Closure $filter = null) {
if ($filter === null || $filter($node)) {
yield $node;
}
if ($node instanceof TemplateElement) {
$node = $node->content;
}
if ($node->hasChildNodes()) {
$children = $node->childNodes;
foreach ($children as $c) {
yield from $this->walkGenerator($c, $filter);
}
}
}
}

24
lib/DOM/traits/WalkShallow.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;
trait WalkShallow {
/**
* Generator which just walks through a node's child nodes. Nonstandard.
*
* @param ?\Closure $filter - An optional closure to use to filter
*/
public function walkShallow(?\Closure $filter = null): \Generator {
$node = (!$this instanceof TemplateElement) ? $this : $this->content;
foreach ($node->childNodes as $child) {
if ($filter === null || $filter($child)) {
yield $child;
}
}
}
}

31
lib/Parser.php

@ -6,6 +6,16 @@
declare(strict_types=1);
namespace MensBeam\HTML;
use MensBeam\HTML\Parser\Charset;
use MensBeam\HTML\Parser\Data;
use MensBeam\HTML\Parser\ParseError;
use MensBeam\HTML\Parser\ParseErrorDummy;
use MensBeam\HTML\Parser\OpenElementsStack;
use MensBeam\HTML\Parser\TemplateInsertionModesStack;
use MensBeam\HTML\Parser\Tokenizer;
use MensBeam\HTML\Parser\TreeBuilder;
use MensBeam\HTML\Parser\Output;
class Parser {
public static $fallbackEncoding = "windows-1252";
@ -30,7 +40,7 @@ class Parser {
self::XMLNS_NAMESPACE => "xmlns",
];
public static function parse(string $data, ?\DOMDocument $document = null, ?string $encodingOrContentType = null, ?\DOMElement $fragmentContext = null, ?String $file = null): \DOMDocument {
public static function parse(string $data, ?string $encodingOrContentType = null, ?\DOMDocument $document = null, ?\DOMElement $fragmentContext = null, ?int $fragmentQuirks = null, ?String $file = null): Output {
// Initialize the various classes needed for parsing
$document = $document ?? new \DOMDocument;
if ((error_reporting() & \E_USER_WARNING)) {
@ -43,7 +53,7 @@ class Parser {
$stack = new OpenElementsStack($fragmentContext);
$tokenizer = new Tokenizer($decoder, $stack, $errorHandler);
$tokenList = $tokenizer->tokenize();
$treeBuilder = new TreeBuilder($document, $decoder, $tokenizer, $tokenList, $errorHandler, $stack, new TemplateInsertionModesStack, $fragmentContext);
$treeBuilder = new TreeBuilder($document, $decoder, $tokenizer, $tokenList, $errorHandler, $stack, new TemplateInsertionModesStack, $fragmentContext, $fragmentQuirks);
// Override error handling
$errorHandler->setHandler();
try {
@ -53,19 +63,22 @@ class Parser {
// Restore error handling
$errorHandler->clearHandler();
}
return $document;
// prepare the output
$out = new Output;
$out->document = $document;
$out->encoding = $decoder->encoding;
$out->quirksMode = $treeBuilder->quirksMode;
return $out;
}
public static function parseFragment(string $data, ?\DOMDocument $document = null, ?string $encodingOrContentType = null, ?\DOMElement $fragmentContext = null, ?String $file = null): DocumentFragment {
public static function parseFragment(\DOMElement $fragmentContext, ?int $fragmentQuirks, string $data, ?string $encodingOrContentType = null, ?\DOMDocument $document = null, ?String $file = null): \DOMDocumentFragment {
// Create the requisite parsing context if none was supplied
$document = $document ?? new \DOMDocument;
$tempDocument = new \DOMDocument;
$fragmentContext = $fragmentContext ?? $document->createElement("div");
// parse the fragment into the temporary document
self::parse($data, $tempDocument, $encodingOrContentType, $fragmentContext, $file);
self::parse($data, $encodingOrContentType, $document, $fragmentContext, $fragmentQuirks, $file);
// extract the nodes from the temp document into a fragment
$fragment = $document->createDocumentFragment();
foreach ($tempDocument->documentElement->childNodes as $node) {
$fragment = $fragmentContext->ownerDocument->createDocumentFragment();
foreach ($document->documentElement->childNodes as $node) {
$node = $document->importNode($node, true);
$fragment->appendChild($node);
}

4
lib/Parser/ActiveFormattingElementsList.php

@ -4,7 +4,9 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
use MensBeam\HTML\Parser;
# 8.2.3.3. The list of active formatting elements
# Initially, the list of active formatting elements is empty. It is used to

2
lib/Parser/CharacterReference.php

@ -1,6 +1,6 @@
<?php
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
// This file is machine-generated
// DO NOT MODIFY

2
lib/Parser/Charset.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
use MensBeam\Intl\Encoding;
use MensBeam\Mime\MimeType;

4
lib/Parser/Data.php

@ -4,10 +4,10 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
use MensBeam\HTML\Parser;
use MensBeam\Intl\Encoding;
use MensBeam\Intl\Encoding\Encoding as EncodingEncoding;
class Data {
use ParseErrorEmitter;

2
lib/Parser/Exception.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
class Exception extends \Exception {
const INVALID_CODE = 100;

2
lib/Parser/LoopException.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
class LoopException extends \Exception {
}

2
lib/Parser/NameCoercion.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
use MensBeam\Intl\Encoding\UTF8;

2
lib/Parser/NotImplementedException.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
class NotImplementedException extends \Exception {
}

4
lib/Parser/OpenElementsStack.php

@ -4,7 +4,9 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
use MensBeam\HTML\Parser;
class OpenElementsStack extends Stack {
protected const IMPLIED_END_TAGS = [

10
lib/DOM/Text.php → lib/Parser/Output.php

@ -4,8 +4,10 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
class Text extends \DOMText {
use LeafNode, Moonwalk, ToString;
}
class Output {
public $document;
public $encoding;
public $quirksMode;
}

2
lib/Parser/ParseError.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
class ParseError {
// tokenization parse errors; these have been standardized

2
lib/Parser/ParseErrorDummy.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
class ParseErrorDummy extends ParseError {
public function setHandler() {

2
lib/Parser/ParseErrorEmitter.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
trait ParseErrorEmitter {
/** @var ParseError $errorHandler */

2
lib/Parser/Stack.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
abstract class Stack implements \ArrayAccess, \Countable, \IteratorAggregate {
protected $_storage = [];

2
lib/Parser/TemplateInsertionModesStack.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
class TemplateInsertionModesStack extends Stack {
public function __get($property) {

2
lib/Parser/Token.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
abstract class Token {}

3
lib/Parser/Tokenizer.php

@ -4,8 +4,9 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
use MensBeam\HTML\Parser;
use MensBeam\Intl\Encoding\UTF8;
class Tokenizer {

8
lib/Parser/TreeBuilder.php

@ -4,7 +4,9 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
use MensBeam\HTML\Parser;
class TreeBuilder {
use ParseErrorEmitter, NameCoercion;
@ -43,8 +45,8 @@ class TreeBuilder {
protected $mangledElements = false;
/** @var bool Flag used to track whether name mangling has been performed for attributes; this is a minor optimization */
protected $mangledAttributes = false;
/** @var int The quirks-mode setting of the document being parsed */
protected $quirksMode = Parser::NO_QUIRKS_MODE;
/** @var int The quirks-mode setting of the document being built */
public $quirksMode = Parser::NO_QUIRKS_MODE;
// Constants used for insertion modes
protected const INITIAL_MODE = 0;

2
lib/Parser/ctype.php

@ -3,7 +3,7 @@
* Copyright 2017 , Dustin Wilson, J. King et al.
* See LICENSE and AUTHORS files for details */
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
// This file adds shims for matching single characters
// using the same API as the ctype extension, if the

17
package.json

@ -1,17 +0,0 @@
{
"devDependencies": {
"autoprefixer": "^9.6.1",
"postcss": "^7.0.0",
"postcss-cli": "^7.1.1",
"postcss-color-function": "^4.1.0",
"postcss-csso": "^4.0.0",
"postcss-custom-media": "^7.0.8",
"postcss-custom-properties": "^9.0.2",
"postcss-discard-comments": "^4.0.2",
"postcss-import": "^12.0.1",
"postcss-media-minmax": "^4.0.0",
"postcss-nested": "^4.1.2",
"postcss-sassy-mixins": "^2.1.0",
"postcss-scss": "^2.0.0"
}
}

17
postcss.config.js

@ -1,17 +0,0 @@
module.exports = ctx => ({
//map: ctx.options.map,
parser: 'postcss-scss',
//syntax: 'postcss-scss',
plugins: {
'postcss-import': { root: ctx.file.dirname },
'postcss-discard-comments': {},
'postcss-sassy-mixins': {},
'postcss-custom-media': {preserve: false},
'postcss-media-minmax': {},
'postcss-custom-properties': {preserve: false},
'postcss-color-function': {},
'postcss-nested': {},
'autoprefixer': {},
'postcss-csso': {},
}
})

2
tests/bootstrap.php

@ -4,7 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace MensBeam\HTML;
namespace MensBeam\HTML\Parser;
const NS_BASE = __NAMESPACE__."\\";
define(NS_BASE."BASE", dirname(__DIR__).DIRECTORY_SEPARATOR);

8
tests/cases/TestCharset.php

@ -6,10 +6,10 @@
declare(strict_types=1);
namespace MensBeam\HTML\TestCase;
use MensBeam\HTML\Charset;
use MensBeam\HTML\Parser\Charset;
/**
* @covers \MensBeam\HTML\Charset
* @covers \MensBeam\HTML\Parser\Charset
*/
class TestCharset extends \PHPUnit\Framework\TestCase {
/** @dataProvider provideCharsets */
@ -73,8 +73,8 @@ class TestCharset extends \PHPUnit\Framework\TestCase {
$tests = [];
$blacklist = [];
$files = new \AppendIterator();
$files->append(new \GlobIterator(\MensBeam\HTML\BASE."tests/html5lib-tests/encoding/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\BASE."tests/cases/encoding/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\Parser\BASE."tests/html5lib-tests/encoding/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\Parser\BASE."tests/cases/encoding/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
foreach ($files as $file) {
if (!in_array(basename($file), $blacklist)) {
$tests[] = $file;

318
tests/cases/TestDOM.php

@ -1,318 +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\TestCase;
use MensBeam\HTML\Document;
use MensBeam\HTML\Parser;
use MensBeam\HTML\TemplateElement;
class TestDOM extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideNamespacedElements
* @covers \MensBeam\HTML\Document::createElementNS
*/
public function testCreateNamespacedElements(?string $nsIn, string $nameIn, ?string $nsOut, string $local, string $prefix): void {
$d = new Document;
$e = $d->createElementNS($nsIn, $nameIn);
$this->assertSame($nsOut, $e->namespaceURI);
$this->assertSame($local, $e->localName);
$this->assertSame($prefix, $e->prefix);
}
public function provideNamespacedElements(): iterable {
return [
[null, "test", null, "test", ""],
[null, "test:test", null, "testU00003Atest", ""],
["http://www.w3.org/2000/svg", "svg", "http://www.w3.org/2000/svg", "svg", ""],
["http://www.w3.org/2000/svg", "svg:svg", "http://www.w3.org/2000/svg", "svg", "svg"],
["fake_ns", "test:test", "fake_ns", "test", "test"],
["fake_ns", "test:test:test", "fake_ns", "testU00003Atest", "test"],
["fake_ns", "te st:test", "fake_ns", "test", "teU000020st"],
[null, "9", null, "U000039", ""],
["http://www.w3.org/1999/xhtml", "test", null, "test", ""],
["http://www.w3.org/1999/xhtml", "TEST", null, "test", ""],
[null, "TEST", null, "test", ""],
["fake_ns", "TEST", "fake_ns", "TEST", ""],
["http://www.w3.org/2000/svg", "TEST", "http://www.w3.org/2000/svg", "TEST", ""],
["http://www.w3.org/1998/Math/MathML", "TEST", "http://www.w3.org/1998/Math/MathML", "TEST", ""],
];
}
/**
* @dataProvider provideBareElements
* @covers \MensBeam\HTML\Document::createElement
*/
public function testCreateBareElements(string $nameIn, $nameOut): void {
$d = new Document;
$e = $d->createElement($nameIn);
$this->assertNull($e->namespaceURI);
$this->assertSame("", $e->prefix);
$this->assertSame($nameOut, $e->localName);
}
public function provideBareElements(): iterable {
return [
["test", "test"],
["test:test", "testU00003Atest"],
["9", "U000039"],
["TEST", "test"],
];
}
/** @covers \MensBeam\HTML\Document::createElementNS */
public function testCreateTemplateElements(): void {
$d = new Document;
$t = $d->createElement("template");
$this->assertInstanceOf(TemplateElement::class, $t);
$this->assertNotNull($t->ownerDocument);
$t = $d->createElement("TEMPLATE");
$this->assertInstanceOf(TemplateElement::class, $t);
$this->assertNotNull($t->ownerDocument);
$t = $d->createElementNS(null, "template");
$this->assertInstanceOf(TemplateElement::class, $t);
$this->assertNotNull($t->ownerDocument);
$t = $d->createElementNS(null, "TEMPLATE");
$this->assertInstanceOf(TemplateElement::class, $t);
$this->assertNotNull($t->ownerDocument);
$t = $d->createElementNS("http://www.w3.org/1999/xhtml", "template");
$this->assertInstanceOf(TemplateElement::class, $t);
$this->assertNotNull($t->ownerDocument);
$t = $d->createElementNS("http://www.w3.org/1999/xhtml", "TEMPLATE");
$this->assertInstanceOf(TemplateElement::class, $t);
$this->assertNotNull($t->ownerDocument);
}
/**
* @dataProvider provideNamespacedAttributeCreations
* @covers \MensBeam\HTML\Document::createAttributeNS
*/
public function testCreateNamespacedAttributes(?string $nsIn, string $nameIn, string $local, string $prefix): void {
$d = new Document;
$d->appendChild($d->createElement("html"));
$a = $d->createAttributeNS($nsIn, $nameIn);
$this->assertSame($local, $a->localName);
$this->assertSame($nsIn, $a->namespaceURI);
$this->assertSame($prefix, $a->prefix);
}
public function provideNamespacedAttributeCreations(): iterable {
return [
[null, "test", "test", ""],
[null, "test:test", "testU00003Atest", ""],
[null, "test", "test", ""],
[null, "TEST:TEST", "TESTU00003ATEST", ""],
["fake_ns", "test", "test", ""],
["fake_ns", "test:test", "test", "test"],
["fake_ns", "TEST:TEST", "TEST", "TEST"],
["fake_ns", "test:test:test", "testU00003Atest", "test"],
["fake_ns", "TEST:TEST:TEST", "TESTU00003ATEST", "TEST"],
];
}
/**
* @dataProvider provideBareAttributeCreations
* @covers \MensBeam\HTML\Document::createAttribute
*/
public function testCreateBareAttributes(string $nameIn, string $nameOut): void {
$d = new Document;
$d->appendChild($d->createElement("html"));
$a = $d->createAttribute($nameIn);
$this->assertSame($nameOut, $a->name);
$this->assertNull($a->namespaceURI);
}
public function provideBareAttributeCreations(): iterable {
return [
["test", "test"],
["test:test", "testU00003Atest"],
["TEST", "TEST"],
["TEST:TEST", "TESTU00003ATEST"],
];
}
/**
* @dataProvider provideNamespacedAttributeSettings
* @covers \MensBeam\HTML\Element::setAttributeNS
*/
public function testSetNamespoacedAttributes(?string $elementNS, ?string $attrNS, string $nameIn, string $nameOut): void {
$d = new Document;
$e = $d->createElementNS($elementNS, "test");
$this->assertSame(0, $e->attributes->length);
$e->setAttributeNS($attrNS, $nameIn, "test");
$this->assertSame(1, $e->attributes->length);
$a = $e->attributes[0];
$this->assertSame($nameOut, $a->nodeName);
$this->assertSame($attrNS, $a->namespaceURI);
}
public function provideNamespacedAttributeSettings(): iterable {
return [
[null, null, "test", "test"],
[null, null, "TEST", "test"],
["http://www.w3.org/1999/xhtml", null, "test", "test"],
["http://www.w3.org/1999/xhtml", null, "TEST", "test"],
[null, null, "test:test", "testU00003Atest"],
[null, null, "TEST:TEST", "testU00003Atest"],
["http://www.w3.org/1999/xhtml", null, "test:test", "testU00003Atest"],
["http://www.w3.org/1999/xhtml", null, "TEST:TEST", "testU00003Atest"],
[null, "http://www.w3.org/1999/xhtml", "test:test", "test:test"],
[null, "http://www.w3.org/1999/xhtml", "TEST:TEST", "TEST:TEST"],
["http://www.w3.org/1998/Math/MathML", null, "test", "test"],
["http://www.w3.org/1998/Math/MathML", null, "TEST", "TEST"],
[null, "http://www.w3.org/2000/xmlns/", "xmlns:xlink", "xmlns:xlink"],
[null, "http://www.w3.org/2000/xmlns/", "xmlns:XLINK", "xmlns:XLINK"],
[null, "fake_ns", "test:test:test", "test:testU00003Atest"],
[null, "fake_ns", "TEST:TEST:TEST", "TEST:TESTU00003ATEST"],
];
}
/**
* @dataProvider provideBareAttributeSettings
* @covers \MensBeam\HTML\Element::setAttribute
*/
public function testSetBareAttributes(?string $elementNS, string $nameIn, string $nameOut): void {
$d = new Document;
$e = $d->createElementNS($elementNS, "test");
$this->assertSame(0, $e->attributes->length);
$e->setAttribute($nameIn, "test");
$this->assertSame(1, $e->attributes->length);
$a = $e->attributes[0];
$this->assertSame($nameOut, $a->nodeName);
$this->assertNull($a->namespaceURI);
}
public function provideBareAttributeSettings(): iterable {
return [
[null, "test", "test"],
[null, "TEST", "test"],
["http://www.w3.org/1999/xhtml", "test", "test"],
["http://www.w3.org/1999/xhtml", "TEST", "test"],
[null, "test:test", "testU00003Atest"],
[null, "TEST:TEST", "testU00003Atest"],
["http://www.w3.org/1999/xhtml", "test:test", "testU00003Atest"],
["http://www.w3.org/1999/xhtml", "TEST:TEST", "testU00003Atest"],
["http://www.w3.org/1998/Math/MathML", "test", "test"],
["http://www.w3.org/1998/Math/MathML", "TEST", "TEST"],
];
}
/**
* @dataProvider provideAttributeNodeSettings
* @covers \MensBeam\HTML\Element::setAttributeNode
* @covers \MensBeam\HTML\Element::setAttributeNodeNS
*/
public function testSetAttributeNodes(bool $ns, ?string $elementNS, ?string $attrNS, string $name): void {
$d = new Document;
$e = $d->createElementNS($elementNS, "test");
$d->appendChild($e);
$this->assertSame(0, $e->attributes->length);
$a = $d->createAttributeNS($attrNS, $name);
if ($ns) {
$e->setAttributeNodeNS($a);
} else {
$e->setAttributeNode($a);
}
$this->assertSame(1, $e->attributes->length);
$a = $e->attributes[0];
$this->assertSame($name, $a->nodeName);
$this->assertSame($attrNS, $a->namespaceURI);
}
public function provideAttributeNodeSettings(): iterable {
return [
[true, null, null, "test"],
[true, null, null, "TEST"],
[true, "http://www.w3.org/1999/xhtml", null, "test"],
[true, "http://www.w3.org/1999/xhtml", null, "TEST"],
[true, null, null, "testU00003Atest"],
[true, null, null, "TESTU00003ATEST"],
[true, "http://www.w3.org/1999/xhtml", null, "testU00003Atest"],
[true, "http://www.w3.org/1999/xhtml", null, "TESTU00003ATEST"],
[true, null, "http://www.w3.org/1999/xhtml", "test:test"],
[true, null, "http://www.w3.org/1999/xhtml", "TEST:TEST"],
[true, "http://www.w3.org/1998/Math/MathML", null, "test"],
[true, "http://www.w3.org/1998/Math/MathML", null, "TEST"],
[true, null, "http://www.w3.org/2000/xmlns/", "xmlns:xlink"],
[true, null, "http://www.w3.org/2000/xmlns/", "xmlns:XLINK"],
[true, null, "fake_ns", "test:testU00003Atest"],
[true, null, "fake_ns", "TEST:TESTU00003ATEST"],
[false, null, null, "test"],
[false, null, null, "TEST"],
[false, "http://www.w3.org/1999/xhtml", null, "test"],
[false, "http://www.w3.org/1999/xhtml", null, "TEST"],
[false, null, null, "testU00003Atest"],
[false, null, null, "TESTU00003ATEST"],
[false, "http://www.w3.org/1999/xhtml", null, "testU00003Atest"],
[false, "http://www.w3.org/1999/xhtml", null, "TESTU00003ATEST"],
[false, null, "http://www.w3.org/1999/xhtml", "test:test"],
[false, null, "http://www.w3.org/1999/xhtml", "TEST:TEST"],
[false, "http://www.w3.org/1998/Math/MathML", null, "test"],
[false, "http://www.w3.org/1998/Math/MathML", null, "TEST"],
[false, null, "http://www.w3.org/2000/xmlns/", "xmlns:xlink"],
[false, null, "http://www.w3.org/2000/xmlns/", "xmlns:XLINK"],
[false, null, "fake_ns", "test:testU00003Atest"],
[false, null, "fake_ns", "TEST:TESTU00003ATEST"],
];
}
/**
* @covers \MensBeam\HTML\Element::hasAttribute
* @covers \MensBeam\HTML\Element::getAttribute
* @covers \MensBeam\HTML\Element::getAttributeNS
*/
public function testCheckForAttribute(): void {
$d = new Document;
$d->appendChild($d->createElement("html"));
$e = $d->documentElement;
$e->setAttribute("ook", "eek");
$e->setAttributeNS(Parser::XML_NAMESPACE, "xml:base", "http://example.com/");
$e->setAttributeNS(Parser::XMLNS_NAMESPACE, "xmlns:xlink", Parser::XLINK_NAMESPACE);
$e->setAttributeNS("fake_ns", "ook:eek", "ack");
// perform boolean tests
$this->assertFalse($e->hasAttribute("blah"));
$this->assertFalse($e->hasAttribute("OOK"));;
$this->assertFalse($e->hasAttribute("eek"));
$this->assertFalse($e->hasAttribute("ack"));
$this->assertTrue($e->hasAttribute("ook"));
$this->assertTrue($e->hasAttribute("xml:base"));
$this->assertTrue($e->hasAttribute("xmlns:xlink"));
$this->assertTrue($e->hasAttribute("ook:eek"));
$this->assertFalse($e->hasAttributeNS(null, "blah"));
$this->assertFalse($e->hasAttributeNS(null, "OOK"));
$this->assertFalse($e->hasAttributeNS(null, "eek"));
$this->assertTrue($e->hasAttributeNS(null, "ook"));
$this->assertTrue($e->hasAttributeNS(Parser::XML_NAMESPACE, "base"));
$this->assertTrue($e->hasAttributeNS(Parser::XMLNS_NAMESPACE, "xlink"));
$this->assertTrue($e->hasAttributeNS("fake_ns", "eek"));
// perform retrival tests
$this->assertNull($e->getAttribute("blah"));
$this->assertNull($e->getAttribute("OOK"));
$this->assertNull($e->getAttribute("eek"));
$this->assertNull($e->getAttribute("ack"));
$this->assertSame("eek", $e->getAttribute("ook"));
$this->assertSame("http://example.com/", $e->getAttribute("xml:base"));
$this->assertSame(Parser::XLINK_NAMESPACE, $e->getAttribute("xmlns:xlink"));
$this->assertSame("ack", $e->getAttribute("ook:eek"));
$this->assertNull($e->getAttributeNS(null, "blah"));
$this->assertNull($e->getAttributeNS(null, "OOK"));
$this->assertNull($e->getAttributeNS(null, "ack"));
$this->assertSame("eek", $e->getAttributeNS(null, "ook"));
$this->assertSame("http://example.com/", $e->getAttributeNS(Parser::XML_NAMESPACE, "base"));
$this->assertSame(Parser::XLINK_NAMESPACE, $e->getAttributeNS(Parser::XMLNS_NAMESPACE, "xlink"));
$this->assertSame("ack", $e->getAttributeNS("fake_ns", "eek"));
}
/** @covers \MensBeam\HTML\Element::__get */
public function testGetInnerAndOuterHtml(): void {
$d = new Document;
$d->appendChild($d->createElement("html"));
$d->documentElement->appendChild($d->createTextNode("OOK"));
$this->assertSame("OOK", $d->documentElement->innerHTML);
$this->assertSame("<html>OOK</html>", $d->documentElement->outerHTML);
$this->assertNull($d->documentElement->innerHtml);
$this->assertNull($d->documentElement->outerHtml);
}
}

138
tests/cases/TestSerializer.php

@ -1,138 +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\TestCase;
use MensBeam\HTML\Document;
use MensBeam\HTML\Parser;
/**
* @covers \MensBeam\HTML\Document
* @covers \MensBeam\HTML\DocumentFragment
* @covers \MensBeam\HTML\Element
* @covers \MensBeam\HTML\TemplateElement
* @covers \MensBeam\HTML\Comment
* @covers \MensBeam\HTML\Text
* @covers \MensBeam\HTML\ProcessingInstruction
*/
class TestSerializer extends \PHPUnit\Framework\TestCase {
/** @dataProvider provideStandardSerializerTests */
public function testStandardTreeTests(array $data, bool $fragment, string $exp): void {
$node = $this->buildTree($data, $fragment);
$this->assertSame($exp, (string) $node);
}
public function provideStandardSerializerTests(): iterable {
$blacklist = [];
$files = new \AppendIterator();
$files->append(new \GlobIterator(\MensBeam\HTML\BASE."tests/cases/serializer/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
foreach ($files as $file) {
$index = 0;
$l = 0;
if (!in_array(basename($file), $blacklist)) {
$lines = array_map(function($v) {
return rtrim($v, "\n");
}, file($file));
while ($l < sizeof($lines)) {
$pos = $l + 1;
assert(in_array($lines[$l], ["#document", "#fragment"]), new \Exception("Test $file #$index does not start with #doocument or #fragment tag at line ".($l + 1)));
$fragment = $lines[$l] === "#fragment";
// collect the test input
$data = [];
for (++$l; $l < sizeof($lines); $l++) {
if (preg_match('/^#(script-(on|off)|output)$/', $lines[$l])) {
break;
}
$data[] = $lines[$l];
}
// set the script mode, if present
assert(preg_match('/^#(script-(on|off)|output)$/', $lines[$l]) === 1, new \Exception("Test $file #$index follows data with something other than script flag or output at line ".($l + 1)));
$script = null;
if ($lines[$l] === "#script-off") {
$script = false;
$l++;
} elseif ($lines[$l] === "#script-on") {
$script = true;
$l++;
}
// collect the output string
$exp = [];
assert($lines[$l] === "#output", new \Exception("Test $file #$index follows input with something other than output at line ".($l + 1)));
for (++$l; $l < sizeof($lines); $l++) {
if ($lines[$l] === "" && in_array(($lines[$l + 1] ?? ""), ["#document", "#fragment"])) {
break;
}
assert(preg_match('/^[^#]/', $lines[$l]) === 1, new \Exception("Test $file #$index contains unrecognized data after output at line ".($l + 1)));
$exp[] = $lines[$l];
}
$exp = implode("\n", $exp);
if (!$script) {
yield basename($file)." #$index (line $pos)" => [$data, $fragment, $exp];
}
$l++;
$index++;
}
}
}
}
protected function buildTree(array $data, bool $fragment): \DOMNode {
$document = new Document;
if ($fragment) {
$document->appendChild($document->createElement("html"));
$out = $document->createDocumentFragment();
} else {
$out = $document;
}
$cur = $out;
$pad = 2;
// process each line in turn
for ($l = 0; $l < sizeof($data); $l++) {
preg_match('/^(\|\s+)(.+)/', $data[$l], $m);
// pop any parents as long as the padding of the line is less than the expected padding
$p = strlen((string) $m[1]);
assert($p >= 2 && $p <= $pad && !($p % 2), new \Exception("Input data is invalid on line ".($l + 1)));
while ($p < $pad) {
$pad -= 2;
$cur = $cur->parentNode;
}
// act based upon what the rest of the line looks like
$d = $m[2];
if (preg_match('/^<!-- (.*?) -->$/', $d, $m)) {
// comment
$cur->appendChild($document->createComment($m[1]));
} elseif (preg_match('/^<!DOCTYPE(?: ([^ >]*)(?: "([^"]*)" "([^"]*)")?)?>$/', $d, $m)) {
// doctype
$name = strlen((string) ($m[1] ?? "")) ? $m[1] : " ";
$public = strlen((string) ($m[2] ?? "")) ? $m[2] : "";
$system = strlen((string) ($m[3] ?? "")) ? $m[3] : "";
$cur->appendChild($document->implementation->createDocumentType($name, $public, $system));
} elseif (preg_match('/^<\?([^ ]+) ([^>]*)>$/', $d, $m)) {
$cur->appendChild($document->createProcessingInstruction($m[1], $m[2]));
} elseif (preg_match('/^<(?:([^ ]+) )?([^>]+)>$/', $d, $m)) {
// element
$ns = strlen((string) $m[1]) ? (array_flip(Parser::NAMESPACE_MAP)[$m[1]] ?? $m[1]) : null;
$cur = $cur->appendChild($document->createElementNS($ns, $m[2]));
$pad += 2;
} elseif (preg_match('/^(?:([^" ]+) )?([^"=]+)="((?:[^"]|"(?!$))*)"$/', $d, $m)) {
// attribute
$ns = strlen((string) $m[1]) ? (array_flip(Parser::NAMESPACE_MAP)[$m[1]] ?? $m[1]) : "";
$cur->setAttributeNS($ns, $m[2], $m[3]);
} elseif (preg_match('/^"((?:[^"]|"(?!$))*)("?)$/', $d, $m)) {
// text
$t = $m[1];
while (!strlen((string) $m[2])) {
preg_match('/^((?:[^"]|"(?!$))*)("?)$/', $data[++$l], $m);
$t .= "\n".$m[1];
}
$cur->appendChild($document->createTextNode($t));
} else {
throw new \Exception("Input data is invalid on line ".($l + 1));
}
}
return $out;
}
}

49
tests/cases/TestTokenizer.php

@ -6,29 +6,30 @@
declare(strict_types=1);
namespace MensBeam\HTML\TestCase;
use MensBeam\HTML\Data;
use MensBeam\HTML\EOFToken;
use MensBeam\HTML\OpenElementsStack;
use MensBeam\HTML\ParseError;
use MensBeam\HTML\Tokenizer;
use MensBeam\HTML\CharacterToken;
use MensBeam\HTML\CommentToken;
use MensBeam\HTML\DOCTYPEToken;
use MensBeam\HTML\EndTagToken;
use MensBeam\HTML\NullCharacterToken;
use MensBeam\HTML\StartTagToken;
use MensBeam\HTML\TokenAttr;
use MensBeam\HTML\WhitespaceToken;
use MensBeam\HTML\Parser\Data;
use MensBeam\HTML\Parser\EOFToken;
use MensBeam\HTML\Parser\OpenElementsStack;
use MensBeam\HTML\Parser\ParseError;
use MensBeam\HTML\Parser\Tokenizer;
use MensBeam\HTML\Parser\CharacterToken;
use MensBeam\HTML\Parser\CommentToken;
use MensBeam\HTML\Parser\DOCTYPEToken;
use MensBeam\HTML\Parser\EndTagToken;
use MensBeam\HTML\Parser\NullCharacterToken;
use MensBeam\HTML\Parser\StartTagToken;
use MensBeam\HTML\Parser\TokenAttr;
use MensBeam\HTML\Parser\WhitespaceToken;
use MensBeam\Intl\Encoding\UTF8;
/**
* @covers \MensBeam\HTML\Data
* @covers \MensBeam\HTML\Tokenizer
* @covers \MensBeam\HTML\CharacterToken
* @covers \MensBeam\HTML\CommentToken
* @covers \MensBeam\HTML\DataToken
* @covers \MensBeam\HTML\TagToken
* @covers \MensBeam\HTML\DOCTYPEToken
* @covers \MensBeam\HTML\TokenAttr
* @covers \MensBeam\HTML\Parser\Data
* @covers \MensBeam\HTML\Parser\Tokenizer
* @covers \MensBeam\HTML\Parser\CharacterToken
* @covers \MensBeam\HTML\Parser\CommentToken
* @covers \MensBeam\HTML\Parser\DataToken
* @covers \MensBeam\HTML\Parser\TagToken
* @covers \MensBeam\HTML\Parser\DOCTYPEToken
* @covers \MensBeam\HTML\Parser\TokenAttr
*/
class TestTokenizer extends \PHPUnit\Framework\TestCase {
const STATE_MAP = [
@ -87,8 +88,8 @@ class TestTokenizer extends \PHPUnit\Framework\TestCase {
$tests = [];
$blacklist = ["xmlViolation.test"];
$files = new \AppendIterator();
$files->append(new \GlobIterator(\MensBeam\HTML\BASE."tests/html5lib-tests/tokenizer/*.test", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\BASE."tests/cases/tokenizer/*.test", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\Parser\BASE."tests/html5lib-tests/tokenizer/*.test", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\Parser\BASE."tests/cases/tokenizer/*.test", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
foreach ($files as $file) {
if (!in_array(basename($file), $blacklist)) {
$tests[] = $file;
@ -101,7 +102,7 @@ class TestTokenizer extends \PHPUnit\Framework\TestCase {
if (preg_match_all("/\\\\u([0-9a-f]{4})/i", $str, $matches)) {
for ($a = 0; $a < sizeof($matches[0]); $a++) {
$esc = $matches[0][$a];
$chr = \MensBeam\Intl\Encoding\UTF8::encode(hexdec($matches[1][$a]));
$chr = UTF8::encode(hexdec($matches[1][$a]));
$str = str_replace($esc, $chr, $str);
}
}

38
tests/cases/TestTreeConstructor.php

@ -6,29 +6,27 @@
declare(strict_types=1);
namespace MensBeam\HTML\TestCase;
use MensBeam\HTML\Data;
use MensBeam\HTML\LoopException;
use MensBeam\HTML\NotImplementedException;
use MensBeam\HTML\OpenElementsStack;
use MensBeam\HTML\ParseError;
use MensBeam\HTML\Parser;
use MensBeam\HTML\TemplateInsertionModesStack;
use MensBeam\HTML\Tokenizer;
use MensBeam\HTML\TreeBuilder;
use MensBeam\HTML\Parser\Data;
use MensBeam\HTML\Parser\LoopException;
use MensBeam\HTML\Parser\NotImplementedException;
use MensBeam\HTML\Parser\OpenElementsStack;
use MensBeam\HTML\Parser\ParseError;
use MensBeam\HTML\Parser\TemplateInsertionModesStack;
use MensBeam\HTML\Parser\Tokenizer;
use MensBeam\HTML\Parser\TreeBuilder;
/**
* @covers \MensBeam\HTML\Document
* @covers \MensBeam\HTML\Element
* @covers \MensBeam\HTML\Tokenizer
* @covers \MensBeam\HTML\TreeBuilder
* @covers \MensBeam\HTML\ActiveFormattingElementsList
* @covers \MensBeam\HTML\TemplateInsertionModesStack
* @covers \MensBeam\HTML\OpenElementsStack
* @covers \MensBeam\HTML\Stack
* @covers \MensBeam\HTML\TagToken
* @covers \MensBeam\HTML\Parser\Tokenizer
* @covers \MensBeam\HTML\Parser\TreeBuilder
* @covers \MensBeam\HTML\Parser\ActiveFormattingElementsList
* @covers \MensBeam\HTML\Parser\TemplateInsertionModesStack
* @covers \MensBeam\HTML\Parser\OpenElementsStack
* @covers \MensBeam\HTML\Parser\Stack
* @covers \MensBeam\HTML\Parser\TagToken
*/
class TestTreeConstructor extends \PHPUnit\Framework\TestCase {
use \MensBeam\HTML\EscapeString;
use \MensBeam\HTML\Parser\NameCoercion;
protected $out;
protected $depth;
@ -379,8 +377,8 @@ class TestTreeConstructor extends \PHPUnit\Framework\TestCase {
public function provideStandardTreeTests(): iterable {
$blacklist = [];
$files = new \AppendIterator();
$files->append(new \GlobIterator(\MensBeam\HTML\BASE."tests/html5lib-tests/tree-construction/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\BASE."tests/cases/tree-construction/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\Parser\BASE."tests/html5lib-tests/tree-construction/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
$files->append(new \GlobIterator(\MensBeam\HTML\Parser\BASE."tests/cases/tree-construction/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME));
foreach ($files as $file) {
$index = 0;
$l = 0;

32
tests/cases/serializer/menbeam01.dat

@ -1,32 +0,0 @@
#fragment
| <fake_ns test:test>
#output
<test:test></test:test>
#fragment
| <fake_ns test:te<st>
#output
<test:te<st></test:te<st>
#fragment
| <test<test>
#output
<test<test></test<test>
#fragment
| <span>
| test<test="test"
#output
<span test<test="test"></span>
#fragment
| <wbr>
| "You should not see this text."
#output
<wbr>
#fragment
| <wbr>
| class="test"
#output
<wbr class="test">

34
tests/cases/serializer/menbeam02.dat

@ -1,34 +0,0 @@
#document
| <html>
#output
<html></html>
#document
| <!DOCTYPE html>
| <html>
#output
<!DOCTYPE html><html></html>
#document
| <!DOCTYPE html "public" "system">
| <html>
#output
<!DOCTYPE html><html></html>
#document
| <!DOCTYPE test>
| <html>
#output
<!DOCTYPE test><html></html>
#document
| <!DOCTYPE>
| <html>
#output
<!DOCTYPE ><html></html>
#document
| <html>
| <?php echo "Hello world!"; ?>
#output
<html><?php echo "Hello world!"; ?></html>

913
tests/cases/serializer/wpt01.dat

@ -1,913 +0,0 @@
#fragment
| <span>
#output
<span></span>
#fragment
| <span>
| <a>
#output
<span><a></a></span>
#fragment
| <span>
| <a>
| b="c"
#output
<span><a b="c"></a></span>
#fragment
| <span>
| <a>
| b="&"
#output
<span><a b="&amp;"></a></span>
#fragment
| <span>
| <a>
| b=" "
#output
<span><a b="&nbsp;"></a></span>
#fragment
| <span>
| <a>
| b="""
#output
<span><a b="&quot;"></a></span>
#fragment
| <span>
| <a>
| b="<"
#output
<span><a b="<"></a></span>
#fragment
| <span>
| <a>
| b=">"
#output
<span><a b=">"></a></span>
#fragment
| <span>
| <a>
| href="javascript:"<>""
#output
<span><a href="javascript:&quot;<>&quot;"></a></span>
#fragment
| <span>
| <svg svg>
| xlink xlink:href="a"
#output
<span><svg xlink:href="a"></svg></span>
#fragment
| <span>
| <svg svg>
| xmlns xmlns:svg="test"
#output
<span><svg xmlns:svg="test"></svg></span>
#fragment
| <span>
| "a"
#output
<span>a</span>
#fragment
| <span>
| "&"
#output
<span>&amp;</span>
#fragment
| <span>
| " "
#output
<span>&nbsp;</span>
#fragment
| <span>
| "<"
#output
<span>&lt;</span>
#fragment
| <span>
| ">"
#output
<span>&gt;</span>
#fragment
| <span>
| """
#output
<span>"</span>
#fragment
| <span>
| <style>
| "<&>"
#output
<span><style><&></style></span>
#fragment
| <span>
| <script>
| type="test"
| "<&>"
#output
<span><script type="test"><&></script></span>
#fragment
| <script>
| type="test"
| "<&>"
#output
<script type="test"><&></script>
#fragment
| <span>
| <xmp>
| "<&>"
#output
<span><xmp><&></xmp></span>
#fragment
| <span>
| <iframe>
| "<&>"
#output
<span><iframe><&></iframe></span>
#fragment
| <span>
| <noembed>
| "<&>"
#output
<span><noembed><&></noembed></span>
#fragment
| <span>
| <noframes>
| "<&>"
#output
<span><noframes><&></noframes></span>
#fragment
| <span>
| <noscript>
| "<&>"
#script-off
#output
<span><noscript>&lt;&amp;&gt;</noscript></span>
#fragment
| <span>
| <noscript>
| "<&>"
#script-on
#output
<span><noscript><&></noscript></span>
#fragment
| <span>
| <!-- data -->
#output
<span><!--data--></span>
#fragment
| <span>
| <a>
| <b>
| <c>
| <d>
| "e"
| <f>
| <g>
| "h"
#output
<span><a><b><c></c></b><d>e</d><f><g>h</g></f></a></span>
#fragment
| <span>
| b="c"
#output
<span b="c"></span>
#fragment
| <span>
| <svg svg>
| xml xml:foo="test"
#output
<span><svg xml:foo="test"></svg></span>
#fragment
| <span>
| <svg svg>
| xml abc:foo="test"
#output
<span><svg xml:foo="test"></svg></span>
#fragment
| <span>
| <svg svg>
| xmlns xmlns:foo="test"
#output
<span><svg xmlns:foo="test"></svg></span>
#fragment
| <span>
| <svg svg>
| xmlns xmlns="test"
#output
<span><svg xmlns="test"></svg></span>
#fragment
| <span>
| <svg svg>
| fake_ns abc:def="test"
#output
<span><svg abc:def="test"></svg></span>
#fragment
| <pre>
| "
"
#output
<pre>
</pre>
#fragment
| <pre>
| "a
"
#output
<pre>a
</pre>
#fragment
| <span>
| <pre>
| "
"
#output
<span><pre>
</pre></span>
#fragment
| <span>
| <pre>
| "a
"
#output
<span><pre>a
</pre></span>
#fragment
| <textarea>
| "
"
#output
<textarea>
</textarea>
#fragment
| <textarea>
| "a
"
#output
<textarea>a
</textarea>
#fragment
| <span>
| <textarea>
| "
"
#output
<span><textarea>
</textarea></span>
#fragment
| <span>
| <textarea>
| "a
"
#output
<span><textarea>a
</textarea></span>
#fragment
| <listing>
| "
"
#output
<listing>
</listing>
#fragment
| <listing>
| "a
"
#output
<listing>a
</listing>
#fragment
| <span>
| <listing>
| "
"
#output
<span><listing>
</listing></span>
#fragment
| <span>
| <listing>
| "a
"
#output
<span><listing>a
</listing></span>
#fragment
| <area>
#output
<area>
#fragment
| <span>
| <area>
| <a>
| "test"
| <b>
#output
<span><area><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <area>
| <b>
#output
<span><a>test</a><area><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <area>
#output
<span><a>test</a><b></b><area></span>
#fragment
| <base>
#output
<base>
#fragment
| <span>
| <base>
| <a>
| "test"
| <b>
#output
<span><base><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <base>
| <b>
#output
<span><a>test</a><base><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <base>
#output
<span><a>test</a><b></b><base></span>
#fragment
| <basefont>
#output
<basefont>
#fragment
| <span>
| <basefont>
| <a>
| "test"
| <b>
#output
<span><basefont><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <basefont>
| <b>
#output
<span><a>test</a><basefont><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <basefont>
#output
<span><a>test</a><b></b><basefont></span>
#fragment
| <bgsound>
#output
<bgsound>
#fragment
| <span>
| <bgsound>
| <a>
| "test"
| <b>
#output
<span><bgsound><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <bgsound>
| <b>
#output
<span><a>test</a><bgsound><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <bgsound>
#output
<span><a>test</a><b></b><bgsound></span>
#fragment
| <br>
#output
<br>
#fragment
| <span>
| <br>
| <a>
| "test"
| <b>
#output
<span><br><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <br>
| <b>
#output
<span><a>test</a><br><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <br>
#output
<span><a>test</a><b></b><br></span>
#fragment
| <col>
#output
<col>
#fragment
| <span>
| <col>
| <a>
| "test"
| <b>
#output
<span><col><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <col>
| <b>
#output
<span><a>test</a><col><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <col>
#output
<span><a>test</a><b></b><col></span>
#fragment
| <embed>
#output
<embed>
#fragment
| <span>
| <embed>
| <a>
| "test"
| <b>
#output
<span><embed><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <embed>
| <b>
#output
<span><a>test</a><embed><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <embed>
#output
<span><a>test</a><b></b><embed></span>
#fragment
| <frame>
#output
<frame>
#fragment
| <span>
| <frame>
| <a>
| "test"
| <b>
#output
<span><frame><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <frame>
| <b>
#output
<span><a>test</a><frame><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <frame>
#output
<span><a>test</a><b></b><frame></span>
#fragment
| <hr>
#output
<hr>
#fragment
| <span>
| <hr>
| <a>
| "test"
| <b>
#output
<span><hr><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <hr>
| <b>
#output
<span><a>test</a><hr><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <hr>
#output
<span><a>test</a><b></b><hr></span>
#fragment
| <img>
#output
<img>
#fragment
| <span>
| <img>
| <a>
| "test"
| <b>
#output
<span><img><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <img>
| <b>
#output
<span><a>test</a><img><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <img>
#output
<span><a>test</a><b></b><img></span>
#fragment
| <input>
#output
<input>
#fragment
| <span>
| <input>
| <a>
| "test"
| <b>
#output
<span><input><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <input>
| <b>
#output
<span><a>test</a><input><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <input>
#output
<span><a>test</a><b></b><input></span>
#fragment
| <keygen>
#output
<keygen>
#fragment
| <span>
| <keygen>
| <a>
| "test"
| <b>
#output
<span><keygen><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <keygen>
| <b>
#output
<span><a>test</a><keygen><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <keygen>
#output
<span><a>test</a><b></b><keygen></span>
#fragment
| <link>
#output
<link>
#fragment
| <span>
| <link>
| <a>
| "test"
| <b>
#output
<span><link><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <link>
| <b>
#output
<span><a>test</a><link><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <link>
#output
<span><a>test</a><b></b><link></span>
#fragment
| <meta>
#output
<meta>
#fragment
| <span>
| <meta>
| <a>
| "test"
| <b>
#output
<span><meta><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <meta>
| <b>
#output
<span><a>test</a><meta><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <meta>
#output
<span><a>test</a><b></b><meta></span>
#fragment
| <param>
#output
<param>
#fragment
| <span>
| <param>
| <a>
| "test"
| <b>
#output
<span><param><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <param>
| <b>
#output
<span><a>test</a><param><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <param>
#output
<span><a>test</a><b></b><param></span>
#fragment
| <source>
#output
<source>
#fragment
| <span>
| <source>
| <a>
| "test"
| <b>
#output
<span><source><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <source>
| <b>
#output
<span><a>test</a><source><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <source>
#output
<span><a>test</a><b></b><source></span>
#fragment
| <track>
#output
<track>
#fragment
| <span>
| <track>
| <a>
| "test"
| <b>
#output
<span><track><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <track>
| <b>
#output
<span><a>test</a><track><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <track>
#output
<span><a>test</a><b></b><track></span>
#fragment
| <wbr>
#output
<wbr>
#fragment
| <span>
| <wbr>
| <a>
| "test"
| <b>
#output
<span><wbr><a>test</a><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <wbr>
| <b>
#output
<span><a>test</a><wbr><b></b></span>
#fragment
| <span>
| <a>
| "test"
| <b>
| <wbr>
#output
<span><a>test</a><b></b><wbr></span>

6
tests/phpunit.dist.xml

@ -16,9 +16,6 @@
</filter>
<testsuites>
<testsuite name="DOM">
<file>cases/TestDOM.php</file>
</testsuite>
<testsuite name="Charset">
<file>cases/TestCharset.php</file>
</testsuite>
@ -28,8 +25,5 @@
<testsuite name="Tree">
<file>cases/TestTreeConstructor.php</file>
</testsuite>
<testsuite name="Serializer">
<file>cases/TestSerializer.php</file>
</testsuite>
</testsuites>
</phpunit>

1158
yarn.lock

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