PHPで日本語テキストを正規化

(1/1)
新聞記事をメモするとき、英字が全角であったり、漢数字が混じっていたりする。プログラムで処理するには半角で統一されていた方が都合がいい。
そこで今回は、PHPを使い、全角英字や漢数字混じりの日本語テキストの全角・半角を統一し、算用数字を漢字に変換することもできるようにするプログラムをつくってみることにする。
プログラムは、「PHPで漢数字混じりのテキストを半角数字に統一する」の発展形である。

数字ではなく単語として使われている漢数字「一緒」「三大祭」を認識させるため、「PHPで MeCabのユーザー辞書を作成する」で紹介した MeCab、または「PHPで形態素解析を行う」で紹介した Yahoo!JAPAN 日本語形態素解析 Web サービスを利用し、テキストを形態素に分解してから正規化を行う。

(2021年7月25日)PHP8対応,リファラ・チェック改良

目次

サンプル・プログラムの実行例

PHPで日本語テキストを正規化

サンプル・プログラム

圧縮ファイルの内容
NormalizeText.phpサンプル・プログラム本体
pahooNormalizeText.phpテキスト正規化クラス pahooNormalizeText。
テキスト正規化クラスの使い方は「PHPで日本語テキストを正規化」を参照ください。include_pathが通ったディレクトリに配置してください。
special_table.txt特殊文字テーブル

解説:準備

0012: class pahooNormalizeText {
0013:     var $items;      //検索結果格納用
0014:     var $error;      //エラーフラグ
0015:     var $errmsg; //エラーメッセージ
0016:     var $hits;       //検索ヒット件数
0017:     var $webapi; //直前に呼び出したWebAPI URL
0018: 
0019:     //Yahoo! JAPAN Webサービス アプリケーションID
0020:     //https://developers.google.com/maps/documentation/javascript/get-api-key
0021:     var $YAHOO_APPLICATION_ID = '**********************************';
0022: 
0023:     //gooラボ アプリケーションID
0024:     //https://labs.goo.ne.jp/apiregister/
0025:     var $GOOLABS_APPLICATION_ID = '***********************************';
0026: 
0027:     //MeCabの実行プログラム;各自の環境に合わせて変更のこと
0028:     var $MECAB = 'C:\Program Files (x86)\MeCab\bin\mecab.exe';
0029:     //ユーザー辞書
0030:     var $FILE_UDIC_MECAB =  'C:\Program Files (x86)\MeCab\dic\user_wiki.dic';
0031:     //特殊変換ファイル名
0032:     var $FILE_SPECIAL = 'special_table.txt';
0033: 
0034:     //正規化モード
0035:     var $OPTION_SPC_TRIM1 = 't';        //行頭・行末の空白文字を除く
0036:     var $OPTION_SPC_TRIM2 = 'T';        //全角文字と隣り合う空白文字を除く
0037:     var $OPTION_NUM_HAN   = 'n';        //数字を半角に統一
0038:     var $OPTION_NUM_ZEN   = 'N';        //数字を全角に統一
0039:     var $OPTION_NUM_KAN   = 'K';        //数字を漢字に統一
0040:     var $OPTION_ALP_HAN   = 'a';        //英字を半角に統一
0041:     var $OPTION_ALP_ZEN   = 'A';        //英字を全角に統一
0042:     var $OPTION_YAK_HAN   = 'y';        //記号を半角に統一
0043:     var $OPTION_YAK_ZEN   = 'Y';        //記号を全角に統一
0044:     var $OPTION_KATA_HAN  = 'h';        //カタカナを半角に統一
0045:     var $OPTION_KATA_ZEN  = 'H';        //カタカナを全角に統一
0046:     var $OPTION_SPEC_HAN  = 's';        //特殊文字を半角に統一
0047:     var $OPTION_SPEC_ZEN  = 'S';        //特殊文字を全角に統一
0048: 
0049:     //年号エスケープ記号(元号・西暦変換させない)
0050:     var $ESCYEAR = '\\';

テキストの正規化に必要なプログラムは、pahooNormalizeText クラスとして別ファイルに分離した。

PHPで形態素解析を行う」で紹介した Yahoo! JAPAN Webサービスを利用するには Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を参照されたい。

形態素解析として MeCab を使う場合は、実行プログラムのパスを $MECAB に、ユーザー辞書を $FILE_UDIC_MECAB に、作業ファイルを $TMP_FNAME に代入しておく。MeCabユーザー辞書の作り方は「PHPで MeCabのユーザー辞書を作成する」を参照いただきたい。

それから、変換オプションとして変数 $OPTION_ シリーズを用意しておく。

解説:記号の全角⇔半角相互変換

0467: /**
0468:  * 記号の全角⇔半角相互変換
0469:  * @param   string $str入力テキスト
0470:  * @param   int $mode変換モード:0=半角から全角, 1=全角から半角
0471:  * @return  string変換後文字列
0472: */
0473: function convert_yakumono($str$mode) {
0474:     $tbl = array(
0475:         array(' ', ' '),      // 半角スペース⇔全角スペース
0476:         array('!', ''),
0477:         array('\'', ''),
0478:         array('#', ''),
0479:         array('$', ''),
0480:         array('%', ''),
0481:         array('&', ''),
0482:         array("'", ''),
0483:         array('(', ''),
0484:         array(')', ''),
0485:         array('*', ''),
0486:         array('+', ''),
0487:         array(',', ''),
0488:         array('-', ''),
0489:         array('.', ''),
0490:         array('/', ''),
0491:         array(':', ''),
0492:         array(';', ''),
0493:         array('<', ''),
0494:         array('=', ''),
0495:         array('>', ''),
0496:         array('?', ''),
0497:         array('@', ''),
0498:         array('[', ''),
0499:         array('\\', ''),
0500:         array(']', ''),
0501:         array('^', ''),
0502:         array('_', '_'),
0503:         array('`', ''),
0504:         array('{', ''),
0505:         array('|', ''),
0506:         array('}', ''),
0507:         array('~', ''),
0508:         array('', ''),
0509:         array('', ''),
0510:         array('', ''),
0511:         array('', ''),
0512:         array('', '')
0513:     );
0514: 
0515:     foreach ($tbl as $yakumono) {
0516:         $ch1 = $yakumono[$mode];
0517:         $ch2 = $yakumono[1 - $mode];
0518:         $str = str_replace($ch1$ch2$str);
0519:     }
0520: 
0521:     return $str;
0522: }

PHPには、英数字の全角⇔半角相互変換するための組み込み関数  mb_convert_kana  があるが、これだけだと記述記号(約物)の変換ができない。そこで、ユーザー関数 convert_yakumono を用意した。

仕組みは簡単で、半角記号と全角記号のペアを配列 $tbl に用意しておき、入力文字を1文字1文字比較して、変換するというもの。
この配列を追加・削除すれば、全角⇔半角変換を自在に設定できる。

解説:特殊文字の全角⇔半角相互変換

0524: /**
0525:  * 特殊文字の全角⇔半角相互変換
0526:  * @param   string $sour入力テキスト
0527:  * @param   int $mode変換モード:0=半角から全角, 1=全角から半角
0528:  * @return  string変換後文字列
0529: */
0530: function convert_special($sour$mode) {
0531:     $tbl = array();
0532: 
0533:     //特殊文字テーブル読み込み
0534:     $infp = @fopen($this->FILE_SPECIAL, 'r');
0535:     if ($infp == FALSE) {
0536:         $this->error = TRUE;
0537:         $this->errmsg = $this->FILE_SPECIAL . ' が見つからない.';
0538:         return FALSE;
0539:     }
0540:     $cnt = 0;
0541:     while (! feof($infp)) {
0542:         $str = trim(fgets($infp));
0543:         $arr = preg_split("/\t/ui", $str);
0544:         if (count($arr) >= 2) {
0545:             $tbl[$cnt][0] = $arr[0];
0546:             $tbl[$cnt][1] = $arr[1];
0547:             $cnt++;
0548:         }
0549:     }
0550: 
0551:     //変換
0552:     foreach ($tbl as $special) {
0553:         $ch1 = $special[$mode];
0554:         $ch2 = $special[1 - $mode];
0555:         $sour = str_replace($ch1$ch2$sour);
0556:     }
0557: 
0558:     return $sour;
0559: }

記号とは別に、あらかじめ用意した変換テーブル(テキストファイル)にしたがい、全角⇔半角相互変換を行うユーザー関数 convert_special を用意した。

変換テーブルのファイル名は変数 $FILE_SPECIAL に入れておく。変換テーブルはテキストファイルで、1行が1文字の変換に対応しており、タブの左側に半角文字を、右側に対応する全角文字を記述する。

変換の仕組みは、前述のユーザー関数 convert_yakumono と同じである。
したがって、じつは、左側は半角文字でなくても構わないし、右側は全角文字でなくとも構わない。また、各々が2文字以上になっても大丈夫。
サンプルとして用意した変換テーブルには、"@笑顔@" を "😀" に変換するようなルールを記述してある。

解説:日本語テキストを半角に統一

0719: /**
0720:  * 日本語テキストを半角に統一
0721:  * @param   string $str  漢数字混じりテキスト
0722:  * @param   bool   $trim 行頭・行末の空白を除くかどうか
0723:  * @param   int    $method形態素分解方式
0724:  *                      0=MeCab, それ以外=Yahoo!JAPAN日本語形態素解析Webサービス
0725:  * @return  strin変換後テキスト
0726: */
0727: function toHankaku($str$trim$method) {
0728:     //数字パターン
0729:     $pat_kannum = '/^[^数]*[01234567890123456789○〇一二三四五六七八九十百千万億兆京]+$/ui';
0730: 
0731:     //中黒の小数点
0732:     $str = preg_replace_callback('/([0123456789○〇一二三四五六七八九十百千万億兆京]+)・([01234567890123456789○〇一二三四五六七八九十百千万億兆京]+)/iu',
0733:     function ($mat) {
0734:         return $this->kan2num($mat[1], 3) . '.' . $this->kan2num($mat[2], 3);
0735:     }, $str);
0736: 
0737:     //形態素に分解
0738:     $items = array();
0739:     if ($method == 0)   $this->getParseMeCab($str$items);
0740:     else                $this->getParseYahoo($str$items);
0741: 
0742:     //変換処理
0743:     $dest = '';
0744:     $flag = FALSE;
0745:     $numstr = '';
0746:     $i = 0;
0747:     for ($i = 0; $i < count($items); $i++) {
0748:         if ($flag == FALSE) {
0749:             //漢数字の1文字目
0750:             if ((preg_match($pat_kannum$items[$i]['surface']) > 0) && (preg_match('/[数|幾]|副詞*/ui', $items[$i]['pos']) > 0)) {
0751:                 $numstr = $items[$i]['surface'];
0752:                 $flag = TRUE;
0753:             //数字ではない
0754:             } else {
0755:                 $dest .= $items[$i]['surface'];
0756:             }
0757:         } else {
0758:             //漢数字の2文字目以降
0759:             if (preg_match($pat_kannum$items[$i]['surface']) > 0) {
0760:                 $numstr .= $items[$i]['surface'];
0761:                 $flag = TRUE;
0762:             //数字以外
0763:             } else {
0764:                 //ここまでの漢数字を半角数字に
0765:                 $dest .= $this->kan2num($numstr, 2) . $items[$i]['surface'];
0766:                 $numstr = '';
0767:                 $flag = FALSE;
0768:             }
0769:         }
0770:     }
0771: 
0772:     if ($flag == TRUE) {
0773:         $dest .= $this->kan2num($numstr, 2);
0774:     }
0775: 
0776:     //英字などの半角変換
0777:     $dest = $this->convert_special($dest, 1);
0778:     $dest = mb_convert_kana($dest, 'ask');
0779:     $dest = $this->convert_yakumono($dest, 1);
0780: 
0781:     //行頭・行末空白処理
0782:     if ($trim) {
0783:         $arr = preg_split("/\n/iu", $dest);
0784:         $dest = '';
0785:         foreach ($arr as $str) {
0786:             $str = preg_replace('/^[  \t\n\r]+/ui', '', $str); //行頭空白削除
0787:             $str = preg_replace('/[  \t\n\r]+$/ui', '', $str);  //行末空白削除
0788:             $dest .= $str . "\n";
0789:         }
0790:     }
0791: 
0792:     return $dest;
0793: }

入力されたテキストは、すべて半角に統一し、それから変換オプションに応じて全角や漢数字に変換することにする。
半角に変換するのを担当するのがユーザー関数 toHankaku だ。

まず、中黒の小数点が混じっていたら、組み込み関数  preg_replace_callback  を使って、半角数字(小数)に変換する。

次に、テキストを形態素に分解する。
前述の通り、MeCab または Yahoo!JAPAN 日本語形態素解析 Web サービス を利用する。
形態素に分解できたら、漢数字を半角数字に変換していく。

最後に、英字などの半角変換を組み込み関数  mb_convert_kana  で、記号はユーザー関数 convert_yakumono で、行頭・行末空白処理を組み込み関数  preg_replace  を使って処理する。

解説:日本語テキストを正規化

0795: /**
0796:  * 日本語テキストを正規化する
0797:  * @param   string $str  漢数字混じりテキスト
0798:  * @param   string $option変換オプション
0799:  * @param   bool   $trim 行頭・行末の空白を除くかどうか
0800:  * @param   int    $method形態素分解方式
0801:  *                      0=MeCab, それ以外=Yahoo!JAPAN日本語形態素解析Webサービス
0802:  * @return  string変換後テキスト
0803: */
0804: function normalizeText($str$option$trim$method) {
0805:     //全角文字と隣り合う空白文字を除く
0806:     if (strstr($option$this->OPTION_SPC_TRIM2) != FALSE) {
0807:         $str = preg_replace('/[  \t]+([\p{Han}\p{Hiragana}\p{Katakana}].)/ui', '\1', $str);
0808:     }
0809: 
0810:     //いったん半角に
0811:     $dest = $this->toHankaku($str$trim$method);
0812: 
0813:     //特殊変換:半角→全角変換
0814:     if (strstr($option$this->OPTION_SPEC_ZEN) != FALSE) {
0815:         $dest = $this->convert_special($dest, 0);
0816:     }
0817:     //数字:半角→全角変換
0818:     if (strstr($option$this->OPTION_NUM_ZEN) != FALSE) {
0819:         $dest = mb_convert_kana($dest, 'N');
0820:     //数字:半角→漢数字変換
0821:     } else if (strstr($option$this->OPTION_NUM_KAN) != FALSE) {
0822:         $dest = preg_replace_callback('/[0-9\.]+/ui', array($this, "num2kan_decimal"), $dest);
0823:     }
0824:     //英字:半角→全角変換
0825:     if (strstr($option$this->OPTION_ALP_ZEN) != FALSE) {
0826:         $dest = mb_convert_kana($dest, 'R');
0827:     }
0828:     //記号:半角→全角変換
0829:     if (strstr($option$this->OPTION_YAK_ZEN) != FALSE) {
0830:         $dest = $this->convert_yakumono($dest, 0);
0831:     }
0832:     //カタカナ:半角→全角変換
0833:     if (strstr($option$this->OPTION_KATA_ZEN) != FALSE) {
0834:         $dest = mb_convert_kana($dest, 'VK');
0835:     }
0836:     //数字:半角変換
0837:     if (strstr($option$this->OPTION_NUM_HAN) != FALSE) {
0838:         $dest = $this->hanfloat($dest);
0839:     }
0840: 
0841:     return $dest;
0842: }

入力テキストを正規化するのはユーザー関数 normalizeText である。

前述のように、まずユーザー関数 toHankaku によってテキスト全体を半角に統一する。
続いて、変換オプションの値に応じて全角や漢数字に変換していく。

参考サイト

(この項おわり)
header