| --- |
| jupytext: |
| formats: ipynb,md:myst |
| text_representation: |
| extension: .md |
| format_name: myst |
| format_version: '0.8' |
| jupytext_version: 1.4.2 |
| kernelspec: |
| display_name: Python 3 |
| language: python |
| name: python3 |
| --- |
| |
| # Using `markdown_it` |
| |
| > This document can be opened to execute with [Jupytext](https://jupytext.readthedocs.io)! |
| |
| markdown-it-py may be used as an API *via* the `markdown_it` package. |
| |
| The raw text is first parsed to syntax 'tokens', |
| then these are converted to other formats using 'renderers'. |
| |
| +++ |
| |
| ## Quick-Start |
| |
| The simplest way to understand how text will be parsed is using: |
| |
| ```{code-cell} |
| from markdown_it import MarkdownIt |
| ``` |
| |
| ```{code-cell} |
| md = MarkdownIt() |
| md.render("some *text*") |
| ``` |
| |
| ```{code-cell} |
| for token in md.parse("some *text*"): |
| print(token) |
| print() |
| ``` |
| |
| ## The Parser |
| |
| +++ |
| |
| The `MarkdownIt` class is instantiated with parsing configuration options, |
| dictating the syntax rules and additional options for the parser and renderer. |
| You can define this configuration *via* a preset name (`'zero'`, `'commonmark'` or `'default'`), |
| or by directly supplying a dictionary. |
| |
| ```{code-cell} |
| from markdown_it.presets import zero |
| zero.make() |
| ``` |
| |
| ```{code-cell} |
| md = MarkdownIt("zero") |
| md.options |
| ``` |
| |
| ```{code-cell} |
| print(md.get_active_rules()) |
| ``` |
| |
| ```{code-cell} |
| print(md.get_all_rules()) |
| ``` |
| |
| You can find all the parsing rules in the source code: |
| `parser_core.py`, `parser_block.py`, |
| `parser_inline.py`. |
| Any of the parsing rules can be enabled/disabled, and these methods are chainable: |
| |
| ```{code-cell} |
| md.render("- __*emphasise this*__") |
| ``` |
| |
| ```{code-cell} |
| md.enable(["list", "emphasis"]).render("- __*emphasise this*__") |
| ``` |
| |
| You can temporarily modify rules with the `reset_rules` context manager. |
| |
| ```{code-cell} |
| with md.reset_rules(): |
| md.disable("emphasis") |
| print(md.render("__*emphasise this*__")) |
| md.render("__*emphasise this*__") |
| ``` |
| |
| Additionally `renderInline` runs the parser with all block syntax rules disabled. |
| |
| ```{code-cell} |
| md.renderInline("__*emphasise this*__") |
| ``` |
| |
| ### Plugins load |
| |
| Plugins load collections of additional syntax rules and render methods into the parser |
| |
| ```{code-cell} |
| from markdown_it import MarkdownIt |
| from markdown_it.extensions.front_matter import front_matter_plugin |
| from markdown_it.extensions.footnote import footnote_plugin |
| |
| md = ( |
| MarkdownIt() |
| .use(front_matter_plugin) |
| .use(footnote_plugin) |
| .enable('table') |
| ) |
| text = (""" |
| --- |
| a: 1 |
| --- |
| |
| a | b |
| - | - |
| 1 | 2 |
| |
| A footnote [^1] |
| |
| [^1]: some details |
| """) |
| md.render(text) |
| ``` |
| |
| ## The Token Stream |
| |
| |
| +++ |
| |
| Before rendering, the text is parsed to a flat token stream of block level syntax elements, with nesting defined by opening (1) and closing (-1) attributes: |
| |
| ```{code-cell} |
| md = MarkdownIt("commonmark") |
| tokens = md.parse(""" |
| Here's some *text* |
| |
| 1. a list |
| |
| > a *quote*""") |
| [(t.type, t.nesting) for t in tokens] |
| ``` |
| |
| Naturally all openings should eventually be closed, |
| such that: |
| |
| ```{code-cell} |
| sum([t.nesting for t in tokens]) == 0 |
| ``` |
| |
| All tokens are the same class, which can also be created outside the parser: |
| |
| ```{code-cell} |
| tokens[0] |
| ``` |
| |
| ```{code-cell} |
| from markdown_it.token import Token |
| token = Token("paragraph_open", "p", 1, block=True, map=[1, 2]) |
| token == tokens[0] |
| ``` |
| |
| The `'inline'` type token contain the inline tokens as children: |
| |
| ```{code-cell} |
| tokens[1] |
| ``` |
| |
| You can serialize a token (and its children) to a JSONable dictionary using: |
| |
| ```{code-cell} |
| print(tokens[1].as_dict()) |
| ``` |
| |
| This dictionary can also be deserialized: |
| |
| ```{code-cell} |
| Token.from_dict(tokens[1].as_dict()) |
| ``` |
| |
| In some use cases `nest_tokens` may be useful, to collapse the opening/closing tokens into single tokens: |
| |
| ```{code-cell} |
| from markdown_it.token import nest_tokens |
| nested_tokens = nest_tokens(tokens) |
| [t.type for t in nested_tokens] |
| ``` |
| |
| This introduces a single additional class `NestedTokens`, |
| containing an `opening`, `closing` and `children`, which can be a list of mixed |
| `Token` and `NestedTokens`. |
| |
| ```{code-cell} |
| nested_tokens[0] |
| ``` |
| |
| ## Renderers |
| |
| +++ |
| |
| After the token stream is generated, it's passed to a [renderer](https://github.com/executablebooks/markdown-it-py/tree/master/markdown_it/renderer.py). |
| It then plays all the tokens, passing each to a rule with the same name as token type. |
| |
| Renderer rules are located in `md.renderer.rules` and are simple functions |
| with the same signature: |
| |
| ```python |
| def function(renderer, tokens, idx, options, env): |
| return htmlResult |
| ``` |
| |
| +++ |
| |
| You can inject render methods into the instantiated render class. |
| |
| ```{code-cell} |
| md = MarkdownIt("commonmark") |
| |
| def render_em_open(self, tokens, idx, options, env): |
| return '<em class="myclass">' |
| |
| md.add_render_rule("em_open", render_em_open) |
| md.render("*a*") |
| ``` |
| |
| This is a slight change to the JS version, where the renderer argument is at the end. |
| Also `add_render_rule` method is specific to Python, rather than adding directly to the `md.renderer.rules`, this ensures the method is bound to the renderer. |
| |
| +++ |
| |
| You can also subclass a render and add the method there: |
| |
| ```{code-cell} |
| from markdown_it.renderer import RendererHTML |
| |
| class MyRenderer(RendererHTML): |
| def em_open(self, tokens, idx, options, env): |
| return '<em class="myclass">' |
| |
| md = MarkdownIt("commonmark", renderer_cls=MyRenderer) |
| md.render("*a*") |
| ``` |
| |
| Plugins can support multiple render types, using the `__ouput__` attribute (this is currently a Python only feature). |
| |
| ```{code-cell} |
| from markdown_it.renderer import RendererHTML |
| |
| class MyRenderer1(RendererHTML): |
| __output__ = "html1" |
| |
| class MyRenderer2(RendererHTML): |
| __output__ = "html2" |
| |
| def plugin(md): |
| def render_em_open1(self, tokens, idx, options, env): |
| return '<em class="myclass1">' |
| def render_em_open2(self, tokens, idx, options, env): |
| return '<em class="myclass2">' |
| md.add_render_rule("em_open", render_em_open1, fmt="html1") |
| md.add_render_rule("em_open", render_em_open2, fmt="html2") |
| |
| md = MarkdownIt("commonmark", renderer_cls=MyRenderer1).use(plugin) |
| print(md.render("*a*")) |
| |
| md = MarkdownIt("commonmark", renderer_cls=MyRenderer2).use(plugin) |
| print(md.render("*a*")) |
| ``` |
| |
| Here's a more concrete example; let's replace images with vimeo links to player's iframe: |
| |
| ```{code-cell} |
| import re |
| from markdown_it import MarkdownIt |
| |
| vimeoRE = re.compile(r'^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)') |
| |
| def render_vimeo(self, tokens, idx, options, env): |
| token = tokens[idx] |
| aIndex = token.attrIndex('src') |
| if (vimeoRE.match(token.attrs[aIndex][1])): |
| |
| ident = vimeoRE.match(token.attrs[aIndex][1])[2] |
| |
| return ('<div class="embed-responsive embed-responsive-16by9">\n' + |
| ' <iframe class="embed-responsive-item" src="//player.vimeo.com/video/' + |
| ident + '"></iframe>\n' + |
| '</div>\n') |
| return self.image(tokens, idx, options, env) |
| |
| md = MarkdownIt("commonmark") |
| md.add_render_rule("image", render_vimeo) |
| print(md.render("")) |
| ``` |
| |
| Here is another example, how to add `target="_blank"` to all links: |
| |
| ```{code-cell} |
| from markdown_it import MarkdownIt |
| |
| def render_blank_link(self, tokens, idx, options, env): |
| aIndex = tokens[idx].attrIndex('target') |
| if (aIndex < 0): |
| tokens[idx].attrPush(['target', '_blank']) # add new attribute |
| else: |
| tokens[idx].attrs[aIndex][1] = '_blank' # replace value of existing attr |
| |
| # pass token to default renderer. |
| return self.renderToken(tokens, idx, options, env) |
| |
| md = MarkdownIt("commonmark") |
| md.add_render_rule("link_open", render_blank_link) |
| print(md.render("[a]\n\n[a]: b")) |
| ``` |