mirror of
https://github.com/xoofx/markdig.git
synced 2026-07-09 02:26:32 +00:00
How to handle partial markdown syntax when rendering streamed output #731
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @JamesNK on GitHub (Mar 15, 2025).
It's common to return markdown in AI responses. With streaming, markdown returned will have partially complete syntax as the response is returned.
For example, the start sequence for an inline or fenced code block is returned in the stream response, but end sequence doesn't arrive for another second. Inbetween that time the start sequence is rendered as text to the HTML output, which doesn't look good.
Is there a way to exclude partially received syntax from rendered HTML? e.g. if a backtick is received to start an inline code span, the backtick and content after it isn't rendered until the end backtick is received:
Alternatively, a way to implicitly complete the begun start sequence, e.g.
@xoofx commented on GitHub (Mar 15, 2025):
Possibly, but it will be non-trivial. Several cases to cover (inline code, fenced code block...etc.). It would have to be configurable...etc. PR welcome by making it optional by default.
@MihaZupan commented on GitHub (Mar 15, 2025):
It also very much depends on how perfect you want such logic to be.
Something like the following code will handle most? cases you might care about:
CompletePartialElements: https://gist.github.com/MihaZupan/03eecb8c66459b87a06b82a719bd0efcTrimming out such elements instead of manufacturing the closed variants is even simpler.
@JamesNK commented on GitHub (Mar 16, 2025):
That's great. It's basically what I was expecting I'd have to do.
I've noticed some things it misses: If the partial content is in a list then it isn't processed. Fixed by looking in block content for the last inline:
The other thing that isn't fixed, which I finally figure out what is going on, is a sublist causing a heading to be temporarly added during rendering.
Given this markdown:
Textis temporarily a<h2>header because the dash below the text is the alternative syntax for a header. It disappears as soon as the sublist item has text:I fixed it with this hack:
@MihaZupan commented on GitHub (Mar 16, 2025):
Try this:
@JamesNK commented on GitHub (Mar 16, 2025):
That's much cleaner than trimming markdown text. Thanks.
I fixed another issue I saw, which was the start of bold/italics but no following text. For example, the following markdown would contain the double stars:
Updated gist that includes various improvements in your method: https://gist.github.com/JamesNK/4d4fbef86cdde2ab54df8bae8421bed6
There is one remaining issue I've seen which is incomplete links. For example,
The incomplete link markdown is visible until the link is complete:
@JamesNK commented on GitHub (Mar 17, 2025):
Incomplete links are handled. gist updated: https://gist.github.com/JamesNK/4d4fbef86cdde2ab54df8bae8421bed6.
I think this is good enough.
@MihaZupan If you have time, take a look and see whether there are any mistakes, or improvements to the method.
@MihaZupan commented on GitHub (Mar 17, 2025):
Looks good.
Maybe
to handle

@MihaZupan commented on GitHub (Mar 17, 2025):
Oh also
@xoofx commented on GitHub (Mar 18, 2025):
Feel free to make a PR @JamesNK if you are satisfied with the code. Keeping it as an extension method on
MarkdownDocumentmight be the less disruptive and might be useful for others.@JamesNK commented on GitHub (Mar 18, 2025):
Where would the method go?
What name do you want?
Do you have other MarkdownDocument extension methods it would sit beside, or would it be on its own? It feels kind of experimental.
I'm happy having the code sit inside my app but I can see the value in having it built in so other folks doing AI + streaming + markdown could use this..
@xoofx commented on GitHub (Mar 18, 2025):
You could add it to MarkdownObjectExtensions, apply it maybe to
ContainerBlockon the signature (doesn't have to be strictly aMarkdownDocument)Maybe
static void CompleteOpenElements(this ContainerBlock container);It is ok. There are other parts of Markdig that are not fully finished - even API wise.
@aropb commented on GitHub (Jul 8, 2025):
If I don't want to re-process the entire text when I receive each part of it, can I do this?