web-dev-qa-db-fra.com

Javascript: lookbehind négatif équivalent?

Existe-t-il un moyen d'obtenir l'équivalent d'un négatif à la recherche de dans les expressions régulières javascript? Je dois faire correspondre une chaîne qui ne commence pas par un jeu de caractères spécifique.

Il semble que je suis incapable de trouver une expression rationnelle qui le fasse sans échouer si la partie correspondante est trouvée au début de la chaîne. Les réponses négatives semblent être la seule réponse, mais javascript n'en a pas.

EDIT: C'est la regex que j'aimerais travailler, mais elle ne le fait pas:

(?<!([abcdefg]))m

Donc, cela correspondrait au "m" dans "jim" ou "m", mais pas "jam"

117
Andrew Ensley

Lookbehind Assertions a obtenu accepté dans la spécification ECMAScript en 2018. Cela a été implémenté dans V8 et fourni sans indicateurs avec Google Chrome v62 et dans Node. .js v6 derrière un drapeau et v9 sans drapeau . Ainsi, si vous développez pour un environnement exclusivement Chrome (tel que Electron ) ou Node , vous pouvez commencer à utiliser Lookbehind dès aujourd'hui!

Regard positif sur l'utilisation:

console.log(
  "$9.99  €8.47".match(/(?<=\$)\d+(\.\d*)?/) // Matches "9.99"
);

Regard négatif sur l'utilisation:

console.log(
  "$9.99  €8.47".match(/(?<!\$)\d+(?:\.\d*)/) // Matches "8.47"
);

Support sur d'autres plateformes:

  • Mozilla Firefox y travaille: Tracked here .
  • Microsoft Edge y travaille également: Tracked here (voix utilisateur suggestion ).
30
Okku

Comme Javascript prend en charge lookahead négatif , une façon de le faire est:

  1. inverser la chaîne d'entrée

  2. correspondre à une regex inversée

  3. inverser et reformater les allumettes


const reverse = s => s.split('').reverse().join('');

const test = (stringToTests, reversedRegexp) => stringToTests
  .map(reverse)
  .forEach((s,i) => {
    const match = reversedRegexp.test(s);
    console.log(stringToTests[i], match, 'token:', match ? reverse(reversedRegexp.exec(s)[0]) : 'Ø');
  });

Exemple 1:

Suite de la question de andrew-ensley:

test(['jim', 'm', 'jam'], /m(?!([abcdefg]))/)

Les sorties:

jim true token: m
m true token: m
jam false token: Ø

Exemple 2:

Après le commentaire @neaumusic (correspond à max-height mais pas line-height, le jeton étant height):

test(['max-height', 'line-height'], /thgieh(?!(-enil))/)

Les sorties:

max-height true token: height
line-height false token: Ø
77
JBE

Supposons que vous souhaitiez trouver tous les int non précédés de unsigned:

Avec prise en charge de la recherche négative:

(?<!unsigned )int

Sans prise en charge du regard négatif:

((?!unsigned ).{9}|^.{0,8})int

L'idée est essentiellement de saisir n caractères précédents et d'exclure les correspondances avec une anticipation négative, mais également de faire correspondre les cas où il n'y a pas n caractères précédents. (où n est la longueur du regard).

Donc, la regex en question:

(?<!([abcdefg]))m

traduirait en:

((?!([abcdefg])).|^)m

Vous devrez peut-être jouer avec des groupes de capture pour trouver l'emplacement exact de la chaîne qui vous intéresse ou vous souhaitez remplacer une partie spécifique par quelque chose d'autre.

49
Kamil Szot

La stratégie de Mijoja fonctionne pour votre cas spécifique mais pas en général:

js>newString = "Fall ball bill balll llama".replace(/(ba)?ll/g,
   function($0,$1){ return $1?$0:"[match]";});
Fa[match] ball bi[match] balll [match]AMA

Voici un exemple où l'objectif est de faire correspondre un double-l mais pas s'il est précédé de "ba". Notez que le mot "balll" - le vrai lookbehind devrait avoir supprimé les deux premiers l mais correspondre à la deuxième paire. Mais en faisant correspondre les 2 premiers l et en ignorant cette correspondance comme un faux positif, le moteur d'expressions rationnelles procède à partir de end de cette correspondance et ignore tous les caractères du faux positif.

41
Jason S

Utilisation

newString = string.replace(/([abcdefg])?m/, function($0,$1){ return $1?$0:'m';});
34
Mijoja

Vous pouvez définir un groupe sans capture en annulant votre jeu de caractères:

(?:[^a-g])m

... qui correspondrait à chaque mPASprécédé d’une de ces lettres.

9
Klemen Slavič

en suivant l’idée de Mijoja et en s’inspirant des problèmes exposés par JasonS, j’ai eu cette idée; J'ai vérifié un peu mais je ne suis pas sûr de moi, donc une vérification par quelqu'un de plus expert que moi dans js regex serait géniale :)

var re = /(?=(..|^.?)(ll))/g
         // matches empty string position
         // whenever this position is followed by
         // a string of length equal or inferior (in case of "^")
         // to "lookbehind" value
         // + actual value we would want to match

,   str = "Fall ball bill balll llama"

,   str_done = str
,   len_difference = 0
,   doer = function (where_in_str, to_replace)
    {
        str_done = str_done.slice(0, where_in_str + len_difference)
        +   "[match]"
        +   str_done.slice(where_in_str + len_difference + to_replace.length)

        len_difference = str_done.length - str.length
            /*  if str smaller:
                    len_difference will be positive
                else will be negative
            */

    }   /*  the actual function that would do whatever we want to do
            with the matches;
            this above is only an example from Jason's */



        /*  function input of .replace(),
            only there to test the value of $behind
            and if negative, call doer() with interesting parameters */
,   checker = function ($match, $behind, $after, $where, $str)
    {
        if ($behind !== "ba")
            doer
            (
                $where + $behind.length
            ,   $after
                /*  one will choose the interesting arguments
                    to give to the doer, it's only an example */
            )
        return $match // empty string anyhow, but well
    }
str.replace(re, checker)
console.log(str_done)

ma sortie personnelle:

Fa[match] ball bi[match] bal[match] [match]AMA

le principe consiste à appeler checker à chaque point de la chaîne entre deux caractères, chaque fois que cette position est le point de départ de:

--- toute sous-chaîne de la taille de ce qui n'est pas voulu (ici 'ba', donc ..) (si cette taille est connue, sinon cela peut être plus difficile à faire)

--- --- ou plus petit que si c'est le début de la chaîne: ^.?

et, ensuite,

--- ce qui doit être réellement recherché (ici 'll').

À chaque appel de checker, il y aura un test pour vérifier si la valeur précédant ll n'est pas ce que nous ne voulons pas (!== 'ba'); si c'est le cas, nous appelons une autre fonction, et ce sera celle-ci (doer) qui effectuera les modifications sur str, si son objectif est celui-ci, ou plus génériquement, qui permettra de saisir manuellement les données nécessaires traiter les résultats de l'analyse de str.

ici nous changeons la chaîne, nous devons donc garder une trace de la différence de longueur afin de compenser les emplacements donnés par replace, tous calculés sur str, qui elle-même ne change jamais.

comme les chaînes primitives sont immuables, nous aurions pu utiliser la variable str pour stocker le résultat de l'opération entière, mais je pensais que l'exemple, déjà compliqué par les remplacements, serait plus clair avec une autre variable (str_done).

je suppose qu'en termes de performances, cela doit être assez dur: tous ces remplacements inutiles de '' into '', this str.length-1 fois, plus ici le remplacement manuel par faiseur, ce qui signifie beaucoup de découpage…. probablement dans ce cas particulier ci-dessus, on pourrait regrouper les éléments en coupant la chaîne une seule fois en morceaux autour desquels on souhaite insérer [match] et .join() avec [match] lui-même.

l’autre chose est que je ne sais pas comment il gérera des cas plus complexes, c’est-à-dire des valeurs complexes pour le faux lookbehind ... la longueur étant peut-être la donnée la plus problématique à obtenir.

et, dans checker, en cas de multiples possibilités de valeurs non désirées pour $ behind, nous devrons faire un test dessus avec encore un autre regex (à mettre en cache (créé) en dehors de checker est préférable, afin d'éviter le même objet regex être créé à chaque appel de checker) pour savoir s’il s’agit ou non de ce que nous cherchons à éviter.

espérons que j'ai été clair; sinon n'hésite pas, j'essaierai mieux. :)

1
Homer Simpson

Voici comment j'ai réalisé str.split(/(?<!^)@/) pour Node.js 8 (qui ne prend pas en charge lookbehind):

str.split('').reverse().join('').split(/@(?!$)/).map(s => s.split('').reverse().join('')).reverse()

Travaux? Oui (unicode non testé). Désagréable? Oui.

0
Fishrock123

Cela le fait effectivement

"jim".match(/[^a-g]m/)
> ["im"]
"jam".match(/[^a-g]m/)
> null

Rechercher et remplacer l'exemple

"jim jam".replace(/([^a-g])m/g, "$1M")
> "jiM jam"

Notez que la chaîne de recherche négative doit comporter 1 caractère pour que cela fonctionne.

0
Curtis Yallop

Comme mentionné précédemment, JavaScript permet les recherches maintenant. Dans les anciens navigateurs, vous avez toujours besoin d'une solution de contournement.

Je parie que, dans ma tête, il n’ya aucun moyen de trouver une expression rationnelle sans regarder derrière qui donne exactement le résultat. Tout ce que vous pouvez faire, c'est travailler avec des groupes. Supposons que vous avez une expression rationnelle (?<!Before)Wanted, où Wanted est l'expression que vous voulez associer et Before, qui indique ce qui ne devrait pas précéder la correspondance. Le mieux que vous puissiez faire est d’annuler la regex Before et d’utiliser la regex NotBefore(Wanted). Le résultat souhaité est le premier groupe $1.

Dans votre cas, Before=[abcdefg] qui est facile à nier NotBefore=[^abcdefg]. Ainsi, l'expression régulière serait [^abcdefg](m). Si vous avez besoin de la position Wanted, vous devez également grouper NotBefore, de sorte que le résultat souhaité soit le deuxième groupe.

Si les correspondances du modèle Before ont une longueur fixe n, c'est-à-dire si le modèle ne contient pas de jetons répétitifs, vous pouvez éviter de nier le modèle Before et utiliser l'expression régulière (?!Before).{n}(Wanted), mais il faut toujours utiliser le premier groupe ou utiliser l'expression régulière (?!Before)(.{n})(Wanted) et utiliser le deuxième groupe. Dans cet exemple, le motif Before a en fait une longueur fixe, à savoir 1, utilisez donc l'expression régulière (?![abcdefg]).(m) ou (?![abcdefg])(.)(m). Si vous êtes intéressé par toutes les correspondances, ajoutez l'indicateur g, consultez mon extrait de code:

function TestSORegEx() {
  var s = "Donald Trump doesn't like jam, but Homer Simpson does.";
  var reg = /(?![abcdefg])(.{1})(m)/gm;
  var out = "Matches and groups of the regex " + 
            "/(?![abcdefg])(.{1})(m)/gm in \ns = \"" + s + "\"";
  var match = reg.exec(s);
  while(match) {
    var start = match.index + match[1].length;
    out += "\nWhole match: " + match[0] + ", starts at: " + match.index
        +  ". Desired match: " + match[2] + ", starts at: " + start + ".";   
    match = reg.exec(s);
  }
  out += "\nResulting string after statement s.replace(reg, \"$1*$2*\")\n"
         + s.replace(reg, "$1*$2*");
  alert(out);
}
0

En utilisant votre cas, si vous voulez remplacer m par quelque chose, par exemple. convertissez-le en majuscule M, vous pouvez annuler l'ensemble défini dans le groupe de capture.

correspond à ([^a-g])m, remplace par $1M

"jim jam".replace(/([^a-g])m/g, "$1M")
\\jiM jam

([^a-g]) fera correspondre tout caractère non (^) dans la plage a-g et le stockera dans le premier groupe de capture afin que vous puissiez y accéder avec $1.

Nous trouvons donc im dans jim et le remplaçons par iM, ce qui donne jiM.

0
Traxo