提问人:Fabrício Matté 提问时间:3/2/2014 最后编辑:Fabrício Matté 更新时间:3/2/2014 访问量:542
是否可以找到与单个正则表达式重叠的匹配项?
is it possible to find overlapping matches with a single regex?
问:
下面是一个示例,它多次执行以查找嵌套/重叠的匹配项:preg_replace
$text = '[foo][foo][/foo][/foo]';
//1st: ^^^^^ ^^^^^^
//2nd: ^^^^^ ^^^^^^
//3rd: fails
do {
$text = preg_replace('~\[foo](.*?)\[/foo]~', '[bar]$1[/bar]', $text, -1, $replace_count);
} while ($replace_count);
echo $text; //'[bar][bar][/bar][/bar]'
我对结果和行为感到满意。但是,像上面的示例一样,扫描整个字符串 3 次似乎效率低下。是否有任何正则表达式魔术可以在单个替换中做到这一点?
条件:
- 我不能简单地替换为 ,我需要确保在开始标签之后有一个匹配的结束标签,并一次替换它们。它们是否嵌套并不重要。未配对,不应更换。
~\[(/)?foo]~
[$1bar]
[/foo]
[foo]
[foo]
[/foo]
在 JS 中,我可以将正则表达式对象的属性设置为匹配的开头,以便它从最后一个匹配的开始再次开始匹配。我在 PHP 中找不到任何替换正则表达式的选项,并且使用 ing 也可能效率低下。我环顾四周,PCRE是否会有“在这个位置开始下一场比赛”或类似的东西,但我没有运气。lastIndex
startIndex
substr()
有没有更好的方法?
为了澄清未配对的标签,给定输入:
[foo][foo][/foo]
我对任何一个或作为输出都很好。前者是遗留行为。[bar][foo][/bar]
[foo][bar][/bar]
答:
更好的方法是找到结束并回溯,直到找到开始或。用其他东西替换匹配区域并继续这样做,直到找不到结局。但是用普通的或普通的旧,不是.[/foo]
[foo]
[foo(space).*]
strpos/stripos
substr
regex
它可能是可以实现的,但我总是用常规搜索来做这种事情,因为它也更快。regex
评论
Is there a better approach?
Is there any regex magic to do this in a single replace?
注意?
strpos
substr_replace
substr_replace
对于这种特定情况,不可能有一个完整的正则表达式解决方案。
您的解决方案适用于匹配配对标签(在常识中):
$pattern = '~\[foo]((?>[^[]++|\[(?!/?foo]))*)\[/foo]~';
$result = $text;
do {
$result = preg_replace($pattern, '[bar]$1[/bar]', $result, -1, $count);
} while ($count);
另一种仅解析字符串一次的方法:
$arr = preg_split('~(\[/?foo])~', $text, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
$stack = array();
foreach ($arr as $key=>$item) {
if ($item == '[foo]') $stack[] = $key;
else if ($item == '[/foo]' && !empty($stack)) {
$arr[array_pop($stack)] = '[bar]';
$arr[$key] = '[/bar]';
}
}
$result = implode($arr);
第二个脚本的性能与深度无关。
要回答标题问题,是的,可以找到具有单个正则表达式的重叠匹配项,但是,您不能使用这种模式进行替换,例如:
$pattern = '~(?=(\[foo]((?>[^[]++|\[(?!/?foo)|(?1))*)\[/foo]))~';
preg_match_all($pattern, $text, $matches);
诀窍是使用前瞻和捕获组。请注意,整个匹配始终是一个空字符串,这就是您不能将此模式与preg_replace一起使用的原因。
评论
(?>[^[]++|\[(?!/?foo]))*
.*?
.*?
.*?
.*?
不确保两个标签之间没有其他标签。
评论
[foo][foo][/foo]
[foo]