Як розділити довгий регулярний вираз на кілька рядків у JavaScript?


138

У мене дуже довгий регулярний вираз, який я хочу розділити на кілька рядків у своєму коді 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
@ Олег В.Волков. Ні. Людина могла використовувати розділені вікна у vim, віртуальний термінал у серверній кімнаті. Неправильно припускати, що всі будуть кодувати в тому самому вікні перегляду, що і ви. Крім того, обмеження рядків до 80 символів змушує вас розбити код на більш дрібні функції.
synic

Ну, я, безумовно, бачу вашу мотивацію, щоб хотіти зробити це тут - як тільки цей регулярний вираз розбивається на кілька рядків, як продемонстрував Koolilnc, він одразу стає ідеальним прикладом для читання коду, що самодокументує. ¬_¬
Марк Амері

Відповіді:


115

Ви можете перетворити його в рядок і створити вираз, зателефонувавши 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);
})();


4
A new RegExp- це відмінний спосіб для багаторядкових регулярних виразів. Замість того, щоб приєднуватися до масивів, ви можете просто скористатися оператором конкатенації рядків:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab

43
Обережно: довгий літеральний регулярний вираз може бути розбитий на кілька рядків, використовуючи вищевказану відповідь. Однак це потребує обережності, оскільки ви не можете просто скопіювати буквений регулярний вираз (визначений за допомогою //) і вставити його як аргумент рядка в конструктор RegExp. Це пояснюється тим, що символи зворотної косої риси вживаються при оцінці рядкового літералу . Приклад: /Hey\sthere/не можна замінити на new RegExp("Hey\sthere"). Замість цього його слід замінити на new RegExp("Hey\\sthere")Зверніть увагу на додатковий нахил! Отже, я віддаю перевагу просто залишити довгий виразний літерал на одній довгій лінії
Кайо

5
Ще чіткіший спосіб зробити це - створити названі змінні, що містять значущі підрозділи, та приєднати їх до рядків чи масиву. Це дозволяє вам побудувати RegExpтак, що це набагато простіше зрозуміти.
Кріс Кричо

117

Розширюючи відповідь @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
Це дійсно зручно для додавання коментарів до тривалого зворотного перегляду. Однак це обмежено наявністю відповідних дужок в одному рядку.
Натан С. Уотсон-Хей

Однозначно, це! Супер приємно з можливістю коментувати кожен підрегекс.
GaryO

Дякую, це допомогло ввести джерело у функцію regex
Код

Дуже розумний. Дякую, ця ідея мені дуже допомогла. Як бічна примітка: я інкапсулював всю цю функцію, щоб зробити її ще чистішою: combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))Використання:combineRegex(/regex1/, /regex2/, ...)
Scindix

25

Використання рядків у 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

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

6

Завдяки дивовижному світу буквених шаблонів тепер ви можете писати великі, багаторядкові, добре коментовані та навіть семантично вкладені регулярні виразки в 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 не підтримує зворотні посилання або групи з іменами. Тож зауважте, що приклад, наведений у цій відповіді, насправді є кастрованою версією і може бути легко підманутись у прийнятті недійсних рядків.


1
вам варто подумати про те, щоб експортувати це як пакет NodeJS, це дивовижно
rmobis

1
Хоча я ніколи цього не робив сам, тут є досить ґрунтовний підручник: zellwk.com/blog/publish-to-npm . Я б запропонував перевірити np в кінці сторінки. Я ніколи його не використовував, але Сіндре Сорхус - фокусник з цими речами, тому я б не передавав це.
rmobis

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

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
Короткий зміст - Хоча ваші коментарі щодо зменшення складності регулярних виразів є дійсними, ОП спеціально запитує, як "розділити довгий регулярний вираз на кілька рядків". Тож хоча ваша порада дійсна, вона була надана з неправильних причин. наприклад, зміна бізнес-логіки для обходу мови програмування. Крім того, приклад коду, який ви навели, є досить потворним.
сонливий

4
@sleepycal Я думаю, що Барт відповів на питання. Дивіться останній розділ його відповіді. Він відповів на питання, а також дав альтернативу.
Нідхін Давид

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

Я спробував покращити відповідь korun, інкапсулювавши все і реалізуючи підтримку розбиття груп захоплення та наборів символів - зробивши цей метод набагато більш універсальним.

Щоб скористатися цим фрагментом, вам потрібно викликати варіативну функцію 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
);


0

@ Відмінна відповідь Хешброуна привела мене до правильного шляху. Ось моя версія, також натхненна цим блогом .

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