Repository: networkx/networkx

Issue:
Multi-line entries in GML files not supported
I am dealing with GML files that contain string entries spread across multiple lines. Networkx cannot parse such entries (Cytoscape for example can).

### Current Behavior

Parsing GML files with multi-line entries fails with  `NetworkXError: cannot tokenize...`.

### Expected Behavior

Networkx should be able to parse those entries

### Steps to Reproduce

Example GML file:

    graph
    [
	    node
	    [
		    id 0
		    label "multiline node"
		    label2 "multiline1
	    multiline2
	    multiline3"
		    alt_name "id 0"
	    ]
    ]

Example code:

    nx.read_gml("multiline.gml")
    # fails with NetworkXError: cannot tokenize...

<!--- Provide a minimal example that reproduces the bug -->

### Environment

<!--- Please provide details about your local environment -->

Python version: 3.9.7 and 3.11.4
NetworkX version: 2.6.3 and latest (git commit ff9c27b40)

### Additional context

Already working on a fix
Multi-line entries in GML files not supported
I am dealing with GML files that contain string entries spread across multiple lines. Networkx cannot parse such entries (Cytoscape for example can).

### Current Behavior

Parsing GML files with multi-line entries fails with  `NetworkXError: cannot tokenize...`.

### Expected Behavior

Networkx should be able to parse those entries

### Steps to Reproduce

Example GML file:

    graph
    [
	    node
	    [
		    id 0
		    label "multiline node"
		    label2 "multiline1
	    multiline2
	    multiline3"
		    alt_name "id 0"
	    ]
    ]

Example code:

    nx.read_gml("multiline.gml")
    # fails with NetworkXError: cannot tokenize...

<!--- Provide a minimal example that reproduces the bug -->

### Environment

<!--- Please provide details about your local environment -->

Python version: 3.9.7 and 3.11.4
NetworkX version: 2.6.3 and latest (git commit ff9c27b40)

### Additional context

Already working on a fix


Discussion:

Resolving patch:
diff --git a/networkx/readwrite/gml.py b/networkx/readwrite/gml.py
--- a/networkx/readwrite/gml.py
+++ b/networkx/readwrite/gml.py
@@ -311,9 +311,34 @@ def tokenize():
         ]
         tokens = re.compile("|".join(f"({pattern})" for pattern in patterns))
         lineno = 0
+        multilines = []  # entries spread across multiple lines
         for line in lines:
-            length = len(line)
             pos = 0
+
+            # deal with entries spread across multiple lines
+            #
+            # should we actually have to deal with escaped "s then do it here
+            if multilines:
+                multilines.append(line.strip())
+                if line[-1] == '"':  # closing multiline entry
+                    # multiline entries will be joined by space. cannot
+                    # reintroduce newlines as this will break the tokenizer
+                    line = " ".join(multilines)
+                    multilines = []
+                else:  # continued multiline entry
+                    lineno += 1
+                    continue
+            else:
+                if line.count('"') == 1:  # opening multiline entry
+                    if line.strip()[0] != '"' and line.strip()[-1] != '"':
+                        # since we expect something like key "value", the " should not be found at ends
+                        # otherwise tokenizer will pick up the formatting mistake.
+                        multilines = [line.rstrip()]
+                        lineno += 1
+                        continue
+
+            length = len(line)
+
             while pos < length:
                 match = tokens.match(line, pos)
                 if match is None:


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
@@ -614,6 +614,28 @@ def test_outofrange_integers(self):
             os.close(fd)
             os.unlink(fname)
 
+    def test_multiline(self):
+        # example from issue #6836
+        multiline_example = """
+graph
+[
+    node
+    [
+	    id 0
+	    label "multiline node"
+	    label2 "multiline1
+    multiline2
+    multiline3"
+	    alt_name "id 0"
+    ]
+]
+"""
+        G = nx.parse_gml(multiline_example)
+        assert G.nodes["multiline node"] == {
+            "label2": "multiline1 multiline2 multiline3",
+            "alt_name": "id 0",
+        }
+
 
 @contextmanager
 def byte_file():