我有一个匹配模板系统的正则表达式,不幸的是,它似乎会在一些简单易懂的查找中崩溃apache(它在Windows上运行)。我已经研究了这个问题,并提出了一些提高堆栈大小的建议,其中没有一个似乎有用,我也不喜欢通过提高限制来处理这些问题,因为它通常只是推动了bug走向未来。
无论如何,任何关于如何改变正则表达式以使其不太可能被污染的想法?
我的想法是捕获最里面的块(在这种情况下为{block:test}This should be caught first!{/block:test}
),然后我将str_replace out起始/结束标记并通过正则表达式重新运行整个事务直到没有块左
正则表达式:
~(?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">«</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">»</div>
</div>
我想这是一个很长的镜头。 :(
答案 0 :(得分: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
另一种解决方案:
在PCRE源代码中,您可以从config.h
中删除评论:
/* #undef NO_RECURSE */
来自config.h
的文字副本:
PCRE使用递归函数调用来处理匹配时的回溯。在有限大小的堆栈的系统上,这有时可能是一个问题。
定义NO_RECURSE以获取在match()函数中不使用递归的版本;相反,它使用pcre_recurse_malloc()通过steam创建自己的堆栈,以从堆中获取内存。
或者您可以从pcre.backtrack_limit
更改pcre.recursion_limit
和php.ini
(http://www.php.net/manual/en/pcre.configuration.php)
答案 1 :(得分: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的出色建议,我还使用了命名的反向引用(?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_-]+\})[^{]*+)*+
)
...因为你知道它永远不会消耗它后来必须回馈的东西。这样可以加快速度,但实际上并不是必需的。
答案 2 :(得分: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;