解析 pyparsing 组混合字符词

Parsing pyparsing group of mixed character words

我正在尝试使用 pyparsing 解析来自维基百科信息框的数据字段。首先,以下代码有效:

from pyparsing import *

test_line = """{{Infobox company | name                = Exxon Mobil Corp | num_employees_year  = 2015 }}"""

data_group = Group(
    Suppress("|") +
    OneOrMore(White()).suppress() +
    Word(alphanums + printables)("key") +
    OneOrMore(White()).suppress() +
    Suppress("=") +
    OneOrMore(White()).suppress() +
    OneOrMore(Word(alphanums))("value") +
    ZeroOrMore(White()).suppress()
)

infobox_parser = (
    Literal("{{").suppress() +
    Word("Infobox") +
    White().suppress() +
    Word("company") +
    OneOrMore(White()).suppress() +
    OneOrMore(data_group)("values") +
    Literal("}}").suppress()
)

print(infobox_parser.parseString(test_line))

产生结果:

['Infobox', 'company', ['name', 'Exxon', 'Mobil', 'Corp'], ['num_employees_year', '2015']]

问题是当我将测试字符串更改为

test_line = """{{Infobox company | name                = Exxon Mobil Corp. | num_employees_year  = 2015 }}"""

它失败了,因为我引入了“.”。作为 'Corp.' 的一部分。我想我可以通过将 Group 对象更改为

来解决这个问题
data_group = Group(
    Suppress("|") +
    OneOrMore(White()).suppress() +
    Word(alphanums + printables)("key") +
    OneOrMore(White()).suppress() +
    Suppress("=") +
    OneOrMore(White()).suppress() +
    OneOrMore(Word(alphanums + printables))("value") +
    ZeroOrMore(White()).suppress()
)

但我收到以下错误:

pyparsing.ParseException: Expected "}}" (at char 91), (line:1, col:92)

我在这里错过了什么?提前致谢。

只是几件事。最重要的是,pyparsing 不像正则表达式那样进行回溯。也就是说,这样的事情是行不通的:

data = '{' + OneOrMore(Word(printables))("data") + '}'
print(data.parseString('{ this is some data }'))

为什么?因为终止 '}' 匹配为 Word(printables),所以 OneOrMore 将一直持续到最后,然后失败,因为没有终止 ' }' 读取数据后才能找到。

直到最近,解决方案是在 OneOrMore 表达式中包含一个警卫,一个负面的 lookahed 说法实际上 "I want Word(printables), but first check if it's a '}' - I don't want that",看起来像这样:

data = '{' + OneOrMore(~Literal('}') + Word(printables))("data") + '}'

但这太常见了,我最近在 ZeroOrMoreOneOrMore:

中添加了一个可选的 stopOn 参数
data = '{' + OneOrMore(Word(printables), stopOn=Literal('}'))("data") + '}'

在你的例子中,每个 data_group 解析一个 key=value 对,当你只解析 OneOrMore(Word(alphanums)) 时你的值很好。但是一旦你将其更改为 OneOrMore(Word(alphanums+printables)),你的重复项将贪婪地匹配下一个 '|'或终止 '}}',并像上面的示例一样失败。

其他几项:

  • pyparsing 将为您跳过空格。所有这些 White() 元素都是完全不必要的。

  • 在一些地方您使用 Word 不正确,例如 Word("Infobox")。在您的有限示例中,这可以匹配,但请记住,Word 是用您要匹配的字符集定义为单词组的,因此 Word("Infobox") 不仅会匹配 "Infobox",还会匹配任何由字母 'I'、'n'、'f'、'o'、'b'、and/or、'x' 组成的其他单词,例如"Inbox"、"IbIx"、"xoxoxox" 等。在这种情况下,您想要的 pyparsing class 将是 LiteralKeyword.

  • 退一步看,您的 data_group 似乎是 key=value 对,带有分隔符“|”。我建议为此使用 delimitedList

  • 最后,使用dump()输出解析后的数据,这将有助于可视化结构和结果名称。

进行这些更改后,您的代码如下所示:

data_group = Group(
    Word(alphas, alphanums+'_')("key") +
    Suppress("=") +
    originalTextFor(OneOrMore(Word(printables), stopOn=Literal('|') | '}}'))("value") 
)

infobox_parser = (
    Literal("{{").suppress() +
    Keyword("Infobox") +
    Keyword("company") + '|' + 
    Group(delimitedList(data_group, '|'))("values") +
    Literal("}}").suppress()
)

print(infobox_parser.parseString(test_line).dump())

给予:

['Infobox', 'company', '|', [['name', 'Exxon Mobil Corp.'], ['num_employees_year', '2015']]]
- values: [['name', 'Exxon Mobil Corp.'], ['num_employees_year', '2015']]
  [0]:
    ['name', 'Exxon Mobil Corp.']
    - key: name
    - value: Exxon Mobil Corp.
  [1]:
    ['num_employees_year', '2015']
    - key: num_employees_year
    - value: 2015