Регулярное выражение, которое использует балансирующие группы

У меня есть базовый текстовый шаблонизатор, который использует такой синтаксис:

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и нарушить баланс. Ленивый матч в группе будет вести себя аналогично (( )+? вместо (?> )+).

Другие вопросы по тегам