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






3.83/5 (5投票s)
本文介绍了一个 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`";
好吧,更多的代码行,但是
- 每组符号只定义一次,然后重用,在模式的不同部分没有重复的组。
- 每一行都短得多,并且包含命名的字面量,这使得表达式更容易理解。
本文描述了一个类,该类创建用于在 C# 程序中使用类似的语法。它处理此类表达式并返回使用扩展模式创建的 Regex 对象。
想法
好的,这个想法尽可能简单。我们创建一个允许添加“变量”的类。每个变量可以是一个正则表达式表达式或类似正则表达式的表达式,并引用先前添加的变量。然后,模式以相同的形式设置。之后,我们收到现成的Regex
对象并根据需要使用它。
我们使用`引号来标记变量。如果我们想使用引号本身(也许仍然有人需要它 :),我们可以写 "\`"。
实现细节
创建了类VarRegex
。它有一个嵌套的可枚举类VariablesCollection
,围绕着一个Dictionary<String, String>
构建。这个类允许使用索引器属性添加和修改变量,检索它们的Count
,Clear
变量列表和枚举它们的值。主要的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 日:初始帖子