~ubuntu-branches/ubuntu/raring/dot2tex/raring

« back to all changes in this revision

Viewing changes to dot2tex/dot2tex.py

  • Committer: Bazaar Package Importer
  • Author(s): Peter Collingbourne
  • Date: 2008-05-26 15:32:30 UTC
  • mfrom: (1.1.6 upstream)
  • Revision ID: james.westby@ubuntu.com-20080526153230-8wlo39v4lp9kha4j
Tags: 2.8.1-1
* New upstream release
* debian/control:
  - replaced dependency on pydot with pyparsing and graphviz
  - replaced python-all-dev Build-Dep with python
  - moved Build-Depends on pyparsing to Build-Depends-Indep
* debian/copyright: added 2008 to upstream copyright
* debian/watch: escaped the dots
* debian/pyversions, debian/control: package now works with only
  Python 2.4+

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
Various tools for converting graphs generated by the graphviz library
6
6
to formats for use with LaTeX.
7
7
 
8
 
Copyright (c) 2006-2007, Kjell Magne Fauske
 
8
Copyright (c) 2006-2008, Kjell Magne Fauske
9
9
 
10
10
"""
11
11
 
12
 
# Copyright (c) 2006-2007, Kjell Magne Fauske
 
12
# Copyright (c) 2006-2008, Kjell Magne Fauske
13
13
#
14
14
# Permission is hereby granted, free of charge, to any person obtaining a copy
15
15
# of this software and associated documentation files (the "Software"), to
30
30
# IN THE SOFTWARE.
31
31
 
32
32
__author__ = 'Kjell Magne Fauske'
33
 
__version__ = '2.7.0'
 
33
__version__ = '2.8.1'
34
34
__license__ = 'MIT'
35
35
 
36
 
        
 
36
 
37
37
from itertools import izip
38
38
from optparse import OptionParser
39
39
import optparse
41
41
import logging
42
42
import tempfile
43
43
from StringIO import StringIO
 
44
from random import randint
 
45
 
 
46
import dotparsing
44
47
 
45
48
# intitalize logging module
46
49
log = logging.getLogger("dot2tex")
51
54
# tell the handler to use this format
52
55
console.setFormatter(formatter)
53
56
log.addHandler(console)
54
 
 
55
 
 
56
 
try:
57
 
    import pydot
58
 
except:
59
 
    log.error("Could not load the pydot module.")
60
 
    sys.exit(1)
 
57
logstream = None
 
58
loghandler = None
61
59
 
62
60
DEFAULT_TEXTENCODING = 'utf8'
63
61
DEFAULT_OUTPUT_FORMAT = 'pgf'
95
93
                        r'\{',r'\}',r'\^{}',r'\&']
96
94
charmap = dict(zip(special_chars,special_chars_escape))
97
95
 
98
 
# Pydot has hardcoded Graphviz' graph, node and edge attributes.
99
 
# Dot2tex extends the DOT language with several new attributes. Fortunately
100
 
# it is possible to modify pydot at runtime to accept the new attributes.
101
 
special_graph_attrs = [
102
 
    'd2tdocpreamble',
103
 
    'd2tfigpreamble',
104
 
    'd2tfigpostamble',
105
 
    'd2tgraphstyle',
106
 
    'd2talignstr',
107
 
    'd2tvalignmode',
108
 
    'd2tnominsize',
109
 
    'texlbl',
110
 
    'd2ttikzedgelabels',
111
 
    'd2toutputformat',
112
 
    'd2tstyleonly',
113
 
    'd2tnodeoptions',
114
 
    'd2tedgeoptions',
115
 
    'd2toptions',
116
 
]
117
 
 
118
 
special_nodeandedge_attrs = [
119
 
    'texmode',
120
 
    'texlbl',
121
 
    'lblstyle',
122
 
    'topath',
123
 
    'exstyle',
124
 
]
125
 
 
126
 
# Modify pydot to understand dot2tex's extensions to the DOT language
127
 
pydot.Graph.attributes.extend(special_graph_attrs)
128
 
pydot.Edge.attributes.extend(special_nodeandedge_attrs)
129
 
pydot.Node.attributes.extend(special_nodeandedge_attrs)
 
96
 
 
97
 
130
98
 
131
99
helpmsg = """\
132
100
Failed to parse the input data. Is it a valid dot file?
145
113
        s = s.replace(a, b)
146
114
    return s
147
115
 
148
 
def escapeTeXChars(string):
 
116
def escape_texchars(string):
149
117
    r"""Escape the special LaTeX-chars %{}_^
150
118
 
151
119
    Examples:
152
 
        
153
 
    >>> escapeTeXChars('10%')
 
120
 
 
121
    >>> escape_texchars('10%')
154
122
    '10\\%'
155
 
    >>> escapeTeXChars('%{}_^\\$')
 
123
    >>> escape_texchars('%{}_^\\$')
156
124
    '\\%\\{\\}\\_\\^{}$\\backslash$\\$'
157
125
    """
158
126
    return "".join([charmap.get(c,c) for c in string])
159
 
    
 
127
 
 
128
 
 
129
def tikzify(s):
 
130
    if s.strip():
 
131
        return mreplace(s,r'\,:.','-+_*')
 
132
    else:
 
133
        return "d2tnn%i" % (len(s)+1)
160
134
 
161
135
def nsplit(seq, n=2):
162
136
    """Split a sequence into pieces of length n
169
143
    [('a', 'a'), ('b', 'b'), ('c', 'c')]
170
144
    >>> nsplit('aabbcc',n=3)
171
145
    [('a', 'a', 'b'), ('b', 'c', 'c')]
172
 
    
 
146
 
173
147
    # Note that cc is discarded
174
148
    >>> nsplit('aabbcc',n=4)
175
149
    [('a', 'a', 'b', 'b')]
181
155
    """
182
156
    for i in xrange(0, len(s), cl):
183
157
        yield s[i:i+cl]
184
 
    
185
 
def replaceTags(template, tags, tagsreplace):
 
158
 
 
159
def replace_tags(template, tags, tagsreplace):
186
160
    """Replace occurences of tags with tagreplace
187
161
 
188
162
    Example:
189
 
    >>> replaceTags('a b c d',('b','d'),{'b':'bbb','d':'ddd'})
 
163
    >>> replace_tags('a b c d',('b','d'),{'b':'bbb','d':'ddd'})
190
164
    'a bbb c ddd'
191
165
    """
192
166
    s = template
203
177
    else:
204
178
        return False
205
179
 
206
 
def createXdot(dotdata,prog='dot'):
 
180
def create_xdot(dotdata,prog='dot'):
207
181
    # The following code is from the pydot module written by Ero Carrera
208
 
    progs = pydot.find_graphviz()
209
 
    
 
182
    progs = dotparsing.find_graphviz()
 
183
 
210
184
    #prog = 'dot'
211
185
    if progs is None:
212
186
        return None
226
200
    stdin, stdout, stderr = os.popen3(cmd,'t')
227
201
    stdin.close()
228
202
    stderr.close()
229
 
    
 
203
 
230
204
    # I'm not quite sure why this is necessary, but some files
231
205
    # produces data with line endings that confuses pydot/pyparser.
232
 
    data = stdout.readlines()
233
 
    lines = [line for line in data if line.strip()]
234
 
    data = "".join(lines)
235
 
    
 
206
    data = stdout.read()
 
207
    #lines = [line for line in data if line.strip()]
 
208
    #data = "".join(data)
 
209
 
236
210
    stdout.close()
237
211
    os.unlink(tmp_name)
238
 
    
 
212
 
239
213
    return data
240
214
 
241
 
def parseDotData(dotdata):
 
215
def parse_dot_data(dotdata):
242
216
    """Wrapper for pydot.graph_from_dot_data
243
217
 
244
218
    Redirects error messages to the log.
246
220
    saveout = sys.stdout
247
221
    fsock = StringIO()
248
222
    sys.stdout = fsock
249
 
    graph = pydot.graph_from_dot_data(dotdata)
250
 
    log.debug('Output from pydot:\n'+fsock.getvalue())
 
223
    #graph = pydot.graph_from_dot_data(dotdata)
 
224
    parser = dotparsing.DotDataParser()
 
225
    graph = parser.parse_dot_data(dotdata)
 
226
    del(parser)
 
227
    log.debug('Output from dotparser:\n'+fsock.getvalue())
251
228
    fsock.close()
252
229
    sys.stdout = sys.__stdout__
 
230
    log.debug('Parsed graph:\n%s',str(graph))
253
231
    return graph
254
232
 
255
 
def parseDrawString(drawstring):
 
233
def parse_drawstring(drawstring):
256
234
    """Parse drawstring and returns a list of draw operations"""
257
235
    # The draw string parser is a bit clumsy and slow
258
236
    def doeE(c,s):
263
241
        points = map(int,tokens)
264
242
        didx = sum(map(len,tokens))+len(points)+1
265
243
        return didx, (c , points[0], points[1], points[2], points[3])
266
 
    
 
244
 
267
245
    def doPLB(c, s):
268
246
        # P n x1 y1 ... xn yn  Filled polygon using the given n points
269
247
        # p n x1 y1 ... xn yn  Unfilled polygon using the given n points
276
254
        didx = sum(map(len,tokens[1:n*2+1]))+n*2+2
277
255
        npoints = nsplit(points, 2)
278
256
        return didx, (c, npoints)
279
 
    
 
257
 
280
258
    def doCS(c,s):
281
 
        # C n -c1c2...cn  Set fill color. 
 
259
        # C n -c1c2...cn  Set fill color.
282
260
        # c n -c1c2...cn  Set pen color.
283
261
        # Graphviz uses the following color formats:
284
262
        #   "#%2x%2x%2x"    Red-Green-Blue (RGB)
291
269
        d = s[tmp:tmp+n]
292
270
        didx = len(d)+tmp+1
293
271
        return didx, (c, d)
294
 
    
 
272
 
295
273
    def doFont(c,s):
296
274
        # F s n -c1c2...cn
297
275
        # Set font. The font size is s points. The font name consists of
303
281
        d = s[tmp:tmp+n]
304
282
        didx = len(d)+tmp
305
283
        return didx, (c, size, d)
306
 
    
 
284
 
307
285
    def doText(c,s):
308
286
        # T x y j w n -c1c2...cn
309
287
        # Text drawn using the baseline point (x,y). The text consists of the
342
320
            elif c == 'T':
343
321
                didx, cmd = doText(c, s[idx+1:])
344
322
                cmdlist.append(cmd)
345
 
                
 
323
 
346
324
        except:
347
325
            pass
348
 
            
 
326
 
349
327
        idx += didx
350
328
    return cmdlist,stat
351
329
 
352
330
 
353
331
 
354
 
def getGraphList(gg, l = []):
 
332
def get_graphlist(gg, l = []):
355
333
    """Traverse a graph with subgraphs and return them as a list"""
356
334
    if not l:
357
335
        outer = True
358
336
    else:
359
337
        outer = False
360
338
    l.append(gg)
361
 
    if gg.subgraph_list:
362
 
        for g in gg.subgraph_list:
363
 
            getGraphList(g,l)
 
339
    if gg.get_subgraphs():
 
340
        for g in gg.get_subgraphs():
 
341
            get_graphlist(g,l)
364
342
    if outer: return l
365
343
 
366
 
class EndOfGraphElement:
 
344
class EndOfGraphElement(object):
367
345
    def __init__(self):
368
346
        pass
369
347
 
370
 
def getAllGraphElements(graph, l=[]):
 
348
def get_all_graph_elements(graph, l=[]):
371
349
    """Return all nodes and edges, including elements in subgraphs"""
372
350
    if not l:
373
351
        outer = True
374
352
        l.append(graph)
375
353
    else:
376
354
        outer = False
377
 
    for element in graph.sorted_graph_elements:
378
 
        if isinstance(element, pydot.Node):
379
 
            l.append(element)
380
 
        elif isinstance(element,pydot.Edge):
381
 
            l.append(element)
382
 
        elif isinstance(element, pydot.Graph):
383
 
            l.append(element)
384
 
            getAllGraphElements(element,l)
 
355
    for element in graph.allitems:
 
356
        if isinstance(element, dotparsing.DotSubGraph):
 
357
            l.append(element)
 
358
            get_all_graph_elements(element,l)
385
359
        else:
386
 
            log.warning('Unknown graph element')
387
 
            
 
360
            l.append(element)
 
361
 
388
362
    if outer:
389
363
        return l
390
364
    else:
391
365
        l.append(EndOfGraphElement())
392
366
 
393
367
 
394
 
class DotConvBase:
 
368
class DotConvBase(object):
395
369
    """Dot2TeX converter base"""
396
370
    def __init__(self, options = {}):
397
371
        self.color = ""
 
372
        self.opacity = None
398
373
        self.template = options.get('template','')
399
374
        self.textencoding = options.get('encoding',DEFAULT_TEXTENCODING)
400
375
        self.templatevars = {}
401
376
        self.body = ""
402
377
        if options.get('templatefile',''):
403
 
            self.loadTemplate(options['templatefile'])
 
378
            self.load_template(options['templatefile'])
 
379
        if options.get('template',''):
 
380
            self.template = options['template']
404
381
 
405
382
        self.options = options
406
383
        if options.get('texpreproc',False) or options.get('autosize',False):
408
385
        else:
409
386
            self.dopreproc = False
410
387
 
411
 
    def loadTemplate(self, templatefile):
 
388
    def load_template(self, templatefile):
412
389
        try:
413
390
            self.template = open(templatefile).read()
414
391
        except:
415
392
            pass
416
 
        
417
 
    def convertFile(self, filename):
 
393
 
 
394
    def convert_file(self, filename):
418
395
        """Load dot file and convert"""
419
396
        pass
420
 
    
421
 
    def startFig(self):
422
 
        return ""
423
 
    
424
 
    def endFig(self):
425
 
        return ""
426
 
    
427
 
    def drawEllipse(self, drawop, style = None):
428
 
        return ""
429
 
 
430
 
    def drawBezier(self, drawop, style = None):
431
 
        return ""
432
 
    
433
 
    def drawPolygon(self, drawop, style = None):
434
 
        return ""
435
 
    
436
 
    def drawPolyLine(self, drawop, style = None):
437
 
        return ""
438
 
    
439
 
    def drawText(self, drawop, style = None):
440
 
        return ""
441
 
    
442
 
    def outputNodeComment(self, node):
 
397
 
 
398
    def start_fig(self):
 
399
        return ""
 
400
 
 
401
    def end_fig(self):
 
402
        return ""
 
403
 
 
404
    def draw_ellipse(self, drawop, style = None):
 
405
        return ""
 
406
 
 
407
    def draw_bezier(self, drawop, style = None):
 
408
        return ""
 
409
 
 
410
    def draw_polygon(self, drawop, style = None):
 
411
        return ""
 
412
 
 
413
    def draw_polyline(self, drawop, style = None):
 
414
        return ""
 
415
 
 
416
    def draw_text(self, drawop, style = None):
 
417
        return ""
 
418
 
 
419
    def output_node_comment(self, node):
443
420
        return "  %% Node: %s\n" % node.name
444
 
    
445
 
    def outputEdgeComment(self, edge):
 
421
 
 
422
    def output_edge_comment(self, edge):
446
423
        src = edge.get_source()
447
424
        dst = edge.get_destination()
448
425
        if self.directedgraph:
450
427
        else:
451
428
            edge = '--'
452
429
        return "  %% Edge: %s %s %s\n" % (src, edge, dst)
453
 
    
454
 
    def setColor(self, node):
455
 
        return ""
456
 
    
457
 
    def setStyle(self, node):
458
 
        return ""
459
 
    
460
 
    def drawEdge(self, edge):
461
 
        return ""
462
 
    
463
 
    def startNode(self, node):
464
 
        return ""
465
 
    def endNode(self,node):
466
 
        return ""
467
 
    def startGraph(self, graph):
468
 
        return ""
469
 
    def endGraph(self,graph):
470
 
        return ""
471
 
    
472
 
    def startEdge(self):
473
 
        return ""
474
 
    
475
 
    def endEdge(self):
476
 
        return ""
477
 
    
478
 
    def filterStyles(self, style):
 
430
 
 
431
    def set_color(self, node):
 
432
        return ""
 
433
 
 
434
    def set_style(self, node):
 
435
        return ""
 
436
 
 
437
    def draw_edge(self, edge):
 
438
        return ""
 
439
 
 
440
    def start_node(self, node):
 
441
        return ""
 
442
    def end_node(self,node):
 
443
        return ""
 
444
    def start_graph(self, graph):
 
445
        return ""
 
446
    def end_graph(self,graph):
 
447
        return ""
 
448
 
 
449
    def start_edge(self):
 
450
        return ""
 
451
 
 
452
    def end_edge(self):
 
453
        return ""
 
454
 
 
455
    def filter_styles(self, style):
479
456
        return style
480
 
    
481
 
    def convertColor(self, drawopcolor,pgf=False):
 
457
 
 
458
    def convert_color(self, drawopcolor,pgf=False):
482
459
        """Convert color to a format usable by LaTeX and XColor"""
483
460
        # Graphviz uses the following color formats:
484
461
        #   "#%2x%2x%2x"    Red-Green-Blue (RGB)
485
462
        #   "#%2x%2x%2x%2x" Red-Green-Blue-Alpha (RGBA)
486
463
        #   H[, ]+S[, ]+V   Hue-Saturation-Value (HSV) 0.0 <= H,S,V <= 1.0
487
464
        #   string  color name
488
 
        
 
465
 
489
466
        # Is the format RBG(A)?
490
467
        if drawopcolor.startswith('#'):
491
468
            t = list(chunks(drawopcolor[1:],2))
501
478
                return (colstr, opacity)
502
479
            else:
503
480
                return "[rgb]{%s,%s,%s}" % tuple(rgb[0:3])
504
 
            
 
481
 
505
482
        elif (len(drawopcolor.split(' '))==3) or (len(drawopcolor.split(','))==3):
506
483
            # are the values space or comma separated?
507
484
            hsb = drawopcolor.split(',')
517
494
            drawopcolor = drawopcolor.replace(' ','')
518
495
            return drawopcolor
519
496
 
520
 
    
521
 
 
522
 
    def doDrawString(self, drawstring, drawobj):
 
497
 
 
498
 
 
499
    def do_drawstring(self, drawstring, drawobj):
523
500
        """Parse and draw drawsting
524
501
 
525
 
        Just a wrapper around doDrawOp.
 
502
        Just a wrapper around do_draw_op.
526
503
        """
527
 
        drawoperations,stat = parseDrawString(drawstring)
528
 
        return self.doDrawOp(drawoperations, drawobj,stat)
529
 
    
530
 
    def doDrawOp(self, drawoperations, drawobj,stat):
 
504
        drawoperations,stat = parse_drawstring(drawstring)
 
505
        return self.do_draw_op(drawoperations, drawobj,stat)
 
506
 
 
507
    def do_draw_op(self, drawoperations, drawobj,stat):
531
508
        """Excecute the operations in drawoperations"""
532
509
        s = ""
533
510
        for drawop in drawoperations:
537
514
            # duplicate mode
538
515
            if style and not self.options.get('duplicate', False):
539
516
                # map Graphviz styles to backend styles
540
 
                style = self.filterStyles(style)
 
517
                style = self.filter_styles(style)
541
518
                styles = [self.styles.get(key.strip(),key.strip()) \
542
519
                            for key in style.split(',') if key]
543
520
                style = ','.join(styles)
545
522
                style = None
546
523
 
547
524
            if op in ['e','E']:
548
 
                s += self.drawEllipse(drawop, style)
 
525
                s += self.draw_ellipse(drawop, style)
549
526
            elif op in ['p','P']:
550
 
                s += self.drawPolygon(drawop, style)
 
527
                s += self.draw_polygon(drawop, style)
551
528
            elif op == 'L':
552
 
                s += self.drawPolyLine(drawop, style)
 
529
                s += self.draw_polyline(drawop, style)
553
530
            elif op in ['C','c']:
554
 
                s += self.setColor(drawop)
 
531
                s += self.set_color(drawop)
555
532
            elif op == 'S':
556
 
                s += self.setStyle(drawop)
 
533
                s += self.set_style(drawop)
557
534
            elif op in ['B']:
558
 
                s += self.drawBezier(drawop, style)
 
535
                s += self.draw_bezier(drawop, style)
559
536
            elif op in ['T']:
560
537
                # Need to decide what to do with the text
561
538
                # Note that graphviz removes the \ character from the draw
574
551
##                if not multiline and label <> '\N':
575
552
##                    text =  drawobj.label
576
553
                texmode = self.options.get('texmode','verbatim')
577
 
                if getattr(drawobj,'texmode', ''):
578
 
                    texmode = drawobj.texmode
579
 
                if getattr(drawobj,'texlbl', ''):
 
554
                if drawobj.attr.get('texmode', ''):
 
555
                    texmode = drawobj.attr['texmode']
 
556
                if drawobj.attr.get('texlbl', ''):
580
557
                    # the texlbl overrides everything
581
 
                    text = drawobj.texlbl
 
558
                    text = drawobj.attr['texlbl']
582
559
                elif texmode == 'verbatim':
583
560
                    # verbatim mode
584
 
                    text = escapeTeXChars(text)
 
561
                    text = escape_texchars(text)
585
562
                    pass
586
563
                elif texmode == 'math':
587
564
                    # math mode
594
571
                    self.options.get('valignmode','center')=='center':
595
572
                    # do this for single line only
596
573
                    # Todo: Make this optional
597
 
                    pos = getattr(drawobj,'lp',None) or \
598
 
                          getattr(drawobj,'pos',None)
 
574
                    pos = drawobj.attr.get('lp',None) or \
 
575
                          drawobj.attr.get('pos',None)
599
576
                    if pos:
600
577
                        coord = pos.split(',')
601
578
                        if len(coord)==2:
602
579
                            drawop[1] = coord[0]
603
580
                            drawop[2] = coord[1]
604
581
                        pass
605
 
                lblstyle = getattr(drawobj,'lblstyle',None)
606
 
                exstyle = getattr(drawobj,'exstyle','')
 
582
                lblstyle = drawobj.attr.get('lblstyle',None)
 
583
                exstyle = drawobj.attr.get('exstyle','')
607
584
                if exstyle:
608
585
                    if lblstyle:
609
586
                        lblstyle += ',' +exstyle
610
587
                    else:
611
588
                        lblstyle = exstyle
612
 
                s += self.drawText(drawop,lblstyle)
 
589
                s += self.draw_text(drawop,lblstyle)
613
590
        return s
614
591
 
615
 
    def doNodes(self):
 
592
    def do_nodes(self):
616
593
        s = ""
617
594
        for node in self.nodes:
618
595
            self.currentnode = node
619
 
            dstring = getattr(node,'_draw_',"")
620
 
            lstring = getattr(node,'_ldraw_',"")
621
 
            
622
 
            
 
596
            dstring = node.attr.get('_draw_',"")
 
597
            lstring = node.attr.get('_ldraw_',"")
 
598
 
 
599
 
623
600
            drawstring = dstring+" "+lstring
624
 
            
 
601
 
625
602
            if not drawstring.strip(): continue
626
603
            # detect node type
627
 
            shape = node.shape
 
604
            shape = node.attr.get('shape','')
628
605
            if not shape:
629
606
                shape = 'ellipse' # default
630
607
            # extract size information
631
 
            x,y = node.pos.split(',')
632
 
            
 
608
            x,y = node.attr.get('pos','').split(',')
 
609
 
633
610
            # width and height are in inches. Convert to bp units
634
 
            w = float(node.width)*in2bp
635
 
            h = float(node.height)*in2bp
636
 
            
637
 
            s += self.outputNodeComment(node)
638
 
            s += self.startNode(node)
639
 
            #drawoperations = parseDrawString(drawstring)
640
 
            s += self.doDrawString(drawstring, node)
641
 
            s += self.endNode(node)
 
611
            w = float(node.attr['width'])*in2bp
 
612
            h = float(node.attr['height'])*in2bp
 
613
 
 
614
            s += self.output_node_comment(node)
 
615
            s += self.start_node(node)
 
616
            #drawoperations = parse_drawstring(drawstring)
 
617
            s += self.do_drawstring(drawstring, node)
 
618
            s += self.end_node(node)
642
619
        self.body += s
643
 
        
644
 
    def getEdgePoints(self, edge):
645
 
        points = edge.pos.split(' ')
646
 
        # check direction
647
 
        arrowstyle = '--'
648
 
        i = 0
649
 
        if points[i].startswith('s'):
650
 
            p = points[0].split(',')
651
 
            tmp = "%s,%s" % (p[1],p[2])
652
 
            if points[1].startswith('e'):
653
 
                points[2] =tmp
654
 
            else:
655
 
                points[1] = tmp
656
 
            del points[0]
657
 
            arrowstyle = '<-'
658
 
            i += 1
659
 
        if points[0].startswith('e'):
660
 
            p = points[0].split(',')
661
 
            points.pop()
662
 
            points.append("%s,%s" % (p[1],p[2]))
663
 
            del points[0]
664
 
            arrowstyle = '->'
665
 
            i += 1
666
 
        if i>1: arrowstyle = '<->'
667
 
        return arrowstyle, points
668
 
 
669
 
    def doEdges(self):
 
620
 
 
621
    def get_edge_points(self, edge):
 
622
        # edge BNF
 
623
        #   <edge>   :: <spline> (';' <spline>)*
 
624
        #   <spline> :: <endp>? <startp>? <point> <triple>+
 
625
        #   <point>  :: <x> ',' <y>
 
626
        #   <triple> :: <point> <point> <point>
 
627
        #   <endp>   :: "e" "," <x> "," <y>
 
628
##        spline ( ';' spline )*
 
629
##where spline  =   (endp)? (startp)? point (triple)+
 
630
##and triple    =   point point point
 
631
##and endp  =   "e,%d,%d"
 
632
##and startp    =   "s,%d,%d"
 
633
##If a spline has points p1 p2 p3 ... pn, (n = 1 (mod 3)), the points correspond to the control points of a B-spline from p1 to pn. If startp is given, it touches one node of the edge, and the arrowhead goes from p1 to startp. If startp is not given, p1 touches a node. Similarly for pn and endp.
 
634
        pos = edge.attr.get('pos')
 
635
        segments = pos.split(';')
 
636
 
 
637
        segret = []
 
638
        for pos in segments:
 
639
            points = pos.split(' ')
 
640
            # check direction
 
641
            arrowstyle = '--'
 
642
            i = 0
 
643
            if points[i].startswith('s'):
 
644
                p = points[0].split(',')
 
645
                tmp = "%s,%s" % (p[1],p[2])
 
646
                if points[1].startswith('e'):
 
647
                    points[2] =tmp
 
648
                else:
 
649
                    points[1] = tmp
 
650
                del points[0]
 
651
                arrowstyle = '<-'
 
652
                i += 1
 
653
            if points[0].startswith('e'):
 
654
                p = points[0].split(',')
 
655
                points.pop()
 
656
                points.append("%s,%s" % (p[1],p[2]))
 
657
                del points[0]
 
658
                arrowstyle = '->'
 
659
                i += 1
 
660
            if i>1: arrowstyle = '<->'
 
661
            segret.append((arrowstyle,points))
 
662
 
 
663
        return segret
 
664
 
 
665
    def do_edges(self):
670
666
        s = ""
671
 
        s += self.setColor(('cC',"black"))
 
667
        s += self.set_color(('cC',"black"))
672
668
        for edge in self.edges:
673
 
            dstring = getattr(edge,'_draw_',"")
674
 
            lstring = getattr(edge,'_ldraw_',"")
675
 
            hstring = getattr(edge,'_hdraw_',"")
676
 
            tstring = getattr(edge,'_tdraw_',"")
677
 
            tlstring = getattr(edge,'_tldraw_',"")
678
 
            hlstring = getattr(edge,'_hldraw_',"")
679
 
            
 
669
            dstring = edge.attr.get('_draw_',"")
 
670
            lstring = edge.attr.get('_ldraw_',"")
 
671
            hstring = edge.attr.get('_hdraw_',"")
 
672
            tstring = edge.attr.get('_tdraw_',"")
 
673
            tlstring = edge.attr.get('_tldraw_',"")
 
674
            hlstring = edge.attr.get('_hldraw_',"")
 
675
 
680
676
            # Note that the order of the draw strings should be the same
681
677
            # as in the xdot output.
682
678
            drawstring = dstring + " " + hstring + " " + tstring \
683
679
                            + " " + lstring + " " + tlstring + " " + hlstring
684
 
            drawop,stat = parseDrawString(drawstring);
 
680
            drawop,stat = parse_drawstring(drawstring);
685
681
            if not drawstring.strip():
686
682
                continue
687
 
            s += self.outputEdgeComment(edge)
 
683
            s += self.output_edge_comment(edge)
688
684
            if self.options.get('duplicate', False):
689
 
                s += self.startEdge()
690
 
                s += self.doDrawOp(drawop, edge,stat)
691
 
                s += self.endEdge()
 
685
                s += self.start_edge()
 
686
                s += self.do_draw_op(drawop, edge,stat)
 
687
                s += self.end_edge()
692
688
            else:
693
 
                s += self.drawEdge(edge)
694
 
                s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
 
689
                s += self.draw_edge(edge)
 
690
                s += self.do_drawstring(lstring+" "+tlstring+" "+hlstring, edge)
695
691
        self.body += s
696
 
        
697
 
    def doGraph(self):
698
 
        dstring = getattr(self.graph,'_draw_',"")
699
 
        lstring = getattr(self.graph,'_ldraw_',"")
 
692
 
 
693
    def do_graph(self):
 
694
        dstring = self.graph.attr.get('_draw_',"")
 
695
        lstring = self.graph.attr.get('_ldraw_',"")
700
696
        # print lstring
701
697
        # Avoid filling background of graphs with white
702
698
        if dstring.startswith('c 5 -white C 5 -white') \
703
 
            and not getattr(self.graph,'style'):
 
699
            and not self.graph.attr.get('style'):
704
700
            dstring = ''
705
701
        if getattr(self.graph,'_draw_',None):
706
702
            # bug
707
703
            dstring = "c 5 -black " + dstring #self.graph._draw_
708
704
            pass
709
705
        drawstring = dstring+" "+lstring
710
 
        if drawstring.strip(): 
711
 
            s = self.startGraph(self.graph)
712
 
            g = self.doDrawString(drawstring, self.graph)
713
 
            e = self.endGraph(self.graph)
 
706
        if drawstring.strip():
 
707
            s = self.start_graph(self.graph)
 
708
            g = self.do_drawstring(drawstring, self.graph)
 
709
            e = self.end_graph(self.graph)
714
710
            if g.strip():
715
711
                self.body += s +g + e
716
712
 
717
 
    def setOptions(self):
 
713
    def set_options(self):
718
714
        # process options
719
715
        # Warning! If graph attribute is true and command line option is false,
720
716
        # the graph attribute will be used. Command line option should have
732
728
        log.debug('Start conversion')
733
729
        try:
734
730
            try:
735
 
                maingraph = parseDotData(dotdata)
 
731
                maingraph = parse_dot_data(dotdata)
736
732
            except:
737
733
                log.info('Failed first attempt to parse graph')
738
734
                if not self.dopreproc:
739
735
                    log.info('Could not parse input dotdata directly. '
740
736
                             'Trying to create xdot data.')
741
737
                    try:
742
 
                        tmpdata = createXdot(dotdata,self.options.get('prog','dot'))
 
738
                        tmpdata = create_xdot(dotdata,self.options.get('prog','dot'))
743
739
                        log.debug('xdotdata:\n'+tmpdata)
744
 
                        maingraph = parseDotData(tmpdata)
 
740
                        maingraph = parse_dot_data(tmpdata)
 
741
                        log.debug('dotparsing graph:\n'+str(maingraph))
745
742
                    except:
746
743
                        raise
747
744
 
752
749
                    # need to convert to xdot format
753
750
                    # Warning. Pydot will not include custom attributes
754
751
                    log.debug('Trying to create xdotdata')
755
 
                    
756
 
                    tmpdata = createXdot(dotdata,self.options.get('prog','dot'))
 
752
 
 
753
                    tmpdata = create_xdot(dotdata,self.options.get('prog','dot'))
757
754
                    log.debug('xdotdata:\n'+tmpdata)
758
755
                    if tmpdata == None or not tmpdata.strip():
759
756
                        log.error('Failed to create xdotdata. Is Graphviz installed?')
760
757
                        sys.exit(1)
761
 
                    maingraph = parseDotData(tmpdata)
 
758
                    maingraph = parse_dot_data(tmpdata)
 
759
                    log.debug('dotparsing graph:\n'+str(maingraph))
762
760
                else:
763
761
                    # old version
764
762
                    pass
765
 
               
 
763
 
766
764
            self.maingraph = maingraph
767
765
            self.pencolor = ""
768
766
            self.fillcolor = ""
769
767
            self.linewidth = 1
770
768
            # Detect graph type
771
 
            if maingraph.graph_type == 'digraph':
772
 
                self.directedgraph = True
773
 
            else:
774
 
                self.directedgraph = False
 
769
            self.directedgraph = maingraph.directed
 
770
 
775
771
        except:
776
772
            log.error(helpmsg)
 
773
            raise
777
774
            sys.exit(1)
778
 
            raise
 
775
 
779
776
        if self.dopreproc:
780
 
            return self.doPreviewPreproc()
781
 
        
 
777
            return self.do_preview_preproc()
 
778
 
782
779
        # Romove annoying square
783
780
        # Todo: Remove squares from subgraphs. See pgram.dot
784
 
        dstring = getattr(self.maingraph,'_draw_',"")
 
781
        dstring = self.maingraph.attr.get('_draw_',"")
785
782
        if dstring:
786
 
            self.maingraph._draw_ = ""
787
 
 
788
 
        setDotAttr(self.maingraph)
789
 
        self.setOptions()
790
 
            
 
783
            self.maingraph.attr['_draw_'] = ""
 
784
 
 
785
        #setDotAttr(self.maingraph)
 
786
        self.set_options()
 
787
 
791
788
        # A graph can consists of nested graph. Extract all graphs
792
 
        graphlist = getGraphList(self.maingraph, [])
793
 
    
794
 
        self.body += self.startFig()
 
789
        graphlist = get_graphlist(self.maingraph, [])
 
790
 
 
791
        self.body += self.start_fig()
795
792
 
796
793
        # To get correct drawing order we need to iterate over the graphs
797
794
        # multiple times. First we draw the graph graphics, then nodes and
800
797
        # todo: support the outputorder attribute
801
798
        for graph in graphlist:
802
799
            self.graph = graph
803
 
            self.doGraph()
 
800
            self.do_graph()
804
801
 
805
 
        if not self.options.get('flattengraph',False):
806
 
            for graph in graphlist:
807
 
                self.graph = graph
808
 
                self.nodes = cleanDotNodes(graph)
809
 
                self.edges = graph.edge_list
810
 
                if not self.options.get('switchdraworder',False):
811
 
                    self.doEdges() # tmp
812
 
                    self.doNodes()
813
 
                else:
814
 
                    self.doNodes()
815
 
                    self.doEdges()
816
 
        else:
817
 
            nodelist = []
818
 
            edgelist = []
819
 
            for graph in graphlist:
820
 
                self.graph = graph
821
 
                nodelist.extend(cleanDotNodes(graph))
822
 
                edgelist.extend(graph.edge_list)
823
 
            self.nodes = nodelist
824
 
            self.edges = edgelist
 
802
##        if not self.options.get('flattengraph',False):
 
803
##            for graph in graphlist:
 
804
##                self.graph = graph
 
805
##                self.nodes = clean_dot_nodes(graph)
 
806
##                self.edges = graph.edge_list
 
807
##                if not self.options.get('switchdraworder',False):
 
808
##                    self.do_edges() # tmp
 
809
##                    self.do_nodes()
 
810
##                else:
 
811
##                    self.do_nodes()
 
812
##                    self.do_edges()
 
813
##        else:
 
814
        if True:
 
815
##            nodelist = []
 
816
##            edgelist = []
 
817
##            for graph in graphlist:
 
818
##                self.graph = graph
 
819
##                nodelist.extend(clean_dot_nodes(graph))
 
820
##                edgelist.extend(graph.edge_list)
 
821
            self.nodes = maingraph.allnodes
 
822
            self.edges = maingraph.alledges
825
823
            if not self.options.get('switchdraworder',False):
826
 
                self.doEdges() # tmp
827
 
                self.doNodes()
 
824
                self.do_edges() # tmp
 
825
                self.do_nodes()
828
826
            else:
829
 
                self.doNodes()
830
 
                self.doEdges()
831
 
            
832
 
        self.body += self.endFig()
 
827
                self.do_nodes()
 
828
                self.do_edges()
 
829
 
 
830
        self.body += self.end_fig()
833
831
        return self.output()
834
 
    
835
 
    def cleanTemplate(self, template):
 
832
 
 
833
    def clean_template(self, template):
836
834
        """Remove preprocsection or outputsection"""
837
835
        if not self.dopreproc and self.options.get('codeonly',False):
838
836
            r = re.compile('<<startcodeonlysection>>(.*?)<<endcodeonlysection>>',
841
839
            if m:
842
840
                return m.group(1).strip()
843
841
        if not self.dopreproc and self.options.get('figonly',False):
844
 
            r = re.compile('<<startfigonlysection>>(.*?)<<endfigonlysection>>',
 
842
            r = re.compile('<<start_figonlysection>>(.*?)<<end_figonlysection>>',
845
843
                    re.DOTALL | re.MULTILINE)
846
844
            m = r.search(template)
847
845
            if m:
848
846
                return m.group(1)
849
847
 
850
 
        
 
848
 
851
849
        if self.dopreproc:
852
850
            r = re.compile('<<startoutputsection>>.*?<<endoutputsection>>',
853
851
                    re.DOTALL | re.MULTILINE)
855
853
            r = re.compile('<<startpreprocsection>>.*?<<endpreprocsection>>',
856
854
                    re.DOTALL | re.MULTILINE)
857
855
        # remove codeonly and figonly section
858
 
        r2 = re.compile('<<startfigonlysection>>.*?<<endfigonlysection>>',
 
856
        r2 = re.compile('<<start_figonlysection>>.*?<<end_figonlysection>>',
859
857
                    re.DOTALL | re.MULTILINE)
860
858
        tmp = r2.sub('',template)
861
859
        r2 = re.compile('<<startcodeonlysection>>.*?<<endcodeonlysection>>',
863
861
        tmp = r2.sub('',tmp)
864
862
        return r.sub('',tmp)
865
863
 
866
 
    def initTemplateVars(self):
 
864
    def init_template_vars(self):
867
865
        vars = {}
868
866
        # get bounding box
869
 
        bbstr = self.maingraph.bb
 
867
        bbstr = self.maingraph.attr.get('bb','')
870
868
        if bbstr:
871
869
            bb = bbstr.split(',')
872
870
            vars['<<bbox>>'] = "(%sbp,%sbp)(%sbp,%sbp)\n" % (bb[0],bb[1],bb[2],bb[3])
896
894
        else:
897
895
            vars['<<gvcols>>'] = ""
898
896
        self.templatevars = vars
899
 
            
 
897
 
900
898
    def output(self):
901
 
        self.initTemplateVars()
902
 
        template = self.cleanTemplate(self.template)
903
 
        code = replaceTags(template ,self.templatevars.keys(),
 
899
        self.init_template_vars()
 
900
        template = self.clean_template(self.template)
 
901
        code = replace_tags(template ,self.templatevars.keys(),
904
902
                           self.templatevars)
905
903
        #code = self.template.replace('<<figcode>>', self.body)
906
904
        return code
907
 
    
908
 
    def getLabel(self, drawobj):
 
905
 
 
906
    def get_label(self, drawobj):
909
907
        text = ""
910
908
        texmode = self.options.get('texmode','verbatim')
911
909
        if getattr(drawobj,'texmode', ''):
912
910
            texmode = drawobj.texmode
913
911
        text = getattr(drawobj,'label',None)
914
 
        
 
912
 
915
913
        #log.warning('text %s %s',text,drawobj.to_string())
916
 
        
 
914
 
917
915
        if text == None or text.strip() == '\N':
918
 
            
919
 
            if not isinstance(drawobj, pydot.Edge):
 
916
 
 
917
            if not isinstance(drawobj, dotparsing.DotEdge):
920
918
                text = getattr(drawobj,'name',None) or \
921
919
                    getattr(drawobj,'graph_name','')
 
920
                text = text.replace("\\\\","\\")
922
921
            else:
923
922
                text = ''
924
923
        elif text.strip() == '\N':
925
924
            text =  ''
926
925
        else:
927
926
            text = text.replace("\\\\","\\")
928
 
        
 
927
 
929
928
        if getattr(drawobj,'texlbl', ''):
930
929
            # the texlbl overrides everything
931
930
            text = drawobj.texlbl
932
931
        elif texmode == 'verbatim':
933
932
            # verbatim mode
934
 
            text = escapeTeXChars(text)
 
933
            text = escape_texchars(text)
935
934
            pass
936
935
        elif texmode == 'math':
937
936
            # math mode
938
937
            text = "$%s$" % text
939
 
        
 
938
 
940
939
        return text
941
940
 
942
941
    # temp
943
 
    def getLabeld(self, drawobj):
 
942
    def get_labeld(self, drawobj):
944
943
        text = ""
945
944
        texmode = self.options.get('texmode','verbatim')
946
945
        if drawobj.get('texmode', ''):
957
956
            text = drawobj["texlbl"]
958
957
        elif texmode == 'verbatim':
959
958
            # verbatim mode
960
 
            text = escapeTeXChars(text)
 
959
            text = escape_texchars(text)
961
960
            pass
962
961
        elif texmode == 'math':
963
962
            # math mode
964
963
            text = "$%s$" % text
965
964
        return text
966
965
 
967
 
    def getNodePreprocCode(self, node):
968
 
        return node['texlbl']
969
 
 
970
 
    def getEdgePreprocCode(self,edge):
971
 
        return edge.texlbl
972
 
 
973
 
    def getGraphPreprocCode(self,graph):
974
 
        return graph.texlbl
975
 
        
976
 
 
977
 
    def getMargins(self, element):
 
966
    def get_node_preproc_code(self, node):
 
967
        return node.attr.get('texlbl','')
 
968
 
 
969
    def get_edge_preproc_code(self,edge):
 
970
        return edge.attr.get('texlbl','')
 
971
 
 
972
    def get_graph_preproc_code(self,graph):
 
973
        return graph.attr.get('texlbl','')
 
974
 
 
975
 
 
976
    def get_margins(self, element):
978
977
        """Return element margins"""
979
 
        margins = element.__dict__.get('margin',None)
980
 
        #print margins
981
 
        #print type(margins)
 
978
        margins = element.attr.get('margin',None)
 
979
 
982
980
        if margins:
983
981
            margins = margins.split(',')
984
982
            if len(margins) == 1:
988
986
                ymargin = float(margins[1])
989
987
        else:
990
988
            # use default values
991
 
            if isinstance(element,pydot.Edge):
 
989
            if isinstance(element,dotparsing.DotEdge):
992
990
                xmargin = DEFAULT_EDGELABEL_XMARGIN
993
991
                ymargin = DEFAULT_EDGELABEL_YMARGIN
994
992
            else:
998
996
 
999
997
    # Todo: Add support for head and tail labels!
1000
998
    # Todo: Support rect nodes if possible.
1001
 
    def doPreviewPreproc(self):
1002
 
        setDotAttr(self.maingraph)
1003
 
        self.initTemplateVars()
1004
 
        template = self.cleanTemplate(self.template)
1005
 
        template = replaceTags(template ,self.templatevars.keys(),
 
999
    def do_preview_preproc(self):
 
1000
        #setDotAttr(self.maingraph)
 
1001
        self.init_template_vars()
 
1002
        template = self.clean_template(self.template)
 
1003
        template = replace_tags(template ,self.templatevars.keys(),
1006
1004
                           self.templatevars)
1007
1005
        pp = TeXDimProc(template, self.options)
1008
1006
        processednodes = {}
1009
1007
        processededges = {}
1010
1008
        processedgraphs = {}
1011
 
        
 
1009
 
1012
1010
        usednodes = {}
1013
1011
        usededges = {}
1014
1012
        usedgraphs = {}
1015
1013
        # iterate over every element in the graph
1016
1014
        counter = 0
1017
 
        for element in getAllGraphElements(self.maingraph,[]):
1018
 
            if isinstance(element, pydot.Node):
1019
 
                # is it a node statement?
1020
 
                node = element
1021
 
                if node.name.startswith('"'):
1022
 
                    node_stmt = True
1023
 
                else:
1024
 
                    # node defined in a edge statement (edge_stmt)
1025
 
                    node_stmt = False
1026
 
                name = node.name.replace('"','')
1027
 
                if node_stmt:
1028
 
                    node.name=name
1029
 
                #print node.texmode
1030
 
                if name=='node' or name=='edge' or name=='graph': continue
1031
 
                
1032
 
                label = self.getLabel(node)
1033
 
                tmpnode = processednodes.get(name,None)
1034
 
                
1035
 
                    
1036
 
                # has the node been defined before?
1037
 
                if tmpnode:
1038
 
                    pass
1039
 
##                    # is it a new label?
1040
 
##                    if node.label or node.texlbl:
1041
 
##                        #if tmpnode['texlbl'] <> label and node_stmt:
1042
 
##                        tmpnode['texlbl'] = label
1043
 
                else:
1044
 
                    tmpnode = dict(texlbl=None, width=None, height=None,
1045
 
                                    margin=None, style=None, fixedsize=None,
1046
 
                                    shape=None, texmode=None,label=None,
1047
 
                                    lblstyle=None)
1048
 
                    processednodes[name]= tmpnode
1049
 
                if node.width:
1050
 
                    tmpnode['width'] = node.width
1051
 
                if node.height:
1052
 
                    tmpnode['height'] = node.height
1053
 
                if node.margin:
1054
 
                    tmpnode['margin'] = node.margin
1055
 
                if node.style:
1056
 
                    tmpnode['style'] = node.style
1057
 
                if node.texmode:
1058
 
                    tmpnode['texmode'] = node.texmode
1059
 
                if node.label or node.label=='':
1060
 
                    tmpnode['label'] = node.label
1061
 
                if node.texlbl:
1062
 
                    tmpnode['texlbl'] = node.texlbl
1063
 
                if node.lblstyle:
1064
 
                    tmpnode['lblstyle'] = node.lblstyle
1065
 
                if node.shape:
1066
 
                    tmpnode['shape'] = node.shape
1067
 
                if node.fixedsize:
1068
 
                    tmpnode['fixedsize'] = node.fixedsize.lower()
1069
 
                    #log.debug('nodeinfo %s %s',name, node.fixedsize)
1070
 
                if node.shape:
1071
 
                    tmpnode['shape'] = node.shape
1072
 
            elif isinstance(element, pydot.Edge):
1073
 
                if not element.label: continue
1074
 
                # Ensure that the edge name is unique.
1075
 
                name = element.src + element.dst +str(counter)
1076
 
                label = self.getLabel(element)
1077
 
                element.texlbl = label
1078
 
                processededges[name]=element
1079
 
            elif isinstance(element, pydot.Graph):
1080
 
                if not getattr(element,'label',None) and \
1081
 
                    not getattr(element,'texlbl',None): continue
1082
 
                name = element.graph_name
1083
 
                label = self.getLabel(element)
1084
 
                element.texlbl = label
1085
 
                processedgraphs[name]=element
1086
 
                
1087
 
            else:
1088
 
                pass
1089
 
            counter += 1
1090
 
 
1091
 
        for name, item in processednodes.items():
1092
 
            if item['fixedsize'] == 'true' or item['style'] == 'invis':
1093
 
                continue
1094
 
            if item['shape'] == 'record':
 
1015
 
 
1016
##            elif isinstance(element, dotparsing.DotSubGraph):
 
1017
##                if not getattr(element,'label',None) and \
 
1018
##                    not getattr(element,'texlbl',None): continue
 
1019
##                name = element.graph_name
 
1020
##                label = self.get_label(element)
 
1021
##                element.texlbl = label
 
1022
##                processedgraphs[name]=element
 
1023
##
 
1024
##            else:
 
1025
##                pass
 
1026
##            counter += 1
 
1027
 
 
1028
        for node in self.maingraph.allnodes:
 
1029
            name = node.name
 
1030
            if node.attr.get('fixedsize','') == 'true' \
 
1031
                or node.attr.get('style','') in ['invis','invisible']:
 
1032
                    continue
 
1033
            if node.attr.get('shape','') == 'record':
1095
1034
                log.warning('Record nodes not supported in preprocessing mode: %s',name)
1096
1035
                continue
1097
 
            item['name'] = name
1098
 
            texlbl = self.getLabeld(item)
 
1036
            texlbl = self.get_label(node)
 
1037
 
1099
1038
            if texlbl:
1100
 
                item['texlbl'] = texlbl
1101
 
                code = self.getNodePreprocCode(item)
1102
 
                pp.addSnippet(name,code)
1103
 
            #log.warning(item['texlbl'])
1104
 
            usednodes[name] = item
1105
 
        for name, item in processededges.items():
1106
 
            code = self.getEdgePreprocCode(item)
1107
 
            pp.addSnippet(name,code)
1108
 
            usededges[name] = item
1109
 
        for name, item in processedgraphs.items():
1110
 
            code = self.getGraphPreprocCode(item)
1111
 
            pp.addSnippet(name,code)
1112
 
            usedgraphs[name] = item
1113
 
            
1114
 
        
 
1039
                node.attr['texlbl'] = texlbl
 
1040
                code = self.get_node_preproc_code(node)
 
1041
                pp.add_snippet(name,code)
 
1042
 
 
1043
 
 
1044
            usednodes[name] = node
 
1045
        for edge in dotparsing.flatten(self.maingraph.alledges):
 
1046
            if not edge.attr.get('label'): continue
 
1047
            # Ensure that the edge name is unique.
 
1048
            name = edge.src.name + edge.dst.name +str(counter)
 
1049
            label = self.get_label(edge)
 
1050
            edge.attr['texlbl'] = label
 
1051
            code = self.get_edge_preproc_code(edge)
 
1052
            pp.add_snippet(name,code)
 
1053
            usededges[name] = edge
 
1054
            counter += 1
 
1055
 
 
1056
        for graph in self.maingraph.allgraphs:
 
1057
            if not graph.attr.get('label',None) and \
 
1058
               not graph.attr.get('texlbl',None): continue
 
1059
            # Make sure that the name is unique
 
1060
            name = graph.name + str(counter)
 
1061
 
 
1062
            counter += 1
 
1063
            label = self.get_label(graph)
 
1064
            graph.attr['texlbl'] = label
 
1065
            code = self.get_graph_preproc_code(graph)
 
1066
            pp.add_snippet(name,code)
 
1067
            usedgraphs[name] = graph
 
1068
 
1115
1069
        ok = pp.process()
1116
 
        
 
1070
 
1117
1071
        if not ok:
1118
1072
            errormsg = """\
1119
1073
Failed to preprocess the graph.
1122
1076
"""
1123
1077
            log.error(errormsg)
1124
1078
            sys.exit(1)
1125
 
        
 
1079
 
1126
1080
        for name,item in usednodes.items():
1127
 
            if not item['texlbl']:continue
1128
 
            node = pydot.Node('"'+name+'"',texlbl=item['texlbl'],margin=item['margin'])
 
1081
            if not item.attr.get('texlbl'):continue
 
1082
            node = item
1129
1083
            hp,dp,wt = pp.texdims[name]
1130
1084
            if self.options.get('rawdim',False):
1131
1085
                # use dimesions from preview.sty directly
1132
 
                node.width = wt
1133
 
                node.height = hp+dp
1134
 
                node.label=" "
1135
 
                node.fixedsize='true'
1136
 
                self.maingraph.add_node(node)
 
1086
                node.attr['width'] = wt
 
1087
                node.attr['height'] = hp+dp
 
1088
                node.attr['label'] = " "
 
1089
                node.attr['fixedsize']='true'
 
1090
                self.maingraph.allitems.append(node)
1137
1091
                continue
1138
 
            xmargin, ymargin = self.getMargins(node)
 
1092
 
 
1093
            xmargin, ymargin = self.get_margins(node)
1139
1094
            #print xmargin, ymargin
1140
1095
            ht = hp+dp
1141
 
            minwidth = float(item['width'] or DEFAULT_NODE_WIDTH)
1142
 
            minheight = float(item['height'] or DEFAULT_NODE_HEIGHT)
 
1096
            minwidth = float(item.attr.get('width') or DEFAULT_NODE_WIDTH)
 
1097
            minheight = float(item.attr.get('height') or DEFAULT_NODE_HEIGHT)
1143
1098
            if self.options.get('nominsize',False):
1144
1099
                width = wt+2*xmargin
1145
1100
                height = ht+2*ymargin
1146
1101
            else:
1147
 
                
1148
1102
                if (wt+2*xmargin) < minwidth:
1149
1103
                    width = minwidth
1150
1104
                    #log.warning("%s %s %s %s %s",minwidth,wt,width,name,tmp)
1159
1113
            # Treat shapes with equal widht and height differently
1160
1114
            # Warning! Rectangles will not alway fit inside a circle
1161
1115
            #          Should use the diagonal.
1162
 
            if item['shape'] in ['circle','Msquare','doublecircle','Mcircle']:
 
1116
            if item.attr.get('shape','') in ['circle','Msquare','doublecircle','Mcircle']:
1163
1117
                #log.warning('%s %s', name, item['shape'])
1164
1118
                if  wt< height and width < height:
1165
1119
                    width = height
1167
1121
                    height=width
1168
1122
                    pass
1169
1123
 
1170
 
            node.width = width
1171
 
            node.height = height
1172
 
            node.label=" "
1173
 
            
1174
 
            node.fixedsize='true'
1175
 
            self.maingraph.add_node(node)
 
1124
            node.attr['width'] = width
 
1125
            node.attr['height'] = height
 
1126
            node.attr['label'] = " "
 
1127
 
 
1128
            node.attr['fixedsize']='true'
 
1129
            self.maingraph.allitems.append(node)
 
1130
 
1176
1131
        for name,item in usededges.items():
1177
 
            
 
1132
 
1178
1133
            edge = item
1179
1134
            hp,dp,wt = pp.texdims[name]
1180
 
            xmargin, ymargin = self.getMargins(edge)
 
1135
            xmargin, ymargin = self.get_margins(edge)
1181
1136
            labelcode = '<<<table border="0" cellborder="0" cellpadding="0">'\
1182
1137
                        '<tr><td fixedsize="true" width="%s" height="%s">a</td>'\
1183
1138
                        '</tr></table>>>'
1184
 
            edge.label=labelcode % ((wt + 2*xmargin)*72, (hp+dp+2*ymargin)*72)
 
1139
            edge.attr['label']=labelcode % ((wt + 2*xmargin)*72, (hp+dp+2*ymargin)*72)
 
1140
            #self.maingraph.allitems.append(edge)
1185
1141
        for name,item in usedgraphs.items():
1186
1142
            graph = item
1187
1143
            hp,dp,wt = pp.texdims[name]
1188
 
            xmargin, ymargin = self.getMargins(graph)
 
1144
            xmargin, ymargin = self.get_margins(graph)
1189
1145
            labelcode = '<<<table border="0" cellborder="0" cellpadding="0">'\
1190
1146
                        '<tr><td fixedsize="true" width="%s" height="%s">a</td>'\
1191
1147
                        '</tr></table>>>'
1192
 
            graph.label=labelcode % ((wt + 2*xmargin)*72, (hp+dp+2*ymargin)*72)
1193
 
            
1194
 
        self.maingraph.d2toutputformat = self.options.get('format',
 
1148
            graph.attr['label']=labelcode % ((wt + 2*xmargin)*72, (hp+dp+2*ymargin)*72)
 
1149
 
 
1150
 
 
1151
        self.maingraph.attr['d2toutputformat'] = self.options.get('format',
1195
1152
                                             DEFAULT_OUTPUT_FORMAT)
1196
 
        graphcode = self.maingraph.to_string()
1197
 
        graphcode = graphcode.replace('"<<<','<<')
1198
 
        graphcode = graphcode.replace('>>>"','>>')
 
1153
        graphcode = str(self.maingraph)
 
1154
        graphcode = graphcode.replace('<<<','<<')
 
1155
        graphcode = graphcode.replace('>>>','>>')
1199
1156
        return graphcode
1200
1157
 
1201
 
            
 
1158
 
1202
1159
 
1203
1160
 
1204
1161
PSTRICKS_TEMPLATE = r"""\documentclass{article}
1234
1191
<<endoutputsection>>%
1235
1192
\end{document}
1236
1193
%
1237
 
<<startfigonlysection>>
 
1194
<<start_figonlysection>>
1238
1195
\begin{pspicture}[linewidth=1bp<<graphstyle>>]<<bbox>>
1239
1196
  \pstVerb{2 setlinejoin} % set line join style to 'mitre'
1240
1197
<<figpreamble>>%
1241
1198
<<drawcommands>>
1242
1199
<<figpostamble>>%
1243
1200
\end{pspicture}
1244
 
<<endfigonlysection>>
 
1201
<<end_figonlysection>>
1245
1202
%
1246
1203
<<startcodeonlysection>>
1247
1204
<<figpreamble>>%
1264
1221
            filled = "",
1265
1222
          )
1266
1223
 
1267
 
    def doGraphtmp(self):
 
1224
    def do_graphtmp(self):
1268
1225
        self.pencolor = "";
1269
1226
        self.fillcolor = ""
1270
1227
        self.color = ""
1271
1228
        self.body += '{\n'
1272
 
        DotConvBase.doGraph(self)
 
1229
        DotConvBase.do_graph(self)
1273
1230
        self.body += '}\n'
1274
 
    
1275
 
    def startFig(self):
 
1231
 
 
1232
    def start_fig(self):
1276
1233
        # get bounding box
1277
1234
        bbstr = self.maingraph.bb
1278
1235
        if bbstr:
1285
1242
        #return s
1286
1243
        return ""
1287
1244
 
1288
 
    def endFig(self):
 
1245
    def end_fig(self):
1289
1246
        #return '\end{pspicture}\n'
1290
1247
        return ""
1291
 
    
1292
 
    def drawEllipse(self, drawop, style = None):
 
1248
 
 
1249
    def draw_ellipse(self, drawop, style = None):
1293
1250
        op, x,y,w,h = drawop
1294
1251
        s = ""
1295
1252
        #s =  "  %% Node: %s\n" % node.name
1305
1262
                stylestr += ','+ style
1306
1263
            else:
1307
1264
                stylestr = style
1308
 
            
 
1265
 
1309
1266
        s += "  \psellipse[%s](%sbp,%sbp)(%sbp,%sbp)\n" % (stylestr,x,y, \
1310
1267
                # w+self.linewidth,h+self.linewidth)
1311
1268
                w,h)
1312
 
        
 
1269
 
1313
1270
 
1314
1271
        return s
1315
 
    
1316
 
    def drawPolygon(self, drawop, style = None):
 
1272
 
 
1273
    def draw_polygon(self, drawop, style = None):
1317
1274
        op, points = drawop
1318
1275
        pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
1319
1276
        stylestr = ""
1329
1286
 
1330
1287
        s = "  \pspolygon[%s]%s\n" % (stylestr, "".join(pp))
1331
1288
        return s
1332
 
        
1333
 
    def drawPolyLine(self, drawop, style = None):
 
1289
 
 
1290
    def draw_polyline(self, drawop, style = None):
1334
1291
        op, points = drawop
1335
1292
        pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
1336
1293
        s = "  \psline%s\n" % "".join(pp)
1337
1294
        return s
1338
 
    
1339
 
    def drawBezier(self, drawop, style = None):
 
1295
 
 
1296
    def draw_bezier(self, drawop, style = None):
1340
1297
        op, points = drawop
1341
1298
        pp= []
1342
1299
        for point in points:
1345
1302
        #points = ['(%sbp, %sbp)' % (p[0],p[1]) for p in points]
1346
1303
        arrowstyle = ""
1347
1304
        return "  \psbezier{%s}%s\n" % (arrowstyle, "".join(pp))
1348
 
    
1349
 
        
1350
 
 
1351
 
    def drawText(self, drawop, style=None):
1352
 
        
 
1305
 
 
1306
 
 
1307
 
 
1308
    def draw_text(self, drawop, style=None):
 
1309
 
1353
1310
        if len(drawop)==7:
1354
1311
            c, x, y, align, w, text, valign = drawop
1355
1312
        else:
1365
1322
            alignstr = '['+alignstr+valign+']'
1366
1323
        s = "  \\rput%s(%sbp,%sbp){%s}\n" % (alignstr, x,y,text)
1367
1324
        return s
1368
 
        
1369
 
    def setColor(self, drawop):
 
1325
 
 
1326
    def set_color(self, drawop):
1370
1327
        c, color = drawop
1371
1328
        #color = color.replace('grey','gray')
1372
1329
        #self.pencolor = "";
1373
1330
        #self.fillcolor = ""
1374
1331
        #self.color = ""
1375
 
        color = self.convertColor(color)
 
1332
        color = self.convert_color(color)
1376
1333
        s = ""
1377
1334
        if c == 'c':
1378
1335
            # set pen color
1386
1343
                self.fillcolor = color
1387
1344
                s = "  \psset{fillcolor=%s}\n" % color
1388
1345
            else: return ""
 
1346
        elif c == 'cC':
 
1347
            if self.color <> color:
 
1348
                self.color = color;
 
1349
                self.pencolor = self.fillcolor = color;
 
1350
                s = "  \psset{linecolor=%s}\n" % (color);
 
1351
        else:
 
1352
            log.warning('Unhandled color: %s',drawop)
1389
1353
        return s
1390
 
    
1391
 
    def setStyle(self, drawop):
 
1354
 
 
1355
    def set_style(self, drawop):
1392
1356
        c, style = drawop
1393
1357
        psstyle = self.styles.get(style,"")
1394
1358
        if psstyle:
1395
1359
            return "  \psset{%s}\n" % psstyle
1396
1360
        else:
1397
1361
            return ""
1398
 
        
1399
 
    def filterStyles(self, style):
 
1362
 
 
1363
    def filter_styles(self, style):
1400
1364
        fstyles = []
1401
1365
        for item in style.split(','):
1402
1366
            keyval = item.strip()
1403
1367
            if keyval.find('setlinewidth') < 0:
1404
1368
                fstyles.append(keyval)
1405
1369
        return ', '.join(fstyles)
1406
 
        
1407
 
    def startNode(self, node):
 
1370
 
 
1371
    def start_node(self, node):
1408
1372
        self.pencolor = "";
1409
1373
        self.fillcolor = ""
1410
1374
        self.color = ""
1411
1375
        return "{%\n"
1412
 
    
1413
 
    def endNode(self,node):
 
1376
 
 
1377
    def end_node(self,node):
1414
1378
        return "}%\n"
1415
 
    
1416
 
    def startEdge(self):
 
1379
 
 
1380
    def start_edge(self):
1417
1381
        self.pencolor = "";
1418
1382
        self.fillcolor = ""
1419
1383
        return "{%\n"
1420
1384
 
1421
 
    def endEdge(self):
 
1385
    def end_edge(self):
1422
1386
        return "}%\n"
1423
1387
 
1424
 
    def startGraph(self, graph):
 
1388
    def start_graph(self, graph):
1425
1389
        self.pencolor = "";
1426
1390
        self.fillcolor = ""
1427
1391
        self.color = ""
1428
1392
        return "{\n"
1429
1393
 
1430
 
    def endGraph(self,node):
 
1394
    def end_graph(self,node):
1431
1395
        return "}\n"
1432
 
        
1433
 
 
1434
 
    def drawEllipseNode(self, x,y,w,h,node):
1435
 
        s =  "  %% Node: %s\n" % node.name
1436
 
        s += "  \psellipse(%sbp,%sbp)(%sbp,%sbp)\n" % (x,y, \
1437
 
                 w/2+self.linewidth,h/2+self.linewidth)
1438
 
        # label
1439
 
        if node.label:
1440
 
            label = node.label
1441
 
        else:
1442
 
            label = node.name
1443
 
        s += "  \\rput(%sbp,%sbp){$%s$}\n" % (x,y,label)
1444
 
 
1445
 
        return s
1446
 
    
1447
 
    
1448
 
    
1449
 
    def drawEdge(self, edge):
 
1396
 
 
1397
 
 
1398
##    def draw_ellipseNode(self, x,y,w,h,node):
 
1399
##        s =  "  %% Node: %s\n" % node.name
 
1400
##        s += "  \psellipse(%sbp,%sbp)(%sbp,%sbp)\n" % (x,y, \
 
1401
##                 w/2+self.linewidth,h/2+self.linewidth)
 
1402
##        # label
 
1403
##        if node.label:
 
1404
##            label = node.label
 
1405
##        else:
 
1406
##            label = node.name
 
1407
##        s += "  \\rput(%sbp,%sbp){$%s$}\n" % (x,y,label)
 
1408
##
 
1409
##        return s
 
1410
##
 
1411
 
 
1412
 
 
1413
    def draw_edge(self, edge):
1450
1414
        s = ""
1451
 
        if edge.style == 'invis':
 
1415
        if edge.attr.get('style','') in ['invis','invisible']:
1452
1416
            return ""
1453
 
        arrowstyle, points = self.getEdgePoints(edge)
1454
 
        if arrowstyle == '--': arrowstyle=''
1455
 
        color = getattr(edge,'color','')
1456
 
        if self.color <> color:
1457
 
            if color:
1458
 
                s += self.setColor(('c',color))
1459
 
            else:
1460
 
                # reset to default color
1461
 
                s += self.setColor(('c','black'))
1462
 
        pp= []
1463
 
        for point in points:
1464
 
            p = point.split(',')
1465
 
            pp.append("(%sbp,%sbp)" % (p[0],p[1]))
 
1417
        edges = self.get_edge_points(edge)
 
1418
        for arrowstyle, points in edges:
 
1419
            if arrowstyle == '--': arrowstyle=''
 
1420
            color = getattr(edge,'color','')
 
1421
            if self.color <> color:
 
1422
                if color:
 
1423
                    s += self.set_color(('c',color))
 
1424
                else:
 
1425
                    # reset to default color
 
1426
                    s += self.set_color(('c','black'))
 
1427
            pp= []
 
1428
            for point in points:
 
1429
                p = point.split(',')
 
1430
                pp.append("(%sbp,%sbp)" % (p[0],p[1]))
1466
1431
 
1467
 
        edgestyle = edge.style
1468
 
        styles = []
1469
 
        if arrowstyle:
1470
 
            styles.append('arrows=%s' % arrowstyle)
1471
 
        if edgestyle:
1472
 
            edgestyles = [self.styles.get(key.strip(),key.strip()) for key in edgestyle.split(',') if key]
1473
 
            styles.extend(edgestyles)
1474
 
        if styles:
1475
 
            stylestr = ",".join(styles)
1476
 
        else:
1477
 
            stylestr = ""
1478
 
        if not self.options.get('straightedges',False):
1479
 
            s += "  \psbezier[%s]%s\n" % (stylestr,"".join(pp))
1480
 
        else:
1481
 
            s += "  \psline[%s]%s%s\n" % (stylestr,  pp[0], pp[-1])
1482
 
        #s += "  \psbezier[%s]{%s}%s\n" % (stylestr, arrowstyle,"".join(pp))
1483
 
##        if edge.label:
1484
 
##            x,y = edge.lp.split(',')
1485
 
##            #s += "\\rput(%s,%s){%s}\n" % (x,y,edge.label)
 
1432
            edgestyle = edge.attr.get('style','')
 
1433
            styles = []
 
1434
            if arrowstyle:
 
1435
                styles.append('arrows=%s' % arrowstyle)
 
1436
            if edgestyle:
 
1437
                edgestyles = [self.styles.get(key.strip(),key.strip()) \
 
1438
                    for key in edgestyle.split(',') if key]
 
1439
                styles.extend(edgestyles)
 
1440
            if styles:
 
1441
                stylestr = ",".join(styles)
 
1442
            else:
 
1443
                stylestr = ""
 
1444
            if not self.options.get('straightedges',False):
 
1445
                s += "  \psbezier[%s]%s\n" % (stylestr,"".join(pp))
 
1446
            else:
 
1447
                s += "  \psline[%s]%s%s\n" % (stylestr,  pp[0], pp[-1])
 
1448
            #s += "  \psbezier[%s]{%s}%s\n" % (stylestr, arrowstyle,"".join(pp))
 
1449
    ##        if edge.label:
 
1450
    ##            x,y = edge.lp.split(',')
 
1451
    ##            #s += "\\rput(%s,%s){%s}\n" % (x,y,edge.label)
1486
1452
        return s
1487
 
    
1488
 
    def initTemplateVars(self):
1489
 
        DotConvBase.initTemplateVars(self)
 
1453
 
 
1454
    def init_template_vars(self):
 
1455
        DotConvBase.init_template_vars(self)
1490
1456
        # Put a ',' before <<graphstyle>>
1491
1457
        graphstyle = self.templatevars.get('<<graphstyle>>','')
1492
1458
        if graphstyle:
1495
1461
                graphstyle = ','+graphstyle
1496
1462
                self.templatevars['<<graphstyle>>'] = graphstyle
1497
1463
 
1498
 
        
 
1464
 
1499
1465
PGF_TEMPLATE = r"""\documentclass{article}
1500
1466
\usepackage[x11names, rgb]{xcolor}
1501
1467
\usepackage[<<textencoding>>]{inputenc}
1531
1497
%
1532
1498
\end{document}
1533
1499
%
1534
 
<<startfigonlysection>>
 
1500
<<start_figonlysection>>
1535
1501
\begin{tikzpicture}[>=latex,join=bevel,<<graphstyle>>]
1536
1502
  \pgfsetlinewidth{1bp}
1537
1503
<<figpreamble>>%
1538
1504
<<drawcommands>>
1539
1505
<<figpostamble>>%
1540
1506
\end{tikzpicture}
1541
 
<<endfigonlysection>>
 
1507
<<end_figonlysection>>
1542
1508
<<startcodeonlysection>>
1543
1509
<<figpreamble>>%
1544
1510
<<drawcommands>>
1560
1526
            dotted='\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}',
1561
1527
            bold = '\pgfsetlinewidth{1.2pt}')
1562
1528
 
1563
 
    def startFig(self):
 
1529
    def start_fig(self):
1564
1530
        # get bounding box
1565
1531
        # get bounding box
1566
1532
        s = ""
1569
1535
##            bb = bbstr.split(',')
1570
1536
##            s += "%%(%sbp,%sbp)(%sbp,%sbp)\n" % \
1571
1537
##                (bb[0],bb[1],bb[2],bb[3])
1572
 
        
 
1538
 
1573
1539
        return s
1574
1540
 
1575
 
    def endFig(self):
 
1541
    def end_fig(self):
1576
1542
        #return '\end{tikzpicture}'
1577
1543
        return ""
1578
 
    
1579
 
    def startNode(self, node):
 
1544
 
 
1545
    def start_node(self, node):
1580
1546
        # Todo: Should find a more elgant solution
1581
1547
        self.pencolor = "";
1582
1548
        self.fillcolor = ""
1583
1549
        self.color = ""
1584
1550
        return "\\begin{scope}\n"
1585
 
    
1586
 
    def endNode(self,node):
 
1551
 
 
1552
    def end_node(self,node):
1587
1553
        return "\\end{scope}\n"
1588
 
    
1589
 
    def startEdge(self):
 
1554
 
 
1555
    def start_edge(self):
1590
1556
        # Todo: Should find a more elgant solution
1591
1557
        #self.pencolor = "";
1592
1558
        #self.fillcolor = ""
1593
1559
        #self.color = ""
1594
1560
        return "\\begin{scope}\n"
1595
1561
 
1596
 
    def endEdge(self):
 
1562
    def end_edge(self):
1597
1563
        return "\\end{scope}\n"
1598
 
    
1599
 
    def startGraph(self, graph):
 
1564
 
 
1565
    def start_graph(self, graph):
1600
1566
        # Todo: Should find a more elgant solution
1601
1567
        self.pencolor = "";
1602
1568
        self.fillcolor = ""
1603
1569
        self.color = ""
1604
1570
        return "\\begin{scope}\n"
1605
1571
 
1606
 
    def endGraph(self,graph):
 
1572
    def end_graph(self,graph):
1607
1573
        return "\\end{scope}\n"
1608
1574
        #return "\\end{scope}"
1609
 
    
1610
 
    def setColor(self, drawop):
 
1575
 
 
1576
    def set_color(self, drawop):
1611
1577
        c, color = drawop
1612
1578
        # Todo: Should find a more elgant solution
1613
1579
        #self.pencolor = "";
1614
1580
        #self.fillcolor = ""
1615
1581
        #self.color = ""
1616
 
        res = self.convertColor(color, True)
 
1582
        res = self.convert_color(color, True)
1617
1583
        opacity = None
1618
1584
        if len(res)==2:
1619
1585
            ccolor, opacity = res
1663
1629
                    self.opacity = None
1664
1630
            else: return ""
1665
1631
        return s
1666
 
    
1667
 
    def setStyle(self, drawop):
 
1632
 
 
1633
    def set_style(self, drawop):
1668
1634
        c, style = drawop
1669
1635
        pgfstyle = self.dashstyles.get(style,"")
1670
1636
        if pgfstyle:
1671
1637
            return "  %s\n" % pgfstyle
1672
1638
        else:
1673
1639
            return ""
1674
 
    
1675
 
    def filterStyles(self, style):
 
1640
 
 
1641
    def filter_styles(self, style):
1676
1642
        fstyles = []
1677
1643
        for item in style.split(','):
1678
1644
            keyval = item.strip()
1681
1647
        return ', '.join(fstyles)
1682
1648
 
1683
1649
 
1684
 
    def drawEllipse(self, drawop, style = None):
 
1650
    def draw_ellipse(self, drawop, style = None):
1685
1651
        op, x,y,w,h = drawop
1686
1652
        s = ""
1687
1653
        #s =  "  %% Node: %s\n" % node.name
1702
1668
                # w+self.linewidth,h+self.linewidth)
1703
1669
                w,h)
1704
1670
        return s
1705
 
    
1706
 
    def drawPolygon(self, drawop, style = None):
 
1671
 
 
1672
    def draw_polygon(self, drawop, style = None):
1707
1673
        op, points = drawop
1708
1674
        pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
1709
1675
        cmd = "draw"
1710
1676
        if op == 'P':
1711
1677
            cmd = "filldraw"
1712
 
            
 
1678
 
1713
1679
        if style:
1714
1680
            stylestr = " [%s]" % style
1715
1681
        else:
1716
1682
            stylestr = ''
1717
1683
        s = "  \%s%s %s -- cycle;\n" % (cmd, stylestr, " -- ".join(pp))
1718
1684
        return s
1719
 
    
1720
 
    def drawPolyLine(self, drawop, style = None):
 
1685
 
 
1686
    def draw_polyline(self, drawop, style = None):
1721
1687
        op, points = drawop
1722
1688
        pp = ['(%sbp,%sbp)' % (p[0],p[1]) for p in points]
1723
1689
        ##if style:
1726
1692
##            stylestr = ''
1727
1693
        stylestr = ''
1728
1694
        return "  \draw%s %s;\n" %(stylestr, " -- ".join(pp))
1729
 
        
1730
 
    
1731
 
    def drawText(self, drawop, style = None):
 
1695
 
 
1696
 
 
1697
    def draw_text(self, drawop, style = None):
1732
1698
        # The coordinates given by drawop are not the same as the node
1733
1699
        # coordinates! This may give som odd results if graphviz' and
1734
1700
        # LaTeX' fonts are very different.
1755
1721
            lblstyle = '['+lblstyle+']'
1756
1722
        s = "  \draw (%sbp,%sbp) node%s {%s};\n" % (x,y,lblstyle,text)
1757
1723
        return s
1758
 
    
1759
 
    def drawBezier(self, drawop, style = None):
 
1724
 
 
1725
    def draw_bezier(self, drawop, style = None):
1760
1726
        s = ""
1761
1727
        c, points = drawop
1762
1728
        arrowstyle = '--'
1779
1745
##            style = '[%s]' % arrowstyle
1780
1746
 
1781
1747
        s += "  \draw%s %s .. %s;\n" % (stylestr, " .. ".join(pstrs), pp[-1])
1782
 
        
 
1748
 
1783
1749
        return s
1784
 
    
1785
 
 
1786
 
    def doEdges(self):
 
1750
 
 
1751
 
 
1752
    def do_edges(self):
1787
1753
        s = ""
1788
 
        s += self.setColor(('cC',"black"))
 
1754
        s += self.set_color(('cC',"black"))
1789
1755
        for edge in self.edges:
1790
1756
            dstring = getattr(edge,'_draw_',"")
1791
1757
            lstring = getattr(edge,'_ldraw_',"")
1798
1764
            # as in the xdot output.
1799
1765
            drawstring = dstring + " " + hstring + " " + tstring \
1800
1766
                            + " " + lstring + " " + tlstring + " " + hlstring
1801
 
            drawop,stat = parseDrawString(drawstring);
 
1767
            drawop,stat = parse_drawstring(drawstring);
1802
1768
            if not drawstring.strip():
1803
1769
                continue
1804
 
            s += self.outputEdgeComment(edge)
 
1770
            s += self.output_edge_comment(edge)
1805
1771
            if self.options.get('duplicate', False):
1806
 
                s += self.startEdge()
1807
 
                s += self.doDrawOp(drawop, edge,stat)
1808
 
                s += self.endEdge()
 
1772
                s += self.start_edge()
 
1773
                s += self.do_draw_op(drawop, edge,stat)
 
1774
                s += self.end_edge()
1809
1775
            else:
1810
1776
                topath = getattr(edge,'topath',None)
1811
 
                s += self.drawEdge(edge)
 
1777
                s += self.draw_edge(edge)
1812
1778
                if not self.options.get('tikzedgelabels',False) and not topath:
1813
 
                    s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
 
1779
                    s += self.do_drawstring(lstring+" "+tlstring+" "+hlstring, edge)
1814
1780
                else:
1815
 
                    s += self.doDrawString(tlstring+" "+hlstring, edge)
1816
 
                #s += self.drawEdge(edge)
1817
 
                #s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
 
1781
                    s += self.do_drawstring(tlstring+" "+hlstring, edge)
 
1782
                #s += self.draw_edge(edge)
 
1783
                #s += self.do_drawstring(lstring+" "+tlstring+" "+hlstring, edge)
1818
1784
        self.body += s
1819
1785
 
1820
 
     
1821
 
    def drawEdge(self, edge):
 
1786
 
 
1787
    def draw_edge(self, edge):
1822
1788
        s = ""
1823
 
        if edge.style == 'invis':
 
1789
        if edge.attr.get('style','') in ['invis','invisible']:
1824
1790
            return ""
1825
 
        arrowstyle, points = self.getEdgePoints(edge)
1826
 
        # PGF uses the fill style when drawing some arrowheads. We have to
1827
 
        # ensure that the fill color is the same as the pen color.
1828
 
        color = getattr(edge,'color','')
1829
 
        
1830
 
        if self.color <> color:
1831
 
            if color:
1832
 
                s += self.setColor(('cC',color))
1833
 
            else:
1834
 
                # reset to default color
1835
 
                s += self.setColor(('cC','black'))
1836
 
            
1837
 
        pp= []
1838
 
        for point in points:
1839
 
            p = point.split(',')
1840
 
            pp.append("(%sbp,%sbp)" % (p[0],p[1]))
1841
 
 
1842
 
        edgestyle = edge.style
1843
 
            
1844
 
        styles = []
1845
 
        if arrowstyle <> '--':
1846
 
            #styles.append(arrowstyle)
1847
 
            styles = [arrowstyle]
1848
 
 
1849
 
        if edgestyle:
1850
 
            edgestyles = [self.styles.get(key.strip(),key.strip()) for key in edgestyle.split(',') if key]
1851
 
            styles.extend(edgestyles)
1852
 
        
1853
 
        stylestr = ",".join(styles)
1854
 
        topath = getattr(edge,'topath',None)
1855
 
 
1856
 
        pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
1857
 
        extra = ""
1858
 
        if self.options.get('tikzedgelabels',False) or topath:
1859
 
            edgelabel = self.getLabel(edge)
1860
 
            #log.warning('label: %s', edgelabel)
1861
 
            lblstyle = getattr(edge,'lblstyle','')
1862
 
            if lblstyle:
1863
 
                lblstyle = '['+lblstyle+']'
1864
 
            else:
1865
 
                lblstyle = ''
1866
 
            if edgelabel:
1867
 
                extra = " node%s {%s}" % (lblstyle,edgelabel)
1868
 
        src = pp[0]
1869
 
        dst = pp[-1]
1870
 
        if topath:
1871
 
                s += "  \draw [%s] %s to[%s]%s %s;\n" % (stylestr, src,
1872
 
                                                             topath, extra,dst)
1873
 
        elif not self.options.get('straightedges',False):
1874
 
            #s += "  \draw [%s] %s .. %s;\n" % (stylestr, " .. ".join(pstrs), pp[-1])
1875
 
            s += "  \draw [%s] %s ..%s %s;\n" % (stylestr, " .. ".join(pstrs), extra,pp[-1])
1876
 
        else:
1877
 
            s += "  \draw [%s] %s --%s %s;\n" % (stylestr, pp[0],extra, pp[-1])
1878
 
        
 
1791
        edges = self.get_edge_points(edge)
 
1792
        for arrowstyle, points in edges:
 
1793
        #arrowstyle, points = self.get_edge_points(edge)
 
1794
            # PGF uses the fill style when drawing some arrowheads. We have to
 
1795
            # ensure that the fill color is the same as the pen color.
 
1796
            color = getattr(edge,'color','')
 
1797
 
 
1798
            if self.color <> color:
 
1799
                if color:
 
1800
                    s += self.set_color(('cC',color))
 
1801
                else:
 
1802
                    # reset to default color
 
1803
                    s += self.set_color(('cC','black'))
 
1804
 
 
1805
            pp= []
 
1806
            for point in points:
 
1807
                p = point.split(',')
 
1808
                pp.append("(%sbp,%sbp)" % (p[0],p[1]))
 
1809
 
 
1810
            edgestyle = edge.attr.get('style','')
 
1811
 
 
1812
            styles = []
 
1813
            if arrowstyle <> '--':
 
1814
                #styles.append(arrowstyle)
 
1815
                styles = [arrowstyle]
 
1816
 
 
1817
            if edgestyle:
 
1818
                edgestyles = [self.styles.get(key.strip(),key.strip()) for key in edgestyle.split(',') if key]
 
1819
                styles.extend(edgestyles)
 
1820
 
 
1821
            stylestr = ",".join(styles)
 
1822
            topath = getattr(edge,'topath',None)
 
1823
 
 
1824
            pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
 
1825
            extra = ""
 
1826
            if self.options.get('tikzedgelabels',False) or topath:
 
1827
                edgelabel = self.get_label(edge)
 
1828
                #log.warning('label: %s', edgelabel)
 
1829
                lblstyle = getattr(edge,'lblstyle','')
 
1830
                if lblstyle:
 
1831
                    lblstyle = '['+lblstyle+']'
 
1832
                else:
 
1833
                    lblstyle = ''
 
1834
                if edgelabel:
 
1835
                    extra = " node%s {%s}" % (lblstyle,edgelabel)
 
1836
            src = pp[0]
 
1837
            dst = pp[-1]
 
1838
            if topath:
 
1839
                    s += "  \draw [%s] %s to[%s]%s %s;\n" % (stylestr, src,
 
1840
                                                                 topath, extra,dst)
 
1841
            elif not self.options.get('straightedges',False):
 
1842
                #s += "  \draw [%s] %s .. %s;\n" % (stylestr, " .. ".join(pstrs), pp[-1])
 
1843
                s += "  \draw [%s] %s ..%s %s;\n" % (stylestr, " .. ".join(pstrs), extra,pp[-1])
 
1844
            else:
 
1845
                s += "  \draw [%s] %s --%s %s;\n" % (stylestr, pp[0],extra, pp[-1])
 
1846
 
1879
1847
        return s
1880
 
    
1881
 
    def initTemplateVars(self):
1882
 
        DotConvBase.initTemplateVars(self)
 
1848
 
 
1849
    def init_template_vars(self):
 
1850
        DotConvBase.init_template_vars(self)
1883
1851
        if self.options.get('crop',False):
1884
1852
            cropcode = "\usepackage[active,tightpage]{preview}\n" + \
1885
1853
                "\PreviewEnvironment{tikzpicture}\n" + \
1890
1858
        vars['<<cropcode>>'] = cropcode
1891
1859
        self.templatevars.update(vars)
1892
1860
 
1893
 
    def getNodePreprocCode(self,node):
1894
 
        lblstyle = node.get('lblstyle','')
1895
 
        text = node.get('texlbl','')
1896
 
        if lblstyle:
1897
 
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
1898
 
        else:
1899
 
            return r"\tikz \node {" + text +"};"
1900
 
 
1901
 
    def getEdgePreprocCode(self,edge):
1902
 
        lblstyle = getattr(edge,'lblstyle','')
1903
 
        if lblstyle:
1904
 
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, edge.texlbl)
1905
 
        else:
1906
 
            return r"\tikz \node " + "{"+edge.texlbl +"};"
1907
 
 
1908
 
    def getGraphPreprocCode(self,graph):
1909
 
        lblstyle = getattr(graph,'lblstyle','')
1910
 
        if lblstyle:
1911
 
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, graph.texlbl)
1912
 
        else:
1913
 
            return r"\tikz \node " + graph.texlbl +"};"
1914
 
        
1915
 
 
1916
 
 
1917
 
 
1918
 
        
 
1861
    def get_node_preproc_code(self,node):
 
1862
        lblstyle = node.attr.get('lblstyle','')
 
1863
        text = node.attr.get('texlbl','')
 
1864
        if lblstyle:
 
1865
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
 
1866
        else:
 
1867
            return r"\tikz \node {" + text +"};"
 
1868
 
 
1869
    def get_edge_preproc_code(self,edge):
 
1870
        lblstyle = edge.attr.get('lblstyle','')
 
1871
        text = edge.attr.get('texlbl','')
 
1872
        if lblstyle:
 
1873
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
 
1874
        else:
 
1875
            return r"\tikz \node " + "{"+text +"};"
 
1876
 
 
1877
    def get_graph_preproc_code(self,graph):
 
1878
        lblstyle = graph.attr.get('lblstyle','')
 
1879
        text = graph.attr.get('texlbl','')
 
1880
        if lblstyle:
 
1881
            return "  \\tikz \\node[%s] {%s};\n" % (lblstyle, text)
 
1882
        else:
 
1883
            return r"\tikz \node {" + text +"};"
 
1884
 
 
1885
 
 
1886
 
 
1887
 
 
1888
 
1919
1889
 
1920
1890
TIKZ_TEMPLATE = r"""\documentclass{article}
1921
1891
\usepackage[x11names, rgb]{xcolor}
1950
1920
%
1951
1921
\end{document}
1952
1922
%
1953
 
<<startfigonlysection>>
 
1923
<<start_figonlysection>>
1954
1924
\begin{tikzpicture}[>=latex,join=bevel,<<graphstyle>>]
1955
1925
<<figpreamble>>%
1956
1926
<<drawcommands>>
1957
1927
<<figpostamble>>%
1958
1928
\end{tikzpicture}
1959
 
<<endfigonlysection>>
 
1929
<<end_figonlysection>>
1960
1930
<<startcodeonlysection>>
1961
1931
<<figpreamble>>%
1962
1932
<<drawcommands>>
1979
1949
                'octagon' : 'regular polygon, regular polygon sides=8',
1980
1950
                }
1981
1951
 
 
1952
    compassmap = {'n': 'north','ne':'north east','e':'east',
 
1953
                  'se':'south east','s':'south','sw':'south west',
 
1954
                  'w':'west','nw':'north west','center':'center' }
 
1955
 
1982
1956
    def __init__(self, options={}):
1983
1957
        # to connect nodes they have to defined. Therefore we have to ensure
1984
1958
        # that code for generating nodes is outputted first.
1990
1964
            self.template = TIKZ_TEMPLATE
1991
1965
 
1992
1966
        self.styles = dict(dashed='dashed', dotted='dotted',
1993
 
                            bold='very thick', filled='fill', invis="",
 
1967
                            bold='very thick', filled='fill', invis="",invisible="",
1994
1968
                            rounded='rounded corners', )
1995
1969
        self.dashstyles = dict(
1996
1970
            dashed = '\pgfsetdash{{3pt}{3pt}}{0pt}',
1997
1971
            dotted='\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}',
1998
1972
            bold = '\pgfsetlinewidth{1.2pt}')
1999
1973
 
2000
 
    def setOptions(self):
2001
 
        Dot2PGFConv.setOptions(self)
 
1974
    def set_options(self):
 
1975
        Dot2PGFConv.set_options(self)
2002
1976
        self.options['tikzedgelabels'] = self.options.get('tikzedgelabels','') \
2003
1977
            or getboolattr(self.maingraph,'d2ttikzedgelabels','')
2004
1978
        self.options['styleonly'] = self.options.get('styleonly','') \
2007
1981
            or getattr(self.maingraph,'d2tnodeoptions','')
2008
1982
        self.options['edgeoptions'] = self.options.get('edgeoptions','') \
2009
1983
            or getattr(self.maingraph,'d2tedgeoptions','')
2010
 
            
2011
 
    def outputNodeComment(self, node):
 
1984
 
 
1985
    def output_node_comment(self, node):
2012
1986
        # With the node syntax comments are unnecessary
2013
1987
        return ""
2014
1988
 
2015
 
    def getNodePreprocCode(self,node):
2016
 
        lblstyle = node.get('lblstyle','')
2017
 
        
2018
 
        shape = node.get('shape','ellipse')
 
1989
    def set_tikzcolor(self,color,colorname):
 
1990
        res = self.convert_color(color, True)
 
1991
        if len(res)==2:
 
1992
            ccolor, opacity = res
 
1993
            if not (opacity == '1'):
 
1994
                log.warning('Opacity not supported yet: %s',res)
 
1995
        else:
 
1996
            ccolor = res
 
1997
        s = ""
 
1998
        if ccolor.startswith('{'):
 
1999
            # rgb or hsb
 
2000
            s += "  \definecolor{%s}%s;\n" % (colorname,ccolor)
 
2001
            cname = colorname
 
2002
        else:
 
2003
            cname = color
 
2004
 
 
2005
        return s,cname
 
2006
 
 
2007
 
 
2008
    def get_node_preproc_code(self,node):
 
2009
        lblstyle = node.attr.get('lblstyle','')
 
2010
 
 
2011
        shape = node.attr.get('shape','ellipse')
2019
2012
        shape = self.shapemap.get(shape, shape)
2020
2013
        #s += "%% %s\n" % (shape)
2021
 
        label = node.get('texlbl','')
2022
 
        style = node.get('style'," ") or " ";
 
2014
        label = node.attr.get('texlbl','')
 
2015
        style = node.attr.get('style'," ") or " ";
2023
2016
        if lblstyle:
2024
2017
            if style.strip():
2025
2018
                style += ','+lblstyle
2035
2028
                        (shape, style, label)
2036
2029
        return sn
2037
2030
 
2038
 
    def doNodes(self):
 
2031
    def do_nodes(self):
2039
2032
        s = ""
2040
2033
        nodeoptions=  self.options.get('nodeoptions',None)
2041
2034
        if nodeoptions:
2042
2035
            s += "\\begin{scope}[%s]\n" % nodeoptions
2043
2036
        for node in self.nodes:
2044
2037
            self.currentnode = node
2045
 
            # detect node type
2046
 
            shape = getattr(node,'shape','ellipse')
2047
 
            shape = self.shapemap.get(shape, shape)
 
2038
 
 
2039
            if node.attr.get('style') in ['invis','invisible']:
 
2040
                shape="coordinate"
 
2041
            else:
 
2042
                # detect node type
 
2043
                shape = getattr(node,'shape','ellipse')
 
2044
                shape = self.shapemap.get(shape, shape)
2048
2045
            if shape == None:
2049
2046
                shape='ellipse'
2050
 
            
 
2047
 
2051
2048
            pos = getattr(node,'pos',None)
2052
2049
            if not pos:
2053
2050
                continue
2054
2051
            x,y = pos.split(',')
2055
 
            label = self.getLabel(node)
 
2052
            label = self.get_label(node)
2056
2053
 
2057
2054
            pos = "%sbp,%sbp" % (x,y)
2058
 
            style = node.style or "";
2059
 
            if node.lblstyle:
2060
 
                if style:
2061
 
                    style += ','+node.lblstyle
2062
 
                else:
2063
 
                    style = node.lblstyle
2064
 
            if node.exstyle:
2065
 
                if style:
2066
 
                    style += ','+node.exstyle
2067
 
                else:
2068
 
                    style = node.exstyle
 
2055
            style = node.attr.get('style') or "";
 
2056
            if node.attr.get('lblstyle'):
 
2057
                if style:
 
2058
                    style += ','+node.attr['lblstyle']
 
2059
                else:
 
2060
                    style = node.attr['lblstyle']
 
2061
            if node.attr.get('exstyle'):
 
2062
                if style:
 
2063
                    style += ','+node.attr['exstyle']
 
2064
                else:
 
2065
                    style = node.attr['exstyle']
2069
2066
            sn = ""
2070
 
            sn += self.outputNodeComment(node)
2071
 
            sn += self.startNode(node)
2072
 
            if self.options.get('styleonly'):
 
2067
            sn += self.output_node_comment(node)
 
2068
            sn += self.start_node(node)
 
2069
            if shape=="coordinate":
 
2070
                sn += "  \\coordinate (%s) at (%s);\n" % (tikzify(node.name),pos)
 
2071
            elif self.options.get('styleonly'):
2073
2072
                sn += "  \\node (%s) at (%s) [%s] {%s};\n" % \
2074
 
                    (node.name, pos, style, label)
 
2073
                    (tikzify(node.name), pos, style, label)
2075
2074
            else:
2076
 
                color = getattr(node,'color','')
 
2075
                color = node.attr.get('color','')
2077
2076
                drawstr = 'draw'
2078
2077
                if style.strip() == 'filled':
 
2078
                    fillcolor = node.attr.get('fillcolor') or \
 
2079
                            node.attr.get('color') or "gray"
2079
2080
                    drawstr='fill,draw'
2080
2081
                    style = ''
2081
2082
                    if color:
2082
 
                        drawstr += ','+color
 
2083
                        code, color = self.set_tikzcolor(color,'strokecolor')
 
2084
                        sn += code
 
2085
                        code, fillcolor = self.set_tikzcolor(fillcolor,'fillcolor')
 
2086
                        sn += code
 
2087
                        drawstr = "draw=%s,fill=%s" % (color,fillcolor)
 
2088
                    else:
 
2089
                        code, fillcolor = self.set_tikzcolor(fillcolor,'fillcolor')
 
2090
                        sn += code
 
2091
                        drawstr = "draw,fill=%s" % fillcolor
2083
2092
                elif color:
 
2093
                    code, color = self.set_tikzcolor(color,'strokecolor')
 
2094
                    sn += code
2084
2095
                    drawstr += '='+color
2085
 
                    
 
2096
 
2086
2097
                if style.strip():
2087
2098
                    sn += "  \\node (%s) at (%s) [%s,%s,%s] {%s};\n" % \
2088
 
                            (node.name, pos, drawstr, shape, style, label)
 
2099
                            (tikzify(node.name), pos, drawstr, shape, style, label)
2089
2100
                else:
2090
2101
                    sn += "  \\node (%s) at (%s) [%s,%s] {%s};\n" % \
2091
 
                            (node.name, pos, drawstr, shape,  label)
2092
 
            sn += self.endNode(node)
2093
 
            
2094
 
                    
 
2102
                            (tikzify(node.name), pos, drawstr, shape,  label)
 
2103
            sn += self.end_node(node)
 
2104
 
 
2105
 
2095
2106
            s += sn
2096
2107
        if nodeoptions:
2097
2108
            s += "\\end{scope}\n"
2098
2109
        self.body += s
2099
 
    
2100
 
    def doEdges(self):
 
2110
 
 
2111
    def do_edges(self):
2101
2112
        s = ""
2102
2113
        edgeoptions = self.options.get('edgeoptions',None)
2103
2114
        if edgeoptions:
2110
2121
            tlstring = getattr(edge,'_tldraw_',"")
2111
2122
            hlstring = getattr(edge,'_hldraw_',"")
2112
2123
            topath = getattr(edge,'topath',None)
2113
 
            s += self.drawEdge(edge)
 
2124
            s += self.draw_edge(edge)
2114
2125
            if not self.options.get('tikzedgelabels',False) and not topath:
2115
 
                s += self.doDrawString(lstring+" "+tlstring+" "+hlstring, edge)
 
2126
                s += self.do_drawstring(lstring+" "+tlstring+" "+hlstring, edge)
2116
2127
            else:
2117
 
                s += self.doDrawString(tlstring+" "+hlstring, edge)
2118
 
                
 
2128
                s += self.do_drawstring(tlstring+" "+hlstring, edge)
 
2129
 
2119
2130
        if edgeoptions:
2120
2131
            s += "\\end{scope}\n"
2121
2132
        self.body += s
2122
 
        
2123
 
    def drawEdge(self, edge):
 
2133
 
 
2134
    def draw_edge(self, edge):
2124
2135
        s = ""
2125
 
        if edge.style == 'invis':
 
2136
        if edge.attr.get('style','') in ['invis','invisible']:
2126
2137
            return ""
2127
 
        arrowstyle, points = self.getEdgePoints(edge)
2128
 
        # PGF uses the fill style when drawing some arrowheads. We have to
2129
 
        # ensure that the fill color is the same as the pen color.
2130
 
        color = getattr(edge,'color','')
2131
 
        pp= []
2132
 
        for point in points:
2133
 
            p = point.split(',')
2134
 
            pp.append("(%sbp,%sbp)" % (p[0],p[1]))
2135
 
 
2136
 
        edgestyle = edge.style
2137
 
        #print edgestyle
2138
 
 
2139
 
        styles = []
2140
 
        if arrowstyle <> '--':
2141
 
            #styles.append(arrowstyle)
2142
 
            styles = [arrowstyle]
2143
 
 
2144
 
        if edgestyle:
2145
 
            edgestyles = [self.styles.get(key.strip(),key.strip()) for key in edgestyle.split(',') if key]
2146
 
            styles.extend(edgestyles)
2147
 
 
2148
 
        stylestr = ",".join(styles)
2149
 
        if color:
2150
 
            stylestr = color+','+stylestr
2151
 
        src = edge.get_source()
2152
 
        dst = edge.get_destination()
2153
 
        topath = getattr(edge,'topath',None)
2154
 
 
2155
 
        pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
2156
 
        pstrs[0] = "(%s) ..controls %s and %s " % (src, pp[1], pp[2])
2157
 
        extra = ""
2158
 
        if self.options.get('tikzedgelabels',False) or topath:
2159
 
            edgelabel = self.getLabel(edge)
2160
 
            #log.warning('label: %s', edgelabel)
2161
 
            lblstyle = getattr(edge,'lblstyle','')
2162
 
            exstyle = getattr(edge,'exstyle','')
2163
 
            if exstyle:
 
2138
        edges = self.get_edge_points(edge)
 
2139
        if len(edges) > 1:
 
2140
            log.warning('The tikz output format does not support edge'\
 
2141
                'concentrators yet. Expect ugly output or try the pgf or '\
 
2142
                'pstricks output formats.')
 
2143
        for arrowstyle, points in edges:
 
2144
            # PGF uses the fill style when drawing some arrowheads. We have to
 
2145
            # ensure that the fill color is the same as the pen color.
 
2146
            color = edge.attr.get('color','')
 
2147
            pp= []
 
2148
            for point in points:
 
2149
                p = point.split(',')
 
2150
                pp.append("(%sbp,%sbp)" % (p[0],p[1]))
 
2151
 
 
2152
            edgestyle = edge.attr.get('style')
 
2153
            #print edgestyle
 
2154
 
 
2155
            styles = []
 
2156
            if arrowstyle <> '--':
 
2157
                #styles.append(arrowstyle)
 
2158
                styles = [arrowstyle]
 
2159
 
 
2160
            if edgestyle:
 
2161
                edgestyles = [self.styles.get(key.strip(),key.strip()) \
 
2162
                    for key in edgestyle.split(',') if key]
 
2163
                styles.extend(edgestyles)
 
2164
 
 
2165
            stylestr = ",".join(styles)
 
2166
            if color:
 
2167
                code,color = self.set_tikzcolor(color,'strokecolor')
 
2168
                s += code;
 
2169
                stylestr = color+','+stylestr
 
2170
            src = tikzify(edge.get_source())
 
2171
            # check for a port
 
2172
            if edge.src_port:
 
2173
                src_anchor = self.compassmap.get(edge.src_port.split(':')[-1],'')
 
2174
                if src_anchor:
 
2175
                    src = "%s.%s" % (src,src_anchor)
 
2176
            dst = tikzify(edge.get_destination())
 
2177
            if edge.dst_port:
 
2178
                dst_anchor = self.compassmap.get(edge.dst_port.split(':')[-1],'')
 
2179
                if dst_anchor:
 
2180
                    dst = "%s.%s" % (dst,dst_anchor)
 
2181
            topath = edge.attr.get('topath',None)
 
2182
 
 
2183
            pstrs = ["%s .. controls %s and %s " % p for p in nsplit(pp, 3)]
 
2184
            pstrs[0] = "(%s) ..controls %s and %s " % (src, pp[1], pp[2])
 
2185
            extra = ""
 
2186
            if self.options.get('tikzedgelabels',False) or topath:
 
2187
                edgelabel = self.get_label(edge)
 
2188
                #log.warning('label: %s', edgelabel)
 
2189
                lblstyle = getattr(edge,'lblstyle','')
 
2190
                exstyle = getattr(edge,'exstyle','')
 
2191
                if exstyle:
 
2192
                    if lblstyle:
 
2193
                        lblstyle += ',' +exstyle
 
2194
                    else:
 
2195
                        lblstyle = exstyle
2164
2196
                if lblstyle:
2165
 
                    lblstyle += ',' +exstyle
 
2197
                    lblstyle = '['+lblstyle+']'
2166
2198
                else:
2167
 
                    lblstyle = exstyle
2168
 
            if lblstyle:
2169
 
                lblstyle = '['+lblstyle+']'
 
2199
                    lblstyle = ''
 
2200
                if edgelabel:
 
2201
                    extra = " node%s {%s}" % (lblstyle,edgelabel)
 
2202
 
 
2203
            if topath:
 
2204
                s += "  \draw [%s] (%s) to[%s]%s (%s);\n" % (stylestr, src,
 
2205
                                                             topath, extra,dst)
 
2206
            elif not self.options.get('straightedges',False):
 
2207
                s += "  \draw [%s] %s ..%s (%s);\n" % (stylestr,
 
2208
                    " .. ".join(pstrs), extra,dst)
2170
2209
            else:
2171
 
                lblstyle = ''
2172
 
            if edgelabel:
2173
 
                extra = " node%s {%s}" % (lblstyle,edgelabel)
2174
 
            
2175
 
        if topath:
2176
 
            s += "  \draw [%s] (%s) to[%s]%s (%s);\n" % (stylestr, src,
2177
 
                                                         topath, extra,dst)
2178
 
        elif not self.options.get('straightedges',False):
2179
 
            s += "  \draw [%s] %s ..%s (%s);\n" % (stylestr,
2180
 
                " .. ".join(pstrs), extra,dst)
2181
 
        else:
2182
 
            s += "  \draw [%s] (%s) --%s (%s);\n" % (stylestr, src, extra,dst)
 
2210
                s += "  \draw [%s] (%s) --%s (%s);\n" % (stylestr, src, extra,dst)
2183
2211
 
2184
2212
        return s
2185
 
    
2186
 
    def startNode(self, node):
2187
 
        return ""
2188
 
    
2189
 
    def endNode(self,node):
2190
 
        return ""
2191
 
  
 
2213
 
 
2214
    def start_node(self, node):
 
2215
        return ""
 
2216
 
 
2217
    def end_node(self,node):
 
2218
        return ""
 
2219
 
2192
2220
dimext = r"""
2193
2221
^\!\s Preview:\s Snippet\s
2194
2222
(?P<number>\d*)\s ended.
2212
2240
        self.options = options
2213
2241
        self.dimext_re = re.compile(dimext,re.MULTILINE|re.VERBOSE)
2214
2242
        pass
2215
 
    def addSnippet(self,id, code):
 
2243
    def add_snippet(self,id, code):
2216
2244
        """A a snippet of code to be processed"""
2217
2245
        self.snippets_id.append(id)
2218
2246
        self.snippets_code.append(code)
2243
2271
        f.close()
2244
2272
        s = open(self.tempfilename,'r').read()
2245
2273
        log.debug('Code written to %s\n' % self.tempfilename + s)
2246
 
        self.parseLogFile()
 
2274
        self.parse_log_file()
2247
2275
        shutil.rmtree(self.tempdir)
2248
2276
        log.debug('Temporary directory and files deleted')
2249
2277
        if self.texdims:
2252
2280
            return False
2253
2281
        # cleanup
2254
2282
 
2255
 
    def parseLogFile(self):
 
2283
    def parse_log_file(self):
2256
2284
        logfilename = os.path.splitext(self.tempfilename)[0]+'.log'
2257
2285
        tmpdir = os.getcwd()
2258
2286
        os.chdir(os.path.split(logfilename)[0])
2286
2314
        #for i in range(len(self.snippets_id)):
2287
2315
        #    self.texdims[self.snippets_id[i]] = self.texdimlist[i]
2288
2316
        self.texdims = dict(zip(self.snippets_id,self.texdimlist))
2289
 
    
2290
 
 
2291
 
def cleanDotNodes(graph):
2292
 
    """Remove unnecessary data from node list"""
2293
 
    nodes = graph.get_node_list()
2294
 
    tmp = [node for node in nodes if node.name.startswith('"')]
2295
 
    for node in nodes:
2296
 
        node.name = node.name.replace('"','')
2297
 
    # There are some strange issues with what nodes are returned
2298
 
    # across different operating systems and versions of pydot, pyparsing,
2299
 
    # and Graphviz. This is just a quick fix. Need to investigate this further
2300
 
    if tmp:
2301
 
        return tmp
2302
 
    else:
2303
 
        return nodes
2304
 
 
2305
 
 
2306
 
# todo: Should also support graph attributes
2307
 
def setDotAttr(graph):
2308
 
    """Distribute default attributes to nodes and edges"""
2309
 
    elements = getAllGraphElements(graph,[])
2310
 
    stack = []
2311
 
    definednodes = {}
2312
 
    defattrs = {}
2313
 
    defedgeattrs = {}
2314
 
    for element in elements:
2315
 
        if isinstance(element,pydot.Node):
2316
 
            node = element
2317
 
            nodeattrs = node.__dict__
2318
 
            # is it an attribute statement? (attr_stmt)
2319
 
            if node.name == 'node':
2320
 
                # The node attributes are stored in node.__dict__. Extract all
2321
 
                # items that are not None or functions
2322
 
                defattrs.update([a for a in nodeattrs.items()
2323
 
                                 if (a[1] or a[1]=='') and not callable(a[1])])
2324
 
            elif node.name == 'edge':
2325
 
                defedgeattrs.update([a for a in nodeattrs.items()
2326
 
                                 if a[1] and not callable(a[1])])
2327
 
                continue
2328
 
            elif node.name == 'graph':
2329
 
                continue
2330
 
            else:
2331
 
                name = node.name.replace('"','')
2332
 
                if definednodes.get(name,False):
2333
 
                    continue
2334
 
                    #pass
2335
 
                else:
2336
 
                    definednodes[name] = True
2337
 
                for key,value in defattrs.items():
2338
 
                    if value=='\N':continue
2339
 
                    if not node.__dict__.get(key):
2340
 
                        node.__dict__[key]=value
2341
 
        elif isinstance(element,pydot.Edge):
2342
 
            edge = element
2343
 
            for key,value in defedgeattrs.items():
2344
 
                if not edge.__dict__.get(key):
2345
 
                    edge.__dict__[key]=value
2346
 
        elif isinstance(element,pydot.Graph):
2347
 
            # push current set of attributes on the stack
2348
 
            stack.append((defattrs,defedgeattrs))
2349
 
            defattrs = defattrs.copy()
2350
 
            defedgeattrs = defedgeattrs.copy()
2351
 
        elif isinstance(element,EndOfGraphElement):
2352
 
            defattrs,defedgeattrs = stack.pop()
2353
 
    
2354
 
 
2355
 
def convertDot(data, backend='pstricks', options = {}):
2356
 
    """Convert xdot data to a backend compatible format
2357
 
 
2358
 
    Input:
2359
 
        data - dot
2360
 
        backend - pstricks, pgf, eepic
2361
 
    
2362
 
    """
2363
 
    graph = pydot.graph_from_dot_data(data)
2364
 
    nodes = cleanDotNodes(graph)
2365
 
    edges = graph.edge_list
2366
 
    # Detect graph type
2367
 
    if graph.graph_type == 'digraph':
2368
 
        directedgraph = True
2369
 
    else:
2370
 
        directedgraph = False
2371
 
    # process edges and nodes
2372
 
    for node in nodes:
2373
 
        pass
2374
 
        
2375
 
 
2376
 
def debug(filename = 'd:/pycode/algs/t.dot'):
2377
 
    dotdata = open(filename).read()
2378
 
    r = convertDot(dotdata)
2379
 
    print r
2380
 
 
2381
 
 
2382
 
def processCmdLine():
2383
 
    """Set up and parse command line options"""
 
2317
 
 
2318
 
 
2319
def create_options_parser():
 
2320
    """Create and and return an options parser"""
2384
2321
    usage = "Usage: %prog [options] <files>"
2385
2322
    parser = OptionParser(usage)
2386
2323
    parser.add_option("-f", "--format",
2451
2388
                  help="Use v to process the graph", metavar="v"),
2452
2389
    parser.add_option('--autosize',dest='autosize',
2453
2390
                  help="Preprocess graph and then run Graphviz", action="store_true",default=False)
2454
 
 
2455
 
    #parser.add_option("--var", action="append", dest="uservars")
2456
 
 
 
2391
    return parser
 
2392
 
 
2393
 
 
2394
def process_cmd_line():
 
2395
    """Set up and parse command line options"""
 
2396
 
 
2397
    parser = create_options_parser()
2457
2398
    (options, args) = parser.parse_args()
2458
 
    
 
2399
 
2459
2400
    return options,args,parser
2460
2401
 
2461
2402
def _runtests():
2463
2404
    doctest.testmod()
2464
2405
 
2465
2406
 
2466
 
def printVersionInfo():
 
2407
def print_version_info():
2467
2408
    print "Dot2tex version % s" % __version__
2468
2409
 
2469
 
def main():
 
2410
def load_dot_file(filename):
 
2411
 
 
2412
    try:
 
2413
        dotdata = open(filename,'rU').readlines()
 
2414
    except:
 
2415
        log.error("Could not open input file %s" % filename)
 
2416
        sys.exit(1)
 
2417
 
 
2418
    log.info('Data read from %s' % filename)
 
2419
    return dotdata
 
2420
 
 
2421
 
 
2422
## Program interface
 
2423
 
 
2424
def main(run_as_module=False,dotdata=None,options=None):
 
2425
    """Run dot2tex and convert graph
 
2426
 
 
2427
    """
2470
2428
    import platform
2471
 
    options, args,parser = processCmdLine()
 
2429
    global logstream
 
2430
    global loghandler
 
2431
    if not run_as_module:
 
2432
        options, args,parser = process_cmd_line()
2472
2433
    if options.runtests:
2473
2434
        log.warning('running tests')
2474
2435
        _runtests()
2475
2436
        sys.exit(0)
2476
 
    
 
2437
 
2477
2438
    if options.debug:
2478
2439
        # initalize log handler
2479
 
        hdlr = logging.FileHandler('dot2tex.log')
 
2440
        if run_as_module:
 
2441
            if loghandler:
 
2442
                #loghandler.flush()
 
2443
                log.removeHandler(loghandler)
 
2444
 
 
2445
            hdlr = logging.StreamHandler(StringIO(""))
 
2446
            loghandler = hdlr
 
2447
            logstream = hdlr.stream
 
2448
        else:
 
2449
            hdlr = logging.FileHandler('dot2tex.log')
2480
2450
        log.addHandler(hdlr)
2481
2451
        formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
2482
2452
        hdlr.setFormatter(formatter)
2490
2460
    log.info("System information:\n"
2491
2461
             "  Python: %s \n"
2492
2462
             "  Platform: %s\n"
2493
 
             "  Pydot: %s\n"
2494
2463
             "  Pyparsing: %s",
2495
2464
        sys.version_info, platform.platform(),
2496
 
        pydot.__version__,pydot.dot_parser.pyparsing_version)
 
2465
        dotparsing.pyparsing_version)
2497
2466
    log.info('dot2tex called with: %s' % sys.argv)
2498
2467
    log.info('Program started in %s' % os.getcwd())
2499
 
 
2500
 
    if options.printversion:
2501
 
        #print options.hest
2502
 
        printVersionInfo()
2503
 
        sys.exit(0)
2504
 
    if len(args) == 0:
2505
 
        log.info('Data read from standard input')
2506
 
        dotdata = sys.stdin.readlines()
2507
 
    elif len(args) == 1:
2508
 
        try:
2509
 
            dotdata = open(args[0], 'rU').readlines()
2510
 
        except:
2511
 
            log.error("Could not open input file %s" % args[0])
2512
 
            sys.exit(1)
2513
 
        log.info('Data read from %s' % args[0])
 
2468
    if not run_as_module:
 
2469
        if options.printversion:
 
2470
            #print options.hest
 
2471
            print_version_info()
 
2472
            sys.exit(0)
 
2473
        if len(args) == 0:
 
2474
            log.info('Data read from standard input')
 
2475
            dotdata = sys.stdin.readlines()
 
2476
        elif len(args) == 1:
 
2477
            dotdata = load_dot_file(args[0])
 
2478
    else:
 
2479
        # Make sure dotdata is compatitle with the readlines data
 
2480
        dotdata = dotdata.splitlines(True)
 
2481
    s = ""
 
2482
    # look for a line containing an \input
 
2483
    m = re.search(r"^\s*\\input\{(?P<filename>.+?)\}\s*$",
 
2484
        "".join(dotdata),re.MULTILINE)
 
2485
    if m:
 
2486
        filename = m.group(1)
 
2487
        log.info('Found \\input{%s}',filename)
 
2488
        dotdata = load_dot_file(filename)
 
2489
 
 
2490
 
2514
2491
 
2515
2492
    # I'm not quite sure why this is necessary, but some files
2516
2493
    # produces data with line endings that confuses pydot/pyparser.
2518
2495
    log.debug('Input data:\n'+"".join(dotdata))
2519
2496
    lines = [line for line in dotdata if line.strip()]
2520
2497
    dotdata = "".join(lines)
2521
 
    #dotdata = convert_line_endings(dotdata,0)
2522
 
    #optparse.check_choice()
2523
 
    
 
2498
 
2524
2499
    # check for output format attribute
2525
2500
    fmtattr = re.findall(r'd2toutputformat=(.*?);',dotdata)
2526
2501
    extraoptions = re.findall(r'd2toptions\s*=\s*"(.*?)"\s*;?',dotdata)
2540
2515
            hdlr.setFormatter(formatter)
2541
2516
            log.setLevel(logging.DEBUG)
2542
2517
            nodebug = False
2543
 
            
2544
 
    
 
2518
 
 
2519
 
2545
2520
    output_format = options.format or gfmt or DEFAULT_OUTPUT_FORMAT
2546
2521
    options.format = output_format
2547
 
    
 
2522
 
2548
2523
    if output_format in ('pstricks', 'pst'):
2549
2524
        conv = Dot2PSTricksConv(options.__dict__)
2550
2525
    elif output_format == 'pgf':
2566
2541
            f.write(s)
2567
2542
            f.close()
2568
2543
        else:
2569
 
            print s
2570
 
        #if options.texpreproc:
2571
 
        #    print conv.preproc.writeTempFile()
 
2544
            if not run_as_module:
 
2545
                print s
2572
2546
    except SystemExit:
2573
 
        pass
 
2547
        if run_as_module:
 
2548
            raise
2574
2549
    except Exception:
2575
2550
        #log.error("Could not convert the xdot input.")
2576
2551
        log.exception('Failed to process input')
2577
 
        
 
2552
 
2578
2553
    log.info('------- End of run -------')
2579
 
 
2580
 
 
 
2554
    if run_as_module:
 
2555
        return s
 
2556
 
 
2557
def convert_graph(dotsource,**kwargs):
 
2558
    """Process dotsource and return LaTeX code
 
2559
 
 
2560
    Conversion options can be specified as keyword options. Example:
 
2561
        convert_graph(data,format='tikz',crop=True)
 
2562
 
 
2563
    """
 
2564
    parser = create_options_parser()
 
2565
    (options,args) =  parser.parse_args([])
 
2566
    if kwargs.get('preproc',None):
 
2567
        kwargs['texpreproc'] = kwargs['preproc']
 
2568
        del kwargs['preproc']
 
2569
 
 
2570
    options.__dict__.update(kwargs)
 
2571
    tex = main(True, dotsource,options)
 
2572
    return tex
2581
2573
 
2582
2574
if __name__ == '__main__':
2583
2575
    main()
2584
 
    
2585
 
    
 
2576
 
 
2577
 
2586
2578