texjporg / tex-jp-build

Minimum source repository to build Japanese TeX processing tools
23 stars 6 forks source link

[ptexenc] 入力ファイルの文字コードの自動判定 #142

Closed t-tk closed 9 months ago

t-tk commented 2 years ago

入力ファイルの文字コードの自動判定について、以前から議論がありましたが、新たに提案します。

(案) ptexenc に新しく書き起こした自前の簡単な文字コードの自動判定のルーチンを入れる。

文字コードの自動判定のルーチンの機能の要望は根強く、 nkfの判定ルーチンをptexencで利用できるようにしたものを土村さんがお書きになっておりW32TeX に採用されていますが、 TeX Live のソースには入っておらず、現在Windowsでしか利用できない状態です。 W32TeXが開発終了になり Windows での今後も先行き不透明です。

以前、ICUの文字コード自動判定機能を入れてはどうかと私から提案しましたが、ICUが巨大で管理しづらくなる懸念のご指摘を受け廃案になっています。 nkf を TeX Live のソースに入れる案も以前からありますが、ICUほどでないものの欲しい機能の割に大きくて私は乗り気になりません。

そこで今回、ptexenc の管理下で扱える程度の小さいルーチンを書いてみました。 処理内容等については続けて投稿します。


(2024/02/21 追記) 判定の方針は少しずつ修正してきたので、備忘録を兼ねて、現行版の判定の方針を以下に記載します。

  1. Shift_JISは、半角カタカナが含まれないと仮定。
  2. EUC-JPは、SS2, SS3が含まれないと仮定。
  3. Shift_JIS, EUC-JPともに JIS X 0208 のみを仮定。機種依存文字が含まれないと仮定。
  4. UTF-8は、Unicodeの全範囲を仮定。
  5. ISO-2022-JP は、いわゆる漢字イン(ESC $ @またはESC $ B)が現れたら即座にISO-2022-JPであると判定する。
  6. ISO-8859 は、0xA0..0xFF を含むが 0x80..0x9F は含まないと仮定。
  7. Shift_JIS, EUC-JP, UTF-8, ISO-8859 の4種の候補はファイルの先頭から上記の仮定に合っているか確認していき、候補が一つに絞られた時点で推測完了。
  8. 候補に Shift_JIS, EUC-JP, UTF-8のうち1種と ISO-8859 が残った場合かつ ISO-8859 と看做した場合の候補の文字が 2000字以上になった場合、マルチバイト文字と看做し得る複数バイトの並びよりも ISO-8859 の文字が充分に大きくなければ、heuristic な判定としてマルチバイト文字の文字コード(Shift_JIS, EUC-JP, UTF-8のうち1種)と判定。
  9. ファイルの先頭10000バイト以内にASCIIの範囲しか現れなかったらASCIIと判定。
  10. ファイルの先頭320バイト以内または文字コード判定終了前に 0x00 が現れたらBINARYと判定。
t-tk commented 2 years ago

で、書いてみたコード https://github.com/texjporg/tex-jp-build/commit/02266b1e4ca3e52cd94d0be3be5944ddf78c75aa について説明します。

新しく文字コードを推測するルーチン ptenc_guess_enc() (140行ほど)を書いた。 入出力の形式は土村さん作の ptexenc_nkf() と大体同じ。 判定の方針は、以下の通り。

  1. Shift_JISは、半角カタカナが含まれないと仮定。
  2. EUC-JPは、SS2, SS3が含まれないと仮定。
  3. Shift_JIS, EUC-JPともに JIS X 0208 のみを仮定。機種依存文字が含まれないと仮定。
  4. UTF-8は、Unicodeの全範囲を仮定。
  5. ISO-2022-JP は、いわゆる漢字イン(ESC $ @またはESC $ B)が現れたら即座にISO-2022-JPであると判定する。
  6. Shift_JIS, EUC-JP, UTF-8 の三種の候補はファイルの先頭から上記の仮定に合っているか確認していき、候補が一つに絞られた時点で推測完了。
  7. ファイルの最後まで先頭10000バイト以内にASCIIの範囲しか現れなかったらASCIIと判定。

機種依存文字やShift_JISの半角カタカナが含まれると判定に失敗し、大体のケースで BINARY と判定されます。 機種依存文字や半角カタカナはptexencの扱うソフトウェアでは使わない原則になっているので問題ないはずです。 実用的な入力ファイルのほとんどの場合は判定に成功するはずです。 逆に半角カタカナも候補に含めると、関連ソフトで扱わないのにも関わらず判定精度を落とす結果になりそうです。

また、ファイルの先頭から合法であるかどうかを頼りに判定していきファイル全体を見ないので、ごく一部のゴミを除いて大多数が正しい文字コードであるような場合で、ゴミが最初の方に現れると誤判定する可能性があります。

で、合法な文字コードの候補が複数存在するままファイルの終端まで達する場合があります。 候補が(Shift_JISとEUC-JP), (Shift_JISとUTF-8), (EUC-JPとUTF-8)となる可能性があります。 その場合は AMBIGUOUSと判定しつつ file_enc を選ぶようにしています。 このため、候補が(Shift_JISとEUC-JP)なのにも関わらずUTF-8になってしまう可能性もありますが、候補が絞り込めない状態であること自体が自動判定の限界であり回避すべきことなので、これ以上頑張らないことにするつもりです。

(Shift_JISとEUC-JPとUTF-8)の3個とも候補のままになるのは半角カタカナが含まれない条件の下ではありえないようです。

文字集合の使用頻度をもとに推測する方法なども知られていますが、そこまで頑張るつもりはありません。 今の判定精度で充分と考えています。

まだ迷っている点は以下。 [1] 判定が確定するまでファイルの中を探し続ける方針のため、ASCIIのファイルで非常に長い場合判定が付かず最後まで読むことになる。 ptexencが扱うファイルの中にそういうものがありそうで、無駄にも思える。 そういう場合に判定を打ち切るよう長さ制限を設けるべきか? → ASCIIのチェックは先頭10000以内限定に変更した。 [2] TeX Live の配布物として自動判定のdefaultをオンにすべきかオフにすべきか?

t-tk commented 2 years ago

[1] 判定が確定するまでファイルの中を探し続ける方針を少し変更しました。 ファイルの先頭から10000バイトの中に8bit文字が見つからない場合 ASCII と推定する。 このとき、ファイルの文字コードには file_enc が使われる。 ファイルの先頭から10000バイト以内に8bit文字が見つかっている場合は、ファイルの最後に達するか、もしくは文字コードが一意に決まるまで判定を継続する。 ほぼ大多数の場合にこれで問題ないはず。 失敗するケースはもともと自動判定に向かないレアケースなのでこれ以上頑張らない。

[2] TeX Live の配布物として自動判定のdefaultをオンにするか否かは、[1]の方針によりデメリットが減ったためオンにしてもよいと今は考えています。

[3] 文字コードに 0x00 が含まれる場合にバイナリーと判定する手法が、git やその他多くのソフトで採用されているらしいのでこちらにも入れてみました。

t-tk commented 2 years ago

mendexでも guess_input_kanji_encoding が効くようにしました。 自動判定のdefaultは、ptex, eptex, platex, pbibtex, mendex で効くようにしてみました。 テストも増やして、期待通りの動作をしているようです。 そろそろTeX Liveにコミットしてもいいかなと考えています。

aminophen commented 2 years ago

色々とありがとうございます。動作確認がまだなのですが,2点コメントです。

t-tk commented 2 years ago

コメントありがとうございます。 --[no-]guess-input-encオプションは未検討でした。 とりあえず、(u)pTeX 系列は W32TeX と同様のものが https://github.com/texjporg/tex-jp-build/commit/9c0627ae3a928bd986593ea949519c9b7f3c1c8d で入ったと思います。

kpathsearchの変数は環境変数からも渡せるので↓のようにして有効化できますが、コマンドラインオプションでも欲しいでしょうか?

$ guess_input_kanji_encoding=1 ./mendex -U -d $srcdir/tests/uni.dict -s $srcdir/tests/sjis.ist 
aminophen commented 2 years ago

ありがとうございます。

kpathsearchの変数は環境変数からも渡せる

それはその通りで,bash だと一行でいけますが Windows の cmd だといったん set しないといけない(export に相当)のが少し面倒でした。あとコマンドラインオプションのパース方法の恩恵(文字列最短マッチ)もあって,-no-guess-input-enc を短縮して -no-g でもいけたのは楽でした。

aminophen commented 2 years ago

platex 同様に platex-dev も自動判定有効がいいです。 > texmf.cnf

t-tk commented 2 years ago

mendex に --[no-]guess-input-enc, (u)pbibtex に [-no]-guess-input-enc を入れてみました。 mendex は文字列最短マッチではないのでオプションの文字列全部を書く必要があります。

platex-dev の自動判定は、そうですね。まだ入れていませんが入れましょう。

t-tk commented 2 years ago

TeX Live svnにコミットしました。 r63556, r63557, r63558 ここのブランチもmasterにマージしました。

ここは閉じます。何かあれば再開お願いします。

t-tk commented 10 months ago

入力ファイルの文字コードのptexenc内部ルーチンでの自動判定について、 日本語用としてはすでに充分な内容になっていると思いますが、以下の検討を開始しました。

  1. 自動判定の候補に ISO-8859 を追加したい
  2. nkfに似た機能の文字コード変換ソフトを導入したい

1について、 TeX関連のレガシーエンコーディングとして欧文では ISO-8859-1 (Latin1) であることが非常に多いが、文字コードの自動推測または自動判定をする手軽なソフトが無い。(nkfはISO-8859を対象にしていない。perl Encode.pmはちゃんと使えば使えるが少々面倒)

2について、 ptexenc での判定は nkf など外部の判定と違う部分があり、手軽に ptexenc での自動判定(nkf --guess相当)を知りたいケースがある。 文字コード変換についても、nkf など別ソフトと違う部分があるため、ptexenc での結果を見たいケースがある。 nkf は海外の環境では用意されていない場合があり、pTeX 関連で使いたいときにnkfの代わりに使える状態にしておきたい。

まず、1についてパッチを書きました。

t-tk commented 10 months ago

nkfに似た機能の文字コード変換ソフトを導入したい

について、ptekf という名称 (仮称) の簡単なソフトを追加しました。 今のところ以下のようになっています。ご意見、ご感想などあればお願いします。

ptekf の名称は、nkf に似せた ptexenc kanji filter のような意味です。 ただし、stdinからstdoutへ流すfilter の動作は想定していません。 特に、入力は stdinを想定しておらず、ファイルのみを想定しています。

汎用の漢字変換ツールの目的ではなく、あくまで ptexenc で行っている漢字変換を、テキストファイルの入出力で確認することを目的とします。 なので、ptexenc の関数をなるべくそのまま使うようにしています。 ptekf のバイナリを TeX Live の配布に含めるつもりですが、開発やデバッグ用のつもりで、あまり凝ったことはしていません。 nkf に似せるのも、少ししかしていません。オプション -U, -uptekf では UTF-8 入出力に割り当てています。 UTF-8からレガシーエンコーディングに変換する際は、変換できない文字は NULL \0 になるようです。

大体、私が想定した通り動いているようです。 今のところ、内部コードは uptex 決め打ちになっています。内部コードを euc, sjis にすることも可能と思いますが、現在見送っています。

guess で ISO-8859 が判定できるなど、自動判定の入力の仮定やアルゴリズムは nkf とかなり違います。

ptekf  ver.20240127 (utf8.uptex) (ptexenc version 1.4.4/dev, TeX Live 2024)
    Copyright (C) 2024 Japanese TeX Development Community
Usage: ptekf -[OPTION] [--] in_file1 [in_file2 ..]
  j/s/e/u  Specify output encoding ISO-2022-JP, Shift_JIS, EUC-JP, UTF8
  J/S/E/U  Specify input encoding ISO-2022-JP, Shift_JIS, EUC-JP, UTF8
  G        Guess the input encoding and output to stdout or files
  --guess    -g  Guess the input encoding (no conversion)
  --version  -v  Print version number
  --help     -h  Print this help
  --buffer   -b  Output internal buffer without code conversion
Default input/output encoding depends on kpathsearch parameters PTEX_KANJI_ENC, guess_input_kanji_encoding

Email bug reports to issue@texjp.org.
t-tk commented 9 months ago

TeX Live svn にコミットしました。 r69656 ここは閉じます。

h20y6m commented 9 months ago

ptekf ですが、改行が CRLF なファイルを入力すると出力の改行が二重(LF+LF)になっている気がします。

t-tk commented 9 months ago

ご報告ありがとうございます。 改行コードは触っていないつもりでしたが、変わってしまうのですね。なぜだろう? まずは調査からですね…

t-tk commented 9 months ago

mfgets() で変換してしまっていたようです。↓で手元では直ったように見えます。

diff --git a/source/texk/ptexenc/ptekf.c b/source/texk/ptexenc/ptekf.c
index 4c52009f6..e3fcdcf29 100644
--- a/source/texk/ptexenc/ptekf.c
+++ b/source/texk/ptexenc/ptekf.c
@@ -87,7 +87,7 @@ static char *mfgets(char *buff, int size, FILE *fp)
   if ((len = input_line2(fp, (unsigned char *)buff, NULL, 0, size, &c)) == 0
       && c != '\r' && c != '\n') return NULL;
   if (c == '\n' || c == '\r') {
-    if (len+1 < size) strcat(buff+len, "\n");
+    if (len+1 < size) { buff[len]=(unsigned char)c; buff[len+1]='\0'; }
     else ungetc(c, fp);
   }
   if (c == EOF) return NULL;
h20y6m commented 9 months ago

ありがとうございます。こちらでもうまく動いているようです。

t-tk commented 9 months ago

最後の行が改行で終わっていない入力ファイルの場合、最後の行が出力されない問題があるようです。 これも合わせて直そうと思います。

t-tk commented 9 months ago

今度は改行周りのテスト ptekf-eol.testも入れたので大丈夫と思います。 「最後の行が改行で終わっていない入力ファイルの場合、最後の行が出力されない問題」は直っていると思います。 ついでに、ptekf--guess オプションで改行コードが出るようにしました。 文字コード判定のついでにチェックしているだけです。早々と文字コードが確定するとそれ以上は見ないため、ファイルの後ろの方で改行コードが混在しているケースでは混在していることを見逃します。

TeX Live svn にコミットしました。 r69798 ここは閉じます。