Recherche de chaine avec Regex [Résolu]

Signaler
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020
-
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020
-
Bonjour,

Je cherche à retrouver une séquence spécifique dans un texte.
Pour être plus précis, je recherche le nom des images référencées dans une page html.
Pour cela, j'utilise l'expression régulière suivante:
src="(.*\.gif|.*\.jp[e]?g|.*\.png)"


Cette expression fonctionne à peu près, avec les inconvénients majeurs ci-dessous:

Quand le texte cible comporte plusieurs fois de suite la chaine cherchée, le match me ramène l'ensemble des deux. En fait, la valeur renvoyée dans group[1].value commence bien juste après le premier 'src="' mais se termine au dernier guillemet de la N-ième chaine cherchée. Exemple :
Chaine cible :
<p class="TitrePage "> <img src="drapeau_myanmar.png" width="132" height="87" class="ImgL"> <img src="drapeau_boudhiste.png" width="136" height="89" class="ImgR"> Voyage au Myanmar<br>

me renvoie :
drapeau_myanmar.png" width="132" height="87" class="ImgL"> <img src="drapeau_boudhiste.png

alors que je devrais avoir deux groupes, l'un avec drapeau_myanmar.png et l'autre avec drapeau_boudhiste.png.

Autre exemple :
<p align="center"><img src="images/Pano.jpg" alt="Pano.jpg" width="600" height="381"></p>

me renvoie :
images/Pano.jpg" alt="Pano.jpg

au lieu de
images/Pano.jpg


Quelqu'un peut-il m'éclairer sur la modification de l'expression régulière pour obtenir le résultat souhaité ? Ci-dessous le code d'exploitation de l'expression régulière (lvwResults est un ListView affichant le résultat des recherches).

private void btnSearch_Click(object sender, EventArgs e)
{
    lvwResults.Items.Clear();

    string Rx = new Regex("src=\"(.*\\.gif|.*\\.jp[e]?g|.*\\.png)\"", RegexOptions.IgnoreCase);
    MatchCollection Matches = Rx.Matches(txtSource.Text);
    if (Matches.Count == 0)
    {
        lvwResults.Items.Add("Non trouvé.");
        return;
    }

    foreach (Match match in Matches)
        foreach (Group group in match.Groups)
        {
            if (group == match.Groups[0])
                continue;
            ListViewItem Item = new ListViewItem();
            Item.Text = group.Value;
            Item.SubItems.Add(group.Index.ToString());
            lvwResults.Items.Add(Item);
        }
}

7 réponses

Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020
426
Bonjour

Mais il va falloir que j'implémente tous les caractères autorisés dans une URL non codée pour que l'appli soit générique (ne serait-ce que les deux-points pour une URL absolue http://...)


Une alternative est de remplacer
[\w/-]*
par
.*?
qui signifie "n'importe quel caractère répété un nombre inconnu le plus petit possible"

Mais si tu as un caractère normalement interdit dans une URL ça sera capturé. Moi ça ne me plait pas mais c'est toi qui voit en fonction de ton besoin et de la fiabilité des données en entrée.
Regexstrom semble ne pas marcher ce matin, alors avec regex101
https://regex101.com/r/91sVYJ/1


Une autre alternative serait de faire tout sauf les caractères interdits, c'est p'tet plus court que d'ajouter tous les caractères autorisés
Là un exemple avec juste 4 caractères interdits
https://regex101.com/r/91sVYJ/2

Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020
426
Ha oui et j'oubliais dans l'alternative .*?, si tu as 2 images sur une lignes, dont la première est d'un format imprévu (par exemple bmp) et bien ça capture toute la ligne comme avant
https://regex101.com/r/91sVYJ/3
Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020
426
Bonjour

et si à la place de plusieurs groupes on faisait en sorte que les captures ne prennent que le chemin de l'image et qu'il y ait une capture par chemin?

(?<=src=")\w*(?:\.gif|\.jp[e]?g|\.png)


Un exemple utilisant le lien ci dessous
http://regexstorm.net/tester?p=%28%3f%3c%3dsrc%3d%22%29%5cw*%28%3f%3a%5c.gif%7c%5c.jp%5be%5d%3fg%7c%5c.png%29&i=%3cp+class%3d%22TitrePage+%22%3e+%3cimg+src%3d%22drapeau_myanmar.png%22+width%3d%22132%22+height%3d%2287%22+class%3d%22ImgL%22%3e+%3cimg+src%3d%22drapeau_boudhiste.png%22+width%3d%22136%22+height%3d%2289%22+class%3d%22ImgR%22%3e+Voyage+au+Myanmar%3cbr%3e+%3cimg+src%3d%22drapeau_boudhiste1.png%22+width%3d%22136%22+height%3d%2289%22+class%3d%22ImgR%22%3e+Voyage+au+Myanmar%3cbr%3e

Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020
426
Encore un peu plus simple
(?<=src=")\w*\.(?:gif|jp[e]?g|png)
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020

Bonjour Whismeril.
Toujours à l'écoute. Cela faisait un moment qu'on n'avait pas discuté.

J'ai failli crier de joie... jusqu'à ce que j'applique la formule magique à mon appli C# : je n'obtenais aucun résultat (:-(

Après une étude en pas à pas, j'ai découvert un truc curieux: alors qu'avec mon expression initiale chaque match avait 2 groupes ([0] = le match entier, [1] = capture), avec cette nouvelle expression chaque match n'a plus qu'un seul groupe qui contient la capture.

J'avoue ne pas comprendre du tout ce changement de fonctionnement.
Je ne comprends pas non plus le début de l'expression.
En particulier, je ne comprends pas la signification des caractères ?<=
Pour moi, le point d'interrogation signifie "Une ou zéro fois le caractère précédent". Or il n'y en pas...
Ensuite, à quoi correspond le "<=" ? A ma connaissance, ce ne sont pas des caractères spéciaux. Il faudrait donc que le mot src soit précédé des ces deux caractères. Or ils ne le sont pas, et l'expression fonctionne. Grand mystère pour moi !
Ensuite, la séquence ?<=src=" est placée entre parenthèses. Elle devrait donc sortir dans les captures, ce qui n'est pas le cas. Quid??
Enfin, je ne comprends pas la séquence ?: placée dans la seconde capture.

J'ai encore pas mal de progrès à faire concernant les expressions régulières...

Cependant, la requête ne fonctionne pas dans tous les cas. Exemple : Dans le texte
          <p><img src="maquette_kuthawdaw.png" width="907"></p>
          <p class="centered"><img src="photos/2019-11-28/large/20191128_095919_GM.jpg" width="49%"> 
          <img src="photos/2019-11-28/large/20191128_100050_GM.jpg" width="49%"></p>
          <p class="centered"><img src="photos/2019-11-28/large/20191128_101710_GM.jpg" width="49%"> 
          <img src="photos/2019-11-28/large/20191128_101541_GM.jpg" width="49%"></p>

il ne sort que maquette_kuthawdaw.png. Et cela ne vient pas du C#, le site regexstrom donne la même chose. A priori, l'expression ne supporte pas le chemin dans l'URL de l'image. Peut-être le \w* qui filtre (ce caractère générique ne comprend pas les slashes). J'ai tenté de le remplacer pas [\w/]* mais cela ne fonctionne pas.
Je pense qu'il va falloir encore gratter avant d'obtenir une expression qui couvre tous les cas. C'est bien de ne pas ramener de texte supplémentaire, mais il faudrait récupérer toutes les occurrences.

En tous cas, ma routine est singulièrement simplifiée :
MatchCollection Matches = Rx.Matches(txtSource.Text);
foreach (Match match in Matches)
{
    ListViewItem Item = new ListViewItem();
    Item.Text = match.Value;
    Item.SubItems.Add(match.Index.ToString());
    lvwResults.Items.Add(Item);
}


J'ai découvert par la même occasion le site regexstorm, qui peut s'avérer très utile. Si je l'avais connu plus tôt, je ne me serais pas échiné à faire une appli exprès pour tester les expressions régulières.

Merci pour le coup de main.



Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020
426
Après une étude en pas à pas, j'ai découvert un truc curieux: alors qu'avec mon expression initiale chaque match avait 2 groupes ([0] = le match entier, [1] = capture), avec cette nouvelle expression chaque match n'a plus qu'un seul groupe qui contient la capture.

Oui je l'ai fait exprès, je n'ai pas dû être assez clair
et si à la place de plusieurs groupes on faisait en sorte que les captures ne prennent que le chemin de l'image et qu'il y ait une capture par chemin?


J'ai tenté de le remplacer pas [\w/]* mais cela ne fonctionne pas.
oui, parce qu'il faut ajouter les -
(?<=src=")[\w/-]*\.(?:gif|jp[e]?g|png)

http://regexstorm.net/tester?p=%28%3f%3c%3dsrc%3d%22%29%5b%5cw%2f-%5d*%5c.%28%3f%3agif%7cjp%5be%5d%3fg%7cpng%29&i=++++++++++%3cp%3e%3cimg+src%3d%22maquette_kuthawdaw.png%22+width%3d%22907%22%3e%3c%2fp%3e%0d%0a++++++++++%3cp+class%3d%22centered%22%3e%3cimg+src%3d%22photos%2f2019-11-28%2flarge%2f20191128_095919_GM.jpg%22+width%3d%2249%25%22%3e+%0d%0a++++++++++%3cimg+src%3d%22photos%2f2019-11-28%2flarge%2f20191128_100050_GM.jpg%22+width%3d%2249%25%22%3e%3c%2fp%3e%0d%0a++++++++++%3cp+class%3d%22centered%22%3e%3cimg+src%3d%22photos%2f2019-11-28%2flarge%2f20191128_101710_GM.jpg%22+width%3d%2249%25%22%3e+%0d%0a++++++++++%3cimg+src%3d%22photos%2f2019-11-28%2flarge%2f20191128_101541_GM.jpg%22+width%3d%2249%25%22%3e%3c%2fp%3e


Regexstorm utilise le moteur de regex .Net, ce qui est un gros plus pour le C#, il existe plein d'autres sites pour tester les regex en fonction de ton langage, l'un des plus connus est regex101.

Sur regexstorm, dans le bandeau du haut, clique sur "Reference" et tu auras la description de chaque instruction

(?<=src=")
veut dire que scr=" doit être devant mais est exclus de la capture.


(?:truc)
veut dire que truc fait partie de la capture mais ne doit pas faire l'objet d'un groupe, ça permet de limiter la portée d'un ou par exemple.
azertyuiop|qsdfghjklm
veut dire azertyuiop ou qsdfghjklm
azertyuio(p|q)sdfghjklm
veut dire azertyuio(p ou q)sdfghjklm avec p ou q donne un groupe
azertyuio(?:p|q)sdfghjklm
veut dire azertyuio(p ou q)sdfghjklm avec p ou q ne donne pas un groupe
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020

Merci Whismeril

J'étais sur le bon chemin, j'avais oublié le "-".
Mais il va falloir que j'implémente tous les caractères autorisés dans une URL non codée pour que l'appli soit générique (ne serait-ce que les deux-points pour une URL absolue http://...)

Je n'avais jamais trouvé dans les documentations d'expressions régulières les clauses comme ?<= ou ?:
Où peut-on trouver une doc exhaustive, que je renforce mes connaissances?
Car si j'ai compris la première clause, ce n'est pas le cas de la seconde. si le contenu des secondes parenthèses ne doit par créer de groupe, pourquoi je les retrouve dans les matches sous le group[0]?

Bref, j'ai encore du boulot.
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020

PS : Depuis, j'ai trouvé une bonne doc sur le site de regexstorm.
Mais il va me falloir un peu de temps pour assimiler toutes les subtilités du "grouping construct".
Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020
426
Le groupe 0 c’est la capture complète
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020

Ça je le savais, c'est la raison des lignes de la première mouture de ma routine :
 if (group == match.Groups[0])
  continue;
pour ne pas traiter la capture complète.
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020

Cette dernière version semble fonctionner parfaitement (jusqu'à preuve du contraire sur un cas encore particulier).
Je vais donc l'utiliser.
Merci pour le coup de main.

Et pour les visiteurs qui auraient le même problème que moi, l'expression complète proposée par Whismeril est :
(?<=src=")[^<>'#]*\.(?:gif|jp[e]?g|png)

Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020
426
Ok mais il manque quand même un paquet de caractères interdits https://assiste.com/Caracteres_interdits_dans_les_URLs.html et en plus il faut gérer la spécificité du %
Messages postés
182
Date d'inscription
vendredi 1 septembre 2006
Statut
Membre
Dernière intervention
19 juillet 2020
>
Messages postés
14596
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
11 août 2020

il faut gérer la spécificité du %
C'est vrai, mais sur mes sites il est extrêmement rare que les images aient des caractères nécessitant un codage URI car c'est moi qui maitrise leur appellation. La gestion serait en effet nécessaire si l'on devrait traiter des URL.Mais cela peut se traiter en amont de la recherche par une fonction
Uri.UnescapeDataString().

En attendant, j'ai testé sur un de mes sites qui comporte plus de 18000 images (!) dont 90% de photos, et cela marche plutôt bien.
Cela me permet de vérifier les liens et surtout de trouver les fichiers qui ne sont pas référencés et donc de faire le ménage.
Bien sûr, l'appli ne sait pas détecter les liens dynamiques créés en PHP ou JavaScript.mais ceux-la sont dans des répertoires spécifiques et je peux les ignorer.