Các nhóm bắt giữ trong regex JavaScript?


208

Theo như tôi biết thì không có thứ gọi là các nhóm bắt giữ trong JavaScript. Cách khác để có được chức năng tương tự là gì?


1
Các nhóm thu thập trong javascript là theo số .. $ 1 là nhóm bị bắt đầu tiên, $ 2, $ 3 ... lên đến $ 99 nhưng có vẻ như bạn muốn một cái gì đó khác - không tồn tại
Erik

24
@Erik bạn đang nói về các nhóm bắt được đánh số , OP nói về các nhóm bắt được đặt tên . Chúng tồn tại, nhưng chúng tôi muốn biết liệu có hỗ trợ cho chúng trong JS không.
Alba Mendez

4
Có một đề xuất để đưa regex có tên vào JavaScript , nhưng có thể là nhiều năm trước khi chúng ta thấy điều đó, nếu chúng ta từng làm.
fregante

Firefox đã trừng phạt tôi vì đã cố gắng sử dụng các nhóm bắt giữ có tên trên một trang web ... lỗi của tôi thực sự. stackoverflow.com/a/58221254/782034
Nick Grealy

Câu trả lời:


134

ECMAScript 2018 giới thiệu các nhóm bắt giữ có tên vào các biểu thức JavaScript.

Thí dụ:

  const auth = 'Bearer AUTHORIZATION_TOKEN'
  const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
  console.log(token) // "Prints AUTHORIZATION_TOKEN"

Nếu bạn cần hỗ trợ các trình duyệt cũ hơn, bạn có thể làm mọi thứ với các nhóm bắt bình thường (được đánh số) mà bạn có thể làm với các nhóm bắt có tên, bạn chỉ cần theo dõi các số - có thể là cồng kềnh nếu thứ tự bắt nhóm trong regex thay đổi.

Chỉ có hai lợi thế "cấu trúc" của các nhóm bắt giữ được đặt tên mà tôi có thể nghĩ đến:

  1. Trong một số hương vị regex (.NET và JGSoft, theo như tôi biết), bạn có thể sử dụng cùng tên cho các nhóm khác nhau trong regex của bạn ( xem ở đây để biết ví dụ về vấn đề này ). Nhưng hầu hết các hương vị regex không hỗ trợ chức năng này.

  2. Nếu bạn cần tham khảo các nhóm bắt được đánh số trong tình huống chúng được bao quanh bởi các chữ số, bạn có thể gặp vấn đề. Giả sử bạn muốn thêm số 0 vào một chữ số và do đó muốn thay thế (\d)bằng $10. Trong JavaScript, điều này sẽ hoạt động (miễn là bạn có ít hơn 10 nhóm bắt trong regex của bạn), nhưng Perl sẽ nghĩ rằng bạn đang tìm kiếm số phản hồi 10thay vì số 1, theo sau là a 0. Trong Perl, bạn có thể sử dụng ${1}0trong trường hợp này.

Ngoài ra, các nhóm bắt giữ được đặt tên chỉ là "đường cú pháp". Nó chỉ giúp sử dụng các nhóm bắt giữ khi bạn thực sự cần chúng và sử dụng các nhóm không bắt giữ (?:...)trong tất cả các trường hợp khác.

Vấn đề lớn hơn (theo ý kiến ​​của tôi) với JavaScript là nó không hỗ trợ các biểu thức dài dòng, điều này sẽ giúp việc tạo các biểu thức chính quy phức tạp dễ đọc trở nên dễ dàng hơn rất nhiều.

Thư viện XRegExp của Steve Levithan giải quyết những vấn đề này.


5
Nhiều hương vị cho phép sử dụng cùng một tên nhóm chụp nhiều lần trong một biểu thức chính quy. Nhưng chỉ .NET và Perl 5.10+ làm cho điều này đặc biệt hữu ích bằng cách giữ giá trị được nắm bắt bởi nhóm cuối cùng của tên đã tham gia trận đấu.
slevithan

103
Lợi thế rất lớn là: bạn chỉ có thể thay đổi RegExp của mình, không cần ánh xạ từ số sang biến. Các nhóm không bắt giữ giải quyết vấn đề này, ngoại trừ một trường hợp: nếu thứ tự của các nhóm thay đổi thì sao? Ngoài ra, thật đáng chú ý khi đưa phần ký tự thêm này vào các nhóm khác ...
Alba Mendez

55
Cái gọi là đường cú pháp không giúp làm ngọt các khả năng đọc mã!
Ông trùm

1
Tôi nghĩ rằng có một lý do khác để đặt tên cho các nhóm bắt giữ thực sự có giá trị. Ví dụ: nếu bạn muốn sử dụng regex để phân tích ngày từ một chuỗi, bạn có thể viết một hàm linh hoạt lấy giá trị và biểu thức chính quy. Miễn là regex đã đặt tên các ảnh chụp cho năm, tháng và ngày bạn có thể chạy qua một loạt các biểu thức thông thường với mã tối thiểu.
Dewey Vozel

4
Kể từ tháng 10 năm 2019, Firefox, IE 11 và Microsoft Edge (tiền Chromium) không hỗ trợ chụp nhóm được đặt tên. Hầu hết các trình duyệt khác (thậm chí cả Opera và Samsung di động) đều làm được. caniuse.com/ Quảng cáo
JDB vẫn còn nhớ Monica

63

Bạn có thể sử dụng XRegExp , một triển khai trình duyệt thông thường tăng cường, có thể mở rộng, biểu thức chính, bao gồm hỗ trợ cho cú pháp, cờ và phương thức bổ sung:

  • Thêm regex mới và cú pháp văn bản thay thế, bao gồm hỗ trợ toàn diện cho chụp có tên .
  • Thêm hai cờ regex mới : s, để tạo dấu chấm khớp với tất cả các ký tự (còn gọi là chế độ dotall hoặc singleline), và x, cho khoảng cách tự do và nhận xét (còn gọi là chế độ mở rộng).
  • Cung cấp một bộ các hàm và phương thức làm cho việc xử lý regex phức tạp trở nên dễ dàng.
  • Tự động sửa các lỗi không thống nhất giữa các trình duyệt thường gặp trong hành vi và cú pháp regex.
  • Cho phép bạn dễ dàng tạo và sử dụng các plugin thêm cú pháp và cờ mới vào ngôn ngữ biểu thức chính quy của XRegExp.

60

Một giải pháp khả thi khác: tạo một đối tượng chứa tên nhóm và chỉ mục.

var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };

Sau đó, sử dụng các khóa đối tượng để tham chiếu các nhóm:

var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];

Điều này cải thiện khả năng đọc / chất lượng của mã bằng cách sử dụng kết quả của regex, nhưng không phải là khả năng đọc của chính regex.


58

Trong ES6, bạn có thể sử dụng tính năng phá hủy mảng để bắt các nhóm của mình:

let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];

// count === '27'
// unit === 'months'

Để ý:

  • dấu phẩy đầu tiên trong lần letbỏ qua cuối cùng giá trị đầu tiên của mảng kết quả, đó là toàn bộ chuỗi khớp
  • cái || []sau .exec()sẽ ngăn lỗi phá hủy khi không có kết quả khớp (vì .exec()sẽ trả về null)

1
Dấu phẩy đầu tiên là bởi vì phần tử đầu tiên của mảng được trả về bằng khớp là biểu thức đầu vào, phải không?
Emilio Grisolía

1
String.prototype.matchtrả về một mảng với: toàn bộ chuỗi khớp ở vị trí 0, sau đó bất kỳ nhóm nào sau đó. Dấu phẩy đầu tiên cho biết "bỏ qua phần tử ở vị trí 0"
fregante

2
Câu trả lời yêu thích của tôi ở đây cho những người có mục tiêu transpiling hoặc ES6 +. Điều này không nhất thiết ngăn ngừa các lỗi không nhất quán cũng như các chỉ số được đặt tên có thể nếu ví dụ như một biểu thức chính được sử dụng lại thay đổi, nhưng tôi nghĩ rằng sự đồng nhất ở đây dễ dàng bù đắp cho điều đó. Tôi đã chọn cho RegExp.prototype.exechơn String.prototype.matchở những nơi mà các chuỗi có thể nullhay undefined.
Mike Hill

22

Cập nhật: Cuối cùng nó đã biến nó thành JavaScript (ECMAScript 2018)!


Các nhóm bắt giữ được đặt tên có thể biến nó thành JavaScript rất sớm.
Đề xuất cho nó là ở giai đoạn 3 rồi.

Một nhóm chụp có thể được đặt tên trong dấu ngoặc nhọn bằng (?<name>...)cú pháp, cho bất kỳ tên định danh nào. Biểu thức chính quy cho một ngày sau đó có thể được viết là /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u. Mỗi tên phải là duy nhất và tuân theo ngữ pháp cho ECMAScript IdentifierName .

Các nhóm được đặt tên có thể được truy cập từ các thuộc tính của thuộc tính nhóm của kết quả biểu thức chính quy. Các tham chiếu được đánh số cho các nhóm cũng được tạo, giống như đối với các nhóm không được đặt tên. Ví dụ:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

Đó là một đề xuất giai đoạn 4 tại thời điểm này.
GOTO 0

nếu bạn đang sử dụng '18, cũng có thể sử dụng triệt để; let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
Hashbrown

6

Các nhóm được đặt tên cung cấp một điều: ít nhầm lẫn với các biểu thức chính quy phức tạp.

Nó thực sự phụ thuộc vào trường hợp sử dụng của bạn nhưng có lẽ việc in ấn regex của bạn có thể giúp ích.

Hoặc bạn có thể thử và xác định các hằng số để tham khảo các nhóm đã bắt của bạn.

Nhận xét sau đó cũng có thể giúp hiển thị cho những người khác đọc mã của bạn, những gì bạn đã làm.

Đối với phần còn lại tôi phải đồng ý với câu trả lời của Tims.


5

Có một thư viện node.js có tên là regrec mà bạn có thể sử dụng trong các dự án node.js của mình (trên trình duyệt bằng cách đóng gói thư viện với browserify hoặc các tập lệnh đóng gói khác). Tuy nhiên, thư viện không thể được sử dụng với các biểu thức thông thường có chứa các nhóm bắt giữ không được đặt tên.

Nếu bạn đếm các dấu ngoặc bắt mở trong biểu thức chính quy, bạn có thể tạo ánh xạ giữa các nhóm bắt được đặt tên và các nhóm bắt được đánh số trong biểu thức chính quy của bạn và có thể trộn và kết hợp tự do. Bạn chỉ cần xóa tên nhóm trước khi sử dụng regex. Tôi đã viết ba chức năng chứng minh điều đó. Xem ý chính này: https://gist.github.com/gbirke/2cc2370135b665eee3ef


Đó là trọng lượng nhẹ đáng ngạc nhiên, tôi sẽ thử nó
fregante

Nó có hoạt động với các nhóm được đặt tên lồng trong các nhóm thông thường trong các biểu thức chính quy phức tạp không?
ElSajko

Nó không hoàn hảo. Lỗi khi: getMap ("((a | b (: <foo> c)))"); foo nên là nhóm thứ ba, không phải thứ hai. /(a|b(c)))/g.exec("bc "); ["bc", "bc", "bc", "c"]
ElSajko

3

Như Tim Pietzcker đã nói ECMAScript 2018 giới thiệu các nhóm bắt giữ có tên vào các biểu thức JavaScript. Nhưng điều tôi không tìm thấy trong các câu trả lời ở trên là làm thế nào để sử dụng nhóm bị bắt có tên trong chính regex.

bạn có thể sử dụng nhóm được đặt tên với cú pháp này : \k<name>. ví dụ

var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/

và như Forivin đã nói, bạn có thể sử dụng nhóm bị bắt trong kết quả đối tượng như sau:

let result = regexObj.exec('2019-28-06 year is 2019');
// result.groups.year === '2019';
// result.groups.month === '06';
// result.groups.day === '28';

  var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/mgi;

function check(){
    var inp = document.getElementById("tinput").value;
    let result = regexObj.exec(inp);
    document.getElementById("year").innerHTML = result.groups.year;
    document.getElementById("month").innerHTML = result.groups.month;
    document.getElementById("day").innerHTML = result.groups.day;
}
td, th{
  border: solid 2px #ccc;
}
<input id="tinput" type="text" value="2019-28-06 year is 2019"/>
<br/>
<br/>
<span>Pattern: "(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>";
<br/>
<br/>
<button onclick="check()">Check!</button>
<br/>
<br/>
<table>
  <thead>
    <tr>
      <th>
        <span>Year</span>
      </th>
      <th>
        <span>Month</span>
      </th>
      <th>
        <span>Day</span>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <span id="year"></span>
      </td>
      <td>
        <span id="month"></span>
      </td>
      <td>
        <span id="day"></span>
      </td>
    </tr>
  </tbody>
</table>


2

Mặc dù bạn không thể làm điều này với JavaScript vanilla, nhưng có lẽ bạn có thể sử dụng một số Array.prototypechức năng như Array.prototype.reduceđể biến các trận đấu được lập chỉ mục thành các trận đấu được đặt tên bằng một số phép thuật .

Rõ ràng, giải pháp sau đây sẽ cần các trận đấu xảy ra theo thứ tự:

// @text Contains the text to match
// @regex A regular expression object (f.e. /.+/)
// @matchNames An array of literal strings where each item
//             is the name of each group
function namedRegexMatch(text, regex, matchNames) {
  var matches = regex.exec(text);

  return matches.reduce(function(result, match, index) {
    if (index > 0)
      // This substraction is required because we count 
      // match indexes from 1, because 0 is the entire matched string
      result[matchNames[index - 1]] = match;

    return result;
  }, {});
}

var myString = "Hello Alex, I am John";

var namedMatches = namedRegexMatch(
  myString,
  /Hello ([a-z]+), I am ([a-z]+)/i, 
  ["firstPersonName", "secondPersonName"]
);

alert(JSON.stringify(namedMatches));


Điều đó thật tuyệt. Tôi chỉ đang suy nghĩ .. liệu có thể tạo ra một hàm regex chấp nhận một regex tùy chỉnh không? Vì vậy, bạn có thể đi như thếvar assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
Forivin

@Forivin Rõ ràng bạn có thể đi xa hơn và phát triển tính năng này. Sẽ không khó để làm cho nó hoạt động: D
Matías Fidemraizer

Bạn có thể mở rộng RegExpđối tượng bằng cách thêm một hàm vào nguyên mẫu của nó.
Ông TA

@ Mr.TA AFAIK, không nên mở rộng các đối tượng tích hợp
Matías Fidemraizer 16/2/2016

0

Không có ECMAScript 2018?

Mục tiêu của tôi là làm cho nó hoạt động tương tự nhất có thể với những gì chúng ta đã quen với các nhóm được đặt tên. Trong khi trong ECMAScript 2018, bạn có thể đặt ?<groupname>bên trong nhóm để chỉ ra một nhóm được đặt tên, trong giải pháp của tôi cho javascript cũ hơn, bạn có thể đặt (?!=<groupname>)bên trong nhóm để làm điều tương tự. Vì vậy, đó là một bộ dấu ngoặc đơn bổ sung và thêm !=. Khá gần!

Tôi gói tất cả vào một hàm nguyên mẫu chuỗi

Đặc trưng

  • hoạt động với javascript cũ
  • không có mã phụ
  • khá đơn giản để sử dụng
  • Regex vẫn hoạt động
  • các nhóm được ghi lại trong chính regex
  • tên nhóm có thể có khoảng trắng
  • trả về đối tượng có kết quả

Hướng dẫn

  • đặt (?!={groupname})bên trong mỗi nhóm bạn muốn đặt tên
  • nhớ loại bỏ bất kỳ nhóm không bắt giữ nào ()bằng cách đặt ?:ở đầu nhóm đó. Chúng sẽ không được đặt tên.

mảngays.js

// @@pattern - includes injections of (?!={groupname}) for each group
// @@returns - an object with a property for each group having the group's match as the value 
String.prototype.matchWithGroups = function (pattern) {
  var matches = this.match(pattern);
  return pattern
  // get the pattern as a string
  .toString()
  // suss out the groups
  .match(/<(.+?)>/g)
  // remove the braces
  .map(function(group) {
    return group.match(/<(.+)>/)[1];
  })
  // create an object with a property for each group having the group's match as the value 
  .reduce(function(acc, curr, index, arr) {
    acc[curr] = matches[index + 1];
    return acc;
  }, {});
};    

sử dụng

function testRegGroups() {
  var s = '123 Main St';
  var pattern = /((?!=<house number>)\d+)\s((?!=<street name>)\w+)\s((?!=<street type>)\w+)/;
  var o = s.matchWithGroups(pattern); // {'house number':"123", 'street name':"Main", 'street type':"St"}
  var j = JSON.stringify(o);
  var housenum = o['house number']; // 123
}

kết quả của o

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
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.