1# coding: latin-1 2# Copyright (c) 2009,2010,2011,2012,2013,2014 Dirk Baechle. 3# www: https://bitbucket.org/dirkbaechle/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 %dottoxml.py [options] <infile.dot> <outfile.graphml> 20 21 convert a DOT file to Graphml XML (and various other formats) 22""" 23 24import sys 25import locale 26import optparse 27 28import dot 29 30# Usage message 31usgmsg = "Usage: dottoxml.py [options] infile.dot outfile.graphml" 32 33def usage(): 34 print "dottoxml 1.6, 2014-04-10, Dirk Baechle\n" 35 print usgmsg 36 print "Hint: Try '-h' or '--help' for further infos!" 37 38def exportDot(o, nodes, edges, options): 39 o.write("graph [\n") 40 41 for k,nod in nodes.iteritems(): 42 nod.exportDot(o,options) 43 for el in edges: 44 el.exportDot(o,nodes,options) 45 46def exportGML(o, nodes, edges, options): 47 o.write("graph [\n") 48 o.write(" comment \"Created by dottoxml.py\"\n") 49 o.write(" directed 1\n") 50 o.write(" IsPlanar 1\n") 51 52 for k,nod in nodes.iteritems(): 53 nod.exportGML(o,options) 54 for el in edges: 55 el.exportGML(o,nodes,options) 56 57 o.write("]\n") 58 59def exportGraphml(o, nodes, edges, options): 60 import xml.dom.minidom 61 doc = xml.dom.minidom.Document() 62 root = doc.createElement(u'graphml') 63 root.setAttribute(u'xmlns',u'http://graphml.graphdrawing.org/xmlns') 64 root.setAttribute(u'xmlns:xsi',u'http://www.w3.org/2001/XMLSchema-instance') 65 root.setAttribute(u'xmlns:y',u'http://www.yworks.com/xml/graphml') 66 root.setAttribute(u'xsi:schemaLocation',u'http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd') 67 doc.appendChild(root) 68 key = doc.createElement(u'key') 69 key.setAttribute(u'for',u'node') 70 key.setAttribute(u'id',u'd0') 71 key.setAttribute(u'yfiles.type',u'nodegraphics') 72 root.appendChild(key) 73 74 key = doc.createElement(u'key') 75 key.setAttribute(u'attr.name',u'description') 76 key.setAttribute(u'attr.type',u'string') 77 key.setAttribute(u'for',u'node') 78 key.setAttribute(u'id',u'd1') 79 root.appendChild(key) 80 81 key = doc.createElement(u'key') 82 key.setAttribute(u'for',u'edge') 83 key.setAttribute(u'id',u'd2') 84 key.setAttribute(u'yfiles.type',u'edgegraphics') 85 root.appendChild(key) 86 87 key = doc.createElement(u'key') 88 key.setAttribute(u'attr.name',u'description') 89 key.setAttribute(u'attr.type',u'string') 90 key.setAttribute(u'for',u'edge') 91 key.setAttribute(u'id',u'd3') 92 root.appendChild(key) 93 94 key = doc.createElement(u'key') 95 key.setAttribute(u'for',u'graphml') 96 key.setAttribute(u'id',u'd4') 97 key.setAttribute(u'yfiles.type',u'resources') 98 root.appendChild(key) 99 100 graph = doc.createElement(u'graph') 101 graph.setAttribute(u'edgedefault',u'directed') 102 graph.setAttribute(u'id',u'G') 103 graph.setAttribute(u'parse.edges',u'%d' % len(edges)) 104 graph.setAttribute(u'parse.nodes',u'%d' % len(nodes)) 105 graph.setAttribute(u'parse.order', u'free') 106 107 for k,nod in nodes.iteritems(): 108 nod.exportGraphml(doc, graph, options) 109 for el in edges: 110 el.exportGraphml(doc, graph, nodes, options) 111 112 root.appendChild(graph) 113 114 data = doc.createElement(u'data') 115 data.setAttribute(u'key',u'd4') 116 res = doc.createElement(u'y:Resources') 117 data.appendChild(res) 118 root.appendChild(data) 119 120 o.write(doc.toxml(encoding="utf-8")) 121 122def exportGDF(o, nodes, edges, options): 123 o.write("nodedef> name\n") 124 for k,nod in nodes.iteritems(): 125 nod.exportGDF(o, options) 126 for el in edges: 127 el.exportGDF(o,nodes,options) 128 o.write("edgedef> node1,node2\n") 129 130def main(): 131 parser = optparse.OptionParser(usage=usgmsg) 132 parser.add_option('-f', '--format', 133 action='store', dest='format', default='Graphml', 134 help='selects the output format (Graphml|GML|GDF) [default : %default]') 135 parser.add_option('-v', '--verbose', 136 action='store_true', dest='verbose', default=False, 137 help='enables messages (infos, warnings)') 138 parser.add_option('-s', '--sweep', 139 action='store_true', dest='sweep', default=False, 140 help='sweep nodes (remove nodes that are not connected)') 141 parser.add_option('--nn', '--no-nodes', 142 action='store_false', dest='NodeLabels', default=True, 143 help='do not output any node labels [Graphml]') 144 parser.add_option('--ne', '--no-edges', 145 action='store_false', dest='EdgeLabels', default=True, 146 help='do not output any edge labels [Graphml]') 147 parser.add_option('--nu', '--no-uml', 148 action='store_false', dest='NodeUml', default=True, 149 help='do not output any node methods/attributes in UML [Graphml]') 150 parser.add_option('--na', '--no-arrows', 151 action='store_false', dest='Arrows', default=True, 152 help='do not output any arrows [Graphml]') 153 parser.add_option('--nc', '--no-colors', 154 action='store_false', dest='Colors', default=True, 155 help='do not output any colors [Graphml]') 156 parser.add_option('--la', '--lump-attributes', 157 action='store_true', dest='LumpAttributes', default=False, 158 help='lump class attributes/methods together with the node label [Graphml]') 159 parser.add_option('--sc', '--separator-char', 160 action='store', dest='SepChar', default='_', metavar='SEPCHAR', 161 help='default separator char when lumping attributes/methods [default : "_"]') 162 parser.add_option('--ae', '--auto-edges', 163 action='store_true', dest='EdgeLabelsAutoComplete', default=False, 164 help='auto-complete edge labels') 165 parser.add_option('--ah', '--arrowhead', 166 action='store', dest='DefaultArrowHead', default='none', metavar='TYPE', 167 help='sets the default appearance of arrow heads for edges (normal|diamond|dot|...) [default : %default]') 168 parser.add_option('--at', '--arrowtail', 169 action='store', dest='DefaultArrowTail', default='none', metavar='TYPE', 170 help='sets the default appearance of arrow tails for edges (normal|diamond|dot|...) [default : %default]') 171 parser.add_option('--cn', '--color-nodes', 172 action='store', dest='DefaultNodeColor', default='#CCCCFF', metavar='COLOR', 173 help='default node color [default : "#CCCCFF"]') 174 parser.add_option('--ce', '--color-edges', 175 action='store', dest='DefaultEdgeColor', default='#000000', metavar='COLOR', 176 help='default edge color [default : "#000000"]') 177 parser.add_option('--cnt', '--color-nodes-text', 178 action='store', dest='DefaultNodeTextColor', default='#000000', metavar='COLOR', 179 help='default node text color for labels [default : "#000000"]') 180 parser.add_option('--cet', '--color-edges-text', 181 action='store', dest='DefaultEdgeTextColor', default='#000000', metavar='COLOR', 182 help='default edge text color for labels [default : "#000000"]') 183 parser.add_option('--ienc', '--input-encoding', 184 action='store', dest='InputEncoding', default='', metavar='ENCODING', 185 help='override encoding for input file [default : locale setting]') 186 parser.add_option('--oenc', '--output-encoding', 187 action='store', dest='OutputEncoding', default='', metavar='ENCODING', 188 help='override encoding for text output files [default : locale setting]') 189 190 options, args = parser.parse_args() 191 192 if len(args) < 2: 193 usage() 194 sys.exit(1) 195 196 infile = args[0] 197 outfile = args[1] 198 199 options.DefaultNodeColor = dot.colorNameToRgb(options.DefaultNodeColor, '#CCCCFF') 200 options.DefaultEdgeColor = dot.colorNameToRgb(options.DefaultEdgeColor, '#000000') 201 options.DefaultNodeTextColor = dot.colorNameToRgb(options.DefaultNodeTextColor, '#000000') 202 options.DefaultEdgeTextColor = dot.colorNameToRgb(options.DefaultEdgeTextColor, '#000000') 203 204 preferredEncoding = locale.getpreferredencoding() 205 if options.InputEncoding == "": 206 options.InputEncoding = preferredEncoding 207 if options.OutputEncoding == "": 208 options.OutputEncoding = preferredEncoding 209 210 if options.verbose: 211 print "Input file: %s " % infile 212 print "Output file: %s " % outfile 213 print "Output format: %s" % options.format.lower() 214 print "Input encoding: %s" % options.InputEncoding 215 if options.format.lower() == "graphml": 216 print "Output encoding: utf-8 (fix for Graphml)" 217 else: 218 print "Output encoding: %s" % options.OutputEncoding 219 220 # Collect nodes and edges 221 nodes = {} 222 edges = [] 223 default_edge = None 224 default_node = None 225 nid = 1 226 eid = 1 227 f = open(infile, 'r') 228 content = f.read().splitlines() 229 f.close() 230 231 idx = 0 232 while idx < len(content): 233 l = unicode(content[idx], options.InputEncoding) 234 if '->' in l: 235 # Check for multiline edge 236 if '[' in l and ']' not in l: 237 ml = "" 238 while ']' not in ml: 239 idx += 1 240 ml = unicode(content[idx], options.InputEncoding) 241 l = ' '.join([l.rstrip(), ml.lstrip()]) 242 # Process edge 243 e = dot.Edge() 244 e.initFromString(l) 245 e.id = eid 246 eid += 1 247 if default_edge: 248 e.complementAttributes(default_edge) 249 edges.append(e) 250 elif '[' in l: 251 # Check for multiline node 252 if ']' not in l: 253 ml = "" 254 while ']' not in ml: 255 idx += 1 256 ml = unicode(content[idx], options.InputEncoding) 257 l = ' '.join([l.rstrip(), ml.lstrip()]) 258 # Process node 259 n = dot.Node() 260 n.initFromString(l) 261 lowlabel = n.label.lower() 262 if (lowlabel != 'graph' and 263 lowlabel != 'edge' and 264 lowlabel != 'node'): 265 n.id = nid 266 nid += 1 267 if default_node: 268 n.complementAttributes(default_node) 269 nodes[n.label] = n 270 else: 271 if lowlabel == 'edge': 272 default_edge = n 273 elif lowlabel == 'node': 274 default_node = n 275 elif 'charset=' in l: 276 # Pick up input encoding from DOT file 277 li = l.strip().split('=') 278 if len(li) == 2: 279 ienc = li[1].strip('"') 280 if ienc != "": 281 options.InputEncoding = ienc 282 if options.verbose: 283 print "Info: Picked up input encoding '%s' from the DOT file." % ienc 284 idx += 1 285 286 # Add single nodes, if required 287 for e in edges: 288 if not nodes.has_key(e.src): 289 n = dot.Node() 290 n.label = e.src 291 n.id = nid 292 nid += 1 293 nodes[e.src] = n 294 if not nodes.has_key(e.dest): 295 n = dot.Node() 296 n.label = e.dest 297 n.id = nid 298 nid += 1 299 nodes[e.dest] = n 300 nodes[e.src].referenced = True 301 nodes[e.dest].referenced = True 302 303 if options.verbose: 304 print "\nNodes: %d " % len(nodes) 305 print "Edges: %d " % len(edges) 306 307 if options.sweep: 308 rnodes = {} 309 for key, n in nodes.iteritems(): 310 if n.referenced: 311 rnodes[key] = n 312 nodes = rnodes 313 if options.verbose: 314 print "\nNodes after sweep: %d " % len(nodes) 315 316 # Output 317 o = open(outfile, 'w') 318 format = options.format.lower() 319 if format == 'dot': 320 exportDot(o, nodes, edges, options) 321 elif format == 'graphml': 322 exportGraphml(o, nodes, edges, options) 323 elif format == 'gdf': 324 exportGDF(o, nodes, edges, options) 325 else: # GML 326 exportGML(o, nodes, edges, options) 327 o.close() 328 329 if options.verbose: 330 print "\nDone." 331 332if __name__ == '__main__': 333 main() 334 335