Bagaimana saya bisa mencocokkan banyak kejadian dengan regex dalam JavaScript mirip dengan preg_match_all () PHP?

160

Saya mencoba untuk menguraikan string url-encoded yang terdiri dari pasangan kunci = nilai yang dipisahkan oleh salah satu &atau &.

Berikut ini hanya akan cocok dengan kejadian pertama, memecah kunci dan nilai menjadi elemen hasil yang terpisah:

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

Hasil untuk string '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' adalah:

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

Menggunakan flag global, 'g', akan cocok dengan semua kejadian, tetapi hanya mengembalikan sub-string yang sepenuhnya cocok, bukan kunci dan nilai yang dipisahkan:

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

Hasil untuk string '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' adalah:

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

Sementara saya dapat memisahkan string &dan memecah setiap pasangan kunci / nilai secara terpisah, apakah ada cara menggunakan dukungan ekspresi reguler JavaScript untuk mencocokkan beberapa kemunculan pola yang /(?:&|&)?([^=]+)=([^&]+)/mirip dengan preg_match_all()fungsi PHP ?

Saya bertujuan untuk beberapa cara untuk mendapatkan hasil dengan sub-pertandingan terpisah seperti:

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

atau

[['1111342', 'Adam%20Franco'], ['348572', 'Bob%20Jones']]
Adam Franco
sumber
9
agak aneh bahwa tidak ada yang merekomendasikan menggunakan di replacesini. var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });selesai "matchAll" dalam JavaScript adalah "replace" dengan fungsi handler pengganti, bukan string.
Mike 'Pomax' Kamermans
Perhatikan bahwa bagi mereka yang masih menemukan pertanyaan ini pada tahun 2020, jawabannya adalah "jangan gunakan regex, gunakan URLSearchParams , yang melakukan semua ini untuk Anda."
Mike 'Pomax' Kamermans

Jawaban:

161

Diangkat dari komentar

Komentar 2020: daripada menggunakan regex, kami sekarang memiliki URLSearchParams, yang melakukan semua ini untuk kami, jadi tidak ada kode khusus, apalagi regex, yang diperlukan lagi.

- Mike 'Pomax' Kamermans

Dukungan peramban tercantum di sini https://caniuse.com/#feat=urlsearchparams


Saya akan menyarankan regex alternatif, menggunakan sub-kelompok untuk menangkap nama dan nilai parameter secara individual dan 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 adalah sebuah objek:

{
  f: "q"
  geocode: ""
  hl: "de"
  yaitu: "UTF8"
  iwloc: "addr"
  ll: "50.116616,8.680573"
  q: "Frankfurt am Main"
  sll: "50.106047,8.679886"
  sumber: "s_q"
  spn: "0.35972,0.833588"
  sspn: "0.370369,0.833588"
  z: "11"
}

Regex terurai sebagai berikut:

(?: # grup yang tidak menangkap
  \? | & # "?" atau "&"
  (?: amp;)? # (izinkan "& amp;", untuk URL yang disandikan HTML dengan salah)
) # end grup yang tidak menangkap
( # grup 1
  [^ = & #] + # karakter apa saja kecuali "=", "&" atau "#"; setidaknya sekali
) # end group 1 - ini akan menjadi nama parameter
(?: # grup yang tidak menangkap
  =? # an "=", opsional
  (# grup 2
    [^ & #] * # karakter apa pun kecuali "&" atau "#"; beberapa kali
  ) # end group 2 - ini akan menjadi nilai parameter
) # end grup yang tidak menangkap
Tomalak
sumber
23
Inilah yang saya harapkan. Apa yang belum pernah saya lihat dalam dokumentasi JavaScript menyebutkan bahwa metode exec () akan terus mengembalikan set hasil berikutnya jika dipanggil lebih dari sekali. Sekali lagi terima kasih atas tipnya!
Adam Franco
1
Itu karena ini: regular-expressions.info/javascript.html (Baca: "Cara Menggunakan Objek JavaScript RegExp")
Tomalak
1
ada bug dalam kode ini: titik koma setelah "sementara" harus dihapus.
Jan Willem B
1
Karena saya biasanya hanya menggunakan grup normal (yaitu menangkap) jika saya benar-benar tertarik pada konten mereka.
Tomalak
1
@KnightYoshi Ya. Dalam JavaScript ekspresi apapun juga memproduksi hasil sendiri (seperti x = yakan menugaskan yuntuk xdan juga memproduksi y). Ketika kami menerapkan pengetahuan itu untuk if (match = re.exec(url)): Ini A) melakukan tugas dan B) mengembalikan hasil re.exec(url)ke while. Sekarang re.execkembali nulljika tidak ada kecocokan, yang merupakan nilai palsu. Jadi efeknya loop akan berlanjut selama ada kecocokan.
Tomalak
67

Anda perlu menggunakan sakelar 'g' untuk pencarian global

var result = mystring.match(/(&|&)?([^=]+)=([^&]+)/g)
meouw
sumber
33
Ini sebenarnya tidak menyelesaikan masalah: "Menggunakan bendera global, 'g', akan cocok dengan semua kejadian, tetapi hanya mengembalikan sub-string sepenuhnya cocok, bukan kunci dan nilai yang dipisahkan."
Adam Franco
40

Sunting tahun 2020

Gunakan URLSearchParams , karena pekerjaan ini tidak lagi memerlukan segala jenis kode khusus. Browser dapat melakukan ini untuk Anda dengan konstruktor tunggal:

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

hasil panen

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

Jadi tidak ada alasan untuk menggunakan regex untuk ini lagi.

Jawaban asli

Jika Anda tidak ingin bergantung pada "pencocokan buta" yang datang dengan execpencocokan gaya berjalan , JavaScript memang datang dengan fungsi yang cocok dengan semua yang ada di dalamnya, tetapi itu adalah bagian dari replacepanggilan fungsi, saat menggunakan "apa yang harus dilakukan dengan tangkapan fungsi penanganan " :

var data = {};

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

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

selesai

Alih-alih menggunakan fungsi penanganan grup tangkap untuk benar-benar mengembalikan string pengganti (untuk penanganan penggantian, argumen pertama adalah kecocokan pola penuh, dan argumen berikutnya adalah grup tangkapan individu), kami hanya mengambil tangkapan grup 2 dan 3, dan cache yang berpasangan.

Jadi, daripada menulis fungsi parsing yang rumit, ingatlah bahwa fungsi "matchAll" dalam JavaScript hanyalah "ganti" dengan fungsi handler pengganti, dan banyak efisiensi pencocokan pola dapat diperoleh.

Mike 'Pomax' Kamermans
sumber
Saya punya string something "this one" and "that one". Saya ingin menempatkan semua string yang dikutip ganda dalam daftar yaitu [yang ini, yang itu]. Sejauh ini mystring.match(/"(.*?)"/)berfungsi dengan baik dalam mendeteksi yang pertama, tapi saya tidak tahu bagaimana mengadaptasi solusi Anda untuk satu kelompok penangkapan tunggal.
nu everest
2
Sepertinya Anda harus memposting pertanyaan tentang Stackoverflow untuk itu, daripada mencoba menyelesaikannya dalam komentar.
Mike 'Pomax' Kamermans
Saya telah membuat pertanyaan baru: stackoverflow.com/questions/26174122/…
nu everest
1
Tidak yakin mengapa jawaban ini memiliki sedikit upvotes tetapi ini adalah jawaban terbaik untuk pertanyaan itu.
Calin
Hai @ Mike'Pomax'Kamermans, garis panduan komunitas secara khusus merekomendasikan entri pengeditan untuk memperbaikinya, lihat: stackoverflow.com/help/behavior . Inti dari jawaban Anda sangat membantu, tetapi saya menemukan bahasa "ingat bahwa matchAll diganti" tidak jelas dan bukan penjelasan mengapa kode Anda (yang tidak jelas) berfungsi. Saya pikir Anda harus mendapatkan perwakilan yang layak, jadi saya mengedit jawaban Anda daripada menduplikatnya dengan teks yang lebih baik. Sebagai penanya asli pertanyaan ini, saya senang mengembalikan penerimaan - atas jawaban ini (dan hasil edit) jika Anda tetap menginginkannya.
Adam Franco
21

Untuk mengambil grup, saya terbiasa menggunakan preg_match_allPHP dan saya sudah mencoba mereplikasi fungsinya di sini:

<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>
Aram Kocharyan
sumber
3
@teh_senaus Anda perlu menentukan pengubah global dengan /gmenjalankan yang lain exec()tidak akan mengubah indeks saat ini dan akan mengulang selamanya.
Aram Kocharyan
Jika saya menelepon untuk memvalidasi kode ini myRe.test (str) dan kemudian coba lakukan execAll, ia membintangi pada pertandingan kedua dan kami kehilangan pertandingan pertama.
fdrv
@ fdrv Anda harus mereset lastIndex ke nol sebelum memulai loop: this.lastIndex = 0;
CF
15

Tetapkan gpengubah untuk pertandingan global:

/…/g
Gumbo
sumber
11
Ini sebenarnya tidak menyelesaikan masalah: "Menggunakan bendera global, 'g', akan cocok dengan semua kejadian, tetapi hanya mengembalikan sub-string sepenuhnya cocok, bukan kunci dan nilai yang dipisahkan."
Adam Franco
11

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

Menemukan kecocokan berturut-turut

Jika ekspresi reguler Anda menggunakan bendera "g", Anda dapat menggunakan metode exec () beberapa kali untuk menemukan kecocokan berturut-turut dalam string yang sama. Ketika Anda melakukannya, pencarian dimulai pada substring str yang ditentukan oleh properti lastIndex ekspresi reguler (test () juga akan memajukan properti lastIndex). Misalnya, anggap Anda memiliki skrip ini:

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

Script ini menampilkan teks berikut:

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

Catatan: Jangan letakkan literal ekspresi reguler (atau konstruktor RegExp) dalam kondisi while atau itu akan membuat infinite loop jika ada kecocokan karena propertiIndex terakhir disetel ulang pada setiap iterasi. Juga pastikan bahwa flag global diatur atau loop akan terjadi di sini juga.

KIM Taegyoon
sumber
Jika saya menelepon untuk memvalidasi kode ini myRe.test (str) dan kemudian coba lakukan sementara, itu dibintangi pada pertandingan kedua dan kami kehilangan pertandingan pertama.
fdrv
Anda juga dapat menggabungkan String.prototype.matchdengan gflag: 'abbcdefabh'.match(/ab*/g)returns['abb', 'ab']
thom_nic
2

Jika seseorang (seperti saya) membutuhkan metode Tomalak dengan dukungan array (mis., Beberapa pilih), ini dia:

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

memasukkan ?my=1&my=2&my=things

hasil 1,2,things(sebelumnya hanya dikembalikan: barang)

fedu
sumber
1

Hanya untuk tetap dengan pertanyaan yang diajukan seperti yang ditunjukkan oleh judul, Anda dapat benar-benar mengulangi setiap pertandingan dalam menggunakan string String.prototype.replace(). Sebagai contoh, berikut ini tidak hanya untuk mendapatkan array dari semua kata berdasarkan ekspresi reguler:

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"]

Jika saya ingin mendapatkan kelompok tangkapan atau bahkan indeks setiap pertandingan saya bisa melakukannya juga. Berikut ini menunjukkan bagaimana setiap pertandingan dikembalikan dengan seluruh pertandingan, grup tangkapan pertama dan indeks:

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

Setelah menjalankan hal di atas, wordsakan menjadi sebagai berikut:

[
  {
    "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
  }
]

Untuk mencocokkan beberapa kejadian mirip dengan apa yang tersedia di PHP dengan preg_match_allAnda dapat menggunakan jenis pemikiran ini untuk membuat Anda sendiri atau menggunakan sesuatu seperti YourJS.matchAll(). SJ Anda kurang lebih mendefinisikan fungsi ini sebagai berikut:

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;
}
Chris West
sumber
Karena Anda ingin mengurai string kueri URL, Anda juga bisa menggunakan sesuatu seperti YourJS.parseQS()( yourjs.com/snippets/56 ), meskipun banyak perpustakaan lain juga menawarkan fungsi ini.
Chris West
Memodifikasi variabel dari lingkup luar dalam satu lingkaran yang seharusnya mengembalikan pengganti agak buruk. Pengganti penyalahgunaan Anda di sini
Juan Mendes
1

Jika Anda dapat menggunakan mapini adalah solusi empat baris:

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

Tidak cantik, tidak efisien, tetapi setidaknya itu kompak. ;)

fboes
sumber
1

Gunakan 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"]]
jnnnnn
sumber
1

HELlo dari 2020. Biarkan saya membawa String.prototype.matchAll () ke perhatian Anda:

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

Output:

1111342 => Adam%20Franco
348572 => Bob%20Jones
Klesun
sumber
Akhirnya! Sebuah catatan peringatan: "ECMAScript 2020, edisi ke-11, memperkenalkan metode matchAll untuk Strings, untuk menghasilkan iterator untuk semua objek pertandingan yang dihasilkan oleh ekspresi reguler global" . Menurut situs yang ditautkan dalam jawaban, sebagian besar browser & nodeJS mendukungnya saat ini, tetapi tidak untuk IE, Safari, atau Samsung Internet. Semoga dukungan akan segera meluas, tapi YMMV sebentar.
Adam Franco
0

Untuk menangkap beberapa parameter menggunakan nama yang sama, saya memodifikasi loop sementara dalam metode Tomalak seperti ini:

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

memasukkan: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

pengembalian: {firstname : ["george", "bill"], lastname : ["bush", "clinton"]}

ivar
sumber
Meskipun saya menyukai ide Anda, itu tidak bekerja dengan baik dengan params tunggal, seperti yang ?cinema=1234&film=12&film=34saya harapkan {cinema: 1234, film: [12, 34]}. Sunting jawaban Anda untuk mencerminkan ini.
TWiStErRob
0

Yah ... Saya punya masalah yang sama ... Saya ingin pencarian bertahap / langkah dengan RegExp (misalnya: mulai mencari ... melakukan beberapa pemrosesan ... melanjutkan pencarian sampai pertandingan terakhir)

Setelah banyak pencarian internet ... seperti biasa (ini mengubah kebiasaan sekarang) saya berakhir di StackOverflow dan menemukan jawabannya ...

Whats tidak dirujuk dan yang perlu disebutkan adalah " lastIndex" Saya sekarang mengerti mengapa objek RegExp mengimplementasikan lastIndexproperti " "

ZEE
sumber
0

Membagi itu sepertinya pilihan terbaik bagi saya:

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

Untuk menghindari regex hell Anda dapat menemukan pasangan pertama Anda, memotong sepotong kemudian mencoba untuk menemukan yang berikutnya pada substring. Dalam C # ini terlihat seperti ini, maaf saya belum porting ke JavaScript untuk Anda.

        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;
andrew pate
sumber