pyparsing中嵌套结构的技巧

8 投票
2 回答
1701 浏览
提问于 2025-04-18 07:27

我在用PyParsing解析嵌套结构时遇到了困难。我查阅了很多关于PyParsing的'嵌套'示例,但还是不知道怎么解决我的问题。

这是我内部结构的样子:

texture_unit optionalName
{
    texture required_val
    prop_name1 prop_val1
    prop_name2 prop_val1
}

而这是我外部结构的样子,不过它可以包含零个或多个内部结构。

pass optionalName
{
    prop_name1 prop_val1
    prop_name2 prop_val1

    texture_unit optionalName
    {
        // edit 2: showing use of '.' character in value
        texture required_val.file.name optional_val // edit 1: forgot this line in initial post.

        // edit 2: showing potentially multiple values
        prop_name3 prop_val1 prop_val2
        prop_name4 prop_val1
    }
}

我已经成功解析了内部结构。以下是我为此写的代码。

prop_ = pp.Group(pp.Word(pp.alphanums+'_')+pp.Group(pp.OneOrMore(pp.Word(pp.alphanums+'_'+'.'))))
texture_props_ = pp.Group(pp.Literal('texture') + pp.Word(pp.alphanums+'_'+'.')) + pp.ZeroOrMore(prop_)
texture_ = pp.Forward()
texture_ << pp.Literal('texture_unit').suppress() + pp.Optional(pp.Word(pp.alphanums+'_')).suppress() + pp.Literal('{').suppress() + texture_props_ + pp.Literal('}').suppress()

这是我尝试解析外部结构的代码,

pass_props_ = pp.ZeroOrMore(prop_)
pass_ = pp.Forward()
pass_ << pp.Literal('pass').suppress() + pp.Optional(pp.Word(pp.alphanums+'_'+'.')).suppress() + pp.Literal('{').suppress() + pass_props_ + pp.ZeroOrMore(texture_) + pp.Literal('}').suppress()

当我执行:

pass_.parseString( testPassStr )

控制台显示了“}”是预期的错误。

我觉得这和C结构示例非常相似,但我不确定缺少了什么关键部分。我也想知道在使用nestedExpr时,如何控制最终的数据结构。

2 个回答

4

这里有两个问题:

  1. 在你的语法中,你把 texture 这个字面量标记为在 texture_unit 块中是必须的,但在你的第二个例子中没有出现 texture
  2. 在第二个例子中,pass_props_texture_unit optionalName 重复了。之后,pp.Literal('}') 期待的是 },但实际上给的是 {。这就是错误的原因。

我们可以通过这样修改 pass_ 规则来检查这个问题:

pass_ << pp.Literal('pass').suppress() + pp.Optional(pp.Word(pp.alphanums+'_'+'.')).suppress() + \
             pp.Literal('{').suppress() + pass_props_

print pass_.parseString(s2)

这样会给我们以下输出:

[['prop_name', ['prop_val', 'prop_name', 'prop_val', 'texture_unit', 'optionalName']]]

我们可以看到 pass_props_texture_unit optionalName 是重复的。
所以,我们想要做的是:prop_ 可以包含字母、数字、下划线 _ 和点 .,但不能与 texture_unit 字面量匹配。我们可以用 regex负向前瞻 来实现:

prop_ = pp.Group(  pp.Regex(r'(?!texture_unit)[a-z0-9_]+')+ pp.Group(pp.OneOrMore(pp.Regex(r'(?!texture_unit)[a-z0-9_.]+'))) )

最后,工作示例看起来是这样的:

import pyparsing as pp

s1 = '''texture_unit optionalName
    {
    texture required_val
    prop_name prop_val
    prop_name prop_val
}'''

prop_ = pp.Group(  pp.Regex(r'(?!texture_unit)[a-z0-9_]+')+ pp.Group(pp.OneOrMore(pp.Regex(r'(?!texture_unit)[a-z0-9_.]+'))) )
texture_props_ = pp.Group(pp.Literal('texture') + pp.Word(pp.alphanums+'_'+'.')) + pp.ZeroOrMore(prop_)
texture_ = pp.Forward()
texture_ = pp.Literal('texture_unit').suppress() + pp.Word(pp.alphanums+'_').suppress() +\
           pp.Literal('{').suppress() + pp.Optional(texture_props_) + pp.Literal('}').suppress()

print texture_.parseString(s1)

s2 = '''pass optionalName
{
    prop_name1 prop_val1.name
    texture_unit optionalName1
    {
        texture required_val1
        prop_name2 prop_val12
        prop_name3 prop_val13
    }
    texture_unit optionalName2
    {
        texture required_va2l
        prop_name2 prop_val22
        prop_name3 prop_val23
    }
}'''

pass_props_ = pp.ZeroOrMore(prop_  )
pass_ = pp.Forward()

pass_ = pp.Literal('pass').suppress() + pp.Optional(pp.Word(pp.alphanums+'_'+'.')).suppress() +\
        pp.Literal('{').suppress() + pass_props_ + pp.ZeroOrMore(texture_ ) + pp.Literal('}').suppress()

print pass_.parseString(s2)

输出:

[['texture', 'required_val'], ['prop_name', ['prop_val', 'prop_name', 'prop_val']]]
[['prop_name1', ['prop_val1.name']], ['texture', 'required_val1'], ['prop_name2', ['prop_val12', 'prop_name3', 'prop_val13']], ['texture', 'required_va2l'], ['prop_name2', ['prop_val22', 'prop_name3', 'prop_val23']]]
1

我想要的答案和使用“Forward”解析器有关,这在Cstruct的例子中有展示(链接在原帖中)。

定义嵌套结构的语法时,最难的部分是要定义所有可能的成员类型,这些成员类型还需要包括结构本身,而这个结构还没有被定义。

定义嵌套结构的pyparsing语法的“窍门”是延迟定义结构,但在定义结构成员时包含一个“前向声明”的版本,这样成员也可以包含结构。然后将结构语法完成为成员的列表。

struct = Forward()
member = blah | blah2 | struct
struct << ZeroOrMore( Group(member) )

这里也有讨论: Pyparsing:将半JSON嵌套的纯文本数据解析为列表

原帖(我的)描述的测试数据和语法不够具体,导致本该失败的地方却匹配上了。@NorthCat正确地发现了语法中不想要的匹配。然而,建议定义很多“负向前瞻”似乎难以管理。

与其定义不该匹配的内容,我的解决方案是明确列出所有可能的匹配。匹配的内容是成员关键字,使用'oneOf('用空格分隔的单词列表')。一旦我列出了所有可能的匹配,我意识到我的结构并不是嵌套结构,而实际上是一个有限深度的结构,每个深度都有不同的语法描述。所以,我的成员定义不需要使用前向声明的技巧。

我的成员定义的结束符和Cstruct例子中的不同。我的成员定义不是像C++那样以';'(分号)结束,而是需要在行末结束。在pyparsing中,你可以用'LineEnd'解析器来指定行的结束。所以,我将我的成员定义为不包括'LineEnd'的值列表,像这样,注意最后一个定义中使用的“非”(~)操作符:

EOL = LineEnd().suppress()
ident = Word( alphas+"_", alphanums+"_$@#." )
integer = Word(nums)
real = Combine(Optional(oneOf('+ -')) + Word(nums) + '.' + Optional(Word(nums)))
propVal = real | integer | ident
propList = Group(OneOrMore(~EOL + propVal))

撰写回答