latexmkに追加の処理を挿入して句読点を置換したい

# はじめに

TeX文章を書いている際、ソースとなる`.tex`ファイルに手を加えずに句読点をカンマとピリオドに置き換えたかった。
[【LaTeX】句読点をdviの時点で置き換える方法](https://qiita.com/migawariw/items/30bb1745fcf2e7d4d8b1)を参考にしたが、自分の環境でいくつか問題が発生した。
解決策を講じる過程で、latexmkでのコマンド実行についていくつか情報を得たので、備忘録もかねて投稿する。
latexmkへの処理追加の方法と、句読点置換手法に分けて説明する。

# 結論

先に結論を述べる。

## latexmkへの処理追加

latexmkでのタイプセット時に処理を追加する方法は少なくとも以下の3通り。

- ワンライナーで外部コマンドを記述する (上記記事で言及の手法)
- 外部コマンドの代わりにサブルーチンを使用する[^subroutine_instead_of_external_command]
- カスタムの依存関係を追加する[^custom_dependency]

## 句読点置換処理

https://gist.github.com/Gs-itisitcat/91c33518c37f56df0306d568893ae9a8

# 既存方式で発生した問題

上記記事の手法を試した際、手元の環境で正常に動作しないという問題が発生した。
環境はWIndows10, texlive2022, VSCodeのLaTeX-Workshopからの実行およびGit Bashからの実行, sedはGit Bash付属の物を使用。
具体的には、実行するとdvispcを引数無しで実行した場合と同じヘルプメッセージが出力され、タイプセットが正常に終了しなかった。

いろいろ試した結果、2重引用符の外側から引用符で囲うと動作した。

```diff_perl:.latexmkrc
- $dvipdf = "dvispc %O -a %B.dvi %B.txt && sed -i -e s/0x3001/0xff0c/ -e s/0x3002/0xff0e/ %B.txt && dvispc %O -x %O %B.txt %B.dvi && dvipdfmx %O -o %D %S";
+ $dvipdf = '"dvispc %O -a %B.dvi %B.txt && sed -i -e s/0x3001/0xff0c/ -e s/0x3002/0xff0e/ %B.txt && dvispc %O -x %O %B.txt %B.dvi && dvipdfmx %O -o %D %S"';
```

文字列を分けて結合する等も試したが、いずれもうまくいかなかった。
このような動作となった原因は執筆時点で不明。

一方、この状態ではLinuxでは動作しない(dockerコンテナで試した)。
また、sedの入っていないWindowsでも動作せず、Macでもsedの種類が違いその対応が必要なよう[^mac_sed]だった。
条件分岐するという手もあったが、環境に依らず実行可能な記法があればそれを用いたかった。

# latexmkの処理をカスタムする

latexmkrc内にperl処理として記述すれば、latexmkが動く環境であればOSに依らず動くと考えた結果、検索とChatGPTへの質問を介し、latexmkのコマンドマニュアルに行きついた。
全文を読んだわけではないが、latexmkで実行される処理をカスタムする方法として、少なくとも3通りの手法があった。

## 手法1: ワンライナーで処理を記述

上記記事の手法。
置き換えたい処理の部分を、行いたい一連のコマンドを1行で記述する。

```perl
$dvipdf = "dvipdfmx %O -o %D %S"
```

## 手法2: subroutineの使用

私はperlに明るくないため正確なところは分からないが、subroutineはおそらくperlの関数相当の物。
コマンドラインで実行するコマンドの代わりにサブルーチンを実行することができる。

```perl
sub dvipdf_subroutine {
    my @args = @_;
    return system('dvipdfmx', @args);
}
$dvipdf = "internal dvipdf_subroutine %O -o %D %S"
```

外部コマンドを記述していた部分に、`internal`に続けてsubroutine名を書く。
systemで外部コマンドを実行できる。
その他のperlの言語使用はここでは言及しない。
この手法を使えば、perlが書ければ、特定の処理のタイミングで任意の処理を行えるはず。

## 手法3: カスタムの依存関係を追加

カスタムの依存関係は、ある拡張子のファイルが存在した場合に、別の拡張子のファイルを生成する手法。
subroutineとして記述する。
詳しく試す前に上記subroutineの使用方式で問題が解決し、またこの手法ではうまくいかなかったため細かくは調べていない。
[latexmk General Commands Manual][latexmk_command_manual]の50ページから説明がある。

ドキュメント53ページから例を引用する。
```perl
add_cus_dep('ndx', 'nnd', 0, 'ndx2nnd');
sub ndx2nnd {
return system("makeindex -o \"$_[0].nnd\" \"$_[0].ndx\"" );
}
push @generated_exts, 'ndx', 'nnd';
```

# 句読点を置換する。

以上3通りのいずれか使って、dviから句読点を置換する処理を記述したい。
結果として、手法2を用いて実装した。

結論にも埋め込んだが、念のためここにも直接記載する。
コマンドラインへの経過出力の部分は省いた。

```perl:.latexmkrc
# subroutine to replace "、"(0x3001) and "。"(0x3002) to "，"(0xff0c) and "．"(0xff0e) while type-setting
sub replacing_dvipdf {
    my ($basename, @args) = @_;

    system('dvispc', '-a', "$basename.dvi", "$basename.dvi.txt");

    open(FROM, "<", "$basename.dvi.txt") or die "Cannot open $basename.dvi.txt: $!";
    open(TO, ">", "$basename.txt") or die "Cannot open $basename.txt: $!";
    while (<FROM>) {
        s/0x3001/0xff0c/g;
        s/0x3002/0xff0e/g;
        print TO;
    }
    close(FROM);
    close(TO);
    # what the above code does is equivalent to the following command:
    # system('sed', '-i', '-e', 's/0x3001/0xff0c/g', '-e', 's/0x3002/0xff0e/g', "$basename.txt");

    system('dvispc', '-x', "$basename.txt", "$basename.dvi");
    return system('dvipdfmx', @args);
}

# DVI / PDF
# replacing punctuation marks type-set
$dvipdf = "internal replacing_dvipdf %B %O -o %D %S";
```

処理としては

1. `deispc`で`.dvi`を`.txt`に変換
2. 変換したファイルと置換先のファイルを開く
3. 1行づつ読み込み、perlの処理で置換
4. `deispc`で`.txt`を元の`.dvi`に上書き
5. dvipdfmkを実行

とした。
perlは調べ調べ書いたため、目的に対し最適な処理ではない可能性はある。
ファイルが巨大な場合を考え、一括で読み込んで置換するのではなく1行ずつの読み込みとした。

.latexmkrc上への記述のため、Windowsであってもsedやperlの個別導入無しに実行できるはずだ。
実際この処理を.latexmkrcに記述し、実行したところ、手元のWindowsおよびdocker上のLinux環境で想定通りに動作した。

# おわりに

latexmkに処理を追加する手法を調べても、これらの手法を記載した日本語記事をついぞ見つけることができず、多くの時間を要したので、同様の悩みを持った人の一助になれれば幸いである。

なお、本記事記載のコードで私に権利のあるものはCC0ライセンスで自由に使用してもらって構わない。

# 参考

[【LaTeX】句読点をdviの時点で置き換える方法](https://qiita.com/migawariw/items/30bb1745fcf2e7d4d8b1)
[upLaTeX文書の句読点を置き換える](https://doratex.hatenablog.jp/entry/20220907/1662553045)
[latexmk による LaTeX ワークフロー内で句読点を自動で置換する](https://qiita.com/yudai-nkt/items/900a5f1c87689e033ff3)
[Macの（BSD版）sed での上書き保存](https://qiita.com/catfist/items/1156ae0c7875f61417ee)
[General Commands Manual](https://ftp.jaist.ac.jp/pub/CTAN/support/latexmk/latexmk.pdf)

[latexmk_command_manual]: https://ftp.jaist.ac.jp/pub/CTAN/support/latexmk/latexmk.pdf 
[^subroutine_instead_of_external_command]: [latexmk General Commands Manual][latexmk_command_manual] p.27 FORMAT OF COMMAND SPECIFICATIONS の Running a subroutine instead of an external command
[^custom_dependency]: [latexmk General Commands Manual][latexmk_command_manual] p.50 CUSTOM DEPENDENCIES
[^mac_sed]: [Macの（BSD版）sed での上書き保存](https://qiita.com/catfist/items/1156ae0c7875f61417ee)