~ubuntu-branches/ubuntu/trusty/pydot/trusty-proposed

« back to all changes in this revision

Viewing changes to dot_parser.py

  • Committer: Bazaar Package Importer
  • Author(s): Peter Collingbourne
  • Date: 2008-05-26 15:35:04 UTC
  • mfrom: (1.1.1 upstream) (2.1.1 hardy)
  • Revision ID: james.westby@ubuntu.com-20080526153504-2rkqt0euic27xbjb
Tags: 1.0.2-1
* New upstream release.
* debian/patches/01-setup-py-nodata.patch: patched setup.py to stop
  data files being installed to /usr
* debian/pyversions, debian/control: package now works with only
  Python 2.4+
* debian/control:
  - dependency on python-parsing changed to (>= 1.4.10) due to
    nestedExpr symbol requirement
  - added Conflicts on dot2tex (<< 2.8.0) due to incompatibility
  - added DM-Upload-Allowed: yes
  - moved python-support to Build-Depends as required for clean target
  - changed Section to python
* debian/copyright: updated year
* debian/watch: escaped the dots

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
Author: Michael Krause <michael@krause-software.de>
10
10
"""
11
11
 
12
 
__author__ = 'Michael Krause'
 
12
__author__ = ['Michael Krause', 'Ero Carrera']
13
13
__license__ = 'MIT'
14
14
 
 
15
 
15
16
import sys
16
17
import glob
17
18
import pydot
18
19
import re
19
20
 
20
21
from pyparsing import __version__ as pyparsing_version
21
 
from pyparsing import Literal, CaselessLiteral, Word,   \
22
 
        Upcase, OneOrMore, ZeroOrMore, Forward, NotAny,         \
23
 
        delimitedList, oneOf, Group, Optional, Combine,         \
24
 
        alphas, nums, restOfLine, cStyleComment, nums,          \
25
 
        alphanums, printables, empty, quotedString,                     \
26
 
        ParseException, ParseResults, CharsNotIn, _noncomma,\
27
 
        dblQuotedString
 
22
 
 
23
from pyparsing import ( nestedExpr, Literal, CaselessLiteral, Word, Upcase, OneOrMore, ZeroOrMore,
 
24
    Forward, NotAny, delimitedList, oneOf, Group, Optional, Combine, alphas, nums,
 
25
    restOfLine, cStyleComment, nums, alphanums, printables, empty, quotedString,
 
26
    ParseException, ParseResults, CharsNotIn, _noncomma, dblQuotedString, QuotedString, ParserElement )
 
27
 
28
28
 
29
29
 
30
30
class P_AttrList:
31
 
        def __init__(self, toks):
32
 
                self.attrs = {}
33
 
                i = 0
34
 
                while i < len(toks):
35
 
                        attrname = toks[i]
36
 
                        attrvalue = toks[i+1]
37
 
                        self.attrs[attrname] = attrvalue
38
 
                        i += 2
39
 
 
40
 
        def __repr__(self):
41
 
                return "%s(%r)" % (self.__class__.__name__, self.attrs)
 
31
 
 
32
    def __init__(self, toks):
 
33
 
 
34
        self.attrs = {}
 
35
        i = 0
 
36
        
 
37
        while i < len(toks):
 
38
        
 
39
            attrname = toks[i]
 
40
            attrvalue = toks[i+1]
 
41
            self.attrs[attrname] = attrvalue
 
42
            i += 2
 
43
 
 
44
    def __repr__(self):
 
45
 
 
46
        return "%s(%r)" % (self.__class__.__name__, self.attrs)
 
47
 
42
48
 
43
49
 
44
50
class DefaultStatement(P_AttrList):
45
 
        def __init__(self, default_type, attrs):
46
 
                self.default_type = default_type
47
 
                self.attrs = attrs
48
 
 
49
 
        def __repr__(self):
50
 
                return "%s(%s, %r)" %   \
51
 
                        (self.__class__.__name__, self.default_type, self.attrs)
52
 
 
 
51
 
 
52
    def __init__(self, default_type, attrs):
 
53
 
 
54
        self.default_type = default_type
 
55
        self.attrs = attrs
 
56
 
 
57
    def __repr__(self):
 
58
 
 
59
        return "%s(%s, %r)" % (self.__class__.__name__,
 
60
            self.default_type, self.attrs)
 
61
 
 
62
 
 
63
top_graphs = list()
53
64
 
54
65
def push_top_graph_stmt(str, loc, toks):
55
 
        attrs = {}
56
 
        g = None
57
 
        
58
 
        for element in toks:
59
 
                if      isinstance(element, ParseResults) or    \
60
 
                        isinstance(element, tuple) or                   \
61
 
                        isinstance(element, list):
62
 
                        
63
 
                        element = element[0]
64
 
 
65
 
                if element == 'strict':
66
 
                        attrs['strict'] = True
67
 
                elif element in ['graph', 'digraph']:
68
 
                        attrs['graph_type'] = element
69
 
                elif type(element) == type(''):
70
 
                        attrs['graph_name'] = element
71
 
                elif isinstance(element, pydot.Graph):
72
 
                        g = pydot.Graph(**attrs)
73
 
                        g.__dict__.update(element.__dict__)
74
 
                        for e in g.get_edge_list():
75
 
                                e.parent_graph = g
76
 
                        for e in g.get_node_list():
77
 
                                e.parent_graph = g
78
 
                        for e in g.get_subgraph_list():
79
 
                                e.set_graph_parent(g)
80
 
 
81
 
                elif isinstance(element, P_AttrList):
82
 
                        attrs.update(element.attrs)
83
 
                else:
84
 
                        raise ValueError, "Unknown element statement: %r " % element
85
 
        
86
 
        if g is not None:
87
 
                g.__dict__.update(attrs)
88
 
                return g
 
66
 
 
67
    attrs = {}
 
68
    g = None
 
69
    
 
70
    for element in toks:
 
71
    
 
72
        if( isinstance(element, (ParseResults, tuple, list)) and
 
73
            len(element) == 1 and isinstance(element[0], basestring) ):
 
74
            
 
75
            element = element[0]
 
76
            
 
77
        if element == 'strict':
 
78
            attrs['strict'] = True
 
79
            
 
80
        elif element in ['graph', 'digraph']:
 
81
 
 
82
            attrs = {}
 
83
            
 
84
            g = pydot.Dot(graph_type=element, **attrs)
 
85
            attrs['type'] = element
 
86
            
 
87
            top_graphs.append( g )
 
88
            
 
89
        elif isinstance( element, basestring):
 
90
            g.set_name( element )
 
91
            
 
92
        elif isinstance(element, pydot.Subgraph):
 
93
        
 
94
            g.obj_dict['attributes'].update( element.obj_dict['attributes'] )
 
95
            g.obj_dict['edges'].update( element.obj_dict['edges'] )
 
96
            g.obj_dict['nodes'].update( element.obj_dict['nodes'] )
 
97
            g.obj_dict['subgraphs'].update( element.obj_dict['subgraphs'] )
 
98
            
 
99
            g.set_parent_graph(g)
 
100
            
 
101
        elif isinstance(element, P_AttrList):
 
102
            attrs.update(element.attrs)
 
103
 
 
104
        elif isinstance(element, (ParseResults, list)):
 
105
            add_elements(g, element)
 
106
            
 
107
        else:
 
108
            raise ValueError, "Unknown element statement: %r " % element
 
109
    
 
110
        
 
111
    
 
112
    if len( top_graphs ) == 1:
 
113
        return top_graphs[0]
 
114
        
 
115
    return top_graphs
89
116
 
90
117
 
91
118
def add_defaults(element, defaults):
92
 
        d = element.__dict__
93
 
        for key, value in defaults.items():
94
 
                if not d.get(key):
95
 
                        d[key] = value
 
119
 
 
120
    d = element.__dict__
 
121
    for key, value in defaults.items():
 
122
        if not d.get(key):
 
123
            d[key] = value
 
124
 
96
125
 
97
126
 
98
127
def add_elements(g, toks, defaults_graph=None, defaults_node=None, defaults_edge=None):
99
 
        
100
 
        if defaults_graph is None:
101
 
                defaults_graph = {}
102
 
        if defaults_node is None:
103
 
                defaults_node = {}
104
 
        if defaults_edge is None:
105
 
                defaults_edge = {}
106
 
                
107
 
        for element in toks:
108
 
                if isinstance(element, pydot.Graph):
109
 
                        add_defaults(element, defaults_graph)
110
 
                        g.add_subgraph(element)
111
 
                elif isinstance(element, pydot.Node):
112
 
                        add_defaults(element, defaults_node)
113
 
                        g.add_node(element)
114
 
                elif isinstance(element, pydot.Edge):
115
 
                        add_defaults(element, defaults_edge)
116
 
                        g.add_edge(element)
117
 
                elif isinstance(element, ParseResults):
118
 
                        for e in element:
119
 
                                add_elements(g, [e], defaults_graph, defaults_node, defaults_edge)
120
 
                elif isinstance(element, DefaultStatement):
121
 
                        if element.default_type == 'graph':
122
 
                                default_graph_attrs = pydot.Node('graph')
123
 
                                default_graph_attrs.__dict__.update(element.attrs)
124
 
                                g.add_node(default_graph_attrs)
125
 
                                defaults_graph.update(element.attrs)
126
 
                                g.__dict__.update(element.attrs)
127
 
                        elif element.default_type == 'node':
128
 
                                default_node_attrs = pydot.Node('node')
129
 
                                default_node_attrs.__dict__.update(element.attrs)
130
 
                                g.add_node(default_node_attrs)
131
 
#                               defaults_node.update(element.attrs)
132
 
                        elif element.default_type == 'edge':
133
 
                                default_edge_attrs = pydot.Node('edge')
134
 
                                default_edge_attrs.__dict__.update(element.attrs)
135
 
                                g.add_node(default_edge_attrs)
136
 
                                defaults_edge.update(element.attrs)
137
 
                        else:
138
 
                                raise ValueError, "Unknown DefaultStatement: %s " % element.default_type
139
 
                elif isinstance(element, P_AttrList):
140
 
                        g.__dict__.update(element.attrs)
141
 
                else:
142
 
                        raise ValueError, "Unknown element statement: %r " % element
143
 
 
144
 
 
145
 
def push_graph_stmt(str, loc, toks):
146
 
        g = pydot.Subgraph()
147
 
        add_elements(g, toks)
148
 
        return g
 
128
    
 
129
    if defaults_graph is None:
 
130
        defaults_graph = {}
 
131
    if defaults_node is None:
 
132
        defaults_node = {}
 
133
    if defaults_edge is None:
 
134
        defaults_edge = {}
 
135
        
 
136
    for elm_idx, element in enumerate(toks):
 
137
    
 
138
        if isinstance(element, (pydot.Subgraph, pydot.Cluster)):
 
139
        
 
140
            add_defaults(element, defaults_graph)
 
141
            g.add_subgraph(element)
 
142
            
 
143
        elif isinstance(element, pydot.Node):
 
144
        
 
145
            add_defaults(element, defaults_node)
 
146
            g.add_node(element)
 
147
            
 
148
        elif isinstance(element, pydot.Edge):
 
149
        
 
150
            add_defaults(element, defaults_edge)
 
151
            g.add_edge(element)
 
152
            
 
153
        elif isinstance(element, ParseResults):
 
154
        
 
155
            for e in element:
 
156
                add_elements(g, [e], defaults_graph, defaults_node, defaults_edge)
 
157
                
 
158
        elif isinstance(element, DefaultStatement):
 
159
        
 
160
            if element.default_type == 'graph':
 
161
            
 
162
                default_graph_attrs = pydot.Node('graph', **element.attrs)
 
163
                g.add_node(default_graph_attrs)
 
164
 
 
165
            elif element.default_type == 'node':
 
166
            
 
167
                default_node_attrs = pydot.Node('node', **element.attrs)
 
168
                g.add_node(default_node_attrs)
 
169
 
 
170
            elif element.default_type == 'edge':
 
171
            
 
172
                default_edge_attrs = pydot.Node('edge', **element.attrs)
 
173
                g.add_node(default_edge_attrs)
 
174
                defaults_edge.update(element.attrs)
 
175
 
 
176
            else:
 
177
                raise ValueError, "Unknown DefaultStatement: %s " % element.default_type
 
178
                
 
179
        elif isinstance(element, P_AttrList):
 
180
        
 
181
            g.obj_dict['attributes'].update(element.attrs)
 
182
 
 
183
        else:
 
184
            raise ValueError, "Unknown element statement: %r" % element
 
185
 
 
186
 
 
187
def push_graph_stmt(str, loc, toks):            
 
188
                       
 
189
    g = pydot.Subgraph('')
 
190
    add_elements(g, toks)
 
191
    return g
149
192
 
150
193
 
151
194
def push_subgraph_stmt(str, loc, toks): 
152
195
 
153
 
        for e in toks:
154
 
                if len(e)==3:
155
 
                        g = e[2]
156
 
                        g.set_name(e[1])
157
 
                if len(e)==1:
158
 
                        e[0].set_name('')
159
 
                        return e[0]
 
196
    g = pydot.Subgraph('')
 
197
    
 
198
    for e in toks:
 
199
        if len(e)==3:
 
200
            g = e[2]
 
201
            g.set_name(e[1])
160
202
 
161
 
        return g
 
203
    return g
162
204
 
163
205
 
164
206
def push_default_stmt(str, loc, toks):
165
 
        # The pydot class instances should be marked as
166
 
        # default statements to be inherited by actual
167
 
        # graphs, nodes and edges.
168
 
        # print "push_default_stmt", toks
169
 
        default_type = toks[0][0]
170
 
        if len(toks) > 1:
171
 
                attrs = toks[1].attrs
172
 
        else:
173
 
                attrs = {}
174
 
 
175
 
        if default_type in ['graph', 'node', 'edge']:
176
 
                return DefaultStatement(default_type, attrs)
177
 
        else:
178
 
                raise ValueError, "Unknown default statement: %r " % toks
 
207
 
 
208
    # The pydot class instances should be marked as
 
209
    # default statements to be inherited by actual
 
210
    # graphs, nodes and edges.
 
211
    # print "push_default_stmt", toks
 
212
    #
 
213
    default_type = toks[0][0]
 
214
    if len(toks) > 1:
 
215
        attrs = toks[1].attrs
 
216
    else:
 
217
        attrs = {}
 
218
 
 
219
    if default_type in ['graph', 'node', 'edge']:
 
220
        return DefaultStatement(default_type, attrs)
 
221
    else:
 
222
        raise ValueError, "Unknown default statement: %r " % toks
179
223
 
180
224
 
181
225
def push_attr_list(str, loc, toks):
182
 
        p = P_AttrList(toks)
183
 
        return p
 
226
 
 
227
    p = P_AttrList(toks)
 
228
    return p
184
229
 
185
230
 
186
231
def get_port(node):
187
232
 
188
 
        if len(node)>1:
189
 
                if isinstance(node[1], ParseResults):
190
 
                        if len(node[1][0])==2:
191
 
                                if node[1][0][0]==':':
192
 
                                        return node[1][0][1]
193
 
                                        
194
 
        return None
195
 
 
196
 
        
197
 
def do_node_ports(n_prev, n_next):
198
 
        port = get_port(n_prev)
199
 
        if port is not None:
200
 
                n_prev_port = ':'+port
201
 
        else:
202
 
                n_prev_port = ''
203
 
                
204
 
        port = get_port(n_next)
205
 
        if port is not None:
206
 
                n_next_port = ':'+port
207
 
        else:
208
 
                n_next_port = ''
209
 
                
210
 
        return (n_prev_port, n_next_port)
211
 
 
212
 
        
 
233
    if len(node)>1:
 
234
        if isinstance(node[1], ParseResults):
 
235
            if len(node[1][0])==2:
 
236
                if node[1][0][0]==':':
 
237
                    return node[1][0][1]
 
238
                    
 
239
    return None
 
240
 
 
241
    
 
242
def do_node_ports(node):
 
243
 
 
244
    node_port = ''
 
245
    if len(node) > 1:
 
246
        node_port = ''.join( [str(a)+str(b) for a,b in node[1] ] )
 
247
 
 
248
    return node_port
 
249
 
 
250
    
213
251
def push_edge_stmt(str, loc, toks):
214
 
        
215
 
        tok_attrs = [a for a in toks if isinstance(a, P_AttrList)]
216
 
        attrs = {}
217
 
        for a in tok_attrs:
218
 
                attrs.update(a.attrs)
219
 
        
220
 
        e = []
221
 
        n_prev = toks[0]
222
 
        if isinstance(toks[2][0], pydot.Graph):
223
 
                n_next_list = [[n.get_name(),] for n in toks[2][0].get_node_list()]
224
 
                for n_next in [n for n in n_next_list]:
225
 
                        n_prev_port, n_next_port = do_node_ports(n_prev, n_next)
226
 
                        e.append(pydot.Edge(n_prev[0]+n_prev_port, n_next[0]+n_next_port, **attrs))
227
 
        else:           
228
 
                for n_next in [n for n in tuple(toks)[2::2]]:
229
 
                        n_prev_port, n_next_port = do_node_ports(n_prev, n_next)
230
 
                        e.append(pydot.Edge(n_prev[0]+n_prev_port, n_next[0]+n_next_port, **attrs))
231
 
                        n_prev = n_next
232
 
                
233
 
        return e
234
 
 
235
 
 
236
 
def push_node_stmt(str, loc, toks):
237
 
 
238
 
        if len(toks) == 2:
239
 
                attrs = toks[1].attrs
240
 
        else:
241
 
                attrs = {}
242
 
                
243
 
        node_name = toks[0]
244
 
        if isinstance(node_name, list) or isinstance(node_name, tuple):
245
 
                if len(node_name)>0:
246
 
                        node_name = node_name[0]
247
 
        
248
 
        n = pydot.Node('"'+node_name+'"', **attrs)
249
 
        return n
250
 
 
251
 
 
252
 
def strip_quotes( s, l, t ):
253
 
        return [ t[0].strip('"') ]
254
 
 
 
252
    
 
253
    tok_attrs = [a for a in toks if isinstance(a, P_AttrList)]
 
254
    attrs = {}
 
255
    for a in tok_attrs:
 
256
        attrs.update(a.attrs)
 
257
 
 
258
    e = []
 
259
 
 
260
    if isinstance(toks[0][0], pydot.Graph):
 
261
    
 
262
        n_prev = pydot.frozendict(toks[0][0].obj_dict)
 
263
    else:        
 
264
        n_prev = toks[0][0] + do_node_ports( toks[0] )
 
265
 
 
266
    if isinstance(toks[2][0], ParseResults):
 
267
    
 
268
        n_next_list = [[n.get_name(),] for n in toks[2][0] ]
 
269
        for n_next in [n for n in n_next_list]:
 
270
            n_next_port = do_node_ports(n_next)
 
271
            e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs))
 
272
 
 
273
    elif isinstance(toks[2][0], pydot.Graph):
 
274
    
 
275
        e.append(pydot.Edge(n_prev, pydot.frozendict(toks[2][0].obj_dict), **attrs))
 
276
 
 
277
    elif isinstance(toks[2][0], pydot.Node):
 
278
    
 
279
        node = toks[2][0]
 
280
        
 
281
        if node.get_port() is not None:
 
282
            name_port = node.get_name() + ":" + node.get_port()
 
283
        else:
 
284
            name_port = node.get_name()
 
285
        
 
286
        e.append(pydot.Edge(n_prev, name_port, **attrs))
 
287
 
 
288
    elif isinstance(toks[2][0], type('')):
 
289
    
 
290
        for n_next in [n for n in tuple(toks)[2::2]]:
 
291
 
 
292
            if isinstance(n_next, P_AttrList) or not isinstance(n_next[0], type('')):
 
293
                continue
 
294
 
 
295
            n_next_port = do_node_ports( n_next )
 
296
            e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs))
 
297
                
 
298
            n_prev = n_next[0]+n_next_port
 
299
            
 
300
    else:
 
301
        # UNEXPECTED EDGE TYPE
 
302
        pass
 
303
        
 
304
    return e
 
305
 
 
306
 
 
307
 
 
308
def push_node_stmt(s, loc, toks):
 
309
 
 
310
    if len(toks) == 2:
 
311
        attrs = toks[1].attrs
 
312
    else:
 
313
        attrs = {}
 
314
        
 
315
    node_name = toks[0]
 
316
    if isinstance(node_name, list) or isinstance(node_name, tuple):
 
317
        if len(node_name)>0:
 
318
            node_name = node_name[0]
 
319
    
 
320
    n = pydot.Node(str(node_name), **attrs)
 
321
    return n
 
322
 
 
323
 
 
324
 
 
325
    
 
326
    
255
327
 
256
328
graphparser = None
 
329
 
257
330
def graph_definition():
258
 
        global graphparser
259
 
        
260
 
        if not graphparser:
261
 
                # punctuation
262
 
                colon  = Literal(":")
263
 
                lbrace = Literal("{")
264
 
                rbrace = Literal("}")
265
 
                lbrack = Literal("[")
266
 
                rbrack = Literal("]")
267
 
                lparen = Literal("(")
268
 
                rparen = Literal(")")
269
 
                equals = Literal("=")
270
 
                comma  = Literal(",")
271
 
                dot    = Literal(".")
272
 
                slash  = Literal("/")
273
 
                bslash = Literal("\\")
274
 
                star   = Literal("*")
275
 
                semi   = Literal(";")
276
 
                at     = Literal("@")
277
 
                minus  = Literal("-")
278
 
                
279
 
                # keywords
280
 
                strict_    = Literal("strict")
281
 
                graph_     = Literal("graph")
282
 
                digraph_   = Literal("digraph")
283
 
                subgraph_  = Literal("subgraph")
284
 
                node_      = Literal("node")
285
 
                edge_      = Literal("edge")
286
 
 
287
 
                
288
 
                # token definitions
289
 
                
290
 
                identifier = Word(alphanums + "_" ).setName("identifier")
291
 
                
292
 
                double_quoted_string = dblQuotedString
293
 
 
294
 
                alphastring_ = OneOrMore(CharsNotIn(_noncomma))
295
 
 
296
 
                ID = (identifier | double_quoted_string.setParseAction(strip_quotes) |\
297
 
                        alphastring_).setName("ID")
298
 
                        
299
 
                html_text = Combine(Literal("<<") + OneOrMore(CharsNotIn(",]")))
300
 
                
301
 
                float_number = Combine(Optional(minus) +        \
302
 
                        OneOrMore(Word(nums + "."))).setName("float_number")
303
 
                        
304
 
                righthand_id =  (float_number | ID | html_text).setName("righthand_id")
305
 
 
306
 
                port_angle = (at + ID).setName("port_angle")
307
 
                
308
 
                port_location = (Group(colon + ID) |    \
309
 
                        Group(colon + lparen + ID + comma + ID + rparen)).setName("port_location")
310
 
                        
311
 
                port = (Group(port_location + Optional(port_angle)) |   \
312
 
                        Group(port_angle + Optional(port_location))).setName("port")
313
 
                        
314
 
                node_id = (ID + Optional(port))
315
 
                a_list = OneOrMore(ID + Optional(equals.suppress() + righthand_id) +    \
316
 
                        Optional(comma.suppress())).setName("a_list")
317
 
                        
318
 
                attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) +    \
319
 
                        rbrack.suppress()).setName("attr_list")
320
 
                        
321
 
                attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt")
322
 
 
323
 
                edgeop = (Literal("--") | Literal("->")).setName("edgeop")
324
 
 
325
 
                stmt_list = Forward()
326
 
                graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) +    \
327
 
                        rbrace.suppress()).setName("graph_stmt")
328
 
                        
329
 
                subgraph = (Group(Optional(subgraph_ + Optional(ID)) + graph_stmt) |    \
330
 
                        Group(subgraph_ + ID)).setName("subgraph")
331
 
                        
332
 
                edgeRHS = OneOrMore(edgeop + Group(node_id | subgraph))
333
 
                
334
 
                edge_stmt = Group(node_id | subgraph) + edgeRHS + Optional(attr_list)
335
 
 
336
 
                node_stmt = (node_id + Optional(attr_list) + Optional(semi.suppress())).setName("node_stmt")
337
 
                
338
 
                assignment = (ID + equals.suppress() + righthand_id).setName("assignment")
339
 
                stmt =  (assignment | edge_stmt | attr_stmt | subgraph | node_stmt).setName("stmt")
340
 
                stmt_list << OneOrMore(stmt + Optional(semi.suppress()))
341
 
 
342
 
                graphparser = (Optional(strict_) + Group((graph_ | digraph_)) + \
343
 
                        Optional(ID) + graph_stmt).setResultsName("graph")
344
 
 
345
 
                singleLineComment = "//" + restOfLine
346
 
                
347
 
                
348
 
                # actions
349
 
                
350
 
                graphparser.ignore(singleLineComment)
351
 
                graphparser.ignore(cStyleComment)
352
 
 
353
 
                assignment.setParseAction(push_attr_list)
354
 
                a_list.setParseAction(push_attr_list)
355
 
                edge_stmt.setParseAction(push_edge_stmt)
356
 
                node_stmt.setParseAction(push_node_stmt)
357
 
                attr_stmt.setParseAction(push_default_stmt)
358
 
 
359
 
                subgraph.setParseAction(push_subgraph_stmt)
360
 
                graph_stmt.setParseAction(push_graph_stmt)
361
 
                graphparser.setParseAction(push_top_graph_stmt)
362
 
 
363
 
                
364
 
        return graphparser
 
331
 
 
332
    global graphparser
 
333
    
 
334
    if not graphparser:
 
335
    
 
336
        # punctuation
 
337
        colon  = Literal(":")
 
338
        lbrace = Literal("{")
 
339
        rbrace = Literal("}")
 
340
        lbrack = Literal("[")
 
341
        rbrack = Literal("]")
 
342
        lparen = Literal("(")
 
343
        rparen = Literal(")")
 
344
        equals = Literal("=")
 
345
        comma  = Literal(",")
 
346
        dot    = Literal(".")
 
347
        slash  = Literal("/")
 
348
        bslash = Literal("\\")
 
349
        star   = Literal("*")
 
350
        semi   = Literal(";")
 
351
        at     = Literal("@")
 
352
        minus  = Literal("-")
 
353
        
 
354
        # keywords
 
355
        strict_    = CaselessLiteral("strict")
 
356
        graph_     = CaselessLiteral("graph")
 
357
        digraph_   = CaselessLiteral("digraph")
 
358
        subgraph_  = CaselessLiteral("subgraph")
 
359
        node_      = CaselessLiteral("node")
 
360
        edge_      = CaselessLiteral("edge")
 
361
        
 
362
        
 
363
        # token definitions
 
364
        
 
365
        identifier = Word(alphanums + "_" ).setName("identifier")
 
366
        
 
367
        double_quoted_string = QuotedString('"', multiline=True, unquoteResults=False) # dblQuotedString
 
368
 
 
369
        alphastring_ = OneOrMore(CharsNotIn(_noncomma))
 
370
 
 
371
        def parse_html(s, loc, toks):
 
372
        
 
373
            return '<%s>' % ''.join(toks[0])
 
374
            
 
375
        
 
376
        opener = '<'
 
377
        closer = '>'
 
378
        html_text = nestedExpr( opener, closer, 
 
379
            ( CharsNotIn( 
 
380
                opener + closer ).setParseAction( lambda t:t[0] ))
 
381
            ).setParseAction(parse_html)
 
382
 
 
383
        ID = ( identifier | html_text | 
 
384
            double_quoted_string | #.setParseAction(strip_quotes) |
 
385
            alphastring_ ).setName("ID")
 
386
            
 
387
        
 
388
        float_number = Combine(Optional(minus) +        
 
389
            OneOrMore(Word(nums + "."))).setName("float_number")
 
390
            
 
391
        righthand_id =  (float_number | ID ).setName("righthand_id")
 
392
 
 
393
        port_angle = (at + ID).setName("port_angle")
 
394
        
 
395
        port_location = (OneOrMore(Group(colon + ID)) | 
 
396
            Group(colon + lparen + ID + comma + ID + rparen)).setName("port_location")
 
397
            
 
398
        port = (Group(port_location + Optional(port_angle)) |   
 
399
            Group(port_angle + Optional(port_location))).setName("port")
 
400
            
 
401
        node_id = (ID + Optional(port))
 
402
        a_list = OneOrMore(ID + Optional(equals.suppress() + righthand_id) +    
 
403
            Optional(comma.suppress())).setName("a_list")
 
404
            
 
405
        attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) +    
 
406
            rbrack.suppress()).setName("attr_list")
 
407
            
 
408
        attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt")
 
409
 
 
410
        edgeop = (Literal("--") | Literal("->")).setName("edgeop")
 
411
 
 
412
        stmt_list = Forward()
 
413
        graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) +    
 
414
            rbrace.suppress() + Optional(semi.suppress()) ).setName("graph_stmt")
 
415
            
 
416
            
 
417
        edge_point = Forward()
 
418
        
 
419
        edgeRHS = OneOrMore(edgeop + edge_point)
 
420
        edge_stmt = edge_point + edgeRHS + Optional(attr_list)
 
421
        
 
422
        subgraph = Group(subgraph_ + Optional(ID) + graph_stmt).setName("subgraph")
 
423
        
 
424
        edge_point << Group(subgraph | graph_stmt | node_id )
 
425
 
 
426
        node_stmt = (node_id + Optional(attr_list) + Optional(semi.suppress())).setName("node_stmt")
 
427
        
 
428
        assignment = (ID + equals.suppress() + righthand_id).setName("assignment")
 
429
        stmt =  (assignment | edge_stmt | attr_stmt | subgraph | graph_stmt | node_stmt).setName("stmt")
 
430
        stmt_list << OneOrMore(stmt + Optional(semi.suppress()))
 
431
 
 
432
        graphparser = OneOrMore( (Optional(strict_) + Group((graph_ | digraph_)) +      
 
433
            Optional(ID) + graph_stmt).setResultsName("graph") )
 
434
 
 
435
        singleLineComment = Group("//" + restOfLine) | Group("#" + restOfLine)
 
436
        
 
437
        
 
438
        # actions
 
439
        
 
440
        graphparser.ignore(singleLineComment)
 
441
        graphparser.ignore(cStyleComment)
 
442
 
 
443
        assignment.setParseAction(push_attr_list)
 
444
        a_list.setParseAction(push_attr_list)
 
445
        edge_stmt.setParseAction(push_edge_stmt)
 
446
        node_stmt.setParseAction(push_node_stmt)
 
447
        attr_stmt.setParseAction(push_default_stmt)
 
448
 
 
449
        subgraph.setParseAction(push_subgraph_stmt)
 
450
        graph_stmt.setParseAction(push_graph_stmt)
 
451
        graphparser.setParseAction(push_top_graph_stmt)
 
452
 
 
453
        
 
454
    return graphparser
365
455
 
366
456
 
367
457
def parse_dot_data(data):
368
 
        try:
369
 
                data = data.replace('\\\n', '')
370
 
                graphparser = graph_definition()
371
 
                if pyparsing_version >= '1.2':
372
 
                        graphparser.parseWithTabs()
373
 
                tokens = graphparser.parseString(data)
374
 
                graph = tokens.graph
375
 
                return graph
376
 
        except ParseException, err:
377
 
                print err.line
378
 
                print " "*(err.column-1) + "^"
379
 
                print err
380
 
                return None
 
458
 
 
459
    global top_graphs
 
460
    
 
461
    top_graphs = list()
 
462
 
 
463
    try:
 
464
    
 
465
        graphparser = graph_definition()
 
466
        
 
467
        if pyparsing_version >= '1.2':
 
468
            graphparser.parseWithTabs()
 
469
            
 
470
        tokens = graphparser.parseString(data)
 
471
 
 
472
        if len(tokens) == 1:
 
473
            return tokens[0]
 
474
        else:
 
475
            return [g for g in tokens]
 
476
        
 
477
    except ParseException, err:
 
478
    
 
479
        print err.line
 
480
        print " "*(err.column-1) + "^"
 
481
        print err
 
482
        return None