Puis-je remplacer des groupes dans Java regex?

95

J'ai ce code, et je veux savoir, si je peux remplacer uniquement des groupes (pas tous les modèles) dans Java regex. Code:

 //...
 Pattern p = Pattern.compile("(\\d).*(\\d)");
    String input = "6 example input 4";
    Matcher m = p.matcher(input);
    if (m.find()) {

        //Now I want replace group one ( (\\d) ) with number 
       //and group two (too (\\d) ) with 1, but I don't know how.

    }
Wokena
la source
6
Pouvez-vous clarifier votre question, comme peut-être donner le résultat attendu pour cette entrée?
Michael Myers

Réponses:

125

Utilisez $n(où n est un chiffre) pour faire référence aux sous-séquences capturées dans replaceFirst(...). Je suppose que vous vouliez remplacer le premier groupe par la chaîne littérale "nombre" et le deuxième groupe par la valeur du premier groupe.

Pattern p = Pattern.compile("(\\d)(.*)(\\d)");
String input = "6 example input 4";
Matcher m = p.matcher(input);
if (m.find()) {
    // replace first number with "number" and second number with the first
    String output = m.replaceFirst("number $3$1");  // number 46
}

Considérez (\D+)pour le deuxième groupe au lieu de (.*). *est un matcher gourmand, et consommera d'abord le dernier chiffre. Le matcher devra alors revenir en arrière lorsqu'il se rendra compte que la finale (\d)n'a rien à correspondre, avant de pouvoir correspondre au dernier chiffre.

Chadwick
la source
7
Cela aurait été bien si vous aviez publié un exemple de sortie
winklerrr
6
Cela fonctionne sur le premier match, mais ne fonctionnera pas s'il y a beaucoup de groupes et que vous les parcourez avec un certain temps (m.find ())
Hugo Zaragoza
1
Je suis d'accord avec Hugo, c'est une manière terrible de mettre en œuvre la solution ... Pourquoi diable est-ce la réponse acceptée et non la réponse d'acdcjunior - qui est la solution parfaite: petite quantité de code, haute cohésion et faible couplage, beaucoup moins de chance (sinon aucune chance) d'effets secondaires indésirables ... soupir ...
FireLight
Cette réponse n'est actuellement pas valide. Le m.replaceFirst("number $2$1");devrait êtrem.replaceFirst("number $3$1");
Daniel Eisenreich
52

Vous pouvez utiliser Matcher#start(group)et Matcher#end(group)pour créer une méthode de remplacement générique:

public static String replaceGroup(String regex, String source, int groupToReplace, String replacement) {
    return replaceGroup(regex, source, groupToReplace, 1, replacement);
}

public static String replaceGroup(String regex, String source, int groupToReplace, int groupOccurrence, String replacement) {
    Matcher m = Pattern.compile(regex).matcher(source);
    for (int i = 0; i < groupOccurrence; i++)
        if (!m.find()) return source; // pattern not met, may also throw an exception here
    return new StringBuilder(source).replace(m.start(groupToReplace), m.end(groupToReplace), replacement).toString();
}

public static void main(String[] args) {
    // replace with "%" what was matched by group 1 
    // input: aaa123ccc
    // output: %123ccc
    System.out.println(replaceGroup("([a-z]+)([0-9]+)([a-z]+)", "aaa123ccc", 1, "%"));

    // replace with "!!!" what was matched the 4th time by the group 2
    // input: a1b2c3d4e5
    // output: a1b2c3d!!!e5
    System.out.println(replaceGroup("([a-z])(\\d)", "a1b2c3d4e5", 2, 4, "!!!"));
}

Consultez la démo en ligne ici .

acdcjunior
la source
1
Cela devrait vraiment être la réponse acceptée, c'est la solution la plus complète et la plus «prête à l'emploi» sans introduire un niveau de couplage au code d'accompagnement. Bien que je recommande de changer les noms de méthode de l'un d'entre eux. À première vue, cela ressemble à un appel récursif dans la première méthode.
FireLight
Opportunité de modification manquée. Reprenez la partie sur l'appel récursif, n'a pas analysé le code correctement. Les surcharges fonctionnent bien ensemble
FireLight
23

Désolé de battre un cheval mort, mais c'est un peu bizarre que personne ne l'ait signalé - "Oui, vous pouvez, mais c'est le contraire de la façon dont vous utilisez la capture de groupes dans la vraie vie".

Si vous utilisez Regex comme il est censé être utilisé, la solution est aussi simple que ceci:

"6 example input 4".replaceAll("(?:\\d)(.*)(?:\\d)", "number$11");

Ou comme indiqué à juste titre par shmosel ci-dessous,

"6 example input 4".replaceAll("\d(.*)\d", "number$11");

... car dans votre regex il n'y a aucune bonne raison de regrouper les décimales du tout.

Vous n'utilisez généralement pas de groupes de capture sur les parties de la chaîne que vous souhaitez supprimer , vous les utilisez sur la partie de la chaîne que vous souhaitez conserver .

Si vous voulez vraiment des groupes que vous souhaitez remplacer, ce que vous voulez probablement à la place est un moteur de création de modèles (par exemple, moustache, ejs, StringTemplate, ...).


En aparté pour les curieux, même les groupes non capturants dans les regex sont juste là pour le cas où le moteur de regex en a besoin pour reconnaître et ignorer le texte variable. Par exemple, dans

(?:abc)*(capture me)(?:bcd)*

vous en avez besoin si votre entrée peut ressembler à "abcabc capture me bcdbcd" ou "abc capture me bcd" ou même simplement "capture me".

Ou pour le dire autrement: si le texte est toujours le même et que vous ne le capturez pas, il n'y a aucune raison d'utiliser des groupes.

Yaro
la source
1
Les groupes non capturants sont inutiles; \d(.*)\dsuffira.
shmosel
1
Je ne comprends pas $11ici. Pourquoi 11?
Alexis
1
@Alexis - Ceci est une bizarrerie java regex: si le groupe 11 n'a pas été défini, java interprète $ 11 comme $ 1 suivi de 1.
Yaro
9

Ajoutez un troisième groupe en ajoutant des parens autour .*, puis remplacez la sous-séquence par "number" + m.group(2) + "1". par exemple:

String output = m.replaceFirst("number" + m.group(2) + "1");
mkb
la source
4
En fait, Matcher supporte le style de référence $ 2, donc m.replaceFirst ("number $ 21") ferait la même chose.
Michael Myers
En fait, ils ne font pas la même chose. "number$21"fonctionne et "number" + m.group(2) + "1"ne fonctionne pas.
Alan Moore
2
Il semble number$21remplacer le groupe 21, pas le groupe 2 + la chaîne «1».
Fernando M. Pinheiro
C'est une concaténation de chaînes simples, non? pourquoi devons-nous appeler replaceFirst?
Zxcv Mnb
2

Vous pouvez utiliser les méthodes matcher.start () et matcher.end () pour obtenir les positions des groupes. Ainsi, en utilisant ces positions, vous pouvez facilement remplacer n'importe quel texte.

ydanneg
la source
1

remplacez les champs de mot de passe de l'entrée:

{"_csrf":["9d90c85f-ac73-4b15-ad08-ebaa3fa4a005"],"originPassword":["uaas"],"newPassword":["uaas"],"confirmPassword":["uaas"]}



  private static final Pattern PATTERN = Pattern.compile(".*?password.*?\":\\[\"(.*?)\"\\](,\"|}$)", Pattern.CASE_INSENSITIVE);

  private static String replacePassword(String input, String replacement) {
    Matcher m = PATTERN.matcher(input);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
      Matcher m2 = PATTERN.matcher(m.group(0));
      if (m2.find()) {
        StringBuilder stringBuilder = new StringBuilder(m2.group(0));
        String result = stringBuilder.replace(m2.start(1), m2.end(1), replacement).toString();
        m.appendReplacement(sb, result);
      }
    }
    m.appendTail(sb);
    return sb.toString();
  }

  @Test
  public void test1() {
    String input = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"123\"],\"newPassword\":[\"456\"],\"confirmPassword\":[\"456\"]}";
    String expected = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"**\"],\"newPassword\":[\"**\"],\"confirmPassword\":[\"**\"]}";
    Assert.assertEquals(expected, replacePassword(input, "**"));
  }
fantaisie
la source
0

Voici une solution différente, qui permet également le remplacement d'un seul groupe en plusieurs matchs. Il utilise des piles pour inverser l'ordre d'exécution, de sorte que l'opération de chaîne puisse être exécutée en toute sécurité.

private static void demo () {

    final String sourceString = "hello world!";

    final String regex = "(hello) (world)(!)";
    final Pattern pattern = Pattern.compile(regex);

    String result = replaceTextOfMatchGroup(sourceString, pattern, 2, world -> world.toUpperCase());
    System.out.println(result);  // output: hello WORLD!
}

public static String replaceTextOfMatchGroup(String sourceString, Pattern pattern, int groupToReplace, Function<String,String> replaceStrategy) {
    Stack<Integer> startPositions = new Stack<>();
    Stack<Integer> endPositions = new Stack<>();
    Matcher matcher = pattern.matcher(sourceString);

    while (matcher.find()) {
        startPositions.push(matcher.start(groupToReplace));
        endPositions.push(matcher.end(groupToReplace));
    }
    StringBuilder sb = new StringBuilder(sourceString);
    while (! startPositions.isEmpty()) {
        int start = startPositions.pop();
        int end = endPositions.pop();
        if (start >= 0 && end >= 0) {
            sb.replace(start, end, replaceStrategy.apply(sourceString.substring(start, end)));
        }
    }
    return sb.toString();       
}
Jonas_Hess
la source