Skip to content

Commit

Permalink
Fixes #780 where only the first paragraph of an alert block is proces…
Browse files Browse the repository at this point in the history
…sed.
  • Loading branch information
xoofx committed Mar 14, 2024
1 parent d434f00 commit 6549d3b
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 9 deletions.
56 changes: 48 additions & 8 deletions src/Markdig.Tests/Specs/AlertBlockSpecs.generated.cs
Expand Up @@ -88,19 +88,59 @@ public void ExtensionsAlertBlocks_Example002()
// Testing rendering for multiple lines</p>
// <pre><code class="language-csharp">var test = &quot;I can also add code to panels
// </code></pre>
// <p></p>
// <p><code>Inline code testing</code></p>
// </div>

TestParser.TestSpec("> [!NOTE]\n> Highlights information that users should take into account, even when skimming.\n> Testing rendering for multiple lines\n> ```csharp\n> var test = \"I can also add code to panels\n> ```\n> `Inline code testing`", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.\nTesting rendering for multiple lines</p>\n<pre><code class=\"language-csharp\">var test = &quot;I can also add code to panels\n</code></pre>\n<p></p>\n</div>", "advanced", context: "Example 2\nSection Extensions / Alert Blocks\n");
TestParser.TestSpec("> [!NOTE]\n> Highlights information that users should take into account, even when skimming.\n> Testing rendering for multiple lines\n> ```csharp\n> var test = \"I can also add code to panels\n> ```\n> `Inline code testing`", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.\nTesting rendering for multiple lines</p>\n<pre><code class=\"language-csharp\">var test = &quot;I can also add code to panels\n</code></pre>\n<p><code>Inline code testing</code></p>\n</div>", "advanced", context: "Example 2\nSection Extensions / Alert Blocks\n");
}

// Multiline:
[Test]
public void ExtensionsAlertBlocks_Example003()
{
// Example 3
// Section: Extensions / Alert Blocks
//
// The following Markdown:
// > [!NOTE]
// > Highlights information that users should take into account, even when skimming.
// >
// > Testing rendering for multiple lines
// >
// > `Inline code testing`
// >
// > Other line
// >
// > > Nested quote
// > >
// > > Final nested quote line
// >
// > Final line of alert
//
// Should be rendered as:
// <div class="markdown-alert markdown-alert-note">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
// <p>Highlights information that users should take into account, even when skimming.</p>
// <p>Testing rendering for multiple lines</p>
// <p><code>Inline code testing</code></p>
// <p>Other line</p>
// <blockquote>
// <p>Nested quote</p>
// <p>Final nested quote line</p>
// </blockquote>
// <p>Final line of alert</p>
// </div>

TestParser.TestSpec("> [!NOTE]\n> Highlights information that users should take into account, even when skimming.\n> \n> Testing rendering for multiple lines\n> \n> `Inline code testing`\n> \n> Other line\n> \n> > Nested quote\n> >\n> > Final nested quote line\n> \n> Final line of alert", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.</p>\n<p>Testing rendering for multiple lines</p>\n<p><code>Inline code testing</code></p>\n<p>Other line</p>\n<blockquote>\n<p>Nested quote</p>\n<p>Final nested quote line</p>\n</blockquote>\n<p>Final line of alert</p>\n</div>", "advanced", context: "Example 3\nSection Extensions / Alert Blocks\n");
}

// An alert inline (e.g `[!NOTE]`) must come first in a quote block, and must be followed by optional spaces with a new line. If no new lines are found, it will not be considered as an alert block.
//
// Followed by space and new line:
[Test]
public void ExtensionsAlertBlocks_Example003()
public void ExtensionsAlertBlocks_Example004()
{
// Example 3
// Example 4
// Section: Extensions / Alert Blocks
//
// The following Markdown:
Expand All @@ -113,14 +153,14 @@ public void ExtensionsAlertBlocks_Example003()
// Highlights information that users should take into account, even when skimming.</p>
// </blockquote>

TestParser.TestSpec("> [!NOTE] This is invalid because no new line\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>[!NOTE] This is invalid because no new line\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 3\nSection Extensions / Alert Blocks\n");
TestParser.TestSpec("> [!NOTE] This is invalid because no new line\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>[!NOTE] This is invalid because no new line\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 4\nSection Extensions / Alert Blocks\n");
}

// Must come first in a quote block:
[Test]
public void ExtensionsAlertBlocks_Example004()
public void ExtensionsAlertBlocks_Example005()
{
// Example 4
// Example 5
// Section: Extensions / Alert Blocks
//
// The following Markdown:
Expand All @@ -133,7 +173,7 @@ public void ExtensionsAlertBlocks_Example004()
// Highlights information that users should take into account, even when skimming.</p>
// </blockquote>

TestParser.TestSpec("> This is not a [!NOTE]\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>This is not a [!NOTE]\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 4\nSection Extensions / Alert Blocks\n");
TestParser.TestSpec("> This is not a [!NOTE]\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>This is not a [!NOTE]\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 5\nSection Extensions / Alert Blocks\n");
}
}
}
34 changes: 33 additions & 1 deletion src/Markdig.Tests/Specs/AlertBlockSpecs.md
Expand Up @@ -62,7 +62,39 @@ Example with code blocks and mix formatting:
Testing rendering for multiple lines</p>
<pre><code class="language-csharp">var test = &quot;I can also add code to panels
</code></pre>
<p></p>
<p><code>Inline code testing</code></p>
</div>
````````````````````````````````

Multiline:

```````````````````````````````` example
> [!NOTE]
> Highlights information that users should take into account, even when skimming.
>
> Testing rendering for multiple lines
>
> `Inline code testing`
>
> Other line
>
> > Nested quote
> >
> > Final nested quote line
>
> Final line of alert
.
<div class="markdown-alert markdown-alert-note">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
<p>Highlights information that users should take into account, even when skimming.</p>
<p>Testing rendering for multiple lines</p>
<p><code>Inline code testing</code></p>
<p>Other line</p>
<blockquote>
<p>Nested quote</p>
<p>Final nested quote line</p>
</blockquote>
<p>Final line of alert</p>
</div>
````````````````````````````````

Expand Down
4 changes: 4 additions & 0 deletions src/Markdig/Extensions/Alerts/AlertInlineParser.cs
Expand Up @@ -118,6 +118,10 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
alertBlock.Add(block);
}

// Workaround to replace the parent container
// Experimental API, so we are keeping it internal for now until we are sure it's the way we want to go
processor.ReplaceParentContainer(quoteBlock, alertBlock);

return true;
}
}
23 changes: 23 additions & 0 deletions src/Markdig/Parsers/InlineProcessor.cs
Expand Up @@ -29,6 +29,8 @@ public class InlineProcessor
private readonly List<StringLineGroup.LineOffset> lineOffsets = [];
private int previousSliceOffset;
private int previousLineIndexForSliceOffset;
internal ContainerBlock? PreviousContainerToReplace;
internal ContainerBlock? NewContainerToReplace;

/// <summary>
/// Initializes a new instance of the <see cref="InlineProcessor" /> class.
Expand Down Expand Up @@ -203,6 +205,24 @@ public int GetSourcePosition(int sliceOffset)
return 0;
}

/// <summary>
/// Replace a parent container. This method is experimental and should be used with caution.
/// </summary>
/// <param name="previousParentContainer">The previous parent container to replace</param>
/// <param name="newParentContainer">The new parent container</param>
/// <exception cref="InvalidOperationException">If a new parent container has been already setup.</exception>
internal void ReplaceParentContainer(ContainerBlock previousParentContainer, ContainerBlock newParentContainer)
{
// Limitation for now, only one parent container can be replaced.
if (PreviousContainerToReplace != null)
{
throw new InvalidOperationException("A block is already being replaced");
}

PreviousContainerToReplace = previousParentContainer;
NewContainerToReplace = newParentContainer;
}

/// <summary>
/// Processes the inline of the specified <see cref="LeafBlock"/>.
/// </summary>
Expand All @@ -211,6 +231,9 @@ public void ProcessInlineLeaf(LeafBlock leafBlock)
{
if (leafBlock is null) ThrowHelper.ArgumentNullException_leafBlock();

PreviousContainerToReplace = null;
NewContainerToReplace = null;

// clear parser states
Array.Clear(ParserStates, 0, ParserStates.Length);

Expand Down
33 changes: 33 additions & 0 deletions src/Markdig/Parsers/MarkdownParser.cs
Expand Up @@ -170,6 +170,39 @@ private static void ProcessInlines(InlineProcessor inlineProcessor, MarkdownDocu
if (leafBlock.ProcessInlines)
{
inlineProcessor.ProcessInlineLeaf(leafBlock);

// Experimental code to handle a replacement of a parent container
// Not satisfied with this code, so we are keeping it internal for now
if (inlineProcessor.PreviousContainerToReplace != null)
{
if (container == inlineProcessor.PreviousContainerToReplace)
{
item = new ContainerItem(inlineProcessor.NewContainerToReplace!) { Index = item.Index };
container = item.Container;
}
else
{
bool parentBlockFound = false;
for (int i = blockCount - 2; i >= 0; i--)
{
ref var parentBlock = ref blocks[i];
if (parentBlock.Container == inlineProcessor.PreviousContainerToReplace)
{
parentBlock = new ContainerItem(inlineProcessor.NewContainerToReplace!) { Index = parentBlock.Index };
break;
}
}

if (!parentBlockFound)
{
throw new InvalidOperationException("Cannot find the parent block to replace");
}
}

inlineProcessor.PreviousContainerToReplace = null;
inlineProcessor.NewContainerToReplace = null;
}

if (leafBlock.RemoveAfterProcessInlines)
{
container.RemoveAt(item.Index);
Expand Down

0 comments on commit 6549d3b

Please sign in to comment.