如何使用正则表达式匹配一些嵌套结构?

3
例如,我有一个类似这样的字符串:
{% a %}
    {% b %}
    {% end %}
{% end %}

我想获取{% a %}{% end %}之间的内容,即{% b %} {% end %}。 以前我使用{% \S+ %}(.*){% end %}来实现这一点。 但是当我在其中添加c时:

 {% a %}
        {% b %}
        {% end %}
    {% end %}
{% c %}
{% end %}

它不起作用...我该如何使用正则表达式完成这个任务?


3
它是否是任意深度的嵌套结构?如果是,那不是一个正则语言。 - eldarerathis
2
你可能会更容易地使用正则表达式匹配单个元素,并使用堆栈来匹配开放/关闭块。 - GWW
2
@eldarethis:那是一个红鲱鱼,请不要重复它。因为使用现代模式匹配嵌套结构非常简单,所以它并不适用 - tchrist
1
@casablanca:请停止发布那些愚蠢和无关的链接。它们不适用,而且还是错误的。 - tchrist
2
@eldarerathis: 真是太好了,PHP的正则表达式并不是REGULAR - ridgerunner
显示剩余2条评论
3个回答

4
给定以下测试数据:
$text = '
{% a %}
    {% b %}
        {% a %}
        {% end %}
    {% end %}
        {% b %}
        {% end %}
{% end %}
{% c %}
{% end %}
';

这个经过测试的脚本可以解决问题:
<?php
$re = '/
    # Match nested {% a %}{% b %}...{% end %}{% end %} structures.
    \{%[ ]\w[ ]%\}       # Opening delimiter.
    (?:                  # Group for contents alternatives.
      (?R)               # Either a nested recursive component,
    |                    # or non-recursive component stuff.
      [^{]*+             # {normal*} Zero or more non-{
      (?:                # Begin: "unrolling-the-loop"
        \{               # {special} Allow a { as long
        (?!              # as it is not the start of
          %[ ]\w[ ]%\}   # a new nested component, or
        | %[ ]end[ ]%\}  # the end of this component.
        )                # Ok to match { followed by
        [^{]*+           # more {normal*}. (See: MRE3!)
      )*+                # End {(special normal*)*} construct.
    )*+                  # Zero or more contents alternatives
    \{%[ ]end[ ]%\}      # Closing delimiter.
    /ix';
$count = preg_match_all($re, $text, $m);
if ($count) {
    printf("%d Matches:\n", $count);
    for ($i = 0; $i < $count; ++$i) {
        printf("\nMatch %d:\n%s\n", $i + 1, $m[0][$i]);
    }
}
?>

这是输出结果:

2 Matches:

Match 1:
{% a %}
    {% b %}
        {% a %}
        {% end %}
    {% end %}
        {% b %}
        {% end %}
{% end %}

Match 2:
{% c %}
{% end %}

编辑:如果你需要匹配一个包含多个单词字符的开始标签,将两个\w替换为(?!end)\w++,(tchrist 的回答中正确地实现了这一点)。


2

这里有一个Perl的演示,可以用于处理你的数据集。同样的方法也适用于PHP。

#!/usr/bin/env perl

use strict;
use warnings;

my $string = <<'EO_STRING';
    {% a %}
            {% b %}
            {% end %}
        {% end %}
    {% c %}
    {% end %}
EO_STRING


print "MATCH: $&\n" while $string =~ m{
    \{ % \s+ (?!end) \w+ \s+ % \}
    (?: (?: (?! % \} | % \} ) . ) | (?R) )*
    \{ % \s+ end \s+ % \}
}xsg;

当运行时,会产生以下结果:
MATCH: {% a %}
            {% b %}
            {% end %}
        {% end %}
MATCH: {% c %}
    {% end %}

有其他几种写法。你可能有其他未展示的限制条件,但这应该能帮助你入门。


0
你需要的是递归正则表达式。PHP支持使用(?R)
我对此不太熟悉,无法帮助你处理模式本身,但希望这能给你指明正确的方向。

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