Repository: networkx/networkx

Issue:
GEXF output does not conform to GEXF and XML Schema standards
On master (currently 0d8d93a0e43829d6020e8babf59d411af6259a6c) and Python 3.7.3, if I run the following script:

```python
import io
import networkx

g = networkx.Graph()
g.add_node(1, weight=float('inf'))
g.add_node(2, weight=float('nan'))
with io.BytesIO() as f:
	networkx.write_gexf(g, f)
	print(f.getvalue().decode())
```
I get the following output:
```xml
<?xml version='1.0' encoding='utf-8'?>
<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="node" mode="static">
      <attribute id="0" title="weight" type="double" />
    </attributes>
    <meta>
      <creator>NetworkX 2.4rc1.dev_20190906193120</creator>
      <lastmodified>06/09/2019</lastmodified>
    </meta>
    <nodes>
      <node id="1" label="1">
        <attvalues>
          <attvalue for="0" value="inf" />
        </attvalues>
      </node>
      <node id="2" label="2">
        <attvalues>
          <attvalue for="0" value="nan" />
        </attvalues>
      </node>
    </nodes>
    <edges />
  </graph>
</gexf>
```
This contains three errors in conformance with the GEXF specification. Below is a diff showing what the output should have been.
```diff
--- is.gexf	2019-09-06 14:34:24.000000000 -0500
+++ should_be.gexf	2019-09-06 14:50:28.000000000 -0500
@@ -1,22 +1,21 @@
 <?xml version='1.0' encoding='utf-8'?>
-<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">
+<gexf version="1.2" xmlns="http://www.gexf.net/1.2draft" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd">
+  <meta lastmodifieddate="2019-09-06">
+    <creator>NetworkX 2.4rc1.dev_20190906193120</creator>
+  </meta>
   <graph defaultedgetype="undirected" mode="static" name="">
     <attributes class="node" mode="static">
       <attribute id="0" title="weight" type="double" />
     </attributes>
-    <meta>
-      <creator>NetworkX 2.4rc1.dev_20190906193120</creator>
-      <lastmodified>06/09/2019</lastmodified>
-    </meta>
     <nodes>
       <node id="1" label="1">
         <attvalues>
-          <attvalue for="0" value="inf" />
+          <attvalue for="0" value="INF" />
         </attvalues>
       </node>
       <node id="2" label="2">
         <attvalues>
-          <attvalue for="0" value="nan" />
+          <attvalue for="0" value="NaN" />
         </attvalues>
       </node>
     </nodes>
```
For the error in the schema location (see section 2.2 of the [GEXF primer](https://gephi.org/gexf/1.2draft/gexf-12draft-primer.pdf)), the line to blame is
https://github.com/networkx/networkx/blob/0d8d93a0e43829d6020e8babf59d411af6259a6c/networkx/readwrite/gexf.py#L246

I'm not sure where floats are converted to text, but I'm guessing that the current code relies on Python to do it. However, the [XML Schema spec](https://www.w3.org/TR/xmlschema-2/#double-lexical-representation), which the GEXF spec relies on for data type definitions, says that not-a-numbers should be spelled as `NaN`,  infinity as `INF`, and negative infinity as `-INF`.

For what it's worth, fixing the schema location issue made Gephi 0.9.2 start displaying my graphs correctly.

The GEXF primer says the `meta` tag must be declared before the `graph` tag, and that `lastmodifieddate` is an attribute, not a tag, and should contain the date in ISO (yyyy-mm-dd) format.


Discussion:
+1 this issue/PR. Adding lastmodified as a tag does not allow me to import the GEXF file into Cytoscape (with gexf-app). Changing it to lastmodifieddate as attribute fixed it for me.

For the time being, I just patched the function as below in my local file (~/.local/lib/python3.6/site-packages/networkx/readwrite/gexf.py) [Ubuntu 18.04 / Python 3.6.8 x64 / installed via pip3]

```
def add_meta(self, G, graph_element):
        # add meta element with creator and date
        meta_element = Element('meta', lastmodifieddate = time.strftime('%Y-%m-%d'))
        SubElement(meta_element, 'creator').text = 'NetworkX {}'.format(nx.__version__)
        graph_element.append(meta_element)`
```

Would appreciate a merge so others can profit from the fix. But yeah, for the time being I just patched it this way :man_shrugging: 

Resolving patch:
diff --git a/networkx/readwrite/gexf.py b/networkx/readwrite/gexf.py
--- a/networkx/readwrite/gexf.py
+++ b/networkx/readwrite/gexf.py
@@ -25,10 +25,12 @@
 import networkx as nx
 from networkx.utils import open_file, make_str
 try:
-    from xml.etree.cElementTree import Element, ElementTree, SubElement, tostring
+    from xml.etree.cElementTree import (Element, ElementTree, SubElement,
+                                        tostring)
 except ImportError:
     try:
-        from xml.etree.ElementTree import Element, ElementTree, SubElement, tostring
+        from xml.etree.ElementTree import (Element, ElementTree, SubElement,
+                                           tostring)
     except ImportError:
         pass
 
@@ -36,7 +38,8 @@
 
 
 @open_file(1, mode='wb')
-def write_gexf(G, path, encoding='utf-8', prettyprint=True, version='1.2draft'):
+def write_gexf(G, path, encoding='utf-8', prettyprint=True,
+               version='1.2draft'):
     """Write G in GEXF format to path.
 
     "GEXF (Graph Exchange XML Format) is a language for describing
@@ -98,14 +101,14 @@ def generate_gexf(G, encoding='utf-8', prettyprint=True, version='1.2draft'):
     Parameters
     ----------
     G : graph
-       A NetworkX graph
+    A NetworkX graph
     encoding : string (optional, default: 'utf-8')
-       Encoding for text data.
+    Encoding for text data.
     prettyprint : bool (optional, default: True)
-       If True use line breaks and indenting in output XML.
+    If True use line breaks and indenting in output XML.
     version : string (default: 1.2draft)
-       Version of GEFX File Format (see https://gephi.org/gexf/format/schema.html).
-       Supported values: "1.1draft", "1.2draft"
+    Version of GEFX File Format (see https://gephi.org/gexf/format/schema.html)
+    Supported values: "1.1draft", "1.2draft"
 
 
     Examples
@@ -154,7 +157,7 @@ def read_gexf(path, node_type=None, relabel=False, version='1.2draft'):
        If True relabel the nodes to use the GEXF node "label" attribute
        instead of the node "id" attribute as the NetworkX node label.
     version : string (default: 1.2draft)
-       Version of GEFX File Format (see https://gephi.org/gexf/format/schema.html).
+    Version of GEFX File Format (see https://gephi.org/gexf/format/schema.html)
        Supported values: "1.1draft", "1.2draft"
 
     Returns
@@ -223,7 +226,7 @@ class GEXF(object):
                  (np.uint16, "int"), (np.uint32, "int"),
                  (np.uint64, "int"), (np.int_, "int"),
                  (np.intc, "int"), (np.intp, "int"),
-                ] + types
+                 ] + types
 
     xml_type = dict(types)
     python_type = dict(reversed(a) for a in types)
@@ -243,7 +246,7 @@ def set_version(self, version):
         self.NS_GEXF = d['NS_GEXF']
         self.NS_VIZ = d['NS_VIZ']
         self.NS_XSI = d['NS_XSI']
-        self.SCHEMALOCATION = d['NS_XSI']
+        self.SCHEMALOCATION = d['SCHEMALOCATION']
         self.VERSION = d['VERSION']
         self.version = version
 
@@ -267,6 +270,14 @@ def __init__(self, graph=None, encoding='utf-8', prettyprint=True,
                             'xsi:schemaLocation': self.SCHEMALOCATION,
                             'version': self.VERSION})
 
+        # Make meta element a non-graph element
+        # Also add lastmodifieddate as attribute, not tag
+        meta_element = Element('meta')
+        subelement_text = 'NetworkX {}'.format(nx.__version__)
+        SubElement(meta_element, 'creator').text = subelement_text
+        meta_element.set('lastmodifieddate', time.strftime('%Y-%m-%d'))
+        self.xml.append(meta_element)
+
         ET.register_namespace('viz', self.NS_VIZ)
 
         # counters for edge and attribute identifiers
@@ -311,18 +322,10 @@ def add_graph(self, G):
         graph_element = Element('graph', defaultedgetype=default, mode=mode,
                                 name=name)
         self.graph_element = graph_element
-        self.add_meta(G, graph_element)
         self.add_nodes(G, graph_element)
         self.add_edges(G, graph_element)
         self.xml.append(graph_element)
 
-    def add_meta(self, G, graph_element):
-        # add meta element with creator and date
-        meta_element = Element('meta')
-        SubElement(meta_element, 'creator').text = 'NetworkX {}'.format(nx.__version__)
-        SubElement(meta_element, 'lastmodified').text = time.strftime('%d/%m/%Y')
-        graph_element.append(meta_element)
-
     def add_nodes(self, G, graph_element):
         nodes_element = Element('nodes')
         for node, data in G.nodes(data=True):
@@ -439,7 +442,8 @@ def add_attributes(self, node_or_edge, xml_obj, data, default):
                 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)
+                raise TypeError('attribute value type is not allowed: %s'
+                                % val_type)
             if isinstance(v, list):
                 # dynamic data
                 for val, start, end in v:
@@ -449,12 +453,20 @@ def add_attributes(self, node_or_edge, xml_obj, data, default):
                         self.alter_graph_mode_timeformat(start)
                         self.alter_graph_mode_timeformat(end)
                         break
-                attr_id = self.get_attr_id(make_str(k), self.xml_type[val_type],
+                attr_id = self.get_attr_id(make_str(k),
+                                           self.xml_type[val_type],
                                            node_or_edge, default, mode)
                 for val, start, end in v:
                     e = Element('attvalue')
                     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 start is not None:
                         e.attrib['start'] = make_str(start)
                     if end is not None:
@@ -463,7 +475,8 @@ def add_attributes(self, node_or_edge, xml_obj, data, default):
             else:
                 # static data
                 mode = 'static'
-                attr_id = self.get_attr_id(make_str(k), self.xml_type[val_type],
+                attr_id = self.get_attr_id(make_str(k),
+                                           self.xml_type[val_type],
                                            node_or_edge, default, mode)
                 e = Element('attvalue')
                 e.attrib['for'] = attr_id
@@ -471,6 +484,13 @@ 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'
                 attvalues.append(e)
         xml_obj.append(attvalues)
         return data
@@ -532,7 +552,8 @@ def add_viz(self, element, node_data):
 
             thickness = viz.get('thickness')
             if thickness is not None:
-                e = Element('{%s}thickness' % self.NS_VIZ, value=str(thickness))
+                e = Element('{%s}thickness' % self.NS_VIZ,
+                            value=str(thickness))
                 element.append(e)
 
             shape = viz.get('shape')
@@ -591,7 +612,8 @@ def add_spells(self, node_or_edge_element, node_or_edge_data):
         return node_or_edge_data
 
     def alter_graph_mode_timeformat(self, start_or_end):
-        # if 'start' or 'end' appears, alter Graph mode to dynamic and set timeformat
+        # If 'start' or 'end' appears, alter Graph mode to dynamic and
+        # set timeformat
         if self.graph_element.get('mode') == 'static':
             if start_or_end is not None:
                 if isinstance(start_or_end, str):
@@ -687,7 +709,8 @@ def make_graph(self, graph_xml):
             self.timeformat = 'string'
 
         # node and edge attributes
-        attributes_elements = graph_xml.findall('{%s}attributes' % self.NS_GEXF)
+        attributes_elements = graph_xml.findall('{%s}attributes' %
+                                                self.NS_GEXF)
         # dictionaries to hold attributes and attribute defaults
         node_attr = {}
         node_default = {}
@@ -710,7 +733,8 @@ def make_graph(self, graph_xml):
 
         # Hack to handle Gephi0.7beta bug
         # add weight attribute
-        ea = {'weight': {'type': 'double', 'mode': 'static', 'title': 'weight'}}
+        ea = {'weight': {'type': 'double', 'mode': 'static',
+                                 'title': 'weight'}}
         ed = {}
         edge_attr.update(ea)
         edge_default.update(ed)
@@ -769,6 +793,14 @@ 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):
@@ -917,7 +949,8 @@ def decode_attr_elements(self, gexf_keys, obj_xml):
                 try:  # should be in our gexf_keys dictionary
                     title = gexf_keys[key]['title']
                 except KeyError:
-                    raise nx.NetworkXError('No attribute defined for=%s.' % key)
+                    raise nx.NetworkXError('No attribute defined for=%s.'
+                                           % key)
                 atype = gexf_keys[key]['type']
                 value = a.get('value')
                 if atype == 'boolean':
@@ -1015,7 +1048,7 @@ def setup_module(module):
     from nose import SkipTest
     try:
         import xml.etree.cElementTree
-    except:
+    except Exception as e:
         raise SkipTest('xml.etree.cElementTree not available.')
 
 
@@ -1024,5 +1057,5 @@ def teardown_module(module):
     import os
     try:
         os.unlink('test.gexf')
-    except:
+    except Exception as e:
         pass


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
@@ -38,8 +38,10 @@ def setUp(self):
         self.simple_directed_fh = \
             io.BytesIO(self.simple_directed_data.encode('UTF-8'))
 
-        self.attribute_data = """<?xml version="1.0" encoding="UTF-8"?>
-<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
+        self.attribute_data = """<?xml version="1.0" encoding="UTF-8"?>\
+<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi="http://www.w3.\
+org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gexf.net/\
+1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
   <meta lastmodifieddate="2009-03-20">
     <creator>Gephi.org</creator>
     <description>A Web network</description>
@@ -135,7 +137,8 @@ def setUp(self):
         self.simple_undirected_graph.add_node('1', label='World')
         self.simple_undirected_graph.add_edge('0', '1', id='0')
 
-        self.simple_undirected_fh = io.BytesIO(self.simple_undirected_data.encode('UTF-8'))
+        self.simple_undirected_fh = io.BytesIO(self.simple_undirected_data
+                                                   .encode('UTF-8'))
 
     def test_read_simple_directed_graphml(self):
         G = self.simple_directed_graph
@@ -304,13 +307,15 @@ def test_write_with_node_attributes(self):
             G.nodes[i]['start'] = i
             G.nodes[i]['end'] = i + 1
 
-        if sys.version_info < (3,8):
-            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">
+        if sys.version_info < (3, 8):
+            expected = """<gexf version="1.2" xmlns="http://www.gexf.net/1.2\
+draft" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:\
+schemaLocation="http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/\
+gexf.xsd">
+  <meta lastmodifieddate="{}">
+    <creator>NetworkX {}</creator>
+  </meta>
   <graph defaultedgetype="undirected" mode="dynamic" name="" timeformat="long">
-    <meta>
-      <creator>NetworkX {}</creator>
-      <lastmodified>{}</lastmodified>
-    </meta>
     <nodes>
       <node end="1" id="0" label="0" pid="0" start="0" />
       <node end="2" id="1" label="1" pid="1" start="1" />
@@ -323,14 +328,16 @@ def test_write_with_node_attributes(self):
       <edge id="2" source="2" target="3" />
     </edges>
   </graph>
-</gexf>""".format(nx.__version__, time.strftime('%d/%m/%Y'))
+</gexf>""".format(time.strftime('%Y-%m-%d'), nx.__version__)
         else:
-            expected = """<gexf 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" version="1.2">
+            expected = """<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi\
+="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=\
+"http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/\
+gexf.xsd" version="1.2">
+  <meta lastmodifieddate="{}">
+    <creator>NetworkX {}</creator>
+  </meta>
   <graph defaultedgetype="undirected" mode="dynamic" name="" timeformat="long">
-    <meta>
-      <creator>NetworkX {}</creator>
-      <lastmodified>{}</lastmodified>
-    </meta>
     <nodes>
       <node id="0" label="0" pid="0" start="0" end="1" />
       <node id="1" label="1" pid="1" start="1" end="2" />
@@ -343,21 +350,23 @@ def test_write_with_node_attributes(self):
       <edge source="2" target="3" id="2" />
     </edges>
   </graph>
-</gexf>""".format(nx.__version__, time.strftime('%d/%m/%Y'))
-
+</gexf>""".format(time.strftime('%Y-%m-%d'), nx.__version__)
         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)])
-        if sys.version_info < (3,8):
-            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">
+
+        if sys.version_info < (3, 8):
+            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.gexf.net/1.2draft http://www.gexf.net/1.2draft/\
+gexf.xsd">
+  <meta lastmodifieddate="{}">
+    <creator>NetworkX {}</creator>
+  </meta>
   <graph defaultedgetype="undirected" mode="static" name="">
-    <meta>
-      <creator>NetworkX {}</creator>
-      <lastmodified>{}</lastmodified>
-    </meta>
     <nodes>
       <node id="0" label="0" />
       <node id="1" label="1" />
@@ -370,14 +379,15 @@ def test_edge_id_construct(self):
       <edge id="1" source="2" target="3" />
     </edges>
   </graph>
-</gexf>""".format(nx.__version__, time.strftime('%d/%m/%Y'))
+</gexf>""".format(time.strftime('%Y-%m-%d'), nx.__version__)
         else:
-            expected = """<gexf 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" version="1.2">
+            expected = """<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi\
+="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.\
+gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
+  <meta lastmodifieddate="{}">
+    <creator>NetworkX {}</creator>
+  </meta>
   <graph defaultedgetype="undirected" mode="static" name="">
-    <meta>
-      <creator>NetworkX {}</creator>
-      <lastmodified>{}</lastmodified>
-    </meta>
     <nodes>
       <node id="0" label="0" />
       <node id="1" label="1" />
@@ -390,7 +400,7 @@ def test_edge_id_construct(self):
       <edge source="2" target="3" id="1" />
     </edges>
   </graph>
-</gexf>""".format(nx.__version__, time.strftime('%d/%m/%Y'))
+</gexf>""".format(time.strftime('%Y-%m-%d'), nx.__version__)
 
         obtained = '\n'.join(nx.generate_gexf(G))
         assert_equal(expected, obtained)
@@ -401,10 +411,15 @@ def test_numpy_type(self):
             import numpy
         except ImportError:
             return
-        nx.set_node_attributes(G, {n:n for n in numpy.arange(4)}, 'number')
+        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">
+        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.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd">
+  <meta lastmodifieddate="{}">
+    <creator>NetworkX {}</creator>
+  </meta>
   <graph defaultedgetype="undirected" mode="static" name="">
     <attributes class="edge" mode="static">
       <attribute id="1" title="edge-number" type="float" />
@@ -412,10 +427,6 @@ def test_numpy_type(self):
     <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>
@@ -448,7 +459,7 @@ def test_numpy_type(self):
       <edge id="2" source="2" target="3" />
     </edges>
   </graph>
-</gexf>""".format(nx.__version__, time.strftime('%d/%m/%Y'))
+</gexf>""".format(time.strftime('%Y-%m-%d'), nx.__version__)
         obtained = '\n'.join(nx.generate_gexf(G))
         assert_equal(expected, obtained)
 
@@ -460,3 +471,21 @@ def test_bool(self):
         fh.seek(0)
         H = nx.read_gexf(fh, node_type=int)
         assert_equal(H.nodes[1]['testattr'], True)
+
+    # Test for NaN, INF and -INF
+    def test_specials(self):
+        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)
+        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)