pandoc はドキュメントを変換するためのプログラムです.pandoc には,変換処理をカスタマイズするための「フィルター」という仕組みが用意されています.
本記事の目的は,フィルターを自作するために必要となる pandoc AST の知識(特に Text.Pandoc.Definition にある情報)を,将来ふたたびフィルターを書くハメになった自分が使いやすいようにまとめておくことです.ただ,せっかく公開記事にするのだからとフィルターの基礎知識から説明しているうちに,その部分が長くなりすぎてしまったので,その基礎知識の説明を独立させて「前編」として公開することにしました.本来の目的は後編で果たすことになります.
本記事では,pandoc のインストールや基本的な使い方については説明しません.公式の ユーザーズガイド ( 日本語版もあります )などをご覧ください.
pandoc の仕組み
フィルターの説明をするために,まず pandoc による変換の仕組みを説明します.
pandoc は,ユーザーから変換元のドキュメントと変換先の形式を受け取ります.しかし,変換元のドキュメントを,ダイレクトに変換先の形式に変換するわけではありません.まず pandoc は,変換元のドキュメントを,pandoc AST という形式のドキュメントに変換します.そのあと pandoc は,この pandoc AST 形式のドキュメントを,ユーザーに指定された変換先の形式に変換します.2段階の変換を行っているわけです.
変換元の文書を pandoc AST に変換するプログラムをリーダーといい,pandoc AST をユーザーに指定された変換先の形式に変換するプログラムをライターといいます.pandoc は様々な形式の入力・出力に対応していますが,pandoc がある形式の「入力(出力)に対応している」とは,その形式の「リーダー(ライター)がある」ということにほかなりません.
フィルター
pandoc には,変換処理をカスタマイズする仕組みが用意されています.それがフィルターです.
フィルターとは,pandoc AST 形式のドキュメントを pandoc AST 形式のドキュメントに変換するプログラムです.pandoc の実行時にフィルターを指定すると,先ほど説明した2段階の変換処理の1段階目と2段階目のあいだに割り込む形でこのフィルター(プログラム)が実行されることになります.
なお正確には,フィルターが扱うのは,入出力ともに pandoc AST そのものではなく,pandoc AST と等価な JSON 形式のファイルです.(pandoc AST と JSON の間の変換には,JSON のリーダーとライターが使われます.)
フィルターは pandoc コマンドの --filter オプションで指定でき,
pandoc -f SOURCEFORMAT -t TARGETFORMAT --filter FILTER
というコマンドは
pandoc -f SOURCEFORMAT -t json | FILTER | pandoc -f json -t TARGETFORMAT
というコマンドと等価です.
フィルターの特徴・ほかのカスタマイズ方法との比較
上述のように,pandoc のフィルターは,pandoc の通常の変換処理に割り込んで pandoc AST をいじります.一方,pandoc の処理の前後で特定の置換をおこなおうとすると,当たり前ですが変換元・変換先の形式のドキュメントに対する変換処理をすることになります.両者の違いの第一は,フィルターは変換前後の形式に依存しない形で書けること,違いの第二は,フィルターが pandoc 内部のリーダーによって AST(抽象構文木)化されたドキュメントを扱えるということです.この「第二」の方が特に重要で,たとえばドキュメント内の数式に対して何らかの処理を行いたい場合に,もとの文章から数式部分を取り出す正規表現を書く必要はなく,単に各ブロックについて "t" キーの値が "Math" か否かを判定すればよいことになるわけです(詳細は,本記事の後編で説明します).markdownドキュメント内の一定レベル以上の見出しに対して処理をする,といった場合も同様の恩恵が受けられます.
一方で,フィルターを書くには,pandoc AST の仕様を知っているか,あるいは調べながら書く必要があります.pandoc AST の定義そのものは Text.Pandoc.Definition に書かれています.定義は複雑ではないのですが,実際にフィルターを書くときに扱うことになる JSON 形式による既述ではないため,「翻訳」がやや面倒です.本記事の後編では,定義を調べる手間を軽減するため,その一部と利用法をまとめます.
なお,pandoc による変換処理をカスタマイズするほかの方法として,変換先の形式が決まっているならば,ライター自体をカスタマイズしてしまう方法もあります.pandoc の -t オプションは,任意のライターをパスで指定することができるのです.pandoc のライターは全て Lua で書かれたファイルですので,既存のライターをコピーし編集した上で,ライターとして指定します.この方法は,本記事ではこれ以上扱いません.たとえば takeda-at さんの Qiita の記事「 PandocのカスタムWriterでMarkdownからはてな記法に変換 」に例と説明があります.
フィルターを書く
前述のように,フィルターは,pandoc AST 形式のドキュメントを変換するプログラムでした.正確に言うと,フィルターは pandoc AST と等価な JSON ファイルを標準入力から受け取り,pandoc AST と等価な JSON ファイルを標準出力に渡すプログラムです.そのようなプログラムでありさえすれば何でもフィルターとして使えるわけですので,どのプログラミング言語で書くかは問題ではありません.ただ,Haskell, python, PHP, perl, node.js については,pandoc のフィルターを書くためのモジュールが公式に配布されていますので,いくらか書きはじめやすいと思います.