|
5 | 5 | using System.IO.Abstractions; |
6 | 6 | using Cysharp.IO; |
7 | 7 | using Elastic.Documentation.Configuration; |
| 8 | +using Elastic.Markdown.Helpers; |
8 | 9 | using Elastic.Markdown.Myst.CodeBlocks; |
9 | 10 | using Elastic.Markdown.Myst.Comments; |
10 | 11 | using Elastic.Markdown.Myst.Directives; |
|
23 | 24 |
|
24 | 25 | namespace Elastic.Markdown.Myst; |
25 | 26 |
|
26 | | -public class MarkdownParser(BuildContext build, IParserResolvers resolvers) |
| 27 | +public partial class MarkdownParser(BuildContext build, IParserResolvers resolvers) |
27 | 28 | { |
28 | 29 | private BuildContext Build { get; } = build; |
29 | 30 | public IParserResolvers Resolvers { get; } = resolvers; |
@@ -69,7 +70,11 @@ public static MarkdownDocument ParseMarkdownStringAsync(BuildContext build, IPar |
69 | 70 | CrossLinkResolver = resolvers.CrossLinkResolver |
70 | 71 | }; |
71 | 72 | var context = new ParserContext(state); |
72 | | - var markdownDocument = Markdig.Markdown.Parse(markdown, pipeline, context); |
| 73 | + |
| 74 | + // Preprocess substitutions in link patterns before Markdig parsing |
| 75 | + var preprocessedMarkdown = PreprocessLinkSubstitutions(markdown, context); |
| 76 | + |
| 77 | + var markdownDocument = Markdig.Markdown.Parse(preprocessedMarkdown, pipeline, context); |
73 | 78 | return markdownDocument; |
74 | 79 | } |
75 | 80 |
|
@@ -105,7 +110,10 @@ private static async Task<MarkdownDocument> ParseAsync( |
105 | 110 | else |
106 | 111 | inputMarkdown = await path.FileSystem.File.ReadAllTextAsync(path.FullName, ctx); |
107 | 112 |
|
108 | | - var markdownDocument = Markdig.Markdown.Parse(inputMarkdown, pipeline, context); |
| 113 | + // Preprocess substitutions in link patterns before Markdig parsing |
| 114 | + var preprocessedMarkdown = PreprocessLinkSubstitutions(inputMarkdown, (ParserContext)context); |
| 115 | + |
| 116 | + var markdownDocument = Markdig.Markdown.Parse(preprocessedMarkdown, pipeline, context); |
109 | 117 | return markdownDocument; |
110 | 118 | } |
111 | 119 |
|
@@ -166,4 +174,30 @@ public static MarkdownPipeline Pipeline |
166 | 174 | return PipelineCached; |
167 | 175 | } |
168 | 176 | } |
| 177 | + |
| 178 | + [System.Text.RegularExpressions.GeneratedRegex(@"\[([^\]]+)\]\(([^\)]+)\)", System.Text.RegularExpressions.RegexOptions.Multiline)] |
| 179 | + private static partial System.Text.RegularExpressions.Regex LinkPattern(); |
| 180 | + |
| 181 | + /// <summary> |
| 182 | + /// Preprocesses substitutions specifically in link patterns [text](url) before Markdig parsing |
| 183 | + /// </summary> |
| 184 | + private static string PreprocessLinkSubstitutions(string markdown, ParserContext context) => |
| 185 | + LinkPattern().Replace(markdown, match => |
| 186 | + { |
| 187 | + var linkText = match.Groups[1].Value; |
| 188 | + var linkUrl = match.Groups[2].Value; |
| 189 | + |
| 190 | + // Only preprocess external links to preserve internal link validation behavior |
| 191 | + // Check if URL contains substitutions and looks like it might resolve to an external URL |
| 192 | + if (linkUrl.Contains("{{") && (linkUrl.Contains("http") || linkText.Contains("{{"))) |
| 193 | + { |
| 194 | + // Apply substitutions to both link text and URL |
| 195 | + var processedText = linkText.ReplaceSubstitutions(context); |
| 196 | + var processedUrl = linkUrl.ReplaceSubstitutions(context); |
| 197 | + return $"[{processedText}]({processedUrl})"; |
| 198 | + } |
| 199 | + |
| 200 | + // Return original match for internal links |
| 201 | + return match.Value; |
| 202 | + }); |
169 | 203 | } |
0 commit comments