Отложенное сопоставление регулярных выражений - PullRequest
0 голосов
/ 09 июля 2020

У меня есть эта строка

(Mozilla/5.0 \(X11; Linux x86_64\) AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/data Safari/data2) /Producer (Skia/PDF m80) /CreationDate (D:20200420090009+00'00') /ModDate (D:20200420090009+00'00')

Я хочу получить первое вхождение (), где нет \ before (или). В этом случае я бы получил

(Mozilla/5.0 \(X11; Linux x86_64\) AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/data Safari/data2)

Я использую это выражение регулярного выражения

\([\s\S]*[^\\]{1}\)?

Однако я получаю всю строку

1 Ответ

2 голосов
/ 09 июля 2020

Ваше регулярное выражение может быть разбито следующим образом.

[Пробелы и символы новой строки предназначены для ясности]

\(             match a literal (
  [\s\S]*      match 0 or more of whitespace or not-whitespace (anything)
  [^\\]{1}     match 1 thing which is not \
\)?            optionally match a literal )

regex101 demo

Это тот [\s\S]*, который в конечном итоге хлюпает во всем.

? на конце не означает ленивость, это делает сопоставление ) необязательным. Чтобы быть ленивым, ? нужно поставить перед открытым квалификатором, например *? или +?, {3,}? или {1,5}?.

Чтобы соответствовать только первому набору круглых скобок, мы хотим лениво сопоставлять что-либо между неэкранированными скобками. Ленивое сопоставление чего-либо - это просто .*?.

Сопоставление неэкранированных скобок немного сложнее. Мы могли бы сопоставить [^\\]\), но для этого требуется соответствующий символ. Это не сработает, если открывающая скобка находится в начале строки, потому что перед ( нет символа. Мы можем решить эту проблему, также сопоставив начало строки: (?:[^\\]|^)\).

(?:           non-capturing group
  [^\\]         match a non \
  |             or
  ^             the beginning of the string
) 
\(            match a literal (
  .*?         lazy match 0 or more of anything
[^\\]         match a non \ 
\)            match a literal )

regex101 demo

Но это будет сорвано (). Соответствует всем ()(foo).

(?:[^\\]|^) соответствует началу строки. \( соответствует первому (. Остается .*?[^\\]\) смотрит на )(foo). Первый ) не соответствует, потому что нет ведущего символа, он уже был использован. Итак, .*? поглощает символы, пока не получит его o), которое соответствует [^\\]\).

Граничную проблему лучше решить отрицательным взглядом назад . (?<!\\) говорит, что предыдущий символ не должен быть \, который вообще не включает никаких символов. Взгляд назад не потребляет то, что совпадает, поэтому его можно использовать, чтобы заглянуть вперед и назад. Большинство, но не все движки регулярных выражений поддерживают их.

(?<!\\) \(    match a literal ( which is not after a \
  .*?         lazy match 0 or more of anything
(?<!\\) \)    match a literal ) which is not after a \

regex101 demo

Однако есть библиотеки для синтаксического анализа User-Agents. ua-parser имеет библиотеки для многих языков,

...