Obtenir les noms de groupes dans java regex

J'essaie de recevoir à la fois un motif et une chaîne et de renvoyer une carte de nom de groupe - > résultat correspondant.

Exemple:

(?<user>.*)

Je voudrais retourner pour une carte contenant "user" comme clé et tout ce qu'elle correspond comme valeur.

Le problème est que je n'arrive pas à obtenir le nom du groupe à partir de L'api Java regex. Je ne peux obtenir les valeurs correspondantes que par nom ou par index. Je n'ai pas la liste des noms de groupe et ni Pattern ni Matcher ne semblent exposer cette information. J'ai vérifié sa source et il semble que l'information est là-il est tout simplement pas exposé à l'utilisateur.

J'ai essayé les deux java de Java.util.regex et jregex. (et ne se soucient pas vraiment si quelqu'un a suggéré une autre bibliothèque qui est bonne, supportée et haute en termes de performance qui prend en charge cette fonctionnalité).

27
demandé sur Roy Reznik 2013-03-23 20:04:21

3 réponses

Il N'y a pas D'API en Java pour obtenir les noms des groupes de capture nommés. Je pense que c'est une fonctionnalité manquante.

La solution la plus simple consiste à choisir des groupes de capture nommés candidats à partir du modèle, puis à essayer d'accéder au groupe nommé à partir de la correspondance. En d'autres termes, vous ne connaissez pas les noms exacts des groupes de capture nommés, jusqu'à ce que vous branchez une chaîne qui correspond à l'ensemble du motif.

Le Pattern pour capturer les noms du groupe de capture nommé est \(\?<([a-zA-Z][a-zA-Z0-9]*)> (dérivé basé sur Pattern documentation de classe ).

(le moyen le plus difficile est d'implémenter un analyseur pour regex et d'obtenir les noms des groupes de capture).

Un exemple d'implémentation:

import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.MatchResult;

class RegexTester {

    public static void main(String args[]) {
        Scanner scanner = new Scanner(System.in);

        String regex = scanner.nextLine();
        StringBuilder input = new StringBuilder();
        while (scanner.hasNextLine()) {
            input.append(scanner.nextLine()).append('\n');
        }

        Set<String> namedGroups = getNamedGroupCandidates(regex);

        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(input);
        int groupCount = m.groupCount();

        int matchCount = 0;

        if (m.find()) {
            // Remove invalid groups
            Iterator<String> i = namedGroups.iterator();
            while (i.hasNext()) {
                try {
                    m.group(i.next());
                } catch (IllegalArgumentException e) {
                    i.remove();
                }
            }

            matchCount += 1;
            System.out.println("Match " + matchCount + ":");
            System.out.println("=" + m.group() + "=");
            System.out.println();
            printMatches(m, namedGroups);

            while (m.find()) {
                matchCount += 1;
                System.out.println("Match " + matchCount + ":");
                System.out.println("=" + m.group() + "=");
                System.out.println();
                printMatches(m, namedGroups);
            }
        }
    }

    private static void printMatches(Matcher matcher, Set<String> namedGroups) {
        for (String name: namedGroups) {
            String matchedString = matcher.group(name);
            if (matchedString != null) {
                System.out.println(name + "=" + matchedString + "=");
            } else {
                System.out.println(name + "_");
            }
        }

        System.out.println();

        for (int i = 1; i < matcher.groupCount(); i++) {
            String matchedString = matcher.group(i);
            if (matchedString != null) {
                System.out.println(i + "=" + matchedString + "=");
            } else {
                System.out.println(i + "_");
            }
        }

        System.out.println();
    }

    private static Set<String> getNamedGroupCandidates(String regex) {
        Set<String> namedGroups = new TreeSet<String>();

        Matcher m = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>").matcher(regex);

            while (m.find()) {
                namedGroups.add(m.group(1));
            }

            return namedGroups;
        }
    }
}

Il y a une mise en garde à cette implémentation, cependant. Il ne fonctionne actuellement pas avec regex dans Pattern.COMMENTS mode.

36
répondu nhahtdh 2015-12-17 02:45:45

C'est la deuxième approche facile du problème: nous appellerons la méthode non publique namedGroups() dans la classe Pattern pour obtenir un Map<String, Integer> qui mappe les noms de groupe aux numéros de groupe via API de réflexion Java. L'avantage de cette approche est que nous n'avons pas besoin d'une chaîne qui contient une correspondance avec l'expression rationnelle pour trouver les groupes nommés exacts.

personnellement, je pense que ce n'est pas vraiment un avantage, car il est inutile de connaître les groupes nommés d'une expression rationnelle où une correspondance avec le regex n'existe pas parmi les chaînes d'entrée.

Cependant, veuillez prendre note de les inconvénients:

  • cette approche peut ne pas s'appliquer si le code est exécuté dans un système avec des restrictions de sécurité pour refuser toute tentative d'accès à des méthodes non publiques (Pas de modificateur, méthodes protégées et privées).
  • le code est uniquement applicable à JRE d'Oracle ou OpenJDK.
  • le code peut également casser dans les futures versions, puisque nous appelons un non-public méthode.
  • Il peut également y avoir des performances de la fonction d'appel via la réflexion. (Dans ce cas, le hit de performance provient principalement de la surcharge de réflexion, car il ne se passe pas grand-chose dans la méthode namedGroups()). Je ne sais pas comment le hit de performance affecte la performance globale , alors veuillez faire des mesures sur votre système.

import java.util.Collections;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Pattern;

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

class RegexTester {
  public static void main(String args[]) {
    Scanner scanner = new Scanner(System.in);

    String regex = scanner.nextLine();
    // String regex = "(?<group>[a-z]*)[trick(?<nothing>ha)]\\Q(?<quoted>Q+E+)\\E(.*)(?<Another6group>\\w+)";
    Pattern p = Pattern.compile(regex);

    Map<String, Integer> namedGroups = null;
    try {
      namedGroups = getNamedGroups(p);
    } catch (Exception e) {
      // Just an example here. You need to handle the Exception properly
      e.printStackTrace();
    }

    System.out.println(namedGroups);
  }


  @SuppressWarnings("unchecked")
  private static Map<String, Integer> getNamedGroups(Pattern regex)
      throws NoSuchMethodException, SecurityException,
             IllegalAccessException, IllegalArgumentException,
             InvocationTargetException {

    Method namedGroupsMethod = Pattern.class.getDeclaredMethod("namedGroups");
    namedGroupsMethod.setAccessible(true);

    Map<String, Integer> namedGroups = null;
    namedGroups = (Map<String, Integer>) namedGroupsMethod.invoke(regex);

    if (namedGroups == null) {
      throw new InternalError();
    }

    return Collections.unmodifiableMap(namedGroups);
  }
}
16
répondu nhahtdh 2014-12-16 06:08:58

Vous voulez utiliser la petite bibliothèquename-regexp . C'est un wrapper mince autour de java.util.regex avec le support des groupes de capture nommés pour les utilisateurs Java 5 ou 6.

Exemple d'utilisation:

Pattern p = Pattern.compile("(?<user>.*)");
Matcher m = p.matcher("JohnDoe");
System.out.println(m.namedGroups()); // {user=JohnDoe}

Maven:

<dependency>
  <groupId>com.github.tony19</groupId>
  <artifactId>named-regexp</artifactId>
  <version>0.2.3</version>
</dependency>

Références:

2
répondu Stephan 2017-01-19 17:16:33