繁体   English   中英

优雅的结构化文本文件解析

[英]Elegant structured text file parsing

我需要解析实时聊天对话的笔录。 我看到文件的第一个想法是针对该问题抛出正则表达式,但我想知道人们使用了哪些其他方法。

我以前在标题中加上了优雅,因为我以前发现这种类型的任务有难以仅依靠正则表达式进行维护的危险。

成绩单由www.providesupport.com生成,并通过电子邮件发送到一个帐户,然后从电子邮件中提取纯文本成绩单附件。

解析文件的原因是要提取对话文本以供以后使用,而且还要标识访问者和操作员的姓名,以便可以通过CRM使用该信息。

这是一个成绩单文件的示例:

Chat Transcript

Visitor: Random Website Visitor 
Operator: Milton
Company: Initech
Started: 16 Oct 2008 9:13:58
Finished: 16 Oct 2008 9:45:44

Random Website Visitor: Where do i get the cover sheet for the TPS report?
* There are no operators available at the moment. If you would like to leave a message, please type it in the input field below and click "Send" button
* Call accepted by operator Milton. Currently in room: Milton, Random Website Visitor.
Milton: Y-- Excuse me. You-- I believe you have my stapler?
Random Website Visitor: I really just need the cover sheet, okay?
Milton: it's not okay because if they take my stapler then I'll, I'll, I'll set the building on fire...
Random Website Visitor: oh i found it, thanks anyway.
* Random Website Visitor is now off-line and may not reply. Currently in room: Milton.
Milton: Well, Ok. But… that's the last straw.
* Milton has left the conversation. Currently in room:  room is empty.

Visitor Details
---------------
Your Name: Random Website Visitor
Your Question: Where do i get the cover sheet for the TPS report?
IP Address: 255.255.255.255
Host Name: 255.255.255.255
Referrer: Unknown
Browser/OS: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)

不,实际上,对于您描述的特定类型的任务,我怀疑有没有比正则表达式更“干净”的方法了。 看来您的文件已嵌入换行符,因此通常我们在这里要做的是应用每行正则表达式使该行成为分解单位。 同时,您创建一个小型状态机,并使用正则表达式匹配来触发该状态机中的转换。 通过这种方式,您知道文件中的位置以及可以期望的字符数据类型。 另外,请考虑使用命名捕获组并从外部文件加载正则表达式。 这样,如果您的成绩单格式发生变化,则只需调整正则表达式即可,而不必编写特定于解析的新代码。

使用Perl,您可以使用Parse :: RecDescent

这很简单,您的语法稍后将可维护。

这是两个基于lepl解析器生成器库的解析器。 它们都产生相同的结果。

from pprint import pprint
from lepl import AnyBut, Drop, Eos, Newline, Separator, SkipTo, Space

# field = name , ":" , value
name, value = AnyBut(':\n')[1:,...], AnyBut('\n')[::'n',...]    
with Separator(~Space()[:]):
    field = name & Drop(':') & value & ~(Newline() | Eos()) > tuple

header_start   = SkipTo('Chat Transcript' & Newline()[2])
header         = ~header_start & field[1:] > dict
server_message = Drop('* ') & AnyBut('\n')[:,...] & ~Newline() > 'Server'
conversation   = (server_message | field)[1:] > list
footer_start   = 'Visitor Details' & Newline() & '-'*15 & Newline()
footer         = ~footer_start & field[1:] > dict
chat_log       = header & ~Newline() & conversation & ~Newline() & footer

pprint(chat_log.parse_file(open('chat.log')))

更严格的解析器

from pprint import pprint
from lepl import And, Drop, Newline, Or, Regexp, SkipTo

def Field(name, value=Regexp(r'\s*(.*?)\s*?\n')):
    """'name , ":" , value' matcher"""
    return name & Drop(':') & value > tuple

Fields = lambda names: reduce(And, map(Field, names))

header_start   = SkipTo(Regexp(r'^Chat Transcript$') & Newline()[2])
header_fields  = Fields("Visitor Operator Company Started Finished".split())
server_message = Regexp(r'^\* (.*?)\n') > 'Server'
footer_fields  = Fields(("Your Name, Your Question, IP Address, "
                         "Host Name, Referrer, Browser/OS").split(', '))

with open('chat.log') as f:
    # parse header to find Visitor and Operator's names
    headers, = (~header_start & header_fields > dict).parse_file(f)
    # only Visitor, Operator and Server may take part in the conversation
    message = reduce(Or, [Field(headers[name])
                          for name in "Visitor Operator".split()])
    conversation = (message | server_message)[1:]
    messages, footers = ((conversation > list)
                         & Drop('\nVisitor Details\n---------------\n')
                         & (footer_fields > dict)).parse_file(f)

pprint((headers, messages, footers))

输出:

({'Company': 'Initech',
  'Finished': '16 Oct 2008 9:45:44',
  'Operator': 'Milton',
  'Started': '16 Oct 2008 9:13:58',
  'Visitor': 'Random Website Visitor'},
 [('Random Website Visitor',
   'Where do i get the cover sheet for the TPS report?'),
  ('Server',
   'There are no operators available at the moment. If you would like to leave a message, please type it in the input field below and click "Send" button'),
  ('Server',
   'Call accepted by operator Milton. Currently in room: Milton, Random Website Visitor.'),
  ('Milton', 'Y-- Excuse me. You-- I believe you have my stapler?'),
  ('Random Website Visitor', 'I really just need the cover sheet, okay?'),
  ('Milton',
   "it's not okay because if they take my stapler then I'll, I'll, I'll set the building on fire..."),
  ('Random Website Visitor', 'oh i found it, thanks anyway.'),
  ('Server',
   'Random Website Visitor is now off-line and may not reply. Currently in room: Milton.'),
  ('Milton', "Well, Ok. But… that's the last straw."),
  ('Server',
   'Milton has left the conversation. Currently in room:  room is empty.')],
 {'Browser/OS': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)',
  'Host Name': '255.255.255.255',
  'IP Address': '255.255.255.255',
  'Referrer': 'Unknown',
  'Your Name': 'Random Website Visitor',
  'Your Question': 'Where do i get the cover sheet for the TPS report?'})

您可能要考虑完整的解析器生成器。

正则表达式非常适合在文本中搜索较小的子字符串,但如果您真的有兴趣将整个文件解析为有意义的数据,则它们的功能可能会严重不足。

如果子字符串的上下文很重要,它们尤其不足。

大多数人对所有内容都使用正则表达式,因为这就是他们所知道的。 他们从未学习过任何解析器生成工具,并且最终对许多生产规则组合和语义动作处理进行了编码,而使用解析器生成器可以免费获得这些代码。

正则表达式非常出色,但如果您需要解析器,则无可替代。

建立解析器 我无法确定您的数据是否足够常规,但这可能值得研究。

使用多行,带注释的正则表达式可以在某种程度上缓解维护问题。 尝试避免使用单行超级正则表达式!

另外,考虑将正则表达式分解为单独的任务,每个要获取的“东西”一个。 例如。

visitor = text.find(/Visitor:(.*)/)
operator = text.find(/Operator:(.*)/)
body = text.find(/whatever....)

代替

text.match(/Visitor:(.*)\nOperator:(.*)...whatever to giant regex/m) do
  visitor = $1
  operator = $2
  etc.
end

然后,可以轻松更改任何特定项目的解析方式。 就解析具有多个“聊天块”的文件而言,只有一个与单个聊天块匹配的简单正则表达式,遍历文本并将匹配数据从此传递给其他匹配器组。

这显然会影响性能,但是除非您处理大量文件,否则我不会担心。

考虑使用Ragel http://www.complang.org/ragel/

这就是引擎盖下杂种的力量。 多次解析一个字符串会大大减慢速度。

我使用了Paul McGuire的pyParsing类库,并且对它印象深刻,因为它的文档齐全,易于上手,并且规则易于调整和维护。 顺便说一句,规则在您的python代码中表达。 显然,日志文件具有足够的规律性,可以将每一行解析为一个独立的单元。

只是快速的帖子,我只看了您的成绩单示例,但最近我也不得不研究文本解析,并希望避免采用手动解析的方法。 我确实遇到过Ragel ,但我才刚开始动脑筋 ,但它看起来非常有用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM