Repository: networkx/networkx

Issue:
Possible bug in version attribute in GEXF.py
In ```gexf.py```, from lines 769 to 773, we currently have https://github.com/networkx/networkx/blob/4a0f42b477e5b20377ba7502de555b67b429adc3/networkx/readwrite/gexf.py#L769-L773

while in lines 822-830, it is:

https://github.com/networkx/networkx/blob/4a0f42b477e5b20377ba7502de555b67b429adc3/networkx/readwrite/gexf.py#L822-L830

which seemed somewhat inconsistent to me (**version** vs **VERSION**). So I looked above, and found:

https://github.com/networkx/networkx/blob/4a0f42b477e5b20377ba7502de555b67b429adc3/networkx/readwrite/gexf.py#L159-L161

which indicates that the ```version``` parameter would only accept ```1.1draft/1.2draft```, while the ```VERSION``` parameter will have ```1.1/1.2``` in it. 

https://github.com/networkx/networkx/blob/4a0f42b477e5b20377ba7502de555b67b429adc3/networkx/readwrite/gexf.py#L250-L251
Hence, the checks in both add_edges and add_nodes with ```self.version``` fails always.

**How to test?**

```python
import networkx as nx
G=nx.Graph()
G.add_node(0, label='1', color='green')
G.nodes[0]['viz']['color'] = {'r' : 0, 'g' : 0, 'b' : 256}
fh = io.BytesIO()
nx.write_gexf(G, fh, version='1.1draft')
fh.seek(0)
H = nx.read_gexf(fh, node_type=int)
```
The above code will execute fine, but as soon as we omit the version parameter (which defaults to 1.2draft), it raises the exception as it expects a ```rgba``` tuple. Clearly, the VERSION parameter is working.

However:

```python
import networkx as nx
G=nx.Graph()
G.add_node(0, label='1', color='green', version='1.1draft')
G.nodes[0]['spells']=[(1,2)]
print("\n".join(nx.generate_gexf(G)))
```
will still have the spell attribute, but neither will have slice. Same for version=1.2draft (can be checked via **print()** in them too)

Thus, I believe that all the ```self.version = 1.1 ``` checks should be made into ```self.VERSION = 1.1 ```, unless I am misunderstanding it? At any case, the code should be consistent for the check.

**Additional context:** I am currently working on a PR to improve the code coverage of test_gexf.py as per discussion in #3606 and noticed this weird issue. Clarification regarding this bug(?) will be welcome so that I could incorporate it in my PR


Discussion:
Yes!  Anywhere it checks ```self.version == '1.1'```, that should be ```self.VERSION```.
I guess that means we need a test that slices work.

Thanks!

Resolving patch:
diff --git a/networkx/readwrite/gexf.py b/networkx/readwrite/gexf.py
--- a/networkx/readwrite/gexf.py
+++ b/networkx/readwrite/gexf.py
@@ -356,7 +356,7 @@ def add_nodes(self, G, graph_element):
             # add node element and attr subelements
             default = G.graph.get('node_default', {})
             node_data = self.add_parents(node_element, node_data)
-            if self.version == '1.1':
+            if self.VERSION == '1.1':
                 node_data = self.add_slices(node_element, node_data)
             else:
                 node_data = self.add_spells(node_element, node_data)
@@ -420,7 +420,7 @@ def edge_key_data(G):
             edge_element = Element('edge',
                                    source=source_id, target=target_id, **kw)
             default = G.graph.get('edge_default', {})
-            if self.version == '1.1':
+            if self.VERSION == '1.1':
                 edge_data = self.add_slices(edge_element, edge_data)
             else:
                 edge_data = self.add_spells(edge_element, edge_data)
@@ -768,7 +768,7 @@ def add_node(self, G, node_xml, node_attr, node_pid=None):
         # get attributes and subattributues for node
         data = self.decode_attr_elements(node_attr, node_xml)
         data = self.add_parents(data, node_xml)  # add any parents
-        if self.version == '1.1':
+        if self.VERSION == '1.1':
             data = self.add_slices(data, node_xml)  # add slices
         else:
             data = self.add_spells(data, node_xml)  # add spells
@@ -899,7 +899,7 @@ def add_edge(self, G, edge_element, edge_attr):
         data = self.decode_attr_elements(edge_attr, edge_element)
         data = self.add_start_end(data, edge_element)
 
-        if self.version == '1.1':
+        if self.VERSION == '1.1':
             data = self.add_slices(data, edge_element)  # add slices
         else:
             data = self.add_spells(data, edge_element)  # add spells


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
@@ -140,8 +140,7 @@ def test_read_simple_directed_graphml(self):
         H = nx.read_gexf(self.simple_directed_fh)
         assert sorted(G.nodes()) == sorted(H.nodes())
         assert sorted(G.edges()) == sorted(H.edges())
-        assert (sorted(G.edges(data=True)) ==
-                sorted(H.edges(data=True)))
+        assert sorted(G.edges(data=True)) == sorted(H.edges(data=True))
         self.simple_directed_fh.seek(0)
 
     def test_write_read_simple_directed_graphml(self):
@@ -152,17 +151,15 @@ def test_write_read_simple_directed_graphml(self):
         H = nx.read_gexf(fh)
         assert sorted(G.nodes()) == sorted(H.nodes())
         assert sorted(G.edges()) == sorted(H.edges())
-        assert (sorted(G.edges(data=True)) ==
-                sorted(H.edges(data=True)))
+        assert sorted(G.edges(data=True)) == sorted(H.edges(data=True))
         self.simple_directed_fh.seek(0)
 
     def test_read_simple_undirected_graphml(self):
         G = self.simple_undirected_graph
         H = nx.read_gexf(self.simple_undirected_fh)
         assert sorted(G.nodes()) == sorted(H.nodes())
-        assert (
-            sorted(sorted(e) for e in G.edges()) ==
-            sorted(sorted(e) for e in H.edges()))
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
         self.simple_undirected_fh.seek(0)
 
     def test_read_attribute_graphml(self):
@@ -260,9 +257,8 @@ def test_default_attribute(self):
         fh.seek(0)
         H = nx.read_gexf(fh, node_type=int)
         assert sorted(G.nodes()) == sorted(H.nodes())
-        assert (
-            sorted(sorted(e) for e in G.edges()) ==
-            sorted(sorted(e) for e in H.edges()))
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
         # Reading a gexf graph always sets mode attribute to either
         # 'static' or 'dynamic'. Remove the mode attribute from the
         # read graph for the sake of comparing remaining attributes.
@@ -498,3 +494,106 @@ def test_specials(self):
         assert H.nodes[1]['networkx_key'] == 'a'
         assert H.nodes[2]['networkx_key'] == 'b'
         assert H.nodes[3]['networkx_key'] == 'c'
+
+    def test_simple_list(self):
+        G = nx.Graph()
+        list_value = [(1, 2, 3), (9, 1, 2)]
+        G.add_node(1, key=list_value)
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh)
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert H.nodes[1]['networkx_key'] == list_value
+
+    def test_dynamic_mode(self):
+        G = nx.Graph()
+        G.add_node(1, label='1', color='green')
+        G.graph['mode'] = 'dynamic'
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh)
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert sorted(G.nodes()) == sorted(H.nodes())
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
+
+    def test_multigraph_with_missing_attributes(self):
+        G = nx.MultiGraph()
+        G.add_node(0, label='1', color='green')
+        G.add_node(1, label='2', color='green')
+        G.add_edge(0, 1, id='0', weight=3, type='undirected', start=0, end=1)
+        G.add_edge(0, 1)
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh)
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert sorted(G.nodes()) == sorted(H.nodes())
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
+
+    def test_missing_viz_attributes(self):
+        G = nx.Graph()
+        G.add_node(0, label='1', color='green')
+        G.nodes[0]['viz'] = {'size': 54}
+        G.nodes[0]['viz']['position'] = {'x': 0, 'y': 1, 'z': 0}
+        G.nodes[0]['viz']['color'] = {'r': 0, 'g': 0, 'b': 256}
+        G.nodes[0]['viz']['shape'] = 'http://random.url'
+        G.nodes[0]['viz']['thickness'] = 2
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh, version='1.1draft')
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert sorted(G.nodes()) == sorted(H.nodes())
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
+
+        # Second graph for the other branch
+        G = nx.Graph()
+        G.add_node(0, label='1', color='green')
+        G.nodes[0]['viz'] = {'size': 54}
+        G.nodes[0]['viz']['position'] = {'x': 0, 'y': 1, 'z': 0}
+        G.nodes[0]['viz']['color'] = {'r': 0, 'g': 0, 'b': 256, 'a': 0.5}
+        G.nodes[0]['viz']['shape'] = 'ftp://random.url'
+        G.nodes[0]['viz']['thickness'] = 2
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh)
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert sorted(G.nodes()) == sorted(H.nodes())
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
+
+    def test_slice_and_spell(self):
+        # Test spell first, so version = 1.2
+        G = nx.Graph()
+        G.add_node(0, label='1', color='green')
+        G.nodes[0]['spells'] = [(1, 2)]
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh)
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert sorted(G.nodes()) == sorted(H.nodes())
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
+
+        G = nx.Graph()
+        G.add_node(0, label='1', color='green')
+        G.nodes[0]['slices'] = [(1, 2)]
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh, version='1.1draft')
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert sorted(G.nodes()) == sorted(H.nodes())
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))
+
+    def test_add_parent(self):
+        G = nx.Graph()
+        G.add_node(0, label='1', color='green', parents=[1, 2])
+        fh = io.BytesIO()
+        nx.write_gexf(G, fh)
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+        assert sorted(G.nodes()) == sorted(H.nodes())
+        assert (sorted(sorted(e) for e in G.edges())
+                == sorted(sorted(e) for e in H.edges()))