パターン内の括弧毎にマッチした部分文字列を取得

広告

パターンをターゲット文字列にマッチさせた場合、パターン全体がターゲット文字列のどの部分にマッチしたかを取得することができますが、パターン内の各項目毎にマッチした部分を取得することもできます。

次の例を見てください。

ターゲット文字列  "2009year"
パターン          "¥d+y"

上記ではパターンはターゲット文字列にマッチし、そしてターゲット文字列の中の"2009y"の部分にマッチします。これをより詳細に見ると次のように考えることができます。

2009 y ear
---- -
 ¥d+ y

パターンの中の"¥d+"の部分が"2009"にマッチし、"y"の部分が"y"にマッチしています。ここではパターンの中の各項目毎にターゲット文字列のどの部分にマッチしたかを取り出す方法を確認します。

まずパターンの中で、マッチした部分を取り出したい部分を括弧()で囲います。

"(¥d+)(y)"

これで"¥dt"と"y"に部分にマッチした部分を後から取り出す準備ができました。括弧が記述された順番にグループ1、グループ2と名前が付けられます。

グループ1  (¥d+)
グループ2  (y)

次にMatcherクラスで定義されているgroupメソッドを使ってマッチした部分を取り出します。

前回のマッチ操作で指定されたグループによって前方参照された入力部分シーケンスを返します。

正規表現エンジン m、入力シーケンス s、およびグループインデックス g が指定されている場合、
m.group(g) と s.substring(m.start(g), m.end(g)) は同じ表現になります。

前方参照を行う正規表現グループには、左から右方向に 1 からインデックスが付きます。グルー
プ 0 はパターン全体を表します。 つまり、m.group(0) と m.group() は同じ表現です。

マッチは正常終了したが、指定されたグループが入力シーケンスに検出されなかった場合、null が
返されます。パターン ((a*) など) によっては、空の文字列とマッチすることがあります。これら
のグループが入力シーケンス内の空の文字列とマッチした場合、空の文字列が返されます。

パラメータ:
  group - この正規表現エンジンのパターンに指定されている前方参照を行う正規表現グループの
    インデックス 
戻り値:
  前回のマッチ時にグループによって前方参照された部分シーケンス (空の場合もある)。グループ
    が入力の一部へのマッチに失敗した場合は null
例外:
  IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗し
    た場合 
  IndexOutOfBoundsException - 指定されたインデックスを持つ前方参照を行う正規表現グループが
    そのパターンに含まれない場合

パターン全体にマッチした文字列を取得するのに使用したgroupメソッドと同名のメソッドですが、引数を1つ指定してメソッドを呼び出して下さい。引数にはマッチした部分を取り出したいグループのインデックスを指定します。すると指定したインデックスのグループにマッチした部分文字列を取得することができます。

例えば次のように記述します。

String str = "2009year";
String regex = "(¥¥d+)(y)";
Pattern p = Pattern.compile(regex);

Matcher m = p.matcher(str);
if (m.find()){
  String matchstr = m.group();
  System.out.println(matchstr + "の部分にマッチしました");

  System.out.println("group1:" + m.group(1));
  System.out.println("group2:" + m.group(2));
}

上記ではパターン全体がマッチした部分に加えて、パターンのグループ毎にマッチした部分を表示します。

(¥d+)        2009
(y)          y

なお、グループのインデックスに0を指定した場合、パターン全体を表します。よって m.group() と記述した場合と m.group(0) と記述した場合に取得する値は同じです。

括弧とグループの関係

パターン内に記述された括弧が出現する順番にグループのインデックスが割り当てられていきます。

"(¥d+)(y)"

グループ1  (¥d+)
グループ2  (y)

括弧はこのような記述方法に加えて、他の括弧を中に持つような括弧を記述することもできます。

"((¥d+)(y))"

この場合、括弧とグループの関係は次の通りです。

"((¥d+)(y))"

グループ1  ((¥d+)(y))
グループ2  (¥d+)
グループ3  (y)

考え方としては、パターン内に記述された"("の出現の順番に沿ってインデックスが割り当てられます。パターン内で一番最初に現れた"("は"((¥d+)(y))"の部分ですが、この"("に対応する")"までの部分にグループ1が割り当てられます。パターン内で二番目に現れた"("は"((¥d+)(y))"の部分ですが、この"("に対応する")"までの部分にグループ2が割り当てられます。

このように左から順に"("に対してグループのインデックスが割り当てられていくと考えて下さい。

グループ数の取得

パターンに含まれるグループの数を取得することができます。Matcherクラスで定義されているgroupCountメソッドを使います。

この正規表現エンジンのパターンに指定されている前方参照を行う正規表現グループの数を返しま
す。

グループ 0 はパターン全体を表します。グループ 0 は、このカウントに含まれません。

グループインデックスがこのメソッドから返された値以下の正の整数である場合は、この正規表現
エンジンで有効です。 

戻り値:
  この正規表現エンジンのパターンに指定されている前方参照を行う正規表現グループの数

メソッドを実行するとパターン内のグループ数を返します。例えば"(¥d+)(y)"なら2を返し、"((¥d+)(y))"なら3を返します。

例えば次のように記述します。

String str = "2009year";
String regex = "(¥¥d+)(y)";
Pattern p = Pattern.compile(regex);

Matcher m = p.matcher(str);
if (m.find()){
  String matchstr = m.group();
  System.out.println(matchstr + "の部分にマッチしました");

  for (int i = 0 ; i <= m.groupCount() ; i++){
    System.out.println("group" + i + ":" + m.group(i));
  }
}

このようにすることで、パターン内のグループ数の数だけマッチした部分文字列を取得して表示します。

グループ毎にマッチした部分のターゲット文字列全体に対するインデックス

startメソッドとendメソッドを使うことでマッチした部分のターゲット文字列全体に対するインデックスを取得できましたが、同名のメソッドで引数を指定することでグループ毎にマッチした部分のターゲット文字列全体に対するインデックスうぃ取得できます。

startメソッド:

前回のマッチ操作で指定されたグループによって前方参照された部分シーケンスの、開始インデック
スを返します。

前方参照を行う正規表現グループには、左から右方向に 1 からインデックスが付きます。グループ0
はパターン全体を表します。 つまり、m.start(0) と m.start() は同じ表現です。 

パラメータ:
  group - この正規表現エンジンのパターンに指定されている前方参照を行う正規表現グループのイ
    ンデックス 
戻り値:
  グループによって前方参照された最初の文字のインデックス。マッチは成功したがグループ自体は
    どの部分にもマッチしなかった場合は -1 
例外:
  IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗し
    た場合 
  IndexOutOfBoundsException - 指定されたインデックスを持つ前方参照を行う正規表現グループが
    そのパターンに含まれない場合

endメソッド:

前回のマッチ操作で指定されたグループによって前方参照された部分シーケンスの、最終文字の後の
オフセットを返します。

前方参照を行う正規表現グループには、左から右方向に 1 からインデックスが付きます。グループ0
はパターン全体を表します。 つまり、m.end(0) と m.end() は同じ表現です。 

パラメータ:
  group - この正規表現エンジンのパターンに指定されている前方参照を行う正規表現グループのイ
    ンデックス 
戻り値:
  グループによって前方参照された最後の文字の後のオフセット。マッチは成功したがグループ自体
    はどの部分にもマッチしなかった場合は -1
例外:
  IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗し
    た場合 
  IndexOutOfBoundsException - 指定されたインデックスを持つ前方参照を行う正規表現グループが
    そのパターンに含まれない場合

それぞれ引数にはグループのインデックスを指定して下さい。

サンプルプログラム

では実際に試してみます。

JSample2_1.java

import java.util.regex.Pattern;
import java.util.regex.Matcher;

class JSample2_1{
  public static void main(String args[]){
    String str1 = "2009year";
    String str2 = "Price is 380yen";

    String regex1 = "(¥¥d+)(y)";
    Pattern p1 = Pattern.compile(regex1);

    String regex2 = "((¥¥d+)(y))";
    Pattern p2 = Pattern.compile(regex2);

    System.out.println("パターン : " + regex1);

    check(p1, str1);
    check(p1, str2);

    System.out.println("パターン : " + regex2);

    check(p2, str1);
    check(p2, str2);
  }

  private static void check(Pattern p, String target){
    Matcher m = p.matcher(target);

    if (m.find()){
      int start = m.start();
      int end = m.end();
      System.out.println("○ " + target);
      System.out.println("[全体] " + m.group());
      for (int i = 1 ; i <= m.groupCount(); i ++){
        System.out.println("[Group" + i + "] " + m.group(i));
      }
    }else{
      System.out.println("× " + target);
    }
  }
}

ではコンパイルを行った上で実行してみます。

p2-1

( Written by Tatsuo Ikura )