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

增强的字符串处理 III

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (17投票s)

2009年9月12日

CPOL

7分钟阅读

viewsIcon

32444

downloadIcon

417

文本文件可以通过使用“知道”如何评估这些构造的构造和等效类来增强。

本文内容是什么?

能够根据您组合的预定义编程规则来评估字符串。这些规则使用正则表达式,并用 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 示例集。实际上,您需要

  1. 定义需要传递给 EnhancedStringEval 类构造函数的上下文。
  2. 实例化 EnhancedStringEval,传递刚刚创建的上下文。
  3. 使用 EnhancedStringEvalEvaluateString(..) 方法来评估字符串。
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” 为前缀。为简单起见,我们只允许 num1num2 是整数。

匹配上述加法构造所需的正则表达式是:

{Add::(?<num1>\d+)::(?<num2>\d+)}

到达 ProcessXxx 的分隔符和界符需要使用 DelimitersAndSeparator 类中定义的等效项 OpenDelimEquivalentCloseDelimEquivalentSeparatorEquivalent,从而使正则表达式如下所示:

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 是在需要抛出异常时提供的,在这种情况下,您应该抛出 EnhancedStringExceptionEnhancedStringException 类的一个构造函数重载接受三个参数:string keyEnhancedStrPairElement elemstring 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”。

尽情享用!

© . All rights reserved.