CommonMark.jl
A CommonMark-compliant parser for Julia.
Features
- Spec compliant: Passes the CommonMark spec test suite
- Multiple outputs: HTML, LaTeX, Typst, terminal (ANSI), Jupyter notebooks
- Markdown roundtrip: Parse and re-emit normalized markdown
- Modular parser: Enable/disable individual syntax rules
- Extensions: Tables, footnotes, math, front matter, admonitions, and more
Installation
using Pkg
Pkg.add("CommonMark")Quick Start
Create a parser, parse some markdown, and render it to HTML:
using CommonMark
parser = Parser()
ast = parser("Hello *world*")
html(ast)"<p>Hello <em>world</em></p>\n"The parser returns an abstract syntax tree (AST) that can be rendered to multiple output formats or inspected programmatically.
Parsing
The parser is callable on strings:
ast = parser("# Heading\n\nParagraph")
html(ast)"<h1>Heading</h1>\n<p>Paragraph</p>\n"For files, use open with the parser:
ast = open(parser, "document.md")Output Formats
The same AST can be rendered to different formats. Each format has its own writer function that returns a string or writes to a file/IO.
ast = parser("# Title\n\n**Bold** and *italic*.")HTML for web pages:
html(ast)"<h1>Title</h1>\n<p><strong>Bold</strong> and <em>italic</em>.</p>\n"LaTeX for documents and papers:
latex(ast)"\\section{Title}\n\\textbf{Bold} and \\textit{italic}.\\par\n"Markdown for normalization and roundtripping:
markdown(ast)"# Title\n\n**Bold** and *italic*.\n"Other formats include typst() for Typst documents, term() for terminal output with ANSI colors, notebook() for Jupyter notebooks, and json() for Pandoc AST JSON (enables export to docx, epub, rst, etc.). Use json(Dict, ast) to get the dict without JSON serialization.
All writer functions accept a filename or IO as the first argument:
html("output.html", ast)
term(stdout, ast)Writers also accept an optional env dictionary for configuration. Front matter from the document is merged automatically:
env = Dict{String,Any}("title" => "My Page", "lang" => "en")
html(ast, env)Transforms
Writers accept a transform keyword argument to intercept and modify nodes during rendering. Use transforms for URL rewriting, syntax highlighting, or wrapping output in templates:
# Rewrite .md links to .html for static sites
function rewrite_links(::MIME"text/html", link::CommonMark.Link, node, entering, writer)
entering || return (node, entering)
dest = replace(link.destination, ".md" => ".html")
(CommonMark.Node(CommonMark.Link; dest, title=link.title), entering)
end
rewrite_links(mime, ::CommonMark.AbstractContainer, node, entering, writer) = (node, entering)
ast = parser("[Guide](guide.md)")
html(ast; transform = rewrite_links)"<p><a href=\"guide.html\">Guide</a></p>\n"See the Transforms page for full documentation.
Customization
The parser is modular. Each piece of syntax (headings, lists, emphasis, etc.) is handled by a rule that can be enabled or disabled independently.
By default, all standard CommonMark syntax is enabled. Extensions add syntax beyond the spec:
using CommonMark
parser = Parser()
enable!(parser, TableRule())
enable!(parser, FootnoteRule())
enable!(parser, MathRule())
ast = parser("""
| A | B |
|---|---|
| 1 | 2 |
""")
html(ast)"<table><thead><tr><th align=\"left\">A</th><th align=\"left\">B</th></tr></thead><tbody><tr><td align=\"left\">1</td><td align=\"left\">2</td></tr></tbody></table>"Default rules can be disabled if you want stricter or simpler parsing:
parser = Parser()
disable!(parser, SetextHeadingRule()) # Only allow # headings, not underlinedSee Core Rules for the default syntax and Extensions for additional features like tables, math, admonitions, and a string macro for interpolation (cm"Hello $(name)!").