@blog.justoneplanet.info

日々勉強

PHP Perl-compatible Regular Expressions(Perl互換正規表現)

正規表現を使うと非常に複雑な文字列の抽出、検索や置換、分割が行える。正規表現とは検索パターンを表す文字列である。PHPではPOSIXの正規表現とPerlの正規表現をサポートしている。Perl-compatible Regular Expressionsは略してPCREと呼ばれる。

Perl互換正規表現 POSIXの正規表現
速度
機能
読みやすさ

最後の読みやすさについて、個人的にはPerl互換正規表現の方が読みやすいとは思う。

■PCRE正規表現の区切り文字

慣例ではスラッシュ「/」が使用されるが、urlなどを正規表現で表す際に、エスケープするのが面倒になるので#が使われることもある。通常では使用されないが、英数字とバックスラッシュ以外の文字ならば区切り文字として使用が可能である。

<?php
if(preg_match('/[a-z]/', 'string')){
    //code
}
if(preg_match('#(http://|https://)[a-zA-Z0-9./_-]+#', 'http://justoneplanet.info/')){
    //code
}
?>

■文字クラス

. 任意の一文字にマッチする
^ 文字列の最初にマッチする、もしくは行の先頭にマッチする(mフラグが有効な場合はnの次の文字)
$ 文字列の最後にマッチする、もしくは行の末尾にマッチする(mフラグが有効な場合はnの前の文字)
s 改行コード、半角スペース(全角スペースは含まない)、タブにマッチする [rn t]
d 全ての数字にマッチする [0-9]
D 全ての数字以外にマッチする [^0-9]
w 単語に使用する文字(アルファベット等)にマッチする [0-9a-zA-Z_]
W 単語に使用する文字(アルファベット等)以外にマッチする [^0-9a-zA-Z_]

全角スペースの扱いには要注意だ。その辺りが初心者のときは全然わからなかった。。。

■量指定子

貪欲 貪欲でない
* 直前の文字の0個以上の繰り返し *?
+ 直前の文字の1個以上の繰り返し +?
? 直前の文字が0個もしくは1個だけ存在する(直前の文字が繰り返しではないことを意味する) ??
{n} 直前の文字がn個繰り返し {n}?
{n,} 直前の文字が少なくともn個繰り返し {n,}?
{n,m} 直前の文字が最低n個、m個以下の繰り返し {n,m}?

貪欲でない量指定子の使用法

以下のように通常「.*」を使用すると、一つ目のstrongタグの開始から二つ目のstrongタグの終了までマッチしてしまう。残りのパターンを満たす限りできるだけ短い範囲でマッチするようにするには貪欲でない量指定子を使用する。

<?php
preg_match('/(<.*>)/', '<strong>PHP</strong>のPerl互換正規表現', $match);
var_dump($match);
/*
array(2) {
  [0]=>
  string(20) "<strong>PHP</strong>"
  [1]=>
  string(20) "<strong>PHP</strong>"
}
*/
preg_match('/(<.*?>)/', '<strong>PHP</strong>のPerl互換正規表現', $match);
var_dump($match);
/*
array(2) {
  [0]=>
  string(8) "<strong>"
  [1]=>
  string(8) "<strong>"
}
*/
?>

但し、以下のコードの方が高速である。

<?php
preg_match('/(<[^>]*>)/', '<strong>PHP</strong>のPerl互換正規表現', $match);
var_dump($match);
/*
array(2) {
  [0]=>
  string(8) "<strong>"
  [1]=>
  string(8) "<strong>"
}
*/
?>

■選択肢

以下のように「|」の記号を使って正規表現内で選択肢を指定することができる。

<?php
preg_match_all('/dog|cat/i', 'Dogs and Cats is so cute!!', $matches);
var_dump($matches);
/*
array(1) {
  [0]=>
  array(2) {
    [0]=>
    string(3) "Dog"
    [1]=>
    string(3) "Cat"
  }
}
*/
?>

但し「dogもしくはcatで始まる文字列」とする場合、以下のようにグルーピングしなければならない。

<?php
preg_match_all('/^(cat|dog)/i', 'Dogs and Cats is so cute!!', $matches);
var_dump($matches);
/*
array(2) {
  [0]=>
  array(1) {
    [0]=>
    string(3) "Dog"
  }
  [1]=>
  array(1) {
    [0]=>
    string(3) "Dog"
  }
}
*/
?>

■サブパターン

パターンの一部を()で括ると後でキャプチャとして使用することができる。また、以下のようにグルーピングしてパターン化することができる。

<?php
preg_match('/(abc)+/', 'abcabcbbceed', $match);
var_dump($match);
/*
array(2) {
  [0]=>
  string(6) "abcabc"
  [1]=>
  string(3) "abc"
}
*/
?>

上述の例では、グルーピングしたabcの繰り返しにマッチするかどうかがチェックされる。また、$match[1]にはキャプチャした文字列が格納されている。ちなみにキャプチャする必要がない場合は以下のようにする。

<?php
preg_match('/(?:abc)+/', 'abcabcbbceed', $match);
var_dump($match);
/*
array(1) {
  [0]=>
  string(6) "abcabc"
}
*/
?>

■後置きオプション

i 大文字、小文字を区別しない
m ^を改行直後の文字、$を改行直前の文字にマッチさせる(マルチラインモード)
e 置換文字にPHPのコードを指定すると、evalした結果で文字列を置き換える(使い方によっては危険)

後置きオプションは以下のように使用する。

<?php
preg_match('/(?:abc)+/i', 'abcabcbbceed', $match);
var_dump($match);
/*
array(1) {
  [0]=>
  string(6) "abcabc"
}
*/
?>

■先読みと戻り読み

以下全てキャプチャは行わない。

(?=pattern) 肯定先読み
(?!pattern) 否定先読み
(?<=pattern) 肯定戻り読み
(?<!pattern) 否定戻り読み

先読み(次の文字が~だったら)と、戻り読み(前の文字が~だったら)は以下のように使用する。

<?php
preg_match('/(?<=<h4>).+(?=[0-9])/', '<h4>story1', $match);
var_dump($match);
/*
array(1) {
  [0]=>
  string(5) "story"
}
*/
?>

これは非常に強力である。ちなみに、JavaScriptには「戻り読み」が存在しない。

■Perl互換正規表現での文字列のマッチングと抽出

文字列のマッチングと抽出を行うには、以下のようにpreg_match関数を使用する。第一引数では検索パターンを指定し、第二引数では対象文字列を指定する。以降はオプションで、第三引数ではマッチした文字列が格納される変数を指定する。

<?php
$str = 'She sells seashells.';
if(preg_match('/she/im', $str, $matches)){
    var_dump($matches);
}
/*
array(1) {
  [0]=>
  string(3) "She"
}
*/
?>
<?php
$str = 'She sells seashells.';
if(preg_match('/(She)s(sell)s*s(seashell)s*/', $str, $matches)){
    var_dump($matches);
}
/*
array(4) {
  [0]=>
  string(19) "She sells seashells"
  [1]=>
  string(3) "She"
  [2]=>
  string(4) "sell"
  [3]=>
  string(8) "seashell"
}
*/
?>

preg_match関数は、1回マッチした時点で検索を終了するので、戻り値が1より大きくなることはない。従って、全て検索させたい場合には、以下のようにpreg_match_all関数を使用する。

<?php
$str = 'She sells seashells.';
if(preg_match_all('/she/i', $str, $matches)){
    var_dump($matches);
}
/*
array(1) {
  [0]=>
  array(2) {
    [0]=>
    string(3) "She"
    [1]=>
    string(3) "she"
  }
}
*/
?>

各関数について

int preg_match(string $pattern, string $str[, array $matches])
マッチした回数を返すが0か1となる。従って、全て調べるにはpreg_match_all関数を使用しなければならない。マッチした部分については$matchesに格納される。
int preg_match_all(string $pattern, string $str, array $matches)
パターンがマッチした総数を返す(0も含む)。但しエラーが発生した場合はfalseを返す。

■Perl互換正規表現での文字列の置換

以下のようにpreg_replace関数を使って、Perl互換正規表現での文字列置換を行う。この例では独自のタグをHTMLタグに変換する処理を行っている。いくつかのCMSにはこのようなタグが導入されている。これはユーザーのHTML入力を安全にする方法の一つである。

<?php
$str = '[b]Make Me Bold![/b]';
$str = preg_replace('#[b](.*?)[/b]#i', '<strong>$1</strong>', $str);
var_dump($str);
/*string(30) "<strong>Make Me Bold!</strong>"*/
?>

また、以下のように検索パターンと置換文字列に配列を指定することもできる。

<?php
$subjects['body'] = '[b]Make Me Bold![/b]';
$subjects['title'] = '[i]Make Me Italics![/i]';

$reg[] = '#[b](.*?)[/b]#i';
$reg[] = '#[i](.*?)[/i]#i';

$rep[] = '<strong>$1</strong>';
$rep[] = '<i>$1</i>';

$results = preg_replace($reg, $rep, $subjects);
var_dump($results);
/*
array(2) {
  ["body"]=>
  string(30) "<strong>Make Me Bold!</strong>"
  ["title"]=>
  string(23) "<i>Make Me Italics!</i>"
}
*/
?>

検索パターンの配列と置換文字列の配列の長さを比較し、検索パターンが多く置換文字列がない場合は空文字が置換文字列となる。また検索パターンのみ配列ということもあり得る。

各関数について

mixed preg_replace(mixed $pattern, mixed $replace,mixed $str)
正規表現$patternでマッチングを行い$replaceに置換する。

コメントはまだありません»

No comments yet.

RSS feed for comments on this post.TrackBack URL

Leave a comment