如何在JavaScript中将长正则表达式拆分为多行?


138

我有一个很长的正则表达式,我希望在JavaScript代码中将其分成多行,以根据JSLint规则将每行长度保持80个字符。我认为这对阅读更好。这是模式样本:

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 2012年

2
如今,宽屏显示器不是80个字符吗?
Oleg V. Volkov 2012年

7
@ OlegV.Volkov否。一个人可能在vim(服务器机房中的虚拟终端)中使用拆分窗口。假设每个人都将在与您相同的视口中进行编码是错误的。此外,将行数限制为80个字符会迫使您将代码分解为较小的函数。
synic 2012年

好吧,我肯定会在这里看到您想要这样做的动机-正如Koolilnc所展示的那样,一旦将此正则表达式划分为多行,它便立即成为可读的,自记录代码的完美示例。¬_¬–
Mark Amery

Answers:


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. 表达式文字转换为字符串时,您需要转义所有反斜线,因为在评估字符串文字时会消耗反斜线。(有关更多详细信息,请参见Kayo的评论。)
  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 2014年

43
警告:使用上述答案,长正则表达式文字可能会分成多行。但是,它需要注意,因为您不能简单地复制正则表达式文字(用定义//)并将其作为字符串参数粘贴到RegExp构造函数中。这是因为在评估字符串文字时会消耗反斜杠字符。示例:/Hey\sthere/不能替换为new RegExp("Hey\sthere")。而是应将其替换为new RegExp("Hey\\sthere")注意额外的反斜杠!因此,我宁愿只在长行上保留一个长的正则表达式文字
Kayo

5
一种更清晰的方法是创建包含有意义的子节的命名变量,并将它们作为字符串或数组连接。这样RegExp一来,您就可以以更容易理解的方式构造。
克里斯·克里斯乔

115

扩展@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
正是我要找的东西,超级干净。谢谢!
玛丽安(Marian Zagoruiko)

10
这对于在长正则表达式中添加注释非常方便。但是,它受同一行上带有匹配括号的限制。
内森·沃森

绝对是这个!具有注释每个子正则表达式的功能,非常好。
GaryO

谢谢,它有助于将源代码放入正则表达式功能中
代码

非常聪明。谢谢,这个主意对我有很大帮助。combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))combineRegex(/regex1/, /regex2/, ...)
顺便提一句

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

5

多亏了模板文字的奇妙世界,您现在可以在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

1
尽管我自己从未做过,但是这里有一个非常详尽的教程:zellwk.com/blog/publish-to-npm。我建议在页面末尾检查np。我从未使用过它,但是Sindre Sorhus是拥有这些东西的魔术师,因此我不会将其遗忘。
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

为了避免Array 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
拒绝投票-尽管您对降低正则表达式复杂性的评论是正确的,但OP专门询问如何“将多行正则表达式拆分为多行”。因此,尽管您的建议是有效的,但由于错误的原因而给出了建议。例如,更改业务逻辑以围绕编程语言工作。此外,您提供的代码示例非常难看。
sleepycal 2014年

4
@sleepycal我认为Bart已回答了问题。请参阅他的答案的最后一部分。他回答了这个问题,并给出了替代方案。
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

我试图通过封装所有内容并实现对拆分捕获组和字符集的支持来改善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

@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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.