使用正则表达式解析专有标记语法 - 如何检测嵌套标记?

时间:2012-07-01 11:32:47

标签: php regex

我想解析这样的事情:

Hi [{tagname:content}] [{tag1:xnkudfdhkfujhkdjki diidfo now nested tag
[{tag2: more data here}] kj udf}]

我正在使用此PCRE正则表达式获取\[{(.*?)}\]之间的所有数据并且它可以正常工作,但不适用于嵌套标记。我不是PCRE专家。

4 个答案:

答案 0 :(得分:4)

PCRE,就像Perl一样,可以将嵌套结构与任意深度匹配。这是一个经过测试的脚本:

正则表达式匹配嵌套括号

<?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 {{}的限制。 1}},pcre.backtrack_limitpcre.recursion_limit配置变量。请注意,如果主题字符串太大和/或嵌套对于给定的主机系统来说太深,则此正则表达式解决方案当然可能会失败。 PHP / PCRE库甚至可能导致正在运行的可执行文件生成堆栈溢出,分段错误和程序崩溃!请参阅我对相关问题的回答,深入讨论如何以及为什么会发生这种情况(以及如何避免它并优雅地处理此类错误): RegExp in preg_match function returning browser errorPHP regex: is there anything wrong with this code?

答案 1 :(得分:3)

这是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中的子字符串,只能是字符或字符范围。

所以,太可怕了。但它有效:)

答案 2 :(得分:2)

使用正则表达式进行无限制嵌套时,没有通用的解决方案。他们不是为此而做的。

以下内容匹配[{和}]分隔的注释,允许在其中包含一级嵌套注释。 使用负前瞻而不是。*?如果主题字符串包含不平衡的[{characters。

,则防止灾难性的回溯
\[{(?:(?!}]|\[{).)*+(?:\[{(?:(?!}]|\[{).)*+}](?:(?!}]|\[{).)*+)*+.*?}]

答案 3 :(得分:2)

正则表达式不是解析器。

对于轻量级解决方案,我建议您使用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+。应该更改它以反映允许的标记名称。如您所见,解析树中可能有额外的空字符串,但您可以轻松删除它们。

我知道,你的问题是关于PCREs,但这有点像要求用正确的锤子拧螺丝。

BTW,基于递归正则表达式引擎的解析器有一个理论上的缺点,它可以变得非常真实:因为它们必须重新扫描每个输入元素的次数与它在树中的深度一样多,所以假设没有回溯,它们的最坏情况时间复杂度是O (N 2 )。