例えばHTMLはCP932、DBはUTF-8、メールはISO-2022-JP
そんな環境だったときに困るのが文字コード
servletで動いているウェブアプリがあったとして、HTMLからJava、DBへの流れは特に意識しないでも問題ない。Javaがやってくれるから
でも、困っちゃうのがアプリに登録されているネタを別の媒体に出力する時だ。
主にあるのがメール。ISO-2022-JPは、CP932(shift_jisの拡張)で使えた文字が使えない。
官公庁にだす書類などでは第一水準と第二水準にしなければならないとかあるそうだ。
e-taxの使える漢字はどのようなものなのかを調べようと思ったら、JIS X 0221で使える文字みたいなことが書いてあった。
JIS X 0221はUnicodeだそうで、ならば結構な数の漢字が使えそうだと思ったのだけれども、
使える漢字使えない漢字一覧がPDFで公開されていて、どれが使えるのか使えないのか仕組みがよくわからなかった。
そもそもJIS X 0221とは何者なのかも僕自身よくわかってないので、e-taxについてはとりあえず触れないでおこう
文字化けされちゃう文字の判別法
文字コードの変換について調べてみると、Javaの仕組みがわかってきた。
その文字コードにないコードを指定すると『?』に変わる。
String#getBytesを使って、指定した文字コードに変換をする。
例えば
“山”というString ⇒ Shift_jisのコードに変換 ⇒ 8E52という値 ⇒ Shft_jisから変換 ⇒ “山”というString
一方存在しない漢字の場合
“緣”というString ⇒ Shift_jisのコードに変換 ⇒ 該当無し⇒3Fという値 ⇒ Shift_jisから変換 ⇒ “?”というString
というわけで、最後が?になったら文字化けされる可能性があるというわけだ。
判別するためのサンプルコードを書いてみた。
import java.nio.charset.Charset; public class MojiChecker { public boolean isChangeable(Character word , Charset sourceChar) { String wordstr = word.toString(); byte[] wordbyte = wordstr.getBytes(sourceChar); String word2 = new String(wordbyte,sourceChar); //テスト用 System.out.println(wordstr); System.out.println(toStr(wordbyte)); System.out.println(word2); //テスト用終わり return wordstr.equals(word2); } /* * テスト用のbyte配列からStringの16進数表記を返すメソッド */ private static String toStr(byte[] bs){ StringBuilder sb = new StringBuilder(); for(byte b : bs){ int i = b & 0xFF; sb.append(Integer.toHexString(i)); } return sb.toString(); } public static void main(String[] args) { MojiChecker m = new MojiChecker(); if(!m.isChangeable('緣',Charset.forName("sjis") )) { System.out.println("変換できません"); } } }
Javaは内部的にはUnicodeを使っている。
よって、その文字はUnicodeから各文字コードに変換できるかどうかを判別するメソッドになっている。
CP932からISO-2022-JPに変換できるかどうかは、対象の文字をそれぞれCP932とISO-2022-JPで変換可能かどうかをメソッドで判別してもらって
結果をANDで返せばいいと思う。
第一水準と第二水準のみ使えるアプリの場合の判別法
国税に出す書類が第一水準と第二水準の漢字のみOKだから、理由や名前を登録するときに第三水準の漢字などを入れてほしくないとかいう場合について考える。
JIS X 0208という文字コードは第二水準までの漢字しか対応していないらしい。
というわけで先ほどのメソッドに文字コードをJIS X 0208を指定すれば確認できそうだ。
やってみよう。
//一般的な漢字 System.out.println(m.isChangeable('愛',Charset.forName("x-JIS0208"))); //true //使えないかなと思いきや System.out.println(m.isChangeable('齦',Charset.forName("x-JIS0208"))); //true //こんなマイナーなのも使える System.out.println(m.isChangeable('冏',Charset.forName("x-JIS0208"))); //true //でもこれは使えない System.out.println(m.isChangeable('髙',Charset.forName("x-JIS0208"))); //false System.out.println(m.isChangeable('﨑',Charset.forName("x-JIS0208"))); //false
Javaでサポートされているエンコーディングはこちらのサイトを参考に
http://docs.oracle.com/javase/jp/6/technotes/guides/intl/encoding.doc.html
これで入力欄などのチェックで、使えなさそうな漢字を事前に入れさせないようにできる。
本当は異字体を変換してくれる方法があれば楽なんだけれども…
いや、世の中がすべてUnicodeで統一してくれたらいいのだけれども…