65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 RegExpBuilder 实现正则表达式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (46投票s)

2013年8月20日

CPOL

6分钟阅读

viewsIcon

52041

本文将介绍一个名为RegExpBuilder的新库,该库旨在将非常难以阅读的正正则表达式转换为易于理解的格式,以便于构建和理解。

 

You can use the RegExpBuilder library to create human-readable Regular Expressions.

您可以使用RegExpBuilder库来创建更易于阅读的正正则表达式。

正则表达式

对于上述陈述,通常会有两种截然不同的反应。第一种反应可能会导致您对着屏幕大喊“谋杀”,而第二种反应则可能会凑近屏幕说:“是的?告诉我更多!”。本文将主要关注那些目前正在努力从房间另一边寻找鼠标的读者。

在处理大量文本并尝试使用不同的模式或表达式从中提取非常具体的数据时,正则表达式可以是非常强大的工具。但是,它们并非世界上最友好的东西,无论是看还是写。

//Example of an incredibly ugly regular expression to match dates in a variety of formats
((0?[13578]|10|12)(-|\/)((0[0-9])|([12])([0-9]?)|(3[01]?))(-|\/)((\d{4})|(\d{2}))|(0?[2469]|11)(-|\/)((0[0-9])|([12])([0-9]?)|(3[0]?))(-|\/)((\d{4}|\d{2})))

本文将介绍一个名为RegExpBuilder的新库,该库由Andrew Jones(thebinarysearchtree)在github上发布,旨在将这些非常难以阅读的正正则表达式转换为人类可读的格式,以便于构建和理解。

值得注意的是,该库并非旨在完全取代传统的正则表达式,但它可以为那些在编写正则表达式方面有困难的人提供一个工具,使其能够更“人性化地”访问这些表达式。

问题

您需要编写一个非常基本的正则表达式来执行某些模式匹配,但您不知道如何编写正则表达式(或者您知道,但它们总是写错)。

使用RegExpBuilder

RegExpBuilder可以针对多种环境,如Dart、Java、JavaScript和Python。在本文中,我们将重点关注JavaScript的使用,因为通过一些交互式示例可以非常容易地进行演示。

开始使用RegExpBuilder非常简单,只需将适当的文件或引用包含到您的应用程序中(取决于您的环境),如下所示:
<!-- Example of directly referencing the RegExpBuilder.js file from github -->
<script type='text/javascript' src='https://raw.github.com/thebinarysearchtree/RegExpBuilder/master/RegExpBuilder.js' />

让我们来看一些示例,比较和对比一些常见的正则表达式与使用RegExpBuilder构建的正则表达式,以了解它们的外观。您也可以参考此处可用的文档,这在回顾这些基本示例时可能会有所帮助。

示例一:处理货币

一个常见的正则表达式可能是验证一个值是否为“货币”。在本例中,我们将“货币”视为美元,它将包括一个明确的美元符号“$”,后面跟着一系列数字,然后是一个点“.”,最后是两位小数。

$123.45 //Valid Example
使用正则表达式,您会得到类似这样的东西
^\$\d+\.\d{2}$
对于不熟悉正则表达式的读者,我们来分解一下
^      //Start of Expression
\$     //An explicit '$' symbol (escaped with a slash)
\d+    //One or more digits (digits denoted by the \d and one or more indicated by the '+')
\.     //An explicit '.' symbol (this must be escaped as '.' matches a variety of characters in Regular Expressions)
\d{2}  //Exactly 2 digits (notice the digit symbol from earlier followed by the braces used to denote quantity)
$      //End of the expression
使用RegExpBuilder构建时,相同的内容将看起来像这样
//Constant collection of digits (this will be used throughout these examples)
var digits = ["0", "1", "2", "3", "4", 
   "5", "6", "7", "8", "9"];

var regex = new RegExpBuilder()
                .startOfLine()           // ^
                .then("$");              // \$
                .some().from(digits)     // \d+
                .then(".")               // \.
                .exactly(2).from(digits) // \d{2}
                .endOfLine()             // $
                .getRegExp();            // (Builds the Regular Expression)
您会立即注意到可读性的差异。RegExpBuilder的美妙之处在于它实际上“易于阅读”,因此也易于编写。现在,让我们使用一个非常简单的Javascript alert来看看RegExpBuilder为我们生成了什么
//Alerts the "generated" Regular Expression
alert(regex);
结果如下
Generated Currency Expression

我们的货币示例($##.##)生成的正则表达式示例

基本上,它们的操作方式与传统的正则表达式完全相同,只是在生成时保持了更高的可读性(对于那些看不懂它们的人来说)。

示例二:处理电话号码(基础

电话号码是基于正则表达式验证的另一个常见用例。虽然它们可能非常复杂,但我们将定义一个非常基础的用于演示

555-555-5555 //A very common US Phone number example
其表达式可能看起来像这样
^\d{3}-\d{3}-\d{4}
并且可以这样解释
^      //Start of Expression
\d{3}  //Exactly three digits (area code)
-      //An explicit '-'
\d{3}  //Exactly three more digits (first component of phone number)
-      //Another hyphen
\d{4}  //Exactly four digits
$      //End of the expression
不难吧?让我们尝试使用RegExpBuilder……
var dashes = new RegExpBuilder()
                 .exactly(3).from(digits).then("-")  // \d{3}-
                 .exactly(3).from(digits).then("-")  // \d{3}-
                 .exactly(4).from(digits)            // \d{4}
                 .getRegExp();
这将呈现出类似这样的内容
GeneratedPhoneBasic

美国电话号码(###-###-####)的生成表达式示例 

这不是很吸引人,对吧?那么,稍微修改一下,允许可选的区号呢,比如
555-5555     //Valid
555-555-5555 //Valid
这将有一个看起来像这样的表达式
^(\d{3}-){0,1}\d{3}-\d{4}$
与之前的示例相比,唯一的改变是我们使用括号对第一个部分进行了分组,并指示该组可以出现0次或1次(可选)。
(\d{3}-){0,1}
您会发现RegExpBuilder允许您创建其他RegExpBuilder对象,并将它们作为组传递,以便在处理复杂的表达式时,通过like()函数轻松地分离所有组件。
//Build our first section (the optional area code part)
var areacode = new RegExpBuilder()
                   .exactly(3).from(digits).then("-");  // \d{3}-

//Build a Regular Expression to validate against using the RegExpBuilder
var regex = new RegExpBuilder()
                .startOfLine()                          // ^
                .min(0).max(1).like(areacode).asGroup() // (\d{3}-){0,1}
                .exactly(3).from(digits).then("-")      // \d{3}-
                .exactly(4).from(digits)                // \d{4}
                .endOfLine()                            // $
                .getRegExp();
它与上述现有正则表达式的功能相同,并生成以下内容
Generated Regular Expression for accepting US Phone Numbers with optional area code (ie ###-###-#### or ###-####)

接受带可选区号的美国电话号码的生成正则表达式(即###-###-####或###-####)

示例二:处理电话号码(高级

如何增加更多灵活性,使其能够接受值之间的句点“.”或空格“ ”,以及可选的区号,如下所示

 555.555.5555 //Acceptable
 555-5555     //Acceptable
 555 5555     //Acceptable
 5-5-5-5-5-5- //Obviously not acceptable
 555.555-5555 //Judges? Nope. Not allowed.
这里要记住的一个重要因素是,我们希望保持一致性,并且不希望不同的符号像上一个示例那样混淆,所以我们将表达式分成三个部分(一个用于处理破折号,一个用于处理空格,一个用于处理句点),我们应该得到类似这样的结果
^(((\d{3}-){0,1}\d{3}-\d{4})|((\d{3}\s){0,1}\d{3}\s\d{4})|((\d{3}\.){0,1}\d{3}\.\d{4}))$
与其逐字逐句地输入一长页的分解,我将总结如下
^                          //Start of Expression
(                          //Wraps all of the expressions
((\d{3}-)?\d{3}-\d{4})     //Takes care of dashes-format with optional area code (notice the ? behind the first "group")
|                          //An explicit OR
((\d{3}\s)?\d{3}\s\d{4})   //The white-space group (\s denotes white space)
|                          //Another OR
((\d{3}\.)?\d{3}\.\d{4})   //The period notation
)                          //Closes the outer "wrapper"
$                          //End of expression
现在我们将进入一些“真正的”复杂性,但至少它将是人类可读的
//Handle prefixes (optional area codes for each format)
var areacode_dash = new RegExpBuilder().exactly(3).from(digits).then("-");  // \d{3}-
var areacode_space = new RegExpBuilder().exactly(3).from(digits).then(" "); // \d{3} 
var areacode_dot = new RegExpBuilder().exactly(3).from(digits).then(".");   // \d{3}\.

//Build each of the individual components (dashes, spaces and dots)
var dashes = new RegExpBuilder()
                 .min(0).max(1).like(areacode_dash).asGroup()  // (\d{3}-){0,1}
                 .exactly(3).from(digits).then("-")            // \d{3}-
                 .exactly(4).from(digits);                     // \d{4}

var spaces = new RegExpBuilder()
                 .min(0).max(1).like(areacode_space).asGroup()  // (\d{3}-){0,1}
                 .exactly(3).from(digits).then(" ")             // \d{3} 
                 .exactly(4).from(digits);                      // \d{4}

var dots = new RegExpBuilder()
               .min(0).max(1).like(areacode_dot).asGroup()  // (\d{3}\.){0,1}
               .exactly(3).from(digits).then(".")           // \d{3}\.
               .exactly(4).from(digits);                    // \d{4}

//Handle build final expression
var regex = new RegExpBuilder()
                .startOfLine()             // ^
                .eitherLike(dashes)        // ((\d{3}-){0,1}\d{3}-\d{4})
                .orLike(spaces).asGroup()  // |((\d{3}\s){0,1}\d{3}\s\d{4})
                .orLike(dots).asGroup()    // |((\d{3}\.){0,1}\d{3}\.\d{4}))
                .endOfLine()               // $
                .getRegExp();
让我们测试一下,看看它会生成什么
RegExpBuilder's generated expression for handling three different formats of US Phone Numbers with optional Area Codes.

RegExpBuilder为处理三种不同格式的带可选区号的美国电话号码生成的表达式。

我的天。尽管这可能是一个非常庞大的表达式,但它实际上与前面提供的普通表达式一样有效,并且非常易读(尽管很庞大)。

摘要

我认为从这个库中 takeaway 最重要的一点是,它并不适合所有人。如果您熟悉正则表达式的工作方式,它很可能会花费您不必要的时间。它面向那些不喜欢使用传统正则表达式并且希望有一种以非常通用和人类可读的方式编写和使用它们的方法的人。对于那些严重依赖表达式的大型项目,这将是一个很好的维护工具,这样开发人员就不必去想“这个乱七八糟的东西到底有什么用?”。

显然,由于其本质,这些表达式并非经过优化,因为该库显然侧重于提高可读性而非性能。我相信有很多人愿意在此基础上进行扩展,并可能将其扩展得更优化、更灵活,或者您喜欢的任何其他功能。如果您喜欢这篇文章,或者它激起了您的兴趣,请随时在github上查看该项目。我还创建了一个示例项目,其中包含本文中的所有示例,以便您可以随意修改。

© . All rights reserved.