9
9
Author: Michael Krause <michael@krause-software.de>
12
__author__ = 'Michael Krause'
12
__author__ = ['Michael Krause', 'Ero Carrera']
13
13
__license__ = 'MIT'
20
21
from pyparsing import __version__ as pyparsing_version
21
from pyparsing import Literal, CaselessLiteral, Word, \
22
Upcase, OneOrMore, ZeroOrMore, Forward, NotAny, \
23
delimitedList, oneOf, Group, Optional, Combine, \
24
alphas, nums, restOfLine, cStyleComment, nums, \
25
alphanums, printables, empty, quotedString, \
26
ParseException, ParseResults, CharsNotIn, _noncomma,\
23
from pyparsing import ( nestedExpr, Literal, CaselessLiteral, Word, Upcase, OneOrMore, ZeroOrMore,
24
Forward, NotAny, delimitedList, oneOf, Group, Optional, Combine, alphas, nums,
25
restOfLine, cStyleComment, nums, alphanums, printables, empty, quotedString,
26
ParseException, ParseResults, CharsNotIn, _noncomma, dblQuotedString, QuotedString, ParserElement )
31
def __init__(self, toks):
37
self.attrs[attrname] = attrvalue
41
return "%s(%r)" % (self.__class__.__name__, self.attrs)
32
def __init__(self, toks):
41
self.attrs[attrname] = attrvalue
46
return "%s(%r)" % (self.__class__.__name__, self.attrs)
44
50
class DefaultStatement(P_AttrList):
45
def __init__(self, default_type, attrs):
46
self.default_type = default_type
50
return "%s(%s, %r)" % \
51
(self.__class__.__name__, self.default_type, self.attrs)
52
def __init__(self, default_type, attrs):
54
self.default_type = default_type
59
return "%s(%s, %r)" % (self.__class__.__name__,
60
self.default_type, self.attrs)
54
65
def push_top_graph_stmt(str, loc, toks):
59
if isinstance(element, ParseResults) or \
60
isinstance(element, tuple) or \
61
isinstance(element, list):
65
if element == 'strict':
66
attrs['strict'] = True
67
elif element in ['graph', 'digraph']:
68
attrs['graph_type'] = element
69
elif type(element) == type(''):
70
attrs['graph_name'] = element
71
elif isinstance(element, pydot.Graph):
72
g = pydot.Graph(**attrs)
73
g.__dict__.update(element.__dict__)
74
for e in g.get_edge_list():
76
for e in g.get_node_list():
78
for e in g.get_subgraph_list():
81
elif isinstance(element, P_AttrList):
82
attrs.update(element.attrs)
84
raise ValueError, "Unknown element statement: %r " % element
87
g.__dict__.update(attrs)
72
if( isinstance(element, (ParseResults, tuple, list)) and
73
len(element) == 1 and isinstance(element[0], basestring) ):
77
if element == 'strict':
78
attrs['strict'] = True
80
elif element in ['graph', 'digraph']:
84
g = pydot.Dot(graph_type=element, **attrs)
85
attrs['type'] = element
87
top_graphs.append( g )
89
elif isinstance( element, basestring):
92
elif isinstance(element, pydot.Subgraph):
94
g.obj_dict['attributes'].update( element.obj_dict['attributes'] )
95
g.obj_dict['edges'].update( element.obj_dict['edges'] )
96
g.obj_dict['nodes'].update( element.obj_dict['nodes'] )
97
g.obj_dict['subgraphs'].update( element.obj_dict['subgraphs'] )
101
elif isinstance(element, P_AttrList):
102
attrs.update(element.attrs)
104
elif isinstance(element, (ParseResults, list)):
105
add_elements(g, element)
108
raise ValueError, "Unknown element statement: %r " % element
112
if len( top_graphs ) == 1:
91
118
def add_defaults(element, defaults):
93
for key, value in defaults.items():
121
for key, value in defaults.items():
98
127
def add_elements(g, toks, defaults_graph=None, defaults_node=None, defaults_edge=None):
100
if defaults_graph is None:
102
if defaults_node is None:
104
if defaults_edge is None:
108
if isinstance(element, pydot.Graph):
109
add_defaults(element, defaults_graph)
110
g.add_subgraph(element)
111
elif isinstance(element, pydot.Node):
112
add_defaults(element, defaults_node)
114
elif isinstance(element, pydot.Edge):
115
add_defaults(element, defaults_edge)
117
elif isinstance(element, ParseResults):
119
add_elements(g, [e], defaults_graph, defaults_node, defaults_edge)
120
elif isinstance(element, DefaultStatement):
121
if element.default_type == 'graph':
122
default_graph_attrs = pydot.Node('graph')
123
default_graph_attrs.__dict__.update(element.attrs)
124
g.add_node(default_graph_attrs)
125
defaults_graph.update(element.attrs)
126
g.__dict__.update(element.attrs)
127
elif element.default_type == 'node':
128
default_node_attrs = pydot.Node('node')
129
default_node_attrs.__dict__.update(element.attrs)
130
g.add_node(default_node_attrs)
131
# defaults_node.update(element.attrs)
132
elif element.default_type == 'edge':
133
default_edge_attrs = pydot.Node('edge')
134
default_edge_attrs.__dict__.update(element.attrs)
135
g.add_node(default_edge_attrs)
136
defaults_edge.update(element.attrs)
138
raise ValueError, "Unknown DefaultStatement: %s " % element.default_type
139
elif isinstance(element, P_AttrList):
140
g.__dict__.update(element.attrs)
142
raise ValueError, "Unknown element statement: %r " % element
145
def push_graph_stmt(str, loc, toks):
147
add_elements(g, toks)
129
if defaults_graph is None:
131
if defaults_node is None:
133
if defaults_edge is None:
136
for elm_idx, element in enumerate(toks):
138
if isinstance(element, (pydot.Subgraph, pydot.Cluster)):
140
add_defaults(element, defaults_graph)
141
g.add_subgraph(element)
143
elif isinstance(element, pydot.Node):
145
add_defaults(element, defaults_node)
148
elif isinstance(element, pydot.Edge):
150
add_defaults(element, defaults_edge)
153
elif isinstance(element, ParseResults):
156
add_elements(g, [e], defaults_graph, defaults_node, defaults_edge)
158
elif isinstance(element, DefaultStatement):
160
if element.default_type == 'graph':
162
default_graph_attrs = pydot.Node('graph', **element.attrs)
163
g.add_node(default_graph_attrs)
165
elif element.default_type == 'node':
167
default_node_attrs = pydot.Node('node', **element.attrs)
168
g.add_node(default_node_attrs)
170
elif element.default_type == 'edge':
172
default_edge_attrs = pydot.Node('edge', **element.attrs)
173
g.add_node(default_edge_attrs)
174
defaults_edge.update(element.attrs)
177
raise ValueError, "Unknown DefaultStatement: %s " % element.default_type
179
elif isinstance(element, P_AttrList):
181
g.obj_dict['attributes'].update(element.attrs)
184
raise ValueError, "Unknown element statement: %r" % element
187
def push_graph_stmt(str, loc, toks):
189
g = pydot.Subgraph('')
190
add_elements(g, toks)
151
194
def push_subgraph_stmt(str, loc, toks):
196
g = pydot.Subgraph('')
164
206
def push_default_stmt(str, loc, toks):
165
# The pydot class instances should be marked as
166
# default statements to be inherited by actual
167
# graphs, nodes and edges.
168
# print "push_default_stmt", toks
169
default_type = toks[0][0]
171
attrs = toks[1].attrs
175
if default_type in ['graph', 'node', 'edge']:
176
return DefaultStatement(default_type, attrs)
178
raise ValueError, "Unknown default statement: %r " % toks
208
# The pydot class instances should be marked as
209
# default statements to be inherited by actual
210
# graphs, nodes and edges.
211
# print "push_default_stmt", toks
213
default_type = toks[0][0]
215
attrs = toks[1].attrs
219
if default_type in ['graph', 'node', 'edge']:
220
return DefaultStatement(default_type, attrs)
222
raise ValueError, "Unknown default statement: %r " % toks
181
225
def push_attr_list(str, loc, toks):
186
231
def get_port(node):
189
if isinstance(node[1], ParseResults):
190
if len(node[1][0])==2:
191
if node[1][0][0]==':':
197
def do_node_ports(n_prev, n_next):
198
port = get_port(n_prev)
200
n_prev_port = ':'+port
204
port = get_port(n_next)
206
n_next_port = ':'+port
210
return (n_prev_port, n_next_port)
234
if isinstance(node[1], ParseResults):
235
if len(node[1][0])==2:
236
if node[1][0][0]==':':
242
def do_node_ports(node):
246
node_port = ''.join( [str(a)+str(b) for a,b in node[1] ] )
213
251
def push_edge_stmt(str, loc, toks):
215
tok_attrs = [a for a in toks if isinstance(a, P_AttrList)]
218
attrs.update(a.attrs)
222
if isinstance(toks[2][0], pydot.Graph):
223
n_next_list = [[n.get_name(),] for n in toks[2][0].get_node_list()]
224
for n_next in [n for n in n_next_list]:
225
n_prev_port, n_next_port = do_node_ports(n_prev, n_next)
226
e.append(pydot.Edge(n_prev[0]+n_prev_port, n_next[0]+n_next_port, **attrs))
228
for n_next in [n for n in tuple(toks)[2::2]]:
229
n_prev_port, n_next_port = do_node_ports(n_prev, n_next)
230
e.append(pydot.Edge(n_prev[0]+n_prev_port, n_next[0]+n_next_port, **attrs))
236
def push_node_stmt(str, loc, toks):
239
attrs = toks[1].attrs
244
if isinstance(node_name, list) or isinstance(node_name, tuple):
246
node_name = node_name[0]
248
n = pydot.Node('"'+node_name+'"', **attrs)
252
def strip_quotes( s, l, t ):
253
return [ t[0].strip('"') ]
253
tok_attrs = [a for a in toks if isinstance(a, P_AttrList)]
256
attrs.update(a.attrs)
260
if isinstance(toks[0][0], pydot.Graph):
262
n_prev = pydot.frozendict(toks[0][0].obj_dict)
264
n_prev = toks[0][0] + do_node_ports( toks[0] )
266
if isinstance(toks[2][0], ParseResults):
268
n_next_list = [[n.get_name(),] for n in toks[2][0] ]
269
for n_next in [n for n in n_next_list]:
270
n_next_port = do_node_ports(n_next)
271
e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs))
273
elif isinstance(toks[2][0], pydot.Graph):
275
e.append(pydot.Edge(n_prev, pydot.frozendict(toks[2][0].obj_dict), **attrs))
277
elif isinstance(toks[2][0], pydot.Node):
281
if node.get_port() is not None:
282
name_port = node.get_name() + ":" + node.get_port()
284
name_port = node.get_name()
286
e.append(pydot.Edge(n_prev, name_port, **attrs))
288
elif isinstance(toks[2][0], type('')):
290
for n_next in [n for n in tuple(toks)[2::2]]:
292
if isinstance(n_next, P_AttrList) or not isinstance(n_next[0], type('')):
295
n_next_port = do_node_ports( n_next )
296
e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs))
298
n_prev = n_next[0]+n_next_port
301
# UNEXPECTED EDGE TYPE
308
def push_node_stmt(s, loc, toks):
311
attrs = toks[1].attrs
316
if isinstance(node_name, list) or isinstance(node_name, tuple):
318
node_name = node_name[0]
320
n = pydot.Node(str(node_name), **attrs)
256
328
graphparser = None
257
330
def graph_definition():
263
lbrace = Literal("{")
264
rbrace = Literal("}")
265
lbrack = Literal("[")
266
rbrack = Literal("]")
267
lparen = Literal("(")
268
rparen = Literal(")")
269
equals = Literal("=")
273
bslash = Literal("\\")
280
strict_ = Literal("strict")
281
graph_ = Literal("graph")
282
digraph_ = Literal("digraph")
283
subgraph_ = Literal("subgraph")
284
node_ = Literal("node")
285
edge_ = Literal("edge")
290
identifier = Word(alphanums + "_" ).setName("identifier")
292
double_quoted_string = dblQuotedString
294
alphastring_ = OneOrMore(CharsNotIn(_noncomma))
296
ID = (identifier | double_quoted_string.setParseAction(strip_quotes) |\
297
alphastring_).setName("ID")
299
html_text = Combine(Literal("<<") + OneOrMore(CharsNotIn(",]")))
301
float_number = Combine(Optional(minus) + \
302
OneOrMore(Word(nums + "."))).setName("float_number")
304
righthand_id = (float_number | ID | html_text).setName("righthand_id")
306
port_angle = (at + ID).setName("port_angle")
308
port_location = (Group(colon + ID) | \
309
Group(colon + lparen + ID + comma + ID + rparen)).setName("port_location")
311
port = (Group(port_location + Optional(port_angle)) | \
312
Group(port_angle + Optional(port_location))).setName("port")
314
node_id = (ID + Optional(port))
315
a_list = OneOrMore(ID + Optional(equals.suppress() + righthand_id) + \
316
Optional(comma.suppress())).setName("a_list")
318
attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) + \
319
rbrack.suppress()).setName("attr_list")
321
attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt")
323
edgeop = (Literal("--") | Literal("->")).setName("edgeop")
325
stmt_list = Forward()
326
graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) + \
327
rbrace.suppress()).setName("graph_stmt")
329
subgraph = (Group(Optional(subgraph_ + Optional(ID)) + graph_stmt) | \
330
Group(subgraph_ + ID)).setName("subgraph")
332
edgeRHS = OneOrMore(edgeop + Group(node_id | subgraph))
334
edge_stmt = Group(node_id | subgraph) + edgeRHS + Optional(attr_list)
336
node_stmt = (node_id + Optional(attr_list) + Optional(semi.suppress())).setName("node_stmt")
338
assignment = (ID + equals.suppress() + righthand_id).setName("assignment")
339
stmt = (assignment | edge_stmt | attr_stmt | subgraph | node_stmt).setName("stmt")
340
stmt_list << OneOrMore(stmt + Optional(semi.suppress()))
342
graphparser = (Optional(strict_) + Group((graph_ | digraph_)) + \
343
Optional(ID) + graph_stmt).setResultsName("graph")
345
singleLineComment = "//" + restOfLine
350
graphparser.ignore(singleLineComment)
351
graphparser.ignore(cStyleComment)
353
assignment.setParseAction(push_attr_list)
354
a_list.setParseAction(push_attr_list)
355
edge_stmt.setParseAction(push_edge_stmt)
356
node_stmt.setParseAction(push_node_stmt)
357
attr_stmt.setParseAction(push_default_stmt)
359
subgraph.setParseAction(push_subgraph_stmt)
360
graph_stmt.setParseAction(push_graph_stmt)
361
graphparser.setParseAction(push_top_graph_stmt)
338
lbrace = Literal("{")
339
rbrace = Literal("}")
340
lbrack = Literal("[")
341
rbrack = Literal("]")
342
lparen = Literal("(")
343
rparen = Literal(")")
344
equals = Literal("=")
348
bslash = Literal("\\")
355
strict_ = CaselessLiteral("strict")
356
graph_ = CaselessLiteral("graph")
357
digraph_ = CaselessLiteral("digraph")
358
subgraph_ = CaselessLiteral("subgraph")
359
node_ = CaselessLiteral("node")
360
edge_ = CaselessLiteral("edge")
365
identifier = Word(alphanums + "_" ).setName("identifier")
367
double_quoted_string = QuotedString('"', multiline=True, unquoteResults=False) # dblQuotedString
369
alphastring_ = OneOrMore(CharsNotIn(_noncomma))
371
def parse_html(s, loc, toks):
373
return '<%s>' % ''.join(toks[0])
378
html_text = nestedExpr( opener, closer,
380
opener + closer ).setParseAction( lambda t:t[0] ))
381
).setParseAction(parse_html)
383
ID = ( identifier | html_text |
384
double_quoted_string | #.setParseAction(strip_quotes) |
385
alphastring_ ).setName("ID")
388
float_number = Combine(Optional(minus) +
389
OneOrMore(Word(nums + "."))).setName("float_number")
391
righthand_id = (float_number | ID ).setName("righthand_id")
393
port_angle = (at + ID).setName("port_angle")
395
port_location = (OneOrMore(Group(colon + ID)) |
396
Group(colon + lparen + ID + comma + ID + rparen)).setName("port_location")
398
port = (Group(port_location + Optional(port_angle)) |
399
Group(port_angle + Optional(port_location))).setName("port")
401
node_id = (ID + Optional(port))
402
a_list = OneOrMore(ID + Optional(equals.suppress() + righthand_id) +
403
Optional(comma.suppress())).setName("a_list")
405
attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) +
406
rbrack.suppress()).setName("attr_list")
408
attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt")
410
edgeop = (Literal("--") | Literal("->")).setName("edgeop")
412
stmt_list = Forward()
413
graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) +
414
rbrace.suppress() + Optional(semi.suppress()) ).setName("graph_stmt")
417
edge_point = Forward()
419
edgeRHS = OneOrMore(edgeop + edge_point)
420
edge_stmt = edge_point + edgeRHS + Optional(attr_list)
422
subgraph = Group(subgraph_ + Optional(ID) + graph_stmt).setName("subgraph")
424
edge_point << Group(subgraph | graph_stmt | node_id )
426
node_stmt = (node_id + Optional(attr_list) + Optional(semi.suppress())).setName("node_stmt")
428
assignment = (ID + equals.suppress() + righthand_id).setName("assignment")
429
stmt = (assignment | edge_stmt | attr_stmt | subgraph | graph_stmt | node_stmt).setName("stmt")
430
stmt_list << OneOrMore(stmt + Optional(semi.suppress()))
432
graphparser = OneOrMore( (Optional(strict_) + Group((graph_ | digraph_)) +
433
Optional(ID) + graph_stmt).setResultsName("graph") )
435
singleLineComment = Group("//" + restOfLine) | Group("#" + restOfLine)
440
graphparser.ignore(singleLineComment)
441
graphparser.ignore(cStyleComment)
443
assignment.setParseAction(push_attr_list)
444
a_list.setParseAction(push_attr_list)
445
edge_stmt.setParseAction(push_edge_stmt)
446
node_stmt.setParseAction(push_node_stmt)
447
attr_stmt.setParseAction(push_default_stmt)
449
subgraph.setParseAction(push_subgraph_stmt)
450
graph_stmt.setParseAction(push_graph_stmt)
451
graphparser.setParseAction(push_top_graph_stmt)
367
457
def parse_dot_data(data):
369
data = data.replace('\\\n', '')
370
graphparser = graph_definition()
371
if pyparsing_version >= '1.2':
372
graphparser.parseWithTabs()
373
tokens = graphparser.parseString(data)
376
except ParseException, err:
378
print " "*(err.column-1) + "^"
465
graphparser = graph_definition()
467
if pyparsing_version >= '1.2':
468
graphparser.parseWithTabs()
470
tokens = graphparser.parseString(data)
475
return [g for g in tokens]
477
except ParseException, err:
480
print " "*(err.column-1) + "^"