Transforms

Transforms let you intercept and modify AST nodes during rendering. Use them for URL rewriting, syntax highlighting, document wrappers, and other output customizations.

using CommonMark
parser = Parser()

Basic Usage

Pass a transform function to any writer:

function my_transform(mime, container, node, entering, writer)
    # Return (node, entering) - possibly modified
    (node, entering)
end

ast = parser("Hello *world*")
html(ast; transform = my_transform)
"<p>Hello <em>world</em></p>\n"

The transform is called for every node during tree traversal, both when entering and leaving container nodes.

Signature

transform(mime::MIME, container, node::Node, entering::Bool, writer::Writer) -> (Node, Bool)
ParameterDescription
mimeOutput format (e.g., MIME"text/html"())
containerThe node's container type (e.g., Link, CodeBlock)
nodeThe AST node being rendered
enteringtrue when entering, false when leaving
writerThe writer instance (access writer.env for config)

Important: You must define a catch-all fallback that passes nodes through unchanged:

my_transform(mime, ::CommonMark.AbstractContainer, node, entering, writer) = (node, entering)

Examples

URL Rewriting

Transform link destinations for your site structure:

function xform(::MIME"text/html", link::CommonMark.Link, node, entering, writer)
    if entering
        name, _ = splitext(link.destination)
        new_node = CommonMark.Node(CommonMark.Link;
            dest = "/docs/$name.html",
            title = link.title,
        )
        (new_node, entering)
    else
        (node, entering)
    end
end
xform(mime, ::CommonMark.AbstractContainer, node, entering, writer) =
    (node, entering)

ast = parser("[Guide](guide.md)")
html(ast; transform = xform)
"<p><a href=\"/docs/guide.html\">Guide</a></p>\n"

Syntax Highlighting

Replace code blocks with pre-rendered HTML:

function xform(::MIME"text/html", ::CommonMark.CodeBlock, node, entering, writer)
    lang = node.t.info === nothing ? "" : node.t.info
    highlighted = """
        <pre class="highlight"><code class="language-$lang">$(node.literal)</code></pre>
        """
    (CommonMark.Node(CommonMark.HtmlBlock, highlighted), entering)
end
xform(mime, ::CommonMark.AbstractContainer, node, entering, writer) =
    (node, entering)

ast = parser("```julia\nx = 1\n```")
html(ast; transform = xform)
"<pre class=\"highlight\"><code class=\"language-julia\">x = 1\n</code></pre>\n"

Document Wrapper

Add HTML structure around the rendered content:

function xform(::MIME"text/html", ::CommonMark.Document, node, entering, writer)
    if entering
        title = get(writer.env, "title", "Untitled")
        CommonMark.literal(writer, """<!DOCTYPE html>
<html>
<head><title>$title</title></head>
<body>
""")
    else
        CommonMark.literal(writer, """</body>
</html>
""")
    end
    (node, entering)
end
xform(mime, ::CommonMark.AbstractContainer, node, entering, writer) =
    (node, entering)

ast = parser("# Hello\n\nWorld")
env = Dict{String,Any}("title" => "My Page")
html(ast, env; transform = xform)
"<!DOCTYPE html>\n<html>\n<head><title>My Page</title></head>\n<body>\n<h1>Hello</h1>\n<p>World</p>\n</body>\n</html>\n"

Format-Specific Transforms

Dispatch on MIME type for format-specific behavior:

function xform(::MIME"text/html", link::CommonMark.Link, node, entering, writer)
    if entering
        dest = link.destination * "?format=html"
        (CommonMark.Node(CommonMark.Link; dest = dest, title = link.title), entering)
    else
        (node, entering)
    end
end

function xform(::MIME"text/latex", link::CommonMark.Link, node, entering, writer)
    if entering
        dest = link.destination * "?format=latex"
        (CommonMark.Node(CommonMark.Link; dest = dest, title = link.title), entering)
    else
        (node, entering)
    end
end

xform(mime, ::CommonMark.AbstractContainer, node, entering, writer) =
    (node, entering)

ast = parser("[link](page)")
println(html(ast; transform = xform))
println(latex(ast; transform = xform))
<!DOCTYPE html>
<html>
<head><title>Untitled</title></head>
<body>
<p><a href="page?format=html">link</a></p>
</body>
</html>

\href{page?format=latex}{link}\par

Accessing Configuration

Use writer.env to read configuration passed to the writer:

function xform(::MIME"text/html", link::CommonMark.Link, node, entering, writer)
    if entering
        base = get(writer.env, "base_url", "")
        dest = base * link.destination
        (CommonMark.Node(CommonMark.Link; dest = dest, title = link.title), entering)
    else
        (node, entering)
    end
end
xform(mime, ::CommonMark.AbstractContainer, node, entering, writer) =
    (node, entering)

ast = parser("[link](page)")
env = Dict{String,Any}("base_url" => "https://example.com/")
html(ast, env; transform = xform)
"<!DOCTYPE html>\n<html>\n<head><title></title></head>\n<body>\n<p><a href=\"https://example.com/page\">link</a></p>\n</body>\n</html>\n"

Migration from Previous API

For existing users

Skip this section if you're new to CommonMark.jl.

If you were using the previous env-based hooks, here's how to migrate:

PreviousNew
env["smartlink-engine"]Transform on Link / Image
env["syntax-highlighter"]Transform on CodeBlock
env["template-engine"]Transform on Document

The new system uses Julia's multiple dispatch, so you define methods for specific container types rather than passing function values in a dictionary.