[Java] Pattern, (non-)capturing groupes

Fermé
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 - 1 oct. 2011 à 16:35
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 - 3 oct. 2011 à 00:14
Bonjour,

J'essaye de faire du pattern matching pour parser Method.toGenericString() mais j'ai un problème pour récupérer les informations que je veux à cause de l'imbrications de groupes (X) et (?:Y).

Pour l'instant je voudrais récupérer les noms de classes passées en paramètres de la méthode.
Exemple, si j'ai "(int,float,java.lang.String)", je veux récuperer "int", "float" et "java.lang.String".
Ma fonction de test m'indique que mes exemples sont bien matché, mais elle ne récupère pas les groupes que je souhaite capturer...

// Indique si l'exemple est bien matché, et affiche les groupes récupérés.
private static void test(int id,String pattern,String exemple)
{
	System.out.println("Test "+id+" "+exemple.matches(pattern));
		
	CharSequence cs = exemple.subSequence(0, exemple.length());
	String[] tab = Pattern.compile(pattern).split(cs);
		
	for (int i=0; i<tab.length; i++)
		System.out.println("\t"+tab[i]);
}

// Définit les pattern et fait quelques tests
private static void essai()
{	
	String modifier = "(?:[<]?[\\w]+[>]?[\\s]+)*";
		test(11,modifier,"public abstract <E> ");
	
	String pack = "(?:[\\w]+[\\.]+)*";
		test(21,pack,"java.util.");
	
	String classe = "(?:"+pack+"[\\w]+"+")";
		test(31,classe,"java.util.Collection");
	
	String type = classe+"(?:<(?:[\\?][\\s]+extends[\\s]+)?"+classe+">)?(?:\\[\\])*";
		test(41,type,"int");
		test(42,type,"java.lang.String[][]");
		test(43,type,"java.util.Collection<E>");
		test(44,type,"java.util.Collection<? extends E>[]");
	
	String param = "\\(("+type+")?((?:[\\s]*\\,[\\s]*)("+type+"))*\\)";
		test(51,param,"()");
		test(52,param,"(int)");
		test(53,param,"(float,double)");
}

Pour le test 52 je voudrait capturer "int" et pour le 53 "float" et "double" or je ne capture rien !
Je pense que le soucis vient qu'au final je me retrouve avec une imbrication du type ((?:X)) où je ne veux pas capturer le groupe le plus interne mais uniquement le groupe le plus externe...

Si quelqu'un a une idée, je l'en remercie d'avance ;-)
A voir également:

3 réponses

KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
1 oct. 2011 à 21:33
J'ai un petit peu avancé dans mon problème, mais rien encore de vraiment concluant...

Lorsque j'utilise Matcher, le groupCount est toujours identique pour un Pattern donné, et ce indépendamment de la chaîne de caractères parsé. Or j'utilises des quantificateurs ? + et * mais ceux-ci ne semblent pas agir....
Ainsi dans mes tests 5 avec "(X)?(?:(?:Y)(Z))*", j'aurai toujours deux résultats, correspondant à X et Z.
Cependant si le groupe n'est pas matché (pour X ou Z) j'obtient null, et s'il est matché plusieurs fois (pour Z) la valeur du groupe est écrasée au fur et à mesure pour ne conserver que la dernière !

Bref, ça ne marche pas, et j'aurais bien besoin d'un petit coup de main si quelqu'un s'y connaît ;-)

private static void test(int id,String regex,String exemple)
{
	System.out.println("Test "+id+" "+exemple.matches(regex));
	
	Matcher matcher = Pattern.compile(regex).matcher(exemple);
	matcher.find();
    
	for (int i=1, n=matcher.groupCount(); i<=n; i++)
		System.out.println("\t"+matcher.group(i));
}

private static void essai()
{	
	String modifier = "(?:(?:[\\<]?[\\w]+[\\>]?[\\s]+)*)";
		test(11,modifier,"public abstract <E> ");
	
	String pack = "(?:(?:[\\w]+[\\.]+)*)";
		test(21,pack,"java.util.");
	
	String classe = "(?:"+pack+"[\\w]+"+")";
		test(31,classe,"java.util.Collection");
	
	String type = "(?:"+classe+"(?:[\\<](?:[\\?][\\s]+extends[\\s]+)?"+classe+"[\\>])?(?:\\[\\])*)";
		test(41,type,"int");
		test(42,type,"java.lang.String[][]");
		test(43,type,"java.util.Collection<E>");
		test(44,type,"java.util.Collection<? extends E>[]");
	
	String param = "\\(("+type+")?(?:(?:[\\s]*\\,[\\s]*)("+type+"))*\\)";
		test(51,param,"()");
		test(52,param,"(int)");
		test(53,param,"(float,double)");
		test(54,param,"(char,byte,short)");
}
0
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 130
2 oct. 2011 à 00:56
salut KX

Je ne comprends le problème tel que tu l'exposes
Il faut dire que je ne connais pas Java
Pourrais tu réduire ton problème à sa plus simple expression: plusieurs chaînes, et dire ce que tu veux en extraire avec une regex.
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
2 oct. 2011 à 11:15
Merci heyquem de me répondre,

Ce que je veux c'est récupérer (entre autre) chaque types pris en paramètre d'une fonction.
Ces types peuvent être assez compliqués par exemple "java.util.Collection<? extends E>[]" mais pour simplifier on n'a qu'à dire que ce sont des mots \w+ et je veux les capturer.
Les différents types sont séparés par des virgules avec éventuellement des espaces avant ou après, là encore simplifions en disant qu'il n'y a que des espaces \s+ mais que je ne veux pas capturer.

Exemples :
1) ""			--> {}
2) "int"		--> {"int"}
3) "float double"	--> {"float","double"}
4) "char byte short"	--> {"char","byte","short"}

En Java (j'ignore si c'est pareil dans les autres langages) quand on veut capturer un groupe X, on le note entre parenthèses (X) et quand on ne veut pas le capturer on fait (?:X)
Mon expression régulière commence par (\w+)? pour prendre 0 ou 1 fois les premieres lettres, c'est à dire sans aucun espace derrière (pour matcher 1 et 2)
Suivi de 0 ou plusieurs fois un bloc B que je ne capture pas (?:B)* avec B qui est la succession d'1 ou plusieurs espaces que je ne capture pas (?:\s+) et d'une ou plusieurs lettres que je capture (\w+)
Au final j'ai donc une expression régulière comme ceci: (\w+)?(?:(?:\s+)(\w+))*

Avec cette expression régulière mes quatre exemples sont bien matchés. Le problème c'est qu'au moment de récupérer les groupes avec les outils Java, je récupère toujours 2 groupes, correspondant à chacun des deux blocs que j'ai mis en gras dans l'expression régulière. Avec la valeur null lorsqu'on est dans le cas 0 fois (pour ? et *) et la dernière valeur lorsqu'on est dans le cas plusieurs fois (pour *)

C'est à dire que mes exemples donnent :
1) ""			--> {null,null}
2) "int"		--> {"int",null}
3) "float double"	--> {"float","double"}
4) "char byte short"	--> {"char","short"}


Et pour info, le code Java correspondant est celui-ci :

public static void main(String...args)
{	
	String regex = "(\\w+)?(?:(?:\\s+)(\\w+))*";

	simple(regex,"");	
	simple(regex,"int");	
	simple(regex,"float double");	
	simple(regex,"char byte short");		
}

private static void simple(String regex,String exemple)
{
	Matcher matcher = Pattern.compile(regex).matcher(exemple);
	matcher.find();
    
	System.out.print(exemple+"\t{");
    
	for (int i=1, n=matcher.groupCount(); i<=n; i++)
		System.out.print(matcher.group(i)+" ");
    
	System.out.println("}");
}

Je ne sais pas si c'est vraiment plus simple à comprendre comme ceci, parce que même en simplifiant ça reste quand même assez compliqué... Sinon j'y serais surement arrivé moi même mais là je bloque :(

Pour information, je suis "obligé" d'utiliser le langage Java, puisque c'est pour permettre de parser le nom des méthodes, c'est à dire retrouver une méthode Java existante en lui donnant la chaîne de caractères correspondante.
Exemple : je tape au clavier "public void java.io.PrintStream.println(char[])" et le programme me permet de retrouver la méthode spécifié et ainsi l'invoquer dynamiquement...
0
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 130
Modifié par heyquem le 2/10/2011 à 19:03
Je ne vais pas répondre à ta fausse question , mais à la vraie.
Je veux dire que ta question relève à mon avis du problème XY:
https://meta.stackexchange.com/questions/56366/can-we-get-people-to-directly-ask-about-their-problems-instead-of-topics-they-thi

Je crois en effet que tu t'es compliqué la vie
1/ en te persuadant qu'il te fallait définir deux groupes
2/ en essayant de capturer une succession des groupes dans la chaîne analysée avec le deuxième groupe de la regex quantifié avec *
Mais tu es ainsi parti sur une fausse piste

Pensant avoir compris ce que tu veux, je préfère raisonner sur le problème réel, plutôt que de considérer les deux simplifications que tu proposes (des mots \w+ à la place des types compliqués, et des blancs à la place de virgule+blancs).

En Python, cela donne ce qui suit. Excuse moi, mais je ne connais que Python


findall(x) et [ mat.group(1) for mat in regx.finditer(x)] ont le même résultat; c'est voulu, dans mon code: si on ne veut que la liste des groupes capturés, on utilise findall(); si on veut faire des traitements sur les groupes capturés, on y accède en itérant progressivement avec finditer()

regx = re.compile('(?:\A|,) *([^,]+?)(?:(?<! ) *?(?=,|\Z))') 


for chaine_de_parametres in ("","   ", 
          "int","   int","int   ","   int   ", 
          "float,double", 
          "float,  double","float  ,double","float  ,   double", 
          "  float,double","   float,  double","   float  ,double","   float  ,   double", 
          "float,double   ","float,  double   ","float  ,double   ","float  ,   double   ", 
          "  float,double   ","  float,  double   "," float  ,double    ","  float  ,   double   ", 
          "char,byte,short", 
          "char  ,byte,short","char,   byte,short","char  ,  byte,short", 

          "char,byte  ,short","char,byte,  short","char,byte  ,  short", 
          "char  ,byte,  short","char  ,byte  ,short", 
          "char, byte , short"," char , byte,  short", 
          "  char, byte,short  ","  char,  byte  ,short  " ,"   char  ,  byte,  short  ", 
          "char,  byte  ,  short  ","   char   ,byte,  short  ","char  , byte,  short", 
          "java.util.Collection<? extends E>[], char ,  byte", 
          "    int,  java.util.Collection<? extends E>[], char   byte"): 
    print 'chaine_de_parametres  == ', repr(chaine_de_parametres) 
    print regx.findall(chaine_de_parametres) 
    print [ mat.group(1) for mat in regx.finditer(chaine_de_parametres)] 
    print


Le résultat est :

chaine_de_parametres  ==  '' 
[] 
[] 

chaine_de_parametres  ==  '   ' 
[] 
[] 

chaine_de_parametres  ==  'int' 
['int'] 
['int'] 

chaine_de_parametres  ==  '   int' 
['int'] 
['int'] 

chaine_de_parametres  ==  'int   ' 
['int'] 
['int'] 

chaine_de_parametres  ==  '   int   ' 
['int'] 
['int'] 

chaine_de_parametres  ==  'float,double' 
['float', 'double'] 
['float', 'double'] 

chaine_de_parametres  ==  'float,  double' 
['float', 'double'] 
['float', 'double'] 

chaine_de_parametres  ==  'float  ,double' 
['float', 'double'] 
['float', 'double'] 

chaine_de_parametres  ==  'float  ,   double' 
['float', 'double'] 
['float', 'double'] 

.... etc 

chaine_de_parametres  ==  '  float,double   ' 
['float', 'double'] 
['float', 'double'] 

chaine_de_parametres  ==  '  float,  double   ' 
['float', 'double'] 
['float', 'double'] 

chaine_de_parametres  ==  ' float  ,double    ' 
['float', 'double'] 
['float', 'double'] 

chaine_de_parametres  ==  '  float  ,   double   ' 
['float', 'double'] 
['float', 'double'] 

chaine_de_parametres  ==  'char,byte,short' 
['char', 'byte', 'short'] 
['char', 'byte', 'short'] 

chaine_de_parametres  ==  'char  ,byte,short' 
['char', 'byte', 'short'] 
['char', 'byte', 'short'] 

chaine_de_parametres  ==  'char,   byte,short' 
['char', 'byte', 'short'] 
['char', 'byte', 'short'] 

....... etc 

chaine_de_parametres  ==  'char,  byte  ,  short  ' 
['char', 'byte', 'short'] 
['char', 'byte', 'short'] 

chaine_de_parametres  ==  '   char   ,byte,  short  ' 
['char', 'byte', 'short'] 
['char', 'byte', 'short'] 

chaine_de_parametres  ==  'char  , byte,  short' 
['char', 'byte', 'short'] 
['char', 'byte', 'short'] 

chaine_de_parametres  ==  'java.util.Collection<? extends E>[], char ,  byte' 
['java.util.Collection<? extends E>[]', 'char', 'byte'] 
['java.util.Collection<? extends E>[]', 'char', 'byte'] 

chaine_de_parametres  ==  '    int,  java.util.Collection<? extends E>[], char   byte' 
['int', 'java.util.Collection<? extends E>[]', 'char   byte'] 
['int', 'java.util.Collection<? extends E>[]', 'char   byte']



La regex située plus haut extrait les types même si la liste de paramètres est incorrecte:

par exemple
 '  ,  , char , byte,  short' 
doit être incorrecte, je pense, mais la regex parvient à extraire les types.
On pourra améliorer la regex si tu veux la rendre telle qu'elle puisse aussi valider la chaîne de paramètres
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
2 oct. 2011 à 19:15
Je ne connais pas Python et pour le coup c'est moi qui ne comprends pas ^^
Je crois que ce que tu fais c'est simplement utiliser la virgule comme délimiteur de chaque champ, tu découpes à la virgule et tu prends les groupes les uns après les autres.
Le problème c'est que dans mes types je peux avoir des virgules aussi, par exemple "Map<K,V>"
Et que si je dois parser "Map<K,V>" je pense que tes regex vont renvoyer "Map<K" et "V>"
0
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 130
2 oct. 2011 à 19:42
Ne pas comprendre Python ? oh oh oh, ah ah ah , je ne le crois pas

L'essentiel est de toutes façons le pattern de la regex:

'(?:\A|,) *([^,]+?)(?:(?<! ) *?(?=,|\Z))'


"Je crois que ce que tu fais c'est simplement utiliser la virgule comme délimiteur de chaque champ"
Tout à fait.

Mais comme je me doutais bien que le principe de délimiter selon la virgule risquait d'être insuffisant, je n'ai pas utilisé un simple splitting, mais une regex qu'il faut donc maintenant améliorer, tout simplement.

S'il peut y avoir des virgules au sein des types, comment Java distingue-t-il si une virgule est à l'intérieur d'un type ou une séparatrice de types ?
J'ai jeté un coup d'oeil sur la page de Method.toGenericString() que tu as indiquée, mais je n'y vois pas de types avec des virgules internes.
Pourrais tu donner plusieurs chaînes de paramètres, de façon à ce que je saisisse le principe de leur écriture.
Est-ce que les types sont toujours entourés de double quotes ?
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
Modifié par KX le 2/10/2011 à 19:59
En fait quand il y a des virgules elles sont entourées par des <>
Les types ne sont pas entourés par les guillemets, mais comme je manipule des chaînes de caractères je les mets.

Par exemple toGenericString() donne des choses de cette forme :
public void java.util.AbstractMap.putAll(java.util.Map.<? extends K,? extends V>)

Le problème c'est qu'en construisant ses propres méthodes ça peut se complexifier :
public java.util.Map<? extends K,? extends W> fusion(java.util.Map<java.util.Map<? extends K,? extends V>,java.util.Map<? extends V,? extends W>>)


Autre comportement particulier, il y a les [] qui peuvent se rajouter à n'importe quel type mais il ne sont pas trop gênants.
0
heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 130
2 oct. 2011 à 20:05
public java.util.Map<? extends K,? extends W> fusion


c'est la déclaration et le nom de la fonction ?
est ce que ça se trouve dans la chaîne qui est soumise à l'examen par la regex ?
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
Modifié par KX le 2/10/2011 à 20:08
Au final oui, je passerai tout à parser, mais cette partie là n'est pas très compliquée parce que j'ai un nombre fixe de groupes à récupérer, donc je n'ai pas de problème.
0