Python port of markdown-it providing CommonMark-compliant markdown parsing with configurable syntax and pluggable architecture
—
HTML rendering system with customizable render rules and support for custom renderers to generate different output formats from parsed markdown tokens.
Default renderer that converts tokens to HTML output.
class RendererHTML:
"""HTML renderer with customizable render rules."""
__output__ = "html" # Output format identifier
def __init__(self, parser: MarkdownIt = None):
"""
Initialize HTML renderer.
Parameters:
- parser: MarkdownIt instance (for reference)
"""
def render(self, tokens: list[Token], options: dict, env: dict) -> str:
"""
Render token stream to HTML.
Parameters:
- tokens: list of tokens to render
- options: parser options
- env: environment data
Returns:
- str: rendered HTML output
"""
def renderInline(self, tokens: list[Token], options: dict, env: dict) -> str:
"""
Render inline tokens to HTML.
Parameters:
- tokens: list of inline tokens
- options: parser options
- env: environment data
Returns:
- str: rendered HTML without block wrappers
"""
def renderToken(self, tokens: list[Token], idx: int, options: dict, env: dict) -> str:
"""
Render single token at specified index.
Parameters:
- tokens: full token list
- idx: index of token to render
- options: parser options
- env: environment data
Returns:
- str: rendered HTML for single token
"""Usage Example:
from markdown_it import MarkdownIt
from markdown_it.renderer import RendererHTML
md = MarkdownIt()
tokens = md.parse("# Hello\n\n**Bold** text.")
# Direct rendering
html = md.renderer.render(tokens, md.options, {})
print(html)
# Custom renderer instance
custom_renderer = RendererHTML()
html = custom_renderer.render(tokens, md.options, {})Customize rendering for specific token types.
# Render rules dictionary
rules: dict[str, callable] # Maps token types to render functions
def add_render_rule(self, name: str, function: callable, fmt: str = "html") -> None:
"""
Add custom render rule for token type.
Parameters:
- name: token type name
- function: render function with signature (tokens, idx, options, env) -> str
- fmt: output format (must match renderer.__output__)
"""Usage Example:
from markdown_it import MarkdownIt
def custom_heading_rule(tokens, idx, options, env):
"""Custom heading renderer with anchor links."""
token = tokens[idx]
if token.nesting == 1: # opening tag
# Get heading text from next inline token
heading_text = tokens[idx + 1].content if idx + 1 < len(tokens) else ""
anchor_id = heading_text.lower().replace(" ", "-")
return f'<{token.tag} id="{anchor_id}">'
else: # closing tag
return f'</{token.tag}>'
md = MarkdownIt()
md.add_render_rule("heading_open", custom_heading_rule)
md.add_render_rule("heading_close", custom_heading_rule)
html = md.render("# Hello World")
# Output: <h1 id="hello-world">Hello World</h1>Create custom renderers for different output formats.
class RendererProtocol(Protocol):
"""Protocol defining renderer interface."""
__output__: str # Output format identifier
def render(self, tokens: list[Token], options: dict, env: dict) -> Any:
"""Render tokens to output format."""Usage Example:
from markdown_it import MarkdownIt
from markdown_it.renderer import RendererProtocol
class MarkdownRenderer(RendererProtocol):
"""Custom renderer that outputs markdown (round-trip)."""
__output__ = "markdown"
def __init__(self, parser=None):
self.rules = {}
def render(self, tokens, options, env):
result = ""
for i, token in enumerate(tokens):
if token.type in self.rules:
result += self.rules[token.type](tokens, i, options, env)
else:
result += self.default_render(token)
return result
def default_render(self, token):
if token.type == "heading_open":
return "#" * int(token.tag[1]) + " "
elif token.type == "paragraph_open":
return ""
elif token.type == "inline":
return token.content
elif token.type in ["heading_close", "paragraph_close"]:
return "\n\n"
return ""
# Use custom renderer
md = MarkdownIt(renderer_cls=MarkdownRenderer)
markdown_output = md.render("# Title\n\nParagraph text.")Standard render rules for common token types:
# Block elements
def code_block(self, tokens, idx, options, env): ...
def fence(self, tokens, idx, options, env): ...
def blockquote_open(self, tokens, idx, options, env): ...
def blockquote_close(self, tokens, idx, options, env): ...
def hr(self, tokens, idx, options, env): ...
# Lists
def bullet_list_open(self, tokens, idx, options, env): ...
def bullet_list_close(self, tokens, idx, options, env): ...
def list_item_open(self, tokens, idx, options, env): ...
def list_item_close(self, tokens, idx, options, env): ...
# Inline elements
def text(self, tokens, idx, options, env): ...
def html_inline(self, tokens, idx, options, env): ...
def html_block(self, tokens, idx, options, env): ...
def softbreak(self, tokens, idx, options, env): ...
def hardbreak(self, tokens, idx, options, env): ...
# Links and media
def link_open(self, tokens, idx, options, env): ...
def link_close(self, tokens, idx, options, env): ...
def image(self, tokens, idx, options, env): ...
# Tables
def table_open(self, tokens, idx, options, env): ...
def tr_open(self, tokens, idx, options, env): ...
def td_open(self, tokens, idx, options, env): ...
def th_open(self, tokens, idx, options, env): ...Override built-in render rules for customization:
from markdown_it import MarkdownIt
class CustomRenderer(RendererHTML):
"""Custom HTML renderer with modifications."""
def code_block(self, tokens, idx, options, env):
"""Custom code block rendering with line numbers."""
token = tokens[idx]
info = token.info.strip() if token.info else ""
lang = info.split()[0] if info else ""
code = token.content
lines = code.rstrip().split('\n')
html = f'<pre><code class="language-{lang}">'
for i, line in enumerate(lines, 1):
html += f'<span class="line-number">{i:3d}</span>{line}\n'
html += '</code></pre>'
return html
def strong_open(self, tokens, idx, options, env):
"""Use <b> instead of <strong>."""
return '<b>'
def strong_close(self, tokens, idx, options, env):
"""Use <b> instead of <strong>."""
return '</b>'
# Use custom renderer
md = MarkdownIt(renderer_cls=CustomRenderer)
html = md.render("```python\nprint('hello')\n```\n\n**Bold text**")Utility methods for rendering HTML attributes:
def renderAttrs(self, token: Token) -> str:
"""
Render token attributes to HTML attribute string.
Parameters:
- token: token with attributes
Returns:
- str: HTML attribute string
"""
def renderToken(self, tokens: list[Token], idx: int, options: dict, env: dict) -> str:
"""
Default token rendering with attribute support.
Parameters:
- tokens: token list
- idx: token index
- options: parser options
- env: environment data
Returns:
- str: rendered token HTML
"""Usage Example:
from markdown_it.token import Token
from markdown_it.renderer import RendererHTML
renderer = RendererHTML()
# Token with attributes
token = Token("div_open", "div", 1)
token.attrSet("class", "container")
token.attrSet("id", "main")
# Render attributes
attrs_html = renderer.renderAttrs(token)
print(attrs_html) # ' class="container" id="main"'
# Full token rendering
html = renderer.renderToken([token], 0, {}, {})
print(html) # '<div class="container" id="main">'HTML escaping utilities for safe output:
from markdown_it.common.utils import escapeHtml, unescapeAll
def escapeHtml(raw: str) -> str:
"""
Escape HTML characters for safe output.
Parameters:
- raw: raw text to escape
Returns:
- str: HTML-escaped text
"""
def unescapeAll(str: str) -> str:
"""
Unescape entities and backslash escapes.
Parameters:
- str: text to unescape
Returns:
- str: unescaped text
"""URL handling in render context:
def custom_link_render(tokens, idx, options, env):
"""Custom link rendering with security checks."""
token = tokens[idx]
if token.nesting == 1: # link_open
href = token.attrGet("href")
if href:
# Custom URL validation
if not is_safe_url(href):
return '<span class="invalid-link">'
# Add custom attributes
token.attrSet("rel", "noopener")
token.attrSet("target", "_blank")
# Render with attributes
return renderer.renderToken(tokens, idx, options, env)
else: # link_close
return '</a>' if token.attrGet("href") else '</span>'
md = MarkdownIt()
md.add_render_rule("link_open", custom_link_render)
md.add_render_rule("link_close", custom_link_render)Supporting multiple renderers:
from markdown_it import MarkdownIt
class MultiFormatRenderer:
"""Wrapper supporting multiple output formats."""
def __init__(self):
self.html_renderer = RendererHTML()
self.markdown_renderer = MarkdownRenderer()
def render(self, tokens, options, env, format="html"):
if format == "html":
return self.html_renderer.render(tokens, options, env)
elif format == "markdown":
return self.markdown_renderer.render(tokens, options, env)
else:
raise ValueError(f"Unknown format: {format}")
# Usage
md = MarkdownIt()
tokens = md.parse("# Title\n\n**Bold** text.")
multi_renderer = MultiFormatRenderer()
html = multi_renderer.render(tokens, md.options, {}, "html")
markdown = multi_renderer.render(tokens, md.options, {}, "markdown")Install with Tessl CLI
npx tessl i tessl/pypi-markdown-it-py