将列表列表变为pyparsing.Dict结构

时间:2018-04-01 07:30:28

标签: pyparsing

下式给出:

innput = '''
(crossSectionDescriptor
    (layer 0
        (layerType SURFACE)
        (layerMaterial "AIR")
        (layerThickness 0)
        (layerElectricalConductivity 0)
        (layerThermalConductivity 0.00027)
        (layerDielectricConstant 1.000000)
    )
    (layer 1
        (layerType CONDUCTOR)
        (layerName "TOP")
        (layerMaterial "COPPER")
        (layerThickness 1.200000)
        (layerElectricalConductivity 595900.000000)
        (layerThermalConductivity 3.980000)
        (layerDielectricConstant 1.000000)
        (layerArtworkNegativeFlag FALSE)
        (layerIsShieldFlag FALSE)
    )
    (layer 2
        (layerType DIELECTRIC)
        (layerMaterial "FR-4")
        (layerThickness 8.000000)
        (layerElectricalConductivity 0)
        (layerThermalConductivity 0.012)
        (layerDielectricConstant 4.500000)
        (layerLossTangent 0.035)
    )
'''
innput_forced = '''
(crossSectionDescriptor
    (layer
        (0
            (layerType SURFACE)
            (layerMaterial "AIR")
            (layerThickness 0)
            (layerElectricalConductivity 0)
            (layerThermalConductivity 0.00027)
            (layerDielectricConstant 1.000000)
        )
        (1
            (layerType CONDUCTOR)
            (layerName "TOP")
            (layerMaterial "COPPER")
            (layerThickness 1.200000)
            (layerElectricalConductivity 595900.000000)
            (layerThermalConductivity 3.980000)
            (layerDielectricConstant 1.000000)
            (layerArtworkNegativeFlag FALSE)
            (layerIsShieldFlag FALSE)
        )
        (2
            (layerType DIELECTRIC)
            (layerMaterial "FR-4")
            (layerThickness 8.000000)
            (layerElectricalConductivity 0)
            (layerThermalConductivity 0.012)
            (layerDielectricConstant 4.500000)
            (layerLossTangent 0.035)
        )
    )
)
'''

def adjustmentNeeded( toks): # s, loc, toks are implicit
    adjustmentNeeded = True
    nodeNameSet = set()
    for tok in toks:
        if type( tok) is pp.ParseResults \
       and len( tok) > 2 \
       and type( tok[0]) is not pp.ParseResults \
       and type( tok[1]) is not pp.ParseResults \
           and restAreLists( tok[2:]):
              nodeNameSet.add( tok[0]) # silently rejects duplicates
        else:
            adjustmentNeeded = False
       #    break
    if adjustmentNeeded:
        # all tokens are lists of length 3 or more where the first element is the same value and the second element is not a list
        new_toks = [ nodeNameSet.pop()]
        for tok in toks:
            tok.pop(0) # removes redundant layer
            new_toks.append( tok.asList() )
        return( pp.ParseResults( new_toks ) )
    else:
        return( toks)

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

nodeName = v_string
keyy = v_string
valu = pp.Or( [ v_string, v_quoted_string, v_number])

item  = pp.Group( pp.Literal('(').suppress() + keyy + pp.OneOrMore( valu) + pp.Literal(')').suppress() )
node = pp.Forward()  # recursive structure
node << pp.Dict( pp.Group( pp.Literal('(').suppress() + \
                                  nodeName + \
                                  pp.Optional( valu)('valu') + \
                                  pp.Dict( pp.OneOrMore( item ^ dict_node)).setParseAction( adjustmentNeeded) + \
                                  pp.Literal(')').suppress()
                        )
               ) #.setParseAction( makeAdjustments)

pprinter = pprint.PrettyPrinter( indent=1)

现在使用这个:

pprinter.pprint(node.parseString(innput).asDict())

产量

{'crossSectionDescriptor': ['layer',
                            ['0',
                             ['layerType', 'SURFACE'],
                             ['layerMaterial', '"AIR"'],
                             ['layerThickness', '0'],
                             ['layerElectricalConductivity', '0'],
                             ['layerThermalConductivity', '0.00027'],
                             ['layerDielectricConstant', '1.000000']],
                            ['1',
                             ['layerType', 'CONDUCTOR'],
                             ['layerName', '"TOP"'],
                             ['layerMaterial', '"COPPER"'],
                             ['layerThickness', '1.200000'],
                             ['layerElectricalConductivity',
                              '595900.000000'],
                             ['layerThermalConductivity', '3.980000'],
                             ['layerDielectricConstant', '1.000000'],
                             ['layerArtworkNegativeFlag', 'FALSE'],
                             ['layerIsShieldFlag', 'FALSE']],
                            ['2',
                             ['layerType', 'DIELECTRIC'],
                             ['layerMaterial', '"FR-4"'],
                             ['layerThickness', '8.000000'],
                             ['layerElectricalConductivity', '0'],
                             ['layerThermalConductivity', '0.012'],
                             ['layerDielectricConstant', '4.500000'],
                             ['layerLossTangent', '0.035']]]}    

令人心碎地接近我想要的

这里的目标是创建一系列嵌套的dicts。我添加了一个setParseAction函数来按摩节点&#39;子节点变成适当的词组。

这只是一个更大文件的一小部分 - 我不能假设这个&#39;层&#39;将始终是恒定的第一元素,因此无法对其进行编码。事实上,如果有一种方法可以使用pp.addCondition()来表示&#34;一个或多个dict_nodes,其中每个节点具有相同的第一个标记&#34;,这可能是有用的。

任何建议,指示或建设性批评都是值得赞赏的。

TIA,

code_warrior

1 个答案:

答案 0 :(得分:0)

定义node_name,以便识别您的“图层#”表单,并只返回图层编号:

layer_num = pp.Suppress('layer') + pp.Word(pp.nums)
nodeName = layer_num | v_string

使用'|'运算符为您提供MatchFirst,您强制pyparsing首先尝试layer_num表达式,如果v_string表达式失败,则只接受layer_num

您还可以通过定义layer_num来保留索引的“图层”部分,以返回字符串'layer _#':

layer_num = ('layer' + pp.Word(pp.nums)).setParseAction('_'.join)

这将为您提供'layer_0','layer_1'等

的图层索引

其他一些提示,可能适用也可能不适用,我不完全了解您的数据:

试试v_string

# v_string = pp.Word(pp.alphanums+'_'+'-'+'.')
v_string = pp.Word(pp.alphas, pp.alphanums + '_-.')

只用一个参数定义v_string的方式,你会接受像'.....','。 - .-。--- ....-。-__'这样的字符串,'234234.02340'。 2参数形式强制v_strings以字母字符开头,这更有可能防止其他不需要的形式(仍然会接受类似'abc .....'的东西,但这也可以解决)。

Pyparsing有几个提供的字符串表达式:

  • sglQuotedString - 单引号中的字符串
  • dblQuotedString - 双引号中的字符串
  • quotedString - 单引号或双引号中的字符串

这些表达式也包括封闭引号,与QuotedString类不同。如果要删除它们,请添加pyparsing parse操作removeQuotes

# v_quoted_string = pp.Combine( '"' + v_string + '"')
v_quoted_string = pp.dblQuotedString

新的pyparsing_common命名空间类包含几种匹配常用表达式的格式,包括整数,实数,IP地址等。数值表达式是使用转换解析操作定义的,因此解析后的值已经转换为Python整数和浮点数。 pyparsing_common还包括两个任意数字表达式:

  • number - 匹配int或real forms,并返回相应类型的值
  • fnumber - 匹配int或real forms,并将所有值作为float返回

您的v_number可以替换为:

# v_number = pp.Regex(r'[+-]?(?P<float1>\d+)(?P<float2>\.\d+)?(?P<float3>[Ee][+-]?\d+)?')
v_number = pp.pyparsing_common.fnumber()

还有一个额外好处,即数值将在分析时转换为实际的浮点数。

沿着类似的行,你可以定义一个布尔类型来匹配TRUEFALSE等值:

v_boolean = pp.Keyword('TRUE') | pp.Keyword('FALSE')
v_boolean.addParseAction(lambda t: t[0] == 'TRUE')

解析操作将'TRUE'转换为Python True,将'FALSE'转换为Python False。然后将其拼接到valu

的表达式中
valu = v_quoted_string  | v_number | v_boolean | v_string

最后,我一直偏爱MatchFirst而不是,因为我认为大多数情况下有替代方案,如果按正确的顺序进行测试,它们可以毫不含糊地解决。这是我的valu版本:

# valu = pp.Or( [ v_string, v_quoted_string, v_number])
valu = v_quoted_string | v_number | v_string
valu = pp.MatchFirst([v_quoted_string, v_number, v_string]) # if you prefer

由于v_numberv_string不接受引号,因此在预定数字或不带引号的字符串时,首先检查带引号的字符串是不会意外匹配的。类似地,在检查字符串之前检查数字表单将选择更具体的表达式,而不是全部匹配 - 任意组合的字母 - 数字 - 点 - 破折号或下划线。相反,Or将始终测试每个给定的表达式,并选择最长的匹配。我发现这很浪费 - 如果你的valu遇到一个带引号的字符串,它仍然会尝试将其解析为数字和不带引号的字符串,即使这些匹配是不可能的。

另外,我个人的口味是使用pyparsing定义的运算符:'|'适用于MatchFirst,'{'代表Or,'+'代表And,'&amp;'对于Each,{〜}代表NotAny。对我来说,它使语法更类似于BNF,并且比带有表达式列表的显式类构造调用更具可读性。但这纯粹是我自己的风格 - 你编写代码的方式没有任何问题。