PHP 8.4.0 RC4 available for testing

条件付きサブパターン

サブパターンを条件付きでマッチング処理させることが可能です。 言明の結果や直前のキャプチャ用サブパターンがマッチしたかどうかにより、 サブパターン中の 2 つの選択肢を選択させます。条件付きサブパターン (conditional subpattern) には、 2 つの形式があります。

(?(条件)真パターン)
(?(条件)真パターン|偽パターン)

条件が満たされた場合、真パターンが使われます。そうでない場合は、 (もしあれば)偽パターンが使われます。サブパターン内に 3 つ以上の 選択肢があると、コンパイル時にエラーになります。

条件には 2 種類あります。条件のカッコ内が数値の場合、 その番号のキャプチャ用サブパターンがマッチしている場合に条件が 真となります。カッコ内の数値は 1 以上でないといけません。 次のパターンを見てみましょう。可読性を高めるために意味のない空白文字が 挿入され(PCRE_EXTENDED オプションが指定されていると仮定します)、 説明を簡単にするため 3 つの部分に分割されています。

( \( )?    [^()]+    (?(1) \) )

最初の部分は、オプションで、開きカッコにマッチします。 開きカッコが有る場合、1 番のキャプチャ文字列にセットされます。 第 2 の部分は、カッコでない一つ以上の文字にマッチします。第 3 の部分は、 最初のカッコがマッチしたかどうかをテストする条件付きサブパターンです。 カッコにマッチしている場合、つまり、対象文字列が開きカッコで 始まっている場合、条件は真となり、真パターンが実行され、閉じカッコが 必要となります。そうでない場合、偽パターンが存在しないため、 サブパターンは何にもマッチしません。言いかえると、このパターンは、 カッコなしの並びにマッチするものですが、オプションで カッコで囲まれた並びにもマッチします。

(R) という文字列を条件として指定すると、パターン全体もしくは サブパターンへの再帰的呼び出しがなされた場合に、その条件が満たされることになります。 「トップレベル」においては、この条件は偽となります。

数値の他にも、条件として言明が使用できます。先読み言明と 戻り読み言明のいずれも使え、肯定の言明も否定の言明も使用できます。 次のパターンを見てみましょう。このパターンにも意味のない空白文字が 挿入されており、2 行目に 2 つの選択肢が 書かれています。

(?(?=[^a-z]*[a-z])
\d{2}-[a-z]{3}-\d{2}  |  \d{2}-\d{2}-\d{2} )

条件は、肯定の先読み言明であり、英字以外の文字の並びの後に英字が 続くものにマッチします。言いかえると、対象文字列に少なくとも英字が 一つあるかどうかが調べられます。英字がみつかると、最初の選択肢に対して 検索対象とのマッチングが行われます。みつからない場合は、2番目の選択肢に 対してマッチングが行われます。このパターンは、2つの形式 dd-aaa-dd または dd-dd-dd のどちらかの文字列にマッチします。ここで、aaa は英字、 dd は数字です。

add a note

User Contributed Notes 1 note

up
3
Anonymous
13 years ago
Repetition of a subpattern will repeat conditionals that are contained inside it, updating subpattern matches with iteration.

Consider the following code, which scans thru HTML, keeping track of angle brackets "<" ">". If open bracket "<" matches, then closing bracket ">" must follow before repetition can possibly end. That way regex will effectively match only outside of tags.

<?php
$pattern
='%(*ANY)(.*?(<)(?(2).*?>)(.*?))*?\'\'%s';
$replace='\1Fred';
$subject=
'<html><body class=\'\'>\'\' went to '\'\meyer and ran
into <b>\'\'</b>.
</body></html>'
echo preg_replace("%(*ANY)(.*?((<)(?(3).*?>).*?)*?)\'\'%s",'\1Fred',$subject);
?>

Output will be:
'<html><body class=\'\'>Fred went to Fredmeyer and ran
into <b>Fred</b>.
</body></html>'
To Top