差分表示


encodeURIComponent()を使ってAjaxで送信した日本語文字(〜など)が,サーバ側で文字化けする場合への対処.
//parent=Ajax,JavaScript

ブラウザからサーバに日本語文字列を送信する時の話だが,
普通にHTMLでサブミットする場合は,ブラウザが勝手にそのページの文字コードかつURLエンコーディングした結果を送信してくれるので,
特に何も考えなくて良いのだが,Ajaxの場合はエンコードを自前でやらねばならない.

この場合,JavaScriptのencodeURIComponent()関数を使うのが一般的のようだ.
これを使うと,
-ページのエンコーディングからUTF-8への変換
-その変換結果の,URLエンコーディング

の両方を行ってくれる.

サーバ側では,受信した内容をURLデコードして,
さらに必ずUTF-8で来ているはずなので,その前提で必要な文字コードへ簡単に変換して使うことができる.

...のはずだったのだが,文字化けが生じてはまってしまった.

このページでは,その顛末を書きます.

なお,Windowsのブラウザで発生した現象で,ページのエンコーディングはEUC-JPです.

*現象
文字「〜」を,Windowsブラウザ上のJavaScriptのencodeURIComponent()でUTF-8に変換し,
それをサーバに送信したが,サーバ側でPerlのEncode.pmを使ってデコードできなかった.

&ref(encuri_mjbk1.png,100%,left)

**ブラウザでの変換
ブラウザのアドレスバーに,
 javascript:alert(encodeURIComponent('〜'))

と入力すれば判るように,encodeURIComponent()では以下の変換結果が得られる.
,変換前,変換後,備考
,〜,%ef%bd%9e,U+FF5E (FULLWIDTH TILDE)

これは,いわゆるMicrosoft仕様の変換だ.(MS932)

:Unicode WAVE DASH - FULLWIDTH TILDE問題 (Wikipedia):http://ja.wikipedia.org/wiki/Unicode#WAVE_DASH_-_FULLWIDTH_TILDE.E5.95.8F.E9.A1.8C

**サーバでの変換
PerlのEncode.pm,Jcode.pmで「euc-jp」を使った場合,
UTF-8からEUC-JPへの変換には,以下のようにJIS X 0221準拠の変換が適用される.
,変換前,変換後,備考
,%ef%bd%9e,??,化け文字

そのため,ブラウザから「%ef%bd%9e」として送信されてきた文字は,
本来の「〜」に変換されずに文字化けとなってしまう.

*対策
理屈の上では,以下の3案が考えられる
+ブラウザでのEUCからUTF-8への変換を,MS932ではなくJIS X準拠の方式で行う
+ブラウザでの変換後に,MS932とJIS Xでマッピングが異なる文字をJIS Xのコードに置換する
+サーバでのUTF-8からEUCへの変換を,JIS X準拠ではなくMS932の方式で行う

このうち,1.はどうすればよいのかさっぱり判らないので却下.

3.については,こういうものがあるらしい
:Encode-EUCJPMS モジュール:http://search.cpan.org/~naruse/Encode-EUCJPMS/

が,インストールするのが面倒なのでやめた.

ということで,2.のコード置換をやることにした.具体的には,「〜」のマッピングは以下になっているので,
,エンコーディング,MS932,JIS X 0221
,〜の位置,ef bd 9e = U+FF5E(FULLWIDTH TILDE),e3 80 9c = U+301C(WAVE DASH)

Encode.pmのeuc-jpでデコードするためには,「ef bd 9e」→「e3 80 9c」に変換すればよい.

&ref(encuri_mjbk2.png,100%,left)

**変換の具体的な内容
昔,JavaでWindows-31Jが使えなかった時代によく見ていたページ
:Javaの日本語関連コンバータにおけるマッピングの違い:http://www.ingrid.org/java/i18n/encoding/ja-conv.html

を参照しつつ,実際の動きを調べたところ,Encode.pmで正常にデコードするためには以下のコード置換が必要だとわかった.
を参照しつつ,実際の動きを調べたところ,Encode.pmで文字化けせずに変換するためには以下のコード置換が必要だとわかった.
--(
$a =~ s/%ef%bd%9e/%e3%80%9c/gi; # 〜 U+FF5E(FULLWIDTH TILDE) → U+301C(WAVE DASH)
$a =~ s/%e2%88%a5/%e2%80%96/gi; # ‖ U+2225(PARALLEL TO) → U+2016(DOUBLE VERTICAL LINE)
$a =~ s/%ef%bc%8d/%e2%88%92/gi; # − U+FF0D(FULLWIDTH HYPHEN-MINUS) → U+2212(MINUS SIGN)
$a =~ s/%ef%bf%a0/%c2%a2/gi;    # ¢ U+FFE0(FULLWIDTH CENT SIGN) → U+00A2(CENT SIGN)
$a =~ s/%ef%bf%a1/%c2%a3/gi;    # £ U+FFE1(FULLWIDTH POUND SIGN) → U+00A3(POUND SIGN)
$a =~ s/%ef%bf%a2/%c2%ac/gi;    # ¬ U+FFE2(FULLWIDTH NOT SIGN) → U+00AC(NOT SIGN)
--)
サーバ側のPerlでこの変換を実行することで,ひとまず文字化け問題は解決した.

この作業をやるために,[[UTF-8とUTF-16を相互変換するJavaScript>UTF-16とUTF-8のコード変換]]を書く羽目になった.

なお,上記の6文字とたいていセットになってくる
 ― U+2015(HORIZONTAL BAR)

については,encodeURIComponent()でJIS準拠の変換が行われていたので,特に変換する必要はなかった.

*雑感
JavaでWindows-31Jが使えるようになってから,この問題のことはすっかり忘れていたが,
UnicodeとWindowsを使う限り,逃れることはできないのか...

そういえば,Macのブラウザだとどうなるのかが判らないが,
まぁこの変換を入れたところで,副作用は出ないだろう(多分).

*参考資料
:Unicode - Wikipedia:http://ja.wikipedia.org/wiki/Unicode
:Javaの日本語関連コンバータにおけるマッピングの違い:http://www.ingrid.org/java/i18n/encoding/ja-conv.html
:UCS-2とUTF-8:http://homepage1.nifty.com/nomenclator/unicode/ucs_utf.htm
:UTF-8とUTF-16を相互変換するJavaScript:[[UTF-16とUTF-8のコード変換]]


© 2023 KMIソフトウェア