sakura icon indicating copy to clipboard operation
sakura copied to clipboard

文字コード自動判定(UTF-8をSJISと誤認)

Open tutimura opened this issue 7 years ago • 13 comments

はじめまして。 身近で文字コード判定で、少し変な挙動があったので、報告します。「こんにちわ」を表示するCプログラムで発現しました。(「こんにちは」では発現しません。)

UTF-8 で

ちわ\

というテキストファイルが、自動判定に任せると、SJISの

縺。繧十

になるという話です。

バイト列で見ると、

e3 81 a1 e3 82 8f 5c

となっていて、UTF-8 と SJIS のどちらでも正しく(!)解釈できます。

ソースコードをざっと見てみました。間違ってたらすいません。 charset/CESI.cpp の CESI::SortMBCInfo() で、nPoints の最大のエンコードを採用するようです。同点の場合には UTF-8 が優先されるはずですが、この場合には運の悪いことが起こっていました。

nPoints の計算方法を比べてみました。 UTF-8 は void CESI::GetEncodingInfo_utf8( const char* pS, const int nLen ) の num_of_utf8_encoded_bytes、SJIS は void CESI::GetEncodingInfo_sjis( const char* pS, const int nLen ) の num_of_sjis_encoded_bytes で、正しくエンコードできたバイト数を数えているようです。

ただし、7bit文字を除外して数えているので、 UTF-8 なら、末尾の \ を除いた6byte, SJIS なら、途中の半角カナを含めて7byteになり、 SJIS が採用されるという流れのようです。

文字コードの自動判定に完璧を求めるのは、そもそも無理な話だとわかっているのですが、ディフォルトのエンコードを UTF-8 にしていても SJIS に判定されるので、何とかならないかと思ったりもします。(ヒストリはクリアされているという前提です。)

判定の最初の段階で、ディフォルトエンコードでエラーなく読み取れれば、他のエンコードを調べるまでもなく、ディフォルトを採用してくれればいいような気もしていますが、ロジックが複雑になるのは好ましくないでしょうから、悩ましいですね。

tutimura avatar Sep 26 '18 04:09 tutimura

@tutimura さん、投稿ありがとうございます。 当方でも、2.3.2.0で再現しました。

おっしゃる通り文字コード判定は、完璧は難しい分野です。 ある程度優先順位付けをしてどちらに倒したほうがより多くの人が救えるか、 また優先順位を変更するような機構が必要なのかもしれません。 ひとまず投稿感謝。

KENCHjp avatar Sep 26 '18 05:09 KENCHjp

@tutimura さん 報告ありがとうございます。

これで改修提案の大義名分が・・・(キラキラ

文字コードの自動判定に完璧を求めるのは、そもそも無理な話だとわかっているのですが、ディフォルトのエンコードを UTF-8 にしていても SJIS に判定されるので、何とかならないかと思ったりもします。

ユーザ指定が優先されるように優先度を調整できたらいいですよね・・・。 実は最近、Grepでバイナリファイルをスキップしたいみたいなissue #424 があがってまして、文字コード判定の効率化について少し考えなおしてみていたところでした。

サクラエディタの文字コード判定は、かなり精度が高い部類だと思ってます。

個人的には解析クラスのクラス名CESIが気に食わんのですが、かなり優秀なので変えるにしてもあまり大きく変えないような感じで進むんじゃないかと思ってます。 ※CESIは某国の工業規格を決める組織の略称と同じ(日本でいうJIS、米国でいうANSIにあたる)

判定の最初の段階で、ディフォルトエンコードでエラーなく読み取れれば、他のエンコードを調べるまでもなく、ディフォルトを採用してくれればいいような気もしていますが、ロジックが複雑になるのは好ましくないでしょうから、悩ましいですね。

これと同じ対応がとれないか考えていました。 no errorなら「既定のエンコード」でいいじゃん、っていうショートカット思考です。 いまのスコア方式はかなり複雑なので、軽減する方向なら条件をいれてもいいのかな、と思っています。

berryzplus avatar Sep 26 '18 10:09 berryzplus

no errorなら「既定のエンコード」でいいじゃん、っていうショートカット思考です。

tutimura さんの遭遇した状況からは、これに同意します。しかし天邪鬼なので、「既定のエンコード」でいいじゃん、では困る状況について考えを巡らせてみたくもなります。

たとえば既定のエンコードが Shift_JIS だった場合に、UTF-8 のテキストを開くとどうなるでしょうか。文字コード判定のコードは完全に未知なのですが、Shift_JIS ではありえないバイト列というものがあるのでしょうか。

追記: リーディングバイトの連続?

つまり、ほとんどのありふれた UTF-8 のテキストが Shift_JIS と判定されることがありうるのなら、その実用性はどうなの?ってことです。(実際にそういうことがあると言っているわけではありません。確かめる前の仮定の話です)

ds14050 avatar Sep 26 '18 10:09 ds14050

現状ではa to wした値をw to aしてみて、元どおりにならなかったらNGとしてます。

C++の偉い人の見解では、文字コードの判定や変換はc++規格に含められるほど簡単じゃない(…のでicu とかライブラリ使ってください)になってるらしいです。

Icuは検討してますがファイルサイズがバカでかいのがネックです。

berryzplus avatar Sep 26 '18 11:09 berryzplus

人間からは典型的な文字化けに見える、見たことのない画数の多い漢字の羅列も、たぶんエラーのない Shift_JIS テキストなんですよね。

ただ UTF-8 のテキストを Shift_JIS として開いた場合は、ステータスバーに「?86」「?e3」「?a0」などと表示される非文字がそこそこの割合で含まれていました。これがたぶんエラー。

既定の文字コードが Unicode だった場合はどうでしょうか。何がエラーになって、自動判定にお鉢が回ることになるのでしょう。

ds14050 avatar Sep 26 '18 11:09 ds14050

人間からは典型的な文字化けに見える、見たことのない画数の多い漢字の羅列

ある種の人々からは「見たことのある、いかにも文字化けっぽい文字列」である気もします。 積極的にやるつもりはありませんが、この辺はAIを駆使して学習させたらいいのかも知れません。

既定の文字コードが Unicode だった場合はどうでしょうか。

典型的にはサロゲート範囲の文字が単独で現れるケースがNGになります。 Unicodeは20bitの数値で文字を表現するのでWCHARの16bitでは足りません。 16bitを2文字で1文字を表現する仕組みがあるんです。-> サロゲートペア

コードポイントが未定義とか、非文字とか、そういうのを判定していったほうが精度は上がると思うんですけど、現状ではそこまでしとらんですね・・・。

berryzplus avatar Sep 26 '18 12:09 berryzplus

判定はヒューリスティックなので、あちらをたてればこちらがたたずになるのが見えています。仮に対応するならば、いみじくも tutimura さんが最初に指摘していたように

ただし、7bit文字を除外して数えているので、 UTF-8 なら、末尾の \ を除いた6byte, SJIS なら、途中の半角カナを含めて7byteになり、 SJIS が採用されるという流れのようです。

このあたりの不公平を是正するのが有力かなと思います。なぜ7ビット文字を除外するのか、知らずに手は入れられませんが。

それとは別に、既定のエンコーディングに設定として重みを付けることも考えられます。

  • 常に既定のエンコーディングで開く (自動判定OFF)
  • 既定のエンコーディングではありえない場合に限り自動判定をする (二人の考え)
  • 既定のエンコーディングを参考にしつつ、自動判定する (今はコレ)

これなら「既定のエンコーディングではありえない場合」がほとんど存在せず「常に既定のエンコーディングで開く」同然になってしまうとしても(※自分が心配しているのはこれです)、それはそのエンコーディングと重み付けをユーザーが選んだ結果ということになり、それを避ける選択肢も提供されています。

ds14050 avatar Sep 26 '18 17:09 ds14050

あれ、これってバグな認識ですか?

確かに「もうちょっとなんとかなるんじゃね?」という意見が出てますけど。コードは完璧に仕様通りで通常使用ではほぼ問題なく、「誤認は起こりうるもの」で見解は一致してそうな気がします。

こういうのこそ、政治的にどっちか判断するケースなような…

berryzplus avatar Sep 27 '18 03:09 berryzplus

あれ、これってバグな認識ですか?

期待(設計)してる通りに動作してるので(あれば)、バグ(不具合)ではないとおもいますよ。 kobokeさんが言ってた安易にバグと言うのは違うっていうのはこういうことかなと。

ただ万人ではないにしても大半の人にとって不都合ならはそれは変更してもいいのかなと。 どう変更するか変更したことによりあらたにバーターとなることがないかってのは精査がひつようかもですが。 それこそ新たな不都合を生んでしまいかねないですし。

KENCHjp avatar Sep 27 '18 04:09 KENCHjp

あれ、これってバグな認識ですか?

いいえ。「仮に対応するならば」と書いています。「……ならば」だけでは印象が弱いかと思い、推敲段階で「仮に」を付け加えています。

ついでに、既定のエンコーディングに高すぎる優先度を与えることを牽制しています。

重み付けの設定を付け加えるという思いつきは、有害な副作用なしに、ユーザーに対してオプションを提供できると考えています。

ds14050 avatar Sep 27 '18 07:09 ds14050

短剣符記号などのUTF-8にしかないバイトシーケンスに対応頂くと助かります。 https://asiamoth.com/201110222342/

katahiromz avatar Jun 04 '19 07:06 katahiromz

「美乳」の良さが分かる人! :smile:

※注:「美」「乳」は EUC にしか存在しないバイト並びで構成される有名な文字で、あるバイナリ列がEUC-JPであるかどうかの判定に使うテーブルを「美乳テーブル」と呼称することがあります。

サクラエディタの場合、WindowsのMultiBytoToWideChar/WideCharToMultiByteを使った変換⇒逆変換を織り交ぜて判定しているので、単純にテーブルスキャンで EUC、UTF-8 を判定しているわけでもないような気がします。

この話に着手するのは、いまのリリースでHTML Helpが化ける問題を解決できてからになるかなぁ、と思っています。

berryzplus avatar Jun 05 '19 15:06 berryzplus

少なくともデフォルトの文字コードがUTF-8になっている場合は、UTF-8で変換して試す→失敗(不正なコードポイント発見)したらSJIS等を試す でよいと思います。

0xCE 0xB1 0xCE 0xB2αβ (UTF-8) / ホアホイ (Shift_JIS)

ICUは最近のWindowsだと内蔵しているそうです。

tats-u avatar May 19 '24 06:05 tats-u