Skip to main content

xhtmlmd

A Rust Markdown parser and XHTML renderer.

The parser is tree-oriented. It preserves the structure and attributes needed for XHTML output, but it does not try to round-trip source text. The dialect is CommonMark/GFM for the core and GFM features, with Pandoc-leaning choices where extension families disagree.

xhtmlmd is largely implemented using AI, except for the tests. The tests are largely adapted from cmark-gfm, PHP Markdown Extra, kramdown, Pandoc, and Mistlefoot. Credit for xhtmlmd really belongs to the authors of these tests, and of the CommonMark docs, which is where the hard work was done.

Implemented syntax

  • Core block syntax: paragraphs, ATX/setext headings, thematic breaks, block quotes, ordered/unordered lists, indented code, raw HTML, link reference definitions.
  • Tables: GFM/PHP Extra pipe tables with alignment, and Pandoc grid tables with alignment, headerless tables, block cell content, row spans, column spans, and footers.
  • GFM: task lists, ~~x~~ strikethrough, angle and bare autolinks, plus opt-in tagfiltering.
  • Code: backtick/tilde fenced code blocks, info strings, and Pandoc-style code attributes.
  • HTML-in-Markdown: block containers opened with markdown="1"; the control attribute is stripped, indented code blocks are disabled inside the container, and fenced code is the code-block syntax there.
  • Math: four modes: brackets for \(...\), \[...\], and $$...$$, dollars for those plus $...$ using Pandoc's non-space/digit dollar rules, on to preserve \(...\) and \[...\] delimiters for client-side renderers such as KaTeX, and off. Brackets mode is the default.
  • Attributes and inline spans: Pandoc/kramdown-style {#id .class key="value"}, block IALs {: ...}, span IALs, ALDs such as {:note: #id .class} with references, superscript ^x^, subscript ~x~, and highlight ==x==.
  • Definition lists: PHP Markdown Extra/Pandoc-style Term followed by : definition or ~ definition.
  • Footnotes: [^id] references to defined [^id]: definitions with indented continuation blocks.
  • Abbreviations: *[HTML]: Hyper Text Markup Language definitions render matching text as <abbr>.
  • Fenced divs: Pandoc/Quarto/Djot-style ::: containers with attributes or a single class word.

Usage

Install via pip to get both the Python API and the native xhtmlmd CLI:

pip install xhtmlmd

The CLI reads Markdown from stdin or from an optional file path and writes an XHTML fragment to stdout:

echo '# Hello' | xhtmlmd
xhtmlmd input.md > out.xhtml
xhtmlmd --math=on input.md > out.xhtml
xhtmlmd --math=dollars input.md > out.xhtml

Python API:

from xhtmlmd import to_xhtml

html = to_xhtml(r"\(x^2\)")
html_for_katex = to_xhtml(r"\(x^2\)", math="on")
html_with_dollars = to_xhtml("$x$", math="dollars")

Callbacks

Python callers can override rendered nodes with callbacks. Each callback receives a node dict and the default XHTML for that node. Return None to keep the default, or return replacement XHTML.

Callback names:

  • Blocks: paragraph, heading, block_quote, list, definition_list, code_block, html_block, html_container, thematic_break, table, div, math_block
  • Inlines: text, soft_break, hard_break, emph, strong, strike, superscript, subscript, highlight, code, link, image, autolink, abbr, html_inline, math_inline, footnote_ref, span
from fastpylight import highlight
from xhtmlmd import to_xhtml

def highlight_code(node, default_html):
    if node["lang"] != "python": return None
    return highlight(node["text"], node["lang"]) + "\n"

html = to_xhtml(markdown, callbacks={"code_block": highlight_code})

Callbacks can also render bracket math as MathML:

from math_core import LatexToMathML
from xhtmlmd import to_xhtml

mathml = LatexToMathML()

def render_math(node, default_html):
    html = mathml.convert_with_local_state(node["tex"], displaystyle=node["type"] == "math_block")
    return html + ("\n" if node["type"] == "math_block" else "")

html = to_xhtml(markdown, callbacks={"math_inline": render_math, "math_block": render_math})

Block spans

blocks reports where each top-level block sits in the source, so callers can split a document into per-block source slices without regenerating Markdown from a tree. Each dict has type (the callback names above, plus link_ref, abbr_def, attr_def, and footnote_def) and half-open 0-based start/end line indices; code and math blocks also carry their inner text, and fences carry info/lang.

from xhtmlmd import blocks

src = open("input.md").read()
lines = src.split("\n")
for b in blocks(src):
    print(b["type"], "\n".join(lines[b["start"]:b["end"]]))

Command-line usage (the xhtmlmd script is installed with the package):

xhtmlmd input.md > out.xhtml
cat input.md | xhtmlmd --math=dollars

Parsing strategy

The parser uses the two-phase strategy described in the CommonMark parsing-strategy appendix: first build the block tree and collect link reference definitions, then parse raw inline text with the completed reference table. It tracks visual columns and byte offsets for each line and builds blocks with an arena-backed open-container stack. The stack has typed nodes for block quotes, lists, paragraphs/setext candidates, fenced and indented code, raw HTML, table candidates, grid tables, math, footnote definitions, definition lists, fenced divs, and markdown-in-HTML containers. Inlines are scanned into atoms, bracket openers, and delimiter runs; links/images/spans resolve through the bracket stack, while emphasis/strong/strikethrough resolve through the delimiter stack. Inputs that can otherwise explode have explicit bounds: inline nesting, block/container nesting, link label length, and link parenthesis nesting.

The link parser uses raw reference-label scanning, bounded parenthesis nesting, bounded link labels, URI escaping for rendered href/src attributes, and a plain-text fast path for inputs with no possible inline constructs. This keeps adversarial inputs such as deeply nested brackets, long blockquote runs, repeated ![[](), and unclosed comments in predictable time.

Raw HTML is preserved by default. Supported raw HTML container tags such as div, section, table, svg, math, and custom elements stay open across blank lines until their matching close tag, with same-tag nesting counted; void and self-closing tags do not open balanced containers. Markdown inside raw HTML remains raw unless the open tag that starts the Markdown block uses markdown="1"; this crate does not recursively look for markdown controls inside otherwise-raw HTML. Options::default().tagfilter is false; enabling it applies GFM-style filtering for tags such as script, style, xmp, and textarea. This is compatibility and extra protection, not a replacement for sanitizing untrusted rendered HTML.

Raw HTML passthrough means unbalanced source HTML produces an unbalanced fragment, exactly as CommonMark specifies. The opt-in balance option (Options::default().balance is false; --balance on the CLI) restores well-formedness after rendering: unclosed elements are closed at the end of the fragment, stray closing tags are dropped, a closing tag that skips over open elements closes them first, void elements are rewritten to self-closing form, and rawtext elements such as script are copied verbatim to their real close. It deliberately does not apply HTML5 implied-end-tag rules (no <p> auto-close) or rewrite attributes.

Tests

maturin develop && pytest -q

The spec-conformance suite is tests/test_conformance.py: it renders the fixtures under tests/source/ and compares normalized HTML trees. Run just that file with pytest tests/test_conformance.py -v to see per-example ids.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xhtmlmd-0.1.9.tar.gz (142.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

xhtmlmd-0.1.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (470.9 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.9-cp313-cp313-macosx_11_0_arm64.whl (420.1 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

xhtmlmd-0.1.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (470.9 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.9-cp312-cp312-macosx_11_0_arm64.whl (420.2 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

xhtmlmd-0.1.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (472.0 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.9-cp311-cp311-macosx_11_0_arm64.whl (421.9 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

xhtmlmd-0.1.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (472.6 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.9-cp310-cp310-macosx_11_0_arm64.whl (422.3 kB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

Details for the file xhtmlmd-0.1.9.tar.gz.

File metadata

  • Download URL: xhtmlmd-0.1.9.tar.gz
  • Upload date:
  • Size: 142.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for xhtmlmd-0.1.9.tar.gz
Algorithm Hash digest
SHA256 811e2ba7195fac7d33b660031ed324d2896544066bc3cae7558bf40aef3b6bf9
MD5 9e82260fda4191285a5c6796105bb894
BLAKE2b-256 eb6980cb4aa10240203c08c5fe0f8582ea8722889377b516a240938475efd1d9

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9.tar.gz:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 21fb47ceba6a1c5bd5343190963b905497fc278f2485e0c4e8f43c324ff80fb2
MD5 a00276b1234c45a48f96729df5433ebb
BLAKE2b-256 fca6c1e78d53146cc0a4c54db0082be8786f7acdf5555f66e6dea82e8d86fb4d

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 62bfb908812f285bb388c0be6859f0cc99b0f203e6b27120e7117ddf2a59205d
MD5 a3a3bc659a2c26fd37c68a78f929eaac
BLAKE2b-256 884fd9a5e3e7f3ab2e6441a35005f3402d51df89fadc66ee3489cd49179be300

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 8d099c5f66f355b6e2df29eaa1a658f8bbd5beeeac357793a9a5d64fc66f5b7b
MD5 3658250582d912b4ff69c72dcb38e116
BLAKE2b-256 e6609d236e651a796f6d89b22f0d6839593793cdeae00e24f4ac4d8aa76c67c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e6075e5040e255374717dcf0bd287607065ccbba5f80c5a9c2493bd79e3b4fc2
MD5 c66c5bd6d5c3f896c60a7a01d105063d
BLAKE2b-256 391fee1ea95bb8c7ad1b1ad9f72beadf94d0970f5c1840eea0ad4d3d2cbf5836

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 18c3ef44920896741b65d2e007dfb8cd2b57ffaaa1131f937deafc66bf3a3716
MD5 13611828953c50bb827cfef15d1dbf70
BLAKE2b-256 37fe57b384cdc44251f29b813c99623d9f56ed947cdac932bc83e297f7037efa

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 728a85172d030ffe0d643e321aab3aa1d4bf822880bdfd20e68e58b39a6739e7
MD5 318f597bdb3b256a9b62265501dc1e5a
BLAKE2b-256 958f6f5d448b93793797d18123220f260a8b58cd2ca56b88c17615d57d17e1e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 469980b8ebd12bc69c1f3cb930c6a1540a8b6e52faae8d5d050cfe3f0012235e
MD5 261265f52b6618969a8366b65c8c2810
BLAKE2b-256 52ff4643ee95f0f73da357025759bfcf04a5d4aabd6a740d780e3f2fca9a0d4d

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xhtmlmd-0.1.9-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.9-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9bbde71d4fbf48bef663351b9db2336fd02916240f32f6883e20d6c8a8ec281b
MD5 79d04891f4e8efb1b1bbfcd55c8eb011
BLAKE2b-256 d9e6ea087f27827ab47fef076026fc252b840778a6c9e35f3af322516991a1bc

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.9-cp310-cp310-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/xhtmlmd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page