我有一个基本的文本模板引擎,它使用如下语法:
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)(?!))
但这并不完全符合我的预期。例如,它捕获了很多空组。帮助
答案 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+)
组,其中包含上次捕获的值。在我的正则表达式中,我保留了您的简单正则表达式的Name
和Contents
组,并限制了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
而不是( )+?
)。