Feature-request: Transform relative file links #755

Open
opened 2026-01-29 14:44:48 +00:00 by claunia · 2 comments
Owner

Originally created by @Abrynos on GitHub (Oct 6, 2025).

Sometimes text is distributed among multiple markdown files, which refer to each other with links such as [Configuration-section](./configuration.md).

When transforming to HTML, it would be nice to link to the corresponding files (./configuration.html in the example above).

While I already have written a RelativeFileLinkExtension and included it in our project, I'd prefer to have it officially supported and would therefore like to create a merge-request for it. Would this be a welcome contribution, @xoofx ?

Code from our project as follows:

public static MarkdownPipelineBuilder TransformFileLinks(this MarkdownPipelineBuilder pipeline)
{
    pipeline.Extensions.AddIfNotAlready<RelativeFileLinkExtension>();
    return pipeline;
}
internal sealed class RelativeFileLinkExtension : IMarkdownExtension
{
    /// <inheritdoc/>
    public void Setup(MarkdownPipelineBuilder pipeline)
    {
        // Make sure we do not have a delegate twice
        pipeline.DocumentProcessed -= OnDocumentProcessed;
        pipeline.DocumentProcessed += OnDocumentProcessed;
    }

    /// <inheritdoc/>
    public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { }

    private static void OnDocumentProcessed(MarkdownDocument document)
    {
        foreach (MarkdownObject node in document.Descendants())
        {
            if (node is not LinkInline link)
            {
                continue;
            }

            string? url = link.Url;
            if (string.IsNullOrEmpty(url))
            {
                continue;
            }

            const string oldExtension = "md";
            const string newExtension = "html";

            // we only want to modify relative paths linking to other .md files
            if (url.StartsWith('.') && url.EndsWith($".{oldExtension}", StringComparison.InvariantCultureIgnoreCase))
            {
                link.Url = string.Create(url.Length + (newExtension.Length - oldExtension.Length), url, static (chars, source) =>
                {
                    source.AsSpan()[..(source.Length - (newExtension.Length - oldExtension.Length))].CopyTo(chars);
                    newExtension.AsSpan().CopyTo(chars[^newExtension.Length..]);
                });
            }
        }
    }
}

Originally created by @Abrynos on GitHub (Oct 6, 2025). Sometimes text is distributed among multiple markdown files, which refer to each other with links such as `[Configuration-section](./configuration.md)`. When transforming to HTML, it would be nice to link to the corresponding files (`./configuration.html` in the example above). While I already have written a `RelativeFileLinkExtension` and included it in our project, I'd prefer to have it officially supported and would therefore like to create a merge-request for it. Would this be a welcome contribution, @xoofx ? Code from our project as follows: ```csharp public static MarkdownPipelineBuilder TransformFileLinks(this MarkdownPipelineBuilder pipeline) { pipeline.Extensions.AddIfNotAlready<RelativeFileLinkExtension>(); return pipeline; } ``` ```csharp internal sealed class RelativeFileLinkExtension : IMarkdownExtension { /// <inheritdoc/> public void Setup(MarkdownPipelineBuilder pipeline) { // Make sure we do not have a delegate twice pipeline.DocumentProcessed -= OnDocumentProcessed; pipeline.DocumentProcessed += OnDocumentProcessed; } /// <inheritdoc/> public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { } private static void OnDocumentProcessed(MarkdownDocument document) { foreach (MarkdownObject node in document.Descendants()) { if (node is not LinkInline link) { continue; } string? url = link.Url; if (string.IsNullOrEmpty(url)) { continue; } const string oldExtension = "md"; const string newExtension = "html"; // we only want to modify relative paths linking to other .md files if (url.StartsWith('.') && url.EndsWith($".{oldExtension}", StringComparison.InvariantCultureIgnoreCase)) { link.Url = string.Create(url.Length + (newExtension.Length - oldExtension.Length), url, static (chars, source) => { source.AsSpan()[..(source.Length - (newExtension.Length - oldExtension.Length))].CopyTo(chars); newExtension.AsSpan().CopyTo(chars[^newExtension.Length..]); }); } } } } ```
claunia added the questionenhancementPR Welcome! labels 2026-01-29 14:44:48 +00:00
Author
Owner

@Abrynos commented on GitHub (Oct 7, 2025):

Also some feedback on the implementation would be appreciated, because I'm sure this is far from ideal 😅

@Abrynos commented on GitHub (Oct 7, 2025): Also some feedback on the implementation would be appreciated, because I'm sure this is far from ideal 😅
Author
Owner

@xoofx commented on GitHub (Oct 28, 2025):

Yes, this is what I do for example in a static website generator here but the resolution is specific to my case (e.g. central place to resolve all links...etc.)

But it could be of some use in simple scenarios. PR welcome. 🙂

@xoofx commented on GitHub (Oct 28, 2025): Yes, this is what I do for example in a static website generator [here](https://github.com/lunet-io/lunet/blob/6aca1c930003c39322d6e68146dd0591b01a7082/src/Lunet.Markdig/MarkdownPlugin.cs#L102-L192) but the resolution is specific to my case (e.g. central place to resolve all links...etc.) But it could be of some use in simple scenarios. PR welcome. 🙂
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/markdig#755