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

MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 - Modifié le 20 janv. 2019 à 14:48
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 - 23 janv. 2019 à 21:02
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 ?

14 réponses

Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
20 janv. 2019 à 16:28
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
20 janv. 2019 à 19:59
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.
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
21 janv. 2019 à 13:40
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.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
21 janv. 2019 à 16:35
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
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
Modifié le 21 janv. 2019 à 16:44
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.
0
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
21 janv. 2019 à 18:40
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.
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
21 janv. 2019 à 19:48
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 ?
0
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
21 janv. 2019 à 21:16
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.
0
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
21 janv. 2019 à 21:53
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.
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
22 janv. 2019 à 09:53
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.

@+
0
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
22 janv. 2019 à 13:26
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.
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
22 janv. 2019 à 15:53
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é.

@+
0
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
22 janv. 2019 à 16:24
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?
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
22 janv. 2019 à 16:50
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.

@+
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
Modifié le 23 janv. 2019 à 14:22
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;
        }
0
Whismeril Messages postés 19028 Date d'inscription mardi 11 mars 2003 Statut Non membre Dernière intervention 24 avril 2024 656
23 janv. 2019 à 21:02
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'*
0
Rejoignez-nous