人気ブログランキング | 話題のタグを見る

[PHP] 文字コード変換エラー取得したい

php で文字コード変換のエラーを拾いたい、という話。
ありていに言うと、webページはUTF-8だけど、
DB が euc-jp なので、
euc-jp でない入力は事前にチェックしたいのねん。
で、こんなテストを書いてみた。

UTF-8 では表示できる簡体字を入力にして、
EUC-JP に変換して、ありゃ失敗だよ、とやりたい。
「書」も「芸」も、簡体字は日本語にない文字なので、こんな風に書いてみる
(例えば「機」の簡体字は「机」なので日本語にある)。
$str = "\xE4\xB9\xA6"; // 「書」の簡体字(UTF-8)
$str = "\xE8\x89\xBA"; // 「芸」の簡体字(UTF-8)

$str2 = FALSE;
try {
 $str2 = mb_convert_encoding($str, 'EUC-JP', 'UTF-8'); // UTF-8 -> EUC-JP
} catch {
 echo 'ERROR';
}
echo $str2 == FALSE ? 'ERROR' : 'TRUE';

困ったことに、これでは TRUE が返る。
mb_convert_encoding は例外も出さないし、エラーも出さないし、
無理やり変換してくれる。
ちなみに変換結果は「??」になる。
化けてるんじゃなくて、本当に「?」が2つという、
なんなんだよそれwww!という動作。

次に iconv を試してみる。
上記の mb_convert_encoding の部分をこんな風に書き換える。
$str2 = iconv("UTF-8", "EUC-JP", $str);

ちなみにこちらも、コマンドラインに Notice は出すものの、
例外もエラーも出さない。
この場合は、$str2 が FALSE になる。
しかし、入力文字列がもっと長くて、途中まで変換できちゃった場合には、
変換できたところまでが $str2 に入ってしまうようなので、
$str2 == FALSE という判断ができない。
変換後と変換前とで、長さを比べることもできないしなぁ
(変換に成功すると UTF-8 の方が長くなる)。

成否判定部分を perl みたいに書いてみたりもしたのだが。
// (ASCII | 半角カナ | 3バイト | 2バイト)+
$eucjp = "/^([\x00-\x7F]|\x8E[\xA0-\xDF]|\x8F[\xA1-\xFE][\xA1-\xFE]|[\xA1-\xFE][\xA1-\xFE])+$/";
echo (preg_match($eucjp, $str2) != 0) ? TRUE : FALSE;

これだと、EUC-JP の文字でも FALSE が返ってしまう。
正規表現間違ってないよね?

iconv が吐いた字をファイルに落としてみたら、
簡体字がちゃんと GB2312 に変換されていた。
気利かせすぎ!
EUC-JP にならないなら、素直に諦めてくれて良いんだってば!

困ったなぁ。
みんなはどうしてるんだろう???


追記:
コメントにて、解決方法のご提案を頂きました。

★方法1:mb_check_encoding を使う
PHP: mb_check_encoding - Manual
上記マニュアルによると、
バイトストリームが指定したエンコーディングで有効なものかどうかを調べます
とあります。
残念ながら、これは今回の目的には合致しません。
今回の困っている点は、
・mb_convert_encoding では、変換結果にホントに「??」という文字列を出してくる
・iconv では、文字コード変換を途中までちゃんとやってくれる
なのですが、これで出力された文字列は euc-jp で有効なので、
変換に失敗したかどうかはわかりません。
でも使えそうな関数なので、
覚えて置いて損はないなぁと思いました。

★方法2:可逆かどうかで判断
utf-8 → euc-jp へ変換し、
変換後の文字列を
euc-jp → utf-8 へ変換し、
最初と最後が同じか、比較する、というものです。
なるほど、これならちゃんと FALSE が返ります。
すごく効率的!という感じはちょっと減りますが、確実な感じはしますね。

★方法3:mb_detect_encoding を使う
mitsukenさんからこの関数は如何でしょう?とご提案を頂きました。
PHP: mb_detect_encoding - Manual
検出した文字エンコーディングを返しますだそうです。
文字コード変換自体を行うわけではないので、
文字コード変換後に、変換結果が正しいか、判定するのに使うのだと思います。
が、これも方法1と同様で、
mb_convert_encoding も iconv も一部分は正しく変換してくるので、
結果として全部は変換できていないのに、
変換された一部分は euc-jp としては本当に「正しい」のです。
だから、最後まで全部変換できたのかどうかは、
これでは判定できないと思います。
うーん難しい。


☆ちょっと横道
文字をバイトで指定するときは
$str1 = b"\xE4\xB9\xA6";

のようにしたら良いですよとアドバイスを頂きました。
これは PHP5.2.1 からの機能のようです。
バージョンがこれ以上なら使えそうですね。
by xiaoxia | 2007-07-12 18:22 | プログラム言語
<< 読んだ本について色々 漫画バトン >>