Javascript: equivalente lookbehind negativo?

Esiste un modo per ottenere l’equivalente di una visualizzazione negativa nelle espressioni regolari javascript? Devo abbinare una stringa che non inizia con un set specifico di caratteri.

Sembra che non riesca a trovare una regex che faccia ciò senza fallire se la parte corrispondente viene trovata all’inizio della stringa. Le schermate negative sembrano essere l’unica risposta, ma javascript non ne ha una.

EDIT: Questa è la regex che mi piacerebbe lavorare, ma non lo fa:

(?<!([abcdefg]))m

Quindi corrisponderebbe alla ‘m’ in ‘jim’ o ‘m’, ma non ‘jam’

Lookbehind Assertions è stato accettato nelle specifiche ECMAScript nel 2018. Questo è stato implementato in V8 e spedito senza flag con Google Chrome v62 e in Node.js v6 dietro un flag e v9 senza un flag . Quindi, se stai sviluppando per un ambiente solo Chrome (come Electron ) o Node , puoi iniziare a usare lookbehind oggi!

Uso positivo del lookbehind:

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

Poiché Javascript supporta il lookahead negativo , un modo sicuro per farlo è:

Diciamo che vuoi fare una ricerca in questo modo

 (? 
  1. Invertire la stringa in modo che corrisponda
  2. Applica il tuo pattern "invertito" usando un lookahead (fai attenzione all'espressione di corrispondenza invertita all'interno del lookahead, in questo caso rimane uguale)

     m(?!([abcdefg])) 
  3. invertire tutti i token abbinati

Esempi:

Definisco le seguenti funzioni:

 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]) : 'Ø' ); }); 

Esempio 1:

Seguendo la domanda di @ andrew-ensley:

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

Uscite:

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

Esempio 2:

Seguendo il commento @neaumusic (corrisponde max-height ma non line-height , l' line-height del token):

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

Uscite:

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

Supponiamo che tu voglia trovare tutti int non preceduti da unsigned :

Con il supporto per il look-back negativo:

 (? 

Senza supporto per il look-back negativo:

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

Fondamentalmente l'idea è di afferrare n caratteri precedenti ed escludere la corrispondenza con look-out negativo, ma anche i casi in cui non ci sono caratteri precedenti. (dove n è la lunghezza di guardare indietro).

Quindi la regex in questione:

 (? 

tradurrebbe in:

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

Potrebbe essere necessario giocare con i gruppi di cattura per trovare il punto esatto della stringa che ti interessa o si desidera sostituire la parte specifica con qualcos'altro.

La strategia di Mijoja funziona per il tuo caso specifico ma non in generale:

 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 

Ecco un esempio in cui l’objective è quello di far corrispondere un double-l ma non se è preceduto da “ba”. Nota la parola “balll” – il vero lookbehind dovrebbe aver soppresso i primi 2 ma ha abbinato la seconda coppia. Ma facendo corrispondere i primi 2 e ignorando quella corrispondenza come un falso positivo, il motore regexp procede dalla fine di quella corrispondenza e ignora qualsiasi carattere all’interno del falso positivo.

Uso

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

Puoi definire un gruppo non catturante negando il tuo set di caratteri:

 (?:[^ag])m 

… che corrisponderebbe a tutti i NON preceduti da nessuna di quelle lettere.

seguendo l’idea di Mijoja, e attingendo ai problemi esposti da JasonS, ho avuto questa idea; ho controllato un po ‘ma non sono sicuro di me stesso, quindi una verifica da parte di qualcuno più esperto di me in jge regex sarebbe fantastico 🙂

 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) 

la mia uscita personale:

 Fa[match] ball bi[match] bal[match] [match]ama 

il principio è chiamare il checker in ogni punto della stringa tra due caratteri qualsiasi, ogni volta che quella posizione è il punto di partenza di:

— qualsiasi sottostringa della dimensione di ciò che non è desiderato (qui 'ba' , quindi .. ) (se tale dimensione è nota, altrimenti deve essere più difficile da fare forse)

— — o inferiore a quello se è l’inizio della stringa: ^.?

e, in seguito,

— cosa deve essere effettivamente cercato (qui 'll' ).

Ad ogni chiamata di checker , ci sarà un test per verificare se il valore prima di ll non è quello che non vogliamo ( !== 'ba' ); se è così, chiamiamo un’altra funzione, e dovrà essere questa ( doer ) che farà le modifiche su str, se lo scopo è questo, o più genericamente, che otterrà in input i dati necessari per manualmente elaborare i risultati della scansione di str .

qui cambiamo la stringa, quindi abbiamo dovuto tenere traccia della differenza di lunghezza per compensare le posizioni date da replace , tutte calcolate su str , che di per sé non cambia mai.

poiché le stringhe primitive sono immutabili, avremmo potuto utilizzare la variabile str per memorizzare il risultato dell’intera operazione, ma ho pensato che l’esempio, già complicato dalle sostituzioni, sarebbe più chiaro con un’altra variabile ( str_done ).

Immagino che in termini di prestazioni debba essere piuttosto duro: tutte quelle inutili sostituzioni di “in”, this str.length-1 volte, più qui la sostituzione manuale da parte doer, il che significa molto affettare … probabilmente in questo caso specifico sopra che potrebbe essere raggruppato, tagliando la corda solo una volta in pezzi intorno a dove vogliamo inserire [match] e .join() con [match] stesso.

l’altra cosa è che non so come gestirà i casi più complessi, cioè i valori complessi per il finto lookbehind … la lunghezza è forse il dato più problematico da ottenere.

e, in un checker , in caso di più possibilità di valori non desiderati per $ behind, dovremo fare un test su di esso con l’ennesima regex (essere messo in cache (creato) al di fuori del checker è il migliore, per evitare lo stesso object regex a essere creato ad ogni chiamata per checker ) per sapere se è o meno ciò che cerchiamo di evitare.

spero di essere stato chiaro; se no non esitare, ci proverò meglio. 🙂

Questo efficacemente lo fa

 "jim".match(/[^ag]m/) > ["im"] "jam".match(/[^ag]m/) > null 

Cerca e sostituisci esempio

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

Nota che la stringa look-behind negativa deve essere lunga 1 carattere per far funzionare tutto questo.

Usando il tuo caso, se vuoi sostituire m con qualcosa, ad esempio convertilo in maiuscolo M , puoi annullare il set nel gruppo di cattura.

match ([^ag])m , sostituire con $1M

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

([^ag]) corrisponderà a qualsiasi char not ( ^ ) nell’intervallo ag e lo memorizzerà nel gruppo di prima acquisizione, quindi potrai accedervi con $1 .

Quindi troviamo im in jim e lo sostituiamo con iM che risulta in jiM .

/(?![abcdefg])[^abcdefg]m/gi sì, questo è un trucco.

Questo potrebbe aiutare, a seconda del contesto:

Questo corrisponde alla m in jim ma non alla jam:

 "jim jam".replace(/[ag]m/g, "").match(/m/g)