From 6549d3b726b32146f3025204abb3bfeec53e3c1c Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Thu, 14 Mar 2024 18:31:11 +0100 Subject: [PATCH] Fixes #780 where only the first paragraph of an alert block is processed. --- .../Specs/AlertBlockSpecs.generated.cs | 56 ++++++++++++++++--- src/Markdig.Tests/Specs/AlertBlockSpecs.md | 34 ++++++++++- .../Extensions/Alerts/AlertInlineParser.cs | 4 ++ src/Markdig/Parsers/InlineProcessor.cs | 23 ++++++++ src/Markdig/Parsers/MarkdownParser.cs | 33 +++++++++++ 5 files changed, 141 insertions(+), 9 deletions(-) diff --git a/src/Markdig.Tests/Specs/AlertBlockSpecs.generated.cs b/src/Markdig.Tests/Specs/AlertBlockSpecs.generated.cs index 78cf29c1..a3f920df 100644 --- a/src/Markdig.Tests/Specs/AlertBlockSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/AlertBlockSpecs.generated.cs @@ -88,19 +88,59 @@ public void ExtensionsAlertBlocks_Example002() // Testing rendering for multiple lines

//
var test = "I can also add code to panels
             //     
- //

+ //

Inline code testing

// - 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`", "
\n

Note

\n

Highlights information that users should take into account, even when skimming.\nTesting rendering for multiple lines

\n
var test = "I can also add code to panels\n
\n

\n
", "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`", "
\n

Note

\n

Highlights information that users should take into account, even when skimming.\nTesting rendering for multiple lines

\n
var test = "I can also add code to panels\n
\n

Inline code testing

\n
", "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: + //
+ //

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

+ //
+ + 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", "
\n

Note

\n

Highlights information that users should take into account, even when skimming.

\n

Testing rendering for multiple lines

\n

Inline code testing

\n

Other line

\n
\n

Nested quote

\n

Final nested quote line

\n
\n

Final line of alert

\n
", "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: @@ -113,14 +153,14 @@ public void ExtensionsAlertBlocks_Example003() // Highlights information that users should take into account, even when skimming.

// - TestParser.TestSpec("> [!NOTE] This is invalid because no new line\n> Highlights information that users should take into account, even when skimming.", "
\n

[!NOTE] This is invalid because no new line\nHighlights information that users should take into account, even when skimming.

\n
", "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.", "
\n

[!NOTE] This is invalid because no new line\nHighlights information that users should take into account, even when skimming.

\n
", "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: @@ -133,7 +173,7 @@ public void ExtensionsAlertBlocks_Example004() // Highlights information that users should take into account, even when skimming.

// - TestParser.TestSpec("> This is not a [!NOTE]\n> Highlights information that users should take into account, even when skimming.", "
\n

This is not a [!NOTE]\nHighlights information that users should take into account, even when skimming.

\n
", "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.", "
\n

This is not a [!NOTE]\nHighlights information that users should take into account, even when skimming.

\n
", "advanced", context: "Example 5\nSection Extensions / Alert Blocks\n"); } } } diff --git a/src/Markdig.Tests/Specs/AlertBlockSpecs.md b/src/Markdig.Tests/Specs/AlertBlockSpecs.md index 015c0290..c2a4b77c 100644 --- a/src/Markdig.Tests/Specs/AlertBlockSpecs.md +++ b/src/Markdig.Tests/Specs/AlertBlockSpecs.md @@ -62,7 +62,39 @@ Example with code blocks and mix formatting: Testing rendering for multiple lines

var test = "I can also add code to panels
 
-

+

Inline code testing

+ +```````````````````````````````` + +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 +. +
+

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

```````````````````````````````` diff --git a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs index 347b4d52..f5a5881d 100644 --- a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs +++ b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs @@ -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; } } diff --git a/src/Markdig/Parsers/InlineProcessor.cs b/src/Markdig/Parsers/InlineProcessor.cs index 8309dc03..d4624c4f 100644 --- a/src/Markdig/Parsers/InlineProcessor.cs +++ b/src/Markdig/Parsers/InlineProcessor.cs @@ -29,6 +29,8 @@ public class InlineProcessor private readonly List lineOffsets = []; private int previousSliceOffset; private int previousLineIndexForSliceOffset; + internal ContainerBlock? PreviousContainerToReplace; + internal ContainerBlock? NewContainerToReplace; /// /// Initializes a new instance of the class. @@ -203,6 +205,24 @@ public int GetSourcePosition(int sliceOffset) return 0; } + /// + /// Replace a parent container. This method is experimental and should be used with caution. + /// + /// The previous parent container to replace + /// The new parent container + /// If a new parent container has been already setup. + 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; + } + /// /// Processes the inline of the specified . /// @@ -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); diff --git a/src/Markdig/Parsers/MarkdownParser.cs b/src/Markdig/Parsers/MarkdownParser.cs index 206bcc1b..ebdb364a 100644 --- a/src/Markdig/Parsers/MarkdownParser.cs +++ b/src/Markdig/Parsers/MarkdownParser.cs @@ -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);