Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

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

PEARライブラリ活用 (5)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/07/15 14:00

Unicodeでは、意味的に同じ文字を複数の方法で表現することができます。しかし、表現がバラバラなままだと、検索などで問題が発生することは容易に想像できます。そのため、表記を統一する仕組みとして「正規化」が用意されています。本稿ではPEARのI18N_UnicodeNormalizerを用いて、PHPでUnicodeの正規化を行う方法を紹介します。

目次

はじめに

 与えられた文字列を含む文書を返す検索機能を実装しているところを想像してください。

 検索語として「ページ」が与えられれば、「ページ」という文字列を含む文書を返します。これは特に難しいことではありません。

 半角の「ページ」が与えられたらどうでしょう。「ページ」と「ページ」を区別する必要がないような、一般的な文書検索においては、「ページ」という文字列を含む文書を返すのが望ましいはずです(もちろん、この2つは常に同一視できるわけではありません。同一視できない例として本稿があります)。

 もしかしたら、「㌻」で検索しようとする人がいるかもしれませんし、日本語を母国語としない人が、「ぺ」(「ヘ」と半角の半濁点「゚」)や「ヘ゜」(半角カナ「ヘ」と半濁点「゜」)を使うかもしれません。

 人間なら簡単に対応できることですが、コンピュータで対応するには特別な処理が必要になります。例えばUnicodeにおいては、人間にとっては同じとみなせるような文字が複数登録され、それぞれに番号が振られています。Unicodeを使うシステムは、文字列を数字の列として扱うことがほとんどで、異なる数字列で表現された文字列が実は同じものなのだというような処理は、普通は行われないのです。このような問題は、日本語の濁点・半濁点や全角・半角以外にも、アクセントなどの修飾の付いた文字において起こりえます。

 異なる方法で表現された文字列を同一視するために、Unicodeでは「正規化」という仕組みが用意されています(Unicode Normalization Formsを参照)。半角の「ページ」や「㌻」を「ページ」と同一視することができるのです。

 正規化機能は複雑な処理ですが、多くのプログラミング言語であらかじめ用意されています。PHPならPEAR I18N_UnicodeNormalizer、.NET Framework 2.0以降ならString.Normalizeメソッド、Java 1.6以降ならjava.text.Normalizerによって正規化が可能です。

 本稿では、PEAR I18N_UnicodeNormalizerを使ってPHPで正規化を行う方法を紹介します。

対象読者

  • PHPの基本的な文法を知っている方

必要な環境

 PHPにPEAR I18N_UnicodeNormalizer 1.0.0をインストールした環境で動作を確認しました(インストール方法は後述)。

用語

 「全角」や「半角」という用語は、文字を表示する際に、その枠の幅が狭いものと広いものの2種類しかなかった時代のなごりです。広いものは狭いものの2倍の幅があったので、広いもので表示された文字は「全角文字」、狭い幅で表示された文字は「半角文字」と呼ばれました。この呼び名は今でも残っていますが(Halfwidth and Fullwidth Formsなどを参照)、文字枠の幅とは無関係です。本稿では、Unicodeの名前の付け方で呼び分けることにします。たとえば、ASCIIの「A」は単に「A」と呼びますが、「いわゆる全角のA」は「全角のA」と呼びます。同様に、カタカナの「ペ」(U+30da)は単に「ペ」と呼んで、「いわゆる半角のペ」を「半角のペ」と呼んでいます。

 半角文字を「1バイト文字」、全角文字を「2バイト文字」呼ぶことがありますが、この用語は使うべきではありません(本稿でも使いません)。半角文字が1バイト、全角文字が2バイトで表現されるのは、Shift_JISなどの一部の文字コードにおいてだけだからです。例えばUTF-8においては、半角カナのすべての文字や漢字の大部分は3バイトです(4バイト必要な漢字もあります)。

準備

 コマンドプロンプトから次のようにI18N_UnicodeNormalizerをインストールします。

 なお、Windows VistaとXAMPPで環境構築した場合は、コマンドの実行前に「c:\xampp\php\pear.ini」の「"\xampp」を「"C:\xampp」に修正しておいてください。

c:
cd \xampp\php
pear install I18N_UnicodeNormalizer

 PHPのファイルはUTF-8で作成し、次のように文字コードをUTF-8にしておきましょう。

mb_internal_encoding('UTF-8');

数値文字参照

 本稿で扱う文字はUTF-8のファイルならそのまま記述できるのですが、画面では違いが分かりにくいものがあるので、数値文字参照と使うことにしましょう。例えば、コードポイントが30daの文字つまり「ペ」(U+30da)は、ペ(16進表記)やペ(10進表記)と書くことができます。

 数値文字参照で表した1文字は、ブラウザ上では正しく表示されるのですが、PHPの内部では1文字として扱われません。長さを調べると分かります。

$str='ペ';
echo "<p>「{$str}」の長さは".mb_strlen($str).'</p>';//「ペ」の長さは8

 正しく扱うために、関数html_entity_decodeを使って文字列に変換します。この関数には、文字列とシングルおよびダブルクォートの扱い方、文字コードを引数として与えます(今はクォート文字はどうでもいいので、ENT_NOQUOTESとして変換しないようにしています)。

$str=html_entity_decode('&#x30da;',ENT_NOQUOTES,'UTF-8');
echo "<p>「{$str}」の長さは".mb_strlen($str).'</p>';//「ペ」の長さは1

 関数html_entity_decodeとは逆に、文字列を構成する各文字のコードポイントを知るための関数codePointsを用意しておきましょう。文字列を与えると、各文字のコードポイントの配列を返します(文字コードの変換に関数mb_convert_encodingを使っています。関数iconvでも文字コードを変換できますが、その際には、変換後にU+feffという画面には表示されない文字が勝手に挿入されてしまうので注意してください。$i=0ではなく$i=1forループを開始するといいでしょう)。

function codePoints($utf8_str){
    //入力をUTF-32に変換
    $utf32_str=mb_convert_encoding($utf8_str,'UTF-32','UTF-8');

    for($i=0;$i<mb_strlen($utf32_str,'UTF-32');++$i)
        //1文字ずつ16進文字列に変換
        $result[]=bin2hex(mb_substr($utf32_str,$i,1,'UTF-32'));
    return $result;
}

echo '<p>';
print_r(codePoints('ページ'));
//Array ( [0] => 000030da [1] => 000030fc [2] => 000030b8 )
echo '</p>';

 {ペ, ー, ジ}は{U+30da, U+30fc, U+30b8}であることが分かります。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

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

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2017年5月時点での登録メンバは52名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂き...

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

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XMLD...

バックナンバー

連載:PEARライブラリ活用

もっと読む

All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5