Як я можу об'єднати регулярні вирази в JavaScript?


145

Чи можна зробити щось подібне?

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

Або я повинен використовувати новий RegExp()синтаксис і об'єднувати рядок? Я вважаю за краще використовувати буквальне, оскільки код є більш зрозумілим і стислим.


2
Це легше мати справу з уцілілих символів регулярних виразів , якщо ви використовуєте String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
іоносферні

Відповіді:


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

Це просто багатослівніше, ніж просто вираз один і два є буквальними рядками замість буквальних регулярних виразів.


2
Майте на увазі, що кожен сегмент повинен бути дійсним регулярним виразом при використанні цього підходу. Побудова виразу, такого як new RegExp(/(/.source + /.*/.source + /)?/.source);, здається, не працює.
Сем

Це рішення не працює у випадку груп, що підтримують відповідність. Дивіться мою відповідь на робоче рішення у такому випадку.
Mikaël Mayer

Якщо вам потрібно уникнути чару, скористайтеся подвійними нахилами: new Regexp ('\\ $' + "flum")
Jeff Lowery

Ви можете отримати доступ до прапорів, якщо вам потрібно "<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.

У моїй ситуації я бігаю у випуску ^ і $, використовуваних у кількох виразах, які я намагаюся об'єднати разом! Ці вирази - це граматичні фільтри, які використовуються в програмі. Тепер я не хочу використовувати деякі з них разом для розгляду справи ПРЕПОЗИЦІЙ. Можливо, мені доведеться "нарізати" джерела, щоб видалити початковий і кінцевий ^ (та / або) $ :) Привіт, Олексій.


Мені подобається використання властивості source. Якщо ви - як я - використовуйте 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; }
Mikaël Mayer

3

Краще використовувати буквальний синтаксис якомога частіше. Він коротший, більш розбірливий, і вам не потрібні котирування втечі чи подвійні люки. З "Шаблони Javascript", Стоян Стефанов, 2010.

Але використання Нового може бути єдиним способом об'єднання.

Я б уникнув овалів. Це не безпечно.


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

2

Ви можете зробити щось на кшталт:

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

Сегменти будуть рядками (а не регулярними виразами), переданими як окремі аргументи.


1

Ні, буквальний шлях не підтримується. Вам доведеться використовувати RegExp.


1

Використовуйте конструктор з двома парамами та уникайте проблем із записом '/':

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

Ви можете сформулювати джерело регулярних виразів як з прямого, так і з класу 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'.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.