アトミックズのホームページはPHPというプログラムで動かしてるんだけども、そのプログラムのことでちょっと記事を。
PHPの関数で「preg_match_all」ってのがあって、これは対象文字列の中から正規表現でヒットする箇所全てを抜き出す関数ってことで、ふつーに使ってる分にはいいんだけど、こいつにフラグをつけられるオプションがいくつかあって、そのオプションの中で「PREG_OFFSET_CAPTURE」ってのがあるんだわ。
これは、ヒットした全ての箇所においての位置情報(オフセット値)が返ってくるんだけど、バイト単位なんだよね。
僕が欲しかったのは日本語で使うマルチバイト単位の位置情報なので、そういう関数が無いかネット上で調べたんだけど、どうも無さそうだし、作ってるヒトも見当たらなかったので自分で書いてみたよ。
というわけで前置きが長くなったけど、コードをメモしておく~。
preg_match_all の PREG_OFFSET_CAPTURE で、
マルチバイトのオフセット値が返ってくる関数
<?php
function preg_match_all_mb_offset ($pattern, $moji, &$match_array) {
if (preg_match_all($pattern, $moji, $match, PREG_OFFSET_CAPTURE)) {
foreach ($match as &$value1) {
foreach ($value1 as &$value2) {
// バイト単位のoffset値をマルチバイト単位の値に書き換え
$value2[1] = mb_strlen(substr($moji, 0, $value2[1]));
}
}
$match_array = $match;
return true;
}
else {
return false;
}
}
// 使い方と結果はOFFSET値以外は preg_match_all と同じ。
// 勝手に PREG_OFFSET_CAPTURE モードで動きます。
$moji = "あいうえおかabcきくdeけこうえお";
$pattern = "うえお";
if (preg_match_all_mb_offset("/$pattern/is", $moji, $match_array)) {
print_r($match_array);
}
?>
上記のコードの実行結果は
オフセット値:「2」と「15」が返ってきます。
ちなみに通常の preg_match_all の PREG_OFFSET_CAPTURE オプションで実行すると、
オフセット値:「6」と「35」が返ってきます。
中でドロ臭い作業を繰り返してるので、もっと効率の良いコードがあるだろ!ってかたは、是非ツッコミお願い!
ちなみに、オフセット値を書き換えているコード↓
$value2[1] = mb_strlen(substr($moji, 0, $value2[1]));
の部分ですが、文字列の先頭から元々のオフセット値の「手前まで」を抽出したいので「-1」したくなるんだけど、ここはオフセット値ではなく「何個」抽出するかの「数」なので、0番目からの抽出ですから1つズレるんです。なので結果的に「-1」しなくて良い。また、マルチバイトのカウントも抽出した直後の文字をオフセット値としたいので文字数カウントに「+1」したいところですが、ここも0番目からの振り出しなので結果的に「+1」は必要無い。
$value2[1] = mb_strlen(substr($moji, 0, $value2[1])-1)+1;
↑と書いて、どハマリしたのは内緒だよ!
~追記~
もし対象文字列が何万文字もある場合、毎回先頭からの文字数を何度もカウントしてると効率が悪いので、文字列が膨大なら以下の function のほうが高速かも。(*前回の文字数カウントを引き継ぐ設計)
function preg_match_all_mb_offset ($pattern, $moji, &$match_array) {
if (preg_match_all($pattern, $moji, $match, PREG_OFFSET_CAPTURE)) {
foreach ($match as &$value1) {
$b = $mb = 0;
foreach ($value1 as &$value2) {
// バイト単位のoffset値をマルチバイト単位の値に書き換え(前回の続きからカウント)
$temp = $value2[1];
$value2[1] = $mb = $mb + mb_strlen(substr($moji, $b, $value2[1]-$b));
$b = $temp;
}
}
$match_array = $match;
return true;
}
else {
return false;
}
}
検索ロボット用:
preg_match preg_match_all mb_preg_match_all mb_preg_match PREG_OFFSET_CAPTURE offset マルチバイト 2バイト文字 オフセット値 関数