Руководство по экранированию символов в Java RegExps

Руководство по экранированию символов в Java RegExps

1. обзор

API регулярных выражений в Java,java.util.regex, широко используется для сопоставления с образцом. Чтобы узнать больше, вы можете следить за этимarticle.

В этой статье мы сосредоточимся на экранировании символов в регулярном выражении и покажем, как это можно сделать в Java.

2. Специальные символы RegExp

Согласно документации API регулярных выражений Java, в регулярном выражении присутствует набор специальных символов, также известных как метасимволы.

Когда мы хотим разрешить символам как есть, а не интерпретировать их с их особыми значениями, мы должны избежать их. Экранируя эти символы, мы заставляем их обрабатываться как обычные символы при сопоставлении строки с данным регулярным выражением.

Метасимволы, которые мы обычно должны избегать таким образом:

<([\{\^-=$!|]})? +.> *

Давайте посмотрим на простой пример кода, в котором мы сопоставляем вводString с шаблоном, выраженным в регулярном выражении.

Этот тест показывает, что для данной входной строкиfoof, когда шаблонfoo. (foo, заканчивающийся символом точки) совпадает, он возвращает значениеtrue, которое указывает, что совпадение было успешным.

@Test
public void givenRegexWithDot_whenMatchingStr_thenMatches() {
    String strInput = "foof";
    String strRegex = "foo.";

    assertEquals(true, strInput.matches(strRegex));
}

Вы можете задаться вопросом, почему совпадение было успешным, если во входных данныхString? нет символа точки (.)

Ответ прост. Точка (.) Является метасимволом - особое значение точки здесь в том, что на ее месте может быть «любой символ». Следовательно, ясно, как средство сопоставления определило, что совпадение найдено.

Допустим, мы не хотим рассматривать символ точки (.) С его уникальным значением. Вместо этого мы хотим, чтобы это интерпретировалось как точка. Это означает, что в предыдущем примере мы не хотим, чтобы шаблонfoo. совпадал во входных данныхString.

Как бы мы справились с такой ситуацией? Ответ:we need to escape the dot (.) character so that its special meaning gets ignored.

Давайте рассмотрим это более подробно в следующем разделе.

3. Убегающие персонажи

В соответствии с документацией по API Java для регулярных выражений, есть два способа избежать символов, которые имеют особое значение. Другими словами, заставить их обращаться с обычными персонажами.

Посмотрим, что это такое:

  1. Предшествующий метасимвол с обратной косой чертой (\)

  2. Заключите метасимвол с\Q и\E

Это просто означает, что в примере, который мы видели ранее, если мы хотим экранировать символ точки, нам нужно поставить символ обратной косой черты перед символом точки. В качестве альтернативы мы можем поместить символ точки между \ Q и \ E.

3.1. Экранирование с помощью обратной косой черты

Это одна из техник, которые мы можем использовать для экранирования метасимволов в регулярном выражении. Однако мы знаем, что обратная косая черта также является escape-символом в литералах JavaString. Поэтому нам нужно удвоить символ обратной косой черты при использовании его перед любым символом (включая сам символ \).

Следовательно, в нашем примере нам нужно изменить регулярное выражение, как показано в этом тесте:

@Test
public void givenRegexWithDotEsc_whenMatchingStr_thenNotMatching() {
    String strInput = "foof";
    String strRegex = "foo\\.";

    assertEquals(false, strInput.matches(strRegex));
}

Здесь символ точки экранируется, поэтому средство сопоставления просто обрабатывает его как точку и пытается найти шаблон, который заканчивается точкой (т.е. foo.).

В этом случае он возвращаетfalse, поскольку на входеString для этого шаблона нет совпадения.

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 преобразует данный шаблон регулярного выраженияString в литеральный шаблонString.. Это означает, что все метасимволы во входныхString являются рассматриваются как обычные персонажи.

Использование этого метода было бы более удобной альтернативой, чем использование\Q &\E, поскольку он оборачивает данныйString вместе с ними.

Давайте посмотрим на этот метод в действии:

@Test
public void givenRegexWithPipeEscQuoteMeth_whenSplitStr_thenSplits() {
    String strInput = "foo|bar|hello|world";
    String strRegex = "|";

    assertEquals(4,strInput.split(Pattern.quote(strRegex)).length);
}

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

Символ вертикальной черты экранируется методомPattern.quote(), аsplit() интерпретирует его как литералString, на который он делит ввод.

Как мы видим, это более чистый подход, и разработчикам не нужно запоминать все escape-последовательности.

5. Дополнительные примеры

Давайте посмотрим, как работает методreplaceAll() дляjava.util.regex.Matcher.

Если нам нужно заменить все вхождения данного символаString другим, мы можем использовать этот метод, передав ему регулярное выражение.

Представьте, что у нас есть вход с несколькими вхождениями символа$. В результате мы хотим получить ту же строку с замененным символом$ на £.

Этот тест демонстрирует, как шаблон$ передается без экранирования:

@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.

Мы обсудили, почему необходимо избегать регулярных выражений, и различные способы, которыми это может быть достигнуто.

Как всегда, исходный код, связанный с этой статьей, можно найти вover on GitHub.