使用平衡组的正则表达式

时间:2010-11-26 11:21:52

标签: c# .net regex balancing-groups

我有一个基本的文本模板引擎,它使用如下语法:

foo bar
%IF MY_VAR
  some text
  %IF OTHER_VAR
    some other text
  %ENDIF
%ENDIF
bar foo

我在使用正则表达式时遇到问题,因为它没有考虑嵌套的IF / ENDIF块。

我正在使用的当前正则表达式为:%IF (?<Name>[\w_]+)(?<Contents>.*?)%ENDIF

我一直在阅读平衡捕获组(.NET的正则表达式库的一个特性),因为我知道这是支持.NET中“递归”正则表达式的推荐方法。

我一直在玩平衡小组,到目前为止已经提出了以下内容:

(
 (
  (?'Open'%IF\s(?<Name>[\w_]+))
  (?<Contents>.*?)
 )+
 (
  (?'Close-Open'%ENDIF)(?<Remainder>.*?)
 )+
)*
(?(Open)(?!))

但这并不完全符合我的预期。例如,它捕获了很多空组。帮助

1 个答案:

答案 0 :(得分:5)

要使用平衡的IF语句捕获整个IF / ENDIF块,可以使用此正则表达式:

%IF\s+(?<Name>\w+)
(?<Contents>
    (?> #Possessive group, so . will not match IF/ENDIF
        \s|
        (?<IF>%IF)|     #for IF, push
        (?<-IF>%ENDIF)| #for ENDIF, pop
        . # or, anything else, but don't allow
    )+
    (?(IF)(?!)) #fail on extra open IFs
)   #/Contents
%ENDIF

这里的要点是:无法在每个命名组中的一个Match中捕获。例如,您将只获得一个(?<Name>\w+)组,其中包含上次捕获的值。在我的正则表达式中,我保留了您的简单正则表达式的NameContents组,并限制了Contents组内的平衡 - 正则表达式仍然包含在IF和{ {1}}。

如果您的数据更复杂,那么会变得有趣。例如:

ENDIF

在这里,您将收到两个匹配项,一个用于%IF MY_VAR some text %IF OTHER_VAR some other text %ENDIF %IF OTHER_VAR2 some other text 2 %ENDIF %ENDIF %IF OTHER_VAR3 some other text 3 %ENDIF ,另一个用于MY_VAR。如果你想在OTHER_VAR3的内容上捕获两个ifs,你必须在其MY_VAR组上重新运行正则表达式(如果你必须使用前瞻,你可以绕过它 - 包装整个正则表达式在Contents中,你需要使用位置和长度以某种方式将它放入逻辑结构中。

现在,我不会解释太多,因为它似乎得到了基础知识,但是关于内容组的简短说明 - 我使用占有性组来避免回溯。否则,点最终可能匹配整个(?=...)并打破平衡。群组上的延迟匹配行为相似(IF而不是( )+?)。