JavaのRegular Expression
先日、Javaでは無かったのですが、Regular Expressionを使う機会があり、ふと、Javaではどうなっていたかが気になって試してみました。
JavaでRegular Expressionを試すとなるとJavaのチュートリアルに掲載されているコード(Test Harness (The Java™ Tutorials > Essential Classes > Regular Expressions))が有名ではないかと思うのですが、JDK 6で追加されたjava.io.Consoleを使っているところがうまく動きません。このコードをNetBeansで試すと、Consoleが無いとかで例外になってしまうし、Macのターミナルで試すと日本語を満足に入力できないし、、、で、とりあえず、こんなRegular Expression実行用のコードを書いて試してみました。日本語がまじっているとRegular Expressionのパターンを毎回入力するのがかなり面倒だったので、評価文字列だけ何度も入力できるようにしてあります。
package cirrus; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; public class SimpleRegex { public enum Choice {r, regex, i, inputs, e, exit}; private SimpleRegex() throws IOException { Pattern pattern = null; BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while (true) { System.out.println("r[egex], i[nputs] or e[xit]? "); try { switch (Enum.valueOf(Choice.class, reader.readLine())) { case regex: case r: pattern = getPattern(reader); break; case inputs: case i: eval(pattern, reader); break; case exit: case e: System.exit(0); break; default: continue; } } catch (IllegalArgumentException e) { } } } private Pattern getPattern(BufferedReader reader) throws IOException { System.out.print("regex? "); return Pattern.compile(reader.readLine(), Pattern.UNICODE_CASE); } private void eval(Pattern pattern, BufferedReader reader) throws IOException { if (pattern == null) { System.out.println("no regex"); return; } System.out.print("inputs? "); Matcher matcher = pattern.matcher(reader.readLine()); boolean found = false; while (matcher.find()) { System.out.println("Found \"" + matcher.group() + "\" starting at " + matcher.start() + " and enging at " + matcher.end() + "."); found = true; } if (!found) { System.out.println("No match found."); } } public static void main(String[] args) throws IOException { new SimpleRegex(); } }
日本語まじりのパターンを指定するときは Pattern.compile(reader.readLine(), Pattern.UNICODE_CASE); のようにcompileメソッドでflagの指定が必要でした。このflagを指定しない場合、簡単なパターンでは問題が無いのですが、いろいろな記号やアルファベットもまぜた複雑なパターンではマッチするはずのところもマッチしませんでした。このflagというのは他の言語やgrepコマンドなどでは i(case insensitive), m (multi-line mode; Rubyではmは違う意味)といったオプション指定がありますが、それをJavaで行いたい場合に指定する引数です。
Javaの場合、文字は全てUnicodeで扱われているので、Regular ExpressionもUnicodeベースです。パターンに通常の文字を書いても内部的にはUnicodeでマッチングが行われているので、例えば、パターンに [a-ん]を指定すると、Unicodeのcodepointで英語のアルファベットの小文字の"a"のところから日本語のひらがなの"ん"の部分まで含まれます。試しにひらがな、フランス語、英語を入力してみたら、3つともちゃんとマッチしました。
java -cp . cirrus.SimpleRegex r[egex], i[nputs] or e[xit]? r regex? [a-ん] r[egex], i[nputs] or e[xit]? i inputs? すみません Found "す" starting at 0 and enging at 1. Found "み" starting at 1 and enging at 2. Found "ま" starting at 2 and enging at 3. Found "せ" starting at 3 and enging at 4. Found "ん" starting at 4 and enging at 5. r[egex], i[nputs] or e[xit]? i inputs? Désolé Found "D" starting at 0 and enging at 1. Found "é" starting at 1 and enging at 2. Found "s" starting at 2 and enging at 3. Found "o" starting at 3 and enging at 4. Found "l" starting at 4 and enging at 5. Found "é" starting at 5 and enging at 6. r[egex], i[nputs] or e[xit]? i inputs? Sorry! Found "S" starting at 0 and enging at 1. Found "o" starting at 1 and enging at 2. Found "r" starting at 2 and enging at 3. Found "r" starting at 3 and enging at 4. Found "y" starting at 4 and enging at 5. r[egex], i[nputs] or e[xit]?
言語やコマンドに採用されているRegular Expression engineによってはUnicode blockをまたがった指定はできないらしいのですが、Javaの場合、codepointのどこからどこの範囲と指定できるので、Unicode blockの境界は無いみたいです。加えて、[a-\u30fc](アルファベットの"a"からカタカナの"ン"までの範囲)のようなパターンも有効です。例えば、"Java[あ-ん]*[a-\u30fc]{2}"というパターンを指定してみると、
java -cp . cirrus.SimpleRegex r[egex], i[nputs] or e[xit]? r regex? Java[あ-ん]*[a-\u30fc]{2} r[egex], i[nputs] or e[xit]? i inputs? Javaとpナッツ Found "Javaとpナ" starting at 0 and enging at 7. r[egex], i[nputs] or e[xit]? i inputs? Javaあかさたなハマヤラワ Found "Javaあかさたなハマ" starting at 0 and enging at 11. r[egex], i[nputs] or e[xit]?
という結果になりました。
UnicodeとRegular Expressionについては
に詳しい解説がありました。Wikipediaでもそれなりに解説されています。