2008.05.27 Tuesday
PHP の include で半角カナが文字化け
インターネットの世界では NG とされている半角カナですが, 携帯サイトの構築では避けて通ることができません。
しかし昔から半角カナには文字化けの問題が憑き物。
半角カナを含む PHPファイルを include (require も同じ) して出力すると, 半角カナ文字列の一部が文字化けするという謎の症状に悩まされました。
表示全体が文字化けするならまだ分かりますが, 半角カナ部分のごく一部の場合もあれば, 行末まで化けたりする場合も。
しかも全てのファイルではなく, 一部のファイルのみそういった症状が起こります。
半角カナの前後に適当な文字を挿入してやると文字化けが解消されたりと奇妙な挙動。
携帯サイト向けの開発環境なので, --enable-zend-multibyte つけてビルドして mbstring.script_encoding = SJIS としてあるのですが, 今までの経験と第六感からここら辺が怪しい感じが。
PHP が 4.3.x(!) とかなり古かったのでこのせいかと思い, 4.4.8 や 5.2.6 にバージョンアップしてみたのですが, 症状は変わらず。
グーグル様に『PHP 半角カナ 一部文字化け』とお伺いを立てても, 有力な情報もなし。
さて困った。
しかし昔から半角カナには文字化けの問題が憑き物。
半角カナを含む PHPファイルを include (require も同じ) して出力すると, 半角カナ文字列の一部が文字化けするという謎の症状に悩まされました。
表示全体が文字化けするならまだ分かりますが, 半角カナ部分のごく一部の場合もあれば, 行末まで化けたりする場合も。
しかも全てのファイルではなく, 一部のファイルのみそういった症状が起こります。
半角カナの前後に適当な文字を挿入してやると文字化けが解消されたりと奇妙な挙動。
携帯サイト向けの開発環境なので, --enable-zend-multibyte つけてビルドして mbstring.script_encoding = SJIS としてあるのですが, 今までの経験と第六感からここら辺が怪しい感じが。
PHP が 4.3.x(!) とかなり古かったのでこのせいかと思い, 4.4.8 や 5.2.6 にバージョンアップしてみたのですが, 症状は変わらず。
グーグル様に『PHP 半角カナ 一部文字化け』とお伺いを立てても, 有力な情報もなし。
さて困った。
「半角カナがあるからいけないんだ!」
と, 出力内容の半角カナ部分を全角カナに変換した後, バッファリングして include し, バッファ文字列の全角カナを半角カナに戻すという無理やりなことをやってみました。
※参考 [ke-tai.org] PHPで絵文字を壊さずに文字コードや全角半角変換を行う方法
一応はこれで文字化けせずきちんと出力されるようになりました。
が, もともと全角カナだったものまで半角カナになってしまうという副作用が...
(携帯サイトなので全角カナの需要はほぼ無いんですけどね)
実はこの問題, 結構前にも発生していたようで, 既に偉い人によって社内で解決済みでした。
それなら早く言ってよ〜!ヽ(`Д´)ノ
で, 結局原因は何なんだということですが, 一言でいうと“PHP のバグ”。
script_encoding が SJIS の場合, 一旦 EUC に変換されて実行され, 出力時に再度 SJIS に変換されます。
出力は一定のブロックサイズで区切られるのですが, この際に EUC半角カナの 1バイト目(0x8E)で切られてしまうことがあり, その場合に文字化けが発生していたようです。
ですので include だけではなく, 通常の PHP の実行でも文字化けは発生するかも知れませんね。
詳しくは Zend/zend_multibyte.c の sjis_output_filter の下記部分が該当します。
半角カナの 1バイト目で切られてしまった場合は q が NULLバイトを指している状態ですが, ロジックではそこまでを文字列として読み込んで処理をおこなっているので, その部分で出力がおかしくなっているようです。
現時点での最新版 5.2.6 でもこうなっていたので, まだ解決されてなさそうですね...。
半角カナが 1バイト目で切られた場合, ポインタを戻してやればきちんと動くようになりました。
script_encoding が SJIS で同様の現象が発生している方はお試し下さいませ。
(動作保障はしませんが...)
と, 出力内容の半角カナ部分を全角カナに変換した後, バッファリングして include し, バッファ文字列の全角カナを半角カナに戻すという無理やりなことをやってみました。
mb_convert_kana($contents, 'KV', 'SJIS-win');
$fp = fopen($fp, $tmpfile);
fwrite($fp, $contents);
fclose($fp);
ob_start();
include $tmpfile;
$output = mb_convert_kana(ob_get_contents(), 'k', 'SJIS-win');
ob_end_clean();
echo $output;
※参考 [ke-tai.org] PHPで絵文字を壊さずに文字コードや全角半角変換を行う方法
一応はこれで文字化けせずきちんと出力されるようになりました。
が, もともと全角カナだったものまで半角カナになってしまうという副作用が...
(携帯サイトなので全角カナの需要はほぼ無いんですけどね)
実はこの問題, 結構前にも発生していたようで, 既に偉い人によって社内で解決済みでした。
それなら早く言ってよ〜!ヽ(`Д´)ノ
で, 結局原因は何なんだということですが, 一言でいうと“PHP のバグ”。
script_encoding が SJIS の場合, 一旦 EUC に変換されて実行され, 出力時に再度 SJIS に変換されます。
出力は一定のブロックサイズで区切られるのですが, この際に EUC半角カナの 1バイト目(0x8E)で切られてしまうことがあり, その場合に文字化けが発生していたようです。
ですので include だけではなく, 通常の PHP の実行でも文字化けは発生するかも知れませんね。
詳しくは Zend/zend_multibyte.c の sjis_output_filter の下記部分が該当します。
/* hankaku kana */
if (*q == (char)0x8e) {
q++;
if (*q) {
*p++ = *q++;
}
continue;
}
半角カナの 1バイト目で切られてしまった場合は q が NULLバイトを指している状態ですが, ロジックではそこまでを文字列として読み込んで処理をおこなっているので, その部分で出力がおかしくなっているようです。
現時点での最新版 5.2.6 でもこうなっていたので, まだ解決されてなさそうですね...。
半角カナが 1バイト目で切られた場合, ポインタを戻してやればきちんと動くようになりました。
/* hankaku kana */
if (*q == (char)0x8e) {
q++;
if (*q) {
*p++ = *q++;
} else {
q--;
break;
}
continue;
}
script_encoding が SJIS で同様の現象が発生している方はお試し下さいませ。
(動作保障はしませんが...)









Comments