Je n'arrête pas de me heurter à des situations où je dois capturer un certain nombre de jetons d'une chaîne et après d'innombrables tentatives, je ne pouvais pas trouver un moyen de simplifier le processus.
Alors disons que le texte est:
début: test-test-lorem-ipsum-sir-doloret-etc-etc-quelque-chose: fin
Cet exemple contient 8 éléments, mais supposons qu’il puisse contenir entre 3 et 10 éléments.
J'aimerais idéalement quelque chose comme ceci:start:(?:(\w+)-?){3,10}:end
Nice and clean MAIS il ne capture que le dernier match. vois ici
J'utilise habituellement quelque chose comme ceci dans des situations simples:
start:(\w+)-(\w+)-(\w+)-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?:end
Trois groupes sont obligatoires et sept autres sont facultatifs en raison de la limite maximale de 10, mais cela n'a pas l'air sympa et il serait difficile d'écrire et de suivre si la limite maximale était de 100 et si les matches étaient plus complexes. démo
Et le mieux que j'ai pu faire jusqu'à présent:
start:(\w+)-((?1))-((?1))-?((?1))?-?((?1))?-?((?1))?-?((?1))?-?((?1))?:end
plus court surtout si les matches sont complexes mais restent longs. démo
Tout le monde a réussi à le faire fonctionner comme une solution à une seule expression rationnelle sans programmation?
Je suis surtout intéressé par la façon dont cela peut être fait dans PCRE mais d’autres saveurs seraient également acceptables.
Le but est de valider une correspondance et de capturer des jetons individuels à l'intérieur de match 0
par RegEx seul, sans aucune limitation de système d'exploitation/logiciel/langage de programmation
Avec l'aide de @ nhahtdh, je suis arrivé à la RegExp ci-dessous en utilisant \G
:
(?:start:(?=(?:[\w]+(?:-|(?=:end))){3,10}:end)|(?!^)\G-)([\w]+)
demo encore plus court, mais peut être décrit sans répéter le code
Je suis également intéressé par la version ECMA et, comme elle ne prend pas en charge \G
, je me demande s’il existe un autre moyen, en particulier sans utiliser le modificateur /g
.
Bien qu'il soit théoriquement possible d'écrire une seule expression, il est beaucoup plus pratique de faire correspondre les limites extérieures en premier, puis de scinder la partie interne.
Dans ECMAScript, je l’écrirais comme ceci:
'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end'
.match(/^start:([\w-]+):end$/)[1] // match the inner part
.split('-') // split inner part (this could be a split regex as well)
En PHP:
$txt = 'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end';
if (preg_match('/^start:([\w-]+):end$/', $txt, $matches)) {
print_r(explode('-', $matches[1]));
}
Bien sûr, vous pouvez utiliser l'expression rationnelle dans cette chaîne citée.
"(?<a>\\w+)-(?<b>\\w+)-(?:(?<c>\\w+)" \
"(?:-(?<d>\\w+)(?:-(?<e>\\w+)(?:-(?<f>\\w+)" \
"(?:-(?<g>\\w+)(?:-(?<h>\\w+)(?:-(?<i>\\w+)" \
"(?:-(?<j>\\w+))?" \
")?)?)?" \
")?)?)?" \
")"
Est-ce que c'est une bonne idée? Non je ne pense pas.
Vous n'êtes pas sûr de pouvoir le faire de cette manière, mais vous pouvez utiliser le drapeau global pour trouver tous les mots entre les deux points, voir:
Vous devrez cependant valider vous-même le nombre de groupes. Sans l'indicateur global, vous n'obtenez qu'une seule correspondance, pas toutes les correspondances - remplacez {3,10}
par {1,5}
et obtenez le résultat «monsieur» à la place.
import re
s = "start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end"
print re.findall(r"(\b\w+?\b)(?:-|:end)", s)
produit
['test', 'test', 'lorem', 'ipsum', 'sir', 'doloret', 'etc', 'etc', 'something']
Lorsque vous combinez:
On peut en déduire que cela ne peut être fait.
Update: Il y a des moteurs de regex pour lesquels p. 1 n'est pas nécessairement vrai. Dans ce cas, l'expression rationnelle que vous avez indiquée start:(?:(\w+)-?){3,10}:end
fera l'affaire ( source ).