在Lookbehind中具有条件的正则表达式

时间:2014-04-23 19:44:44

标签: .net regex regex-lookarounds

这是使用.NET正则表达式引擎。

我试图在lookbehind子句中使用条件。当表达式在lookbehind之外使用时,它的行为与我期望的一样 - 但是当放置在lookbehind中时,它具有不同的行为。

以下是尝试复制问题的效果的简单示例。

匹配

good morning

使用正则表达式:

(?<=(?(?=go)good|bad)\s)morning

不会产生任何匹配。

如果没有看后面的尝试:

(?(?=go)good|bad)\smorning

我得到一场比赛&#34;早上好&#34;

通过摆弄,我发现前瞻性光标位置,当它在lookbehind内时,是在&#34; good&#34;:

之后
(?<=(?(?=\smor)good|bad)\s)morning

这匹配&#34;早上&#34;。

我的问题是这是预期还是某种错误?

显然这个例子不是现实世界 - 当我偶然发现这个问题时我试图解决的问题如下:表达式使用条件来确定下一个单词的长度,然后使用两组不同的规则用于匹配该单词。类似于:

(?<=\s+(?(?=[^\s]{1,2}\s)[A-Z0-9]+|(?![A-Z]+\s)[0-9-A-Z/"']+))\s+matching\s+text

匹配&#34;匹配的文字&#34;只有当一个或两个字母单词由字母和数字组成,或者一个较长的单词不仅包含字母而且可以包含数字,字母,斜杠,短划线,引号和撇号时。

以下内容应匹配&#34;匹配文字&#34;:

1 matching text
a matching text

它只匹配第一个,因为条件评估为假(它正在查看&#34;匹配&#34;而不是&#34; a&#34;)和负面展望搜索a包含所有字母的单词在&#34; a&#34;。

上失败

进一步的例子:

必须匹配&#34;匹配文字&#34;:

123-1 matching text
9B matching text
15/16 matching text
"45" matching text
A matching text
AA matching text
A1 matching text

不得匹配&#34;匹配文字&#34;

and matching text
" matching text
A- matching text

5 个答案:

答案 0 :(得分:3)

您的问题非常适合深入了解外观和前瞻。这是一个很丰富的问题​​,但对我来说似乎主要的问题是:

为什么(?<=(?(?=go)good|bad)\s)morning失败了#34;早上好&#34;

因为你说&#34;我的问题是这个预期还是某种错误?&#34;这是我要解决的问题。

简而言之,在字符串中的某个位置x,你想说:

  1. 如果紧接着是&#34;去&#34;即(?=go)然后立即 匹配&#34;早上&#34;。显然这是不相容的。如果你有 &#34; gorning&#34;,你将有机会。紧随其后的是不可能的 两者都是&#34; mo&#34;和&#34;去&#34;。该分支中有更多语法,但这就是它失败的地方。
  2. 否则,(即后面的内容不是&#34;去&#34;),那么如果紧接着的是&#34;坏&#34;,则匹配&#34;上午&#34;。这将匹配&#34;早晨&#34;在&#34;早上好&#34;,但不是&#34;早上好&#34;。
  3. 因此正则表达式无法匹配&#34;早上好&#34;。 QED。

    现在让我们看看几乎相同表达的正确程序。它成功匹配&#34; goodgopher&#34;和#34; badphilosopher&#34;。一旦你理解为什么这有效,你就会明白为什么对方没有。

    using System;
    using System.Text.RegularExpressions;
    class Program {
    static void Main() {
    
    // Simplest    
    string s1 = "badphilosopher";
    string s2 = "goodgopher";
    string s3 = "badgopher";
    string pattern = @"(?<=(?(?=go)good|bad))\w+pher";
    Console.WriteLine(Regex.IsMatch(s1, pattern) );
    Console.WriteLine(Regex.IsMatch(s2, pattern));
    Console.WriteLine(Regex.IsMatch(s3, pattern));     
    Console.WriteLine("\nPress Any Key to Exit.");
    Console.ReadKey();
    } // END Main
    } // END Program
    

    输出:

    True
    True
    False
    

    关键是理解一个外观断言:&#34;在字符串中的这个确切位置,我被跟随(或前面)......&#34;

    评估时,外观在字符串的一部分中牢牢固定。如果你在一行中串起几个外观,那么这个位置就不会跳转。

    那么上面的正则表达式是如何工作的?

    在字符串中的当前位置,我们声明:

    If at this position in the string, if we are preceded by x
       case 1: if at this position in the string what follows is "go"
               then x is good
       case 2: else
               then x is bad
    Then match any number of word characters followed by philosopher
    

    为了清楚地了解前瞻和后瞻,您可能需要阅读此页面关于regex lookarounds

    评论中的子问题:

    (?<=(?(?=go)good|bad))\s+\w+pher匹配&#34;糟糕的哲学家&#34;并且&#34;坏的gopher&#34;。为什么?

    这就是原因。在任何一个&#34;坏哲学家&#34;或者&#34;糟糕的地鼠&#34;,把自己放在&#34; d&#34;之后。这是你的位置x。在这个位置,

    1. 您可以匹配\s+\w+pher,因为这正是您面前的
    2. 在这两种情况下,位置x,紧接着的内容不是&#34; go&#34;:在&#34; d&#34;之后,在一种情况下,接下来的是&#34; g&#34;,在另一种情况下,它是&#34; P&#34; (注意字母前面的空格)。因此,对于这两者,你不是(?=go)情况,而是在其他情况下,你必须断言&#34;紧接在位置x之前的是&#34;坏&#34;。真的吗?是。
    3. 因此正则表达式可以在两个字符串中找到匹配项。 QED。但是匹配并没有找到你想要的地方,而是在&#34; d&#34;。
    4. 之后

      但......发动机真正做了什么?

      到目前为止,我还没能说服OP关于正则表达式引擎所采用的路径,所以我想我会粘贴一条痕迹。遗憾的是,我没有.NET正则表达式的跟踪工具,但我确实有一个用于PCRE的跟踪工具,一个同样强大的正则表达式引擎。

      跟踪显示了pcretest报告的引擎路径,用于三个测试字符串。请注意上面提供的.NET代码中与正则表达式的一个细微差别:我们good|bad代替goo|bad以适应PCRE缺乏对可变长度负面外观的支持。

      跟踪清楚地显示PCRE从左到右评估正则表达式:首先是lookbehind,然后是\w+pher

      我无法100%确定.NET以同样的方式进行,但这肯定是正则表达式引擎的标准。当然,支持可变长度外观的引擎有可能以不同的方式进行。

      PCRE version 8.34 2013-12-15
      
      ~(?<=(?(?=go)goo|bad))\w+pher~C
      badphilosopher
      --->badphilosopher
       +0 ^                  (?<=(?(?=go)goo|bad))
       +0  ^                 (?<=(?(?=go)goo|bad))
       +0   ^                (?<=(?(?=go)goo|bad))
       +0    ^               (?<=(?(?=go)goo|bad))
       +4    ^                                      ^(?(?=go)goo|bad)
       +6    ^                                      ^(?=go)
       +9    ^                                      ^g
      +16    ^                                      ^b
      +17    ^                                      ^a
      +18    ^                                      ^d
      +19    ^               )
      +20    ^               )
      +21    ^               \w+
      +24    ^          ^    p
      +24    ^         ^     p
      +24    ^        ^      p
      +24    ^       ^       p
      +24    ^      ^        p
      +25    ^       ^       h
      +26    ^        ^      e
      +27    ^         ^     r
      +28    ^          ^    
       0: philosopher
      googopher
      --->googopher
       +0 ^             (?<=(?(?=go)goo|bad))
       +0  ^            (?<=(?(?=go)goo|bad))
       +0   ^           (?<=(?(?=go)goo|bad))
       +0    ^          (?<=(?(?=go)goo|bad))
       +4    ^                                 ^(?(?=go)goo|bad)
       +6    ^                                 ^(?=go)
       +9    ^                                 ^g
      +10    ^                                 ^o
      +11    ^                                 ^)
      +12    ^                                 ^g
      +13    ^                                 ^o
      +14    ^                                 ^o
      +15    ^          |
      +20    ^          )
      +21    ^          \w+
      +24    ^     ^    p
      +24    ^    ^     p
      +24    ^   ^      p
      +24    ^  ^       p
      +24    ^ ^        p
      +25    ^  ^       h
      +26    ^   ^      e
      +27    ^    ^     r
      +28    ^     ^    
       0: gopher
      badgopher
      --->badgopher
       +0 ^             (?<=(?(?=go)goo|bad))
       +0  ^            (?<=(?(?=go)goo|bad))
       +0   ^           (?<=(?(?=go)goo|bad))
       +0    ^          (?<=(?(?=go)goo|bad))
       +4    ^                                 ^(?(?=go)goo|bad)
       +6    ^                                 ^(?=go)
       +9    ^                                 ^g
      +16    ^                                 ^b
      +17    ^                                 ^a
      +18    ^                                 ^d
      +19    ^          )
      +20    ^          )
      +21    ^          \w+
      +24    ^     ^    p
      +24    ^    ^     p
      +24    ^   ^      p
      +24    ^  ^       p
      +24    ^ ^        p
      +25    ^  ^       h
      +26    ^   ^      e
      +27    ^    ^     r
      +28    ^     ^    
       0: gopher
      

答案 1 :(得分:0)

您可以尝试这样的事情:

(?<=(?:(?=\S*\d)[\p{P}\d]+|[A-Z0-9]+)\s)matching text

答案 2 :(得分:0)

我们可以从\s+matching\s+text开始。 要消除and matching text和类似的匹配,我们可以添加负面的lookbehind:(?<!^\s*[A-Z]{3,})

为了包括其他人的匹配,我们可以使用积极的lookbehind:

匹配2个字母的模式:[A-Z\d]{1,2}

匹配更长的模式:[-A-Z\d/"']{3,}&lt; = important,我们删除了仅由具有负后看的字母组成的字符串。

将2种模式组合在一起,我们得到以下结果:(?<=^\s*([A-Z\d]{1,2}|[-A-Z\d/"']{3,}))

完整正则表达式将是:

(?<!^\s*[A-Z]{3,})(?<=^\s*([A-Z\d]{1,2}|[-A-Z\d/"']{3,}))\s+matching\s+text

答案 3 :(得分:0)

我认为这里不需要任何外观。只需正常匹配整个字符串并使用捕获组来提取您感兴趣的部分:

Regex r = new Regex(
    @"(?imn)^([A-Z0-9]{1,2}\s+|(?![A-Z]+\s)[0-9A-Z/""'-]{3,}\s+)(?<captured>matching\s+text)$");
resultString = r.Match(s).Groups["captured"].Value;

答案 4 :(得分:-3)

我想我现在明白了这个问题。 lookbehind的条件内部和不在lookbehind中的条件之间的重要区别是条件执行的时间(或者搜索光标在那个点的时间。

没有lookbehind的例子:

(?(?=go)good|bad)\smorning

早上好

条件在搜索开始时运行。所以搜索光标位于&#39;之前。在&#39; good&#39;。

 good morning
^

所以此时前瞻评估为TRUE,因为它看到&#39; go&#39;

上的匹配

在具有lookbehind的示例中,光标位于不同的位置。

(?<=(?(?=go)good|bad)\s)morning

搜索光标找到文本中的第一个必需项目:&#39; morning&#39;

good morning
     ^

实际的搜索光标留在原处消费早上&#39;如果是背后的比赛。 lookbehind将使用自己的光标来验证早上之前的情况。确定这个早上&#39;是一场比赛。后卫表示有一个&#39; \ s&#39;直到早上&#39;确实存在。临时lookbehind游标移动到空间:

good morning
    ^^

现在它进入条件并在条件语句中运行前瞻。在这一点上,先行者正在寻找“去”。但它看到&#39;上午&#39 ;.所以条件失败了。表达方式表示要尝试匹配“坏”&#39; (或从后面的光标向后轻拍)但它看起来很好(或从后面的光标向后推)。所以没有匹配早上&#39;。

解决方案

由于条件是在感兴趣的词的末尾运行时它处于一个后视(而不是在一个外观后面的那个词的开头),所以秘密是将条件反转为一个后视而不是一个先行:

(?<=(?(?<=good)good|bad)\s)morning

这实际上匹配早晨。在这个例子中看起来没什么意义来寻找一个单词然后匹配它 - 但它说明了这个概念。在问题中陈述的现实案例中,解决方案如下所示:

(?<=(?(?<=\s\S{1,2})[A-Z0-9]+|(?![A-Z]+\s)[0-9-A-Z/"']+))\s+matching\stext

在匹配文本之前查找单词。条件检查以查看它是一个还是两个字符的单词。如果是这样,它应该只包含字母和数字。如果没有,它不仅必须由字母组成。如果满足,则可以包含字母,数字,短划线,斜线,引号和撇号。

改变了两件事:

  1. 修改条件为lookbehind而不是lookahead
  2. 我不得不移动&#39; \ s&#39;从lookbehind的开始到条件,因为条件在该空格之前被处理,并且它导致匹配单词的最后一个或两个字符而不是寻找一个或两个字符单词。这是一个棘手的问题,因为当表达式不在后面时(例如,如果你希望lookbehind中的文本包含在匹配中),这种改变会使匹配变得混乱。

  3. 以下是对原始问题的更多分析。享受!

    @ zx81有一个实际上更好地说明正在发生的事情的例子。这个例子有更多的光标移动,所以它确实有助于说明发生了什么:

        (?<=(?(?=go)good|bad))\w+pher
    
    badphilosopher    <-- 'philosopher' matches
    goodgopher        <-- 'gopher' matches
    badgopher         <-- no match
    

    这个例子有很大的不同,因为使用了\ w +。因此,正则表达式引擎会立即匹配每个示例中的所有文本,因为该短语没有空格并且以“pher”结尾。

    所以对于&#39; badphilosopher&#39;:

    运行Lookbehind并立即运行条件查找&#39; go&#39;但发现&#39; ba&#39;

    badphilosopher
    ^
    

    条件失败,所以它尝试将错误匹配到光标左侧,但是我们位于短语的开头,所以没有匹配。

    它再次检查这两个光标点,因为&#39; \ w + pher&#39;每次匹配:

    badphilosopher
      ^
    

    但是后卫看到&#39; b&#39;然后&#39; ba&#39;

    当光标到达:

    badphilosopher
       ^
    

    该条件再次未能找到“去”。 (它看到&#39; ph&#39;)所以它试图匹配“坏”&#39;在光标的左边找到它!因此,\ w + pher与哲学家文本匹配。

    goodgopher
        ^
    
    除了条件成功之外,

    goodgopher以类似的方式匹配。

    badgopher
       ^
    

    badgopher不匹配,因为条件是成功的,但是很好的&#39;找不到光标的左侧。

    在那里放置一个空格确实会改变,因为/ w + pher不再匹配整个字符串。

        (?<=(?(?=go)good|bad)\s+)\w+pher
    
    bad philosopher    <-- matches philosopher
    good gopher        <-- no match
    bad gopher         <-- matches gopher
    

    在这种情况下,光标在字符串中移动,直到它匹配\ w + pher:

    bad philosopher
        ^
    

    此时它开始处理后视 - 并看到&#39; \ s +&#39;在搜索光标的左侧需要它 - 它找到它并将临时lookbehind光标移动到。

    bad philosopher
       ^^
    

    条件现在正在运行,并且看到了&#39; go&#39;在临时看后面的光标但发现&#39; P&#39 ;.失败意味着尝试将坏匹配临时lookbehind游标的左侧,并且确实在那里找到它。

    good gopher
        ^^
    

    好地鼠&#39;示例进入条件并看到&#39;克&#39;所以它失败了然后寻找“坏”&#39;在光标的左侧,并没有找到它。所以这失败了。

    bad philosopher
       ^^
    

    同样,糟糕的哲学家&#39;到了conditonal并发现&#39; P&#39;并寻找“坏”&#39;在光标的左侧找到它。所以它匹配。

    在没有lookbehind的情况下运行时,所有这些示例都匹配。这可能是违反直觉的 - 但你必须在后面考虑光标的位置。