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 |  |
Separators: , ; whitespace, %20. Multiple URLs build a figtable gallery table; single URL builds <figure>. Destinations may contain spaces and balanced parentheses. |
| Video thumbnail |  |
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 , 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 sameid=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)
| File | Size | Uploaded | |
|---|---|---|---|
| pyxmark-0.1.1.tar.gz | 24.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|