diff --git a/lib/Document.php b/lib/Document.php index aca8774..b6907ab 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -382,16 +382,7 @@ class Document extends Node { $this->_characterSet = $source->encoding; $this->_compatMode = ($source->quirksMode === Parser::NO_QUIRKS_MODE || $source->quirksMode === Parser::LIMITED_QUIRKS_MODE) ? 'CSS1Compat' : 'BackCompat'; - // If there are any templates in the document they must be cloned and replaced - // so their contents may be stored in the HTMLTemplateElement's content document - // fragment. - $templates = (new \DOMXPath($this->innerNode))->query('//template[not(ancestor::template)]'); - // Iterate in reverse to prevent the live nodelist from doing anything screwy - for ($templatesCount = count($templates), $i = $templatesCount - 1; $i >= 0; $i--) { - $t = $templates->item($i); - $clone = $this->cloneInnerNode($t, $this->innerNode, true, true); - $t->parentNode->replaceChild($clone, $t); - } + $this->postParsingTemplatesFix($this->innerNode); } public function loadFile(string $filename, ?string $charset = null): void { diff --git a/lib/Element.php b/lib/Element.php index a01f49c..64d77da 100644 --- a/lib/Element.php +++ b/lib/Element.php @@ -80,6 +80,7 @@ class Element extends Node { # the new value as markup, and with context element. $innerFragment = Parser::parseFragment($innerContext, Parser::NO_QUIRKS_MODE, $value, 'UTF-8'); $fragment = $innerContext->ownerDocument->getWrapperNode($innerFragment); + $this->postParsingTemplatesFix($innerFragment); # 3. If the context object is a template element, then let context object be the # template's template contents (a DocumentFragment). @@ -158,6 +159,7 @@ class Element extends Node { # the new value as markup, and parent as the context element. $innerFragment = Parser::parseFragment($innerParent, Parser::NO_QUIRKS_MODE, $value, 'UTF-8'); $fragment = $this->innerNode->ownerDocument->getWrapperNode($innerFragment); + $this->postParsingTemplatesFix($innerFragment); # 6. Replace the context object with fragment within the context object's # parent. @@ -399,6 +401,78 @@ class Element extends Node { return $this->insertAdjacent($this, $where, $element); } + public function insertAdjacentHTML(string $where, string $data): void { + # The insertAdjacentHTML(position, text) method must run these steps: + // This is from the W3C specification which the WHATWG delegates to concerning + // innerHTML, outerHTML and this one. Using $where and $data for the parameters + // to keep things consistent with the WHATWG specification. + + # 1. Use the first matching item from this list: + $where = strtolower($where); + switch ($where) { + case 'beforebegin': + case 'afterend': + # Let context be the context object's parent. + $context = $this->parentNode; + $innerContext = $this->innerNode->parentNode; + + # If context is null or a Document, throw a "NoModificationAllowedError" DOMException. + if ($context === null || $context instanceof Document) { + throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED); + } + break; + case 'afterbegin': + case 'beforeend': + # Let context be the context object. + $context = $this; + $innerContext = $this->innerNode; + break; + default: throw new DOMException(DOMException::SYNTAX_ERROR); + } + + # 2. If context is not an Element or the following are all true: + # • context's node document is an HTML document, + # • context's local name is "html", and + # • context's namespace is the HTML namespace; + if (!$context instanceof Element || ($context->ownerDocument instanceof Document && $context->localName === 'html' && $context->namespaceURI === Node::HTML_NAMESPACE)) { + # let context 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. + $context = $context->ownerDocument->createElement('body'); + $innerContext = $this->getInnerNode($context); + } + + # 3. Let fragment be the result of invoking the fragment parsing algorithm with + # text as markup, and context as the context element. + $innerFragment = Parser::parseFragment($innerContext, Parser::NO_QUIRKS_MODE, $data, 'UTF-8'); + $fragment = $innerContext->ownerDocument->getWrapperNode($innerFragment); + + # 4. Use the first matching item from this list: + switch ($where) { + case 'beforebegin': + # Insert fragment into the context object's parent before the context object. + $this->parentNode->insertBefore($fragment, $this); + break; + + case 'afterbegin': + # Insert fragment into the context object before its first child. + $this->insertBefore($fragment, $this->firstChild); + break; + + case 'beforeend': + # Append fragment to the context object. + $this->appendChild($fragment); + break; + + case 'afterend': + # Insert fragment into the context object's parent before the context object's + # next sibling. + $this->parentNode->insertBefore($fragment, $this->nextSibling); + break; + } + } + public function insertAdjacentText(string $where, string $data): void { # The insertAdjacentText(where, data) method steps are: # @@ -597,7 +671,7 @@ class Element extends 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) { + switch (strtolower($where)) { case 'beforebegin': # If element’s parent is null, return null. if ($element->parentNode === null) { diff --git a/lib/Node.php b/lib/Node.php index 3a93452..19a67c6 100644 --- a/lib/Node.php +++ b/lib/Node.php @@ -1209,6 +1209,20 @@ abstract class Node { return null; } + protected function postParsingTemplatesFix(\DOMNode $contextNode): void { + // If there are any templates in the document they must be cloned and replaced + // so their contents may be stored in the HTMLTemplateElement's content document + // fragment. + $doc = $this->getInnerDocument(); + $templates = (new \DOMXPath($doc))->query('//template[not(ancestor::template)]', $contextNode); + // Iterate in reverse to prevent the live nodelist from doing anything screwy + for ($templatesCount = count($templates), $i = $templatesCount - 1; $i >= 0; $i--) { + $t = $templates->item($i); + $clone = $this->cloneInnerNode($t, $doc, true, true); + $t->parentNode->replaceChild($clone, $t); + } + } + protected function preInsertionBugFixes(\DOMElement &$element): void { // PHP DOM has a really nasty bug where if a default namespaced element is // inserted to the document and it has non-default namespaced descendants diff --git a/tests/cases/TestDocument.php b/tests/cases/TestDocument.php index 205b5a9..b18915e 100644 --- a/tests/cases/TestDocument.php +++ b/tests/cases/TestDocument.php @@ -268,6 +268,7 @@ class TestDocument extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::getRootNode * @covers \MensBeam\HTML\DOM\Node::hasChildNodes * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity * @covers \MensBeam\HTML\DOM\Inner\Document::__construct @@ -298,6 +299,7 @@ class TestDocument extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct * @covers \MensBeam\HTML\DOM\Node::__construct * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct */ public function testMethod_loadFile(): void { @@ -334,6 +336,7 @@ class TestDocument extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\DOMException::__construct * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct */ public function testMethod_loadFile__errors(): void { @@ -425,6 +428,7 @@ class TestDocument extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct * @covers \MensBeam\HTML\DOM\Node::__construct * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct */ public function testProperty_charset() { @@ -472,6 +476,7 @@ class TestDocument extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::__construct * @covers \MensBeam\HTML\DOM\Node::cloneInnerNode * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct */ public function testProperty_documentURI() { diff --git a/tests/cases/TestElement.php b/tests/cases/TestElement.php index dfd7d12..2dc6eda 100644 --- a/tests/cases/TestElement.php +++ b/tests/cases/TestElement.php @@ -30,6 +30,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::__construct * @covers \MensBeam\HTML\DOM\Node::getInnerDocument * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById * @covers \MensBeam\HTML\DOM\Inner\Document::__construct * @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath @@ -114,6 +115,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::__construct * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode @@ -150,6 +152,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::__construct * @covers \MensBeam\HTML\DOM\Node::getInnerDocument * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get @@ -191,6 +194,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::getInnerDocument * @covers \MensBeam\HTML\DOM\Node::getInnerNode * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode @@ -239,6 +243,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument * @covers \MensBeam\HTML\DOM\Node::getInnerDocument * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode @@ -283,6 +288,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::getInnerDocument * @covers \MensBeam\HTML\DOM\Node::getInnerNode * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode @@ -327,6 +333,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::__construct * @covers \MensBeam\HTML\DOM\Node::getInnerDocument * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Inner\Document::__construct * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get @@ -412,6 +419,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Node::hasChildNodes * @covers \MensBeam\HTML\DOM\Node::insertBefore * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity * @covers \MensBeam\HTML\DOM\ParentNode::walkInner @@ -433,12 +441,12 @@ class TestElement extends \PHPUnit\Framework\TestCase { $dd = $d->createElement('div'); $dd->appendChild($d->createTextNode('beforebegin')); - $p->insertAdjacentElement('beforebegin', $dd); + $p->insertAdjacentElement('beForebEgin', $dd); $this->assertSame('
Ook
', (string)$body); $dd = $d->createElement('div'); $dd->appendChild($d->createTextNode('afterbegin')); - $p->insertAdjacentElement('afterbegin', $dd); + $p->insertAdjacentElement('AfterbeGin', $dd); $this->assertSame('Ook
Ook!', 'UTF-8'); + $body = $d->body; + $p = $d->getElementsByTagName('p')[0]; + $t = $d->getElementsByTagName('template')[0]; + + $p->insertAdjacentHTML('beForebEgin', 'Ook
Ook!', (string)$body); + + $p->insertAdjacentHTML('AfterbeGin', '