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; - } }