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

У меня очень длинное регулярное выражение, которое я хочу разбить на несколько строк в своем коде 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,}))$/;

7 ответов

Решение

Вы можете преобразовать его в строку и создать выражение, вызвав 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, вы можете избежать экранирования вручную каждого специального символа, используя 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)

Использование строк в 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$/
]);

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

Благодаря чудесному миру шаблонных литералов теперь вы можете писать большие, многострочные, хорошо прокомментированные и даже семантически вложенные регулярные выражения в 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 не поддерживает обратные ссылки или именованные группы. Так что обратите внимание, что приведенный в этом ответе пример на самом деле является кастрированной версией, и его можно легко обмануть, приняв недопустимые строки.

Здесь есть хорошие ответы, но для полноты кто-то должен упомянуть основную особенность наследования 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

В приведенном выше регулярном выражении отсутствуют некоторые черные черты, которые не работают должным образом. Итак, я отредактировал регулярное выражение. Пожалуйста, примите во внимание это регулярное выражение, которое работает на 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(''));

Чтобы избежать массива 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,}))$');

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

Чтобы использовать этот фрагмент, вам нужно вызвать вариативную функцию 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
);

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

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

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

/\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 + ")$");

Отличный ответ @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
Другие вопросы по тегам