在.NET的RegEx中,我可以从Capture对象获取Groups集合吗?

时间:2012-12-17 17:57:33

标签: .net regex capture

.NET在其RegularExpression实现中提供了一个Capture集合,因此您可以获取给定重复组的所有实例,而不仅仅是它的最后一个实例。这很好,但我有一个重复的小组与子组,我试图进入子组,因为他们在组下相关,并找不到方法。有什么建议吗?

我已经查看了其他一些问题,例如:

但我发现没有适用的答案肯定(“是的,这里是怎么样”)或否定的(“不,不能完成。”)。

对于一个人为的例子说我有一个输入字符串:

abc d x 1 2 x 3 x 5 6 e fgh

其中“abc”和“fgh”表示我想在较大文档中忽略的文本,“d”和“e”包裹感兴趣的区域,并且在该感兴趣的区域内,“xn [n]”可以重复任何次数。这是我感兴趣的“x”区域中的那些数字对。

所以我正在使用这个正则表达式模式解析它:

.*d (?<x>x ((?<fir>\d+) )?((?<sec>\d+) )?)*?e.*

将在文档中找到一个匹配项,但多次捕获“x”组。以下是我想在此示例中提取的三对:

  • 1,2
  • 3
  • 5,6

但我怎么能得到它们?我可以做以下(在C#中):

using System;
using System.Text;
using System.Text.RegularExpressions;

string input = "abc d x 1 2 x 3 x 5 6 e fgh";
string pattern = @".*d (?<x>x ((?<fir>\d+) )?((?<sec>\d+) )?)*?e.*";
foreach (var x in Regex.Match(input, pattern).Groups["x"].Captures) {
    MessageBox.Show(x.ToString());
}

因为我正在引用组“x”,所以我得到了这些字符串:

  • x 1 2
  • x 3
  • x 5 6

但这并没有让我了解数字本身。所以我可以独立完成“fir”和“sec”,而不仅仅是“x”:

using System;
using System.Text;
using System.Text.RegularExpressions;

string input = "abc d x 1 2 x 3 x 5 6 e fgh";
string pattern = @".*d (?<x>x ((?<fir>\d+) )?((?<sec>\d+) )?)*?e.*";
Match m = Regex.Match(input, pattern);
foreach (var f in m.Groups["fir"].Captures) {
    MessageBox.Show(f.ToString());
}

foreach (var s in m.Groups["sec"].Captures) {
    MessageBox.Show(s.ToString());
}

得到:

  • 1
  • 3
  • 5
  • 2
  • 6

但是我无法知道它是第二对缺少“4”,而不是其他一对。

那该怎么办?我知道我可以轻松地在C#中解析这个问题,甚至可以在“x”组上进行第二次正则表达式测试,但是由于第一次RegEx运行已经完成了所有工作并且结果已知,所以似乎应该有一种方法操纵Match对象以获得我需要的东西。

请记住,这是一个人为的例子,现实世界的情况稍微复杂一些,所以只需要额外抛出C#代码就会很痛苦。但是如果现有的.NET对象无法做到,那么我只需要知道这一点,我就会继续前进。

思想?

4 个答案:

答案 0 :(得分:5)

我不知道完全构建的解决方案,并且在快速搜索后找不到一个,但这并不排除存在一个的可能性。

我最好的建议是使用IndexLength属性来查找匹配的捕获。看起来不是很优雅,但是在编写了一些扩展方法之后,你可能会想出一些非常好的代码。

var input = "abc d x 1 2 x 3 x 5 6 e fgh";

var pattern = @".*d (?<x>x ((?<fir>\d+) )?((?<sec>\d+) )?)*?e.*";

var match = Regex.Match(input, pattern);

var xs = match.Groups["x"].Captures.Cast<Capture>();

var firs = match.Groups["fir"].Captures.Cast<Capture>();
var secs = match.Groups["sec"].Captures.Cast<Capture>();

Func<Capture, Capture, Boolean> test = (inner, outer) =>
    (inner.Index >= outer.Index) &&
    (inner.Index < outer.Index + outer.Length);

var result = xs.Select(x => new
                            {
                                Fir = firs.FirstOrDefault(f => test(f, x)),
                                Sec = secs.FirstOrDefault(s => test(s, x))
                            })
               .ToList();

这是一种使用以下扩展方法的可能解决方案。

internal static class Extensions
{
    internal static IEnumerable<Capture> GetCapturesInside(this Match match,
         Capture capture, String groupName)
    {
        var start = capture.Index;
        var end = capture.Index + capture.Length;

        return match.Groups[groupName]
                    .Captures
                    .Cast<Capture>()
                    .Where(inner => (inner.Index >= start) &&
                                    (inner.Index < end));
    }
}

现在您可以按如下方式重写代码。

var input = "abc d x 1 2 x 3 x 5 6 e fgh";

var pattern = @".*d (?<x>x ((?<fir>\d+) )?((?<sec>\d+) )?)*?e.*";

var match = Regex.Match(input, pattern);

foreach (Capture x in match.Groups["x"].Captures)
{
    var fir = match.GetCapturesInside(x, "fir").SingleOrDefault();
    var sec = match.GetCapturesInside(x, "sec").SingleOrDefault();
}

答案 1 :(得分:3)

它总是成对而不是单身?您可以使用单独的捕获组。当然,使用此方法会丢失项目的顺序。

var input = "abc d x 1 2 x 3 x 5 6 e fgh";
var re = new Regex(@"d\s(?<x>x\s((?<pair>\d+\s\d+)|(?<single>\d+))\s)*e");

var m = re.Match(input);
foreach (Capture s in m.Groups["pair"].Captures) 
{
    Console.WriteLine(s.Value);
}
foreach (Capture s in m.Groups["single"].Captures)
{
    Console.WriteLine(s.Value);
}

1 2
5 6
3

如果您需要订单,我可能会考虑Blam的建议使用第二个正则表达式。

答案 2 :(得分:2)

我建议你研究一下.net正则表达式独特的平衡组。

这是一个正则表达式,用于在发现组(非数字或X)关闭组时停止匹配。然后根据需要通过捕获访问匹配:

string data = "abc d x 1 2 x 3 x 5 6 e fgh";

string pattern =
@"(?xn)    # Specify options in the pattern
           # x - to comment (IgnorePatternWhitespace)
           # n - Explicit Capture to ignore non named matches

(?<X>x)                    # Push the X on the balanced group
  ((\s)(?<Numbers>\d+))+   # Load up on any numbers into the capture group
(?(Paren)(?!))             # Stop any match that has an X
                           #(the end of the balance group)";


var results = Regex.Matches(data, pattern)
                   .OfType<Match>()
                   .Select ((mt, index) => string.Format("Match {0}: {1}",
                                             index,
                                             string.Join(", ",
                                                         mt.Groups["Numbers"]
                                                         .Captures
                                                         .OfType<Capture>()
                                                         .Select (cp => cp.Value))))
                   ;

results.ToList()
       .ForEach( result => Console.WriteLine ( result ));
/* Output

Match 0: 1, 2
Match 1: 3
Match 2: 5, 6

*/ 

答案 3 :(得分:1)

我见过OmegaMan的回答,并且知道您更喜欢C#代码而不是正则表达式解决方案。但无论如何我想提出一个替代方案。

在.NET中,您可以重用命名组。每次用该组捕获某些东西时,它就被推到堆栈上(这就是OmegaMan所指的“平衡组”)。您可以使用它将空捕获推送到堆栈中,找到您找到的每个x

string pattern = @"d (?<x>x(?<d>) (?:(?<d>\d+) )*)*e";

所以现在匹配x后,(?<d>)将空捕获推送到堆栈。这是Console.WriteLine输出(每次捕获一行):

 
1
2

3

5
6

因此,当你走过Regex.Match(input, pattern).Groups["d"].Captures并记下空字符串时,你知道一组新的数字已经开始。