EUC-JPなJava Webアプリの文字化けまとめ

問題1

PostgreSQLのデータベースをJDBC経由でEUC_JPとして運用しているとき、古いバージョン(8.1系:〜8.1.3, 7.4: 〜7.4.12)から、それ以降のバージョンにバージョンアップしたら、character 0xXXXXXX of encoding "UNICODE" has no equivalent in "EUC_JP"というSQLExceptionが出るようになる。

原因

PostgreSQLJDBCドライバでは、JavaからEUC_JPへの変換については、PostgreSQL上の変換ルーチンを用いる。Java上の全ての文字に対応するEUC_JP(厳密にはx-eucjp-open-19970715-ms)のコードがあるわけではないので、変換に失敗する場合がある。たとえば、置換文字(U+FFFD)は、対応するEUC_JPのコードを持たない。

このとき、PostgreSQLの以前のバージョンでは、特にエラーにならなかったが、8.1.4 / 7.4.13以降から、上記のエラーを出すように仕様が変更された。これが原因である。

対策
  1. Java上には、PostgreSQL上の変換ルーチンとまったく同一のエンコーディング(x-eucjp-open-19970715-ms)がなく、変換エラーになるのかどうかは前もって知るのは困難である。
  2. 根本解決としては、PostgreSQLのデータベースをEUC_JPからUTF-8に移行するのが、もっとも簡明かつ確実な方法である。
    1. その際、古いDBをダンプし、dropdbしたあと、空のDBを作って、そこにダンプファイルをpsqlで流し込めばいい。
    2. 従来からEUC_JPでアクセスしていたスクリプトなどのクライアントの下位互換性を保証するために、postgresql.confのclient_encodingはutf-8にしておく。(※もちろん、それらのクライアントでの変換は、縮小変換になってしまう。)

問題2

Javaで作られたWebアプリで、フォームのエンコーディングJSPのレスポンスのContent-TypeのcharsetにEUC-JPが使われていると、丸数字(①)や、くさなぎのなぎ(彅)などが消えたり、?に化ける。

原因

WindowsIEなどでは、「EUC_JP」と指定したときに、実際には「CP51932」というエンコーディングが使われる。SunのJDKは、標準でCP51932をサポートしていない。このため、「EUC-JP」(規格どおりの変換を行うもの)や「x-eucJP-Open」を指定するしかない。

Windowsで入力できる文字には、NEC特殊文字(①etc.)や、NEC選定IBM拡張文字・IBM拡張文字(彅etc.)が含まれており、これらはEUC-JPではまったく変換できない。x-eucJP-Openでは、UTF-16へのデコード時への一方向変換は可能だが、逆のUTF-16からx-eucJP-Openへのエンコードは、NEC特殊文字は成功するものの、NEC選定IBM拡張文字は、JIS X 0212:1990等に従って、元とは別のコードにエンコードされてしまい、結果、Windowsでは表示できない。2009-8-10追記:秀丸エディタで見たところ、JIS X 0212:1990であっても表示できている。あれれ。そういうものか。(wikipedia:EUC-JP 参照)

対策
  1. Javaでは自前のcharsetを作成することができる。CharsetProviderである。しかし、既存のProvider実装はsunパッケージにあり、その内部実装に依存することは危険である。spiを通じて公開されているインタフェースだけだと、Decoratorを使って既存のEUC-JPの実装を流用することができないため、非常に効率の悪いコードになるか、EUC-JPの変換規則を完全に自作するかする必要があり、これは苦の多い作業となってしまう。
    • Apache HarmonyまたはGNU Classpath、それに、有志の人が作成したProviderも数種が存在するが、どれもパフォーマンスやライセンスの点から、十分な検証なしに採用するのはためらわれる。
  2. 機種依存文字については、NEC特殊文字だけをサポートするようにすれば、x-eucJP-Openが使える。しかし、範囲外の文字を入力されたかどうかチェックをしなければいけないため大変である。
  3. 根本解決としては、まず、Webアプリについては、UTF-8を使うように変更する。
  4. この際、Windowsが内部で保持しているUnicodeコードポイントがそのままJavaに渡ってくることになるので注意。たとえば、波線の表現としてU+301C(〜)を期待していても、実際にはU+FF5E(~)が渡ってくることになる。
  5. Web画面以外で、Linux上のアプリとやりとりするなどの理由で、Javaとの間を、いわゆるEUCでやりとりをしなければならない場合は、IBM拡張文字を切り捨てた上で、やむをえずx-eucJP-Openを使う。ただしこの場合、XML日本語プロファイル 解説の解説表3.2におけるx-eucjp-open-19970715-msとx-eucjp-jisx0221-1995の違いがあるので、その部分は、Java側で変換表の補正が必要となる。