diff --git a/src/Sanitizer.php b/src/Sanitizer.php index 5f299fb..0f7d2a9 100644 --- a/src/Sanitizer.php +++ b/src/Sanitizer.php @@ -184,8 +184,11 @@ protected function replaceNode(DOMNode $source, ?DOMNode $target): ?DOMNode if ($target === null) { $source->parentNode->removeChild($source); } elseif ($source !== $target) { - if ($source->ownerDocument !== $target->ownerDocument) { - $source->ownerDocument->importNode($target); + if ($source->ownerDocument !== $target->ownerDocument + && $source->ownerDocument !== null + && $target->ownerDocument !== null + ) { + $target = $source->ownerDocument->importNode($target, true); } $source->parentNode->replaceChild($target, $source); } diff --git a/src/Visitor/CommonVisitor.php b/src/Visitor/CommonVisitor.php index 92c5602..f1b01f7 100644 --- a/src/Visitor/CommonVisitor.php +++ b/src/Visitor/CommonVisitor.php @@ -114,8 +114,8 @@ public function leaveNode(DOMNode $domNode): ?DOMNode if (!$domNode instanceof DOMElement) { return $domNode; } - $tag = $this->behavior->getTag($domNode->nodeName); - if ($tag === null) { + $node = $this->behavior->getNode($domNode->nodeName); + if ($node === null) { // pass custom elements, in case it has been declared if ($this->behavior->shallAllowCustomElements() && $this->isCustomElement($domNode)) { return $domNode; @@ -124,7 +124,10 @@ public function leaveNode(DOMNode $domNode): ?DOMNode return null; } // completely remove node, in case it is expected to exist with children only - if (!$this->hasNonEmptyChildren($domNode) && $tag->shallPurgeWithoutChildren()) { + if ($node instanceof Behavior\Tag + && $node->shallPurgeWithoutChildren() + && !$this->hasNonEmptyChildren($domNode) + ) { return null; } return $domNode; diff --git a/tests/ScenarioTest.php b/tests/ScenarioTest.php index 386413e..2418ea2 100644 --- a/tests/ScenarioTest.php +++ b/tests/ScenarioTest.php @@ -226,6 +226,42 @@ public function tagIsHandled(Behavior\NodeHandler $nodeHandler, string $payload, self::assertSame($expectation, $sanitizer->sanitize($payload)); } + /** + * @test + */ + public function tagHandlingIsDelegated(): void + { + $behavior = (new Behavior()) + ->withFlags(Behavior::REMOVE_UNEXPECTED_CHILDREN) + ->withName('scenario-test') + ->withTags(new Behavior\Tag('div', Behavior\Tag::ALLOW_CHILDREN)) + ->withNodes( + new Behavior\NodeHandler( + new Behavior\Tag('my-placeholder'), + new Behavior\Handler\ClosureHandler( + static function (NodeInterface $node, ?DOMNode $domNode): ?\DOMNode { + if ($domNode === null) { + return null; + } + $newDocument = new \DOMDocument(); + $text = $newDocument->createTextNode($domNode->textContent); + $span = $newDocument->createElement('div'); + $span->setAttribute('class', 'delegated'); + $span->appendChild($text); + return $span; + } + ) + ) + ); + $sanitizer = new Sanitizer( + $behavior, + new CommonVisitor($behavior) + ); + $payload = '