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)| Parameter | Description |
|---|---|
mime | Output format (e.g., MIME"text/html"()) |
container | The node's container type (e.g., Link, CodeBlock) |
node | The AST node being rendered |
entering | true when entering, false when leaving |
writer | The 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}\parAccessing 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
If you were using the previous env-based hooks, here's how to migrate:
| Previous | New |
|---|---|
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.