Comment simuler un « lookbehind » dans IndexMatic²
January 12, 2022 | IndexMatic² | fr | en
La plupart des opérateurs Grep sont pris en charge par IndexMatic², mais pas le lookbehind qui permet de capturer une expression selon ce qui la PRÉCÈDE. Il arrive pourtant qu'une telle condition s'impose dans l'élaboration d'une requête. Voyons comment contourner ce petit obstacle…
Rappelons d'abord qu'IndexMatic² ne reconnaît que les expressions régulières ExtendScript, un dialecte qui prend en charge les assertions « vers l'avant », dites LOOKAHEAD positif ou négatif, usant de la forme X(?=Y)
ou X(?!Y)
. Mais il restera de marbre devant les schémas « vers l'arrière » de type LOOKBEHIND, formalisés respectivement (?<=Y)X
et (?<!Y)X
en syntaxe Grep.
Il n'est pas rare cependant que les éléments préfixes d'une expression décident de sa pertinence pour votre index. Dans de nombreux cas, par exemple « Premier ministre » versus « ministre », il suffira de capturer l'expression complète /Premier ministre/
pour la discriminer ipso facto de sa forme indésirable. Puisque c'est bien le terme « Premier ministre » que vous souhaitez indexer, aucune difficulté ne se fait jour. Le problème du LOOKBEHIND ne se pose concrètement que si vous devez capturer une certaine forme à condition qu'elle possède tel préfixe mais sans capturer ledit préfixe.
Lookbehind positif
Supposez maintenant que vous produisiez l'index d'un catalogue de produits. Mettons que chaque nom de produit susceptible d'indexation soit systématiquement précédé d'un code à trois chiffres suivi d'une barre verticale, par ex. 123|étagère, et qu'il n'y ait pas d'autre moyen formel d'extraire l'élément cible. Dans pareille situation, le préfixe \d\d\d\|
devient la condition d'extraction du terme étagère, sauf que ce code ne doit pas apparaître dans l'index.
En GREP, il n'y aurait qu'à envoyer une commande de la forme (?<=\d\d\d\|)\w+
pour extraire le nom d'un produit se trouvant dans le sillage de n'importe quel code. Mais cela échouerait dans ExtendScript.
Cependant, IndexMatic² peut contourner le problème. Il suffit en effet de capturer le motif tout entier et de sélectionner en sortie le segment qui nous intéresse :
/(\d\d\d\|)(\w+)/ => $2
Le schéma général d'un lookbehind simulé est donc (PRÉFIXE)(TERME) => $2
, PRÉFIXE représentant la condition en amont, TERME représentant le motif à extraire. Grâce aux parenthèses capturantes, la règle => $2
ignore l'élément préfixe $1
et ne produit finalement que le TERME voulu $2
.
Note. — Cette astuce comporte un inconvénient subtil mais fâcheux : IndexMatic² indique toujours le numéro de page associé à la capture tout entière. Si par accident l'élément PRÉFIXE se trouve en page 2 alors que TERME le suit en page 3, l'index final indiquera TERME en page 2, car c'est en effet la position où débute la concordance /(PRÉFIXE)(TERME)/
.
Lookbehind négatif
Nous cherchons désormais à capturer un terme pour autant qu'il ne soit pas précédé par un certain motif. Problème en apparence antipodique, voire un poil plus retors puisque nous ne pouvons pas exprimer « positivement » un préfixe valide. Pourtant, il reste étonnamment facile de tamiser les candidats pour ne retenir que la forme autorisée, par l'astuce schématisée ci-dessous :
(PRÉFIXE TERME)|(TERME) => $2
Cette fois, la première parenthèse (PRÉFIXE TERME)
matérialise la forme que nous voulons exclure, tandis que la seconde parenthèse, (TERME)
seul, représente ce que nous tolérons. Un petit tour de magie s'opère alors. Du fait de l'alternative, la variable $1
est assignée à chaque fois que PRÉFIXE TERME
est trouvé dans le texte, alors que $2
ne recevra un TERME
que dans le cas contraire. Mais puisque la requête expose $2
et jamais $1
, tous les contextes qui satisfont PRÉFIXE TERME
sont finalement ignorés. Vous obtenez donc l'effet exact d'un lookbehind négatif.
À titre d'illustration, renversons la contrainte de l'exemple examiné plus haut. Le but est maintenant d'indexer les expressions de la forme \w+
sauf celles préfixées par un code \d\d\d\|
. Il suffit d'adresser à IndexMatic² la requête suivante :
/(\d\d\d\|\w+)|(\w+)/ => $2
Notez en passant que le schème /(X)|(Y)/ => $2
a d'innombrables applications. Mettons que votre objectif soit d'identifier toutes les occurrences du mot instance, sauf celles composées entre guillemets. Il suffit là encore de capturer les deux formes concurrentes — la plus longue devant apparaître en premier — puis d'exposer le terme $2
:
/(["«] ?instance ?["»])|(instance)/ => $2
La technique est la même : on fait digérer la forme indésirable par le premier bloc afin que le second ne puisse plus l'enregistrer.