def load(graph, fobj, queryCallback=None, warningCallback=None): """Load .gml file Arguments: graph -- the graph to be created from the loaded file fobj -- a file object of the GML file to be read queryCallback -- function to display a default query dialog (default None) warningCallback -- function to display a default warning dialog (default None) """ try: root = GMLList(Stream(fobj)) except EOF: raise ImportError, "Premature end of file" except Unbalanced: raise ImportError, "Unbalanced delimiters in file" except TypeMismatch: raise ImportError, "Unintelligible value in file" except EOList: #!!! shouldn't happen raise ImportError, "Internal error" #XXX# #XXX# Debugging print the tree #XXX# def traverse(a, i=0): if i <= 0: print "" print "" if isinstance(a, GMLList): print (" " * i) + repr(a) for k, v in a.data.items(): print " " * i + " " + repr(k) for x in v: traverse(x.data, i=i + 1) else: print (" " * i) + repr(a) #XXX# #XXX# #XXX# if 'graph' not in root.data: raise ImportError, "No graphs in file." % str(root.data) # We only load the first graph in the file. if len(root.data['graph']) > 1 and warningCallback: warningCallback("GLuskap does not support multiple graphs per file. " "The first graph in the file will be processed.") # Check GML version if it is supported if 'version' in root.data and root.data['version'][0].data != 1: if queryCallback: result = queryCallback("Unsupported GML version %s." "\n\nWould you like to continue?" % str(root.data['version'][0].data), override=True, buttons=['Yes', 'No']) if result == CustomButtonDialog.ID['Yes']: pass else: raise ImportError, "Import aborted." else: raise ImportError, "Unsupported GML version %s." % str(root.data['version'][0].data) graphbranch = root.data['graph'][0].data if not isinstance(graphbranch, GMLList): raise ImportError, "GML format error: graph is not a List" if 'directed' in graphbranch.data and graphbranch.data['directed'] != 0 and warningCallback: warningCallback("GLuskap does not support directed graphs. " "All edges will be processed as undirected.") # Boolean variables to control if the rest of the import errors are handled # automatically yestoall = False yesall_duplicate_vert = False yesall_duplicate_edge = False yesall_degenerate_edge = False yesall_color = False yesall_nonexist_vert = False yesall_invalid_bends = False # # Vertices (Nodes) # vertexIndex = {} for vxb in graphbranch.data['node']: vxbranch = vxb.data if not isinstance(vxbranch, GMLList): raise ImportError, ("GML format error: node '%s' is not a List" % (str(vxbranch))) if 'id' not in vxbranch.data: raise ImportError, "GML format error: node without ID" # # Extension - we allow node IDs of type String as well as # integer. # if not isinstance(vxbranch.data['id'][0].data, (int, GMLString)): raise ImportError, ("GML format error: node ID '%s' of " "unsupportedtype" % (vxbranch.data['id'][0].data)) id = str(vxbranch.data['id'][0].data) if id in vertexIndex: # Duplicate vertex if yesall_duplicate_vert or yestoall: continue if queryCallback: result = queryCallback("Duplicate vertex detected: id %s" % (id)) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_duplicate_vert = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, "Duplicate vertex detected: id %s" % (id) v = Vertex(id=id) if 'label' in vxbranch.data: v.name = str(vxbranch.data['label'][0].data) if 'graphics' in vxbranch.data: pos = list(v.pos) rad = v.radius color = list(v.color) grbranch = vxbranch.data['graphics'][0].data if 'x' in grbranch.data: pos[X_AXIS] = float(grbranch.data['x'][0].data) if 'y' in grbranch.data: pos[Y_AXIS] = float(grbranch.data['y'][0].data) if 'z' in grbranch.data: pos[Z_AXIS] = float(grbranch.data['z'][0].data) if 'w' in grbranch.data: radius = float(grbranch.data['w'][0].data) if 'h' in grbranch.data: radius = max(radius, float(grbranch.data['h'][0].data)) if 'd' in grbranch.data: radius = max(radius, float(grbranch.data['d'][0].data)) if 'fill' in grbranch.data: fail = False fillstr = grbranch.data['fill'][0].data if not isinstance(fillstr, GMLString): fail = True fail = "fill must be a String" elif not len(str(fillstr)) > 0: fail = "fill string too short" elif str(fillstr)[0] != '#': # Named color try: color = colorByName(str(fillstr)) except UnknownColor: fail = "unrecognized named color" else: # Hex color fillstr = str(fillstr) if len(fillstr) == 7: r = fillstr[1:3] g = fillstr[3:5] b = fillstr[5:7] elif len(fillstr) == 4: r = fillstr[1] g = fillstr[2] b = fillstr[3] else: fail = "fill must be of form '#fff' or '#ffffff'" if not fail: color = [x / 255.0 for x in (int(r, 16), int(g, 16), int(b, 16))] if fail and not yesall_color and not yestoall: # Invalid vertex color if queryCallback: result = queryCallback("Invalid vertex fill color (%s): " "id %s, fill '%s' " "\n\nUse default color and continue?" % (fail, id, fillstr), override=True) if result == CustomButtonDialog.ID['Yes']: pass elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_color = True elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True else: raise ImportError, "Import aborted." else: raise ImportError, ("Invalid vertex fill color (%s): " "id %s, fill '%s'" % (fail, id, fillstr)) # # Apply attributes # v.pos = tuple(pos) v.radius = radius v.color = tuple(color) graph.addVertex(v) vertexIndex[v.id] = v # # Edges # edgeIndex = {} for edgeb in graphbranch.data['edge']: edgebranch = edgeb.data if not isinstance(edgebranch, GMLList): raise ImportError, ("GML format error: edge '%s' is not a List" % (str(edgebranch))) if 'source' not in edgebranch.data: raise ImportError, "GML format error: edge without source" if 'target' not in edgebranch.data: raise ImportError, "GML format error: edge without target" # # Extension - we allow node IDs of type String as well as # integer. # if not isinstance(edgebranch.data['source'][0].data, (int, GMLString)): raise ImportError, ("GML format error: edge source ID '%s' " "of unsupported type" % (edgebranch.data['source'][0].data)) if not isinstance(edgebranch.data['target'][0].data, (int, GMLString)): raise ImportError, ("GML format error: edge target ID '%s' " "of unsupported type" % (edgebranch.data['target'][0].data)) src = str(edgebranch.data['source'][0].data) tgt = str(edgebranch.data['target'][0].data) if src not in vertexIndex: # Nonexistent source vertex if yesall_nonexist_vert or yestoall: continue if queryCallback: result = queryCallback("Edge specifies nonexistent " "source vertex %s" % (src)) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_nonexist_vert = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Edge specifies nonexistent " "source vertex %s" % (src)) if tgt not in vertexIndex: # Nonexistent target vertex if yesall_nonexist_vert or yestoall: continue if queryCallback: result = queryCallback("Edge specifies nonexistent target " "vertex %s" % (tgt)) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_nonexist_vert = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Edge specifies nonexistent " "target vertex %s" % (tgt)) e = Edge(source=vertexIndex[src], target=vertexIndex[tgt]) if (src, tgt) in edgeIndex: # Duplicate edge if yesall_duplicate_edge or yestoall: continue if queryCallback: result = queryCallback("Duplicate edge detected: " "%s -> %s" % (src, tgt)) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_duplicate_edge = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Duplicate edge detected:" "%s -> %s" % (src, tgt)) if (tgt, src) in edgeIndex: # Reversed Duplicate edge if yesall_duplicate_edge or yestoall: continue if queryCallback: result = queryCallback("Duplicate (reversed) edge detected: " "%s -> %s" % (src, tgt)) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_duplicate_edge = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Duplicate (reversed) edge detected: " "%s -> %s" % (src, tgt)) if src == tgt: # Degenerate edge if yesall_degenerate_edge or yestoall: continue if queryCallback: result = queryCallback("Degenerate edge detected: %s -> %s" % (src, tgt)) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_degenerate_edge = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Degenerate edge detected: %s -> %s" % (src, tgt)) if 'label' in edgebranch.data: e.attribute = str(edgebranch.data['label'][0].data) grbranch = None if 'graphics' in edgebranch.data: rad = e.radius color = list(e.color) grbranch = edgebranch.data['graphics'][0].data if 'width' in grbranch.data: e.radius = float(grbranch.data['width'][0].data) if ('type' in grbranch.data and str(grbranch.data['type'][0].data) != "line" and warningCallback): warningCallback("Edge of graphic type '%s' converted to line." % str(grbranch.data['type'][0].data)) if 'fill' in grbranch.data: fail = False fillstr = grbranch.data['fill'][0].data if not isinstance(fillstr, GMLString): fail = True fail = "fill must be a String" elif not len(str(fillstr)) > 0: fail = "fill string too short" elif str(fillstr)[0] != '#': # Named color try: color = colorByName(str(fillstr)) except UnknownColor: fail = "unrecognized named color" else: # Hex color fillstr = str(fillstr) if len(fillstr) == 7: r = fillstr[1:3] g = fillstr[3:5] b = fillstr[5:7] elif len(fillstr) == 4: r = fillstr[1] g = fillstr[2] b = fillstr[3] else: fail = "fill must be of form '#fff' or '#ffffff'" if not fail: color = [x / 255.0 for x in (int(r, 16), int(g, 16), int(b, 16))] if fail and not yesall_color and not yestoall: # Invalid edge fill color if queryCallback: result = queryCallback("Invalid edge fill color (%s): " "%s -> %s, fill '%s'" "\n\nUse default color and continue?" % (fail, src, tgt, fillstr), override=True) if result == CustomButtonDialog.ID['Yes']: pass elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_color = True elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True else: raise ImportError, "Import aborted." else: raise ImportError, ("Invalid edge fill color (%s): " "%s -> %s, fill '%s'" % (fail, src, tgt, fillstr)) # # Apply attributes # e.color = tuple(color) graph.addEdge(e) edgeIndex[(src, tgt)] = e # # Handle bending edges here # if grbranch != None and 'line' in grbranch.data: linebranch = grbranch.data['line'][0].data if 'point' in linebranch.data: if len(linebranch.data['point']) < 2: if yestoall or yesall_invalid_bends: continue # Insufficient number of bendpoints if queryCallback: result = queryCallback("Insufficient number of points" " for edge %s -> %s" % (src, tgt)) if result == CustomButtonDialog.ID['Yes']: pass elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_invalid_bends = True elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True else: raise ImportError, "Import aborted." else: raise ImportError, ("Insufficient number of points" "for edge %s -> %s" % (src, tgt)) else: # Delete the first and last points - those are the src and tgt del linebranch.data['point'][0] del linebranch.data['point'][-1] tmpEdge = e tgtVx = e.target for ptb in linebranch.data['point']: ptbranch = ptb.data pos = [0.0, 0.0, 0.0] if 'x' in ptbranch.data: pos[X_AXIS] = float(ptbranch.data['x'][0].data) if 'y' in ptbranch.data: pos[Y_AXIS] = float(ptbranch.data['y'][0].data) if 'z' in ptbranch.data: pos[Z_AXIS] = float(ptbranch.data['z'][0].data) bendVx = graph.bendEdge(tmpEdge, tuple(pos)) tmpEdge = graph.findEdgeBetween(bendVx, tgtVx)[0]
def load(graph, fobj, queryCallback=None, warningCallback=None): """Load .gv or .dot file Arguments: graph -- the graph to be created from the loaded file fobj -- a file object of the DOT file to be read queryCallback -- function to display a default query dialog (default None) warningCallback -- function to display a default warning dialog (default None) """ # Set global variables to in DOTGraph for automatically replacing # attributes of duplicate nodes and edges global _yestoall_nodes global _yestoall_edges _yestoall_nodes = None _yestoall_edges = None try: dotgraph = DOTGraph(Stream(fobj), queryCallback=queryCallback, warningCallback=warningCallback) except EOF: raise ImportError, "Premature end of file" except Unbalanced: raise ImportError, "Unbalanced delimiters in file" # Boolean variables to control if the rest of the import errors are handled # automatically yestoall = False yesall_color = False yesall_degenerate_edge = False def getColor(color): """Retun an RGB float triple of a color string. Return False if invalid Arguments: color -- a string representing a color """ # Hex color if color[0] == '#' and len(color) == 7: fillstr = color r = fillstr[1:3] g = fillstr[3:5] b = fillstr[5:7] try: color = [x / 255.0 for x in (int(r, 16), int(g, 16), int(b, 16))] return color except: return False # HSV float triple elif ',' in color or ' ' in color: color = color.replace(" ", "").split(',', 3) try: color = [float(x) for x in color] color = colorsys.hsv_to_rgb(color[0], color[1], color[2]) return color except: return False # X11 Color Name else: try: color = colorByName(color) return color except UnknownColor: return False # Add all the vertices in the dotgraph to the context graph for vertex, attributes in dotgraph.nodes.items(): v = Vertex(id=vertex) if 'label' in attributes: v.name = attributes['label'] if 'width' in attributes: v.radius = float(attributes['width']) if 'pos' in attributes: # remove ! and spaces, then split numbers pos = attributes['pos'][:-1].replace(" ", "").split(',', 3) if len(pos) < 3: pos.append(0.0) v.pos = (float(pos[0]), float(pos[1]), float(pos[2])) if 'color' in attributes: color = getColor(attributes['color']) if color != False: v.color = tuple(color) elif not yesall_color and not yestoall: # Invalid color if queryCallback: result = queryCallback("Invalid vertex color %s for %s. " "Assign default color?" % (attributes['color'], vertex), True) if result == CustomButtonDialog.ID['Yes']: pass elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_color = True elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True else: raise ImportError, "Import aborted." else: raise ImportError("Invalid vertex color %s for %s" % (attributes['color'], vertex)) graph.addVertex(v) # Add all the edges in the dotgraph to the context grpah for edge, attributes in dotgraph.edges.items(): source = graph.findVertexByID(edge[0]) target = graph.findVertexByID(edge[1]) if source == target: # Degenerate edge if yesall_degenerate_edge or yestoall: continue if queryCallback: result = queryCallback("Degenerate edge detected: %s -> %s" % (source.id, target.id)) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_degenerate_edge = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError("Degenerate edge detected: %s -> %s" % (source.id, target.id)) e = Edge(source, target) if 'penwidth' in attributes: e.radius = float(attributes['penwidth']) if 'color' in attributes: color = getColor(attributes['color']) if color != False: e.color = tuple(color) elif not yesall_color and not yestoall: # Invalid color if queryCallback: result = queryCallback("Invalid edge color %s for (%s,%s) " "Assign default color?" % (attributes['color'], edge[0], edge[1]), True) if result == CustomButtonDialog.ID['Yes']: pass elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_color = True elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True else: raise ImportError, "Import aborted." else: raise ImportError("Invalid edge color %s for (%s,%s)" % (attributes['color'], edge[0], edge[1])) graph.addEdge(e)
def load(graph, fobj, queryCallback=None, warningCallback=None): """ Load a TLP file and create a graph from it Arguments: graph -- the graph to be created from the loaded file fobj -- a file object of the TLP file to be read queryCallback -- function to display a default query dialog (default None) warningCallback -- function to display a default warning dialog (default None) """ try: tlpgraph = TLPGraph(Stream(fobj)) except Unbalanced: raise ImportError, "Unbalanced delimiters in file" except TypeMismatch: raise ImportError, "Unintelligible value in file" except EOF: raise ImportError, "Premature end of file" # Boolean variables to control if the rest of the import errors are handled # automatically yestoall = False yesall_duplicate_edge = False yesall_degenerate_edge = False yesall_nonexist_vert = False # Make vertices and apply properties for vertex, properties in tlpgraph.nodes.items(): v = Vertex(vertex) if "viewLabel" in properties: v.name = properties["viewLabel"] if "viewLayout" in properties: pos = string_to_tuple(properties["viewLayout"]) v.pos = tuple([float(x) for x in pos]) if "viewSize" in properties: v.radius = float(string_to_tuple(properties["viewSize"])[0]) if "viewColor" in properties: color = string_to_tuple(properties["viewColor"])[:3] v.color = tuple([float(x) / 255 for x in color]) graph.addVertex(v) # Make edges and apply properties edges = tlpgraph.edges.values() edges.extend(tlpgraph.lost_edges) for properties in edges: source = properties["source"] target = properties["target"] # Raise an error if edge is degenerate, or source or target vertices # are non existent. Automatically handle errors if yes all is selected if source == target: # Degenerate Edge if yesall_degenerate_edge or yestoall: continue if queryCallback: result = queryCallback("Degenerate edge detected: %s -> %s" % (source, target)) if result == CustomButtonDialog.ID["Yes"]: continue elif result == CustomButtonDialog.ID["Yes to similar errors"]: yesall_degenerate_edge = True continue elif result == CustomButtonDialog.ID["Yes to all errors"]: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError("Degenerate edge detected: %s -> %s" % (source, target)) if source not in tlpgraph.nodes.keys(): # Nonexistent vertex if yesall_nonexist_vert or yestoall: continue if queryCallback: result = queryCallback("Edge specifies nonexistent " "source vertex %s" % (source)) if result == CustomButtonDialog.ID["Yes"]: continue elif result == CustomButtonDialog.ID["Yes to similar errors"]: yesall_nonexist_vert = True continue elif result == CustomButtonDialog.ID["Yes to all errors"]: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Edge specifies nonexistent source vertex" " %s" % (source)) if target not in tlpgraph.nodes.keys(): # Nonexistent vertex if yesall_nonexist_vert or yestoall: continue if queryCallback: result = queryCallback("Edge specifies nonexistent " "target vertex %s" % (target)) if result == CustomButtonDialog.ID["Yes"]: continue elif result == CustomButtonDialog.ID["Yes to similar errors"]: yesall_nonexist_vert = True continue elif result == CustomButtonDialog.ID["Yes to all errors"]: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Edge specifies nonexistent target vertex" " %s" % (target)) source = graph.findVertexByID(source) target = graph.findVertexByID(target) found_edges = graph.findEdgeBetween(source, target) if len(found_edges) == 0: e = Edge(source, target) else: # Raise error for duplicate edges if source.id == found_edges[0].source.id: if yesall_duplicate_edge or yestoall: continue if queryCallback: result = queryCallback("Duplicate edge detected: %s -> %s" % (source.id, target.id)) if result == CustomButtonDialog.ID["Yes"]: continue elif result == CustomButtonDialog.ID["Yes to " "similar errors"]: yesall_duplicate_edge = True continue elif result == CustomButtonDialog.ID["Yes to all errors"]: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Duplicate edge detected: %s -> %s" % (source.id, target.id)) # Raise error for reversed duplicate edge elif source.id == found_edges[0].target.id: if yesall_duplicate_edge or yestoall: continue if queryCallback: result = queryCallback("Duplicate edge detected " "(reversed): %s -> %s" % (target.id, source.id)) if result == CustomButtonDialog.ID["Yes"]: continue elif result == CustomButtonDialog.ID["Yes to " "similar errors"]: yesall_duplicate_edge = True continue elif result == CustomButtonDialog.ID["Yes to all errors"]: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Duplicate edge detected (reversed): " "%s -> %s" % (target.id, source.id)) if "viewColor" in properties: color = string_to_tuple(properties["viewColor"])[:3] e.color = tuple([float(x) / 255 for x in color]) if "viewSize" in properties: radius = string_to_tuple(properties["viewSize"]) e.radius = (float(radius[0]) + float(radius[1])) / 2 graph.addEdge(e) if "viewLayout" in properties and properties["viewLayout"] != "()": # Convert the string of bendpoint tuples to a tuple of strings # By removing whitespace and inserting a pipe between # each tuple, then splitting at each pipe bends = properties["viewLayout"] bends = bends.replace(" ", "").replace(")(", ");(") bends = bends.replace("),(", ");(") bends = tuple(bends[1:-1].split(";")) # Create bends for bend in bends: bend = string_to_tuple(bend) bend = tuple([float(x) for x in bend]) graph.bendEdge(e, bend)
def load(graph, fobj, queryCallback=None, warningCallback=None): """ Load a GraphML file and create a graph from it Arguments: graph -- the graph to be created from the loaded file fobj -- a file object of the GraphML file to be read queryCallback -- function to display a default query dialog (default None) warningCallback -- function to display a default warning dialog (default None) """ from xml.dom.minidom import parse grml_dom = parse(fobj) GraphML = grml_dom.documentElement typeTable = { 'int': int, 'long': long, 'float': float, 'double': float, 'string': str, 'boolean': bool, } # # Read the keys part of the GraphML file # keyInfo = {} defaults = {} # Boolean variables to control if the rest of the import errors are handled # automatically yestoall = False yesall_duplicate_vert = False yesall_duplicate_edge = False yesall_degenerate_edge = False yesall_color = False yesall_nonexist_vert = False # Store the graph attributes in a dictionary named keyInfo, # and storing any defaults in the dictionary named defaults for keyNode in GraphML.getElementsByTagName("key"): attrs = dict( [(str(a), str(b)) for a, b in keyNode.attributes.items()] ) id = attrs.pop('id') if 'for' in attrs and attrs['for'] not in defaults: defaults[attrs['for']] = {} # Add the desc to the info for this key desc = keyNode.getElementsByTagName("desc") if len(desc) > 0: attrs['desc'] = str(desc[0].childNodes[0].wholeText).strip() # Convert the type to a python native type if 'attr.type' in attrs.keys(): attrs['attr.type'] = typeTable[attrs['attr.type']] # If there's a default, store it default = keyNode.getElementsByTagName("default") if len(default) > 0: defaults[attrs['for']][id] = attrs['attr.type'](default[0].childNodes[0].wholeText.strip()) # Dupicate id's are mapped depending on 'for' type if id not in keyInfo: keyInfo[id] = {} if 'for' in attrs: keyInfo[id][attrs['for']] = attrs else: keyInfo[id]['forNotSpecified'] = attrs # We read only the first graph in a GraphML file. graphs = GraphML.getElementsByTagName('graph') if len(graphs) < 1: raise ImportError, "No graphs in this file!" if len(graphs) > 1 and warningCallback: warningCallback("Multiple graphs per file are not supported. " "The first graph in the file will be processed.") graphNode = graphs[0] attrs = dict( [(str(a), str(b)) for a, b in graphNode.attributes.items()] ) if 'edgedefault' not in attrs: raise ImportError, "No edge default directedness found!" if attrs['edgedefault'] != 'undirected' and warningCallback: warningCallback("Directed graphs are not supported. " "Directed edges will be converted.") # # Set up an index of vertex IDs and edge tuples - this allows # us to check uniqueness and also eliminates costly # findVertexByID calls. # vertexIndex = {} edgeIndex = {} # Read vertices (aka nodes) for vertNode in graphNode.getElementsByTagName('node'): vertAttrs = dict( [(str(a), str(b)) for a, b in vertNode.attributes.items()] ) # Create a dict that maps vertex attribute names to their values for dataNode in vertNode.getElementsByTagName('data'): dataAttrs = dict( [(str(a), str(b)) for a, b in dataNode.attributes.items()] ) if 'node' in keyInfo[dataAttrs['key']]: key = keyInfo[dataAttrs['key']]['node'] else: key = keyInfo[dataAttrs['key']]['forNotSpecified'] if dataNode.childNodes != []: if 'attr.type' in key.keys(): value = key['attr.type'](dataNode.childNodes[0].wholeText.strip()) else: value = str(dataNode.childNodes[0].wholeText.strip()) if 'attr.name' in key.keys(): vertAttrs[key['attr.name']] = value # Assign defaults if len(defaults) > 0 and defaults['node']: for prop, value in defaults['node'].items(): if prop not in vertAttrs: vertAttrs[prop] = value # Raise ImportError if vertex is a duplicate if vertAttrs['id'] in vertexIndex: if yesall_duplicate_vert or yestoall: continue if queryCallback: result = queryCallback("Duplicate vertex detected: id %s" % (vertAttrs['id'])) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_duplicate_vert = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Duplicate vertex detected: id %s" % (vertAttrs['id'])) v = Vertex(id=vertAttrs['id']) # Determine the color of the vertex, accepting either # r,g,b or red,green,blue for the names of the color attributes if ('red' in vertAttrs or 'blue' in vertAttrs or 'green' in vertAttrs or 'r' in vertAttrs or 'b' in vertAttrs or 'g' in vertAttrs): color = [0.0, 0.0, 0.0] if 'red' in vertAttrs: color[0] = color[0] + vertAttrs['red'] elif 'r' in vertAttrs: color[0] = color[0] + vertAttrs['r'] if 'green' in vertAttrs: color[1] = color[1] + vertAttrs['green'] elif 'g' in vertAttrs: color[1] = color[1] + vertAttrs['g'] if 'blue' in vertAttrs: color[2] = color[2] + vertAttrs['blue'] elif 'b' in vertAttrs: color[2] = color[2] + vertAttrs['b'] # If color uses 0 to 255 for colors instead of 0.0 to 1.0, convert if color[0] > 1.0 or color[1] > 1.0 or color[2] > 1.0: color[0] /= 255 color[1] /= 255 color[2] /= 255 v.color = tuple(color) # Determine the color of the vertex if using 'fill' and hex elif 'fill' in vertAttrs: fail = False fillstr = vertAttrs['fill'] if len(fillstr) == 7: r = fillstr[1:3] g = fillstr[3:5] b = fillstr[5:7] elif len(fillstr) == 4: r = fillstr[1] g = fillstr[2] b = fillstr[3] else: fail = "fill must be of form '#fff' or '#ffffff'" if not fail: color = [x / 255.0 for x in (int(r, 16), int(g, 16), int(b, 16))] v.color = tuple(color) elif fail and not yesall_color and not yestoall: if queryCallback: result = queryCallback("Invalid vertex fill color (%s): id " "%s, fill '%s' \n\nUse default color" "and continue?" % (fail, id, fillstr), override=True) if result == CustomButtonDialog.ID['Yes']: pass elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_color = True elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True else: raise ImportError, "Import aborted." else: raise ImportError, ("Invalid vertex fill color (%s): id %s, " "fill '%s'" % (fail, id, fillstr)) # Determine the position of each vertex if 'x' in vertAttrs or 'y' in vertAttrs or 'z' in vertAttrs: pos = [0.0, 0.0, 0.0] if 'x' in vertAttrs and (isinstance(vertAttrs['x'], float) or isinstance(vertAttrs['x'], int)): pos[0] = pos[0] + vertAttrs['x'] if 'y' in vertAttrs and (isinstance(vertAttrs['y'], float) or isinstance(vertAttrs['y'], int)): pos[1] = pos[1] + vertAttrs['y'] if 'z' in vertAttrs and (isinstance(vertAttrs['z'], float) or isinstance(vertAttrs['z'], int)): pos[2] = pos[2] + vertAttrs['z'] v.pos = tuple(pos) # Accept either radius or size for the radius of a vertex if 'radius' in vertAttrs: v.radius = vertAttrs['radius'] elif 'size' in vertAttrs: v.radius = vertAttrs['size'] # Accept either name or label for the label of a vertex if 'name' in vertAttrs: v.name = vertAttrs['name'] elif 'label' in vertAttrs: v.name = vertAttrs['label'] graph.addVertex(v) vertexIndex[v.id] = v # Read edges for edgeNode in graphNode.getElementsByTagName('edge'): edgeAttrs = dict( [(str(a), str(b)) for a, b in edgeNode.attributes.items()] ) # Create a dict edgeAttrs that maps edge attributes to their values for dataNode in edgeNode.getElementsByTagName('data'): dataAttrs = dict( [(str(a), str(b)) for a, b in dataNode.attributes.items()] ) if 'edge' in keyInfo[dataAttrs['key']]: key = keyInfo[dataAttrs['key']]['edge'] else: key = keyInfo[dataAttrs['key']]['forNotSpecified'] if dataNode.childNodes != []: if 'attr.type' in key.keys(): value = key['attr.type'](dataNode.childNodes[0].wholeText.strip()) else: value = str(dataNode.childNodes[0].wholeText.strip()) if 'attr.name' in key.keys(): edgeAttrs[key['attr.name']] = value # Assign defaults if len(defaults) > 0 and defaults['edge']: for prop, value in defaults['edge'].items(): if prop not in edgeAttrs: edgeAttrs[prop] = value # Raise ImportError if edge references a nonexistent vertex, or if # it is a duplicate edge, or if source is the same as the target if edgeAttrs['source'] not in vertexIndex: if yesall_nonexist_vert or yestoall: continue if queryCallback: result = queryCallback("Edge specifies nonexistent source " "vertex %s" % (edgeAttrs['source'])) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_nonexist_vert = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Edge specifies nonexistent source vertex " "%s" % (edgeAttrs['source'])) if edgeAttrs['target'] not in vertexIndex: if yesall_nonexist_vert or yestoall: continue if queryCallback: result = queryCallback("Edge specifies nonexistent target " "vertex %s" % (edgeAttrs['target'])) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_nonexist_vert = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Edge specifies nonexistent target vertex " "%s" % (edgeAttrs['target'])) if (edgeAttrs['source'], edgeAttrs['target']) in edgeIndex: if yesall_duplicate_edge or yestoall: continue if queryCallback: result = queryCallback("Duplicate edge detected: %s -> %s" % (edgeAttrs['source'], edgeAttrs['target'])) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_duplicate_edge = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Duplicate edge detected: %s -> %s" % (edgeAttrs['source'], edgeAttrs['target'])) if (edgeAttrs['target'], edgeAttrs['source']) in edgeIndex: if yesall_duplicate_edge or yestoall: continue if queryCallback: result = queryCallback("Duplicate (reversed) edge detected: " "%s -> %s" % (edgeAttrs['source'], edgeAttrs['target'])) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_duplicate_edge = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError, ("Duplicate (reversed) edge detected: %s -> %s" % (edgeAttrs['source'], edgeAttrs['target'])) if edgeAttrs['source'] == edgeAttrs['target']: if yesall_degenerate_edge or yestoall: continue if queryCallback: result = queryCallback("Degenerate edge detected: %s -> %s" % (edgeAttrs['source'], edgeAttrs['target'])) if result == CustomButtonDialog.ID['Yes']: continue elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_degenerate_edge = True continue elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True continue else: raise ImportError, "Import aborted." else: raise ImportError("Degenerate edge detected: %s -> %s" % (edgeAttrs['source'], edgeAttrs['target'])) e = Edge(source=vertexIndex[edgeAttrs['source']], \ target=vertexIndex[edgeAttrs['target']]) # Determine the color of the vertex, accepting either # r,g,b or red,green,blue for the names of the color attributes if ('red' in edgeAttrs or 'blue' in edgeAttrs or 'green' in edgeAttrs or 'r' in edgeAttrs or 'b' in edgeAttrs or 'g' in edgeAttrs): color = [0.0, 0.0, 0.0] if 'red' in edgeAttrs: color[0] = color[0] + edgeAttrs['red'] elif 'r' in edgeAttrs: color[0] = color[0] + edgeAttrs['r'] if 'green' in edgeAttrs: color[1] = color[1] + edgeAttrs['green'] elif 'g' in edgeAttrs: color[1] = color[1] + edgeAttrs['g'] if 'blue' in edgeAttrs: color[2] = color[2] + edgeAttrs['blue'] elif 'b' in edgeAttrs: color[2] = color[2] + edgeAttrs['b'] # If color uses 0 to 255 for colors instead of 0.0 to 1.0, convert if color[0] > 1.0 or color[1] > 1.0 or color[2] > 1.0: color[0] /= 255 color[1] /= 255 color[2] /= 255 e.color = tuple(color) # Determine the color of the edge if using 'fill' and hex elif 'fill' in edgeAttrs: fail = False fillstr = edgeAttrs['fill'] if len(fillstr) == 7: r = fillstr[1:3] g = fillstr[3:5] b = fillstr[5:7] elif len(fillstr) == 4: r = fillstr[1] g = fillstr[2] b = fillstr[3] else: fail = "fill must be of form '#fff' or '#ffffff'" if not fail: color = [x / 255.0 for x in (int(r, 16), int(g, 16), int(b, 16))] e.color = tuple(color) if fail and not yesall_color and not yestoall: if queryCallback: result = queryCallback("Invalid edge fill color (%s): " "%s -> %s, fill '%s' " "\n\nUse default color and continue?" % (fail, edgeAttrs['source'], edgeAttrs['target'], fillstr), override=True) if result == CustomButtonDialog.ID['Yes']: pass elif result == CustomButtonDialog.ID['Yes to similar errors']: yesall_color = True elif result == CustomButtonDialog.ID['Yes to all errors']: yestoall = True else: raise ImportError, "Import aborted." else: raise ImportError, ("Invalid edge fill color (%s): " "%s -> %s, fill '%s'" % (fail, edgeAttrs['source'], edgeAttrs['target'], fillstr)) # Accept radius, size, or weight for the name of edge radius if 'radius' in edgeAttrs: e.radius = edgeAttrs['radius'] elif 'size' in edgeAttrs: e.radius = edgeAttrs['size'] elif 'weight' in edgeAttrs: e.radius = edgeAttrs['weight'] if 'attribute' in edgeAttrs: e.attribute = edgeAttrs['attribute'] graph.addEdge(e) edgeIndex[(edgeAttrs['source'], edgeAttrs['target'])] = e