解析xdot使用pyparsing绘制属性
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对),其他任何东西都应该引发一个分析错误,因为后面可能有更多的标签(在同一行),但是数字本身并不是有效。
希望能让事情变得更清楚一些。
好吧,这就是我最后想到的,使用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
为了回应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, flag
和length
的名字,这将在以后派上用场。让我们来解析和处理字符串:
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。我个人比字典符号更喜欢虚线的属性符号,允许你写''line.flag.length''和''line.data''。 – PaulMcG 2012-03-29 12:29:02
@PaulMcGuire我认为他们都有他们的用途,在这种情况下,虚线符号可能更清晰,但我经常从函数调用传递结果名称,使字典符号有用。 – Hooked 2012-03-29 13:48:43
@PaulMcGuire是'pyparsing'所有东西的常驻专家,非常感谢您在本网站上提供的所有帮助!我很想知道是否有一种方法可以像OP那样使用下一个'n'字符(包含空格),其中'n'是从先前的标记中读取的。 – Hooked 2012-03-29 13:51:04
经过一番思考,我想出了一个答案(下面给出)。如果有更好的方法,我们还是乐意听取其他意见。 尽管如此,我对PyParsing非常满意 - 即使我的结果如下(仍然有点“手动”),比手工编写(和阅读)要容易得多。 – 2012-03-28 10:40:54
因此'P 2 811 190 815 180 806 185'会产生一个解析错误,而不是像之前所说的“最后未解析的文本”? – Hooked 2012-03-28 15:51:27
@Hooked:抱歉 - 我试图让事情变得简单,当我只是在自己测试一些东西时,只是为了得到我期待的结果而不用担心解析错误。但是'S 5 -solid P 1 690 181 680 179 C 4 -blue'应该在680(我认为是第24列)中给出了一个解析错误。 – 2012-03-28 22:19:14