38
38
'Aric Hagberg (hagberg@lanl.gov)'
41
__all__ = ['write_graphml', 'read_graphml',
41
__all__ = ['write_graphml', 'read_graphml', 'generate_graphml',
42
42
'GraphMLWriter', 'GraphMLReader']
44
44
import networkx as nx
45
from networkx.utils import _get_fh
46
from xml.etree.cElementTree import Element, ElementTree as ET
45
from networkx.utils import _get_fh, make_str
48
from xml.etree.cElementTree import Element, ElementTree as ET, tostring
49
def write_graphml(G, path, encoding='utf-8'):
53
def write_graphml(G, path, encoding='utf-8',prettyprint=True):
50
54
"""Write G in GraphML XML format to path
68
76
edges together) hyperedges, nested graphs, or ports.
70
78
fh = _get_fh(path, mode='wb')
71
writer = GraphMLWriter(encoding=encoding)
79
writer = GraphMLWriter(encoding=encoding,prettyprint=prettyprint)
72
80
writer.add_graph_element(G)
83
def generate_graphml(G, encoding='utf-8',prettyprint=True):
84
"""Generate GraphML lines for G
90
encoding : string (optional)
91
Encoding for text data.
92
prettyprint : bool (optional)
93
If True use line breaks and indenting in output XML.
97
>>> G=nx.path_graph(4)
98
>>> linefeed=chr(10) # linefeed=\n
99
>>> s=linefeed.join(nx.generate_graphml(G)) # a string
100
>>> for line in nx.generate_graphml(G): # doctest: +SKIP
105
This implementation does not support mixed graphs (directed and unidirected
106
edges together) hyperedges, nested graphs, or ports.
108
writer = GraphMLWriter(encoding=encoding,prettyprint=prettyprint)
109
writer.add_graph_element(G)
110
for line in str(writer).splitlines():
75
113
def read_graphml(path,node_type=str):
76
114
"""Read graph in GraphML format from path.
92
133
This implementation does not support mixed graphs (directed and unidirected
93
134
edges together), hypergraphs, nested graphs, or ports.
136
For multigraphs the GraphML edge "id" will be used as the edge
137
key. If not specified then they "key" attribute will be used. If
138
there is no "key" attribute a default NetworkX multigraph edge key
141
Files with the yEd "yfiles" extension will can be read but the graphics
142
information is discarded.
144
yEd compressed files ("file.graphmlz" extension) can be read by renaming
145
the file to "file.graphml.gz".
97
148
fh=_get_fh(path,mode='rb')
104
155
class GraphML(object):
105
156
NS_GRAPHML = "http://graphml.graphdrawing.org/xmlns"
106
157
NS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
158
#xmlns:y="http://www.yworks.com/xml/graphml"
159
NS_Y = "http://www.yworks.com/xml/graphml"
107
160
SCHEMALOCATION = \
108
161
' '.join(['http://graphml.graphdrawing.org/xmlns',
109
162
'http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'])
111
xml_type = {str:"string", int:"int", float:"float", float:"double"}
112
python_type = dict(reversed(a) for a in list(xml_type.items()))
165
chr(12345) # Fails on Py!=3.
166
unicode = str # Py3k's str is our unicode type
170
types=((str,"yfiles"),(str,"string"), (unicode,"string"),
171
(int,"int"), (float,"float"), (float,"double"))
172
xml_type = dict(types)
173
python_type = dict(reversed(a) for a in types)
115
176
class GraphMLWriter(GraphML):
116
def __init__(self, encoding="utf-8"):
177
def __init__(self, graph=None, encoding="utf-8",prettyprint=True):
179
import xml.etree.cElementTree
181
raise ImportError("GraphML writer requires xml.elementtree.ElementTree")
182
self.prettyprint=prettyprint
117
183
self.encoding = encoding
118
184
self.xml = Element("graphml",
119
185
{'xmlns':self.NS_GRAPHML,
121
187
'xsi:schemaLocation':self.SCHEMALOCATION}
191
if graph is not None:
192
self.add_graph_element(graph)
125
def get_key(self, name, attr_type, edge_or_node,default):
126
keys_key = (name, attr_type, edge_or_node)
197
self.indent(self.xml)
198
return tostring(self.xml)
200
def get_key(self, name, attr_type, scope, default):
201
keys_key = (name, attr_type, scope)
128
203
return self.keys[keys_key]
130
205
new_id = "d%i" % len(list(self.keys))
131
206
self.keys[keys_key] = new_id
132
207
key_kwargs = {"id":new_id,
134
209
"attr.name":name,
135
210
"attr.type":attr_type}
136
211
key_element=Element("key",**key_kwargs)
137
212
# add subelement for data default value if present
138
213
if default is not None:
139
214
default_element=Element("default")
140
default_element.text=str(default)
215
default_element.text=make_str(default)
141
216
key_element.append(default_element)
142
217
self.xml.insert(0,key_element)
145
221
def add_data(self, name, element_type, value,
149
225
Make a data element for an edge or a node. Keep a log of the
150
226
type in the keys table.
152
key_id = self.get_key(name,self.xml_type[element_type],
153
edge_or_node, default)
228
if element_type not in self.xml_type:
229
raise nx.NetworkXError('GraphML writer does not support '
230
'dict types as data values.')
231
key_id = self.get_key(name, self.xml_type[element_type], scope, default)
154
232
data_element = Element("data", key=key_id)
155
data_element.text = str(value)
233
data_element.text = make_str(value)
156
234
return data_element
158
def add_attributes(self, node_or_edge, xml_obj, data, default):
236
def add_attributes(self, scope, xml_obj, data, default):
159
237
"""Appends attributes to edges or nodes.
161
239
for k,v in list(data.items()):
162
240
default_value=default.get(k)
163
obj=self.add_data(str(k),type(v),str(v),
164
edge_or_node=node_or_edge,
165
default=default_value)
241
obj=self.add_data(make_str(k), type(v), make_str(v),
242
scope=scope, default=default_value)
166
243
xml_obj.append(obj)
168
245
def add_nodes(self, G, graph_element):
169
246
for node,data in G.nodes_iter(data=True):
170
node_element = Element("node", id = str(node))
247
node_element = Element("node", id = make_str(node))
171
248
default=G.graph.get('node_default',{})
172
249
self.add_attributes("node", node_element, data, default)
173
250
graph_element.append(node_element)
175
252
def add_edges(self, G, graph_element):
176
253
if G.is_multigraph():
177
254
for u,v,key,data in G.edges_iter(data=True,keys=True):
178
edge_element = Element("edge",source=str(u),target=str(v),
255
edge_element = Element("edge",source=make_str(u),
180
257
default=G.graph.get('edge_default',{})
181
258
self.add_attributes("edge", edge_element, data, default)
259
self.add_attributes("edge", edge_element,
260
{'key':key}, default)
182
261
graph_element.append(edge_element)
184
263
for u,v,data in G.edges_iter(data=True):
185
edge_element = Element("edge",source=str(u),target=str(v))
264
edge_element = Element("edge",source=make_str(u),
186
266
default=G.graph.get('edge_default',{})
187
267
self.add_attributes("edge", edge_element, data, default)
188
268
graph_element.append(edge_element)
199
279
graph_element = Element("graph",
200
280
edgedefault = default_edge_type,
283
data=dict((k,v) for (k,v) in G.graph.items()
284
if k not in ['node_default','edge_default'])
285
self.add_attributes("graph", graph_element, data, default)
203
286
self.add_nodes(G,graph_element)
204
287
self.add_edges(G,graph_element)
205
288
self.xml.append(graph_element)
212
295
self.add_graph_element(G)
214
297
def dump(self, stream):
299
self.indent(self.xml)
215
300
document = ET(self.xml)
216
301
header='<?xml version="1.0" encoding="%s"?>'%self.encoding
217
302
stream.write(header.encode(self.encoding))
218
303
document.write(stream, encoding=self.encoding)
305
def indent(self, elem, level=0):
306
# in-place prettyprint formatter
309
if not elem.text or not elem.text.strip():
311
if not elem.tail or not elem.tail.strip():
314
self.indent(elem, level+1)
315
if not elem.tail or not elem.tail.strip():
318
if level and (not elem.tail or not elem.tail.strip()):
221
322
class GraphMLReader(GraphML):
222
323
"""Read a GraphML document. Produces NetworkX graph objects.
224
325
def __init__(self, node_type=str):
327
import xml.etree.cElementTree
329
raise ImportError("GraphML reader requires xml.elementtree.ElementTree")
225
330
self.node_type=node_type
226
self.multigraph=False # assume graph and test for multigraph
331
self.multigraph=False # assume multigraph and test for parallel edges
228
333
def __call__(self, stream):
229
334
self.xml = ET(file=stream)
301
410
target = self.node_type(edge_element.get("target"))
302
411
data = self.decode_data_elements(graphml_keys, edge_element)
303
412
# GraphML stores edge ids as an attribute
304
# NetworkX uses them as keys in multigraphs too
413
# NetworkX uses them as keys in multigraphs too if no key
414
# attribute is specified
305
415
edge_id = edge_element.get("id")
307
417
data["id"] = edge_id
308
418
if G.has_edge(source,target):
419
# mark this as a multigraph
309
420
self.multigraph=True
310
if G.is_multigraph():
311
G.add_edge(source, target, key=edge_id, **data)
313
G.add_edge(source, target, **data)
422
# no id specified, try using 'key' attribute as id
423
edge_id=data.pop('key',None)
424
G.add_edge(source, target, key=edge_id, **data)
315
426
def decode_data_elements(self, graphml_keys, obj_xml):
316
427
"""Use the key information to decode the data XML if present.
333
447
graphml_key_defaults = {}
334
448
for k in graph_element.findall("{%s}key" % self.NS_GRAPHML):
335
449
attr_id = k.get("id")
450
attr_type=k.get('attr.type')
451
attr_name=k.get("attr.name")
452
if attr_type is None:
453
attr_name=k.get('yfiles.type')
455
if attr_name is None:
456
raise nx.NetworkXError("Unknown key type in file.")
336
457
graphml_keys[attr_id] = {
337
"name":k.get("attr.name"),
338
"type":self.python_type[k.get("attr.type")],
459
"type":self.python_type[attr_type],
339
460
"for":k.get("for")}
340
461
# check for "default" subelement of key element
341
462
default=k.find("{%s}default" % self.NS_GRAPHML)
342
463
if default is not None:
343
464
graphml_key_defaults[attr_id]=default.text
344
465
return graphml_keys,graphml_key_defaults
467
# fixture for nose tests
468
def setup_module(module):
469
from nose import SkipTest
471
import xml.etree.cElementTree
473
raise SkipTest("xml.etree.cElementTree not available")
475
# fixture for nose tests
476
def teardown_module(module):
478
os.unlink('test.graphml')