This document describes usage of mistletoe and its API from the developer's point of view.
When a markdown document gets parsed by mistletoe, the result is represented
as an abstract syntax tree (AST), stored in an instance of Document
.
This object contains a hierarchy of all the various tokens which were recognized
during the parsing process, for example, Paragraph
, Heading
, and RawText
.
The tokens which represent a line or a block of lines in the input markdown
are called block tokens. Examples include List
, Paragraph
, ThematicBreak
,
and also the Document
itself.
The tokens which represent the actual content within a block are called span tokens,
or, with CommonMark terminology, inline tokens.
In this category you will find tokens like RawText
, Link
, and Emphasis
.
Block tokens may have block tokens, span tokens, or no tokens at all as children
in the AST; this depends on the type of token. Span tokens may only have span
tokens as children.
Every token has properties called children
and parent
that can be used
for traversing the nodes hierarchy.
In order to see what exactly gets parsed, one can simply use the AstRenderer
on a given markdown input, for example:
mistletoe text.md --renderer mistletoe.ast_renderer.AstRenderer
Say that the input file contains for example:
# Heading 1
text
# Heading 2
[link](https://www.example.com)
Then we will get this JSON output from the AST renderer:
{
"type": "Document",
"footnotes": {},
"line_number": 1,
"children": [
{
"type": "Heading",
"line_number": 1,
"level": 1,
"children": [
{
"type": "RawText",
"content": "Heading 1"
}
]
},
{
"type": "Paragraph",
"line_number": 3,
"children": [
{
"type": "RawText",
"content": "text"
}
]
},
{
"type": "Heading",
"line_number": 5,
"level": 1,
"children": [
{
"type": "RawText",
"content": "Heading 2"
}
]
},
{
"type": "Paragraph",
"line_number": 7,
"children": [
{
"type": "Link",
"target": "https://www.example.com",
"title": "",
"children": [
{
"type": "RawText",
"content": "link"
}
]
}
]
}
]
}
mistletoe records the starting line of all block tokens that it encounters during
parsing and stores it as the line_number
attribute of each token.
(This feature is not available for span tokens yet.)
Sometimes all you need is the information from the AST. But more often, you'll want to take that information and turn it into some other format like HTML. This is called rendering. mistletoe provides a set of built-in renderers for different formats, and it's also possible to define your own renderer.
When passing an AST to a renderer, the tree is recursively traversed and methods corresponding to individual token types get called on the renderer in order to create the output in the desired format.
Here's an example of how to add GitHub-style wiki links to the parsing process, and provide a renderer for this new token.
GitHub wiki links are span-level tokens, meaning that they reside inline,
and don't really look like chunky paragraphs. To write a new span-level
token, all we need to do is make a subclass of SpanToken
:
from mistletoe.span_token import SpanToken
class GithubWiki(SpanToken):
pass
mistletoe uses regular expressions to search for span-level tokens in the
parsing process. As a refresher, GitHub wiki looks something like this:
[[alternative text | target]]
. We define a class variable, pattern
,
that stores the compiled regex:
class GithubWiki(SpanToken):
pattern = re.compile(r"\[\[ *(.+?) *\| *(.+?) *\]\]")
def __init__(self, match):
pass
The regex will be picked up by SpanToken.find
, which is used by the
tokenizer to find all tokens of its kind in the document.
If regexes are too limited for your use case, consider overriding
the find
method; it should return a list of all token occurrences.
Three other class variables are available for our custom token class, and their default values are shown below:
class SpanToken:
parse_group = 1
parse_inner = True
precedence = 5
Note that alternative text can also contain other span-level tokens. For
example, [[*alt*|link]]
is a GitHub link with an Emphasis
token as its
child. To parse child tokens, parse_inner
should be set to True
(the default value in this case), and parse_group
should correspond
to the match group in which child tokens might occur
(also the default value, 1, in this case).
Once these two class variables are set correctly,
GithubWiki.children
attribute will automatically be set to
the list of child tokens.
Note that there is no need to manually set this attribute,
unlike previous versions of mistletoe.
Lastly, the SpanToken
constructors take a regex match object as its argument.
We can simply store off the target
attribute from match_obj.group(2)
.
from mistletoe.span_token import SpanToken
class GithubWiki(SpanToken):
pattern = re.compile(r"\[\[ *(.+?) *\| *(.+?) *\]\]")
def __init__(self, match_obj):
self.target = match_obj.group(2)
There you go: a new token in 5 lines of code.
Normally there is no need to override the precedence
value of a custom token.
The default value is the same as InlineCode
, AutoLink
and HtmlSpan
,
which means that whichever token comes first will be parsed. In our case:
`code with [[ text` | link ]]
... will be parsed as:
<code>code with [[ text</code> | link ]]
If we set GithubWiki.precedence = 6
, we have:
`code with <a href="link">text`</a>
Adding a custom token to the parsing process usually involves a lot
of nasty implementation details. Fortunately, mistletoe takes care
of most of them for you. Simply passing your custom token class to
super().__init__()
does the trick:
from mistletoe.html_renderer import HtmlRenderer
class GithubWikiRenderer(HtmlRenderer):
def __init__(self):
super().__init__(GithubWiki)
We then only need to tell mistletoe how to render our new token:
def render_github_wiki(self, token):
template = '<a href="{target}">{inner}</a>'
target = token.target
inner = self.render_inner(token)
return template.format(target=target, inner=inner)
Cleaning up, we have our new renderer class:
from mistletoe.html_renderer import HtmlRenderer, escape_url
class GithubWikiRenderer(HtmlRenderer):
def __init__(self):
super().__init__(GithubWiki)
def render_github_wiki(self, token):
template = '<a href="{target}">{inner}</a>'
target = escape_url(token.target)
inner = self.render_inner(token)
return template.format(target=target, inner=inner)
It is preferred that all mistletoe's renderers be used as context managers. This is to ensure that your custom tokens are cleaned up properly, so that you can parse other Markdown documents with different token types in the same program.
from mistletoe import Document
from contrib.github_wiki import GithubWikiRenderer
with open('foo.md', 'r') as fin:
with GithubWikiRenderer() as renderer:
rendered = renderer.render(Document(fin))
For more info, take a look at the base_renderer
module in mistletoe.
The docstrings might give you a more granular idea of customizing mistletoe
to your needs.
Suppose you have some Markdown that you want to process and then output as Markdown again. Thanks to the text-like nature of Markdown, it is often possible to do this with text search-and-replace tools... but not always. For example, if you want to replace a text fragment in the plain text, but not in the embedded code samples, then the search-and-replace approach won't work.
In this case you can use mistletoe's MarkdownRenderer
:
- Parse Markdown to an AST (usually held in a
Document
token). - Make modifications to the AST.
- Render back to Markdown using
MarkdownRenderer.render()
.
Here is an example of how you can replace text in selected parts of the AST:
import mistletoe
from mistletoe.block_token import BlockToken, Heading, Paragraph, SetextHeading
from mistletoe.markdown_renderer import MarkdownRenderer
from mistletoe.span_token import InlineCode, RawText, SpanToken
def update_text(token: SpanToken):
"""Update the text contents of a span token and its children.
`InlineCode` tokens are left unchanged."""
if isinstance(token, RawText):
token.content = token.content.replace("mistletoe", "The Amazing mistletoe")
if not isinstance(token, InlineCode) and hasattr(token, "children"):
for child in token.children:
update_text(child)
def update_block(token: BlockToken):
"""Update the text contents of paragraphs and headings within this block,
and recursively within its children."""
if isinstance(token, (Paragraph, SetextHeading, Heading)):
for child in token.children:
update_text(child)
for child in token.children:
if isinstance(child, BlockToken):
update_block(child)
with open("README.md", "r") as fin:
with MarkdownRenderer() as renderer:
document = mistletoe.Document(fin)
update_block(document)
md = renderer.render(document)
print(md)
The MarkdownRenderer
can also reflow the text in the document to a given
maximum line length. And it can do so while preserving the formatting of code
blocks and other tokens where line breaks matter. To use this feature,
specify a max_line_length
parameter in the call to the MarkdownRenderer
constructor.