Как я могу объединить литералы регулярных выражений в JavaScript?

145

Можно ли сделать что-то подобное?

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

Или я должен использовать новый RegExp()синтаксис и объединить строку? Я бы предпочел использовать литерал, так как код более очевиден и лаконичен.

eyelidlessness
источник
2
Проще справиться с экранированными символами регулярных выражений, если использовать String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

Ответы:

190

Вот как создать регулярное выражение без использования литерального синтаксиса регулярного выражения. Это позволяет вам выполнять произвольные манипуляции со строками до того, как они станут объектом регулярного выражения:

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

Если у вас есть два литерала регулярных выражений, вы можете объединить их, используя эту технику:

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

Это просто более многословно, чем просто иметь выражение 1 и 2, являющиеся литеральными строками вместо литеральных регулярных выражений.

Jerub
источник
2
Имейте в виду, что каждый сегмент должен быть допустимым регулярным выражением при использовании этого подхода. new RegExp(/(/.source + /.*/.source + /)?/.source);Кажется, что создание выражения не работает.
Сэм
Это решение не работает в случае групп с обратным соответствием. Смотрите мой ответ для рабочего решения в этом случае.
Микаэль Майер
Если вам нужно экранировать символ, используйте двойную обратную косую черту: new Regexp ('\\ $' + "flum")
Джефф Лоури
Вы можете получить доступ к флагам, если вам нужно, с помощью «<regexp> .flags», так что теоретически вы также можете объединить их.
bnunamak
Откуда ты expression_one? Вы имеете в виду regex1?
TallOrderDev
30

Простая случайная конкатенация объектов регулярных выражений может иметь некоторые побочные эффекты. Вместо этого используйте 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

Это также даст вам возможность сохранить флаги регулярного выражения из предыдущего RegExp, используя стандартные флаги RegExp.

jsFiddle

Иафет Сальва
источник
Это можно улучшить с помощьюRegExp.prototype.flags
Дмитрий Паржицкий
19

Я не совсем согласен с опцией "eval".

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

выдаст "// abcd // efgh //", что не является ожидаемым результатом.

Используя источник как

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

даст "/ abcdefgh /", и это правильно.

Логично, что нет необходимости оценивать, вы знаете свое выражение. Вам просто нужен ИСТОЧНИК или как он написан, не обязательно его значение. Что касается флагов, вам просто нужно использовать необязательный аргумент RegExp.

В моей ситуации я сталкиваюсь с проблемой использования ^ и $ в нескольких выражениях, которые я пытаюсь объединить вместе! Эти выражения представляют собой грамматические фильтры, используемые в программе. Теперь я не хочу использовать некоторые из них вместе, чтобы справиться с ситуацией ПРЕДЛОЖЕНИЙ. Возможно, мне придется «нарезать» источники, чтобы удалить начальный и конечный ^ (и / или) $ :) Приветствия, Алекс.

Alex
источник
Мне нравится использование свойства источника. Если вы - как и я - используете jslint, это будет раздражать, если вы сделаете что-то вроде этого:var regex = "\.\..*"
Nils-o-mat
7

Проблема Если регулярное выражение содержит группы с обратным соответствием, такие как \ 1.

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

Тогда просто контактирование с источниками не будет работать. В самом деле, сочетание этих двух:

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

Решение: сначала мы подсчитываем количество совпадающих групп в первом регулярном выражении, затем для каждого обратно совпадающего токена во втором мы увеличиваем его на количество совпадающих групп.

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

Тест:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true
Микаэль Майер
источник
2
Да (я не буду изменять это здесь). Эта функция является ассоциативной, поэтому вы можете использовать следующий код:function concatenateList() { var res = arguments[0]; for(var i = 1; i < arguments.length; i++) { res = concatenate(res, arguments[i]); } return res; }
Микаэль Майер
3

Было бы предпочтительно использовать буквальный синтаксис как можно чаще. Это короче, более разборчиво, и вам не нужны escape-кавычки или двойные обратные зазоры. Из "Javascript Patterns", Стоян Стефанов 2010.

Но использование New может быть единственным способом объединения.

Я бы избежал Eval. Это небезопасно.

Джонатан Райт
источник
1
Я думаю, что сложные регулярные выражения более разборчивы, когда разбиты и прокомментированы, как в вопросе.
Сэм
3

При условии что:

  • вы знаете, что вы делаете в своем регулярном выражении;
  • у вас есть много частей регулярных выражений для формирования шаблона, и они будут использовать один и тот же флаг;
  • Вы находите более читабельным разделение своих маленьких кусочков шаблона в массив;
  • Вы также хотите иметь возможность комментировать каждую часть для следующего разработчика или себя позже;
  • вы предпочитаете визуально упростить свое регулярное выражение, /this/gа не new RegExp('this', 'g');
  • это нормально для вас, чтобы собрать регулярное выражение в дополнительном шаге, а не в одном месте с самого начала;

Тогда вы можете написать так:

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

тогда вы можете сделать что-то вроде:

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

В моем конкретном случае (редактор с зеркальным отображением кода) гораздо проще выполнить одно большое регулярное выражение, чем много замен, например, следующих, поскольку каждый раз, когда я заменяю тег html для переноса выражения, следующий шаблон будет быть более трудным для цели, не затрагивая сам тег html (и без хорошего внешнего вида, который, к сожалению, не поддерживается в 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>')
Antoni
источник
2

Вы могли бы сделать что-то вроде:

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

Сегменты будут строками (а не литералами регулярных выражений), передаваемыми как отдельные аргументы.

Нил Стрейн
источник
1

Нет, буквальный путь не поддерживается. Вам придется использовать RegExp.

Aupajo
источник
1

Используйте конструктор с 2 параметрами и избегайте проблемы с завершающим символом '/':

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
pH 7
источник
1

Вы можете объединить источник регулярных выражений как из литерала, так и из класса RegExp:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);
Джефф Лоури
источник
1

мне проще было бы объединить источники, напр .:

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

значение c приведет к:

/ \ D + \ W + /

Даниэль Арагао
источник
-2

Я предпочитаю использовать, eval('your expression')потому что он не добавляет /на каждом конце, /что ='new RegExp'делает.

Praesagus
источник