Come dividere una lunga espressione regolare in più righe in JavaScript?


138

Ho un'espressione regolare molto lunga, che desidero dividere in più righe nel mio codice JavaScript per mantenere ogni lunghezza di 80 caratteri in base alle regole di JSLint. È solo meglio per la lettura, penso. Ecco un esempio di modello:

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,}))$/;

4
Sembra che tu stia (cercando di) convalidare gli indirizzi e-mail. Perché non farlo semplicemente /\S+@\S+\.\S+/?
Bart Kiers,

1
Probabilmente dovresti cercare un modo per farlo senza un'espressione regolare o con più espressioni regolari più piccole. Sarebbe molto più leggibile di un'espressione regolare così a lungo. Se la tua espressione regolare è più di circa 20 caratteri, probabilmente c'è un modo migliore per farlo.
ForbesLindesay,

2
80 caratteri non sono ormai obsoleti con monitor ampi?
Oleg V. Volkov,

7
@ OlegV.Volkov No. Una persona potrebbe usare finestre divise in vim, un terminale virtuale in una sala server. È sbagliato supporre che tutti stiano codificando nella stessa vista come te. Inoltre, limitare le linee a 80 caratteri ti costringe a suddividere il codice in funzioni più piccole.
synic,

Bene, vedo certamente la tua motivazione per voler fare questo qui - una volta che questa regex è divisa su più righe, come dimostrato da Koolilnc, diventa immediatamente un perfetto esempio di codice leggibile e autocompattante. ¬_¬
Mark Amery,

Risposte:


115

È possibile convertirlo in una stringa e creare l'espressione chiamando 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(''));

Appunti:

  1. durante la conversione dell'espressione letterale in una stringa, è necessario evitare tutte le barre rovesciate poiché le barre rovesciate vengono consumate durante la valutazione di una stringa letterale . (Vedi il commento di Kayo per maggiori dettagli.)
  2. RegExp accetta i modificatori come secondo parametro

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

[ Aggiunta ES20xx (modello con tag)]

In ES20xx puoi usare modelli con tag . Vedi lo snippet.

Nota:

  • Svantaggio è che non è possibile utilizzare spazi bianchi pianura nella stringa di espressione regolare (utilizzare sempre \s, \s+, \s{1,x}, \t, \necc).

(() => {
  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);
})();


4
A new RegExpè un ottimo modo per le espressioni regolari multilinea. Invece di unire le matrici, puoi semplicemente usare un operatore di concatenazione di stringhe:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab,

43
Attenzione: un'espressione regolare lunga letterale potrebbe essere suddivisa in più righe usando la risposta sopra. Tuttavia ha bisogno di attenzione perché non puoi semplicemente copiare l'espressione regolare letterale (definita con //) e incollarla come argomento stringa nel costruttore RegExp. Questo perché i caratteri di barra rovesciata vengono consumati quando si valuta il valore letterale della stringa . Esempio: /Hey\sthere/non può essere sostituito da new RegExp("Hey\sthere"). Invece dovrebbe essere sostituito da new RegExp("Hey\\sthere")Nota la barra rovesciata extra! Quindi preferisco lasciare un lungo regex letterale su una lunga fila
Kayo,

5
Un modo ancora più chiaro per farlo è quello di creare variabili con nome che contengono sottosezioni significative e unire quelle come stringhe o in un array. Ciò ti consente di costruirlo RegExpin un modo che è molto più facile da capire.
Chris Krycho,

117

Estendendo la risposta @KooiInc, puoi evitare di sfuggire manualmente a ogni carattere speciale utilizzando la sourceproprietà RegExpdell'oggetto.

Esempio:

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 se vuoi evitare di ripetere la .sourceproprietà puoi farlo usando la Array.map()funzione:

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

In ES6 la funzione della mappa può essere ridotta a: .map(r => r.source)


3
Esattamente quello che stavo cercando, super pulito. Grazie!
Marian Zagoruiko,

10
Questo è davvero conveniente per aggiungere commenti a una lunga regexp. Tuttavia, è limitato dalla presenza di parentesi corrispondenti sulla stessa riga.
Nathan S. Watson-Haigh,

Sicuramente, questo! Super bello con la possibilità di commentare ogni sotto-regex.
GaryO,

Grazie, ha aiutato a mettere la sorgente nella funzione regex
Codice

Molto intelligente. Grazie, questa idea mi ha aiutato molto. Proprio come una nota a combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))combineRegex(/regex1/, /regex2/, ...)
margine

25

L'uso delle stringhe new RegExpè scomodo perché è necessario sfuggire a tutte le barre rovesciate. Puoi scrivere regex più piccole e concatenarle.

Dividiamo questa regex

/^foo(.*)\bar$/

Useremo una funzione per rendere le cose più belle in seguito

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

E ora facciamo rock

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

Dal momento che ha un costo, prova a creare il regex reale solo una volta e poi usalo.


Questo è molto bello - non solo non devi fare ulteriori scappamenti, ma mantieni anche la speciale evidenziazione della sintassi per i sotto-regex!
Quezak,

un avvertimento però: è necessario assicurarsi che i sotto-regex siano autonomi o avvolgerli ciascuno in un nuovo gruppo di parentesi. Esempio: multilineRegExp([/a|b/, /c|d])risulta /a|bc|d/, mentre intendevi (a|b)(c|d).
Quezak,

6

Ci sono buone risposte qui, ma per completezza qualcuno dovrebbe menzionare la caratteristica principale dell'ereditarietà di Javascript con la catena di prototipi . Qualcosa del genere illustra l'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


Questa è la migliore risposta qui.
parttimeturtle

6

Grazie al meraviglioso mondo dei letterali template è ora possibile scrivere regex grandi, multilinea, ben commentate e persino semanticamente annidate in 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 questo ora puoi scrivere regex in questo modo:

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

Uscite

/I'm a special regex{3}/

O che dire di multilinea?

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

Uscite hel, pulite!
"E se avessi davvero bisogno di cercare una nuova riga?", Bene, allora uso \nsciocco!
Lavorando su Firefox e Chrome.


Okay, "che ne dici di qualcosa di un po 'più complesso?"
Certo, ecco un pezzo di un oggetto JS che distrugge il parser su cui stavo lavorando :

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

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

E eseguirlo con una piccola demo?

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

Uscite riuscite

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

Nota la corretta acquisizione della stringa tra virgolette.
L'ho provato su Chrome e Firefox, funziona a meraviglia!

Se sei curioso puoi controllare cosa stavo facendo e la sua dimostrazione .
Anche se funziona solo su Chrome, perché Firefox non supporta i riferimenti indietro o i gruppi con nome. Quindi nota che l'esempio fornito in questa risposta è in realtà una versione sterilizzata e potrebbe essere facilmente indotto ad accettare stringhe non valide.


1
dovresti pensare di esportarlo come pacchetto NodeJS, è meraviglioso
rmobis

1
Anche se non l'ho mai fatto da solo, c'è un tutorial abbastanza completo qui: zellwk.com/blog/publish-to-npm . Suggerirei di controllare np, alla fine della pagina. Non l'ho mai usato, ma Sindre Sorhus è un mago con queste cose, quindi non lo lascerei perdere.
rmobis,

4

Nella regex sopra mancano alcune barre nere che non funzionano correttamente. Quindi, ho modificato il regex. Si prega di considerare questa regex che funziona al 99,99% per la convalida della posta elettronica.

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

1

Per evitare la matrice join, è anche possibile utilizzare la sintassi seguente:

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

0

Personalmente, sceglierei una regex meno complicata:

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

Certo, è meno preciso del tuo modello attuale, ma cosa stai cercando di realizzare? Stai cercando di rilevare errori accidentali che i tuoi utenti potrebbero inserire o sei preoccupato che i tuoi utenti possano provare a inserire indirizzi non validi? Se è il primo, sceglierei uno schema più semplice. Se è il secondo, una migliore verifica rispondendo a un'e-mail inviata a quell'indirizzo potrebbe essere un'opzione migliore.

Tuttavia, se si desidera utilizzare il modello corrente, sarebbe (IMO) più facile da leggere (e mantenere!) Costruendolo da sottotitoli più piccoli, come questo:

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 + ")$");

21
Downvoting - Sebbene i tuoi commenti sulla riduzione della complessità della regex siano validi, OP in particolare chiede come "dividere la regex lunga su più righe". Quindi, sebbene il tuo consiglio sia valido, è stato dato per motivi sbagliati. ad esempio, cambiare la logica aziendale per aggirare un linguaggio di programmazione. Inoltre, l'esempio di codice che hai fornito è abbastanza brutto.
sleepycal

4
@sleepycal Penso che Bart abbia risposto alla domanda. Vedi l'ultima sezione della sua risposta. Ha risposto alla domanda e ha fornito un'alternativa.
Nidhin David,

0

Puoi semplicemente usare l'operazione stringa.

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

0

Ho provato a migliorare la risposta di korun incapsulando tutto e implementando il supporto per la divisione di gruppi di acquisizione e set di caratteri, rendendo questo metodo molto più versatile.

Per utilizzare questo frammento devi chiamare la funzione variadica i combineRegexcui argomenti sono gli oggetti di espressione regolare che devi combinare. La sua implementazione può essere trovata in fondo.

I gruppi di acquisizione non possono essere suddivisi direttamente in questo modo, poiché lascerebbero alcune parti con una sola parentesi. Il tuo browser fallirebbe con un'eccezione.

Invece sto semplicemente passando il contenuto del gruppo di acquisizione all'interno di un array. Le parentesi vengono aggiunte automaticamente quando si combineRegexincontra un array.

Inoltre i quantificatori devono seguire qualcosa. Se per qualche motivo l'espressione regolare deve essere divisa davanti a un quantificatore, è necessario aggiungere una coppia di parentesi. Questi verranno rimossi automaticamente. Il punto è che un gruppo di acquisizione vuoto è piuttosto inutile e in questo modo i quantificatori hanno qualcosa a cui fare riferimento. Lo stesso metodo può essere utilizzato per cose come gruppi non acquisiti ( /(?:abc)/diventa [/()?:abc/]).

Questo è meglio spiegato usando un semplice esempio:

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

potrebbe diventare:

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

Se è necessario dividere i set di caratteri, è possibile utilizzare gli oggetti ( {"":[regex1, regex2, ...]}) anziché gli array ( [regex1, regex2, ...]). Il contenuto della chiave può essere qualsiasi, purché l'oggetto contenga una sola chiave. Nota che invece di ()te devi usare ]come inizio fittizio se il primo carattere potrebbe essere interpretato come quantificatore. Cioè /[+?]/diventa{"":[/]+?/]}

Ecco lo snippet e un esempio più 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
);


0

La grande risposta di Hashbrown mi ha portato sulla strada giusta. Ecco la mia versione, anch'essa ispirata a questo 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]);
}

Usalo in questo modo:

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*/
`

Per creare questo RegExpoggetto:

/(\d+)([a-z]{1,3})/i
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.