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

时间:2012-03-30 03:53:13

标签: c# regex

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

  1. 匹配“wtvCode”组的值是可选的;
  2. 匹配“countryCode”组的值是可选的;
  3. countryCode规则与areaCityCode规则重叠,用于某些值。在这种情况下,当countryCode缺失时,其表达式会捕获areaCityCode值。
  4. 代码示例如下。

    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

2 个答案:

答案 0 :(得分:2)

首先,我建议使用?量词来使事物可选,而不是现在使用的空替代品。对于国家/地区代码,请添加另一个?以使其不贪婪。这样它最初会尝试捕获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);

正如您所看到的,我对您的代码进行了一些其他更改,最重要的是从([-|/|#| |]|)切换到[-/# ]。括号内的管道只匹配文字|,我很确定你不想要它。最后一根管道使隔板成为可选;我希望他们不真的必须是可选的,因为这会使这项工作变得更加困难。

答案 1 :(得分:1)

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

首先,反向工作更有意义,换句话说从右到左,因为文本末尾比起初更多需要字段。通过消除对WTV和国家代码的怀疑,正则表达式解析器变得更容易工作(虽然在写作模式的人的知识上更难)。

第二个是在regex(?()|())中使用if条件。这允许我们测试场景并实现一种匹配模式而不是另一种。我在我的博客上描述了if条件Regular Expressions and the If Conditional。下面的模式测试是否有WTV&amp;国家/地区,如果匹配,如果不匹配则检查可选国家/地区。

而不是连接模式,为什么不使用IgnorePatternWhitespace来允许模式的注释,如下所示:

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]。