PHP
unicode

不用意にレガシーエンコーディングから Unicode に変換しない

一対一の関係だけではない

ウィキペディアや Unicode の解説サイトのおかげで外国語の文字を調べることが容易になりましたが、一方で レガシーエンコーディングのすべての文字が Unicode と一対一で対応していると思い込んでいる人が一定数います。この記事では本当は一対一ではない関係や、一対一であると思い込んだために失敗した事例を取り上げます。

肉眼を信じない

文字のプログラミングに取り組むときに心がけることは肉眼を信じないことです。見た目は同じでもまったく異なる文字が存在します。キリル文字のなかにはラテン文字と区別しにくい文字があり、ラテン文字とキリル文字を混在させた国際ドメインを使ったフィッシング詐欺が問題になったことがあります。

さらに OS にフォントがインストールされていないために、豆腐のような四角形として表示されてしまい見わけがつかない文字はたくさんあります。

対策はバイナリデータを確認することです。Unicode エスケープシーケンスも役立ちます。なお、豆腐が表示されないように世界中の文字を表示することを目的にしているのが Noto フォントです。

往復変換が保障されない文字

レガシーエンコーディングの文字のなかには Unicode のコードポイントに対応する文字が存在しないことがあります。Unicode のコードポイントから求めることができないレガシーエンコーディングの文字が存在すると言い換えることもできます。

そのため、レガシーエンコーディングと Unicode のあいだで往復変換すると、変換前とは異なる文字に置き換わってしまうことがあります。肉眼では区別がつかない文字であっても、まったく違うデータになっている可能性があります。

マイクロソフトは SHIFT-JIS と Unicode のあいだで往復変換が保障されない文字のリストを公開し、注意を促しています。

往復変換が保障されないほかの事例は文字が定義されていない範囲です。この場合、ライブラリによって戻り値が異なったり、想定しない戻り値になる可能性があります。

想定しない文字の変換

想定しない文字を変換してしまう事例として PHP の mb_strtouppermb_strtolowermb_convert_case を取り上げます。確認したバージョンは PHP 7.1 です。これらの関数は大文字小文字変換をする関数です。問題は内部の処理において文字列を UTF-32 に変換してから大文字小文字を変換し、再び、もとのエンコーディングに戻すことをしています。そのため、大文字小文字以外の往復変換が保障され以内文字も変換してしまう問題を抱えています。具体的な文字は前述のマイクロソフトのサイトで確認できます。

大文字小文字は身近な処理ですが、トルコ語のような言語、ロケールごとの対応、半角だけでなく全角の大文字小文字を変換することなど以外と複雑です。全角半角の変換は ICU の Transliterator を使うことができます。

複数の変換候補のある絵文字

絵文字の変換は一対多の事例です。絵文字が Unicode に登録される前、通信キャリアはレガシーエンコーディングのコードポイントに独自の割り当てをしていました。Unicode の絵文字をレガシーエンコーディングに変換する場合、複数の変換候補から1つ選ばなければならないということです。PHP の場合、通信キャリアの名前を冠したエンコーディング (SJIS-Mobile#DOCOMO) の名前を登録しています。

変換表が導入されていない場合、想定しない文字に変換されてしまう可能性があります。また、通信キャリアの端末ごとに絵文字の形が大きく変わるので、絵文字に込められた意図が失われてしまう可能性があります。

ミャンマーでも絵文字と似た問題があります。フォント作成者ごとに Unicode のコードポイントに独自の割り当てをしているために、フォントを変更すると、文字を正しく表示できなくなります。正しく文字を表示するには、元のフォントのコードポイントから切替先のフォントのコードポイントへの対応表およびそれを使ったツールが必要になります。