¿Cómo puedo concatenar literales regex en JavaScript?

145

¿Es posible hacer algo como esto?

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

¿O tengo que usar una nueva RegExp()sintaxis y concatenar una cadena? Prefiero usar el literal ya que el código es más evidente y conciso.

párpado
fuente
2
Es más fácil tratar con caracteres regex escapados si usa String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

Respuestas:

190

Aquí se explica cómo crear una expresión regular sin usar la sintaxis literal de la expresión regular. Esto le permite realizar una manipulación arbitraria de cadenas antes de que se convierta en un objeto de expresión 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");

Si tiene dos literales de expresión regular, de hecho puede concatenarlos utilizando 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

Es simplemente más prolijo que simplemente tener la expresión uno y dos como cadenas literales en lugar de expresiones regulares literales.

Jerub
fuente
2
Tenga en cuenta que cada segmento debe ser una expresión regular válida al usar este enfoque. Construir una expresión como new RegExp(/(/.source + /.*/.source + /)?/.source);no parece funcionar.
Sam
Esta solución no funciona en el caso de grupos de correspondencia inversa. Vea mi respuesta para una solución de trabajo en ese caso.
Mikaël Mayer
Si necesita escapar de un personaje, utilice barras invertidas dobles: nuevo Regexp ('\\ $' + "flum")
Jeff Lowery
Puede acceder a las banderas si tiene que hacerlo con "<regexp> .flags", por lo que teóricamente podría combinarlas también.
bnunamak
¿De dónde vienes expression_one? Qué quiere decir regex1?
TallOrderDev
30

Solo concatenar aleatoriamente objetos de expresiones regulares puede tener algunos efectos secundarios adversos. Utilice RegExp.source en su lugar:

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

Esto también le dará la capacidad de retener los indicadores de expresión regular de un RegExp anterior utilizando los indicadores RegExp estándar.

jsFiddle

Japheth Salva
fuente
Esto se puede mejorar usandoRegExp.prototype.flags
Dmitry Parzhitsky
19

No estoy del todo de acuerdo con la opción "eval".

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

dará "// abcd // efgh //", que no es el resultado deseado.

Usando fuente como

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

dará "/ abcdefgh /" y eso es correcto.

Lógicamente no hay necesidad de EVALUAR, ya sabes tu EXPRESIÓN. Solo necesita su FUENTE o cómo está escrito, no necesariamente su valor. En cuanto a las banderas, solo necesita usar el argumento opcional de RegExp.

En mi situación, corro el problema de que ^ y $ se usan en varias expresiones ¡Estoy tratando de concatenar juntos! Esas expresiones son filtros gramaticales utilizados en todo el programa. Ahora quiero usar algunos de ellos juntos para manejar el caso de PREPOSICIONES. Puede que tenga que "cortar" las fuentes para eliminar el principio y el final ^ (y / o) $ :) Saludos, Alex.

Alex
fuente
Me gusta el uso de la propiedad de origen. Si usted, como yo, usa jslint, se molestará si hace algo como esto:var regex = "\.\..*"
Nils-o-mat
7

Problema Si el regexp contiene grupos de correspondencia inversa como \ 1.

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

Entonces simplemente contactando las fuentes no funcionará. De hecho, la combinación de los dos es:

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

La solución: primero contamos el número de grupos coincidentes en la primera expresión regular, luego para cada ficha de coincidencia inversa en el segundo, lo incrementamos por el número de grupos coincidentes.

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

Prueba:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true
Mikaël Mayer
fuente
2
Sí (aunque no lo modificaré aquí). Esta función es asociativa, por lo que puede utilizar el siguiente 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

Sería preferible utilizar la sintaxis literal tan a menudo como sea posible. Es más corto, más legible, y no necesita comillas de escape o contragolpes de doble escape. De "Patrones Javascript", Stoyan Stefanov 2010.

Pero usar New puede ser la única forma de concatenar.

Yo evitaría eval. No es seguro.

Jonathan Wright
fuente
1
Creo que las expresiones regulares complejas son más legibles cuando se dividen y comentan como en la pregunta.
Sam
3

Siempre que:

  • sabes lo que haces en tu expresión regular;
  • tienes muchas piezas de expresiones regulares para formar un patrón y usarán la misma bandera;
  • le resulta más legible separar sus pequeños fragmentos de patrones en una matriz;
  • también desea poder comentar cada parte para el próximo desarrollador o para usted mismo más adelante;
  • prefiere simplificar visualmente su expresión regular como en /this/glugar de new RegExp('this', 'g');
  • está bien que ensambles la expresión regular en un paso adicional en lugar de tenerla en una sola pieza desde el principio;

Entonces te gustaría escribir de esta manera:

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

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

En mi caso particular (un editor similar al código espejo), es mucho más fácil realizar una gran expresión regular, en lugar de muchos reemplazos como el siguiente, ya que cada vez que reemplazo con una etiqueta html para ajustar una expresión, el siguiente patrón será ser más difícil de apuntar sin afectar la etiqueta html en sí (y sin el buen aspecto que desafortunadamente no es compatible con 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
fuente
2

Podrías hacer algo como:

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

Los segmentos serían cadenas (en lugar de literales regex) pasadas como argumentos separados.

Neil Strain
fuente
1

No, la forma literal no es compatible. Tendrás que usar RegExp.

Aupajo
fuente
1

Utilice el constructor con 2 parámetros y evite el problema con el final '/':

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
fuente
1

Puede concat fuente de expresiones regulares tanto de la clase literal como de la clase RegExp:

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

la forma más fácil para mí sería concatenar las fuentes, ej .:

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

el valor c dará como resultado:

/ \ d + \ w + /

Daniel Aragão
fuente
-2

Prefiero usar eval('your expression')porque no agrega el /en cada extremo /que lo ='new RegExp'hace.

Praesagus
fuente