読者です 読者をやめる 読者になる 読者になる

HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

正規表現におけるメタ文字をエスケープするメソッド

Java Java 1.4 to 5.0

今まで、正規表現のメタ文字をエスケープするのに、バックスラッシュとかをいっぱい書いていたのだが、簡単にエスケープする記法があることに最近気づいた。さらにそのような文字列を生成するためのメソッドがあることにも気づいた。

ということで、実際にプログラムを作って実行してみる。

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」という形になっている。