Repository: networkx/networkx

Issue:
Cannot parse GML files from Cytoscape, possible regression?
This does not appear to be a duplicate of #321 -- I cannot parse .GML files produced from Cytoscape, including the "simple.gml" [file](https://networkx.lanl.gov/trac/attachment/ticket/324/simple.gml) as reported in #321. Below is the output I receive if parsing the file provided in that issue. 

From reading the comments, it looks like the suggestion is to either modify the GML parser directly in networkx (allowing slightly out of format GML files), or to modify the output from Cytoscape. If I issue a PR for the GML parser modification, would there be support for it?

```python
10:20:28 (mcdonadt@8086):~> nx.__version__
Out[5]: '1.10'

10:20:32 (mcdonadt@8086):~> g = nx.read_gml('Downloads/simple.gml')
---------------------------------------------------------------------------
NetworkXError                             Traceback (most recent call last)
<ipython-input-6-e05f5232526f> in <module>()
----> 1 g = nx.read_gml('Downloads/simple.gml')

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in read_gml(path, label, destringizer)

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/utils/decorators.py in _open_file(func, *args, **kwargs)
    218         # Finally, we call the original function, making sure to close the fobj.
    219         try:
--> 220             result = func(*new_args, **kwargs)
    221         finally:
    222             if close_fobj:

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in read_gml(path, label, destringizer)
    208             yield line
    209
--> 210     G = parse_gml_lines(filter_lines(path), label, destringizer)
    211     return G
    212

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in parse_gml_lines(lines, label, destringizer)
    381
    382     tokens = tokenize()
--> 383     graph = parse_graph()
    384
    385     directed = graph.pop('directed', False)

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in parse_graph()
    370
    371     def parse_graph():
--> 372         curr_token, dct = parse_kv(next(tokens))
    373         if curr_token[0] is not None:  # EOF
    374             unexpected(curr_token, 'EOF')

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in parse_kv(curr_token)
    355                 curr_token = next(tokens)
    356             elif type == 4:  # dict start
--> 357                 curr_token, value = parse_dict(curr_token)
    358             else:
    359                 unexpected(curr_token, "an int, float, string or '['")

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in parse_dict(curr_token)
    365     def parse_dict(curr_token):
    366         curr_token = consume(curr_token, 4, "'['")    # dict start
--> 367         curr_token, dct = parse_kv(curr_token)
    368         curr_token = consume(curr_token, 5, "']'")  # dict end
    369         return curr_token, dct

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in parse_kv(curr_token)
    355                 curr_token = next(tokens)
    356             elif type == 4:  # dict start
--> 357                 curr_token, value = parse_dict(curr_token)
    358             else:
    359                 unexpected(curr_token, "an int, float, string or '['")

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in parse_dict(curr_token)
    364
    365     def parse_dict(curr_token):
--> 366         curr_token = consume(curr_token, 4, "'['")    # dict start
    367         curr_token, dct = parse_kv(curr_token)
    368         curr_token = consume(curr_token, 5, "']'")  # dict end

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in consume(curr_token, type, expected)
    334     def consume(curr_token, type, expected):
    335         if curr_token[0] == type:
--> 336             return next(tokens)
    337         unexpected(curr_token, expected)
    338

/Users/mcdonadt/miniconda3/envs/unifrac-network/lib/python3.4/site-packages/networkx/readwrite/gml.py in tokenize()
    321                 else:
    322                     raise NetworkXError('cannot tokenize %r at (%d, %d)' %
--> 323                                         (line[pos:], lineno + 1, pos + 1))
    324             lineno += 1
    325         yield (None, None, lineno + 1, 1)  # EOF

NetworkXError: cannot tokenize 'root_index\t-3' at (5, 3)
```

Discussion:

Patch:
diff --git a/networkx/readwrite/gml.py b/networkx/readwrite/gml.py
index d9e49d336..d3c8b8527 100644
--- a/networkx/readwrite/gml.py
+++ b/networkx/readwrite/gml.py
@@ -286,7 +286,7 @@ def parse_gml_lines(lines, label, destringizer):
     """
     def tokenize():
         patterns = [
-            r'[A-Za-z][0-9A-Za-z]*\s+',  # keys
+            r'[A-Za-z][0-9A-Za-z_]*\s+',  # keys
             r'[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)(?:[Ee][+-]?[0-9]+)?',  # reals
             r'[+-]?[0-9]+',   # ints
             r'".*?"',         # strings


Test patch:
diff --git a/networkx/readwrite/tests/test_gml.py b/networkx/readwrite/tests/test_gml.py
index 85b069127..042061032 100644
--- a/networkx/readwrite/tests/test_gml.py
+++ b/networkx/readwrite/tests/test_gml.py
@@ -65,6 +65,90 @@ graph [
   ]
 ]
 """
+    def test_parse_gml_cytoscape_bug(self):
+        # example from issue #321, originally #324 in trac
+        cytoscape_example = """
+Creator "Cytoscape"
+Version 1.0
+graph   [
+    node    [
+        root_index  -3
+        id  -3
+        graphics    [
+            x   -96.0
+            y   -67.0
+            w   40.0
+            h   40.0
+            fill    "#ff9999"
+            type    "ellipse"
+            outline "#666666"
+            outline_width   1.5
+        ]
+        label   "node2"
+    ]
+    node    [
+        root_index  -2
+        id  -2
+        graphics    [
+            x   63.0
+            y   37.0
+            w   40.0
+            h   40.0
+            fill    "#ff9999"
+            type    "ellipse"
+            outline "#666666"
+            outline_width   1.5
+        ]
+        label   "node1"
+    ]
+    node    [
+        root_index  -1
+        id  -1
+        graphics    [
+            x   -31.0
+            y   -17.0
+            w   40.0
+            h   40.0
+            fill    "#ff9999"
+            type    "ellipse"
+            outline "#666666"
+            outline_width   1.5
+        ]
+        label   "node0"
+    ]
+    edge    [
+        root_index  -2
+        target  -2
+        source  -1
+        graphics    [
+            width   1.5
+            fill    "#0000ff"
+            type    "line"
+            Line    [
+            ]
+            source_arrow    0
+            target_arrow    3
+        ]
+        label   "DirectedEdge"
+    ]
+    edge    [
+        root_index  -1
+        target  -1
+        source  -3
+        graphics    [
+            width   1.5
+            fill    "#0000ff"
+            type    "line"
+            Line    [
+            ]
+            source_arrow    0
+            target_arrow    3
+        ]
+        label   "DirectedEdge"
+    ]
+]
+"""
+        nx.parse_gml(cytoscape_example)
 
     def test_parse_gml(self):
         G = nx.parse_gml(self.simple_data, label='label')