Clean, modern parser and HTML renderer for XMD, a markdown dialect for complex article layouts
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyxmark-0.1.0.tar.gz.
File metadata
- Download URL: pyxmark-0.1.0.tar.gz
- Upload date:
- Size: 23.5 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ef26bf96ee44446a677df69e5b7ef08c5936f86ea5e666e2dbdcf19b20946a5
|
|
| MD5 |
e92d0dad20a01c202d6631bf7b8dd291
|
|
| BLAKE2b-256 |
0469925a41702343eb598007c35c02901ada2c47234a6f0efb0f7eab2817ad89
|
File details
Details for the file pyxmark-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyxmark-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a270caee9f73a198e0a93fd479bcaac8e9413f2c2ce7b4cdbfecbc9499a964b
|
|
| MD5 |
1beeaf04758c909b37d7d558112eba85
|
|
| BLAKE2b-256 |
391a71059149f8d22aa52b36ffc367db08fbc22aa26b623f60a48c155d3ac0d4
|