■キャプチャ・コレクション
グループにマッチする文字列が1つだけのときは、前述の解説どおりValueプロパティへアクセスすれば目的の文字列を取得できるが、1つのグループに複数の文字列がマッチするときは、別の方法が必要になる。
例えば、IPアドレスにマッチするために、以下のパターンを指定したとしよう。
(\d+)(\.(\d+)){3}
この場合、Groups[2]とGroups[3]に対しては、量指定子“{3}”のため、マッチングが3回行われる。
つまり、1つのグループに対して、3つの文字列がキャプチャされるということだ。こうしたグループからキャプチャ文字列を取得するには、Valueプロパティではなく、GroupクラスのCapturesコレクション・プロパティへアクセスする。Capturesコレクション・プロパティは、Captureオブジェクトを要素に格納するCaptureCollectionクラスのオブジェクトである。Capturesコレクションには、キャプチャした文字列の数だけCaptureオブジェクトが格納される。
このパターンで4つの数字をキャプチャするプログラムをリスト4に示す。
using System;
using System.Text.RegularExpressions;
class Regexip {
static void Main(string[] args) {
if (args.Length < 1) {
Console.WriteLine("regexip <IP Address>");
} else {
Regex regex = new Regex(@"(\d+)(\.(\d+)){3}");
Match m = regex.Match(args[0]);
if (m.Success) {
Console.WriteLine(
"Groups[1].Value = " + m.Groups[1].Value);
for (int i = 0; i < m.Groups[3].Captures.Count; i++) {
Console.WriteLine(
"Groups[3].Captures[" + i + "].Value = "
+ m.Groups[3].Captures[i].Value);
}
}
}
}
}
このプログラムは次のような実行結果となる。
C:\>regexip 192.168.0.1
Groups[1].Value = 192
Groups[3].Captures[0].Value = 168
Groups[3].Captures[1].Value = 0
Groups[3].Captures[2].Value = 1
■キャプチャの無効化とグループの指定
リスト4では、指定した正規表現パターンには3つのグループが指定されているのに、Groups[1]とGroups[3]しか参照していないことに気づかれただろうか。なぜGroups[2]にはアクセスしていないのだろうか。もう一度さっきのパターンを見てみよう。
(\d+)(\.(\d+)){3}
すでに解説したとおり、グループには2つの用途がある。1つはキャプチャ用のグループを設定するため。もう1つは量指定子(ここでは“{3}”)の適用範囲をグループ化するためだ。このパターンでは、Groups[1]とGroups[3]がキャプチャ用途に、Groups[2]は“{3}”の適用範囲をグループ化するために利用されている。ということは、Groups[2]はグループ化さえされていれば十分で、Groups[2]にキャプチャされた文字列は本来不要なものだということだ。こういうときは、グループ化に“(?: 〜 )”を利用すれば、キャプチャを行わず、グループ化だけを行うことができる。こうすると2番目のグループがスキップされ、先ほどではGroups[3]に格納されていたキャプチャ文字列が、Groups[2]に格納されるようになる。
(\d+)(?:\.(\d+)){3}
さらに、今度はGroups[1]とGroups[2]に分かれているキャプチャ文字列を1つのグループに統合して格納されるようにグループを修正しよう。そうすれば、いままでのように2つのグループを扱わずに済み、4つの数字がすべてGroups[1]に格納されるようになる。こうすれば、プログラムも単純になる。このためにはパターンを修正し、グループ化に“(?<グループ・ナンバー> 〜 )”を利用する。ここでは、2つのグループに共通のグループ・ナンバー「1」を指定している。
(?<1>\d+)(?:\.(?<1>\d+)){3}
こうすると、デフォルトのグループではなく、指定されたグループ「1」へキャプチャ文字列が格納されるようになる。このパターンを利用するように修正したプログラムをリスト5に示す。
using System;
using System.Text.RegularExpressions;
class Regexip {
static void Main(string[] args) {
if (args.Length < 1) {
Console.WriteLine("regexip <IP Address>");
} else {
Regex regex = new Regex(@"(?<1>\d+)(?:\.(?<1>\d+)){3}");
Match m = regex.Match(args[0]);
if (m.Success) {
for (int i = 0; i < m.Groups[1].Captures.Count; i++) {
Console.WriteLine(
"Groups[1].Captures[" + i + "].Value = "
+ m.Groups[1].Captures[i].Value);
}
}
}
}
}
このプログラムの実行結果は次のようになる。
C:\>regexip 192.168.0.1
Groups[1].Captures[0].Value = 192
Groups[1].Captures[1].Value = 168
Groups[1].Captures[2].Value = 0
Groups[1].Captures[3].Value = 1
■検索結果クラスの関係
さて、検索結果の診断とキャプチャ文字列の扱い方について、いろいろ解説してきたので、ここで一度まとめておこう。
検索結果を扱うクラスとして、3つのクラスが登場した。Matchクラス、Groupクラス、それにCaptureクラスである。ここまでに、それぞれのクラスの用途を以下のように解説してきた。
クラス | 用途 |
---|---|
Match | 検索結果を格納する |
Group | グループに対応する。Groupsコレクション・プロパティの要素 |
Capture | キャプチャ文字列を格納する。Capturesコレクション・プロパティの要素 |
各クラスはそれぞれに用途が異なるように見えるが、実は密接な関係がある。MatchクラスはGroupクラスのサブクラスとして、GroupクラスはCaptureクラスのサブクラスとして、それぞれ宣言されているのである。これらのクラスの継承関係と、それぞれのクラスが持っているプロパティを次の図に示す。
前回の最初に示したサンプル・プログラムでは、MatchオブジェクトのSuccessプロパティを参照して、一致する文字列が存在するかどうかを調べたし、今回のサンプル・プログラムでは、同じくMatchオブジェクトのValueプロパティを参照してパターン全体に一致した文字列を表示した。そのことを思い出しながら見てほしい。どちらもMatchクラスで直接宣言されているプロパティではない。
つまり、GroupクラスのサブクラスであるMatchクラスは、グループがキャプチャした文字列の格納先でもあるということだ。Groupsコレクションの解説をしたとき、Groups[0]には正規表現パターン全体を囲うグループが格納され、明示的に指定したグループはGroups[1]以降に格納されると述べたことを覚えているだろうか。このGroups[0]こそがMatchオブジェクトだと考えてよい。同じように、Groups[n]とGroups[n].Captures[<末尾のインデックス>]も同一オブジェクトを参照しているものと考えてよさそうだ。仕様で決まっていることではないが、そのように実装されているということを知っておくと理解が早いだろう。
このような関係になっているため、m.Groups[0].Valueとm.Valueには同じ文字列が格納されいてるし、キャプチャした文字列が1つだけのグループでは、Groups[n].ValueとGroups[n].Captures[0].Valueに同じ文字列が格納されている。Valueプロパティは一種のショートカットとして用意されているプロパティだと考えればいいだろう。
Copyright© Digital Advantage Corp. All Rights Reserved.