Repository: networkx/networkx

Issue:
Update `write_gml` docstring.
See discussion in #1449.  I think it would be helpful if we added a few comments to the docstring to make some of the conversion issues more transparent for `write_gml`.



Discussion:
Was this resolved? As per as the discussion in #1486, this needs more work.

@SanketDG Do you have any concrete suggestions?

@chebee7i said that it was best to link to user documentation. I think using the [cross referencing feature](http://sphinx-doc.org/domains.html#cross-referencing-python-objects) from sphinx will be a good way to link to the documentation, in this case [`readwrite.gml.html`](http://networkx.github.io/documentation/latest/reference/readwrite.gml.html)

We write the issues involved with the stringizers along with examples in the module level docstring of `networkx.readwrite.gml`. Then using `:mod:`~networkx.readwrite.gml`` (cross referencing) in the docstring of `write_gml()` should create a link to `readwrite.gml.html`. This will be version independent.

I think the only downside is that I can't name the link so the link will just look like this: [gml](#). With a proper message, this shouldn't be a problem.

Can we defer this and release 1.10 now?

Yes, creating the examples for this will require a discussion, so it's best if we keep it out of `1.10` if its release is near.

Until I looked up online why my `write_gml` wasn't working, I was confused why I couldn't write nodes that had underscores in them.

Can we add to the documentation that graph, node, and edge attributes cannot have underscores when writing using `write_gml`?

It might be better to point to the specification http://www.fim.uni-passau.de/fileadmin/files/lehrstuhl/brandenburg/projekte/gml/gml-technical-report.pdf
since there are also other restrictions on what keys and values can be.

Good suggestion, @hagberg.

@chebee7i, are you still interested in working on this PR? Can you add this to the list of things to fix?


Resolving patch:
diff --git a/networkx/readwrite/gml.py b/networkx/readwrite/gml.py
--- a/networkx/readwrite/gml.py
+++ b/networkx/readwrite/gml.py
@@ -19,16 +19,25 @@
 standard file format in the Graphlet graph editor system. It has been
 overtaken and adapted by several other systems for drawing graphs."
 
-See http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html
+GML files are stored using a 7-bit ASCII encoding with any extended
+ASCII characters (iso8859-1) appearing as HTML character entities.
+You will need to give some thought into how the exported data should
+interact with different languages and even different Python versions.
+Re-importing from gml is also a concern.
 
-Format
-------
-See http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html
-for format specification.
+Without specifying a `stringizer`/`destringizer`, the code is capable of
+handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
+specification.  For other data types, you need to explicitly supply a
+`stringizer`/`destringizer`.
 
-Example graphs in GML format
-http://www-personal.umich.edu/~mejn/netdata/
+For better interoperability of data generated by Python 2 and Python 3,
+we've provided `literal_stringizer` and `literal_destringizer`.
 
+For additional documentation on the GML file format, please see the
+`GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
+
+Several example graphs in GML format may be found on Mark Newman's
+`Network data page <http://www-personal.umich.edu/~mejn/netdata/>`_.
 """
 try:
     try:
@@ -144,7 +153,7 @@ def literal_destringizer(rep):
 
 @open_file(0, mode='rb')
 def read_gml(path, label='label', destringizer=None):
-    """Read graph in GML format from path.
+    """Read graph in GML format from `path`.
 
     Parameters
     ----------
@@ -156,7 +165,7 @@ def read_gml(path, label='label', destringizer=None):
         attributes indicated by `label`. Default value: 'label'.
 
     destringizer : callable, optional
-        A destringizer that recovers values stored as strings in GML. If it
+        A `destringizer` that recovers values stored as strings in GML. If it
         cannot convert a string to a value, a `ValueError` is raised. Default
         value : None.
 
@@ -172,17 +181,21 @@ def read_gml(path, label='label', destringizer=None):
 
     See Also
     --------
-    write_gml, parse_gml
+    write_gml, parse_gml, literal_destringizer
 
     Notes
     -----
-    The GML specification says that files should be ASCII encoded, with any
-    extended ASCII characters (iso8859-1) appearing as HTML character entities.
+    GML files are stored using a 7-bit ASCII encoding with any extended
+    ASCII characters (iso8859-1) appearing as HTML character entities.
+    Without specifying a `stringizer`/`destringizer`, the code is capable of
+    handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
+    specification.  For other data types, you need to explicitly supply a
+    `stringizer`/`destringizer`.
 
-    References
-    ----------
-    GML specification:
-    http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html
+    For additional documentation on the GML file format, please see the
+    `GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
+
+    See the module docstring :mod:`networkx.readwrite.gml` for additional details.
 
     Examples
     --------
@@ -219,7 +232,7 @@ def parse_gml(lines, label='label', destringizer=None):
         attributes indicated by `label`. Default value: 'label'.
 
     destringizer : callable, optional
-        A destringizer that recovers values stored as strings in GML. If it
+        A `destringizer` that recovers values stored as strings in GML. If it
         cannot convert a string to a value, a `ValueError` is raised. Default
         value : None.
 
@@ -235,17 +248,24 @@ def parse_gml(lines, label='label', destringizer=None):
 
     See Also
     --------
-    write_gml, read_gml
+    write_gml, read_gml, literal_destringizer
 
     Notes
     -----
-    This stores nested GML attributes as dictionaries in the
-    NetworkX graph, node, and edge attribute structures.
+    This stores nested GML attributes as dictionaries in the NetworkX graph,
+    node, and edge attribute structures.
 
-    References
-    ----------
-    GML specification:
-    http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html
+    GML files are stored using a 7-bit ASCII encoding with any extended
+    ASCII characters (iso8859-1) appearing as HTML character entities.
+    Without specifying a `stringizer`/`destringizer`, the code is capable of
+    handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
+    specification.  For other data types, you need to explicitly supply a
+    `stringizer`/`destringizer`.
+
+    For additional documentation on the GML file format, please see the
+    `GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
+
+    See the module docstring :mod:`networkx.readwrite.gml` for additional details.
     """
     def decode_line(line):
         if isinstance(line, bytes):
@@ -277,7 +297,7 @@ def filter_lines(lines):
 
 
 def parse_gml_lines(lines, label, destringizer):
-    """Parse GML into a graph.
+    """Parse GML `lines` into a graph.
     """
     def tokenize():
         patterns = [
@@ -441,12 +461,12 @@ def pop_attr(dct, category, attr, i):
 
 
 def literal_stringizer(value):
-    """Convert a value to a Python literal in GML representation.
+    """Convert a `value` to a Python literal in GML representation.
 
     Parameters
     ----------
     value : object
-        The value to be converted to GML representation.
+        The `value` to be converted to GML representation.
 
     Returns
     -------
@@ -541,7 +561,7 @@ def stringize(value):
 
 
 def generate_gml(G, stringizer=None):
-    """Generate a single entry of the graph G in GML format.
+    r"""Generate a single entry of the graph `G` in GML format.
 
     Parameters
     ----------
@@ -549,9 +569,9 @@ def generate_gml(G, stringizer=None):
         The graph to be converted to GML.
 
     stringizer : callable, optional
-        A stringizer which converts non-int/float/dict values into strings. If
-        it cannot convert a value into a string, it should raise a
-        `ValueError` raised to indicate that. Default value: None.
+        A `stringizer` which converts non-int/non-float/non-dict values into
+        strings. If it cannot convert a value into a string, it should raise a
+        `ValueError` to indicate that. Default value: None.
 
     Returns
     -------
@@ -564,6 +584,10 @@ def generate_gml(G, stringizer=None):
         If `stringizer` cannot convert a value into a string, or the value to
         convert is not a string while `stringizer` is None.
 
+    See Also
+    --------
+    literal_stringizer
+
     Notes
     -----
     Graph attributes named 'directed', 'multigraph', 'node' or
@@ -571,6 +595,53 @@ def generate_gml(G, stringizer=None):
     named 'source' or 'target' (or 'key' if `G` is a multigraph)
     are ignored because these attribute names are used to encode the graph
     structure.
+
+    GML files are stored using a 7-bit ASCII encoding with any extended
+    ASCII characters (iso8859-1) appearing as HTML character entities.
+    Without specifying a `stringizer`/`destringizer`, the code is capable of
+    handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
+    specification.  For other data types, you need to explicitly supply a
+    `stringizer`/`destringizer`.
+
+    For additional documentation on the GML file format, please see the
+    `GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
+
+    See the module docstring :mod:`networkx.readwrite.gml` for additional details.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_node("1")
+    >>> print("\n".join(nx.generate_gml(G)))
+    graph [
+      node [
+        id 0
+        label "1"
+      ]
+    ]
+    >>> G = nx.OrderedMultiGraph([("a", "b"), ("a", "b")])
+    >>> print("\n".join(nx.generate_gml(G)))
+    graph [
+      multigraph 1
+      node [
+        id 0
+        label "a"
+      ]
+      node [
+        id 1
+        label "b"
+      ]
+      edge [
+        source 0
+        target 1
+        key 0
+      ]
+      edge [
+        source 0
+        target 1
+        key 1
+      ]
+    ]
     """
     valid_keys = re.compile('^[A-Za-z][0-9A-Za-z]*$')
 
@@ -682,7 +753,7 @@ def write_gml(G, path, stringizer=None):
         .bz2 will be compressed.
 
     stringizer : callable, optional
-        A stringizer which converts non-int/non-float/non-dict values into
+        A `stringizer` which converts non-int/non-float/non-dict values into
         strings. If it cannot convert a value into a string, it should raise a
         `ValueError` to indicate that. Default value: None.
 
@@ -694,16 +765,28 @@ def write_gml(G, path, stringizer=None):
 
     See Also
     --------
-    read_gml, generate_gml
+    read_gml, generate_gml, literal_stringizer
 
     Notes
     -----
     Graph attributes named 'directed', 'multigraph', 'node' or
-    'edge',node attributes named 'id' or 'label', edge attributes
+    'edge', node attributes named 'id' or 'label', edge attributes
     named 'source' or 'target' (or 'key' if `G` is a multigraph)
     are ignored because these attribute names are used to encode the graph
     structure.
 
+    GML files are stored using a 7-bit ASCII encoding with any extended
+    ASCII characters (iso8859-1) appearing as HTML character entities.
+    Without specifying a `stringizer`/`destringizer`, the code is capable of
+    handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
+    specification.  For other data types, you need to explicitly supply a
+    `stringizer`/`destringizer`.
+
+    For additional documentation on the GML file format, please see the
+    `GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
+
+    See the module docstring :mod:`networkx.readwrite.gml` for additional details.
+
     Examples
     --------
     >>> G = nx.path_graph(4)


Test patch:
diff --git a/networkx/readwrite/tests/test_gml.py b/networkx/readwrite/tests/test_gml.py
--- a/networkx/readwrite/tests/test_gml.py
+++ b/networkx/readwrite/tests/test_gml.py
@@ -65,6 +65,7 @@ def setUp(self):
   ]
 ]
 """
+
     def test_parse_gml_cytoscape_bug(self):
         # example from issue #321, originally #324 in trac
         cytoscape_example = """
@@ -197,18 +198,18 @@ def test_relabel_duplicate(self):
         data = """
 graph
 [
-	label	""
-	directed	1
-	node
-	[
-		id	0
-		label	"same"
-	]
-	node
-	[
-		id	1
-		label	"same"
-	]
+        label   ""
+        directed        1
+        node
+        [
+                id      0
+                label   "same"
+        ]
+        node
+        [
+                id      1
+                label   "same"
+        ]
 ]
 """
         fh = io.BytesIO(data.encode('UTF-8'))