Jak mogę dopasować wiele wystąpień do wyrażenia regularnego w JavaScript podobnego do preg_match_all () w PHP?


160

Próbuję przeanalizować ciągi zakodowane w postaci adresu URL, które składają się z par klucz = wartość oddzielonych przez albo &lub &.

Poniższe elementy dopasują tylko pierwsze wystąpienie, dzieląc klucze i wartości na oddzielne elementy wyników:

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

Wyniki dla ciągu „1111342 = Adam% 20Franco & 348572 = Bob% 20Jones” byłyby następujące:

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

Użycie flagi globalnej „g” spowoduje dopasowanie wszystkich wystąpień, ale zwróci tylko w pełni dopasowane podciągi, a nie rozdzielone klucze i wartości:

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

Wyniki dla ciągu „1111342 = Adam% 20Franco & 348572 = Bob% 20Jones” byłyby następujące:

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

Chociaż mógłbym podzielić ciąg &i rozdzielić każdą parę klucz / wartość osobno, czy istnieje sposób, aby użyć obsługi wyrażeń regularnych JavaScript, aby dopasować wiele wystąpień wzorca, /(?:&|&)?([^=]+)=([^&]+)/podobnie jak preg_match_all()funkcja PHP ?

Staram się znaleźć jakiś sposób, aby uzyskać wyniki z oddzielnymi dopasowaniami, na przykład:

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

lub

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

9
to trochę dziwne, że nikt nie zalecał replacetutaj używania . var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });Gotowe. „matchAll” w JavaScript to „zamień” zastępczą funkcją obsługi zamiast ciągu.
Mike 'Pomax' Kamermans

Zwróć uwagę, że dla tych, którzy nadal napotykają to pytanie w 2020 roku, odpowiedź brzmi: „nie używaj wyrażenia regularnego, użyj adresu URLSearchParams , który robi to wszystko za Ciebie”.
Mike 'Pomax' Kamermans

Odpowiedzi:


161

Podniesiony z komentarzy

Komentarz 2020: zamiast używać wyrażenia regularnego, mamy teraz URLSearchParams, który robi to wszystko za nas, więc żaden niestandardowy kod, nie mówiąc już o wyrażeniu regularnym, nie jest już potrzebny.

- Mike 'Pomax' Kamermans

Obsługę przeglądarek można znaleźć tutaj https://caniuse.com/#feat=urlsearchparams


Sugerowałbym alternatywne wyrażenie regularne, wykorzystujące podgrupy do przechwytywania nazw i wartości parametrów indywidualnie oraz 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 jest obiektem:

{
  f: „q”
  geokod: „”
  hl: "de"
  tj .: „UTF8”
  iwloc: "addr"
  ll: „50.116616,8.680573”
  q: „Frankfurt nad Menem”
  sll: „50.106047,8.679886”
  źródło: „s_q”
  spn: „0.35972,0.833588”
  sspn: „0.370369,0.833588”
  z: „11”
}

Wyrażenie regularne rozkłada się w następujący sposób:

(?: # grupa nieprzechwytująca
  \? | & # "?" lub „&”
  (?: amp;)? # (zezwól na „& amp;” w przypadku błędnie zakodowanych adresów URL w formacie HTML)
) # koniec grupy bez przechwytywania
( # Grupa 1
  [^ = & #] + # dowolny znak oprócz „=”, „&” lub „#”; przynajmniej raz
) # end group 1 - będzie to nazwa parametru
(?: # grupa nieprzechwytująca
  =? # an "=", opcjonalnie
  (# grupa 2
    [^ & #] * # dowolny znak oprócz „&” lub „#”; dowolną liczbę razy
  ) # end group 2 - będzie to wartość parametru
) # koniec grupy bez przechwytywania

23
To jest to, na co liczyłem. To, czego nigdy nie widziałem w dokumentacji JavaScript, to wzmianka, że ​​metoda exec () będzie nadal zwracać następny zestaw wyników, jeśli zostanie wywołana więcej niż raz. Jeszcze raz dziękuję za świetną wskazówkę!
Adam Franco,

1
Dzieje się tak z tego powodu: regular-expressions.info/javascript.html (Przeczytaj: „Jak używać obiektu JavaScript RegExp”)
Tomalak,

1
w tym kodzie jest błąd: średnik po „while” powinien zostać usunięty.
Jan Willem B

1
Ponieważ generalnie używam zwykłych grup (tj. Przechwytujących) tylko wtedy, gdy jestem naprawdę zainteresowany ich zawartością.
Tomalak

1
@KnightYoshi Yes. W JavaScript każde wyrażenie również daje swój własny wynik (jak x = yprzypisałoby się ydo, xa także wyprodukował y). Kiedy zastosujemy tę wiedzę do if (match = re.exec(url)): To A) wykonuje przypisanie i B) zwraca wynik re.exec(url)do while. Teraz re.execzwraca, nulljeśli nie ma dopasowania, co jest fałszywą wartością. W efekcie pętla będzie trwała tak długo, jak długo będzie pasować.
Tomalak

67

Do wyszukiwania globalnego musisz użyć przełącznika „g”

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

33
W rzeczywistości nie rozwiązuje to problemu: „Użycie flagi globalnej 'g' dopasuje wszystkie wystąpienia, ale zwróci tylko w pełni dopasowane podciągi, a nie rozdzielone klucze i wartości”.
Adam Franco

40

Edycja 2020

Użyj URLSearchParams , ponieważ ta praca nie wymaga już żadnego niestandardowego kodu. Przeglądarki mogą to zrobić za Ciebie za pomocą jednego konstruktora:

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

plony

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

Nie ma więc powodu, aby używać do tego regex.

Oryginalna odpowiedź

Jeśli nie chcesz polegać na „dopasowywaniu na ślepo”, które towarzyszy execdopasowaniu stylu, JavaScript ma wbudowaną funkcję dopasowywania wszystkiego, ale jest częścią replacewywołania funkcji, gdy używa się opcji „co zrobić z przechwytywaniem funkcja obsługi grup :

var data = {};

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

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

Gotowe.

Zamiast używać funkcji obsługi grup przechwytywania do faktycznego zwracania ciągów zastępczych (w celu obsługi zamiany pierwszy argument jest pełnym dopasowaniem wzorca, a kolejne argumenty są indywidualnymi grupami przechwytywania), po prostu bierzemy przechwytywania grup 2 i 3 i buforujemy tę parę.

Tak więc, zamiast pisać skomplikowane funkcje analizujące, pamiętaj, że funkcja „matchAll” w JavaScript jest po prostu „zastąpieniem” zastępczą funkcją obsługi i można uzyskać znaczną wydajność dopasowywania wzorców.


Mam sznurek something "this one" and "that one". Chcę umieścić wszystkie ciągi w podwójnych cudzysłowach na liście, tj. [Ten, tamten]. Jak dotąd mystring.match(/"(.*?)"/)działa dobrze przy wykrywaniu pierwszego z nich, ale nie wiem, jak dostosować twoje rozwiązanie do pojedynczej grupy przechwytywania.
nu everest

2
Wygląda na to, że powinieneś zadać pytanie w Stackoverflow, zamiast próbować rozwiązać je w komentarzach.
Mike 'Pomax' Kamermans


1
Nie jestem pewien, dlaczego ta odpowiedź ma tak mało głosów za, ale jest najlepszą odpowiedzią na pytanie.
Calin

Cześć @ Mike'Pomax'Kamermans, przewodniki społeczności szczególnie zalecają edytowanie wpisów w celu ich ulepszenia, patrz: stackoverflow.com/help/behavior . Rdzeń twojej odpowiedzi jest niezwykle pomocny, ale stwierdziłem, że język „pamiętaj, że matchAll jest zamieniany” nie był jasny i nie wyjaśniał, dlaczego Twój kod (co nie jest oczywiste) działa. Pomyślałem, że powinieneś dostać zasłużonego przedstawiciela, więc zredagowałem twoją odpowiedź, zamiast powielać ją z ulepszonym tekstem. Jako osoba, która pierwotnie zadała to pytanie, z przyjemnością cofnę akceptację - tej odpowiedzi (i zmiany), jeśli nadal tego chcesz.
Adam Franco

21

Do przechwytywania grup jestem przyzwyczajony do używania preg_match_allw PHP i próbowałem tutaj odtworzyć jego funkcjonalność:

<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 musisz określić globalny modyfikator, w /gprzeciwnym razie uruchomienie exec()nie zmieni bieżącego indeksu i będzie się zapętlać na zawsze.
Aram Kocharyan,

Jeśli zadzwonię w celu sprawdzenia poprawności tego kodu myRe.test (str), a następnie spróbuję wykonać execAll, zaczyna się w drugim dopasowaniu i przegraliśmy pierwsze dopasowanie.
fdrv

@fdrv Przed rozpoczęciem pętli musisz zresetować lastIndex do zera: this.lastIndex = 0;
CF

15

Ustaw gmodyfikator dla dopasowania globalnego:

/…/g

11
W rzeczywistości nie rozwiązuje to problemu: „Użycie flagi globalnej 'g' dopasuje wszystkie wystąpienia, ale zwróci tylko w pełni dopasowane podciągi, a nie rozdzielone klucze i wartości”.
Adam Franco

11

Źródło:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

Znajdowanie kolejnych dopasowań

Jeśli Twoje wyrażenie regularne używa flagi „g”, możesz użyć metody exec () wiele razy, aby znaleźć kolejne dopasowania w tym samym ciągu. Gdy to zrobisz, wyszukiwanie rozpocznie się od podłańcucha ciągu określonego przez właściwość lastIndex wyrażenia regularnego (test () również przesunie właściwość lastIndex). Na przykład załóżmy, że masz ten skrypt:

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

Ten skrypt wyświetla następujący tekst:

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

Uwaga: Nie umieszczaj literału wyrażenia regularnego (ani konstruktora RegExp) w warunku while, ponieważ spowoduje to utworzenie nieskończonej pętli, jeśli istnieje dopasowanie, ponieważ właściwość lastIndex jest resetowana po każdej iteracji. Upewnij się również, że flaga globalna jest ustawiona, w przeciwnym razie również wystąpi pętla.


Jeśli zadzwonię, aby sprawdzić poprawność tego kodu myRe.test (str), a następnie spróbuję zrobić, to pojawia się na drugim meczu i przegraliśmy pierwszy mecz.
fdrv

Można również łączyć String.prototype.matchz gflagą: 'abbcdefabh'.match(/ab*/g)powroty['abb', 'ab']
thom_nic

2

Jeśli ktoś (taki jak ja) potrzebuje metody Tomalaka z obsługą tablic (czyli wielokrotnego wyboru), oto ona:

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

Wejście ?my=1&my=2&my=things

wynik 1,2,things(wcześniej zwrócone tylko: rzeczy)


1

Aby pozostać przy proponowanym pytaniu wskazanym w tytule, możesz iterować po każdym dopasowaniu w ciągu za pomocą String.prototype.replace(). Na przykład poniższe robi właśnie to, aby uzyskać tablicę wszystkich słów na podstawie wyrażenia regularnego:

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

Gdybym chciał uzyskać grupy przechwytywania lub nawet indeks każdego dopasowania, też mógłbym to zrobić. Poniżej pokazano, jak każde dopasowanie jest zwracane z całym dopasowaniem, pierwszą grupą przechwytywania i indeksem:

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

Po uruchomieniu powyższego wordsbędzie wyglądał następująco:

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

Aby dopasować wiele wystąpień podobnych do tego, co jest dostępne w PHP preg_match_all, możesz użyć tego typu myślenia, aby stworzyć własne lub użyć czegoś podobnego YourJS.matchAll(). YourJS mniej więcej definiuje tę funkcję w następujący sposób:

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

Ponieważ chcesz przeanalizować ciąg zapytania adresu URL, możesz również użyć czegoś takiego jak YourJS.parseQS()( yourjs.com/snippets/56 ), chociaż wiele innych bibliotek również oferuje tę funkcję.
Chris West,

Modyfikowanie zmiennej z zewnętrznego zakresu w pętli, która ma zwrócić wymianę, jest trochę złe. Twoje niewłaściwe użycie zastąp tutaj
Juan Mendes

1

Jeśli możesz uciec z korzystaniem z maptego, jest to rozwiązanie czteroliniowe:

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

Nie jest ładny, nie jest wydajny, ale przynajmniej jest kompaktowy. ;)


1

Zastosowanie 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 od 2020 roku. Zwrócę uwagę na String.prototype.matchAll () :

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

Wyjścia:

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

Wreszcie! Uwaga: „ECMAScript 2020, 11. edycja, wprowadza metodę matchAll dla ciągów znaków, aby utworzyć iterator dla wszystkich obiektów dopasowania generowanych przez globalne wyrażenie regularne” . Według strony, do której link znajduje się w odpowiedzi, większość przeglądarek i nodeJS obsługuje ją obecnie, ale nie IE, Safari czy Samsung Internet. Miejmy nadzieję, że wsparcie wkrótce się rozszerzy, ale YMMV na chwilę.
Adam Franco

0

Aby przechwycić kilka parametrów o tej samej nazwie, zmodyfikowałem pętlę while w metodzie Tomalaka w następujący sposób:

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

Wejście: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

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


Chociaż podoba mi się twój pomysł, to nie działa dobrze z pojedynczymi parametrami, tak jak ?cinema=1234&film=12&film=34bym się spodziewał {cinema: 1234, film: [12, 34]}. Zredagowałem odpowiedź, aby to odzwierciedlić.
TWiStErRob

0

Cóż ... Miałem podobny problem ... Chcę wyszukiwania przyrostowego / krokowego za pomocą RegExp (np .: rozpocznij wyszukiwanie ... wykonaj jakieś przetwarzanie ... kontynuuj wyszukiwanie do ostatniego dopasowania)

Po wielu poszukiwaniach w Internecie ... jak zawsze (to teraz staje się nawykiem) ląduję w StackOverflow i znalazłem odpowiedź ...

To, czego nie ma, a sprawy, o których należy wspomnieć, to „ lastIndex” Teraz rozumiem, dlaczego obiekt RegExp implementuje właściwość „ lastIndex


0

Dzielenie wygląda dla mnie na najlepszą opcję:

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

0

Aby uniknąć piekła wyrażeń regularnych, możesz znaleźć swoje pierwsze dopasowanie, odetnij kawałek, a następnie spróbuj znaleźć następny na podłańcuchu. W C # wygląda to mniej więcej tak, przepraszam, że nie przeniosłem go do JavaScript.

        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;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.