1
|
1 # coding: latin-1
|
|
2 # Copyright (c) 2009,2010 Dirk Baechle.
|
|
3 # www: http://www.mydarc.de/dl9obn/programming/python/dottoxml
|
|
4 # mail: dl9obn AT darc.de
|
|
5 #
|
|
6 # This program is free software; you can redistribute it and/or modify it under
|
|
7 # the terms of the GNU General Public License as published by the Free Software
|
|
8 # Foundation; either version 2 of the License, or (at your option) any later
|
|
9 # version.
|
|
10 #
|
|
11 # This program is distributed in the hope that it will be useful, but WITHOUT
|
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
14 #
|
|
15 # You should have received a copy of the GNU General Public License along with
|
|
16 # this program; if not, write to the Free Software Foundation, Inc.,
|
|
17 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
18 """
|
|
19 Helper classes and functions for the dottoxml.py tool
|
|
20 """
|
|
21
|
|
22 import re
|
|
23 import X11Colors
|
|
24
|
|
25 r_label = re.compile(r'label\s*=\s*"\s*\{[^\}]*\}\s*"\s*')
|
|
26 r_labelstart = re.compile(r'label\s*=\s*"\s*\{')
|
|
27 r_labelclose = re.compile(r'\}\s*"')
|
|
28
|
|
29 def compileAttributes(attribs):
|
|
30 """ return the list of attributes as a DOT text string """
|
|
31 atxt = ""
|
|
32 first = True
|
|
33 for key, value in attribs.iteritems():
|
|
34 if not first:
|
|
35 atxt += ", %s=\"%s\"" % (key, value)
|
|
36 else:
|
|
37 atxt += "%s=\"%s\"" % (key, value)
|
|
38 first = False
|
|
39
|
|
40 return "[%s]" % atxt
|
|
41
|
|
42 def parseAttributes(attribs):
|
|
43 """ parse the attribute list and return a key/value dict for it """
|
|
44 adict = {}
|
|
45 tlist = []
|
|
46 lmode = False
|
|
47 ltext = ''
|
|
48 # First pass: split entries by ,
|
|
49 for a in attribs.split(','):
|
|
50 if r_label.findall(a):
|
|
51 tlist.append(a)
|
|
52 elif r_labelstart.findall(a):
|
|
53 ltext = a
|
|
54 lmode = True
|
|
55 else:
|
|
56 if lmode:
|
|
57 ltext += ",%s" % a
|
|
58 if r_labelclose.findall(a):
|
|
59 lmode = False
|
|
60 tlist.append(ltext)
|
|
61 else:
|
|
62 tlist.append(a)
|
|
63
|
|
64 # Second pass: split keys from values by =
|
|
65 for t in tlist:
|
|
66 apos = t.find('=')
|
|
67 if apos > 0:
|
|
68 adict[t[:apos].strip()] = t[apos+1:].strip().strip('"')
|
|
69
|
|
70 return adict
|
|
71
|
|
72 def getLabelAttributes(label):
|
|
73 """ return the sections of the label attributes in a list structure """
|
|
74 sections = []
|
|
75 slist = label.split('|')
|
|
76 for s in slist:
|
|
77 mlist = []
|
|
78 s = s.replace('\\r','\\l')
|
|
79 s = s.replace('\\n','\\l')
|
|
80 alist = s.split('\\l')
|
|
81 for a in alist:
|
|
82 a = a.strip()
|
|
83 if a != "":
|
|
84 mlist.append(a)
|
|
85 sections.append(mlist)
|
|
86 return sections
|
|
87
|
|
88 def colorNameToRgb(fcol, defaultcol):
|
|
89 """ convert the color name fcol to an RGB string, if required """
|
|
90 if not fcol.startswith('#'):
|
|
91 return X11Colors.color_map.get(fcol, defaultcol)
|
|
92 else:
|
|
93 return fcol
|
|
94
|
|
95 def getColorAttribute(attribs, key, defaultcol, conf):
|
|
96 """ extract the color for the attribute key and convert it
|
|
97 to RGB format if required
|
|
98 """
|
|
99 if conf.Colors:
|
|
100 if attribs.has_key(key):
|
|
101 return colorNameToRgb(attribs[key], defaultcol)
|
|
102 return defaultcol
|
|
103
|
|
104 def escapeNewlines(label):
|
|
105 """ convert the newline escape sequences in the given label """
|
|
106 l = label.replace('\\n','\n')
|
|
107 l = l.replace('\\l','\n')
|
|
108 l = l.replace('\\r','\n')
|
|
109 return l
|
|
110
|
|
111 class Node:
|
|
112 """ a single node in the graph """
|
|
113 def __init__(self):
|
|
114 self.label = ""
|
|
115 self.id = 0
|
|
116 self.attribs = {}
|
|
117 self.referenced = False
|
|
118 self.sections = []
|
|
119
|
|
120 def initFromString(self, line):
|
|
121 """ extract node info from the given text line """
|
|
122 spos = line.find('[')
|
|
123 atts = ""
|
|
124 if spos >= 0:
|
|
125 atts = line[spos+1:]
|
|
126 line = line[:spos].strip()
|
|
127 # Process label
|
|
128 self.label = line.strip('"')
|
|
129 # Process attributes
|
|
130 if len(atts):
|
|
131 spos = atts.rfind(']')
|
|
132 if spos > 0:
|
|
133 atts = atts[:spos]
|
|
134 self.attribs = parseAttributes(atts)
|
|
135 # Process sections
|
|
136 if self.attribs.has_key("label"):
|
|
137 tlabel = self.attribs["label"]
|
|
138 if (tlabel != "" and
|
|
139 tlabel.startswith('{') and
|
|
140 tlabel.endswith('}')):
|
|
141 tlabel = tlabel[1:-1]
|
|
142 self.sections = getLabelAttributes(tlabel)
|
|
143 # HACK DW, no label in attrs, dann label in attrs = node.label
|
|
144 else:
|
|
145 self.attribs["label"]=self.label
|
|
146
|
|
147 def getLabel(self, conf, multiline=False):
|
|
148 """ return the label of the node """
|
|
149 if conf.NodeLabels:
|
|
150 if self.attribs.has_key('label'):
|
|
151 if len(self.sections) > 0:
|
|
152 if multiline:
|
|
153 return '\n'.join(self.sections[0])
|
|
154 else:
|
|
155 return ','.join(self.sections[0])
|
|
156 else:
|
|
157 return self.attribs['label']
|
|
158 else:
|
|
159 return self.label
|
|
160 else:
|
|
161 return ""
|
|
162
|
|
163 def getLabelWidth(self, conf, multiline=False):
|
|
164 """ return the maximum width label of the node label"""
|
|
165 if conf.NodeLabels:
|
|
166 if self.attribs.has_key('label'):
|
|
167 if len(self.sections) > 0:
|
|
168 if multiline:
|
|
169 # Find maximum label width
|
|
170 width = 1
|
|
171 for s in self.sections[0]:
|
|
172 if len(s) > width:
|
|
173 width = len(s)
|
|
174 for s in self.sections[1]:
|
|
175 if len(s) > width:
|
|
176 width = len(s)
|
|
177 for s in self.sections[2]:
|
|
178 if len(s) > width:
|
|
179 width = len(s)
|
|
180 return width
|
|
181 else:
|
|
182 return len(','.join(self.sections[0]))
|
|
183 else:
|
|
184 return len(self.attribs['label'])
|
|
185 else:
|
|
186 return len(self.label)
|
|
187 else:
|
|
188 return 0
|
|
189
|
|
190 def complementAttributes(self, node):
|
|
191 """ from node copy all new attributes, that do not exist in self """
|
|
192 for a in node.attribs:
|
|
193 if not self.attribs.has_key(a):
|
|
194 self.attribs[a] = node.attribs[a]
|
|
195
|
|
196 def exportDot(self, o, conf):
|
|
197 """ write the node in DOT format to the given file """
|
|
198 if len(self.attribs) > 0:
|
|
199 o.write("\"%s\" %s;\n" % (self.label, compileAttributes(self.attribs)))
|
|
200 else:
|
|
201 o.write("\"%s\";\n" % (self.label))
|
|
202
|
|
203 def exportGDF(self, o, conf):
|
|
204 """ write the node in GDF format to the given file """
|
|
205 tlabel = self.getLabel(conf)
|
|
206 if tlabel == "":
|
|
207 tlabel = "n%d" % self.id
|
|
208 o.write("%s\n" % tlabel)
|
|
209
|
|
210 def exportGML(self, o, conf):
|
|
211 """ write the node in GML format to the given file """
|
|
212 o.write(" node [\n")
|
|
213 o.write(" id %d\n" % self.id)
|
|
214 o.write(" label\n")
|
|
215 o.write(" \"%s\"\n" % self.getLabel(conf))
|
|
216 o.write(" ]\n")
|
|
217
|
|
218 def exportGraphml(self, doc, parent, conf):
|
|
219 """ export the node in Graphml format and append it to the parent XML node """
|
|
220 node = doc.createElement(u'node')
|
|
221 node.setAttribute(u'id',u'n%d' % self.id)
|
|
222
|
|
223 data0 = doc.createElement(u'data')
|
|
224 data0.setAttribute(u'key', u'd0')
|
|
225
|
|
226 exportUml = False
|
|
227 if len(self.sections) > 0 and conf.NodeUml and not conf.LumpAttributes:
|
|
228 exportUml = True
|
|
229 snode = doc.createElement(u'y:UMLClassNode')
|
|
230 else:
|
|
231 snode = doc.createElement(u'y:ShapeNode')
|
|
232 geom = doc.createElement(u'y:Geometry')
|
|
233 geom.setAttribute(u'height',u'30.0')
|
|
234 geom.setAttribute(u'width',u'30.0')
|
|
235 geom.setAttribute(u'x',u'0.0')
|
|
236 geom.setAttribute(u'y',u'0.0')
|
|
237 snode.appendChild(geom)
|
|
238 color = getColorAttribute(self.attribs, 'color', conf.DefaultNodeColor, conf)
|
|
239 fill = doc.createElement(u'y:Fill')
|
|
240 fill.setAttribute(u'color',u'%s' % color)
|
|
241 fill.setAttribute(u'transparent',u'false')
|
|
242 snode.appendChild(fill)
|
|
243 border = doc.createElement(u'y:BorderStyle')
|
|
244 border.setAttribute(u'color',u'#000000')
|
|
245 border.setAttribute(u'type',u'line')
|
|
246 border.setAttribute(u'width',u'1.0')
|
|
247 snode.appendChild(border)
|
|
248 color = getColorAttribute(self.attribs, 'fontcolor', conf.DefaultNodeTextColor, conf)
|
|
249 label = doc.createElement(u'y:NodeLabel')
|
|
250 if conf.LumpAttributes:
|
|
251 label.setAttribute(u'alignment',u'left')
|
|
252 else:
|
|
253 label.setAttribute(u'alignment',u'center')
|
|
254 label.setAttribute(u'autoSizePolicy',u'content')
|
|
255 label.setAttribute(u'fontFamily',u'Dialog')
|
|
256 label.setAttribute(u'fontSize',u'12')
|
|
257 label.setAttribute(u'fontStyle',u'plain')
|
|
258 label.setAttribute(u'hasBackgroundColor',u'false')
|
|
259 label.setAttribute(u'hasLineColor',u'false')
|
|
260 label.setAttribute(u'modelName',u'internal')
|
|
261 label.setAttribute(u'modelPosition',u'c')
|
|
262 label.setAttribute(u'textColor',u'%s' % color)
|
|
263 label.setAttribute(u'visible',u'true')
|
|
264 nodeLabelText = escapeNewlines(self.getLabel(conf, True))
|
|
265 if conf.LumpAttributes:
|
|
266 # Find maximum label width
|
|
267 width = self.getLabelWidth(conf, True)
|
|
268 nodeLabelText += '\n' + conf.SepChar*width + '\n'
|
|
269 nodeLabelText += u'%s\n' % '\n'.join(self.sections[1])
|
|
270 nodeLabelText += conf.SepChar*width + '\n'
|
|
271 nodeLabelText += u'%s' % '\n'.join(self.sections[2])
|
|
272 label.appendChild(doc.createTextNode(u'%s' % nodeLabelText))
|
|
273 snode.appendChild(label)
|
|
274 if exportUml and not conf.LumpAttributes:
|
|
275 shape = doc.createElement(u'y:UML')
|
|
276 shape.setAttribute(u'clipContent',u'true')
|
|
277 shape.setAttribute(u'constraint',u'')
|
|
278 shape.setAttribute(u'omitDetails',u'false')
|
|
279 shape.setAttribute(u'stereotype',u'')
|
|
280 shape.setAttribute(u'use3DEffect',u'true')
|
|
281
|
|
282 alabel = doc.createElement(u'y:AttributeLabel')
|
|
283 alabel.appendChild(doc.createTextNode(u'%s' % '\n'.join(self.sections[1])))
|
|
284 shape.appendChild(alabel)
|
|
285 mlabel = doc.createElement(u'y:MethodLabel')
|
|
286 mlabel.appendChild(doc.createTextNode(u'%s' % '\n'.join(self.sections[2])))
|
|
287 shape.appendChild(mlabel)
|
|
288 else:
|
|
289 shape = doc.createElement(u'y:Shape')
|
|
290 shape.setAttribute(u'type',u'rectangle')
|
|
291 snode.appendChild(shape)
|
|
292 data0.appendChild(snode)
|
|
293 node.appendChild(data0)
|
|
294
|
|
295 data1 = doc.createElement(u'data')
|
|
296 data1.setAttribute(u'key', u'd1')
|
|
297 node.appendChild(data1)
|
|
298
|
|
299 if self.attribs.get("URL","")!="":
|
|
300
|
|
301 data3 = doc.createElement(u'data')
|
|
302 data3.setAttribute(u'key', u'd4')
|
|
303 data3.appendChild(doc.createTextNode(u'%s' % self.attribs.get("URL","")))
|
|
304 node.appendChild(data3)
|
|
305
|
|
306 parent.appendChild(node)
|
|
307
|
|
308 class Edge:
|
|
309 """ a single edge in the graph """
|
|
310 def __init__(self):
|
|
311 self.id = 0
|
|
312 self.src = ""
|
|
313 self.dest = ""
|
|
314 self.attribs = {}
|
|
315
|
|
316 def initFromString(self, line):
|
|
317 """ extract edge info from the given text line """
|
|
318 spos = line.find('[')
|
|
319 atts = ""
|
|
320 if spos >= 0:
|
|
321 atts = line[spos+1:]
|
|
322 line = line[:spos].strip()
|
|
323
|
|
324 # Process labels
|
|
325 ll = line.replace('->',' ').split()
|
|
326 if len(ll) > 1:
|
|
327 self.src = ll[0].strip('"')
|
|
328 self.dest = ll[1].rstrip(';').strip('"')
|
|
329 # Process attributes
|
|
330 if len(atts):
|
|
331 spos = atts.rfind(']')
|
|
332 if spos > 0:
|
|
333 atts = atts[:spos]
|
|
334 self.attribs = parseAttributes(atts)
|
|
335
|
|
336 def getLabel(self, nodes, conf):
|
|
337 """ return the label of the edge """
|
|
338 if conf.EdgeLabels:
|
|
339 if self.attribs.has_key('label'):
|
|
340 return self.attribs['label']
|
|
341 else:
|
|
342 if conf.EdgeLabelsAutoComplete:
|
|
343 srclink = self.src
|
|
344 destlink = self.dest
|
|
345 if (nodes[self.src].attribs.has_key('label')):
|
|
346 srclink = nodes[self.src].attribs['label']
|
|
347 if (nodes[self.dest].attribs.has_key('label')):
|
|
348 destlink = nodes[self.dest].attribs['label']
|
|
349 return "%s -> %s" % (srclink, destlink)
|
|
350 else:
|
|
351 return ""
|
|
352 else:
|
|
353 return ""
|
|
354
|
|
355 def complementAttributes(self, edge):
|
|
356 """ from edge copy all new attributes, that do not exist in self """
|
|
357 for a in edge.attribs:
|
|
358 if not self.attribs.has_key(a):
|
|
359 self.attribs[a] = edge.attribs[a]
|
|
360
|
|
361 def exportDot(self, o, nodes, conf):
|
|
362 """ write the edge in DOT format to the given file """
|
|
363 if len(self.attribs) > 0:
|
|
364 o.write("\"%s\" -> \"%s\" %s;\n" % (self.src, self.dest, compileAttributes(self.attribs)))
|
|
365 else:
|
|
366 o.write("\"%s\" -> \"%s\";\n" % (self.src, self.dest))
|
|
367
|
|
368 def exportGDF(self, o, nodes, conf):
|
|
369 """ write the edge in GDF format to the given file """
|
|
370 slabel = self.src.getLabel(conf)
|
|
371 if slabel == "":
|
|
372 slabel = "n%d" % self.src.id
|
|
373 dlabel = self.dest.getLabel(conf)
|
|
374 if dlabel == "":
|
|
375 dlabel = "n%d" % self.dest.id
|
|
376 o.write("%s,%s\n" % (slabel, dlabel))
|
|
377
|
|
378 def exportGML(self, o, nodes, conf):
|
|
379 """ write the edge in GML format to the given file """
|
|
380 o.write(" edge [\n")
|
|
381 o.write(" source %d\n" % nodes[self.src].id)
|
|
382 o.write(" target %d\n" % nodes[self.dest].id)
|
|
383 o.write(" label\n")
|
|
384 o.write(" \"%s\"\n" % self.getLabel(nodes, conf))
|
|
385 o.write(" ]\n")
|
|
386
|
|
387 def exportGraphml(self, doc, parent, nodes, conf):
|
|
388 """ export the edge in Graphml format and append it to the parent XML node """
|
|
389 edge = doc.createElement(u'edge')
|
|
390 edge.setAttribute(u'id',u'e%d' % self.id)
|
|
391 edge.setAttribute(u'source',u'n%d' % nodes[self.src].id)
|
|
392 edge.setAttribute(u'target',u'n%d' % nodes[self.dest].id)
|
|
393
|
|
394 data2 = doc.createElement(u'data')
|
|
395 data2.setAttribute(u'key', u'd2')
|
|
396
|
|
397 pedge = doc.createElement(u'y:PolyLineEdge')
|
|
398 line = doc.createElement(u'y:LineStyle')
|
|
399 color = getColorAttribute(self.attribs, 'color', conf.DefaultEdgeColor, conf)
|
|
400 line.setAttribute(u'color',u'%s' % color)
|
|
401 line.setAttribute(u'type', u'line')
|
|
402 line.setAttribute(u'width', u'1.0')
|
|
403 pedge.appendChild(line)
|
|
404 arrow = doc.createElement(u'y:Arrows')
|
|
405 arrow_tail = conf.DefaultArrowTail
|
|
406 arrow_head = conf.DefaultArrowHead
|
|
407 if conf.Arrows:
|
|
408 if self.attribs.has_key('arrowtail'):
|
|
409 arrow_head = self.attribs['arrowtail']
|
|
410 if self.attribs.has_key('arrowhead'):
|
|
411 arrow_tail = self.attribs['arrowhead']
|
|
412 arrow.setAttribute(u'source',u'%s' % arrow_head)
|
|
413 arrow.setAttribute(u'target',u'%s' % arrow_tail)
|
|
414 pedge.appendChild(arrow)
|
|
415 if conf.EdgeLabels:
|
|
416 tlabel = self.getLabel(nodes, conf)
|
|
417 if tlabel != "":
|
|
418 label = doc.createElement(u'y:EdgeLabel')
|
|
419 color = getColorAttribute(self.attribs, 'fontcolor', conf.DefaultEdgeTextColor, conf)
|
|
420 label.setAttribute(u'alignment',u'center')
|
|
421 label.setAttribute(u'distance',u'2.0')
|
|
422 label.setAttribute(u'fontFamily',u'Dialog')
|
|
423 label.setAttribute(u'fontSize',u'12')
|
|
424 label.setAttribute(u'fontStyle',u'plain')
|
|
425 label.setAttribute(u'hasBackgroundColor',u'false')
|
|
426 label.setAttribute(u'hasLineColor',u'false')
|
|
427 label.setAttribute(u'modelName',u'six_pos')
|
|
428 label.setAttribute(u'modelPosition',u'tail')
|
|
429 label.setAttribute(u'textColor',u'%s' % color)
|
|
430 label.setAttribute(u'visible',u'true')
|
|
431 label.setAttribute(u'preferredPlacement',u'anywhere')
|
|
432 label.setAttribute(u'ratio',u'0.5')
|
|
433 label.appendChild(doc.createTextNode(u'%s' % escapeNewlines(tlabel)))
|
|
434 pedge.appendChild(label)
|
|
435 bend = doc.createElement(u'y:BendStyle')
|
|
436 bend.setAttribute(u'smoothed', u'false')
|
|
437 pedge.appendChild(bend)
|
|
438 data2.appendChild(pedge)
|
|
439 edge.appendChild(data2)
|
|
440
|
|
441 data3 = doc.createElement(u'data')
|
|
442 data3.setAttribute(u'key', u'd3')
|
|
443 edge.appendChild(data3)
|
|
444
|
|
445 parent.appendChild(edge)
|