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

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

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 Ответ

5 голосов
/ 26 ноября 2010

Чтобы захватить весь блок 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. Если вы хотите захватить два if в содержимом MY_VAR, вам нужно перезапустить регулярное выражение для его группы Contents (вы можете обойти его, если воспользуетесь предвидением, если вам нужно - оберните все регулярное выражение в (?=...) , но вам нужно как-то поместить его в логическую структуру, используя позиции и длины).

Теперь я не буду слишком много объяснять, потому что кажется, что вы получаете основы, но короткое примечание о группе содержимого - я использую притяжательную группу, чтобы избежать возврата назад. В противном случае точка могла бы в конечном итоге соответствовать целым IF с и нарушить баланс. Ленивый матч в группе будет вести себя аналогично (( )+? вместо (?> )+).

...