[英]PHP PCRE allow nested patterns (recursion) in a string
我得到了像1(8()3(6()7())9()3())2(4())3()1(0()3())
這樣的字符串,它表示一棵樹。 如果我們更深一層,將出現一個括號。 在同一級別上的數字是鄰居。
現在要添加節點,比如我想將增加5
到,我們有一個每路1
對第一和3
在第二個層次,所以我想提出一個5()
每經過3(
這是內部a 1(
。因此必須將5()
相加3次,結果應為1(8()3(5()6()7())9()3(5()))2(4())3()1(0()3(5()))
問題是,我沒有使用PCRE遞歸的代碼。 如果我匹配不帶固定路徑(如1(
和3(
的樹表示字符串,它會起作用,但是當我生成具有這些固定模式的正則表達式時,它將不起作用。
這是我的代碼:
<?php
header('content-type: text/plain;utf-8');
$node = [1, 3, 5];
$path = '1(8()3(6()7())9()3())2(4())3()1(0()3())';
echo $path.'
';
$nes = '\((((?>[^()]+)|(?R))*)\)';
$nes = '('.$nes.')*';
echo preg_match('/'.$nes.'/x', $path) ? 'matches' : 'matches not';
echo '
';
// creates a regex with the fixed path structure, but allows nested elements in between
// in this example something like: /^anyNestedElementsHere 1( anyNestedElementsHere 3( anyNestedElementsHere ))/
$re = $nes;
for ($i = 0; $i < count($node)-1; $i++) {
$re .= $node[$i].'\(';
if ($i != count($node)-2)
$re .= $nes;
}
$re = '/^('.$re.')/x';
echo str_replace($nes, ' '.$nes.' ', $re).'
';
echo preg_match($re, $path) ? 'matches' : 'matches not';
echo '
';
// append 5()
echo preg_replace($re, '${1}'.$node[count($node)-1].'()', $path);
?>
這是輸出,您可以在其中查看生成的正則表達式的樣子:
1(8()3(6()7())9()3())2(4())3()1(0()3())
matches
/^( (\((((?>[^()]+)|(?R))*)\))* 1\( (\((((?>[^()]+)|(?R))*)\))* 3\()/x
matches not
1(8()3(6()7())9()3())2(4())3()1(0()3())
希望您理解我的問題,希望您能告訴我我的錯誤在哪里。
非常感謝!
正則表達式
下面的正則表達式遞歸匹配嵌套的括號,在第一層找到一個開口1(
在第二層上找到一個開口3(
(作為直接子代)。它也嘗試連續的匹配,無論是在同一層上還是在相應層上向下水平找到另一個匹配。
~
(?(?=\A) # IF: First match attempt (if at start of string) - -
# we are on 1st level => find next "1("
(?<balanced_brackets>
# consumes balanced brackets recursively where there is no match
[^()]*+
\( (?&balanced_brackets)*? \)
)*?
# match "1(" => enter level 2
1\(
| # ELSE: Successive matches - - - - - - - - - - - - - -
\G # Start at end of last match (level 3)
# Go down to level 2 - match ")"
(?&balanced_brackets)*?
\)
# or go back to level 1 - matching another ")"
(?>
(?&balanced_brackets)*?
\)
# and enter level 2 again
(?&balanced_brackets)*?
1\(
)*?
) # - - - - - - - - - - - -
# we are on level 2 => consume balanced brackets and match "3("
(?&balanced_brackets)*?
3\K\( # also reset the start of the match
~x
替代
(5()
文本
Input:
1(8()3(6()7())9()3())2(4())3()1(0()3())
Output:
1(8()3(5()6()7())9()3(5()))2(4())3()1(0()3(5()))
^^^ ^^^ ^^^
[1] [2] [3]
我們首先使用conditional subpattern
來區分:
\\G assertion
錨)。 (?(?=\A) # IF followed by start of string
# This is the first attempt
| # ELSE
# This is another attempt
\G # and we'll anchor it to the end of last match
)
對於第一個匹配項 ,我們將使用所有不匹配1(
嵌套括號,以便將光標移到第一級可以找到成功匹配項的位置。
Recursion
和子Subroutines
。 (?<balanced_brackets> # ANY NUMBER OF BALANCED BRACKETS
[^()]*+ # match any characters
\( # opening bracket
(?&balanced_brackets)*? # with nested bracket (recursively)
\) # closing bracket in the main level
)*? # Repeated any times (lazy)
注意,這是一個named group
,我們將在模式中將其多次用作子例程調用,以消耗不需要的平衡括號,例如(?&balanced_brackets)*?
。
下一級 。 現在,要進入級別2,我們需要匹配:
1\(
最后,我們將消耗所有平衡的括號,直到找到第3級的開頭:
(?&balanced_brackets)*?
3\(
而已。 我們剛剛匹配了第一個匹配項,因此我們可以在該位置插入替換文本。
下一場比賽 。 對於連續的匹配嘗試,我們可以:
)
以查找另一次出現3(
)
,然后從那里匹配與第一個匹配相同的策略。 這可以通過以下子模式實現:
\G # anchored to the end of last match (level 3)
(?&balanced_brackets)*? # consume any balanced brackets
\) # go down to level 2
#
(?> # And optionally
(?&balanced_brackets)*? # consume level 2 brackets
\) # to go down to level 1
(?&balanced_brackets)*? # consume level 1 brackets
1\( # and go up to level 2 again
)*? # As many times as it needs to (lazy)
總結一下,我們可以匹配第三個級別的開頭:
(?&balanced_brackets)*?
3\(
我們還將在模式結尾附近使用\\K
重置比賽開始 ,以僅匹配最后一個左括號。 因此,我們可以簡單地用(5()
代替,避免使用反向引用。
我們只需要使用上面使用的相同值調用preg_replace()
。
為什么您的正則表達式失敗?
如您所問,該模式已錨定到字符串的開頭。 它只能匹配第一個匹配項。
/^( (\((((?>[^()]+)|(?R))*)\))* 1\( (\((((?>[^()]+)|(?R))*)\))* 3\()/x
而且,它不匹配第一次出現,因為構造(?R)
遞歸了整個模式(試圖再次匹配^
)。 我們可以將(?R)
更改為(?2)
。
但是,主要原因是因為它在任何打開\\(
之前都沒有消耗字符。例如:
Input:
1(8()3(6()7())9()3())2(4())3()1(0()3())
^
#this "8" can't be consumed with the pattern
還應考慮一種行為: PCRE將遞歸視為atomic 。 因此,您必須確保模式會像上面的示例一樣使用不需要的括號,但也要避免在各自的級別匹配1(
或3(
。
我將這個問題分解為兩個較小的部分:
首先,使用以下正則表達式提取1
節點:
(?(DEFINE)
(?<tree>
(?: \d+ \( (?&tree) \) )*
)
)
\b 1 \( (?&tree) \)
為此使用preg_replace_callback
。 這將匹配1(8()3(6()7())9()3())
和1(0()3())
。
接下來,只需要用3(5()
替換3(
3(5()
就可以了。
PHP中的示例:
$path = '1(8()3(6()7())9()3())2(4())3()1(0()3())';
$path = preg_replace_callback('#
(?(DEFINE)
(?<tree>
(?: \d+ \( (?&tree) \) )*
)
)
\b 1 \( (?&tree) \)
#x', function($m) {
return str_replace('3(', '3(5()', $m[0]);
}, $path);
結果是: 1(8()3(5()6()7())9()3(5()))2(4())3()1(0()3(5()))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.