Bagaimana cara membagi ekspresi reguler yang panjang menjadi beberapa baris di JavaScript?

142

Saya memiliki ekspresi reguler yang sangat panjang, yang ingin saya bagi menjadi beberapa baris dalam kode JavaScript saya agar setiap baris memiliki panjang 80 karakter sesuai dengan aturan JSLint. Itu lebih baik untuk membaca, saya pikir. Berikut contoh polanya:

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
sumber
4
Sepertinya Anda (mencoba) memvalidasi alamat email. Mengapa tidak melakukannya /\S+@\S+\.\S+/?
Bart Kiers
1
Anda mungkin harus mencari cara untuk melakukannya tanpa ekspresi reguler, atau dengan beberapa ekspresi reguler yang lebih kecil. Itu akan jauh lebih mudah dibaca daripada ekspresi reguler selama itu. Jika ekspresi reguler Anda lebih dari sekitar 20 karakter, mungkin ada cara yang lebih baik untuk melakukannya.
ForbesLindesay
2
Bukankah 80 karakter sudah usang saat ini dengan monitor lebar?
Oleg V. Volkov
7
@ OlegV.Volkov Tidak. Seseorang dapat menggunakan jendela terpisah di vim, terminal virtual di ruang server. Salah jika menganggap semua orang akan membuat kode di viewport yang sama dengan Anda. Selain itu, membatasi baris hingga 80 karakter akan memaksa Anda untuk memecah kode menjadi fungsi yang lebih kecil.
sinis
Baiklah, saya pasti melihat motivasi Anda untuk ingin melakukan ini di sini - setelah regex ini dibagi menjadi beberapa baris, seperti yang ditunjukkan oleh Koolilnc, ini segera menjadi contoh sempurna dari kode yang dapat didokumentasikan sendiri dan dapat dibaca. ¬_¬
Mark Amery

Jawaban:

119

Anda dapat mengubahnya menjadi string dan membuat ekspresi dengan memanggil 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(''));

Catatan:

  1. saat mengonversi ekspresi literal ke string, Anda perlu mengosongkan semua garis miring terbalik karena garis miring terbalik digunakan saat mengevaluasi literal string . (Lihat komentar Kayo untuk detail lebih lanjut.)
  2. RegExp menerima pengubah sebagai parameter kedua

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

[ Tambahan ES20xx (template dengan tag)]

Di ES20xx Anda dapat menggunakan template yang diberi tag . Lihat cuplikannya.

catatan:

  • Kelemahan di sini adalah bahwa Anda tidak dapat menggunakan spasi polos dalam ekspresi string biasa (selalu menggunakan \s, \s+, \s{1,x}, \t, \ndll).

(() => {
  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
sumber
4
A new RegExpadalah cara yang bagus untuk ekspresi reguler multiline. Alih-alih menggabungkan array, Anda bisa menggunakan operator penggabungan string:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab
44
Perhatian: Literal ekspresi reguler yang panjang dapat dibagi menjadi beberapa baris menggunakan jawaban di atas. Namun itu perlu perhatian karena Anda tidak bisa begitu saja menyalin literal ekspresi reguler (didefinisikan dengan //) dan menempelkannya sebagai argumen string ke konstruktor RegExp. Ini karena karakter garis miring terbalik dikonsumsi saat mengevaluasi string literal . Contoh: /Hey\sthere/tidak bisa diganti dengan new RegExp("Hey\sthere"). Sebaliknya itu harus diganti dengan new RegExp("Hey\\sthere")catatan garis miring terbalik ekstra! Oleh karena itu saya lebih suka meninggalkan literal regex panjang pada satu baris panjang
Kayo
5
Cara yang lebih jelas untuk melakukan ini adalah untuk menciptakan bernama variabel memegang subbagian bermakna, dan bergabung mereka sebagai string atau array. Itu memungkinkan Anda membangun dengan RegExpcara yang lebih mudah dipahami.
Chris Krycho
121

Memperluas jawaban @KooiInc, Anda bisa menghindari pelolosan setiap karakter khusus secara manual dengan menggunakan sourceproperti RegExpobjek.

Contoh:

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

atau jika Anda ingin menghindari pengulangan .sourceproperti, Anda dapat melakukannya menggunakan Array.map()fungsi:

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

Di ES6, fungsi peta dapat dikurangi menjadi: .map(r => r.source)

korun
sumber
3
Persis seperti yang saya cari, sangat bersih. Terima kasih!
Marian Zagoruiko
10
Ini benar-benar nyaman untuk menambahkan komentar ke regexp panjang. Namun, itu dibatasi dengan memiliki tanda kurung yang cocok di baris yang sama.
Nathan S. Watson-Haigh
Pastinya, ini! Sangat bagus dengan kemampuan mengomentari setiap sub-ekspresi reguler.
GaryO
Terima kasih, ini membantu menempatkan sumber dalam fungsi regex
Kode
Sangat pintar. Terima kasih, ide ini banyak membantu saya. Sekadar catatan: Saya merangkum semuanya dalam sebuah fungsi untuk membuatnya lebih bersih: combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))Penggunaan:combineRegex(/regex1/, /regex2/, ...)
Scindix
26

Menggunakan string dalam new RegExpcanggung karena Anda harus menghindari semua garis miring terbalik. Anda dapat menulis regex yang lebih kecil dan menggabungkannya.

Mari kita pisahkan regex ini

/^foo(.*)\bar$/

Kami akan menggunakan fungsi untuk membuat segalanya lebih indah nanti

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

Dan sekarang mari bergoyang

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

Karena ada biaya, coba buat regex asli hanya sekali, lalu gunakan.

Riccardo Galli
sumber
Ini sangat keren - tidak hanya Anda tidak perlu melakukan pelolosan tambahan, tetapi juga Anda tetap menggunakan sorotan sintaks khusus untuk sub-ekspresi reguler!
quezak
satu peringatan: Anda perlu memastikan sub-ekspresi reguler Anda berdiri sendiri, atau membungkus masing-masing dalam grup braket baru. Contoh: multilineRegExp([/a|b/, /c|d])menghasilkan /a|bc|d/, sementara yang Anda maksud (a|b)(c|d).
quezak
7

Berkat dunia literal template yang menakjubkan, Anda sekarang dapat menulis regex bersarang besar, multi-baris, memiliki komentar yang baik, dan bahkan semantik di 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])
    ))
);

Dengan menggunakan ini, Anda sekarang dapat menulis ekspresi reguler seperti ini:

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

Keluaran

/I'm a special regex{3}/

Atau bagaimana dengan multiline?

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

Hasil hel, rapi!
"Bagaimana jika saya benar-benar perlu mencari baris baru?", Baiklah gunakan \nkonyol!
Bekerja di Firefox dan Chrome saya.


Oke, "bagaimana dengan sesuatu yang sedikit lebih rumit?"
Tentu, ini adalah bagian dari objek yang merusak parser JS yang saya kerjakan :

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

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

Dan menjalankannya dengan sedikit 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]);

Keluaran berhasil

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

Perhatikan keberhasilan menangkap string yang dikutip.
Saya mengujinya di Chrome dan Firefox, berhasil!

Jika penasaran, Anda dapat melihat apa yang saya lakukan , dan peragaannya .
Meskipun hanya berfungsi di Chrome, karena Firefox tidak mendukung referensi latar atau grup bernama. Jadi perhatikan bahwa contoh yang diberikan dalam jawaban ini sebenarnya adalah versi yang disterilkan dan mungkin dengan mudah ditipu untuk menerima string yang tidak valid.

Hashbrown
sumber
2
Anda harus berpikir untuk mengekspor ini sebagai paket NodeJS, ini luar biasa
rmobis
1
Meskipun saya belum pernah melakukannya sendiri, ada tutorial yang cukup lengkap di sini: zellwk.com/blog/publish-to-npm . Saya sarankan untuk memeriksa np, di akhir halaman. Saya tidak pernah menggunakannya, tetapi Sindre Sorhus adalah seorang pesulap dengan hal-hal ini, jadi saya tidak akan melewatkannya.
rmobis
6

Ada jawaban yang bagus di sini, tetapi untuk kelengkapan seseorang harus menyebutkan fitur inti Javascript dari pewarisan dengan rantai prototipe . Sesuatu seperti ini menggambarkan idenya:

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
sumber
Ini adalah jawaban terbaik disini.
parttimeturtle
4

Regex di atas kehilangan beberapa garis miring hitam yang tidak berfungsi dengan benar. Jadi, saya mengedit regex. Harap pertimbangkan regex ini yang berfungsi 99,99% untuk validasi 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
sumber
1

Untuk menghindari Array join, Anda juga dapat menggunakan sintaks berikut:

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

Secara pribadi, saya akan menggunakan regex yang tidak terlalu rumit:

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

Tentu, ini kurang akurat dari pola Anda saat ini, tetapi apa yang ingin Anda capai? Apakah Anda mencoba menemukan kesalahan yang tidak disengaja yang mungkin masuk pengguna, atau Anda khawatir pengguna Anda mungkin mencoba memasukkan alamat yang tidak valid? Jika itu yang pertama, saya akan memilih pola yang lebih mudah. Jika yang terakhir, beberapa verifikasi dengan menanggapi email yang dikirim ke alamat itu mungkin merupakan pilihan yang lebih baik.

Namun, jika Anda ingin menggunakan pola Anda saat ini, akan (IMO) lebih mudah dibaca (dan dipelihara!) Dengan membangunnya dari sub-pola yang lebih kecil, seperti ini:

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
sumber
21
Downvoting - Meskipun komentar Anda tentang pengurangan kompleksitas regex adalah valid, OP secara khusus menanyakan bagaimana "membagi regex panjang menjadi beberapa baris". Jadi meskipun nasihat Anda valid, itu diberikan untuk alasan yang salah. misalnya mengubah logika bisnis untuk bekerja di sekitar bahasa pemrograman. Selain itu, contoh kode yang Anda berikan cukup jelek.
sleepycal
4
@sleepycal Saya pikir Bart telah menjawab pertanyaan itu. Lihat bagian terakhir dari jawabannya. Dia telah menjawab pertanyaan itu serta memberikan alternatif.
Nidhin David
0

Anda cukup menggunakan operasi 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
sumber
0

Saya mencoba meningkatkan jawaban korun dengan merangkum semuanya dan menerapkan dukungan untuk memisahkan kelompok penangkap dan kumpulan karakter - menjadikan metode ini jauh lebih serbaguna.

Untuk menggunakan cuplikan ini, Anda perlu memanggil fungsi variadic combineRegexyang argumennya adalah objek ekspresi reguler yang perlu Anda gabungkan. Implementasinya dapat ditemukan di bagian bawah.

Grup penangkap tidak dapat dipisahkan secara langsung seperti itu karena akan meninggalkan beberapa bagian hanya dengan satu tanda kurung. Browser Anda akan gagal dengan pengecualian.

Sebagai gantinya saya hanya meneruskan konten dari grup penangkapan di dalam array. Tanda kurung secara otomatis ditambahkan saat combineRegexmenemukan sebuah array.

Selanjutnya pengukur perlu mengikuti sesuatu. Jika karena alasan tertentu ekspresi reguler perlu dipisahkan di depan pembilang, Anda perlu menambahkan sepasang tanda kurung. Ini akan dihapus secara otomatis. Intinya adalah bahwa grup tangkap kosong sangat tidak berguna dan dengan cara ini pembilang memiliki sesuatu untuk dirujuk. Metode yang sama dapat digunakan untuk hal-hal seperti grup non-capturing ( /(?:abc)/menjadi [/()?:abc/]).

Ini paling baik dijelaskan menggunakan contoh sederhana:

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

akan menjadi:

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

Jika Anda harus memisahkan kumpulan karakter, Anda bisa menggunakan objek ( {"":[regex1, regex2, ...]}) daripada array ( [regex1, regex2, ...]). Isi kunci dapat berupa apa saja selama objek tersebut hanya berisi satu kunci. Perhatikan bahwa alih-alih ()Anda harus menggunakan ]permulaan dummy jika karakter pertama dapat diartikan sebagai pembilang. Yaitu /[+?]/menjadi{"":[/]+?/]}

Berikut cuplikan dan contoh yang lebih lengkap:

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

Jawaban hebat @ Hashbrown membuat saya berada di jalur yang benar. Ini versi saya, juga terinspirasi dari blog ini .

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

Gunakan seperti ini:

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

Untuk membuat RegExpobjek ini :

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