Browse code

Replace pydot and pyparsing by a much faster hand written lexer and parser.

Jose.R.Fonseca authored on 26/10/2008 15:18:31
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,1234 +0,0 @@
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
-
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 1 changed files
1 1
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
+