【问题标题】:Parsing xdot draw attributes with pyparsing使用 pyparsing 解析 xdot 绘制属性
【发布时间】:2012-03-27 23:04:02
【问题描述】:

PyParsing 新手。我正在尝试解决如何解析 xdot 文件中的绘图(和类似)属性。有许多项目的后续元素的数量在开始时以整数形式给出 - 类似于 NetStrings。我查看了一些示例代码来处理类似网络字符串的构造,但它似乎对我不起作用。

以下是一些示例:

3个点的多边形(P后面的3表示跟随的点数):
P 3 811 190 815 180 806 185应该解析为'P', [[811, 190], [815, 180], [806, 185]]

具有 2 个点的多边形:
P 2 811 190 815 180 806 185 应解析为 'P', [[811, 190], [815, 180]](末尾带有未解析的文本)

笔填充颜色(C后面的4表示'-'后面要消耗的字符数):
C 4 -blue应该解析为'C', 'blue'


更新信息:
我认为我在没有更多上下文的情况下将这些示例放在自己的行中是一种误导。这是一个真实的例子:

S 5 -solid S 15 -setlinewidth(1) c 5 -black C 5 -black P 3 690 181 680 179 687 187

请参阅http://www.graphviz.org/doc/info/output.html#d:xdot 了解实际规格。

请注意,文本字段中可能存在大量空格 - 上面的 setlinewidth(1) 可以是“abcd efgh hijk”,只要它正好是 15 个字符,就应该与“S”标签链接。在“P”标签之后应该正好有 7 个数字(初始计数器 + 3 对),其他任何东西都应该引发解析错误,因为后面可能有更多标签(在同一行上),但数字本身不是有效。

希望这能让事情变得更清楚一些。

【问题讨论】:

  • 经过一番思考,我想出了一个答案(如下所示)。仍然很想听听其他意见,如果有更好的方法。尽管如此,我对 PyParsing 还是很满意——即使我下面的结果(仍然有点“手动”)比“手工”编写(和阅读)要容易得多。
  • 所以P 2 811 190 815 180 806 185 引发了一个解析错误,不像您之前所说的“末尾有未解析的文本”?
  • @Hooked:很抱歉 - 我试图让事情保持简单,当我自己测试时,得到我想要的结果而不担心解析是有意义的错误。但是S 5 -solid P 1 690 181 680 179 C 4 -blue 确实应该在 680 处给出解析错误(我认为是第 24 列)。

标签: pyparsing


【解决方案1】:

嗯,这就是我最后想出的,使用scanString。

int_ = Word(nums).setParseAction(lambda t: int(t[0]))
float_ = Combine(Word(nums) + Optional('.' + ZeroOrMore(Word(nums, exact=1)))).setParseAction(lambda t: float(t[0]))
point = Group(int_ * 2 ).setParseAction(lambda t: tuple(t[0]))
ellipse = ((Literal('E') ^ 'e') + point + int_ + int_).setResultsName('ellipse')
n_points_start =  (Word('PpLBb', exact=1) + int_).setResultsName('n_points')
text_start = ((('T' + point + int_*3 ) ^ ('F' + float_ + int_) ^ (Word('CcS') + int_) ) + '-').setResultsName('text')
xdot_attr_parser = ellipse ^ n_points_start ^ text_start

def parse_xdot_extended_attributes(data):
    results = []
    while True:
        try:
            tokens, start, end = xdot_attr_parser.scanString(data, maxMatches = 1).next()
            data = data[end:]
            name = tokens.getName()
            if name == 'n_points':
                number_to_get = int(tokens[-1])
                points, start, end = (point * number_to_get).scanString(data, maxMatches = 1).next()
                result = tokens[:1]
                result.append(points[:])
                results.append(result)
                data = data[end:]
            elif name == 'text':
                number_to_get = int(tokens[-2])
                text, data = data[:number_to_get], data[number_to_get:]
                result = tokens[:-2]
                result.append(text)
                results.append(result)
            else:
                results.append(tokens)
        except StopIteration:
            break
    return results

【讨论】:

    【解决方案2】:

    针对 OP 的编辑,下面的答案不再完整。

    我将尝试在这里找到您问题的核心,而忽略更精细的细节。希望它能让你在语法的其余部分走上正轨。考虑到这两行,基本上你是在问:

    P 3 811 190 815 180 806 185
    P 2 811 190 815 180 806 185
    

    如何解析数据,以便在第二行中只读取两个点?就个人而言,我会阅读所有的数据并进行后解析。如果您为结果命名,您可以让自己的工作变得无比轻松。例如:

    from pyparsing import *
    
    EOL = LineEnd().suppress()
    
    number = Word(nums).setParseAction(lambda x: int(x[0]))
    point_pair = Group(number + number)
    
    poly_flag  = Group(Literal("P") + number("length"))("flag")
    poly_type  = poly_flag + Group(OneOrMore(point_pair))("data")
    
    xdot_line = Group(poly_type) + EOL
    grammar   = OneOrMore(xdot_line)
    

    请注意,我们有一个 data, flaglength 名称,稍后会派上用场。让我们解析和处理字符串:

    S = "P 3 811 190 815 180 806 185\nP 2 811 190 815 180 806 185\n"
    P = grammar.parseString(S)
    
    for line in P:
        L = line["flag"]["length"]  
        while len(line["data"]) > L: 
            line["data"].pop()
    

    给出有用且结构化的结果:

    [['P', 3], [[811, 190], [815, 180], [806, 185]]]
    [['P', 2], [[811, 190], [815, 180]]]
    

    扩展语法

    从这里,您可以一个接一个地独立构建语法片段。每次添加新类型时,将其添加到xdot_line,即

    xdot_line = Group(poly_type | pen_fill_type) + EOL
    

    【讨论】:

    • +1 用于使用结果名称。我个人更喜欢 dotted 属性表示法而不是 dict 表示法,允许您编写 line.flag.lengthline.data
    • @PaulMcGuire 我认为它们都有自己的用途,在这种情况下,点分表示法可能更清晰,但我经常从函数调用中传递结果名称,从而使 dict 表示法有用。
    • @PaulMcGuire 是所有事物的常驻专家pyparsing,感谢您在本网站上提供的所有帮助!我很想知道是否有一种方法可以像 OP 似乎想要的那样消耗下一个 n 字符(包括空格),其中 n 是从先前的令牌中读取的。
    • @PaulMcGuire:是的,这是真正的问题。有没有办法使用下一个“n”个字符(或更一般地说,n 个标记),其中 n 是从先前的标记中读取的,而不使用我使用的 scanString 方法。
    • countedArray(expr) 助手读取前导整数“n”,后跟“n”expr 表达式,通过对变量重复部分使用强制转发表达式。我刚刚尝试了一个疯狂的实验并且它有效 - 尝试使用 CharsNotIn("",exact=n) 来代替 n*expr 变量重复。也就是说,提取countedArray的代码并编写自己的派生词,也许叫它countedChars
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多