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

我不喜欢正则表达式...

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (64投票s)

2012 年 4 月 19 日

CPOL

4分钟阅读

viewsIcon

157105

downloadIcon

305

本文将向您介绍一组 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)。但 **这种语法绝不** 会取代正则表达式。它远不能提供同等水平的功能,尤其是在验证方面。  

频繁操作

您需要正则表达式引擎执行哪些频繁操作?

  1. 确定要分析的文本是否与给定模式匹配:Like 
  2. 在要分析的文本中查找给定模式的出现:Search 
  3. 从要分析的文本中检索字符串: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 语法专家的人来说也显而易见。  

© . All rights reserved.