Jak zmienić tekst na wyrażenie regularne w Javie


320

Czy Java ma wbudowaną metodę ucieczki przed dowolnym tekstem, aby można go było uwzględnić w wyrażeniu regularnym? Na przykład, jeśli moi użytkownicy wprowadzą „5 $”, chciałbym dopasować to dokładnie zamiast „5” po zakończeniu wprowadzania.

Odpowiedzi:


450

Od wersji Java 1.5 tak :

Pattern.quote("$5");

88
Proszę nie, żeby to nie unikało samego łańcucha, ale owija go za pomocą \Qi \E. Może to prowadzić do nieoczekiwanych rezultatów, na przykład Pattern.quote("*.wav").replaceAll("*",".*")skutkować \Q.*.wav\Ei nie .*\.wav, jak można się spodziewać.
Matthias Ronge

11
@Paramaeleon Dlaczego miałbyś oczekiwać tego foo (x) .bar () == x.bar ()?
Michael

7
@Paramaeleon Myślę, że nie rozumiesz przypadku użycia.
vikingsteve

18
Chciałbym tylko zaznaczyć, że ten sposób ucieczki dotyczy ucieczki także w wyrażeniach, które wprowadzasz później . To może być zaskakujące. Jeśli to zrobisz "mouse".toUpperCase().replaceAll("OUS","ic"), wróci MicE. Would't można oczekiwać, że do powrotu MICE, ponieważ nie stosuje się toUpperCase()na ic. W moim przykładzie quote()jest również stosowany na .*wstawce replaceAll(). Musisz zrobić coś innego, być .replaceAll("*","\\E.*\\Q")może zadziałałoby, ale to sprzeczne z intuicją.
Matthias Ronge

2
@Paramaleon Gdyby zadziałało poprzez dodanie indywidualnych znaków ucieczki, twój początkowy przykład nadal nie zrobiłby tego, co chciałeś ... gdyby osobno unikał znaków, zmieniłby *.wavsię w wzorzec wyrażenia regularnego \*\.wav, a replaceAll zmieniłby go \.*\.wav, co oznaczałoby, że dopasuj pliki, których nazwa składa się z dowolnej liczby kropek, po których następuje .wav. Najprawdopodobniej byś tego potrzebował, replaceAll("\\*", ".*")gdyby poszli z bardziej delikatną implementacją, która polega na rozpoznawaniu wszystkich możliwych aktywnych charcheterów wyrażeń regularnych i unikaniu ich indywidualnie ... czy to byłoby o wiele łatwiejsze?
Theodore Murdock,

112

Różnica pomiędzy Pattern.quotei Matcher.quoteReplacementnie była dla mnie jasna, zanim zobaczyłem następujący przykład

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

29
W szczególności Pattern.quotezastępuje znaki specjalne w ciągach wyszukiwania wyrażeń regularnych, takich jak. | + () Itp., I Matcher.quoteReplacementzastępuje znaki specjalne w ciągach zastępujących, takich jak \ 1, dla odwołań wstecznych.
Steven

9
Nie zgadzam się Pattern.quote otacza swój argument \ Q i \ E. Nie ucieka od znaków specjalnych.
David Medinets,

5
Matcher.quoteReplacement („4 $ i% $”) produkuje „4 \ $ i% \ $”. Ucieka przed znakami specjalnymi.
David Medinets,

4
Innymi słowy: quoteReplacementdba tylko o dwa symbole $i \ które mogą być na przykład użyte w ciągach zastępczych jako odniesienia wsteczne $1lub \1. Dlatego nie można go używać do ucieczki / cytowania wyrażenia regularnego.
SebastianH

1
Niesamowite. Oto przykład, gdzie chcemy zamienić $Group$z T$UYO$HI. $Symbol jest wyjątkowy zarówno w strukturze oraz w wymianie:"$Group$ Members".replaceFirst(Pattern.quote("$Group$"), Matcher.quoteReplacement("T$UYO$HI"))
arun

29

Może być za późno, aby odpowiedzieć, ale możesz również użyć Pattern.LITERAL, który zignoruje wszystkie znaki specjalne podczas formatowania:

Pattern.compile(textToFormat, Pattern.LITERAL);

Jest to szczególnie miłe, ponieważ można go połączyć zPattern.CASE_INSENSITIVE
mjjaniec

13

Myślę, że to, czego szukasz \Q$5\E. Zobacz także Pattern.quote(s)wprowadzone w Java5.

Aby uzyskać szczegółowe informacje, zobacz Wzór javadoc.


Jestem ciekawy, czy jest jakaś różnica między tym a użyciem flagi LITERAL, ponieważ javadoc mówi, że nie ma wbudowanej flagi do włączania i wyłączania LITERAL
Chris Mazzola,

15
Zauważ, że dosłownie użycie \ Q i \ E jest w porządku, tylko jeśli znasz swój wkład. Pattern.quote (s) zajmie się również przypadkiem, w którym tekst faktycznie zawiera te sekwencje.
Jeremy Huiskamp

10

Po pierwsze, jeśli

  • używasz replaceAll ()
  • NIE korzystasz z Matcher.quoteReplacement ()
  • tekst, który ma zostać zastąpiony, zawiera 1 $

nie umieści 1 na końcu. Spojrzy na wyrażenie regularne wyszukiwania dla pierwszej pasującej grupy i podrzędne TO. To właśnie oznacza 1, 2 lub 3 USD w tekście zastępczym: pasujące grupy ze wzorca wyszukiwania.

Często podłączam długie ciągi tekstu do plików .properties, a następnie generuję z nich tematy i treści wiadomości e-mail. Rzeczywiście wydaje się, że jest to domyślny sposób wykonywania i18n w Spring Framework. Umieszczam tagi XML, jako symbole zastępcze, w ciągach i używam replaceAll (), aby zamienić tagi XML na wartości w czasie wykonywania.

Natknąłem się na problem polegający na tym, że użytkownik wprowadził liczbę dolarów i centów ze znakiem dolara. Zadławiono replaceAll (), a w stracktrace pojawiają się:

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)

W tym przypadku użytkownik wpisał gdzieś „3 $” i replaceAll () zaczął szukać wyrażenia regularnego wyszukiwania dla trzeciej pasującej grupy, nie znalazł jednej i rzygnął.

Dany:

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

zastępując

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

z

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

Rozwiązać problem. Użytkownik może bez problemu wprowadzać dowolne znaki, w tym znaki dolara. Zachowywał się dokładnie tak, jak można się spodziewać.


6

Aby zabezpieczyć wzór, możesz zastąpić wszystkie symbole „\\\\”, z wyjątkiem cyfr i liter. A potem możesz umieścić w tym chronionym wzorze swoje specjalne symbole, aby ten wzór działał nie jak głupi cytowany tekst, ale naprawdę jak wzór, ale twój własny. Bez specjalnych symboli użytkownika.

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

Nie musisz uciekać przed spacjami. Możesz więc zmienić swój wzór na „([^ a-zA-z0-9])”.
Erel Segal-Halevi

5
Mała literówka, duże konsekwencje: „([^ a-zA-z0-9])” również nie pasuje (tzn. Nie ma znaku ucieczki) [, \,], ^, na pewno chcesz uciec! Literówka jest drugim „z”, które powinno być „Z”, w przeciwnym razie wszystko od ASCII 65 do ASCII 122 jest włączone
Zefiro

3

Pattern.quote („blabla”) działa dobrze.

Pattern.quote () działa dobrze. Zawiera zdanie ze znakami „ \ Q ” i „ \ E ”, a jeśli nie ma „\ Q” i „\ E”. Jeśli jednak potrzebujesz wykonać prawdziwe wyrażenie specjalne (lub niestandardowe), możesz użyć tego kodu:

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

Ta metoda zwraca: Niektóre / \ s / wText * / \, **

Kod na przykład i testy:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.