web-dev-qa-db-fra.com

Réduire et capturer un motif répété dans une expression regex unique

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.

Mettre à jour:

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

Mise à jour 2 (prime):

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.

31
CSᵠ

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]));
}
6
Ja͢ck

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.

1
minopret

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:

http://regex101.com/r/gK0lX1

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']

0
spiralx

Lorsque vous combinez:

  1. Votre observation: tout type de répétition d'un groupe de capture unique entraînera un écrasement de la dernière capture, renvoyant ainsi uniquement la dernière capture du groupe de capture.
  2. La connaissance: tout type de capture basé sur les parties, au lieu de la totalité, rend impossible la définition d'une limite quant au nombre de répétitions du moteur de regex. La limite devrait être des métadonnées (pas de regex). 
  3. Avec une exigence selon laquelle la réponse ne peut pas impliquer de programmation (bouclage), ni une réponse qui implique simplement de copier-coller des groupes de capture comme vous l'avez fait dans votre question.

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 ). 

0