关于递归模式的php正则表达式

时间:2013-03-23 07:59:16

标签: php regex recursive-regex

我有这段代码:

$string="some text {@block}outside{@block}inside{@}outside{@} other text";

function catchPattern($string,$layer){
  preg_match_all(
    "/\{@block\}".
      "(".
        "(".
           "[^()]*|(?R)".
        ")*".
      ")".
    "\{@\}/",$string,$nodes);
  if(count($nodes)>1){
    for($i=0;$i<count($nodes[1]); $i++){
      if(is_string($nodes[1][$i])){
        if(strlen($nodes[1][$i])>0){
          echo "<pre>Layer ".$layer.":   ".$nodes[1][$i]."</pre><br />";
          catchPattern($nodes[1][$i],$layer+1);
        }
      }
    }
  }
}

catchPattern($string,0);

这给了我这个输出:

Layer 0:   outside{@block}inside{@}outside

Layer 1:   inside

一切都没问题!但是如果我改变了一个字符串和regexp:

$string="some text {@block}outside{@block}inside{@end}outside{@end} other text";

function catchPattern($string,$layer){
  preg_match_all(
    "/\{@block\}".
      "(".
        "(".
           "[^()]*|(?R)".
        ")*".
      ")".
    "\{@end\}/",$string,$nodes);
  if(count($nodes)>1){
    for($i=0;$i<count($nodes[1]); $i++){
      if(is_string($nodes[1][$i])){
        if(strlen($nodes[1][$i])>0){
          echo "<pre>Layer ".$layer.":   ".$nodes[1][$i]."</pre><br />";
          catchPattern($nodes[1][$i],$layer+1);
        }
      }
    }
  }
}

catchPattern($string,0);

我没有得到任何输出。为什么?我期待相同的输出。

1 个答案:

答案 0 :(得分:5)

问题是回溯限制已经用尽。您随时可以修改backtracking limit但是,对于我遇到的情况,重写正则表达式是更好的解决方案

您无论如何都无法修改现有的正则表达式并期望使其正常工作,尤其是对于递归正则表达式。您似乎将现有的括号匹配正则表达式并进行修改。你的正则表达式有一些问题:

  • [^()]*:没有理由在()部分的文本中排除{@block}{@end}。但更严重的问题是它匹配{}。引擎将一直到最近的()或字符串的末尾,无法匹配,然后回溯。这就是达到回溯限制的原因。

    可以通过将此部分更改为[^{}]以禁用{}内的{@block}{@end}来解决此问题。由于递归,嵌套的{@block}{@end}仍将匹配。

    请注意,这将完全禁止在{} 中将{@block}{@end}指定为文字。可以修改正则表达式以允许这种情况,具体取决于转义方案。

    我还将[^{}]的量词从*更改为+,因为当整个群组([^{}]+|(?R))的量词是时,没有理由匹配空字符串*

    /\{@block\}((?:[^{}]+|(?R))*)\{@end\}/
    
  • 上述修改后,第二个问题是输入字符串无效。量词的默认行为是将执行回溯,直到找到匹配或所有可能性都用完为止。因此,在这些情况下,您将达到回溯限制。

    由于[^{}]+可以匹配以及递归正则表达式可以匹配的是互斥 1 ,因此正则表达式不是模糊的,可以在没有回溯的情况下进行匹配。我们可以通过使用占有量词来告诉引擎不要回溯,这是正常量词,后面加+

最终的解决方案是:

/\{@block\}((?:[^{}]++|(?R))*+)\{@end\}/

Demo

脚注

1 :很明显,因为文本匹配[^{}]+永远不会以{开头,而与递归正则表达式匹配的文本必须以{开头