[Pandoc] Markdownに埋め込まれたPlantUMLソースを画像に置換する


はじめに

pandoc単体では、Markdownに埋め込まれたPlantUMLを自動的に処理することはできず、先人たちはmarkdownのパース結果をjsonフォーマットで取り出し、python等で苦心して変換していた(記憶がある)

幸いにも、バージョン2.0以降、組み込まれたLua言語によるフィルタ処理が使えるようになった。

実際、pandoc公式のレポジトリ(pandoc/lua-filters)には、多くのLuaフィルタが用意されており、その中にあるlua-filters/diagram-generatorPlantUMLのソースを画像に置き換えるフィルタがある。

ただしこのフィルタ、ローカルにインストールして動かすタイプのため、準備が何かとメンドイ。
今日日、dockerコンテナ立ち上げるくらいで済ませて欲しいものである。

ところで、PlantUMLは、テキスト エンコード 方式でエンコードして投げつけると、画像を返してくれるPlantUML Serverという機能(Web App)を持っている。 (see: https://plantuml.com/ja/text-encoding)

また、docker hubPlantUML Serverイメージも用意してくれている(plantuml/plantuml-server)。

これらを悪魔合体させてLuaフィルタに組み込んでやれば、幾分楽になるのではという思いでチャレンジしてみた。

ざっと調べてみたところ、Luaフィルタ+PlantUML Serverは見つからなかったので新規性あるかも

レポジトリ

簡易的な内容のmarkdownについて置換できたので、githubにレポジトリぶち上げときました(ritalin/pandoc-plantuml-luafilter)。詳細はそちらを参照してください、

処理内容の詳細

PlantUML Serverにリクエストを送るための、テキスト エンコード 方式をサイトから引用すると、

  1. テキストをUTF-8でエンコードします。
  2. DeflateまたはBrotliアルゴリズムで圧縮します。
  3. Base64に類似したアルゴリズムを使って、ASCIIに再エンコーディングします。

とされている。

1については、pandocは、ソースをUTF-8で記述しなければならないため、多分解決していると思われる。

2については、純Lua製のSafeteeWoW/LibDeflateが見つかった。1

問題は、Luaフィルタで外部ライブラリを使うことができるかどうかということ。
pandocドキュメントには、

Initialization of pandoc’s Lua interpreter can be controlled by placing a file init.lua in pandoc’s data directory.

とあり、一応組み込むことは可能っぽい。しかしグローバルに依存ライブラリを入れるのはちょっといただけない。
ということで、代案はないか探した。いっぱい探した。

結果、パッケージのソース検索パスに入れてあげれば、グローバルに組み込まなくても使えることが判明。
検索パスはpackage.pathに保持されているため、

local search_paths = {
    package.path,
    paths.join({ root_dir, "modules", "LibDeflate", "?.lua" })
}
package.path = table.concat(search_paths, ";")

としておくことで、

local libDeflate = require("LibDeflate")

のような一般的な形式で外部ライブラリをロードできた。
また、副効果としてフィルタパスに親ディレクトリ(..)を含んでいても解決してくれるようになった。

3については、PlantUML公式のサンプルをもとに頑張って実装した。
pandocLua実装はバージョン5.3なので、ビット演算子が標準に組み込まれてくれてたおかげ(Bitwise Operators)でずいぶん楽できた。

次に、PlantUML Serverにリクエストを送ること。
これは、pandocサイドに用意されたpandoc.mediabagモジュールにfetchメソッドがあり、それを使うことで解決。

ファイル保存は、標準ライブラリのioモジュールを使用。

最後に、コードブロックを画像タグに置換して完了。

local img_el = pandoc.Image({}, image_src, "")

return pandoc.Para { img_el }

制限事項

  • 日本語を含むPlantUMLのソースで試していないためおかしくなるかも
  • 内蔵のLuaフィルタでは、フォルダ作ったり、ファイルの存在チェックしたり、URLが正しいかチェックしたりできないので2、 設定間違えると謎のエラーが出るかもしれません。

あとがき

Lua初めて触ったけど、右往左往してなんとか形にはできた。


  1. ちなみにBrotliのライブラリも存在していたが、ネイティブライブラリに依存していて手軽さに欠けるため断念。 

  2. 正確にはLuaFileSystemを使えば可能ではあるが、ネイティブライブラリ依存(要はソースからのビルドが必要)