Làm cách nào để tách một biểu thức chính quy dài thành nhiều dòng trong JavaScript?


138

Tôi có một biểu thức chính quy rất dài, mà tôi muốn chia thành nhiều dòng trong mã JavaScript của mình để giữ cho mỗi dòng có độ dài 80 ký tự theo quy tắc JSLint. Nó chỉ tốt hơn để đọc, tôi nghĩ. Đây là mẫu mẫu:

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
Có vẻ như bạn (đang cố gắng) xác thực địa chỉ email. Tại sao không đơn giản làm /\S+@\S+\.\S+/?
Bart Kiers

1
Có lẽ bạn nên tìm cách để làm điều đó mà không có biểu thức chính quy hoặc với nhiều biểu thức chính quy nhỏ hơn. Điều đó sẽ dễ đọc hơn nhiều so với một biểu thức thông thường dài như vậy. Nếu biểu thức thông thường của bạn dài hơn 20 ký tự, có lẽ cách tốt hơn để làm điều đó.
ForbesLindesay

2
Ngày nay không phải là 80 ký tự đã lỗi thời với màn hình rộng?
Oleg V. Volkov

7
@ OlegV.Volkov Không. Một người có thể sử dụng các cửa sổ tách trong vim, một thiết bị đầu cuối ảo trong phòng máy chủ. Thật sai lầm khi cho rằng mọi người sẽ mã hóa trong cùng một khung nhìn như bạn. Hơn nữa, việc giới hạn các dòng của bạn xuống 80 ký tự buộc bạn phải chia mã của mình thành các hàm nhỏ hơn.
synic

Chà, tôi chắc chắn thấy động lực của bạn khi muốn làm điều này ở đây - một khi regex này được chia thành nhiều dòng, như được trình bày bởi Koolilnc, nó ngay lập tức trở thành một ví dụ hoàn hảo về mã tự ghi, có thể đọc được. _¬
Mark Amery

Câu trả lời:


115

Bạn có thể chuyển đổi nó thành một chuỗi và tạo biểu thức bằng cách gọi 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(''));

Ghi chú:

  1. khi chuyển đổi biểu thức bằng chữ thành một chuỗi, bạn cần thoát tất cả các dấu gạch chéo ngược vì dấu gạch chéo ngược được sử dụng khi đánh giá một chuỗi ký tự . (Xem bình luận của Kayo để biết thêm chi tiết.)
  2. RegExp chấp nhận sửa đổi như một tham số thứ hai

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

[ Bổ sung ES20xx (mẫu được gắn thẻ)]

Trong ES20xx bạn có thể sử dụng các mẫu được gắn thẻ . Xem đoạn trích.

Ghi chú:

  • Nhược điểm ở đây là bạn không thể sử dụng khoảng trắng đồng bằng trong chuỗi biểu thức chính quy (luôn luôn sử dụng \s, \s+, \s{1,x}, \t, \nvv).

(() => {
  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 RegExplà một cách tuyệt vời cho các biểu thức chính quy đa dòng. Thay vì tham gia mảng, bạn chỉ có thể sử dụng toán tử nối chuỗi:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab

43
Thận trọng: Một biểu thức chính quy dài có thể được chia thành nhiều dòng bằng cách sử dụng câu trả lời ở trên. Tuy nhiên, nó cần được chăm sóc vì bạn không thể sao chép biểu thức chính quy (được định nghĩa bằng //) và dán nó làm đối số chuỗi vào hàm tạo RegExp. Điều này là do các ký tự dấu gạch chéo ngược được tiêu thụ khi đánh giá chuỗi ký tự . Ví dụ: /Hey\sthere/không thể thay thế bằng new RegExp("Hey\sthere"). Thay vào đó, nó nên được thay thế bằng new RegExp("Hey\\sthere")Lưu ý dấu gạch chéo ngược! Do đó, tôi thích chỉ để lại một regex dài theo nghĩa đen trên một dòng dài
Kayo

5
Một cách thậm chí rõ ràng hơn để làm điều này là tạo các biến được đặt tên chứa các phần phụ có ý nghĩa và nối chúng thành các chuỗi hoặc trong một mảng. Điều đó cho phép bạn xây dựng RegExptheo cách dễ hiểu hơn nhiều.
Chris Krycho

117

Mở rộng câu trả lời @KooiInc, bạn có thể tránh thoát thủ công mọi ký tự đặc biệt bằng cách sử dụng thuộc sourcetính của RegExpđối tượng.

Thí dụ:

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

hoặc nếu bạn muốn tránh lặp lại thuộc .sourcetính, bạn có thể thực hiện bằng Array.map()chức năng:

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

Trong ES6, chức năng bản đồ có thể được giảm xuống: .map(r => r.source)


3
Chính xác những gì tôi đang tìm kiếm, siêu sạch. Cảm ơn!
Marian Zagoruiko

10
Điều này thực sự thuận tiện cho việc thêm ý kiến ​​cho một regrec dài. Tuy nhiên, nó bị giới hạn bởi có dấu ngoặc đơn phù hợp trên cùng một dòng.
Nathan S. Watson-Haigh

Chắc chắn, điều này! Siêu đẹp với khả năng bình luận từng regex phụ.
Gary

Cảm ơn, nó đã giúp đưa nguồn vào chức năng regex

Rất thông minh. Cảm ơn, ý tưởng này đã giúp tôi rất nhiều. Cũng như một ghi chú bên lề: Tôi đã gói gọn toàn bộ trong một chức năng để làm cho nó thậm chí còn sạch hơn: combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))Cách sử dụng:combineRegex(/regex1/, /regex2/, ...)
Scindix

25

Sử dụng chuỗi trong new RegExplà khó xử vì bạn phải thoát khỏi tất cả các dấu gạch chéo ngược. Bạn có thể viết regexes nhỏ hơn và ghép chúng lại.

Hãy chia regex này

/^foo(.*)\bar$/

Chúng ta sẽ sử dụng một chức năng để làm cho mọi thứ đẹp hơn sau này

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

Và bây giờ hãy đá

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

Vì nó có chi phí, hãy cố gắng xây dựng regex thực sự chỉ một lần và sau đó sử dụng nó.


Điều này rất tuyệt - không chỉ bạn không phải thực hiện thoát bổ sung mà còn giữ nguyên cú pháp tô sáng đặc biệt cho các biểu thức con!
quezak

một cảnh báo: bạn cần đảm bảo rằng các biểu thức con của bạn được khép kín hoặc bao bọc từng nhóm trong một nhóm khung mới. Ví dụ: multilineRegExp([/a|b/, /c|d])kết quả /a|bc|d/, trong khi bạn có nghĩa (a|b)(c|d).
quezak

6

Có những câu trả lời hay ở đây, nhưng để hoàn thiện, ai đó nên đề cập đến tính năng kế thừa cốt lõi của Javascript với chuỗi nguyên mẫu . Một cái gì đó như thế này minh họa ý tưởng:

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


Đây là câu trả lời tốt nhất ở đây.
parttimeturtle

5

Nhờ vào thế giới tuyệt vời của các mẫu chữ, giờ đây bạn có thể viết các biểu thức lớn, đa dòng, được nhận xét tốt và thậm chí về ngữ nghĩa được lồng trong ngữ nghĩa trong 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])
    ))
);

Sử dụng cái này bây giờ bạn có thể viết regexes như thế này:

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

Đầu ra

/I'm a special regex{3}/

Hay những gì về 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]

Đầu ra hel, gọn gàng!
"Điều gì sẽ xảy ra nếu tôi thực sự cần tìm kiếm một dòng mới?", Vậy thì hãy sử dụng một cách \nngớ ngẩn!
Hoạt động trên Firefox và Chrome của tôi.


Được rồi, "làm thế nào về một cái gì đó phức tạp hơn một chút?"
Chắc chắn, đây là một phần của một đối tượng phá hủy trình phân tích cú pháp JS mà tôi đang làm việc :

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

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

Và chạy nó với một bản demo nhỏ?

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

Đầu ra thành công

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

Lưu ý việc chụp thành công chuỗi trích dẫn.
Tôi đã thử nghiệm nó trên Chrome và Firefox, hoạt động rất tốt!

Nếu tò mò bạn có thể kiểm tra những gì tôi đang làm , và trình diễn của nó .
Mặc dù nó chỉ hoạt động trên Chrome, vì Firefox không hỗ trợ các phản hồi hoặc các nhóm được đặt tên. Vì vậy, lưu ý ví dụ đưa ra trong câu trả lời này thực sự là một phiên bản mới và có thể dễ dàng bị lừa khi chấp nhận các chuỗi không hợp lệ.


1
bạn nên nghĩ đến việc xuất cái này dưới dạng gói NodeJS, thật tuyệt vời
rmobis

1
Mặc dù tôi chưa bao giờ tự làm điều đó, nhưng có một hướng dẫn khá kỹ lưỡng ở đây: zellwk.com/blog/publish-to-npm . Tôi đề nghị kiểm tra np, ở cuối trang. Tôi chưa bao giờ sử dụng nó, nhưng Sindre Sorhus là một pháp sư với những thứ này, vì vậy tôi sẽ không bỏ qua nó.
rmobis

4

Regex ở trên thiếu một số dấu gạch chéo màu đen không hoạt động đúng. Vì vậy, tôi đã chỉnh sửa regex. Vui lòng xem xét regex này hoạt động 99,99% để xác thực 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(''));

1

Để tránh Mảng join, bạn cũng có thể sử dụng cú pháp sau:

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

Cá nhân, tôi muốn có một regex ít phức tạp hơn:

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

Chắc chắn, nó ít chính xác hơn mẫu hiện tại của bạn, nhưng bạn đang cố gắng đạt được điều gì? Bạn đang cố gắng để bắt lỗi vô ý mà người dùng của bạn có thể nhập hoặc bạn lo lắng rằng người dùng của bạn có thể cố gắng nhập địa chỉ không hợp lệ? Nếu đó là lần đầu tiên, tôi sẽ chọn một mô hình dễ dàng hơn. Nếu đó là sau, một số xác minh bằng cách trả lời e-mail được gửi đến địa chỉ đó có thể là một lựa chọn tốt hơn.

Tuy nhiên, nếu bạn muốn sử dụng mẫu hiện tại của mình, thì (IMO) sẽ dễ đọc hơn (và duy trì!) Bằng cách xây dựng nó từ các mẫu con nhỏ hơn, như thế này:

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
Downvote - Mặc dù ý kiến ​​của bạn về việc giảm độ phức tạp của regex là hợp lệ, OP đặc biệt đang hỏi làm thế nào để "phân chia regex dài trên nhiều dòng". Vì vậy, mặc dù lời khuyên của bạn là hợp lệ, nó đã được đưa ra vì những lý do sai lầm. ví dụ thay đổi logic kinh doanh để làm việc xung quanh một ngôn ngữ lập trình. Hơn nữa, ví dụ mã bạn đưa ra là khá xấu xí.
ngủ

4
@s ngủycal Tôi nghĩ Bart đã trả lời câu hỏi. Xem phần cuối câu trả lời của anh ấy. Ông đã trả lời câu hỏi cũng như đưa ra một giải pháp thay thế.
Nidhin David

0

Bạn chỉ có thể sử dụng hoạt động chuỗi.

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

Tôi đã cố gắng cải thiện câu trả lời của korun bằng cách gói gọn mọi thứ và thực hiện hỗ trợ để tách các nhóm bắt và bộ ký tự - làm cho phương pháp này linh hoạt hơn nhiều.

Để sử dụng đoạn mã này, bạn cần gọi hàm matrixdic combineRegexcó đối số là các đối tượng biểu thức chính quy bạn cần kết hợp. Nó thực hiện có thể được tìm thấy ở phía dưới.

Các nhóm bắt giữ không thể được phân chia trực tiếp theo cách đó vì nó sẽ để lại một số phần chỉ bằng một dấu ngoặc đơn. Trình duyệt của bạn sẽ thất bại với một ngoại lệ.

Thay vào đó tôi chỉ đơn giản là chuyển nội dung của nhóm chụp trong một mảng. Các dấu ngoặc đơn được tự động thêm vào khi combineRegexgặp một mảng.

Hơn nữa định lượng cần phải theo một cái gì đó. Nếu vì một lý do nào đó, biểu thức chính quy cần được phân tách trước bộ định lượng, bạn cần thêm một cặp dấu ngoặc. Chúng sẽ được gỡ bỏ tự động. Vấn đề là một nhóm chụp trống là khá vô dụng và theo cách này, các bộ lượng hóa có một cái gì đó để tham khảo. Phương pháp tương tự có thể được sử dụng cho những thứ như các nhóm không bắt giữ ( /(?:abc)/trở thành [/()?:abc/]).

Điều này được giải thích tốt nhất bằng cách sử dụng một ví dụ đơn giản:

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

sẽ trở thành:

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

Nếu bạn phải phân chia các bộ ký tự, bạn có thể sử dụng các đối tượng ( {"":[regex1, regex2, ...]}) thay vì mảng ( [regex1, regex2, ...]). Nội dung của khóa có thể là bất cứ thứ gì miễn là đối tượng chỉ chứa một khóa. Lưu ý rằng thay vì ()bạn phải sử dụng ]như bắt đầu giả nếu ký tự đầu tiên có thể được hiểu là định lượng. Tức là /[+?]/trở thành{"":[/]+?/]}

Đây là đoạn trích và một ví dụ đầy đủ hơn:

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

Câu trả lời tuyệt vời của @ Hashbrown đã đưa tôi đi đúng hướng. Đây là phiên bản của tôi, cũng lấy cảm hứng từ blog này .

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

Sử dụng nó như thế này:

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

Để tạo RegExpđối tượng này :

/(\d+)([a-z]{1,3})/i
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.