Browse code

Require pydot version 0.9.10.

Last commit was bogus, as I had leftover pydot-0.9.10's .pyc files.

Actually, this is just temporary solution -- pydot broke backwards
compatability in many ways and it is better to cut its dependency as it does
not provide a reliable API.

Jose.R.Fonseca authored on 13/07/2008 02:19:21
Showing 3 changed files

1 1
new file mode 100644
... ...
@@ -0,0 +1,380 @@
1
+# -*- coding: Latin-1 -*-
2
+"""Graphviz's dot language parser.
3
+
4
+The dotparser parses graphviz files in dot and dot files and transforms them
5
+into a class representation defined by pydot.
6
+
7
+The module needs pyparsing (tested with version 1.2.2) and pydot (tested with 0.9.10)
8
+
9
+Author: Michael Krause <michael@krause-software.de>
10
+"""
11
+
12
+__author__ = 'Michael Krause'
13
+__license__ = 'MIT'
14
+
15
+import sys
16
+import glob
17
+import pydot
18
+import re
19
+
20
+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
28
+
29
+
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)
42
+
43
+
44
+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
+
53
+
54
+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
89
+
90
+
91
+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
96
+
97
+
98
+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
149
+
150
+
151
+def push_subgraph_stmt(str, loc, toks):	
152
+
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]
160
+
161
+	return g
162
+
163
+
164
+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
179
+
180
+
181
+def push_attr_list(str, loc, toks):
182
+	p = P_AttrList(toks)
183
+	return p
184
+
185
+
186
+def get_port(node):
187
+
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
+	
213
+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
+
255
+
256
+graphparser = None
257
+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
365
+
366
+
367
+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
0 381
new file mode 100644
... ...
@@ -0,0 +1,1234 @@
1
+# -*- coding: Latin-1 -*-
2
+"""Graphviz's dot language Python interface.
3
+
4
+This module provides with a full interface to create handle modify
5
+and process graphs in Graphviz's dot language.
6
+
7
+References:
8
+
9
+pydot Homepage:	http://www.dkbza.org/pydot.html
10
+Graphviz:	http://www.research.att.com/sw/tools/graphviz/
11
+DOT Language:	http://www.research.att.com/~erg/graphviz/info/lang.html
12
+
13
+Programmed and tested with Graphviz 1.16 and Python 2.3.4 on GNU/Linux
14
+by Ero Carrera (c) 2004	[ero@dkbza.org]
15
+
16
+Distributed under MIT license [http://opensource.org/licenses/mit-license.html].
17
+"""
18
+
19
+__author__ = 'Ero Carrera'
20
+__version__ = '0.9.10'
21
+__license__ = 'MIT'
22
+
23
+import os
24
+import tempfile
25
+import copy
26
+import dot_parser
27
+
28
+
29
+def graph_from_dot_data(data):
30
+	"""Load graph as defined by data in DOT format.
31
+	
32
+	The data is assumed to be in DOT format. It will
33
+	be parsed and a Dot class will be returned, 
34
+	representing the graph.
35
+	"""
36
+	
37
+	graph = dot_parser.parse_dot_data(data)
38
+	if graph is not None:
39
+		dot = Dot()
40
+		dot.__dict__.update(graph.__dict__)
41
+		return dot
42
+		
43
+	return None
44
+
45
+def graph_from_dot_file(path):
46
+	"""Load graph as defined by a DOT file.
47
+	
48
+	The file is assumed to be in DOT format. It will
49
+	be loaded, parsed and a Dot class will be returned, 
50
+	representing the graph.
51
+	"""
52
+	
53
+	fd = file(path, 'rb')
54
+	data = fd.read()
55
+	fd.close()
56
+	
57
+	return graph_from_dot_data(data)
58
+
59
+
60
+def graph_from_edges(edge_list, node_prefix='', directed=False):
61
+	"""Creates a basic graph out of an edge list.
62
+	
63
+	The edge list has to be a list of tuples representing
64
+	the nodes connected by the edge.
65
+	The values can be anything: bool, int, float, str.
66
+	
67
+	If the graph is undirected by default, it is only
68
+	calculated from one of the symmetric halves of the matrix.
69
+	"""
70
+	if directed:
71
+		graph = Dot(graph_type='digraph')
72
+	else:
73
+		graph = Dot(graph_type='graph')
74
+	for edge in edge_list:
75
+		e = Edge(node_prefix+str(edge[0]), node_prefix+str(edge[1]))
76
+		graph.add_edge(e)
77
+	return graph
78
+
79
+
80
+def graph_from_adjacency_matrix(matrix, node_prefix='', directed=False):
81
+	"""Creates a basic graph out of an adjacency matrix.
82
+	
83
+	The matrix has to be a list of rows of values
84
+	representing an adjacency matrix.
85
+	The values can be anything: bool, int, float, as long
86
+	as they can evaluate to True or False.
87
+	"""
88
+	node_orig = 1
89
+	if directed:
90
+		graph = Dot(graph_type='digraph')
91
+	else:
92
+		graph = Dot(graph_type='graph')
93
+	for row in matrix:
94
+		if not directed:
95
+			skip = matrix.index(row)
96
+			r = row[skip:]
97
+		else:
98
+			skip = 0
99
+			r = row
100
+		node_dest = skip+1
101
+		for e in r:
102
+			if e:
103
+				graph.add_edge(	\
104
+					Edge(	node_prefix+str(node_orig), \
105
+						node_prefix+str(node_dest)))
106
+			node_dest += 1
107
+		node_orig += 1
108
+	return graph
109
+
110
+def graph_from_incidence_matrix(matrix, node_prefix='', directed=False):
111
+	"""Creates a basic graph out of an incidence matrix.
112
+	
113
+	The matrix has to be a list of rows of values
114
+	representing an incidence matrix.
115
+	The values can be anything: bool, int, float, as long
116
+	as they can evaluate to True or False.
117
+	"""
118
+	node_orig = 1
119
+	if directed:
120
+		graph = Dot(graph_type='digraph')
121
+	else:
122
+		graph = Dot(graph_type='graph')
123
+	for row in matrix:
124
+		nodes = []
125
+		c = 1
126
+		for node in row:
127
+			if node:
128
+				nodes.append(c*node)
129
+			c += 1
130
+			nodes.sort()
131
+		if len(nodes) == 2:
132
+			graph.add_edge(	\
133
+				Edge(node_prefix+str(abs(nodes[0])),	\
134
+					node_prefix+str(nodes[1]) ))
135
+	if not directed:
136
+		graph.set_simplify(True)
137
+	return graph
138
+
139
+			
140
+def find_graphviz():
141
+	"""Locate Graphviz's executables in the system.
142
+
143
+	Attempts  to locate  graphviz's  executables in a Unix system.
144
+	It will look for 'dot', 'twopi' and 'neato' in all the directories
145
+	specified in the PATH environment variable.
146
+	It will return a dictionary containing the program names as keys
147
+	and their paths as values.
148
+	"""
149
+	progs = {'dot': '', 'twopi': '', 'neato': '', 'circo': '', 'fdp': ''}
150
+	if not os.environ.has_key('PATH'):
151
+		return None
152
+	for path in os.environ['PATH'].split(os.pathsep):
153
+		for prg in progs.keys():
154
+			if os.path.exists(path+os.path.sep+prg):
155
+				progs[prg] = path+os.path.sep+prg
156
+			elif os.path.exists(path+os.path.sep+prg + '.exe'):
157
+				progs[prg] = path+os.path.sep+prg + '.exe'
158
+	return progs
159
+
160
+class Common:
161
+	"""Common information to several classes.
162
+	
163
+	Should not be directly used, several classes are derived from
164
+	this one.
165
+	"""
166
+	chars_ID = None
167
+	parent_graph = None
168
+	
169
+	def char_range(self, a,b):
170
+		"""Generate a list containing a range of characters.
171
+		
172
+		Returns a list of characters starting from 'a' up to 'b'
173
+		both inclusive.
174
+		"""
175
+		return map(chr, range(ord(a), ord(b)+1))
176
+	
177
+	def is_ID(self, s):
178
+		"""Checks whether a string is an dot language ID.
179
+		
180
+		It will check whether the string is solely composed
181
+		by the characters allowed in an ID or not.
182
+		"""
183
+		if not self.chars_ID:
184
+			self.chars_ID = self.char_range('a','z')+self.char_range('A','Z')+ \
185
+					self.char_range('0','9')+['_']
186
+		for c in s:
187
+			if c not in self.chars_ID:
188
+				return False
189
+		return True
190
+
191
+class Error(Exception):
192
+	"""General error handling class.
193
+	"""
194
+	def __init__(self, value):
195
+		self.value = value
196
+	def __str__(self):
197
+		return self.value
198
+
199
+
200
+class Node(object, Common):
201
+	"""A graph node.
202
+	
203
+	This class represents a graph's node with all its attributes.
204
+	
205
+	node(name, attribute=value, ...)
206
+	
207
+	name: node's name
208
+	
209
+	All the attributes defined in the Graphviz dot language should
210
+	be supported.
211
+	"""
212
+	attributes = ['showboxes', 'URL', 'fontcolor', 'fontsize', 'label', 'fontname',	\
213
+		'comment', 'root', 'toplabel', 'vertices', 'width', 'z', 'bottomlabel',	\
214
+		'distortion', 'fixedsize', 'group', 'height', 'orientation', 'pin',	\
215
+		'rects', 'regular', 'shape', 'shapefile', 'sides', 'skew', 'pos',	\
216
+		'layer', 'tooltip', 'style', 'target', 'color', 'peripheries',
217
+		'fillcolor', 'margin', 'nojustify']
218
+
219
+	def __init__(self, name, **attrs):
220
+	
221
+		if isinstance(name, str) and not name.startswith('"'):
222
+			idx = name.find(':')
223
+			if idx>0:
224
+				name = name[:idx]
225
+		
226
+		self.name = str(name)
227
+		for attr in self.attributes:
228
+			# Set all the attributes to None.
229
+			self.__setattr__(attr, None)
230
+			# Generate all the Setter methods.
231
+			self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
232
+			# Generate all the Getter methods.
233
+			self.__setattr__('get_'+attr, lambda a=attr : self.__get_attribute__(a))
234
+		for attr, val in attrs.items():
235
+			self.__setattr__(attr, val)
236
+
237
+	def __getstate__(self):
238
+
239
+		dict = copy.copy(self.__dict__)
240
+		for attr in self.attributes:
241
+			del dict['set_'+attr]
242
+			del dict['get_'+attr]
243
+   
244
+		return dict
245
+	
246
+	def __setstate__(self, state):
247
+		for k, v in state.items():
248
+			self.__setattr__(k, v)
249
+	
250
+	def __get_attribute__(self, attr):
251
+		"""Look for default attributes for this node"""
252
+		attr_val = self.__getattribute__(attr)
253
+		if attr_val is None:
254
+			defaults = self.parent_graph.get_node('node')
255
+			if defaults:
256
+				attr_val = defaults.__getattribute__(attr)
257
+				if attr_val:
258
+					return attr_val
259
+		else:
260
+			return attr_val
261
+		return None
262
+	
263
+	
264
+	def set_name(self, node_name):
265
+		"""Set the node's name."""
266
+		
267
+		self.name = str(node_name)
268
+		
269
+	def get_name(self):
270
+		"""Get the node's name."""
271
+		
272
+		return self.name
273
+
274
+	def set(self, name, value):
275
+		"""Set an attribute value by name.
276
+		
277
+		Given an attribute 'name' it will set its value to 'value'.
278
+		There's always the possibility of using the methods:
279
+			set_'name'(value)
280
+		which are defined for all the existing attributes.
281
+		"""
282
+		if name in self.attributes:
283
+			self.__dict__[name] = value
284
+			return True
285
+		# Attribute is not known
286
+		return False
287
+
288
+	def to_string(self):
289
+		"""Returns a string representation of the node in dot language.
290
+		"""
291
+		
292
+		if not isinstance(self.name, str):
293
+			self.name = str(self.name)
294
+		
295
+		# RMF: special case defaults for node, edge and graph properties.
296
+		if self.name in ['node', 'edge', 'graph'] or self.name.startswith('"'):
297
+			node = self.name
298
+		else:
299
+			node = '"'+self.name+'"'
300
+
301
+		node_attr = None
302
+		all_attrs = [a for a in self.attributes]
303
+		all_attrs += [a for a  in Graph.attributes if a not in all_attrs]
304
+		all_attrs += [a for a  in Edge.attributes if a not in all_attrs]
305
+		for attr in all_attrs:
306
+			if self.__dict__.has_key(attr)	\
307
+			and self.__getattribute__(attr) is not None:
308
+				if not node_attr:
309
+					node_attr = ''
310
+				else:
311
+					node_attr += ', '
312
+				node_attr += attr+'='
313
+				val = str(self.__dict__[attr])
314
+				
315
+				if val.startswith('<') and val.endswith('>'):
316
+					node_attr += val
317
+				elif ((isinstance(val, str) or isinstance(val, unicode)) and \
318
+					not self.is_ID(val)) or val == '' :
319
+					
320
+					node_attr += '"'+val+'"'
321
+				else:
322
+					node_attr += str(val)
323
+
324
+		if node_attr:
325
+			node += ' ['+node_attr+']'
326
+		node += ';'
327
+
328
+		return node
329
+
330
+
331
+class Edge(object, Common):
332
+	"""A graph edge.
333
+	
334
+	This class represents a graph's edge with all its attributes.
335
+	
336
+	edge(src, dst, attribute=value, ...)
337
+	
338
+	src: source node's name
339
+	dst: destination node's name
340
+	
341
+	All the attributes defined in the Graphviz dot language should
342
+	be supported.
343
+	
344
+ 	Attributes can be set through the dynamically generated methods:
345
+	
346
+	 set_[attribute name], i.e. set_label, set_fontname
347
+	 
348
+	or using the instance's attributes:
349
+	
350
+	 Edge.[attribute name], i.e. edge_instance.label, edge_instance.fontname
351
+	"""
352
+	attributes = ['style', 'target', 'pos', 'layer', 'tooltip', 'color', 'showboxes',\
353
+		'URL', 'fontcolor', 'fontsize', 'label', 'fontname', 'comment', 'lp',	\
354
+		'arrowhead', 'arrowsize', 'arrowtail', 'constraint', 'decorate', 'dir',	\
355
+		'headURL', 'headclip', 'headhref', 'headlabel', 'headport',		\
356
+		'headtarget', 'headtooltip', 'href', 'labelangle', 'labeldistance',	\
357
+		'labelfloat', 'labelfontcolor', 'labelfontname', 'labelfontsize', 'len',\
358
+		'lhead', 'ltail', 'minlen', 'samehead', 'sametail', 'weight', 'tailURL',\
359
+		'tailclip', 'tailhref', 'taillabel', 'tailport', 'tailtarget',		\
360
+		'tailtooltip', 'nojustify']
361
+
362
+	def __init__(self, src, dst, **attrs):
363
+		self.src = src
364
+		self.dst = dst
365
+		for attr in self.attributes:
366
+			# Set all the attributes to None.
367
+			self.__setattr__(attr, None)
368
+			# Generate all the Setter methods.
369
+			self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
370
+			# Generate all the Getter methods.
371
+			self.__setattr__('get_'+attr, lambda a=attr : self.__get_attribute__(a))
372
+		for attr, val in attrs.items():
373
+			self.__setattr__(attr, val)
374
+
375
+	def __getstate__(self):
376
+
377
+		dict = copy.copy(self.__dict__)
378
+		for attr in self.attributes:
379
+			del dict['set_'+attr]
380
+			del dict['get_'+attr]
381
+
382
+		return dict
383
+	
384
+	def __setstate__(self, state):
385
+		for k, v in state.items():
386
+			self.__setattr__(k, v)
387
+	
388
+	def __get_attribute__(self, attr):
389
+		"""Look for default attributes for this edge"""
390
+		attr_val = self.__getattribute__(attr)
391
+		if attr_val is None:
392
+			defaults = self.parent_graph.get_node('edge')
393
+			if defaults:
394
+				attr_val = defaults.__getattribute__(attr)
395
+				if attr_val:
396
+					return attr_val
397
+		else:
398
+			return attr_val
399
+		return None
400
+	
401
+	
402
+	def get_source(self):
403
+		"""Get the edges source node name."""
404
+	
405
+		return self.src
406
+		
407
+	def get_destination(self):
408
+		"""Get the edge's destination node name."""
409
+		
410
+		return self.dst
411
+			
412
+	def __eq__(self, edge):
413
+		"""Compare two edges.
414
+		
415
+		If the parent graph is directed, arcs linking
416
+		node A to B are considered equal and A->B != B->A
417
+		
418
+		If the parent graph is undirected, any edge
419
+		connecting two nodes is equal to any other
420
+		edge connecting the same nodes, A->B == B->A
421
+		"""
422
+		
423
+		if not isinstance(edge, Edge):
424
+			raise Error, 'Can\'t compare and edge to a non-edge object.'
425
+		if self.parent_graph.graph_type=='graph':
426
+			# If the graph is undirected, the edge has neither
427
+			# source nor destination.
428
+			if	(self.src==edge.src and self.dst==edge.dst) or	\
429
+				(self.src==edge.dst and self.dst==edge.src):
430
+				return True
431
+		else:
432
+			if	self.src==edge.src and self.dst==edge.dst:
433
+				return True
434
+		return False
435
+
436
+		
437
+	def set(self, name, value):
438
+		"""Set an attribute value by name.
439
+		
440
+		Given an attribute 'name' it will set its value to 'value'.
441
+		There's always the possibility of using the methods:
442
+			set_'name'(value)
443
+		which are defined for all the existing attributes.
444
+		"""
445
+		if name in self.attributes:
446
+			self.__dict__[name] = value
447
+			return True
448
+		# Attribute is not known
449
+		return False
450
+
451
+	
452
+	def parse_node_ref(self, node_str):
453
+	
454
+		if not isinstance(node_str, str):
455
+			node_str = str(node_str)
456
+	
457
+		if node_str[0]=='"' and node_str[-1]=='"' and node_str[0].count('"')%2!=0:
458
+			return node_str
459
+		
460
+		node_port_idx = node_str.rfind(':')
461
+		
462
+		if node_port_idx>0 and node_str[0]=='"' and node_str[node_port_idx-1]=='"':
463
+			return node_str
464
+				
465
+		node_str = node_str.replace('"', '')
466
+		
467
+		if node_port_idx>0:
468
+			a = node_str[:node_port_idx]
469
+			b = node_str[node_port_idx+1:]
470
+			if self.is_ID(a):
471
+				node = a
472
+			else:
473
+				node = '"'+a+'"'
474
+			if self.is_ID(b):
475
+				node += ':'+b
476
+			else:
477
+				node+=':"'+b+'"'	
478
+			return node
479
+			
480
+		return '"'+node_str+'"'
481
+		
482
+	
483
+	def to_string(self):
484
+		"""Returns a string representation of the edge in dot language.
485
+		"""
486
+
487
+		src = self.parse_node_ref(self.src)
488
+		dst = self.parse_node_ref(self.dst)
489
+		
490
+		edge = src	
491
+		if	self.parent_graph and		\
492
+			self.parent_graph.graph_type and	\
493
+			self.parent_graph.graph_type=='digraph':
494
+			edge+=' -> '
495
+		else:
496
+			edge+=' -- '
497
+		edge+=dst
498
+
499
+		edge_attr = None
500
+		for attr in self.attributes:
501
+			if self.__dict__.has_key(attr)	\
502
+			and self.__getattribute__(attr) is not None:
503
+				if not edge_attr:
504
+					edge_attr = ''
505
+				else:
506
+					edge_attr+=', '
507
+				edge_attr+=attr+'='
508
+				val = str(self.__dict__[attr])
509
+				if (isinstance(val, str) or isinstance(val, unicode)) and not self.is_ID(val):
510
+					edge_attr+='"'+val+'"'
511
+				else:
512
+					edge_attr+=str(val)
513
+
514
+		if edge_attr:
515
+			edge+=' ['+edge_attr+']'
516
+		edge+=';'
517
+
518
+		return edge
519
+	
520
+class Graph(object, Common):
521
+
522
+	"""Class representing a graph in Graphviz's dot language.
523
+
524
+	This class implements the methods to work on a representation
525
+	of a graph in Graphviz's dot language.
526
+	
527
+	graph(graph_name='G', type='digraph', strict=False, suppress_disconnected=False, attribute=value, ...)
528
+	
529
+	graph_name:
530
+		the graph's name
531
+	type:
532
+		can be 'graph' or 'digraph'
533
+	suppress_disconnected:
534
+		defaults to False, which will remove from the
535
+		graph any disconnected nodes.
536
+	simplify:
537
+		if True it will avoid displaying equal edges, i.e.
538
+		only one edge between two nodes. removing the
539
+		duplicated ones.
540
+		
541
+	All the attributes defined in the Graphviz dot language should
542
+	be supported.
543
+	
544
+	Attributes can be set through the dynamically generated methods:
545
+	
546
+	 set_[attribute name], i.e. set_size, set_fontname
547
+	 
548
+	or using the instance's attributes:
549
+	
550
+	 Graph.[attribute name], i.e. graph_instance.label, graph_instance.fontname
551
+	"""
552
+	
553
+	attributes = ['Damping', 'bb', 'center', 'clusterrank', 'compound', 'concentrate',\
554
+		'defaultdist', 'dim', 'fontpath', 'epsilon', 'layers', 'layersep',	\
555
+		'margin', 'maxiter', 'mclimit', 'mindist', 'pack', 'packmode', 'model',	\
556
+		'page', 'pagedir', 'nodesep', 'normalize', 'nslimit1', 'ordering',	\
557
+		'orientation', 'outputorder', 'overlap', 'remincross', 'resolution',	\
558
+		'rankdir', 'ranksep', 'ratio', 'rotate', 'samplepoints', 'searchsize',	\
559
+		'sep', 'size', 'splines', 'start', 'stylesheet', 'truecolor',		\
560
+		'viewport', 'voro_margin', 'quantum', 'bgcolor', 'labeljust',		\
561
+		'labelloc', 'root', 'showboxes', 'URL', 'fontcolor', 'fontsize',	\
562
+		'label' ,'fontname', 'comment', 'lp', 'target', 'color', 'style',	\
563
+		'concentrators', 'rank', 'dpi', 'mode', 'nojustify', 'nslimit']
564
+
565
+	def __init__(self, graph_name='G', graph_type='digraph', strict=False,	\
566
+		suppress_disconnected=False, simplify=False, **attrs):
567
+		
568
+		if graph_type not in ['graph', 'digraph']:
569
+			raise Error, 'Invalid type. Accepted graph types are: graph, digraph, subgraph'
570
+		self.graph_type = graph_type
571
+		self.graph_name = graph_name
572
+		self.strict = strict
573
+		self.suppress_disconnected = suppress_disconnected
574
+		self.simplify = simplify
575
+		self.node_list = []
576
+		self.edge_list = []
577
+		self.edge_src_list = []
578
+		self.edge_dst_list = []
579
+		self.subgraph_list = []
580
+		self.sorted_graph_elements = []
581
+		self.parent_graph = self
582
+		for attr in self.attributes:
583
+			# Set all the attributes to None.
584
+			self.__setattr__(attr, None)
585
+			# Generate all the Setter methods.
586
+			self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
587
+			# Generate all the Getter methods.
588
+			self.__setattr__('get_'+attr, lambda a=attr : self.__get_attribute__(a))
589
+		for attr, val in attrs.items():
590
+			self.__setattr__(attr, val)
591
+	
592
+	def __getstate__(self):
593
+
594
+		dict = copy.copy(self.__dict__)
595
+		for attr in self.attributes:
596
+			del dict['set_'+attr]
597
+			del dict['get_'+attr]
598
+			
599
+		return dict
600
+		
601
+	def __setstate__(self, state):
602
+		for k, v in state.items():
603
+			self.__setattr__(k, v)
604
+			
605
+	def __get_attribute__(self, attr):
606
+		"""Look for default attributes for this graph"""
607
+		attr_val = self.__getattribute__(attr)
608
+		if attr_val is None:
609
+			defaults = self.get_node('graph')
610
+			if defaults:
611
+				attr_val = defaults.__getattribute__(attr)
612
+				if attr_val:
613
+					return attr_val
614
+		else:
615
+			return attr_val
616
+		return None
617
+
618
+	def set_simplify(self, simplify):
619
+		"""Set whether to simplify or not.
620
+		
621
+		If True it will avoid displaying equal edges, i.e.
622
+		only one edge between two nodes. removing the
623
+		duplicated ones.
624
+		"""
625
+		
626
+		self.simplify = simplify
627
+
628
+	def get_simplify(self):
629
+		"""Get whether to simplify or not.
630
+		
631
+		Refer to set_simplify for more information.
632
+		"""
633
+		
634
+		return self.simplify
635
+
636
+			
637
+	def set_type(self, graph_type):
638
+		"""Set the graph's type, 'graph' or 'digraph'."""
639
+		self.graph_type = graph_type
640
+		
641
+	def get_type(self):
642
+		"""Get the graph's type, 'graph' or 'digraph'."""
643
+		return self.graph_type
644
+
645
+	def set_name(self, graph_name):
646
+		"""Set the graph's name."""
647
+		
648
+		self.graph_name = graph_name
649
+
650
+	def get_name(self):
651
+		"""Get the graph's name."""
652
+		
653
+		return self.graph_name
654
+					
655
+	def set_strict(self, val):
656
+		"""Set graph to 'strict' mode.
657
+		
658
+		This option is only valid for top level graphs.
659
+		"""
660
+		
661
+		self.strict = val
662
+
663
+	def get_strict(self, val):
664
+		"""Get graph's 'strict' mode (True, False).
665
+		
666
+		This option is only valid for top level graphs.
667
+		"""
668
+		
669
+		return self.strict
670
+		
671
+	def set_suppress_disconnected(self, val):
672
+		"""Suppress disconnected nodes in the output graph.
673
+		
674
+		This option will skip nodes in the graph with no  incoming or outgoing
675
+		edges. This option works also for subgraphs and has effect only in the
676
+		current graph/subgraph.
677
+		"""
678
+		
679
+		self.suppress_disconnected = val
680
+			
681
+	def get_suppress_disconnected(self, val):
682
+		"""Get if suppress disconnected is set.
683
+		
684
+		Refer to set_suppress_disconnected for more information.
685
+		"""
686
+		
687
+		self.suppress_disconnected = val
688
+			
689
+	def set(self, name, value):
690
+		"""Set an attribute value by name.
691
+		
692
+		Given an attribute 'name' it will set its value to 'value'.
693
+		There's always the possibility of using the methods:
694
+		
695
+			set_'name'(value)
696
+			
697
+		which are defined for all the existing attributes.
698
+		"""
699
+		if name in self.attributes:
700
+			self.__dict__[name] = value
701
+			return True
702
+		# Attribute is not known
703
+		return False
704
+
705
+	def get(self, name):
706
+		"""Get an attribute value by name.
707
+		
708
+		Given an attribute 'name' it will get its value.
709
+		There's always the possibility of using the methods:
710
+		
711
+			get_'name'()
712
+			
713
+		which are defined for all the existing attributes.
714
+		"""
715
+		return self.__dict__[name]
716
+		
717
+	def add_node(self, graph_node):
718
+		"""Adds a node object to the graph.
719
+
720
+		It takes a node object as its only argument and returns
721
+		None.
722
+		"""
723
+		
724
+		if not isinstance(graph_node, Node):
725
+			raise Error, 'add_node received a non node class object'
726
+			
727
+		node = self.get_node(graph_node.get_name())
728
+		if node is None or graph_node.get_name() in ('graph', 'node', 'edge'):
729
+				self.node_list.append(graph_node)
730
+				graph_node.parent_graph = self.parent_graph
731
+		elif (node.__dict__.has_key('added_from_edge') and node.added_from_edge):
732
+			for k, v in graph_node.__dict__.items():
733
+				if v is not None and node.__dict__.has_key(k) and node.__dict__[k] is None:
734
+					node.__dict__[k] = v
735
+					
736
+		self.sorted_graph_elements.append(graph_node)
737
+
738
+	def get_node(self, name):
739
+		"""Retrieved a node from the graph.
740
+		
741
+		Given a node's name the corresponding Node
742
+		instance will be returned.
743
+		
744
+		If multiple nodes exist with that name, a list of
745
+		Node instances is returned.
746
+		If only one node exists, the instance is returned.
747
+		None is returned otherwise.
748
+		"""
749
+		
750
+		match = [node for node in self.node_list if node.get_name() == str(name)]
751
+		
752
+		l = len(match)
753
+		if l==1:
754
+			return match[0]
755
+		elif l>1:
756
+			return match
757
+		else:
758
+			return None
759
+	
760
+	def get_node_list(self):
761
+		"""Get the list of Node instances.
762
+		
763
+		This method returns the list of Node instances
764
+		composing the graph.
765
+		"""
766
+		
767
+		return [n for n in self.node_list if n.get_name() not in ('graph', 'edge', 'node')]
768
+			
769
+	def add_edge(self, graph_edge):
770
+		"""Adds an edge object to the graph.
771
+		
772
+		It takes a edge object as its only argument and returns
773
+		None.
774
+		"""
775
+		
776
+		if not isinstance(graph_edge, Edge):
777
+			raise Error, 'add_edge received a non edge class object'
778
+			
779
+		self.edge_list.append(graph_edge)
780
+		
781
+		src = self.get_node(graph_edge.get_source())
782
+		if src is None:
783
+			self.add_node(Node(graph_edge.get_source(), added_from_edge=True))
784
+		
785
+		dst = self.get_node(graph_edge.get_destination())
786
+		if dst is None:
787
+			self.add_node(Node(graph_edge.get_destination(), added_from_edge=True))
788
+		
789
+		graph_edge.parent_graph = self.parent_graph
790
+		
791
+		if graph_edge.src not in self.edge_src_list:
792
+			self.edge_src_list.append(graph_edge.src)
793
+			
794
+		if graph_edge.dst not in self.edge_dst_list:
795
+			self.edge_dst_list.append(graph_edge.dst)
796
+			
797
+		self.sorted_graph_elements.append(graph_edge)
798
+
799
+	def get_edge(self, src, dst):
800
+		"""Retrieved an edge from the graph.
801
+		
802
+		Given an edge's source and destination the corresponding
803
+		Edge instance will be returned.
804
+		
805
+		If multiple edges exist with that source and destination,
806
+		a list of Edge instances is returned.
807
+		If only one edge exists, the instance is returned.
808
+		None is returned otherwise.
809
+		"""
810
+		
811
+		match = [edge for edge in self.edge_list if edge.src == src and edge.dst == dst]
812
+		
813
+		l = len(match)
814
+		if l==1:
815
+			return match[0]
816
+		elif l>1:
817
+			return match
818
+		else:
819
+			return None
820
+	
821
+	def get_edge_list(self):
822
+		"""Get the list of Edge instances.
823
+		
824
+		This method returns the list of Edge instances
825
+		composing the graph.
826
+		"""
827
+		
828
+		return self.edge_list
829
+			
830
+	def add_subgraph(self, sgraph):
831
+		"""Adds an edge object to the graph.
832
+		
833
+		It takes a subgraph object as its only argument and returns
834
+		None.
835
+		"""
836
+		if not isinstance(sgraph, Subgraph) and not isinstance(sgraph, Cluster):
837
+			raise Error, 'add_subgraph received a non subgraph class object'
838
+			
839
+		self.subgraph_list.append(sgraph)
840
+		
841
+		sgraph.set_graph_parent(self.parent_graph)
842
+		
843
+		self.sorted_graph_elements.append(sgraph)
844
+		
845
+		return None
846
+	
847
+	def get_subgraph(self, name):
848
+		"""Retrieved a subgraph from the graph.
849
+		
850
+		Given a subgraph's name the corresponding
851
+		Subgraph instance will be returned.
852
+		
853
+		If multiple subgraphs exist with the same name,	a list of
854
+		Subgraph instances is returned.
855
+		If only one Subgraph exists, the instance is returned.
856
+		None is returned otherwise.
857
+		"""
858
+		
859
+		match = [sgraph for sgraph in self.subgraph_list if sgraph.graph_name == name]
860
+		
861
+		l = len(match)
862
+		if l==1:
863
+			return match[0]
864
+		elif l>1:
865
+			return match
866
+		else:
867
+			return None
868
+		
869
+	def get_subgraph_list(self):
870
+		"""Get the list of Subgraph instances.
871
+		
872
+		This method returns the list of Subgraph instances
873
+		in the graph.
874
+		"""
875
+		
876
+		return self.subgraph_list
877
+			
878
+	def set_graph_parent(self, parent):
879
+		"""Sets a graph and its elements to point the the parent.
880
+		
881
+		Any subgraph added to a parent graph receives a reference
882
+		to the parent to access some common data.
883
+		"""
884
+		self.parent_graph = parent
885
+		
886
+		for elm in self.edge_list:
887
+			elm.parent_graph = parent
888
+			
889
+		for elm in self.node_list:
890
+			elm.parent_graph = parent
891
+			
892
+		for elm in self.subgraph_list:
893
+			elm.parent_graph = parent
894
+			elm.set_graph_parent(parent)
895
+		
896
+	def to_string(self):
897
+		"""Returns a string representation of the graph in dot language.
898
+		
899
+		It will return the graph and all its subelements in string from.
900
+		"""
901
+		graph = ''
902
+		if self.__dict__.has_key('strict'):
903
+			if self==self.parent_graph and self.strict:
904
+				graph+='strict '
905
+		
906
+		graph+=self.graph_type+' '+self.graph_name+' {\n'
907
+
908
+		for attr in self.attributes:
909
+			if self.__dict__.has_key(attr)	\
910
+			and self.__getattribute__(attr) is not None:
911
+				graph += attr+'='
912
+				val = str(self.__dict__[attr])
913
+				if isinstance(val, str) and not self.is_ID(val):
914
+					graph += '"'+val+'"'
915
+				else:
916
+					graph += str(val)
917
+				graph+=';\n'
918
+
919
+
920
+		edges_done = []
921
+		for elm in self.sorted_graph_elements:
922
+			if isinstance(elm, Node):
923
+				if self.suppress_disconnected:
924
+					if elm.name not in self.edge_src_list+self.edge_dst_list:
925
+						continue
926
+				graph += elm.to_string()+'\n'
927
+			elif isinstance(elm, Edge):
928
+				if self.simplify and elm in edges_done:
929
+					continue
930
+				graph += elm.to_string()+'\n'
931
+				edges_done.append(elm)
932
+			else:
933
+				graph += elm.to_string()+'\n'
934
+
935
+		graph += '}\n'
936
+		
937
+		return graph
938
+
939
+
940
+class Subgraph(Graph):
941
+
942
+	"""Class representing a subgraph in Graphviz's dot language.
943
+
944
+	This class implements the methods to work on a representation
945
+	of a subgraph in Graphviz's dot language.
946
+	
947
+	subgraph(graph_name='subG', suppress_disconnected=False, attribute=value, ...)
948
+	
949
+	graph_name:
950
+		the subgraph's name
951
+	suppress_disconnected:
952
+		defaults to false, which will remove from the
953
+		subgraph any disconnected nodes.
954
+	All the attributes defined in the Graphviz dot language should
955
+	be supported.
956
+	
957
+	Attributes can be set through the dynamically generated methods:
958
+	
959
+	 set_[attribute name], i.e. set_size, set_fontname
960
+	 
961
+	or using the instance's attributes:
962
+	
963
+	 Subgraph.[attribute name], i.e.
964
+	 	subgraph_instance.label, subgraph_instance.fontname
965
+	"""
966
+	
967
+	attributes = Graph.attributes
968
+	
969
+	# RMF: subgraph should have all the attributes of graph so it can be passed
970
+	# as a graph to all methods
971
+	def __init__(self, graph_name='subG', suppress_disconnected=False,	\
972
+		simplify=False, **attrs):
973
+		
974
+		self.graph_type = 'subgraph'
975
+		self.graph_name = graph_name
976
+		self.suppress_disconnected = suppress_disconnected
977
+		self.simplify = simplify
978
+		self.node_list = []
979
+		self.edge_list = []
980
+		self.edge_src_list = []
981
+		self.edge_dst_list = []
982
+		self.subgraph_list = []
983
+		self.sorted_graph_elements = []
984
+		for attr in self.attributes:
985
+			# Set all the attributes to None.
986
+			self.__setattr__(attr, None)
987
+			# Generate all the Setter methods.
988
+			self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
989
+			# Generate all the Getter methods.
990
+			self.__setattr__('get_'+attr, lambda a=attr : self.__get_attribute__(a))
991
+		for attr, val in attrs.items():
992
+			self.__setattr__(attr, val)
993
+	
994
+	def __getstate__(self):
995
+
996
+		dict = copy.copy(self.__dict__)
997
+		for attr in self.attributes:
998
+			del dict['set_'+attr]
999
+			del dict['get_'+attr]
1000
+   
1001
+		return dict
1002
+	
1003
+	def __setstate__(self, state):
1004
+		for k, v in state.items():
1005
+			self.__setattr__(k, v)
1006
+	
1007
+	def __get_attribute__(self, attr):
1008
+		"""Look for default attributes for this subgraph"""
1009
+		attr_val = self.__getattribute__(attr)
1010
+		if attr_val is None:
1011
+			defaults = self.get_node('graph')
1012
+			if defaults:
1013
+				attr_val = defaults.__getattribute__(attr)
1014
+				if attr_val:
1015
+					return attr_val
1016
+		else:
1017
+			return attr_val
1018
+		return None
1019
+
1020
+
1021
+class Cluster(Graph):
1022
+
1023
+	"""Class representing a cluster in Graphviz's dot language.
1024
+
1025
+	This class implements the methods to work on a representation
1026
+	of a cluster in Graphviz's dot language.
1027
+	
1028
+	cluster(graph_name='subG', suppress_disconnected=False, attribute=value, ...)
1029
+	
1030
+	graph_name:
1031
+		the cluster's name (the string 'cluster' will be always prepended)
1032
+	suppress_disconnected:
1033
+		defaults to false, which will remove from the
1034
+		cluster any disconnected nodes.
1035
+	All the attributes defined in the Graphviz dot language should
1036
+	be supported.
1037
+	
1038
+	Attributes can be set through the dynamically generated methods:
1039
+	
1040
+	 set_[attribute name], i.e. set_color, set_fontname
1041
+	 
1042
+	or using the instance's attributes:
1043
+	
1044
+	 Cluster.[attribute name], i.e.
1045
+	 	cluster_instance.color, cluster_instance.fontname
1046
+	"""
1047
+	
1048
+	attributes = ['pencolor', 'bgcolor', 'labeljust', 'labelloc', 'URL', 'fontcolor',	\
1049
+		'fontsize', 'label', 'fontname', 'lp', 'style', 'target', 'color',	\
1050
+		'peripheries', 'fillcolor', 'nojustify']
1051
+
1052
+	def __init__(self, graph_name='subG', suppress_disconnected=False,	\
1053
+		simplify=False, **attrs):
1054
+		
1055
+		#if type not in ['subgraph']:
1056
+		#	raise Error, 'Invalid type. Accepted graph types are: subgraph'
1057
+		self.graph_type = 'subgraph'
1058
+		self.graph_name = 'cluster_'+graph_name
1059
+		self.suppress_disconnected = suppress_disconnected
1060
+		self.simplify = simplify
1061
+		self.node_list = []
1062
+		self.edge_list = []
1063
+		self.edge_src_list = []
1064
+		self.edge_dst_list = []
1065
+		self.subgraph_list = []
1066
+		self.sorted_graph_elements = []
1067
+		for attr in self.attributes:
1068
+			# Set all the attributes to None.
1069
+			self.__setattr__(attr, None)
1070
+			# Generate all the Setter methods.
1071
+			self.__setattr__('set_'+attr, lambda x, a=attr : self.__setattr__(a, x))
1072
+			# Generate all the Getter methods.
1073
+			self.__setattr__('get_'+attr, lambda a=attr : self.__get_attribute__(a))
1074
+		for attr, val in attrs.items():
1075
+			self.__setattr__(attr, val)
1076
+	
1077
+	def __getstate__(self):
1078
+
1079
+		dict = copy.copy(self.__dict__)
1080
+		for attr in self.attributes:
1081
+			del dict['set_'+attr]
1082
+			del dict['get_'+attr]
1083
+   
1084
+		return dict
1085
+	
1086
+	def __setstate__(self, state):
1087
+		for k, v in state.items():
1088
+			self.__setattr__(k, v)
1089
+	
1090
+	def __get_attribute__(self, attr):
1091
+		"""Look for default attributes for this cluster"""
1092
+		attr_val = self.__getattribute__(attr)
1093
+		if attr_val is None:
1094
+			defaults = self.get_node('graph')
1095
+			if defaults:
1096
+				attr_val = defaults.__getattribute__(attr)
1097
+				if attr_val:
1098
+					return attr_val
1099
+		else:
1100
+			return attr_val
1101
+		return None
1102
+
1103
+
1104
+class Dot(Graph):
1105
+	"""A container for handling a dot language file.
1106
+
1107
+	This class implements methods to write and process
1108
+	a dot language file. It is a derived class of
1109
+	the base class 'Graph'.
1110
+	"""
1111
+	
1112
+	progs = None
1113
+	
1114
+	formats = ['ps', 'ps2', 'hpgl', 'pcl', 'mif', 'pic', 'gd', 'gd2', 'gif', 'jpg',	\
1115
+		'jpeg', 'png', 'wbmp', 'ismap', 'imap', 'cmap', 'cmapx', 'vrml', 'vtx', 'mp',	\
1116
+		'fig', 'svg', 'svgz', 'dia', 'dot', 'canon', 'plain', 'plain-ext', 'xdot']
1117
+
1118
+	def __init__(self, prog='dot', **args):
1119
+		Graph.__init__(self, **args)
1120
+		
1121
+		self.prog = prog
1122
+		
1123
+		# Automatically creates all the methods enabling the creation
1124
+		# of output in any of the supported formats.
1125
+		for frmt in self.formats:
1126
+			self.__setattr__('create_'+frmt, lambda f=frmt, prog=self.prog : self.create(format=f, prog=prog))
1127
+			f = self.__dict__['create_'+frmt]
1128
+			f.__doc__ = '''Refer to docstring from 'create' for more information.'''
1129
+			
1130
+		for frmt in self.formats+['raw']:
1131
+			self.__setattr__('write_'+frmt, lambda path, f=frmt, prog=self.prog : self.write(path, format=f, prog=prog))
1132
+			f = self.__dict__['write_'+frmt]
1133
+			f.__doc__ = '''Refer to docstring from 'write' for more information.'''
1134
+
1135
+			
1136
+	def __getstate__(self):
1137
+
1138
+		dict = copy.copy(self.__dict__)
1139
+		for attr in self.attributes:
1140
+			del dict['set_'+attr]
1141
+			del dict['get_'+attr]
1142
+   			
1143
+		for k in [x for x in dict.keys() if x.startswith('write_')] + \
1144
+			[x for x in dict.keys() if x.startswith('create_')]:
1145
+			del dict[k]
1146
+   
1147
+		return dict
1148
+	
1149
+	def __setstate__(self, state):
1150
+		self.__init__()
1151
+		for k, v in state.items():
1152
+			self.__setattr__(k, v)
1153
+
1154
+				
1155
+	def set_prog(self, prog):
1156
+		"""Sets the default program.
1157
+		
1158
+		Sets the default program in charge of processing
1159
+		the dot file into a graph.
1160
+		"""
1161
+		self.prog = prog
1162
+
1163
+	def write(self, path, prog=None, format='raw'):
1164
+		"""Writes a graph to a file.
1165
+
1166
+		Given a filename 'path' it will open/create and truncate
1167
+		such file and write on it a representation of the graph
1168
+		defined by the dot object and in the format specified by
1169
+		'format'.
1170
+		The format 'raw' is used to dump the string representation
1171
+		of the Dot object, without further processing.
1172
+		The output can be processed by any of graphviz tools, defined
1173
+		in 'prog', which defaults to 'dot'
1174
+		Returns True or False according to the success of the write
1175
+		operation.
1176
+		
1177
+		There's also the preferred possibility of using:
1178
+		
1179
+			write_'format'(path, prog='program')
1180
+			
1181
+		which are automatically defined for all the supported formats.
1182
+		[write_ps(), write_gif(), write_dia(), ...]
1183
+		"""
1184
+
1185
+		if prog is None:
1186
+			prog = self.prog
1187
+		
1188
+		dot_fd = file(path, "w+b")
1189
+		if format == 'raw':
1190
+			dot_fd.write(self.to_string())
1191
+		else:
1192
+			dot_fd.write(self.create(prog, format))
1193
+		dot_fd.close()
1194
+
1195
+		return True
1196
+
1197
+	def create(self, prog=None, format='ps'):
1198
+		"""Creates and returns a Postscript representation of the graph.
1199
+
1200
+		create will write the graph to a temporary dot file and process
1201
+		it with the program given by 'prog' (which defaults to 'twopi'),
1202
+		reading the Postscript output and returning it as a string is the
1203
+		operation is successful.
1204
+		On failure None is returned.
1205
+		
1206
+		There's also the preferred possibility of using:
1207
+		
1208
+			create_'format'(prog='program')
1209
+			
1210
+		which are automatically defined for all the supported formats.
1211
+		[create_ps(), create_gif(), create_dia(), ...]
1212
+		"""
1213
+		if prog is None:
1214
+			prog = self.prog
1215
+			
1216
+		if self.progs is None:
1217
+			self.progs = find_graphviz()
1218
+			if self.progs is None:
1219
+				return None
1220
+		if not self.progs.has_key(prog):
1221
+			# Program not found ?!?!
1222
+			return None
1223
+			
1224
+		tmp_fd, tmp_name = tempfile.mkstemp()
1225
+		os.close(tmp_fd)
1226
+		self.write(tmp_name)
1227
+		stdin, stdout, stderr = os.popen3(self.progs[prog]+' -T'+format+' '+tmp_name, 'b')
1228
+		stdin.close()
1229
+		stderr.close()
1230
+		data = stdout.read()
1231
+		stdout.close()
1232
+		os.unlink(tmp_name)
1233
+		return data
1234
+
... ...
@@ -37,6 +37,9 @@ import pangocairo
37 37
 
38 38
 import pydot
39 39
 
40
+if pydot.__version__ != '0.9.10':
41
+    sys.stderr('pydot version 0.9.10 required, but version %s found\n' % pydot.__version__)
42
+
40 43
 
41 44
 # See http://www.graphviz.org/pub/scm/graphviz-cairo/plugin/cairo/gvrender_cairo.c
42 45