正規表現におけるメタ文字をエスケープするメソッド
今まで、正規表現のメタ文字をエスケープするのに、バックスラッシュとかをいっぱい書いていたのだが、簡単にエスケープする記法があることに最近気づいた。さらにそのような文字列を生成するためのメソッドがあることにも気づいた。
- パターン(置換前)文字列のエスケープ
- 置換後文字列のエスケープ
ということで、実際にプログラムを作って実行してみる。
import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { /** * @param args */ public static void main(String[] args) { System.out.println("### \"Main.java\" を \"Main$java\" に置き換えたいのだが‥"); System.out.println(); System.out.println("### '.'をエスケープしなければひどいことになる。"); try { System.out.println("Main.java".replaceAll(".", "\\$")); } catch (RuntimeException e) { e.printStackTrace(System.out); } System.out.println(); System.out.println("### '$'をエスケープしなければエラーになる。"); try { System.out.println("Main.java".replaceAll("\\.", "$")); } catch (RuntimeException e) { e.printStackTrace(System.out); } System.out.println(); System.out.println("### Java 2 SE 1.4 では‥(1)"); try { System.out.println("Main.java".replaceAll("\\.", "\\$")); } catch (RuntimeException e) { e.printStackTrace(System.out); } System.out.println(); System.out.println("### Java 2 SE 1.4 では‥(2)"); try { System.out.println("Main.java".replaceAll("[.]", "\\$")); } catch (RuntimeException e) { e.printStackTrace(System.out); } System.out.println(); System.out.println("### Java 2 SE 1.4 では‥(3)"); try { System.out.println("Main.java".replaceAll("\\Q.\\E", "\\$")); } catch (RuntimeException e) { e.printStackTrace(System.out); } System.out.println(); System.out.println("### Java 2 SE 5.0 では‥"); try { System.out.println("Main.java".replaceAll(Pattern.quote("."), Matcher.quoteReplacement("$"))); } catch (RuntimeException e) { e.printStackTrace(System.out); } System.out.println(); System.out.println("### ちなみに‥"); String[] strs = { "", " ", "\\Q\\E", "\\Q", "\\E", "123abc", "\\", "-^@[;:]", ",./!\"#", "$", "%&'()=~|", "`{+*}<>?_", }; for (String s : strs) { String src; if (s.length() == 0) { src = "(empty string)"; } else if (s.equals(" ")) { src = "(white space)"; } else { src = s; } System.out.printf("|%-16s|%-16s|%-16s|\n", src, Pattern.quote(s), Matcher.quoteReplacement(s)); } } }
実行結果は次のとおり。
### "Main.java" を "Main$java" に置き換えたいのだが‥ ### '.'をエスケープしなければひどいことになる。 $$$$$$$$$ ### '$'をエスケープしなければエラーになる。 java.lang.StringIndexOutOfBoundsException: String index out of range: 1 at java.lang.String.charAt(String.java:558) at java.util.regex.Matcher.appendReplacement(Matcher.java:704) at java.util.regex.Matcher.replaceAll(Matcher.java:806) at java.lang.String.replaceAll(String.java:2000) at Main.main(Main.java:21) ### Java 2 SE 1.4 では‥(1) Main$java ### Java 2 SE 1.4 では‥(2) Main$java ### Java 2 SE 1.4 では‥(3) Main$java ### Java 2 SE 5.0 では‥ Main$java ### ちなみに‥ |(empty string) |\Q\E | | |(white space) |\Q \E | | |\Q\E |\Q\Q\E\\E\Q\E |\\Q\\E | |\Q |\Q\Q\E |\\Q | |\E |\Q\E\\E\Q\E |\\E | |123abc |\Q123abc\E |123abc | |\ |\Q\\E |\\ | |-^@[;:] |\Q-^@[;:]\E |-^@[;:] | |,./!"# |\Q,./!"#\E |,./!"# | |$ |\Q$\E |\$ | |%&'()=~| |\Q%&'()=~|\E |%&'()=~| | |`{+*}<>?_ |\Q`{+*}<>?_\E |`{+*}<>?_ |
パターン文字列のエスケープは、単に「\Q」と「\E」で括るだけ。置換後文字列は、バックスラッシュ('\')とドル記号('$')がメタ文字。
このメソッドを使えば、「バックスラッシュを何個並べればいいんだっけ?」と悩むこともなかったのか‥
(2010/03/15追記)
「\Q\E」というパターン文字列の場合は単に「\Q」と「\E」で括るだけではないことに気づいたので、「\Q」と「\E」をそれぞれ単体で渡した場合の処理結果を追加。
XMLのCDATAセクションと同じで、「\Q〜\E」の中では開始記号「\Q」は特別扱いされず、終了記号の「\E」だけが特別扱いされるというもの。
「\E」を単体で渡すと、「\Q\E\\E\Q\E」という文字列が返ってくるが、これは単純に「\E」という文字列をデリミタとしてsplitでもしているのだろう。「\Q\E」+「\\E」+「\Q\E」という形になっている。