Benannte Erfassungsgruppen in JavaScript-Regex?

208

Soweit ich weiß, gibt es in JavaScript keine benannten Erfassungsgruppen. Was ist der alternative Weg, um ähnliche Funktionen zu erhalten?

mmierins
quelle
1
Erfassungsgruppen in Javascript sind nach Nummer sortiert. $ 1 ist die erste erfasste Gruppe, $ 2, $ 3 ... bis zu $ ​​99, aber es hört sich so an, als ob Sie etwas anderes wollen - was nicht existiert
Erik
24
@Erik Sie sprechen von nummerierten Erfassungsgruppen, die OPs sprechen von benannten Erfassungsgruppen. Sie existieren, aber wir möchten wissen, ob sie in JS unterstützt werden.
Alba Mendez
4
Es gibt einen Vorschlag, benannten regulären Ausdruck in JavaScript zu integrieren , aber es könnte Jahre dauern, bis wir das sehen, wenn wir es jemals tun.
Fregante
Firefox hat mich dafür bestraft, dass ich versucht habe, benannte Erfassungsgruppen auf einer Website zu verwenden ... meine eigene Schuld. stackoverflow.com/a/58221254/782034
Nick Grealy

Antworten:

134

ECMAScript 2018 führt benannte Erfassungsgruppen in JavaScript-Regexes ein.

Beispiel:

  const auth = 'Bearer AUTHORIZATION_TOKEN'
  const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
  console.log(token) // "Prints AUTHORIZATION_TOKEN"

Wenn Sie ältere Browser unterstützen müssen, können Sie alles mit normalen (nummerierten) Erfassungsgruppen tun, was Sie mit benannten Erfassungsgruppen tun können. Sie müssen nur die Zahlen im Auge behalten - was möglicherweise umständlich ist, wenn die Reihenfolge der Erfassungsgruppe in Ihrer Regex-Änderungen.

Ich kann mir nur zwei "strukturelle" Vorteile benannter Erfassungsgruppen vorstellen:

  1. In einigen Regex-Varianten (.NET und JGSoft, soweit ich weiß) können Sie denselben Namen für verschiedene Gruppen in Ihrer Regex verwenden ( siehe hier für ein Beispiel, wo dies wichtig ist ). Die meisten Regex-Varianten unterstützen diese Funktionalität jedoch ohnehin nicht.

  2. Wenn Sie sich in einer Situation, in der sie von Ziffern umgeben sind, auf nummerierte Erfassungsgruppen beziehen müssen, kann ein Problem auftreten. Angenommen , Sie haben eine Null zu einer Ziffer hinzugefügt werden soll und daher ersetzt werden soll (\d)mit $10. In JavaScript funktioniert dies (solange Sie weniger als 10 Erfassungsgruppen in Ihrem regulären Ausdruck haben), aber Perl wird denken, dass Sie nach einer Referenznummer 10anstelle einer Nummer suchen 1, gefolgt von einer 0. In Perl können Sie ${1}0in diesem Fall verwenden.

Ansonsten sind benannte Erfassungsgruppen nur "syntaktischer Zucker". Es ist hilfreich, Erfassungsgruppen nur dann zu verwenden, wenn Sie sie wirklich benötigen, und unter (?:...)allen anderen Umständen nicht erfasste Gruppen zu verwenden .

Das größere Problem (meiner Meinung nach) mit JavaScript ist, dass es keine ausführlichen regulären Ausdrücke unterstützt, was die Erstellung lesbarer, komplexer regulärer Ausdrücke erheblich erleichtern würde.

Die XRegExp-Bibliothek von Steve Levithan löst diese Probleme.

Tim Pietzcker
quelle
5
In vielen Varianten können Sie denselben Erfassungsgruppennamen mehrmals in einem regulären Ausdruck verwenden. Aber nur .NET und Perl 5.10+ machen dies besonders nützlich, indem der Wert beibehalten wird, der von der letzten Gruppe eines Namens erfasst wurde, der am Match teilgenommen hat.
Slevithan
103
Der große Vorteil ist: Sie können einfach Ihre RegExp ändern, keine Zuordnung von Zahlen zu Variablen. Nicht erfassende Gruppen lösen dieses Problem, außer in einem Fall: Was passiert, wenn sich die Reihenfolge der Gruppen ändert? Es ist auch ärgerlich, diese zusätzlichen Zeichen auf die anderen Gruppen zu setzen ...
Alba Mendez
55
Der so genannte syntaktische Zucker tut Hilfe versüßt die Lesbarkeit des Codes!
Mrchief
1
Ich denke, es gibt einen anderen Grund für benannte Erfassungsgruppen, der wirklich wertvoll ist. Wenn Sie beispielsweise einen regulären Ausdruck verwenden möchten, um ein Datum aus einer Zeichenfolge zu analysieren, können Sie eine flexible Funktion schreiben, die den Wert und den regulären Ausdruck verwendet. Solange der reguläre Ausdruck Captures für Jahr, Monat und Datum benannt hat, können Sie eine Reihe regulärer Ausdrücke mit minimalem Code durchlaufen.
Dewey Vozel
4
Ab Oktober 2019 unterstützen Firefox, IE 11 und Microsoft Edge (vor Chromium) keine benannten Gruppenerfassungen. Die meisten anderen Browser (sogar Opera und Samsung Mobile) tun dies. caniuse.com/…
JDB erinnert sich noch an Monica
63

Sie können XRegExp verwenden , eine erweiterte, erweiterbare, browserübergreifende Implementierung regulärer Ausdrücke, einschließlich der Unterstützung zusätzlicher Syntax, Flags und Methoden:

  • Fügt neue Regex- und Ersatztext-Syntax hinzu, einschließlich umfassender Unterstützung für die benannte Erfassung .
  • Fügt zwei neue Regex-Flags hinzu : s, damit der Punkt mit allen Zeichen übereinstimmt (auch bekannt als Dotall- oder Singleline-Modus) und xfür freie Abstände und Kommentare (auch als erweiterter Modus bezeichnet).
  • Bietet eine Reihe von Funktionen und Methoden, mit denen die komplexe Regex-Verarbeitung zum Kinderspiel wird.
  • Behebt automatisch die am häufigsten auftretenden browserübergreifenden Inkonsistenzen im Regex-Verhalten und in der Syntax.
  • Ermöglicht das einfache Erstellen und Verwenden von Plugins, die der regulären Ausdruckssprache von XRegExp neue Syntax und Flags hinzufügen.
Yunga Palatino
quelle
60

Eine andere mögliche Lösung: Erstellen Sie ein Objekt, das die Gruppennamen und Indizes enthält.

var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };

Verwenden Sie dann die Objektschlüssel, um auf die Gruppen zu verweisen:

var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];

Dies verbessert die Lesbarkeit / Qualität des Codes unter Verwendung der Ergebnisse des regulären Ausdrucks, jedoch nicht der Lesbarkeit des regulären Ausdrucks selbst.

Herr TA
quelle
58

In ES6 können Sie die Array-Destrukturierung verwenden, um Ihre Gruppen abzufangen:

let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];

// count === '27'
// unit === 'months'

Beachten:

  • Das erste Komma im letzten letüberspringt den ersten Wert des resultierenden Arrays, bei dem es sich um die gesamte übereinstimmende Zeichenfolge handelt
  • die || []nach .exec()einem Destrukturierung Fehler vermeiden , wenn es keine Übereinstimmungen geben (weil .exec()zurückkehren null)
fregante
quelle
1
Das erste Komma ist, weil das erste Element des von match zurückgegebenen Arrays der Eingabeausdruck ist, oder?
Emilio Grisolía
1
String.prototype.matchGibt ein Array zurück mit: der gesamten übereinstimmenden Zeichenfolge an Position 0, danach alle Gruppen danach. Das erste Komma sagt "überspringe das Element an Position 0"
fregante
2
Meine Lieblingsantwort hier für diejenigen mit Transpiling- oder ES6 + -Zielen. Dies verhindert nicht unbedingt Inkonsistenzfehler wie auch benannte Indizes, wenn sich beispielsweise ein wiederverwendeter regulärer Ausdruck ändert, aber ich denke, die Prägnanz hier macht das leicht wieder wett. Ich habe entschieden RegExp.prototype.execüber String.prototype.matchan Orten , wo die Zeichenfolge sein kann , nulloder undefined.
Mike Hill
22

Update: Es hat es endlich in JavaScript geschafft (ECMAScript 2018)!


Benannte Erfassungsgruppen könnten es sehr bald in JavaScript schaffen.
Der Vorschlag dafür befindet sich bereits in Phase 3.

Einer Erfassungsgruppe kann mithilfe der (?<name>...)Syntax für jeden Bezeichnernamen ein Name in eckigen Klammern zugewiesen werden. Der reguläre Ausdruck für ein Datum kann dann wie folgt geschrieben werden /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u. Jeder Name sollte eindeutig sein und der Grammatik für ECMAScript IdentifierName folgen .

Auf benannte Gruppen kann über die Eigenschaften einer Gruppeneigenschaft des Ergebnisses des regulären Ausdrucks zugegriffen werden. Ebenso wie für nicht benannte Gruppen werden nummerierte Verweise auf die Gruppen erstellt. Beispielsweise:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';
Forivin
quelle
Derzeit handelt es sich um einen Vorschlag für Stufe 4.
GOTO 0
Wenn Sie '18 verwenden, können Sie genauso gut mit der Destrukturierung all-in gehen. let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
Hashbrown
6

Die Benennung erfasster Gruppen bietet eines: weniger Verwirrung mit komplexen regulären Ausdrücken.

Es hängt wirklich von Ihrem Anwendungsfall ab, aber vielleicht könnte es hilfreich sein, Ihren regulären Ausdruck hübsch auszudrucken.

Oder Sie können versuchen, Konstanten zu definieren, die auf Ihre erfassten Gruppen verweisen.

Kommentare können dann auch dazu beitragen, anderen, die Ihren Code lesen, zu zeigen, was Sie getan haben.

Im Übrigen muss ich Tims Antwort zustimmen.

Yashima
quelle
5

Es gibt eine node.js-Bibliothek namens named-regexp , die Sie in Ihren node.js-Projekten verwenden können (im Browser durch Packen der Bibliothek mit browserify oder anderen Paketskripten). Die Bibliothek kann jedoch nicht mit regulären Ausdrücken verwendet werden, die nicht benannte Erfassungsgruppen enthalten.

Wenn Sie die öffnenden Erfassungsklammern in Ihrem regulären Ausdruck zählen, können Sie eine Zuordnung zwischen benannten Erfassungsgruppen und den nummerierten Erfassungsgruppen in Ihrer Regex erstellen und diese frei mischen und anpassen. Sie müssen nur die Gruppennamen entfernen, bevor Sie den regulären Ausdruck verwenden können. Ich habe drei Funktionen geschrieben, die das demonstrieren. Siehe das Wesentliche: https://gist.github.com/gbirke/2cc2370135b665eee3ef

Chiborg
quelle
Das ist überraschend leicht, ich werde es versuchen
fregante
Funktioniert es mit verschachtelten benannten Gruppen in regulären Gruppen in komplexen regulären Ausdrücken?
ElSajko
Es ist nicht perfekt. Fehler wenn: getMap ("((a | b (: <foo> c)))"); foo sollte die dritte Gruppe sein, nicht die zweite. /((a|b(c)))/g.exec("bc "); ["bc", "bc", "bc", "c"]
ElSajko
3

Wie Tim Pietzcker sagte, führt ECMAScript 2018 benannte Erfassungsgruppen in JavaScript-Regexes ein. Was ich in den obigen Antworten jedoch nicht gefunden habe, war, wie die benannte erfasste Gruppe im regulären Ausdruck selbst verwendet wird.

Sie können benannte erfasste Gruppen mit folgender Syntax verwenden : \k<name>. beispielsweise

var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/

und wie Forivin sagte, können Sie die erfasste Gruppe im Objektergebnis wie folgt verwenden:

let result = regexObj.exec('2019-28-06 year is 2019');
// result.groups.year === '2019';
// result.groups.month === '06';
// result.groups.day === '28';

  var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/mgi;

function check(){
    var inp = document.getElementById("tinput").value;
    let result = regexObj.exec(inp);
    document.getElementById("year").innerHTML = result.groups.year;
    document.getElementById("month").innerHTML = result.groups.month;
    document.getElementById("day").innerHTML = result.groups.day;
}
td, th{
  border: solid 2px #ccc;
}
<input id="tinput" type="text" value="2019-28-06 year is 2019"/>
<br/>
<br/>
<span>Pattern: "(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>";
<br/>
<br/>
<button onclick="check()">Check!</button>
<br/>
<br/>
<table>
  <thead>
    <tr>
      <th>
        <span>Year</span>
      </th>
      <th>
        <span>Month</span>
      </th>
      <th>
        <span>Day</span>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <span id="year"></span>
      </td>
      <td>
        <span id="month"></span>
      </td>
      <td>
        <span id="day"></span>
      </td>
    </tr>
  </tbody>
</table>

Hamed Mahdizadeh
quelle
2

Während Sie dies mit Vanille-JavaScript nicht tun können, können Sie möglicherweise eine Array.prototypeFunktion verwenden Array.prototype.reduce, um indizierte Übereinstimmungen mit etwas Magie in benannte Übereinstimmungen umzuwandeln .

Für die folgende Lösung müssen die Übereinstimmungen natürlich in der richtigen Reihenfolge erfolgen:

// @text Contains the text to match
// @regex A regular expression object (f.e. /.+/)
// @matchNames An array of literal strings where each item
//             is the name of each group
function namedRegexMatch(text, regex, matchNames) {
  var matches = regex.exec(text);

  return matches.reduce(function(result, match, index) {
    if (index > 0)
      // This substraction is required because we count 
      // match indexes from 1, because 0 is the entire matched string
      result[matchNames[index - 1]] = match;

    return result;
  }, {});
}

var myString = "Hello Alex, I am John";

var namedMatches = namedRegexMatch(
  myString,
  /Hello ([a-z]+), I am ([a-z]+)/i, 
  ["firstPersonName", "secondPersonName"]
);

alert(JSON.stringify(namedMatches));

Matías Fidemraizer
quelle
Das ist ziemlich toll. Ich denke nur ... wäre es nicht möglich, eine Regex-Funktion zu erstellen, die einen benutzerdefinierten Regex akzeptiert? Damit du gehen kannst wievar assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
Forivin
@Forivin Natürlich können Sie diese Funktion weiterentwickeln. Es wäre nicht schwer, es zum
Laufen
Sie können das RegExpObjekt erweitern, indem Sie seinem Prototyp eine Funktion hinzufügen.
Herr TA
@ Mr.TA AFAIK, es wird nicht empfohlen, eingebaute Objekte zu erweitern
Matías Fidemraizer
0

Sie haben kein ECMAScript 2018?

Mein Ziel war es, es so ähnlich wie möglich zu machen, wie wir es von benannten Gruppen gewohnt sind. Während Sie in ECMAScript 2018 ?<groupname>innerhalb der Gruppe eine benannte Gruppe angeben können, können Sie in meiner Lösung für älteres Javascript (?!=<groupname>)innerhalb der Gruppe platzieren, um dasselbe zu tun. Es ist also ein zusätzlicher Satz Klammern und ein zusätzlicher !=. Ziemlich knapp!

Ich habe alles in eine String-Prototyp-Funktion eingewickelt

Eigenschaften

  • funktioniert mit älterem Javascript
  • kein zusätzlicher Code
  • ziemlich einfach zu bedienen
  • Regex funktioniert immer noch
  • Gruppen werden im regulären Ausdruck selbst dokumentiert
  • Gruppennamen können Leerzeichen haben
  • Gibt ein Objekt mit Ergebnissen zurück

Anleitung

  • Platzieren Sie (?!={groupname})innerhalb jeder Gruppe, die Sie benennen möchten
  • Denken Sie daran, nicht erfassende Gruppen zu entfernen, ()indem Sie sie ?:an den Anfang dieser Gruppe setzen. Diese werden nicht benannt.

arrays.js

// @@pattern - includes injections of (?!={groupname}) for each group
// @@returns - an object with a property for each group having the group's match as the value 
String.prototype.matchWithGroups = function (pattern) {
  var matches = this.match(pattern);
  return pattern
  // get the pattern as a string
  .toString()
  // suss out the groups
  .match(/<(.+?)>/g)
  // remove the braces
  .map(function(group) {
    return group.match(/<(.+)>/)[1];
  })
  // create an object with a property for each group having the group's match as the value 
  .reduce(function(acc, curr, index, arr) {
    acc[curr] = matches[index + 1];
    return acc;
  }, {});
};    

Verwendung

function testRegGroups() {
  var s = '123 Main St';
  var pattern = /((?!=<house number>)\d+)\s((?!=<street name>)\w+)\s((?!=<street type>)\w+)/;
  var o = s.matchWithGroups(pattern); // {'house number':"123", 'street name':"Main", 'street type':"St"}
  var j = JSON.stringify(o);
  var housenum = o['house number']; // 123
}

Ergebnis von o

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
toddmo
quelle