Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

デフォルトの文字コードが反映されない。SJISに設定してもUTF-8で読み込まれる。 #1418

Closed
ghost opened this issue Sep 29, 2020 · 16 comments · Fixed by #1428

Comments

@ghost
Copy link

ghost commented Sep 29, 2020

問題内容

BugReport/195

デフォルトの文字コードが反映されない。 XMLファイルの編集にて発生。 タイプ別設定でSJISをデフォルトに設定しても、毎回UTF-8で読み込まれてしまい、文字化けが発生する。 2.2.0.1より前のバージョンでは起きなかった。

encoding名がMS932など認識できないときにUTF-8に設定されるバグがありました -- Moca 2016-04-05 (火) 13:44:53

再現方法

  1. XML宣言におけるencodingに「ms932」や「sjis」などを記述し、UTF-8以外の文字コードで保存したものを用意します。
  2. サクラエディタを起動し、タイプ別設定にXML用の設定を作成します。
    この時、文字コードとしてSJISを使うように指定しておきます。
  3. 作成したXML文書を開きます。

この手順において、当該ファイルはUTF-8で読み込まれます。
これを正しい文字コードで開き直した後は、履歴に残っている限り問題は発生しません。
なお、初回起動の状態でもUTF-8で読み込まれました。

再現頻度

SJISを使うように設定したXMLのタイプ別設定がある状態で、一度も開いたことのないXML文書を扱うときは必ず遭遇することになると思います。

問題の原因

XML宣言から文字コードを判別する処理に問題があると思われます。

当該ファイルを開く際の文字コード判別処理ではCESI::AutoDetectByXMLが実行されていました。
この中で読み取った文字列を文字コードの定義(encodingNameToCode)と比較する処理において、どの定義とも一致しなかった場合はループが終了してしまいます。
このループの後に実行されるコードは「encoding指定無しでxml宣言が終了した」と判断して呼び出し元にUTF-8である旨を返答しており、これによって誤った文字コードでファイルを開いていました。
また、「encoding指定が無い」XML文書はUTF-8またはUTF-16のどちらかとして取り扱うことがXMLの仕様書で規定されていますが、このコードはUTF-16である可能性を考慮していませんので、もしUTF-16で作成されながらencoding指定を省略していた場合(仕様上は可能です)、同様の問題が発生します。

環境情報

バージョン: 2.2.0.1

2.3.0.0 でも同じ症状が起きました。 -- anonymous 2016-04-05 (火) 10:59:32

手元では現時点で最新のmasterでも再現できました。

このバグに対するパッチについて

上記バグ修正→upatchid:1050。 xmlでencodingがない場合、通常の自動認識の優先度を上げました -- Moca 2016-04-05 (火) 13:54:10

sf.netには、本件に対するパッチとしてpatchunicode#1050がすでに提案されていますが、UTF-16でエンコーディング宣言を省略したパターンに対する考慮がありません。
また、文字コード名の別名を追加する変更も行われていますが、追加される別名はXMLの仕様的に正しくないようです。

なお余談になりますが、encodingNameToCodeには重複が1件(windows-1252)あります。patchunicode#1050ではさらに1件(shift_jis)増えています。

以上、BugReport/195及びpatchunicode#1050からの転記と、自分なりの調査の報告をさせていただきます。

@ghost
Copy link
Author

ghost commented Sep 29, 2020

追加される別名はXMLの仕様的に正しくない

本文に記載すると長くなりそうなので、コメントに分けました

patchunicode#1050を確認するにあたって、XMLの仕様を見てみました。
XML宣言におけるエンコーディングの宣言はチャプター4.3.3にあり、次の形式になっています。

EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )
EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*

UTF-8/UTF-16のほかに、IANAが定義するものと"x-"接頭辞から始まるもので上記のフォーマットを満たしたものが使えるようです。
IANAの定義ではMIMEで使える名前とエイリアス も含まれています。
また、エイリアスに含まれる"cs"で始まるものは、MIBで利用するためにRFC3808で定義されたという注釈がありました。MIBはSNMPなどで利用されるデータベースとのことです。
なお、HTML/CSSも以前はIANAの定義を参照していたようですが、W3Cの文書によると現在はWHATWGが提供している文字コードのリストを参照しており(リンク先のチャプター4.2にあります)、左側の列から選ぶようにと記述されていました。

IANAの定義とpatchunicode#1050の変更を突き合せたところ、「MS_Kanji」と"cs"で始まるもの以外は含まれていませんでした。
仮に追加するとした場合、少なくともIANAの定義かWHATWGのリストに含まれるものから選ぶべきではないかと思いました。

@berryzplus
Copy link
Contributor

困ったこと

問題が 2つ 書いてあるように見えます。
タイトル通りの問題を解決する issue と捉えてよいでしょうか?
タイトルに書いてない問題もかなり重要な課題だと思うのでちょっと扱いに困る感じです。

問題1

デフォルトの文字コードが反映されない。SJISに設定してもUTF-8で読み込まれる。

原因: encodding="SJIS" がサポートされていないため。
対策1: ユーザーが、サポートされているエンコーディング名 Windows-31J に書き換える。
対策2: ユーザーが、サポートされているエンコーディング名 x-sjis に書き換える。
対策3: 開発者が、サポートするエンコーディングに SJIS を追加する。

普通に考えて、対策3の一択ですよね 😄

問題2

encoding名がMS932など認識できないときにUTF-8に設定されるバグがありました

原因: エンコーディング名を認識できないときにエンコーディングを判定するロジックが誤っている?
対策: 正しくはUTF-8UTF-16のいずれかとして扱う仕様なので、ロジックを修正する。

No. encoding encoding別名 判定条件
1 UTF-16 UTF16LE 先頭2バイトが{ 0xff, 0xfe }と一致する
2 UTF-16 UTF16BE 先頭2バイトが{ 0xfe, 0xff }と一致する
3 UTF-8 UTF8BOM 先頭3バイトが{ 0xef, 0xbb, 0xbf }と一致する
4 UTF-8 UTF8N 上記以外

修正対象はこの関数なんですけど、全体的にリファクタリングしたほうが良さそうな雰囲気です。

/*! ファイル中のエンコーディング指定を利用した文字コード自動選択
* @return 決定した文字コード。 未決定は-1を返す
*/
ECodeType CESI::AutoDetectByXML( const char* pBuf, int nSize )
{
// ASCII comportible encoding XML

修正するのは簡単だけど、レビューするのがしんどそう・・・

@berryzplus
Copy link
Contributor

ん?ちょっと読み違っているかも。

2. この時、文字コードとしてSJISを使うように指定しておきます。

要するに、問題2であげたエンコーディングの判定表が正しくないですね 😭

No. encoding encoding別名 判定条件
1 UTF-16 UTF16LE 先頭2バイトが{ 0xff, 0xfe }と一致する
2 UTF-16 UTF16BE 先頭2バイトが{ 0xfe, 0xff }と一致する
3 UTF-8 UTF8BOM 先頭3バイトが{ 0xef, 0xbb, 0xbf }と一致する
4 (設定に依存) - 上記以外、かつ、タイプ別設定にデフォルトエンコーディングがある
5 UTF-8 UTF8N 上記以外

現状、タイプ別設定はファイルの拡張子で行っていますが、XML文書内容に基づくエンコーディング判定の処理のコンテキストには解析中データのタイプ別設定を参照できません。

なのでまぁ、ファイルの拡張子に基づいてタイプ別設定のデフォルトエンコーディングを適用するのは、設計的に不可能です。

@ghost
Copy link
Author

ghost commented Sep 29, 2020

お疲れ様です。

タイトル通りの問題を解決する issue と捉えてよいでしょうか?
タイトルに書いてない問題もかなり重要な課題だと思うのでちょっと扱いに困る感じです。

自分はsf.netのページをそう解釈しました。

「SJISを設定しても」のくだりはタイプ別設定の画面の話だと思います。
タイプ別設定のウィンドウタブにデフォルトの文字コードを設定する箇所があり、ここでSJISを選択した、ということだと思いました。
既定は「改行コード:CR+LF」「文字コード:UTF-8」「BOMなし」「CPなし」「自動判別時にCESU-8を優先しない」が設定されています。
選択肢は、SJIS/EUC-JP/Latin1/UTF-16/UTF-16BE/UTF-8/CESU-8の7個です。
ここでSJISを選択しているのにUTF-8で読み込まれた、ということかと思います。
(なお、本文の再現方法はこれを踏まえてSJISを選ぶように書いています)

問題①については、追加対応で良いと思います。
記述を修正するというシチュエーションもあるので、IANA定義にないものも含めて、ありえそうなものを足しておきたいです。
ただ、IANA定義にあるのに追加されていないものがあるので、それをどうしたらいいか悩んでいます(数が多いです)。

@berryzplus
Copy link
Contributor

現状、タイプ別設定はファイルの拡張子で行っていますが、XML文書内容に基づくエンコーディング判定の処理のコンテキストには解析中データのタイプ別設定を参照できません。

あ、持ってました。

public:
const SEncodingConfig* m_pEncodingConfig;

@ghost
Copy link
Author

ghost commented Sep 29, 2020

問題②について(コメントを分けました)ですが、試したところUTF-16でencoding指定無しのパターンでは、懸念していた問題は発生しませんでした。自分の杞憂に終わったようです。

CESI::DetectUnicodeBomがCESI::AutoDetectByXMLよりも先に呼ばれており、BOMがある場合はこの段階でUnicodeであると決まってしまうので正しく処理されます。
BOMがない場合はCESI::AutoDetectByXMLに入りますが、突合せのループ処理に入らずに別の処理でやはりUnicodeと判定されていました。
このため、対応しないといけないのは「encoding指定がないか、あっても認識できない文字コードが指定されている場合で、Unicode以外の文字コードで作成されている」パターンだけのようです。

ちなみに、当時の担当者であるMocaさんの対処方法をパッチから読み取ると、次のようになっていました。

  1. 別名を追加する
  2. 認識できない文字コードの時はCODE_UTF8ではなくCODE_NONEを返す
  3. encoding指定がないときはCODE_AUTODETECTを返して自動判別処理を行うようにしたうえで、それでも決定できない場合に限りCODE_UTF8を返す

コードからだと、CODE_NONEのまま文字コード判定処理が終わってしまった場合は、デフォルト設定の文字コードが使われるように見えます。そうであればタイプ別設定の文字コードが反映されそうです。

@berryzplus
Copy link
Contributor

ちなみに、当時の担当者であるMocaさんの対処方法をパッチから読み取ると、次のようになっていました。

  1. 別名を追加する
  2. 認識できない文字コードの時はCODE_UTF8ではなくCODE_NONEを返す
  3. encoding指定がないときはCODE_AUTODETECTを返して自動判別処理を行うようにしたうえで、それでも決定できない場合に限りCODE_UTF8を返す

コードからだと、CODE_NONEのまま文字コード判定処理が終わってしまった場合は、デフォルト設定の文字コードが使われるように見えます。そうであればタイプ別設定の文字コードが反映されそうです。

この内容で問題なさそうに思います。

@ghost
Copy link
Author

ghost commented Sep 29, 2020

追加する別名ですが、

  1. サクラエディタで保存時に指定できる文字コード
  2. IANAのリストとWHATWGのリストのどちらかにある

とりあえずこの2点を満たすものを抽出してみました。それでも39個あります。

  • CESU-8 (CODE_CESU8):2個
    • "cscesu8", "cscesu-8"
  • EUC-JP (CODE_EUC):3個
    • "cseucpkdfmtjapanese", "extended_unix_code_packed_format_for_japanese", "x-euc-jp"
  • ISO-2022-JP (CODE_JIS):1個
    • "csiso2022jp"
  • ISO-8859-1 (CODE_LATIN1):9個
    • "cp819", "csisolatin1", "ibm819", "iso-ir-100", "iso8859-1", "iso88591", "iso_8859-1", "iso_8859-1:1987", "l1"
  • Shift_JIS (CODE_SJIS):5個
    • "csshiftjis", "ms932", "ms_kanji", "shift-jis", "sjis"
  • UTF-16 (nCode未割当):BEが2個・LEが7個
    • "unicodefffe", "utf-16be"
    • "csunicode", "iso-10646-ucs-2", "ucs-2", "unicode", "unicodefeff", "utf-16", "utf-16le"
  • UTF-7 (CODE_UTF7):1個
    • "csutf7"
  • UTF-8 (CODE_UTF8):5個
    • "unicode-1-1-utf-8", "unicode11utf8", "unicode20utf8", "utf8", "x-unicode20utf8"
  • Windows-31J (CODE_SJIS):1個
    • "csWindows31J"
  • Windows-1252(nCode = 1252またはCODE_LATIN1):3個
    • "cp1252", "cswindows1252", "x-cp1252"

@berryzplus
Copy link
Contributor

追加する別名ですが、

  1. サクラエディタで保存時に指定できる文字コード
  2. IANAのリストとWHATWGのリストのどちらかにある

とりあえずこの2点を満たすものを抽出してみました。それでも39個あります。

39個くらいならそのまま入れてもいいんじゃないかと思います。

IANAのリスト ⊂ WHATWGのリスト という関係(IANAのリストはWHATWGのリストに含まれる)で、
IANAのリスト ⊂ Windowsのコードページリスト という関係(IANAのリストはWindowsのコードページリストに含まれる)だったと思います。

問題はサクラエディタが IANAのリスト にも Windowsのコードページリスト にも存在しないコードページ CESU-8 に対応している(?)ことで、サクラエディタがサポートするコードページの範囲が不当に狭く見えてる気がしています。

サクラエディタには、CPチェックボックスにより Windowsのコードページリスト に含まれるすべてのコードページに対応することができます。なので、エンコーディング名を Windowsのコードページ に変換することができれば、かなりたくさんのエンコーディングに対応することができるはずです。

@ghost
Copy link
Author

ghost commented Oct 1, 2020

パッチを読み返したらshift_jisは重複していませんでした。すみませんでした。
(今更ながら訂正を本文に反映しました。)

CPの機能を知らなかったので、いろいろと試してみました。
その過程でISO-8859-1に28591番が割り当てられていることに気が付きました。
そこで、CODE_LATIN1は1252と28591のどちらを指しているのか気になっています。
encodingNameToCode上ではwindows-1252が次のように記述されています。

{ "windows-1252", 12, CODE_LATIN1 },
(中略)
{"windows-1252",  12, 1252},

実際にwindows-1252という宣言に遭遇した場合、CODE_LATIN1と判定されるようです。

@berryzplus
Copy link
Contributor

そこで、CODE_LATIN1は1252と28591のどちらを指しているのか気になっています。

Windows-1252(=ISO-8859-15、ISO-8859-1の改訂版)です。

// Windows 拡張部
nret = ::MultiByteToWideChar( 1252, 0, reinterpret_cast<const char*>(pr), 1, reinterpret_cast<wchar_t*>(pw), 4 );

encodingNameToCode上ではwindows-1252が次のように記述されています。

{ "windows-1252", 12, CODE_LATIN1 },
(中略)
{"windows-1252",  12, 1252},

実際にwindows-1252という宣言に遭遇した場合、CODE_LATIN1と判定されるようです。

この記述の意味は、わずかに異なります。

CODE_LATIN1 と判定した場合、 Latin1変換専用クラスである CLatin1 が使われます。
1252 と判定した場合、汎用コードページ変換クラスである CCodePage が使われます。

両者の違いは、独自にカスタムした変換を使うかどうかで、実際どっちが速いのか?はぼくも知らないっす。

@k-takata
Copy link
Member

k-takata commented Oct 1, 2020

Windows-1252(=ISO-8859-15、ISO-8859-1の改訂版)です。

Windows-1252 は ISO-8859-15 とは異なります。

@berryzplus
Copy link
Contributor

Windows-1252(=ISO-8859-15、ISO-8859-1の改訂版)です。

Windows-1252 は ISO-8859-15 とは異なります。

https://ja.wikipedia.org/wiki/Windows-1252
https://ja.wikipedia.org/wiki/ISO/IEC_8859-15

符号位置が異なるのでベツモノらしいです。

@ghost
Copy link
Author

ghost commented Oct 3, 2020

#1422 を提出しました。

@ghost
Copy link
Author

ghost commented Oct 14, 2020

タイプ別設定にある文字コード設定が反映されるようにする変更を、#1428で提出しました。

@ghost
Copy link
Author

ghost commented Oct 19, 2020

ご対応ありがとうございました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants