Como concatenar literais de regex em JavaScript?

145

É possível fazer algo assim?

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

Ou tenho que usar nova RegExp()sintaxe e concatenar uma string? Eu preferiria usar o literal, pois o código é mais evidente e conciso.

ausência de pálpebra
fonte
2
É mais fácil lidar com caracteres regex de escape se você usar String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

Respostas:

190

Aqui está como criar uma expressão regular sem usar a sintaxe literal da expressão regular. Isso permite que você faça a manipulação de string arbitrária antes que ela se torne um objeto de expressão regular:

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 você possui dois literais de expressão regular, é possível concatená-los usando esta técnica:

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

É apenas mais prolixo do que apenas ter a expressão um e dois sendo strings literais em vez de expressões regulares literais.

Jerub
fonte
2
Lembre-se de que cada segmento deve ser uma expressão regular válida ao usar essa abordagem. Construir uma expressão como new RegExp(/(/.source + /.*/.source + /)?/.source);parece não funcionar.
24413 Sam
Esta solução não funciona no caso de grupos de correspondência inversa. Veja minha resposta para uma solução de trabalho nesse caso.
Mikaël Mayer
Se você precisa escapar de um char, então use barras invertidas duplas: novo Regexp ( '\\ $' + "Flum")
Jeff Lowery
Você pode acessar os sinalizadores se precisar com "<regexp> .flags", portanto, teoricamente, você pode combiná-los também.
bnunamak
De onde você está indo expression_one? Você quer dizer regex1?
TallOrderDev
30

Apenas concatenar aleatoriamente objetos de expressões regulares pode ter alguns efeitos colaterais adversos. Use o 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

Isso também permitirá que você mantenha os sinalizadores de expressão regular de um RegExp anterior usando os sinalizadores RegExp padrão.

jsFiddle

Japheth Salva
fonte
Isto pode ser melhorado utilizandoRegExp.prototype.flags
Dmitry Parzhitsky
19

Não concordo totalmente com a opção "eval".

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

dará "// abcd // efgh //", que não é o resultado pretendido.

Usando fonte como

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

dará "/ abcdefgh /" e isso está correto.

Logicamente, não há necessidade de AVALIAR, você conhece sua EXPRESSÃO. Você só precisa da sua FONTE ou de como ela está escrita, não necessariamente do seu valor. Quanto aos sinalizadores, você só precisa usar o argumento opcional de RegExp.

Na minha situação, eu corro na questão de ^ e $ sendo usados ​​em várias expressões que estou tentando concatenar juntos! Essas expressões são filtros gramaticais usados ​​em todo o programa. Agora não quero usar alguns deles juntos para lidar com o caso de PREPOSIÇÕES. Talvez eu precise "cortar" as fontes para remover o início e o final ^ (e / ou) $ :) Saúde, Alex.

Alex
fonte
Eu gosto do uso da propriedade de origem. Se você - como eu - uso JSLint vai nag se você fizer algo como isto:var regex = "\.\..*"
Nils-o-mat
7

Problema Se o regexp contiver grupos de correspondência retroativa como \ 1.

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

Então apenas contatenar as fontes não funcionará. De fato, a combinação dos dois é:

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

A solução: primeiro, contamos o número de grupos correspondentes no primeiro regex; depois, para cada token de correspondência retroativa no segundo, incrementamos o número de grupos correspondentes.

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

Teste:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true
Mikaël Mayer
fonte
2
Sim (eu não vou modificá-lo aqui em cima). Esta função é associativa, portanto, você pode usar o seguinte código: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

Seria preferível usar a sintaxe literal o mais rápido possível. É mais curto, mais legível e você não precisa de aspas de escape ou de escapes duplos. De "Padrões Javascript", Stoyan Stefanov 2010.

Mas usar Novo pode ser a única maneira de concatenar.

Eu evitaria avaliar. Não é seguro.

Jonathan Wright
fonte
1
Penso que expressões regulares complexas são mais legíveis quando divididas e comentadas como na pergunta.
Sam
3

Fornecendo:

  • você sabe o que faz no seu regexp;
  • você tem muitas partes de regex para formar um padrão e elas usarão a mesma bandeira;
  • você acha mais legível separar seus pequenos pedaços de padrão em uma matriz;
  • você também quer comentar cada parte do próximo desenvolvedor ou você mesmo mais tarde;
  • você prefere simplificar visualmente seu regex /this/gdo que new RegExp('this', 'g');
  • não há problema em você montar o regex em uma etapa extra, em vez de tê-lo inteiro desde o início;

Então você pode escrever desta maneira:

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');

você pode fazer algo como:

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

No meu caso específico (um editor semelhante ao código-espelho), é muito mais fácil executar uma grande regex, em vez de muitas substituições como a seguir, sempre que eu substituo por uma tag html para quebrar uma expressão, o próximo padrão será ser mais difícil de segmentar sem afetar a própria tag html (e sem a boa aparência que infelizmente não é suportada em 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>')
antoni
fonte
2

Você poderia fazer algo como:

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

Os segmentos seriam seqüências de caracteres (em vez de literais de regex) passadas como argumentos separados.

Neil Strain
fonte
1

Não, a maneira literal não é suportada. Você terá que usar o RegExp.

Aupajo
fonte
1

Use o construtor com 2 parâmetros e evite o problema de arrastar '/':

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
ph7
fonte
1

Você pode concat fonte de regex da classe literal e RegExp:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);
Jeff Lowery
fonte
1

a maneira mais fácil para mim seria concatenar as fontes, ex .:

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

o valor c resultará em:

/ \ d + \ w + /

Daniel Aragão
fonte
-2

Eu prefiro usar eval('your expression')porque não adiciona o /em cada extremidade /que ='new RegExp'faz.

Praesagus
fonte