Làm cách nào tôi có thể khớp nhiều lần xuất hiện với biểu thức chính quy trong JavaScript tương tự như preg_match_all () của PHP?


160

Tôi đang cố phân tích các chuỗi được mã hóa url được tạo thành từ các cặp key = value được phân tách bằng một trong hai &hoặc &.

Sau đây sẽ chỉ khớp lần xuất hiện đầu tiên, tách các khóa và giá trị thành các phần tử kết quả riêng biệt:

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/)

Kết quả cho chuỗi '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' sẽ là:

['1111342', 'Adam%20Franco']

Sử dụng cờ toàn cầu, 'g', sẽ khớp với tất cả các lần xuất hiện, nhưng chỉ trả về các chuỗi con được khớp hoàn toàn, không phải các khóa và giá trị được phân tách:

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/g)

Kết quả cho chuỗi '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' sẽ là:

['1111342=Adam%20Franco', '&348572=Bob%20Jones']

Mặc dù tôi có thể tách chuỗi trên &và tách riêng từng cặp khóa / giá trị, nhưng có cách nào sử dụng hỗ trợ biểu thức chính quy của JavaScript để khớp nhiều lần xuất hiện của mẫu /(?:&|&)?([^=]+)=([^&]+)/tương tự như preg_match_all()hàm của PHP không?

Tôi đang hướng tới một số cách để có kết quả với các trận đấu phụ được phân tách như:

[['1111342', '348572'], ['Adam%20Franco', 'Bob%20Jones']]

hoặc là

[['1111342', 'Adam%20Franco'], ['348572', 'Bob%20Jones']]

9
đó là một chút kỳ lạ mà không ai đề nghị sử dụng replaceở đây. var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });làm xong. "Match ALL" trong JavaScript là "thay thế" bằng hàm xử lý thay thế thay vì chuỗi.
Mike 'Pomax' Kamermans

Lưu ý rằng đối với những người vẫn tìm thấy câu hỏi này vào năm 2020, câu trả lời là "không sử dụng regex, hãy sử dụng URLSearchParams , đây là tất cả những điều này cho bạn."
Mike 'Pomax' Kamermans

Câu trả lời:


161

Hoist từ các ý kiến

Nhận xét năm 2020: thay vì sử dụng regex, hiện tại chúng tôi có URLSearchParams, tất cả những điều này cho chúng tôi, vì vậy không có mã tùy chỉnh, chứ chưa nói đến regex, là cần thiết nữa.

- Mike 'Pomax' Kamermans

Hỗ trợ trình duyệt được liệt kê tại đây https://caniuse.com/#feat=urlsearchparams


Tôi sẽ đề xuất một biểu thức chính thay thế, sử dụng các nhóm phụ để nắm bắt tên và giá trị của các tham số riêng lẻ và re.exec():

function getUrlParams(url) {
  var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
      match, params = {},
      decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};

  if (typeof url == "undefined") url = document.location.href;

  while (match = re.exec(url)) {
    params[decode(match[1])] = decode(match[2]);
  }
  return params;
}

var result = getUrlParams("http://maps.google.de/maps?f=q&source=s_q&hl=de&geocode=&q=Frankfurt+am+Main&sll=50.106047,8.679886&sspn=0.370369,0.833588&ie=UTF8&ll=50.116616,8.680573&spn=0.35972,0.833588&z=11&iwloc=addr");

result là một đối tượng:

{
  f: "q"
  mã địa lý: ""
  hl: "de"
  tức là: "UTF8"
  iwloc: "thêm"
  sẽ: "50.116616,8.680573"
  q: "Frankfurt am chính"
  sll: "50.106047,8.679886"
  nguồn: "s_q"
  spn: "0.35972,0.833588"
  sspn: "0,370369,0.833588"
  z: "11"
}

Regex bị phá vỡ như sau:

(?: # nhóm không bắt giữ
  \? | & # "?" hoặc là "&"
  (?: amp;)? # (cho phép "& amp;", đối với các URL được mã hóa HTML sai)
) # kết thúc nhóm không bắt giữ
( # nhóm 1
  [^ = & #] + # bất kỳ ký tự nào ngoại trừ "=", "&" hoặc "#"; ít nhất một lần
) # end nhóm 1 - đây sẽ là tên của tham số
(?: # nhóm không bắt giữ
  =? # an "=", tùy chọn
  (# nhóm 2
    [^ & #] * # bất kỳ ký tự nào ngoại trừ "&" hoặc "#"; bất kỳ số lần
  ) # end nhóm 2 - đây sẽ là giá trị của tham số
) # kết thúc nhóm không bắt giữ

23
Đây là những gì tôi đã hy vọng. Điều tôi chưa từng thấy trong tài liệu JavaScript là đề cập rằng phương thức exec () sẽ tiếp tục trả về tập kết quả tiếp theo nếu được gọi nhiều lần. Cảm ơn một lần nữa cho lời khuyên tuyệt vời!
Adam Franco

1
Nó là vì điều này: thường xuyên- expresspress.info / javascript.html (Đọc qua: "Cách sử dụng Đối tượng RegExp JavaScript")
Tomalak

1
có một lỗi trong mã này: dấu chấm phẩy sau "while" sẽ bị xóa.
Jan Willem B

1
Bởi vì tôi thường chỉ sử dụng các nhóm bình thường (tức là chụp) nếu tôi thực sự quan tâm đến nội dung của họ.
Tomalak

1
@KnightYoshi Vâng. Trong JavaScript, bất kỳ biểu thức nào cũng tạo ra kết quả của riêng nó (như x = ysẽ gán ycho xvà cũng tạo ra y). Khi chúng tôi áp dụng kiến thức để if (match = re.exec(url)): A này) thực hiện việc chuyển nhượng B) trả về kết quả của việc re.exec(url)đến while. Bây giờ re.exectrả về nullnếu không có kết quả khớp, đó là một giá trị giả. Vì vậy, trong thực tế, vòng lặp sẽ tiếp tục miễn là có một trận đấu.
Tomalak

67

Bạn cần sử dụng công tắc 'g' cho tìm kiếm toàn cầu

var result = mystring.match(/(&|&)?([^=]+)=([^&]+)/g)

33
Điều này thực sự không giải quyết được vấn đề: "Sử dụng cờ toàn cầu, 'g', sẽ khớp với tất cả các lần xuất hiện, nhưng chỉ trả về các chuỗi con được khớp hoàn toàn, không phải các khóa và giá trị được tách riêng."
Adam Franco

40

Chỉnh sửa năm 2020

Sử dụng URLSearchParams , vì công việc này không còn yêu cầu bất kỳ loại mã tùy chỉnh nào. Trình duyệt có thể làm điều này cho bạn với một hàm tạo:

const str = "1111342=Adam%20Franco&348572=Bob%20Jones";
const data = new URLSearchParams(str);
for (pair of data) console.log(pair)

sản lượng

Array [ "1111342", "Adam Franco" ]
Array [ "348572", "Bob Jones" ]

Vì vậy, không có lý do để sử dụng regex cho điều này nữa.

Câu trả lời gốc

Nếu bạn không muốn dựa vào "kết hợp mù" đi kèm với execkết hợp kiểu chạy , JavaScript sẽ đi kèm với chức năng khớp tất cả được tích hợp, nhưng đó là một phần của replacechức năng gọi, khi sử dụng "phải làm gì với chụp nhóm " chức năng xử lý :

var data = {};

var getKeyValue = function(fullPattern, group1, group2, group3) {
  data[group2] = group3;
};

mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, getKeyValue);

làm xong.

Thay vì sử dụng chức năng xử lý nhóm chụp để thực sự trả về các chuỗi thay thế (để xử lý thay thế, đối số đầu tiên là khớp mẫu đầy đủ và các đối số tiếp theo là các nhóm bắt riêng lẻ), chúng tôi chỉ cần lấy các nhóm 2 và 3 và lưu vào cặp.

Vì vậy, thay vì viết các hàm phân tích cú pháp phức tạp, hãy nhớ rằng hàm "match ALL" trong JavaScript chỉ đơn giản là "thay thế" bằng hàm xử lý thay thế và có thể có nhiều hiệu quả khớp mẫu.


Tôi có một chuỗi something "this one" and "that one". Tôi muốn đặt tất cả các chuỗi trích dẫn kép trong một danh sách tức là [cái này, cái kia]. Cho đến nay mystring.match(/"(.*?)"/)hoạt động tốt trong việc phát hiện cái đầu tiên, nhưng tôi không biết làm thế nào để điều chỉnh giải pháp của bạn cho một nhóm bắt giữ duy nhất.
nu everest

2
Có vẻ như bạn nên đăng câu hỏi lên Stackoverflow cho điều đó, thay vì cố gắng giải quyết nó trong các bình luận.
Mike 'Pomax' Kamermans

Tôi đã tạo một câu hỏi mới: stackoverflow.com/questions/26174122/ Cách
nu everest

1
Không chắc chắn tại sao câu trả lời này có rất ít upvote nhưng nó là câu trả lời tốt nhất cho câu hỏi.
Calin

Xin chào @ Mike'Pomax'Kamermans, các dòng hướng dẫn cộng đồng đặc biệt khuyên bạn nên chỉnh sửa các mục để cải thiện chúng, xem: stackoverflow.com/help/behavior . Cốt lõi của câu trả lời của bạn là cực kỳ hữu ích, nhưng tôi thấy ngôn ngữ "hãy nhớ rằng matchAll là thay thế" không rõ ràng và không phải là một lời giải thích về lý do tại sao mã của bạn (không rõ ràng) hoạt động. Tôi nghĩ bạn nên có được đại diện xứng đáng, vì vậy tôi đã chỉnh sửa câu trả lời của bạn thay vì sao chép nó với văn bản được cải thiện. Là người hỏi ban đầu của câu hỏi này, tôi rất vui khi được hoàn nguyên sự chấp nhận - về câu trả lời này (và bản chỉnh sửa) nếu bạn vẫn muốn tôi làm.
Adam Franco

21

Để chụp các nhóm, tôi đã quen sử dụng preg_match_alltrong PHP và tôi đã cố gắng sao chép chức năng của nó ở đây:

<script>

// Return all pattern matches with captured groups
RegExp.prototype.execAll = function(string) {
    var match = null;
    var matches = new Array();
    while (match = this.exec(string)) {
        var matchArray = [];
        for (i in match) {
            if (parseInt(i) == i) {
                matchArray.push(match[i]);
            }
        }
        matches.push(matchArray);
    }
    return matches;
}

// Example
var someTxt = 'abc123 def456 ghi890';
var results = /[a-z]+(\d+)/g.execAll(someTxt);

// Output
[["abc123", "123"],
 ["def456", "456"],
 ["ghi890", "890"]]

</script>

3
@teh_senaus bạn cần chỉ định công cụ sửa đổi toàn cầu /gnếu không chạy exec()sẽ không thay đổi chỉ mục hiện tại và sẽ lặp lại mãi mãi.
Aram Kocharyan

Nếu tôi gọi để xác thực mã này myRe.test (str) và sau đó thử thực hiện execAll, nó sẽ xuất hiện ở trận đấu thứ hai và chúng tôi đã thua trận đấu đầu tiên.
fdrv 15/03/2016

@fdrv Bạn phải đặt lại chỉ số cuối cùng về 0 trước khi bắt đầu vòng lặp: this.lastIndex = 0;
CF

15

Đặt công cụ gsửa đổi cho trận đấu toàn cầu:

/…/g

11
Điều này thực sự không giải quyết được vấn đề: "Sử dụng cờ toàn cầu, 'g', sẽ khớp với tất cả các lần xuất hiện, nhưng chỉ trả về các chuỗi con được khớp hoàn toàn, không phải các khóa và giá trị được tách riêng."
Adam Franco

11

Nguồn:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

Tìm trận đấu liên tiếp

Nếu biểu thức chính quy của bạn sử dụng cờ "g", bạn có thể sử dụng phương thức exec () nhiều lần để tìm các kết quả khớp nối tiếp trong cùng một chuỗi. Khi bạn làm như vậy, tìm kiếm bắt đầu ở chuỗi con của str được chỉ định bởi thuộc tính Last Index của biểu thức chính quy (test () cũng sẽ tiến tới thuộc tính Last Index). Ví dụ: giả sử bạn có tập lệnh này:

var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
  var msg = 'Found ' + myArray[0] + '. ';
  msg += 'Next match starts at ' + myRe.lastIndex;
  console.log(msg);
}

Kịch bản này hiển thị văn bản sau:

Found abb. Next match starts at 3
Found ab. Next match starts at 912

Lưu ý: Không đặt biểu thức chính quy (hoặc hàm tạo RegExp) trong điều kiện while hoặc nó sẽ tạo ra một vòng lặp vô hạn nếu có một kết quả khớp do thuộc tính Last Index được đặt lại sau mỗi lần lặp. Ngoài ra, hãy chắc chắn rằng cờ toàn cầu được đặt hoặc vòng lặp cũng sẽ xảy ra ở đây.


Nếu tôi gọi để xác thực mã này myRe.test (str) và sau đó thử làm trong khi nó xuất hiện ở trận đấu thứ hai và chúng tôi đã thua trận đầu tiên.
fdrv 15/03/2016

Bạn cũng có thể kết hợp String.prototype.matchvới gcờ: 'abbcdefabh'.match(/ab*/g)trả về['abb', 'ab']
thom_nic

2

Nếu ai đó (như tôi) cần phương pháp của Tomalak với hỗ trợ mảng (nghĩa là nhiều lựa chọn), thì đây là:

function getUrlParams(url) {
  var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
      match, params = {},
      decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};

  if (typeof url == "undefined") url = document.location.href;

  while (match = re.exec(url)) {
    if( params[decode(match[1])] ) {
        if( typeof params[decode(match[1])] != 'object' ) {
            params[decode(match[1])] = new Array( params[decode(match[1])], decode(match[2]) );
        } else {
            params[decode(match[1])].push(decode(match[2]));
        }
    }
    else
        params[decode(match[1])] = decode(match[2]);
  }
  return params;
}
var urlParams = getUrlParams(location.search);

đầu vào ?my=1&my=2&my=things

kết quả 1,2,things(chỉ trả lại trước đó: điều)


1

Chỉ cần gắn bó với câu hỏi được đề xuất như được chỉ định bởi tiêu đề, bạn thực sự có thể lặp lại qua từng trận đấu trong một chuỗi bằng cách sử dụng String.prototype.replace(). Ví dụ, sau đây chỉ cần lấy một mảng của tất cả các từ dựa trên biểu thức chính quy:

function getWords(str) {
  var arr = [];
  str.replace(/\w+/g, function(m) {
    arr.push(m);
  });
  return arr;
}

var words = getWords("Where in the world is Carmen Sandiego?");
// > ["Where", "in", "the", "world", "is", "Carmen", "Sandiego"]

Nếu tôi muốn có được các nhóm bắt hoặc thậm chí chỉ số của mỗi trận đấu, tôi cũng có thể làm điều đó. Phần sau đây cho biết cách mỗi trận đấu được trả về với toàn bộ trận đấu, nhóm bắt giữ thứ nhất và chỉ mục:

function getWords(str) {
  var arr = [];
  str.replace(/\w+(?=(.*))/g, function(m, remaining, index) {
    arr.push({ match: m, remainder: remaining, index: index });
  });
  return arr;
}

var words = getWords("Where in the world is Carmen Sandiego?");

Sau khi chạy ở trên, wordssẽ như sau:

[
  {
    "match": "Where",
    "remainder": " in the world is Carmen Sandiego?",
    "index": 0
  },
  {
    "match": "in",
    "remainder": " the world is Carmen Sandiego?",
    "index": 6
  },
  {
    "match": "the",
    "remainder": " world is Carmen Sandiego?",
    "index": 9
  },
  {
    "match": "world",
    "remainder": " is Carmen Sandiego?",
    "index": 13
  },
  {
    "match": "is",
    "remainder": " Carmen Sandiego?",
    "index": 19
  },
  {
    "match": "Carmen",
    "remainder": " Sandiego?",
    "index": 22
  },
  {
    "match": "Sandiego",
    "remainder": "?",
    "index": 29
  }
]

Để phù hợp với nhiều lần xuất hiện tương tự như những gì có sẵn trong PHP với preg_match_allbạn, bạn có thể sử dụng kiểu suy nghĩ này để làm cho riêng mình hoặc sử dụng một cái gì đó như thế YourJS.matchAll(). YourJS ít nhiều định nghĩa chức năng này như sau:

function matchAll(str, rgx) {
  var arr, extras, matches = [];
  str.replace(rgx.global ? rgx : new RegExp(rgx.source, (rgx + '').replace(/[\s\S]+\//g , 'g')), function() {
    matches.push(arr = [].slice.call(arguments));
    extras = arr.splice(-2);
    arr.index = extras[0];
    arr.input = extras[1];
  });
  return matches[0] ? matches : null;
}

Vì bạn muốn phân tích chuỗi truy vấn của URL, bạn cũng có thể sử dụng một cái gì đó như YourJS.parseQS()( yourjs.com/snippets/56 ), mặc dù rất nhiều thư viện khác cũng cung cấp chức năng này.
Chris West

Sửa đổi một biến từ một phạm vi bên ngoài trong một vòng lặp được cho là trả lại một sự thay thế là loại xấu. Thay thế lạm dụng của bạn ở đây
Juan Mendes

1

Nếu bạn có thể thoát khỏi việc sử dụng mapthì đây là giải pháp bốn dòng:

var mystring = '1111342=Adam%20Franco&348572=Bob%20Jones';

var result = mystring.match(/(&|&amp;)?([^=]+)=([^&]+)/g) || [];
result = result.map(function(i) {
  return i.match(/(&|&amp;)?([^=]+)=([^&]+)/);
});

console.log(result);

Không đẹp, không hiệu quả, nhưng ít nhất nó nhỏ gọn. ;)


1

Sử dụng window.URL:

> s = 'http://www.example.com/index.html?1111342=Adam%20Franco&348572=Bob%20Jones'
> u = new URL(s)
> Array.from(u.searchParams.entries())
[["1111342", "Adam Franco"], ["348572", "Bob Jones"]]

1

Hеllo từ năm 2020. Hãy để tôi chú ý đến String.prototype.match ALL () :

let regexp = /(?:&|&amp;)?([^=]+)=([^&]+)/g;
let str = '1111342=Adam%20Franco&348572=Bob%20Jones';

for (let match of str.matchAll(regexp)) {
    let [full, key, value] = match;
    console.log(key + ' => ' + value);
}

Đầu ra:

1111342 => Adam%20Franco
348572 => Bob%20Jones

Cuối cùng! Lưu ý thận trọng: "ECMAScript 2020, phiên bản thứ 11, giới thiệu phương thức matchAll cho Chuỗi, để tạo ra một trình vòng lặp cho tất cả các đối tượng khớp được tạo bởi biểu thức chính quy toàn cục" . Theo trang web được liên kết trong câu trả lời, hầu hết các trình duyệt & nodeJS hiện hỗ trợ nó, nhưng không phải IE, Safari hoặc Samsung Internet. Hy vọng sự hỗ trợ sẽ sớm được mở rộng, nhưng YMMV trong một thời gian.
Adam Franco

0

Để nắm bắt một số tham số bằng cùng tên, tôi đã sửa đổi vòng lặp while trong phương thức của Tomalak như sau:

  while (match = re.exec(url)) {
    var pName = decode(match[1]);
    var pValue = decode(match[2]);
    params[pName] ? params[pName].push(pValue) : params[pName] = [pValue];
  }

đầu vào: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

trả về: {firstname : ["george", "bill"], lastname : ["bush", "clinton"]}


Mặc dù tôi thích ý tưởng của bạn, nhưng nó không hoạt động tốt với các thông số duy nhất, như ?cinema=1234&film=12&film=34tôi mong đợi {cinema: 1234, film: [12, 34]}. Chỉnh sửa câu trả lời của bạn để phản ánh điều này.
TWiStErRob

0

Chà ... tôi đã có một vấn đề tương tự ... Tôi muốn tìm kiếm gia tăng / bước với RegExp (ví dụ: bắt đầu tìm kiếm ... thực hiện một số xử lý ... tiếp tục tìm kiếm cho đến khi kết hợp cuối cùng)

Sau rất nhiều lần tìm kiếm trên mạng ... như mọi khi (điều này hiện đang trở thành thói quen), tôi kết thúc với StackOverflow và tìm thấy câu trả lời ...

Điều không được đề cập và vấn đề cần đề cập là " lastIndex" Bây giờ tôi đã hiểu tại sao đối tượng RegExp thực hiện thuộc tính " lastIndex"


0

Chia nó có vẻ như là lựa chọn tốt nhất đối với tôi:

'1111342=Adam%20Franco&348572=Bob%20Jones'.split('&').map(x => x.match(/(?:&|&amp;)?([^=]+)=([^&]+)/))

0

Để tránh regex hell bạn có thể tìm thấy trận đấu đầu tiên của mình, hãy cắt một đoạn sau đó cố gắng tìm trận tiếp theo trên chuỗi con. Trong C #, giao diện này trông giống như thế này, xin lỗi tôi đã không chuyển nó sang JavaScript cho bạn.

        long count = 0;
        var remainder = data;
        Match match = null;
        do
        {
            match = _rgx.Match(remainder);
            if (match.Success)
            {
                count++;
                remainder = remainder.Substring(match.Index + 1, remainder.Length - (match.Index+1));
            }
        } while (match.Success);
        return count;
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.