Wie kann ich mehrere Vorkommen mit einem regulären Ausdruck in JavaScript abgleichen, der PHPs preg_match_all () ähnelt?

159

Ich versuche, URL-codierte Zeichenfolgen zu analysieren, die aus Schlüssel / Wert-Paaren bestehen, die durch entweder &oder getrennt sind &.

Das Folgende stimmt nur mit dem ersten Vorkommen überein, wobei die Schlüssel und Werte in separate Ergebniselemente aufgeteilt werden:

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

Die Ergebnisse für die Zeichenfolge '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' wären:

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

Die Verwendung des globalen Flags 'g' stimmt mit allen Vorkommen überein, gibt jedoch nur die vollständig übereinstimmenden Teilzeichenfolgen zurück, nicht die getrennten Schlüssel und Werte:

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

Die Ergebnisse für die Zeichenfolge '1111342 = Adam% 20Franco & 348572 = Bob% 20Jones' wären:

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

Obwohl ich die Zeichenfolge aufteilen &und jedes Schlüssel / Wert-Paar einzeln aufteilen könnte , gibt es eine Möglichkeit, die Unterstützung für reguläre Ausdrücke von JavaScript zu verwenden, um mehrere Vorkommen des Musters /(?:&|&)?([^=]+)=([^&]+)/abzugleichen, die der PHP- preg_match_all()Funktion ähneln ?

Ich strebe nach einer Möglichkeit, Ergebnisse zu erzielen, wenn die Teilspiele wie folgt getrennt sind:

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

oder

[['1111342', 'Adam%20Franco'], ['348572', 'Bob%20Jones']]
Adam Franco
quelle
9
Es ist ein wenig seltsam, dass niemand replacehier empfohlen hat. var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });getan. "matchAll" in JavaScript ist "Ersetzen" durch eine Ersetzungshandlerfunktion anstelle einer Zeichenfolge.
Mike 'Pomax' Kamermans
Beachten Sie, dass für diejenigen, die diese Frage im Jahr 2020 noch finden, die Antwort lautet: "Verwenden Sie keinen regulären Ausdruck , verwenden Sie URLSearchParams , das alles für Sie erledigt."
Mike 'Pomax' Kamermans

Antworten:

161

Aus den Kommentaren gehisst

Kommentar 2020: Anstatt Regex zu verwenden, haben wir jetzt einen URLSearchParams, der all dies für uns erledigt. Daher ist kein benutzerdefinierter Code mehr erforderlich, geschweige denn Regex.

- Mike 'Pomax' Kamermans

Die Browserunterstützung finden Sie hier https://caniuse.com/#feat=urlsearchparams


Ich würde einen alternativen regulären Ausdruck vorschlagen, bei dem Untergruppen verwendet werden, um den Namen und den Wert der Parameter einzeln und zu erfassen 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 ist ein Objekt:

{
  f: "q"
  Geocode: ""
  hl: "de"
  dh: "UTF8"
  iwloc: "addr"
  ll: 50.116616,8.680573
  q: "Frankfurt am Main"
  sll: 50.106047,8.679886
  Quelle: "s_q"
  spn: 0,35972,0,833588
  sspn: 0,370369,0,833588
  z: 11
}}

Die Regex gliedert sich wie folgt:

(?: # nicht erfassende Gruppe
  \? | & # "?" oder "&"
  (?:Ampere;)? # (Erlaube "& amp;" für falsch HTML-codierte URLs)
) # Nicht erfassende Gruppe beenden
( # Gruppe 1
  [^ = & #] + # ein beliebiges Zeichen außer "=", "&" oder "#"; mindestens einmal
) # Gruppe 1 beenden - Dies ist der Name des Parameters
(?: # nicht erfassende Gruppe
  =? # an "=", optional
  (# Gruppe 2
    [^ & #] * # ein beliebiges Zeichen außer "&" oder "#"; beliebig oft
  ) # end group 2 - Dies ist der Wert des Parameters
) # Nicht erfassende Gruppe beenden
Tomalak
quelle
23
Darauf habe ich gehofft. Was ich in der JavaScript-Dokumentation noch nie gesehen habe, ist die Erwähnung, dass die exec () -Methode weiterhin die nächste Ergebnismenge zurückgibt, wenn sie mehrmals aufgerufen wird. Nochmals vielen Dank für den tollen Tipp!
Adam Franco
1
Dies geschieht aus diesem Grund: regulär-expressions.info / javascript.html (Lesen Sie durch: "Verwendung des JavaScript-RegExp-Objekts")
Tomalak
1
Dieser Code enthält einen Fehler: Das Semikolon nach dem "while" sollte entfernt werden.
Jan Willem B
1
Weil ich im Allgemeinen nur normale (dh erfasste) Gruppen verwende, wenn ich tatsächlich an deren Inhalten interessiert bin.
Tomalak
1
@KnightYoshi Ja. In JavaScript auch jeder Ausdruck produziert sein eigenes Ergebnis (wie x = yzuweisen würde yzu xund produzieren auch y). Wenn wir dieses Wissen anwenden auf if (match = re.exec(url)): Dieses A) erledigt die Aufgabe und B) gibt das Ergebnis von re.exec(url)an das zurück while. Gibt jetzt re.execzurück, nullwenn keine Übereinstimmung vorliegt. Dies ist ein falscher Wert. Tatsächlich wird die Schleife so lange fortgesetzt, wie eine Übereinstimmung vorliegt.
Tomalak
67

Sie müssen den Schalter 'g' für eine globale Suche verwenden

var result = mystring.match(/(&|&)?([^=]+)=([^&]+)/g)
meouw
quelle
33
Dies löst das Problem nicht wirklich: "Die Verwendung des globalen Flags 'g' stimmt mit allen Vorkommen überein, gibt jedoch nur die vollständig übereinstimmenden Teilzeichenfolgen zurück, nicht die getrennten Schlüssel und Werte."
Adam Franco
40

2020 bearbeiten

Verwenden Sie URLSearchParams , da für diesen Job kein benutzerdefinierter Code mehr erforderlich ist. Browser können dies mit einem einzigen Konstruktor für Sie tun:

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

ergibt

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

Es gibt also keinen Grund mehr, dafür Regex zu verwenden.

Ursprüngliche Antwort

Wenn Sie sich nicht auf das "Blind Matching" verlassen möchten, das mit dem Running execStyle Matching einhergeht, verfügt JavaScript über eine integrierte Match-All-Funktionalität, die jedoch Teil des replaceFunktionsaufrufs ist, wenn Sie ein "Was tun mit der Erfassung?" Verwenden Gruppen " Handhabungsfunktion :

var data = {};

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

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

getan.

Anstatt die Funktion zum Behandeln von Erfassungsgruppen zu verwenden, um tatsächlich Ersatzzeichenfolgen zurückzugeben (für das Ersetzen von Ersetzungen ist das erste Argument die vollständige Musterübereinstimmung, und nachfolgende Argumente sind einzelne Erfassungsgruppen), nehmen wir einfach die Erfassungen der Gruppen 2 und 3 und zwischenspeichern dieses Paar.

Denken Sie also daran, dass die Funktion "matchAll" in JavaScript nicht durch komplizierte Parsing-Funktionen geschrieben wird, sondern einfach durch eine Ersatz-Handler-Funktion "ersetzt" wird und dass eine hohe Effizienz beim Mustervergleich erzielt werden kann.

Mike 'Pomax' Kamermans
quelle
Ich habe eine Schnur something "this one" and "that one". Ich möchte alle Zeichenfolgen in doppelten Anführungszeichen in eine Liste einfügen, dh [diese, diese]. Bisher mystring.match(/"(.*?)"/)funktioniert es gut, die erste zu erkennen, aber ich weiß nicht, wie ich Ihre Lösung für eine einzelne Erfassungsgruppe anpassen soll.
Nu Everest
2
klingt so, als ob Sie eine Frage zu Stackoverflow stellen sollten, anstatt zu versuchen, sie in Kommentaren zu lösen.
Mike 'Pomax' Kamermans
Ich habe eine neue Frage erstellt: stackoverflow.com/questions/26174122/…
nu everest
1
Ich bin mir nicht sicher, warum diese Antwort so wenig positive Stimmen hat, aber es ist die beste Antwort auf die Frage.
Calin
Hallo @ Mike'Pomax'Kamermans, in den Community-Richtlinien wird ausdrücklich empfohlen, Einträge zu bearbeiten, um sie zu verbessern. Siehe: stackoverflow.com/help/behavior . Der Kern Ihrer Antwort ist außerordentlich hilfreich, aber ich fand, dass die Sprache "Denken Sie daran, dass matchAll ersetzt wird" nicht klar war und keine Erklärung dafür war, warum Ihr Code (der nicht offensichtlich ist) funktioniert. Ich dachte, Sie sollten den wohlverdienten Repräsentanten bekommen, also habe ich Ihre Antwort bearbeitet, anstatt sie mit verbessertem Text zu duplizieren. Als ursprünglicher Fragesteller dieser Frage kann ich die Akzeptanz dieser Antwort (und der Bearbeitung) gerne rückgängig machen, wenn Sie dies weiterhin möchten.
Adam Franco
21

Zum Erfassen von Gruppen bin ich es gewohnt, preg_match_allin PHP zu arbeiten, und ich habe versucht, die Funktionalität hier zu replizieren:

<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
quelle
3
@teh_senaus Sie müssen den globalen Modifikator angeben, /gandernfalls exec()wird der aktuelle Index nicht geändert und für immer wiederholt.
Aram Kocharyan
Wenn ich anrufe, um diesen Code zu validieren, myRe.test (str) und dann versuche, execAll auszuführen, wird er beim zweiten Match angezeigt und wir haben das erste Match verloren.
fdrv
@fdrv Sie müssen den lastIndex auf Null zurücksetzen, bevor Sie die Schleife starten: this.lastIndex = 0;
CF
15

Legen Sie den gModifikator für eine globale Übereinstimmung fest:

/…/g
Gumbo
quelle
11
Dies löst das Problem nicht wirklich: "Die Verwendung des globalen Flags 'g' stimmt mit allen Vorkommen überein, gibt jedoch nur die vollständig übereinstimmenden Teilzeichenfolgen zurück, nicht die getrennten Schlüssel und Werte."
Adam Franco
11

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

Aufeinanderfolgende Übereinstimmungen finden

Wenn Ihr regulärer Ausdruck das Flag "g" verwendet, können Sie die Methode exec () mehrmals verwenden, um aufeinanderfolgende Übereinstimmungen in derselben Zeichenfolge zu finden. Wenn Sie dies tun, beginnt die Suche mit der Teilzeichenfolge von str, die durch die lastIndex-Eigenschaft des regulären Ausdrucks angegeben wird (test () erweitert auch die lastIndex-Eigenschaft). Angenommen, Sie haben dieses Skript:

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

Dieses Skript zeigt den folgenden Text an:

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

Hinweis: Platzieren Sie das Literal für reguläre Ausdrücke (oder den RegExp-Konstruktor) nicht in der while-Bedingung, da sonst eine Endlosschleife erstellt wird, wenn eine Übereinstimmung vorliegt, da die lastIndex-Eigenschaft bei jeder Iteration zurückgesetzt wird. Stellen Sie außerdem sicher, dass das globale Flag gesetzt ist, da sonst auch hier eine Schleife auftritt.

KIM Taegyoon
quelle
Wenn ich anrufe, um diesen Code zu validieren, myRe.test (str) und dann zu versuchen, während, wird er beim zweiten Spiel angezeigt und wir haben das erste Spiel verloren.
fdrv
Sie können auch kombinieren String.prototype.matchmit der gFlagge: 'abbcdefabh'.match(/ab*/g)Rückkehr['abb', 'ab']
thom_nic
2

Wenn jemand (wie ich) Tomalaks Methode mit Array-Unterstützung benötigt (dh Mehrfachauswahl), ist dies hier:

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

Eingang ?my=1&my=2&my=things

Ergebnis 1,2,things(früher nur zurückgegeben: Dinge)

fedu
quelle
1

Um bei der vorgeschlagenen Frage zu bleiben, wie im Titel angegeben, können Sie tatsächlich jede Übereinstimmung in einer Zeichenfolge mit iterieren String.prototype.replace(). Das Folgende bewirkt beispielsweise genau das, um ein Array aller Wörter basierend auf einem regulären Ausdruck zu erhalten:

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

Wenn ich Erfassungsgruppen oder sogar den Index jedes Spiels erhalten wollte, könnte ich das auch tun. Das Folgende zeigt, wie jede Übereinstimmung mit der gesamten Übereinstimmung, der 1. Erfassungsgruppe und dem Index zurückgegeben wird:

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

Nach dem Ausführen des oben genannten wordswird wie folgt sein:

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

Um mehrere Vorkommen, die denen in PHP ähneln, zuzuordnen preg_match_all, können Sie diese Art des Denkens verwenden, um Ihre eigenen zu erstellen oder so etwas zu verwenden YourJS.matchAll(). YourJS definiert diese Funktion mehr oder weniger wie folgt:

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
quelle
Da Sie die Abfragezeichenfolge einer URL analysieren möchten, können Sie auch Folgendes verwenden YourJS.parseQS()( yourjs.com/snippets/56 ), obwohl viele andere Bibliotheken diese Funktionalität ebenfalls anbieten.
Chris West
Das Ändern einer Variablen aus einem äußeren Bereich in einer Schleife, die einen Ersatz zurückgeben soll, ist irgendwie schlecht. Ihr Missbrauch hier ersetzen
Juan Mendes
1

Wenn Sie damit durchkommen können, mapist dies eine vierzeilige Lösung:

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

Ist nicht schön, nicht effizient, aber zumindest kompakt. ;)

fboes
quelle
1

Verwendung 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
quelle
1

Hallo von 2020. Lassen Sie mich auf String.prototype.matchAll () aufmerksam machen:

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

Ausgänge:

1111342 => Adam%20Franco
348572 => Bob%20Jones
Klesun
quelle
Schließlich! Ein Hinweis zur Vorsicht: "ECMAScript 2020, die 11. Ausgabe, führt die matchAll-Methode für Strings ein, um einen Iterator für alle Match-Objekte zu erstellen, die von einem globalen regulären Ausdruck generiert werden . " Laut der in der Antwort verlinkten Site unterstützen die meisten Browser und NodeJS dies derzeit, jedoch nicht IE, Safari oder Samsung Internet. Hoffentlich wird sich die Unterstützung bald erweitern, aber YMMV für eine Weile.
Adam Franco
0

Um mehrere Parameter mit demselben Namen zu erfassen, habe ich die while-Schleife in Tomalaks Methode wie folgt geändert:

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

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

kehrt zurück: {firstname : ["george", "bill"], lastname : ["bush", "clinton"]}

ivar
quelle
Obwohl mir Ihre Idee gefällt, funktioniert sie mit einzelnen Parametern nicht so gut, wie ?cinema=1234&film=12&film=34ich es erwarten würde {cinema: 1234, film: [12, 34]}. Ihre Antwort wurde bearbeitet, um dies widerzuspiegeln.
TWiStErRob
0

Nun ... ich hatte ein ähnliches Problem ... Ich möchte eine inkrementelle / schrittweise Suche mit RegExp (z. B. Suche starten ... etwas verarbeiten ... Suche bis zur letzten Übereinstimmung fortsetzen)

Nach vielen Internet-Suchen ... wie immer (das wird jetzt zur Gewohnheit) lande ich in StackOverflow und habe die Antwort gefunden ...

Was nicht erwähnt wird und zu erwähnen ist, ist " lastIndex". Ich verstehe jetzt, warum das RegExp-Objekt die lastIndexEigenschaft " " implementiert

ZEE
quelle
0

Das Aufteilen scheint mir die beste Option zu sein:

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

Um die Regex-Hölle zu vermeiden, könnten Sie Ihre erste Übereinstimmung finden, einen Teil abhacken und dann versuchen, die nächste auf dem Teilstring zu finden. In C # sieht das ungefähr so ​​aus. Entschuldigung, ich habe es nicht für Sie auf JavaScript portiert.

        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
quelle