.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”组。以下是我想在此示例中提取的三对:
但我怎么能得到它们?我可以做以下(在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”,所以我得到了这些字符串:
但这并没有让我了解数字本身。所以我可以独立完成“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());
}
得到:
但是我无法知道它是第二对缺少“4”,而不是其他一对。
那该怎么办?我知道我可以轻松地在C#中解析这个问题,甚至可以在“x”组上进行第二次正则表达式测试,但是由于第一次RegEx运行已经完成了所有工作并且结果已知,所以似乎应该有一种方法操纵Match对象以获得我需要的东西。
请记住,这是一个人为的例子,现实世界的情况稍微复杂一些,所以只需要额外抛出C#代码就会很痛苦。但是如果现有的.NET对象无法做到,那么我只需要知道这一点,我就会继续前进。
思想?
答案 0 :(得分:5)
我不知道完全构建的解决方案,并且在快速搜索后找不到一个,但这并不排除存在一个的可能性。
我最好的建议是使用Index
和Length
属性来查找匹配的捕获。看起来不是很优雅,但是在编写了一些扩展方法之后,你可能会想出一些非常好的代码。
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
并记下空字符串时,你知道一组新的数字已经开始。