Skip to main content

xmark

Modern parser and HTML renderer for XMD — a markdown dialect for complex article layouts.

Plain markdown runs out of room once an article needs pull quotes, sidebars, terminal transcripts, image galleries and typographic polish. XMD adds those as first-class syntax, and xmark renders them to HTML with stable, sequential element ids you can link to. Built on mistune.

Install

pip install pyxmark

Usage

from xmark import render_markdown

html = render_markdown(
    "First paragraph\n\nSecond line",
    target="web",          # or "pdf"
    article_id=42,
    uploads_path="https://cdn.example.com/uploads",
    default_lang="python",
    paywall_marker="",     # substituted for the ------ cut line
    wrapper=None,          # e.g. "xmd" -> wraps output in <article>
)

CLI reads stdin, writes HTML to stdout:

echo "Hello **world**" | xmark --target web --article-id 1

XMD syntax

Standard markdown (headings, lists, blockquotes, fenced code, tables, inline formatting, links, images) via mistune with the strikethrough and table plugins. Bare URLs are not autolinked, and task lists are not special.

Extensions beyond standard markdown:

Element Syntax Notes
Lead paragraph First paragraph in document Becomes <div class=lead>. Optional $ line prefix stripped. Not promoted inside lists/quotes/panels; a leading list or quote disables promotion.
Catcher | text (consecutive lines) Each line becomes <div class=catcher><span>…</span></div>. Inline markdown parsed. Lines forming a GFM table stay a table.
Cite -- author Single line, <cite>. Inline markdown parsed.
Panel (fenced) ~~~type … ~~~ Types: www, info, warning, danger, greeting (h4 + type class), term (raw <pre>), empty/panel (default, no h4), anything else (default + h4 with original case). A typed fence closes the previous panel and opens the next; unclosed panels run to EOF. Tilde code fences are hijacked by this rule — use backticks for code.
Panel (bracket) [ type … ] (lone lines) Same types as fenced. A bare [ opens an untitled default panel. Without a closing ] the line stays plain text.
Inline math $formula$ <span class="math">…</span> in paragraphs, list items, catchers and cites — not in headings, and not inside <code>. Render client-side with KaTeX auto-render.
Underline _text_ <u>. __text__ is still <strong>.
Images/gallery ![alt](url1,url2 "title") Separators: , ; whitespace, %20. Multiple URLs build a figtable gallery table; single URL builds <figure>. Destinations may contain spaces and balanced parentheses.
Video thumbnail ![alt](youtube_or_vimeo_url,thumb "title") youtube.com/vimeo.com URLs (not youtu.be). A missing thumb falls back to img.youtube.com/vi/<id>/hqdefault.jpg for YouTube.
Non-breaking span ++text++ Preprocessed to <span class=nobr>, skipped by typograf.
Paywall cut ------ (6+ dashes) The free/paid boundary. Replaced pre-parse with the paywall_marker string; dropped entirely when it is empty.
Inline tags %tag attrs … %tag Block-level: <tag attrs>…</tag> with inner markdown parsed. Opening line requires attrs after the tag.

Rendering details

Element ids. Every rendered element gets a sequential id (id=p1, id=code2, id=h2-3, …), assigned children-first, so a nested list gets lower ids than its parent. Ids are stable for a given input, which makes them usable as deep links.

Code highlighting. Pygments, emitted line-wise as <code class='highlight <lang>'><div class=wrapper><div class="l l1">… with per-line divs and short token classes, so blocks can wrap under white-space: pre.

Typography. The typograf stage processes text nodes only, skipping h1..h4, pre, code, kbd, script, style and math elements plus the nobr and term classes. It injects soft hyphens into Russian words, glues prepositions and numbers with &nbsp;, and converts -> arrows. The document is normalized on the way out: entities decoded, &/</> re-escaped, attributes double-quoted, and void elements serialized as <br>, <hr>, <img …>.

Uploads. Relative src= paths are prefixed with uploads_path; no files are fetched.

Two behaviors worth knowing when you diff output:

  • A catcher block shares one id. Every | line of a block renders with the same id=catcherN; the next block gets N+1.
  • Gallery tables are preceded by an empty <p> — <p id=pN></p><table∆ class='figtable…'> — because the table is hoisted out of its paragraph wrapper.

Development

uv sync
uv run pytest
uv run ruff check
uv run ruff format

Metadata

Release files for pyxmark 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyxmark 0.1.1
File Size Uploaded
pyxmark-0.1.1.tar.gz 24.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyxmark 0.1.1
File Interpreter ABI Platform
pyxmark-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 47.0 kB

Release files / pyxmark-0.1.1.tar.gz

Download URL pyxmark-0.1.1.tar.gz
Size 24.0 kB
Tags Source
SHA-256 checksum
How to use checksums
bda3cd80cd986b5d10c5aa4a225eda135689f3bad82f9f040163f318830e70bf
BLAKE2b-256 checksum
How to use checksums
150ec8ba0d9a303eb90603bdd01009cd9f0a0d1b35299f060553ed9dcbb980c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pyxmark-0.1.1-py3-none-any.whl

Download URL pyxmark-0.1.1-py3-none-any.whl
Size 23.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
daee60cfc036501df874459989ef7e3da28acacf1b44b0870048cf2b45577d3b
BLAKE2b-256 checksum
How to use checksums
d37dc4ef57501a94a917fabba92362b08d0e3bf908388ad81caceb695300307e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page