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 $...$ and $$...$$ 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_counter(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})

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.

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.6.tar.gz (131.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.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (463.9 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.6-cp313-cp313-macosx_11_0_arm64.whl (419.0 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

xhtmlmd-0.1.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (464.0 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.6-cp312-cp312-macosx_11_0_arm64.whl (419.0 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

xhtmlmd-0.1.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (464.8 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.6-cp311-cp311-macosx_11_0_arm64.whl (421.6 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

xhtmlmd-0.1.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (465.2 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

xhtmlmd-0.1.6-cp310-cp310-macosx_11_0_arm64.whl (422.1 kB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: xhtmlmd-0.1.6.tar.gz
  • Upload date:
  • Size: 131.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.6.tar.gz
Algorithm Hash digest
SHA256 3072c1f85c4a7061cb161b07ee8a2fe4855b658145c62d1d45fb0300fdca5620
MD5 af09b23cb2149287e755c610efc37814
BLAKE2b-256 e79667b00041ad426c9b8c5c51516986ebe33e8d39b4841bdb7af944df2a0e07

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6.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.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a49b40053cdc36d8eba910aed62206b70744b22c57f7b32dbcb3456972e453a4
MD5 a330cb9b98304d0a334b9c692ad0a454
BLAKE2b-256 2b18109cde9485ea8d1be6cf8b46c32300e4ed14c9b6f3830985beb0b9b2b05d

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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.6-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a58bf615abcf012493e03939971735a37e34258101ae59db62073d7f510b0554
MD5 1dbb229aa3839eb157a3fc7228e01228
BLAKE2b-256 96973aeb1de1bbb6968fff7dc5437d9280d79c29be83d2915efd3fe71d980241

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d8204f1f3722dc929db8ba398fee8bf8714f7b0de45ca545c5ee5121736b684f
MD5 d6da119df4f810902d29f44916dbd330
BLAKE2b-256 d7c3d16789f8aeac6682c12c895645450cb07e501007fa16a36a2011a2b9e498

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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.6-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3eecea913c1121fc476b628017cac4495023fdb5e41a5573d7739bb18a192372
MD5 b2db7f1ba1a4603beea9e0c5ac7f5fba
BLAKE2b-256 b4d48080845ef7ba8fccc9124707642e8570f0aae0181b05746bf60eec9cb666

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 19c9d7a76836c88da165334290da06ddaf884e018c0d6306b1ff291fb9554641
MD5 a2e6dbab0930a1605347950e1be62d83
BLAKE2b-256 b56b6851f38bee298ce82f0ebfe203c01596d9dccf817b629378c95f24256e02

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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.6-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0963e6f434afe1468f2fd4e4d8f99abfbf00b19494ec1e0262d5e0948c29162c
MD5 6df7dd373897ba990a1f52a040a1d92c
BLAKE2b-256 3b5b7f288a1cdb67a7464a016083df11b5e43b352b30cbd50b3bf708c1b45e87

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 837c3c5a69e962ae7e5cf14cb2fe33a7bf7c61dad95356f691840dd9aa87f801
MD5 8212999f139bbb7bf9bcac09f315aa7c
BLAKE2b-256 97e3d39bb6b952d464dfd314b73bb87b5df384b0b6884cf8f78989ec9710f8ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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.6-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for xhtmlmd-0.1.6-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8bfa31a8174fb41f0ae0971761902eeb6cc26763f2c8646eb5adfc1dfb8df9ec
MD5 da5e1182a64526f818f2028135556a19
BLAKE2b-256 244bb4c92ae86e1cb21898901c87292da3e5f2d348084acd5e986e0975ee2559

See more details on using hashes here.

Provenance

The following attestation bundles were made for xhtmlmd-0.1.6-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