Como dividir uma expressão regular longa em várias linhas em JavaScript?

138

Eu tenho uma expressão regular muito longa, que desejo dividir em várias linhas no meu código JavaScript para manter cada comprimento de linha de 80 caracteres de acordo com as regras JSLint. É apenas melhor para leitura, eu acho. Aqui está uma amostra de padrão:

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
fonte
4
Parece que você está (tentando) validar endereços de email. Por que não simplesmente fazer /\S+@\S+\.\S+/?
Bart Kiers
1
Você provavelmente deve procurar uma maneira de fazer isso sem uma expressão regular ou com várias expressões regulares menores. Isso seria muito mais legível do que uma expressão regular por tanto tempo. Se sua expressão regular tiver mais de 20 caracteres, provavelmente existe uma maneira melhor de fazê-lo.
precisa saber é o seguinte
2
Hoje em dia, 80 caracteres não são obsoletos com monitores amplos?
Oleg V. Volkov
7
@ OlegV.Volkov Não. Uma pessoa pode estar usando janelas divididas no vim, um terminal virtual em uma sala de servidores. É errado supor que todos estarão codificando na mesma janela de visualização que você. Além disso, limitar suas linhas a 80 caracteres obriga a dividir seu código em funções menores.
synic 10/10/12
Bem, certamente vejo sua motivação para querer fazer isso aqui - uma vez que esse regex é dividido em várias linhas, como demonstrado pelo Koolilnc, ele imediatamente se torna um exemplo perfeito de código legível e auto-documentável. ¬_¬
Mark Amery

Respostas:

115

Você pode convertê-lo em uma sequência e criar a expressão chamando 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. ao converter a expressão literal em uma cadeia, você precisa escapar de todas as barras invertidas, pois as barras invertidas são consumidas ao avaliar um literal de cadeia . (Veja o comentário de Kayo para mais detalhes.)
  2. RegExp aceita modificadores como um segundo parâmetro

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

[ Adição ES20xx (modelo marcado)]

No ES20xx, você pode usar modelos com tags . Veja o trecho.

Nota:

  • Desvantagem aqui é que você não pode usar espaços em branco liso na string de expressão regular (sempre usar \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
fonte
4
A new RegExpé uma ótima maneira de expressões regulares de várias linhas. Em vez de ingressar em matrizes, você pode simplesmente usar um operador de concatenação de strings:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab
43
Cuidado: Uma literal de expressão regular longa pode ser dividida em várias linhas usando a resposta acima. No entanto, ele precisa de cuidados, porque você não pode simplesmente copiar a expressão regular literal (definida com //) e colá-la como o argumento de string no construtor RegExp. Isso ocorre porque os caracteres de barra invertida são consumidos ao avaliar a string literal . Exemplo: /Hey\sthere/não pode ser substituído por new RegExp("Hey\sthere"). Em vez disso, deve ser substituído por new RegExp("Hey\\sthere")Observe a barra invertida extra! Por isso prefiro deixar apenas um longo literal regex em uma linha longa
Kayo
5
Uma maneira ainda mais clara de fazer isso é criar variáveis ​​nomeadas contendo subseções significativas e juntá- las como strings ou em uma matriz. Isso permite construir de RegExpuma maneira que é muito mais fácil de entender.
precisa saber é o seguinte
117

Estendendo a resposta do @KooiInc, você pode evitar o escape manual de todos os caracteres especiais usando a sourcepropriedade do RegExpobjeto.

Exemplo:

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

ou se você quiser evitar repetir a .sourcepropriedade, poderá fazê-lo usando a Array.map()função:

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

No ES6, a função de mapa pode ser reduzida para: .map(r => r.source)

Korun
fonte
3
Exatamente o que eu estava procurando, super limpo. Obrigado!
Marian Zagoruiko
10
Isso é realmente conveniente para adicionar comentários a uma longa regexp. No entanto, é limitado por ter parênteses correspondentes na mesma linha.
Nathan S. Watson-Haigh
Definitivamente, isso! Super legal com a capacidade de comentar cada sub-regex.
GaryO 26/03/19
Obrigado, ajudou a colocar a fonte na função regex
Código
Muito esperto. Obrigado, essa ideia me ajudou muito. Apenas como uma observação lateral: Encapsulei a coisa toda em uma função para torná-la ainda mais limpa: combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))Uso:combineRegex(/regex1/, /regex2/, ...)
Scindix
25

O uso de strings new RegExpé estranho porque você deve escapar de todas as barras invertidas. Você pode escrever regexes menores e concatená-las.

Vamos dividir esse regex

/^foo(.*)\bar$/

Usaremos uma função para tornar as coisas mais bonitas mais tarde

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

E agora vamos agitar

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

Como tem um custo, tente criar o regex real apenas uma vez e use-o.

Riccardo Galli
fonte
Isso é muito legal - não apenas você não precisa fazer escapes adicionais, mas também mantém o destaque especial da sintaxe para os sub-regexes!
quezak 24/07
uma ressalva: você precisa garantir que suas sub-expressões regulares sejam independentes ou agrupar cada uma em um novo grupo de colchetes. Exemplo: multilineRegExp([/a|b/, /c|d])resulta em /a|bc|d/, enquanto você quis dizer (a|b)(c|d).
quezak 24/07
6

Há boas respostas aqui, mas, para ser completo, alguém deve mencionar o principal recurso de herança do Javascript com a cadeia de protótipos . Algo assim ilustra a idéia:

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
fonte
Esta é a melhor resposta aqui.
parttimeturtle 6/04
5

Graças ao maravilhoso mundo dos literais de modelo, agora você pode escrever expressões grandes, com várias linhas, bem comentadas e até semanticamente aninhadas no 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])
    ))
);

Usando isso, agora você pode escrever regexes como este:

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

Saídas

/I'm a special regex{3}/

Ou o que dizer de multilinhas?

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

Saídas hel, arrumado!
"E se eu precisar realmente pesquisar uma nova linha?", Então use \nbobo!
Trabalhando no meu Firefox e Chrome.


Ok, "que tal algo um pouco mais complexo?"
Claro, aqui está uma parte de um analisador de JS de destruição de objetos em que eu estava trabalhando :

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*[,}{]
    ).*)
$`

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

E executando-o com uma pequena demonstração?

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

Resultados com sucesso

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

Observe a captura bem-sucedida da sequência de caracteres citada.
Eu testei no Chrome e Firefox, funciona um prazer!

Se curioso, você pode conferir o que eu estava fazendo e sua demonstração .
Embora só funcione no Chrome, porque o Firefox não suporta referências anteriores ou grupos nomeados. Portanto, observe que o exemplo dado nesta resposta é na verdade uma versão castrada e pode ser facilmente enganado para aceitar cadeias inválidas.

Hashbrown
fonte
1
você deve pensar em exportar isso como um pacote NodeJS, é maravilhoso
rmobis
1
Embora eu nunca tenha feito isso sozinho, há um tutorial bastante completo aqui: zellwk.com/blog/publish-to-npm . Sugiro verificar np, no final da página. Eu nunca o usei, mas Sindre Sorhus é um mágico com essas coisas, então eu não deixaria passar.
rmobis
4

O regex acima está faltando algumas barras pretas que não estão funcionando corretamente. Então, editei o regex. Por favor, considere este regex que funciona 99,99% para validação de email.

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

Para evitar a matriz join, você também pode usar a seguinte sintaxe:

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
fonte
0

Pessoalmente, eu usaria uma regex menos complicada:

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

Claro, é menos preciso que o seu padrão atual, mas o que você está tentando realizar? Você está tentando detectar erros acidentais que seus usuários podem digitar ou está preocupado que eles possam tentar digitar endereços inválidos? Se for o primeiro, eu adotaria um padrão mais fácil. Nesse caso, alguma verificação ao responder a um email enviado para esse endereço pode ser uma opção melhor.

No entanto, se você quiser usar seu padrão atual, seria (IMO) mais fácil de ler (e manter!) Construindo-o a partir de sub-padrões menores, 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
fonte
21
Voto negativo - embora seus comentários sobre a redução da complexidade da regex sejam válidos, o OP está perguntando especificamente como "dividir a regex longa em várias linhas". Portanto, embora seu conselho seja válido, ele foi dado pelos motivos errados. por exemplo, mudar a lógica de negócios para contornar uma linguagem de programação. Além disso, o exemplo de código que você deu é bastante feio.
sleepycal
4
@sleepycal Acho que Bart respondeu à pergunta. Veja a última seção de sua resposta. Ele respondeu à pergunta e também deu uma alternativa.
Nidhin David
0

Você pode simplesmente usar a operação de string.

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
fonte
0

Tentei melhorar a resposta do korun encapsulando tudo e implementando o suporte para dividir grupos de captura e conjuntos de caracteres - tornando esse método muito mais versátil.

Para usar esse snippet, você precisa chamar a função variada combineRegexcujos argumentos são os objetos de expressão regular que você precisa combinar. Sua implementação pode ser encontrada na parte inferior.

Os grupos de captura não podem ser divididos diretamente dessa maneira, pois isso deixaria algumas partes com apenas um parêntese. Seu navegador falharia com uma exceção.

Em vez disso, estou simplesmente passando o conteúdo do grupo de captura dentro de uma matriz. Os parênteses são adicionados automaticamente quando combineRegexencontra uma matriz.

Além disso, os quantificadores precisam seguir algo. Se, por algum motivo, a expressão regular precisar ser dividida na frente de um quantificador, você precisará adicionar um par de parênteses. Estes serão removidos automaticamente. O ponto é que um grupo de captura vazio é bastante inútil e, dessa forma, os quantificadores têm algo a que se referir. O mesmo método pode ser usado para coisas como grupos que não capturam ( /(?:abc)/torna-se [/()?:abc/]).

Isso é melhor explicado usando um exemplo simples:

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

se tornaria:

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

Se você precisar dividir conjuntos de caracteres, poderá usar objetos ( {"":[regex1, regex2, ...]}) em vez de matrizes ( [regex1, regex2, ...]). O conteúdo da chave pode ser qualquer coisa, desde que o objeto contenha apenas uma chave. Observe que, em vez de ()você ter que usar ]como início fictício, se o primeiro caractere puder ser interpretado como quantificador. Ou seja, /[+?]/torna-se{"":[/]+?/]}

Aqui está o trecho e um exemplo mais 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
fonte
0

A ótima resposta de @ Hashbrown me colocou no caminho certo. Aqui está a minha versão, também inspirada neste 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]);
}

Use-o assim:

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 criar este RegExpobjeto:

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