增强的字符串处理 III
文本文件可以通过使用“知道”如何评估这些构造的构造和等效类来增强。
本文内容是什么?
能够根据您组合的预定义编程规则来评估字符串。这些规则使用正则表达式,并用 C# 编写。配置文件、XML 文档或任何其他文档可能包含一个可变的构造,通过评估过程,您可以得到所需的最终文档。
感言
我最近处理的一个项目需要生成大量 XML 文档作为传输消息,这些 XML 文档至少每天生成一次。这些 XML 文档可以从头创建,也可以使用本文描述的方法创建。使用本文描述的方法,从嵌入了用于每次新 XML 消息生成的构造的修改后的 XML 模板开始,最终被证明是最佳选择,尤其是在 XML 文档需求不断变化的情况下。
引言
一种解释字符串增强本质的潜在好方法是举个例子。考虑配置文件中的 appSettings
部分。如果我们有一个目录在配置中重复出现多次,我们可以将其封装为 {key::BaseDir}
,如下所示:
<add key="BaseDir" value="c:\somedirectory"/>
<add key="TestFile" value="{key::BaseDir}\FileName"/>
而不是
<add key="TestFile" value="c:\somedirectory\FileName"/>
如果目录 "c:\somedirectory" 在 appSettings
的多个条目中使用,那么使用 {key::BaseDir}
将会是一个明确的选择,特别是当 {key::BaseDir}
对应的值会随时间变化时。
构造 {key::BaseDir}
是需要被评估的,为此我们将引入一个类 ProcessKey
,它将“知道”如何进行评估。在这种情况下,“key”字面意思就是字符串“key”,尽管通常构造可以有任何字符串作为键,我们将引入一个类 ProcessXxx
,它将“知道”如何执行评估。
随文章提供的代码将展示一些不同键及其等效 ProcessXxx
类的示例。
动机与目标
- 主要动机:能够使用一种简单的构造形式
{key::value}
,其中“key”和“value”是占位符。Key 是驱动评估的 ID,value 是评估指令。 - 不区分大小写:提供能够以不区分大小写的方式输入具有键和值的构造。
- 嵌套/顺序:构造可以嵌套在其他构造中,也可以与其他构造并列。
- 可扩展:系统应能够根据需要扩展自身以支持更多构造。
术语
构造的组成为
- 开界符:在
{key::BaseDir}
示例中,它是开括号“{“
。 - 闭界符:在
{key::BaseDir}
示例中,它是闭括号“}”
。 - 分隔符:分隔构造的各个部分。在
{key::BaseDir}
示例中,它是双冒号“::”
。 - 键:构造的 ID。在
{key::BaseDir}
示例中,它是字面量“key”
。 - 值:进程将用于评估构造的值。在
{key::BaseDir}
示例中,它是BaseDir
。“value” 部分可能包含由分隔符分隔的子结构。例如{Add::2::3}
。“value” 是 "2::3"。
Windows 或 Web,C# 或 VB
文章附带的代码示例是一个用 C# 编写的 Windows 控制台应用程序,但 nothing prevents you from using the concept,as is,in a Windows Forms 应用程序、WPF 应用程序、ASP Web 环境(带或不带 WPF)、Windows 服务,或将 EnhancedStringEvaluate DLL 直接与 VB 或任何其他语言客户端一起使用。
我已在生产代码中使用了 EnhancedStringEvaluate DLL,该 DLL 已在多个 Windows Forms 应用程序、Windows 控制台应用程序和多个 Windows 服务应用程序中使用。
如果您只想:继续前进
如果您理解事物是如何实现的,而不是解决当前问题,那么
- 将 EnhancedStringEvaluate csproj 包含在您的解决方案中,并将其添加到客户端的 References 中。一种不太吸引人的替代方法是将 EnhancedStringEvaluate DLL(不带代码)包含在项目的 References 中。
- 修改或添加
ProcessXxx
类(如下面的红色圆圈所示)的集合。您可以选择创建一个单独的 csproj 来存放ProcessXxx
类集。
此后,您就可以开始了,请参阅 EvaluateSampleTest.EnhancedStringEvaluateTest 示例集。实际上,您需要
- 定义需要传递给
EnhancedStringEval
类构造函数的上下文。 - 实例化
EnhancedStringEval
,传递刚刚创建的上下文。 - 使用
EnhancedStringEval
的EvaluateString(..)
方法来评估字符串。
var context = new List<IProcessEvaluate> { new ProcessXxx() };
var eval = new EnhancedStringEval(context);
string evaluatedStr = eval.EvaluateString("{Key::Value}");
为了让它读取上面 {key::BaseDir}
的例子,上面的客户端代码将变成
var context = new List<IProcessEvaluate> { new ProcessKey() };
var eval = new EnhancedStringEval(context);
string directory = eval.EvaluateString("{Key::BaseDir}");
创建新的 ProcessXxx
假设您心中有一个 (key, value) 构造,那么首先要做的是定义识别该构造所需的正则表达式。例如,如果我们想定义一个产生两个数字的数学加法的构造,那么我们的构造将是:“{Add::num1::num2}”。意图是 “{Add::2::3}” 将产生“5”。我们期望我们的文档包含这样的构造。
使用我们的方法处理的文档(文档包含一行)
The sum of 2 and 3 is {Add::2::3}
该构造可以如下调用:
IList<IProcessEvaluate> context = new List<IProcessEvaluate> { new ProcessAdd() };
var eval = new EnhancedStringEval(context);
Open_the_document()
foreach (string line = read_next_line_until_EOF())
{
string res = eval.EvaluateString(line);
// will yield “The sum of 2 and 3 is 5”
}
快速浏览一下随文章提供的代码中的 ProcessXxx
集合,我们发现没有一个提供 ProcessAdd
类。这意味着我们需要自己编写一个。
新的 ProcessAdd
类需要
- 继承自
ProcessEvaluateBase
抽象类。 - 提供一个类构造函数,该构造函数将初始化识别
{Add::num1::num2}
构造的正则表达式。 - 重写
RePattern
属性。 - 重写
PatternReplace
方法。PatternReplace
将评估模式。
类名约定是以 “Process” 为前缀。为简单起见,我们只允许 num1 和 num2 是整数。
匹配上述加法构造所需的正则表达式是:
{Add::(?<num1>\d+)::(?<num2>\d+)}
到达 ProcessXxx
的分隔符和界符需要使用 DelimitersAndSeparator
类中定义的等效项 OpenDelimEquivalent
、CloseDelimEquivalent
和 SeparatorEquivalent
,从而使正则表达式如下所示:
RePattern = new Regex(string.Format(@”{0}Add{2}(?<num1>\d+){2}(?<num2>\d+){1}",
delim. OpenDelimEquivalent, delim.CloseDelimEquivalent,
delim.SeparatorEquivalent), RegexOptions.IgnoreCase);
等效项的精确定义并不重要,重要的是您需要使用它们。
最后一部分 PatternReplace
是我们评估构造并产生结果字符串的地方。在我们的例子中,这只是一个简单的数字相加。我们有信心 num1 和 num2 是数字字符串,因为否则正则表达式就不会识别出该构造是有效的。
注意:PatternReplace
方法有两个参数:第一个是 Match m
,第二个是 EnhancedStringEventArgs ea
。第一个参数 Match m
是一个预期参数,它携带匹配的表达式。如果行是:“The sum of 2 and 3 is {Add::2::3}”,那么只有 “{Add::2::3}” 匹配正则表达式 RePattern
。第二个参数 EnhancedStringEventArgs ea
是在需要抛出异常时提供的,在这种情况下,您应该抛出 EnhancedStringException
。EnhancedStringException
类的一个构造函数重载接受三个参数:string key
、EnhancedStrPairElement elem
和 string message
。Key 在构造中定义,在 “{Add::num1::num2}”
示例中,它是 “Add”。对于第二个参数 elem
,传递:ea.EhancedPairElem
。对于第三个参数,传递您的消息。因此,例如一个异常(比如其中一个数字太大)将是:
throw new EnhancedStringException("Add", ea.EhancedPairElem, "Number too big");
整个类是:
using EnhancedStringEvaluate;
using System;
using System.Text.RegularExpressions;
namespace TestEvaluation.ProcessEvaluate
{
/// <summary>
/// Purpose:
/// Add two integral numbers: {Add::number1::number2}
/// </summary>
public sealed class ProcessAdd : ProcessEvaluateBase
{
public ProcessAdd() : this(DelimitersAndSeparator.DefaultDelimitersAndSeparator) { }
public ProcessAdd(IDelimitersAndSeparator delim) : base(delim)
{
if (delim == null) throw new ArgumentException("Delim may not be null", "delim");
const RegexOptions reo = RegexOptions.Singleline | RegexOptions.IgnoreCase
| RegexOptions.Compiled;
string pattern = string.Format(@"({0})Add{2}(?<Num1>\d+){2}(?<Num2>\d+)({1})",
delim.OpenDelimEquivalent, delim.CloseDelimEquivalent, delim.SeparatorEquivalent);
RePattern = new Regex(pattern, reo);
}
protected override Regex RePattern { get; set; }
protected override string PatternReplace(Match m, EnhancedStringEventArgs ea)
{
string sNum1 = m.Groups["Num1"].Value;
string sNum2 = m.Groups["Num2"].Value;
int num1 = int.Parse(sNum1, NumberStyles.Any);
int num2 = int.Parse(sNum2, NumberStyles.Any);
int res = num1 + num2;
return res.ToString();
}
}
}
至此,我们完成了。接下来我将专注于解释 EnhancedStringEvaluate DLL 如何工作。
类架构
EnhancedStringEval 概述
EnhancedStringEval
类是驱动其评估的引擎,它通过将评估委托给 ProcessXxx
类中的字符串替换方法 PatternReplace(..)
来实现。该过程遵循策略模式。
您不需要了解策略模式就可以理解本文。如果您了解策略模式,那么这些知识将有助于您理解 EnhancedStringEvaluate
类中的代码。不了解策略模式也不会有影响。
让我们从高层次的角度讨论代码,并以以下代码为基础进行思考:
IList<IProcessEvaluate> context = new List<IProcessEvaluate> {
new ProcessXxx1(..),
new ProcessXxx2(..),
. . .
};
var eval = new EnhancedStringEval(context);
. . .
string res = eval.EvaluateString(line);
上面的代码不是上面“类架构”图的一部分,它是调用以评估某行代码的客户端代码的一部分。以下讨论将使用术语“上下文”、“行”以及其他与上面代码相关的术语。
如您所见,在上面的代码中,EnhancedStringEval
类的终端是 eval.EvaluateString
方法,该方法将繁重的工作委托给 EvaluatedStringPure
方法(请参阅 EvaluateString
的代码)。EvaluateStringPure
调用上下文中 ProcessXxx
类,以查看该行是否包含可评估的构造。
如果没有可用的构造进行评估,那么我们就继续并将输入的 line 保持原样。另一方面,如果输入的 line 确实包含一个可评估的构造,那么相应的 ProcessXxx
将发挥其作用。
结束语
感谢您阅读本文,希望您觉得它很有用。
使用的工具
编码示例使用了 VS 2012,在 Windows 8 平台上使用 .NET 4.5。用于绘制 UML 图的工具是“Visual Paradigm for UML Community Edition”。
尽情享用!