例えば、
「は~い!電話番号は0120-931-931です」
という Unicode(UTF-8) で書かれたテキストがあった時、
use Jcode;
open(IN, "utf8.txt");
$str = join '', (<IN>);
close(IN);
$str = Jcode->new($str)->h2z->sjis;
open(OUT, ">sjis.txt");
print OUT $str;
close(OUT);
という具合に、Jcodeモジュールを使って UTF-8 から Shift_JIS コードへの変換を行うと、結果は、
「は?い!電話番号は0120?931?931です」
みたいに化ける・・・というか、一部の文字が変換出来なくて ? マークに置き換わってる。
「え?Shift_JIS にだって、'~'や'-'はあるじゃん?普通に変換出来るんじゃねえの?」と思うでしょ?それが正常な反応。
そう、何故か Unicode の'~'を、Shift_JIS の'~'に変換するという当たり前のことが出来ないのである。
原因は Unicode の訳の分からない設計、というか実装(の自由度?というか曖昧さ?)にある。
実は、'~'という文字が Unicode(UTF-8)には2つあるのだ。
'WAVE DASH'と'FULLWIDTH TILDE'である。
一方、Shift_JIS の'~'は'WAVE DASH'だけである。'FULLWIDTH TILDE'はない。
つまり、UTF-8 の'~'が'WAVE DASH'として保存されていれば、Shift_JIS に変換する時も何の問題もないのである。
さあ、わかったね。
そう。UTF-8 では、君が画面から'~'を入力したら、それは'FULLWIDTH TILDE'として保存されるのである。
'-'もまたしかり。
Shift_JIS で'-'であれば'MINUS SIGN'なのだが、UTF-8 では'FULLWIDTH HYPHEN-MINUS'なのである。
当然、Shift_JIS には'FULLWIDTH TILDE'や'FULLWIDTH HYPHEN-MINUS'に相当する文字はないので(見た目は'~'や'-'でも、コンピュータ上は別の文字として扱われる)Shift_JIS に変換したところで「変換先のコードがわかんない!」ってことになり ? に置き換えられるのである。(同じ文字でコードが2つあることが問題ではなく、他の文字セット(Shift_JIS や EUC-JP)には'MINUS SIGN'しかないのに、ディフォルトで'FULLWIDTH HYPHEN-MINUS'として保存する Unicode の実装がおかしいという話しね)
じゃあ、どうしたら良いのか?
要は、「まず Shift_JIS に存在する文字に相当する文字に変換して、それから Unicode から Shift_JIS への変換を行う」とすれば良いわけだ。
Unicode のままで、「FULLWIDTH HYPHEN-MINUS から MINUS SIGN に変換」「FULLWIDTH TILDE から WAVE DASH に変換」を行い、それから Shift_JIS に変換するのである。
そうすれば、例えば UTF-8 の'MINUS SIGN'を Shift_JIS の'MINUS SIGN'に変換するだけなので、当然正常に変換は行われる。
具体的には、上記のソースの
$str = Jcode->new($str)->h2z->sjis;
の前に、
$str =~ s/\xEF\xBC\x8D/\xE2\x88\x92/g; # - FULLWIDTH HYPHEN-MINUS→MINUS SIGN
$str =~ s/\xEF\xBD\x9E/\xE3\x80\x9C/g; # ~ FULLWIDTH TILDE→WAVE DASH
という2行を置いてやる。
これで、'~'や'-'が ? に変換されることはなくなるね。
ああ、面倒くせえ、Unicode。(^^;
もちろん頭からお尻まで全部 Unicode で作ってしまえば話は簡単なのだが、世の中にはまだまだ EUC-JP や Shift_JIS で作られたデータも多い。
この妙な例外処理は、まだまだ必要なようだ。