正则表达式的替代(流畅?)界面设计
-
21-09-2019 - |
题
我刚刚看到了一个巨大的 Java 正则表达式,这让我对正则表达式的一般可维护性进行了一些思考。我相信大多数人——除了一些糟糕的 Perl 贩子——都会同意正则表达式很难维护。
我正在考虑如何解决这种情况。到目前为止,我最有希望的想法是使用 流畅的界面. 。举个例子,而不是:
Pattern pattern = Pattern.compile("a*|b{2,5}");
可以写这样的东西
import static util.PatternBuilder.*
Pattern pattern = string("a").anyTimes().or().string("b").times(2,5).compile();
Pattern alternative =
or(
string("a").anyTimes(),
string("b").times(2,5)
)
.compile();
在这个非常简短的示例中,创建正则表达式的常见方法对于任何平庸的才华横溢的开发人员来说仍然非常容易阅读。然而,想想那些充满两行或更多行、每行 80 个字符的怪异表达式。当然,(详细的)流畅的界面需要几行而不是两行,但我确信它会更具可读性(因此可维护)。
现在我的问题是:
您知道正则表达式有类似的方法吗?
您是否同意这种方法比使用简单字符串更好?
您将如何设计 API?
您会在您的项目中使用如此简洁的实用程序吗?
您认为实施起来会很有趣吗?;)
编辑:想象一下,可能存在比我们在正则表达式中都没有的简单构造更高级别的方法,例如
// matches aaaab@example.com - think of it as reusable expressions
Pattern p = string{"a").anyTimes().string("b@").domain().compile();
编辑 - 评论简短摘要:
有趣的是,大多数人认为正则表达式会继续存在——尽管需要工具来阅读它们,也需要聪明的人想出使它们可维护的方法。虽然我不确定流畅的界面是最好的方法,但我确信一些聪明的工程师 - 我们?;) - 应该花一些时间让正则表达式成为过去 - 它们已经陪伴我们 50 年了,知道这一点就足够了,你不觉得吗?
开放赏金
赏金将奖励给正则表达式新方法的最佳创意(无需代码)。
编辑-一个很好的例子:
这就是我正在谈论的那种模式 - 对第一个能够翻译它的人表示额外的荣誉 - RegexBuddies 允许(它来自 Apache 项目顺便说一句) 额外荣誉授予 奇伊 和 梅兹:它是一种符合 RFC 的电子邮件地址验证模式 - 尽管它 RFC822 (看 ex-parrot.com), 不是 5322 - 不过不确定是否有区别 - 如果有的话,我将奖励下一个适合 5322 的补丁;)
private static final String pattern = "(?:(?:\\r\\n)?[ \\t])*(?:(?:(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t]"
+ ")+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:"
+ "\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:("
+ "?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ "
+ "\\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\0"
+ "31]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\"
+ "](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+"
+ "(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:"
+ "(?:\\r\\n)?[ \\t])*))*|(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z"
+ "|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)"
+ "?[ \\t])*)*\\<(?:(?:\\r\\n)?[ \\t])*(?:@(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\"
+ "r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?["
+ " \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)"
+ "?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t]"
+ ")*))*(?:,@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?["
+ " \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*"
+ ")(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t]"
+ ")+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)"
+ "*:(?:(?:\\r\\n)?[ \\t])*)?(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+"
+ "|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r"
+ "\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:"
+ "\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t"
+ "]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031"
+ "]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\]("
+ "?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?"
+ ":(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?"
+ ":\\r\\n)?[ \\t])*))*\\>(?:(?:\\r\\n)?[ \\t])*)|(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?"
+ ":(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?"
+ "[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*:(?:(?:\\r\\n)?[ \\t])*(?:(?:(?:[^()<>@,;:\\\".\\[\\] "
+ "\\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|"
+ "\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>"
+ "@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\""
+ "(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t]"
+ ")*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\"
+ "\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?"
+ ":[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\["
+ "\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*|(?:[^()<>@,;:\\\".\\[\\] \\000-"
+ "\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|("
+ "?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*\\<(?:(?:\\r\\n)?[ \\t])*(?:@(?:[^()<>@,;"
+ ":\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[(["
+ "^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\""
+ ".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\"
+ "]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*(?:,@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\"
+ "[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\"
+ "r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] "
+ "\\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]"
+ "|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)*:(?:(?:\\r\\n)?[ \\t])*)?(?:[^()<>@,;:\\\".\\[\\] \\0"
+ "00-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\"
+ ".|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,"
+ ";:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?"
+ ":[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*"
+ "(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\"."
+ "\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:["
+ "^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]"
+ "]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*\\>(?:(?:\\r\\n)?[ \\t])*)(?:,\\s*("
+ "?:(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\"
+ "\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:("
+ "?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=["
+ "\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t"
+ "])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t"
+ "])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?"
+ ":\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|"
+ "\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*|(?:"
+ "[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\"
+ "]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*\\<(?:(?:\\r\\n)"
+ "?[ \\t])*(?:@(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\""
+ "()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)"
+ "?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>"
+ "@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*(?:,@(?:(?:\\r\\n)?["
+ " \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,"
+ ";:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t]"
+ ")*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\"
+ "\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)*:(?:(?:\\r\\n)?[ \\t])*)?"
+ "(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\"."
+ "\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:"
+ "\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\["
+ "\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])"
+ "*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])"
+ "+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\"
+ ".(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z"
+ "|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*\\>(?:("
+ "?:\\r\\n)?[ \\t])*))*)?;\\s*)";
解决方案
您将如何设计 API?
我会从 Hibernate criteria API 借用一个页面。而不是使用:
string("a").anyTimes().or().string("b").times(2,5).compile()
使用如下模式:
Pattern.or(Pattern.anyTimes("a"), Pattern.times("b", 2, 5)).compile()
这种表示法更简洁一些,我觉得更容易理解模式的层次结构/结构。每个方法都可以接受字符串或模式片段作为第一个参数。
您知道正则表达式有类似的方法吗?
不是随手的,不是。
您是否同意这种方法比使用简单字符串更好?
是的,绝对...如果您使用正则表达式来处理任何非常复杂的事情。对于非常短且简单的情况,字符串更方便。
您会在您的项目中使用如此简洁的实用程序吗?
可能,随着它被证明/稳定......将其整合到像 Apache Commons 这样的更大的实用项目中可能是一个优势。
您认为实施起来会很有趣吗?;)
+1
其他提示
马丁·福勒建议 另一种策略. 。即获取正则表达式中有意义的部分并用变量替换它们。他使用以下示例:
"^score\s+(\d+)\s+for\s+(\d+)\s+nights?\s+at\s+(.*)"
变成
String scoreKeyword = "^score\s+";
String numberOfPoints = "(\d+)";
String forKeyword = "\s+for\s+";
String numberOfNights = "(\d+)";
String nightsAtKeyword = "\s+nights?\s+at\s+";
String hotelName = "(.*)";
String pattern = scoreKeyword + numberOfPoints +
forKeyword + numberOfNights + nightsAtKeyword + hotelName;
这更具可读性和可维护性。
上一个示例的目标是从字符串列表中解析 numberOfPoints、numberOfNights 和 hotelName,如下所示:
score 400 for 2 nights at Minas Tirith Airport
对于没有任何正则表达式经验的人来说可能会稍微容易一些,但是在有人学习了您的系统之后,他仍然无法在其他地方读取正常的正则表达式。
另外,我认为您的版本对于正则表达式专家来说更难阅读。
我建议像这样注释正则表达式:
Pattern pattern = Pattern.compile(
"a* # Find 0 or more a \n" +
"| # ... or ... \n" +
"b{2,5} # Find between 2 and 5 b \n",
Pattern.COMMENTS);
这对于任何经验水平的人来说都很容易阅读,对于没有经验的人来说,它同时教授正则表达式。此外,注释可以根据具体情况进行定制,以解释正则表达式背后的业务规则,而不仅仅是结构。
另外,还有一个工具,比如 正则表达式好友 可以将你的正则表达式翻译成:
Match either the regular expression below (attempting the next alternative only if this one fails) «a*» Match the character "a" literally «a*» Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*» Or match regular expression number 2 below (the entire match attempt fails if this one fails to match) «b{2,5}» Match the character "b" literally «b{2,5}» Between 2 and 5 times, as many times as possible, giving back as needed (greedy) «{2,5}»
这是一个有趣的概念,但它的提出存在一些缺陷。
但首先要回答所提出的关键问题:
现在我的问题是:
1.您知道正则表达式有类似的方法吗?
没有一个没有提到过。以及我通过阅读问题和答案发现的内容。
2.您是否同意这种方法比使用简单字符串更好?
如果它像宣传的那样工作,那么肯定会让调试变得更加容易。
3.您将如何设计 API?
请参阅下一节中我的笔记。我以您的示例和链接的 .NET 库作为起点。
4.您会在您的项目中使用如此简洁的实用程序吗?
尚未决定。我使用当前版本的神秘正则表达式没有任何问题。我需要一个工具来将现有的正则表达式转换为流畅的语言版本。
5.您认为实施起来会很有趣吗?;)
与编写实际代码相比,我更喜欢研究更高层次的方法。这解释了这个答案的文字墙。
以下是我注意到的几个问题以及我处理这些问题的方法。
结构不清晰。
您的示例似乎通过连接字符串来创建正则表达式。这不是很稳健。我认为这些方法应该添加到 String 和 Patern/Regex 对象中,因为这将使实现和代码更清晰。除此之外,它类似于正则表达式的经典定义方式。
仅仅因为我看不到它以任何其他方式工作,我对提议方案的其余注释将假设所有方法都作用于并返回 Pattern 对象。
编辑: 我似乎自始至终都使用了以下约定。所以我已经澄清了它们并将它们移到了这里。
实例方法:模式增强。例如:捕捉、重复、环视断言。
运营商:操作顺序。交替、串联
常数:字符类、边界(代替 \w、$、\b 等)
如何处理捕获/聚类?
捕获是正则表达式的重要组成部分。
我看到每个 Pattern 对象在内部存储为一个集群。(?:模式)用 Perl 术语来说。允许图案标记轻松混合和混合,而不会干扰其他部分。
我希望捕获作为模式上的实例方法来完成。获取一个变量来存储匹配的字符串。
pattern.capture(variable)
将模式存储在变量中。如果捕获是要多次匹配的表达式的一部分,则变量应包含模式所有匹配的字符串数组。
流利的语言可能非常含糊。
流畅的语言不太适合正则表达式的递归性质。因此需要考虑操作顺序。仅将方法链接在一起并不允许使用非常复杂的正则表达式。这种工具正是在这种情况下有用的。
做
Pattern pattern = string("a").anyTimes().or().string("b").times(2,5).compile();
生产 /a*|b{2,5}/
或者 /(a*|b){2,5}/
?
这样的方案将如何处理嵌套交替?例如: /a*|b(c|d)|e/
我看到了处理正则表达式中的交替的三种方法
- 作为运营商:
pattern1 or pattern2 => pattern # /pattern1|pattern2/
- 作为类方法:
Pattern.or( pattern1, pattern2[, pattern3]*) => pattern # /pattern1|patern2|patern3|...|/
- 作为实例方法:
pattern1.or(pattern2) => pattern # /pattern1|patern2/
我会以同样的方式处理串联。
- 作为运营商:
pattern1 + pattern2 => pattern # /pattern1pattern2/
- 作为类方法:
Pattern.concatenate( pattern1, pattern2[, pattern3]*) => pattern # /pattern1patern2patern3.../
- 作为实例方法:
pattern1.then(pattern2) => pattern # /pattern1patern2/
如何扩展常用模式
所提出的方案使用 .domain()
这似乎是一个常见的正则表达式。将用户定义的模式视为方法并不容易添加新模式。在像 Java 这样的语言中,库的用户必须重写该类才能为常用模式添加方法。
我的建议是解决这个问题,将每件作品都视为一个对象。可以为每个常用的正则表达式创建一个模式对象,例如匹配域。鉴于我之前关于捕获的想法,确保捕获适用于包含捕获部分的同一常见模式的多个副本并不难。
还应该有与各种字符类匹配的模式常量。
零宽度环视断言
扩展我的想法,即所有片段都应该隐式聚集。使用实例方法环顾断言应该不会太难。
pattern.zeroWidthLookBehind()
会产生 (?<patten)
.
仍然需要考虑的事情。
- 反向引用:希望前面讨论的命名捕获不会太难
- 实际如何实施。我没有过多考虑内部结构。这才是真正的魔法发生的地方。
- 翻译:确实应该有一个工具可以在经典正则表达式(例如 Perl 方言)和新方案之间进行转换。新方案的翻译可能是一揽子计划的一部分
将它们放在一起,我提出的与电子邮件地址匹配的模式版本:
Pattern domain_label = LETTER_CHARACTER + (LETTER_CHARACTER or "-" or DIGIT_CHARACTER).anyTimes()
Pattern domain = domain_label + ("." + domain_label).anyTimes()
Pattern pattern = (LETTER_CHARACTER + ALPHANUMERIC_CHARACTER + "@" + domain).compile
事后看来,我的方案很大程度上借鉴了 Martin Fowler 的使用方法。尽管我并不打算让事情发展成这样,但这确实使使用这样的系统更易于维护。它还解决了福勒方法(捕获顺序)的一两个问题。
我自己的尝试可以在 GitHub. 。尽管我认为它不值得用于简单的表达式,但除了可读性的改进之外,它确实提供了一些优点:
- 它负责括号匹配
- 它处理所有“特殊”字符的转义,这些字符可能很快导致反斜杠地狱
几个简单的例子:
// Matches a single digit
RegExBuilder.build(anyDigit()); // "[0-9]"
// Matches exactly 2 digits
RegExBuilder.build(exactly(2).of(anyDigit())); // "[0-9]{2}"
// Matches between 2 and 4 letters
RegExBuilder.build(between(2,4).of(anyLetter())); // "[a-zA-Z]{2,4}"
还有一个更复杂的(或多或少验证电子邮件地址):
final Token ALPHA_NUM = anyOneOf(range('A','Z'), range('a','z'), range('0','9'));
final Token ALPHA_NUM_HYPEN_UNDERSCORE = anyOneOf(characters('_','-'), range('A','Z'), range('a','z'), range('0','9'));
String regexText = RegExBuilder.build(
// Before the '@' symbol we can have letters, numbers, underscores and hyphens anywhere
oneOrMore().of(
ALPHA_NUM_HYPEN_UNDERSCORE
),
zeroOrMore().of(
text("."), // Periods are also allowed in the name, but not as the initial character
oneOrMore().of(
ALPHA_NUM_HYPEN_UNDERSCORE
)
),
text("@"),
// Everything else is the domain name - only letters, numbers and periods here
oneOrMore().of(
ALPHA_NUM
),
zeroOrMore().of(
text("."), // Periods must not be the first character in the domain
oneOrMore().of(
ALPHA_NUM
)
),
text("."), // At least one period is required
atLeast(2).of( // Period must be followed by at least 2 letters (this is the TLD)
anyLetter()
)
);
有一个 流畅的正则表达式库 对于.NET。
简短回答:我已经从 linting 和编译的角度看到了它,我认为这是需要考虑的事情。
长答案:我工作的公司为企业内容过滤应用程序提供基于硬件的正则表达式引擎。考虑在网络路由器中以 20GB/秒的速度运行防病毒或防火墙应用程序,而不是占用宝贵的服务器或处理器周期。大多数防病毒、反垃圾邮件或防火墙应用程序的核心都是一堆正则表达式。
无论如何,正则表达式的编写方式对扫描性能有巨大影响。您可以用多种不同的方式编写正则表达式来完成同一件事,其中一些方式的性能会快得多。我们为客户编写了编译器和 linter,帮助他们维护和调整表达式。
回到OP的问题,我不会定义一个全新的语法,而是编写一个linter(抱歉,我们的语法是专有的),将正则表达式剪切并粘贴到其中,这将分解旧的正则表达式并输出“流利的英语”,以便某人更好地理解。我还会添加相对性能检查和常见修改建议。
对我来说,简短的答案是,一旦您遇到正则表达式(或执行相同操作的其他模式匹配),它们的长度足以导致问题......您可能应该首先考虑它们是否是适合这项工作的工具。
老实说,任何流畅的界面似乎都比标准正则表达式更难阅读。对于非常短的表达,流利的版本是冗长的,但不会太长;它是可读的。但那么长的东西的正则表达式也是如此。
对于中等大小的正则表达式,流畅的界面变得笨拙;足够长,即使不是不可能,也很难阅读。
对于很长的正则表达式(即电子邮件地址),正则表达式实际上很难(如果不是不可能)阅读,流畅的版本在 10 页前就变得无法阅读。
您知道正则表达式有类似的方法吗?
不,除了之前的答案
您是否同意这种方法比使用简单字符串更好?
有点 - 我认为我们可以使用更具描述性的标记,而不是用单个字符来表示结构,但我怀疑这会让复杂的逻辑变得更清晰。
您将如何设计 API?
将每个构造转换为方法名称,并允许嵌套函数调用,以便很容易获取字符串并将方法名称替换为其中。
我认为大部分价值在于定义一个强大的实用函数库,例如匹配“电子邮件”、“电话号码”、“不包含 X 的行”等。可以定制。
您会在您的项目中使用如此简洁的实用程序吗?
也许 - 但可能只适用于较长的情况,在这种情况下,调试函数调用比调试字符串编辑更容易,或者有一个很好的实用程序函数可供使用。
您认为实施起来会很有趣吗?;)
当然!
回答问题的最后一部分(感谢)
private static final String pattern = "(?:(?:\\r\\n)?[ \\t])*(?:(?:(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t]"
+ ")+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:"
+ "\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:("
+ "?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ "
+ "\\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\0"
+ "31]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\"
+ "](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+"
+ "(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:"
+ "(?:\\r\\n)?[ \\t])*))*|(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z"
+ "|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)"
+ "?[ \\t])*)*\\<(?:(?:\\r\\n)?[ \\t])*(?:@(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\"
+ "r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?["
+ " \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)"
+ "?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t]"
+ ")*))*(?:,@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?["
+ " \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*"
+ ")(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t]"
+ ")+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)"
+ "*:(?:(?:\\r\\n)?[ \\t])*)?(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+"
+ "|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r"
+ "\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:"
+ "\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t"
+ "]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031"
+ "]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\]("
+ "?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?"
+ ":(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?"
+ ":\\r\\n)?[ \\t])*))*\\>(?:(?:\\r\\n)?[ \\t])*)|(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?"
+ ":(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?"
+ "[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*:(?:(?:\\r\\n)?[ \\t])*(?:(?:(?:[^()<>@,;:\\\".\\[\\] "
+ "\\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|"
+ "\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>"
+ "@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\""
+ "(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t]"
+ ")*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\"
+ "\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?"
+ ":[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\["
+ "\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*|(?:[^()<>@,;:\\\".\\[\\] \\000-"
+ "\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|("
+ "?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*\\<(?:(?:\\r\\n)?[ \\t])*(?:@(?:[^()<>@,;"
+ ":\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[(["
+ "^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\""
+ ".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\"
+ "]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*(?:,@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\"
+ "[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\"
+ "r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] "
+ "\\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]"
+ "|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)*:(?:(?:\\r\\n)?[ \\t])*)?(?:[^()<>@,;:\\\".\\[\\] \\0"
+ "00-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\"
+ ".|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,"
+ ";:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\"(?"
+ ":[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*"
+ "(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\"."
+ "\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:["
+ "^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]"
+ "]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*\\>(?:(?:\\r\\n)?[ \\t])*)(?:,\\s*("
+ "?:(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\"
+ "\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:("
+ "?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=["
+ "\\[\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t"
+ "])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t"
+ "])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?"
+ ":\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|"
+ "\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*|(?:"
+ "[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\"
+ "]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*\\<(?:(?:\\r\\n)"
+ "?[ \\t])*(?:@(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\""
+ "()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)"
+ "?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>"
+ "@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*(?:,@(?:(?:\\r\\n)?["
+ " \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,"
+ ";:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t]"
+ ")*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\"
+ "\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)*:(?:(?:\\r\\n)?[ \\t])*)?"
+ "(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\"."
+ "\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:"
+ "\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\["
+ "\"()<>@,;:\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])"
+ "*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])"
+ "+|\\Z|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\"
+ ".(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z"
+ "|(?=[\\[\"()<>@,;:\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*\\>(?:("
+ "?:\\r\\n)?[ \\t])*))*)?;\\s*)";
匹配符合 RFC 标准的电子邮件地址:D
正则表达式是有限状态机的描述。经典的文本表示不一定是坏事。它结构紧凑,相对明确,并且得到了很好的采用。
状态转换图可能是更好的表示,但这可能很难在源代码中使用。
一种可能性是从各种容器和组合器对象构建它。
大致如下(将其从伪代码转换为选择的语言留给渴望的人作为练习):
domainlabel = oneormore(characterclass("a-zA-Z0-9-")) separator = literal(".") domain = sequence(oneormore(sequence(domainlabel, separator)), domainlabel) localpart = oneormore(characterclassnot("@")) emailaddress = sequence(localpart, literal("@"), domain)
请注意,上面的内容会错误地将任意多个电子邮件地址分类为有效地址,这些地址不符合它们需要遵循的语法,因为该语法需要的不仅仅是简单的 FSM 来进行完整解析。不过,我不相信它会将有效地址错误分类为无效地址。
它应该对应于 [^@]+@([a-zA-Z0-9-]+.)+.([a-zA-Z0-9-]+)
4.您会在您的项目中使用如此简洁的实用程序吗?
我很可能不会。我认为这是一个使用正确的工具来完成工作的例子。这里有一些很好的答案,例如: 1579202 来自杰里米·斯坦。我最近在最近的一个项目中“获得”了正则表达式,发现它们非常有用,而且如果您知道语法,经过正确的注释,它们是可以理解的。
我认为“了解语法”部分是人们对正则表达式失去兴趣的原因,对于那些不理解正则表达式的人来说,它们看起来很神秘,但它们是应用计算机科学中的强大工具(例如,正则表达式)。以编写软件为生)我觉得聪明的专业人士应该并且应该能够学会正确地使用它们并使用它们。
正如他们所说的“强大的责任是巨大的责任。”我已经看到人们在所有地方都使用正则表达式,但是用花时间彻底学习语法的人明智地使用了他们,他们非常有帮助。对我来说,添加另一层会在某种程度上违背他们的目的,或者至少会剥夺他们的力量。
这只是我自己的观点,我可以理解那些想要这样的框架的人来自哪里,或者那些会避免正则表达式的人,但我很难从那些没有花时间的人那里听到“正则表达式很糟糕”了解它们并做出明智的决定。
为了让每个人都满意(正则表达式大师和流体接口支持者),请确保流体接口可以输出适当的原始正则表达式模式,并且还使用工厂方法获取常规正则表达式并为其生成流体代码。
我们来比较一下:我经常使用 (N)Hibernate ICriteria 查询,这可以被认为是 流利 映射到 SQL。我曾经(现在仍然)对它们充满热情,但它们是否使 SQL 查询更清晰?不,恰恰相反,但另一个好处却出现了:以编程方式构建语句、对它们进行子类化以及创建您自己的抽象等变得更加容易。
我的意思是,如果做得好,为给定的语言使用新的界面是值得的,但不要把它看得太高。在许多情况下,它不会变得更容易阅读(嵌套减法字符类、后向捕获、if 分支等一些难以流畅组合的高级概念)。但在很多情况下,更大灵活性的好处超过了语法复杂性所增加的开销。
要添加到可能的替代方法列表中并使其脱离仅 Java 的上下文,请考虑 LINQ 语法。它可能是这样的(有点做作)(from
, where
和 select
是 LINQ 中的关键字):
// for ^str(aa|bb){3}
from part in mystring
where part startswith "str"
and part hasgroups "aa" or "bb" as first /* "aa" or "bb" in group 'first' */
and part repeats first 3 /* repeat group 'first' 3 times */
select part + "extra" /* can contain complete statement block */
我知道,这只是一个粗略的想法。LINQ的好处是它由编译器检查,是一种语言中的语言。默认情况下,LINQ 还可以表示为流畅的链式语法,这使得它(如果设计良好)可以与其他 OO 语言兼容。
我说去做吧,我确信实施起来很有趣。
我建议使用查询模型(类似于 jQuery、django ORM),其中每个函数返回一个查询对象,这样您就可以将它们链接在一起。
any("a").some("b").one("@").some(chars).one(".").some(chars) //a*b+@\w+\.\w+
在哪里 chars
已预定义以适合任何字符。
or
可以通过使用选择来实现:
any("a").choice("x", "z") // a(x|z)
每个函数的参数可以是字符串或另一个查询。例如, chars
上面提到的变量可以定义为一个查询:
//this one is ascii only
chars = raw("a-zA-Z0-9")
因此,如果感觉使用流畅的查询系统很麻烦,您可以使用一个“原始”函数来接受正则表达式字符串作为输入。
实际上,这些函数只能返回原始正则表达式,链接它们只是连接这些原始正则表达式字符串。
any("a") ---> "a*"
raw("b+") ----> "b+"
one(".") ---> "\."
choice("a", "b") ----> (a|b)
我不确定用流畅的 API 替换 regexp 会带来什么好处。
请注意,我不是正则表达式向导(几乎每次需要创建正则表达式时我都必须重新阅读文档)。
流畅的 API 会使任何中等复杂度的正则表达式(假设大约 50 个字符)比所需的更复杂,并且最终不易于阅读,尽管它可能会改善 创建 借助代码补全,我们可以在 IDE 中使用正则表达式。但代码维护通常比代码开发成本更高。
事实上,我什至不确定是否有可能有一个足够智能的 API 在创建新的正则表达式时真正为开发人员提供足够的指导,而不是谈论模棱两可的情况,正如之前的答案中提到的那样。
您提到了 RFC 的正则表达式示例。我 99% 确信(仍然有 1% 的希望;-))任何 API 都不会使该示例变得更简单,但相反,这只会使它更难以阅读!这是一个典型的例子,无论如何你都不想使用正则表达式!
即使对于正则表达式的创建,由于模式不明确的问题,使用流畅的 API 很可能第一次就无法得到正确的表达式,而必须更改多次,直到获得真正想要的内容。
毫无疑问,我确实喜欢流畅的界面;我开发了一些使用它们的库,并且我使用了几个基于它们的第三方库(例如Java 测试的 FEST)。但我不认为它们可以成为解决任何问题的金锤子。
如果我们只考虑 Java,我认为正则表达式的主要问题是 必要的反斜杠转义 Java 中的字符串常量。这使得在 Java 中创建和理解正则表达式变得异常困难。因此,对我来说,增强 Java 正则表达式的第一步是改变语言, 格罗维, ,其中字符串常量不需要转义反斜杠。
到目前为止,这是我改进 Java 中正则表达式的唯一建议。