PythonでPandocのフィルターを書く
私は汎用ドキュメントフォーマット変換ツールpandocを愛用している。Pandocについて詳しくは日本語版ユーザーズガイドなどを参照されたい。
ところが、場合によっては変換時に微妙にドキュメントに手を加えたいことがある。例えば、マークダウンで書いたドキュメントをHTMLに変換する際リンクのURLを一斉に置換したいかもしれない。
正規表現で変換するのが簡単だが、実はpandocにはフィルター機能が提供されている。フィルターを使えば、パースされたドキュメントのシンタックスツリーを利用できる。フィルターはpandoc本体と同様にHaskellで書くこともできるが、仕組み的には何言語でも書くことができ、Pythonも公式でサポートされている。
以下のようにpandocがパースしたドキュメントの構文木をJSONフォーマットに変換し、標準入出力を介してフィルターに渡す仕組みだ(図はマニュアルより)。
source format
↓
(pandoc)
↓
JSON-formatted AST
↓
(filter)
↓
JSON-formatted AST
↓
(pandoc)
↓
target format
これを使えばインテリジェントなフィルターを書くことができる。まず、公式で提供されているpandocfiltersをインストールしよう。
pip install pandocfilters
これを使って早速ドキュメント中のリンクURLを変更するフィルターを書こう。
from pandocfilters import toJSONFilter, Link
def myfilter(key, value, format_, meta):
if key == 'Link':
value[1][0] = "prefix/" + value[1][0]
return Link(*value)
if __name__ == "__main__":
toJSONFilter(myfilter)
実行するには、pandoc実行時にfilterオプションを指定する。カレントディレクトリのスクリプトを指定するには、"./convertlink.py"と書かなければならないので気をつけて。
## sample document
text text text
[link](path/to/otherpage)
$ pandoc --filter=./convertlink.py -t markdown sample.txt
sample document
---------------
text text text
[link](prefix/path/to/otherpage)
その他のTips
pandocが使用する構文木(pandoc AST)のサンプルはpandocで出力することができる。jsonを指定するとJSON、nativeを指定するとHaskellフォーマットで確認できる。
$ pandoc -t json sample.txt
[{"unMeta":{}},[{"t":"Header","c":[2,["sample-document",[],[]],[{"t":"Str","c":"sample"},{"t":"Space","c":[]},{"t":"Str","c":"document"}]]},{"t":"Para","c":[{"t":"Str","c":"text"},{"t":"Space","c":[]},{"t":"Str","c":"text"},{"t":"Space","c":[]},{"t":"Str","c":"text"}]},{"t":"Para","c":[{"t":"Link","c":[[{"t":"Str","c":"link"}],["path/to/otherpage",""]]}]}]]
$ pandoc -t native sample.txt
[Header 2 ("sample-document",[],[]) [Str "sample",Space,Str "document"]
,Para [Str "text",Space,Str "text",Space,Str "text"]
,Para [Link [Str "link"] ("path/to/otherpage","")]]
フォーマットの詳細はText.Pandoc.Definitionのドキュメントにある。
またフィルターオプションの指定は以下のコマンドパイプラインと等価なので、デバッグ中はこれを利用できる。
$ pandoc -t json sample.txt | python ./convertlink.py | pandoc -f json -t markdown