Jak mogę połączyć literały regex w JavaScript?


145

Czy można coś takiego zrobić?

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

Czy muszę użyć nowej RegExp()składni i połączyć ciąg? Wolałbym użyć dosłownego, ponieważ kod jest zarówno bardziej oczywisty, jak i zwięzły.


2
Łatwiej jest poradzić sobie ze znakami ucieczki regex, jeśli używasz String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

Odpowiedzi:


190

Oto jak utworzyć wyrażenie regularne bez używania składni literału wyrażenia regularnego. Pozwala to na dowolną manipulację ciągiem znaków, zanim stanie się on obiektem wyrażenia regularnego:

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

Jeśli masz dwa literały wyrażeń regularnych, możesz je połączyć za pomocą tej techniki:

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

Jest to po prostu bardziej rozwlekłe niż posiadanie wyrażenia pierwszego i drugiego jako ciągów znaków zamiast dosłownych wyrażeń regularnych.


2
Należy pamiętać, że przy stosowaniu tego podejścia każdy segment musi być prawidłowym wyrażeniem regularnym. Skonstruowanie wyrażenia takiego jak new RegExp(/(/.source + /.*/.source + /)?/.source);nie wydaje się działać.
Sam

To rozwiązanie nie działa w przypadku grup dopasowanych wstecznie. Zobacz moją odpowiedź na działające rozwiązanie w takim przypadku.
Mikaël Mayer

Jeśli chcesz uciec od znaku, użyj podwójnych odwrotnych ukośników: nowe Regexp ('\\ $' + "flum")
Jeff Lowery,

Możesz uzyskać dostęp do flag, jeśli musisz, za pomocą "<regexp> .flags", więc teoretycznie możesz je również łączyć.
bnunamak

Skąd się wybierasz expression_one? Czy masz na myśli regex1?
TallOrderDev

30

Samo losowe łączenie obiektów wyrażeń regularnych może mieć pewne niepożądane skutki uboczne. Zamiast tego użyj 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

Zapewni to również możliwość zachowania flag wyrażeń regularnych z poprzedniego RegExp przy użyciu standardowych flag RegExp.

jsFiddle


Można to poprawić za pomocąRegExp.prototype.flags
Dmitry Parzhitsky

19

Nie całkiem zgadzam się z opcją „eval”.

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

zwróci „// abcd // efgh //”, co nie jest zamierzonym wynikiem.

Korzystanie ze źródeł takich jak

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

da "/ abcdefgh /" i to jest poprawne.

Logicznie, nie ma potrzeby OCENIANIA, znasz swoje WYRAŻENIE. Potrzebujesz tylko jego ŹRÓDŁA lub tego, jak jest napisane, niekoniecznie jego wartości. Jeśli chodzi o flagi, wystarczy użyć opcjonalnego argumentu RegExp.

W mojej sytuacji poruszam się w kwestii ^ i $ używanych w kilku wyrażeniach, które próbuję połączyć! Wyrażenia te są filtrami gramatycznymi używanymi w programie. Teraz nie chciałbym używać niektórych z nich razem do obsługi PREPOZYCJI. Być może będę musiał „pokroić” źródła, aby usunąć początek i koniec ^ (i / lub) $ :) Pozdrawiam, Alex.


Podoba mi się użycie właściwości source. Jeśli - tak jak ja - użyjesz jslint, to będzie zrzędzić, jeśli zrobisz coś takiego:var regex = "\.\..*"
Nils-o-mat

7

Problem Jeśli wyrażenie regularne zawiera grupy z dopasowaniem wstecznym, takie jak \ 1.

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

Wtedy zwykłe kontatowanie źródeł nie zadziała. Rzeczywiście, połączenie tych dwóch to:

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

Rozwiązanie: najpierw liczymy liczbę pasujących grup w pierwszym wyrażeniu regularnym, a następnie dla każdego tokena dopasowania wstecznego w drugim zwiększamy go o liczbę pasujących grup.

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

Test:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true

2
Tak (nie będę go tutaj modyfikować). Ta funkcja jest asocjacyjna, więc możesz użyć następującego kodu: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

Byłoby lepiej, aby jak najczęściej używać składni literału. Jest krótszy, bardziej czytelny i nie potrzebujesz cudzysłowów ucieczki ani podwójnych znaków zwrotnych. Z „Wzorce JavaScript”, Stoyan Stefanov 2010.

Ale użycie New może być jedynym sposobem na konkatenację.

Unikałbym eval. To nie jest bezpieczne.


1
Myślę, że złożone wyrażenia regularne są bardziej czytelne, gdy zostaną podzielone i skomentowane, jak w pytaniu.
Sam

3

Pod warunkiem, że:

  • wiesz, co robisz w swoim wyrażeniu regularnym;
  • masz wiele elementów wyrażenia regularnego tworzących wzorzec i będą one używać tej samej flagi;
  • bardziej czytelne jest rozdzielenie małych fragmentów wzoru na tablicę;
  • chcesz także móc później skomentować każdą część dla następnego dewelopera lub siebie;
  • wolisz wizualnie uprościć swoje wyrażenie regularne /this/gzamiast new RegExp('this', 'g');
  • możesz złożyć wyrażenie regularne w dodatkowym kroku, zamiast mieć je w jednym kawałku od początku;

W takim razie możesz napisać w ten sposób:

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

możesz wtedy zrobić coś takiego:

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

W moim konkretnym przypadku (edytor podobny do kodu lustrzanego) znacznie łatwiej jest wykonać jedno duże wyrażenie regularne niż wiele zastępstw, takich jak śledzenie, ponieważ za każdym razem, gdy zastępuję tag html, aby zawinąć wyrażenie, następny wzorzec będzie trudniej jest kierować reklamy bez wpływu na sam tag HTML (i bez dobrego lookbehind, którego niestety nie obsługuje javascript):

.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>')

2

Możesz zrobić coś takiego:

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

Segmenty byłyby łańcuchami (a nie literałami wyrażenia regularnego) przekazanymi jako oddzielne argumenty.


1

Nie, dosłowny sposób nie jest obsługiwany. Będziesz musiał użyć RegExp.


1

Użyj konstruktora z 2 parametrami i uniknij problemu z końcowym znakiem „/”:

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

1

Możesz konkatować źródło wyrażenia regularnego zarówno z klasy literału, jak i klasy RegExp:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);

1

łatwiejszą drogą byłoby dla mnie konkatenację źródeł, np .:

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

wartość c spowoduje:

/ \ d + \ w + /


-2

Wolę używać eval('your expression'), ponieważ nie dodać /na każdym końcu /, że ='new RegExp'nie.

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.