From a9bd7c6a5e1a0bd0992555ce3aeeafb4d4aee438 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Wed, 25 Mar 2026 05:21:12 +0900 Subject: [PATCH 1/5] Fix to calculate LinkReferenceDefinition span positions from StringLineGroup.Lines (#931) * Fix to calculate LinkReferenceDefinition span positions from StringLineGroup.Lines * Add test case in TestMultilineInBlockquote * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Change from startPosition + span.End - span.Start to GetAbsolutePosition(lines, span.End) * Move ConvertToAbsoluteSpan and GetAbsolutePosition to StringLineGroup * Remove '\r' check in Write(ReadOnlySpan) --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../TestLinkReferenceDefinition.cs | 12 +++++++ src/Markdig/Helpers/StringLineGroup.cs | 28 +++++++++++++++ src/Markdig/Parsers/ParagraphBlockParser.cs | 36 +++++++++---------- src/Markdig/Renderers/TextRendererBase.cs | 4 +++ 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/Markdig.Tests/RoundtripSpecs/TestLinkReferenceDefinition.cs b/src/Markdig.Tests/RoundtripSpecs/TestLinkReferenceDefinition.cs index a272df0d7..ccc2834a2 100644 --- a/src/Markdig.Tests/RoundtripSpecs/TestLinkReferenceDefinition.cs +++ b/src/Markdig.Tests/RoundtripSpecs/TestLinkReferenceDefinition.cs @@ -209,4 +209,16 @@ public void TestSetextHeader(string value) { RoundTrip(value); } + + [TestCase("> [foo]: /url 'the title'\n")] + [TestCase("> [foo]: /url\n> 'the title'\n")] + [TestCase("> [foo]:\n> /url 'the title'\n")] + [TestCase("> [foo]:\n> /url\n> 'the title'\n")] + [TestCase("> [foo]:\n> /url\n>\n> [foo]\n")] + [TestCase("> [foo]:\n> /url 'the title'\n>\n> [foo]\n")] + [TestCase("> [foo]:\n> /url\n> 'the title'\n>\n> [foo]\n")] + public void TestMultilineInBlockquote(string value) + { + RoundTrip(value); + } } diff --git a/src/Markdig/Helpers/StringLineGroup.cs b/src/Markdig/Helpers/StringLineGroup.cs index 14b6680a9..22072f45c 100644 --- a/src/Markdig/Helpers/StringLineGroup.cs +++ b/src/Markdig/Helpers/StringLineGroup.cs @@ -2,6 +2,7 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using Markdig.Syntax; using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -190,6 +191,33 @@ public void Trim() } } + internal SourceSpan ConvertToAbsoluteSpan(SourceSpan span) + { + if (span.IsEmpty || Count == 0) return span; + + var startPosition = GetAbsolutePosition(span.Start); + var endPosition = GetAbsolutePosition(span.End); + + return new SourceSpan(startPosition, endPosition); + } + + private int GetAbsolutePosition(int position) + { + int offset = 0; + for (int i = 0; i < Count; i++) + { + ref StringSlice slice = ref Lines[i].Slice; + var lineLength = slice.Length + slice.NewLine.Length(); + + if (i == Count - 1 || position < offset + lineLength) + { + return slice.Start + (position - offset); + } + offset += lineLength; + } + return Lines[Count - 1].Slice.End + 1; + } + /// /// Gets or sets the enumerator. /// diff --git a/src/Markdig/Parsers/ParagraphBlockParser.cs b/src/Markdig/Parsers/ParagraphBlockParser.cs index 3773b3c14..71412ee03 100644 --- a/src/Markdig/Parsers/ParagraphBlockParser.cs +++ b/src/Markdig/Parsers/ParagraphBlockParser.cs @@ -212,13 +212,11 @@ private static bool TryMatchLinkReferenceDefinition(ref StringLineGroup lines, B // Correct the locations of each field linkReferenceDefinition.Line = lines.Lines[0].Line; - int startPosition = lines.Lines[0].Slice.Start; - - linkReferenceDefinition.Span = linkReferenceDefinition.Span .MoveForward(startPosition); - linkReferenceDefinition.LabelSpan = linkReferenceDefinition.LabelSpan .MoveForward(startPosition); - linkReferenceDefinition.UrlSpan = linkReferenceDefinition.UrlSpan .MoveForward(startPosition); - linkReferenceDefinition.TitleSpan = linkReferenceDefinition.TitleSpan .MoveForward(startPosition); + linkReferenceDefinition.Span = lines.ConvertToAbsoluteSpan(linkReferenceDefinition.Span); + linkReferenceDefinition.LabelSpan = lines.ConvertToAbsoluteSpan(linkReferenceDefinition.LabelSpan); + linkReferenceDefinition.UrlSpan = lines.ConvertToAbsoluteSpan(linkReferenceDefinition.UrlSpan); + linkReferenceDefinition.TitleSpan = lines.ConvertToAbsoluteSpan(linkReferenceDefinition.TitleSpan); lines = iterator.Remaining(); } else @@ -256,24 +254,23 @@ private static bool TryMatchLinkReferenceDefinitionTrivia(ref StringLineGroup li // Correct the locations of each field lrd.Line = lines.Lines[0].Line; var text = lines.Lines[0].Slice.Text; - int startPosition = lines.Lines[0].Slice.Start; - - triviaBeforeLabel = triviaBeforeLabel.MoveForward(startPosition); - labelWithTrivia = labelWithTrivia.MoveForward(startPosition); - triviaBeforeUrl = triviaBeforeUrl.MoveForward(startPosition); - unescapedUrl = unescapedUrl.MoveForward(startPosition); - triviaBeforeTitle = triviaBeforeTitle.MoveForward(startPosition); - unescapedTitle = unescapedTitle.MoveForward(startPosition); - triviaAfterTitle = triviaAfterTitle.MoveForward(startPosition); - lrd.Span = lrd.Span.MoveForward(startPosition); + + triviaBeforeLabel = lines.ConvertToAbsoluteSpan(triviaBeforeLabel); + labelWithTrivia = lines.ConvertToAbsoluteSpan(labelWithTrivia); + triviaBeforeUrl = lines.ConvertToAbsoluteSpan(triviaBeforeUrl); + unescapedUrl = lines.ConvertToAbsoluteSpan(unescapedUrl); + triviaBeforeTitle = lines.ConvertToAbsoluteSpan(triviaBeforeTitle); + unescapedTitle = lines.ConvertToAbsoluteSpan(unescapedTitle); + triviaAfterTitle = lines.ConvertToAbsoluteSpan(triviaAfterTitle); + lrd.Span = lines.ConvertToAbsoluteSpan(lrd.Span); lrd.TriviaBefore = new StringSlice(text, triviaBeforeLabel.Start, triviaBeforeLabel.End); - lrd.LabelSpan = lrd.LabelSpan.MoveForward(startPosition); + lrd.LabelSpan = lines.ConvertToAbsoluteSpan(lrd.LabelSpan); lrd.LabelWithTrivia = new StringSlice(text, labelWithTrivia.Start, labelWithTrivia.End); lrd.TriviaBeforeUrl = new StringSlice(text, triviaBeforeUrl.Start, triviaBeforeUrl.End); - lrd.UrlSpan = lrd.UrlSpan.MoveForward(startPosition); + lrd.UrlSpan = lines.ConvertToAbsoluteSpan(lrd.UrlSpan); lrd.UnescapedUrl = new StringSlice(text, unescapedUrl.Start, unescapedUrl.End); lrd.TriviaBeforeTitle = new StringSlice(text, triviaBeforeTitle.Start, triviaBeforeTitle.End); - lrd.TitleSpan = lrd.TitleSpan.MoveForward(startPosition); + lrd.TitleSpan = lines.ConvertToAbsoluteSpan(lrd.TitleSpan); lrd.UnescapedTitle = new StringSlice(text, unescapedTitle.Start, unescapedTitle.End); lrd.TriviaAfter = new StringSlice(text, triviaAfterTitle.Start, triviaAfterTitle.End); lrd.LinesBefore = paragraph.LinesBefore; @@ -292,4 +289,5 @@ private static bool TryMatchLinkReferenceDefinitionTrivia(ref StringLineGroup li return atLeastOneFound; } + } diff --git a/src/Markdig/Renderers/TextRendererBase.cs b/src/Markdig/Renderers/TextRendererBase.cs index 199ecb198..983f9c95d 100644 --- a/src/Markdig/Renderers/TextRendererBase.cs +++ b/src/Markdig/Renderers/TextRendererBase.cs @@ -320,6 +320,10 @@ public void Write(ReadOnlySpan content) { WriteIndent(); WriteRaw(content); + if (content[content.Length - 1] == '\n') + { + previousWasLine = true; + } } } From 5365879a23c9f680b9ca6926783daa3f73795dc5 Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Mon, 20 Apr 2026 08:09:26 +0200 Subject: [PATCH 2/5] Fix AbbreviationExtension corrupting emphasis/bold/italic resolution (#935) (#936) * test: add failing regression tests for issue #935 (abbreviation + emphasis) * fix: AbbreviationParser now substitutes post-inline-parse, fixes emphasis corruption (#935) The PostMatch hook approach (running mid-parse) produced a ContainerInline with IsClosed=false that disrupted FindLastContainer(), causing emphasis delimiters to be appended as children of the abbreviation container instead of as root siblings. It also had an off-by-one (i != 0 vs i != content.Start) that caused abbreviations to be missed when the literal started inside emphasis. Replace with a document.ProcessInlinesEnd handler that walks the fully resolved inline tree (after EmphasisInlineParser and LinkInlineParser have run), inserting AbbreviationInline/LiteralInline siblings directly into parent containers without any wrapper ContainerInline. Also update TestSourcePosition.TestAbbreviations to reflect the new tree shape (no container wrapper; empty leading literals at word boundaries are pruned). Fixes #935 --- .../Specs/AbbreviationSpecs.generated.cs | 54 +++++ src/Markdig.Tests/Specs/AbbreviationSpecs.md | 32 ++- src/Markdig.Tests/TestSourcePosition.cs | 2 - .../Abbreviations/AbbreviationParser.cs | 198 +++++++++++------- 4 files changed, 203 insertions(+), 83 deletions(-) diff --git a/src/Markdig.Tests/Specs/AbbreviationSpecs.generated.cs b/src/Markdig.Tests/Specs/AbbreviationSpecs.generated.cs index d965c71a6..95cdf02da 100644 --- a/src/Markdig.Tests/Specs/AbbreviationSpecs.generated.cs +++ b/src/Markdig.Tests/Specs/AbbreviationSpecs.generated.cs @@ -220,5 +220,59 @@ public void ExtensionsAbbreviation_Example011() TestParser.TestSpec("*[Foo]: foo\n*[Foo Bar]: foobar\n\nFoo B", "

Foo B

", "abbreviations|advanced", context: "Example 11\nSection Extensions / Abbreviation\n"); } + + // Abbreviation before emphasis on the same line should be resolved: + [Test] + public void ExtensionsAbbreviation_Example012() + { + // Example 12 + // Section: Extensions / Abbreviation + // + // The following Markdown: + // *[HTML]: HyperText Markup Language + // + // HTML supports **bold** and *italic* + // + // Should be rendered as: + //

HTML supports bold and italic

+ + TestParser.TestSpec("*[HTML]: HyperText Markup Language\n\nHTML supports **bold** and *italic*", "

HTML supports bold and italic

", "abbreviations|advanced", context: "Example 12\nSection Extensions / Abbreviation\n"); + } + + // Abbreviation inside emphasis should be resolved: + [Test] + public void ExtensionsAbbreviation_Example013() + { + // Example 13 + // Section: Extensions / Abbreviation + // + // The following Markdown: + // *[HTML]: HyperText Markup Language + // + // **HTML is great** + // + // Should be rendered as: + //

HTML is great

+ + TestParser.TestSpec("*[HTML]: HyperText Markup Language\n\n**HTML is great**", "

HTML is great

", "abbreviations|advanced", context: "Example 13\nSection Extensions / Abbreviation\n"); + } + + // Abbreviation both before and inside emphasis on the same line should be resolved: + [Test] + public void ExtensionsAbbreviation_Example014() + { + // Example 14 + // Section: Extensions / Abbreviation + // + // The following Markdown: + // *[HTML]: HyperText Markup Language + // + // HTML supports **bold** and **HTML too** + // + // Should be rendered as: + //

HTML supports bold and HTML too

+ + TestParser.TestSpec("*[HTML]: HyperText Markup Language\n\nHTML supports **bold** and **HTML too**", "

HTML supports bold and HTML too

", "abbreviations|advanced", context: "Example 14\nSection Extensions / Abbreviation\n"); + } } } diff --git a/src/Markdig.Tests/Specs/AbbreviationSpecs.md b/src/Markdig.Tests/Specs/AbbreviationSpecs.md index 832b6b2a1..9a31739aa 100644 --- a/src/Markdig.Tests/Specs/AbbreviationSpecs.md +++ b/src/Markdig.Tests/Specs/AbbreviationSpecs.md @@ -118,4 +118,34 @@ The longest matching abbreviation should be used Foo B .

Foo B

-```````````````````````````````` \ No newline at end of file +```````````````````````````````` + +Abbreviation before emphasis on the same line should be resolved: + +```````````````````````````````` example +*[HTML]: HyperText Markup Language + +HTML supports **bold** and *italic* +. +

HTML supports bold and italic

+```````````````````````````````` + +Abbreviation inside emphasis should be resolved: + +```````````````````````````````` example +*[HTML]: HyperText Markup Language + +**HTML is great** +. +

HTML is great

+```````````````````````````````` + +Abbreviation both before and inside emphasis on the same line should be resolved: + +```````````````````````````````` example +*[HTML]: HyperText Markup Language + +HTML supports **bold** and **HTML too** +. +

HTML supports bold and HTML too

+```````````````````````````````` diff --git a/src/Markdig.Tests/TestSourcePosition.cs b/src/Markdig.Tests/TestSourcePosition.cs index fdfb15d95..8d79e1c42 100644 --- a/src/Markdig.Tests/TestSourcePosition.cs +++ b/src/Markdig.Tests/TestSourcePosition.cs @@ -535,13 +535,11 @@ public void TestAbbreviations() { Check("*[HTML]: Hypertext Markup Language\r\n\r\nLater in a text we are using HTML and it becomes an abbr tag HTML\r\n\r\nHTML abbreviation at the beginning of a line", @" paragraph ( 2, 0) 38-102 -container ( 2, 0) 38-102 literal ( 2, 0) 38-66 abbreviation ( 2,29) 67-70 literal ( 2,33) 71-98 abbreviation ( 2,61) 99-102 paragraph ( 4, 0) 107-150 -container ( 4, 0) 107-150 abbreviation ( 4, 0) 107-110 literal ( 4, 4) 111-150 ", "abbreviations"); diff --git a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs index 909e6ee89..e3855cc37 100644 --- a/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs +++ b/src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs @@ -67,14 +67,14 @@ public override BlockState TryOpen(BlockProcessor processor) }; if (!processor.Document.HasAbbreviations()) { - processor.Document.ProcessInlinesBegin += DocumentOnProcessInlinesBegin; + processor.Document.ProcessInlinesEnd += DocumentOnProcessInlinesEnd; } processor.Document.AddAbbreviation(abbr.Label, abbr); return BlockState.BreakDiscard; } - private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline? inline) + private void DocumentOnProcessInlinesEnd(InlineProcessor inlineProcessor, Inline? inline) { var abbreviations = inlineProcessor.Document.GetAbbreviations(); // Should not happen, but another extension could decide to remove them, so... @@ -86,113 +86,151 @@ private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inli // Build a text matcher from the abbreviations labels var prefixTree = new CompactPrefixTree(abbreviations); - inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) => - { - var literal = (LiteralInline)processor.Inline!; - var originalLiteral = literal; - var originalSpanEnd = literal.Span.End; + // Allocate the traversal stack once and reuse it across all leaf blocks. + var stack = new Stack(); - ContainerInline? container = null; + foreach (var leaf in inlineProcessor.Document.Descendants()) + { + if (leaf.Inline is not null) + { + SubstituteInlineTree(leaf.Inline, prefixTree, stack); + } + } + } - // This is slow, but we don't have much the choice - var content = literal.Content; - var text = content.Text; + private static void SubstituteInlineTree( + ContainerInline root, + CompactPrefixTree prefixTree, + Stack stack) + { + stack.Push(root); - for (int i = content.Start; i <= content.End; i++) + while (stack.Count > 0) + { + var container = stack.Pop(); + var child = container.FirstChild; + while (child != null) { - // Abbreviation must be a whole word == start at the start of a line or after a whitespace - if (i != 0) + var next = child.NextSibling; + if (child is LiteralInline literal) { - for (i = i - 1; i <= content.End; i++) - { - if (text[i].IsWhitespace()) - { - i++; - goto ValidAbbreviationStart; - } - } - break; + SubstituteInLiteral(literal, prefixTree); + } + else if (child is ContainerInline childContainer) + { + stack.Push(childContainer); } + child = next; + } + } + } + + private static void SubstituteInLiteral(LiteralInline literal, CompactPrefixTree prefixTree) + { + var content = literal.Content; + var text = content.Text; + var parent = literal.Parent; + + // Nothing to do if this literal has no parent to insert siblings into + if (parent is null) + { + return; + } - ValidAbbreviationStart:; + // Save original span end before any mutations: on the first substitution + // currentLiteral IS literal, so currentLiteral.Span.End = abbrSpanStart - 1 + // would corrupt literal.Span.End, which we need for remaining-literal calculations. + var originalSpanEnd = literal.Span.End; - if (prefixTree.TryMatchLongest(text.AsSpan(i, content.End - i + 1), out KeyValuePair abbreviationMatch)) + // The "current" literal we're truncating as we find abbreviations. + // We start with the original literal — it stays in place and we insert after it. + var currentLiteral = literal; + + for (int i = content.Start; i <= content.End; i++) + { + // Abbreviation must start at the beginning of the content or after whitespace + if (i != content.Start) + { + // Find the next whitespace-separated word start + for (i = i - 1; i <= content.End; i++) { - var match = abbreviationMatch.Key; - if (!IsValidAbbreviationEnding(match, content, i)) + if (text[i].IsWhitespace()) { - continue; + i++; + goto ValidAbbreviationStart; } + } + break; + } - var indexAfterMatch = i + match.Length; + ValidAbbreviationStart:; - // If we don't have a container, create a new one - if (container is null) - { - container = literal.Parent ?? - new ContainerInline - { - Span = originalLiteral.Span, - Line = originalLiteral.Line, - Column = originalLiteral.Column, - }; - } + if (prefixTree.TryMatchLongest(text.AsSpan(i, content.End - i + 1), out KeyValuePair abbreviationMatch)) + { + var match = abbreviationMatch.Key; + if (!IsValidAbbreviationEnding(match, content, i)) + { + continue; + } - var abbrInline = new AbbreviationInline(abbreviationMatch.Value) - { - Span = - { - Start = processor.GetSourcePosition(i, out int line, out int column), - }, - Line = line, - Column = column - }; - abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1; + var indexAfterMatch = i + match.Length; - // Append the previous literal - if (i > content.Start && literal.Parent is null) - { - container.AppendChild(literal); - } + // Compute source position from the original literal's own span/line/column. + // We use literal.Content.Start (the ORIGINAL literal parameter, never reassigned) + // because span positions are always relative to the start of the original literal. + // (InlineProcessor is not available post-parse; this is safe because + // LiteralInlineParser never produces a literal spanning a line break.) + int charOffset = i - literal.Content.Start; // offset from original literal start + var abbrSpanStart = literal.Span.Start + charOffset; + var abbrInline = new AbbreviationInline(abbreviationMatch.Value) + { + Span = new SourceSpan(abbrSpanStart, abbrSpanStart + match.Length - 1), + Line = literal.Line, + Column = literal.Column + charOffset, + }; - literal.Span.End = abbrInline.Span.Start - 1; - // Truncate it before the abbreviation - literal.Content.End = i - 1; + // Truncate currentLiteral to end just before the abbreviation + currentLiteral.Content.End = i - 1; + currentLiteral.Span.End = abbrSpanStart - 1; + // Insert abbreviation after currentLiteral + currentLiteral.InsertAfter(abbrInline); - // Append the abbreviation - container.AppendChild(abbrInline); + // If the truncated literal is now empty (abbreviation was at the very start + // of its content), remove it so it doesn't litter the tree. + if (currentLiteral.Content.End < currentLiteral.Content.Start) + { + currentLiteral.Remove(); + } - // If this is the end of the string, clear the literal and exit - if (content.End == indexAfterMatch - 1) - { - literal = null; - break; - } + // If there is remaining text after the abbreviation, create a new literal for it + if (indexAfterMatch <= content.End) + { + var remainingContent = content; + remainingContent.Start = indexAfterMatch; - // Process the remaining literal - literal = new LiteralInline() + var remainingLiteral = new LiteralInline { + Content = remainingContent, Span = new SourceSpan(abbrInline.Span.End + 1, originalSpanEnd), - Line = line, - Column = column + match.Length, + Line = literal.Line, + Column = literal.Column + (indexAfterMatch - literal.Content.Start), }; - content.Start = indexAfterMatch; - literal.Content = content; + abbrInline.InsertAfter(remainingLiteral); + // Continue scanning from the new literal + currentLiteral = remainingLiteral; + // Update content reference so the loop bounds are correct + content = remainingContent; i = indexAfterMatch - 1; } - } - - if (container != null) - { - if (literal != null) + else { - container.AppendChild(literal); + // No text left — stop + break; } - processor.Inline = container; } - }; + } } private static bool IsValidAbbreviationEnding(string match, StringSlice content, int matchIndex) From 3d6993320588a0cb0b12aaa7adcb48a4849da860 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 20 Apr 2026 16:11:09 +1000 Subject: [PATCH 3/5] Attribute with StringSyntax (#937) --- src/Markdig/Markdown.cs | 18 ++++++------ src/Markdig/Parsers/MarkdownParser.cs | 3 +- .../Polyfills/StringSyntaxAttribute.cs | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 src/Markdig/Polyfills/StringSyntaxAttribute.cs diff --git a/src/Markdig/Markdown.cs b/src/Markdig/Markdown.cs index 3e1d42480..c0effe386 100644 --- a/src/Markdig/Markdown.cs +++ b/src/Markdig/Markdown.cs @@ -54,7 +54,7 @@ private static MarkdownPipeline GetPipeline(MarkdownPipeline? pipeline, string m /// The pipeline. /// A parser context used for the parsing. /// A normalized markdown text. - public static string Normalize(string markdown, NormalizeOptions? options = null, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static string Normalize([StringSyntax("Markdown")] string markdown, NormalizeOptions? options = null, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { var writer = new StringWriter(); Normalize(markdown, writer, options, pipeline, context); @@ -70,7 +70,7 @@ public static string Normalize(string markdown, NormalizeOptions? options = null /// The pipeline. /// A parser context used for the parsing. /// A normalized markdown text. - public static MarkdownDocument Normalize(string markdown, TextWriter writer, NormalizeOptions? options = null, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static MarkdownDocument Normalize([StringSyntax("Markdown")] string markdown, TextWriter writer, NormalizeOptions? options = null, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); @@ -95,7 +95,7 @@ public static MarkdownDocument Normalize(string markdown, TextWriter writer, Nor /// A parser context used for the parsing. /// The HTML string. /// If is null. - public static string ToHtml(string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static string ToHtml([StringSyntax("Markdown")] string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); @@ -159,7 +159,7 @@ public static void ToHtml(this MarkdownDocument document, TextWriter writer, Mar /// A parser context used for the parsing. /// The Markdown document that has been parsed /// if reader or writer variable are null - public static MarkdownDocument ToHtml(string markdown, TextWriter writer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static MarkdownDocument ToHtml([StringSyntax("Markdown")] string markdown, TextWriter writer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); if (writer is null) ThrowHelper.ArgumentNullException_writer(); @@ -181,7 +181,7 @@ public static MarkdownDocument ToHtml(string markdown, TextWriter writer, Markdo /// The pipeline used for the conversion. /// A parser context used for the parsing. /// if markdown or writer variable are null - public static object Convert(string markdown, IMarkdownRenderer renderer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static object Convert([StringSyntax("Markdown")] string markdown, IMarkdownRenderer renderer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); if (renderer is null) ThrowHelper.ArgumentNullException(nameof(renderer)); @@ -201,7 +201,7 @@ public static object Convert(string markdown, IMarkdownRenderer renderer, Markdo /// Whether to parse trivia such as whitespace, extra heading characters and unescaped string values. /// An AST Markdown document /// if markdown variable is null - public static MarkdownDocument Parse(string markdown, bool trackTrivia = false) + public static MarkdownDocument Parse([StringSyntax("Markdown")] string markdown, bool trackTrivia = false) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); @@ -218,7 +218,7 @@ public static MarkdownDocument Parse(string markdown, bool trackTrivia = false) /// A parser context used for the parsing. /// An AST Markdown document /// if markdown variable is null - public static MarkdownDocument Parse(string markdown, MarkdownPipeline? pipeline, MarkdownParserContext? context = null) + public static MarkdownDocument Parse([StringSyntax("Markdown")] string markdown, MarkdownPipeline? pipeline, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); @@ -236,7 +236,7 @@ public static MarkdownDocument Parse(string markdown, MarkdownPipeline? pipeline /// A parser context used for the parsing. /// The Markdown document that has been parsed /// if reader or writer variable are null - public static MarkdownDocument ToPlainText(string markdown, TextWriter writer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static MarkdownDocument ToPlainText([StringSyntax("Markdown")] string markdown, TextWriter writer, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); if (writer is null) ThrowHelper.ArgumentNullException_writer(); @@ -268,7 +268,7 @@ public static MarkdownDocument ToPlainText(string markdown, TextWriter writer, M /// A parser context used for the parsing. /// The result of the conversion /// if markdown variable is null - public static string ToPlainText(string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static string ToPlainText([StringSyntax("Markdown")] string markdown, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (markdown is null) ThrowHelper.ArgumentNullException_markdown(); var writer = new StringWriter(); diff --git a/src/Markdig/Parsers/MarkdownParser.cs b/src/Markdig/Parsers/MarkdownParser.cs index d7c67b17b..082242f69 100644 --- a/src/Markdig/Parsers/MarkdownParser.cs +++ b/src/Markdig/Parsers/MarkdownParser.cs @@ -2,6 +2,7 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Markdig.Helpers; @@ -28,7 +29,7 @@ public static class MarkdownParser /// A parser context used for the parsing. /// An AST Markdown document /// if reader variable is null - public static MarkdownDocument Parse(string text, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) + public static MarkdownDocument Parse([StringSyntax("Markdown")] string text, MarkdownPipeline? pipeline = null, MarkdownParserContext? context = null) { if (text is null) ThrowHelper.ArgumentNullException_text(); diff --git a/src/Markdig/Polyfills/StringSyntaxAttribute.cs b/src/Markdig/Polyfills/StringSyntaxAttribute.cs new file mode 100644 index 000000000..482c1eec5 --- /dev/null +++ b/src/Markdig/Polyfills/StringSyntaxAttribute.cs @@ -0,0 +1,29 @@ +#if !NET7_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class StringSyntaxAttribute : Attribute +{ + public const string Regex = nameof(Regex); + public const string Uri = nameof(Uri); + public const string Json = nameof(Json); + public const string Xml = nameof(Xml); + public const string CompositeFormat = nameof(CompositeFormat); + + public StringSyntaxAttribute(string syntax) + { + Syntax = syntax; + Arguments = Array.Empty(); + } + + public StringSyntaxAttribute(string syntax, params object?[] arguments) + { + Syntax = syntax; + Arguments = arguments; + } + + public string Syntax { get; } + + public object?[] Arguments { get; } +} +#endif From 7dceff2130d81a41b3ec4354c7e146ad9d790a8c Mon Sep 17 00:00:00 2001 From: kapsiR Date: Thu, 7 May 2026 23:13:34 +0200 Subject: [PATCH 4/5] Add opt-in support for nested alert blocks (#938) Introduces AllowNestedAlerts on AlertExtension and AlertInlineParser (default: false) to allow alert blocks inside blockquotes or list items. The UseAlertBlocks() pipeline extension method exposes the new option via an allowNestedAlerts parameter. Resolves #853 Co-authored-by: Claude Sonnet 4.6 --- src/Markdig.Tests/MiscTests.cs | 87 ++++++++++++++++++- .../Extensions/Alerts/AlertExtension.cs | 13 ++- .../Extensions/Alerts/AlertInlineParser.cs | 26 +++++- src/Markdig/MarkdownExtensions.cs | 7 +- 4 files changed, 127 insertions(+), 6 deletions(-) diff --git a/src/Markdig.Tests/MiscTests.cs b/src/Markdig.Tests/MiscTests.cs index ef667f8b6..35dd43312 100644 --- a/src/Markdig.Tests/MiscTests.cs +++ b/src/Markdig.Tests/MiscTests.cs @@ -337,7 +337,7 @@ public void TestGridTableShortLine() "; TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseGridTables().Build()); } - + [Test] public void TestDefinitionListInListItemWithBlankLine() { @@ -387,6 +387,91 @@ Also not a note.

TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build()); } + [Test] + public void TestNestedAlertInsideBlockquote() + { + // >>[!NOTE] should become a blockquote wrapping an alert when AllowNestedAlerts is enabled + var input = @">>[!NOTE] +Also a note. +"; + + var expected = @"
+
+

Note

+

Also a note.

+
+
+"; + TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); + } + + [Test] + public void TestNestedAlertInsideAlert() + { + // An alert inside another alert is never recognized, even with AllowNestedAlerts + var input = @">[!NOTE] +> >[!TIP] +> > A tip inside a note +"; + + var expected = @"
+

Note

+

+
+

[!TIP] +A tip inside a note

+
+
+"; + TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); + } + + [Test] + public void TestAlertInsideListItem() + { + // Alerts inside list items require AllowNestedAlerts + var input = @"- > [!NOTE] + > A note inside a list item +"; + + var expected = @"
    +
  • +
    +

    Note

    +

    A note inside a list item

    +
    +
  • +
+"; + TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); + } + + [Test] + public void TestAlertInsideNestedListItem() + { + // Alert inside a nested list item (list item indented under another list item) requires AllowNestedAlerts + var input = @"- list item 1 +- list item 2 + - > [!NOTE] + > A note inside a nested list item +"; + + var expected = @"
    +
  • list item 1
  • +
  • list item 2 +
      +
    • +
      +

      Note

      +

      A note inside a nested list item

      +
      +
    • +
    +
  • +
+"; + TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks(allowNestedAlerts: true).Build()); + } [Test] public void TestIssue845ListItemBlankLine() diff --git a/src/Markdig/Extensions/Alerts/AlertExtension.cs b/src/Markdig/Extensions/Alerts/AlertExtension.cs index 38fb56984..88724f8da 100644 --- a/src/Markdig/Extensions/Alerts/AlertExtension.cs +++ b/src/Markdig/Extensions/Alerts/AlertExtension.cs @@ -19,13 +19,24 @@ public class AlertExtension : IMarkdownExtension /// public Action? RenderKind { get; set; } + /// + /// Gets or sets a value indicating whether alerts can be nested inside other blocks (e.g. inside a blockquote or a list item). + /// Alerts are never allowed inside another alert block regardless of this setting. + /// Default is false. + /// + public bool AllowNestedAlerts { get; set; } + /// public void Setup(MarkdownPipelineBuilder pipeline) { var inlineParser = pipeline.InlineParsers.Find(); if (inlineParser == null) { - pipeline.InlineParsers.InsertBefore(new AlertInlineParser()); + pipeline.InlineParsers.InsertBefore(new AlertInlineParser() { AllowNestedAlerts = AllowNestedAlerts }); + } + else + { + inlineParser.AllowNestedAlerts = AllowNestedAlerts; } } diff --git a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs index b9259190a..537d6c78a 100644 --- a/src/Markdig/Extensions/Alerts/AlertInlineParser.cs +++ b/src/Markdig/Extensions/Alerts/AlertInlineParser.cs @@ -26,6 +26,13 @@ public AlertInlineParser() OpeningCharacters = ['[']; } + /// + /// Gets or sets a value indicating whether alerts can be nested inside other blocks (e.g. inside a blockquote or a list item). + /// Alerts are never allowed inside another alert block regardless of this setting. + /// Default is false. + /// + public bool AllowNestedAlerts { get; set; } + /// /// Attempts to match the parser at the current position. /// @@ -43,7 +50,8 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) paragraphBlock.Parent is not QuoteBlock quoteBlock || paragraphBlock.Inline?.FirstChild != null || quoteBlock is AlertBlock || - quoteBlock.Parent is not MarkdownDocument) + IsInsideAlertBlock(quoteBlock) || + (!AllowNestedAlerts && quoteBlock.Parent is not MarkdownDocument)) { return false; } @@ -121,4 +129,20 @@ quoteBlock is AlertBlock || return true; } + + private static bool IsInsideAlertBlock(Block block) + { + var parent = block.Parent; + while (parent != null) + { + if (parent is AlertBlock) + { + return true; + } + + parent = parent.Parent; + } + + return false; + } } diff --git a/src/Markdig/MarkdownExtensions.cs b/src/Markdig/MarkdownExtensions.cs index bcc55cb0a..2aec05544 100644 --- a/src/Markdig/MarkdownExtensions.cs +++ b/src/Markdig/MarkdownExtensions.cs @@ -101,11 +101,12 @@ public static MarkdownPipelineBuilder UseAdvancedExtensions(this MarkdownPipelin /// Uses this extension to enable alert blocks. /// /// The pipeline. - /// Replace the default renderer for the kind with a custom renderer + /// Replace the default renderer for the kind with a custom renderer. + /// Allow alerts to be nested inside other blocks (e.g. inside a blockquote or a list item). Alerts inside another alert are never allowed. Default is false. /// The modified pipeline - public static MarkdownPipelineBuilder UseAlertBlocks(this MarkdownPipelineBuilder pipeline, Action? renderKind = null) + public static MarkdownPipelineBuilder UseAlertBlocks(this MarkdownPipelineBuilder pipeline, Action? renderKind = null, bool allowNestedAlerts = false) { - pipeline.Extensions.ReplaceOrAdd(new AlertExtension() { RenderKind = renderKind }); + pipeline.Extensions.ReplaceOrAdd(new AlertExtension() { RenderKind = renderKind, AllowNestedAlerts = allowNestedAlerts }); return pipeline; } From be4da8b42dfdf4d5009c372d814ac2b9f968a8d2 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Tue, 12 May 2026 07:59:41 +0200 Subject: [PATCH 5/5] Remove old changelog.md --- changelog.md | 259 --------------------------------- src/Markdig.Tests/MiscTests.cs | 16 -- 2 files changed, 275 deletions(-) delete mode 100644 changelog.md diff --git a/changelog.md b/changelog.md deleted file mode 100644 index a6fd2683f..000000000 --- a/changelog.md +++ /dev/null @@ -1,259 +0,0 @@ -# Changelog - -## 0.27.0 (23 Jan 2022) -- Fix link reference definition parse bug with title and CRLF ([PR #590](https://github.com/lunet-io/markdig/pull/590)) -- Move tests to net6.0 ([PR #560](https://github.com/lunet-io/markdig/pull/560)) - -## 0.26.0 (27 Aug 2021) -- Fix rendering diff between line endings ([PR #560](https://github.com/lunet-io/markdig/pull/560)) -- Make Mathematics extension respect EnableHtml* options ([PR #570](https://github.com/lunet-io/markdig/pull/570)) - -## 0.25.0 (10 June 2021) -- Fix regression when parsing link reference definitions (#543) -- Make digits in JiraKey's posible ([PR #548](https://github.com/lunet-io/markdig/pull/548)) - -## 0.24.0 (20 Mar 2021) -- Add support for roundtrip Markdown ([PR #481](https://github.com/lunet-io/markdig/pull/481)) -- Introduction of nullability ([PR #522](https://github.com/lunet-io/markdig/pull/522) [PR #524](https://github.com/lunet-io/markdig/pull/524) [PR #525](https://github.com/lunet-io/markdig/pull/525) [PR #526](https://github.com/lunet-io/markdig/pull/526) [PR #527](https://github.com/lunet-io/markdig/pull/527)) -- Various internal cleanup and small performance improvements ([PR #521](https://github.com/lunet-io/markdig/pull/521) [PR #524](https://github.com/lunet-io/markdig/pull/524) [PR #525](https://github.com/lunet-io/markdig/pull/525) [PR #529](https://github.com/lunet-io/markdig/pull/529) [PR #531](https://github.com/lunet-io/markdig/pull/531) [PR #532](https://github.com/lunet-io/markdig/pull/532)) - -## 0.23.0 (16 Jan 2021) -- Add depth limits to avoid pathological-case parsing times/StackOverflows (#500) -- Breaking change: rename AutolineInlineParser to AutolinkInlineParser - -## 0.22.1 (2 Dec 2020) -- Update logo for NuGet package - -## 0.22.0 (05 Oct 2020) -- Fix Setext headings in block quotes. -- Fix tel: treated as autolink ([PR #478](https://github.com/lunet-io/markdig/pull/478)) -- Make Inline.FirstParentOfType public ([PR #474](https://github.com/lunet-io/markdig/pull/474)) -- Fix `&` to be parsed as a punctuation while it was detected as a html entity in certain cases ([PR #471](https://github.com/lunet-io/markdig/pull/471)) -- Add ParentBlock property to ContainerInline ([PR #468](https://github.com/lunet-io/markdig/pull/468)) - -## 0.21.1 (17 Aug 2020) -- Fix Markdig.Signed on GitHub Actions - -## 0.21.0 (17 Aug 2020) -- Restore support for .NET 4.5 (#) -- Add IReadonlyList interface to ContainerBlock to unify and simplify enumeration (#425) -- Fix relative uri detection to be cross-platform compatible (#430) -- Escape URLs scheme (#431) -- Fix media links (#435) -- Fix parsing math blocks with no leading or trailing whitespace (#452) -- Add support for autolink `tel:` uri (#453) -- Fallback to non-punycode encoding for invalid IDN urls (#449) -- Pipe Tables: Normalize using header column count (#455) -- Expose IndentCount of FencedCodeBlock (#464) - -## 0.20.0 (18 Apr 2020) -- Markdig is now compatible only with `NETStandard 2.0`, `NETStandard 2.1`, `NETCoreApp 2.1` and `NETCoreApp 3.1`. -- Many performance improvements from [PR #416](https://github.com/lunet-io/markdig/pull/416) -[PR #417](https://github.com/lunet-io/markdig/pull/417) -[PR #418](https://github.com/lunet-io/markdig/pull/418) -[PR #421](https://github.com/lunet-io/markdig/pull/421) -[PR #422](https://github.com/lunet-io/markdig/pull/422) -[PR #410](https://github.com/lunet-io/markdig/pull/410) - -## 0.18.3 (8 Mar 2020) -- Publish NuGet Symbol packages - -## 0.18.2 (8 Mar 2020) -- Optimize LineReader.ReadLine in [PR #393](https://github.com/lunet-io/markdig/pull/393) -- Use HashSet instead of Dictionary in CharacterMap in [PR #394](https://github.com/lunet-io/markdig/pull/394) -- Use BitVector128 in CharacterMap in [PR #396](https://github.com/lunet-io/markdig/pull/396) -- Optimizations in StringLineGroup in [PR #399](https://github.com/lunet-io/markdig/pull/399) -- Fixed a bug in HeadingRenderer in [PR #402](https://github.com/lunet-io/markdig/pull/402) -- Fixes issue #303 in [PR #404](https://github.com/lunet-io/markdig/pull/404) -- Make output of HtmlTableRenderer XML wellformed in [PR #406](https://github.com/lunet-io/markdig/pull/406) - -## 0.18.1 (21 Jan 2020) -- Re-allow emojis and smileys customization, that was broken in [PR #308](https://github.com/lunet-io/markdig/pull/308) ([PR #386](https://github.com/lunet-io/markdig/pull/386)) -- Add `IHostProvider` for medialink customization (#337), support protocol-less url (#135) ([(PR #341)](https://github.com/lunet-io/markdig/pull/341)) -- Add missing Descendants overload ([(PR #387)](https://github.com/lunet-io/markdig/pull/387)) - -## 0.18.0 (24 Oct 2019) -- Ignore backslashes in GFM AutoLinks ([(PR #357)](https://github.com/lunet-io/markdig/pull/357)) -- Fix SmartyPants quote matching ([(PR #360)](https://github.com/lunet-io/markdig/pull/360)) -- Fix generic attributes with values of length 1 ([(PR #361)](https://github.com/lunet-io/markdig/pull/361)) -- Fix link text balanced bracket matching ([(PR #375)](https://github.com/lunet-io/markdig/pull/375)) -- Improve overall performance and substantially reduce allocations ([(PR #377)](https://github.com/lunet-io/markdig/pull/377)) - -## 0.17.1 (04 July 2019) -- Fix regression when escaping HTML characters ([(PR #340)](https://github.com/lunet-io/markdig/pull/340)) -- Update Emoji Dictionary ([(PR #346)](https://github.com/lunet-io/markdig/pull/346)) - -## 0.17.0 (10 May 2019) -- Update to latest CommonMark specs 0.29 ([(PR #327)](https://github.com/lunet-io/markdig/pull/327)) -- Add `AutoLinkOptions` with `OpenInNewWindow`, `UseHttpsForWWWLinks` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327)) -- Add `DisableHeadings` extension method to `MarkdownPipelineBuilder` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327)) -- Drop support for netstandard1.1 and Portable Class Libraries ([(PR #319)](https://github.com/lunet-io/markdig/pull/319)) -- Allow non-ASCII characters in url domain names ([(PR #319)](https://github.com/lunet-io/markdig/pull/319)) -- Add better support for youtu.be link ([(PR #336)](https://github.com/lunet-io/markdig/pull/336)) -- Fix backsticks in Markdown.Normalize ([(PR #334)](https://github.com/lunet-io/markdig/pull/334)) - -## 0.16.0 (25 Feb 2019) -- Improve performance of emoji-abbreviation parser ([(PR #305)](https://github.com/lunet-io/markdig/pull/305)) -- Change output for math extension to use a rendering more compatible with existing Math JS libraries ([(PR #311)](https://github.com/lunet-io/markdig/pull/311)) -- Improve emphasis parser to allow to match more than 2 characters ([(PR #301)](https://github.com/lunet-io/markdig/pull/301)) -- Output attached attributes to a `` from a table row ([(PR #300)](https://github.com/lunet-io/markdig/pull/300)) -- Improve MarkdownObject.Descendants() search ([(PR #288)](https://github.com/lunet-io/markdig/pull/288)) -- Allow to pass a `MarkdownParserContext` ([(PR #285)](https://github.com/lunet-io/markdig/pull/285)) - -## 0.15.7 (11 Jan 2019) -- Add configurable leading count for ATX headers ([(PR #282)](https://github.com/lunet-io/markdig/pull/282)) -- Render XML well-formed boolean attribute ([(PR #281)](https://github.com/lunet-io/markdig/pull/281)) - -## 0.15.6 (28 Dec 2018) -- Fix potential hang when parsing LinkReferenceDefinition #278 -- Fix parsing of an invalid html entity (#277) -- Fix IndexOutOfRangeException while parsing fenced code block with a single trailing space (#276) -- Add tests for checking that ArgumentOutOfRangeException doesn't occur on invalid input md string (#275) - -## 0.15.5 (11 Dec 2018) -- Empty image alt text for link reference definitions ([(PR #254)](https://github.com/lunet-io/markdig/pull/254)) -- Fix AutoLink Match links without slash after domain ([(PR #260)](https://github.com/lunet-io/markdig/pull/260)) -- Make AutoLink ValidPreviousCharacters configurable ([(PR #264)](https://github.com/lunet-io/markdig/pull/264)) -- Ensuring line breaks when renderer does not have html enabled ([(PR #270)](https://github.com/lunet-io/markdig/pull/270)) - -## 0.15.4 (07 Oct 2018) -- Add autolink domain GFM validation ([(PR #253)](https://github.com/lunet-io/markdig/pull/253)) - -## 0.15.3 (15 Sep 2018) -- Add support for RTL ([(PR #239)](https://github.com/lunet-io/markdig/pull/239)) -- Add MarkdownDocument.LineCount ([(PR #241)](https://github.com/lunet-io/markdig/pull/241)) -- Fix source positions for link definitions ([(PR #243)](https://github.com/lunet-io/markdig/pull/243)) -- Add ListItemBlock.Order ([(PR #244)](https://github.com/lunet-io/markdig/pull/244)) -- Add MarkdownDocument.LineStartIndexes ([(PR #247)](https://github.com/lunet-io/markdig/pull/247)) - -## 0.15.2 (21 Aug 2018) -- Fix footnotes parsing when they are defined after a container that has been closed in the meantime (#223) - -## 0.15.1 (10 July 2018) -- Add support for `netstandard2.0` -- Make AutoIdentifierExtension thread safe - -## 0.15.0 (4 Apr 2018) -- Add `ConfigureNewLine` extension method to `MarkdownPipelineBuilder` ([(PR #214)](https://github.com/lunet-io/markdig/pull/214)) -- Add alternative `Use` extension method to `MarkdownPipelineBuilder` that receives an object instance ([(PR #213)](https://github.com/lunet-io/markdig/pull/213)) -- Added class attribute to media link extension ([(PR #203)](https://github.com/lunet-io/markdig/pull/203)) -- Optional link rewriter func for HtmlRenderer #143 ([(PR #201)](https://github.com/lunet-io/markdig/pull/201)) -- Upgrade NUnit3TestAdapter from 3.2 to 3.9 to address Resharper test runner problems ([(PR #199)](https://github.com/lunet-io/markdig/pull/199)) -- HTML renderer supports converting relative URLs on links and images to absolute #143 ([(PR #197)](https://github.com/lunet-io/markdig/pull/197)) - -## 0.14.9 (15 Jan 2018) -- AutoLinkParser should to remove mailto: in outputted text ([(PR #195)](https://github.com/lunet-io/markdig/pull/195)) -- Add support for `music.yandex.ru` and `ok.ru` for MediaLinks extension ([(PR #193)](https://github.com/lunet-io/markdig/pull/193)) -## 0.14.8 (05 Dec 2017) -- Fix potential StackOverflow exception when processing deep nested `|` delimiters (#179) -## 0.14.7 (25 Nov 2017) -- Fix autolink attached attributes not being displayed properly (#175) -## 0.14.6 (21 Nov 2017) -- Fix yaml frontmatter issue when ending with a empty line (#170) -## 0.14.5 (18 Nov 2017) -- Fix changelog link from nuget package -## 0.14.4 (18 Nov 2017) -- Add changelog.md -- Fix bug when a thematic break is inside a fenced code block inside a pending list (#164) -- Add support for GFM autolinks (#165, #169) -- Better handle YAML frontmatter in case the opening `---` is never actually closed (#160) -- Fix link conflict between a link to an image definition and heading auto-identifiers (#159) -## 0.14.3 -- Make EmojiExtension.EnableSmiley public -## 0.14.2 -- Fix issue with emphasis preceded/followed by an HTML entity (#157) -- Add support for link reference definitions for Normalize renderer (#155) -- Add option to disable smiley parsing in EmojiAndSmiley extension -## 0.14.1 -- Fix crash in Markdown.Normalize to handle HtmlBlock correctly -- Add better handling of bullet character for lists in Markdown.Normalize -## 0.14.0 -- Add Markdown.ToPlainText, Add option HtmlRenderer.EnableHtmlForBlock. -- Add Markdown.Normalize, to allow to normalize a markdown document. Add NormalizeRenderer, to render a MarkdownDocument back to markdown. -## 0.13.4 -- Add support for single table header row without a table body rows (#141) -- ADd support for `nomnoml` diagrams -## 0.13.3 -- Add support for Pandoc YAML frontmatter (#138) -## 0.13.2 -- Add support for UAP10.0 (#137) -## 0.13.1 -- Fix indenting issue after a double digit list block using a tab (#134) -## 0.13.0 -- Update to latest CommonMark specs 0.28 -## 0.12.3 - - Fix issue with HTML blocks for heading h2,h3,h4,h5,h6 that were not correctly identified as HTML blocks as per CommonMark spec -## 0.12.2 - - Fix issue with generic attributes used just before a pipe table (issue #121) -## 0.12.1 - - Fix issue with media links extension when a URL to video is used, an unexpected closing `</iframe>` was inserted (issue #119) -## 0.12.0 - - Add new extension JiraLink support (thanks to @clarkd) - - Fix issue in html attributes not parsing correctly properties (thanks to @meziantou) - - Fix issues detected by an automatic static code analysis tool -## 0.11.0 - - Fix issue with math extension and $$ block parsing not handling correctly beginning of a $$ as a inline math instead (issue #107) - - Fix issue with custom attributes for emphasis - - Add support for new special custom arrows emoji (`->` `<-` `<->` `<=` `=>` `<==>`) -## 0.10.7 - - Fix issue when an url ends by a dot `.` -## 0.10.6 - - Fix emphasis with HTML entities -## 0.10.5 - - Several minor fixes -## 0.10.4 - - Fix issue with autolinks - - Normalize number of columns for tables -## 0.10.3 - - Fix issue with pipetables shifting a cell to a new column (issue #73) -## 0.10.2 - - Fix exception when trying to urlize an url with an unicode character outside the supported range by NormD (issue #75) -## 0.10.1 -- Update to latest CommonMark specs -- Fix source span for LinkReferenceDefinition -## 0.10.0 -- Breaking change of the IMarkdownExtension to allow to receive the MarkdownPipeline for the renderers setup -## 0.9.1 -- Fix regression bug with conflicts between autolink extension and html inline/regular links -## 0.9.0 -- Add new Autolink extension -## 0.8.5 -- Allow to force table column alignment to left -## 0.8.4 -- Fix issue when calculating the span of an indented code block within a list. Make sure to include first whitespace on the line -## 0.8.3 -- fix NullReferenceException with Gridtables extension when a single `+` is entered on a line -## 0.8.2 -- fix potential cast exception with Abbreviation extension and empty literals -## 0.8.1 -- new extension to disable URI escaping for non-US-ASCII characters to workaround a bug in Edge/IE -- Fix an issue with abbreviations with left/right multiple non-punctuation/space characters -## 0.8.0 -- Update to latest CommonMark specs -- Fix empty literal -- Add YAML frontmatter extension -## 0.7.5 -- several bug fixes (pipe tables, disable HTML, special attributes, inline maths, abbreviations...) -- add support for rowspan in grid tables -## 0.7.4 -- Fix bug with strong emphasis starting at the beginning of a line -## 0.7.3 -- Fix threading issue with pipeline -## 0.7.2 -- Fix rendering of table colspan with non english locale -- Fix grid table colspan parsing -- Add nofollow extension for links -## 0.7.1 -- Fix issue in smarty pants which could lead to an InvalidCastException -- Update parsers to latest CommonMark specs -## 0.7.0 -- Update to latest NETStandard.Library 1.6.0 -- Fix issue with digits in auto-identifier extension -- Fix incorrect start of span calculated for code indented blocks -## 0.6.2 -- Handle latest CommonMark specs for corner cases for emphasis (See https://talk.commonmark.org/t/emphasis-strong-emphasis-corner-cases/2123/1 ) -## 0.6.1: -- Fix issue with autoidentifier extension overriding manual HTML attributes id on headings -## 0.6.0 -- Fix conflicts between PipeTables and SmartyPants extensions -- Add SelfPipeline extension diff --git a/src/Markdig.Tests/MiscTests.cs b/src/Markdig.Tests/MiscTests.cs index 35dd43312..50500f54a 100644 --- a/src/Markdig.Tests/MiscTests.cs +++ b/src/Markdig.Tests/MiscTests.cs @@ -113,22 +113,6 @@ public void TestAltTextIsCorrectlyEscaped() @"

"); } - [Test] - public void TestChangelogPRLinksMatchDescription() - { - string solutionFolder = Path.GetFullPath(Path.Combine(TestParser.TestsDirectory, "../..")); - string changelogPath = Path.Combine(solutionFolder, "changelog.md"); - string changelog = File.ReadAllText(changelogPath); - var matches = Regex.Matches(changelog, @"\(\[\(PR #(\d+)\)\]\(.*?pull\/(\d+)\)\)"); - Assert.Greater(matches.Count, 0); - foreach (Match match in matches) - { - Assert.True(int.TryParse(match.Groups[1].Value, out int textNr)); - Assert.True(int.TryParse(match.Groups[2].Value, out int linkNr)); - Assert.AreEqual(textNr, linkNr); - } - } - [Test] public void TestFixHang() {