0

我在使用 RegEx 的 .NET 项目中遇到了一个奇怪的问题。请参阅下面的 C# 代码:

const string PATTERN = @"^[a-zA-Z]([-\s\.a-zA-Z]*('(?!'))?[-\s\.a-zA-Z]*)*$";
const string VALUE = "Ingebrigtsen Myre (Øvre)";
System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(PATTERN);
if (!regex.IsMatch(VALUE)) // <--- Infinite loop here
     return string.Empty;
// Some other code

我使用此模式来验证所有类型的名称(拳头名、姓氏、中间名等)。值是一个参数,但我在上面将其作为常量提供,因为问题不会经常重现 - 仅使用特殊符号:*、(、) 等(对不起,但我没有这些符号的完整列表) .

你能帮我解决这个无限循环吗?谢谢你的帮助。

补充:此代码位于项目的最基础级别,我不想在那里进行任何重构 - 我只想快速修复此问题。

补充 2:我知道它在技术上不是一个循环——我的意思是“regex.IsMatch(VALUE)”永远不会结束。我等了大约一个小时,它仍在执行。

4

3 回答 3

3

您的非平凡正则表达式:^[a-zA-Z]([-\s\.a-zA-Z]*('(?!'))?[-\s\.a-zA-Z]*)*$,最好用自由间距模式的注释编写,如下所示:

Regex re_orig = new Regex(@"
    ^                 # Anchor to start of string.
    [a-zA-Z]          # First char must be letter.
    (                 # $1: Zero or more additional parts.
      [-\s\.a-zA-Z]*  # Zero or more valid name chars.
      (               # $2: optional quote.
        '             # Allow quote but only
        (?!')         # if not followed by quote.
      )?              # End $2: optional quote.
      [-\s\.a-zA-Z]*  # Zero or more valid name chars.
    )*                # End $1: Zero or more additional parts.
    $                 # Anchor to end of string.
    ",RegexOptions.IgnorePatternWhitespace);

在英语中,这个正则表达式本质上是说:“匹配一个以字母开头的字符串,[a-zA-Z]后跟零个或多个字母、空格、句点、连字符或单引号,但每个单引号后面可能不会紧跟另一个单引号。”

请注意,您上面的正则表达式允许奇怪的名称,例如:"ABC---...'... -.-.XYZ "这可能是您需要的,也可能不是。它还允许多行输入和以空格结尾的字符串。

上述正则表达式的“无限循环”问题是,当将此正则表达式应用于连续包含两个单引号的长无效输入时,会发生灾难性的回溯。这是一个等效的模式,它匹配(并且不匹配)完全相同的字符串,但不会经历灾难性的回溯:

Regex re_fixed = new Regex(@"
    ^                # Anchor to start of string.
    [a-zA-Z]         # First char must be letter.
    [-\s.a-zA-Z]*    # Zero or more valid name chars.
    (?:              # Zero or more isolated single quotes.
      '              # Allow single quote but only
      (?!')          # if not followed by single quote.
      [-\s.a-zA-Z]*  # Zero or more valid name chars.
    )*               # Zero or more isolated single quotes.
    $                # Anchor to end of string.
    ",RegexOptions.IgnorePatternWhitespace);

在您的代码上下文中,它是简短的形式:

const string PATTERN = @"^[a-zA-Z][-\s.a-zA-Z]*(?:'(?!')[-\s.a-zA-Z]*)*$";
于 2013-08-09T16:03:52.147 回答
1

看看你的正则表达式的这一部分:

( [-\s\.a-zA-Z]* ('(?!'))? [-\s\.a-zA-Z]* )*$
^              ^         ^              ^  ^ 
|              |         |              |  |
|              |         |              |  This group repeats any number of times
|              |         |              charclass repeats any number of times
|              |         This group is optional
|              This character class also repeats any number of times
Outer group (repeated, as seen above)

这意味着,只要您的输入字符串包含一个不在字符类中的字符(例如您的示例中的括号和非 ASCII 字母),前面的字符就会在很多排列中尝试,其数量随着长度呈指数增长的字符串。

为避免这种情况(并允许正则表达式更快地失败,请使用原子组

const string PATTERN = @"^[a-zA-Z](?>(?>[-\s\.a-zA-Z]*)(?>'(?!'))?(?>[-\s\.a-zA-Z])*)*$";
于 2013-08-09T15:48:48.267 回答
0

你在这里有一个“任意数量的任意数量”:

 ...[-\s\.a-zA-Z]*)*

并且由于您的输入匹配,引擎会回溯以尝试将输入划分的所有排列,并且尝试的次数随着输入的长度呈指数增长。

您可以简单地通过添加“+”来制作所有格量词来修复它,一旦使用它就不会回溯以找到其他组合:

const string PATTERN = @"^[a-zA-Z]([-\s\.a-zA-Z]*('(?!'))?[-\s\.a-zA-Z]*+)*$";
                                                                        ^-- added + here

您可以看到一个现场演示(在 rubular 上),演示添加加号修复了循环问题,并且仍然匹配没有奇数字符的输入。

于 2013-08-09T16:49:13.853 回答