niszetの日記

細かい情報を載せていくブログ

(Pandoc) docx -> md への逆変換のメモ(3回目)

まずはdocxにする際に細工をする。

さて、前回の考察の結果として、docx生成時にCodeBlockの言語情報が失われているので再生しようがないため、docx生成時になんとか情報を残す方法を考えねばならぬことがわかりました。

やろうと思えば色々とデータをdocxファイルに埋め込むことは出来ると思いますが(例えば隠し文字とか)、管理が煩雑になるのは避けたいところです。

そこで、スタイルを言語ごとに変えるという方法で考えてみます。例えばR言語であれば"Source Code R"といった具合です。

そのためにはまずdocxのテンプレートファイル中で対応するスタイルを作っておかねばなりません。これもプログラムを書いて自動化できるはずですが、面倒くさいので今回は手でやります。

テンプレートとして使用するdocxファイルを開き、Source Codeのスタイルの段落にカーソルを置いた状態で新規にスタイルを作成すれば、Source Codeを元にしたスタイルを作れるはずです。 このスタイルの名前を冒頭のように"Source Code R"のように必要な言語の数だけ作っておけばオッケーです。まぁコピペするだけなので4-5言語くらいなら手でやった方が早いでしょう…。

Lua filterの準備

さて、Lua filterで処理を考えます。と言ってもどうやって作るか…?ですが、今回はCodeBlockを囲う形でDivを作ることとしました。CodeBlockにclassがあれば、それを"Source Code "の文字列の後につなげてあげれば、上で作ったカスタムスタイルに対応しますね。

ここでは対応する言語名に対してtext.upper()で大文字に変更しています。

ここで注意しないといけないのがCodeBlockをそのまま残すとスタイルが反映されないということです。おそらくですが、CodeBlockが生成するSource Codeに阻まれる形で、外から囲おうとしているSource Code Rを邪魔しているのではないかと…。

そのため、Codeとして再定義することとしました。ただし、Codeはインライン要素なのでDivにそのまま与えることが出来ません。一旦Paraなどのブロック要素に入れてからDivにしまってあげる必要があります。

さて、これらを反映したLua filterのコードは下記のようになります。

local text = require('text')

function CodeBlock(e)
  for k, v in pairs(e.attr.classes) do
    if v == "r" then
      local val = "Source Code " .. text.upper(v)
      local code = pandoc.Code(e.text, e.attr)
      local div = pandoc.Div({pandoc.Para(code)})
      div.attributes["custom-style"] = val
      return(div)
    end
  end
end

これを使うと、"Source Code R"というカスタムスタイルに囲われたCodeを持つParaを生成できます。

Codeは不要ではとも見えるのですが、これはシンタックスハイライトを効かせるために必要です。フィルタが作用するASTの段階ではまだハイライトは実行されていないので、WriterにはCodeあるいはCodeBlockをわたす必要があります(もちろん、先の理由でCodeBlockではうまくいかないのでCode一択です)

これを反映させてdocxを生成後、Word上で"Source Code R"のスタイルでスタイルが適用されている範囲を選択すれば、意図した箇所に対して適用されていることが分かると思います。

これで、テンプレートファイルの改造は必要なものの、入力ファイルや出力の見た目に影響なく変換できました。

あとはこれに対応する逆変換用のコードを書くだけです。次回に続きます。