首页 > 解决方案 > 在pyparsing中使用特定长度的字段标记字符串

问题描述

我正在为 ascii 数据编写一个简单的解析器,其中每一行都必须被解释为 8 个字符块的字段:

"""
|--1---||--2---||--3---||--4---||--5---||--6---||--7---||--8---||--9---|
GRID         119           18.27  562.33  528.87
"""

这一行,应解释为:

1: GRID + 4 blank spaces
2: 5 blank spaces + 119
3: 8 blank spaces
4: 3 blank spaces + 18.27
5: 2 blank spaces + 562.33
6: 2 blank spaces + 528.87
7: 8 blank spaces
8: 8 blank spaces
9: 8 blank spaces

这是我尝试过的

EOL = LineEnd().suppress()
card_keyword = Keyword("GRID").leaveWhitespace().suppress()
number_card_fields = (number + ZeroOrMore(White()))
empty_card_fields = 8 * White()
card_fields = (number_card_fields | empty_card_fields)
card = (card_keyword + OneOrMore(card_fields)).setParseAction(self._card_to_dict)


def _card_to_dict(self, toks):
    _FIELDS_MAPPING = {
        0: "id", 1: "cp", 2: "x1", 3: "x2", 4: "x3", 5: "cd", 6: "ps", 7: "seid"
    }
    mapped_card = {self._FIELDS_MAPPING[idx]: token_field for idx, token_field in enumerate(toks)}
    return mapped_card

test2 = """
GRID         119           18.27  562.33  528.87                        
"""
print(card.searchString(test2))

这次回归

[[{'id': 119, 'cp': '           ', 'x1': 18.27, 'x2': '  ', 'x3': 562.33, 'cd': '  ', 'ps': 528.87, 'seid': '                        \n'}]]

我想获得这个,而不是

[[{'id': 119, 'cp': '        ', 'x1': 18.27, 'x2': 562.33, 'x3': 528.87, 'cd': '        ', 'ps': '        ', 'seid': '        '}]]

我认为问题就在这里number_card_fields = (number + ZeroOrMore(White()))。我不知道如何告诉 pyparsing 这个表达式必须是 8 个字符长。

有人可以帮助我吗?提前感谢您的宝贵支持

标签: pythonpyparsing

解决方案


Pyparsing 允许您指定精确长度的单词。由于您的行是固定大小的字段,因此您的“单词”由任何可打印或空格字符组成,精确大小为 8:

field = Word(printables + " ", exact=8)

这是您的输入行的解析器:

import pyparsing as pp
# clear out whitespace characters - pretty much disables whitespace skipping
pp.ParserElement.setDefaultWhitespaceChars('')

# define an expression that matches exactly 8 printable or space characters
field = pp.Word(pp.printables + " ", exact=8).setName('field')

# a line has one or more fields
parser = field[1, ...]

# try it out
line = "GRID         119           18.27  562.33  528.87"

print(parser.parseString(line).asList())

印刷:

['GRID    ', '     119', '        ', '   18.27', '  562.33', '  528.87']

我觉得这些空格很烦人,所以我们可以在字段中添加一个解析操作来去除它们:

# add a parse action to field to strip leading and trailing spaces
field.addParseAction(lambda t: t[0].strip())It 
print(parser.parseString(line).asList())

现在给出:

['GRID', '119', '', '18.27', '562.33', '528.87']

看起来您希望总共有 8 个字段,并且您希望将数字字段转换为浮点数。这是您的_card_to_dict解析操作的一个模式:

def str_to_value(s):
    if not s:
        return None
    try:
        return float(s)
    except ValueError:
        return s

def _card_to_dict(toks):
    _FIELDS_MAPPING = {
        0: "id", 1: "cp", 2: "x1", 3: "x2", 4: "x3", 5: "cd", 6: "ps", 7: "seid"
    }

    # this is one way to do it, but you can just add the names to toks
    # mapped_card = {self._FIELDS_MAPPING[idx]: token_field for idx, token_field in enumerate(toks)}
    for idx, token_field in enumerate(toks):
        toks[_FIELDS_MAPPING[idx]] = str_to_value(token_field)

parser.addParseAction(_card_to_dict)
result = parser.parseString(line)

您可以将此结果转换为字典:

print(result.asDict())

印刷:

{'cd': 528.87, 'x2': 18.27, 'id': 'GRID', 'cp': 119.0, 'x1': None, 'x3': 562.33}

如果您使用以下方式转储结果:

print(result.dump())

你会得到:

['GRID', '119', '', '18.27', '562.33', '528.87']
- cd: 528.87
- cp: 119.0
- id: 'GRID'
- x1: None
- x2: 18.27
- x3: 562.33

这显示了如何直接访问解析结果,而无需转换为 dict:

print(result['x2'])
print(result.id)

印刷

18.27
GRID

推荐阅读