Як уникнути тексту для регулярного вираження на 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 Я думаю, ви неправильно розумієте випадок використання.
— vikingsteve

18
Я просто хочу зазначити, що цей спосіб втечі застосовується і втечі, і в виразах, які ви вводите згодом . Це може дивувати. Якщо ви "mouse".toUpperCase().replaceAll("OUS","ic")це зробите , повернетесь MicE. Ви would't очікувати , що вона повернеться , MICEтому що ви не застосовувати toUpperCase()на ic. У моєму прикладі quote()також застосовано до .*вставки replaceAll(). Вам доведеться робити щось інше, можливо .replaceAll("*","\\E.*\\Q"), спрацювало б, але це контрінтуїтивно.
— Маттіас Ронге

2
@Paramaleon Якби це працювало, додаючи окремі втечі, ваш початковий приклад все одно не зробив би те, що ви хотіли ... якби він уникав символів окремо, він перетворився б *.wavна схему регулярних \*\.wavвиразів, а замінуAll перетворило б його в \.*\.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. Тому його не слід використовувати для уникнення / цитування регулярного виразу.
— СебастьянH

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.

Докладніше див. Шаблон Явадок.


Мені цікаво, чи є якась різниця між цим та використанням прапор LITERAL, оскільки javadoc каже, що немає вбудованого прапора для вмикання та вимикання LITERAL
— Кріс Маццола

15
Зауважте, що буквально використання \ Q та \ E добре, лише якщо ви знаєте свої дані. Pattern.quote (s) також буде обробляти випадок, коли ваш текст фактично містить ці послідовності.
— Jeremy Huiskamp

10

По-перше, якщо

  • ви використовуєте substituAll ()
  • ви НЕ використовуєте Matcher.quoteReplacement ()
  • текст, який слід замінити, включає $ 1

він не поставить 1 в кінці. Він буде розглядати конвеєр пошуку для першої групи, що відповідає, та суб ТО. Це те, що означає $ 1, $ 2 або $ 3 у тексті заміни: відповідність груп за схемою пошуку.

Я часто підключаю довгі рядки тексту до файлів .properties, а потім генерую з них предмети та тіла електронної пошти. Дійсно, це є типовим способом зробити i18n у Spring Framework. Я поміщаю теги XML, як заповнювачі, у рядки, і використовую substituAll () для заміни тегів XML зі значеннями під час виконання.

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

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" десь у своєму введенні та заміниAll () пішов шукати регекс для третьої групи, що відповідає, не знайшов його, і забрав.

Подано:

// "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 ("блабла") працює добре.

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"));

-2

Символ ^ (заперечення) використовується для відповідності тому, що відсутнє в групі символів.

Це посилання на регулярні вирази

Ось інформація про зображення заперечення:

Інформація про заперечення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.