Markdown.ToHtml does not provide argument to turn on ImplicitParagraph option #81

Closed
opened 2026-01-29 14:25:29 +00:00 by claunia · 8 comments
Owner

Originally created by @KirkMunro on GitHub (Jan 16, 2017).

I'd like to see an option on ToHtml that allows me to specify whether or not ImplicitParagraph should be set on the HtmlRenderer that is used within that method.

I was thinking about making this change with an optional argument, but before submitting a PR I thought I should ask, are there other HtmlRenderer options that would be useful to see exposed in the ToHtml method? These could be combined in an enumerated value argument so that whatever options are needed could be set in one additional argument. I haven't needed to look at the other options though so I don't know whether or not they should be exposed in ToHtml. Thoughts?

Originally created by @KirkMunro on GitHub (Jan 16, 2017). I'd like to see an option on ToHtml that allows me to specify whether or not ImplicitParagraph should be set on the HtmlRenderer that is used within that method. I was thinking about making this change with an optional argument, but before submitting a PR I thought I should ask, are there other HtmlRenderer options that would be useful to see exposed in the ToHtml method? These could be combined in an enumerated value argument so that whatever options are needed could be set in one additional argument. I haven't needed to look at the other options though so I don't know whether or not they should be exposed in ToHtml. Thoughts?
claunia added the questionenhancement labels 2026-01-29 14:25:29 +00:00
Author
Owner

@xoofx commented on GitHub (Jan 26, 2017):

Hm I would rather like to not expose specific/duplicate options to the top level ToHtml function...
If you need this kind of customization, you can always instantiate the HtmlRenderer yourself and do what ToHtml is doing (this is a very tiny wrapper around it)...

@xoofx commented on GitHub (Jan 26, 2017): Hm I would rather like to not expose specific/duplicate options to the top level `ToHtml` function... If you need this kind of customization, you can always instantiate the `HtmlRenderer` yourself and do what `ToHtml` is doing (this is a very tiny wrapper around it)...
Author
Owner

@KirkMunro commented on GitHub (Jan 27, 2017):

I took a closer look at this, and discovered that the problem I'm facing isn't really that ToHtml doesn't expose options to turn ImplicitParagraph on or off. The problem is that Markdig's conversion from markdown to HTML assumes you are converting an entire markdown document to a new HTML document. It makes sense to automatically wrap paragraphs in paragraph tags when you are performing such a conversion, so having that as the default in ToHtml is the right thing to do; however, I'm facing a scenario where I'm converting portions of a markdown document to HTML format, and when I perform those conversions I absolutely do not want paragraphs automatically wrapped in paragraph tags in the HTML output because in this specific case, that negatively impacts the UX where the HTML that is being generated is used. Instead, what I'm after is to separate empty-line-delimited paragraph blocks in markdown with two line break tags on separate lines.

e.g. I want this:

string markdown = @"This is the first paragraph.

This is the *second*.

And there is a **third** as well.";

to convert to this:

This is the first paragraph.<br />
<br />
This is the <em>second</em>.<br />
<br />
And there is a <strong>third</strong> as well.

In order to get that to work with Markdig, I had to write my own paragraph renderer, as follows:

   public class MyParagraphRenderer : HtmlObjectRenderer<ParagraphBlock>
    {
        protected override void Write(HtmlRenderer renderer, ParagraphBlock obj)
        {
            if (!renderer.IsFirstInContainer)
            {
                renderer.EnsureLine();
            }
            renderer.WriteLeafInline(obj);
            if (!renderer.IsLastInContainer)
            {
                renderer.WriteLine("<br />");
                renderer.WriteLine("<br />");
            }
        }
    }

I also had to duplicate the ToHtml methods and the CheckForSelfPipeline method, and insert the following lines right after the HtmlRenderer is created:

renderer.ObjectRenderers.RemoveAll(x => x is ParagraphRenderer);
renderer.ObjectRenderers.Add(new MyParagraphRenderer());

With all of that in place I could get Markdig to convert paragraphs the way I need them converted.

I'm happy the results give me what I needed, but I don't think that having to duplicate so much logic should be necessary. Is there something I'm missing that would eliminate the unnecessary duplication of code that seems to be required the way this is currently set up?

Maybe the HtmlRenderer constructor and the ToHtml method should have an optional parameter that accepts a list of custom renderers that you want to use. In the HtmlRenderer constructor, it could skip any default renderers that are provided in the optional custom renderer list. This approach would greatly simplify the code I had to write to make this work, eliminating most if not all of the duplication that is otherwise required when you need to convert markdown to HTML using something other than the default renderers. If this was in place, all I would have to do is create the custom renderers that I need and then pass them in as a collection to ToHtml to render the markdown as HTML using the logic that is already written inside of Markdig.

@KirkMunro commented on GitHub (Jan 27, 2017): I took a closer look at this, and discovered that the problem I'm facing isn't really that ToHtml doesn't expose options to turn ImplicitParagraph on or off. The problem is that Markdig's conversion from markdown to HTML assumes you are converting an entire markdown document to a new HTML document. It makes sense to automatically wrap paragraphs in paragraph tags when you are performing such a conversion, so having that as the default in ToHtml is the right thing to do; however, I'm facing a scenario where I'm converting portions of a markdown document to HTML format, and when I perform those conversions I absolutely do not want paragraphs automatically wrapped in paragraph tags in the HTML output because in this specific case, that negatively impacts the UX where the HTML that is being generated is used. Instead, what I'm after is to separate empty-line-delimited paragraph blocks in markdown with two line break tags on separate lines. e.g. I want this: ```Markdown string markdown = @"This is the first paragraph. This is the *second*. And there is a **third** as well."; ``` to convert to this: ```Html This is the first paragraph.<br /> <br /> This is the <em>second</em>.<br /> <br /> And there is a <strong>third</strong> as well. ``` In order to get that to work with Markdig, I had to write my own paragraph renderer, as follows: ```CSharp public class MyParagraphRenderer : HtmlObjectRenderer<ParagraphBlock> { protected override void Write(HtmlRenderer renderer, ParagraphBlock obj) { if (!renderer.IsFirstInContainer) { renderer.EnsureLine(); } renderer.WriteLeafInline(obj); if (!renderer.IsLastInContainer) { renderer.WriteLine("<br />"); renderer.WriteLine("<br />"); } } } ``` I also had to duplicate the ToHtml methods and the CheckForSelfPipeline method, and insert the following lines right after the HtmlRenderer is created: ```CSharp renderer.ObjectRenderers.RemoveAll(x => x is ParagraphRenderer); renderer.ObjectRenderers.Add(new MyParagraphRenderer()); ``` With all of that in place I could get Markdig to convert paragraphs the way I need them converted. I'm happy the results give me what I needed, but I don't think that having to duplicate so much logic should be necessary. Is there something I'm missing that would eliminate the unnecessary duplication of code that seems to be required the way this is currently set up? Maybe the HtmlRenderer constructor and the ToHtml method should have an optional parameter that accepts a list of custom renderers that you want to use. In the HtmlRenderer constructor, it could skip any default renderers that are provided in the optional custom renderer list. This approach would greatly simplify the code I had to write to make this work, eliminating most if not all of the duplication that is otherwise required when you need to convert markdown to HTML using something other than the default renderers. If this was in place, all I would have to do is create the custom renderers that I need and then pass them in as a collection to ToHtml to render the markdown as HTML using the logic that is already written inside of Markdig.
Author
Owner

@xoofx commented on GitHub (Jan 27, 2017):

You don't need to modify the markdig to plug your own MyParagraphRenderer

Just build a a plugin and register to the pipeline builder:

public class MyParagraphExtension : IMarkdownExtension
{
	public void Setup(MarkdownPipelineBuilder pipeline)
	{
	}

	public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
	{
		renderer.ObjectRenderers.RemoveAll(x => x is ParagraphRenderer);
		renderer.ObjectRenderers.Add(new MyParagraphRenderer());
	}
}

and add it to the builder builder.Use<MyParagraphExtension>();

But your solution doesn't look right. What you want is the ParagraphRenderer not to render top level paragraphs, but it should be able to render nested paragraphs. It needs a depth level information accessible from the renderer which is not provided today but easy to add (readonly integer property CurrentDepth that returns the current depth level, incremented, decremented everytime we visit an element in HtmlRenderer).

@xoofx commented on GitHub (Jan 27, 2017): You don't need to modify the markdig to plug your own `MyParagraphRenderer` Just build a a plugin and register to the pipeline builder: ```csharp public class MyParagraphExtension : IMarkdownExtension { public void Setup(MarkdownPipelineBuilder pipeline) { } public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { renderer.ObjectRenderers.RemoveAll(x => x is ParagraphRenderer); renderer.ObjectRenderers.Add(new MyParagraphRenderer()); } } ``` and add it to the builder `builder.Use<MyParagraphExtension>();` But your solution doesn't look right. What you want is the `ParagraphRenderer` not to render top level paragraphs, but it should be able to render nested paragraphs. It needs a depth level information accessible from the renderer which is not provided today but easy to add (readonly integer property `CurrentDepth` that returns the current depth level, incremented, decremented everytime we visit an element in `HtmlRenderer`).
Author
Owner

@KirkMunro commented on GitHub (Jan 27, 2017):

Thank you for the fast reply. I'm starting to understand how the extensions work now. With the additional information you provided, I was able to remove all of my ToHtml wrappers and simplify my logic, resulting in this:

using System;
using Markdig;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;

namespace MarkdigSandbox
{
    public class MyParagraphExtension : IMarkdownExtension
    {
        public void Setup(MarkdownPipelineBuilder pipeline)
        {
            // Do I need to implement this?
        }

        public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
        {
            renderer.ObjectRenderers.RemoveAll(x => x is ParagraphRenderer);
            renderer.ObjectRenderers.Add(new MyParagraphRenderer());
        }
    }

    public class MyParagraphRenderer : ParagraphRenderer
    {
        protected override void Write(HtmlRenderer renderer, ParagraphBlock obj)
        {
            if (!renderer.IsFirstInContainer)
            {
                renderer.EnsureLine();
            }
            renderer.WriteLeafInline(obj);
            if (!renderer.IsLastInContainer)
            {
                renderer.WriteLine("<br />");
                renderer.WriteLine("<br />");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string markdown = @"This is the first paragraph.

This is the *second*.

And there is a **third** as well.";

            var builder = new MarkdownPipelineBuilder();
            builder.Extensions.Add(new MyParagraphExtension());
            var pipeline = builder.Build();
            Console.WriteLine(Markdown.ToHtml(markdown, pipeline));
        }
    }
}

Regarding your last comment about a CurrentDepth property, is that something you could add? Or point me to where it should be incremented/decremented and I can add it and submit a PR. Then I could modify my MyParagraphRenderer to do what it does if CurrentDepth is 0, or invoke the base class Write method if CurrentDepth is > 0.

@KirkMunro commented on GitHub (Jan 27, 2017): Thank you for the fast reply. I'm starting to understand how the extensions work now. With the additional information you provided, I was able to remove all of my ToHtml wrappers and simplify my logic, resulting in this: ```CSharp using System; using Markdig; using Markdig.Renderers; using Markdig.Renderers.Html; using Markdig.Syntax; namespace MarkdigSandbox { public class MyParagraphExtension : IMarkdownExtension { public void Setup(MarkdownPipelineBuilder pipeline) { // Do I need to implement this? } public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { renderer.ObjectRenderers.RemoveAll(x => x is ParagraphRenderer); renderer.ObjectRenderers.Add(new MyParagraphRenderer()); } } public class MyParagraphRenderer : ParagraphRenderer { protected override void Write(HtmlRenderer renderer, ParagraphBlock obj) { if (!renderer.IsFirstInContainer) { renderer.EnsureLine(); } renderer.WriteLeafInline(obj); if (!renderer.IsLastInContainer) { renderer.WriteLine("<br />"); renderer.WriteLine("<br />"); } } } class Program { static void Main(string[] args) { string markdown = @"This is the first paragraph. This is the *second*. And there is a **third** as well."; var builder = new MarkdownPipelineBuilder(); builder.Extensions.Add(new MyParagraphExtension()); var pipeline = builder.Build(); Console.WriteLine(Markdown.ToHtml(markdown, pipeline)); } } } ``` Regarding your last comment about a CurrentDepth property, is that something you could add? Or point me to where it should be incremented/decremented and I can add it and submit a PR. Then I could modify my MyParagraphRenderer to do what it does if CurrentDepth is 0, or invoke the base class Write method if CurrentDepth is > 0.
Author
Owner

@KirkMunro commented on GitHub (Feb 2, 2017):

FYI, I discovered that I can use obj.Parent inside of my Write override method to identify whether or not I'm dealing with a top-level paragraph, so CurrentDepth isn't really that necessary. It's not a perfect solution, but it's pretty good. Here's the Write method I have now for my MyParagraphRenderer:

        protected override void Write(HtmlRenderer renderer, ParagraphBlock obj)
        {
            if (obj.Parent is MarkdownDocument)
            {
                if (!renderer.IsFirstInContainer)
                {
                    renderer.EnsureLine();
                }
                renderer.WriteLeafInline(obj);
                if (!renderer.IsLastInContainer)
                {
                    renderer.WriteLine("<br />");
                    renderer.WriteLine("<br />");
                }
                else
                {
                    renderer.EnsureLine();
                }
            }
            else
            {
                base.Write(renderer, obj);
            }
        }

The one piece I'm not loving with this is how I determine when I want to insert double <br/> tags. There are situations where I don't really want them that this doesn't cover (e.g. if I follow a paragraph by a <hr/> tag or a line with * * *). I'm still thinking about that one.

@KirkMunro commented on GitHub (Feb 2, 2017): FYI, I discovered that I can use obj.Parent inside of my Write override method to identify whether or not I'm dealing with a top-level paragraph, so CurrentDepth isn't really that necessary. It's not a perfect solution, but it's pretty good. Here's the Write method I have now for my MyParagraphRenderer: ```CSharp protected override void Write(HtmlRenderer renderer, ParagraphBlock obj) { if (obj.Parent is MarkdownDocument) { if (!renderer.IsFirstInContainer) { renderer.EnsureLine(); } renderer.WriteLeafInline(obj); if (!renderer.IsLastInContainer) { renderer.WriteLine("<br />"); renderer.WriteLine("<br />"); } else { renderer.EnsureLine(); } } else { base.Write(renderer, obj); } } ``` The one piece I'm not loving with this is how I determine when I want to insert double `<br/>` tags. There are situations where I don't really want them that this doesn't cover (e.g. if I follow a paragraph by a `<hr/>` tag or a line with `* * *`). I'm still thinking about that one.
Author
Owner

@xoofx commented on GitHub (Feb 3, 2017):

Good catch, indeed forgot that we have of course access to the Parent!

@xoofx commented on GitHub (Feb 3, 2017): Good catch, indeed forgot that we have of course access to the Parent!
Author
Owner

@xoofx commented on GitHub (Feb 3, 2017):

So Is this issue closable or?

@xoofx commented on GitHub (Feb 3, 2017): So Is this issue closable or?
Author
Owner

@KirkMunro commented on GitHub (Feb 3, 2017):

Yes, I'll close it. I have what I need for now.

@KirkMunro commented on GitHub (Feb 3, 2017): Yes, I'll close it. I have what I need for now.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/markdig#81