diff --git a/src/Markdig.Tests/TestAutoLinks.cs b/src/Markdig.Tests/TestAutoLinks.cs index 005c3d1..c72ac01 100644 --- a/src/Markdig.Tests/TestAutoLinks.cs +++ b/src/Markdig.Tests/TestAutoLinks.cs @@ -21,4 +21,28 @@ public class TestAutoLinks Assert.That(html, Is.EqualTo(expected).IgnoreWhiteSpace); } + + // https://github.com/xoofx/markdig/issues/668 + // A heading's implicit reference must not resolve inside another still-open + // link bracket, which would break the outer link. + [Test] + public void TestAutoIdentifierHeadingLinkDoesNotHijackNestedLinkLabel() + { + var markdown = "# Testing Markdown\n\n" + + "### Header\n\n" + + "[Testing [Header]](https://www.bing.com)\n\n" + + "[Testing [test]](https://www.google.com)\n"; + var expected = "

Testing Markdown

\n" + + "

Header

\n" + + "

Testing [Header]

\n" + + "

Testing [test]

"; + + var pipeline = new MarkdownPipelineBuilder() + .UseAutoLinks() + .UseAutoIdentifiers() + .Build(); + var html = Markdown.ToHtml(markdown, pipeline); + + Assert.That(html, Is.EqualTo(expected).IgnoreWhiteSpace); + } } diff --git a/src/Markdig/Extensions/AutoIdentifiers/HeadingLinkReferenceDefinition.cs b/src/Markdig/Extensions/AutoIdentifiers/HeadingLinkReferenceDefinition.cs index 32562cc..b57c785 100644 --- a/src/Markdig/Extensions/AutoIdentifiers/HeadingLinkReferenceDefinition.cs +++ b/src/Markdig/Extensions/AutoIdentifiers/HeadingLinkReferenceDefinition.cs @@ -24,4 +24,11 @@ public class HeadingLinkReferenceDefinition : LinkReferenceDefinition /// Gets or sets the heading related to this link reference definition. /// public HeadingBlock Heading { get; set; } + + /// + /// + /// Created implicitly for every heading, so it must not resolve inside another + /// still-open link bracket, e.g. [Some text [Heading]](url). + /// + internal override bool AllowResolutionInsideOpenLink => false; } diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index 83ac5db..0701ae4 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -150,6 +150,13 @@ public class LinkInlineParser : InlineParser return false; } + // An implicit definition must not hijack a bracket nested inside a + // still-open link, which would break the outer one. + if (!linkRef.AllowResolutionInsideOpenLink && HasActiveAncestorLink(parent)) + { + return false; + } + Inline? link = null; // Try to use a callback directly defined on the LinkReferenceDefinition if (linkRef.CreateLinkInline != null) @@ -430,6 +437,24 @@ public class LinkInlineParser : InlineParser } } + /// + /// Determines whether the delimiter is nested inside another link/image + /// delimiter that may still resolve. + /// + private static bool HasActiveAncestorLink(LinkDelimiterInline delimiter) + { + var inline = delimiter.Parent; + while (inline != null) + { + if (inline is LinkDelimiterInline { IsActive: true }) + { + return true; + } + inline = inline.Parent; + } + return false; + } + private static void MarkParentAsInactive(Inline? inline) { while (inline != null) diff --git a/src/Markdig/Syntax/LinkReferenceDefinition.cs b/src/Markdig/Syntax/LinkReferenceDefinition.cs index 056d91d..e70a574 100644 --- a/src/Markdig/Syntax/LinkReferenceDefinition.cs +++ b/src/Markdig/Syntax/LinkReferenceDefinition.cs @@ -136,6 +136,16 @@ public class LinkReferenceDefinition : LeafBlock /// public CreateLinkInlineDelegate? CreateLinkInline { get; set; } + /// + /// Gets whether this definition may resolve into a link while its label is + /// nested inside another still-open link or image bracket. + /// + /// + /// true for user-authored definitions, preserving CommonMark's "links + /// cannot contain links". Implicitly generated ones override it to false. + /// + internal virtual bool AllowResolutionInsideOpenLink => true; + /// /// Tries to the parse the specified text into a definition. ///