我有一个使用如下语法的基本文本模板引擎:
foo bar
%IF MY_VAR
some text
%IF OTHER_VAR
some other text
%ENDIF
%ENDIF
bar foo
我用来解析它的正则表达式有问题,它没有考虑嵌套的 IF/ENDIF block 。
我当前使用的正则表达式是:%IF (?<Name>[\w_]+)(?<Contents>.*?)%ENDIF
我一直在阅读有关平衡捕获组(.NET 正则表达式库的一项功能)的文章,因为我知道这是在 .NET 中支持“递归”正则表达式的推荐方法。
我一直在研究平衡组,到目前为止得出了以下结论:
(
(
(?'Open'%IF\s(?<Name>[\w_]+))
(?<Contents>.*?)
)+
(
(?'Close-Open'%ENDIF)(?<Remainder>.*?)
)+
)*
(?(Open)(?!))
但这并不完全符合我的预期。例如,它捕获了很多空组。帮忙?
请您参考如下方法:
要使用平衡的 IF 语句捕获整个 IF/ENDIF block ,您可以使用此正则表达式:
%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 内的平衡group - 正则表达式仍然包含在 IF 中和 ENDIF .
当您的数据更复杂时,If 会变得有趣。例如:
%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 .如果你想捕获 MY_VAR 上的两个 ifs的内容,您必须在其 Contents 上重新运行正则表达式组(如果必须的话,你可以通过使用前瞻来绕过它 - 将整个正则表达式包装在 (?=...) 中,但是你需要使用位置和长度以某种方式将它放入逻辑结构中)。
现在,我不会解释太多,因为看起来你已经掌握了基础知识,但关于内容组的简短说明 - 我使用所有格组来避免回溯。否则,点最终可能会匹配整个 IF。 s 并打破平衡。组上的惰性匹配会表现类似( ( )+? 而不是 (?> )+ )。
