介绍:
事实上我喜欢正则:他们做的很好了。 事实上他们太好了,以至于几乎所有的程序员都必须使用它。
不幸的是每当我需要一个正则的时候,我都会碰到同样的问题:对于那些该死的语法我基本上都忘光了..
。如果我每天都要写一个的话,我可能会很容易的记住他们,可是在一年里我基本上很少写几个..
在厌倦了一次又一次的查看和学习文档之后,我决定通过String的扩展方法来实现他们..
背景:
在处理大文本的验证,提取,编辑,替换或者是删除一个给定模式的文本(例如一个邮箱地址)的时候,正则表达式非常强大和简洁。
为了正确的使用正则表达式,你应该:
一个用于分析的文本。
一个正则表达式解析引擎。
一个正则表达式(文本中用于查找分析的模式)
正则表达式的语法取决于你所使用的正则表达式解析引擎。 在微软的世界里,这个正则表达式引擎类就是
System.Text.RegularExpressions.
它的语法在这里:http://msdn.microsoft.com/en-us/library/az24scfc.aspx
如果你想要一个关于正则表达式语法的介绍,请读一下这篇写的不错的文章:
http://www.codeproject.com/Articles/9099/The-30-Minute-Regex-Tutorial
正则表达式的问题:
他们有他们的优势的缺点:简洁和强大的语法对与正则表达式引擎非常适合,可是并不适合人类阅读。
当你不熟悉他们的语法,你可以花上一整天的时间来写一个正确的表达式,不过可能还要花更长的时间来验证它。让正则表达式满足你的期望是一件事,让它只满足你的期望则是另外一件事。
想法:
如果你熟悉SQL的话,你肯定知道 LIKE 操作符。为什么不把这个操作符带到C#中呢?
为什么没有一个可以处理大部分常见操作和能够被正则表达式引擎执行的简洁语法呢?
一个简化的语法
... 意味着更少的操作符。这里是我自己的一个列表:
? = 任何操作符
% = 0 或多个字符
* = 0 或多个字符,但是没有空格(主要是单词)
# = 任何单个数字 (0-9)
这是这些简单语法的例子:
一个Guid可以被表示成 : ????????-????-????-????-????????????
一个 email address 可能是 : *?@?*.?*
一个日期 : ##/##/####
正则表达式的狂热分子已经跳到桌子上了:很明显最后一个正则表达式并不能确保匹配的是一个合适的日期,当然他们是对的(那个表达式可以匹配99/99/9999). 在涉及验证的时候,它完全不能提供相同级别的功能。
频繁的操作
你需要正则表达式引擎的频繁的操作有哪些呢?
1:判断一个文本是否匹配给定的模式:LIKE
2:根据一个给定的模式来查找文本:Search
3:提取文本中的字符串信息:Extract
做为使用正则表达式引擎的另一个选择,这三个操作符'Like', 'Search' and 'Extract' 已经通过String的扩展方法实现了。
让我们首先来谈谈他们的使用吧...
1. 判断是否一个字符串是否”Like” 给定的模式
如果你懂SQL的话,那么你就懂我说的...
如果一个字符串匹配指定的模式,Like操作符会简单的返回true.
下面所有的例子都返回true,这意味着输入的字符串都匹配他们的模式。
example:一个字符串是一个guid。
var result = "TA0E02391-A0DF-4772-B39A-C11F7D63C495".Like("????????-????-????-????????????");
example:guid结尾的字符串。
var result = "This is a guid TA0E02391-A0DF-4772-B39A-C11F7D63C495".Like(" %????????-????-????-????????????");
example:guid开头的字符串。
var result = "TA0E02391-A0DF-4772-B39A-C11F7D63C495 is a guid".Like("????????-????-????-???????????? %");
example:包含guid的字符串
var result = "this string TA0E02391-A0DF-4772-B39A-C11F7D63C495 contains a guid".Like(" %????????-????-????-????????????%");
2. 在一个字符串中查找给定的模式
Search 扩展方法可以在一个字符串中找到第一个匹配给定模式的子串。
example: 在字符串搜索guid
var result = "this string [TA0E02391-A0DF-4772-B39A-C11F7D63C495] contains a string matching".Search("[????????-????-????-????????????]") Console.WriteLine(result); // output: [TA0E02391-A0DF-4772-B39A-C11F7D63C495]
3. 通过给定的模式‘Extracting’ 字符串中的子串
基本和Like 搜索一样,只是它返回的不是整个的字符串,而是一个匹配模式的子串数组。
example: 在一个字符串中返回guid的构成部分
var result = "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"}
example: 在一个字符串中返回email的构成部分
var result = "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 bool Like(this string item, string searchPattern) { return GetRegex("^" + searchPattern).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<string> 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<string>(); splitted.ForEach(x => { 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,而是提供一个简单的方式来解决我需要使用正则表达式的80%的案例。
这个方式可以让简单的任务变得简单,并且可以让客户端代码更容易编写和让不熟悉的正则表达式语法的人容易理解。