Comment échapper du texte pour une expression régulière en Java

320

Java a-t-il un moyen intégré pour échapper au texte arbitraire afin qu'il puisse être inclus dans une expression régulière? Par exemple, si mes utilisateurs entrent "5 $", je voudrais faire correspondre exactement cela plutôt qu'un "5" après la fin de la saisie.

Mat
la source

Réponses:

450

Depuis Java 1.5, oui :

Pattern.quote("$5");
Mike Stone
la source
88
Veuillez noter que cela n'échappe pas à la chaîne elle-même, mais l'encapsule à l'aide de \Qet \E. Cela peut conduire à des résultats inattendus, par exemple Pattern.quote("*.wav").replaceAll("*",".*")entraînera \Q.*.wav\Eet non .*\.wav, comme vous pouvez vous y attendre.
Matthias Ronge
11
@Paramaeleon Pourquoi vous attendriez-vous à ce que foo (x) .bar () == x.bar ()?
Michael
7
@Paramaeleon Je pense que vous comprenez mal le cas d'utilisation.
vikingsteve
18
Je veux juste souligner que cette façon de s'échapper s'applique également à l'échappement sur les expressions que vous introduisez ensuite . Cela peut surprendre. Si vous le faites, "mouse".toUpperCase().replaceAll("OUS","ic")il reviendra MicE. Vous attendez would't pour revenir MICEparce que vous n'avez pas appliqué toUpperCase()sur ic. Dans mon exemple, il quote()est également appliqué sur l' .*insert replaceAll(). Vous devez faire autre chose, peut .replaceAll("*","\\E.*\\Q")- être que cela fonctionnerait, mais c'est contre-intuitif.
Matthias Ronge
2
@Paramaleon Si cela fonctionnait en ajoutant des échappements individuels, votre exemple initial ne ferait toujours pas ce que vous vouliez ... s'il échappait individuellement aux caractères, il se transformerait *.waven motif regex \*\.wavet le replaceAll le transformerait \.*\.wav, ce qui signifie qu'il le ferait correspond à des fichiers dont le nom consiste en un nombre arbitraire de périodes suivies de .wav. Vous en auriez probablement eu besoin replaceAll("\\*", ".*")s'ils étaient partis avec l'implémentation la plus fragile qui repose sur la reconnaissance de tous les characheurs de regex actifs possibles et leur échappance individuelle ... serait-ce tellement plus facile?
Theodore Murdock
112

La différence entre Pattern.quoteet Matcher.quoteReplacementn'était pas claire pour moi avant de voir l'exemple suivant

s.replaceFirst(Pattern.quote("text to replace"), 
               Matcher.quoteReplacement("replacement text"));
Pavel Feldman
la source
29
Plus précisément, Pattern.quoteremplace les caractères spéciaux dans les chaînes de recherche d'expression régulière, comme. | + () Etc., et Matcher.quoteReplacementremplace les caractères spéciaux dans les chaînes de remplacement, comme \ 1 pour les références arrières.
Steven
9
Je ne suis pas d'accord. Pattern.quote encapsule son argument avec \ Q et \ E. Il n'échappe pas aux caractères spéciaux.
David Medinets
5
Matcher.quoteReplacement ("4 $ &% $") produit "4 \ $ &% \ $". Il échappe aux caractères spéciaux.
David Medinets
4
En d'autres termes: quoteReplacementne se soucie que des deux symboles $et \ qui peuvent par exemple être utilisés dans des chaînes de remplacement comme références arrières $1ou \1. Il ne doit donc pas être utilisé pour échapper / citer une expression régulière.
SebastianH
1
Impressionnant. Voici un exemple où nous voulons remplacer $Group$avec T$UYO$HI. Le $symbole est spécial à la fois dans le motif et dans le remplacement:"$Group$ Members".replaceFirst(Pattern.quote("$Group$"), Matcher.quoteReplacement("T$UYO$HI"))
arun
29

Il peut être trop tard pour répondre, mais vous pouvez également utiliser Pattern.LITERAL, qui ignorerait tous les caractères spéciaux lors du formatage:

Pattern.compile(textToFormat, Pattern.LITERAL);
Androidme
la source
C'est particulièrement agréable car vous pouvez le combiner avecPattern.CASE_INSENSITIVE
mjjaniec
13

Je pense que c'est ce que vous recherchez \Q$5\E. Voir également Pattern.quote(s)introduit dans Java5.

Voir Pattern javadoc pour plus de détails.

Rob Oxspring
la source
Je suis curieux de savoir s'il y a une différence entre cela et l'utilisation du drapeau LITERAL, car le javadoc dit qu'il n'y a pas de drapeau intégré pour activer et désactiver LITERAL
Chris Mazzola
15
Notez que l'utilisation littérale de \ Q et \ E ne convient que si vous connaissez votre entrée. Pattern.quote (s) gérera également le cas où votre texte contient réellement ces séquences.
Jeremy Huiskamp
10

Tout d'abord, si

  • vous utilisez replaceAll ()
  • vous N'utilisez PAS Matcher.quoteReplacement ()
  • le texte à remplacer comprend un $ 1

il ne mettra pas un 1 à la fin. Il examinera l'expression rationnelle de recherche pour le premier groupe et le sous-groupe correspondants. C'est ce que signifie $ 1, $ 2 ou $ 3 dans le texte de remplacement: les groupes correspondants du modèle de recherche.

Je branche fréquemment de longues chaînes de texte dans des fichiers .properties, puis je génère des sujets et des corps d'e-mails à partir de ceux-ci. En effet, cela semble être la façon par défaut de faire i18n dans Spring Framework. J'ai mis des balises XML, en tant qu'espaces réservés, dans les chaînes et j'utilise replaceAll () pour remplacer les balises XML par les valeurs au moment de l'exécution.

J'ai rencontré un problème où un utilisateur saisissait un chiffre en dollars et en cents, avec un signe dollar. replaceAll () s'est étouffé dessus, les éléments suivants apparaissant dans une stracktrace:

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)

Dans ce cas, l'utilisateur avait entré "$ 3" quelque part dans son entrée et replaceAll () est allé chercher dans l'expression régulière de recherche pour le troisième groupe correspondant, n'en a pas trouvé un et a vomi.

Donné:

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

remplacer

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

avec

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

résolu le problème. L'utilisateur peut entrer n'importe quel type de caractères, y compris les signes dollar, sans problème. Il s'est comporté exactement comme vous vous y attendez.

Meower68
la source
6

Pour avoir un motif protégé, vous pouvez remplacer tous les symboles par "\\\\", à l'exception des chiffres et des lettres. Et après cela, vous pouvez mettre dans ce motif protégé vos symboles spéciaux pour que ce motif ne fonctionne pas comme un texte cité stupide, mais vraiment comme un motif, mais le vôtre. Sans symboles spéciaux utilisateur.

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");
    }
}
Moscou garçon
la source
Vous n'êtes pas obligé d'échapper aux espaces. Vous pouvez donc modifier votre modèle en "([^ a-zA-z0-9])".
Erel Segal-Halevi
5
Petite faute de frappe, grandes conséquences: "([^ a-zA-z0-9])" ne correspond pas non plus (c'est-à-dire ne s'échappe pas) [, \,], ^ que vous voulez certainement échapper! La faute de frappe est le deuxième «z» qui devrait être un «Z», sinon tout est compris entre ASCII 65 et ASCII 122
Zefiro
3

Pattern.quote ("blabla") fonctionne bien.

Le Pattern.quote () fonctionne bien. Il entoure la phrase des caractères " \ Q " et " \ E ", et s'il s'échappe "\ Q" et "\ E". Cependant, si vous devez faire un véritable échappement d'expression régulière (ou échappement personnalisé), vous pouvez utiliser ce code:

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

Cette méthode renvoie: Some / \ s / wText * / \, **

Code par exemple et tests:

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

Le symbole ^ (négation) est utilisé pour faire correspondre quelque chose qui n'est pas dans le groupe de caractères.

Ceci est le lien vers les expressions régulières

Voici les informations d'image sur la négation:

Info sur la négation

Akhil Kathi
la source