# Parsing and AST

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.


# Parsing to an AST

The [parse()](../reference/parse.md#multimark.parse) function takes a Markdown string and returns the root [Node](../reference/Node.md#multimark.Node) of the parsed tree.


``` python
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](../reference/NodeType.md#multimark.NodeType.DOCUMENT). Its children are the top-level block elements.


``` python
for child in doc.children:
    print(child)
```


    <Node heading level=1>
    <Node paragraph>


# Walking the Tree

The [walk()](../reference/Node.walk.md#multimark.Node.walk) method performs a depth-first traversal, yielding `(event, node)` pairs. Container nodes produce both `"enter"` and `"exit"` events; leaf nodes produce only `"enter"`.


``` python
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:


``` python
md = "See [Python](https://python.org) and [Rust](https://rust-lang.org).\n"
doc = parse(md)

links = [
    (node.url, node.first_child.literal)
    for event, node in doc.walk()
    if event == "enter" and node.type == NodeType.LINK
]
links
```


    [('https://python.org', 'Python'), ('https://rust-lang.org', 'Rust')]


# Node Properties

Each node type exposes relevant properties. Headings have a [heading_level](../reference/Node.heading_level.md#multimark.Node.heading_level), links have [url](../reference/Node.url.md#multimark.Node.url) and [title](../reference/Node.title.md#multimark.Node.title), code blocks have [fence_info](../reference/Node.fence_info.md#multimark.Node.fence_info), and text nodes have [literal](../reference/Node.literal.md#multimark.Node.literal).


``` python
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:


``` python
doc = parse("First paragraph.\n\nSecond paragraph.\n")

for child in doc.children:
    print(f"Lines {child.start_line}-{child.end_line}: {child.type_string}")
```


    Lines 1-1: paragraph
    Lines 3-3: paragraph


# Mutating the Tree

Nodes support in-place mutation. You can change properties, add or remove nodes, and then render the modified tree.


``` python
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 [example](https://example.com) for details.</p>


You can also build nodes from scratch:


``` python
doc = parse("Some content.\n")

heading = Node.new(NodeType.HEADING)
heading.heading_level = 2
text = Node.new(NodeType.TEXT)
text.literal = "Table of Contents"
heading.append_child(text)
doc.prepend_child(heading)

print(doc.render_html())
```


    <h2>Table of Contents</h2>
    <p>Some content.</p>


# Rendering Subtrees

Any node can render itself independently, which is useful for extracting fragments.


``` python
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()](../reference/Node.render_html.md#multimark.Node.render_html), [render_xml()](../reference/Node.render_xml.md#multimark.Node.render_xml), [render_latex()](../reference/Node.render_latex.md#multimark.Node.render_latex), [render_man()](../reference/Node.render_man.md#multimark.Node.render_man), and [render_commonmark()](../reference/Node.render_commonmark.md#multimark.Node.render_commonmark).


# Streaming Parser

For large documents or streamed input, the [Parser](../reference/Parser.md#multimark.Parser) class provides incremental feeding.


``` python
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](../reference/Parser.md#multimark.Parser) also works as a context manager for automatic resource cleanup, and its [finish()](../reference/Parser.finish.md#multimark.Parser.finish) method returns the root [Node](../reference/Node.md#multimark.Node) for AST access:


``` python
with Parser(extensions=["table"]) as p:
    p.feed("| A | B |\n|---|---|\n| 1 | 2 |\n")
    doc = p.finish()
    print(doc.render_html())
```


    <table>
    <thead>
    <tr>
    <th>A</th>
    <th>B</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td>1</td>
    <td>2</td>
    </tr>
    </tbody>
    </table>


# GFM Extensions with the AST

Extensions like tables, strikethrough, and task lists produce their own node types in the tree. These work with both [parse()](../reference/parse.md#multimark.parse) and [Parser](../reference/Parser.md#multimark.Parser).


``` python
doc = parse(
    "- [x] Done\n- [ ] Pending\n",
    extensions=["tasklist"],
)

for event, node in doc.walk():
    if event == "enter":
        print(f"{node.type_string}: {node.literal or ''}")
```


    document: 
    list: 
    tasklist: 
    paragraph: 
    text: Done
    tasklist: 
    paragraph: 
    text: Pending


# Common Patterns


## Extract all headings for a table of contents


``` python
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')]


## Strip all formatting and extract plain text


``` python
doc = parse("Hello **world**, visit [here](https://x.com).\n")

texts = [
    node.literal
    for event, node in doc.walk()
    if event == "enter" and node.type == NodeType.TEXT
]
" ".join(texts)
```


    'Hello  world , visit  here .'
