Bonjour
Envoyé par
renaud26
Je me permets de réouvrir ce post, car malgré les explications détaillées de Obsidian, je galère encore et toujours avec les regex
Dans ce cas, il est bon d'avoir sous le coude le point d'entrée de la documentation des PCRE : https://www.php.net/manual/fr/book.pcre.php
Mets-le dans un bookmark si ce n'est pas déjà fait, ainsi tu seras certain de retrouver rapidement les fonctions et les constantes associées.
Il y a aussi des puzzles en ligne pour s'entraîner à en faire sur des cas vraiment tordus, dans tous les « sens » du terme :
http://alf.nu/RegexGolf
https://rampion.github.io/RegHex/
… Donc en gros tout ce qui est après le dernier tiret de la chaîne, y compris le tiret lui même.
Est ce possible en 1 seul passage qui supprimerait 236- et -0769503905082.html ?
Oui, c'est possible mais ce n'est pas forcément souhaitable non plus. Deux ou trois expressions successives et bien formatées à base de strstr() ou strpos() sont parfois tout aussi claires dans le programme et consomment moins de ressources. Il n'y a aucun problème à utiliser des expressions régulières au 21ème siècle lorsqu'on les maîtrise bien, mais il faut se souvenir qu'elles nécessitent d'être compilées en interne avant usage quand même, donc elles peuvent devenir gourmandes quand on travaille à (très) grande échelle.
Tu serais également bien inspiré d'utiliser strtok à la place, avec $tokens = strtok($str, "-"); pour indiquer qu'il faut découper la chaîne en « jetons » (tokens) en considérant le tiret comme délimiteur. Quelque soit la fin de ta chaîne, il te suffirait de ne conserver que le deuxième jeton, avec $tokens[2]. Tu utiliserais ensuite (ou avant) str_replace pour éliminer l'éventuelle extension « .html », qu'elle existe ou pas.
J'ajoute, histoire de corser un peu la chose, que certaines chaines se terminent par abcdefg.html et dans ce cas, seul .html doit sauter.
Dans ce cas, les chiffres de fin de chaîne et l'extension « .html » doivent être deux motifs distincts.
Je précise que des chiffres peuvent se trouver dans la chaîne que je veux garder, par exemple abccde25gh....et que ceux-là ne doivent pas sauter
En fait, il faut déjà remarquer qu'à l'étape précédente, tu ciblais une sous-chaine au sein de ta chaîne principale, indépendamment du reste. Laquelle pouvait donc ensuite être remplacée par une chaîne fixe, en l'occurrence la chaîne vide « "" ». On n'avait donc pas à se soucier du reste. Il est tout-à-fait possible de faire la même chose « en deux fois » comme tu l'indiques, ce qui serait un bon exercice : cible d'abord la fin de ta chaîne comme on a ciblé le début ensemble, et ensuite essaie de réunifier les deux expressions.
Par contre, si tu veux le faire en une passe, il faut reconnaître « une seule expression » qui soit contiguë et qui intègre le début et la fin de ta chaîne, qu'il faut remplacer. Donc tu es obligé de cibler la chaîne entière.
Cela signifie également que tu veux transformer une chaîne du type « partie1-partie2-partie3 » en « ""-partie2-"" ». Et là, ça signifie que « partie2 » n'est pas une chaîne fixe. Elle doit être reprise du motif reconnu. Là, on sort complètement du formalisme mathématique et en principe, ça ne devrait plus concerner les expressions régulières en elles-mêmes mais, fort heureusement, il existe les « backrefs » ou « références arrières » qui ont été mises en place dès qu'on a commencé à utiliser ces expressions en informatique car elles se sont rapidement avérées nécessaires.
On a dit plus haut que tu pouvais encadrer une expression régulière entre parenthèses « ( ) », l'expression ainsi formée étant elle-même une expression régulière, donc soumise aux mêmes lois et pouvant admettre des opérateurs. On va en avoir besoin pour séparer les trois parties de la chaîne en question.
Il se trouve que les analyseurs les considèrent également comme des « groupes capturants » et certains pensent même que les parenthèses ne servent qu'à ça, alors qu'elles sont nécessaires pour reconnaître certains motifs. L'instruction preg_match_all permettant d'ailleurs de retrouver le motif de chaque groupe et même sous-groupe s'il y a des parenthèses imbriquées.
Pour faire référence au motif reconnu par un groupe, on utilise \1, \2, \3, etc. pour cibler respectivement le premier groupe, le deuxième, le troisième, etc. dans l'expression de remplacement. Il faut également penser à écrire cette expression entre apostrophes ( '\1' ) ou « échapper » le slash pour éviter qu'il soit lui-même considéré comme un caractère spécial : "\\1".
Soit ici :
preg_replace('^([0-9]*-)?([^.-]*)(-[0-9]*)?(\.html)?$', '\2', $str);
Ça devrait fonctionner. On remarque plusieurs choses :
- L'expression commence par ^ et finit par $. Elle est donc faite pour reconnaître la chaîne entière ou pas du tout.
- Entre les deux, ne se trouvent que des paires de parenthèses éventuellement suivies de ?, avec rien d'autre entre les deux. La chaîne doit donc être formée de ces quatre motifs dans cet ordre, mais trois de ces motifs sont optionnels ;
- Le premier groupe contient [0-9]*-, c'est-à-dire « n'importe quel nombre de chiffres de zéro à neuf, suivis d'un tiret », ce qui est le motif décrit dans les précédents posts. On voit ici qu'il est traité comme une expression régulière à part entière et, une fois que c'est fait, intégré au sein d'une expression plus grande. L'étoile * à la place de + permet de reconnaître « zéro chiffre » et de reconnaître les chaînes qui commencent directement par un tiret, celui-ci étant considéré comme le vrai délimiteur ;
- Ce groupe est suivi par un deuxième groupe qui ne contient que .*. Le point . signifie ici « n'importe quel caractère ». « .* » signifie « donc n'importe quel caractère répété n'importe quel nombre de fois (y compris 0) ». Ça veut dire qu'une telle expression peut donc reconnaître un seul caractère comme elle peut reconnaître le reste de la chaîne et dans ce cas, ce sera tout l'un ou tout l'autre (on parlera du mode greedy plus tard). Cela signifie surtout qu'il peut y avoir plusieurs solutions mais ici, l'expression n'est pas complète et l'analyseur choisira la solution qui permet de l'honorer entièrement. On va voir dans un instant qu'il ne peut y en avoir qu'une seule ;
- Le suivant est (-[0-9]*)?. C'est donc l'opposé du premier : un tiret d'abord suivi d'une suite optionnelle de chiffres. Cette expression est encadrée dans des parenthèses suivi d'un ?. Donc elle doit être reconnue exactement « 0 ou 1 fois », mais intégralement. L'étoile permet donc de reconnaître le tiret sans les chiffres mais il n'est pas possible de reconnaître les chiffres sans le tiret ;
- Vient enfin (\.html)?. On remarque que le point est échappé pour être considéré comme un caractère ordinaire. Suit l'extension, que tu peux éventuellement remplacer par [A-Za-z]+ pour éliminer toutes les extensions et pas seulement « .html », si tu es certain qu'elles ne contiendront que des lettres ;
- L'expression se termine par $, marqueur de fin de chaîne : cela impose à ta chaîne de se terminer par le dernier motif cité puisqu'il n'y a rien entre lui et cet opérateur. Cependant, ce motif est optionnel à cause de ?, donc il peut être correspondre à la place à une chaîne vide. Dans ce cas, ce serait le motif précédant mais lui aussi peut être optionnel. On arrive au précédent .* qui est obligatoire, et la seule façon de le remplir est de couvrir toute la chaînes jusqu'à ces deux motifs, donc jusqu'à la fin de la chaîne s'ils sont vides.
On peut encore raffiner un tout petit peu la chose en tenant compte d'éventuels blancs en début et fin de chaîne, si elles ont été lues sur un écran ou saisies dans un champ. Dans ce cas, le plus simple reste de passer trim($str) plutôt que $str seule, mais il est possible de traiter ce cas à l'intérieur de l'expression régulière également, ce qu'on laissera en exercice au lecteur. :-)
UPDATE : j'ai écrit l'expression ici sans la tester et donc, je me suis moi-même fait avoir : si les deux derniers motifs sont optionnels, alors (.*) peut être bien reconnaître la chaîne jusqu'à son extrémité. On est donc en présence d'une « ambiguïté » dans le sens technique du terme, c'est-à-dire une expression pouvant être interprétée de plusieurs façons différentes.
J'ai donc remplacé (.*) par ([^.-]) pour dire que l'on considère que la partie centrale ne contient ni point ni tiret.
On remarque dans cette dernière expression que ^ au début d'une paire de crochet est l'opérateur de négation : on cible les caractères qui ne sont PAS dans la plage. On souligne également que comme le point . est à l'intérieur des crochets, il est considéré comme un caractère ordinaire et n'a pas besoin être échappé.
Partager