我想解析这样的东西:
Hi [{tagname:content}] [{tag1:xnkudfdhkfujhkdjki diidfo now nested tag
[{tag2: more data here}] kj udf}]
我正在使用这个 PCRE 正则表达式来获取它们之间的所有数据\[{(.*?)}\]
并且它可以工作,但不适用于嵌套标签。我不是 PCRE 专家。
与 Perl 一样,PCRE 可以将嵌套结构匹配到任意深度。这是一个经过测试的脚本:
<?php // test.php Rev:20120701_0800
$re_nested_double_bracket ='% # Rev:20120701_0800
# Match [{...[{...}]...}] structure with arbitrary nesting.
\[\{ # Opening literal double bracket.
( # $1: Contents of double brackets.
(?: # Group for contents alternatives.
[^\[\}]++ # Either one or more non-brackets,
| (?R) # or a nested bracket pair,
| \[ # or the start of opening bracket
(?!\{) # (if not a complete open bracket),
| \} # or the start of closing bracket
(?!\]) # (if not a complete close bracket).
)* # Zero or more contents alternatives.
) # End $1: Contents of double brackets.
\}\] # Closing literal double bracket.
%x';
$input = file_get_contents('testdata.txt');
$count = preg_match_all($re_nested_double_bracket, $input, $matches);
printf("There were %d matches found.\n", $count);
for ($i = 0; $i < $count; ++$i) {
printf(" Match[%d]: %s\n", $i + 1, $matches[0][$i]);
}
?>
当针对原始帖子中的测试数据运行时,正则表达式匹配的内容如下:
There were 2 matches found.
Match[1]: [{tagname:content}]
Match[2]: [{tag1:xnkudfdhkfujhkdjki diidfo now nested tag
[{tag2: more data here}] kj udf}]
请注意,此正则表达式匹配最外面的一组可能嵌套的括号,并将$1
括号之间的内容捕获到组中。如果您想解析任何嵌套的括号,则需要递归地对最外层括号的内容重新运行正则表达式,直到没有更多匹配项为止。
那些声称现代正则表达式引擎(即 Perl、PCRE/PHP、.NET)无法解析嵌套结构的人是完全错误的。正则表达式已经很久很久没有“常规”了......
编辑:2012-07-01 09:00请注意,此解决方案将嵌套括号匹配到任何“任意深度”,但始终受系统内存、可执行堆栈大小和 PHP以及pcre.backtrack_limit
配置变量的限制。请注意,如果主题字符串太大和/或对于给定的主机系统嵌套太深,则此正则表达式解决方案肯定会失败。PHP/PCRE 库甚至可能导致正在运行的可执行文件产生堆栈溢出、分段错误和程序崩溃!有关如何以及为什么会发生这种情况(以及如何避免它并优雅地处理此类错误)的深入讨论,请参阅我对相关问题的回答:
Preg_match 函数中的 RegExp 返回浏览器错误和
pcre.recursion_limit
memory_limit
PHP 正则表达式:这段代码有什么问题吗?.
这是 REGEX 中的常见问题。正如巴克利所说,它们不是为此而设计的。尽管如此,问题还是出现了很多。
根本问题是 REGEX 无法知道嵌套标签的右括号实际上不是外部标签的右括号。
我决定做一些破坏行为并想出了这个恐怖。概念是先拉出不包含其他标签的标签。然后它向外工作,直到它拥有所有标签。
$str = "Hi [{tagname:content}] [{tag1:xnkudfdhkfujhkdjki diidfo now nested tag [{tag2: more data here}] kj udf}]";
$matches = array();
function replace_cb($this_match) {
global $matches;
$this_match = $this_match[0];
foreach($matches as $index => $match) $this_match = str_replace('**'.($index + 1).'**', $match, $this_match);
array_push($matches, $this_match);
return '**'.count($matches).'**';
}
while(preg_match('/\[\{[^\[]*?\}\]/', $str)) $str = preg_replace_callback('/\[\{[^\[]*?\}\]/', 'replace_cb', $str);
print_r($matches);
输出:
Array
(
[0] => [{tagname:content}]
[1] => [{tag2: more data here}]
[2] => [{tag1:xnkudfdhkfujhkdjki diidfo now nested tag [{tag2: more data here}] kj udf}]
)
...因此您最终将所有三个标签分开。
一个弱点是它当前决定一个标签是否包含嵌套标签,如果它包含一个[
. 这应该[{
在一起,但这很难,因为您不能在 REGEX 中否定子字符串,只能否定字符或字符范围。
所以,太可怕了。但它有效:)
正则表达式不是解析器。
对于轻量级解决方案,我建议您使用 JSON 解析器,例如,像这样:
$tree = json_decode('["root","'.
preg_replace('/\[\{(\w+):/',
'",["\1","',
str_replace(array('\\', "\n", '"', '}]'),
array('\\\\', '\n', '\"', '"],"'),
$str).
'"]'));
对于此输入(您的示例):
$str = 'Hi [{tagname:content}] [{tag1:xnkudfdhkfujhkdjki diidfo now nested tag
[{tag2: more data here}] kj udf}]';
你得到这个输出:
$tree = array(
0 => "root",
1 => "Hi ",
2 => array(
0 => "tagname",
1 => "content"
),
3 => " ",
4 => array(
0 => "tag1",
1 => "xnkudfdhkfujhkdjki diidfo now nested tag\n",
2 => array(
0 => "tag2",
1 => " more data here"
),
3 => " kj udf"
),
5 => ""
);
标签名称是0
每个子树的元素(我添加了一个任意"root"
标签)。我假设标签名称是一个简单的\w+
. 应该更改以反映允许的标签名称。如您所见,解析树中可能有额外的空字符串,但您可以轻松摆脱它们。
我知道,您的问题是关于 PCRE 的,但这有点像要求正确的锤子转动螺丝。
顺便说一句,建立在递归正则表达式引擎上的解析器有一个理论上的缺点,它可能变得非常现实:因为它们必须重新扫描每个输入元素的次数与其在树中的深度一样多,所以假设没有回溯,它们最坏情况的时间复杂度是 O(n 2)。
使用正则表达式允许无限嵌套时,没有通用的解决方案。他们不是为此而生的。
以下匹配由 [{ 和 }] 分隔的注释,允许内部嵌套一级注释。使用负前瞻代替 .*? 如果主题字符串包含不平衡的 [{ 字符,以防止灾难性的回溯。
\[{(?:(?!}]|\[{).)*+(?:\[{(?:(?!}]|\[{).)*+}](?:(?!}]|\[{).)*+)*+.*?}]