Repository: networkx/networkx

Issue:
GEXF output does not round-trip
On master (currently 269c5721100797514aa0469d58e637ec3a29e984) and Python 3.7.3, if I run the following script:
```python
import io, math
import networkx

g = networkx.Graph()
g.add_node(1, weight=float('inf'), data='inf')
g.add_node(2, weight=float('nan'), data='nan')
with io.BytesIO() as f:
	networkx.write_gexf(g, f)
	f.seek(0)
	h = networkx.read_gexf(f, node_type=int)
if h.nodes[1]['data'] != 'inf':
	print(f"h.nodes[1]['data'] == {h.nodes[1]['data']!r} != 'inf'")
if h.nodes[2]['data'] != 'nan':
	print(f"h.nodes[2]['data'] == {h.nodes[2]['data']!r} != 'nan'")
if not isinstance(h.nodes[1]['weight'], float) or not math.isinf(h.nodes[1]['weight']):
	print(f"h.nodes[1]['weight'] == {h.nodes[1]['weight']!r} != float('inf')")
if not isinstance(h.nodes[2]['weight'], float) or not math.isnan(h.nodes[2]['weight']):
	print(f"h.nodes[2]['weight'] == {h.nodes[2]['weight']!r} != float('nan')")
```
I get the following output:

```
h.nodes[1]['data'] == 'INF' != 'inf'
h.nodes[2]['data'] == 'NaN' != 'nan'
h.nodes[1]['weight'] == 'INF' != float('inf')
h.nodes[2]['weight'] == 'NaN' != float('nan')
```

It appears that dc21ca4, which fixed #3573, caused `write_gexf` to write `float('inf')` to XML file as `INF` by transforming text without checking data types first, and that `read_gexf` does not cast `float` or `double` XML types to the `float` Python type.


Discussion:
Yes -- you are right... I understand the issue. 
It'd be nice to read 'INF', 'NaN' and '-INF' as floats (and maybe 'inf', etc too)...
I think to conform to the standard, you just rely on the data type definitions at the top of the GEXF file. This line should tell the reader how to convert the data:

```xml
<attribute id="0" title="weight" type="double" />
```

If the type is double or float, convert it to a Python `float`.
Thanks very much for this report!  
It looks like the lines added to the "reader" at line 800 of the file should not be there. The ```decode_attr_elements``` handles them so we don't have to. 
Also, the lines in the writer should check the type to ensure it is float before converting.

And we'll add your example as a test.  Thanks again!


Resolving patch:
diff --git a/networkx/readwrite/gexf.py b/networkx/readwrite/gexf.py
--- a/networkx/readwrite/gexf.py
+++ b/networkx/readwrite/gexf.py
@@ -461,12 +461,13 @@ def add_attributes(self, node_or_edge, xml_obj, data, default):
                     e.attrib['for'] = attr_id
                     e.attrib['value'] = make_str(val)
                     # Handle nan, inf, -inf differently
-                    if e.attrib['value'] == 'inf':
-                        e.attrib['value'] = 'INF'
-                    elif e.attrib['value'] == 'nan':
-                        e.attrib['value'] = 'NaN'
-                    elif e.attrib['value'] == '-inf':
-                        e.attrib['value'] = '-INF'
+                    if val_type == float:
+                        if e.attrib['value'] == 'inf':
+                            e.attrib['value'] = 'INF'
+                        elif e.attrib['value'] == 'nan':
+                            e.attrib['value'] = 'NaN'
+                        elif e.attrib['value'] == '-inf':
+                            e.attrib['value'] = '-INF'
                     if start is not None:
                         e.attrib['start'] = make_str(start)
                     if end is not None:
@@ -484,13 +485,14 @@ def add_attributes(self, node_or_edge, xml_obj, data, default):
                     e.attrib['value'] = make_str(v).lower()
                 else:
                     e.attrib['value'] = make_str(v)
-                    # Handle nan, inf, -inf differently
-                    if e.attrib['value'] == 'inf':
-                        e.attrib['value'] = 'INF'
-                    elif e.attrib['value'] == 'nan':
-                        e.attrib['value'] = 'NaN'
-                    elif e.attrib['value'] == '-inf':
-                        e.attrib['value'] = '-INF'
+                    # Handle float nan, inf, -inf differently
+                    if val_type == float:
+                        if e.attrib['value'] == 'inf':
+                            e.attrib['value'] = 'INF'
+                        elif e.attrib['value'] == 'nan':
+                            e.attrib['value'] = 'NaN'
+                        elif e.attrib['value'] == '-inf':
+                            e.attrib['value'] = '-INF'
                 attvalues.append(e)
         xml_obj.append(attvalues)
         return data
@@ -793,14 +795,6 @@ def add_node(self, G, node_xml, node_attr, node_pid=None):
             for node_xml in subnodes.findall('{%s}node' % self.NS_GEXF):
                 self.add_node(G, node_xml, node_attr, node_pid=node_id)
 
-        # Handle nan, inf, -inf differently
-        for k, v in data.items():
-            if make_str(v) == 'inf':
-                data[k] = 'INF'
-            elif make_str(v) == 'nan':
-                data[k] = 'NaN'
-            elif make_str(v) == '-inf':
-                data[k] = '-INF'
         G.add_node(node_id, **data)
 
     def add_start_end(self, data, xml):


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
@@ -474,18 +474,32 @@ def test_bool(self):
 
     # Test for NaN, INF and -INF
     def test_specials(self):
+        from math import isnan
+        inf, nan = float('inf'), float('nan')
         G = nx.Graph()
-        G.add_node(1, testattr=float('inf'))
-        G.add_node(2, testattr=float('nan'))
-        G.add_node(3, testattr=float('-inf'))
-        list_value = [(1, 2, 3), (2, 1, 2)]
-        G.add_node(4, key=list_value)
+        G.add_node(1, testattr=inf, strdata='inf', key='a')
+        G.add_node(2, testattr=nan, strdata='nan', key='b')
+        G.add_node(3, testattr=-inf, strdata='-inf', key='c')
+
         fh = io.BytesIO()
         nx.write_gexf(G, fh)
         fh.seek(0)
-        # float as inf,-inf and nan are all floats internally
-        H = nx.read_gexf(fh, node_type=float)
-        assert_equal(H.nodes[1]['testattr'], "INF")
-        assert_equal(H.nodes[2]['testattr'], "NaN")
-        assert_equal(H.nodes[3]['testattr'], "-INF")
-        assert_equals(H.nodes[4]['networkx_key'], list_value)
+        filetext = fh.read()
+        fh.seek(0)
+        H = nx.read_gexf(fh, node_type=int)
+
+        assert_in(b'INF', filetext)
+        assert_in(b'NaN', filetext)
+        assert_in(b'-INF', filetext)
+
+        assert_equal(H.nodes[1]['testattr'], inf)
+        assert_true(isnan(H.nodes[2]['testattr']))
+        assert_equal(H.nodes[3]['testattr'], -inf)
+
+        assert_equal(H.nodes[1]['strdata'], 'inf')
+        assert_equal(H.nodes[2]['strdata'], 'nan')
+        assert_equal(H.nodes[3]['strdata'], '-inf')
+
+        assert_equal(H.nodes[1]['networkx_key'], 'a')
+        assert_equal(H.nodes[2]['networkx_key'], 'b')
+        assert_equal(H.nodes[3]['networkx_key'], 'c')