Pandocは様々な文書ファイルを相互変換できるソフトウェアです。
“A unitversal document converter”を名乗るだけのことはあり、 HTML, LaTeX, Docx, Markdownなどの様々なファイル形式に対応します。
更には対応するファイル形式の追加に対応します。入力の場合はカスタムリーダー、出力の場合はカスタムライターと呼ばれ、共にLua言語で定義できます。
今回はカスタムライターに注目します。カスタムリーダーも中々面白そうなのですが、なかなかハードルが高そうなので、将来的に挑戦するとしましょう。
カスタムライターの基本は、文字列の引数を処理して新しい文字列を返すことの繰り返しです。処理を文書の構成要素ごとに関数として定義します。ハイパーリンクであれば、リンク先のURLなど、追加の引数の処理も必要になりますが、基本は文字列処理です。
入力した文字列がどんな構成要素から慣るかは、pandoc -t nativeコマンドで文書を内部表現に変換すれば分かります。たとえば**重要**というMarkdown記法による文字列はPara(段落)、Strong(重要)、Str(文字列)から構成されます。
> echo '**重要**' | pandoc -t native
[ Para [ Strong [ Str "重要" ] ] ]Pandocが定義する文書の構成要素は中々に多いですが、今回は上述の3種類に注目しましょう。これら要素を処理する関数は文字列しか引数にとらないので、比較的単純です。構成要素一覧はLua type referenceをご参照ください。
まずは最も基礎的な、装飾を持たない文字列に対する処理はStr関数で定義します。特に処理が不要であれば、引数s(名称は任意。ここではstringの頭文字を利用)をそのまま返します。
-- 文字列をそのまま返す
function Str(s)
return s
endHTMLなどに出力する場合、文字列とHTMLタグを区別するため、一部の文字をエスケープしてやる必要があります。そのような場合はエスケープ処理用のescape関数を別途定義しておいて、引数sを処理すればいいわけです。
-- HTML向けに文字列エスケープして返す
function Str(s)
return escape(s)
endStrong要素をHTML化するのであれば、引数sを対応するHTML要素の<strong>で囲めばOKです。
function Strong(s)
return "<strong>" .. s .. "</strong>"
endescapeはいらないの?と疑問に思うかもしれませんが、ここでPandocによる文字列の内部表現とそれに従う関数の適用順序が重要になります。改めて、**重要**の内部表現を見てみましょう。
> echo '**重要**' | pandoc -t native
[ Para [ Strong [ Str "重要" ] ] ]まさにこの入れ子構造に従って関数を適用して、以下のように処理が進むわけです。
Para(Strong(Str("重要"))) --> <p><strong>重要</strong></p>ということは、Str関数とStrong関数の両方で文字のエスケープを行うと、入力が「&」であれば、Str関数がエスケープ処理した「&」をStrongに渡し、最終的に「<strong>&amp;</strong>」になってしまいます。ブラウザ上では「&」になってしまい、想定した「&」と違ってしまいます。
Str("&") --> "&"
Strong(Str("&")) --> "<strong>&amp;</strong>"というわけで、各関数を定義する際には、その関数に渡る文字列が先にどのような関数で処理されていたかをイメージする必要があります。やや難易度の高いところですが、イメージが沸かないなら、先の例と同様にpandoc -t nativeを実行して逐次確認が必要になります。
これが分かっていれば、Para関数は引数sを<p>要素で囲めばいいと分かります。
function Para(s)
return "<p>" .. s .. "</p>"
end内部表現[ Para [ Strong [ Str "重要" ] ] ]に合わせて、内側の要素から順にStr, Strong, Para関数を定義しました。これだけではまともな文書ファイルをHTML文書に変換するに足りませんが、必要な文章要素ごとに文字列処理を定義すればいいとイメージは湧いたかと思います。
ENJOY
Atusy's blog