【问题标题】:Flexible text parsing strategies灵活的文本解析策略
【发布时间】:2011-06-17 10:35:09
【问题描述】:

问题

我正在尝试寻找一种灵活的方式来解析电子邮件内容。下面是我正在使用的虚拟电子邮件文本的示例。如果可能的话,我还想避免使用正则表达式。然而,在我解决问题的过程中,我开始认为这是不可避免的。请注意,这只是完整电子邮件的一小部分虚拟子集。我需要将每个字段(例如票号、手机)解析为各自的数据类型。最后,不保证某些字段会出现在电子邮件中(您将在下面显示的我当前的解决方案中看到为什么这是一个问题)。

Header Code:EMERGENCY                               
Ticket No:   123456789 Seq. No: 2
Update of:             

Original Call Date:     01/02/2011     Time:      11:17:03 AM  OP: 1102
Second Call Date:     01/02/2011     Time:      12:11:00 AM  OP: 

Company:           COMPANY NAME
Contact:      CONTACT NAME          Contact Phone: (111)111-1111
Secondary Contact: SECONDARY CONTACT
Alternate Contact:                       Altern. Phone:                  
Best Time to Call: AFTER 4:30P           Fax No:        (111)111-1111
Cell Phone:                              Pager No:                       
Caller Address: 330 FOO
                FOO AVENUE 123

当前解决方案

对于这个简单的示例,我可以使用以下函数成功解析大多数字段。

private T BetweenOperation<T>(string emailBody, string start, string end)
{
 var culture = StringComparison.InvariantCulture;
 int startIndex =
  emailBody.IndexOf(start, culture) + start.Length;
 int endIndex =
  emailBody.IndexOf(end, culture);
 int length = endIndex - startIndex;

 if (length < 0) return default(T);

 return (T)Convert.ChangeType(
  emailBody.Substring(startIndex, length).Trim(), 
  typeof(T));
}

基本上,我的想法是我可以解析两个字段之间的内容。例如,我可以通过做标题代码

// returns "EMERGENCY"
BetweenOperation<string>("email content", "Header Code:", "Ticket No:")

然而,这种方法有很多缺陷。一大缺陷是end 字段并不总是存在。如您所见,有一些具有相同关键字的相似键无法正确解析,例如“Contact”和“Secondary Contact”。这会导致解析器获取太多信息。此外,如果我的 end 字段不存在,我会得到一些不可预测的结果。最后,我可以解析整行,然后使用它传递给BetweenOperation&lt;T&gt;

private string LineOperation(string startWithCriteria)
{
    string[] emailLines = EmailBody.Split(new[] { '\n' });

    return 
        emailLines.Where(emailLine => emailLine.StartsWith(startWithCriteria))
        .FirstOrDefault();
}

在字段名称不唯一(例如时间)的某些情况下,我们会使用LineOperation,并将结果提供给BetweenOperation&lt;T&gt;

问题

如何根据键解析上面显示的内容。例如,键是“标题代码”和“手机”。请注意,我不认为基于制表符空格进行解析,因为某些字段可能有几行长(例如呼叫者地址)或根本不包含任何值(例如备用电话)。

谢谢。

【问题讨论】:

  • 这可能超出了您的需要,但诸如 ANTLR 之类的语言解析器是一种选择。
  • 您是否事先知道字段名称,即有一组固定的字段名称,或者它们可以变化吗?
  • @Eric - 我只知道我可以遇到哪些可能的字段名称。但我无法提前知道(解析不足)哪些电子邮件将包含哪些字段。存在的字段非常一致,只有少数情况下可能会丢失 1-2 个字段。
  • 对,但是你知道所有可能字段的集合,这样解析就简单多了。

标签: c# text-parsing


【解决方案1】:

在我看来,我会按特定顺序对其进行解析,然后相应地修改您的电子邮件正文。

具体顺序

Contact:      CONTACT NAME          Contact Phone: (111)111-1111
Secondary Contact: SECONDARY CONTACT
Alternate Contact: 

搜索字段的顺序应以不属于“字段”中任何其他关键字子集的字词开头(例如,对于联系人,顺序应为“次要联系人:”、“备用联系人:”然后最后是“联系方式:”)

修改您的电子邮件正文,如果您找到所需的字段信息,您需要修改电子邮件正文才能将其删除。按特定序列解析,将确保(我希望)您不会遇到整个不匹配问题,因为您最后删除了子集。

现在还有 end 关键字字段的问题。由于不能保证 end 字段总是存在(而且我不确定它们是否总是按特定顺序排列),因此您必须遍历所有关键字字段,并返回索引并根据索引确定最接近的关键字.

【讨论】:

    【解决方案2】:

    解决此问题的一种方法是首先搜索整个文本以查找您的键的出现。也就是说,构建一个如下所示的数组:

    "Header Code:",1
    "Contact Phone:",233
    "Cell Phone:",-1  // not there
    

    如果您按位置对该数组进行排序,那么您就知道在哪里寻找东西。也就是说,您会知道每个字段后面都有哪些字段。

    您必须对重复项进行处理(即通话日期中的“时间:”和“时间:”)。而且您必须解决“联系人:”和“次要联系人:”,尽管这应该很容易。

    如果您使用标准字符串操作(即IndexOf)执行此操作,效率会有些低,因为您必须在整个文本中搜索每个字符串的所有出现。这对你来说是否是个问题很难说。取决于你需要做多少。

    如果出现问题,您可能需要构建一个 Aho-Corasick 字符串匹配器或类似的东西。或者你可以建立一个又大又丑的正则表达式:

    "(Header Code:)|(Contact Phone:)|(Cell Phone)" ... 等等。可能使用命名捕获,这样你就知道你在捕获什么。它应该工作得相当好,尽管它可能难以维护。

    【讨论】:

    • 蛮力又来了。它不漂亮,但如果它有效。 . .
    【解决方案3】:

    以前我不得不做类似的事情,阅读来自 Pick DB 的报告。如果您的字段是基于位置的,您可以简单地为您的电子邮件创建一个 XML Schema:

    <message>
        <line0>
            <element name="Header Code" start="0" end="MAX" type="string"/> 
            <!-- MAX Indicates whole line -->
        </line0> 
        <line1>
            <element name="Ticket No" start="0" end="20" type="string"/>
            <element name="Seq. No" start="22" end="40" type="int" />
        </line1>
    </message>
    

    然后要解析电子邮件,您将阅读所有文本行。 对于每一行(从 0 开始),您会在架构中找到“行”+ 索引号实体。

    创建一个临时字符串。 “行”+索引实体中的foreach元素在从元素实体中定义的开始到结束值的整行上做一个子字符串......

    根据元素的类型对子字符串进行转换。将实体保存到对象或其他东西。

    您甚至可以通过类在架构中对不同的行 + 索引实体进行分组,从而获得更多创意:

    <message>
        <header>
            <line0>
            ...
            </line0>
        </header>
    </message>
    

    【讨论】:

      【解决方案4】:

      我首先将邮件分成几行,即使用StringReader,一次解析一行,跳过完全空的行。由于您要查找的标签是注释,因此循环遍历每一行中的潜在标签,如果您找到一个出现的地方,请提取正确的部分(您可以为此使用空格)。不知道如何使用 regex 不是一种选择,但如果他们在预行的基础上使用会很有魅力。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多