Непоследовательные и необязательные группы захвата

Рейтинг: 0Ответов: 1Опубликовано: 22.02.2023

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

Объясню на примере. Предположим, что у нас есть некоторые строки, например из log-файла, которые обрабатываются с помощью регулярных выражений:

01.01.2021 - 00:00:00 TRACE part is created
01.01.2021 - 00:00:00 TRACE part.id=42
01.01.2021 - 01:10:11 TRACE part.id=11, order.id=1
02.02.2022 - 02:20:22 DEBUG Part with id 2 added to Order 22
03.03.2023 - 03:30:33 INFO Order (id=3, part.id=33) is generated
04.04.2024 - 04:40:44 WARN Can't find part 4
05.05.2025 - 05:50:55 ERROR Order 5 cannot be created because a part is missing

То есть, дата-время, далее один из пяти предопределённых уровней логирования и через пробел сообщение. В строках имеются записи об order и part, которые нас интересуют. Как видно, в некоторых сообщениях сначала идёт part и затем order, но в некоторых строках наоборот. Нас интересуют только те сообщения, где после order или part через некоторое количество символов идёт число, которое с ним ассоциируется.

Если я использую следующее выражение:

/^(?<dateTime>.+) (?<level>(?:TRACE|DEBUG|INFO|WARN|ERROR)).*(?:part[^0-9]+(?<part>[0-9]+)?).*(?:order[^0-9]+(?<order>[0-9]+)).*$/gmi

Match 1: 01.01.2021 - 01:10:11 TRACE part.id=11, order.id=1
dateTime: 01.01.2021 - 01:10:11
level: TRACE
part: 11
order: 1


Match 2: 02.02.2022 - 02:20:22 DEBUG Part with id 2 added to Order 22
dateTime: 02.02.2022 - 02:20:22
level: DEBUG
part: 2
order: 22

, то получаю совпадения для тех строк, где part и order определены в том-же порядке, как и в регулярном выражении. Если же указываю квантификатор ? для группы захвата part (0 или один раз), то строки Order (id=3, part.id=33) is generated и Order 5 cannot be created because a part is missing также попадают в группу захвата order. Если же указать квантификатор ? для группы захвата order, то в группы захвата order и part вообще ничего не попадает, они как будто пропускаются. Если использовать pipe-символ, например так:

(?:(?:part[^0-9]+(?<part>[0-9]+)?)|(?:order[^0-9]+(?<order>[0-9]+)))

, то в группу захвата попадает только одно значение: либо order, либо part.

Каким образом в регулярном выражении определить необязательные группы захвата в произвольном порядке при их наличии в строке не более одного раза?


Поиграться с выражением можно здесь.

Ответы

▲ 0

Благодаря teran и WiktorStribiżew было найдено решение с использованием положительных опережающих проверок (positive lookahead). Помимо этого пришлось "оборачивать" каждую группу в долнительные скобки, т.к. при использовании ?= не получается указать квантификатор ? для группы:

var regex = "^"
    + "(?<dateTime>.{19}) "
    + "(?<level>[a-z]+) .*?"
    + "(?:(?=.*?message.+?(?<msg>[0-9]+)))?.*?"
    + "(?:(?=.*?part.+?(?<part>[0-9]+)))?.*?"
    + "(?:(?=.*?order.+?(?<order>[0-9]+)))?.*?"
    + "$";

Пример работы

Это решило проблему несоответствия следования групп в выражении и строке, но как видно из примера работы осталась проблема захвата разными группами одного числа при отсутствии оного для предыдущей группы. Например, в первой строке представленного примера, группы part и message захватывают одно и то-же число, так как для группы part соответствующее число отсутствует.