Repository: networkx/networkx

Issue:
write_gexf not compatible with node attributes?
The following minimal example using `write_gexf` fails for me (version 1.11):

```python
import networkx as nx

g = nx.random_graphs.complete_graph(n=20)
nx.set_node_attributes(g, 'parameter', [0 for i in range(len(g))])
nx.write_gexf(g_trans, 'tmp.gexf')
```

Fails with:
```
~/miniconda2/envs/std3/lib/python3.6/site-packages/networkx/readwrite/gexf.py in add_attributes(self, node_or_edge, xml_obj, data, default)
    407             if type(v)==list:
    408                 # dynamic data
--> 409                 for val,start,end in v:
    410                     val_type = type(val)
    411                     if start is not None or end is not None:

TypeError: 'int' object is not iterable
```

It's not a matter of the type, either:
```python
nx.set_node_attributes(g, 'parameter', ['0' for i in range(len(g))])
```

also fails. The code works fine with `write_gml` instead.

Possible duplicate of #2204 , #1490?


Discussion:
This is starting to look specific enough (with 3 Issues lined up as well) that I'm going give it a Milestone 2.3  designation.  We should get this done.

Resolving patch:
diff --git a/networkx/readwrite/gexf.py b/networkx/readwrite/gexf.py
--- a/networkx/readwrite/gexf.py
+++ b/networkx/readwrite/gexf.py
@@ -202,24 +202,28 @@ class GEXF(object):
              (float, "double"),
              (bool, "boolean"),
              (list, "string"),
-             (dict, "string")]
-
-    try:  # Python 3.x
-        blurb = chr(1245)  # just to trigger the exception
-        types.extend([
-            (int, "long"),
-            (str, "liststring"),
-            (str, "anyURI"),
-            (str, "string")])
-    except ValueError:  # Python 2.6+
-        types.extend([
-            (long, "long"),
-            (str, "liststring"),
-            (str, "anyURI"),
-            (str, "string"),
-            (unicode, "liststring"),
-            (unicode, "anyURI"),
-            (unicode, "string")])
+             (dict, "string"),
+             (int, "long"),
+             (str, "liststring"),
+             (str, "anyURI"),
+             (str, "string")]
+
+    # These additions to types allow writing numpy types
+    try:
+        import numpy as np
+    except ImportError:
+        pass
+    else:
+        # prepend so that python types are created upon read (last entry wins)
+        types = [(np.float64, "float"), (np.float32, "float"),
+                 (np.float16, "float"), (np.float_, "float"),
+                 (np.int, "int"), (np.int8, "int"),
+                 (np.int16, "int"), (np.int32, "int"),
+                 (np.int64, "int"), (np.uint8, "int"),
+                 (np.uint16, "int"), (np.uint32, "int"),
+                 (np.uint64, "int"), (np.int_, "int"),
+                 (np.intc, "int"), (np.intp, "int"),
+                ] + types
 
     xml_type = dict(types)
     python_type = dict(reversed(a) for a in types)
@@ -268,6 +272,7 @@ def __init__(self, graph=None, encoding='utf-8', prettyprint=True,
         # counters for edge and attribute identifiers
         self.edge_id = itertools.count()
         self.attr_id = itertools.count()
+        self.all_edge_ids = set()
         # default attributes are stored in dictionaries
         self.attr = {}
         self.attr['node'] = {}
@@ -287,6 +292,11 @@ def __str__(self):
         return s
 
     def add_graph(self, G):
+        # first pass through G collecting edge ids
+        for u, v, dd in G.edges(data=True):
+            eid = dd.get('id')
+            if eid is not None:
+                self.all_edge_ids.add(make_str(eid))
         # set graph attributes
         if G.graph.get('mode') == 'dynamic':
             mode = 'dynamic'
@@ -363,6 +373,9 @@ def edge_key_data(G):
                     edge_id = edge_data.pop('id', None)
                     if edge_id is None:
                         edge_id = next(self.edge_id)
+                        while make_str(edge_id) in self.all_edge_ids:
+                            edge_id = next(self.edge_id)
+                        self.all_edge_ids.add(make_str(edge_id))
                     yield u, v, edge_id, edge_data
             else:
                 for u, v, data in G.edges(data=True):
@@ -370,6 +383,9 @@ def edge_key_data(G):
                     edge_id = edge_data.pop('id', None)
                     if edge_id is None:
                         edge_id = next(self.edge_id)
+                        while make_str(edge_id) in self.all_edge_ids:
+                            edge_id = next(self.edge_id)
+                        self.all_edge_ids.add(make_str(edge_id))
                     yield u, v, edge_id, edge_data
         edges_element = Element('edges')
         for u, v, key, edge_data in edge_key_data(G):
@@ -422,6 +438,8 @@ def add_attributes(self, node_or_edge, xml_obj, data, default):
             if k == 'key':
                 k = 'networkx_key'
             val_type = type(v)
+            if val_type not in self.xml_type:
+                raise TypeError('attribute value type is not allowed: %s' % val_type)
             if isinstance(v, list):
                 # dynamic data
                 for val, start, end in v:


Test patch:
diff --git a/networkx/readwrite/tests/test_gexf.py b/networkx/readwrite/tests/test_gexf.py
--- a/networkx/readwrite/tests/test_gexf.py
+++ b/networkx/readwrite/tests/test_gexf.py
@@ -313,6 +313,88 @@ def test_write_with_node_attributes(self):
         obtained = '\n'.join(nx.generate_gexf(G))
         assert_equal(expected, obtained)
 
+    def test_edge_id_construct(self):
+        G = nx.Graph()
+        G.add_edges_from([(0, 1, {'id': 0}), (1, 2, {'id': 2}), (2, 3)])
+        expected = """<gexf version="1.2" xmlns="http://www.gexf.net/1.2draft" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2001/XMLSchema-instance">
+  <graph defaultedgetype="undirected" mode="static" name="">
+    <meta>
+      <creator>NetworkX {}</creator>
+      <lastmodified>{}</lastmodified>
+    </meta>
+    <nodes>
+      <node id="0" label="0" />
+      <node id="1" label="1" />
+      <node id="2" label="2" />
+      <node id="3" label="3" />
+    </nodes>
+    <edges>
+      <edge id="0" source="0" target="1" />
+      <edge id="2" source="1" target="2" />
+      <edge id="1" source="2" target="3" />
+    </edges>
+  </graph>
+</gexf>""".format(nx.__version__, time.strftime('%d/%m/%Y'))
+        obtained = '\n'.join(nx.generate_gexf(G))
+        assert_equal(expected, obtained)
+
+    def test_numpy_type(self):
+        G = nx.path_graph(4)
+        try:
+            import numpy
+        except ImportError:
+            return
+        nx.set_node_attributes(G, {n:n for n in numpy.arange(4)}, 'number')
+        G[0][1]['edge-number'] = numpy.float64(1.1)
+
+        expected = """<gexf version="1.2" xmlns="http://www.gexf.net/1.2draft" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2001/XMLSchema-instance">
+  <graph defaultedgetype="undirected" mode="static" name="">
+    <attributes class="edge" mode="static">
+      <attribute id="1" title="edge-number" type="float" />
+    </attributes>
+    <attributes class="node" mode="static">
+      <attribute id="0" title="number" type="int" />
+    </attributes>
+    <meta>
+      <creator>NetworkX {}</creator>
+      <lastmodified>{}</lastmodified>
+    </meta>
+    <nodes>
+      <node id="0" label="0">
+        <attvalues>
+          <attvalue for="0" value="0" />
+        </attvalues>
+      </node>
+      <node id="1" label="1">
+        <attvalues>
+          <attvalue for="0" value="1" />
+        </attvalues>
+      </node>
+      <node id="2" label="2">
+        <attvalues>
+          <attvalue for="0" value="2" />
+        </attvalues>
+      </node>
+      <node id="3" label="3">
+        <attvalues>
+          <attvalue for="0" value="3" />
+        </attvalues>
+      </node>
+    </nodes>
+    <edges>
+      <edge id="0" source="0" target="1">
+        <attvalues>
+          <attvalue for="1" value="1.1" />
+        </attvalues>
+      </edge>
+      <edge id="1" source="1" target="2" />
+      <edge id="2" source="2" target="3" />
+    </edges>
+  </graph>
+</gexf>""".format(nx.__version__, time.strftime('%d/%m/%Y'))
+        obtained = '\n'.join(nx.generate_gexf(G))
+        assert_equal(expected, obtained)
+
     def test_bool(self):
         G = nx.Graph()
         G.add_node(1, testattr=True)