ダウンロード サンプルファイル (1.3 KB)
正規化
準備ができたので、さっそく正規化の実験をしてみましょう。例として、「ページ」の「ペ」を取り上げます。
カタカナの「ヘ」にはいわゆる全角と半角の2種類があります。半濁点(マル)にはU+309a(KATAKANA-HIRAGANA SEMIVOICED SOUND MARK)とU+309c(COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK)、U+ff9f(HALFWIDTH KATAKANA SEMIVOICED SOUND MARK)の3種類があります(HiraganaやHalfwidth and Fullwidth Formsを参照)。
かなり特殊なものですが、CJK Compatibilityを見ると分かるように、1文字で「ページ」を表すU+333b(SQUARE PEEZI)もUnicodeには登録されています(個人的にはあまり使わない方がいいとは思いますが、一部(38文字。U+333bも含む)はJISにも登録されているので、「使うな」というわけにもいきません)。
ここは次のような8つの文字列を考えればよいでしょう。本稿執筆時点でのCodeZineの文字コードはいわゆるShift_JIS(正確にはWindows-31J)なので、U+309a (COMBINING KATAKANA-HIRAGANA SEMIVOICED SOUND MARK)を扱えません。そのため、U+309aに相当する部分は「●」としています(数値文字参照も使えないのです)。
| 文字(列) | 数値文字参照 | 補足 |
| ペ | ペ | 最も標準的なもの |
| ヘ● | ペ | 「ヘ」とCOMBINING KATAKANA-HIRAGANA SEMIVOICED SOUND MARK |
| ヘ゜ | ヘ゜ | 「ヘ」とKATAKANA-HIRAGANA SEMI-VOICED SOUND MARK |
| ペ | ペ | 半角の「ヘ」とHALFWIDTH KATAKANA SEMIVOICED SOUND MARK |
| ヘ● | ペ | 半角の「ヘ」とCOMBINING KATAKANA-HIRAGANA SEMIVOICED SOUND MARK |
| ヘ゜ | ヘ゜ | 半角の「ヘ」とKATAKANA-HIRAGANA SEMIVOICED SOUND MARK |
| ペ | ペ | 「ヘ」とHALFWIDTH KATAKANA SEMIVOICED SOUND MARK |
| ㌻ | ㌻ | SQUARE PEEZI |
Unicodeのファイルにならそのまま書いてもいいのですが、目で見て区別しやすいように数値文字参照を使って定義します。
$tmp=array( 'ペ',//ペ 'ペ',//ヘ● 'ヘ゜',//ヘ゜ 'ペ',//ペ 'ペ',//ヘ● 'ヘ゜',//ヘ゜ 'ペ',//ペ '㌻'//㌻ ); $strs=array(); foreach($tmp as $s)//数値文字参照を文字列に変換 $strs[]=html_entity_decode($s,ENT_NOQUOTES,'UTF-8'); echo '<p>'; print_r($strs);//確認 //Array ( [0] => ペ [1] => ヘ● [2] => ヘ゜ [3] => ペ // [4] => ヘ● [5] => ヘ゜ [6] => ペ [7] => ㌻ ) echo '</p>';
実験用の文字列を配列に格納しました。
正規化の形式
一口に「正規化」と言っても、それには次の4つの形式があります。
- NFD(正規分解)
- NFC(正規分解とそれに続く正規合成)
- NFKD(互換分解)
- NFKC(互換分解とそれに続く正規合成)
先に定義したすべての文字列に対して、すべての正規化形式を試してみましょう。
$types=array('NFD','NFC','NFKD','NFKC');//正規化形式 $normalizer=new I18N_UnicodeNormalizer();//正規化用のオブジェクト foreach($strs as $str){//実験用の各文字列について、 echo "<p>「{$str}」 ";print_r(codePoints($str));echo ' の結果</p>'; echo '<table>'; foreach($types as $type){//各形式で、 $result=$normalizer->normalize($str,$type);//正規化する echo '<tr>'; echo "<td>$type</td>"; echo "<td>$result</td>"; echo '<td>';print_r(codePoints($result));echo '</td>'; echo "</tr>"; } echo '</table>'; }
正規分解と互換分解の違いは、「ペ」(U+ff8d,U+ff9f)の結果を見ると分かります。
NFD ペ Array ( [0] => 0000ff8d [1] => 0000ff9f ) NFC ペ Array ( [0] => 0000ff8d [1] => 0000ff9f ) NFKD ヘ● Array ( [0] => 000030d8 [1] => 0000309a ) NFKC ペ Array ( [0] => 000030da )
互換分解の際に、半角カナの「ヘ」は「ヘ」に、半角カナの半濁点「゚」は半濁点「●」に置き換えられています。正規分解ではこのようなことは起こりません。
他の結果も載せておきましょう。
「ペ」は「ヘ」(U+30d8)と「●」(U+309a)に分解することができます。
NFD ヘ● Array ( [0] => 000030d8 [1] => 0000309a ) NFC ペ Array ( [0] => 000030da ) NFKD ヘ● Array ( [0] => 000030d8 [1] => 0000309a ) NFKC ペ Array ( [0] => 000030da )
「ヘ」(U+30d8)と「●」(U+309a)は、合成すれば「ペ」になります。
NFD ヘ● Array ( [0] => 000030d8 [1] => 0000309a ) NFC ペ Array ( [0] => 000030da ) NFKD ヘ● Array ( [0] => 000030d8 [1] => 0000309a ) NFKC ペ Array ( [0] => 000030da )
「ヘ」(U+30d8)と「゜」(U+309c)は、合成しても「ペ」にはなりません。「゜」(U+309c)は半濁点そのものであって、他の文字と結合させて使うものではないからです。
NFD ヘ゜ Array ( [0] => 000030d8 [1] => 0000309c )
NFC ヘ゜ Array ( [0] => 000030d8 [1] => 0000309c )
NFKD ヘ ● Array ( [0] => 000030d8 [1] => 00000020
[2] => 0000309a )
NFKC ヘ ● Array ( [0] => 000030d8 [1] => 00000020
[2] => 0000309a )
半角カナの「ヘ」と半濁点でも、結合すれば「ペ」になります。
NFD ヘ● Array ( [0] => 0000ff8d [1] => 0000309a ) NFC ヘ● Array ( [0] => 0000ff8d [1] => 0000309a ) NFKD ヘ● Array ( [0] => 000030d8 [1] => 0000309a ) NFKC ペ Array ( [0] => 000030da )
「゜」(U+309c)ではうまくいかないのは、先の場合と同じです。
NFD ヘ゜ Array ( [0] => 0000ff8d [1] => 0000309c )
NFC ヘ゜ Array ( [0] => 0000ff8d [1] => 0000309c )
NFKD ヘ ● Array ( [0] => 000030d8 [1] => 00000020
[2] => 0000309a )
NFKC ヘ ● Array ( [0] => 000030d8 [1] => 00000020
[2] => 0000309a )
「ヘ」と半角カナの半濁点でも、結合すれば「ペ」になります。
NFD ペ Array ( [0] => 000030d8 [1] => 0000ff9f ) NFC ペ Array ( [0] => 000030d8 [1] => 0000ff9f ) NFKD ヘ● Array ( [0] => 000030d8 [1] => 0000309a ) NFKC ペ Array ( [0] => 000030da )
「㌻」でさえも、互換分解して結合すれば「ページ」になります。
NFD ㌻ Array ( [0] => 0000333b )
NFC ㌻ Array ( [0] => 0000333b )
NFKD ヘ●ーシ○ Array ( [0] => 000030d8 [1] => 0000309a
[2] => 000030fc [3] => 000030b7
[4] => 00003099 )
NFKC ページ Array ( [0] => 000030da [1] => 000030fc
[2] => 000030b8 )
ここではすべてを「ペ」(U+30da)に統一したかったのですが、NFKCで正規化すれば目的が達せられることが分かります。
おわりに
Unicodeにおいては、意味的には同じ文字を複数の方法で表現することができます。表現がバラバラなままだと、検索などにおいて問題が発生することは容易に想像できます。そのため、表記を統一する仕組みが必要になりますが、それが正規化です。
PHPにおいては、PEAR I18N_UnicodeNormalizerを用いることで簡単に正規化を行うことができます。本稿では、実際に複数の方法で表現した文字を、正規化によって統一する方法を紹介しました。
PHPの将来のバージョンには、正規化のためのクラス(Normalizer)が組み込まれる予定です。そうなれば、本稿で紹介したPEAR I18N_UnicodeNormalizerをあえて使う必要はなくなるでしょう(その際にはぜひPEAR I18N_UnicodeNormalizerはdeprecatedだと明言してほしいものです)。しかし、CVS版などを無理に持ってこない限りNormalizerが使えない現段階では、既に仕様が固まっているPEAR I18N_UnicodeNormalizerを使いましょう。
この記事は参考になりましたか?
既に会員の方はを行ってください。
- この記事の著者
-
山田 祥寛(ヤマダ ヨシヒロ)
静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
-
WINGSプロジェクト 矢吹 太朗(ヤブキ タロウ)
<WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア
