Custom Contains - Custom rendering #301

Closed
opened 2026-01-29 14:33:10 +00:00 by claunia · 12 comments
Owner

Originally created by @LukeTOBrien on GitHub (May 29, 2019).

Hello,

I am moving my project from NodeJS using Markdown-it to .NET Core using Markdig.

I am using the Markdown-it-containers plugin.
As you can see from the Example on that page, the plugin allows custom rendering, so instead of div the developer can use any element they choose.

It my project I make use of this in quite a few cases, for example I have Markdown like this:

::: notes

:::

This renders as:

<aside class="notes">
</aside>

Would there be anyway to do this in Markdig?
I notice in you current spec you do not specify a mechanism for custom rendering.

Thanks

Originally created by @LukeTOBrien on GitHub (May 29, 2019). Hello, I am moving my project from NodeJS using Markdown-it to .NET Core using Markdig. I am using the [Markdown-it-containers](https://github.com/markdown-it/markdown-it-container) plugin. As you can see from the **Example** on that page, the plugin allows custom rendering, so instead of `div` the developer can use any element they choose. It my project I make use of this in quite a few cases, for example I have Markdown like this: ```Markdown ::: notes ::: ``` This renders as: ```HTML <aside class="notes"> </aside> ``` Would there be anyway to do this in Markdig? I notice in you [current spec](https://github.com/lunet-io/markdig/blob/master/src/Markdig.Tests/Specs/CustomContainerSpecs.md) you do not specify a mechanism for custom rendering. Thanks
Author
Owner

@MihaZupan commented on GitHub (May 29, 2019):

The CustomContainers extension emits CustomContainer objects into the document.
You can create your own renderer for that specific object - pretty much copying what the current renderer does.

All you have to do then is ensure that your renderer takes priority over the older one - insert it before that one in the list of renderers.

So you end up with something like

public class HtmlCustomContainerRenderer_ButDifferent : HtmlObjectRenderer<CustomContainer>
{
    public readonly string Tag;

    public HtmlCustomContainerRenderer_ButDifferent(string tag)
    {
        Tag = tag ?? "div";
    }

    protected override void Write(HtmlRenderer renderer, CustomContainer obj)
    {
        renderer.EnsureLine();
        renderer.Write('<').Write(Tag).WriteAttributes(obj).Write('>');
        renderer.WriteChildren(obj);
        renderer.Write("</").Write(Tag).WriteLine(">");
    }
}

used by

var pipeline = new MarkdownPipelineBuilder()
    .UseCustomContainers()
    .Build();

StringWriter writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
pipeline.Setup(renderer);

// Simply add this line
renderer.ObjectRenderers.Insert(0, new HtmlCustomContainerRenderer_ButDifferent("test"));

MarkdownDocument document = Markdown.Parse(markdown, pipeline);

renderer.Render(document);
writer.Flush();
string html = writer.ToString();

Console.WriteLine(html);
@MihaZupan commented on GitHub (May 29, 2019): The CustomContainers extension emits `CustomContainer` objects into the document. You can create your own renderer for that specific object - pretty much copying [what the current renderer does](https://github.com/lunet-io/markdig/blob/master/src/Markdig/Extensions/CustomContainers/HtmlCustomContainerRenderer.cs). All you have to do then is ensure that your renderer takes priority over the older one - insert it before that one in the list of renderers. So you end up with something like ```cs public class HtmlCustomContainerRenderer_ButDifferent : HtmlObjectRenderer<CustomContainer> { public readonly string Tag; public HtmlCustomContainerRenderer_ButDifferent(string tag) { Tag = tag ?? "div"; } protected override void Write(HtmlRenderer renderer, CustomContainer obj) { renderer.EnsureLine(); renderer.Write('<').Write(Tag).WriteAttributes(obj).Write('>'); renderer.WriteChildren(obj); renderer.Write("</").Write(Tag).WriteLine(">"); } } ``` used by ```cs var pipeline = new MarkdownPipelineBuilder() .UseCustomContainers() .Build(); StringWriter writer = new StringWriter(); var renderer = new HtmlRenderer(writer); pipeline.Setup(renderer); // Simply add this line renderer.ObjectRenderers.Insert(0, new HtmlCustomContainerRenderer_ButDifferent("test")); MarkdownDocument document = Markdown.Parse(markdown, pipeline); renderer.Render(document); writer.Flush(); string html = writer.ToString(); Console.WriteLine(html); ```
Author
Owner

@MihaZupan commented on GitHub (May 29, 2019):

I suppose a parameter with a div default could be exposed on the existing extension as well.

@MihaZupan commented on GitHub (May 29, 2019): I suppose a parameter with a `div` default could be exposed on the existing extension as well.
Author
Owner

@LukeTOBrien commented on GitHub (May 29, 2019):

Thank you very much for your quick response.
When I get some time I will test this out.

Would this preserve the class?
There is no test for whether or not to to use the custom renderer, I only want some containers to appear as aside and some to appear as div as per normal.
Would the above code apply to all container rather than just some of them?

I also know that you are currently working on official documentation for the project, I think custom rendering for containers could be a common thing that a lot of folks would want to do, so perhaps this could make it in to the official documentation?

@LukeTOBrien commented on GitHub (May 29, 2019): Thank you very much for your quick response. When I get some time I will test this out. Would this preserve the class? There is no test for whether or not to to use the custom renderer, I only want some containers to appear as `aside` and some to appear as `div` as per normal. Would the above code apply to all container rather than just some of them? I also know that you are currently working on official documentation for the project, I think custom rendering for containers could be a common thing that a lot of folks would want to do, so perhaps this could make it in to the official documentation?
Author
Owner

@MihaZupan commented on GitHub (May 29, 2019):

A renderer is a catch-all for that type. You can of course choose the tag based on the info string that is exposed on the object when rendering said object.

string tag = obj.Info == "notes" ? "smth" : "div";

I don't know what you mean by official documentation. It would certainly be useful but I'm not working on any rn.

@MihaZupan commented on GitHub (May 29, 2019): A renderer is a catch-all for that type. You can of course choose the tag based on the info string that is exposed on the object when rendering said object. ```cs string tag = obj.Info == "notes" ? "smth" : "div"; ``` I don't know what you mean by official documentation. It would certainly be useful but I'm not working on any rn.
Author
Owner

@LukeTOBrien commented on GitHub (May 29, 2019):

I will try some tests when I get home.

About the 'official' documentation thing, on the main readme:

The repository is under construction. There will be a dedicated website and proper documentation at some point!

'Proper' or 'Official', I guess somebody's working on it.
Anyway I have written some Unit Test for the Header Section #342 , so perhaps I could contribute them and also help with documentation?

@LukeTOBrien commented on GitHub (May 29, 2019): I will try some tests when I get home. About the 'official' documentation thing, [on the main readme](https://github.com/lunet-io/markdig/blob/master/readme.md#documentation): > The repository is under construction. There will be a dedicated website and proper documentation at some point! 'Proper' or 'Official', I guess somebody's working on it. Anyway I have written some Unit Test for the Header Section #342 , so perhaps I could contribute them and also help with documentation?
Author
Owner

@xoofx commented on GitHub (May 29, 2019):

'Proper' or 'Official', I guess somebody's working on it.

Yeah, sorry, the readme hasn't been updated since then, but nobody is working on it and there is no plan to work on that.
It is not a huge amount of work, but it is certainly several days of work to get a descent documentation up and running (with proper docgen via docfx...), and I have personally no spare time left allocated for that.

@xoofx commented on GitHub (May 29, 2019): > 'Proper' or 'Official', I guess somebody's working on it. Yeah, sorry, the readme hasn't been updated since then, but nobody is working on it and there is no plan to work on that. It is not a huge amount of work, but it is certainly several days of work to get a descent documentation up and running (with proper docgen via docfx...), and I have personally no spare time left allocated for that.
Author
Owner

@MihaZupan commented on GitHub (May 29, 2019):

I can take a look at docfx, haven't actually tried it yet

@MihaZupan commented on GitHub (May 29, 2019): I can take a look at docfx, haven't actually tried it yet
Author
Owner

@LukeTOBrien commented on GitHub (May 29, 2019):

Hmm, I have never come across docfx.

You already have .md files in the spec folder, if it was me I would use Hugo (Whisper Theme) with a GitHub pages repo and copy the existing .md files.

@LukeTOBrien commented on GitHub (May 29, 2019): Hmm, I have never come across docfx. You already have `.md` files in the [spec folder](https://github.com/lunet-io/markdig/tree/master/src/Markdig.Tests/Specs), if it was me I would use Hugo ([Whisper Theme](https://themes.gohugo.io/hugo-whisper-theme/)) with a GitHub pages repo and copy the existing `.md` files.
Author
Owner

@MihaZupan commented on GitHub (May 29, 2019):

I have used mdbook before on a different project, but it seems fitting to use docfx since it's powered by Markdig. If I gather correctly it's what docs.microsoft.com uses.

The markdown in spec folders essentially doubles as a test suite, but some words about parsing, rendering and the AST would be required.

@MihaZupan commented on GitHub (May 29, 2019): I have used [mdbook](https://github.com/rust-lang-nursery/mdBook) before on a different project, but it seems fitting to use docfx since it's powered by Markdig. If I gather correctly it's what docs.microsoft.com uses. The markdown in spec folders essentially doubles as a test suite, but some words about parsing, rendering and the AST would be required.
Author
Owner

@LukeTOBrien commented on GitHub (May 31, 2019):

Hello there,

I finally got to sit down and test this thing out, it works!
I have also been wondering about commiting this code and #342 into the main Markdig repo - Under perhaps ExsensionHelpers or Helpers namespace. What do you think about that? Would you be interested?

Here is the class I came up with:

    public class ContainerRenderer : HtmlObjectRenderer<CustomContainer>
    {
        public readonly string testClass;
        public readonly string tagName;

        public ContainerRenderer(string testClass, string tagName)
        {
            this.testClass = testClass;
            this.tagName = tagName;
        }

        protected override void Write(HtmlRenderer renderer, CustomContainer obj)
        {
            if (obj.Info == testClass)
            {
                renderer.EnsureLine();

                renderer.Write($"<{tagName}")
                    .WriteAttributes(obj)
                    .Write(">");

                renderer.WriteChildren(obj);

                renderer.Write($"</{tagName}>");
            }
        }
    }

Then for my purposes:

    public class NotesContainerRenderer : ContainerRenderer
    {
        public NotesContainerRenderer()
            : base("notes", "aside")
        {
        }
    }
@LukeTOBrien commented on GitHub (May 31, 2019): Hello there, I finally got to sit down and test this thing out, it works! I have also been wondering about commiting this code and #342 into the main Markdig repo - Under perhaps `ExsensionHelpers` or `Helpers` namespace. What do you think about that? Would you be interested? Here is the class I came up with: ```csharp public class ContainerRenderer : HtmlObjectRenderer<CustomContainer> { public readonly string testClass; public readonly string tagName; public ContainerRenderer(string testClass, string tagName) { this.testClass = testClass; this.tagName = tagName; } protected override void Write(HtmlRenderer renderer, CustomContainer obj) { if (obj.Info == testClass) { renderer.EnsureLine(); renderer.Write($"<{tagName}") .WriteAttributes(obj) .Write(">"); renderer.WriteChildren(obj); renderer.Write($"</{tagName}>"); } } } ``` Then for my purposes: ```csharp public class NotesContainerRenderer : ContainerRenderer { public NotesContainerRenderer() : base("notes", "aside") { } } ```
Author
Owner

@MihaZupan commented on GitHub (May 31, 2019):

If anything, an info-tag mapping could be exposed as a parameter for the CustomContainers extension, I do not think a separate extension is needed.

@MihaZupan commented on GitHub (May 31, 2019): If anything, an info-tag mapping could be exposed as a parameter for the CustomContainers extension, I do not think a separate extension is needed.
Author
Owner

@LukeTOBrien commented on GitHub (May 31, 2019):

Okay. Closing as resolve, thanks!

@LukeTOBrien commented on GitHub (May 31, 2019): Okay. Closing as resolve, thanks!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/markdig#301