Birden çok örneği JavaScript'te PHP'nin preg_match_all () yöntemine benzer bir normal ifadeyle nasıl eşleştirebilirim?


160

Anahtar = değer çiftlerinden biri &veya ayrılmış ayrılmış oluşan url kodlu dizeleri ayrıştırmak çalışıyorum &.

Aşağıdakiler, anahtarları ve değerleri ayrı sonuç öğelerine ayırarak yalnızca ilk tekrarlamayla eşleşir:

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

'1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' dizesinin sonuçları şöyle olacaktır:

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

'G' genel bayrağını kullanmak tüm oluşumlarla eşleşir, ancak ayrılmış anahtarlar ve değerleri değil, yalnızca tam olarak eşleşen alt dizeleri döndürür:

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

'1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' dizesinin sonuçları şöyle olacaktır:

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

Ben dize bölmek &ve her anahtar / değer çifti ayrı ayrı parçalamak rağmen /(?:&|&)?([^=]+)=([^&]+)/, PHP'nin preg_match_all()işlevine benzer desen birden çok kez eşleştirmek için JavaScript düzenli ifade desteği kullanarak herhangi bir yolu var mı?

Aşağıdaki gibi alt-maçlar ile sonuç almak için bir yol hedefliyorum:

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

veya

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

9
replaceburada kimsenin önerilmemesi biraz tuhaf . var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });yapılır. JavaScript'teki "matchAll", bir dize yerine yeni bir işleyici işleviyle "değiştir" dir.
Mike 'Pomax' Kamermans

Bu soruyu 2020'de hala bulanlar için yanıtın "normal ifade kullanmayın, URLSearchParams'ı kullanın , tüm bunları sizin için yapar."
Mike 'Pomax' Kamermans

Yanıtlar:


161

Yorumlardan çekilen

2020 yorumu: regex kullanmak yerine, şimdi URLSearchParamstüm bunları bizim için yapıyor, bu yüzden artık regex dışında özel bir kod gerekli değil.

- Mike 'Pomax' Kamermans

Tarayıcı desteği burada listelenmiştir https://caniuse.com/#feat=urlsearchparams


Parametrelerin adını ve değerini ayrı ayrı yakalamak için alt grupları kullanarak alternatif bir normal ifade öneririm 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 bir nesnedir:

{
  f: "q"
  coğrafi kod: ""
  hl: "de"
  yani: "UTF8"
  iwloc: "adres"
  ll: "50.116616,8.680573"
  q: "Frankfurt am Main"
  sll: "50.106047,8.679886"
  kaynak: "s_q"
  spn: "0.35972,0.833588"
  sspn: "0.370369,0.833588"
  z: "11"
}

Normal ifade aşağıdaki gibi parçalanır:

(?: # ele geçirmeyen grup
  \? | & # "?" veya "&"
  (: Amp;?)? # (yanlış HTML kodlamalı URL'ler için "& amp;" öğesine izin verin)
) # son yakalamayan grup
( # grup 1
  [^ = & #] + # "=", "&" veya "#" dışında herhangi bir karakter; en azından bir kere
) # bitiş grubu 1 - bu parametrenin adı olacak
(?: # ele geçirmeyen grup
  =? # an "=", isteğe bağlı
  (# grup 2
    [^ & #] * # "&" veya "#" dışında herhangi bir karakter; herhangi bir sayıda
  ) # son grup 2 - bu parametrenin değeri olacaktır
) # son yakalamayan grup

23
Ben de bunu umuyordum. JavaScript belgelerinde hiç görmediğim şey, exec () yönteminin birden fazla çağrılırsa bir sonraki sonuç kümesini döndürmeye devam edeceğidir. Büyük ipucu için tekrar teşekkürler!
Adam Franco

1
Bu nedenle yapar: regular-expressions.info/javascript.html (Okuyun: "JavaScript RegExp Nesnesi Nasıl Kullanılır")
Tomalak

1
bu kodda bir hata var: "while" işaretinden sonra noktalı virgül kaldırılmalıdır.
Jan Willem B

1
Çünkü genellikle sadece içerikleriyle ilgileniyorsam normal (yani yakalama) grupları kullanıyorum.
Tomalak

1
@KnightYoshi Evet. JavaScript herhangi ifadesi de (gibi kendi sonucunu üretir x = yatayın yiçin xve ayrıca üretmek y). Bu bilgiyi aşağıdakilere uyguladığımızda if (match = re.exec(url)): Bu A) ödevi yapar ve B) 'nin sonucunu re.exec(url)verir while. Şimdi bir eşleşme yoksa re.execdöndürür null, ki bu bir falsy değeridir. Yani aslında bir eşleşme olduğu sürece döngü devam edecektir.
Tomalak

67

Global bir arama için 'g' anahtarını kullanmanız gerekir

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

33
Bu aslında sorunu çözmez: "'g' genel bayrağını kullanmak tüm oluşumlarla eşleşir, ancak ayrılmış anahtarlar ve değerleri değil, yalnızca tam olarak eşleşen alt dizeleri döndürür."
Adam Franco

40

2020 düzenleme

URLSearchParams kullanın , çünkü bu iş artık herhangi bir özel kod gerektirmez. Tarayıcılar bunu sizin için tek bir kurucu ile yapabilir:

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

verim

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

Yani artık bunun için normal ifade kullanmak için bir neden yok.

Orijinal cevap

Çalışan execstil eşleşmesi ile birlikte gelen "kör eşleme" ye güvenmek istemiyorsanız , JavaScript yerleşik tümüyle eşleme işleviyle birlikte gelir, ancak replace"yakalama ile ne yapmalı?" gruplar " taşıma fonksiyonu :

var data = {};

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

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

yapılır.

Değiştirme dizelerini gerçekten döndürmek için yakalama grubu işleme işlevini kullanmak yerine (değiştirme işlemi için ilk arg, tam kalıp eşleşmesidir ve sonraki bağımsız değişkenler ayrı yakalama gruplarıdır), grup 2 ve 3 yakalamaları alır ve bu önbelleği alırız.

Bu nedenle, karmaşık ayrıştırma işlevleri yazmak yerine, JavaScript'teki "matchAll" işlevinin yalnızca bir yedek işleyici işleviyle "değiştir" olduğunu ve çok sayıda desen eşleştirme verimliliğinin olabileceğini unutmayın.


Bir ipim var something "this one" and "that one". Çift tırnaklı dizelerin tümünü bir listeye yerleştirmek istiyorum yani [bu, şu]. Şimdiye kadar mystring.match(/"(.*?)"/)ilkini tespit etmede iyi çalışıyor, ancak çözümünüzü tek bir yakalama grubu için nasıl uyarlayacağımı bilmiyorum.
nu everest

2
yorumlarda çözmeye çalışmak yerine bunun için Stackoverflow'a bir soru göndermeniz gerektiği gibi görünüyor.
Mike 'Pomax' Kamermans


1
Bu cevabın neden bu kadar az oy verdiğinden emin değilim ama sorunun en iyi cevabı bu.
Calin

Merhaba @ Mike'Pomax'Kamermans, topluluk kılavuz satırları, girişleri iyileştirmek için özellikle düzenlemenizi önerir, bkz . Stackoverflow.com/help/behavior . Cevabınızın çekirdeği son derece yardımcı oldu, ancak "matchAll'ın değiştirildiğini hatırla" dilinin net olmadığını ve kodunuzun neden (açık olmayan) çalıştığının bir açıklaması olmadığını buldum. Hak ettiğiniz temsilcisi almanız gerektiğini düşündüm, bu yüzden cevabınızı iyileştirilmiş metinle çoğaltmak yerine düzenledim. Bu sorunun asıl adamı olarak, yine de istiyorsan, bu cevabın (ve düzenlemenin) kabulünü geri vermekten mutluluk duyuyorum.
Adam Franco

21

Grupları yakalamak için preg_match_all, PHP'de kullanmaya alışkınım ve işlevselliğini burada kopyalamaya çalıştım:

<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 genel değiştiriciyi belirtmeniz gerekir, /gaksi takdirde çalışan exec()geçerli dizini değiştirmez ve sonsuza kadar döngü yapar.
Aram Kocharyan

Bu kodu myRe.test (str) doğrulamak için çağırır ve sonra execAll yapmayı denerseniz, ikinci maçta yıldız olur ve ilk maçı kaybettik.
fdrv

@fdrv Döngüyü başlatmadan önce lastIndex öğesini sıfırlamanız gerekir: this.lastIndex = 0;
CF

15

Genel geşleme için değiştiriciyi ayarlayın :

/…/g

11
Bu aslında sorunu çözmez: "'g' genel bayrağını kullanmak tüm oluşumlarla eşleşir, ancak ayrılmış anahtarlar ve değerleri değil, yalnızca tam olarak eşleşen alt dizeleri döndürür."
Adam Franco

11

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

Art arda maçlar bulma

Normal ifadeniz "g" bayrağını kullanıyorsa, aynı dizede birbirini izleyen eşleşmeleri bulmak için exec () yöntemini birden çok kez kullanabilirsiniz. Bunu yaptığınızda, arama normal ifadenin lastIndex özelliği (test () de lastIndex özelliğini ilerletir) tarafından belirtilen str alt dizesinde başlar. Örneğin, şu komut dosyasına sahip olduğunuzu varsayın:

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

Bu komut dosyası aşağıdaki metni görüntüler:

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

Not: Normal ifade değişmezini (veya RegExp yapıcısını) while durumuna yerleştirmeyin veya lastIndex özelliğinin her yinelemede sıfırlanması nedeniyle bir eşleşme varsa sonsuz bir döngü oluşturur. Ayrıca genel bayrağın ayarlandığından veya burada da bir döngü oluşacağından emin olun.


Bu kodu myRe.test (str) doğrulamak ve sonra denemek denemek, ikinci maçta yıldız ve biz ilk maç kaybetti.
fdrv

Ayrıca birleştirebilirsiniz String.prototype.matchile gbayrak: 'abbcdefabh'.match(/ab*/g)döner['abb', 'ab']
thom_nic

2

Birisi (benim gibi) Tomalak'ın dizi desteği ile (yani çoklu seçim) yöntemine ihtiyaç duyuyorsa, işte burada:

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

giriş ?my=1&my=2&my=things

sonuç 1,2,things(daha önce yalnızca iade: şeyler)


1

Sadece başlıkta belirtildiği gibi önerilen soruya sadık kalmak için, gerçekte kullanarak bir dizede her maçta tekrarlayabilirsiniz String.prototype.replace(). Örneğin, normal bir ifadeye dayanan tüm kelimelerin bir dizisini elde etmek için aşağıdakileri yapın:

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

Eğer yakalama grupları ve hatta her maçın dizinini almak istersem bunu da yapabilirdim. Aşağıda her maçın tüm maç, 1. yakalama grubu ve endeks ile nasıl döndürüldüğü gösterilmektedir:

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

Yukarıdakileri çalıştırdıktan sonra, wordsaşağıdaki gibi olacaktır:

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

PHP'de mevcut olana benzer birden fazla olayı eşleştirmek preg_match_alliçin, kendi türünüzü oluşturmak veya benzeri bir şey kullanmak için bu tür düşünceyi kullanabilirsiniz YourJS.matchAll(). JS'niz aşağı yukarı bu işlevi şu şekilde tanımlar:

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

Bir URL'nin sorgu dizesini ayrıştırmak istediğiniz için YourJS.parseQS()( yourjs.com/snippets/56 ) gibi bir şey de kullanabilirsiniz , ancak diğer birçok kitaplık da bu işlevselliği sunar.
Chris West

Değişimi döndürmesi beklenen bir döngüde dış kapsamdan bir değişkeni değiştirmek biraz kötüdür. Kötüye kullanımın yerine
Juan Mendes

1

Bunu kullanmaktan kurtulabiliyorsanız mapdört satırlık bir çözümdür:

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

Güzel değil, verimli değil, ama en azından kompakt. ;)


1

Kullanım 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 2020'den itibaren. Dikkatinizi String.prototype.matchAll () getireyim :

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

Çıktılar:

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

En sonunda! Dikkat edilmesi gereken bir nokta: "11. baskı olan ECMAScript 2020, genel bir düzenli ifade tarafından oluşturulan tüm eşleme nesneleri için bir yineleyici üretmek üzere Dizeler için matchAll yöntemini sunar" . Yanıtta bağlantı verilen siteye göre, çoğu tarayıcı ve nodeJS şu anda destekliyor, ancak IE, Safari veya Samsung Internet'i desteklemiyor. Umarım destek yakında genişler, ancak bir süre YMMV.
Adam Franco

0

Aynı adı kullanarak birkaç parametre yakalamak için Tomalak'ın yönteminde while döngüsünü değiştirdim:

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

giriş: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

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


Fikrinizi beğenmeme rağmen, ?cinema=1234&film=12&film=34beklediğim gibi tek parametrelerle iyi çalışmıyor {cinema: 1234, film: [12, 34]}. Cevabınızı bunu yansıtacak şekilde düzenledi.
TWiStErRob

0

Şey ... Benzer bir sorunum vardı ... RegExp ile artımlı / adım arama istiyorum (örneğin: aramaya başla ... biraz işlem yap ... son eşleşmeye kadar aramaya devam et)

İnternet arama bir sürü sonra ... her zamanki gibi (bu şimdi bir alışkanlık dönüyor) Ben StackOverflow sonunda ve cevabını buldum ...

Whats atıfta bulunulmuyor ve söz konusu hususlar " lastIndex" RegExp nesnesinin neden " lastIndex" özelliğini uyguladığını anlıyorum


0

Bölmek benim için en iyi seçenek gibi görünüyor:

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

0

Regex cehennemden kaçınmak için ilk eşleşmenizi bulabilir, bir parça kesin ve alt dizede bir sonraki bulmaya çalışın. C # bu böyle bir şey görünüyor, üzgünüm sizin için JavaScript taşındı değil.

        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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.