From 5bfd8c82ec4c3ad089519a52d26a93c83ed4ce5b Mon Sep 17 00:00:00 2001 From: Dustin Wilson Date: Thu, 16 Dec 2021 14:26:26 -0600 Subject: [PATCH] Serializer 100% covered -- FFS --- composer.lock | 4 +- lib/Serializer.php | 36 +++++----- tests/cases/TestSerializer.php | 116 +++++++++++++++++++++++++++++++++ tests/phpunit.dist.xml | 4 +- 4 files changed, 137 insertions(+), 23 deletions(-) diff --git a/composer.lock b/composer.lock index e9d72cd..152bd8d 100644 --- a/composer.lock +++ b/composer.lock @@ -63,7 +63,7 @@ "source": { "type": "git", "url": "mensbeam-gitea:MensBeam/HTML-Parser.git", - "reference": "a8435f7c358faaf39f9a0daa7f7a331a3e7cffca" + "reference": "b90806860312fad8f6da763656784a8a6cf880b0" }, "require": { "ext-dom": "*", @@ -130,7 +130,7 @@ "parsing", "whatwg" ], - "time": "2021-12-05T15:00:39+00:00" + "time": "2021-12-16T15:29:51+00:00" }, { "name": "mensbeam/intl", diff --git a/lib/Serializer.php b/lib/Serializer.php index 828589a..71851f2 100644 --- a/lib/Serializer.php +++ b/lib/Serializer.php @@ -16,6 +16,10 @@ use MensBeam\HTML\Parser\{ class Serializer extends ParserSerializer { + protected static function fragmentHasHost(\DOMDocumentFragment $fragment): bool { + return (Reflection::getProtectedProperty($fragment->ownerDocument->getWrapperNode($fragment), 'host') !== null); + } + protected static function getTemplateContent(\DOMElement $node): \DOMNode { return Reflection::getProtectedProperty($node->ownerDocument->getWrapperNode($node)->content, 'innerNode'); } @@ -39,12 +43,16 @@ class Serializer extends ParserSerializer { } protected static function treatAsBlockWithTemplates(\DOMNode $node): bool { - $xpath = $node->ownerDocument->xpath; - $templates = $xpath->query('.//template[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"][not(ancestor::iframe[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::listing[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noembed[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noframes[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noscript[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::plaintext[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::pre[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::style[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::script[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::textarea[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::title[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::xmp[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"])]', $node); + $parent = $node->parentNode ?? $node; + $document = $node->ownerDocument; + $xpath = $document->xpath; + $templates = $xpath->query('.//template[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"][not(ancestor::iframe[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::listing[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noembed[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noframes[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noscript[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::plaintext[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::pre[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::style[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::script[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::textarea[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::title[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::xmp[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"])]', $parent); foreach ($templates as $t) { - $content = Reflection::getProtectedProperty($t->ownerDocument->getWrapperNode($t)->content, 'innerNode'); - if ($xpath->evaluate(self::BLOCK_QUERY, $content) > 0) { + $content = static::getTemplateContent($t); + $result = ($xpath->evaluate(self::BLOCK_QUERY, $content) > 0); + + if ($result || static::treatAsBlockWithTemplates($content)) { return true; } } @@ -56,24 +64,14 @@ class Serializer extends ParserSerializer { // NOTE: This method is used only when pretty printing. Implementors of userland // PHP DOM solutions with template contents will need to extend this method to // be able to moonwalk through document fragment hosts. - $n = $node; do { - if ($n instanceof \DOMDocumentFragment) { - $host = Reflection::getProtectedProperty($node->ownerDocument->getWrapperNode($n), 'host'); - if ($host !== null) { - $n = Reflection::getProtectedProperty($host->get(), 'innerNode'); - } else { - return true; - } - } else { - if ($n->parentNode !== null && ($n->parentNode->namespaceURI ?? Parser::HTML_NAMESPACE) !== Parser::HTML_NAMESPACE) { - continue; - } + if ($n->parentNode !== null && ($n->parentNode->namespaceURI ?? Parser::HTML_NAMESPACE) !== Parser::HTML_NAMESPACE) { + continue; + } - if (self::treatAsBlock($n->parentNode)) { - return true; - } + if (self::treatAsBlock($n->parentNode)) { + return true; } break; diff --git a/tests/cases/TestSerializer.php b/tests/cases/TestSerializer.php index af2efc8..3ef5c85 100644 --- a/tests/cases/TestSerializer.php +++ b/tests/cases/TestSerializer.php @@ -18,4 +18,120 @@ use MensBeam\HTML\DOM\{ /** @covers \MensBeam\HTML\DOM\Serializer */ class TestSerializer extends \PHPUnit\Framework\TestCase { + public function testMethod_isPreformattedContent(): void { + $d = new Document('
'); + $this->assertSame(<< + + + +
+ + + HTML, $d->serialize(null, [ 'reformatWhitespace' => true ])); + + $frag = $d->createDocumentFragment(); + $p = $frag->appendChild($d->createElement('pre')); + $t = $p->appendChild($d->createElement('template')); + $t->content->appendChild($d->createTextNode('ook')); + $t->content->appendChild($d->createElement('br')); + + $this->assertSame('
', $d->serialize($frag, [ 'reformatWhitespace' => true ])); + + $div = $t->content->appendChild($d->createElement('div')); + $div->appendChild($d->createTextNode('ook')); + + $this->assertSame('
', $d->serialize($frag, [ 'reformatWhitespace' => true ])); + } + + + public function testMethod_treatAsBlockWithTemplates(): void { + $d = new Document('ook'); + + $this->assertSame(<< + ook + + + + HTML, $d->serialize($d->body, [ 'reformatWhitespace' => true ])); + } + + + public function provideMethod_treatForeignRootAsBlock(): iterable { + return [ + [ + function() { + $d = new Document(<< + + + + + + HTML, 'UTF-8'); + + return $d->serialize($d->getElementsByTagName('template')[0]->content->firstChild->firstChild, [ 'reformatWhitespace' => true ]); + }, + + << + + + HTML + ], + + [ + function() { + $d = new Document(<< + + + Ook + + + + HTML, 'UTF-8'); + + return $d->serialize($d->body, [ 'reformatWhitespace' => true ]); + }, + + <<Ook + HTML + ], + + [ + function() { + $d = new Document(<< + + + +
+ + + HTML, 'UTF-8'); + + $svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, 'svg')[0]; + $g = $svg->firstChild->firstChild; + + return $d->serialize($g, [ 'reformatWhitespace' => true ]); + }, + + << + + + HTML + ], + ]; + } + + /** @dataProvider provideMethod_treatForeignRootAsBlock */ + public function testMethod_treatForeignRootAsBlock(\Closure $closure, string $expected): void { + $this->assertSame($expected, $closure()); + } } \ No newline at end of file diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml index 81761e3..1d5d214 100644 --- a/tests/phpunit.dist.xml +++ b/tests/phpunit.dist.xml @@ -33,8 +33,8 @@ cases/TestParentNode.php cases/TestProcessingInstruction.php - +