¿Cómo dividir una expresión regular larga en varias líneas en JavaScript?

138

Tengo una expresión regular muy larga, que deseo dividir en varias líneas en mi código JavaScript para mantener cada longitud de línea 80 caracteres de acuerdo con las reglas de JSLint. Es mejor leer, creo. Aquí hay una muestra de patrón:

var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
Nik Sumeiko
fuente
44
Parece que está (intentando) validar las direcciones de correo electrónico. ¿Por qué no simplemente hacer /\S+@\S+\.\S+/?
Bart Kiers
1
Probablemente debería buscar encontrar una manera de hacerlo sin una expresión regular, o con múltiples expresiones regulares más pequeñas. Eso sería mucho más legible que una expresión regular tanto tiempo. Si su expresión regular tiene más de 20 caracteres, probablemente haya una mejor manera de hacerlo.
ForbesLindesay
2
¿No son 80 caracteres obsoletos hoy en día con monitores anchos?
Oleg V. Volkov
77
@ OlegV.Volkov No. Una persona podría estar usando ventanas divididas en vim, una terminal virtual en una sala de servidores. Es incorrecto suponer que todos codificarán en la misma ventana gráfica que usted. Además, limitar sus líneas a 80 caracteres lo obliga a dividir su código en funciones más pequeñas.
synic
Bueno, ciertamente veo su motivación para querer hacer esto aquí: una vez que esta expresión regular se divide en varias líneas, como lo demuestra Koolilnc, se convierte inmediatamente en un ejemplo perfecto de código legible y autodocumentado. ¬_¬
Mark Amery

Respuestas:

115

Puede convertirlo en una cadena y crear la expresión llamando a new RegExp():

var myRE = new RegExp (['^(([^<>()[\]\\.,;:\\s@\"]+(\\.[^<>(),[\]\\.,;:\\s@\"]+)*)',
                        '|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                        '[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\\.)+',
                        '[a-zA-Z]{2,}))$'].join(''));

Notas:

  1. al convertir la expresión literal en una cadena, debe escapar de todas las barras invertidas, ya que las barras invertidas se consumen al evaluar una cadena literal . (Vea el comentario de Kayo para más detalles).
  2. RegExp acepta modificadores como segundo parámetro

    /regex/g => new RegExp('regex', 'g')

[ Adición ES20xx (plantilla etiquetada)]

En ES20xx puede usar plantillas etiquetadas . Ver el fragmento.

Nota:

  • La desventaja aquí es que no se puede utilizar espacios en blanco normal en la cadena de expresión regular (utilizar siempre \s, \s+, \s{1,x}, \t, \netc).

(() => {
  const createRegExp = (str, opts) => 
    new RegExp(str.raw[0].replace(/\s/gm, ""), opts || "");
  const yourRE = createRegExp`
    ^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|
    (\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
    (([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`;
  console.log(yourRE);
  const anotherLongRE = createRegExp`
    (\byyyy\b)|(\bm\b)|(\bd\b)|(\bh\b)|(\bmi\b)|(\bs\b)|(\bms\b)|
    (\bwd\b)|(\bmm\b)|(\bdd\b)|(\bhh\b)|(\bMI\b)|(\bS\b)|(\bMS\b)|
    (\bM\b)|(\bMM\b)|(\bdow\b)|(\bDOW\b)
    ${"gi"}`;
  console.log(anotherLongRE);
})();

KooiInc
fuente
44
A new RegExpes una excelente forma de expresiones regulares multilínea. En lugar de unir matrices, puede usar un operador de concatenación de cadenas:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab
43
Precaución: una expresión regular larga literal podría dividirse en varias líneas utilizando la respuesta anterior. Sin embargo, necesita cuidado porque no puede simplemente copiar el literal de expresión regular (definido con //) y pegarlo como argumento de cadena en el constructor RegExp. Esto se debe a que los caracteres de barra diagonal inversa se consumen al evaluar el literal de cadena . Ejemplo: /Hey\sthere/no se puede reemplazar por new RegExp("Hey\sthere"). En su lugar, debe reemplazarse por new RegExp("Hey\\sthere")Note la barra invertida adicional. Por lo tanto, prefiero dejar un literal regex largo en una línea larga
Kayo
55
Una forma aún más clara de hacer esto es crear variables de llamada sostiene subsecciones significativas, y uniéndose a los que como cadenas o en una matriz. Eso te permite construir RegExpde una manera que es mucho más fácil de entender.
Chris Krycho
117

Extendiendo la respuesta @KooiInc, puede evitar escapar manualmente de cada carácter especial utilizando la sourcepropiedad del RegExpobjeto.

Ejemplo:

var urlRegex= new RegExp(''
  + /(?:(?:(https?|ftp):)?\/\/)/.source     // protocol
  + /(?:([^:\n\r]+):([^@\n\r]+)@)?/.source  // user:pass
  + /(?:(?:www\.)?([^\/\n\r]+))/.source     // domain
  + /(\/[^?\n\r]+)?/.source                 // request
  + /(\?[^#\n\r]*)?/.source                 // query
  + /(#?[^\n\r]*)?/.source                  // anchor
);

o si desea evitar repetir la .sourcepropiedad, puede hacerlo usando la Array.map()función:

var urlRegex= new RegExp([
  /(?:(?:(https?|ftp):)?\/\/)/      // protocol
  ,/(?:([^:\n\r]+):([^@\n\r]+)@)?/  // user:pass
  ,/(?:(?:www\.)?([^\/\n\r]+))/     // domain
  ,/(\/[^?\n\r]+)?/                 // request
  ,/(\?[^#\n\r]*)?/                 // query
  ,/(#?[^\n\r]*)?/                  // anchor
].map(function(r) {return r.source}).join(''));

En ES6, la función de mapa se puede reducir a: .map(r => r.source)

korun
fuente
3
Exactamente lo que estaba buscando, súper limpio. ¡Gracias!
Marian Zagoruiko
10
Esto es realmente conveniente para agregar comentarios a una expresión regular larga. Sin embargo, está limitado por tener paréntesis coincidentes en la misma línea.
Nathan S. Watson-Haigh
Definitivamente, esto! Súper agradable con la capacidad de comentar cada sub-expresión regular.
GaryO
Gracias, ayudó a poner la fuente en la función regex
Código
Muy inteligente. Gracias, esta idea me ayudó mucho. Como nota al margen: encapsulé todo en una función para hacerlo aún más limpio: combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))Uso:combineRegex(/regex1/, /regex2/, ...)
Scindix
25

Usar cadenas new RegExpes incómodo porque debes escapar de todas las barras invertidas. Puede escribir expresiones regulares más pequeñas y concatenarlas.

Vamos a dividir esta expresión regular

/^foo(.*)\bar$/

Usaremos una función para hacer las cosas más bellas más tarde

function multilineRegExp(regs, options) {
    return new RegExp(regs.map(
        function(reg){ return reg.source; }
    ).join(''), options);
}

Y ahora vamos a rockear

var r = multilineRegExp([
     /^foo/,  // we can add comments too
     /(.*)/,
     /\bar$/
]);

Como tiene un costo, intente construir la expresión regular real solo una vez y luego úsela.

Riccardo Galli
fuente
Esto es muy bueno, no solo no tiene que hacer escapes adicionales, sino que también mantiene el resaltado especial de sintaxis para las sub-expresiones regulares.
Quezak
Sin embargo, una advertencia: debe asegurarse de que sus sub-expresiones regulares sean autónomas o envolver cada una en un nuevo grupo de paréntesis. Ejemplo: multilineRegExp([/a|b/, /c|d])resultados en /a|bc|d/, mientras que quiso decir (a|b)(c|d).
Quezak
6

Aquí hay buenas respuestas, pero para completar, alguien debería mencionar la característica principal de herencia de Javascript con la cadena de prototipos . Algo como esto ilustra la idea:

RegExp.prototype.append = function(re) {
  return new RegExp(this.source + re.source, this.flags);
};

let regex = /[a-z]/g
.append(/[A-Z]/)
.append(/[0-9]/);

console.log(regex); //=> /[a-z][A-Z][0-9]/g

James Donohue
fuente
Esta es la mejor respuesta aquí.
parttimeturtle
6

Gracias al maravilloso mundo de los literales de plantilla , ahora puede escribir expresiones regulares grandes, de varias líneas, bien comentadas e incluso semánticamente anidadas en ES6.

//build regexes without worrying about
// - double-backslashing
// - adding whitespace for readability
// - adding in comments
let clean = (piece) => (piece
    .replace(/((^|\n)(?:[^\/\\]|\/[^*\/]|\\.)*?)\s*\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$1')
    .replace(/((^|\n)(?:[^\/\\]|\/[^\/]|\\.)*?)\s*\/\/[^\n]*/g, '$1')
    .replace(/\n\s*/g, '')
);
window.regex = ({raw}, ...interpolations) => (
    new RegExp(interpolations.reduce(
        (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
        clean(raw[0])
    ))
);

Con esto, ahora puede escribir expresiones regulares como esta:

let re = regex`I'm a special regex{3} //with a comment!`;

Salidas

/I'm a special regex{3}/

¿O qué hay de multilínea?

'123hello'
    .match(regex`
        //so this is a regex

        //here I am matching some numbers
        (\d+)

        //Oh! See how I didn't need to double backslash that \d?
        ([a-z]{1,3}) /*note to self, this is group #2*/
    `)
    [2]

Salidas hel, ordenadas!
"¿Qué pasa si realmente necesito buscar una nueva línea?", Bueno, ¡entonces usa \ntonto!
Trabajando en mi Firefox y Chrome.


De acuerdo, "¿qué tal algo un poco más complejo?"
Claro, aquí hay una pieza de un objeto que desestructura el analizador JS en el que estaba trabajando :

regex`^\s*
    (
        //closing the object
        (\})|

        //starting from open or comma you can...
        (?:[,{]\s*)(?:
            //have a rest operator
            (\.\.\.)
            |
            //have a property key
            (
                //a non-negative integer
                \b\d+\b
                |
                //any unencapsulated string of the following
                \b[A-Za-z$_][\w$]*\b
                |
                //a quoted string
                //this is #5!
                ("|')(?:
                    //that contains any non-escape, non-quote character
                    (?!\5|\\).
                    |
                    //or any escape sequence
                    (?:\\.)
                //finished by the quote
                )*\5
            )
            //after a property key, we can go inside
            \s*(:|)
      |
      \s*(?={)
        )
    )
    ((?:
        //after closing we expect either
        // - the parent's comma/close,
        // - or the end of the string
        \s*(?:[,}\]=]|$)
        |
        //after the rest operator we expect the close
        \s*\}
        |
        //after diving into a key we expect that object to open
        \s*[{[:]
        |
        //otherwise we saw only a key, we now expect a comma or close
        \s*[,}{]
    ).*)
$`

Sale /^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

¿Y ejecutarlo con una pequeña demostración?

let input = '{why, hello, there, "you   huge \\"", 17, {big,smelly}}';
for (
    let parsed;
    parsed = input.match(r);
    input = parsed[parsed.length - 1]
) console.log(parsed[1]);

Salidas exitosas

{why
, hello
, there
, "you   huge \""
, 17
,
{big
,smelly
}
}

Tenga en cuenta la captura exitosa de la cadena citada.
Lo probé en Chrome y Firefox, ¡funciona genial!

Si tienes curiosidad, puedes ver lo que estaba haciendo y su demostración .
Aunque solo funciona en Chrome, porque Firefox no admite referencias o grupos con nombre. Entonces, tenga en cuenta que el ejemplo dado en esta respuesta es en realidad una versión neutralizada y puede ser engañado fácilmente para aceptar cadenas no válidas.

Hashbrown
fuente
1
deberías pensar en exportar esto como un paquete NodeJS, es maravilloso
rmobis
1
Aunque nunca lo he hecho yo mismo, hay un tutorial bastante completo aquí: zellwk.com/blog/publish-to-npm . Sugeriría verificar np, al final de la página. Nunca lo he usado, pero Sindre Sorhus es un mago con estas cosas, así que no lo dejaría pasar.
rmobis
4

A la expresión regular anterior le faltan algunas barras negras que no funcionan correctamente. Entonces, edité la expresión regular. Considere esta expresión regular que funciona 99.99% para la validación de correo electrónico.

let EMAIL_REGEXP = 
new RegExp (['^(([^<>()[\\]\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\.,;:\\s@\"]+)*)',
                    '|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                    '[0-9]{1,3}\])|(([a-zA-Z\\-0-9]+\\.)+',
                    '[a-zA-Z]{2,}))$'].join(''));
Anvesh Reddy
fuente
1

Para evitar la matriz join, también puede usar la siguiente sintaxis:

var pattern = new RegExp('^(([^<>()[\]\\.,;:\s@\"]+' +
  '(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@' +
  '((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' +
  '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');
andreasonny83
fuente
0

Personalmente, optaría por una expresión regular menos complicada:

/\S+@\S+\.\S+/

Claro, es menos preciso que su patrón actual, pero ¿qué está tratando de lograr? ¿Está tratando de detectar errores accidentales que pueden ingresar sus usuarios, o le preocupa que sus usuarios puedan intentar ingresar direcciones no válidas? Si es el primero, elegiría un patrón más fácil. Si es lo último, alguna verificación al responder a un correo electrónico enviado a esa dirección podría ser una mejor opción.

Sin embargo, si desea utilizar su patrón actual, sería (IMO) más fácil de leer (¡y mantener!) Construyéndolo a partir de subpatrones más pequeños, como este:

var box1 = "([^<>()[\]\\\\.,;:\s@\"]+(\\.[^<>()[\\]\\\\.,;:\s@\"]+)*)";
var box2 = "(\".+\")";

var host1 = "(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])";
var host2 = "(([a-zA-Z\-0-9]+\\.)+[a-zA-Z]{2,})";

var regex = new RegExp("^(" + box1 + "|" + box2 + ")@(" + host1 + "|" + host2 + ")$");
Bart Kiers
fuente
21
Votación a favor: aunque sus comentarios sobre la reducción de la complejidad de expresiones regulares son válidos, OP pregunta específicamente cómo "dividir expresiones regulares largas en varias líneas". Entonces, aunque su consejo es válido, se ha dado por razones equivocadas. por ejemplo, cambiar la lógica de negocios para evitar un lenguaje de programación Además, el ejemplo de código que diste es bastante feo.
Sleepycal
44
@sleepycal Creo que Bart ha respondido la pregunta. Vea la última sección de su respuesta. Él ha respondido la pregunta y le ha dado una alternativa.
Nidhin David
0

Simplemente puede usar la operación de cadena.

var pattenString = "^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|"+
"(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|"+
"(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$";
var patten = new RegExp(pattenString);
Mubeena
fuente
0

Intenté mejorar la respuesta de korun encapsulando todo e implementando soporte para dividir grupos de captura y conjuntos de caracteres, haciendo que este método sea mucho más versátil.

Para usar este fragmento, debe llamar a la función variadic combineRegexcuyos argumentos son los objetos de expresión regular que necesita combinar. Su implementación se puede encontrar en la parte inferior.

Sin embargo, los grupos de captura no se pueden dividir directamente de esa manera, ya que dejaría algunas partes con solo un paréntesis. Su navegador fallará con una excepción.

En cambio, simplemente paso el contenido del grupo de captura dentro de una matriz. Los paréntesis se agregan automáticamente cuando se combineRegexencuentra con una matriz.

Además, los cuantificadores necesitan seguir algo. Si por alguna razón la expresión regular necesita dividirse frente a un cuantificador, debe agregar un par de paréntesis. Estos serán eliminados automáticamente. El punto es que un grupo de captura vacío es bastante inútil y de esta manera los cuantificadores tienen algo a lo que referirse. Se puede usar el mismo método para cosas como grupos sin captura (se /(?:abc)/convierte [/()?:abc/]).

Esto se explica mejor con un ejemplo simple:

var regex = /abcd(efghi)+jkl/;

se convertiría:

var regex = combineRegex(
    /ab/,
    /cd/,
    [
        /ef/,
        /ghi/
    ],
    /()+jkl/    // Note the added '()' in front of '+'
);

Si debe dividir conjuntos de caracteres, puede usar objetos ( {"":[regex1, regex2, ...]}) en lugar de matrices ( [regex1, regex2, ...]). El contenido de la clave puede ser cualquier cosa siempre que el objeto solo contenga una clave. Tenga en cuenta que, en lugar de hacerlo (), debe usarlo ]como principio ficticio si el primer carácter puede interpretarse como cuantificador. Es decir, se /[+?]/convierte{"":[/]+?/]}

Aquí está el fragmento y un ejemplo más completo:

function combineRegexStr(dummy, ...regex)
{
    return regex.map(r => {
        if(Array.isArray(r))
            return "("+combineRegexStr(dummy, ...r).replace(dummy, "")+")";
        else if(Object.getPrototypeOf(r) === Object.getPrototypeOf({}))
            return "["+combineRegexStr(/^\]/, ...(Object.entries(r)[0][1]))+"]";
        else 
            return r.source.replace(dummy, "");
    }).join("");
}
function combineRegex(...regex)
{
    return new RegExp(combineRegexStr(/^\(\)/, ...regex));
}

//Usage:
//Original:
console.log(/abcd(?:ef[+A-Z0-9]gh)+$/.source);
//Same as:
console.log(
  combineRegex(
    /ab/,
    /cd/,
    [
      /()?:ef/,
      {"": [/]+A-Z/, /0-9/]},
      /gh/
    ],
    /()+$/
  ).source
);

Scindix
fuente
0

La gran respuesta de @Hashbrown me llevó por el buen camino. Aquí está mi versión, también inspirada en este blog .

function regexp(...args) {
  function cleanup(string) {
    // remove whitespace, single and multi-line comments
    return string.replace(/\s+|\/\/.*|\/\*[\s\S]*?\*\//g, '');
  }

  function escape(string) {
    // escape regular expression
    return string.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
  }

  function create(flags, strings, ...values) {
    let pattern = '';
    for (let i = 0; i < values.length; ++i) {
      pattern += cleanup(strings.raw[i]);  // strings are cleaned up
      pattern += escape(values[i]);        // values are escaped
    }
    pattern += cleanup(strings.raw[values.length]);
    return RegExp(pattern, flags);
  }

  if (Array.isArray(args[0])) {
    // used as a template tag (no flags)
    return create('', ...args);
  }

  // used as a function (with flags)
  return create.bind(void 0, args[0]);
}

Úselo así:

regexp('i')`
  //so this is a regex

  //here I am matching some numbers
  (\d+)

  //Oh! See how I didn't need to double backslash that \d?
  ([a-z]{1,3}) /*note to self, this is group #2*/
`

Para crear este RegExpobjeto:

/(\d+)([a-z]{1,3})/i
Nuno Cruces
fuente