2012-08-07 50 views
10

我有一个匹配模板系统的正则表达式,不幸的是它似乎在一些微不足道的查找中崩溃了apache(它运行在Windows上)。我研究了这个问题,并且提出了一些提升堆栈大小等方面的建议,但其中没有一项似乎有效,我也不喜欢通过增加限制来处理这些问题,因为它通常只是将错误推向未来。PHP正则表达式崩溃apache

无论如何任何想法如何改变正则表达式使其不太可能犯错?

这个想法是捕捉最内层的块(在这种情况下为{block:test}This should be caught first!{/block:test}),然后我将str_replace放开始/结束标记,并通过正则表达式重新运行整个事物,直到没有剩下的块为止。

正则表达式:

~(?P<opening>{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)})(?P<contents>(?:(?!{/?block:[0-9a-z-_]+}).)*)(?P<closing>{/block:\3})~ism 

样本模板:

<div class="f_sponsors s_banners"> 
    <div class="s_previous">&laquo;</div> 
    <div class="s_sponsors"> 
     <ul> 
      {block:sponsors} 
      <li> 
       <a href="{var:url}" target="_blank"> 
        <img src="image/160x126/{var:image}" alt="{var:name}" title="{var:name}" /> 
       </a> 
      {block:test}This should be caught first!{/block:test} 
      </li> 
      {/block:sponsors} 
     </ul> 
    </div> 
    <div class="s_next">&raquo;</div> 
</div> 

这是一个长镜头,我想。 :(

+4

奇怪的,因为它的声音,我曾经有一个正则表达式是保持吃一个特定的Apache实例和'S'(*大写!*)标志修复它。我猜想这是一个未报告的内存泄漏或什么,研究过程导致它被避免。远射,但值得一试,我会说... – DaveRandom 2012-08-07 17:03:19

+1

@DaveRandom,我有同样的问题一次,具有相同的修复!让我们看看它是否适用于OP – 2012-08-07 17:05:37

+2

另一个想法是,转义正则表达式中的字符“{}”字符可能会有所帮助。它们在技术上是元字符,而PCRE似乎对宽大的花括号非常宽容,如果你正确地将它们转义出来,可能会减少它的工作量。另外为什么使用命名的捕获组并且不在后向引用中使用名称? '/ block:\ 3' =>'/ block :(?P = name)'。这对你的正则表达式尤其如此''是可选的,在这种情况下''将是'\ 2',而不是'\ 3' – DaveRandom 2012-08-07 17:07:00

回答

4

试试这个:

'~(?P<opening>\{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)\})(?P<contents>[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*)(?P<closing>\{/block:(?P=name)\})~i' 

或者说,在可读的形式:

'~(?P<opening> 
    \{ 
    (?P<inverse>[!])? 
    block: 
    (?P<name>[a-z0-9\s_-]+) 
    \} 
) 
(?P<contents> 
    [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* 
) 
(?P<closing> 
    \{ 
    /block:(?P=name) 
    \} 
)~ix' 

最重要的部分是(?P<contents>..)组:

[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* 

出发,我们感兴趣的唯一角色是左大括号,所以我们可以用来吸引其他任何角色。只有在我们看到{后,我们才会检查它是否是{/block}标记的开头。如果不是,我们继续并使用它并开始扫描下一个,并在必要时重复。

使用RegexBuddy,我通过将光标放在{block:sponsors}标记和调试开始处来测试每个正则表达式。然后我从关闭的{/block:sponsors}标签中删除了结束大括号,强制失败的匹配并再次调试。你的正则表达式需要940步才能成功,2265步要失败。我的57个步骤成功,83个步骤失败。

在附注中,我删除了s修饰符,因为我没有使用点(.)和m修饰符,因为它从不需要。根据@ DaveRandom的出色建议,我还使用了名为backreference (?P=name)而不是\3。我躲过了所有大括号({}),因为我发现这样更容易阅读。


编辑:如果你想在最里面命名为块匹配,从这个正则表达式的中间部分发生变化:

(?P<contents> 
    [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* 
) 

...这(所建议@在KOBI他的评论):

(?P<contents> 
    [^{]*(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*)* 
) 

本来,(?P<opening>...)组会抢了先打开标签它看到,那么(?P<contents>..)组将会消费任何东西 - 包括其他标签 - 只要它们不是与(?P<opening>...)组找到的结束标签相匹配的结束标签。 (然后(?P<closing>...)组将继续并消耗。)

现在,(?P<contents>...)组拒绝匹配任何标签,打开或关闭(注意/?开头),不管名称是什么。所以正则表达式最初开始匹配{block:sponsors}标签,但是当它遇到{block:test}标签时,它会放弃该匹配并返回搜索开始标签。它从{block:test}标签处重新开始,这次它找到{/block:test}结束标签时成功完成了比赛。

这听起来效率很低,但是实际上并非如此。我之前描述的伎俩,扯上非大括号,淹没了这些错误开始的影响。几乎在每个位置你都在做负面预测,现在你只有在遇到{时才做一个。你甚至可以使用占有欲量词,如为@godspeedlee建议:

(?P<contents> 
    [^{]*+(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*+)*+ 
) 

...因为你知道它永远不会消耗任何东西,它必须在以后给予回复。这会加快一点,但并不是真的有必要。

+0

我已经更新了这个问题,因为它只捕捉最外面的块。不过你们仍然不需要〜sm部分,所以我认为它在正确的轨道上! – Meep3D 2012-08-09 09:47:52

+2

非常好!我*认为*我明白OP想要什么......我认为,“嵌套块”是指任何名称的嵌套块,而不仅仅是相同的名称,它们会被迭代替换。所以'{1} {2} {/ 2} {/ 1}'应该在'1'之前捕获'2'。如果是这样的话,你可以很容易地从'[^ {] *(?:\ {(?!/ block :(?P = name)\})[^ {] *)*' '[^ {] *(?:\ {(?!/?block:[a-z0-9 \ s _-] + \})[^ {] *)*' - http://regexr.com?31qsf – Kobi 2012-08-13 19:35:55

+0

类似问题匹配HTML )。)*#s' crashed而'#] *)?> [^ <] *(?: <(?!/select>)[^ <] *)*#s'工作。虽然添加'U' ungreedy修饰符也可以防止崩溃;我不知道为什么,因为我认为这会导致更多的回溯和低效率 - 如果不是这样,我可能刚刚写了'#。*#Us'并且用它... – Jake 2017-01-14 04:26:06

4

你可以使用atomic group: (?>...)possessive quantifiers: ?+ *+ ++..压制/限制回溯和unrolling loop技术加快匹配我的解决办法:。

\{block:(\w++)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}

我从http://regexr.com?31p03测试

比赛{block:sponsors}...{/block:sponsors}
\{block:(sponsors)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb3

比赛{block:test}...{/block:test}
\{block:(test)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb6

其他的解决方案:从config.h
/* #undef NO_RECURSE */

下面的文字复制:
在PCRE源代码,你可以删除config.h评论:
PCRE在匹配时使用递归函数调用来处理回溯。这在有限尺寸堆栈的系统上有时会成为问题。 定义NO_RECURSE以获得不在match()函数中使用递归的版本;相反,它使用pcre_recurse_malloc()通过蒸汽创建自己的堆栈以从堆中获取内存。

或者您可以从php.ini(http://www.php.net/manual/en/pcre.configuration)更改pcre.backtrack_limitpcre.recursion_limit。PHP)

+0

然而,当你把一个块放在一个块中时,它只捕获最外面的块,而不是最内层的块。我已经更新了一些问题! – Meep3D 2012-08-09 09:43:27

4

解决方案是否必须是单个正则表达式?更有效的方法可能只是寻找{/block:(可能是一个简单的字符串搜索或正则表达式)的第一次出现,然后从该点向后搜索以找到其匹配的开始标记,并适当地替换该跨度并重复,直到出现没有更多的块。如果每次查找第一个结束标记,从模板的顶部开始,那么这将为您提供最深的嵌套块。

镜像算法将很好的工作 - 寻找最后的开放标签,然后从那里向前查找相应的结束标签:

<?php 

$template = //... 

while(true) { 
    $last_open_tag = strrpos($template, '{block:'); 
    $last_inverted_tag = strrpos($template, '{!block:'); 
    // $block_start is the index of the '{' of the last opening block tag in the 
    // template, or false if there are no more block tags left 
    $block_start = max($last_open_tag, $last_inverted_tag); 
    if($block_start === false) { 
    // all done 
    break; 
    } else { 
    // extract the block name (the foo in {block:foo}) - from the character 
    // after the next : to the character before the next }, inclusive 
    $block_name_start = strpos($template, ':', $block_start) + 1; 
    $block_name = substr($template, $block_name_start, 
     strcspn($template, '}', $block_name_start)); 

    // we now have the start tag and the block name, next find the end tag. 
    // $block_end is the index of the '{' of the next closing block tag after 
    // $block_start. If this doesn't match the opening tag something is wrong. 
    $block_end = strpos($template, '{/block:', $block_start); 
    if(strpos($template, $block_name.'}', $block_end + 8) !== $block_end + 8) { 
     // non-matching tag 
     print("Non-matching tag found\n"); 
     break; 
    } else { 
     // now we have found the innermost block 
     // - its start tag begins at $block_start 
     // - its content begins at 
     // (strpos($template, '}', $block_start) + 1) 
     // - its content ends at $block_end 
     // - its end tag ends at ($block_end + strlen($block_name) + 9) 
     // [9 being the length of '{/block:' plus '}'] 
     // - the start tag was inverted iff $block_start === $last_inverted_tag 
     $template = // do whatever you need to do to replace the template 
    } 
    } 
} 

echo $template; 
相关问题