匹配最里面的块(从头到尾,除非内部出现另一个块)

时间:2015-11-19 08:09:26

标签: regex pcre

我不太确定标题是否解释了我的问题所以我去了。我试图在PHP中创建模板系统。语法如下所示:

                #foreach(params):
                   <ul>
                        <li>Username: *{users_username}</li>
                        <li>ID: *{users_id}</li>
                        <li>Email: *{users_email}</li>
                        <li>Password: *{users_password}</li>
                        <li>Index: *{count}</li>
                        <li>Count: *{index}</li>
                  </ul>
                  #foreach(params):
                       <ul>
                           <li>Username: *{users_username}</li>
                           <li>ID: *{users_id}</li>
                           <li>Email: *{users_email}</li>
                           <li>Password: *{users_password}</li>
                           <li>Index: *{count}</li>
                          <li>Count: *{index}</li>
                       </ul>
                 #endforeach;
            #endforeach;

我想首先匹配从... #blockname(parameters):开始到#endblockname;开头的最里面的块。正则表达式还应该捕获块的内容。我认为这可以通过在#blockname(parameters):之后和#endblockname;之前获取所有内容来实现,除非之前有另一个#blockname(parameters):

然后我会多次运行PHP preg_replace,每次都删除另一层块。

如果您有任何疑问,请随时提出。

编辑:到目前为止,我已经尝试了以下内容:@[a-z]{1,}\([^)]*\):((?:(?!@[a-z]{1,}\([^)]*\):).)*?)@end[a-z]{1,};仅在没有换行符时才有效。

1 个答案:

答案 0 :(得分:3)

以下正则表达式将按照您定义的语法匹配最内层的块:

/
^[ \t]*[#]                       # start of line and indent up to "#"
(?<blockname>\w+)                # blockname (captured)
(?:[(](?<params>[^]]*)[)])?:     # text inside brackets (captured)
[ \t]*\n                         # and optional space to end of line

(?<body>(?>                      # get the body (captured)
    (?![ \t]*[#]\w)              #   if not followed by a block definition
    .*\n                         #   match the whole line
)*)

[ \t]*[#]end\g<blockname>;       # until it matches "#end" + the text captured in blockname
/mx

regex101 demo

它使用 named group 来捕获阻止名称

(?<blockname>\w+)

和另一个匹配来自块体的所有行,除非有一行以另一个块定义开头(使用 negative lookahead )。

(?<body>(?>
    (?![ \t]*[#]\w)
    .*\n
)*)

直到它可以匹配块的结尾,使用 backreference to (我们之前匹配的文本)命名组

[#]end\g<blockname>;

这会捕获blocknameparamsbody中的文字,如果您将此正则表达式与preg_match_all()preg_replace_callback()一起使用,您可以方便地获取匹配的文字为:

preg_match_all($re, $str, $matches);

$matches["blockname"][matchNum]
$matches["params"][matchNum]
$matches["body"][matchNum]