使用平衡组的正则表达式

我有一个基本的文本模板引擎,使用这样的语法:

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

我有一个正则表达式的问题,我用来解析它,因为它没有考虑嵌套的IF / ENDIF块。

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

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

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

 ( ( (?'Open'%IF\s(?[\w_]+)) (?.*?) )+ ( (?'Close-Open'%ENDIF)(?.*?) )+ )* (?(Open)(?!)) 

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

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

 %IF\s+(?\w+) (? (?> #Possessive group, so . will not match IF/ENDIF \s| (?%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捕获多个命名组中的一个。 例如,您将只获得一个(?\w+)组中最后捕获的值。 在我的正则表达式中,我保留了简单正则表达式的NameContents组,并限制了Contents组内的平衡 – 正则表达式仍包含在IFENDIF

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

 %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匹配并打破平衡。 组上的惰性匹配行为类似( ( )+?而不是(?> )+ )。