艰难的正则表达式问题

时间:2013-08-16 16:53:38

标签: .net regex

我需要创建一个单个正则表达式(使用.NET 3.5 Regex,如果这很重要),它允许以下三种可能性:

  1. [A-Z]{2,3}
  2. [A-Z]{2,3}-[A-Z][A-Z0-9]{1,2}
  3. [A-Z]{2,3}-[A-Z][A-Z0-9]{1,2}-[A-Z0-9]{2,3}
  4. 因此BIGBIG-A9BIG-A09-SD会匹配, 但BIG-FBIG-A9S-BIG-A09-S不匹配。

    我应该澄清一些事情:

    • 我需要匹配整条线,而不仅仅是它的一部分。

    • 我正在尝试为正在将此字符串键入TextBox的用户提供反馈。因此,我正在寻找一个正则表达式,我可以用它来测试TextBox的内容,只要它发生变化。例如,当用户键入“A”时,反馈应为负数,因为它们尚未匹配案例1.一旦他们键入“AA”,反馈将为正,因为他们已匹配案例1.在键入“AAE”后,他们应该仍然得到积极的反馈,因为他们已经匹配案例1.如果他们输入“AA7”,反馈应该是负面的。如果他们继续输入并输入“AAE-”,则反馈应该再次为负,因为他们还没有遇到案例2,并且已超过案例1.

    我把头发拉出来(剩下的是什么)。

    那里有人知道如何以一种有效的方式表达这一点吗?

3 个答案:

答案 0 :(得分:5)

您的正则表达式模式可以根据作为其他人前缀的属性进行排序。所以尝试嵌套的可选后缀:

(?:[A-Z]{2,3}(?:-[A-Z][A-Z0-9]{1,2}(?:-[A-Z0-9]{2,3})?)?)

答案 1 :(得分:3)

通常,正则表达式根据最左边最长的规则进行匹配,其正式表达式(来自Posix标准)是:

  

执行搜索,就像测试了字符串的所有可能后缀一样   前缀匹配模式;选择包含匹配前缀的最长后缀,   并且所选后缀的最长可能匹配前缀被标识为   匹配顺序。

您的三种可能性中的每一种都是前一种可能性的扩展。从你的第三个也是最长的开始:

[A-Z]{2,3}-[A-Z][A-Z0-9]{1,2}-[A-Z0-9]{2,3}

它有3个组件,每个组件本身就是一个正则表达式:

  • [A-Z]{2,3}
  • -[A-Z][A-Z0-9]{1,2}
  • -[A-Z0-9]{2,3}

首先,您需要分组每个人:

  • ([A-Z]{2,3})
  • (-[A-Z][A-Z0-9]{1,2})
  • (-[A-Z0-9]{2,3})

然后,最后两个可以合并为更长的正则表达式

  • ([A-Z]{2,3})
  • (-[A-Z][A-Z0-9]{1,2})(-[A-Z0-9]{2,3})

注意第3段是可选的,如果我们有第二段,那么使用后缀运算符'?'这样做:

  • ([A-Z]{2,3})
  • (-[A-Z][A-Z0-9]{1,2})(-[A-Z0-9]{2,3})?

现在将整个第二个组件分组,以表明它是一个不可或缺的部分:

  • ([A-Z]{2,3})
  • ((-[A-Z][A-Z0-9]{1,2})(-[A-Z0-9]{2,3})?)

请注意,如果我们有第一个组件,那么整个第二个组件都是可选的,所以再次使用后缀运算符?来表示选项:

  • ([A-Z]{2,3})
  • ((-[A-Z][A-Z0-9]{1,2})(-[A-Z0-9]{2,3})?)?

最后,连接两个正则表达式以形成最终表达式:

([A-Z]{2,3})((-[A-Z][A-Z0-9]{1,2})(-[A-Z0-9]{2,3})?)?

你去吧。正则表达式引擎不需要回溯和最小前瞻。

已编辑注意从下面的O.P.评论中可以看出,所需的匹配是整个字符串。所以......

默认情况下,正则表达式会在源字符串中找到最左边的最长匹配项。如果要匹配整个字符串整行,则需要使用元模式^ 锚定表达式,其中将该位置的匹配锚定为起始线和$,将该匹配锚定在该位置的行尾。所以......

  • ^abc,匹配前缀,匹配以abc开头的所有字符串(abcabcabcabccaabcdefg等)。它与xyzabcab等<。p>

  • 等内容不匹配 类似地,
  • abc$匹配后缀,匹配以abcabcxyzabc等结尾的任何行。它与abxyzababcxyz等内容不匹配。

  • ^abc$ 匹配字符串abc,而不是其他内容。

要匹配整行,请使用上面的正则表达式并删除锚点:

^([A-Z]{2,3})((-[A-Z][A-Z0-9]{1,2})(-[A-Z0-9]{2,3})?)?$

这就是它的全部。但是,应该注意.^$的行为取决于是否使用MultilineSingleLine编译正则表达式选项。 SingleLine表示^$仅在整个字符串的开头和结尾匹配,而.匹配除\n之外的任何字符。 MultiLine表示^$匹配字符串中任意一行的开头和结尾,.匹配任何字符,包括 {{1 }}

如果要删除匹配的各种组件,可以在正则表达式中添加一些标记以创建命名组

\n

这使您可以按名称访问匹配的组,从而使代码更具可读性:

(?<prefix>[A-Z]{2,3})((-(?<middle>[A-Z][A-Z0-9]{1,2}))(-(?<suffix>[A-Z0-9]{2,3}))?)?

性能提示:使用命名组并使用Regex rx = new Regex( @"(?<prefix>[A-Z]{2,3})((-(?<middle>[A-Z][A-Z0-9]{1,2}))(-(?<suffix>[A-Z0-9]{2,3}))?)?" , RegexOptions.ExplicitCapture ); Match m = rx.Match(s) ; if ( m.Success ) { string prefix = m.Groups["prefix"].Value ; string middle = m.Groups["middle"].Value ; string suffix = m.Groups["suffix"].Value ; Console.WriteLine( "prefix: {0}" , prefix ) ; Console.WriteLine( "middle: {0}" , middle ) ; Console.WriteLine( "suffix: {0}", suffix ) ; } 编译正则表达式可能会有很大帮助,尤其是当正则表达式变得复杂时。

如果你是正则表达式的新手(即使你不是!),你应该阅读Jeffrey Friedl最优秀的 opus Mastering Regular Expressions:< / p>

Mastering Regular Expressions

答案 2 :(得分:0)

就我而言,尼古拉斯凯里提供了答案。大多数答案请参阅他的回复。这本书已经订购了!

为了满足我在键入输入时向用户提供持续反馈的要求,我将以下内容注册为Windows窗体的TextChanged事件处理程序。

    private void TheTextChanged(object sender, EventArgs e)
    {
        var rx = new Regex(@"(?<prefix>[A-Z]{2,3})((-(?<middle>[A-Z][A-Z0-9]{1,2}))(-(?<suffix>[A-Z0-9]{2,3}))?)?", RegexOptions.ExplicitCapture);
        Match m = rx.Match(textbox.Text);

        if (m.Success)
        {
            string prefix = m.Groups["prefix"].Value;
            string middle = m.Groups["middle"].Value;
            string suffix = m.Groups["suffix"].Value;
            if (prefix != "")
            {
                textbox.BackColor = prefix.Equals(textbox.Text) ? Color.LightGreen : Color.LightPink;
                if (middle != "")
                {
                    string twoparts = prefix + "-" + middle;
                    textbox.BackColor = twoparts.Equals(textbox.Text) ? Color.LightGreen : Color.LightPink;
                    if (suffix != "")
                    {
                        string threeparts = prefix + "-" + middle + "-" + suffix;
                        textbox.BackColor = threeparts.Equals(textbox.Text) ? Color.LightGreen : Color.LightPink;
                    }
                }
            }
        }
        else
        {
            textbox.BackColor = Color.LightPink;
        }
    }

这是一个WPF版本,只是因为。

    private void TheTextChanged(object sender, TextChangedEventArgs e)
    {
        var rx = new Regex(@"(?<prefix>[A-Z]{2,3})((-(?<middle>[A-Z][A-Z0-9]{1,2}))(-(?<suffix>[A-Z0-9]{2,3}))?)?", RegexOptions.ExplicitCapture);
        Match m = rx.Match(textbox.Text);

        if (m.Success)
        {
            string prefix = m.Groups["prefix"].Value;
            string middle = m.Groups["middle"].Value;
            string suffix = m.Groups["suffix"].Value;
            if (prefix != "")
            {
                textbox.Background = prefix.Equals(textbox.Text) ? Brushes.LightGreen : Brushes.LightPink;
                if (middle != "")
                {
                    string twoparts = prefix + "-" + middle;
                    textbox.Background = twoparts.Equals(textbox.Text) ? Brushes.LightGreen : Brushes.LightPink;
                    if (suffix != "")
                    {
                        string threeparts = prefix + "-" + middle + "-" + suffix;
                        textbox.Background = threeparts.Equals(textbox.Text) ? Brushes.LightGreen : Brushes.LightPink;
                    }
                }
            }
        }
        else
        {
            textbox.Background = Brushes.LightPink;
        }
    }

感谢所有的好答案!!我下次会尝试更加明确。