Skip to content

Commit

Permalink
fix misaligned tags for overlapping marks
Browse files Browse the repository at this point in the history
  • Loading branch information
faltjo committed May 17, 2023
1 parent ff25575 commit 3180f5e
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 6 deletions.
66 changes: 62 additions & 4 deletions src/Core/DOMSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ public function __construct($schema)
$this->schema = $schema;
}

private function renderNode($node, $previousNode = null, $nextNode = null): string
private function renderNode($node, $previousNode = null, $nextNode = null, &$markStack = []): string
{
$html = [];
$markTagsToClose = [];

if (isset($node->marks)) {
foreach ($node->marks as $mark) {
Expand All @@ -35,6 +36,8 @@ private function renderNode($node, $previousNode = null, $nextNode = null): stri
}

$html[] = $this->renderOpeningTag($renderClass, $mark);
# push recently created mark tag to the stack
$markStack[] = [$renderClass, $mark];
}
}
}
Expand All @@ -57,11 +60,12 @@ private function renderNode($node, $previousNode = null, $nextNode = null): stri
}
// child nodes
elseif (isset($node->content)) {
$nestedNodeMarkStack = [];
foreach ($node->content as $index => $nestedNode) {
$previousNestedNode = $node->content[$index - 1] ?? null;
$nextNestedNode = $node->content[$index + 1] ?? null;

$html[] = $this->renderNode($nestedNode, $previousNestedNode, $nextNestedNode);
$html[] = $this->renderNode($nestedNode, $previousNestedNode, $nextNestedNode, $nestedNodeMarkStack);
}
}
// renderText($node)
Expand Down Expand Up @@ -92,14 +96,66 @@ private function renderNode($node, $previousNode = null, $nextNode = null): stri
continue;
}

$html[] = $this->renderClosingTag($extension->renderHTML($mark));
# remember which mark tags to close
$markTagsToClose[] = [$extension, $mark];
}
}
# close mark tags and reopen when necessary
$html = array_merge($html, $this->closeAndReopenTags($markTagsToClose, $markStack));
}

return join($html);
}

private function closeAndReopenTags(array $markTagsToClose, array &$markStack): array
{
$markTagsToReopen = [];
$closingTags = $this->closeMarkTags($markTagsToClose, $markStack, $markTagsToReopen);
$reopeningTags = $this->reopenMarkTags($markTagsToReopen, $markStack);
return array_merge($closingTags, $reopeningTags);
}

private function closeMarkTags($markTagsToClose, &$markStack, &$markTagsToReopen): array
{
$html = [];
while(!empty($markTagsToClose))
{
# close mark tag from the top of the stack
$markTag = array_pop($markStack);
$markExtension = $markTag[0];
$mark = $markTag[1];
$html[] = $this->renderClosingTag($markExtension->renderHTML( $mark ));

# check if the last closed tag is overlapping and has to be reopened
if(count(array_filter($markTagsToClose, function($markToClose) use ($markExtension, $mark){
return $markExtension == $markToClose[0] && $mark == $markToClose[1];
})) == 0)
{
$markTagsToReopen[] = $markTag;
}
else {
# mark tag does not have to be reopened, but deleted from the 'to close' list
$markTagsToClose = array_udiff($markTagsToClose, [$markTag], function ($a1, $a2) {
return strcmp($a1[1]->type, $a2[1]->type);});
}
}
return $html;
}

private function reopenMarkTags($markTagsToReopen, &$markStack): array
{
$html = [];
# reopen the overlapping mark tags and push them to the stack
foreach(array_reverse($markTagsToReopen) as $markTagToOpen)
{
$renderClass = $markTagToOpen[0];
$mark = $markTagToOpen[1];
$html[] = $this->renderOpeningTag($renderClass, $mark);
$markStack[] = [$renderClass, $mark];
}
return $html;
}

private function isMarkOrNode($markOrNode, $renderClass): bool
{
return isset($markOrNode->type) && $markOrNode->type === $renderClass::$name;
Expand Down Expand Up @@ -331,11 +387,13 @@ public function process(array $value): string

$content = is_array($this->document->content) ? $this->document->content : [];

$markStack = [];

foreach ($content as $index => $node) {
$previousNode = $content[$index - 1] ?? null;
$nextNode = $content[$index + 1] ?? null;

$html[] = $this->renderNode($node, $previousNode, $nextNode);
$html[] = $this->renderNode($node, $previousNode, $nextNode, $markStack);
}

return join($html);
Expand Down
1 change: 0 additions & 1 deletion tests/DOMParser/MultipleMarksTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

test('multiple marks are rendered correctly', function () {
$html = '<p><strong><em>Example Text</em></strong></p>';

$result = (new Editor)->setContent($html)->getDocument();

expect($result)->toEqual([
Expand Down
180 changes: 179 additions & 1 deletion tests/DOMSerializer/MultipleMarksTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,184 @@
];

$result = (new Editor)->setContent($document)->getHTML();

expect($result)->toEqual('<p><strong><em>Example Text</em></strong></p>');
});


test('multiple marks get rendered correctly, with additional mark at the first node', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'text',
'marks' => [
[
'type' => 'italic',
],
[
'type' => 'bold',
],
],
'text' => 'lorem ',
],
[
'type' => 'text',
'marks' => [
[
'type' => 'bold',
],
],
'text' => 'ipsum',
],
],
];
$result = (new Editor)->setContent($document)->getHTML();

expect($result)->toEqual('<em><strong>lorem </strong></em><strong>ipsum</strong>');
});


test('multiple marks get rendered correctly, with additional mark at the last node', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'text',
'marks' => [
[
'type' => 'italic',
],
],
'text' => 'lorem ',
],
[
'type' => 'text',
'marks' => [
[
'type' => 'italic',
],
[
'type' => 'bold',
],
],
'text' => 'ipsum',
],
],
];
$result = (new Editor)->setContent($document)->getHTML();

expect($result)->toEqual('<em>lorem <strong>ipsum</strong></em>');
});


test('multiple marks get rendered correctly, when overlapping marks exist', function () {
$document = [
"type" => "doc",
"content" => [
[
"type" => "paragraph",
"content" => [
[
"type" => "text",
"marks" => [
[
"type" => "bold"
]
],
"text" => "lorem "
],
[
"type" => "text",
"marks" => [
[
"type" => "bold"
],
[
"type" => "italic"
]
],
"text" => "ipsum"
],
[
"type" => "text",
"marks" => [
[
"type" => "italic"
]
],
"text" => " dolor"
],
[
"type" => "text",
"text" => " sit"
],
]
]
]
];

$result = (new Editor)
->setContent($document)
->getHTML();

expect($result)->toEqual('<p><strong>lorem <em>ipsum</em></strong><em> dolor</em> sit</p>');
});


test('multiple marks get rendered correctly, when overlapping passage with multiple marks exist', function () {
$document = [
"type" => "doc",
"content" => [
[
"type" => "paragraph",
"content" => [
[
"type" => "text",
"marks" => [
[
"type" => "bold"
],
[
"type" => "strike"
]
],
"text" => "lorem "
],
[
"type" => "text",
"marks" => [
[
"type" => "italic"
],
[
"type" => "bold"
],
[
"type" => "strike"
]
],
"text" => "ipsum"
],
[
"type" => "text",
"marks" => [
[
"type" => "strike"
],
[
"type" => "italic"
],
],
"text" => " dolor"
]
]
]
]
];

$result = (new Editor)
->setContent($document)
->getHTML();

expect($result)->toEqual('<p><strong><strike>lorem <em>ipsum</em></strike></strong><strike><em> dolor</em></strike></p>');
});

0 comments on commit 3180f5e

Please sign in to comment.