MS Office ドキュメントや PDF ファイルを grep したり diff したりする


以前書いたこの記事は珍しく好評だった。

これは、greple コマンド用の -Mmsdoc モジュールを元にして、optex コマンドを使って office ファイルに対して様々なコマンドを実行するという内容だった。

最近 PDF ファイルを git で管理する機会があり、やはりこれも git diff したいと考えた結果、前回とは異なるアプローチで実現してみた。

pdftotext

PDF ファイルをテキストベースで diff すること自体は、さして難しくはない。pdftotext というコマンドを使えば、一時ファイルを作るまでもなく、たとえば bash だったらこんな風に比較することができる。

$ diff <(pdftotext A.pdf -) <(pdftotext B.pdf -)

プロセス置換を使わずとも、こんな風にトリッキーに実行することもできる。打ち込むのは面倒だが、alias や関数にしてしまえばどうっていうことはない。

$ pdftotext A.pdf - | (pdftotext B.pdf - | diff /dev/fd/3 -) 3<&0

ただ、git のために設定しようとするとどうもうまくいかず、間にスクリプトを介さなければならなかった。実現したいのは、このような仕様の違いを吸収して、diff に限らず、コマンドの引数として普通にファイルを指定する統一的なインタフェースを提供することだ。

$ diff A.pdf B.pdf

optex -Mtextconv

今回もやはり optex のモジュールで対応することにする。

結論としては、様々な種類のファイルをテキスト形式に変換する textconv というモジュールを作った。以前 greple のモジュールとして実現した変換機能も組み込み、単独で動作する。現在のところ、docx, pptx, xlsx に加えて pdf の拡張子に対応している。ついでに jpg データが指定されると exif コマンドの出力に置き換えるようになっているが、役に立つかどうかは不明だ。

追記: その後、-Mtextconv と打ち込むのが面倒になって -Mtc というエイリアスモジュールを同梱することにした。2020年3月リリースの Version 0.08 以降で利用可能だ。

使い方は簡単で、optex -Mtextconv に続けて実行したいコマンドを指定するだけだ。grep だろうが diff だろうが、なんだったら vi だって実行できる。試しにやってみたところ vi で開いて :w もできたが、書いたデータを読む方法はわからない。

$ optex -Mtextconv vi shishin4.pdf

そういえば、今度は PowerPoint のページもちゃんと並び替えてます。

PDF を比較する

本来の使い方としては、-Mtextconv はコマンドオプションとして指定するのだが、このモジュールの場合には optex のオプションに指定しても、ほぼ同じ効果を持つ。

$ optex -Mtextconv diff -u shishin4rc.pdf shishin4.pdf

見にくいので diff の代わりに sdif を使うとこんな風に見える。目次のドットの数が変わっているなど、目視では確認しずらいこともわかる。

$ optex -Mtextconv sdif -u shishin4rc.pdf shishin4.pdf

ちなみに、-u を付けずに実行した場合、古い sdif だとエラーになることがある。/dev/fd を使う場合特有の問題だ。最新版では修正されているので、使うのならまずそれをインストールした方がいい。

Word と PDF も diff できる

-Mtextconv モジュールは、複数種類のファイルを扱うことができるので、それらを混在させることも可能だ。だから、Word と PDF を比較することもできる。ただ、構造が変わってしまうので、思ったような結果が得られるとは限らない。可能なら、文章構造を保っている Word ファイルで比較する方が、有用な結果が得られる。

$ optex -Mtextconv sdif -u hakodate-rc.docx hakodate-gaiyo.pdf 

diff.rc

前の記事にも書いたが、~/.optex.d 以下に、コマンド毎の設定ファイルを置くことができる。diff コマンドの設定ファイルは ~/.optex.d/diff.rc なので、ここに以下のような設定を書いておくと、自動的に -Mtextconv オプション付きで実行される。さらに optex --ln diff~/.optex.d/bin にシンボリックリンクを作ってパスに通しておけば、単に diff コマンドを実行するだけでテキストベースでの比較ができるようになる。これで当初の目的は達成した。

~/.optex.d/diff.rc
option default --textconv
option --textconv -Mtextconv $<move>

git の設定

変換ツールを統一したので git の設定は単純だ。~/.config/git/attributes ファイルに以下のような設定をして、ファイル拡張子に対する属性を定義する。全部同じにしてしまっても別に構わない。

~/.config/git/attributes
*.docx   diff=msdoc
*.pptx   diff=msdoc
*.xlsx   diff=msdoc
*.pdf    diff=pdf

そして textconv にテキストに変換するコマンドを指定する。といっても、optex 経由で cat を実行するだけだ。ちなみに、これを pdftotext コマンドでやろうとすると、標準出力に出すオプションがなくてでちょっと困る。

$ git config --global diff.msdoc.textconv "optex -Mtextconv cat"
$ git config --global diff.pdf.textconv "optex -Mtextconv cat"
~/.gitconfig
[diff "msdoc"]
    textconv = optex -Mtextconv cat
[diff "pdf"]
    textconv = optex -Mtextconv cat

この状態で git diff を実行すると、自分の場合、出力は sdif を通して表示される。

git の設定には diff コマンドを指定する方法もあるが、それは以前の記事を参照して欲しい。diff を optex 経由で実行すればいいだけだ。

実は URL も diff できる

実は URL が指定されると w3m コマンドで変換するようになっているので、URL を diff することできる。たとえば、以下のようにすることで Macbook と Macbook Air の仕様を比較できる。

$ optex -Mtextconv sdif --non -u 'https://support.apple.com/kb/'{SP757,SP783}'?viewlocale=ja_JP&locale=ja_JP' 

インストール

optex::textconv

cpanminus をインストールしてからでもいいが、とりあえずこれでインストールはできる。たまにしか使わないなら、むしろこの方がいいかも。

$ curl -sL http://cpanmin.us | perl - App::optex::textconv

sdif

sdif を使いたい場合は App::sdif をインストールする。

$ curl -sL http://cpanmin.us | perl - App::sdif

細かい設定等は前の記事に書いてあるので見てください。

拡張性

モジュール構成はこんな風になっている。-Mtextconv モジュールには変換機能は何も入っていなくて、default.pmmsdoc.pm という2つのモジュールに実装されている。

App/optex/
    ├── textconv.pm
    └── textconv/
        ├── Converter.pm
        ├── default.pm
        └── msdoc.pm

default.pm の中身は @CONVERTER という配列にファイル名のパターンと変換するコマンドを指定してあるだけだ。ファイル名は sprintf の引数で与えられるので %s の部分に展開される。ユーザ設定で簡単に増やせるようにはなっていないが、この部分を編集すればいくらでも拡張は可能だ。

package App::optex::textconv::default;
use App::optex::textconv::Converter 'import';
our @CONVERTER = (
#   [ qr/\.pdf$/i    => "pdftotext -nopgbrk \"%s\" -" ],
    [ qr/\.jpe?g$/i  => "exif \"%s\"" ],
    [ qr[^https?://] => "w3m -dump \"%s\"" ],
    );
1;

msdoc.pm の方はもう少し凝っていて、中でテキストへの変換を実装している。この場合、to_text というサブルーチンが定義されていると、それを呼ぶインタフェースになっている。独自の変換モジュールを作りたい場合は、同じように to_text を定義すればいい。

PDF

特に PowerPoint が生成したものの場合そうなのだが、PDF ファイルのページの切れ目がわかった方が見やすい。そこで PDF は pdf.pm という特別のモジュールで処理するようにした。デフォルトではページ境界にダッシュ78個の行を出力する。オプションでページ番号を表示したり、単に改ページ記号を出力することもできる。

外字と未定義文字

PDF ファイルには外字が含まれていることがある。また、理由はわからないが Word ファイルの中に Unicode の未定義文字が含まれていることがある。

そこで、Unicode のプライベート領域と未使用領域の文字は「〓」に置き換えるようになっている。

Version 0.05

SEE ALSO