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

使用一种“元变量”创建正则表达式对象——更快、更简单

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.83/5 (5投票s)

2007年1月10日

CPOL

3分钟阅读

viewsIcon

33093

downloadIcon

72

本文介绍了一个 VarRegex 类,允许您重用正则表达式的一部分。

引言

正则表达式(Perl 兼容的正则表达式)很棒,毫无疑问(请参考这篇文章了解教程)。但小问题是,每个正则表达式的模式都应该用单个string来表示。

例如,假设我们想为电话号码指定一个模式,规则如下:

  • 数字以一个或多个为一组
  • 空格和减号用作分隔符
  • 至少应该存在一组数字

合适的模式会是什么样子? 像这样:

// the @ sign is used in C# to prevent parsing \ as escape sequence
@"(\d+[\s\-])*\d+"

这意味着我们有一组数字 (\d+) 后面跟着一个分隔符,可以是减号或空格 ([\s\-]),这样的组可以出现任意(可能为0)次,但至少应该存在一组数字 (最终的\d+)。好吧,虽然不太难,但同时也不是很好。

假设,在某个时刻,客户说号码可能包括大写字母(例如 1-800-GO-TO-THE-HELL-NOW)。我们必须两次更改数字组的规范。
如果我们有一些正则表达式,例如,用指数格式表示的实数呢? 像这样……

"[1-9][\d]*[.,][\d]*[1-9][Ee][+\-](0|[1-9][\d]*)"

…… 仅适用于一种(完整)类型的记录,例如 123.456E+120。但我们可以省略整数或小数部分。我们的正则表达式变得非常复杂

"([1-9][\d]*[.,][\d]*[1-9][Ee](0|([+\-]?[1-9][\d]*))
|
([.,][\d]*[1-9][Ee](0|([+\-]?[1-9][\d]*))
|
([1-9][\d]*[.,][Ee](0|([+\-]?[1-9][\d]*))"

嘶,真的吗?

一个梦想

长期以来,我一直有一个梦想 (:-)。一个编写类似这样的东西的梦想

    SIGNIFICANT_DIGIT = @"[1-9]";
    DIGIT = @"[0-9]";
    // ` quote is the rare special character 
    // not having its own meaning in regex syntax
    INT_PART = @"`SIGNIFICANT_DIGIT` `DIGIT`*";
    FLOAT_PART = @"`DIGIT`* `SIGNIFICANT_DIGIT`";
    EXP_PART = @"[Ee](0|[+\-]?`INT_PART`)";
    FULL_EXP = @"`INT_PART` [.,] `FLOAT_PART` `EXP_PART`";
    NO_INT_EXP = @"[.,] `FLOAT_PART` `EXP_PART`";
    NO_FLOAT_EXP = @"`INT_PART` [.,] `EXP_PART`";

    // and finally
    PATTERN = @"`FULL_EXP` | `NO_INT_EXP` | `NO_FLOAT_EXP`";

好吧,更多的代码行,但是

  1. 每组符号只定义一次,然后重用,在模式的不同部分没有重复的组。
  2. 每一行都短得多,并且包含命名的字面量,这使得表达式更容易理解。

本文描述了一个类,该类创建用于在 C# 程序中使用类似的语法。它处理此类表达式并返回使用扩展模式创建的 Regex 对象。

想法

好的,这个想法尽可能简单。我们创建一个允许添加“变量”的类。每个变量可以是一个正则表达式表达式或类似正则表达式的表达式,并引用先前添加的变量。然后,模式以相同的形式设置。之后,我们收到现成的Regex对象并根据需要使用它。

我们使用`引号来标记变量。如果我们想使用引号本身(也许仍然有人需要它 :),我们可以写 "\`"。

实现细节

创建了类VarRegex。它有一个嵌套的可枚举类VariablesCollection,围绕着一个Dictionary<String, String>构建。这个类允许使用索引器属性添加和修改变量,检索它们的CountClear变量列表和枚举它们的值。主要的VariablesCollection方法名为Expand。它接收一个要“扩展”的string,查找变量名出现的次数,并将每个变量的引用替换为其扩展值。

该方法以以下方式实现

public String Expand(String pattern)
{
    if (pattern == "")
    return "";

    string p = pattern;
    p = p.Replace("\\`", ""+(char)1);

    r = new Regex("`([^`]+)`");
    MatchCollection ms = r.Matches(p);

    foreach (Match match in ms)
    {
        string t = match.Groups[1].Value;
        p = p.Replace("`" + t + "`", Expand(variables[t]));
    }

    p = p.Replace(""+(char)1, "`");

    return p;
}

首先,我们排除“假”引号和斜杠。然后我们查找所有带引号的变量名,并将每个名称替换为扩展变量的值。最后,我们返回所有“假”引号(没有斜杠)。好吧,相当容易。每次我们对变量或模式进行一些更改时,都会在我们的VarRegex对象中重新创建Regex对象。类VariablesCollection还利用嵌套的可枚举类ExpandedVariablesCollection,该类允许枚举或按名称接收扩展变量的值。

Using

现在,为引言中的电话号码生成正则表达式的代码将如下所示

VarRegex vr = new VarRegex();
vr.Variables["int"] = @"\d+";
vr.Variables["sep"] = @"[\s\-]";
vr.Variables["gr"] = @"`int``sep`";
vr.Pattern = @"`gr`*`int`";
vr.Options = RegexOptions.IgnoreCase;

string str = @"123 568-99";
Match m = vr.Regex.Match(str);
Console.WriteLine("Result for string {1}: {0}\n", m.Success, str);

限制

主要限制是变量应该按照它们被引用的顺序添加。这意味着,在添加它引用的所有变量之后,应该将变量添加到VarRegex中。

历史

  • 2007 年 1 月 10 日:初始帖子
© . All rights reserved.