MarkdownパーサをPHPで自作する
なぜ自作したか
このサイトの本番環境は共有サーバーで、Node.js が使えません。markdown-it のような定番はNode製なので、PHPだけで完結する変換器が必要でした。PHP製ライブラリを使う手もありますが、依存を増やしたくなかったのと、記法を自分のブログに必要な範囲へ絞りたかったので自作しました。
対応する記法を絞る
CommonMark 完全準拠は目指しません。実際に書く記法だけに対応します。
- 見出し・段落・強調・リスト・リンク・画像・コード・引用・表・水平線
- 目次
[[toc]]・脚注・マーカー(蛍光ペン的な装飾)
「使わない記法のためのコード」は書かない。これだけで実装量は大きく減ります。
設計の要点
先にエスケープ、あとから整形
セキュリティ上いちばん大事な順序です。本文全体を先にHTMLエスケープしてから、エスケープ済みの文字列に対してリンクや強調のタグをプレースホルダ経由で挿入します[1]。逆にすると、整形処理が生成したタグと本文由来のタグの区別がつかなくなります。
行単位のブロック解析
本文を行に分割し、「いまコードブロックの中か」「リストの中か」という状態を持ちながら1パスで処理します。正規表現で全体を一気に変換するより、デバッグが圧倒的に楽です。
foreach ($lines as $line) {
if ($inCodeBlock) { /* コード内は完全エスケープのみ */ }
elseif (str_starts_with($line, '#')) { /* 見出し */ }
// ...
}
学び
パーサ本体より、エスケープとURLの無害化(javascript: スキームの拒否など)に気を使う時間のほうが長かったです。変換器は「文字列を作る装置」なので、何を出力してよいかの規律が品質を決めます。
参考・出典
プレースホルダには本文に現れない制御文字を使い、入力側から偽造できないよう入力正規化で除去しておきます。 ↩