Сопоставить регулярное выражение с изменяемой длиной слова и отрицательной информацией о другом слове?

У меня есть регулярное выражение, которое захватывает шаблон A, только если строка содержит шаблон B где-то до A.

Скажем, ради простоты, что А является \b\d{3}\b (т. е. три цифры), а B - это слово "foo".

Поэтому регулярное выражение у меня есть (?<=\b(?:foo)\b.*?)(?<A>\b\d{3}\b),

(?<=               # look-behind
    \b(?:foo)\b    # pattern B
    .*?            # variable length
)
(?<A>\b\d{3}\b)    # pattern A

Например, для строки

"foo text 111, 222 and not bar something 333 but foo 444 and better 555"

это захватывает

(111, 222, 333, 444, 555)

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

(?<=               # look-behind
    \b(?:foo)\b    # pattern B
    ???????????    # anything that does not contains pattern C
)
(?<A>\b\d{3}\b)    # pattern A

Итак, в примере строки мне нужно будет захватить

(111, 222, 444, 555)

Конечно что то типа (?<=\b(?:foo)\b.*?)(?<!\b(?:bar)\b.*?)(?<A>\b\d{3}\b)

(?<=               # look-behind
    \b(?:foo)\b    # pattern B
    .*?
)
(?<!               # negative look-behind
    \b(?:bar)\b    # pattern C
    .*?
)
(?<A>\b\d{3}\b)    # pattern A

не сработает, так как исключит все после первого появления "бара" и захват будет

(111, 222)

Регулярное выражение (?<=\b(?:foo)\b(?!.*?(?:\bbar\b)).*?)(?<A>\b\d{3}\b)

(?<=                     # look-behind
    \b(?:foo)\b          # pattern B
    (?!                  # negative lookahead
        .*?              # variable lenght
        (?:\bbar\b)      # pattern C
    )
    .*?                  # variable lenght
)
(?<A>\b\d{3}\b)          # pattern A

тоже не будет работать, потому что для первого "foo" в моей тестовой строке он всегда найдет "bar" в качестве суффикса и будет захватывать только

(444, 55)

До сих пор, используя условное сопоставление выражений и (теперь) зная, что, находясь внутри объекта просмотра, .net сопоставляет и захватывает справа налево, я смог создать следующее регулярное выражение (?<=(?(C)(?!)| (?:\bfoo\b))(?:(?<!\bbar)\s|(?<C>\bbar\s)|[^\s])*)(?<A>\b\d{3}\b)

(?<=                     # look-behind
    (?(C)                # if capture group C is not empty
        (?!)             # fail (pattern C was found)
        |                # else
        (?:\bfoo\b)      # pattern B
    )
    (?:
        (?<!\bbar)\s     # space not preceeded by pattern C (consume the space)
        |
        (?<C>\bbar\s)    # pattern C followed by space (capture in capture group C)
        |
        [^\s]            # anything but space (just consume)
    )*                   # repeat as needed
)
(?<A>\b\d{3}\b)          # pattern A

это работает, но слишком сложно, так как шаблоны A, B и C намного сложнее, чем примеры, которые я выложил здесь.

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

3 ответа

Решение

Один простой вариант очень похож на второй шаблон Казимира и Ипполита:

foo(?>(?<A>\b\d{3}\b)|(?!bar).)+
  • Начать с foo
  • (?>...|(?!bar).)+ - Стоп соответствия, если вы видели bar,
  • (?<A>\b\d{3}\b) и захватить все, что вы видите по пути.
  • Атомная группа (?>) в этом случае нет необходимости, возвращение назад не испортит это в любом случае.

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

Точно так же это может быть преобразовано в вид сзади:

(?<=foo(?:(?!bar).)*?)(?<A>\b\d{3}\b)

Преимущество состоит в сопоставлении только чисел. Взгляд сзади утверждает, что есть foo до А, но нет bar,
Рабочий пример

Оба они предполагают, что B и C несколько просты.

Вы можете использовать шаблон на основе \G Якорь, который соответствует позиции после предыдущего матча:

(?:\G(?!\A)|\bfoo\b)(?:(?!\b(?:bar|\d{3})\b).)*(\d{3})

демонстрация

подробности:

(?:
    \G(?!\A) # contiguous to a previous match and not at the start of the string
  |        # OR
    \bfoo\b  # foo: the condition for the first match
)
(?:(?!\b(?:bar|\d{3})\b).)* # all that is not "bar" or a 3 digit number (*)
(\d{3})

(*) Обратите внимание, что если вы можете использовать лучший подшаблон (то есть, который не проверяет каждый символ с заглядыванием, содержащим чередование) для вашей реальной ситуации, не стесняйтесь менять его. (например, что-то на основе классов символов: [^b\d]*(?>(?:\B[b\d]+|b(?!ar\b)|\d(?!\d\d\b))[^b\d]*)*)


Другой способ: поскольку механизм регулярных выражений.net может хранить повторные записи, вы также можете написать это:

\bfoo\b(?:(?:(?!\b(?:bar|\d{3})\b).)*(\d{3}))+

Но на этот раз вам нужно зацикливаться на каждом появлении foo для извлечения результатов в группе 1. Это менее удобно, но шаблон быстрее, так как он не начинается с чередования.

Обратите внимание, что если "bar" а также "\d{3}" начинается и заканчивается символами слова, вы можете написать шаблон более эффективным способом:

\bfoo(?:\W+(?>(?!bar\b)\w+\W+)*?(\d{3}))+\b

Другой способ: разделить строку на "foo" и "bar" (сохраните разделитель), перебрать каждую часть. Если для части задано значение "foo", установите для флага значение "истина", для части для "bar" установите значение "ложь", а если для этого параметра не указано "foo" или "bar", извлеките числа, если флаг имеет значение true.

Так как вы спросили, это возможно с балансировкой групп, но, вероятно, не нужно.

\A                    # Match from the start of the string
(?>                   # Atomic group. no backsies.
    (?<B>(?<-B>)?foo)            # If we see "foo", push it to stack B.
                                 # (?<-B>)? ensures B only has one item - if there are two,
                                 # one is popped.
    |(?<-B>bar)                  # When we see a bar, reset the foo.
    |(?(B)(?<A>\b\d{3}\b)|(?!))  # If foo is set, we are allowed to capture A.
    |.                           # Else, just advance by one character.
)+
\z                    # Match until the end of the string.

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

Если мы хотим быть очень умными (чего, вероятно, нет), мы можем объединить большинство веток в условные:

\A
(?>
  (?(B)
    (?:(?<A>\b\d{3}\b)|(?<-B>bar))
    | # else
    (?<B>foo)
  )
  |.
)+
\z

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

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

Другие вопросы по тегам