我不喜欢正则表达式...
本文将向您介绍一组 3 个简单的扩展方法,
引言
事实上,我确实喜欢正则表达式:它们能很好地完成工作。甚至好到所有开发者都必须使用它们,而且没有办法摆脱它。
不幸的是,每当我需要一个新的正则表达式时,我都会面临同样的问题:我已经忘记了它们该死的语法的几乎所有内容……如果我每天都要写一个,我可能很容易记住它,但事实并非如此,因为我一年 apenas 需要写几个……
我厌倦了反复阅读和学习这些文档,于是我决定实现以下字符串扩展方法……
背景
正则表达式是一种强大而简洁的文本处理方式,用于验证、提取、编辑、替换或删除给定预定义模式(例如:电子邮件地址)的文本部分。
为了正确使用正则表达式,您需要:
- 要分析的文本
- 一个正则表达式引擎
- 一个正则表达式(要在要分析的文本中查找的模式)
正则表达式的语法因您使用的正则表达式引擎而异。在微软的世界里,充当正则表达式引擎的类是 System.Text.RegularExpressions.Regex,其语法在此处描述:http://msdn.microsoft.com/en-us/library/az24scfc.aspx
如果您正在寻找正则表达式语法的入门教程,请阅读这篇优秀的文章:https://codeproject.org.cn/Articles/9099/The-30-Minute-Regex-Tutorial
正则表达式的问题
它们的优点也是它们的缺点:语法(简洁而强大)旨在对正则表达式引擎友好,但对人类来说并不那么友好。
如果不熟悉语法,您可能会花费很长时间来编写一个有效的表达式。
您可能会花费更长的时间来测试该表达式并使其万无一失。确保您的正则表达式匹配您期望的内容是一回事,但确保它只匹配您期望的内容是另一回事。
想法
如果您熟悉 SQL,您会知道 LIKE 运算符。为什么不将该运算符引入 C#?
为什么不为最 **频繁的操作** 提供一个 **简化语法**,您会要求您的正则表达式引擎执行这些操作?
简化语法
……意味着更少的运算符。以下是我非常随意地想出的列表:
- ? = 任意字符
- % = 零个或多个字符
- * = 零个或多个字符,但不能是空格(基本上是一个单词)
- # = 任意单个数字(0-9)
简单表达式的示例:
- GUID 可以表示为:????????-????-????-????-????????????
- 电子邮件地址可以是:*?@?*.?*
- 至于日期:##/##/####
正则表达式的爱好者们已经跳起来了:显然,没有什么能保证最后一个表达式匹配一个有效的日期,他们是对的(该表达式会匹配 99/99/9999)。但 **这种语法绝不** 会取代正则表达式。它远不能提供同等水平的功能,尤其是在验证方面。
频繁操作
您需要正则表达式引擎执行哪些频繁操作?
- 确定要分析的文本是否与给定模式匹配:Like
- 在要分析的文本中查找给定模式的出现:Search
- 从要分析的文本中检索字符串:Extract
这三个操作“Like”、“Search”和“Extract”已作为字符串的扩展方法实现,作为正则表达式引擎的替代方案。
让我们先描述它们的使用方法,然后是代码……
1. 确定一个字符串是否“像”给定的模式
您知道 SQL,那么您就知道我在说什么……
Like 扩展在输入字符串匹配给定模式时简单地返回 true。
以下所有示例都返回 true,表示输入字符串与它们的模式匹配。
示例:一个字符串 **是** 一个 GUID
var result0 = "TA0E02391-A0DF-4772-B39A-C11F7D63C495".Like("????????-????-????-????-????????????");
示例:一个字符串 **以** GUID **结尾**
var result1 = "This is a guid TA0E02391-A0DF-4772-B39A-C11F7D63C495".Like("
%????????-????-????-????-????????????");
示例:一个字符串 **以** GUID **开头**
var result2 = "TA0E02391-A0DF-4772-B39A-C11F7D63C495 is a guid".Like("????????-????-????-????????????%");
示例:一个字符串 **包含** GUID
var result3 = "this string TA0E02391-A0DF-4772-B39A-C11F7D63C495 contains a guid".Like("%????????-????-????-????-????????????%");
示例:一个字符串 **以** GUID **结尾**
var result4 = "TA0E02391-A0DF-4772-B39A-C11F7D63C495".Like("%????????-????-????-????-????????????");
2. 在字符串中“搜索”特定模式
Search 扩展方法检索给定模式在提供的文本中的第一个出现。
示例:在文本中搜索 GUID
var result5 = "this string [TA0E02391-A0DF-4772-B39A-C11F7D63C495] contains a string matching".Search("[????????-????-????-????-????????????]");
Console.WriteLine(result5); // output: [TA0E02391-A0DF-4772-B39A-C11F7D63C495]
3. 根据已知模式从字符串中“提取”值
几乎就像搜索一样,但它不返回匹配模式的整个字符串,而是返回匹配模式组的字符串数组。
示例:在文本中检索 GUID 的组成部分
var result6 = "this string [TA0E02391-A0DF-4772-B39A-C11F7D63C495] contains a string matching".Extract("[????????-????-????-????-????????????]");
// result is an array containing each part of the pattern: {"TA0E02391", "A0DF", "4772", "B39A", "C11F7D63C495"}
示例:在文本中检索电子邮件的组成部分
var result7 = "this string contains an email: toto@domain.com".Extract("*?@?*.?*");
// result is an array containing each part of the pattern: {"toto", "domain", "com"}
这是代码
这里简单的技巧是,这 3 个不同的公共方法依赖于 GetRegex,它将简化表达式转换为有效的 .net 表达式。
public static class StringExt
{
public static bool Like(this string item, string searchPattern)
{
var regex = GetRegex("^" + searchPattern);
return regex.IsMatch(item);
}
public static string Search(this string item, string searchPattern)
{
var match = GetRegex(searchPattern).Match(item);
if (match.Success)
{
return item.Substring(match.Index, match.Length);
}
return null;
}
public static List Extract(this string item, string searchPattern)
{
var result = item.Search(searchPattern);
if (!string.IsNullOrWhiteSpace(result))
{
var splitted = searchPattern.Split(new[] { '?', '%', '*', '#' }, StringSplitOptions.RemoveEmptyEntries);
var temp = result;
var final = new List();
foreach(var x in splitted)
{
var pos = temp.IndexOf(x);
if (pos > 0)
{
final.Add(temp.Substring(0, pos));
temp = temp.Substring(pos);
}
temp = temp.Substring(x.Length);
}
if (temp.Length > 0) final.Add(temp);
return final;
}
return null;
}
// private method which accepts the simplified pattern and transform it into a valid .net regex pattern:
// it escapes standard regex syntax reserved characters
// and transforms the simplified syntax into the native Regex one
static Regex GetRegex(string searchPattern)
{
return new Regex(searchPattern
.Replace("\\", "\\\\")
.Replace(".", "\\.")
.Replace("{", "\\{")
.Replace("}", "\\}")
.Replace("[", "\\[")
.Replace("]", "\\]")
.Replace("+", "\\+")
.Replace("$", "\\$")
.Replace(" ", "\\s")
.Replace("#", "[0-9]")
.Replace("?", ".")
.Replace("*", "\\w*")
.Replace("%", ".*")
, RegexOptions.IgnoreCase);
}
}
结论
如上所述,目的不是取代 Regex,而是为解决我以前需要 Regex 的大约 80% 的情况提供一种非常简单的方法。这种方法使基本任务非常简单,并且使客户端代码非常易于编写,对于不熟悉 Regex 语法专家的人来说也显而易见。