Wie kann ich Regex-Literale in JavaScript verketten?

145

Ist es möglich so etwas zu tun?

var pattern = /some regex segment/ + /* comment here */
    /another segment/;

Oder muss ich eine neue RegExp()Syntax verwenden und eine Zeichenfolge verketten? Ich würde es vorziehen, das Literal zu verwenden, da der Code sowohl selbstverständlicher als auch prägnanter ist.

Augenlidlosigkeit
quelle
1
Es ist einfacher, mit maskierten Regex-Zeichen umzugehen, wenn Sie String.raw () verwenden:let regexSegment1 = String.raw`\s*hello\s*`
iono

Antworten:

190

Hier erfahren Sie, wie Sie einen regulären Ausdruck erstellen, ohne die Literal-Syntax für reguläre Ausdrücke zu verwenden. Auf diese Weise können Sie eine beliebige Zeichenfolgenmanipulation durchführen, bevor sie zu einem Objekt mit regulären Ausdrücken wird:

var segment_part = "some bit of the regexp";
var pattern = new RegExp("some regex segment" + /*comment here */
              segment_part + /* that was defined just now */
              "another segment");

Wenn Sie zwei Literale mit regulären Ausdrücken haben, können Sie diese mithilfe dieser Technik verketten:

var regex1 = /foo/g;
var regex2 = /bar/y;
var flags = (regex1.flags + regex2.flags).split("").sort().join("").replace(/(.)(?=.*\1)/g, "");
var regex3 = new RegExp(expression_one.source + expression_two.source, flags);
// regex3 is now /foobar/gy

Es ist nur wortreicher, als nur Ausdruck eins und zwei als wörtliche Zeichenfolgen anstelle von wörtlichen regulären Ausdrücken zu haben.

Jerub
quelle
1
Beachten Sie, dass jedes Segment ein gültiger regulärer Ausdruck sein muss, wenn Sie diesen Ansatz verwenden. Das Konstruieren eines Ausdrucks wie new RegExp(/(/.source + /.*/.source + /)?/.source);scheint nicht zu funktionieren.
Sam
Diese Lösung funktioniert nicht bei Back-Matching-Gruppen. Siehe meine Antwort für eine funktionierende Lösung in diesem Fall.
Mikaël Mayer
Wenn Sie einem Zeichen entkommen müssen, verwenden Sie doppelte Backslashes: new Regexp ('\\ $' + "flum")
Jeff Lowery
Sie können auf die Flags zugreifen, wenn Sie mit "<regexp> .flags" arbeiten müssen, sodass Sie sie theoretisch auch kombinieren können.
Bnunamak
Woher kommst du expression_one? Meinst du regex1?
TallOrderDev
30

Das zufällige Verketten von Objekten mit regulären Ausdrücken kann einige nachteilige Nebenwirkungen haben. Verwenden Sie stattdessen die RegExp.source :

var r1 = /abc/g;
var r2 = /def/;
var r3 = new RegExp(r1.source + r2.source, 
                   (r1.global ? 'g' : '') 
                   + (r1.ignoreCase ? 'i' : '') + 
                   (r1.multiline ? 'm' : ''));
console.log(r3);
var m = 'test that abcdef and abcdef has a match?'.match(r3);
console.log(m);
// m should contain 2 matches

Auf diese Weise können Sie auch die Flags für reguläre Ausdrücke aus einem früheren RegExp mithilfe der Standard-RegExp-Flags beibehalten.

jsFiddle

Japheth Salva
quelle
Dies kann verbessert werden mitRegExp.prototype.flags
Dmitry Parzhitsky
19

Ich bin mit der Option "eval" nicht ganz einverstanden.

var xxx = /abcd/;
var yyy = /efgh/;
var zzz = new RegExp(eval(xxx)+eval(yyy));

gibt "// abcd // efgh //" aus, was nicht das beabsichtigte Ergebnis ist.

Mit Quelle wie

var zzz = new RegExp(xxx.source+yyy.source);

wird "/ abcdefgh /" geben und das ist richtig.

Logischerweise besteht keine Notwendigkeit zur BEWERTUNG, Sie kennen Ihren AUSDRUCK. Sie brauchen nur seine QUELLE oder wie es geschrieben ist, nicht unbedingt seinen Wert. Für die Flags müssen Sie nur das optionale Argument von RegExp verwenden.

In meiner Situation habe ich das Problem, dass ^ und $ in mehreren Ausdrücken verwendet werden, die ich miteinander verketten möchte! Diese Ausdrücke sind Grammatikfilter, die im gesamten Programm verwendet werden. Jetzt möchte ich einige von ihnen nicht zusammen verwenden, um den Fall von PRÄPOSITIONEN zu behandeln. Möglicherweise muss ich die Quellen "in Scheiben schneiden", um den Anfang und das Ende zu entfernen ^ (und / oder) $ :) Prost, Alex.

Alex
quelle
Ich mag die Verwendung der Source-Eigenschaft. Wenn Sie - wie ich - jslint verwenden, wird es nörgeln, wenn Sie so etwas tun:var regex = "\.\..*"
Nils-o-mat
7

Problem Wenn der reguläre Ausdruck Back-Matching-Gruppen wie \ 1 enthält.

var r = /(a|b)\1/  // Matches aa, bb but nothing else.
var p = /(c|d)\1/   // Matches cc, dd but nothing else.

Dann funktioniert es nicht, nur die Quellen zu verketten. In der Tat ist die Kombination der beiden:

var rp = /(a|b)\1(c|d)\1/
rp.test("aadd") // Returns false

Die Lösung: Zuerst zählen wir die Anzahl der übereinstimmenden Gruppen im ersten regulären Ausdruck, dann erhöhen wir sie für jedes Back-Matching-Token im zweiten um die Anzahl der übereinstimmenden Gruppen.

function concatenate(r1, r2) {
  var count = function(r, str) {
    return str.match(r).length;
  }
  var numberGroups = /([^\\]|^)(?=\((?!\?:))/g; // Home-made regexp to count groups.
  var offset = count(numberGroups, r1.source);    
  var escapedMatch = /[\\](?:(\d+)|.)/g;        // Home-made regexp for escaped literals, greedy on numbers.
  var r2newSource = r2.source.replace(escapedMatch, function(match, number) { return number?"\\"+(number-0+offset):match; });
  return new RegExp(r1.source+r2newSource,
      (r1.global ? 'g' : '') 
      + (r1.ignoreCase ? 'i' : '')
      + (r1.multiline ? 'm' : ''));
}

Prüfung:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true
Mikaël Mayer
quelle
2
Ja (ich werde es hier allerdings nicht ändern). Diese Funktion ist assoziativ, so dass Sie den folgenden Code verwenden können:function concatenateList() { var res = arguments[0]; for(var i = 1; i < arguments.length; i++) { res = concatenate(res, arguments[i]); } return res; }
Mikaël Mayer
3

Es wäre vorzuziehen, die Literal-Syntax so oft wie möglich zu verwenden. Es ist kürzer, besser lesbar und Sie benötigen keine Escape-Anführungszeichen oder Double-Escape-Rückschläge. Aus "Javascript Patterns", Stoyan Stefanov 2010.

Die Verwendung von Neu kann jedoch die einzige Möglichkeit zur Verkettung sein.

Ich würde eval vermeiden. Es ist nicht sicher.

Jonathan Wright
quelle
1
Ich denke, komplexe reguläre Ausdrücke sind besser lesbar, wenn sie wie in der Frage aufgebrochen und kommentiert werden.
Sam
3

Vorausgesetzt, dass:

  • Sie wissen, was Sie in Ihrem regulären Ausdruck tun.
  • Sie haben viele Regex-Teile, um ein Muster zu bilden, und sie verwenden dieselbe Flagge.
  • Sie finden es besser lesbar, Ihre kleinen Musterblöcke in ein Array zu unterteilen.
  • Sie möchten auch in der Lage sein, jeden Teil für den nächsten Entwickler oder sich selbst später zu kommentieren.
  • Sie bevorzugen es, Ihren regulären Ausdruck visuell zu vereinfachen, /this/ganstatt new RegExp('this', 'g');
  • Es ist in Ordnung, wenn Sie den Regex in einem zusätzlichen Schritt zusammenbauen, anstatt ihn von Anfang an in einem Stück zu haben.

Dann möchten Sie vielleicht so schreiben:

var regexParts =
    [
        /\b(\d+|null)\b/,// Some comments.
        /\b(true|false)\b/,
        /\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|length|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/,
        /(\$|jQuery)/,
        /many more patterns/
    ],
    regexString  = regexParts.map(function(x){return x.source}).join('|'),
    regexPattern = new RegExp(regexString, 'g');

Sie können dann etwas tun wie:

string.replace(regexPattern, function()
{
    var m = arguments,
        Class = '';

    switch(true)
    {
        // Numbers and 'null'.
        case (Boolean)(m[1]):
            m = m[1];
            Class = 'number';
            break;

        // True or False.
        case (Boolean)(m[2]):
            m = m[2];
            Class = 'bool';
            break;

        // True or False.
        case (Boolean)(m[3]):
            m = m[3];
            Class = 'keyword';
            break;

        // $ or 'jQuery'.
        case (Boolean)(m[4]):
            m = m[4];
            Class = 'dollar';
            break;

        // More cases...
    }

    return '<span class="' + Class + '">' + m + '</span>';
})

In meinem speziellen Fall (einem Code-Spiegel-ähnlichen Editor) ist es viel einfacher, einen großen regulären Ausdruck auszuführen, als viele Ersetzungen wie die folgenden, da jedes Mal, wenn ich ein Ausdruck durch ein HTML-Tag ersetze, das nächste Muster verwendet wird Es ist schwieriger zu zielen, ohne das HTML-Tag selbst zu beeinflussen (und ohne das gute Aussehen , das in Javascript leider nicht unterstützt wird):

.replace(/(\b\d+|null\b)/g, '<span class="number">$1</span>')
.replace(/(\btrue|false\b)/g, '<span class="bool">$1</span>')
.replace(/\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/g, '<span class="keyword">$1</span>')
.replace(/\$/g, '<span class="dollar">$</span>')
.replace(/([\[\](){}.:;,+\-?=])/g, '<span class="ponctuation">$1</span>')
antoni
quelle
2

Sie könnten so etwas tun wie:

function concatRegex(...segments) {
  return new RegExp(segments.join(''));
}

Die Segmente wären Zeichenfolgen (anstelle von Regex-Literalen), die als separate Argumente übergeben werden.

Neil Strain
quelle
1

Nein, der wörtliche Weg wird nicht unterstützt. Sie müssen RegExp verwenden.

Aupajo
quelle
1

Verwenden Sie den Konstruktor mit 2 Parametern und vermeiden Sie das Problem mit dem abschließenden '/':

var re_final = new RegExp("\\" + ".", "g");    // constructor can have 2 params!
console.log("...finally".replace(re_final, "!") + "\n" + re_final + 
    " works as expected...");                  // !!!finally works as expected

                         // meanwhile

re_final = new RegExp("\\" + "." + "g");              // appends final '/'
console.log("... finally".replace(re_final, "!"));    // ...finally
console.log(re_final, "does not work!");              // does not work
ph7
quelle
1

Sie können Regex-Quellen sowohl aus der Literal- als auch aus der RegExp-Klasse zusammenfassen:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);
Jeff Lowery
quelle
1

Der einfachere Weg für mich wäre, die Quellen zu verketten, z.

a = /\d+/
b = /\w+/
c = new RegExp(a.source + b.source)

Der c-Wert führt zu:

/ \ d + \ w + /

Daniel Aragão
quelle
-2

Ich bevorzuge die Verwendung, eval('your expression')da nicht /an jedem Ende /das hinzugefügt ='new RegExp'wird.

Praesagus
quelle