将正则表达式与一个单词的可变长度后视和另一个单词的可变长度负后视相匹配?

match regex with variable length look-behind of a word and variable length negative look-behind of another word?

我有一个正则表达式捕获模式 A 只有当字符串在 [=54= 之前的某处包含模式 B ]A.

假设,为了简单起见,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

将不起作用,因为它会在 "bar" 第一次出现后排除所有内容,捕获将是

(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)

到目前为止,使用 Conditional Matching of Expressions and (now) knowing that while inside a lookbehind, .net matches and captures from the right to the left,我能够创建以下正则表达式 (?<=(?(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

有效但过于复杂,因为模式 ABC 是比我在这里发布的示例要复杂得多。

是否可以简化这个正则表达式?也许使用平衡组?

您可以使用基于 \G 锚点的模式来匹配上一个匹配项之后的位置:

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

demo

详情:

(?:
    \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 regex engine 可以存储重复的捕获,你也可以这样写:

\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" 则提取数字旗帜是真的。

一个简单的选项与 Casimir et Hippolyte 的第二个模式非常相似:

foo(?>(?<A>\b\d{3}\b)|(?!bar).)+
  • foo
  • 开始
  • (?>|(?!bar).)+ - 如果您看到 bar.
  • ,请停止匹配
  • (?<A>\b\d{3}\b) 并捕获您沿途看到的所有 A。
  • 在这种情况下,原子组 (?>) 不是必需的,回溯不会把事情搞砸。

Working example

同样可以转换为lookbehind:

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

这样做的好处是只匹配数字。回顾断言 A 之前有一个 foo,但没有 bar.
Working example

这两个假设 B 和 C 都比较简单。

既然你问了,平衡组是可能的,但可能不需要。

\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.

Working example

如果我们想变得特别聪明(我们可能不想),我们可以将大多数分支组合成条件语句:

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

Working example

同样,可能,但平衡组不是这里的最佳选择,主要是因为我们没有平衡任何东西,只是检查是否设置了标志。