Skip to main content

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 ![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

Project details


Download files

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

Source Distribution

pyxmark-0.1.0.tar.gz (23.5 kB view details)

Uploaded Source

Built Distribution

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

pyxmark-0.1.0-py3-none-any.whl (22.6 kB view details)

Uploaded Python 3

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

Hashes for pyxmark-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2ef26bf96ee44446a677df69e5b7ef08c5936f86ea5e666e2dbdcf19b20946a5
MD5 e92d0dad20a01c202d6631bf7b8dd291
BLAKE2b-256 0469925a41702343eb598007c35c02901ada2c47234a6f0efb0f7eab2817ad89

See more details on using hashes here.

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

Hashes for pyxmark-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4a270caee9f73a198e0a93fd479bcaac8e9413f2c2ce7b4cdbfecbc9499a964b
MD5 1beeaf04758c909b37d7d558112eba85
BLAKE2b-256 391a71059149f8d22aa52b36ffc367db08fbc22aa26b623f60a48c155d3ac0d4

See more details on using hashes here.

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