comparison env/lib/python3.9/site-packages/dot_parser.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """Graphviz's dot language parser.
2
3 The dotparser parses GraphViz files in
4 dot and dot files and transforms them
5 into a class representation defined by `pydot`.
6
7 Author: Michael Krause <michael@krause-software.de>
8 Fixes by: Ero Carrera <ero.carrera@gmail.com>
9 """
10 from __future__ import division
11 from __future__ import print_function
12 import sys
13
14 from pyparsing import (
15 nestedExpr, Literal, CaselessLiteral,
16 Word, OneOrMore,
17 Forward,
18 Group, Optional, Combine,
19 restOfLine, cStyleComment, nums, alphanums,
20 printables,
21 ParseException, ParseResults, CharsNotIn,
22 QuotedString)
23
24 import pydot
25
26 __author__ = ['Michael Krause', 'Ero Carrera']
27 __license__ = 'MIT'
28
29
30 PY3 = sys.version_info >= (3, 0, 0)
31 if PY3:
32 str_type = str
33 else:
34 str_type = basestring
35
36
37 class P_AttrList(object):
38
39 def __init__(self, toks):
40
41 self.attrs = {}
42 i = 0
43
44 while i < len(toks):
45 attrname = toks[i]
46 if i+2 < len(toks) and toks[i+1] == '=':
47 attrvalue = toks[i+2]
48 i += 3
49 else:
50 attrvalue = None
51 i += 1
52
53 self.attrs[attrname] = attrvalue
54
55
56 def __repr__(self):
57
58 return "%s(%r)" % (self.__class__.__name__, self.attrs)
59
60
61
62 class DefaultStatement(P_AttrList):
63
64 def __init__(self, default_type, attrs):
65
66 self.default_type = default_type
67 self.attrs = attrs
68
69 def __repr__(self):
70
71 return "%s(%s, %r)" % (self.__class__.__name__,
72 self.default_type, self.attrs)
73
74
75 top_graphs = list()
76
77 def push_top_graph_stmt(str, loc, toks):
78
79 attrs = {}
80 g = None
81
82 for element in toks:
83
84 if (isinstance(element, (ParseResults, tuple, list)) and
85 len(element) == 1 and
86 isinstance(element[0], str_type)):
87
88 element = element[0]
89
90 if element == 'strict':
91 attrs['strict'] = True
92
93 elif element in ['graph', 'digraph']:
94
95 attrs = {}
96
97 g = pydot.Dot(graph_type=element, **attrs)
98 attrs['type'] = element
99
100 top_graphs.append( g )
101
102 elif isinstance( element, str_type):
103 g.set_name( element )
104
105 elif isinstance(element, pydot.Subgraph):
106
107 g.obj_dict['attributes'].update( element.obj_dict['attributes'] )
108 g.obj_dict['edges'].update( element.obj_dict['edges'] )
109 g.obj_dict['nodes'].update( element.obj_dict['nodes'] )
110 g.obj_dict['subgraphs'].update( element.obj_dict['subgraphs'] )
111
112 g.set_parent_graph(g)
113
114 elif isinstance(element, P_AttrList):
115 attrs.update(element.attrs)
116
117 elif isinstance(element, (ParseResults, list)):
118 add_elements(g, element)
119
120 else:
121 raise ValueError(
122 'Unknown element statement: {s}'.format(s=element))
123
124
125 for g in top_graphs:
126 update_parent_graph_hierarchy(g)
127
128 if len( top_graphs ) == 1:
129 return top_graphs[0]
130
131 return top_graphs
132
133
134 def update_parent_graph_hierarchy(g, parent_graph=None, level=0):
135
136
137 if parent_graph is None:
138 parent_graph = g
139
140 for key_name in ('edges',):
141
142 if isinstance(g, pydot.frozendict):
143 item_dict = g
144 else:
145 item_dict = g.obj_dict
146
147 if key_name not in item_dict:
148 continue
149
150 for key, objs in item_dict[key_name].items():
151 for obj in objs:
152 if ('parent_graph' in obj and
153 obj['parent_graph'].get_parent_graph()==g):
154 if obj['parent_graph'] is g:
155 pass
156 else:
157 obj['parent_graph'].set_parent_graph(parent_graph)
158
159 if key_name == 'edges' and len(key) == 2:
160 for idx, vertex in enumerate( obj['points'] ):
161 if isinstance( vertex,
162 (pydot.Graph,
163 pydot.Subgraph, pydot.Cluster)):
164 vertex.set_parent_graph(parent_graph)
165 if isinstance( vertex, pydot.frozendict):
166 if vertex['parent_graph'] is g:
167 pass
168 else:
169 vertex['parent_graph'].set_parent_graph(
170 parent_graph)
171
172
173
174 def add_defaults(element, defaults):
175
176 d = element.__dict__
177 for key, value in defaults.items():
178 if not d.get(key):
179 d[key] = value
180
181
182
183 def add_elements(g, toks, defaults_graph=None,
184 defaults_node=None, defaults_edge=None):
185
186 if defaults_graph is None:
187 defaults_graph = {}
188 if defaults_node is None:
189 defaults_node = {}
190 if defaults_edge is None:
191 defaults_edge = {}
192
193 for elm_idx, element in enumerate(toks):
194
195 if isinstance(element, (pydot.Subgraph, pydot.Cluster)):
196
197 add_defaults(element, defaults_graph)
198 g.add_subgraph(element)
199
200 elif isinstance(element, pydot.Node):
201
202 add_defaults(element, defaults_node)
203 g.add_node(element)
204
205 elif isinstance(element, pydot.Edge):
206
207 add_defaults(element, defaults_edge)
208 g.add_edge(element)
209
210 elif isinstance(element, ParseResults):
211
212 for e in element:
213 add_elements(g, [e], defaults_graph,
214 defaults_node, defaults_edge)
215
216 elif isinstance(element, DefaultStatement):
217
218 if element.default_type == 'graph':
219
220 default_graph_attrs = pydot.Node('graph', **element.attrs)
221 g.add_node(default_graph_attrs)
222
223 elif element.default_type == 'node':
224
225 default_node_attrs = pydot.Node('node', **element.attrs)
226 g.add_node(default_node_attrs)
227
228 elif element.default_type == 'edge':
229
230 default_edge_attrs = pydot.Node('edge', **element.attrs)
231 g.add_node(default_edge_attrs)
232 defaults_edge.update(element.attrs)
233
234 else:
235 raise ValueError(
236 'Unknown DefaultStatement: {s}'.format(
237 s=element.default_type))
238
239 elif isinstance(element, P_AttrList):
240
241 g.obj_dict['attributes'].update(element.attrs)
242
243 else:
244 raise ValueError(
245 'Unknown element statement: {s}'.format(s=element))
246
247
248 def push_graph_stmt(str, loc, toks):
249
250 g = pydot.Subgraph('')
251 add_elements(g, toks)
252 return g
253
254
255 def push_subgraph_stmt(str, loc, toks):
256
257 g = pydot.Subgraph('')
258 for e in toks:
259 if len(e)==3:
260 e[2].set_name(e[1])
261 if e[0] == 'subgraph':
262 e[2].obj_dict['show_keyword'] = True
263 return e[2]
264 else:
265 if e[0] == 'subgraph':
266 e[1].obj_dict['show_keyword'] = True
267 return e[1]
268
269 return g
270
271
272 def push_default_stmt(str, loc, toks):
273
274 # The pydot class instances should be marked as
275 # default statements to be inherited by actual
276 # graphs, nodes and edges.
277 #
278 default_type = toks[0][0]
279 if len(toks) > 1:
280 attrs = toks[1].attrs
281 else:
282 attrs = {}
283
284 if default_type in ['graph', 'node', 'edge']:
285 return DefaultStatement(default_type, attrs)
286 else:
287 raise ValueError(
288 'Unknown default statement: {s}'.format(s=toks))
289
290
291 def push_attr_list(str, loc, toks):
292
293 p = P_AttrList(toks)
294 return p
295
296
297 def get_port(node):
298
299 if len(node)>1:
300 if isinstance(node[1], ParseResults):
301 if len(node[1][0])==2:
302 if node[1][0][0]==':':
303 return node[1][0][1]
304
305 return None
306
307
308 def do_node_ports(node):
309
310 node_port = ''
311 if len(node) > 1:
312 node_port = ''.join( [str(a)+str(b) for a,b in node[1] ] )
313
314 return node_port
315
316
317 def push_edge_stmt(str, loc, toks):
318
319 tok_attrs = [a for a in toks if isinstance(a, P_AttrList)]
320 attrs = {}
321 for a in tok_attrs:
322 attrs.update(a.attrs)
323
324 e = []
325
326 if isinstance(toks[0][0], pydot.Graph):
327
328 n_prev = pydot.frozendict(toks[0][0].obj_dict)
329 else:
330 n_prev = toks[0][0] + do_node_ports( toks[0] )
331
332 if isinstance(toks[2][0], ParseResults):
333
334 n_next_list = [[n.get_name(),] for n in toks[2][0] ]
335 for n_next in [n for n in n_next_list]:
336 n_next_port = do_node_ports(n_next)
337 e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs))
338
339 elif isinstance(toks[2][0], pydot.Graph):
340
341 e.append(pydot.Edge(n_prev,
342 pydot.frozendict(toks[2][0].obj_dict),
343 **attrs))
344
345 elif isinstance(toks[2][0], pydot.Node):
346
347 node = toks[2][0]
348
349 if node.get_port() is not None:
350 name_port = node.get_name() + ":" + node.get_port()
351 else:
352 name_port = node.get_name()
353
354 e.append(pydot.Edge(n_prev, name_port, **attrs))
355
356 # if the target of this edge is the name of a node
357 elif isinstance(toks[2][0], str_type):
358
359 for n_next in [n for n in tuple(toks)[2::2]]:
360
361 if (isinstance(n_next, P_AttrList) or
362 not isinstance(n_next[0], str_type)):
363 continue
364
365 n_next_port = do_node_ports( n_next )
366 e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs))
367
368 n_prev = n_next[0]+n_next_port
369 else:
370 raise Exception(
371 'Edge target {r} with type {s} unsupported.'.format(
372 r=toks[2][0], s=type(toks[2][0])))
373
374 return e
375
376
377
378 def push_node_stmt(s, loc, toks):
379
380 if len(toks) == 2:
381 attrs = toks[1].attrs
382 else:
383 attrs = {}
384
385 node_name = toks[0]
386 if isinstance(node_name, list) or isinstance(node_name, tuple):
387 if len(node_name)>0:
388 node_name = node_name[0]
389
390 n = pydot.Node(str(node_name), **attrs)
391 return n
392
393
394
395
396
397
398 graphparser = None
399
400 def graph_definition():
401
402 global graphparser
403
404 if not graphparser:
405
406 # punctuation
407 colon = Literal(":")
408 lbrace = Literal("{")
409 rbrace = Literal("}")
410 lbrack = Literal("[")
411 rbrack = Literal("]")
412 lparen = Literal("(")
413 rparen = Literal(")")
414 equals = Literal("=")
415 comma = Literal(",")
416 dot = Literal(".")
417 slash = Literal("/")
418 bslash = Literal("\\")
419 star = Literal("*")
420 semi = Literal(";")
421 at = Literal("@")
422 minus = Literal("-")
423
424 # keywords
425 strict_ = CaselessLiteral("strict")
426 graph_ = CaselessLiteral("graph")
427 digraph_ = CaselessLiteral("digraph")
428 subgraph_ = CaselessLiteral("subgraph")
429 node_ = CaselessLiteral("node")
430 edge_ = CaselessLiteral("edge")
431
432
433 # token definitions
434
435 identifier = Word(alphanums + "_." ).setName("identifier")
436
437 double_quoted_string = QuotedString(
438 '"', multiline=True, unquoteResults=False, escChar='\\') # dblQuotedString
439
440 noncomma = "".join([c for c in printables if c != ","])
441 alphastring_ = OneOrMore(CharsNotIn(noncomma + ' '))
442
443 def parse_html(s, loc, toks):
444 return '<%s>' % ''.join(toks[0])
445
446
447 opener = '<'
448 closer = '>'
449 html_text = nestedExpr( opener, closer,
450 ( CharsNotIn( opener + closer ) )
451 ).setParseAction(parse_html).leaveWhitespace()
452
453 ID = ( identifier | html_text |
454 double_quoted_string | #.setParseAction(strip_quotes) |
455 alphastring_ ).setName("ID")
456
457
458 float_number = Combine(Optional(minus) +
459 OneOrMore(Word(nums + "."))).setName("float_number")
460
461 righthand_id = (float_number | ID ).setName("righthand_id")
462
463 port_angle = (at + ID).setName("port_angle")
464
465 port_location = (OneOrMore(Group(colon + ID)) |
466 Group(colon + lparen +
467 ID + comma + ID + rparen)).setName("port_location")
468
469 port = (Group(port_location + Optional(port_angle)) |
470 Group(port_angle + Optional(port_location))).setName("port")
471
472 node_id = (ID + Optional(port))
473 a_list = OneOrMore(ID + Optional(equals + righthand_id) +
474 Optional(comma.suppress())).setName("a_list")
475
476 attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) +
477 rbrack.suppress()).setName("attr_list")
478
479 attr_stmt = (Group(graph_ | node_ | edge_) +
480 attr_list).setName("attr_stmt")
481
482 edgeop = (Literal("--") | Literal("->")).setName("edgeop")
483
484 stmt_list = Forward()
485 graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) +
486 rbrace.suppress() +
487 Optional(semi.suppress())).setName("graph_stmt")
488
489
490 edge_point = Forward()
491
492 edgeRHS = OneOrMore(edgeop + edge_point)
493 edge_stmt = edge_point + edgeRHS + Optional(attr_list)
494
495 subgraph = Group(
496 subgraph_ + Optional(ID) + graph_stmt).setName("subgraph")
497
498 edge_point << Group(
499 subgraph | graph_stmt | node_id).setName('edge_point')
500
501 node_stmt = (
502 node_id + Optional(attr_list) +
503 Optional(semi.suppress())).setName("node_stmt")
504
505 assignment = (ID + equals + righthand_id).setName("assignment")
506 stmt = (assignment | edge_stmt | attr_stmt |
507 subgraph | graph_stmt | node_stmt).setName("stmt")
508 stmt_list << OneOrMore(stmt + Optional(semi.suppress()))
509
510 graphparser = OneOrMore(
511 (Optional(strict_) + Group((graph_ | digraph_)) +
512 Optional(ID) + graph_stmt).setResultsName("graph"))
513
514 singleLineComment = Group(
515 "//" + restOfLine) | Group("#" + restOfLine)
516
517
518 # actions
519
520 graphparser.ignore(singleLineComment)
521 graphparser.ignore(cStyleComment)
522
523 assignment.setParseAction(push_attr_list)
524 a_list.setParseAction(push_attr_list)
525 edge_stmt.setParseAction(push_edge_stmt)
526 node_stmt.setParseAction(push_node_stmt)
527 attr_stmt.setParseAction(push_default_stmt)
528
529 subgraph.setParseAction(push_subgraph_stmt)
530 graph_stmt.setParseAction(push_graph_stmt)
531 graphparser.setParseAction(push_top_graph_stmt)
532
533
534 return graphparser
535
536
537 def parse_dot_data(s):
538 """Parse DOT description in (unicode) string `s`.
539
540 @return: Graphs that result from parsing.
541 @rtype: `list` of `pydot.Dot`
542 """
543 global top_graphs
544 top_graphs = list()
545 try:
546 graphparser = graph_definition()
547 graphparser.parseWithTabs()
548 tokens = graphparser.parseString(s)
549 return list(tokens)
550 except ParseException as err:
551 print(err.line)
552 print(" " * (err.column - 1) + "^")
553 print(err)
554 return None