python 从命名值列表中解析语法树

doinxwow  于 2023-01-29  发布在  Python
关注(0)|答案(2)|浏览(97)

我想使用分隔符解析标记/值描述:和·
例如,输入为:

Name:Test•Title: Test•Keywords: A,B,C

预期结果应为名称值dict

{
"name": "Test",
"title": "Title",
"keywords: "A,B,C"
}

可能已经将"A,B,C"中的关键字拆分为一个列表。(这是一个小细节,因为Python内置的字符串的split方法会很乐意这样做)。
同时应用Map

keys={
  "Name": "name",
  "Title": "title",
  "Keywords": "keywords",
}

因为名称和Dict关键字之间的Map将是有帮助的,但是可以是单独的步骤。
我尝试了https://trinket.io/python3/8dbbc783c7下面的代码

# pyparsing named values
# Wolfgang Fahl
# 2023-01-28 for Stackoverflow question
import pyparsing as pp
notes_text="Name:Test•Title: Test•Keywords: A,B,C"
keys={
  "Name": "name",
  "Titel": "title",
  "Keywords": "keywords",
}
keywords=list(keys.keys())
runDelim="•"
name_values_grammar=pp.delimited_list(
  pp.oneOf(keywords,as_keyword=True).setResultsName("key",list_all_matches=True)
  +":"+pp.Suppress(pp.Optional(pp.White()))
  +pp.delimited_list(
    pp.OneOrMore(pp.Word(pp.printables+" ", exclude_chars=",:"))
        ,delim=",")("value")
    ,delim=runDelim).setResultsName("tag", list_all_matches=True)
results=name_values_grammar.parseString(notes_text)
print(results.dump())

和它的变体,但我甚至没有接近预期的结果。目前转储显示:

['Name', ':', 'Test']
 - key: 'Name'
 - tag: [['Name', ':', 'Test']]
  [0]:
    ['Name', ':', 'Test']
 - value: ['Test']

似乎我不知道如何定义语法和工作的分析结果的方式来获得所需的dict结果。
我的主要问题是:

  • 我应该使用解析操作吗?
  • 如何命名零件结果?
  • 如何导航生成的树?
  • 如何从delimitedList中取回列表?
  • list_all_matches = True实现了什么-它的行为似乎很奇怪

我在stackoverflow上搜索了上述问题的答案,但我找不到一个一致的解决方案。

PyParsing看起来是一个很棒的工具,但是我发现它非常不直观。幸运的是,这里有很多答案,所以我希望学习如何让这个例子工作
我自己尝试了一种循序渐进的方法:
首先,我检查了delimitedList行为,请参见https://trinket.io/python3/25e60884eb

# Try out pyparsing delimitedList
# WF 2023-01-28
from pyparsing import printables, OneOrMore, Word, delimitedList

notes_text="A,B,C"

comma_separated_values=delimitedList(Word(printables+" ", exclude_chars=",:"),delim=",")("clist")

grammar = comma_separated_values
result=grammar.parseString(notes_text)
print(f"result:{result}")
print(f"dump:{result.dump()}")
print(f"asDict:{result.asDict()}")
print(f"asList:{result.asList()}")

其返回

result:['A', 'B', 'C']
dump:['A', 'B', 'C']
- clist: ['A', 'B', 'C']
asDict:{'clist': ['A', 'B', 'C']}
asList:['A', 'B', 'C']

这看起来很有希望,关键的成功因素似乎是用"clist"命名这个列表,默认行为看起来很好。
https://trinket.io/python3/bc2517e25a更详细地显示了问题所在。

# Try out pyparsing delimitedList
# see https://stackoverflow.com/q/75266188/1497139
# WF 2023-01-28
from pyparsing import printables, oneOf, OneOrMore,Optional, ParseResults, Suppress,White, Word, delimitedList

def show_result(title:str,result:ParseResults):
  """
  show pyparsing result details
  
  Args:
     result(ParseResults)
  """
  print(f"result for {title}:")
  print(f"  result:{result}")
  print(f"  dump:{result.dump()}")
  print(f"  asDict:{result.asDict()}")
  print(f"  asList:{result.asList()}")
  # asXML is deprecated and doesn't work any more
  # print(f"asXML:{result.asXML()}")

notes_text="Name:Test•Title: Test•Keywords: A,B,C"
comma_text="A,B,C"

keys={
  "Name": "name",
  "Titel": "title",
  "Keywords": "keywords",
}
keywords=list(keys.keys())
runDelim="•"

comma_separated_values=delimitedList(Word(printables+" ", exclude_chars=",:"),delim=",")("clist")

cresult=comma_separated_values.parseString(comma_text)
show_result("comma separated values",cresult)

grammar=delimitedList(
   oneOf(keywords,as_keyword=True)
  +Suppress(":"+Optional(White()))
  +comma_separated_values
  ,delim=runDelim
)("namevalues")

nresult=grammar.parseString(notes_text)
show_result("name value list",nresult)

#ogrammar=OneOrMore(
#   oneOf(keywords,as_keyword=True)
#  +Suppress(":"+Optional(White()))
#  +comma_separated_values
#)
#oresult=grammar.parseString(notes_text)
#show_result("name value list with OneOf",nresult)

输出:

result for comma separated values:
  result:['A', 'B', 'C']
  dump:['A', 'B', 'C']
- clist: ['A', 'B', 'C']
  asDict:{'clist': ['A', 'B', 'C']}
  asList:['A', 'B', 'C']
result for name value list:
  result:['Name', 'Test']
  dump:['Name', 'Test']
- clist: ['Test']
- namevalues: ['Name', 'Test']
  asDict:{'clist': ['Test'], 'namevalues': ['Name', 'Test']}
  asList:['Name', 'Test']

第一个结果对我来说是有意义的,第二个结果是不直观的。我期望一个嵌套的结果-一个带有list的dict的dict。

    • 是什么导致了这种不直观的行为?如何缓解?**
1wnzp6jl

1wnzp6jl1#

目前,我使用的是一种简单的变通方法,请参见https://trinket.io/python3/7ccaa91f7e

# Try out parsing name value list
# WF 2023-01-28
import json
notes_text="Name:Test•Title: Test•Keywords: A,B,C"

keys={
  "Name": "name",
  "Title": "title",
  "Keywords": "keywords",
}
result={}
key_values=notes_text.split("•")
for key_value in key_values:
  key,value=key_value.split(":")
  value=value.strip()
  result[keys[key]]=value # could do another split here if need be
  
print(json.dumps(result,indent=2))

输出:

{
  "name": "Test",
  "title": "Test",
  "keywords": "A,B,C"
}
zbdgwd5y

zbdgwd5y2#

语法问题是:您将OneOrMore封装在delimited_list中,并且只需要外部的一个,并且没有告诉解析器需要如何构造数据以给予名称意义。
您也不需要空白抑制,因为它是自动的。
将parse_all添加到parse_string函数将有助于查看哪些地方没有消耗所有内容。

name_values_grammar = pp.delimited_list(
        pp.Group(
                pp.oneOf(keywords,as_keyword=True).setResultsName("key",list_all_matches=True)
                + pp.Suppress(pp.Literal(':'))
                + pp.delimited_list(
                    pp.Word(pp.printables, exclude_chars=':,').setResultsName('value', list_all_matches=True)
                    , delim=',')
            )
            , delim='•'
        ).setResultsName('tag', list_all_matches=True)
  • 我应该使用parse操作吗?* 正如你所看到的,从技术上讲,你不需要这样做,但是你最终得到的数据结构对于你想要的东西来说可能效率较低。如果语法变得更复杂,我认为使用一些parse操作是有意义的。看看下面的一些例子来Map键名(只有当它们被找到时),并清理列表解析以获得更复杂的语法。
  • 如何命名部分结果?* 默认情况下,在ParseResults对象中,当您请求名称时,将返回最后一个标有名称的部分。使用list_all_matches请求返回所有匹配项只对一些简单结构有用,但它确实有效。请参阅下面的示例。
  • 结果树的导航是如何完成的?* 默认情况下,所有内容都是扁平的。您可以使用pyparsing.Group来告诉解析器不要将其内容扁平化到父列表中(从而保留有用的结构和部分名称)。
  • 如何从delimitedList中获取列表?* 如果您不将delimited_list结果 Package 到另一个列表中,则所做的扁平化操作将删除该结构。请再次解析内部结构上的actions或Group以进行救援。
  • list_all_matches=True实现了什么-它的行为看起来很奇怪 * 它看起来很奇怪是语法结构的一个函数。
import pyparsing as pp

print(
    pp.delimited_list(
            pp.Word(pp.printables, exclude_chars=',').setResultsName('word', list_all_matches=True)
        ).parse_string('x,y,z').dump()
    )

print(
    pp.delimited_list(
                pp.Word(pp.printables, exclude_chars=':,').setResultsName('key', list_all_matches=True)
                + pp.Suppress(pp.Literal(':'))
                + pp.Word(pp.printables, exclude_chars=':,').setResultsName('value', list_all_matches=True)
        )
        .parse_string('x:a,y:b,z:c').dump()
    )

print(
    pp.delimited_list(
        pp.Group(
                pp.Word(pp.printables, exclude_chars=':,').setResultsName('key', list_all_matches=True)
                + pp.Suppress(pp.Literal(':'))
                + pp.Word(pp.printables, exclude_chars=':,').setResultsName('value', list_all_matches=True)
            )
        ).setResultsName('tag', list_all_matches=True)
        .parse_string('x:a,y:b,z:c').dump()
    )

第一个有意义,它给了你一个你所期望的所有标记的列表;第三个也有意义,因为你有一个你可以走的结构;但是第二个你最终得到了两个列表,这两个列表(在一个更复杂的语法中)不一定很容易匹配。
这里有一种构建语法的不同方法,它支持用引号括起带有分隔符的字符串,这样它们就不会变成列表,也不会变成不在Map中的关键字。如果没有解析操作,要做到这一点就比较困难了。

import pyparsing as pp
import json

test_string = "Name:Test•Title: Test•Extra: '1,2,3'•Keywords: A,B,C,'D,E',F"

keys={
  "Name": "name",
  "Title": "title",
  "Keywords": "keywords",
}

g_key = pp.Word(pp.alphas)
g_item = pp.Word(pp.printables, excludeChars='•,\'') | pp.QuotedString(quote_char="'")
g_value = pp.delimited_list(g_item, delim=',')
l_key_value_sep = pp.Suppress(pp.Literal(':'))
g_key_value = g_key + l_key_value_sep + g_value
g_grammar = pp.delimited_list(g_key_value, delim='•')

g_key.add_parse_action(lambda x: keys[x[0]] if x[0] in keys else x)
g_value.add_parse_action(lambda x: [x] if len(x) > 1 else x)
g_key_value.add_parse_action(lambda x: (x[0], x[1].as_list()) if isinstance(x[1],pp.ParseResults) else (x[0], x[1]))

key_values = dict()
for k,v in g_grammar.parse_string(test_string, parse_all=True):
    key_values[k] = v

print(json.dumps(key_values, indent=2))

相关问题