8

.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”,所以我得到了这些字符串:

  • × 1 2
  • × 3
  • × 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

4 回答 4

5

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

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

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();
}
于 2012-12-17T18:48:37.817 回答
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 的建议,使用第二个正则表达式。

于 2012-12-17T18:36:01.227 回答
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

*/ 
于 2012-12-17T19:35:22.113 回答
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并记下空字符串时,您知道一组新的数字已经开始。

于 2012-12-17T20:40:48.487 回答