Как разбить длинное регулярное выражение на несколько строк в JavaScript?

142

У меня очень длинное регулярное выражение, которое я хочу разбить на несколько строк в своем коде JavaScript, чтобы каждая строка длиной 80 символов в соответствии с правилами JSLint. Думаю, это лучше для чтения. Вот образец шаблона:

var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
Ник Сумейко
источник
4
Кажется, вы (пытаетесь) проверить адреса электронной почты. Почему бы просто не сделать /\S+@\S+\.\S+/?
Барт Кирс,
1
Вероятно, вам следует найти способ сделать это без регулярного выражения или с несколькими регулярными выражениями меньшего размера. Это было бы гораздо удобнее для чтения, чем такое длинное регулярное выражение. Если ваше регулярное выражение содержит более 20 символов, вероятно, есть лучший способ сделать это.
ForbesLindesay,
2
Разве 80 символов не устарели в наши дни с широкими мониторами?
Олег В. Волков
7
@ OlegV.Volkov Нет. Человек может использовать разделенные окна в vim, виртуальный терминал в серверной комнате. Неверно предполагать, что все будут кодировать в том же окне просмотра, что и вы. Более того, ограничение ваших строк до 80 символов заставляет вас разбивать код на более мелкие функции.
synic
Что ж, я определенно вижу вашу мотивацию в том, что вы хотите сделать это здесь - как только это регулярное выражение разбивается на несколько строк, как продемонстрировал Koolilnc, оно сразу становится прекрасным примером читаемого, самодокументирующегося кода. ¬_¬
Марк Эмери

Ответы:

119

Вы можете преобразовать его в строку и создать выражение, вызвав new RegExp():

var myRE = new RegExp (['^(([^<>()[\]\\.,;:\\s@\"]+(\\.[^<>(),[\]\\.,;:\\s@\"]+)*)',
                        '|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                        '[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\\.)+',
                        '[a-zA-Z]{2,}))$'].join(''));

Примечания:

  1. при преобразовании литерала выражения в строку необходимо избегать всех обратных косых черт, поскольку обратные косые черты используются при вычислении строкового литерала . (Подробнее см. Комментарий Кайо.)
  2. RegExp принимает модификаторы в качестве второго параметра

    /regex/g => new RegExp('regex', 'g')

[ Дополнение ES20xx (шаблон с тегами)]

В ES20xx вы можете использовать шаблоны с тегами . Смотрите фрагмент.

Заметка:

  • Неудобство в том , что вы не можете использовать обычный пробел в регулярной строке выражения (всегда используйте \s, \s+, \s{1,x}, \t, и \nт.д.).

(() => {
  const createRegExp = (str, opts) => 
    new RegExp(str.raw[0].replace(/\s/gm, ""), opts || "");
  const yourRE = createRegExp`
    ^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|
    (\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
    (([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`;
  console.log(yourRE);
  const anotherLongRE = createRegExp`
    (\byyyy\b)|(\bm\b)|(\bd\b)|(\bh\b)|(\bmi\b)|(\bs\b)|(\bms\b)|
    (\bwd\b)|(\bmm\b)|(\bdd\b)|(\bhh\b)|(\bMI\b)|(\bS\b)|(\bMS\b)|
    (\bM\b)|(\bMM\b)|(\bdow\b)|(\bDOW\b)
    ${"gi"}`;
  console.log(anotherLongRE);
})();

KooiInc
источник
4
A new RegExp- отличный способ для многострочных регулярных выражений. Вместо объединения массивов вы можете просто использовать оператор конкатенации строк:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab
44
Внимание: длинный литерал регулярного выражения может быть разбит на несколько строк, используя приведенный выше ответ. Однако это требует осторожности, потому что вы не можете просто скопировать литерал регулярного выражения (определенный с помощью //) и вставить его в качестве строкового аргумента в конструктор RegExp. Это связано с тем, что символы обратной косой черты используются при вычислении строкового литерала . Пример: /Hey\sthere/нельзя заменить на new RegExp("Hey\sthere"). Вместо этого его следует заменить на. new RegExp("Hey\\sthere")Обратите внимание на дополнительную обратную косую черту! Следовательно, я предпочитаю просто оставлять длинный литерал регулярного выражения на одной длинной строке
Kayo
5
Еще более ясный способ сделать это - создать именованные переменные, содержащие значимые подразделы, и объединить их в виде строк или в виде массива. Это позволяет вам строить RegExpобъект так, чтобы его было легче понять.
Крис Кричо
121

Расширяя ответ @KooiInc, вы можете избежать ручного экранирования каждого специального символа, используя sourceсвойство RegExpобъекта.

Пример:

var urlRegex= new RegExp(''
  + /(?:(?:(https?|ftp):)?\/\/)/.source     // protocol
  + /(?:([^:\n\r]+):([^@\n\r]+)@)?/.source  // user:pass
  + /(?:(?:www\.)?([^\/\n\r]+))/.source     // domain
  + /(\/[^?\n\r]+)?/.source                 // request
  + /(\?[^#\n\r]*)?/.source                 // query
  + /(#?[^\n\r]*)?/.source                  // anchor
);

или если вы не хотите повторять .sourceсвойство, вы можете сделать это с помощью Array.map()функции:

var urlRegex= new RegExp([
  /(?:(?:(https?|ftp):)?\/\/)/      // protocol
  ,/(?:([^:\n\r]+):([^@\n\r]+)@)?/  // user:pass
  ,/(?:(?:www\.)?([^\/\n\r]+))/     // domain
  ,/(\/[^?\n\r]+)?/                 // request
  ,/(\?[^#\n\r]*)?/                 // query
  ,/(#?[^\n\r]*)?/                  // anchor
].map(function(r) {return r.source}).join(''));

В ES6 функция карты может быть сокращена до: .map(r => r.source)

корун
источник
3
Именно то, что я искал, супер-чистый. Благодарность!
Мариан Загоруико,
10
Это действительно удобно для добавления комментариев к длинному регулярному выражению. Однако он ограничен наличием соответствующих круглых скобок в одной строке.
Nathan S. Watson-Haigh
Однозначно это! Очень приятно с возможностью комментировать каждое подрегулярное выражение.
GaryO
Спасибо, это помогло поместить исходный код в функцию регулярного выражения
Код
Очень умный. Спасибо, эта идея мне очень помогла. В качестве примечания: я инкапсулировал все это в функцию, чтобы сделать ее еще чище: combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))Использование:combineRegex(/regex1/, /regex2/, ...)
Scindix
26

Использование строк в new RegExp неудобно, потому что вы должны избегать всех обратных косых черт. Вы можете писать регулярные выражения меньшего размера и объединять их.

Давайте разделим это регулярное выражение

/^foo(.*)\bar$/

Позже мы будем использовать функцию, чтобы сделать вещи красивее

function multilineRegExp(regs, options) {
    return new RegExp(regs.map(
        function(reg){ return reg.source; }
    ).join(''), options);
}

А теперь давай качать

var r = multilineRegExp([
     /^foo/,  // we can add comments too
     /(.*)/,
     /\bar$/
]);

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

Риккардо Галли
источник
Это очень круто - вам не только не нужно делать дополнительное экранирование, но также вы сохраняете специальную подсветку синтаксиса для подрегексов!
quezak
одно предостережение: вам нужно убедиться, что ваши подрегулярные выражения являются самодостаточными, или заключить каждое в новую группу скобок. Пример: multilineRegExp([/a|b/, /c|d])приводит /a|bc|d/, а вы имели в виду (a|b)(c|d).
quezak
7

Благодаря чудесному миру шаблонных литералов теперь вы можете писать большие, многострочные, хорошо прокомментированные и даже семантически вложенные регулярные выражения в ES6.

//build regexes without worrying about
// - double-backslashing
// - adding whitespace for readability
// - adding in comments
let clean = (piece) => (piece
    .replace(/((^|\n)(?:[^\/\\]|\/[^*\/]|\\.)*?)\s*\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$1')
    .replace(/((^|\n)(?:[^\/\\]|\/[^\/]|\\.)*?)\s*\/\/[^\n]*/g, '$1')
    .replace(/\n\s*/g, '')
);
window.regex = ({raw}, ...interpolations) => (
    new RegExp(interpolations.reduce(
        (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
        clean(raw[0])
    ))
);

Используя это, теперь вы можете писать такие регулярные выражения:

let re = regex`I'm a special regex{3} //with a comment!`;

Выходы

/I'm a special regex{3}/

А как насчет многострочного?

'123hello'
    .match(regex`
        //so this is a regex

        //here I am matching some numbers
        (\d+)

        //Oh! See how I didn't need to double backslash that \d?
        ([a-z]{1,3}) /*note to self, this is group #2*/
    `)
    [2]

Выходы hel, аккуратные!
«Что, если мне действительно нужно искать новую строку?», Ну тогда используйте \nглупо!
Работаю над моими Firefox и Chrome.


Хорошо, "как насчет чего-нибудь посложнее?"
Конечно, вот фрагмент JS-парсера, деструктурирующего объект, над которым я работал :

regex`^\s*
    (
        //closing the object
        (\})|

        //starting from open or comma you can...
        (?:[,{]\s*)(?:
            //have a rest operator
            (\.\.\.)
            |
            //have a property key
            (
                //a non-negative integer
                \b\d+\b
                |
                //any unencapsulated string of the following
                \b[A-Za-z$_][\w$]*\b
                |
                //a quoted string
                //this is #5!
                ("|')(?:
                    //that contains any non-escape, non-quote character
                    (?!\5|\\).
                    |
                    //or any escape sequence
                    (?:\\.)
                //finished by the quote
                )*\5
            )
            //after a property key, we can go inside
            \s*(:|)
      |
      \s*(?={)
        )
    )
    ((?:
        //after closing we expect either
        // - the parent's comma/close,
        // - or the end of the string
        \s*(?:[,}\]=]|$)
        |
        //after the rest operator we expect the close
        \s*\}
        |
        //after diving into a key we expect that object to open
        \s*[{[:]
        |
        //otherwise we saw only a key, we now expect a comma or close
        \s*[,}{]
    ).*)
$`

Он выводит /^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

И запустить его с небольшой демонстрацией?

let input = '{why, hello, there, "you   huge \\"", 17, {big,smelly}}';
for (
    let parsed;
    parsed = input.match(r);
    input = parsed[parsed.length - 1]
) console.log(parsed[1]);

Успешно выводит

{why
, hello
, there
, "you   huge \""
, 17
,
{big
,smelly
}
}

Обратите внимание на успешный захват строки в кавычках.
Я тестировал его в Chrome и Firefox, отлично работает!

Если интересно, вы можете проверить, что я делал , и его демонстрацию .
Хотя он работает только в Chrome, потому что Firefox не поддерживает обратные ссылки или именованные группы. Так что обратите внимание, что пример, приведенный в этом ответе, на самом деле является кастрированной версией, и его можно легко обмануть, приняв недопустимые строки.

Хэшбраун
источник
2
вам следует подумать об экспорте этого как пакета NodeJS, это замечательно
rmobis
1
Хотя я никогда не делал этого сам, здесь есть довольно подробное руководство: zellwk.com/blog/publish-to-npm . Я предлагаю проверить np в конце страницы. Я никогда этим не пользовался, но Синдре Сорхус - волшебник с этими вещами, так что я бы не отказался от него.
rmobis
6

Здесь есть хорошие ответы, но для полноты картины следует упомянуть основную функцию Javascript - наследование с цепочкой прототипов . Примерно так это иллюстрирует идею:

RegExp.prototype.append = function(re) {
  return new RegExp(this.source + re.source, this.flags);
};

let regex = /[a-z]/g
.append(/[A-Z]/)
.append(/[0-9]/);

console.log(regex); //=> /[a-z][A-Z][0-9]/g

Джеймс Донохью
источник
Это лучший ответ здесь.
parttimeturtle
4

В приведенном выше регулярном выражении отсутствуют некоторые черные косые черты, которые не работают должным образом. Итак, я отредактировал регулярное выражение. Обратите внимание на это регулярное выражение, которое работает на 99,99% для проверки электронной почты.

let EMAIL_REGEXP = 
new RegExp (['^(([^<>()[\\]\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\.,;:\\s@\"]+)*)',
                    '|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                    '[0-9]{1,3}\])|(([a-zA-Z\\-0-9]+\\.)+',
                    '[a-zA-Z]{2,}))$'].join(''));
Анвеш Редди
источник
1

Чтобы избежать массива join, вы также можете использовать следующий синтаксис:

var pattern = new RegExp('^(([^<>()[\]\\.,;:\s@\"]+' +
  '(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@' +
  '((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' +
  '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');
andreasonny83
источник
0

Лично я бы выбрал менее сложное регулярное выражение:

/\S+@\S+\.\S+/

Конечно, это менее точно чем ваш текущий шаблон, но чего вы пытаетесь достичь? Вы пытаетесь отловить случайные ошибки, которые могут ввести ваши пользователи, или вас беспокоит, что ваши пользователи могут попытаться ввести недопустимые адреса? Если это первое, я бы выбрал более простой узор. Если это последнее, то лучшим вариантом может быть проверка путем ответа на электронное письмо, отправленное на этот адрес.

Однако, если вы хотите использовать свой текущий шаблон, было бы (IMO) легче читать (и поддерживать!), Создавая его из более мелких подшаблонов, например:

var box1 = "([^<>()[\]\\\\.,;:\s@\"]+(\\.[^<>()[\\]\\\\.,;:\s@\"]+)*)";
var box2 = "(\".+\")";

var host1 = "(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])";
var host2 = "(([a-zA-Z\-0-9]+\\.)+[a-zA-Z]{2,})";

var regex = new RegExp("^(" + box1 + "|" + box2 + ")@(" + host1 + "|" + host2 + ")$");
Барт Кирс
источник
21
Голосование против - хотя ваши комментарии об уменьшении сложности регулярного выражения действительны, OP конкретно спрашивает, как «разбить длинное регулярное выражение на несколько строк». Итак, хотя ваш совет и верен, он был дан по неправильным причинам. например, изменение бизнес-логики для работы с языком программирования. Кроме того, приведенный вами пример кода довольно уродлив.
Sleepycal
4
@sleepycal Я думаю, Барт ответил на вопрос. См. Последний раздел его ответа. Он ответил на вопрос, а также предложил альтернативу.
Nidhin David
0

Вы можете просто использовать строковую операцию.

var pattenString = "^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|"+
"(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|"+
"(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$";
var patten = new RegExp(pattenString);
Мубина
источник
0

Я попытался улучшить ответ коруна, инкапсулируя все и реализовав поддержку разделения групп захвата и наборов символов, что сделало этот метод гораздо более универсальным.

Чтобы использовать этот фрагмент, вам нужно вызвать вариативную функцию combineRegex, аргументы которой являются объектами регулярного выражения, которые вам нужно объединить. Его реализацию можно найти внизу.

Группы захвата не могут быть разделены таким образом напрямую, так как некоторые части останутся только с одной круглой скобкой. Ваш браузер не сработает за исключением.

Вместо этого я просто передаю содержимое группы захвата внутри массива. Скобки добавляются автоматически при combineRegexобнаружении массива.

Кроме того, кванторы должны за чем-то следовать. Если по какой-то причине регулярное выражение необходимо разделить перед квантификатором, вам нужно добавить пару круглых скобок. Они будут удалены автоматически. Дело в том, что пустая группа захвата бесполезна, и квантификаторам есть на что ссылаться. Тот же метод можно использовать для таких вещей, как группы без захвата ( /(?:abc)/становится [/()?:abc/]).

Лучше всего это объяснить на простом примере:

var regex = /abcd(efghi)+jkl/;

станет:

var regex = combineRegex(
    /ab/,
    /cd/,
    [
        /ef/,
        /ghi/
    ],
    /()+jkl/    // Note the added '()' in front of '+'
);

Если вы должны разделить наборы символов, вы можете использовать объекты ( {"":[regex1, regex2, ...]}) вместо массивов ( [regex1, regex2, ...]). Содержимое ключа может быть любым, если объект содержит только один ключ. Обратите внимание, что вместо того (), чтобы использовать в ]качестве фиктивного начала, если первый символ можно интерпретировать как квантификатор. Т.е. /[+?]/становится{"":[/]+?/]}

Вот фрагмент и более полный пример:

function combineRegexStr(dummy, ...regex)
{
    return regex.map(r => {
        if(Array.isArray(r))
            return "("+combineRegexStr(dummy, ...r).replace(dummy, "")+")";
        else if(Object.getPrototypeOf(r) === Object.getPrototypeOf({}))
            return "["+combineRegexStr(/^\]/, ...(Object.entries(r)[0][1]))+"]";
        else 
            return r.source.replace(dummy, "");
    }).join("");
}
function combineRegex(...regex)
{
    return new RegExp(combineRegexStr(/^\(\)/, ...regex));
}

//Usage:
//Original:
console.log(/abcd(?:ef[+A-Z0-9]gh)+$/.source);
//Same as:
console.log(
  combineRegex(
    /ab/,
    /cd/,
    [
      /()?:ef/,
      {"": [/]+A-Z/, /0-9/]},
      /gh/
    ],
    /()+$/
  ).source
);

Scindix
источник
0

Отличный ответ @Hashbrown направил меня на верный путь. Вот моя версия, тоже вдохновленная этим блогом .

function regexp(...args) {
  function cleanup(string) {
    // remove whitespace, single and multi-line comments
    return string.replace(/\s+|\/\/.*|\/\*[\s\S]*?\*\//g, '');
  }

  function escape(string) {
    // escape regular expression
    return string.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
  }

  function create(flags, strings, ...values) {
    let pattern = '';
    for (let i = 0; i < values.length; ++i) {
      pattern += cleanup(strings.raw[i]);  // strings are cleaned up
      pattern += escape(values[i]);        // values are escaped
    }
    pattern += cleanup(strings.raw[values.length]);
    return RegExp(pattern, flags);
  }

  if (Array.isArray(args[0])) {
    // used as a template tag (no flags)
    return create('', ...args);
  }

  // used as a function (with flags)
  return create.bind(void 0, args[0]);
}

Используйте это так:

regexp('i')`
  //so this is a regex

  //here I am matching some numbers
  (\d+)

  //Oh! See how I didn't need to double backslash that \d?
  ([a-z]{1,3}) /*note to self, this is group #2*/
`

Чтобы создать этот RegExpобъект:

/(\d+)([a-z]{1,3})/i
Нуно Крусес
источник