|
|
マッチ部分の取得と後方参照 |
H.Kamifuji . |
パターンを使ってターゲット文字列がマッチするかどうかを調べた場合に、マッチしたかどうかに加えてどの部分にマッチしたのかを取得できます。またパターン内でマッチした部分文字列を参照して利用することも出来ます。マッチした部分の取得方法とその利用方法を確認していきます。 当ページでは、Linux CentOS7 の Gnome で動作テストしています。 現在(2021/08)では、JDK-16.0.2 にアップされています。一部、上位互換について、見直しを行っていきます。 現在(2021/11)では、JDK-17.0.1 にアップされています。一部、上位互換について、見直しを行っていきます。 現在(2024/10)では、JDK-23 にアップされています。一部、上位互換について、見直しを行っていきます。 |
|
ターゲットの文字列にパターンを適用しマッチしているかどうか調べるにはMatcherクラスで定義されているfindメソッドを使って調べていました。 String str = "abcdef"; String regex = "abc"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()){ System.out.println("マッチしました"); }findメソッドによってマッチが成功した場合は、start、end、および group メソッドを使用することで、マッチした部分に関する情報を取得することができます。 まずターゲット文字列のどの部分にマッチしたのかを取得するにはMatcherクラスで定義されているgroupメソッドを使います。 group public String group()前回のマッチで一致した入力部分シーケンスを返します。 正規表現エンジン m に入力シーケンス s が指定されている場合、m.group() とs.substring(m.start(), m.end()) は同じ表現になります。 パターン (a* など) によっては、空の文字列とマッチすることがあります。これらのパターンが入パターン (a* など) によっては、空の文字列とマッチすることがあります。これらのパターンが入 戻り値: 前回のマッチで一致した部分シーケンス (空の場合もある)。文字列形式 例外: IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗した場合 groupメソッドを呼び出すと、ターゲット文字列の中でマッチした部分文字列を取得することができます。 例えば次のように記述します。 String str = "abcdef"; String regex = "abc"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()){ String matchstr = m.group(); System.out.println(matchstr + "の部分にマッチしました"); }またマッチした部分のターゲット文字列全体に対するインデックスを取得することもできます。開始インデックスを取得するにはMatcherクラスで定義されているstartメソッドを使い、終了インデックスを取得するにはMatcherクラスで定義されているendメソッドを使います。 startメソッド : start public int start()前回のマッチの開始インデックスを返します。 戻り値: マッチした最初の文字のインデックス 例外: IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗した場合 endメソッド : end public int end()最後にマッチした文字の後のオフセットを返します。 戻り値: 最後にマッチした文字の後のオフセット 例外: IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗した場合 starメソッド及びendメソッドを呼び出すと、ターゲット文字列の中でマッチした部分の開始インデックスと終了インデックスを取得することができます。 例えば次のように記述します。 String str = "abcdef"; String regex = "abc"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()){ int start = m.start(); int end = m.end(); System.out.println("マッチしました"); System.out.println("位置は " + start + " to " + end); }またgroupメソッドを使って取得したマッチした部分の文字列について、startメソッドとendメソッドを使うことで同じように取得することができます。 String str = "abcdef"; String regex = "abc"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()){ int start = m.start(); int end = m.end(); System.out.println(str.substring(start, end) + "の部分にマッチしました"); }この3つのメソッドを使うことで、ターゲット文字列のどの部分にマッチしたのかを取得することができます。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample1_1{ public static void main(String args[]){ String str1 = "10 years old"; String str2 = "What would you like to have?"; String str3 = "Search results"; String regex = " .+ "; Pattern p = Pattern.compile(regex); System.out.println("パターン : " + regex); check(p, str1); check(p, str2); check(p, str3); } private static void check(Pattern p, String target){ Matcher m = p.matcher(target); if (m.find()){ int start = m.start(); int end = m.end(); String str = target.substring(0, start) + "[" + m.group() + "]" + target.substring(end, target.length()); System.out.println("○ " + target); System.out.println(" " + str); }else{ System.out.println("× " + target); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ref]$ javac JSample1_1.java [xxxxxxxx@dddddddddd Ref]$ java JSample1_1 パターン : .+ ○ 10 years old 10[ years ]old ○ What would you like to have? What[ would you like to ]have? × Search results [xxxxxxxx@dddddddddd Ref]$今回のサンプルでは、マッチした場合にどの部分にマッチしたのかを[]で囲って表示しています。 |
パターンをターゲット文字列にマッチさせた場合、パターン全体がターゲット文字列のどの部分にマッチしたかを取得することができますが、パターン内の各項目毎にマッチした部分を取得することもできます。 次の例を見てください。 ターゲット文字列 "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メソッドを使ってマッチした部分を取り出します。 group public String group(int 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メソッドを使います。groupCount public int 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メソッド: start public int start(int group)前回のマッチ操作で指定されたグループによって前方参照された部分シーケンスの、開始インデックスを返します。 前方参照を行う正規表現グループには、左から右方向に 1 からインデックスが付きます。グループ0はパターン全体を表します。 つまり、m.start(0) と m.start() は同じ表現です。 パラメータ: group - この正規表現エンジンのパターンに指定されている前方参照を行う正規表現グループのインデックス 戻り値: グループによって前方参照された最初の文字のインデックス。マッチは成功したがグループ自体は どの部分にもマッチしなかった場合は -1 例外: IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗した場合 IndexOutOfBoundsException - 指定されたインデックスを持つ前方参照を行う正規表現グループが そのパターンに含まれない場合 endメソッド: end public int end(int group)前回のマッチ操作で指定されたグループによって前方参照された部分シーケンスの、最終文字の後のオフセットを返します。 前方参照を行う正規表現グループには、左から右方向に 1 からインデックスが付きます。グループ0はパターン全体を表します。 つまり、m.end(0) と m.end() は同じ表現です。 パラメータ: group - この正規表現エンジンのパターンに指定されている前方参照を行う正規表現グループのインデックス 戻り値: グループによって前方参照された最後の文字の後のオフセット。マッチは成功したがグループ自体 はどの部分にもマッチしなかった場合は -1 例外: IllegalStateException - マッチがまだ試みられていない場合、または前回のマッチ操作が失敗した場合 IndexOutOfBoundsException - 指定されたインデックスを持つ前方参照を行う正規表現グループが そのパターンに含まれない場合 それぞれ引数にはグループのインデックスを指定して下さい。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。/** * パターン内の括弧毎にマッチした部分文字列を取得 */ 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); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ref]$ javac JSample2_1.java [xxxxxxxx@dddddddddd Ref]$ java JSample2_1 パターン : (\d+)(y) ○ 2009year [全体] 2009y [Group1] 2009 [Group2] y ○ Price is 380yen [全体] 380y [Group1] 380 [Group2] y パターン : ((\d+)(y)) ○ 2009year [全体] 2009y [Group1] 2009y [Group2] 2009 [Group3] y ○ Price is 380yen [全体] 380y [Group1] 380y [Group2] 380 [Group3] y [xxxxxxxx@dddddddddd Ref]$ |
パターン内を括弧()を使ってグループ化することで、グループ毎にマッチした文字列を取得することができました。ただ、括弧はこのような目的以外にも使用しています。例えば量指定子の対象をグループ化したり、選択の候補をグループ化したりするのに使用してきました。 "(\d)+" "200(7|8|9)"このような場合でも括弧で囲われていると、指定したインデックスのグループにマッチした文字列を取得することができます。ただし、グループは前のページで記載した通り、括弧が記述された順にインデックスが割り当てられていきます。単に選択をグループ化するために括弧を使った場合でもインデックスが割り当てられてしまいます。その為、グループのインデックス数でループを回している場合などは取得する必要の無いグループの値も取得してしまうことになります。 またグループにマッチした部分文字列は参照できるように別途保存されています。実際には使わない値を保存しておくことは資源の無駄となります。 そこでパターンの中でグループ化のために括弧を使ってはいるが、インデックス割り当ては行わずマッチする部分文字列の参照もしないという場合には()の代わりに(?:)を使います。例えば次のように記述します。 "(?:\d)+" "200(?:7|8|9)"(?:)を使用することで括弧としての意味はそのままですが、インデックスを指定してマッチした部分文字列を取得するなどは行えなくなります。 次の2つのパターンを見てください。 "(200(7|8|9))year" "(200(?:7|8|9))year"パターン"(200(7|8|9))year"では2つのインデックスを指定してマッチする文字列を参照できますが、パターン"(200(?:7|8|9))year"ではマッチした部分文字列を参照できるグループは1つだけです。 例えば次のように記述します。 String str = "2009year"; String regex = "(200(?:7|8|9))year"; 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)); } }では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。/** * グループ化のみ行う括弧(?:..) */ import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample3_1{ public static void main(String args[]){ String str1 = "2009year"; String regex1 = "(200(7|8|9))year"; Pattern p1 = Pattern.compile(regex1); String regex2 = "(200(?:7|8|9))year"; Pattern p2 = Pattern.compile(regex2); System.out.println("パターン : " + regex1); check(p1, str1); System.out.println("パターン : " + regex2); check(p2, str1); } 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); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ref]$ javac JSample3_1.java [xxxxxxxx@dddddddddd Ref]$ java JSample3_1 パターン : (200(7|8|9))year ○ 2009year [全体] 2009year [Group1] 2009 [Group2] 9 パターン : (200(?:7|8|9))year ○ 2009year [全体] 2009year [Group1] 2009 [xxxxxxxx@dddddddddd Ref]$ |
パターン内で括弧を使うことで後からマッチした部分文字列を参照することができますが、パターン内において既にマッチした文字列を参照することも可能です。括弧()で囲まれたパターンにマッチした文字列はメタ文字の"\1"、"\2"...でパターン内から参照できます。 具体的な例で確認します。 "(RED|red)\1"上記の場合、まずターゲット文字列"(RED|red)"にマッチするかどうかが評価されます。マッチした場合、このグループの部分にマッチした部分はパターン内で後方参照することができます。このグループのインデックスは1ですので、パターン内に記述された"\1"はグループ1にマッチした部分文字列にマッチすることになります。 例えば"(RED|red)"が"RED"にマッチした場合、"\1"も"RED"にマッチすることになります。この場合の"(RED|red)\1"は"REDRED"と記載したのと同じことです。同じように"(RED|red)"が"red"にマッチした場合、"\1"も"red"にマッチすることになります。この場合の"(RED|red)\1"は"redred"と記載したのと同じことです。 つまり次の2つのパターンは同じことを表しています。 "(REDRED|redred)"次に<h1>で始まる場合は</h1>までを、<h2>で始まる場合は</h2>までをマッチさせるパターンを考えてみます。 (正確には異なりますが、説明用なので簡略化しています)。 "<(h1|h2)>.*?<\/\1>"このパターンの場合、"<(h1|h2)>"で"<h1>"にマッチした場合は"<\/\1>"は"</h1>"にマッチし、"<(h1|h2)>"で"<h2>"にマッチした場合は"<\/\1>"は"</h2>"にマッチします。 パターン内で後方参照を記述することで、既にマッチした部分文字列を同じものにマッチさせるといったパターンを作成することが可能になります。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。/** * パターン内での後方参照 */ import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample4_1{ public static void main(String args[]){ String str1 = "Today is <div>Tuesday</div>"; String str2 = "Border Color is <span>red</span>"; String str3 = "<span>Hello</div>"; String regex = "<(div|span)>.*?<\\/\\1>"; Pattern p = Pattern.compile(regex); System.out.println("パターン : " + regex); check(p, str1); check(p, str2); check(p, str3); } private static void check(Pattern p, String target){ Matcher m = p.matcher(target); if (m.find()){ System.out.println("○ " + target); }else{ System.out.println("× " + target); } } }上記をコンパイルした後で実行すると次のように表示されます。 [wwwadmin@kamifuji18 Ref]$ javac JSample4_1.java [wwwadmin@kamifuji18 Ref]$ java JSample4_1 パターン : <(div|span)>.*?<\/\1> ○ Today is <div>Tuesday</div> ○ Border Color is <span>red</span> × <span>Hello</div> [wwwadmin@kamifuji18 Ref]$ |
|