Регулярное выражение, которое использует балансирующие группы
У меня есть базовый текстовый шаблонизатор, который использует такой синтаксис:
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 ответ
Чтобы захватить весь блок IF/ENDIF со сбалансированными операторами IF, вы можете использовать это регулярное выражение:
%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
а также 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
группа (вы можете обойти это с помощью Lookahead, если вам нужно - обернуть все регулярное выражение в (?=...)
, но вам нужно как-то поместить его в логическую структуру, используя позиции и длины).
Теперь я не буду объяснять слишком много, потому что кажется, что вы получаете основы, но короткое примечание о группе содержимого - я использую собственническую группу, чтобы избежать возврата назад. В противном случае точка могла бы в конечном итоге соответствовать целому IF
и нарушить баланс. Ленивый матч в группе будет вести себя аналогично (( )+?
вместо (?> )+
).