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.


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.


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


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.


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

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.


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

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.


1

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


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

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

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 + /


-2

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.