Як уникнути тексту для регулярного вираження на 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.