如何编写与嵌套圆括号匹配的递归正则表达式



我正在尝试编写一个匹配嵌套括号的正则表达式,例如:

"(((text(text))))(text()()text)(casual(characters(#$%^^&&#^%#@!&**&#^*!@#^**_)))"

应该匹配这样的字符串,因为所有嵌套的括号都是闭合的:

"(((text)))(text)(casualChars*#(!&#*(!))"

应该不匹配,或者更好的是,应该至少匹配第一个"((((文本)))(文本)"部分。

实际上,我的正则表达式是:

 $regex = '/( (  (() ([^[]*?)  (?R)?  ())  ){0,}) /x';

但它并没有像我预期的那样正常工作。如何解决?我哪里错了?谢谢

此模式有效:

$pattern = '~ ( (?: [^()]+ | (?R) )*+ ) ~x';

括号内的内容简单描述:

"所有不是括号或递归(=其他括号)"x 0或更多次

如果您想捕获括号内的所有子字符串,您必须将此模式放在前瞻中以获得所有重叠的结果:

$pattern = '~(?= ( ( (?: [^()]+ | (?1) )*+ ) ) )~x';
preg_match_all($pattern, $subject, $matches);
print_r($matches[1]);

注意,我添加了一个捕获组,并用(?1):替换了(?R)

(?R) -> refers to the whole pattern (You can write (?0) too)
(?1) -> refers to the first capturing group

这是什么前瞻技巧?

lookahead(或lookbacking)中的子模式与任何内容都不匹配,它只是一个断言(一个测试)。因此,它允许多次检查同一个子字符串。

如果显示整个模式结果(print_r($matches[0]);),则会看到所有结果都是空字符串。获取预视中子模式找到的子字符串的唯一方法是将子模式包含在捕获组中。

注意:递归子模式可以这样改进:

( [^()]*+ (?: (?R) [^()]* )*+ )

当我找到这个答案时,我不知道如何修改模式以使用我自己的分隔符,其中{}。所以我的方法是让它更通用。

下面是一个脚本,用于生成带有您自己的变量左右分隔符的regex模式

$delimiter_wrap  = '~';
$delimiter_left  = '{';/* put YOUR left delimiter here.  */
$delimiter_right = '}';/* put YOUR right delimiter here. */
$delimiter_left  = preg_quote( $delimiter_left,  $delimiter_wrap );
$delimiter_right = preg_quote( $delimiter_right, $delimiter_wrap );
$pattern         = $delimiter_wrap . $delimiter_left
                 . '((?:[^' . $delimiter_left . $delimiter_right . ']++|(?R))*)'
                 . $delimiter_right . $delimiter_wrap;
/* Now you can use the generated pattern. */
preg_match_all( $pattern, $subject, $matches );

下面的代码使用myParser类(它在CC-BY3.0下),它在UTF-8上工作(多亏了myUTF8类)。

它的工作方式是使用递归函数对字符串进行迭代。每次找到(时,它都会调用自己。当它到达字符串的末尾而没有找到相应的)时,它也会检测到不匹配的对。

此外,这段代码使用$callback参数,您可以使用它来处理它找到的每一块。回调接收两个参数:1)字符串,2)级别(0=最深)。无论回调返回什么,都将在字符串的内容中被替换(这些更改在更高级别的回调中可见)。

注意:该代码不包括类型检查。

非递归部分:

function ParseParenthesis(/*string*/ $string, /*function*/ $callback)
{
    //Create a new parser object
    $parser = new Parser($string);
    //Call the recursive part
    $result = ParseParenthesisFragment($parser, $callback);
    if ($result['close'])
    {
        return $result['contents'];
    }
    else
    {
        //UNEXPECTED END OF STRING
        // throw new Exception('UNEXPECTED END OF STRING');
        return false;
    }
}

递归部分:

function ParseParenthesisFragment(/*parser*/ $parser, /*function*/ $callback)
{
    $contents = '';
    $level = 0;
    while(true)
    {
        $parenthesis = array('(', ')');
        // Jump to the first/next "(" or ")"
        $new = $parser->ConsumeUntil($parenthesis);
        $parser->Flush(); //<- Flush is just an optimization
        // Append what we got so far
        $contents .= $new;
        // Read the "(" or ")"
        $element = $parser->Consume($parenthesis);
        if ($element === '(') //If we found "("
        {
            //OPEN
            $result = ParseParenthesisFragment($parser, $callback);
            if ($result['close'])
            {
                // It was closed, all ok
                // Update the level of this iteration
                $newLevel = $result['level'] + 1;
                if ($newLevel > $level)
                {
                    $level = $newLevel;
                }
                // Call the callback
                $new = call_user_func
                (
                    $callback,
                    $result['contents'],
                    $level
                );
                // Append what we got
                $contents .= $new;
            }
            else
            {
                //UNEXPECTED END OF STRING
                // Don't call the callback for missmatched parenthesis
                // just append and return
                return array
                (
                    'close' => false,
                    'contents' => $contents.$result['contents']
                );
            }
        }
        else if ($element == ')') //If we found a ")"
        {
            //CLOSE
            return array
            (
                'close' => true,
                'contents' => $contents,
                'level' => $level
            );
        }
        else if ($result['status'] === null)
        {
            //END OF STRING
            return array
            (
                'close' => false,
                'contents' => $contents
            );
        }
    }
}

最新更新