python - Pyparsing:将类似字典的结构解析为实际的字典
问题描述
我正在尝试将配置文件解析为 python 字典。我无法更改文件的语法。
我正在使用pyparsing。到目前为止,这是我的代码。
def file_parser():
# Example data
data = """
root {
level_one {
key = value
local {
auth = psk
}
remote {
auth = psk
}
children {
net {
local_ts = 1.1.0.0/16
updown = /usr/local/test noticethespace
esp_proposals = yht123-h7583
}
}
version = 2
proposals = ydn162-jhf712-h7583
}
}
usr {
level_one {
key = value
}
}
"""
integer = Word(nums)
ipAddress = Combine(integer + "." + integer + "." + integer + "." + integer)
name = Word(alphas + "_-")
any_word = Word(printables, excludeChars="{} ")
EQ, LBRACE, RBRACE = map(Suppress, "={}")
gram = Forward()
entry = Group(name + ZeroOrMore(EQ) + gram)
struct = Dict(LBRACE + OneOrMore(entry) + RBRACE)
gram << (struct | ipAddress | name | any_word)
result = Dict(OneOrMore(entry)).parseString(data)
print(result)
当我运行此代码时,我收到以下错误:
pyparsing.ParseException: Expected {Dict:({{Suppress:("{") {Group:({W:(ABCD...) [Suppress:("=")]... : ...})}...} Suppress:("}")}) | Combine:({W:(0123...) "." W:(0123...) "." W:(0123...) "." W:(0123...)}) | W:(ABCD...) | W:(0123...)}, found 'c' (at char 191), (line:11, col:13)
此代码的部分内容来自此答案。我调整了此代码以使用我的特定格式。
解决方案
解析递归语法总是需要一些额外的思考。我总是鼓励解析器开发人员采取的一个步骤是在编写任何代码之前为您的语法编写一个 BNF。有时您可以根据自己的原始语法设计来执行此操作,有时您会尝试从示例文本中重构 BNF。无论哪种方式,编写 BNF 都会让您的大脑处于创造性区域而不是编码的逻辑区域,并且您在解析概念而不是代码时进行思考。
在您的情况下,您属于第二组,您正在根据示例文本重建 BNF。您查看示例并看到有名称的部分,并且它看起来像嵌套的 dict 将是一个不错的目标。有名字的东西有哪些?有些事物以'name = a-value'
某种排列命名,有些事物以 命名'name structure-in-braces'
。您创建了遵循以下 BNF 的代码:
name ::= (alpha | "_-")+
integer ::= digit+
ip_address ::= integer '.' integer '.' integer '.' integer
any_word ::= printable+
entry ::= name '='* gram
struct ::= '{' entry+ '}'
gram ::= struct | ip_address | name | any_word+
在您的代码中,您尝试创建一个entry
表达式来处理这两种情况(使用ZeroOrMore(EQ)
),这就是您过早跳转到代码时发生的一种优化。但是这些是非常不同的,应该在你的 BNF 中单独保存。
(您还没有指定您的 IP 地址,在您的示例代码中有一个尾随"/16"
。)
还有一个问题any_word
,它不处理由多个单词组成的值,如果扩展 withOneOrMore
可能会读取太多单词并吃掉下一个name
。
所以让我们重新开始,想想你的命名元素。这是您拥有的行name = something
:
auth = psk
auth = psk
local_ts = 1.1.0.0/16
updown = /usr/local/test noticethespace
esp_proposals = yht123-h7583
version = 2
proposals = ydn162-jhf712-h7583
如果我们想将表达式定义为name_value = name + EQ + value
,那么 value 将是一个 IP 地址、一个整数或该行中剩下的任何其他内容。如果您发现还有其他类型,则需要在此值表达式中包含其他类型,但请务必将“剩下的任何内容”放在最后。
对于嵌套情况,我们希望有name_struct = name + struct
,其中 struct 是name_value
s 或name_struct
s 的列表,用大括号括起来。这就是所有需要说的name_struct
。
这是我根据此描述构建的 BNF:
name ::= alpha + ('_' | '-' | alpha)*
integer ::= digit+
ip_address ::= integer '.' integer '.' integer '.' integer ['/' integer]
value ::= ip_address | integer | rest_of_the_line
name_value ::= name '=' value
name_struct ::= name struct
struct ::= '{' (name_value | name_struct)* '}'
并且整个解析器是一个或多个name_struct
s。
遵循这个 BNF 并将其转换为 pyparsing 表达式,我将 file_parser() 转换为只返回生成的解析器 - 包括示例文本以及解析和打印它太多,无法包含在这个方法中。相反,代码如下:
data = """...sample text..."""
result = file_parser().parseString(data, parseAll=True)
result.pprint()
并打印出:
[['root',
['level_one',
['key', 'value'],
['local', ['auth', 'psk']],
['remote', ['auth', 'psk']],
['children',
['net',
['local_ts', '1.1.0.0/16'],
['updown', '/usr/local/test noticethespace'],
['esp_proposals', 'yht123-h7583']]],
['version', '2'],
['proposals', 'ydn162-jhf712-h7583']]],
['usr', ['level_one', ['key', 'value']]]]
我将file_parser
根据这些建议将实施留给您。在关于 SO 的其他问题中,我继续发布实际的解析器,但我总是想知道我是否做了太多的勺子喂养,而不是将学习经验更多地留在 OP 手中。所以我在这里停下来,确保按照上述 BNF 在 file_parser() 中实现解析器将产生一个可行的解决方案。
一些技巧:
- 使用 pyparsing
restOfLine
读取行尾的所有内容(代替 any_word) - 使用
Group
forname_value
andname_struct
,然后 struct 变成 simplestruct <<= Dict(LBRACE + (key_value | key_struct)[...] + RBRACE)
,我将[...]
其用作新的表示法ZeroOrMore
- 最终的整体解析器将是
Dict(name_struct[...])
推荐阅读
- python - Python 条件
- sql - 水晶报表按浮动时间分组数据
- algorithm - 设计原则 vs 设计模式 vs 算法
- google-cloud-platform - GCP 上的 IAP_CLIENT_ID
- python - 在 lambda docker 上使用 simpletransformers 时出现“[ERROR] OSError: [Errno 30] Read-only file system”
- flask - SQLAlchemy 元类 Marshmellow 中的自定义字段名称
- javascript - 我怎样才能提取
标签在表格单元格中的内容?
- javascript - 当生成器达到精确数字时,如何阻止生成器生成数字?
- javascript - 错误类型错误:items.push 不是 ShoppingCartService.addProduct 的函数
- reactjs - 警告:列表中的每个孩子都应该有一个唯一的“关键”道具,无法调试到底在哪里显示?