SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

PEARライブラリ活用

PHPにおけるUnicode文字列の正規化

PEARライブラリ活用 (5)

  • X ポスト
  • このエントリーをはてなブックマークに追加

正規化

 準備ができたので、さっそく正規化の実験をしてみましょう。例として、「ページ」の「ペ」を取り上げます。

 カタカナの「ヘ」にはいわゆる全角と半角の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種類があります(HiraganaHalfwidth 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(正規分解)
  • 文字列中の各文字を、「正規マッピング」という規則で分解し、「正規順序」という順番で並べること。あるいはその結果。正規分解の結果が互いに等しいことを正規等価性(Canonical Equivalence)という。たとえば「ペ」は、NFDによって「ヘ●」の2文字に分解される。
     
  • NFC(正規分解とそれに続く正規合成)
  • 正規分解の結果を正規合成(“ひらがな+濁点”のような「合成済み文字」を合成)すること。あるいはその結果。たとえば「ヘ●」は、NFCによって「ペ」になる。
     
  • NFKD(互換分解)
  • 文字列中の各文字を、「正規マッピング」および「互換マッピング」という規則で分解し、「正規順序」で並べること。あるいはその結果。互換分解の結果が互いに等しいことを互換等価性(Compatibility Equivalence)という。「互換マッピング」によって、半角カナをいわゆる全角カナにしたり、丸付き数字や全角数字をASCIIの数字に置き換えたりすることができる。たとえば「㌻」は、NFKDによって「ヘ●ーシ○」の5文字に分解される(「○」はU+3099, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)。
     
  • NFKC(互換分解とそれに続く正規合成)
  • 互換分解の結果を正規合成。たとえば「㌻」は、NFKCによって「ページ」の3文字になる。

 先に定義したすべての文字列に対して、すべての正規化形式を試してみましょう。

$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)の結果を見ると分かります。

「ペ」 Array ( [0] => 0000ff8d [1] => 0000ff9f ) の結果
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)に分解することができます。

「ペ」 Array ( [0] => 000030da ) の結果
NFD    ヘ●  Array ( [0] => 000030d8 [1] => 0000309a )
NFC    ペ    Array ( [0] => 000030da )
NFKD   ヘ●  Array ( [0] => 000030d8 [1] => 0000309a )
NFKC   ペ    Array ( [0] => 000030da )

 「ヘ」(U+30d8)と「●」(U+309a)は、合成すれば「ペ」になります。

「ヘ●」 Array ( [0] => 000030d8 [1] => 0000309a ) の結果
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)は半濁点そのものであって、他の文字と結合させて使うものではないからです。

「ヘ゜」 Array ( [0] => 000030d8 [1] => 0000309c ) の結果
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 )

 半角カナの「ヘ」と半濁点でも、結合すれば「ペ」になります。

「ヘ●」 Array ( [0] => 0000ff8d [1] => 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)ではうまくいかないのは、先の場合と同じです。

「ヘ゜」 Array ( [0] => 0000ff8d [1] => 0000309c ) の結果
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 )

 「ヘ」と半角カナの半濁点でも、結合すれば「ペ」になります。

「ペ」 Array ( [0] => 000030d8 [1] => 0000ff9f ) の結果
NFD    ペ   Array ( [0] => 000030d8 [1] => 0000ff9f )
NFC    ペ   Array ( [0] => 000030d8 [1] => 0000ff9f )
NFKD   ヘ●  Array ( [0] => 000030d8 [1] => 0000309a )
NFKC   ペ    Array ( [0] => 000030da )

 「㌻」でさえも、互換分解して結合すれば「ページ」になります。

「㌻」 Array ( [0] => 0000333b ) の結果
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を使いましょう。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
PEARライブラリ活用連載記事一覧

もっと読む

この記事の著者

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、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本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

WINGSプロジェクト 矢吹 太朗(ヤブキ タロウ)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/2668 2008/07/15 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング