Exptressions régulières : NOT(expression complexe)

Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
-
Bonjour,
Je pensais maitriser assez bien les expressions régulières, mais je me retrouve devant un cas que je n'arrive pas à résoudre.

Il s'agit de détecter la non-présence de mots dans un chemin.
Je m'explique :
J'ai des chemins du type :
- X:\machin\truc\.git\.bidule
- X:\machin\truc\.vs\etc
(Les utilisateurs de Visual Studio reconnaitront quelque chose...)
Je sais filtrer la présence de ces chemins par Regex, avec l'expression:
(\\\.git\\)|(\\\.vs\\)

Mais je n'arrive pas à trouver la fonction inverse, qui me permettrait d'éliminer ces chemins.
Je précise qu'il n'est pas question d'utiliser !Rx.IsMatch(), car le filtre doit pouvoir aussi passer des chemins à inclure (comme par exemple "\.cs$").

J'ai essayé de mettre un accent circonflexe après chaque parenthèse ouverte comme pour [^a-z], mais ça ne fonctionne pas, car je pense que dans ce cas l'accent circonflexe est interprété comme l'ancre de début de chaine.
(^\\\.git\\)&(^\\\.vs\\)
ne fonctionne pas.

Quelqu'un a une proposition ?
Afficher la suite 

Votre réponse

14 réponses

Commenter la réponse de Whismeril
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
0
Merci
Merci pour le lien sur cet outil. J'ai le même (en plus simple et moins bien présenté) sur un de mes sites perso.
Cependant, l'expression régulière ne répond pas complètement au besoin.
En effet, elle filtre bien "\.git\" mais aussi "\.gitignore" ce qui n'est pas le but recherché.

Cependant, cela m'a fait découvrir la clause "?!" que je ne connaissais pas (ainsi que toutes celles commençant par ?).
Je vais me pencher là-dessus car je pense que c'est le fameux "NOT" que je cherchais.

Merci.
Commenter la réponse de MGD Software
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
0
Merci
Effectivement, ça fonctionne.
Le problème, c'est que cette expression régulière est à renseigner dans un champ de saisie, dans un logiciel destiné au grand public.
J'avais déjà des réticences à utiliser les expressions régulières, bien qu'ayant prodigué une aide en ligne pour les former, mais là, c'est carrément trop complexe. Même moi je ,'arrive pas à la modifier sans que plus rien ne sorte, par exemple pour ajouter une autre chaine à rechercher, en inclusion ou en exclusion.

En fait, je n'ai pas besoin d'extraire des données, mais simplement avoir le résultat positif du IsMatch si la chaine respecte le pattern.

Par exemple (et c'est vraiment un exemple, j'ignore ce que voudra filtrer l'utilisateur), je voudrais avoir un résultat positif si le nom du fichier contient ".cs" ou ".res" à la fin, mais pas si le chemin contient "\.git\" ou "\.vs\". Le nombre de clauses d'inclusion ou d'exclusion ne doit pas être limitatif.

Je conviens que c'est tordu, mais c'est un de mes besoins personnels avec ce logiciel.

En SQL, cela donnerait :
WHERE (chaine LIKE '%cs' OR chaine LIKE '%.res') AND NOT (chaine LIKE '%\.git\\%' OR chaine LIKE '%\.vs\\%')


S'il n'est pas possible d'obtenir une expression régulière relativement simple, je vais procéder autrement, par exemple avec plusieurs critères différents qui ne soient pas des expressions régulières, comme je fais ailleurs dans un formulaire de recherche multicritère. Je pense qu'en C# on peut filtrer une chaine comme en SQL sans doute avec une expression lamba, mais je n'en ai pas encore eu l'occasion. Je vais chercher.
Commenter la réponse de MGD Software
Messages postés
12724
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
15 février 2019
332
0
Merci
Faut le faire avec Linq.
Tu laisse à l’utisateur un champ (ou une liste) « contient » , un champ « ne contient pas », un champ « finit par », etc et tu construis ta requête à partir de ça.

J’essaye de te faire un exemple dans la soirée
Commenter la réponse de Whismeril
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
0
Merci
Bon, je viens de le faire avec deux boucles foreach sur chaque partie du filtre, une pour l'inclusion, l'autre pour l'exclusion. Je m'interroge actuellement sur laquelle doit avoir la priorité sur l'autre.
Mais ça m'intéresse de voir comment on fait avec Linq, que je maitrise assez mal.
J'ai trouvé des posts où on utilisait WHERE plus des clauses first, etc.
Linq est puissant mais complexe, j'y vais mollo...
Mon bouquin de 600 pages sur le C# n'en consacre que 20 à Linq.
Autant dire que ça ne creuse pas trop profond.
Commenter la réponse de MGD Software
Messages postés
12724
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
15 février 2019
332
0
Merci
Mon exemple est en fait un mélange de boucles et de linq (qui lui aussi fait des boucles)

        List<string> contient = new List<string> { "test2", "a" };
        List<string> neContientPas = new List<string> { "git\\"};
        List<string> finitPar = new List<string> { ".cs", ".resx" };

        private void button1_Click(object sender, EventArgs e)
        {
            string dossierDepart = System.IO.Path.GetDirectoryName(Application.StartupPath);

            List<string> fichiers = (from f in Directory.GetFiles(dossierDepart)
                                     where Recherche(f)
                                     select f
                                     ).ToList();

        }

        private bool Recherche(string Texte)
        {
            //on commence par exclure les "ne contient pas"
            foreach (string aExclure in neContientPas)
                if (Texte.Contains(aExclure))
                    return false;

            //on teste ensuite que la chaine contient au moins un élément de "contient"
            bool ok = false;
            foreach(string aTrouver in contient)
                if(Texte.Contains(aTrouver))
                {
                    ok = true;
                    break;//on sort du foreach
                }

            if (!ok) return false;

            //idem pour les fins de chaines
            foreach (string fin in finitPar)
                if (Texte.EndsWith(fin))
                    return true;

            return false;
        }


En l'état c'est sensible à la casse et aux diacritiques (accents, cédilles, etc...) mais ça peut se gérer.
MGD Software
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
-
C'est exactement ce que j'ai fait !
A part que je n'ai développé que l'équivalent la fonction Recherche(), que j'appelle pour chaque fichier et/ou répertoire.
Comme j'en ai besoin à plusieurs endroits au fur et à mesure des traitements, je n'ai pas à utiliser Linq comme dans fonction button1_Click.
Par contre la démo de Linq m'ouvre des horizons.
question subsidiaire : Si on ne met pas .toList() après l'expression Linq, qu'est-ce qui est renvoyé ? Un tableau de strings ?
Commenter la réponse de Whismeril
Messages postés
12724
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
15 février 2019
332
0
Merci
Non, pour avoir un tableau, il faut ToArray().

Ça renvoie un IEnumerable<string>, mais surtout ça laisse l'exécution différée.
http://sdz.tdct.org/sdz/apprenez-a-developper-en-c.html#Excutiondiffre

C'est à la fois une très bonne chose de Linq mais aussi son plus gros piège.
Donc tant que tu tâtonnes encore avec cette techno, je te conseille de forcer l'exécution immédiate.
Commenter la réponse de Whismeril
Messages postés
12724
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
15 février 2019
332
0
Merci
La même 100% linq
        List<string> contient = new List<string> { "test2", "a" };
        List<string> neContientPas = new List<string> { "git\\", ".vs\\" };
        List<string> finitPar = new List<string> { ".cs", ".resx" };

        private void button1_Click(object sender, EventArgs e)
        {
            string dossierDepart = Path.GetDirectoryName(Path.GetDirectoryName(Application.StartupPath));

            List<string> fichiers = (from f in Directory.GetFiles(dossierDepart)
                                     where !neContientPas.Any(x => f.Contains(x)) && contient.Any(x => f.Contains(x)) && finitPar.Any(x => f.EndsWith(x))
                                     select f
                                     ).ToList();
        }


Any retourne vrai, si au moins un élément de la collection valide la condition.
Il convient donc à "contient" et "finit par". Pour "ne contient pas " il faudrait None(), mais il n'existe pas, or aucun = au moins un barre -> !Any.

Ce code est le précédent ne marche pas si contient ou finitpar est vide.
Voici la correction en Linq, je te laisse faire celle en boucle.

        List<string> contient = new List<string>(); //{ "test2", "a" };
        List<string> neContientPas = new List<string> { "git\\", ".vs\\" };
        List<string> finitPar = new List<string>(); // { ".cs", ".resx" };

        private void button1_Click(object sender, EventArgs e)
        {
            string dossierDepart = Path.GetDirectoryName(Path.GetDirectoryName(Application.StartupPath));

            List<string> fichiers = (from f in Directory.GetFiles(dossierDepart)
                                     where !neContientPas.Any(x => f.Contains(x)) && (contient.Count == 0 || contient.Any(x => f.Contains(x))) && (finitPar.Count == 0 ||finitPar.Any(x => f.EndsWith(x)))
                                     select f
                                     ).ToList();
        }



et si tu veux faire du récursif dans les sous répertoires
from f in Directory.GetFiles(dossierDepart,"*",SearchOption.AllDirectories)


GetFiles te permet de mettre un pattern de recherche, c'est la même syntaxe que la recherche de windows. Si tu ne veux mettre qu'un seul mot clé contenu, tu peux le mettre en pattern.

Enfin, il faudrait peut-être s'affranchir de la casse (en mettant tout en minuscule ou tout en majuscule) et des diacritiques LePivert a poster le code qui les enlève en VB.Net, un petit coup de convertisseur et c'est bon.
Commenter la réponse de Whismeril
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
0
Merci
Merci pour toutes ces infos.

Concernant mes filtres, je vais en rester aux boucles. Comme Linq exécute aussi des boucles, je ne pense pas que ce soit très pénalisant au niveau des performances, mais c'est pour moi plus facile à maintenir.

En fait, j'ai fait un mix entre les deux solutions : au lieu de Contains(), j'utilise quand même un Regex.IsMatch(), mais sur un tableau de petites expressions simples, chacune étant traitée dans les boucles, une pour inclusion, l'autre pour exclusion. Les expressions pour inclusion et exclusion étant bien sûr dans deux tableaux différents, l'inclusion étant prioritaire.

Ça fonctionne plutôt bien et reste à la portée d'un utilisateur moyen avec une bonne aide en ligne (je rappelle que les filtres sont saisis par l'utilisateur) et je pense en rester là. Je verrai plus tard en fonction des réactions des utilisateurs.

@+
Commenter la réponse de MGD Software
Messages postés
12724
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
15 février 2019
332
0
Merci
En terme d’accessibilité la regex, même basique, c’est pas le mieux.
Y’a des codeurs qui n’arrivent pas à écrire une regex, alors des «moldu »....

Une textbox et 2 boutons (un pour chaque filtre), 2 listbox ( pour visualiser) et l’utilisateur écrit le texte « en vrai » me parrait mieux.
Au pire tu autorises la même syntaxe que la recherche windows, certains connaissent un peu.
Commenter la réponse de Whismeril
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
0
Merci
C'est vrai... Après cette discussion, il semblerait que je fasse partie des presque-moldus.

J'ai déjà deux textbox multi-lignes, une pour les inclusions, l'autre pour les exclusions. Chaque ligne correspond à un groupe à traiter.
Effectivement si on n'utilise pas d'expressions régulières, l'utilisation de Contains() convient.

Mais je doute que cette fonction accepte les caractères génériques du type * et ? chers à Windows. Cela reviendrait à transformer à la volée le "*" en ".+" et le "?" en "." et utiliser une expression régulière.
Pour "*", je ne suis pas sûr que ça marche, cela risque d'avoir des effets de bord, d'autant qu'il va falloir échapper tous les caractères spéciaux.
Mais la suggestion est bonne, je vais pour le moment me contenter de morceaux de texte sans caractère générique, ce qui devrait convenir dans la quasi-totalité des cas (on peut éventuellement multiplier les clauses pour couvrir la plupart des cas correspondant à un caractère générique)

Par ailleurs, j'ai beaucoup apprécié le lien vers la page http://sdz.tdct.org/sdz/apprenez-a-developper-en-c.html#Excutiondiffre, et bien que je connaisse beaucoup de notions qui y sont expliquées, j'y ai trouvé pas mal de choses qui ne sont pas dans mon bouquin.

Comme lien vers le livre est obsolète et conduit vers une page 404, je me suis permis de repiquer le texte entier dans Word, de le remettre entièrement en forme et de l'exporter en PDF. Cela donne un bouquin de 427 pages imprimable (formaté pour le recto-verso).
Pour ceux que ça intéresse, il est disponible au téléchargement à l'adresse http://mgd.software.free.fr/downloads/divers/Apprenez-a-developper-en-Csharp.pdf.

Ceci dit, je me replonge dans Visual Studio, mon programme n'attend plus que les filtres pour être terminé.

@+
Whismeril
Messages postés
12724
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
15 février 2019
332 -
Mais je doute que cette fonction accepte les caractères génériques du type * et ? chers à Windows. Cela reviendrait à transformer à la volée le "*" en ".+" et le "?" en "." et utiliser une expression régulière. </bloc> Oui mais c’est ton code qui génèrerait cette regex, pas l’utilisateur

<block>cela risque d'avoir des effets de bord
C’est sûr

Ceci dit, je me replonge dans Visual Studio, mon programme n'attend plus que les filtres pour être terminé.
On n,a pas traité la casse et les diacritiques, veux ru le faire?
Commenter la réponse de MGD Software
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
0
Merci
Pas pour cette application, il faut que le filtrage respecte EXACTEMENT les mots à filtrer.
Je veux qu'on puisse différencier C:\truc\leçon de C:\truc\lecon. Pour la casse, c'est très facile, mais nécessaire puisque Windows ne fait pas la distinction dans les noms de fichiers et répertoires. Je pense savoir faire.

Pour une recherche dans un texte, Gérer les diacritiques serait bien sûr nécessaire, mais lorsque j'en aurai besoin, je ferai d'abord une recherche sur Internet, et si besoin j'entamerai une autre discussion.

Merci pour la proposition, mais je décline pour l'instant.

@+
Commenter la réponse de MGD Software
Messages postés
114
Date d'inscription
vendredi 1 septembre 2006
Dernière intervention
28 janvier 2019
0
Merci
Je pense que j'ai trouvé pour convertir les caractères génériques de windows en expressions régulières :
https://stackoverflow.com/questions/41757762/use-sql-like-operator-in-c-sharp-linq/41758315

Je vais pouvoir filtrer beaucoup mieux.
En effet, la simple recherche d'un mot ne couvre pas tous les cas.
Par exemple, si je recherche en inclusion les fichiers ayant pour extension" .exe" en utilisant Contains(), je vais aussi inclure le répertoire X:\y\.exe\z

Alors qu'en transformant *.exe avec Linq, cela donnera avec les fonctions données dans le lien %.exe, ce qui équivaudra à l'expression régulière ".*\.exe$".
Pile-poil ce qui est voulu.

.... (Le lendemain - J'avais oublié d'envoyer le post !)
Finalement, je n'ai pas utilisé Linq, mais les expressions régulières, en traitant les caractères génériques de Windows. On arrive à gérer la position du pattern dans le sujet grâce à un "*" en début ou fin de pattern. Je n'ai pas testé absolument tous les cas (il n'y en a en fait pas beaucoup avec le peu de caractères génériques de Windows), mais ça semble bien fonctionner.
"*xyz*" trouve xyz n'importe où dans le sujet
"xyz*" ne trouve xyz que si c'est au début du sujet
"*xyz" ne trouve xyz que si c'est à la fin du sujet
Les points, antislash et autres caractères spéciaux de Regex sont traités.

Pour ceux que ça pourrait intéresser, voici le code de la fonction d'exclusion :
WinPatterns est un tableau de chaines de filtrage au format Windows : az*cd?q
        private static bool IsExcluded(string TargetPath, string[] WinPatterns)
        {
            foreach (string Excl in WinPatterns)
            {
                if (Excl == "")    // Pour traiter les chaines vides entrées par erreur par l'utilisateur
                    continue;
                string Pattern = "^" + Regex.Escape(Excl).Replace(@"\*", ".*").Replace(@"\?", ".") + "$";
                if (Regex.IsMatch(TargetPath, Pattern))
                    return true;
            }
            return false;
        }
Whismeril
Messages postés
12724
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
15 février 2019
332 -
Comme tu m'avais dit que tu ne voulais pas le faire, je ne te l'ai pas montré, mais oui c'est assez simple.
Par contre, ça m'étonne que tu aies besoin d'échapper le ? et l'*
Commenter la réponse de MGD Software

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.