递归正则表达式无法工作

4

我所处理的字符串如下:

abc {def ghi {jkl mno} pqr stv} xy z

我需要在标签中添加括号内的数字,应该像这样:

(数字)


abc <tag>def ghi <tag>jkl mno</tag> pqr stv</tag> xy z

我已经尝试过了

'#(?<!\pL)\{  ( ([^{}]+) | (?R) )*  \}(?!\pL)#xu'

但我得到的只是<tag>xy z</tag>。请帮忙,我做错了什么?
2个回答

5

嵌套结构对于正则表达式来说过于复杂(是的,PCRE支持递归,但这并不能解决替换问题)。有两种可能的选择(仍然使用正则表达式)。首先,您可以简单地将开放括号替换为开放标签,并对关闭标签进行相同的操作。然而,这样做也会将不匹配的括号转换:

$str = preg_replace('/\{/', '<tag>', $str);
$str = preg_replace('/\}/', '</tag>', $str);

另外一种选择是仅替换匹配的{},但这会导致需要重复执行操作,因为一次preg_replace调用无法替换多个嵌套级别:
do
{
    $str = preg_replace('/\{([^{]*?)\}/', '<tag>$1</tag>', $str, -1, $count);
}
while ($count > 0)
编辑:虽然PCRE支持使用(?R)实现递归,但这很可能无法帮助替换。原因是如果捕获组重复,则其引用将仅包含最后一个捕获(例如在匹配/(a|b)+/时,在aaaab中,$1将包含b)。我猜递归也是如此。这就是为什么你只能替换最内层的匹配,因为它是递归内捕获组中的最后一次匹配。同样,您也不能尝试使用递归来捕获{}并替换它们,因为它们也可能被匹配多次,只有最后一次匹配会被替换。

仅匹配正确嵌套的语法,然后替换最内部或最外部匹配的括号也没有作用(使用一个preg_replace调用),因为多个匹配永远不会重叠(因此如果找到了3个嵌套的括号,内部的2个括号本身将被忽略以进行进一步的匹配)。


这是 (?R) 的含义。 - tijagi
只有当您的表达式在数学意义上是“正则表达式”时,该定义才有效。 PCRE是PHP使用的内容,无论如何都不是常规的:例如,您可以(如提问者所做)使用(?R)使表达式递归!现在,这是否是一个好主意是另一个问题,但它不应该是不可能的。 - Tikhon Jelvis
1
你说得对,PCRE支持(?R),但我似乎找不到任何(推荐的)使用它进行preg_replace的例子。它通常似乎只用于preg_match - Martin Ender
我不知道那个。谢谢。 - tijagi
1
@TikhonJelvis(当然还有@OP),我已经解释了为什么递归特性在这种情况下不能真正与preg_replace一起使用。 - Martin Ender

3

两步走怎么样:

s!{!<tag>!g;
s!}!</tag>!g;

(perl格式;根据需要将其转换为您的格式)

或者也可以这样:

1 while s!{([^{}]*)}!<tag>$1</tag>!g;


已经考虑过了。这很简单,但如果找不到匹配的括号,可能会破坏标记。 - tijagi
您可以反复调用搜索/替换函数。 - Brian White

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接