文字化け
10月に実稼動を開始した某ウェブアプリで、ある一人のユーザからの入力だけ文字化けが生じている。FORMのうち、ラジオボタンの値は文字化けしていないが、テキストエリアの値がすべて文字化けしている。
WikiやMovableTypeを使っていたとき、Macのブラウザでテキストエリア内が文字化けする現象はさんざん見てきた。でもこのアプリではスタイルシートは一切使用していないし、原因がわからない。ただし、そのユーザが使用したブラウザがともかく気になるが、ApacheのログにはUserAgentは記録されていなかったので、すぐにはブラウザを特定できない。
文字化けしているデータをつらつらと眺めていたら、日付らしきパターンを見つけた。いずれも同じように文字化けしている。これを手がかりに文字化けの原因を考えてみる。
「10月」と入力されたと思われるデータがPostgreSQLでは「10a°e´」になっていた。「月」が「a°e´」になっているわけだ。2バイト文字が6バイト文字になっていることになる。なんでそうなるの?
しばし16進数表示にしてにらめっこ。
「月」はEUC-JPで0xB7EE、Shift_JISで0x8C8E。
「a°e´」はEUC-JPで0x61A1EB65A1AD。
うーむ、わからない。
そこで、Shift_JISの「月」を含むHTMLを作成して、ブラウザの表示エンコーディングをいろいろ変えてみる。すると「MacRoman」を指定したところ、おお、アクセント記号つきのaとeが表示されるではないか。これかあ。
次のように仮定すると、とりあえず説明がつく。
そのユーザが入力した「月」は「0x8c8e」として送信された。
MacRomanのコード表をみると
http://www.alanwood.net/demos/macroman.html
0x8C => U+00E5
0x8E => U+00E9
ここで英字とアクセント記号がどこかの変換処理で2文字に展開された
0x8C => U+00E5 => U+0061 U+00B0 => 61 A1 EB
0x8E => U+00E9 => U+0065 U+00B4 => 65 A1 AD
と考えると、とりあえず説明がつく。
そこで、Javaで以下の出力テストをやってみた。
System.out.println("10"+'\u00e5'+'\u00e9');
System.out.println("10a"+'\u00b0'+'e'+'\u00b4');
結果は
10??
10a°e´
ということは、実行環境がEUC-JPのとき、Stringの出力処理でアクセント付文字がアルファベットとアクセント記号に分解されることはなさそうだ。
とすると、Unicodeの標準分解はどこで発生するのだろう?
http://www.unicode.org/charts/normalization/
http://www.unicode.org/charts/normalization/chart_Latin.html
これをみると、あれれ、
00E5 => 0061 030A
00E9 => 0065 0301
ということは、
0x8C => U+00E5 => U+0061 U+030A => U+0061 U+00B0 => 61 A1 EB
0x8E => U+00E9 => U+0061 U+0301 => U+0065 U+00B4 => 65 A1 AD
なのか? 頭が混乱してきた。