正则表达式中的规则与命名组重叠

本文关键字:重叠 规则 正则表达式 | 更新日期: 2023-09-27 18:29:39

我遇到了解析自定义电话号码的正则表达式的问题:

  1. 与"wtvCode"组匹配的值是可选的
  2. 与"countryCode"组匹配的值是可选的
  3. countryCode规则与areaCityCode规则在某些值上重叠。在这种情况下,当countryCode丢失时,其表达式将捕获areaCityCode值

下面是代码示例。

Regex regex = new Regex(string.Concat(
    "^(",
    "(?<wtvCode>[A-Z]{3}|)",
    "([-|/|#| |]|)",
    "(?<countryCode>[2-9+]{2,5}|)",
    "([-|/|#| |]|)",
    "(?<areaCityCode>[0-9]{2,3}|)",
    "([-|/|#| |]|))",
    "(?<phoneNumber>(([0-9]{8,18})|([0-9]{3,4}([-|/|#| |]|)[0-9]{4})|([0-9]{4}([-|/|#| |]|)[0-9]{4})|([0-9]{4}([-|/|#| |]|)[0-9]{4}([-|/|#| |]|)[0-9]{1,5})))",
    "([-|/|#| |]|)",
    "(?<foo>((A)|(B)))",
    "([-|/|#| |]|)",
    "(?<bar>(([1-9]{1,2})|)",
    ")$"
));
string[] validNumbers = new[] {
    "11-1234-5678-27-A-2",   // missing wtvCode and countryCode
    "48-1234-5678-27-A-2",   // missing wtvCode and countryCode
    "55-48-1234-5678-27-A-2" // missing wtvCode 
};
foreach (string number in validNumbers) {
    Console.WriteLine("countryCode: {0}", regex.Match(number).Groups["countryCode"].Value);
    Console.WriteLine("areaCityCode: {0}", regex.Match(number).Groups["areaCityCode"].Value);
    Console.WriteLine("phoneNumber: {0}", regex.Match(number).Groups["phoneNumber"].Value);
}

其输出为:

// First number
// countryCode:               <- correct
// areaCityCode: 11           <- correct, but that's because "11" is never a countryCode
// phoneNumber: 1234-5678-27  <- correct
// Second number
// countryCode: 48            <- wrong, should be ""
// areaCityCode:              <- wrong, should be "48"
// phoneNumber: 1234-5678-27  <- correct
// Third number
// countryCode: 55            <- correct
// areaCityCode: 48           <- correct
// phoneNumber: 1234-5678-27  <- correct

到目前为止,我还没有成功地修复这个正则表达式,因为它覆盖了我的所有约束,并且当一个值与两个规则匹配时,不会干扰countryCode和areaCityCode。有什么想法吗?

提前谢谢。


更新

手机国家/地区代码的正确正则表达式模式可以在此处找到:https://stackoverflow.com/a/6967885/136381

正则表达式中的规则与命名组重叠

首先,我建议使用?量词来使事情成为可选的,而不是您现在使用的空选项。在国家代码的情况下,添加另一个?使其不贪婪。这样,它最初将尝试捕获areaCityCode组中的第一组数字。只有当整个匹配失败时,它才会返回并使用countryCode组。

Regex regex = new Regex(
    @"^
    ( (?<wtvCode>[A-Z]{3}) [-/# ] )?
    ( (?<countryCode>[2-9+]{2,5}) [-/# ] )??
    ( (?<areaCityCode>[0-9]{2,3}) [-/# ] )?
    (?<phoneNumber> [0-9]{8,18} | [0-9]{3,4}[-/# ][0-9]{4}([-/# ][0-9]{1,5})? )
    ( [-/# ] (?<foo>A|B) )
    ( [-/# ] (?<bar>[1-9]{1,2}) )?
    $",
  RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture);

正如您所看到的,我对您的代码做了一些其他更改,最重要的是从([-|/|#| |]|)切换到[-/# ]。括号内的管道将只匹配字面上的|,我敢肯定你不想要。最后一根管道使分离器成为可选;我希望他们不是真的必须是可选的,因为这会让这份工作变得更加困难。

您和其他响应者忽略了两件事。

首先,反向工作更有意义,换句话说,从右到左,因为文本末尾需要的字段比开头多。通过消除对WTV和Country代码的怀疑,正则表达式解析器的工作变得更加容易(,尽管编写模式的人在智力上更难)。

第二个是在regex(?()|())中使用if条件。这使我们能够测试一个场景,并在另一个场景上实现一种匹配模式。我在我的博客"正则表达式和if条件"中描述了if条件。下面的模式测试是否存在WTV&国家,如果是,它匹配,如果不是,它检查一个可选的国家。

同样,与其连接模式,为什么不使用IgnorePatternHitespace来允许对模式进行注释,如下所示:

string pattern = @"
^
(?([A-Z][^'d]?'d{2,5}(?:[^'d]))  # If WTV & Country Code (CC)
  (?<wtvCode>[A-Z]{3})           # Get WTV & CC
  (?:[^'d]?)
  (?<countryCode>'d{2,5})
  (?:[^'d])                    # Required Break
  |                            # else maybe a CC
  (?<countryCode>'d{2,5})?     # Optional CC
  (?:[^'d]?)                   # Optional Break
 )
(?<areaCityCode>'d'd'd?)        # Required area city
(?:[^'d]?)                      # Optional break (OB)
(?<PhoneStart>'d{4})            # Default Phone # begins
(?:[^'d]?)                      # OB
(?<PhoneMiddle>'d{4})           # Middle
(?:[^'d]?)                      # OB
(?<PhoneEnd>'d'd)               # End
(?:[^'d]?)                      # OB
(?<foo>[AB])                    # Foo?
(?:[^AB]+)
(?<bar>'d)
$
";
    var validNumbers = new List<string>() {
    "11-1234-5678-27-A-2",   // missing wtvCode and countryCode
    "48-1234-5678-27-A-2",   // missing wtvCode and countryCode
    "55-48-1234-5678-27-A-2", // missing wtvCode
    "ABC-501-48-1234-5678-27-A-2" // Calling Belize (501)
};
    validNumbers.ForEach( nm =>
                {
                    // IgnorePatternWhitespace only allows us to comment the pattern; does not affect processing
                    var result = Regex.Match(nm, pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.RightToLeft).Groups;
                    Console.WriteLine (Environment.NewLine + nm);
                    Console.WriteLine("'tWTV code    : {0}", result["wtvCode"].Value);
                    Console.WriteLine("'tcountryCode : {0}", result["countryCode"].Value);
                    Console.WriteLine("'tareaCityCode: {0}", result["areaCityCode"].Value);
                    Console.WriteLine("'tphoneNumber : {0}{1}{2}", result["PhoneStart"].Value, result["PhoneMiddle"].Value, result["PhoneEnd"].Value);
                }
    );

结果:

11-1234-5678-27-A-2
  WTV code    : 
  countryCode : 
  areaCityCode: 11
  phoneNumber : 1234567827
48-1234-5678-27-A-2
  WTV code    : 
  countryCode : 
  areaCityCode: 48
  phoneNumber : 1234567827
55-48-1234-5678-27-A-2
  WTV code    : 
  countryCode : 55
  areaCityCode: 48
  phoneNumber : 1234567827
ABC-501-48-1234-5678-27-A-2
  WTV code    : ABC
  countryCode : 501
  areaCityCode: 48
  phoneNumber : 1234567827

注:

  • 如果在国家代码和城市代码之间没有分隔符,解析程序无法确定什么是城市,什么是国家
  • 您的原始国家/地区代码模式失败[2-9]这个国家有一个0。所以我把它改成[2-90]