2012-03-27 77 views
2

PyParsing的新功能。我试图解决如何解析xdot文件中的绘制(和类似)属性。有许多项目在开始时以整数形式给出了以下元素的数量 - 类似于NetStrings。我已经看过一些示例代码来处理类似构造的网络字符串,但它似乎不适合我。解析xdot使用pyparsing绘制属性

这里有一些样品:

多边形用3点(3 P表示的点的以下的数后):
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]](未解析文本末尾)

笔填色(后4位C表示数字之后的字符“ - ”消费):
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对),其他任何东西都应该引发一个分析错误,因为后面可能有更多的标签(在同一行),但是数字本身并不是有效。

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

+0

经过一番思考,我想出了一个答案(下面给出)。如果有更好的方法,我们还是乐意听取其他意见。 尽管如此,我对PyParsing非常满意 - 即使我的结果如下(仍然有点“手动”),比手工编写(和阅读)要容易得多。 – 2012-03-28 10:40:54

+0

因此'P 2 811 190 815 180 806 185'会产生一个解析错误,而不是像之前所说的“最后未解析的文本”? – Hooked 2012-03-28 15:51:27

+0

@Hooked:抱歉 - 我试图让事情变得简单,当我只是在自己测试一些东西时,只是为了得到我期待的结果而不用担心解析错误。但是'S 5 -solid P 1 690 181 680 179 C 4 -blue'应该在680(我认为是第24列)中给出了一个解析错误。 – 2012-03-28 22:19:14

回答

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 
1

为了回应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 
+0

使用结果名称+1。我个人比字典符号更喜欢虚线的属性符号,允许你写''line.flag.length''和''line.data''。 – PaulMcG 2012-03-29 12:29:02

+0

@PaulMcGuire我认为他们都有他们的用途,在这种情况下,虚线符号可能更清晰,但我经常从函数调用传递结果名称,使字典符号有用。 – Hooked 2012-03-29 13:48:43

+0

@PaulMcGuire是'pyparsing'所有东西的常驻专家,非常感谢您在本网站上提供的所有帮助!我很想知道是否有一种方法可以像OP那样使用下一个'n'字符(包含空格),其中'n'是从先前的标记中读取的。 – Hooked 2012-03-29 13:51:04