Javaの正規表現で名前付きキャプチャを行うクラスを作ってみた

(更新)"(?"で始まるキャプチャされない括弧を考慮した。

必要な機能のみを実装した簡易なものです。

経緯

どうしても、名前付きキャプチャが必要となったので、Javaでの使用方法を調べたところ、どうやら無い。

無いなら作るしかない、というわけで、自分用に作成。

なお、「名前付きキャプチャ」については、別エントリで詳しく書く(未定)とします。

(分からない方は、こちらのリンクで(おそらく)検索できます。)

ソース

※下記のソースはサンプルであり、テストが完全ではありません。

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

/**
 * 名前付きキャプチャを可能とするMatcher
 * Patternの生成は、コンストラクタで行なう
 * 
 * 必要なMethodの簡易実装のため、Interfaceの継承は行わない
 */
//public class NamedMatcher implements MatchResult{
public class NamedMatcher {
	
	/**
	 * Matcherのラッピング
	 */
	private Matcher matcher;
	
	/**
	 * nameの保存
	 */
	private ArrayList<String> names;
	
	/**
	 * キャプチャ名を検索、キャプチャするための正規表現文字列
	 */
	final Pattern namePattern = Pattern.compile("(?<!\\\\)\\(((?=[^?])|(\\?P<(.+?)>));

	/**
	 * コンストラクタ
	 * @param regexString 正規表現
	 * @param text 検索対象の文字列
	 */
	public NamedMatcher(String regexString, String text) {
		//正規表現文字列の分析
		//"("単位に?P<name>を取得し、後で割り当てられるようにnameを保存しておく
		Matcher nameMatcher = namePattern.matcher(regexString);
		
		names = new ArrayList<String>();
		int group = 0;
		while(nameMatcher.find()){
			//group(3)に、nameに対応する文字列が取得できている
			String name = nameMatcher.group(3);
			if(name == null){
				//空文字列の場合は、nameがないため、簡易的にgroupのindexを使用する
				names.add("__" + String.valueOf(group));
			}else{
				names.add(name);
			}
			group++;
		}

		//?P<name>を削除した文字列を生成し、それを正規表現の文字列とする
		//nameMatcherが"(?P<name>"または、"("に該当するはずなので、resetしてそのままreplaceAllで"("に置換
		//"("の数は変わらないため、groupはnamesと同じ数になるはず。
		nameMatcher.reset();
		String replacedText = nameMatcher.replaceAll("(");
		
		Pattern regexPattern = Pattern.compile(replacedText);
		
		matcher = regexPattern.matcher(text);
	}
	
	/**
	 * find
	 * @return
	 */
	public boolean find(){
		return matcher.find();
	}
	
	/**
	 * nameからgroupを取得する
	 * @param name
	 * @return
	 */
	public String group(String name){
		
		//グループの番号を取得する
		int index = names.indexOf(name);

		if(index < 0){
			//indexがマイナスの場合は、存在しないname
			return null;
		}
		
		//0は全体を示すgroupなので+1
		return group(index + 1);
	}
	
	/**
	 * groupを取得する
	 * @param group
	 * @return
	 */
	public String group(int group) {
		
		if(group < 0 || matcher.groupCount() < group ){
			throw new IndexOutOfBoundsException();
		}else{
			return matcher.group(group);
		}
	}

	/**
	 * groupの数を取得する
	 * @return
	 */
	public int groupCount() {
		return matcher.groupCount();
	}
}

使用例

使用方法は、以下のような感じ(このソースは適当です。)

void foo(){
	NamedMatcher namedMatcher = new NamedMatcher("(?P<name>[^\t]+)\t(?P<file>[^\t]+)(\t(?P<address>[^\t]+))?", text);
			
	if(namedMatcher.find()){
		String name = namedMatcher.group("name");
		String file = namedMatcher.group("file");
		String address = namedMatcher.group("address");
	}
}

解説

名前付きの名前に相当する部分を保管しておいて、使用する際(group())のindexとひも付けています。

他の良い方法や、間違いがあれば、ぜひご連絡ください。

修正履歴

2014/12/11 14:45 初版公開
2014/12/11 15:50

nameが存在しない場合に変な動作になったので、とりあえず、nullを返却するようにした。
場合によっては、IndexOutOfBoundsException()の方が良いかもしれない。

2014/12/12 11:20 経緯を追記
2014/12/18 8:15

(?で始まる場合は、キャプチャされない(groupにカウントされない)ことを考慮した。

キャプチャ名のキャプチャ処理で、後方一致指定を使用して、(?P以外の(?の場合はマッチしないようにしました。

最終更新:2014/12/18 08:13
  • このエントリーをはてなブックマークに追加
  • Clip to Evernote
  • Share on Tumblr
  • Delicious

この記事に含まれるキーワード:


サイト内検索

このサイトについて

このサイトは、開発に役立つメモを公開しています。
詳しくは、こちら=>

このサイトのRSSはこちら=>

Twitter

楽天