Markdown中の下付き文字をPandocでWord/LaTeX出力する際にLuaフィルターで処理してみる

:::note info
これは「[TeX & LaTeX Advent Caleandar 2022](https://adventar.org/calendars/7868)」の14日目の記事です。
（13日目は[doratex さん](https://doratex.hatenablog.jp/entry/20221213/1670943470)でした。15日目は[h20y6m さん](https://h20y6m.github.io/posts/2022-12-17-texadvent2022-2/)です。）
:::

<details><summary>更新履歴</summary>

- 2022-12-21: 微修正，次の日のリンクと参考記事を追加
- 2022-12-15: native-sub.luaを追加
- 2022-12-14: 初版公開（Pandoc nativeのSubscript以外の部分について）
</details>

[TeX & LaTeX Advent Caleandar 2022](https://adventar.org/calendars/7868)の重点テーマは，「やっぱりTeX言語(とか)しましょう！」ということですが，
> 以下のいずれかテーマに該当する何かを書きます。
> - 「やっぱりTeX言語(とか)しましょう！」に大いに関連するTeX／LaTeXネタ。
> - 「やっぱりTeX言語(とか)しましょう！」にチョット関連するTeX／LaTeXネタ。
> - 「やっぱりTeX言語(とか)しましょう！」にサッパリ関連しないTeX／LaTeXネタ。

ということですので今回はTeX言語にはサッパリ関係しない，Markdown原稿からPandocを用いてWord/LaTeX出力した[^markdown-to-docx-latex]ときに両方に対応するためにLuaフィルターを書いたりしたことについて書いてみます。
いくつかの例を示しますが，特に（記事公開時点では）**上付き／下付き文字の扱い**について考えます。

その前に，私が実際にお世話になったLuaフィルターを紹介しましょう。Pandoc公式フィルター集の中の[pagebreak](https://github.com/pandoc/lua-filters/tree/master/pagebreak)は，本来LaTeXでのみ有効である `\pagebreak` や `\newpage` を各出力先に対応させてくれます。AsciiDoc/Asciidoctor，ConTeXt，Docx，EPUB，groff ms，HTML，LaTeXに対応しているそうで，実装からも出力フォーマットに応じて変換を掛けていることがわかります。これを利用することで，Markdown原稿→Word出力でも簡単に改ページが実現できました。


# Markdown原稿を変換してみる

ここでは，以下の文章をMarkdownで記述し，LaTeXに変換することを考えましょう。

> エチレンC<sub>2</sub>H<sub>6</sub>を付加重合するとポリエチレン(C<sub>2</sub>H<sub>4</sub>)<sub>*n*</sub>ができる。組成式(C<sub>6</sub>H<sub>11</sub>ON)<sub>*n*</sub>であらわさられるナイロン6は*ε*-カプロラクタムから合成される。
> 酸素欠損*δ*があるペロブスカイトは化学式*AB*O<sub>3−*δ*</sub>であらわされる。

<details><summary>
さて，ここで科学系で頻出の下付き文字をどうMarkdownで表現するかですが，ここでは素直に &lt;sub&gt; タグ等を用いて表記することとし，以下ではその前提で記述を進めます。
</summary>


もちろん， `<sub>subscript</sub>` のようにHTMLタグを使用する以外にもいくつかの選択肢が考えられますが，テキストエディタのビルトインのpreviewerで対応できること，またGitHub上でそのまま表示できることを優先し，ここでは（記法はやや煩雑ですが）HTMLタグを使用した方法をとることとします。

他の記法としては，次のようなものが考えられます。
- Pandocの [`subscript` / `superscirpt` 拡張](https://pandoc-doc-ja.readthedocs.io/ja/latest/users-guide.html#superscripts-and-subscripts)を有効にする：pandoc実行時に `--from=markdown+subscript+superscript` のように指定することでH~2~O（→H<sub>2</sub>O）のような記法を有効にできます
- 数式要素を使って上付き／下付き文字を表現することもできそうですが[^math-sub-superscript]，ここでは扱いません。

</details>

この前提に立つと，上記の文章は以下のようなMarkdown文書で記述できます。

```markdown:sample.md
エチレンC<sub>2</sub>H<sub>6</sub>を付加重合するとポリエチレン(C<sub>2</sub>H<sub>4</sub>)<sub>*n*</sub>ができる。組成式(C<sub>6</sub>H<sub>11</sub>ON)<sub>*n*</sub>であらわさられるナイロン6は*ε*-カプロラクタムから合成される。
酸素欠損*δ*があるペロブスカイトは化学式*AB*O<sub>3−*δ*</sub>であらわされる。
```

では，PandocでWordおよびLaTeXに変換してみましょう。以下を実行してみます。

```shell
pandoc sample.md -o sample.tex
pandoc sample.md -o sample.docx
```

すると，生成されたLaTeXファイルとそれを変換したPDF，およびWordファイルは以下のようになります。

```latex:sample.tex
エチレンC2H6を付加重合するとポリエチレン(C2H4)\emph{n}ができる。組成式(C6H11ON)\emph{n}であらわさられるナイロン6は\emph{ε}-カプロラクタムから合成される。

酸素欠損\emph{δ}があるペロブスカイトは化学式\emph{AB}O3−\emph{δ}であらわされる。
```

![latex-subscript-disabled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332675/75d71804-82cb-560f-31ed-eab51e98f199.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332675/35ec2eba-fcfa-a781-f40e-d237786a75a8.png)

いずれも下付き文字が反映されていませんね......。どうやらPandocではデフォルトではHTMLタグで書かれた下付き文字は単に無視されてしまうようです[^pandoc-options]。

# Part1: LaTeX出力で下付き文字が反映されるようにしてみる

LaTeXの本文中で下付き文字を表記するには， `\textsubscript{}`が利用できます。そこで，まずはやや場当たり的ですが以下のようなLuaフィルターを用いて`<sub>`タグを`\textsubscript{`に，`</sub>`タグを`}`に変換することにしましょう[^substitution]（[公式例](https://pandoc.org/lua-filters.html#center-images-in-latex-and-html-output)や[@Atsushi776](https://twitter.com/Atsushi776)さんの[Pandoc Lua Filtersのreturnの挙動と複数のフィルタを書くときの用例](https://blog.atusy.net/2020/05/31/lua-filter-returns/)を参考にしました）。

```lua:subscipt.lua
function SubOpen(elem)
    if FORMAT:match 'latex' and elem.text == "<sub>" then
        return pandoc.RawInline('latex', string.gsub(elem.text, "<sub>", "\\textsubscript{"), "")
    end
end
function SubClose(elem)
    if FORMAT:match 'latex' and elem.text == "</sub>" then
        return pandoc.RawInline('latex', string.gsub(elem.text, "</sub>", "}"))
    end
end

return {
    {RawInline = SubOpen},
    {RawInline = SubClose}
}
```

ここでは，出力フォーマットがLaTeXである場合に，Pandoc ASTのInline要素（?）が特定のタグである場合に代わりにLaTeXの命令文字列に置換して返すような動作をしています。
実際にPandocの変換とLuaLaTeXによるタイプセットを実行してみましょう。`subscipt.lua`は同じディレクトリに置いておき，以下のようにオプションで指定します。

```powershell
pandoc sample.md --lua-filter=subscript.lua -o sample.tex
lualatex.exe sampele-index.tex
```

<details>
<summary>（ここでは便宜上以下のLaTeXファイルから<code>\input</code>で<code>sample.tex</code>を読み込んでいます）</summary>

```latex
\documentclass[
  article,
  paper=a4,
  onecolumn,
  lualatex
]{jlreq}

\usepackage{hyperref, amsmath, fontspec, graphicx, framed, fancyvrb, color, ifthen}
\usepackage{unicode-math}

\setmainfont{Times New Roman}
\ltjsetparameter{jacharrange={-2,-3}}%ギリシャ文字を欧文フォント扱いに
\begin{document}
\input{sample.tex}%ここで生成されたLaTeXファイルを読み込む
\end{document}
```

</details>

```latex:sample.tex
エチレンC\textsubscript{2}H\textsubscript{6}を付加重合するとポリエチレン(C\textsubscript{2}H\textsubscript{4})\textsubscript{\emph{n}}ができる。組成式(C\textsubscript{6}H\textsubscript{11}ON)\textsubscript{\emph{n}}であらわさられるナイロン6は\emph{ε}-カプロラクタムから合成される。

酸素欠損\emph{δ}があるペロブスカイトは化学式\emph{AB}O\textsubscript{3−\emph{δ}}であらわされる。
```

実際に以下のようなPDFファイルを生成することができます。

![latex-subscript-adhoc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332675/d14bf414-957c-c9b1-3b3c-e3133b61434b.png)

# Part2: Word出力でも下付き文字に変更したい

前節ではややアドホックに`<sub>`タグを置換することでLaTeX出力で下付き文字を実現しました。しかし，この方法でWord出力にも対応しようとするとOffice Open XMLをいじらなくてはいけなくなります（[pagebreak.lua](https://github.com/pandoc/lua-filters/blob/master/pagebreak/pagebreak.lua)のように）。

ここでは方針を変え，Pandocのネイティブの下付き文字の形式に変換してしまうことで個別の出力形式を考えなくて済むようにしようと思います。実際，以下のようにPandocではASTレベルでSubscriptがちゃんと用意されています。

```powershell
echo "H~2~O" | pandoc --from=markdown+subscript --to=native
# => [ Para [ Str "H" , Subscript [ Str "2" ] , Str "O" ] ]
```

ということで，`<sub>`タグが現れたらその中身を`Subscript`に包んで返してやるLuaフィルターを実装すればよいことになります。以下に（あまりきれいではないですが）実装例を示します。（もうちょっとシュッと書けないかな...）


```lua:native-sub.lua
function Inlines(inlines)
    local start = 1
    while start < #inlines do
        if (inlines[start].text == '<sub>') then
            local len = 1
            while (start + len <= #inlines) do
                if (inlines[start + len].text == '</sub>') then
                    local inner = {
                        table.unpack(inlines, start + 1, start + len)
                    }
                    for j = 1, len do inlines:remove(start) end
                    inlines:insert(start, pandoc.Subscript(inner))
                    break
                end
                len = len + 1
            end
        end
        start = start + 1
    end
    return inlines
end
```

LaTeXは前節と同様の出力となります。さらに，Wordでも画像のように下付き文字が有効になりました。

![docx-subscript-enabled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332675/1a5fd16e-b589-e0b8-9785-8cec6676576c.png)

基本的にはインラインの要素を走査してタグをPandoc ASTのSupscriptに置き換えているだけですが，あまり詳しい解説を書いている時間がないのでまた後日追記しようと思います！（えっ

# まとめ

下付き文字の例だけだとLuaフィルターを使うにはややメリットが弱いかなぁと思いますが（中間ファイルの生成などを許容すれば単純なテキスト置換でも実現できてしまいます），冒頭に紹介したpagebreakや，Markdownに対するプリプロセス・LaTeXファイルに対するポストプロセスのようにばらけてしまう処理であってもLuaフィルターに入れ込んでしまってPandocコマンド一発で処理できるのは便利だと思います。

もちろん，ここではLuaフィルターのごく一部を使っているのみですので，他に実現したい機能がある場合にはLuaフィルターは強力なツールとなるでしょう。最後になりますが，私が参考にしたPandoc・Luaフィルター関連の記事・ドキュメントの一部を紹介します。

- [Pandoc公式ドキュメント日本語版](https://pandoc-doc-ja.readthedocs.io/ja/latest/users-guide.html)および[Luaフィルターについての公式ドキュメント](https://pandoc.org/lua-filters.html)
- [@atusy](Atsushi776)さんの[Atusy's blog](https://blog.atusy.net/categories/lua/)（Luaカテゴリ），[Luaフィルターのコレクション](https://github.com/atusy/lua-filters)
- [sky-y](https://twitter.com/skyy_writing)さんの[Luaフィルター集](https://github.com/sky-y/sky-y-lua-filters)・その他記事（[Zoteroから参考文献リストを自動エクスポートする (PandocとZoteroで参考文献：前編)
](https://zenn.dev/sky_y/articles/pandoc-advent-2020-bib1)など）


[^markdown-to-docx-latex]: みなさんは日本語の長めの文書，特に修士論文などを書くときにどのようなツール・形式を選択するでしょうか。いくつか選択肢はあると思いますが，Markdownを選択することは以下のようなメリットがあると考えられます：テキスト形式なのでバージョン・差分管理と相性がよい／記法が比較的簡潔で読みやすい／GitHubの対応しているサービスにそのままおいておけばプレビュー表示してくれる
さて，修士論文では最終的にはA4ページに組まれたPDFとして成果物を生成することになりますが，ここでMarkdownからなんらかの形でPDFに変換することが必要になりますが，今回はPandocでLaTeX形式に変換した後にLuaLaTeXを用いて組むこととしました。この点はすでにいろいろな事例がWeb上で共有されていることかと思います。Pandocはさまざまな形式に対応していますし，LaTeXの柔軟かつ美しい組版は最終出力に適していると思います。
一方，例えば修士論文なんかの場合は複数回のレビューをしてもらう必要がありますが，この点ではMicrosoftのWord（コメント機能とか）が適していることもあるでしょう。記事の後半ではWordへの対応も考えてみる予定です。

[^pandoc-options]: Pandoc拡張のsubscript/superscriptやraw_htmlなどを有効にしても動作は変わりませんでした。オプション一つで`<sub>`タグをPandoc ASTのSubscriptに変換できるならこの記事ほとんどいらないのでどなたかコメントください...!!

[^math-sub-superscript]: ~~数式要素は単に通常の本文テキスト中の上付き／下付き文字を組むのには適さないと思われます。わかりやすいところだとLaTeXの数式環境やWordの数式ツールに変換されてしまいますのでフォントなどが変わってしまうし，`\text`などを使用しないといけないのもあまり筋がよいとは思えません。~~ 数式と地の文を統一的に扱えるLaTeX出力を念頭におくならいわゆる数式環境かどうかはどちらでもよいかと思いますが，MarkdownプレビューやWordで地の文と異なるフォントが適用されたり，そもそもプレーンなMarkdownビューワーではMathJax等による数式表示機能が備わっていないことを考慮すると（数式ではなく）地の文で表記するのがいいと判断される場合もあるかと思います。（2022-12-23追記）

[^substitution]: 単純にMarkdownからテキスト置換を行うプリプロセスを行ってPandoc subscript拡張形式（H~2~O）で表記された拡張中間ファイルを生成し，`--format=markdown+subscript`オプションをつけてPandoc処理を行うことでももちろん可能です。が，せっかくなのでLuaフィルターを使ってみましょう。