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

时间:2011-04-07 15:45:16

标签: php regex

例如,我有一个这样的字符串:

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

我想获取{% a %}{% end %}之间的内容,即{% b %} {% end %}
我曾经使用{%\ S +%}(。*){%end%}来执行此操作。但是当我在其中添加c时:

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

它不起作用......我怎么能用正则表达式做到这一点?

3 个答案:

答案 0 :(得分: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 %}

修改:如果您需要匹配包含多个字词char的开头标记,请将\w代币的两次出现替换为(?!end)\w++,(正确无误)在tchrist的优秀答案中实现。)

答案 1 :(得分: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 %}

还有其他几种方法可以写出来。您可能还有其他未显示的约束,但这应该可以帮助您入门。

答案 2 :(得分:0)

您要找的是recursive regex。 PHP使用(?R)支持它。

我对它本身并不熟悉,能够帮助你解决模式本身,但希望这是朝着正确的方向发展。