ฉันจะเชื่อมต่อตัวอักษร regex ใน JavaScript ได้อย่างไร?


145

เป็นไปได้ไหมที่จะทำอะไรแบบนี้?

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

หรือฉันจะต้องใช้RegExp()ไวยากรณ์ใหม่และเชื่อมสตริง? ฉันต้องการใช้ตัวอักษรเนื่องจากรหัสมีความชัดเจนและรัดกุมมากขึ้น


2
ง่ายต่อการจัดการกับอักขระ regex ที่หลบหนีหากคุณใช้ String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

คำตอบ:


190

นี่คือวิธีสร้างนิพจน์ทั่วไปโดยไม่ใช้ไวยากรณ์ตัวอักษรนิพจน์ปกติ สิ่งนี้ช่วยให้คุณทำการจัดการสตริง arbitary ก่อนที่มันจะกลายเป็นวัตถุนิพจน์ปกติ:

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

หากคุณมีตัวอักษรที่แสดงออกเป็นประจำสองตัวคุณสามารถเชื่อมพวกมันเข้าด้วยกันโดยใช้เทคนิคนี้:

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

มันเป็นคำที่มีความหมายมากกว่าแค่การแสดงความคิดเห็นที่หนึ่งและสองเป็นสตริงตัวอักษรแทนการแสดงออกปกติ


2
โปรดทราบว่าแต่ละส่วนจะต้องเป็นนิพจน์ปกติที่ถูกต้องเมื่อใช้วิธีนี้ การสร้างนิพจน์เช่นnew RegExp(/(/.source + /.*/.source + /)?/.source);ดูเหมือนจะไม่ทำงาน
แซม

วิธีนี้ไม่ได้ผลในกรณีของกลุ่มที่มีการจับคู่ ดูคำตอบของฉันสำหรับวิธีแก้ปัญหาการทำงานในกรณีนั้น
คะเอลเมเยอร์

หากคุณต้องการหลีกเลี่ยงถ่านให้ใช้แบ็กสแลชสองครั้ง: Regexp ใหม่ ('\\ $' + "flum")
Jeff Lowery

คุณสามารถเข้าถึงธงหากคุณต้องใช้ "<regexp> .flags" ดังนั้นในทางทฤษฎีคุณสามารถรวมเข้าด้วยกันได้
bnunamak

คุณexpression_oneมาจากไหน คุณหมายถึงregex1อะไร
TallOrderDev

30

เพียงแค่การเชื่อมวัตถุที่มีนิพจน์ปกติเข้าด้วยกันอาจมีผลข้างเคียงที่ไม่พึงประสงค์ ใช้RegExp.sourceแทน:

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

สิ่งนี้จะให้ความสามารถในการเก็บค่าสถานะการแสดงออกปกติจาก RegExp ก่อนหน้านี้โดยใช้การตั้งค่าสถานะ RegExp มาตรฐาน

jsFiddle


สิ่งนี้สามารถปรับปรุงได้โดยใช้RegExp.prototype.flags
Dmitry Parzhitsky

19

ฉันไม่ค่อยเห็นด้วยกับตัวเลือก "eval"

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

จะให้ "// abcd // efgh //" ซึ่งไม่ใช่ผลลัพธ์ที่ต้องการ

ใช้แหล่งที่มาเช่น

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

จะให้ "/ abcdefgh /" และนั่นถูกต้อง

มีเหตุผลไม่จำเป็นต้องประเมินคุณรู้ว่าการแสดงออกของคุณ คุณเพียงแค่ต้องการแหล่งที่มาหรือวิธีการเขียนไม่จำเป็นต้องมีคุณค่า สำหรับแฟล็กคุณต้องใช้อาร์กิวเมนต์เผื่อเลือกของ RegExp

ในสถานการณ์ของฉันฉันจะทำงานในเรื่องของ ^ และ $ ถูกใช้ในการแสดงออกหลายอย่างที่ฉันพยายามที่จะต่อกัน! นิพจน์เหล่านั้นเป็นตัวกรองไวยากรณ์ที่ใช้ในโปรแกรม ตอนนี้ฉันไม่ต้องการที่จะใช้พวกเขาบางคนร่วมกันในการจัดการกรณีของการบุคคลากร ฉันอาจต้อง "หั่น" แหล่งที่มาเพื่อเริ่มต้นและสิ้นสุด ^ (และ / หรือ) $ :) ไชโยอเล็กซ์


ฉันชอบการใช้คุณสมบัติแหล่งที่มา ถ้าคุณ - อย่างฉัน - ใช้ jslint มันจะดุด่าถ้าคุณทำสิ่งนี้:var regex = "\.\..*"
Nils-o-mat

7

ปัญหาหาก regexp มีกลุ่มการจับคู่ด้านหลังเช่น \ 1

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

จากนั้นเพียงแค่ทำการตัดทอนแหล่งข้อมูลจะไม่ทำงาน แน่นอนการรวมกันของทั้งสองคือ:

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

วิธีการแก้ปัญหา: อันดับแรกเรานับจำนวนกลุ่มการจับคู่ใน regex แรกจากนั้นสำหรับโทเค็นการจับคู่ด้านหลังแต่ละอันในครั้งที่สองเราเพิ่มขึ้นตามจำนวนกลุ่มการจับคู่

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

ทดสอบ:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true

2
ใช่ (ฉันจะไม่แก้ไขที่นี่) ฟังก์ชั่นนี้เชื่อมโยงกันดังนั้นคุณสามารถใช้รหัสต่อไปนี้: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

มันจะดีกว่าที่จะใช้ไวยากรณ์ตัวอักษรบ่อยที่สุด มันสั้นกว่าอ่านง่ายขึ้นและคุณไม่จำเป็นต้องมีเครื่องหมายอัญประกาศ Escape หรือเครื่องหมายแบคแลชสองครั้ง จาก "รูปแบบ Javascript", Stoyan Stefanov 2010

แต่การใช้ใหม่อาจเป็นวิธีเดียวที่จะต่อกัน

ฉันจะหลีกเลี่ยงการทำตัวไม่ถูก มันไม่ปลอดภัย


1
ฉันคิดว่าการแสดงออกปกติที่ซับซ้อนนั้นชัดเจนมากขึ้นเมื่อเลิกใช้และแสดงความคิดเห็นเหมือนในคำถาม
แซม

3

โดยมีเงื่อนไขว่า:

  • คุณรู้ว่าคุณทำอะไรใน regexp ของคุณ
  • คุณมีชิ้นส่วน regex จำนวนมากในการสร้างรูปแบบและพวกเขาจะใช้ธงเดียวกัน
  • คุณพบว่าสามารถอ่านได้มากขึ้นเพื่อแยกรูปแบบขนาดเล็กของคุณเป็นอาร์เรย์
  • คุณยังต้องการที่จะสามารถแสดงความคิดเห็นแต่ละส่วนสำหรับ dev ถัดไปหรือตัวคุณเองในภายหลัง
  • คุณต้องการทำให้ regex ของคุณมองเห็นง่าย/this/gกว่าnew RegExp('this', 'g') ;
  • มันโอเคสำหรับคุณที่จะรวบรวม regex ในขั้นตอนพิเศษมากกว่าที่จะมีมันในหนึ่งชิ้นตั้งแต่เริ่มต้น;

จากนั้นคุณอาจต้องการเขียนวิธีนี้:

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

จากนั้นคุณสามารถทำสิ่งที่ชอบ:

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

ในกรณีเฉพาะของฉัน (ตัวแก้ไขโค้ดเหมือนมิเรอร์) มันง่ายกว่ามากในการดำเนินการ regex ใหญ่หนึ่งแทนที่จะแทนที่มากเช่นต่อไปนี้เป็นทุกครั้งที่ฉันแทนที่ด้วยแท็ก html เพื่อตัดการแสดงออกรูปแบบต่อไปจะ จะยากที่จะกำหนดเป้าหมายโดยไม่มีผลต่อแท็กตัวเอง (และไม่ดีlookbehindที่น่าเสียดายที่ไม่ได้รับการสนับสนุนในจาวาสคริปต์):

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

2

คุณสามารถทำสิ่งที่ชอบ:

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

ส่วนจะเป็นสตริง (มากกว่าตัวอักษร regex) ส่งผ่านเป็นอาร์กิวเมนต์แยก



1

ใช้ตัวสร้างด้วย 2 params และหลีกเลี่ยงปัญหาด้วยการต่อท้าย '/':

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

1

คุณสามารถ concat regex source จากทั้งตัวอักษรและคลาส RegExp:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);

1

วิธีที่ง่ายกว่าสำหรับฉันคือการทำให้แหล่งที่มาเรียงกันเช่น:

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

ค่า c จะส่งผลให้:

/ \ d + w \ + /


-2

ฉันชอบที่จะใช้eval('your expression')เพราะมันไม่ได้เพิ่ม/ในแต่ละปลาย/ที่='new RegExp'ไม่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.