Руководство по экранированию символов в Java RegExps
API регулярных выражений в Java, java.util.regex , широко используется для сопоставления с образцом. Чтобы узнать больше, вы можете следовать этой статье .
В этой статье мы сосредоточимся на экранировании символов в регулярном выражении и покажем, как это можно сделать в Java.
2. Специальные символы регулярного выражения
Согласно документации API регулярных выражений Java, в регулярном выражении присутствует набор специальных символов, также известных как метасимволы.
Когда мы хотим оставить символы такими, какие они есть, вместо того, чтобы интерпретировать их с их особыми значениями, нам нужно их экранировать. Экранируя эти символы, мы заставляем их обрабатываться как обычные символы при сопоставлении строки с заданным регулярным выражением.
Метасимволы, которые нам обычно нужно экранировать таким образом:
Давайте рассмотрим простой пример кода, в котором мы сопоставляем входную строку с шаблоном, выраженным в регулярном выражении.
Этот тест показывает, что для заданной входной строки foof шаблон foo . ( foo , оканчивающийся символом точки) соответствует, он возвращает значение true , что указывает на успешное совпадение.
@Test public void givenRegexWithDot_whenMatchingStr_thenMatches() String strInput = "foof"; String strRegex = "foo."; assertEquals(true, strInput.matches(strRegex)); >
Вы можете задаться вопросом, почему совпадение успешно, если во входной строке нет символа точки (.)?
Ответ прост. Точка (.) — это метасимвол. Особое значение точки здесь в том, что на ее месте может быть «любой символ». Поэтому понятно, как сопоставитель определил, что совпадение найдено.
Допустим, мы не хотим обрабатывать символ точки (.) с его уникальным значением. Вместо этого мы хотим, чтобы он интерпретировался как знак точки. Это означает, что в предыдущем примере мы не хотим, чтобы шаблон foo. чтобы иметь совпадение во входной строке.
Как бы мы поступили в подобной ситуации? Ответ таков: нам нужно экранировать символ точки (.), чтобы его особое значение игнорировалось.
Давайте углубимся в это более подробно в следующем разделе.
3. Экранирование символов
Согласно документации Java API для регулярных выражений, существует два способа экранирования символов, имеющих особое значение. Другими словами, заставить их восприниматься как обычные персонажи.
Давайте посмотрим, что они из себя представляют:
- Перед метасимволом ставится обратная косая черта ( )
- Заключить метасимвол с \Q и \E
Это просто означает, что в примере, который мы видели ранее, если мы хотим избежать символа точки, нам нужно поместить символ обратной косой черты перед символом точки. В качестве альтернативы мы можем поместить символ точки между \Q и \E.
3.1. Экранирование с помощью обратной косой черты
Это один из методов, который мы можем использовать для экранирования метасимволов в регулярном выражении. Однако мы знаем, что символ обратной косой черты также является escape-символом в литералах Java String . Поэтому нам нужно удвоить символ обратной косой черты, когда он используется перед любым символом (включая сам символ ) .
Следовательно, в нашем примере нам нужно изменить регулярное выражение, как показано в этом тесте:
@Test public void givenRegexWithDotEsc_whenMatchingStr_thenNotMatching() String strInput = "foof"; String strRegex = "foo\\."; assertEquals(false, strInput.matches(strRegex)); >
Здесь символ точки экранирован, поэтому сопоставитель просто обрабатывает его как точку и пытается найти шаблон, оканчивающийся точкой (например, foo. ).
В этом случае он возвращает false , так как во входной строке нет совпадения для этого шаблона.
3.2. Экранирование с помощью \Q & \E
В качестве альтернативы мы можем использовать \Q и \E , чтобы экранировать специальный символ. \Q указывает, что все символы до \E должны быть экранированы, а \E означает, что нам нужно завершить экранирование, начатое с \Q .
Это просто означает, что все, что находится между \Q и \E , будет экранировано.
В показанном здесь тесте функция split() класса String выполняет сопоставление, используя предоставленное ему регулярное выражение.
Наше требование состоит в том, чтобы разбить входную строку с помощью вертикальной черты (|) на слова. Поэтому для этого мы используем шаблон регулярного выражения.
Символ вертикальной черты — это метасимвол, который необходимо экранировать в регулярном выражении.
Здесь экранирование выполняется путем помещения символа вертикальной черты между \Q и \E :
@Test public void givenRegexWithPipeEscaped_whenSplitStr_thenSplits() String strInput = "foo|bar|hello|world"; String strRegex = "\\Q|\\E"; assertEquals(4, strInput.split(strRegex).length); >
4. Метод Pattern.quote(String S)
Метод Pattern.Quote(String S) в классе java.util.regex.Pattern преобразует заданную строку шаблона регулярного выражения в буквальную строку шаблона. Это означает, что все метасимволы во входной строке рассматриваются как обычные символы.
Использование этого метода было бы более удобной альтернативой, чем использование \Q & \E , так как он оборачивает в них заданную строку .
Давайте посмотрим на этот метод в действии:
@Test public void givenRegexWithPipeEscQuoteMeth_whenSplitStr_thenSplits() String strInput = "foo|bar|hello|world"; String strRegex = "|"; assertEquals(4,strInput.split(Pattern.quote(strRegex)).length); >
В этом быстром тесте метод Pattern.quote() используется для выхода из заданного шаблона регулярного выражения и преобразования его в строковый литерал. Другими словами, он избегает для нас всех метасимволов, присутствующих в шаблоне регулярного выражения. Он выполняет ту же работу, что и \Q & \E .
Символ вертикальной черты экранируется методом Pattern.quote() , и функция split() интерпретирует его как строковый литерал, на который он делит ввод.
Как мы видим, это гораздо более чистый подход, а также разработчикам не нужно запоминать все управляющие последовательности.
Следует отметить, что Pattern.quote заключает весь блок в одну escape-последовательность. Если бы мы хотели экранировать символы по отдельности, нам пришлось бы использовать алгоритм замены токенов .
5. Дополнительные примеры
Давайте посмотрим, как работает метод replaceAll() в java.util.regex.Matcher .
Если нам нужно заменить все вхождения данной символьной строки на другую, мы можем использовать этот метод, передав ему регулярное выражение.
Представьте, что у нас есть ввод с несколькими вхождениями символа $ . В результате мы хотим получить ту же строку с заменой символа $ на £.
Этот тест демонстрирует, как шаблон $ передается без экранирования:
@Test public void givenRegexWithDollar_whenReplacing_thenNotReplace() String strInput = "I gave $50 to my brother." + "He bought candy for $35. Now he has $15 left."; String strRegex = "$"; String strReplacement = "£"; String output = "I gave £50 to my brother." + "He bought candy for £35. Now he has £15 left."; Pattern p = Pattern.compile(strRegex); Matcher m = p.matcher(strInput); assertThat(output, not(equalTo(m.replaceAll(strReplacement)))); >
Тест утверждает, что $ неправильно заменен на £ .
Теперь, если мы избегаем шаблона регулярного выражения, замена происходит правильно, и тест проходит, как показано в этом фрагменте кода:
@Test public void givenRegexWithDollarEsc_whenReplacing_thenReplace() String strInput = "I gave $50 to my brother." + "He bought candy for $35. Now he has $15 left."; String strRegex = "\\$"; String strReplacement = "£"; String output = "I gave £50 to my brother." + "He bought candy for £35. Now he has £15 left."; Pattern p = Pattern.compile(strRegex); Matcher m = p.matcher(strInput); assertEquals(output,m.replaceAll(strReplacement)); >
Обратите внимание на \\$ здесь, который выполняет трюк, экранируя символ $ и успешно сопоставляя шаблон.
6. Заключение
В этой статье мы рассмотрели экранирование символов в регулярных выражениях в Java.
Мы обсудили, почему регулярные выражения необходимо экранировать, и различные способы, которыми это можно сделать.
Как всегда, исходный код, относящийся к этой статье, можно найти на GitHub .
Как экранировать спецсимволы, например » \?», в строке URL из Java
Есть строка, содержащая знак вопроса ? , или несколько. Строка передается как параметр в URL. Знак вопроса нужно заменить на %3f . Как это сделать?
String text = text.replaceAll("\?", "%3f");
Такой код дает ошибку:
Dangling meta character '?' near index 0
Перевод вопроса с enSO: «Replace a question mark (?) with (\?)», не дословный, но очень похоже.
Отслеживать
задан 29 сен 2017 в 17:27
user236980 user236980
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Разобрался сам. Поскольку знак вопроса «?» является спецсимволом в regexp, то экранировать его нужно не одним «\» слешем, а двумя «\\»
String text = text.replaceAll("\\?", "%3f");
Ну а в общем случае для разных спецсимволов у меня получилось вот что:
String text = text .replaceAll("%", "%25") // Процент .replaceAll(" ", "%20") // Пробел .replaceAll("\t", "%20") // Табуляция (заменяем на пробел) .replaceAll("\n", "%20") // Переход строки (заменяем на пробел) .replaceAll("\r", "%20") // Возврат каретки (заменяем на пробел) .replaceAll("!", "%21") // Восклицательный знак .replaceAll("\"", "%22") // Двойная кавычка .replaceAll("#", "%23") // Октоторп, решетка .replaceAll("\\$", "%24") // Знак доллара .replaceAll("&", "%26") // Амперсанд .replaceAll("'", "%27") // Одиночная кавычка .replaceAll("\\(", "%28") // Открывающаяся скобка .replaceAll("\\)", "%29") // Закрывающаяся скобка .replaceAll("\\*", "%2a") // Звездочка .replaceAll("\\+", "%2b") // Знак плюс .replaceAll(",", "%2c") // Запятая .replaceAll("-", "%2d") // Дефис .replaceAll("\\.", "%2e") // Точка .replaceAll("/", "%2f") // Слеш, косая черта .replaceAll(":", "%3a") // Двоеточие .replaceAll(";", "%3b") // Точка с запятой .replaceAll("", "%3e") // Закрывающаяся угловая скобка .replaceAll("\\?", "%3f") // Вопросительный знак .replaceAll("@", "%40") // At sign, по цене, собачка .replaceAll("\\[", "%5b") // Открывающаяся квадратная скобка .replaceAll("\\\\", "%5c") // Одиночный обратный слеш '\' .replaceAll("\\]", "%5d") // Закрывающаяся квадратная скобка .replaceAll("\\^", "%5e") // Циркумфлекс .replaceAll("_", "%5f") // Нижнее подчеркивание .replaceAll("`", "%60") // Гравис .replaceAll("\\", "%7d") // Закрывающаяся фигурная скобка .replaceAll("~", "%7e"); // Тильда
Экранирование, специальные символы
Как мы уже видели, обратная косая черта \ используется для обозначения классов символов, например \d . Это специальный символ в регулярных выражениях (как и в обычных строках).
Есть и другие специальные символы, которые имеют особое значение в регулярном выражении. Они используются для более сложных поисковых конструкций. Вот полный перечень этих символов: [ ] \ ^ $ . | ? * + ( ) .
Не надо пытаться запомнить этот список: мы разберёмся с каждым из них по отдельности, и таким образом вы выучите их «автоматически».
Экранирование символов
Допустим, мы хотим найти буквально точку. Не «любой символ», а именно точку.
Чтобы использовать специальный символ как обычный, добавьте к нему обратную косую черту: \. .
Это называется «экранирование символа».
alert( "Глава 5.1".match(/\d\.\d/) ); // 5.1 (совпадение!) alert( "Глава 511".match(/\d\.\d/) ); // null ("\." - ищет обычную точку)
Круглые скобки также являются специальными символами, поэтому, если нам нужно использовать именно их, нужно указать \( . В приведённом ниже примере ищется строка «g()» :
alert( "function g()".match(/g\(\)/) ); // "g()"
Если мы ищем обратную косую черту \ , это специальный символ как в обычных строках, так и в регулярных выражениях, поэтому мы должны удвоить её.
alert( "1\\2".match(/\\/) ); // '\'
Косая черта
Символ косой черты ‘/’ , так называемый «слэш», не является специальным символом, но в JavaScript он используется для открытия и закрытия регулярного выражения: /. шаблон. / , поэтому мы должны экранировать его.
Вот как выглядит поиск самой косой черты ‘/’ :
alert( "/".match(/\//) ); // '/'
С другой стороны, если мы не используем короткую запись /. / , а создаём регулярное выражение, используя new RegExp , тогда нам не нужно экранировать косую черту:
alert( "/".match(new RegExp("/")) ); // находит /
new RegExp
Если мы создаём регулярное выражение с помощью new RegExp , то нам не нужно учитывать / , но нужно другое экранирование.
Например, такой поиск не работает:
let regexp = new RegExp("\d\.\d"); alert( "Глава 5.1".match(regexp) ); // null
Аналогичный поиск в примере выше с /\d\.\d/ вполне работал, почему же не работает new RegExp(«\d\.\d») ?
Причина в том, что символы обратной косой черты «съедаются» строкой. Как вы помните, обычные строки имеют свои специальные символы, такие как \n , и для экранирования используется обратная косая черта.
Вот как воспринимается строка «\d.\d»:
alert("\d\.\d"); // d.d
Строковые кавычки «съедают» символы обратной косой черты для себя, например:
- \n – становится символом перевода строки,
- \u1234 – становится символом Юникода с указанным номером,
- …А когда нет особого значения: как например для \d или \z , обратная косая черта просто удаляется.
Таким образом, new RegExp получает строку без обратной косой черты. Вот почему поиск не работает!
Чтобы исправить это, нам нужно удвоить обратную косую черту, потому что строковые кавычки превращают \\ в \ :
let regStr = "\\d\\.\\d"; alert(regStr); // \d\.\d (теперь правильно) let regexp = new RegExp(regStr); alert( "Глава 5.1".match(regexp) ); // 5.1
Итого
- Для поиска специальных символов [ ] \ ^ $ . | ? * + ( ) , нам нужно добавить перед ними \ («экранировать их»).
- Нам также нужно экранировать / , если мы используем /. / (но не new RegExp ).
- При передаче строки в new RegExp нужно удваивать обратную косую черту: \\ для экранирования специальных символов, потому что строковые кавычки «съедят» одну черту.
Как экранировать кавычки в java
Если нужно использовать кавычки, как часть строки, то их нужно экранировать специальным символом: \ . Например:
System.out.println("Dragon's mother said \"No\"");
Вывод получится следующий:
В этом случае экранированные кавычки выводятся «как есть», а не рассматриваются компилятором, как начало или конец строки