En temps normal, la manière la plus simple consiste à d'abord chercher \b[Cc]at\b(.*?)\b[Dd]og\b (ou l'inverse) puis dans un deuxième temps de chercher les mots de trois lettres et plus dans chaque groupes de capture.
Pour réaliser l'opération en une seule expression, il faut utiliser la méthode find() et l'ancre \G. L'ancre \G marque soit le début de la chaîne de caractère (ce qui ne nous intéresse pas ici) soit la position après la dernière correspondance. L'intérêt de cette ancre est de permettre la contiguïté des correspondances lors de recherches successives.
Dans ce but on commence la pattern avec deux points d'entrée possible:- soit avec \G pour obtenir un résultat collé au précédent (contigu donc).
- soit avec le mot cat
Ce qui donne:(remarque que pour éviter que \G matche le début de la chaîne on ajoute (?!\A).)
Ensuite il ne reste plus qu'à décrire ce qui vient après, jusqu'au mot dog (exclu, car sinon la recherche continuera au-delà du mot dog) ou jusqu'au prochain mot de trois lettres (inclus cette fois ci pour permettre la contiguïté avec l'occurrence suivante):
(?:\G(?!\A)|\b[Cc]at\b)(?:[^\w\r\n]+\w{1,2}\b)*+[^\w\r\n]+(?:(?=([Dd]og)\b)|(\w+))
(?:[^\w\r\n]+\w{1,2}\b)*+ décrit tout ce qui n'est pas un mot de trois lettres ou plus pouvant se trouver avant le mot de trois lettre ou plus ou le mot dog. La classe de caractère [^\w\r\n] contient tout ce qui n'est pas un "word character" ou un CRLF pour ne pas sortir de la ligne. J'utilise un quantificateur possessif pour interdire tout retour en arrière une fois le groupe matché.
(?:(?=([Dd]og)\b)|(\w+)) permet de capturer deux choses, soit le mot dog pour s'assurer qu'il est bien présent sur la ligne, soit le mot de trois lettres ou plus. (NB: il est inutile de contrôler le nombre de caractères de par l'utilisation du quantificateur possessif précédemment)
Pour améliorer les performances de cette pattern on peut mettre en facteur le \b au début ce qui évitera des tests inutiles au moteur de regex:
\b(?:\G(?!\A)|[Cc]at\b)(?:[^\w\r\n]+\w{1,2}\b)*+[^\w\r\n]+(?:(?=([Dd]og)\b)|(\w+))
L'analyse des résultats de find() est simple. Les mots de trois lettres et plus sont dans le deuxième groupe de capture. Dés que celui ci est null, il suffit de vérifier que le premier groupe de capture (celui qui matche "dog") existe bien.
Il est possible de modifier cette pattern pour qu'elle trouve indifféremment les séquences commençant par "cat" ou "dog" en ajoutant un test avant (lookahead) au deuxième point d'entrée:
\b(?:\G(?!\A)|([Cc]at|[Dd]og)\b(?=(?>[^\ncCdD]+|\B[CcDd]|[Cc](?!at\b)|[Dd](?!og\b))+(?!(?i)\1)\b(?:[Dd]og|[Cc]at)\b))(?:[^\w\r\n]+\w{1,2}\b)*+[^\w\r\n]+(?:(?=[Cc]at\b|[Dd]og\b)|(\w+))
Ce test avant vérifie qu'il n'y a pas d'autre "cat" ou "dog" jusqu'au prochain "cat" ou "dog" qui doit être différent du mot de départ. À noter que le groupe de capture à la fin a été enlevé. Il n'est plus nécessaire dés lors que l'on a testé dés le départ la présence de "cat" ou "dog" à la fin.
Cette méthode avec le test avant à le désavantage de faire parcourir deux fois la même portion de chaîne au moteur de regex (une fois dans le test avant et une autre fois lors des entrées successives par la branche \G.)
On peut lui préférer une approche plus simple qui consiste à utiliser:
\b(?:\G(?!\A)|([Cc]at|[Dd]og)\b)(?:[^\w\r\n]+\w{1,2}\b)*+[^\w\r\n]+(?:(?=([Dd]og|[Cc]at)\b)|(\w+))
. L'analyse des résultats diffère alors un peu. Il y a maintenant trois groupes de capture:- Groupe 1: Le mot de départ ("cat" ou "dog")
- Groupe 2: Le mot de trois lettres ou plus
- Groupe 3: Le mot d'arrivée
Il faut donc stocker le mot du groupe 1 et le comparer sans prendre en compte la casse avec celui récupérer plus tard dans le groupe 3.
Partager