从一串多个令牌中构建一个pyparsing.Dict - 第二部分

时间:2018-03-14 18:14:13

标签: python pyparsing

由于本论坛的反馈,我已经取得了一些进展(感谢论坛!)。 pyparsing.Dict对象dicct正在填充,但在找到十进制数时会无声地失败。

下式给出:

import pyparsing as pp

lines = '''\
(rate multiple)
(region "mountainous")
(elev       21439)
(alteleva  +21439)
(altelevb  -21439)
(coorda  23899.747)
(coordb +23899.747)
(coordc -23899.747)
(coordd  853.324e21)
(coorde +853.324e21)
(coordf -853.324e21)
(coordg  987.88e+09)
(coordh +987.88e+09)
(coordi -987.88e+09)
(coordj  122.45e-04)
(coordk +122.45e-04)
(coordl -122.45e-04)
'''

leftParen    = pp.Literal('(')
rightParen   = pp.Literal(')')
colon        = pp.Literal(':')
decimalpoint = pp.Literal('.')
doublequote  = pp.Literal('"')
plusorminus  = pp.Literal('+') | pp.Literal('-') 
exp          = pp.CaselessLiteral('E')

v_string = pp.Word(pp.alphanums)
v_quoted_string = pp.Combine( doublequote + v_string + doublequote)
v_number = pp.Regex(r'[+-]?(?P<float1>\d+)(?P<float2>\.\d+)?(?P<float3>[Ee][+-]?\d+)?')

keyy = v_string
valu = v_string | v_quoted_string | v_number

item  = pp.Group( pp.Literal('(').suppress() + keyy + valu + pp.Literal(')').suppress() )
items = pp.ZeroOrMore( item)
dicct = pp.Dict( items)

print "dicct yields: ",  dicct.parseString( lines).dump()

产量

- alteleva: '+21439',
- altelevb: '-21439',
- elev: '21439',
- rate: 'multiple',
- region: '"mountainous"'

改变令牌的顺序证明当脚本遇到第一个十进制数时,脚本会无声地失败,这意味着pp.Regex语句出现了一些巧妙的错误,但我确定无法发现它。 / p>

TIA,

code_warrior

1 个答案:

答案 0 :(得分:1)

你的问题实际上在于这个表达式:

valu = v_string | v_quoted_string | v_number

因为v_string被定义为非常广泛匹配的表达式:

v_string = pp.Word(pp.alphanums)

因为它是valu中的第一个表达式,它将屏蔽以数字开头的v_numbers。这是因为'|'运算符生成pp.MatchFirst个对象,因此匹配的第一个表达式(从左到右读取)将确定使用哪个替代项。您可以转换为使用生成pp.Or个对象的'^'运算符 - Or类将尝试评估所有备选方案,然后使用最长匹配。但请注意,使用Or会带来性能损失,因为即使没有混淆的机会,也会有更多表达式测试匹配。在您的情况下,您可以重新排序表达式以将最不具体的匹配表达式放在最后:

valu = v_quoted_string | v_number | v_string

现在,首先尝试解析值作为带引号的字符串,然后作为数字进行解析,然后仅当这些特定类型与这两种特定类型都不匹配时才会被解析,作为非常通用的类型v_string

其他一些评论:

我个人更喜欢解析引用的字符串,只获得引号内的内容(这是一个字符串,我已经知道了!)。在显示解析后的字符串而没有任何封闭引号的情况下,在转出解析结果时,曾经与旧版本的pyparsing存在一些混淆。但是现在我使用repr()来显示已解析的值,在调用dump()时字符串会显示在引号中,但值本身不包含引号。当它在程序的其他地方使用时,比如保存到数据库或CSV,我不需要引号,我只想要字符串内容。默认情况下,QuotedString类会为我处理此问题。或者使用pp.quotedString().addParseAction(pp.removeQuotes)

最近的一个pyparsing版本引入了pyparsing_common命名空间类,其中包含许多有用的预定义表达式。有几种用于解析不同的数字类型(整数,有符号整数,实数等),以及几个一揽子表达式:number将解析任何数字类型,并生成相应类型的值({{1}将给出一个浮点数,real将给出一个int等等); integer将解析各种数字,但将它们全部作为浮点数返回。我已将fnumber表达式替换为v_number,这也允许我删除为构建pp.pyparsing_common.number()表达式而定义的其他几个部分表达式,例如v_numberdecimalpointplusorminus。您可以在在线文档exp

中查看pyparsing_common中表达式的更多信息

"(" + pp.Word(pp.alphas) + valu + ")"这样的表达式中处理文字字符串时,Pyparsing的默认行为是自动将文字“(”和“)”术语转换为pp.Literal个对象。这可以防止意外丢失已解析的数据,但在标点符号的情况下,您最终会在解析的结果中出现许多混乱且无用的额外字符串。在您的解析器中,您可以通过调用pp.ParserElement.inlineLiteralsUsing并传递pp.Suppress类来替换pyparsing的默认值:

pp.ParserElement.inlineLiteralsUsing(pp.Suppress)

现在你可以写一个像:

这样的表达式
item  = pp.Group('(' + keyy + valu + ')')

将从解析结果中删除分组括号。

进行这些更改后,您的解析器现在简化为:

import pyparsing as pp

# override pyparsing default to suppress literal strings in expressions
pp.ParserElement.inlineLiteralsUsing(pp.Suppress)

v_string = pp.Word(pp.alphanums)
v_quoted_string = pp.QuotedString('"')
v_number = pp.pyparsing_common.number()

keyy = v_string
# define valu using least specific expressions last
valu = v_quoted_string | v_number | v_string

item  = pp.Group('(' + keyy + valu + ')')
items = pp.ZeroOrMore( item)
dicct = pp.Dict( items)

print ("dict yields: ",  dicct.parseString( lines).dump())

对于您的测试输入,给出:

dict yields:  [['rate', 'multiple'], ['region', 'mountainous'], ['elev', 21439], 
['alteleva', 21439], ['altelevb', -21439], ['coorda', 23899.747], ['coordb', 
23899.747], ['coordc', -23899.747], ['coordd', 8.53324e+23], ['coorde', 
8.53324e+23], ['coordf', -8.53324e+23], ['coordg', 987880000000.0], ['coordh', 
987880000000.0], ['coordi', -987880000000.0], ['coordj', 0.012245], ['coordk', 
0.012245], ['coordl', -0.012245]]
- alteleva: 21439
- altelevb: -21439
- coorda: 23899.747
- coordb: 23899.747
- coordc: -23899.747
- coordd: 8.53324e+23
- coorde: 8.53324e+23
- coordf: -8.53324e+23
- coordg: 987880000000.0
- coordh: 987880000000.0
- coordi: -987880000000.0
- coordj: 0.012245
- coordk: 0.012245
- coordl: -0.012245
- elev: 21439
- rate: 'multiple'
- region: 'mountainous'