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:
+ //
", "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:
+ //
", "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
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()
{