Plain text and other formats
editPlain text
MarkdownConverter.ToPlainText renders the text of the document without any markup, for instance for a search index, the summary of a page or a notification:
var text = MarkdownConverter.ToPlainText("# Title\n\nSome **bold** text and [a link](https://example.com).\n\n- First\n- Second");
Assert.Equal("Title\nSome bold text and a link.\nFirst\nSecond\n", text);- Each block ends with a line break; the blank lines between blocks are not written.
- Links and images are replaced by their text; the URLs are dropped.
- The text is not HTML-escaped:
&becomes&, and raw HTML is written without its tags. - Pass a pipeline to read the syntax of the extensions. An overload writes to a
TextWriter.
Normalized Markdown
MarkdownConverter.Normalize parses the Markdown and writes it back in a consistent style: # headings instead of underlined ones, one blank line between blocks. NormalizeOptions (namespace Meziantou.Framework.Markdown.Renderers.Normalize) changes the style:
var markdown = "Title\n=====\n* First\n* Second";
Assert.Equal("# Title\n\n* First\n* Second", MarkdownConverter.Normalize(markdown).ReplaceLineEndings("\n"));
var options = new NormalizeOptions
{
ListItemCharacter = '-', // null keeps the bullet of the source
EmptyLineAfterHeading = false,
};
Assert.Equal("# Title\n- First\n- Second", MarkdownConverter.Normalize(markdown, options).ReplaceLineEndings("\n"));| Option | Default | Effect |
|---|---|---|
| ListItemCharacter | null | The bullet of the list items (-, * or +); null keeps the bullet of the source |
| EmptyLineAfterHeading | true | Adds a blank line after a heading |
| EmptyLineAfterCodeBlock | true | Adds a blank line after a code block |
| EmptyLineAfterThematicBreak | true | Adds a blank line after a thematic break |
| SpaceAfterQuoteBlock | true | Adds a space after the > of a quote |
| ExpandAutoLinks | true | Expands automatic links to the normal inline representation |
To normalize a document after changing its tree, see Normalize the document. To write it back without changing its formatting, see Keep the formatting.
Write Markdown text
To generate Markdown from text that is not Markdown, such as user input, file names or command lines, escape each part with MarkdownEscaper, so that the parser reads it back as the same text instead of interpreting it:
// Escape the text so that Markdown displays it as is: every ASCII punctuation character is escaped
Assert.Equal(@"\*Note\* \[docs\]\!", MarkdownEscaper.Escape("*Note* [docs]!"));
// The delimiters of code are longer than the runs of backticks of the content
Assert.Equal("``a`b``", MarkdownEscaper.CreateCodeSpan("a`b"));
Assert.Equal("`` `code` ``", MarkdownEscaper.CreateCodeSpan("`code`"));
Assert.Equal("```csharp\nvar a = 1;\n```\n", MarkdownEscaper.CreateFencedCodeBlock("var a = 1;", "csharp"));
// Build a link from untrusted parts
var text = MarkdownEscaper.Escape("Docs [v2]");
var destination = MarkdownEscaper.EscapeLinkDestination("https://example.com/a b");
var title = MarkdownEscaper.EscapeLinkTitle("Say \"hi\"");
Assert.Equal("<https://example.com/a b>", destination);
Assert.Equal("\"Say \\\"hi\\\"\"", title);
var html = MarkdownConverter.ToHtml($"[{text}]({destination} {title})");
Assert.Equal("<p><a href=\"https://example.com/a%20b\" title=\"Say "hi"\">Docs [v2]</a></p>\n", html);
// The label of a reference link
Assert.Equal(@"see \[1\]", MarkdownEscaper.EscapeLinkLabel("see [1]"));
// A cell of a pipe table cannot contain a pipe or a line ending
Assert.Equal(@"a \| b c", MarkdownEscaper.EscapeTableCell("a | b\nc"));| Method | Use it for |
|---|---|
| Escape | Text in a paragraph, a heading, a list item, or the text of a link. Every ASCII punctuation character gets a backslash |
| CreateCodeSpan | Inline code. The delimiters are longer than the runs of backticks of the content |
| CreateFencedCodeBlock | A code block with an optional info string, such as the language. The fence is longer than the lines that would close it |
| EscapeLinkDestination | The URL of a link or an image. A URL with spaces, or an empty one, is enclosed in angle brackets |
| EscapeLinkTitle | The title of a link or an image. The result includes the double quotes |
| EscapeLinkLabel | The label of a reference link and of its definition, without the brackets |
| EscapeTableCell | The text of a cell of a pipe table. The line endings become spaces |
Pitfalls:
- Whitespace is not escaped: the spaces at the start and at the end of a line are dropped, four leading spaces start an indented code block, and a blank line starts a new paragraph.
- The parser converts the line endings of a code span to spaces, and a code span cannot be empty.
- A code span in a table cell must also escape its pipes: replace
|with\|in the result ofCreateCodeSpan. - A link label is at most 999 characters, and the parser matches labels case-insensitively.
Other formats
A renderer converts the tree to an output format. To write another format, such as a terminal output or another markup language:
- Derive a class from TextRendererBase<T> (namespace Meziantou.Framework.Markdown.Renderers) for a text format, or implement IMarkdownRenderer.
- Add one object renderer per element type to ObjectRenderers: a class derived from MarkdownObjectRenderer<TRenderer, TObject> whose Write method writes the element, and calls
renderer.WriteChildrenorrenderer.WriteLeafInlinefor its content. - Render a document with
renderer.Render(document)afterpipeline.Setup(renderer), or callMarkdownConverter.Convert(markdown, renderer, pipeline).
The HTML, normalize and roundtrip renderers are built this way: their object renderers, in the Meziantou.Framework.Markdown.Renderers.Html, .Normalize and .Roundtrip namespaces, are examples to start from. To change how the HTML renderer writes one element, replace its object renderer instead; see Writing extensions.