灵活的文本解析策略

5

问题

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

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字段并不总是存在。正如您所看到的,有一些具有相同关键字的类似键,但解析不正确,例如“联系人”和“次要联系人”。这会导致解析器获取过多的信息。此外,如果我的结束字段不存在,我将得到一些不可预测的结果。最后,我可以解析整行文本,然后使用此方法将其传递给BetweenOperation<T>

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

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

我们在某些情况下会使用LineOperation,例如字段名不唯一(例如时间),并将结果馈送给BetweenOperation<T>
问题:
如何基于关键字解析上述显示的内容。例如,“Header Code”和“Cell Phone”。请注意,我认为不能根据空格或制表符进行解析,因为某些字段可能有多行(例如来电者地址)或根本没有值(例如备用电话)。
谢谢。

2
这可能比你需要的更多,但是像ANTLR这样的语言解析器是一个选择。 - user1228
1
你是否预先知道字段名称,即是否有一组固定的字段名称,还是它们可能会变化? - Eric Giguere
@Eric - 我只知道可能会遇到哪些字段名称。但是我无法预先知道(除了解析之外)电子邮件将包含哪些字段。出现的字段非常一致,只有少数情况下可能会缺少1-2个字段。 - Mike
3
没错,但你知道所有可能的领域集合,这使得解析过程更加简单。 - Eric Giguere
4个回答

3

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

特定顺序

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

你需要按照以下顺序搜索字段,首先是不包含其他关键字的单词(例如对于联系人,顺序应为“Secondary Contact:”,“Alternate Contact:”,最后是“Contact:”)
如果找到所需的字段信息,您需要修改电子邮件正文以删除它。按照特定顺序解析将确保您不会遇到整个不匹配问题,因为您最后删除子集。
现在还有一个关键字字段的问题。由于无法保证是否始终存在结束字段(而且我不确定它们是否始终按特定顺序),因此您需要遍历所有关键字字段并返回索引,并根据索引确定最接近的关键字。

2

我曾经需要读取Pick DB报告中的类似内容。如果你的字段是基于位置的,那么你可以简单地创建一个电子邮件消息的XML模式:

<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开始),您将在模式中找到“line”+索引号实体。
创建一个临时字符串。对于“line”+索引实体中的每个元素,在整个行上执行一个子字符串,从元素实体中定义的起始值到结束值...
根据元素的类型进行转换。将实体保存到对象或其他内容中。
您甚至可以通过类将模式中不同的“line”+索引实体分组,以获得更多创意。
<message>
    <header>
        <line0>
        ...
        </line0>
    </header>
</message>

2

解决这个问题的一种方法是首先搜索整个文本,查找关键字的出现情况。也就是说,构建一个类似于以下内容的数组:

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

如果您按位置对该数组进行排序,那么您就知道要查找哪些内容。也就是说,您将知道哪些字段跟随每个字段。
您需要处理重复项(即通话日期中的“时间:”和“时间:”)。并且您需要解决“联系人:”和“次要联系人:”,不过后者应该很容易解决。
如果您使用标准字符串操作(即IndexOf),这将有点低效,因为您必须搜索所有出现的字符串的整个文本。这是否对您构成问题很难说。这取决于您需要处理多少此类操作。
如果这成为问题,您可能需要构建Aho-Corasick字符串匹配器或类似的东西。或者您可以构建一个庞大而丑陋的正则表达式:
“(Header Code:) | (Contact Phone:) | (Cell Phone)” ...等等。可能带有命名捕获,以便您知道正在捕获什么。它应该能够正常工作,尽管可能难以维护。

暴力破解再次拯救了我们。虽然不太美观,但只要能用就行…… - Jim Mischel

2

我首先会将邮件按行拆分,可以使用StringReader,逐行解析并跳过完全空行。由于您要查找的标签是“note”,所以需要在每行中循环遍历潜在的标签,如果找到出现,则提取正确的部分(可以使用空格进行此操作)。不知道为什么不能使用正则表达式,但如果它们是基于每行预处理的,那么将非常好用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接