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