Come posso concatenare i letterali regex in JavaScript?


145

È possibile fare qualcosa del genere?

var pattern = /some regex segment/ + /* comment here */
    /another segment/;

O devo usare una nuova RegExp()sintassi e concatenare una stringa? Preferirei usare il valore letterale poiché il codice è sia più evidente che conciso.


2
È più facile gestire i caratteri regex con escape se si utilizza String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

Risposte:


190

Ecco come creare un'espressione regolare senza utilizzare la sintassi letterale dell'espressione regolare. Ciò ti consente di manipolare arbitrariamente la stringa prima che diventi un oggetto di espressione regolare:

var segment_part = "some bit of the regexp";
var pattern = new RegExp("some regex segment" + /*comment here */
              segment_part + /* that was defined just now */
              "another segment");

Se hai due letterali di espressioni regolari, puoi infatti concatenarli usando questa tecnica:

var regex1 = /foo/g;
var regex2 = /bar/y;
var flags = (regex1.flags + regex2.flags).split("").sort().join("").replace(/(.)(?=.*\1)/g, "");
var regex3 = new RegExp(expression_one.source + expression_two.source, flags);
// regex3 is now /foobar/gy

È solo più prolisso che avere l'espressione uno e due come stringhe letterali anziché letterali espressioni regolari.


2
Tenere presente che ogni segmento deve essere un'espressione regolare valida quando si utilizza questo approccio. Costruire un'espressione come new RegExp(/(/.source + /.*/.source + /)?/.source);non sembra funzionare.
Sam,

Questa soluzione non funziona nel caso di gruppi di back matching. Vedi la mia risposta per una soluzione funzionante in quel caso.
Mikaël Mayer,

Se hai bisogno di sfuggire a un carattere, usa le doppie barre rovesciate: nuovo Regexp ('\\ $' + "flum")
Jeff Lowery,

Puoi accedere ai flag se devi "<regexp> .flags", quindi teoricamente potresti anche combinarli.
bnunamak,

Da dove vieni expression_one? Intendi regex1?
TallOrderDev

30

Il semplice concatenamento casuale di oggetti di espressioni regolari può avere alcuni effetti collaterali negativi. Utilizzare invece RegExp.source :

var r1 = /abc/g;
var r2 = /def/;
var r3 = new RegExp(r1.source + r2.source, 
                   (r1.global ? 'g' : '') 
                   + (r1.ignoreCase ? 'i' : '') + 
                   (r1.multiline ? 'm' : ''));
console.log(r3);
var m = 'test that abcdef and abcdef has a match?'.match(r3);
console.log(m);
// m should contain 2 matches

Questo ti darà anche la possibilità di conservare i flag delle espressioni regolari di un RegExp precedente usando i flag RegExp standard.

jsFiddle


Questo può essere migliorato usandoRegExp.prototype.flags
Dmitry Parzhitsky il

19

Non sono del tutto d'accordo con l'opzione "eval".

var xxx = /abcd/;
var yyy = /efgh/;
var zzz = new RegExp(eval(xxx)+eval(yyy));

darà "// abcd // efgh //" che non è il risultato previsto.

Usando la fonte come

var zzz = new RegExp(xxx.source+yyy.source);

darà "/ abcdefgh /" e questo è corretto.

Logicamente non c'è bisogno di VALUTARE, conosci la tua ESPRESSIONE. Hai solo bisogno della sua SORGENTE o di come è scritta non necessariamente il suo valore. Per quanto riguarda i flag, devi solo usare l'argomento opzionale di RegExp.

Nella mia situazione, mi imbatto nel problema di ^ e $ utilizzati in diverse espressioni che sto cercando di concatenare insieme! Tali espressioni sono filtri grammaticali utilizzati nel programma. Ora non voglio usarne alcuni insieme per gestire il caso delle PREPOSIZIONI. Potrei dover "tagliare" le fonti per rimuovere l'inizio e la fine ^ (e / o) $ :) Saluti, Alex.


Mi piace l'uso della proprietà source. Se - come me - usi jslint, ti farà male se fai qualcosa del genere:var regex = "\.\..*"
Nils-o-mat

7

Problema Se regexp contiene gruppi di back matching come \ 1.

var r = /(a|b)\1/  // Matches aa, bb but nothing else.
var p = /(c|d)\1/   // Matches cc, dd but nothing else.

Quindi solo contatenare le fonti non funzionerà. In effetti, la combinazione dei due è:

var rp = /(a|b)\1(c|d)\1/
rp.test("aadd") // Returns false

La soluzione: prima contiamo il numero di gruppi corrispondenti nella prima regex, quindi per ogni token di corrispondenza posteriore nella seconda, lo incrementiamo del numero di gruppi corrispondenti.

function concatenate(r1, r2) {
  var count = function(r, str) {
    return str.match(r).length;
  }
  var numberGroups = /([^\\]|^)(?=\((?!\?:))/g; // Home-made regexp to count groups.
  var offset = count(numberGroups, r1.source);    
  var escapedMatch = /[\\](?:(\d+)|.)/g;        // Home-made regexp for escaped literals, greedy on numbers.
  var r2newSource = r2.source.replace(escapedMatch, function(match, number) { return number?"\\"+(number-0+offset):match; });
  return new RegExp(r1.source+r2newSource,
      (r1.global ? 'g' : '') 
      + (r1.ignoreCase ? 'i' : '')
      + (r1.multiline ? 'm' : ''));
}

Test:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true

2
Sì (comunque non lo modificherò qui). Questa funzione è associativa, quindi puoi usare il seguente codice:function concatenateList() { var res = arguments[0]; for(var i = 1; i < arguments.length; i++) { res = concatenate(res, arguments[i]); } return res; }
Mikaël Mayer

3

Sarebbe preferibile utilizzare la sintassi letterale il più spesso possibile. È più breve, più leggibile e non sono necessarie virgolette di escape o backlash a doppia escape. Da "Javascript Patterns", Stoyan Stefanov 2010.

Ma usare New potrebbe essere l'unico modo per concatenare.

Eviterei Eval. Non è sicuro.


1
Penso che espressioni regolari complesse siano più leggibili quando vengono scomposte e commentate come nella domanda.
Sam

3

A condizione che:

  • sai cosa fai nella tua regexp;
  • hai molti pezzi regex per formare uno schema e useranno la stessa bandiera;
  • lo trovi più leggibile per separare i piccoli blocchi di pattern in un array;
  • vuoi anche essere in grado di commentare ogni parte per il prossimo sviluppatore o te stesso in seguito;
  • preferisci semplificare visivamente il tuo regex come /this/gpiuttosto che new RegExp('this', 'g');
  • va bene per te assemblare il regex in un passaggio aggiuntivo anziché averlo in un pezzo dall'inizio;

Quindi potresti scrivere in questo modo:

var regexParts =
    [
        /\b(\d+|null)\b/,// Some comments.
        /\b(true|false)\b/,
        /\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|length|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/,
        /(\$|jQuery)/,
        /many more patterns/
    ],
    regexString  = regexParts.map(function(x){return x.source}).join('|'),
    regexPattern = new RegExp(regexString, 'g');

puoi quindi fare qualcosa del tipo:

string.replace(regexPattern, function()
{
    var m = arguments,
        Class = '';

    switch(true)
    {
        // Numbers and 'null'.
        case (Boolean)(m[1]):
            m = m[1];
            Class = 'number';
            break;

        // True or False.
        case (Boolean)(m[2]):
            m = m[2];
            Class = 'bool';
            break;

        // True or False.
        case (Boolean)(m[3]):
            m = m[3];
            Class = 'keyword';
            break;

        // $ or 'jQuery'.
        case (Boolean)(m[4]):
            m = m[4];
            Class = 'dollar';
            break;

        // More cases...
    }

    return '<span class="' + Class + '">' + m + '</span>';
})

Nel mio caso particolare (un editor di tipo mirroring del codice), è molto più semplice eseguire una grande regex, piuttosto che un sacco di sostituzioni come seguire come ogni volta che sostituisco con un tag html per avvolgere un'espressione, il modello successivo sarà essere più difficili da indirizzare senza influire sul tag html stesso (e senza il bello aspetto che purtroppo non è supportato in javascript):

.replace(/(\b\d+|null\b)/g, '<span class="number">$1</span>')
.replace(/(\btrue|false\b)/g, '<span class="bool">$1</span>')
.replace(/\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/g, '<span class="keyword">$1</span>')
.replace(/\$/g, '<span class="dollar">$</span>')
.replace(/([\[\](){}.:;,+\-?=])/g, '<span class="ponctuation">$1</span>')

2

Potresti fare qualcosa del tipo:

function concatRegex(...segments) {
  return new RegExp(segments.join(''));
}

I segmenti sarebbero stringhe (anziché letterali regex) passati come argomenti separati.


1

No, il modo letterale non è supportato. Dovrai usare RegExp.


1

Usa il costruttore con 2 parametri ed evita il problema con '/' finale:

var re_final = new RegExp("\\" + ".", "g");    // constructor can have 2 params!
console.log("...finally".replace(re_final, "!") + "\n" + re_final + 
    " works as expected...");                  // !!!finally works as expected

                         // meanwhile

re_final = new RegExp("\\" + "." + "g");              // appends final '/'
console.log("... finally".replace(re_final, "!"));    // ...finally
console.log(re_final, "does not work!");              // does not work

1

Puoi concedere il sorgente regex sia dalla classe letterale che da quella RegExp:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);

1

il modo più semplice per me sarebbe concatenare le fonti, es .:

a = /\d+/
b = /\w+/
c = new RegExp(a.source + b.source)

il valore c si tradurrà in:

/ \ D + \ w + /


-2

Preferisco usare eval('your expression')perché non aggiunge il /su ogni estremità /che lo ='new RegExp'fa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.