diff --git a/src/HTML5/Serializer/OutputRules.php b/src/HTML5/Serializer/OutputRules.php index a7aa8fc..1b6fcf6 100644 --- a/src/HTML5/Serializer/OutputRules.php +++ b/src/HTML5/Serializer/OutputRules.php @@ -278,6 +278,20 @@ public function comment($ele) $this->wr($ele->ownerDocument->saveXML($ele)); } + /** + * Write an entity reference node. + * + * Such a node carries the entity name only: its nodeValue is null and its + * textContent is empty, so it cannot go through text(). saveXML() gives + * back the reference as it was written, which is what DOMDocument does. + * + * @param \DOMEntityReference $ele The entity reference to write. + */ + public function entityReference($ele) + { + $this->wr($ele->ownerDocument->saveXML($ele)); + } + public function processorInstruction($ele) { $this->wr('') diff --git a/src/HTML5/Serializer/Traverser.php b/src/HTML5/Serializer/Traverser.php index 1e8d792..74a7ad2 100644 --- a/src/HTML5/Serializer/Traverser.php +++ b/src/HTML5/Serializer/Traverser.php @@ -104,6 +104,14 @@ public function node($node) case XML_COMMENT_NODE: $this->rules->comment($node); break; + case XML_ENTITY_REF_NODE: + // entityReference() is not on RulesInterface, so a custom rules + // implementation keeps the previous behaviour of skipping the node + // rather than fataling on a missing method. + if ($this->rules instanceof OutputRules) { + $this->rules->entityReference($node); + } + break; // Currently we don't support embedding DTDs. default: //print ''; diff --git a/test/HTML5/Serializer/OutputRulesTest.php b/test/HTML5/Serializer/OutputRulesTest.php index a410894..72b44f3 100644 --- a/test/HTML5/Serializer/OutputRulesTest.php +++ b/test/HTML5/Serializer/OutputRulesTest.php @@ -366,6 +366,37 @@ public function testText() $this->assertEquals('<script>alert("hi");</script>', stream_get_contents($stream, -1, 0)); } + public function testEntityReference() + { + $dom = $this->html5->loadHTML(' + +
+ '); + + $body = $dom->getElementsByTagName('body')->item(0); + $span = $dom->createElement('span', 'Identité'); + $body->appendChild($span); + + // The entity reference carries the name only: nodeValue is null and + // textContent is empty, so it cannot go through text(). + $reference = $span->lastChild; + $this->assertEquals(XML_ENTITY_REF_NODE, $reference->nodeType); + + // The traverser has to dispatch it, otherwise the node is skipped silently + // and the reference disappears from the output. + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $t->node($span); + $this->assertEquals('Identité', stream_get_contents($stream, -1, 0)); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $r->entityReference($reference); + $this->assertEquals('é', stream_get_contents($stream, -1, 0)); + } + public function testNl() { list($o, $s) = $this->getOutputRules();