from multimark import parse, NodeType
doc = parse("# Hello\n\nA paragraph with **bold** text.\n")
doc<Node document>
multimark exposes the full abstract syntax tree (AST) that cmark-gfm builds during parsing. This lets you inspect document structure, extract content, transform nodes, and render subtrees. This is all backed by the same C library that powers the renderers.
The parse() function takes a Markdown string and returns the root Node of the parsed tree.
from multimark import parse, NodeType
doc = parse("# Hello\n\nA paragraph with **bold** text.\n")
doc<Node document>
The root node is always a DOCUMENT. Its children are the top-level block elements.
The walk() method performs a depth-first traversal, yielding (event, node) pairs. Container nodes produce both "enter" and "exit" events; leaf nodes produce only "enter".
for event, node in doc.walk():
if event == "enter":
print(f"{node.type_string:>15} {node.literal or ''}") document
heading
text Hello
paragraph
text A paragraph with
strong
text bold
text text.
This is the primary way to scan or transform a document. For example, extracting all links:
Each node type exposes relevant properties. Headings have a heading_level, links have url and title, code blocks have fence_info, and text nodes have literal.
doc = parse("## Installation\n\nRun `pip install multimark`.\n")
for event, node in doc.walk():
if event == "enter" and node.type == NodeType.HEADING:
print(f"Level {node.heading_level}: ", end="")
print(node.first_child.literal)Level 2: Installation
Source positions are available on every node:
Nodes support in-place mutation. You can change properties, add or remove nodes, and then render the modified tree.
from multimark import Node
doc = parse("Visit [example](http://example.com) for details.\n")
for event, node in doc.walk():
if node.type == NodeType.LINK and node.url.startswith("http://"):
node.url = node.url.replace("http://", "https://", 1)
print(doc.render_html())<p>Visit <a href="https://example.com">example</a> for details.</p>
You can also build nodes from scratch:
Any node can render itself independently, which is useful for extracting fragments.
doc = parse("# Title\n\nFirst paragraph.\n\nSecond paragraph.\n")
second_para = list(doc.children)[2]
print(second_para.render_html())<p>Second paragraph.</p>
All five output formats are available: render_html(), render_xml(), render_latex(), render_man(), and render_commonmark().
For large documents or streamed input, the Parser class provides incremental feeding.
from multimark import Parser
with Parser(smart=True) as p:
p.feed("# Chapter 1\n\n")
p.feed("Some text with \"quotes\" and -- dashes.\n")
print(p.render_html())<h1>Chapter 1</h1>
<p>Some text with “quotes” and – dashes.</p>
The Parser also works as a context manager for automatic resource cleanup, and its finish() method returns the root Node for AST access:
Extensions like tables, strikethrough, and task lists produce their own node types in the tree. These work with both parse() and Parser.
doc = parse("# Intro\n\n## Setup\n\n### Details\n\n## Usage\n")
toc = []
for event, node in doc.walk():
if event == "enter" and node.type == NodeType.HEADING:
level = node.heading_level
text = node.first_child.literal if node.first_child else ""
toc.append((level, text))
toc[(1, 'Intro'), (2, 'Setup'), (3, 'Details'), (2, 'Usage')]