コンテンツにスキップ

コア概念

前提知識: MDI とは?

Rust クレート、JavaScript・Python・Swift・Kotlin パッケージ、CLI のすべての MDI インターフェースは、同じ 5 つの考え方を共有しています。ここで一度学べば、このサイトの他のすべてのページ(そして各バインディング自身のドキュメント)は、これらの語をすでに理解している前提で書かれています。

mdi-core(Rust)だけが、あるテキストが MDI 構文かどうかを決定します。呼び出しごとに文書全体の UTF-8を受け取ります ―― 断片を渡すことはありません。MDI の境界は周囲の Markdown 文脈に依存するからです。次の3行はすべて ^12^ という部分文字列を含みますが、行全体を見ている mdi-core だけが、どれが縦中横かを判断できます。

第^12^話 ← 縦中横:「12」が直立して描画される
`^12^` ← リテラルなコードスパン:ここでは ^12^ は不活性
**第^12^話** ← 強調(太字)の中でも縦中横は適用される

この判断を再実装することは、他のどの言語・ツールにも許されていません。JavaScript、Python、Swift のパッケージは Rust を呼び出したり、結果をキャッシュしたり、各言語に自然な形へ整形し直したりすることはできますが、ルビ/縦中横/傍点の文法規則を独自に持つことはできません。これが「Rust 主導」という言葉が具体的に意味することです ―― もし2つのツールが「これは有効な MDI か」で食い違ったら、それはバグです。そもそも文法は1つしかない前提だからです。

2. ドキュメント IR(中間表現)

Section titled “2. ドキュメント IR(中間表現)”

解析が生み出すのはスタイル付き出力ではなく木構造です。この木を IR と呼び、すべてのレンダラーが消費する唯一の成果物です。小さな文書に対して parse() を呼ぶと、次のような形が返ります。

{
"irVersion": "1.0",
"syntaxVersion": "2.0",
"capabilities": { "mdi": true, "commonMark": true, "gfm": true, "frontMatter": true, "sourceSpans": true },
"document": {
"span": { "startByte": 0, "endByte": 21 },
"children": [
{
"type": "paragraph",
"span": { "startByte": 0, "endByte": 21 },
"children": [
{ "type": "ruby", "base": "雪女", "ruby": { "type": "group", "value": "ゆきおんな" }, "span": { "startByte": 0, "endByte": 12 } }
]
}
]
},
"diagnostics": []
}

いくつか気づいてほしい点があります。

  • バージョンが2つあります。 syntaxVersion(“2.0”)は MDI という言語のバージョン ―― どの構文が存在し、何を意味するか。irVersion(“1.0”)はワイヤーフォーマットのバージョン ―― 上記の JSON の形そのものです。これらは独立して変化します。将来 MDI 2.1 で構文が追加されても IR が一切変わらないこともあれば、IR を破壊的に作り直しても構文バージョンは上がらないこともあります。
  • CommonMark、GFM、MDI が1つの木にまとまっています。 「Markdown の木」と「MDI の木」を別々に作ってマージするのではなく、見出し・リンク・表・ルビのノードは同じ構造の中で兄弟・親子になります。MDI 構文は Markdown の中に(あるいはその逆にも)入れ子になれます。例は 構文リファレンス の「インラインの入れ子」を参照してください。
  • ソースに由来するすべてのノードは span を持ちます。 これが次の項目です。

ノードの完全な一覧は Document IR を参照してください。

3. span は UTF-8 バイトオフセットであり、文字インデックスではない

Section titled “3. span は UTF-8 バイトオフセットであり、文字インデックスではない”

span.startByte / span.endByte は、元のソース文字列の UTF-8 バイトを単位とした半開区間(startByte は含み、endByte は含まない)です。Unicode コードポイントでも、UTF-16 コード単位でも、書記素クラスタでもありません。

これは実務上重要です。「雪」は1文字ですが UTF-8 では3バイトです。JavaScript(文字列が UTF-16)の <textarea> で span をハイライトしたい場合、バイトオフセットをそのまま文字列インデックスとして使うことはできず、変換が必要です。MDI がホスト言語ネイティブの文字列インデックス型ではなくバイトオフセットを使う理由は、バイトオフセットがすべての言語が曖昧さなく計算できる唯一の表現だからです。「文字インデックス」は定義を選ぶまで well-defined な概念ではなく(UTF-16 単位?コードポイント?書記素クラスタ?)、MDI は JavaScript 固有・Python 固有の選択をワイヤーフォーマットに焼き込みたくありません。

4. 診断はデータであり、例外ではない

Section titled “4. 診断はデータであり、例外ではない”

**診断(diagnostic)**は回復可能な問題を報告するものです ―― diagnostics 配列内の単なるオブジェクトであり、決して例外として投げられません。

{ "severity": "warning", "code": "mdi.version.unsupported", "message": "MDI 2.1 is newer than the supported 2.0", "span": { "startByte": 0, "endByte": 34 } }

parse() はほとんど例外を投げません。不正・曖昧な MDI 構文(閉じない ^、無効な量の [[kern:、一致しないルビの分割ドット)は、各構文ページに書かれたリテラルフォールバック規則で処理されます ―― たいてい診断すら出さずにテキストをそのまま木に残します。これは Markdown 自身が未知の構文を扱うのと同じ寛容な振る舞いです。例外は診断で表現できないもの ―― 文字列でない引数や、ネイティブリソースの失敗(PDF 用の Chromium が見つからないなど)―― のためにとってあります。診断コードの現在の完全な一覧(今日はちょうど1つ)は 診断とUTF-8ソーススパン を参照してください。

5. capabilities は「今回の解析」を表すものであり、将来の約束ではない

Section titled “5. capabilities は「今回の解析」を表すものであり、将来の約束ではない”

capabilities は、すべての解析結果に付く真偽値の集合(mdicommonMarkgfmfrontMattersourceSpans)です。機能があると決め打ちせず、この値を確認してください ―― これは、Rust コアが完全な CommonMark パーサーへ育つ前の過渡期の JavaScript バインディングが「MDI 固有の構文しか解析していない、CommonMark 全体ではない」と呼び出し側に伝えるために使われていた仕組みです。今日はすべての capability が true ですが、IR が明示的にバージョン管理されており、バインディングが推測してはいけないという理由でこのフィールドは今も存在します。

ここまでは意味―― ソースが何を言っているか ―― の話でした。外見(ページサイズ、フォント、特定の出力用のマージン、字下げの方式)を決めるのは、別の後段のステップです。それが export profile であり、これは文書が何であるかを変えることはできず、レンダラーがそれをどうレイアウトするかしか変えられません。レンダラー(renderHtmlrenderTextrenderEpubrenderDocx、そして Chromium を経由する PDF パス)は IR とプロファイルを消費します。Chromium の役割がどこで始まりどこで終わるかは レンダリングモデルと Chromium/PDF の境界 を参照してください。