diff --git a/docs/en/030_Document_Object_Model/010_Document/010_Document-__construct.md b/docs/en/030_Document_Object_Model/010_Document/010_construct.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/010_Document-__construct.md rename to docs/en/030_Document_Object_Model/010_Document/010_construct.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-C14N.md b/docs/en/030_Document_Object_Model/010_Document/020_Document-C14N.md deleted file mode 100644 index c81916d..0000000 --- a/docs/en/030_Document_Object_Model/010_Document/020_Document-C14N.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Document::C14N ---- - -Document::C14N — **DISABLED** - -## Description ## - -```php -public Document::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`. `\DOMDocument::C14N` is an extremely slow and inefficient method to serialize DOM and never should be used. Documented to show difference from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php). \ No newline at end of file diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-C14NFile.md b/docs/en/030_Document_Object_Model/010_Document/020_Document-C14NFile.md deleted file mode 100644 index d7c8dc2..0000000 --- a/docs/en/030_Document_Object_Model/010_Document/020_Document-C14NFile.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Document::C14NFile ---- - -Document::C14NFile — **DISABLED** - -## Description ## - -```php -public Document::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`. `\DOMDocument::C14NFile` is an extremely slow and inefficient method to serialize DOM and never should be used. Documented to show difference from [`\DOMDocument`](https://www.php.net/manual/en/class.domdocument.php). \ No newline at end of file diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-createEntityReference.md b/docs/en/030_Document_Object_Model/010_Document/020_createEntityReference.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-createEntityReference.md rename to docs/en/030_Document_Object_Model/010_Document/020_createEntityReference.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-load.md b/docs/en/030_Document_Object_Model/010_Document/020_load.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-load.md rename to docs/en/030_Document_Object_Model/010_Document/020_load.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-loadHTML.md b/docs/en/030_Document_Object_Model/010_Document/020_loadHTML.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-loadHTML.md rename to docs/en/030_Document_Object_Model/010_Document/020_loadHTML.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-loadHTMLFile.md b/docs/en/030_Document_Object_Model/010_Document/020_loadHTMLFile.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-loadHTMLFile.md rename to docs/en/030_Document_Object_Model/010_Document/020_loadHTMLFile.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-loadXML.md b/docs/en/030_Document_Object_Model/010_Document/020_loadXML.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-loadXML.md rename to docs/en/030_Document_Object_Model/010_Document/020_loadXML.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-save.md b/docs/en/030_Document_Object_Model/010_Document/020_save.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-save.md rename to docs/en/030_Document_Object_Model/010_Document/020_save.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-saveHTMLFile.md b/docs/en/030_Document_Object_Model/010_Document/020_saveHTMLFile.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-saveHTMLFile.md rename to docs/en/030_Document_Object_Model/010_Document/020_saveHTMLFile.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-saveXML.md b/docs/en/030_Document_Object_Model/010_Document/020_saveXML.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-saveXML.md rename to docs/en/030_Document_Object_Model/010_Document/020_saveXML.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-validate.md b/docs/en/030_Document_Object_Model/010_Document/020_validate.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-validate.md rename to docs/en/030_Document_Object_Model/010_Document/020_validate.md diff --git a/docs/en/030_Document_Object_Model/010_Document/020_Document-xinclude.md b/docs/en/030_Document_Object_Model/010_Document/020_xinclude.md similarity index 100% rename from docs/en/030_Document_Object_Model/010_Document/020_Document-xinclude.md rename to docs/en/030_Document_Object_Model/010_Document/020_xinclude.md diff --git a/docs/en/030_Document_Object_Model/010_Document/index.md b/docs/en/030_Document_Object_Model/010_Document/index.md index cd4023c..0b135f8 100644 --- a/docs/en/030_Document_Object_Model/010_Document/index.md +++ b/docs/en/030_Document_Object_Model/010_Document/index.md @@ -11,7 +11,7 @@ Represents an entire HTML document; serves as the root of the document tree. Unl
Info Only new methods and methods which make outward-facing changes from \DOMDocument will be documented here, otherwise they will be linked back to PHP's documentation.
MensBeam\HTML\Document extends \DOMDocument {
- use Walk;
+ use Node, Walk;
/* Constants */
public const NO_QUIRKS_MODE = 0 ;
@@ -19,6 +19,7 @@ Represents an entire HTML document; serves as the root of the document tree. Unl
public const LIMITED_QUIRKS_MODE = 2 ;
/* Properties */
+ public Element|null $body = null ;
public string|null $documentEncoding = null ;
public int $quirksMode = 0 ;
@@ -47,22 +48,23 @@ Represents an entire HTML document; serves as the root of the document tree. Unl
public string $textContent ;
/* Methods */
- public __construct ( )
- public createEntityReference ( string $name ) : false
- public C14N ( bool $exclusive = false , bool $withComments = false , array|null $xpath = null , array|null $nsPrefixes = null ) : false
- public C14NFile ( string $uri , bool $exclusive = false , bool $withComments = false , array|null $xpath = null , array|null $nsPrefixes = null ) : false
- public load ( string $filename , null $options = null , string|null $encodingOrContentType = null ) : bool
- public loadHTML ( string $source , null $options = null , string|null $encodingOrContentType = null ) : bool
- public loadHTMLFile ( string $filename , null $options = null , string|null $encodingOrContentType = null ) : bool
- public loadXML ( string $source , null $options = null ) : false
- public save ( string $filename , null $options = null ) : int|false
- public saveHTMLFile ( string $filename , null $options = null ) : int|false
- public saveXML ( \DOMNode|null $node = null , null $options = null ) : false
- public validate ( ) : true
- public xinclude ( null $options = null ) : false
+ public __construct ( )
+ public createEntityReference ( string $name ) : false
+ public load ( string $filename , null $options = null , string|null $encodingOrContentType = null ) : bool
+ public loadHTML ( string $source , null $options = null , string|null $encodingOrContentType = null ) : bool
+ public loadHTMLFile ( string $filename , null $options = null , string|null $encodingOrContentType = null ) : bool
+ public loadXML ( string $source , null $options = null ) : false
+ public save ( string $filename , null $options = null ) : int|false
+ public saveHTMLFile ( string $filename , null $options = null ) : int|false
+ public saveXML ( \DOMNode|null $node = null , null $options = null ) : false
+ public validate ( ) : true
+ public xinclude ( null $options = null ) : false
+
+ /* Magic Methods */
+ public __toString() : string
/* Methods from Walk */
- public walk ( \Closure $filter ) : \Generator
+ public walk ( \Closure $filter ) : \Generator
/* Methods inherited from \DOMDocument */
public createAttribute ( string $localName ) : \DOMAttr|false
@@ -116,6 +118,9 @@ Represents an entire HTML document; serves as the root of the document tree. Unl
## Properties ##
+ - body
+ - Represents the
body
or frameset
node of the current document, or null
if no such element exists.
+
- documentEncoding
- Encoding of the document, as specified when parsing or when determining encoding type. Use this instead of
\DOMDocument::encoding
.
diff --git a/docs/en/030_Document_Object_Model/Node/010_C14N.md b/docs/en/030_Document_Object_Model/Node/010_C14N.md
new file mode 100644
index 0000000..1228dda
--- /dev/null
+++ b/docs/en/030_Document_Object_Model/Node/010_C14N.md
@@ -0,0 +1,13 @@
+---
+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.
\ No newline at end of file
diff --git a/docs/en/030_Document_Object_Model/Node/010_C14NFile.md b/docs/en/030_Document_Object_Model/Node/010_C14NFile.md
new file mode 100644
index 0000000..0ab0f91
--- /dev/null
+++ b/docs/en/030_Document_Object_Model/Node/010_C14NFile.md
@@ -0,0 +1,13 @@
+---
+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.
\ No newline at end of file
diff --git a/docs/en/030_Document_Object_Model/Node/010_appendChild.md b/docs/en/030_Document_Object_Model/Node/010_appendChild.md
new file mode 100644
index 0000000..5dbd7ea
--- /dev/null
+++ b/docs/en/030_Document_Object_Model/Node/010_appendChild.md
@@ -0,0 +1,47 @@
+---
+title: Node::appendChild
+---
+
+Node::appendChild — Adds new child at the end of the children
+
+## Description ##
+
+```php
+public Node::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.
+
+
+ Warning Only the following element types may be appended to any node using Node
and subject to hierarchy restrictions depending on the type of node being appended to:
+
+
+ Comment
+ DocumentFragment
+ \DOMDocumentType
+ Element
+ ProcessingInstruction
+ Text
+
+
+ Note that \DOMAttr
is missing from this list.
+
+
+## Examples ##
+
+**Example \#1 Adding a child to the body**
+
+```php
+loadHTML('Ook! ');
+
+$node = $dom->createElement('br');
+$dom->body->appendChild($node);
+
+?>
\ No newline at end of file
diff --git a/docs/en/030_Document_Object_Model/Node/index.md b/docs/en/030_Document_Object_Model/Node/index.md
new file mode 100644
index 0000000..fcc9bc7
--- /dev/null
+++ b/docs/en/030_Document_Object_Model/Node/index.md
@@ -0,0 +1,16 @@
+# 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).
+
+trait MensBeam\HTML\Node {
+
+ public appendChild ( \DOMNode $node ) : \DOMNode|false
+ public C14N ( bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
+ public C14NFile ( string $uri , bool $exclusive = false , bool $withComments = false , null $xpath = null , null $nsPrefixes = null ) : false
+ public insertBefore ( \DOMNode $node , \DOMNode|null $child = null ) : \DOMNode|false
+ public removeChild ( \DOMNode $child ) : \DOMNode|false
+ public replaceChild ( \DOMNode $node , \DOMNode $child ) : \DOMNode|false
+
+}
\ No newline at end of file
diff --git a/docs/en/030_Document_Object_Model/Walk/010_Walk-walk.md b/docs/en/030_Document_Object_Model/Walk/010_walk.md
similarity index 85%
rename from docs/en/030_Document_Object_Model/Walk/010_Walk-walk.md
rename to docs/en/030_Document_Object_Model/Walk/010_walk.md
index 2e4cae0..36fdbad 100644
--- a/docs/en/030_Document_Object_Model/Walk/010_Walk-walk.md
+++ b/docs/en/030_Document_Object_Model/Walk/010_walk.md
@@ -10,7 +10,7 @@ Walk::walk — Output generator for walking down the DOM tree
public Walk::walk ( \Closure $filter ) : \Generator
```
-Creates a `\Generator` object for walking down the DOM tree.
+Creates a [`\Generator`](https://www.php.net/manual/en/class.generator.php) object for walking down the DOM tree.
## Examples ##
diff --git a/docs/en/030_Document_Object_Model/Walk/index.md b/docs/en/030_Document_Object_Model/Walk/index.md
index efd6a66..e9b7ec8 100644
--- a/docs/en/030_Document_Object_Model/Walk/index.md
+++ b/docs/en/030_Document_Object_Model/Walk/index.md
@@ -2,10 +2,10 @@
## Introduction ##
-Allows nodes to walk down the DOM via a [`\Generator`](https://www.php.net/manual/en/class.generator.php).
+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).
trait MensBeam\HTML\Walk {
- public walk ( \Closure $filter ) : \Generator
+ public walk ( \Closure $filter ) : \Generator
}
\ No newline at end of file
diff --git a/lib/DOM/Document.php b/lib/DOM/Document.php
index 799762d..0ea5afb 100644
--- a/lib/DOM/Document.php
+++ b/lib/DOM/Document.php
@@ -19,6 +19,8 @@ class Document extends \DOMDocument {
public $mangledElements = false;
public $quirksMode = self::NO_QUIRKS_MODE;
+ protected $_body = null;
+
public function __construct() {
parent::__construct();
@@ -151,6 +153,65 @@ class Document extends \DOMDocument {
ElementMap::destroy($this);
}
+ public function __get(string $prop) {
+ if ($prop === 'body') {
+ if ($this->documentElement === null || $this->documentElement->childNodes->length === 0) {
+ return null;
+ }
+
+ # The body element of a document is the first of the html element's children
+ # that is either a body element or a frameset element, or null if there is no
+ # such element.
+ $n = $this->documentElement->firstChild;
+ do {
+ if ($n instanceof Element && $n->namespaceURI === null && ($n->nodeName === 'body' || $n->nodeName === 'frameset')) {
+ return $n;
+ }
+ } while ($n = $n->nextSibling);
+
+ return null;
+ }
+ }
+
+ public function __set(string $prop, $value) {
+ if ($prop === 'body') {
+ # 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 __toString() {
return $this->serialize();
}
diff --git a/lib/DOM/ElementMap.php b/lib/DOM/ElementMap.php
index 6187313..9daf2b7 100644
--- a/lib/DOM/ElementMap.php
+++ b/lib/DOM/ElementMap.php
@@ -17,6 +17,7 @@ class ElementMap {
foreach (self::$_storage as $k => $v) {
if ($v->isSameNode($element)) {
unset(self::$_storage[$k]);
+ self::$_storage = array_values(self::$_storage);
return true;
}
}
@@ -25,11 +26,20 @@ class ElementMap {
}
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 has(Element $element) {
@@ -45,6 +55,9 @@ class ElementMap {
public static function set(Element $element) {
if (!self::has($element)) {
self::$_storage[] = $element;
+ return true;
}
+
+ return false;
}
}
diff --git a/lib/DOM/traits/Node.php b/lib/DOM/traits/Node.php
index a458eb5..9c5b01d 100644
--- a/lib/DOM/traits/Node.php
+++ b/lib/DOM/traits/Node.php
@@ -9,6 +9,64 @@ 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 {
+ 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;
+ }
+
+ // 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;
+ }
+
+ 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
@@ -134,60 +192,4 @@ trait Node {
}
}
}
-
- public function appendChild($node) {
- $this->preInsertionValidity($node);
-
- $result = parent::appendChild($node);
- if ($result !== false && $result instanceof TemplateElement) {
- ElementMap::set($result);
- }
- return $result;
- }
-
- // 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;
- }
-
- 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;
- }
}