Как экранировать текст для регулярного выражения в Java


320

Есть ли в Java встроенный способ экранирования произвольного текста, чтобы его можно было включить в регулярное выражение? Например, если мои пользователи вводят «5 долларов», я бы хотел, чтобы это совпадало, а не «5» после окончания ввода.

Ответы:


450

Начиная с Java 1.5, да :

Pattern.quote("$5");

88
Пожалуйста, обратите внимание, что это не исключает саму строку, а оборачивает ее, используя \Qи \E. Это может привести к неожиданным результатам, например Pattern.quote("*.wav").replaceAll("*",".*"), приведет к, \Q.*.wav\Eа не так .*\.wav, как вы могли ожидать.
Матиас Ронге

11
@Paramaeleon Почему вы ожидаете, что foo (x) .bar () == x.bar ()?
Майкл

7
@Paramaeleon Я думаю, что вы неправильно понимаете вариант использования.
Викингстев

18
Я просто хочу указать, что этот способ экранирования применяется к экранированию и для выражений, которые вы вводите позже . Это может быть удивительно. Если вы это сделаете, "mouse".toUpperCase().replaceAll("OUS","ic")он вернется MicE. Вы не ожидаете, что он вернется, MICEпотому что вы не подали заявку toUpperCase()на ic. В моем примере quote()это также относится и к .*вкладышу replaceAll(). Вы должны сделать что-то еще, возможно .replaceAll("*","\\E.*\\Q"), сработает, но это противоречит здравому смыслу.
Матиас Ронге

2
@Paramaleon Если бы он работал, добавив отдельные экранированные символы, ваш первоначальный пример все равно не сделал бы то, что вы хотели ... если бы он экранировал символы по отдельности, он превратился бы *.wavв шаблон регулярного выражения \*\.wav, а replaceAll превратил бы его в \.*\.wav, то есть файлы соответствия, имя которых состоит из произвольного числа периодов, за которыми следует .wav. Скорее всего, вам бы понадобилось, replaceAll("\\*", ".*")если бы они пошли с более хрупкой реализацией, которая основывается на распознавании всех возможных активных символов регулярных выражений и экранировании их по отдельности ... это было бы намного проще?
Теодор Мердок

112

Разница между Pattern.quoteи Matcher.quoteReplacementне была понятна мне до того, как я увидел следующий пример

s.replaceFirst(Pattern.quote("text to replace"), 
               Matcher.quoteReplacement("replacement text"));

29
В частности, Pattern.quoteзаменяет специальные символы в строках поиска регулярных выражений, например. | + () И т. Д., И Matcher.quoteReplacementзаменяет специальные символы в строках замены, например \ 1 для обратных ссылок.
Стивен

9
Я не согласна Pattern.quote переносит свой аргумент с помощью \ Q и \ E. Это не ускользает от специальных символов.
Давид Мединец,

5
Matcher.quoteReplacement ("4 $ &% $") выдает "4 \ $ &% \ $". Избегает специальных символов.
Давид Мединец,

4
Другими словами: quoteReplacementтолько заботами о двух символов $и \ которые могут быть использованы , например , в замене строк в качестве обратных ссылок $1или \1. Поэтому его нельзя использовать для экранирования / цитирования регулярного выражения.
Себастьян,

1
Потрясающие. Вот пример , где мы хотим заменить $Group$с T$UYO$HI. $Символ является особенным , как в шаблоне и в замене:"$Group$ Members".replaceFirst(Pattern.quote("$Group$"), Matcher.quoteReplacement("T$UYO$HI"))
Арун

29

Возможно, будет слишком поздно для ответа, но вы также можете использовать Pattern.LITERAL, который будет игнорировать все специальные символы при форматировании:

Pattern.compile(textToFormat, Pattern.LITERAL);

Это особенно приятно, потому что вы можете объединить это сPattern.CASE_INSENSITIVE
mjjaniec

13

Я думаю, что вы после этого \Q$5\E. Также см. Pattern.quote(s)Введено в Java5.

См. Шаблон Javadoc для деталей.


Мне интересно, есть ли какая-либо разница между этим и использованием флага LITERAL, поскольку javadoc говорит, что нет встроенного флага для включения и выключения LITERAL
Крис Маццола

15
Обратите внимание, что буквально использовать \ Q и \ E хорошо, только если вы знаете свой ввод. Pattern.quote (s) также будет обрабатывать случай, когда ваш текст на самом деле содержит эти последовательности.
Джереми Хуискамп

10

Во-первых, если

  • вы используете replaceAll ()
  • Вы НЕ используете Matcher.quoteReplacement ()
  • текст, который будет заменен, включает в себя $ 1

это не будет ставить 1 в конце. Он будет смотреть на регулярное выражение поиска для первой подходящей группы и подпункта THAT. Это означает, что $ 1, $ 2 или $ 3 означают в тексте замены: соответствующие группы из шаблона поиска.

Я часто вставляю длинные строки текста в файлы .properties, а затем генерирую из них темы и сообщения электронной почты. Действительно, это, кажется, способ сделать i18n по умолчанию в Spring Framework по умолчанию. Я помещаю теги XML в качестве заполнителей в строки и использую replaceAll () для замены тегов XML значениями во время выполнения.

Я столкнулся с проблемой, когда пользователь вводил цифру в долларах и центах со знаком доллара. replaceAll () захлебнулся, и в следовой строке появилось следующее:

java.lang.IndexOutOfBoundsException: No group 3
at java.util.regex.Matcher.start(Matcher.java:374)
at java.util.regex.Matcher.appendReplacement(Matcher.java:748)
at java.util.regex.Matcher.replaceAll(Matcher.java:823)
at java.lang.String.replaceAll(String.java:2201)

В этом случае пользователь ввел «$ 3» где-то в своем вводе, а replaceAll () пошёл искать в регулярном выражении поиска третью подходящую группу, не нашел ее и рванул.

Дано:

// "msg" is a string from a .properties file, containing "<userInput />" among other tags
// "userInput" is a String containing the user's input

замена

msg = msg.replaceAll("<userInput \\/>", userInput);

с участием

msg = msg.replaceAll("<userInput \\/>", Matcher.quoteReplacement(userInput));

решил проблему. Пользователь может вводить любые символы, включая знаки доллара, без проблем. Он вел себя именно так, как вы ожидаете.


6

Чтобы иметь защищенный шаблон, вы можете заменить все символы на «\\\\», кроме цифр и букв. И после этого вы можете вставить в этот защищенный шаблон ваши специальные символы, чтобы этот шаблон работал не как глупый цитируемый текст, а как паттерн, но как ваш собственный. Без специальных символов пользователя.

public class Test {
    public static void main(String[] args) {
        String str = "y z (111)";
        String p1 = "x x (111)";
        String p2 = ".* .* \\(111\\)";

        p1 = escapeRE(p1);

        p1 = p1.replace("x", ".*");

        System.out.println( p1 + "-->" + str.matches(p1) ); 
            //.*\ .*\ \(111\)-->true
        System.out.println( p2 + "-->" + str.matches(p2) ); 
            //.* .* \(111\)-->true
    }

    public static String escapeRE(String str) {
        //Pattern escaper = Pattern.compile("([^a-zA-z0-9])");
        //return escaper.matcher(str).replaceAll("\\\\$1");
        return str.replaceAll("([^a-zA-Z0-9])", "\\\\$1");
    }
}

Вам не нужно избегать пробелов. Таким образом, вы можете поменять свой шаблон на «([^ a-zA-z0-9])».
Эрл Сегал-Халеви

5
Маленькая опечатка, большие последствия: "([^ a-zA-z0-9])" также не совпадает (т.е. не сбежит) [, \,], ^, от которой вы наверняка хотите сбежать! Опечатка - это вторая буква «z», которая должна быть буквой «Z», в противном случае все от ASCII 65 до ASCII 122 включается
Zefiro

3

Pattern.quote ("Blabla") работает хорошо.

Pattern.quote () работает хорошо. Он включает в себя предложение с символами « \ Q » и « \ E », и, если он экранирует «\ Q» и «\ E». Однако, если вам нужно сделать реальное экранирование регулярного выражения (или пользовательское экранирование), вы можете использовать этот код:

String someText = "Some/s/wText*/,**";
System.out.println(someText.replaceAll("[-\\[\\]{}()*+?.,\\\\\\\\^$|#\\\\s]", "\\\\$0"));

Этот метод возвращает: Some / \ s / wText * / \, **

Код для примера и тесты:

String someText = "Some\\E/s/wText*/,**";
System.out.println("Pattern.quote: "+ Pattern.quote(someText));
System.out.println("Full escape: "+someText.replaceAll("[-\\[\\]{}()*+?.,\\\\\\\\^$|#\\\\s]", "\\\\$0"));

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.