SSブログ

アーカイブファイルの文字化けを適当いいかげんに直すプログラム [手記さまざま2]

アーカイブファイル内のパスは時として文字化けする。Lhaplusみたいに有名で使い勝手が良いソフトでも時には文字化けする。Susie用のプラグインだと、もっと文字化けが多い。でもちょっと待て。Susie用のプラグインはアーカイブファイル内のディレクトリ構造データをそのまま構造体メンバに格納するだけではないだろうか。もしそうならば、文字化けの責任はそれを文字コード判定もせずに能天気にUnicodeに変換してWindowsアプリを作っている私の側にある。

そこで私は考えた。いったいアーカイブファイル内のパス情報つまり文字列は、どんな文字コードを使っているんだろう。ネット検索した。すると、OS X(つまりMac)やUNIX/LinuxではUTF-8、WindowsではShift JISという情報が出てきた。そうか、日本語環境で作られたzipに限ればおもに2つか、と私は能天気に間違って信じ込んだ。

それから私はUTF-8の文字コード表を調べた。整然としていて気持ちよかった。これなら、ある文字列のバイト列がUTF-8の約束事に従って並んでいるか否かを調べるルーチンを作るのは、私にもできる。日本語環境で作られたzipの文字コードページがおもにUTF-8かShift JISならば、UTF-8の約束事に従っていないものはShift JISだ。完璧でないが、いいかげんなりに現状よりもうまく行くはずだ。そう私は間違って思った。

自作のUTF-8判定ルーチンとMultiByteToWideChar()でやってみた。結果は、文字化けがひどくなった。どうしてだ。この時になってやっと私は、手持ちのアーカイブファイルでどんな文字コードが使われているかを調べ始めた。方法は、文字化けしない解凍ソフトで解凍してどんな文字かを確認し、自作ルーチンではもともとShift JISという想定でUnicodeに変換していたのでその文字化け情報をShift JIS文字コード表で16進数に直し、その16進数データが正しい文字列になる文字コード表を探す。そうしたら、EUCだった。UTF-8とShift JIS以外の可能性が当たり前にあった。

そうなると、ちゃんと文字コードを判定するためのルーチンが要る。マイクロソフトが提供しているちゃんとしたDetectInputCodepage()なら、ちゃんと判定してくれるだろう。と私は能天気に間違って信じ込んだ。私はDetectInputCodepage()とConvertStringToUnicodeEx()で文字コード判定+Unicodeへの変換ルーチンを書いた。ConvertStringToUnicodeEx()が書き出す文字列の末端に'\0'がないことに気づかずにアクセス違反をバンバン出しながらも、なんとか完成した。結果は、全部Shift JISという想定でUnicodeに変換した時と同じ文字に化けた。どうなっているんだ。

DetectInputCodepage()が返す(1番目の)文字コードページを調べたら、呼び出すたびに20127を返していた。他の値はない。20127がどんなコードページか調べたら、そもそもShift JISですらなく、US-ASCIIだった。無茶苦茶だ。MLDETECTCP_NONEじゃ駄目だっての?これだとDefault setting will be used.だからきっとMLDETECTCP_8BITだろうけど。でもShift JISってdouble-byte dataでないASCII文字も含むけどMLDETECTCP_DBCSと言えるの?UTF-8は3バイトもありうるからMLDETECTCP_DBCSじゃないよね。どうすんの?そもそもどんな文字コードかわからないからDetectInputCodepageを使うのに、the type of incoming source textを特定しろと言われても。DetectInputCodepage()は使わないほうが良いってことだ。改めてネット検索したら、DetectInputCodepage()は誤判定が多いと書いている人がいた。文字コード判定は自前でやったほうがましか。

私のソフトが外国で使われることはまずない。日本人が扱うアーカイブファイルならば日本語が文字化けしなければまあ良かろう。このへんは適当・いいかげんに考えないと、対象とする文字コードページが絞り込めなくて調査やコーディングに大変な思いをする。人間、もしも自分の能力に余ると感じたら、あえて適当・いいかげんに対処すべき時もある。同じく本当はShift JISとか表現しちゃいかんのだろう、亜種があるそうだし、でもWindowsでの文字コードページはひとつだったりしそうだ。ここも適当いいかげんが良い。

適当・いいかげんに考えて、アーカイブファイル内に格納する文字コードとして特にありそうなのはShift JIS(Windows上で作ったデータ)、EUC-JP(UNIX系OS上で作ったデータ)、UTF-8(OS X上つまりMacで作ったデータと、おそらくUNIX/Linux系でも)。UTF-16はどうしよう。UTF-16とUnicodeは同一ではないはずだ。Unicodeは文字集合でありUTF-16はその符号化形式だとか書いてあるサイトがあった。ところがUTF-16とUnicodeを同一のように書いているサイトもある。UTF-16は、ええい、見なかったふりをしよう。UTF-32は、見なかったふりをしよう。いわゆるJIS文字コードは、いまさら使うとも思えない。




さあそれでは、Shift JIS、EUC-JP、UTF-8の3つをどうやって識別しようか。

UTF-8は、20hから7ehまででASCII文字をあらわす。(これはEUC-JP、Shift JISと同じ)
c0hからdfhまでが現れたら、次の1バイトは08hからbfhまででなければならない。
e0hからefhまでが現れたら、次の2バイトは08hからbfhまででなければならない。
f0hからf7hまでが現れたら、次の3バイトは08hからbfhまででなければならない。
上記にあてはまらなければUTF-8ではない。
すでに書いた通り適当いいかげんなのだから、使われないはずの5バイト表現6バイト表現は省く。

EUC-JPは、20hから7ehまででASCII文字をあらわす。(これはUTF-8、Shift JISと同じ)
a1hからfehまでが現れたら、次の1バイトはa1hからfehまででなければならない。
8eが現れたら、次の1バイトはa1からdfまででなければならない。(半角カナ)
上記にあてはまらなければEUC-JPではない。

Shift JISは、20hから7ehまででASCII文字をあらわす。(これはUTF-8、EUC-JPと同じ)
a1hからdfhまでで半角カナをあらわす。
81hから9fhまでが現れたら、次の1バイトは40hからfchまででなければならない。
e0hからefhまでが現れたら、次の1バイトは40hからfchまででなければならない。
上記にあてはまらなければShift JISではない。




こまっちゃうのは、複数の文字コードにあてはまるバイト列があることだ。たとえば(16進数で)a4 a2 a4 a4というバイト列はEUC-JPでは "あい" となり、Shift JISでは "、「、、" となる。(ネット上にUPする記事なので半角カナのかわりに全角文字を使った)

さてどうしよう。今どき半角カナはあまり使わないから、半角カナは使っていないと仮定したらどうか。それでもまだまだ複数の文字コードにあてはまるバイト列がある。じゃあ、文字コード一致判定にレベルを設けたらどうか。漢字は昔、都合の良いことにJIS第一水準と第二水準に分けられた。



EUC-JPは、20hから7ehまででASCII文字をあらわす。(これは各文字コード共通)
8eが現れたら、次の1バイトはa1からdfまで。(可能性低い;半角カナ)
a1hからa4hまでが現れたら、次の1バイトはa1hからfehまで。(可能性高い)
a5hが現れたら、次の1バイトは
          a1hからf6h(ヶ)まで。(可能性高い)
          f7hからfehまで。(可能性低い)
a6hからachまでが現れたら、次の1バイトはa1hからfehまで。(可能性低い;ギリシャ文字、ロシア文字、罫線等)
adhが現れたら、次の1バイトはa1hからfehまで。(可能性高い;丸の中に数字、ローマ数字等)
aehからafhまでが現れたら、次のバイトはa1hからfehまで。(可能性低い)
b0hからcehまでが現れたら、次のバイトはa1hからfehまで。(可能性高い;JIS第一水準漢字)
cfhが現れたら、次の1バイトは
          a1hからd3h(腕)まで。(可能性高い;JIS第一水準漢字)
          (d4h以降は使用せずと考えコーディングしない)
d0hからf3hが現れたら、次の1バイトはa1hからfehまで。(可能性低い;JIS第二水準漢字)
f4hが現れたら、次の1バイトは
          a1hからa6hまで。(可能性低い;JIS第二水準漢字)
          (a7h以降は使用せずと考えコーディングしない)
(f5hからf8hが現れることはないと考えコーディングしない)
f9hからfchが現れたら、次の1バイトはa1hからfehまで。(可能性低い;JIS第二水準漢字)
(fdhからfehが現れることはないと考えコーディングしない)
上記にあてはまらなければEUC-JPではない。
バイト列中にあてはまらない部分がひとつでもあれば戻り値0、全部あてはまるが可能性低いが含まれると戻り値1、全部が可能性高いならば戻り値2。


Shift JISは、20hから7ehまででASCII文字をあらわす。(これは各文字コード共通なのでスルー)
a1hからdfhまでで半角カナをあらわす。(可能性低い)
81hから82hまでが現れたら、次の1バイトは40hからfchまで。(可能性高い)
83hが現れたら、次の1バイトは
          40hから96h(ヶ)まで。(可能性高い)
          97hからfchまで。(可能性低い;ギリシャ文字等)
84hから86hまでが現れたら、次の1バイトは40hからfchまで。(可能性低い;ロシア文字、罫線等)
87hが現れたら、次の1バイトは
          40hから9chまで。(可能性高い;丸の中に数字、ローマ数字等)
          9dhからfchまで。(可能性低い)
88hが現れたら、次の1バイトは
          40hから9ehまで。(可能性低い)
          9fhからfchまで。(可能性高い;JIS第一水準漢字)
89hから97hまでが現れたら、次の1バイトは40hからfchまで。(可能性高い;JIS第一水準漢字)
98hが現れたら、次の1バイトは
          40hから72h(腕)まで。(可能性高い;JIS第一水準漢字)
          73hからfchまで。(可能性低い;JIS第二水準漢字)
99hから9fhまでが現れたら、次の1バイトは40hからfchまで。(可能性低い;JIS第二水準漢字)
e0hからefhまでが現れたら、次の1バイトは40hからfchまで。(可能性低い;JIS第二水準漢字)
上記にあてはまらなければShift JISではない。
バイト列中にあてはまらない部分がひとつでもあれば戻り値0、全部あてはまるが可能性低いが含まれると戻り値1、全部が可能性高いならば戻り値2。


UTF-8は、使用している文字集合がJIS X 0208ではなくUnicodeなので、第一水準漢字、第二水準漢字という分け方で判定にレベルを設けるのは大変だ。
20hから7ehまででASCII文字をあらわす。(これは各文字コード共通)
c0hからdfhまでが現れたら、次の1バイトは08hからbfhまででなければならない。
e0hからefhまでが現れたら、次の2バイトは08hからbfhまででなければならない。
f0hからf7hまでが現れたら、次の3バイトは08hからbfhまででなければならない。
上記にあてはまらなければUTF-8ではない。
バイト列中にあてはまらない部分がひとつでもあれば戻り値0、全部あてはまれば戻り値1。


上記の判定にレベルを設ける案は、まだ案であって試していない。実は私はまだ風邪が治っていない。それなのに無理をした馬鹿な私だ。今日はこれ以上できない。風邪が治ってから続きをやるしかない。だから上記の案が有効かどうかは現時点ではまだわからないと付記しておく。