Jak podzielić długie wyrażenie regularne na wiele wierszy w JavaScript?

138

Mam bardzo długie wyrażenie regularne, które chcę podzielić na wiele wierszy w moim kodzie JavaScript, aby każda linia miała długość 80 znaków zgodnie z regułami JSLint. Myślę, że jest po prostu lepszy do czytania. Oto próbka wzoru:

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,}))$/;
Nik Sumeiko
źródło
4
Wygląda na to, że (próbujesz) zweryfikować adresy e-mail. Dlaczego po prostu nie zrobić /\S+@\S+\.\S+/?
Bart Kiers,
1
Prawdopodobnie powinieneś poszukać sposobu, aby to zrobić bez wyrażenia regularnego lub z wieloma mniejszymi wyrażeniami regularnymi. Byłoby to znacznie bardziej czytelne niż tak długie wyrażenie regularne. Jeśli twoje wyrażenie regularne ma więcej niż około 20 znaków, prawdopodobnie istnieje lepszy sposób, aby to zrobić.
ForbesLindesay,
2
Czy w dzisiejszych czasach 80 znaków nie jest przestarzałe przy szerokich monitorach?
Oleg V. Volkov
7
@ OlegV.Volkov Nie. Osoba może używać podzielonych okien w vimie, wirtualnym terminalu w serwerowni. Błędem jest zakładanie, że wszyscy będą kodować w tym samym obszarze roboczym co Ty. Ponadto ograniczenie linii do 80 znaków zmusza cię do podzielenia kodu na mniejsze funkcje.
synic
Cóż, z pewnością widzę tutaj twoją motywację do zrobienia tego - po podzieleniu tego wyrażenia regularnego na wiele linii, jak zademonstrował Koolilnc, natychmiast staje się doskonałym przykładem czytelnego, samodokumentującego się kodu. ¬_¬
Mark Amery,

Odpowiedzi:

115

Możesz przekonwertować go na ciąg i utworzyć wyrażenie, wywołując 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(''));

Uwagi:

  1. podczas konwertowania literału wyrażenia na ciąg znaków należy pominąć wszystkie ukośniki odwrotne, ponieważ ukośniki odwrotne są używane podczas szacowania literału ciągu . (Więcej szczegółów w komentarzu Kayo).
  2. RegExp akceptuje modyfikatory jako drugi parametr

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

[ Dodatek ES20xx (otagowany szablon)]

W ES20xx możesz używać otagowanych szablonów . Zobacz fragment.

Uwaga:

  • Wadą jest to, że nie można używać zwykłego spacje w regularny ciąg wypowiedzi (zawsze używaj \s, \s+, \s{1,x}, \t, \netc).

(() => {
  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
źródło
4
A new RegExpto świetny sposób na wielowierszowe wyrażenia regularne. Zamiast łączyć tablice, możesz po prostu użyć operatora konkatenacji ciągów:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab
43
Uwaga: długi literał wyrażenia regularnego można podzielić na wiele wierszy przy użyciu powyższej odpowiedzi. Jednak wymaga ostrożności, ponieważ nie można po prostu skopiować literału wyrażenia regularnego (zdefiniowanego za pomocą //) i wkleić go jako argument ciągu do konstruktora RegExp. Dzieje się tak, ponieważ znaki ukośnika odwrotnego są używane podczas oceny literału ciągu . Przykład: /Hey\sthere/nie można zastąpić new RegExp("Hey\sthere"). Zamiast tego powinien zostać zastąpiony przez new RegExp("Hey\\sthere")Uwaga na dodatkowy lewy ukośnik! Dlatego wolę po prostu zostawić długi dosłowny regex w jednej długiej linii
Kayo Kwietnia
5
Jeszcze jaśniejszym sposobem jest utworzenie nazwanych zmiennych zawierających znaczące podsekcje i połączenie ich jako łańcuchów lub w tablicy. To pozwoli ci zbudować RegExpznacznie łatwiejszy do zrozumienia sposób.
Chris Krycho
117

Rozszerzając odpowiedź @KooiInc, możesz uniknąć ręcznej zmiany znaczenia każdego znaku specjalnego za pomocą sourcewłaściwości RegExpobiektu.

Przykład:

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

lub jeśli chcesz uniknąć powtarzania .sourcewłaściwości możesz to zrobić za pomocą Array.map()funkcji:

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

W ES6 funkcję mapy można zredukować do: .map(r => r.source)

korun
źródło
3
Dokładnie to, czego szukałem, super czyste. Dzięki!
Marian Zagoruiko
10
Jest to bardzo wygodne przy dodawaniu komentarzy do długich wyrażeń regularnych. Jednak jest on ograniczony przez pasujące nawiasy w tej samej linii.
Nathan S. Watson-Haigh
Zdecydowanie to! Super ładne z możliwością komentowania każdego wyrażenia podrzędnego.
GaryO
Dzięki, pomogło to w umieszczeniu źródła w funkcji regex
Kod
Bardzo mądry. Dzięki, ten pomysł bardzo mi pomógł. Na marginesie: całość combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))combineRegex(/regex1/, /regex2/, ...)
zawarłem
25

Używanie ciągów w new RegExpjest niewygodne, ponieważ musisz uniknąć wszystkich odwrotnych ukośników. Możesz pisać mniejsze wyrażenia regularne i łączyć je.

Podzielmy to wyrażenie regularne

/^foo(.*)\bar$/

Użyjemy funkcji, aby później uczynić rzeczy piękniejszymi

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

A teraz dajmy czadu

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

Ponieważ ma to koszt, spróbuj tylko raz zbudować prawdziwe wyrażenie regularne, a następnie użyj go.

Riccardo Galli
źródło
To jest bardzo fajne - nie tylko nie musisz wykonywać dodatkowych znaków ucieczki, ale także zachowujesz specjalne wyróżnienie składni dla pod-wyrażeń regularnych!
quezak
jedno zastrzeżenie: musisz upewnić się, że pod-wyrażenia regularne są samodzielne lub umieścić je w nowej grupie nawiasów. Przykład: multilineRegExp([/a|b/, /c|d])wyniki w /a|bc|d/, a miałeś na myśli (a|b)(c|d).
quezak
6

Są tutaj dobre odpowiedzi, ale dla kompletności ktoś powinien wspomnieć o podstawowej funkcji dziedziczenia w łańcuchu prototypów w Javascript . Coś takiego ilustruje ten pomysł:

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

James Donohue
źródło
To najlepsza odpowiedź.
parttimeturtle
6

Dzięki cudownemu światu literałów szablonów możesz teraz pisać duże, wielowierszowe, dobrze skomentowane, a nawet semantycznie zagnieżdżone wyrażenia regularne w 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])
    ))
);

Korzystając z tego, możesz teraz pisać wyrażenia regularne w ten sposób:

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

Wyjścia

/I'm a special regex{3}/

A co z multiline?

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

Wyjścia hel, schludnie!
„A co, jeśli muszę faktycznie przeszukać nową linię?”, A więc użyj \ngłupiego!
Pracuję na moim Firefoksie i Chrome.


Ok, „co powiesz na coś bardziej złożonego?”
Jasne, oto fragment niszczącego parsera JS obiektów, nad którym pracowałem :

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*[,}{]
    ).*)
$`

Wyprowadza /^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

I uruchomić to z małym demo?

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

Pomyślnie wyświetla

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

Zwróć uwagę na pomyślne przechwycenie cytowanego ciągu.
Przetestowałem to na Chrome i Firefox, działa świetnie!

Jeśli jesteś ciekawy, możesz sprawdzić , co robiłem , i jego demonstrację .
Chociaż działa tylko w przeglądarce Chrome, ponieważ Firefox nie obsługuje odwołań wstecznych ani nazwanych grup. Zwróć więc uwagę, że przykład podany w tej odpowiedzi jest w rzeczywistości wersją wykastrowaną i może łatwo zostać oszukany, aby zaakceptować nieprawidłowe ciągi.

Hashbrown
źródło
1
powinieneś pomyśleć o wyeksportowaniu tego jako pakietu NodeJS, jest cudowny
rmobis
1
Chociaż nigdy tego nie zrobiłem, jest tutaj dość dokładny tutorial: zellwk.com/blog/publish-to-npm . Proponuję sprawdzić np, na końcu strony. Nigdy go nie używałem, ale Sindre Sorhus jest magiem w tych rzeczach, więc nie mógłbym tego przegapić.
rmobis
4

W powyższym wyrażeniu regularnym brakuje niektórych czarnych ukośników, które nie działają poprawnie. Więc zredagowałem wyrażenie regularne. Weź pod uwagę to wyrażenie regularne, które działa 99,99% w przypadku weryfikacji adresu e-mail.

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(''));
Anvesh Reddy
źródło
1

Aby uniknąć Array join, możesz również użyć następującej składni:

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
źródło
0

Osobiście wybrałbym mniej skomplikowane wyrażenie regularne:

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

Jasne, jest mniej dokładny niż twój obecny wzorzec, ale co próbujesz osiągnąć? Czy próbujesz wyłapać przypadkowe błędy, które mogą wprowadzić Twoi użytkownicy, czy obawiasz się, że Twoi użytkownicy mogą próbować wprowadzić nieprawidłowe adresy? Jeśli to pierwszy, wybrałbym łatwiejszy wzór. Jeśli to drugie, lepszą opcją może być weryfikacja w odpowiedzi na e-mail wysłany na ten adres.

Jeśli jednak chcesz użyć swojego obecnego wzorca, byłoby (IMO) łatwiejsze do odczytania (i utrzymania!), Budując go z mniejszych wzorców podrzędnych, na przykład:

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 + ")$");
Bart Kiers
źródło
21
Głosowanie w dół - chociaż Twoje komentarze dotyczące zmniejszania złożoności wyrażeń regularnych są prawidłowe, OP w szczególności pyta, jak „podzielić długie wyrażenie regularne na wiele wierszy”. Więc chociaż twoja rada jest ważna, została udzielona z niewłaściwych powodów. np. zmiana logiki biznesowej w celu obejścia języka programowania. Ponadto podany przykład kodu jest dość brzydki.
senny
4
@sleepycal Myślę, że Bart odpowiedział na pytanie. Zobacz ostatnią sekcję jego odpowiedzi. Odpowiedział na pytanie i podał alternatywę.
Nidhin David
0

Możesz po prostu użyć operacji na łańcuchach.

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);
Mubeena
źródło
0

Próbowałem ulepszyć odpowiedź korun poprzez hermetyzację wszystkiego i zaimplementowanie wsparcia dla dzielenia grup przechwytywania i zestawów znaków - dzięki czemu ta metoda jest znacznie bardziej wszechstronna.

Aby użyć tego fragmentu, musisz wywołać funkcję wariadyczną, combineRegexktórej argumentami są obiekty wyrażenia regularnego, które musisz połączyć. Jego implementację można znaleźć na dole.

Grupy przechwytywania nie mogą być podzielone bezpośrednio w ten sposób, ponieważ pozostawiłoby to tylko jeden nawias. Twoja przeglądarka nie zadziała z wyjątkiem.

Zamiast tego po prostu przekazuję zawartość grupy przechwytywania do tablicy. Nawiasy są dodawane automatycznie po combineRegexnapotkaniu tablicy.

Ponadto kwantyfikatory muszą za czymś podążać. Jeśli z jakiegoś powodu wyrażenie regularne musi zostać podzielone przed kwantyfikatorem, należy dodać parę nawiasów. Zostaną one automatycznie usunięte. Chodzi o to, że pusta grupa przechwytywania jest całkiem bezużyteczna i dlatego kwantyfikatory mają do czego się odwoływać. Tej samej metody można użyć w przypadku grup bez przechwytywania ( /(?:abc)/staje się [/()?:abc/]).

Najlepiej wyjaśnić to na prostym przykładzie:

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

stanie się:

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

Jeśli musisz podzielić zestawy znaków, możesz użyć obiektów ( {"":[regex1, regex2, ...]}) zamiast tablic ( [regex1, regex2, ...]). Treść klucza może być dowolna, o ile obiekt zawiera tylko jeden klucz. Zauważ, że zamiast tego ()musisz użyć ]jako fikcyjnego początku, jeśli pierwszy znak może być zinterpretowany jako kwantyfikator. To znaczy /[+?]/staje się{"":[/]+?/]}

Oto fragment i bardziej kompletny przykład:

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
źródło
0

Świetna odpowiedź @ Hashbrown poprowadziła mnie na właściwą ścieżkę. Oto moja wersja, również zainspirowana tym blogiem .

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

Użyj tego w ten sposób:

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*/
`

Aby stworzyć ten RegExpobiekt:

/(\d+)([a-z]{1,3})/i
Nuno Cruces
źródło