~ubuntu-branches/ubuntu/utopic/python-networkx/utopic

« back to all changes in this revision

Viewing changes to networkx/readwrite/graphml.py

  • Committer: Bazaar Package Importer
  • Author(s): Sandro Tosi
  • Date: 2011-03-19 12:19:16 UTC
  • mfrom: (1.2.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20110319121916-xrhlpiwc9sa5e028
Tags: 1.4-1
* New upstream release; thanks to Yaroslav Halchenko for the report;
  Closes: #617677
* debian/rules
  - don't compress objects.inv; thanks to Michael Fladischer for the report;
    Closes: #608780
* debian/watch
  - updated to point to PyPi
* debian/control
  - bump python-sphinx versioned b-d-i to 1.0.1 minimum
  - added python-pygraphviz to b-d-i, needed for doc building
* debian/copyright
  - bump upstream and packaging copyright years
* debian/patches/{40_add_networkxcss, 50_boundary-test-fix.patch
                  60_remove_svn_refs.diff 70_set_matplotlib_ps_backend.patch}
  - removed since merged upstream
* debian/patches/{10_doc_relocation, 20_example_dirs_remove,
                  30_use_local_objects.inv}
  - refreshed/adapted to new upstream code

Show diffs side-by-side

added added

removed removed

Lines of Context:
38
38
                            'Aric Hagberg (hagberg@lanl.gov)'
39
39
                            ])
40
40
 
41
 
__all__ = ['write_graphml', 'read_graphml', 
 
41
__all__ = ['write_graphml', 'read_graphml', 'generate_graphml',
42
42
           'GraphMLWriter', 'GraphMLReader']
43
43
 
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
47
46
import warnings
 
47
try:
 
48
    from xml.etree.cElementTree import Element, ElementTree as ET, tostring
 
49
 
 
50
except ImportError:
 
51
    pass
48
52
    
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
51
55
 
52
56
    Parameters
56
60
    path : file or string
57
61
       File or filename to write.  
58
62
       Filenames ending in .gz or .bz2 will be compressed.
 
63
    encoding : string (optional)
 
64
       Encoding for text data.
 
65
    prettyprint : bool (optional)
 
66
       If True use line breaks and indenting in output XML.
59
67
 
60
68
    Examples
61
69
    --------
68
76
    edges together) hyperedges, nested graphs, or ports. 
69
77
    """
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)
73
81
    writer.dump(fh)
74
82
 
 
83
def generate_graphml(G, encoding='utf-8',prettyprint=True):
 
84
    """Generate GraphML lines for G
 
85
 
 
86
    Parameters
 
87
    ----------
 
88
    G : graph
 
89
       A networkx graph
 
90
    encoding : string (optional)
 
91
       Encoding for text data.
 
92
    prettyprint : bool (optional)
 
93
       If True use line breaks and indenting in output XML.
 
94
 
 
95
    Examples
 
96
    --------
 
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
 
101
    ...    print line                   
 
102
 
 
103
    Notes
 
104
    -----
 
105
    This implementation does not support mixed graphs (directed and unidirected 
 
106
    edges together) hyperedges, nested graphs, or ports. 
 
107
    """
 
108
    writer = GraphMLWriter(encoding=encoding,prettyprint=prettyprint)
 
109
    writer.add_graph_element(G)
 
110
    for line in str(writer).splitlines():
 
111
        yield line
 
112
 
75
113
def read_graphml(path,node_type=str):
76
114
    """Read graph in GraphML format from path.
77
115
 
81
119
       File or filename to write.  
82
120
       Filenames ending in .gz or .bz2 will be compressed.
83
121
 
 
122
    node_type: Python type (default: str)
 
123
       Convert node ids to this type 
 
124
 
84
125
    Returns
85
126
    -------
86
127
    graph: NetworkX graph
92
133
    This implementation does not support mixed graphs (directed and unidirected 
93
134
    edges together), hypergraphs, nested graphs, or ports. 
94
135
    
 
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
 
139
    will be provided.
 
140
 
 
141
    Files with the yEd "yfiles" extension will can be read but the graphics
 
142
    information is discarded.
 
143
 
 
144
    yEd compressed files ("file.graphmlz" extension) can be read by renaming
 
145
    the file to "file.graphml.gz".
95
146
 
96
147
    """
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'])
110
163
    
111
 
    xml_type = {str:"string", int:"int", float:"float", float:"double"}
112
 
    python_type = dict(reversed(a) for a in list(xml_type.items()))
 
164
    try:
 
165
        chr(12345)     # Fails on Py!=3.
 
166
        unicode = str  # Py3k's str is our unicode type
 
167
    except ValueError:
 
168
        pass
 
169
 
 
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)
113
174
 
114
175
    
115
176
class GraphMLWriter(GraphML):
116
 
    def __init__(self, encoding="utf-8"):
 
177
    def __init__(self, graph=None, encoding="utf-8",prettyprint=True):
 
178
        try:
 
179
            import xml.etree.cElementTree
 
180
        except ImportError:
 
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}
122
188
                           )
123
189
        self.keys={}
 
190
 
 
191
        if graph is not None:
 
192
            self.add_graph_element(graph)
 
193
 
124
194
    
125
 
    def get_key(self, name, attr_type, edge_or_node,default):
126
 
        keys_key = (name, attr_type, edge_or_node)
 
195
    def __str__(self):
 
196
        if self.prettyprint:
 
197
            self.indent(self.xml)
 
198
        return tostring(self.xml)
 
199
 
 
200
    def get_key(self, name, attr_type, scope, default):
 
201
        keys_key = (name, attr_type, scope)
127
202
        try:
128
203
            return self.keys[keys_key]
129
204
        except KeyError:
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,
133
 
                          "for":edge_or_node,
 
208
                          "for":scope,
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)
143
218
        return new_id
144
219
    
 
220
 
145
221
    def add_data(self, name, element_type, value, 
146
 
                 edge_or_node="edge",
 
222
                 scope="all",
147
223
                 default=None):
148
224
        """
149
225
        Make a data element for an edge or a node. Keep a log of the
150
226
        type in the keys table.
151
227
        """
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
157
235
    
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.
160
238
        """
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)
167
244
            
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), 
179
 
                                       id=str(key))
 
255
                edge_element = Element("edge",source=make_str(u),
 
256
                                       target=make_str(v)) 
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)                
183
262
        else:
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),
 
265
                                       target=make_str(v))
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, 
201
281
                                id=G.name)
202
 
        
 
282
        default={}
 
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)
213
296
 
214
297
    def dump(self, stream):
 
298
        if self.prettyprint:
 
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)
219
304
 
 
305
    def indent(self, elem, level=0):
 
306
        # in-place prettyprint formatter
 
307
        i = "\n" + level*"  "
 
308
        if len(elem):
 
309
            if not elem.text or not elem.text.strip():
 
310
                elem.text = i + "  "
 
311
            if not elem.tail or not elem.tail.strip():
 
312
                elem.tail = i
 
313
            for elem in elem:
 
314
                self.indent(elem, level+1)
 
315
            if not elem.tail or not elem.tail.strip():
 
316
                elem.tail = i
 
317
        else:
 
318
            if level and (not elem.tail or not elem.tail.strip()):
 
319
                elem.tail = i
 
320
 
220
321
 
221
322
class GraphMLReader(GraphML):
222
323
    """Read a GraphML document.  Produces NetworkX graph objects.
223
324
    """
224
325
    def __init__(self, node_type=str):
 
326
        try:
 
327
            import xml.etree.cElementTree
 
328
        except ImportError:
 
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
227
332
        
228
333
    def __call__(self, stream):
229
334
        self.xml = ET(file=stream)
258
363
        # add edges
259
364
        for edge_xml in graph_xml.findall("{%s}edge" % self.NS_GRAPHML):        
260
365
            self.add_edge(G, edge_xml, graphml_keys)                            
 
366
        # add graph data            
 
367
        data = self.decode_data_elements(graphml_keys, graph_xml)
 
368
        G.graph.update(data)
 
369
 
261
370
        # switch to Graph or DiGraph if no parallel edges were found.
262
371
        if not self.multigraph: 
263
372
            if G.is_directed():
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")
306
416
        if edge_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)
312
 
        else:
313
 
            G.add_edge(source, target, **data)
 
421
        if edge_id is None:
 
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)
314
425
            
315
426
    def decode_data_elements(self, graphml_keys, obj_xml):
316
427
        """Use the key information to decode the data XML if present.
323
434
                data_type=graphml_keys[key]['type']
324
435
            except KeyError:
325
436
                raise nx.NetworkXError("Bad GraphML data: no key %s"%key)
326
 
            data[data_name] = data_type(data_element.text)
 
437
            text=data_element.text
 
438
            # assume anything with subelements is a yfiles extension
 
439
            if text is not None and len(list(data_element))==0:
 
440
                data[data_name] = data_type(text)
327
441
        return data
328
442
            
329
443
    def find_graphml_keys(self, graph_element):
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')
 
454
                attr_type='yfiles'
 
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")],
 
458
                "name":attr_name,
 
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
 
466
 
 
467
# fixture for nose tests
 
468
def setup_module(module):
 
469
    from nose import SkipTest
 
470
    try:
 
471
        import xml.etree.cElementTree
 
472
    except:
 
473
        raise SkipTest("xml.etree.cElementTree not available")
 
474
 
 
475
# fixture for nose tests
 
476
def teardown_module(module):
 
477
    import os
 
478
    os.unlink('test.graphml')