Browse Source

Added in Element::insertAdjacentElement & Element::insertAdjacentText

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
3f3fd89bfa
  1. 10
      README.md
  2. 60
      lib/Element.php
  3. 4
      lib/Node.php

10
README.md

@ -1,12 +1,12 @@
[a]: https://dom.spec.whatwg.org/#htmlcollection
[b]: https://webidl.spec.whatwg.org/#idl-sequence
[c]: https://packagist.org/packages/phpgt/dom
[d]: https://html.spec.whatwg.org
[d]: https://dom.spec.whatwg.org
[e]: #limitations
# HTML DOM #
Modern DOM library written in PHP for HTML documents. This implementation is a userland extension of PHP's built-in DOM. It exists because PHP's DOM is inaccurate, inadequate for use with any HTML, and buggy. This implementation aims to fix as much as possible the inaccuracies of the PHP DOM, add in features necessary for modern HTML development, and circumvent most of the bugs.
Modern DOM library written in PHP for HTML documents. This library is an attempt to implement the [WHATWG's DOM specification][d] through a userland extension and encapsulation of PHP's built-in DOM. It exists because PHP's DOM is inaccurate, inadequate for use with any HTML, and extremely buggy. This implementation aims to fix as much as possible the inaccuracies of the PHP DOM, add in features necessary for modern HTML development, and circumvent most of the bugs.
## Usage ##
@ -47,11 +47,11 @@ The primary aim of this library is accuracy. However, due either to limitations
2. Due to a PHP bug which severely degrades performance with large documents and in consideration of existing PHP software, HTML elements in HTML documents are placed in the null namespace internally rather than in the HTML namespace. However, externally they will be shown as having the HTML namespace. Even though null namespaced elements do not exist in the HTML specification one can create them using the DOM. However, in this implementation they will be treated as HTML namespaced elements due to the HTML namespace limitation.
3. The specification is written entirely with browsers in mind and aren't concerned with the DOM's being used outside of the browser. In browser there is always a document created by parsing serialized markup, and the DOM spec always assumes such. This is impossible in the way this PHP library is intended to be used. The default when creating a new `Document` is to set its content type to "application/xml". This isn't ideal when creating an HTML document entirely through the DOM, so this implementation will instead default to "text/html" unless using `XMLDocument`.
4. Again, because the specification assumes the implementation will be a browser, processing instructions are supposed to be parsed as comments. While it makes sense for a browser, this is impractical for a DOM library used outside of the browser where one may want to manipulate them; this library will instead preserve them when parsing a document but will convert them to comments when using `Element::innerHTML`.
5. Per the specification an actual HTML document cannot be created outside of the parser itself unless created via `DOMImplementation::createHTMLDocument`. Also, per the spec `DOMImplementation` cannot be instantiated via its constructor. This would require in this library's use case first creating a document then creating an HTML document via its implementation. This is impractical, so in this library (like PHP DOM itself) a `DOMImplementation` can be instantiated independent of a document.
5. Per the specification an actual HTML document cannot be created outside of the parser itself unless created via `DOMImplementation::createHTMLDocument`. Also, per the spec `DOMImplementation` cannot be instantiated via its constructor. This would require in this library's use case first creating a document then creating an HTML document via the first document's implementation. This is impractical and stupid, so in this library (like PHP DOM itself) a `DOMImplementation` can be instantiated independent of a document.
6. The specification shows `Document` as being able to be instantiated through its constructor and shows `XMLDocument` as inheriting from `Document`. In browsers `XMLDocument` cannot be instantiated through its constructor. We will follow the specification here and allow it.
7. CDATA section nodes, text nodes, and document fragments per the specification can be instantiated by their constructors independent of the `Document::createCDATASectionNode`, `Document::createTextNode`, and `Document::createDocumentFragment` methods respectively. This is not possible currently with this library and probably never will be due to the difficulty of implementing it and the awkwardness of their being different from every other node type in this respect.
8. As the DOM is presently specified, CDATA section nodes cannot be created on an HTML document. However, they can be created (and rightly so) on XML documents. The DOM, however, does not prohibit importing of CDATA section nodes into an HTML document and will be appended to the document as such. This appears to be a glaring omission by the maintainers of the specification. This library will allow importing of CDATA section nodes into HTML documents but will instead convert them to text nodes.
9. This implementation will not implement the `NodeIterator` and `TreeWalker` APIs. They are horribly conceived and impractical APIs that few people actually use because it's literally easier and faster to write recursive loops to walk through the DOM than it is to use those APIs. They have instead been replaced with the `ParentNode::walk` generator.
10. All of the `Range` APIs will also not be implemented due to the sheer complexity of creating them in userland and how it adds undue difficulty to node manipulation in the "core" DOM. Numerous operations reference in excrutiating detail what to do with Ranges when manipulating nodes and would have to be added here to be compliant or mostly so -- slowing everything else down in the process on an already front-heavy library.
9. This implementation will not implement the `NodeIterator` and `TreeWalker` APIs. They are horribly conceived and impractical APIs that few people actually use because it's literally easier and faster to write recursive loops to walk through the DOM than it is to use those APIs. Walking downward through the tree has been replaced with the `ParentNode::walk` generator, and walking through adjacent children and moonwalking up the DOM tree can be accomplished through simple while or do/while loops.
10. All of the `Range` APIs will also not be implemented due to the sheer complexity of creating them in userland and how it adds undue difficulty to node manipulation in the "core" DOM. Numerous operations reference in excrutiating detail what to do with Ranges when manipulating nodes and would have to be added here to be compliant or mostly so -- slowing everything else down in the process on an already extremely front-heavy library.
11. The `DOMParser` and `XMLSerializer` APIs will not be implemented because they are ridiculous and limited in their scope. For instance, `DOMParser::parseFromString` won't set a document's character set to anything but UTF-8. This library needs to be able to print to other encodings due to the nature of how it is used. `Document::__construct` will accept optional `$source` and `$charset` arguments, and there are both `Document::load` and `Document::loadFile` methods for loading DOM from a string or a file respectively.
12. Aside from `HTMLElement`, `HTMLTemplateElement`, `MathMLElement`, and `SVGElement` none of the specific derived element classes (such as `HTMLAnchorElement` or `SVGSVGElement`) are implemented. The focus on this library will be on the core DOM before moving onto those. They may or may not be implemented in the future.

60
lib/Element.php

@ -300,6 +300,23 @@ class Element extends Node {
return $this->innerNode->hasAttributes();
}
public function insertAdjacentElement(string $where, Element $element): ?Element {
# The insertAdjacentElement(where, element) method steps are to return the
# result of running insert adjacent, give this, where, and element.
return $this->insertAdjacent($this, $where, $element);
}
public function insertAdjacentText(string $where, string $data): void {
# The insertAdjacentText(where, data) method steps are:
#
# 1. Let text be a new Text node whose data is data and node document is this’s
# node document.
$text = $this->ownerDocument->createTextNode($data);
# 2. Run insert adjacent, given this, where, and text.
$this->insertAdjacent($this, $where, $text);
}
public function matches(string $selectors): bool {
# The matches(selectors) and webkitMatchesSelector(selectors) method steps are:
@ -481,4 +498,47 @@ class Element extends Node {
public function webkitMatchesSelector(string $selectors): bool {
return $this->matches($selectors);
}
protected function insertAdjacent(Element $element, string $where, Node $node): Node {
# To insert adjacent, given an element element, string where, and a node node,
# run the steps associated with the first ASCII case-insensitive match for
# where:
switch ($where) {
case 'beforebegin':
# If element’s parent is null, return null.
if ($element->parentNode === null) {
return null;
}
# Return the result of pre-inserting node into element’s parent before element.
return $element->parentNode->insertBefore($node, $element);
break;
case 'afterbegin':
# Return the result of pre-inserting node into element before element’s first
# child.
return $element->parentNode->insertBefore($node, $element->firstChild);
break;
case 'beforeend':
# Return the result of pre-inserting node into element before null.
// Isn't this just an appendChild?
return $element->appendChild($node);
break;
case 'afterend':
# If element’s parent is null, return null.
if ($element->parentNode === null) {
return null;
}
# Return the result of pre-inserting node into element’s parent before element’s
# next sibling.
return $element->parentNode->insertBefore($node, $element->nextSibling);
break;
default: throw new DOMException(DOMException::SYNTAX_ERROR);
}
}
}

4
lib/Node.php

@ -1217,8 +1217,8 @@ abstract class Node {
// below walks through this node and temporarily replaces foreign descendants
// with bullshit elements which are then replaced once the node is inserted.
if ($element->namespaceURI === null && ($this instanceof DocumentFragment || $this->getRootNode() !== null) && $element->hasChildNodes()) {
// XPath can't match just unprefixed elements, so we have to do this the old
// fashioned way by walking the DOM.
// XPath can't easily match just unprefixed elements, so we have to do this the
// old fashioned way by walking the DOM.
$foreign = $this->walkInner($element, function(\DOMNode $n) {
if ($n instanceof \DOMElement && ($n->parentNode !== null && $n->parentNode->namespaceURI === null) && $n->namespaceURI !== null && $n->prefix === '') {
return self::WALK_ACCEPT | self::WALK_SKIP_CHILDREN;

Loading…
Cancel
Save