調査環境
- OS:Windows 11
- 確認エディタ:
- メモ帳(BOMなしファイルを自動文字コード判別)
- Visual Studio Code
- サクラエディタ
- 対象:XSL(XMLベースのテンプレートファイル)
事象
Windows上でダウンロードしたXSLファイルをメモ帳で開いたところ、
一部のファイルだけがUTF-8として解釈され、日本語が文字化けする現象が発生した。
調査すると、以下の傾向があった。
- 日本語(マルチバイト文字)が少ない場合は文字化けしない
- 日本語(マルチバイト文字)が多くなると文字化けする
- ファイルはBOM付きUTF-8ではない
- XML宣言には
encoding="EUC-JP"が記載されている - しかし、実体の文字コードは Shift_JIS である
最初は「BOMが付いたのでは?」と疑ったが、原因は別にあった。
なぜ宣言と実体がズレているのか(レガシー構造の背景)
対象システムは長年運用されているレガシー構造で、
- 入出力や周辺ツールはShift_JIS前提
- 一方で、XMLテンプレートや宣言部は過去の設計資産を流用
といった経緯があり、
- 実体:Shift_JIS(現在も実際に利用されている文字コード)
- 宣言:EUC-JP(互換性や影響範囲を考慮し、そのまま残されている)
という「宣言と実体の不整合」が存在していた。
文字コードを変更すると既存顧客環境や連携システムに影響が及ぶ可能性があるため、
リスク回避の観点から修正されずに残っていたケースである。
結論
原因は以下の組み合わせだった。
- ファイル実体は Shift_JIS(CP932)
- BOMなし
- XML宣言は EUC-JP のまま
- エディタの自動文字コード判別が、マルチバイト文字量の増加により誤判定した
つまり、
「文字コードそのものは正しいが、
文字コードを示すヒント(BOMや正しい宣言)が不十分・不整合なため、
エディタ側の自動判別が外れて文字化けした」
という構造だった。
なぜマルチバイト文字が増えると起きるのか
Shift_JISの2バイト文字が増えると、そのバイトパターンが
- UTF-8としても成立しそうに見える
- EUC-JPとしても成立しそうに見える
確率が高くなる。
その結果、
- 日本語が少ない場合
→ Shift_JISと正しく推測される - 日本語が多い場合
→ UTF-8 / EUC-JPと誤推測される
という「文字コード自動判別の揺れ」が発生する。
実体がShift_JISであることの確認方法(バイトレベル検証)
ここではまず、ファイル内から「Shift_JISで日本語になる典型的なバイト列パターン」
(例:2バイト漢字が連続する並び)を探索し、その出現位置(バイトオフセット)を特定した。
その結果、日本語が含まれていることが分かった位置の前後を含む一定範囲を切り出し、
その部分を検証対象とした。
ファイルを文字ではなく「生のバイト列」として読み込み、
同じバイト列を異なる文字コードでデコードして比較する。
$bytes = [System.IO.File]::ReadAllBytes("sample.xsl")
# 日本語が含まれていることが分かっている位置の前後を切り出す
$segment = $bytes[$offsetStart..$offsetEnd]
# Shift_JISとしてデコード
[System.Text.Encoding]::GetEncoding(932).GetString($segment)
# EUC-JPとしてデコード
[System.Text.Encoding]::GetEncoding(51932).GetString($segment)
同一のバイト列が Shift_JIS として解釈した場合のみ意味のある日本語として復元され、
EUC-JP では正しく復元されないことから、
当該ファイルの実体文字コードは Shift_JIS であると論理的に判断できる。
エディタごとの挙動差
| エディタ | 設定 | 挙動 |
|---|---|---|
| メモ帳 | 自動判別 | マルチバイト量により UTF-8 と誤認識 → 文字化け |
| VS Code | Auto Guess Encoding OFF | UTF-8(デフォルト) → 文字化け |
| VS Code | Auto Guess Encoding ON | Shift_JIS判定 → 正常表示 |
| サクラエディタ | 文字コード自動選択 | XML宣言(EUC-JP)が優先され → 文字化け |
正しい解決策
最も安全で再発しない対策は次の考え方である。
「このファイルは Shift_JIS である」とツールが迷わず判断できる状態にすること
具体的には:
- XML宣言を
encoding="Shift_JIS"に修正する - もしくは UTF-8(BOM付き)に統一し、宣言・実体・ツール解釈を揃える
- 少なくとも「宣言と実体の不一致」を放置しない
今回の本質は「文字化け」そのものではなく、
レガシー互換性により残った宣言と実体のズレが、
文字コード自動判別という曖昧な仕組みと組み合わさって顕在化した
という構造的問題だった。
学び
- BOMは「有無」より「自動判別に与える影響」が重要
- 文字化けは「壊れた」のではなく「誤って解釈された」
- 宣言・実体・表示ツールの三層が一致して初めて安全
- レガシー互換性は技術的負債として文字コード問題に直結する
- バイトレベルで確認すれば、推測ではなく証明になる