首页 > 解决方案 > 如何使用 Regex.Split 获取组名称

问题描述

我正在处理可能包含括号之间的内容的字符串,例如:

"Hello World"
"(Hello) World"
"(Hello World)"
"(Hello) (World)"
"bla bla (Hello World) bla bla"
"Hello (World"

我为此目的编写了这个简单的正则表达式: \((.*?)\):

var Inputs = new List<string>
{
    "Hello World",
    "(Hello) World",
    "(Hello World)",
    "(Hello) (World)",
    "bla bla (Hello World) bla bla",
    "Hello (World"
};

foreach (var input in Inputs)
{
    var parts = Regex.Split(input, @"\((.*?)\)");

    Console.WriteLine($"Input : {input}");
    foreach (var part in parts)
    {
        Console.WriteLine($"> '{part}'");
    }
    Console.WriteLine("--------------------------------");
}

这给了我预期的输出:

Input : Hello World
> 'Hello World'
--------------------------------
Input : (Hello) World
> ''
> 'Hello'
> ' World'
--------------------------------
Input : (Hello World)
> ''
> 'Hello World'
> ''
--------------------------------
Input : (Hello) (World)
> ''
> 'Hello'
> ' '
> 'World'
> ''
--------------------------------
Input : bla bla (Hello World) bla bla
> 'bla bla '
> 'Hello World'
> ' bla bla'
--------------------------------
Input : Hello (World
> 'Hello (World'
--------------------------------

但是,我需要对括号之间的捕获部分进行特殊处理。

我想使用命名组,例如(?<others1>.*?)\((?<choice>.*?)\)(?<others2>.*?),但是与组一起工作需要使用诸如Match()and之类的方法,GetGroupNames()并且我得到了错误的结果:

// Inputs are the same than above
foreach (var input in Inputs)
{        
    var rgx = new Regex(@"(?<others1>.*?)\((?<choice>.*?)\)(?<others2>.*?)");

    var matches = rgx.Matches(input);
    var groups = rgx.GetGroupNames();


    Console.WriteLine($"Input : {input}");
    foreach (Match match in matches)
    {
        foreach (var group in groups)
        {
            Group grp = match.Groups[group];
            Console.WriteLine("   {0}: '{1}'", group, grp.Value);
            // if (group == "choice")
            //     SpecialTreatment(grp.Value);
        }
    }
    Console.WriteLine("--------------------------------");
}

输出 :

Input : Hello World // no match
--------------------------------
Input : (Hello) World // Missing ' World'
   0: '(Hello)'
   others1: ''
   choice: 'Hello'
   others2: ''
--------------------------------
Input : (Hello World) // Good
   0: '(Hello World)'
   others1: ''
   choice: 'Hello World'
   others2: ''
--------------------------------
Input : (Hello) (World) // Good
   0: '(Hello)'
   others1: ''
   choice: 'Hello'
   others2: ''
   0: ' (World)'
   others1: ' '
   choice: 'World'
   others2: ''
--------------------------------
Input : bla bla (Hello World) bla bla // missing last part ' bla bla'
   0: 'bla bla (Hello World)'
   others1: 'bla bla '
   choice: 'Hello World'
   others2: ''
--------------------------------
Input : Hello (World // no match
--------------------------------

有没有办法使组名受益Regex.Split()

标签: c#regex

解决方案


坦率地说,这个问题无处不在。您想要最终结果还是想要修复过程中的一个步骤?作为程序员的一部分是将事情分解成小步骤并在每个步骤上工作。你还没有这样做......所以让我们这样做:

  1. 匹配非分组文本或分组文本。
  2. 如果它被分组,则将每个单独的项目分离到一个列表中。
  3. 如果它未分组,则按原样处理。
  4. 如果是分组场景,则在重新组合时,获取列表中比上一个操作索引 +1 的项目。

那么你对哪个有问题?当问一个 SO 问题时,只需将其保留为一小块。


查看bulk-email-generator 问题,该问题的答案可以使用正则表达式来完成,但必须使用匹配中找到的捕获组来正确分离项目。将它们分开后,您可以获取目标项目,该项目是每次匹配后都会增加的索引。

例子

xxx (abc|def|ghi) yyy (ijk|lmn|opq) zzz

最终结果

 xxx abc yyy lmn zzz

模式 这里是模式,注释解释。此正则表达式查找组或非组。从组内,它将单独的文本添加到内部捕获数组:

var pattern = @"          # Either its in a Group
 \(                            #(literal paren start)
     (
       (?<Grouped>[^|(]+)      # Match up to the pipe or paren
       \|?                     # Don't match the pipe but consume it if there
    )+                         # One to many of these piped items
 \)                               # (literal paren stop)
|                          # Or its not in a group
 (?<NotGrouped>[^(]+)          #
";

请注意,我们将告诉正则表达式解析器使用RegexOptions.IgnorePatternWhitespace(允许我们在正则表达式解析之前RegexOptions.ExplicitCapture注释模式,并且这也让正则表达式解析器忽略不在命名匹配捕获中的任何内容(?<NameHere> )

正则表达式结果

Match #0
                   [0]:  xxx
     ["Grouped"] → [1]:
  ["NotGrouped"] → [2]:  xxx
           →2 Captures: xxx
Match #1
                   [0]:  (abc | def | ghi)
     ["Grouped"] → [1]:  ghi
           →1 Captures: abc, def, ghi
 ["NotGrouped"] → [2]:
Match #2
                   [0]:   yyy
     ["Grouped"] → [1]:
  ["NotGrouped"] → [2]:   yyy
           →2 Captures: yyy
Match #3
                   [0]:  (ijk | lmn | opq)
     ["Grouped"] → [1]:  opq
           →1 Captures: ijk, lmn, opq
 ["NotGrouped"] → [2]:
Match #4
                   [0]:   zzz
     ["Grouped"] → [1]:
  ["NotGrouped"] → [2]:   zzz
           →2 Captures: zzz

因此,如果分组是NonPiped我们忽略 Captures(因为只有一个)匹配本身。如果它是数据组之一,我们将重点放在Captures.

C# 解决方案

int index = 0;

string.Join(string.Empty,

Regex.Matches(text, pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)
     .OfType<Match>()
     .Select(mtch => mtch.Groups["NotGrouped"].Success ? mtch.Groups["NotGrouped"].Value
                                                       : mtch.Groups["Grouped"].Captures
                                                                               .OfType<Capture>()
                                                                               .Select(cpt => cpt.Value)
                                                                               .ToList()[index++]
             )

    )

结果是xxx abc yyy lmn zzz


推荐阅读