我是一个完整的 pyparsing 新手,并且正在尝试使用描述存档文件及其内容的多行块来解析一个大文件。
我目前正处于能够解析单个项目的阶段(没有开始换行符,这个硬编码的测试数据近似于在真实文件中读取):
import pyparsing as pp
one_archive = \
"""archive (
name "something wicked this way comes.zip"
file ( name wicked.exe size 140084 date 2022/12/24 23:32:00 crc B2CF5E58 )
file ( name readme.txt size 1704 date 2022/12/24 23:32:00 crc 37F73AEE )
)
"""
pp.ParserElement.set_default_whitespace_chars(' \t')
EOL = pp.LineEnd().suppress()
start_of_archive_block = pp.LineStart() + pp.Keyword('archive (') + EOL
end_of_archive_block = pp.LineStart() + ')' + EOL
archive_filename = pp.LineStart() \
+ pp.Keyword('name').suppress() \
+ pp.Literal('"').suppress() \
+ pp.SkipTo(pp.Literal('"')).set_results_name("archive_name") \
+ pp.Literal('"').suppress() \
+ EOL
field_elem = pp.Keyword('name').suppress() + pp.SkipTo(pp.Literal(' size')).set_results_name("filename") \
^ pp.Keyword('size').suppress() + pp.SkipTo(pp.Literal(' date')).set_results_name("size") \
^ pp.Keyword('date').suppress() + pp.SkipTo(pp.Literal(' crc')).set_results_name("date") \
^ pp.Keyword('crc').suppress() + pp.SkipTo(pp.Literal(' )')).set_results_name("crc")
fields = field_elem * 4
filerow = pp.LineStart() \
+ pp.Literal('file (').suppress() \
+ fields \
+ pp.Literal(')').suppress() \
+ EOL
archive = start_of_archive_block.suppress() \
+ archive_filename \
+ pp.OneOrMore(pp.Group(filerow)) \
+ end_of_archive_block.suppress()
archive.parse_string(one_archive, parse_all=True)
结果是一个 ParseResults 对象,其中包含该单个存档中我需要的所有数据。 (出于某种原因,输入字符串中的尾随换行符不会导致任何问题,尽管我没有采取任何措施来主动处理它。)
但是,尽我所能,我无法从现在开始解析以下更真实的数据。我需要处理的新功能是:
file_metadata
块(我的解析结果中不需要它,可以完全跳过它)archive
项目archive
项目之间的换行符realistic_data = \
"""
file_metadata (
description: blah blah etc.
author: john doe
version: 0.99
)
archive (
name "something wicked this way comes.zip"
file ( name wicked.exe size 140084 date 2022/12/24 23:32:00 crc B2CF5E58 )
file ( name readme.txt size 1704 date 2022/12/24 23:32:00 crc 37F73AEE )
)
archive (
name "naughty or nice.zip"
file ( name naughty.exe size 187232 date 2021/8/4 10:19:55 crc 638BC6AA )
file ( name nice.exe size 298234 date 2021/8/4 10:19:56 crc 99FD31AE )
file ( name whatever.jpg size 25603 date 2021/8/5 11:03:09 crc ABFAC314 )
)
"""
我一直在半随机地尝试各种事情,但我对 pyparsing 工作原理的理解存在很大的根本差距,因此不值得在这里逐条列举。知道自己在做什么的人可能可以立即看到在这里要做什么。
我的最终目标是解析所有这些存档项目并将它们存储在数据库中。
解决办法是什么?
两天后,我成功了。在此期间,关于 pyparsing 的一些东西在我的大脑中响起,我想出了一种更好、更短、更“pyparsing 原生感觉”的处理方式。
给定这些数据,我想忽略其中的file_metadata块,并一一解析所有后面的归档块:
realistic_data = \
"""
file_metadata (
description: blah blah etc.
author: john doe
version: 0.99
)
archive (
name "something wicked this way comes.zip"
file ( name wicked.exe size 140084 date 2022/12/24 23:32:00 crc B2CF5E58 )
file ( name readme.txt size 1704 date 2022/12/24 23:32:00 crc 37F73AEE )
)
archive (
name "naughty or nice.zip"
file ( name naughty.exe size 187232 date 2021/8/4 10:19:55 crc 638BC6AA )
file ( name nice.exe size 298234 date 2021/8/4 10:19:56 crc 99FD31AE )
file ( name whatever.jpg size 25603 date 2021/8/5 11:03:09 crc ABFAC314 )
)
"""
这可以正确解析它,具有良好的分组、命名,并且由于生成器返回
scanString
,忽略元数据标头并可处理大文件:
import pyparsing as pp
LPAREN, RPAREN = map(pp.Suppress, map(pp.Literal, '()'))
# archive and file
BLOCKSTART = pp.Word(pp.alphas).suppress() + LPAREN
BLOCKEND = RPAREN
# archive name and all the fields of a file
LABEL = pp.Word(pp.alphas).suppress()
# Name row
name = LABEL + pp.QuotedString(quote_char='"')('name')
# File row
value = pp.Word(pp.printables, excludeChars=' ')
datevalue = pp.DelimitedList(value, delim=' ', max=2, combine=True)
file = BLOCKSTART + pp.Group(LABEL + value('name') + LABEL + value('size') + LABEL + datevalue('date') + LABEL + value('crc')) + BLOCKEND
files = pp.OneOrMore(file)('files')
# Full archive block
archive = BLOCKSTART + name + files + BLOCKEND
# Returns a generator that produces the archive blocks one by one
archive.scanString(realistic_data)
希望这对遇到类似情况的人有用!