Регулярное выражение, разделенное запятой, а не внутри скобок (. NET) - PullRequest
2 голосов
/ 09 апреля 2020

Мне нужно разделить текст (sql запрос) по каждой запятой, которая не находится в скобках.

Пример (я пометил запятые, которые должны быть включены в разделение):

a."Id",                //<- this comma
a."Description",       //<- this comma
UJsonObject(
   fepv."Id",          //<- NOT this comma
   fepv."SystemName",  //<- NOT this comma
   string_agg(
        translations."Translations", ',' //<- NOT this comma (here can be some nested parenthesis also)
   ) as "Translations"
) as "Translations",   //<- this comma
b."DataSource",        //<- this comma
a."Name",              //<- this comma
a."Value"

Я нашел универсальное решение здесь: https://regex101.com/r/6lQKjP/2, но похоже, что это решение не работает в do tnet.

Я хотел бы использовать Regex.Split, но если это так может быть удовлетворен Regex.Matches я тоже буду счастлив. Также я знаю, что могу написать свой собственный синтаксический анализатор, но я читал, что простые случаи (которые не извлекают вложенные скобки) могут обрабатываться через Regex.

Ответы [ 3 ]

3 голосов
/ 09 апреля 2020

Это регулярное выражение PCRE - (\((?:[^()]++|(?1))*\))(*SKIP)(*F)|, - использует рекурсию, NET не поддерживает ее, но есть способ сделать то же самое с помощью конструкции балансировки. Глаголы From PCRE - (*SKIP) и (*FAIL) - только (*FAIL) могут быть записаны как (?!) (это вызывает безусловный сбой в том месте, где он стоит). NET не поддерживает пропуск совпадения в заданную c позицию и возобновление поиска с этой ошибочной позиции.

Я предлагаю заменить все запятые, которые не находятся во вложенных скобках, на какое-то временное значение, а затем разделить строку этим значением:

var s = Regex.Replace(text, @"\((?>[^()]+|(?<o>)\(|(?<-o>)\))*(?(o)(?!))\)|(,)", m =>
     m.Groups[1].Success ? "___temp___" : m.Value);
var results = s.Split("___temp___");

Подробности

  • \((?>[^()]+|(?<o>)\(|(?<-o>)\))*(?(o)(?!))\) - шаблон, соответствующий вложенным скобкам:
    • \( - ( char
    • (?>[^()]+|(?<o>)\(|(?<-o>)\))* - 0 или более вхождений
      • [^()]+| - 1+ символов, отличных от ( и ) или
      • (?<o>)\(| - ( и a значение помещается в стек «o» группы
      • (?<-o>)\) - ), а значение извлекается из стека «o» группы
    • (?(o)(?!)) - условная конструкция, которая не соответствует, если стек группы "o" не пуст
    • \) - ) char
  • | - или
  • (,) - Группа 1: запятая

Только запятая в Группе 1 заменяется временной подстрокой, поскольку проверка m.Groups[1].Success выполняется в части оценки матча.

0 голосов
/ 10 апреля 2020

Вы можете сопоставить свои токены за один проход, используя регулярное выражение. NET Балансировочные группы :

(?>
    (?<S>\()      # if you see an open parentheses, push it to the stack
    |
    (?<-S>\))     # match a closing parentheses when the stack has a paired open parentheses
    |
    [^,()]        # match any character except parentheses or commas
    |
    (?(S),|(?!))  # if we're already inside parentheses, we're allowed to match a comma
)+
(?(S)(?!))    # at the end, make sure there are no extra open parentheses we didn't close.

Вы можете получить токены как:

var matches = Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace)
                   .Select(m => m.Value).ToList();

Рабочий пример в Sharp Labs

Этот подход немного сложен, но его синтаксис можно расширить без особых проблем. Например, мы можем добавить поддержку -- single line SQL comments комментариев и 'SQL strings':

(?>
    (?<S>\()
    |
    (?<-S>\))
    |
    --.*                 # match from "--" to the end of the line
    |
    '[^']*(?:''[^']*)*'  # match SQL string, single quote, escaped by two single quotes
    |
    [^,()]
    |
    (?(S),|(?!))
)+
(?(S)(?!))

Рабочий пример

0 голосов
/ 09 апреля 2020

Пожалуйста, расцените это как расширенный комментарий. В псевдокоде запятые, не заключенные в круглые скобки, могут быть идентифицированы следующим образом:

commas = []
n = 0
for each index i of string
   c = char at index i of string
   if c == '('
     increase n by 1
   elsif c == ')'
     decrease n by 1 if n > 0, else raise unbalanced parens exception
   elsif c == ','
     add i to commas if n equals 0
   end
end
raise unbalanced parens exception if n > 0

Массив comma будет содержать индексы запятых, на которые нужно разделить строку. Разбить строку по заданным индексам просто.

Переменная n равна числу левых скобок, которые еще не сопоставлены с правыми скобками. Код также подтверждает, что скобки сбалансированы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...