Bagaimana saya bisa menggabungkan regex literal dalam JavaScript?

145

Apakah mungkin melakukan hal seperti ini?

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

Atau apakah saya harus menggunakan RegExp()sintaks baru dan menyatukan string? Saya lebih suka menggunakan literal karena kodenya lebih jelas dan ringkas.

kelopak mata
sumber
2
Lebih mudah untuk berurusan dengan karakter regex yang lolos jika Anda menggunakan String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

Jawaban:

190

Berikut adalah cara membuat ekspresi reguler tanpa menggunakan sintaks literal ekspresi reguler. Ini memungkinkan Anda melakukan manipulasi string arbiter sebelum menjadi objek ekspresi reguler:

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

Jika Anda memiliki dua literal ekspresi reguler, Anda dapat menggabungkannya menggunakan teknik ini:

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

Itu hanya lebih bertele-tele daripada hanya memiliki ekspresi satu dan dua menjadi string literal daripada ekspresi reguler literal.

Jerub
sumber
2
Perlu diingat bahwa setiap segmen harus merupakan ekspresi reguler yang valid saat menggunakan pendekatan ini. Membuat ungkapan seperti new RegExp(/(/.source + /.*/.source + /)?/.source);sepertinya tidak berhasil.
Sam
Solusi ini tidak berfungsi dalam kasus kelompok pencocokan kembali. Lihat jawaban saya untuk solusi yang berfungsi dalam hal ini.
Mikaël Mayer
Jika Anda perlu melarikan diri dari char, maka gunakan double backslash: new Regexp ('\\ $' + "flum")
Jeff Lowery
Anda dapat mengakses flag jika Anda harus dengan "<regexp> .flags", jadi secara teoritis Anda bisa menggabungkannya juga.
bnunamak
Darimana Anda expression_oneberasal? Apakah yang Anda maksud regex1?
TallOrderDev
30

Hanya objek - objek ekspresi reguler yang digabungkan secara acak dapat memiliki beberapa efek samping yang merugikan. Gunakan sumber RegExp.sebagai gantinya:

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

Ini juga akan memberi Anda kemampuan untuk mempertahankan flag ekspresi reguler dari RegExp sebelumnya menggunakan flag RegExp standar.

jsFiddle

Japheth Salva
sumber
Ini dapat ditingkatkan dengan menggunakanRegExp.prototype.flags
Dmitry Parzhitsky
19

Saya tidak begitu setuju dengan opsi "eval".

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

akan memberikan "// abcd // efgh //" yang bukan hasil yang diinginkan.

Menggunakan sumber suka

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

akan memberikan "/ abcdefgh /" dan itu benar.

Logicaly tidak perlu MENGEVALUASI, Anda tahu EKSPRESI Anda. Anda hanya perlu SUMBER nya atau bagaimana itu ditulis tidak perlu nilainya. Adapun bendera, Anda hanya perlu menggunakan argumen opsional dari RegExp.

Dalam situasi saya, saya berlari dalam masalah ^ dan $ digunakan dalam beberapa ekspresi yang saya coba gabungkan bersama! Ekspresi tersebut adalah filter tata bahasa yang digunakan di seluruh program. Sekarang saya tidak ingin menggunakan beberapa dari mereka bersama-sama untuk menangani kasus KATA DEPAN. Saya mungkin harus "mengiris" sumber untuk menghapus awal dan akhir ^ (dan / atau) $ :) Cheers, Alex.

Alex
sumber
Saya suka menggunakan properti-sumber. Jika Anda - seperti saya - gunakan jslint itu akan mengganggu jika Anda melakukan sesuatu seperti ini:var regex = "\.\..*"
Nils-o-mat
7

Masalah Jika regexp berisi grup pencocokan-balik seperti \ 1.

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

Maka hanya mengkombinasikan sumber tidak akan bekerja. Memang, kombinasi keduanya adalah:

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

Solusinya: Pertama kita menghitung jumlah kelompok yang cocok di regex pertama, Kemudian untuk setiap token yang cocok di yang kedua, kita menambahnya dengan jumlah kelompok yang cocok.

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

Uji:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true
Mikaël Mayer
sumber
2
Ya (saya tidak akan memodifikasinya di sini). Fungsi ini asosiatif, sehingga Anda dapat menggunakan kode berikut: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

Akan lebih baik menggunakan sintaksis literal sesering mungkin. Ini lebih pendek, lebih terbaca, dan Anda tidak perlu tanda kutip melarikan diri atau serangan balasan melarikan diri ganda. Dari "Pola Javascript", Stoyan Stefanov 2010.

Tetapi menggunakan New mungkin satu-satunya cara untuk menggabungkan.

Saya akan menghindari eval. Itu tidak aman.

Jonathan Wright
sumber
1
Saya pikir ekspresi reguler yang kompleks lebih mudah dibaca ketika dihancurkan dan dikomentari seperti dalam pertanyaan.
Sam
3

Asalkan:

  • Anda tahu apa yang Anda lakukan di regexp Anda;
  • Anda memiliki banyak potongan regex untuk membentuk pola dan mereka akan menggunakan bendera yang sama;
  • Anda merasa lebih mudah dibaca untuk memisahkan potongan pola kecil Anda menjadi sebuah array;
  • Anda juga ingin dapat mengomentari setiap bagian untuk dev berikutnya atau diri Anda nanti;
  • Anda lebih suka menyederhanakan regex secara visual seperti /this/gdaripada new RegExp('this', 'g');
  • tidak apa-apa bagi Anda untuk merakit regex dalam langkah ekstra daripada memilikinya dalam satu kesatuan dari awal;

Maka Anda mungkin ingin menulis seperti ini:

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

Anda kemudian dapat melakukan sesuatu seperti:

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

Dalam kasus khusus saya (editor seperti kode), jauh lebih mudah untuk melakukan satu regex besar, daripada banyak penggantian seperti mengikuti karena setiap kali saya mengganti dengan tag html untuk membungkus ekspresi, pola selanjutnya akan lebih sulit untuk target tanpa mempengaruhi tag html itu sendiri (dan tanpa baik lookbehind yang sayangnya tidak didukung dalam 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
sumber
2

Anda dapat melakukan sesuatu seperti:

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

Segmen akan berupa string (bukan regex literal) yang diteruskan sebagai argumen terpisah.

Strain Neil
sumber
1

Tidak, cara literalnya tidak didukung. Anda harus menggunakan RegExp.

Aupajo
sumber
1

Gunakan konstruktor dengan 2 params dan hindari masalah dengan trailing '/':

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

Anda bisa menggabungkan sumber regex dari kelas literal dan RegExp:

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

cara yang lebih mudah bagi saya adalah menggabungkan sumber, mis .:

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

nilai c akan menghasilkan:

/ \ d + \ w + /

Daniel Aragão
sumber
-2

Saya lebih suka menggunakan eval('your expression')karena tidak menambahkan /pada setiap akhir /yang ='new RegExp'tidak.

Praesagus
sumber