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

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

Имеется следующее регулярное выражение:

var regex = "^.*?"
          + "(?:(?=.*?message.+?(?<msg>[0-9]+)))?"
          + ".*?"
          + "(?:(?=.*?part.+?(?<part>[0-9]+)))?"
          + ".*?"
          + "(?:(?=.*?order.+?(?<order>[0-9]+)))?"
          + ".*?$";

И строки, которые обрабатываются этим выражением:

abc part is created (message 42)
abc (message 42) part.id=24
abc order.id=123, (message 42) part.id=321
[message 42] Part with id 34 added to Order 22
abc Order (id=3, part.id=56) is generated "message 42"
Can't create order, becasue part 4 not found
Order 5 cannot be created because a part is missing

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

Проблема в том, что при отсутствии числа у первой группы число "захватывается" у следующей. В примере выше это происходит:

  • в первой строке, где группы part и message захватывают одно и то-же число 42;
  • в предпоследней, где группы order и part захватывают одно и то-же число 4;

Каким образом можно исключить наложение групп? Внедрить каким-то образом негативные/позитивные опережающие/ретроспективные проверки?


Ответы

▲ 0

Если вербализировать ту идею которую подал @Stanislav Volodarskiy то получится примерно такой код возвращающий в данном случае массив массивов где подмасив состоит из ключевого слова и значения:

const strings = [
"abc part is created (message 42)",
"abc (message 42) part.id=24",
"abc order.id=123, 5 (message 42) 5 part.id=321 5",
"[message 42] Part with id 34 added to Order 22",
"abc Order (id=3, part.id=56) is generated \"message 42\"",
"Can't create order, becasue part 4 not found",
"Order 5 cannot be created because a part is missing"]

for (const str of strings){
    const temp = []
    for (const spart of str.matchAll(/part|message|order|[0-9]+/gi)){
      temp.push(spart) //собираем только те части которые нам интересны
    }
    const merge = temp.join(' ').toLowerCase() // стандартизируем вывод
    console.log(
       Array.from(merge.matchAll(/(?<name>\w+)\s(?<val>\d+)/g))
         .map(e => (Object.values({...e.groups}))) // создаем подмасивы из пар 
         .toString() // для компактного вывода [[k1, v1], [k2, v2], ...]
    )
}

Для компактности вывода в консоли сниппета пришлось пожертвовать квадратными скобками

[ [ 'message', '42' ] ]
[ [ 'message', '42' ], [ 'part', '24' ] ]
[ [ 'order', '123' ], [ 'message', '42' ], [ 'part', '321' ] ]
[ [ 'message', '42' ], [ 'part', '34' ], [ 'order', '22' ] ]
[ [ 'order', '3' ], [ 'part', '56' ], [ 'message', '42' ] ]
[ [ 'part', '4' ] ]
[ [ 'order', '5' ] ]

В третью строку я специально добавил три 5 чтобы посмотреть на корректность работы алгоритма

Вариант через регулярку (нужна донастройка)

Основная идея - использовать негативный просмотр ?!, Указываем какие группы мы не ожидаем в этом месте (?!.*order.*)|(?!.*part.*)

Код:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Example {
    public static void main(String[] args) {
        //final String regex = "^.*?(?:(?=.*?message.+?(?<msg>[0-9]+)))?.*?(?:(?=.*?part.+?(?<part>[0-9]+)))?.*?(?:(?=.*?order.+?(?<order>[0-9]+)))?.*?$";
        
        final String regex = "^.*?(?:(?=.*?message(?:(?!.*order.*)|(?!.*part.*)|(?:[.]id=|[ ]))(?<msg>[0-9]+)))?"
         + ".*?(?:(?=.*?part(?:(?!.*order.*)|(?!.*message.*)|(?:[.]id=|[ ]))(?<part>[0-9]+)))?"
         + ".*?(?:(?=.*?order(?:(?!.*part.*)|(?!.*message.*)|(?:[.]id=|[ ]))(?<order>[0-9]+)))?.*?$";
        
        final String string = "abc part is created (message 42)\n"
             + "abc (message 42) part.id=24\n"
             + "abc order.id=123, (message 42) part.id=321\n"
             + "[message 42] Part with id 34 added to Order 22\n"
             + "abc Order (id=3, part.id=56) is generated \"message 42\"\n"
             + "Can't create order, becasue part 4 not found\n"
             + "Order 5 cannot be created because a part is missing";
        
        final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
        final Matcher matcher = pattern.matcher(string);
        
        while (matcher.find()) {
            System.out.println("Full match: " + matcher.group(0));
            System.out.println("Group message" + ": " + matcher.group("msg"));
            System.out.println("Group part" + ": " + matcher.group("part"));
            System.out.println("Group order" + ": " + matcher.group("order"));
        }
    }
}

Вывод:

Full match: abc part is created (message 42)
Group message: 42
Group part: null
Group order: null

Full match: abc (message 42) part.id=24
Group message: 42
Group part: 24
Group order: null

Full match: abc order.id=123, (message 42) part.id=321
Group message: 42
Group part: 321
Group order: 123

Full match: [message 42] Part with id 34 added to Order 22
Group message: 42
Group part: null // нужно доработать регулярку для with id
Group order: 22

Full match: abc Order (id=3, part.id=56) is generated "message 42"
Group message: 42
Group part: 56
Group order: null // нужно доработать регулярку для (id=3

Full match: Can't create order, becasue part 4 not found
Group message: null
Group part: 4
Group order: null

Full match: Order 5 cannot be created because a part is missing
Group message: null
Group part: null
Group order: 5

Пример доработки регулярки для конкретных случаев:

  • для Part with id будет | with id |
  • для Order (id=3 будет | \\(id=| экранируем скобку

Вся регулярка:

     final String regex = "^.*?(?:(?=.*?message(?:(?!.*order.*)|(?!.*part.*)|(?:[.]id=| with id | \\(id=|[ ]))(?<msg>[0-9]+)))?"
 + ".*?(?:(?=.*?part(?:(?!.*order.*)|(?!.*message.*)|(?:[.]id=| with id | \\(id=|[ ]))(?<part>[0-9]+)))?"
 + ".*?(?:(?=.*?order(?:(?!.*part.*)|(?!.*message.*)|(?:[.]id=| with id | \\(id=|[ ]))(?<order>[0-9]+)))?.*?$";