跳到內容

Python

先備知識:快速開始、Document IR。

要從 Django/Flask backend、Jupyter manuscript pipeline 或 Python static-site generator 解析/轉譯 .mdi,不必呼叫 CLI。illusion-markdown 經由 PyO3/maturin 將 mdi-core 編譯成 native extension;約 60 行的 Python wrapper 沒有自己的 MDI grammar,每個 function 都直接呼叫同一份 Rust。

Terminal window
pip install illusion-markdown

PyPI distribution 名稱是 illusion-markdown,import name 是 mdi。需要 Python 3.10+;macOS(Intel/Apple Silicon)、Linux x64、Windows x64 有預建 wheel,其他 platform 會使用需要 Rust toolchain 的 source distribution。

import mdi
source = """---
title: 東京の夜
lang: ja
---
# {東京|とうきょう}の夜
第^12^話
"""
result = mdi.parse(source)
print(result["document"]["children"][0]["type"]) # "heading"
html = mdi.render_html(source)
open("book.html", "w", encoding="utf-8").write(html)
mdi.MDI_SPEC_VERSION # "2.0"
mdi.MDI_IR_VERSION # "1.0"
mdi.TextFormat # Literal["txt", "txt-ruby", "narou", "kakuyomu", "aozora", "note"]
mdi.MdiRenderError
mdi.parse(source: str) -> dict
mdi.render_html(source: str) -> str
mdi.render_text(source: str) -> str
mdi.render_text_format(source: str, format: mdi.TextFormat, indent_prefix: str = "") -> str
mdi.render_epub(source: str) -> bytes
mdi.render_docx(source: str) -> bytes
mdi.serialize_mdi(source: str) -> str
mdi.parse_mdi_syntax # deprecated alias

現時沒有 render_pdf。

parse() 回傳不是 typed class/dataclass 的 plain dict[str, Any],由 JSON wire format 直接 json.loads 而來,保留camelCase key:irVersion、syntaxVersion、document、diagnostics,node 裡是 startByte/endByte。因此請用 result["document"]["children"][0]["type"],且 Document IR 完全適用。render_epub/render_docx 是真正的 bytes(Rust 端由 PyBytes 支援),必須以 binary mode("wb")寫檔。

一般格式不正確的 MDI 不會 raise,而以 literal fallback 處理;回傳 dict 的 diagnostics warning 完整清單見診斷:

result = mdi.parse("---\nmdi: '3.0'\n---\n\n本文")
# result["diagnostics"]:
# [{'severity': 'warning', 'code': 'mdi.version.unsupported',
# 'message': 'MDI 3.0 is newer than the supported 2.0',
# 'span': {'startByte': 0, 'endByte': 18}}]

三種真實且不同的失敗模式如下:非 str input 是 PyO3 自動產生的 TypeError;mdi.render_text_format("text", "invalid") 是 ValueError: Unsupported text format: invalid;只有 Rust 的 EPUB/DOCX archive writer 本身失敗(例如底層 I/O)才會是 mdi.MdiRenderError。一般文件不會觸發最後一種。保留 try/except 給這些情況,勿用它取代檢查 result["diagnostics"]。

mdi.parse() 會自行將 decoded irVersion 與 mdi.MDI_IR_VERSION 比較;若不同會 raise RuntimeError: Unsupported MDI IR version: ...。span 是 UTF-8 byte offset,不是 Python str 的 code-point index;要轉換須明確做 source.encode("utf-8")。請見診斷與 source spans。

def byte_span_to_str_index(source: str, byte_offset: int) -> int:
return len(source.encode("utf-8")[:byte_offset].decode("utf-8", errors="ignore"))

以上功能都已發布並受測,並非 speculative binding。套件自己的測試會驗證 IR shape、diagnostic 格式、byte span、六種文字格式、EPUB/DOCX archive 結構與上述錯誤路徑,並強制至少 95% branch coverage。

  • **尚無 PDF function。**Python 可以 spawn subprocess,沒有 WASM 那類根本限制,但 package 今天未 expose mdi.render_pdf;Python workflow 的 PDF 暫用 CLI。
  • **沒有自己的 grammar。**每個 function 都直接呼叫同一個 mdi-core;若與 CLI 或 Rust 不一致,是這個約 60 行 wrapper 的 bug,而不是另一套 parser。
  • 尚未提供 export profile 參數。render_epub/render_docx 目前只收 source。設定型 EPUB/DOCX 已在 Rust 實作,只是 Python wrapper 尚未公開 profile 與 cover 參數。

分割規則由 Rust 統一實作。固定兩行、正文50%字級、零小行間距。首個片段可使用正文行剩餘容量,後續片段使用完整行容量。容量與回傳寬度以割注字級的半個em為單位;這是字寬估算,不保證比例字型的精確均衡。

from mdi import layout_warichu
fragments = layout_warichu([{"type": "text", "value": "一二三四五六"}], 4, first_capacity=2)

結果包含 lines、html、widths、overflow、hardBreakAfter 與 sources。path 是從輸入陣列起算的子節點索引路徑;startUtf8 / endUtf8 是可見文字中的半開UTF-8位元組範圍。相同 group 保留跨格式邊界的書寫素。Ruby、縱中橫及no-break保持不可拆。作者硬換行保留,自動分割不寫回canonical MDI或純文字。靜態HTML/EPUB的閱讀器重排結果可能不同。DOCX使用原生雙行群組;XML與匯入器檢查不代表Word實測。

mdi.parse(source, include_comments=True)

MDI 2.1 recognizes <!-- note --> in all documents, including declared 2.0 and unversioned source. Comments can be empty, multiline or Unicode; the nearest --> closes them and their contents are not interpreted. Code, front matter, link destinations and plain-text MDI parameters remain literal. Escape an opener as \<!--.

Source saving retains comments. Default parse/prepare/mdast APIs omit them with IR 1.0; { includeComments: true } returns positional comment nodes and IR 1.1. Both report syntax 2.1. Existing front-matter declarations are retained. Public body projections and layout exclude comments even with an inclusive tree, and their source-map runs preserve the gaps.

Every publication format always omits valid comments. This intentionally changes old 2.0 output that displayed them as HTML text. Unterminated comments remain literal and return mdi.comment.unterminated: export is allowed, so intended private text may be visible.

Use source serialization or an inclusive IR for lossless comment retention. A filtered external IR cannot restore omitted comments.

MIT License© 2026 Iktahana