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.*

これは、ドキュメント内で1つだけ一致するものを検索しますが、「x」グループを何度もキャプチャします。この例で抽出したい3つのペアは次のとおりです。

  • 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

しかし、それは私に数字自体を理解させません。したがって、「x」だけでなく、「fir」と「sec」を個別に実行できます。

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」が欠落しているのは2番目のペアであり、他のペアの1つではないことを知る方法はありません。

じゃあ何をすればいいの?これをC#で、または「x」グループでの2回目の正規表現テストで簡単に解析できることはわかっていますが、最初の正規表現の実行ですべての作業が完了し、結果がわかっているので、方法があるはずです。 Matchオブジェクトを操作して、必要なものを取得します。

また、これは不自然な例であることを忘れないでください。実際のケースはやや複雑なので、余分なC#コードをスローするだけでも面倒です。しかし、既存の.NETオブジェクトでそれができない場合は、それを知る必要があります。続行します。

考え?

4

4 に答える 4

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();

ここでは、次の拡張方法を使用した1つの可能な解決策を示します。

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の提案に従って2番目の正規表現を使用します。

于 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#コードを好むことを知っています。しかし、とにかく1つの選択肢を提示したかった。

.NETでは、名前付きグループを再利用できます。そのグループで何かがキャプチャされるたびに、それはスタックにプッシュされます(これは、OmegaManが「バランシンググループ」によって参照していたものです)。xこれを使用して、見つけたすべての空のキャプチャをスタックにプッシュできます。

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

したがって、一致xした後(?<d>)、空のキャプチャをスタックにプッシュします。出力は次のConsole.WriteLineとおりです(キャプチャごとに1行)。

 
1
2

3

5
6

したがって、次にウォークスルーRegex.Match(input, pattern).Groups["d"].Capturesして空の文字列に注意すると、新しい数値のグループが開始されたことがわかります。

于 2012-12-17T20:40:48.487 に答える