xiaoanyu123 commited on
Commit
a1a0714
·
verified ·
1 Parent(s): b4616fa

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. pythonProject/.venv/Lib/site-packages/networkx/algorithms/__pycache__/asteroidal.cpython-310.pyc +0 -0
  2. pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/__init__.cpython-310.pyc +0 -0
  3. pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/coding.cpython-310.pyc +0 -0
  4. pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/decomposition.cpython-310.pyc +0 -0
  5. pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/mst.cpython-310.pyc +0 -0
  6. pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/operations.cpython-310.pyc +0 -0
  7. pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/recognition.cpython-310.pyc +0 -0
  8. pythonProject/.venv/Lib/site-packages/networkx/classes/tests/__pycache__/test_graph.cpython-310.pyc +0 -0
  9. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/__init__.cpython-310.pyc +0 -0
  10. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/algebraicconnectivity.cpython-310.pyc +0 -0
  11. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/attrmatrix.cpython-310.pyc +0 -0
  12. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/bethehessianmatrix.cpython-310.pyc +0 -0
  13. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/graphmatrix.cpython-310.pyc +0 -0
  14. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/laplacianmatrix.cpython-310.pyc +0 -0
  15. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/modularitymatrix.cpython-310.pyc +0 -0
  16. pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/spectrum.cpython-310.pyc +0 -0
  17. pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/__init__.cpython-310.pyc +0 -0
  18. pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_attrmatrix.cpython-310.pyc +0 -0
  19. pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_bethehessian.cpython-310.pyc +0 -0
  20. pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_graphmatrix.cpython-310.pyc +0 -0
  21. pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_laplacian.cpython-310.pyc +0 -0
  22. pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_modularity.cpython-310.pyc +0 -0
  23. pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_spectrum.cpython-310.pyc +0 -0
  24. pythonProject/.venv/Lib/site-packages/networkx/readwrite/__init__.py +18 -0
  25. pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/graph6.cpython-310.pyc +0 -0
  26. pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/graphml.cpython-310.pyc +0 -0
  27. pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/leda.cpython-310.pyc +0 -0
  28. pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-310.pyc +0 -0
  29. pythonProject/.venv/Lib/site-packages/networkx/readwrite/adjlist.py +310 -0
  30. pythonProject/.venv/Lib/site-packages/networkx/readwrite/edgelist.py +489 -0
  31. pythonProject/.venv/Lib/site-packages/networkx/readwrite/gexf.py +1065 -0
  32. pythonProject/.venv/Lib/site-packages/networkx/readwrite/gml.py +878 -0
  33. pythonProject/.venv/Lib/site-packages/networkx/readwrite/graph6.py +416 -0
  34. pythonProject/.venv/Lib/site-packages/networkx/readwrite/graphml.py +1052 -0
  35. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/__init__.py +18 -0
  36. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/adjacency.py +156 -0
  37. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/cytoscape.py +178 -0
  38. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/node_link.py +244 -0
  39. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  40. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_adjacency.py +78 -0
  41. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_cytoscape.py +78 -0
  42. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_node_link.py +144 -0
  43. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_tree.py +48 -0
  44. pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tree.py +137 -0
  45. pythonProject/.venv/Lib/site-packages/networkx/readwrite/leda.py +108 -0
  46. pythonProject/.venv/Lib/site-packages/networkx/readwrite/multiline_adjlist.py +393 -0
  47. pythonProject/.venv/Lib/site-packages/networkx/readwrite/p2g.py +104 -0
  48. pythonProject/.venv/Lib/site-packages/networkx/readwrite/pajek.py +286 -0
  49. pythonProject/.venv/Lib/site-packages/networkx/readwrite/sparse6.py +376 -0
  50. pythonProject/.venv/Lib/site-packages/networkx/readwrite/text.py +950 -0
pythonProject/.venv/Lib/site-packages/networkx/algorithms/__pycache__/asteroidal.cpython-310.pyc ADDED
Binary file (5.34 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (311 Bytes). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/coding.cpython-310.pyc ADDED
Binary file (13.3 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/decomposition.cpython-310.pyc ADDED
Binary file (3.22 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/mst.cpython-310.pyc ADDED
Binary file (36.8 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/operations.cpython-310.pyc ADDED
Binary file (4.7 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/tree/__pycache__/recognition.cpython-310.pyc ADDED
Binary file (8.28 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/classes/tests/__pycache__/test_graph.cpython-310.pyc ADDED
Binary file (31.7 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (705 Bytes). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/algebraicconnectivity.cpython-310.pyc ADDED
Binary file (20 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/attrmatrix.cpython-310.pyc ADDED
Binary file (14.7 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/bethehessianmatrix.cpython-310.pyc ADDED
Binary file (2.97 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/graphmatrix.cpython-310.pyc ADDED
Binary file (5.47 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/laplacianmatrix.cpython-310.pyc ADDED
Binary file (18.9 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/modularitymatrix.cpython-310.pyc ADDED
Binary file (4.72 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/__pycache__/spectrum.cpython-310.pyc ADDED
Binary file (4.42 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (178 Bytes). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_attrmatrix.cpython-310.pyc ADDED
Binary file (3.1 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_bethehessian.cpython-310.pyc ADDED
Binary file (1.39 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_graphmatrix.cpython-310.pyc ADDED
Binary file (5.61 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_laplacian.cpython-310.pyc ADDED
Binary file (8.16 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_modularity.cpython-310.pyc ADDED
Binary file (2.7 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/linalg/tests/__pycache__/test_spectrum.cpython-310.pyc ADDED
Binary file (3.22 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/readwrite/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A package for reading and writing graphs in various formats.
3
+
4
+ """
5
+
6
+
7
+ from networkx.readwrite.adjlist import *
8
+ from networkx.readwrite.multiline_adjlist import *
9
+ from networkx.readwrite.edgelist import *
10
+ from networkx.readwrite.pajek import *
11
+ from networkx.readwrite.leda import *
12
+ from networkx.readwrite.sparse6 import *
13
+ from networkx.readwrite.graph6 import *
14
+ from networkx.readwrite.gml import *
15
+ from networkx.readwrite.graphml import *
16
+ from networkx.readwrite.gexf import *
17
+ from networkx.readwrite.json_graph import *
18
+ from networkx.readwrite.text import *
pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/graph6.cpython-310.pyc ADDED
Binary file (11.7 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/graphml.cpython-310.pyc ADDED
Binary file (28.5 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/leda.cpython-310.pyc ADDED
Binary file (2.88 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-310.pyc ADDED
Binary file (9.52 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/readwrite/adjlist.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ **************
3
+ Adjacency List
4
+ **************
5
+ Read and write NetworkX graphs as adjacency lists.
6
+
7
+ Adjacency list format is useful for graphs without data associated
8
+ with nodes or edges and for nodes that can be meaningfully represented
9
+ as strings.
10
+
11
+ Format
12
+ ------
13
+ The adjacency list format consists of lines with node labels. The
14
+ first label in a line is the source node. Further labels in the line
15
+ are considered target nodes and are added to the graph along with an edge
16
+ between the source node and target node.
17
+
18
+ The graph with edges a-b, a-c, d-e can be represented as the following
19
+ adjacency list (anything following the # in a line is a comment)::
20
+
21
+ a b c # source target target
22
+ d e
23
+ """
24
+
25
+ __all__ = ["generate_adjlist", "write_adjlist", "parse_adjlist", "read_adjlist"]
26
+
27
+ import networkx as nx
28
+ from networkx.utils import open_file
29
+
30
+
31
+ def generate_adjlist(G, delimiter=" "):
32
+ """Generate a single line of the graph G in adjacency list format.
33
+
34
+ Parameters
35
+ ----------
36
+ G : NetworkX graph
37
+
38
+ delimiter : string, optional
39
+ Separator for node labels
40
+
41
+ Returns
42
+ -------
43
+ lines : string
44
+ Lines of data in adjlist format.
45
+
46
+ Examples
47
+ --------
48
+ >>> G = nx.lollipop_graph(4, 3)
49
+ >>> for line in nx.generate_adjlist(G):
50
+ ... print(line)
51
+ 0 1 2 3
52
+ 1 2 3
53
+ 2 3
54
+ 3 4
55
+ 4 5
56
+ 5 6
57
+ 6
58
+
59
+ See Also
60
+ --------
61
+ write_adjlist, read_adjlist
62
+
63
+ Notes
64
+ -----
65
+ The default `delimiter=" "` will result in unexpected results if node names contain
66
+ whitespace characters. To avoid this problem, specify an alternate delimiter when spaces are
67
+ valid in node names.
68
+
69
+ NB: This option is not available for data that isn't user-generated.
70
+
71
+ """
72
+ directed = G.is_directed()
73
+ seen = set()
74
+ for s, nbrs in G.adjacency():
75
+ line = str(s) + delimiter
76
+ for t, data in nbrs.items():
77
+ if not directed and t in seen:
78
+ continue
79
+ if G.is_multigraph():
80
+ for d in data.values():
81
+ line += str(t) + delimiter
82
+ else:
83
+ line += str(t) + delimiter
84
+ if not directed:
85
+ seen.add(s)
86
+ yield line[: -len(delimiter)]
87
+
88
+
89
+ @open_file(1, mode="wb")
90
+ def write_adjlist(G, path, comments="#", delimiter=" ", encoding="utf-8"):
91
+ """Write graph G in single-line adjacency-list format to path.
92
+
93
+
94
+ Parameters
95
+ ----------
96
+ G : NetworkX graph
97
+
98
+ path : string or file
99
+ Filename or file handle for data output.
100
+ Filenames ending in .gz or .bz2 will be compressed.
101
+
102
+ comments : string, optional
103
+ Marker for comment lines
104
+
105
+ delimiter : string, optional
106
+ Separator for node labels
107
+
108
+ encoding : string, optional
109
+ Text encoding.
110
+
111
+ Examples
112
+ --------
113
+ >>> G = nx.path_graph(4)
114
+ >>> nx.write_adjlist(G, "test.adjlist")
115
+
116
+ The path can be a filehandle or a string with the name of the file. If a
117
+ filehandle is provided, it has to be opened in 'wb' mode.
118
+
119
+ >>> fh = open("test.adjlist", "wb")
120
+ >>> nx.write_adjlist(G, fh)
121
+
122
+ Notes
123
+ -----
124
+ The default `delimiter=" "` will result in unexpected results if node names contain
125
+ whitespace characters. To avoid this problem, specify an alternate delimiter when spaces are
126
+ valid in node names.
127
+ NB: This option is not available for data that isn't user-generated.
128
+
129
+ This format does not store graph, node, or edge data.
130
+
131
+ See Also
132
+ --------
133
+ read_adjlist, generate_adjlist
134
+ """
135
+ import sys
136
+ import time
137
+
138
+ pargs = comments + " ".join(sys.argv) + "\n"
139
+ header = (
140
+ pargs
141
+ + comments
142
+ + f" GMT {time.asctime(time.gmtime())}\n"
143
+ + comments
144
+ + f" {G.name}\n"
145
+ )
146
+ path.write(header.encode(encoding))
147
+
148
+ for line in generate_adjlist(G, delimiter):
149
+ line += "\n"
150
+ path.write(line.encode(encoding))
151
+
152
+
153
+ @nx._dispatchable(graphs=None, returns_graph=True)
154
+ def parse_adjlist(
155
+ lines, comments="#", delimiter=None, create_using=None, nodetype=None
156
+ ):
157
+ """Parse lines of a graph adjacency list representation.
158
+
159
+ Parameters
160
+ ----------
161
+ lines : list or iterator of strings
162
+ Input data in adjlist format
163
+
164
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
165
+ Graph type to create. If graph instance, then cleared before populated.
166
+
167
+ nodetype : Python type, optional
168
+ Convert nodes to this type.
169
+
170
+ comments : string, optional
171
+ Marker for comment lines
172
+
173
+ delimiter : string, optional
174
+ Separator for node labels. The default is whitespace.
175
+
176
+ Returns
177
+ -------
178
+ G: NetworkX graph
179
+ The graph corresponding to the lines in adjacency list format.
180
+
181
+ Examples
182
+ --------
183
+ >>> lines = ["1 2 5", "2 3 4", "3 5", "4", "5"]
184
+ >>> G = nx.parse_adjlist(lines, nodetype=int)
185
+ >>> nodes = [1, 2, 3, 4, 5]
186
+ >>> all(node in G for node in nodes)
187
+ True
188
+ >>> edges = [(1, 2), (1, 5), (2, 3), (2, 4), (3, 5)]
189
+ >>> all((u, v) in G.edges() or (v, u) in G.edges() for (u, v) in edges)
190
+ True
191
+
192
+ See Also
193
+ --------
194
+ read_adjlist
195
+
196
+ """
197
+ G = nx.empty_graph(0, create_using)
198
+ for line in lines:
199
+ p = line.find(comments)
200
+ if p >= 0:
201
+ line = line[:p]
202
+ if not len(line):
203
+ continue
204
+ vlist = line.strip().split(delimiter)
205
+ u = vlist.pop(0)
206
+ # convert types
207
+ if nodetype is not None:
208
+ try:
209
+ u = nodetype(u)
210
+ except BaseException as err:
211
+ raise TypeError(
212
+ f"Failed to convert node ({u}) to type {nodetype}"
213
+ ) from err
214
+ G.add_node(u)
215
+ if nodetype is not None:
216
+ try:
217
+ vlist = list(map(nodetype, vlist))
218
+ except BaseException as err:
219
+ raise TypeError(
220
+ f"Failed to convert nodes ({','.join(vlist)}) to type {nodetype}"
221
+ ) from err
222
+ G.add_edges_from([(u, v) for v in vlist])
223
+ return G
224
+
225
+
226
+ @open_file(0, mode="rb")
227
+ @nx._dispatchable(graphs=None, returns_graph=True)
228
+ def read_adjlist(
229
+ path,
230
+ comments="#",
231
+ delimiter=None,
232
+ create_using=None,
233
+ nodetype=None,
234
+ encoding="utf-8",
235
+ ):
236
+ """Read graph in adjacency list format from path.
237
+
238
+ Parameters
239
+ ----------
240
+ path : string or file
241
+ Filename or file handle to read.
242
+ Filenames ending in .gz or .bz2 will be uncompressed.
243
+
244
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
245
+ Graph type to create. If graph instance, then cleared before populated.
246
+
247
+ nodetype : Python type, optional
248
+ Convert nodes to this type.
249
+
250
+ comments : string, optional
251
+ Marker for comment lines
252
+
253
+ delimiter : string, optional
254
+ Separator for node labels. The default is whitespace.
255
+
256
+ Returns
257
+ -------
258
+ G: NetworkX graph
259
+ The graph corresponding to the lines in adjacency list format.
260
+
261
+ Examples
262
+ --------
263
+ >>> G = nx.path_graph(4)
264
+ >>> nx.write_adjlist(G, "test.adjlist")
265
+ >>> G = nx.read_adjlist("test.adjlist")
266
+
267
+ The path can be a filehandle or a string with the name of the file. If a
268
+ filehandle is provided, it has to be opened in 'rb' mode.
269
+
270
+ >>> fh = open("test.adjlist", "rb")
271
+ >>> G = nx.read_adjlist(fh)
272
+
273
+ Filenames ending in .gz or .bz2 will be compressed.
274
+
275
+ >>> nx.write_adjlist(G, "test.adjlist.gz")
276
+ >>> G = nx.read_adjlist("test.adjlist.gz")
277
+
278
+ The optional nodetype is a function to convert node strings to nodetype.
279
+
280
+ For example
281
+
282
+ >>> G = nx.read_adjlist("test.adjlist", nodetype=int)
283
+
284
+ will attempt to convert all nodes to integer type.
285
+
286
+ Since nodes must be hashable, the function nodetype must return hashable
287
+ types (e.g. int, float, str, frozenset - or tuples of those, etc.)
288
+
289
+ The optional create_using parameter indicates the type of NetworkX graph
290
+ created. The default is `nx.Graph`, an undirected graph.
291
+ To read the data as a directed graph use
292
+
293
+ >>> G = nx.read_adjlist("test.adjlist", create_using=nx.DiGraph)
294
+
295
+ Notes
296
+ -----
297
+ This format does not store graph or node data.
298
+
299
+ See Also
300
+ --------
301
+ write_adjlist
302
+ """
303
+ lines = (line.decode(encoding) for line in path)
304
+ return parse_adjlist(
305
+ lines,
306
+ comments=comments,
307
+ delimiter=delimiter,
308
+ create_using=create_using,
309
+ nodetype=nodetype,
310
+ )
pythonProject/.venv/Lib/site-packages/networkx/readwrite/edgelist.py ADDED
@@ -0,0 +1,489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ **********
3
+ Edge Lists
4
+ **********
5
+ Read and write NetworkX graphs as edge lists.
6
+
7
+ The multi-line adjacency list format is useful for graphs with nodes
8
+ that can be meaningfully represented as strings. With the edgelist
9
+ format simple edge data can be stored but node or graph data is not.
10
+ There is no way of representing isolated nodes unless the node has a
11
+ self-loop edge.
12
+
13
+ Format
14
+ ------
15
+ You can read or write three formats of edge lists with these functions.
16
+
17
+ Node pairs with no data::
18
+
19
+ 1 2
20
+
21
+ Python dictionary as data::
22
+
23
+ 1 2 {'weight':7, 'color':'green'}
24
+
25
+ Arbitrary data::
26
+
27
+ 1 2 7 green
28
+ """
29
+
30
+ __all__ = [
31
+ "generate_edgelist",
32
+ "write_edgelist",
33
+ "parse_edgelist",
34
+ "read_edgelist",
35
+ "read_weighted_edgelist",
36
+ "write_weighted_edgelist",
37
+ ]
38
+
39
+ import networkx as nx
40
+ from networkx.utils import open_file
41
+
42
+
43
+ def generate_edgelist(G, delimiter=" ", data=True):
44
+ """Generate a single line of the graph G in edge list format.
45
+
46
+ Parameters
47
+ ----------
48
+ G : NetworkX graph
49
+
50
+ delimiter : string, optional
51
+ Separator for node labels
52
+
53
+ data : bool or list of keys
54
+ If False generate no edge data. If True use a dictionary
55
+ representation of edge data. If a list of keys use a list of data
56
+ values corresponding to the keys.
57
+
58
+ Returns
59
+ -------
60
+ lines : string
61
+ Lines of data in adjlist format.
62
+
63
+ Examples
64
+ --------
65
+ >>> G = nx.lollipop_graph(4, 3)
66
+ >>> G[1][2]["weight"] = 3
67
+ >>> G[3][4]["capacity"] = 12
68
+ >>> for line in nx.generate_edgelist(G, data=False):
69
+ ... print(line)
70
+ 0 1
71
+ 0 2
72
+ 0 3
73
+ 1 2
74
+ 1 3
75
+ 2 3
76
+ 3 4
77
+ 4 5
78
+ 5 6
79
+
80
+ >>> for line in nx.generate_edgelist(G):
81
+ ... print(line)
82
+ 0 1 {}
83
+ 0 2 {}
84
+ 0 3 {}
85
+ 1 2 {'weight': 3}
86
+ 1 3 {}
87
+ 2 3 {}
88
+ 3 4 {'capacity': 12}
89
+ 4 5 {}
90
+ 5 6 {}
91
+
92
+ >>> for line in nx.generate_edgelist(G, data=["weight"]):
93
+ ... print(line)
94
+ 0 1
95
+ 0 2
96
+ 0 3
97
+ 1 2 3
98
+ 1 3
99
+ 2 3
100
+ 3 4
101
+ 4 5
102
+ 5 6
103
+
104
+ See Also
105
+ --------
106
+ write_adjlist, read_adjlist
107
+ """
108
+ if data is True:
109
+ for u, v, d in G.edges(data=True):
110
+ e = u, v, dict(d)
111
+ yield delimiter.join(map(str, e))
112
+ elif data is False:
113
+ for u, v in G.edges(data=False):
114
+ e = u, v
115
+ yield delimiter.join(map(str, e))
116
+ else:
117
+ for u, v, d in G.edges(data=True):
118
+ e = [u, v]
119
+ try:
120
+ e.extend(d[k] for k in data)
121
+ except KeyError:
122
+ pass # missing data for this edge, should warn?
123
+ yield delimiter.join(map(str, e))
124
+
125
+
126
+ @open_file(1, mode="wb")
127
+ def write_edgelist(G, path, comments="#", delimiter=" ", data=True, encoding="utf-8"):
128
+ """Write graph as a list of edges.
129
+
130
+ Parameters
131
+ ----------
132
+ G : graph
133
+ A NetworkX graph
134
+ path : file or string
135
+ File or filename to write. If a file is provided, it must be
136
+ opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed.
137
+ comments : string, optional
138
+ The character used to indicate the start of a comment
139
+ delimiter : string, optional
140
+ The string used to separate values. The default is whitespace.
141
+ data : bool or list, optional
142
+ If False write no edge data.
143
+ If True write a string representation of the edge data dictionary..
144
+ If a list (or other iterable) is provided, write the keys specified
145
+ in the list.
146
+ encoding: string, optional
147
+ Specify which encoding to use when writing file.
148
+
149
+ Examples
150
+ --------
151
+ >>> G = nx.path_graph(4)
152
+ >>> nx.write_edgelist(G, "test.edgelist")
153
+ >>> G = nx.path_graph(4)
154
+ >>> fh = open("test.edgelist", "wb")
155
+ >>> nx.write_edgelist(G, fh)
156
+ >>> nx.write_edgelist(G, "test.edgelist.gz")
157
+ >>> nx.write_edgelist(G, "test.edgelist.gz", data=False)
158
+
159
+ >>> G = nx.Graph()
160
+ >>> G.add_edge(1, 2, weight=7, color="red")
161
+ >>> nx.write_edgelist(G, "test.edgelist", data=False)
162
+ >>> nx.write_edgelist(G, "test.edgelist", data=["color"])
163
+ >>> nx.write_edgelist(G, "test.edgelist", data=["color", "weight"])
164
+
165
+ See Also
166
+ --------
167
+ read_edgelist
168
+ write_weighted_edgelist
169
+ """
170
+
171
+ for line in generate_edgelist(G, delimiter, data):
172
+ line += "\n"
173
+ path.write(line.encode(encoding))
174
+
175
+
176
+ @nx._dispatchable(graphs=None, returns_graph=True)
177
+ def parse_edgelist(
178
+ lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True
179
+ ):
180
+ """Parse lines of an edge list representation of a graph.
181
+
182
+ Parameters
183
+ ----------
184
+ lines : list or iterator of strings
185
+ Input data in edgelist format
186
+ comments : string, optional
187
+ Marker for comment lines. Default is `'#'`. To specify that no character
188
+ should be treated as a comment, use ``comments=None``.
189
+ delimiter : string, optional
190
+ Separator for node labels. Default is `None`, meaning any whitespace.
191
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
192
+ Graph type to create. If graph instance, then cleared before populated.
193
+ nodetype : Python type, optional
194
+ Convert nodes to this type. Default is `None`, meaning no conversion is
195
+ performed.
196
+ data : bool or list of (label,type) tuples
197
+ If `False` generate no edge data or if `True` use a dictionary
198
+ representation of edge data or a list tuples specifying dictionary
199
+ key names and types for edge data.
200
+
201
+ Returns
202
+ -------
203
+ G: NetworkX Graph
204
+ The graph corresponding to lines
205
+
206
+ Examples
207
+ --------
208
+ Edgelist with no data:
209
+
210
+ >>> lines = ["1 2", "2 3", "3 4"]
211
+ >>> G = nx.parse_edgelist(lines, nodetype=int)
212
+ >>> list(G)
213
+ [1, 2, 3, 4]
214
+ >>> list(G.edges())
215
+ [(1, 2), (2, 3), (3, 4)]
216
+
217
+ Edgelist with data in Python dictionary representation:
218
+
219
+ >>> lines = ["1 2 {'weight': 3}", "2 3 {'weight': 27}", "3 4 {'weight': 3.0}"]
220
+ >>> G = nx.parse_edgelist(lines, nodetype=int)
221
+ >>> list(G)
222
+ [1, 2, 3, 4]
223
+ >>> list(G.edges(data=True))
224
+ [(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})]
225
+
226
+ Edgelist with data in a list:
227
+
228
+ >>> lines = ["1 2 3", "2 3 27", "3 4 3.0"]
229
+ >>> G = nx.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
230
+ >>> list(G)
231
+ [1, 2, 3, 4]
232
+ >>> list(G.edges(data=True))
233
+ [(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})]
234
+
235
+ See Also
236
+ --------
237
+ read_weighted_edgelist
238
+ """
239
+ from ast import literal_eval
240
+
241
+ G = nx.empty_graph(0, create_using)
242
+ for line in lines:
243
+ if comments is not None:
244
+ p = line.find(comments)
245
+ if p >= 0:
246
+ line = line[:p]
247
+ if not line:
248
+ continue
249
+ # split line, should have 2 or more
250
+ s = line.strip().split(delimiter)
251
+ if len(s) < 2:
252
+ continue
253
+ u = s.pop(0)
254
+ v = s.pop(0)
255
+ d = s
256
+ if nodetype is not None:
257
+ try:
258
+ u = nodetype(u)
259
+ v = nodetype(v)
260
+ except Exception as err:
261
+ raise TypeError(
262
+ f"Failed to convert nodes {u},{v} to type {nodetype}."
263
+ ) from err
264
+
265
+ if len(d) == 0 or data is False:
266
+ # no data or data type specified
267
+ edgedata = {}
268
+ elif data is True:
269
+ # no edge types specified
270
+ try: # try to evaluate as dictionary
271
+ if delimiter == ",":
272
+ edgedata_str = ",".join(d)
273
+ else:
274
+ edgedata_str = " ".join(d)
275
+ edgedata = dict(literal_eval(edgedata_str.strip()))
276
+ except Exception as err:
277
+ raise TypeError(
278
+ f"Failed to convert edge data ({d}) to dictionary."
279
+ ) from err
280
+ else:
281
+ # convert edge data to dictionary with specified keys and type
282
+ if len(d) != len(data):
283
+ raise IndexError(
284
+ f"Edge data {d} and data_keys {data} are not the same length"
285
+ )
286
+ edgedata = {}
287
+ for (edge_key, edge_type), edge_value in zip(data, d):
288
+ try:
289
+ edge_value = edge_type(edge_value)
290
+ except Exception as err:
291
+ raise TypeError(
292
+ f"Failed to convert {edge_key} data {edge_value} "
293
+ f"to type {edge_type}."
294
+ ) from err
295
+ edgedata.update({edge_key: edge_value})
296
+ G.add_edge(u, v, **edgedata)
297
+ return G
298
+
299
+
300
+ @open_file(0, mode="rb")
301
+ @nx._dispatchable(graphs=None, returns_graph=True)
302
+ def read_edgelist(
303
+ path,
304
+ comments="#",
305
+ delimiter=None,
306
+ create_using=None,
307
+ nodetype=None,
308
+ data=True,
309
+ edgetype=None,
310
+ encoding="utf-8",
311
+ ):
312
+ """Read a graph from a list of edges.
313
+
314
+ Parameters
315
+ ----------
316
+ path : file or string
317
+ File or filename to read. If a file is provided, it must be
318
+ opened in 'rb' mode.
319
+ Filenames ending in .gz or .bz2 will be uncompressed.
320
+ comments : string, optional
321
+ The character used to indicate the start of a comment. To specify that
322
+ no character should be treated as a comment, use ``comments=None``.
323
+ delimiter : string, optional
324
+ The string used to separate values. The default is whitespace.
325
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
326
+ Graph type to create. If graph instance, then cleared before populated.
327
+ nodetype : int, float, str, Python type, optional
328
+ Convert node data from strings to specified type
329
+ data : bool or list of (label,type) tuples
330
+ Tuples specifying dictionary key names and types for edge data
331
+ edgetype : int, float, str, Python type, optional OBSOLETE
332
+ Convert edge data from strings to specified type and use as 'weight'
333
+ encoding: string, optional
334
+ Specify which encoding to use when reading file.
335
+
336
+ Returns
337
+ -------
338
+ G : graph
339
+ A networkx Graph or other type specified with create_using
340
+
341
+ Examples
342
+ --------
343
+ >>> nx.write_edgelist(nx.path_graph(4), "test.edgelist")
344
+ >>> G = nx.read_edgelist("test.edgelist")
345
+
346
+ >>> fh = open("test.edgelist", "rb")
347
+ >>> G = nx.read_edgelist(fh)
348
+ >>> fh.close()
349
+
350
+ >>> G = nx.read_edgelist("test.edgelist", nodetype=int)
351
+ >>> G = nx.read_edgelist("test.edgelist", create_using=nx.DiGraph)
352
+
353
+ Edgelist with data in a list:
354
+
355
+ >>> textline = "1 2 3"
356
+ >>> fh = open("test.edgelist", "w")
357
+ >>> d = fh.write(textline)
358
+ >>> fh.close()
359
+ >>> G = nx.read_edgelist("test.edgelist", nodetype=int, data=(("weight", float),))
360
+ >>> list(G)
361
+ [1, 2]
362
+ >>> list(G.edges(data=True))
363
+ [(1, 2, {'weight': 3.0})]
364
+
365
+ See parse_edgelist() for more examples of formatting.
366
+
367
+ See Also
368
+ --------
369
+ parse_edgelist
370
+ write_edgelist
371
+
372
+ Notes
373
+ -----
374
+ Since nodes must be hashable, the function nodetype must return hashable
375
+ types (e.g. int, float, str, frozenset - or tuples of those, etc.)
376
+ """
377
+ lines = (line if isinstance(line, str) else line.decode(encoding) for line in path)
378
+ return parse_edgelist(
379
+ lines,
380
+ comments=comments,
381
+ delimiter=delimiter,
382
+ create_using=create_using,
383
+ nodetype=nodetype,
384
+ data=data,
385
+ )
386
+
387
+
388
+ def write_weighted_edgelist(G, path, comments="#", delimiter=" ", encoding="utf-8"):
389
+ """Write graph G as a list of edges with numeric weights.
390
+
391
+ Parameters
392
+ ----------
393
+ G : graph
394
+ A NetworkX graph
395
+ path : file or string
396
+ File or filename to write. If a file is provided, it must be
397
+ opened in 'wb' mode.
398
+ Filenames ending in .gz or .bz2 will be compressed.
399
+ comments : string, optional
400
+ The character used to indicate the start of a comment
401
+ delimiter : string, optional
402
+ The string used to separate values. The default is whitespace.
403
+ encoding: string, optional
404
+ Specify which encoding to use when writing file.
405
+
406
+ Examples
407
+ --------
408
+ >>> G = nx.Graph()
409
+ >>> G.add_edge(1, 2, weight=7)
410
+ >>> nx.write_weighted_edgelist(G, "test.weighted.edgelist")
411
+
412
+ See Also
413
+ --------
414
+ read_edgelist
415
+ write_edgelist
416
+ read_weighted_edgelist
417
+ """
418
+ write_edgelist(
419
+ G,
420
+ path,
421
+ comments=comments,
422
+ delimiter=delimiter,
423
+ data=("weight",),
424
+ encoding=encoding,
425
+ )
426
+
427
+
428
+ @nx._dispatchable(graphs=None, returns_graph=True)
429
+ def read_weighted_edgelist(
430
+ path,
431
+ comments="#",
432
+ delimiter=None,
433
+ create_using=None,
434
+ nodetype=None,
435
+ encoding="utf-8",
436
+ ):
437
+ """Read a graph as list of edges with numeric weights.
438
+
439
+ Parameters
440
+ ----------
441
+ path : file or string
442
+ File or filename to read. If a file is provided, it must be
443
+ opened in 'rb' mode.
444
+ Filenames ending in .gz or .bz2 will be uncompressed.
445
+ comments : string, optional
446
+ The character used to indicate the start of a comment.
447
+ delimiter : string, optional
448
+ The string used to separate values. The default is whitespace.
449
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
450
+ Graph type to create. If graph instance, then cleared before populated.
451
+ nodetype : int, float, str, Python type, optional
452
+ Convert node data from strings to specified type
453
+ encoding: string, optional
454
+ Specify which encoding to use when reading file.
455
+
456
+ Returns
457
+ -------
458
+ G : graph
459
+ A networkx Graph or other type specified with create_using
460
+
461
+ Notes
462
+ -----
463
+ Since nodes must be hashable, the function nodetype must return hashable
464
+ types (e.g. int, float, str, frozenset - or tuples of those, etc.)
465
+
466
+ Example edgelist file format.
467
+
468
+ With numeric edge data::
469
+
470
+ # read with
471
+ # >>> G=nx.read_weighted_edgelist(fh)
472
+ # source target data
473
+ a b 1
474
+ a c 3.14159
475
+ d e 42
476
+
477
+ See Also
478
+ --------
479
+ write_weighted_edgelist
480
+ """
481
+ return read_edgelist(
482
+ path,
483
+ comments=comments,
484
+ delimiter=delimiter,
485
+ create_using=create_using,
486
+ nodetype=nodetype,
487
+ data=(("weight", float),),
488
+ encoding=encoding,
489
+ )
pythonProject/.venv/Lib/site-packages/networkx/readwrite/gexf.py ADDED
@@ -0,0 +1,1065 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Read and write graphs in GEXF format.
2
+
3
+ .. warning::
4
+ This parser uses the standard xml library present in Python, which is
5
+ insecure - see :external+python:mod:`xml` for additional information.
6
+ Only parse GEFX files you trust.
7
+
8
+ GEXF (Graph Exchange XML Format) is a language for describing complex
9
+ network structures, their associated data and dynamics.
10
+
11
+ This implementation does not support mixed graphs (directed and
12
+ undirected edges together).
13
+
14
+ Format
15
+ ------
16
+ GEXF is an XML format. See http://gexf.net/schema.html for the
17
+ specification and http://gexf.net/basic.html for examples.
18
+ """
19
+ import itertools
20
+ import time
21
+ from xml.etree.ElementTree import (
22
+ Element,
23
+ ElementTree,
24
+ SubElement,
25
+ register_namespace,
26
+ tostring,
27
+ )
28
+
29
+ import networkx as nx
30
+ from networkx.utils import open_file
31
+
32
+ __all__ = ["write_gexf", "read_gexf", "relabel_gexf_graph", "generate_gexf"]
33
+
34
+
35
+ @open_file(1, mode="wb")
36
+ def write_gexf(G, path, encoding="utf-8", prettyprint=True, version="1.2draft"):
37
+ """Write G in GEXF format to path.
38
+
39
+ "GEXF (Graph Exchange XML Format) is a language for describing
40
+ complex networks structures, their associated data and dynamics" [1]_.
41
+
42
+ Node attributes are checked according to the version of the GEXF
43
+ schemas used for parameters which are not user defined,
44
+ e.g. visualization 'viz' [2]_. See example for usage.
45
+
46
+ Parameters
47
+ ----------
48
+ G : graph
49
+ A NetworkX graph
50
+ path : file or string
51
+ File or file name to write.
52
+ File names ending in .gz or .bz2 will be compressed.
53
+ encoding : string (optional, default: 'utf-8')
54
+ Encoding for text data.
55
+ prettyprint : bool (optional, default: True)
56
+ If True use line breaks and indenting in output XML.
57
+ version: string (optional, default: '1.2draft')
58
+ The version of GEXF to be used for nodes attributes checking
59
+
60
+ Examples
61
+ --------
62
+ >>> G = nx.path_graph(4)
63
+ >>> nx.write_gexf(G, "test.gexf")
64
+
65
+ # visualization data
66
+ >>> G.nodes[0]["viz"] = {"size": 54}
67
+ >>> G.nodes[0]["viz"]["position"] = {"x": 0, "y": 1}
68
+ >>> G.nodes[0]["viz"]["color"] = {"r": 0, "g": 0, "b": 256}
69
+
70
+
71
+ Notes
72
+ -----
73
+ This implementation does not support mixed graphs (directed and undirected
74
+ edges together).
75
+
76
+ The node id attribute is set to be the string of the node label.
77
+ If you want to specify an id use set it as node data, e.g.
78
+ node['a']['id']=1 to set the id of node 'a' to 1.
79
+
80
+ References
81
+ ----------
82
+ .. [1] GEXF File Format, http://gexf.net/
83
+ .. [2] GEXF schema, http://gexf.net/schema.html
84
+ """
85
+ writer = GEXFWriter(encoding=encoding, prettyprint=prettyprint, version=version)
86
+ writer.add_graph(G)
87
+ writer.write(path)
88
+
89
+
90
+ def generate_gexf(G, encoding="utf-8", prettyprint=True, version="1.2draft"):
91
+ """Generate lines of GEXF format representation of G.
92
+
93
+ "GEXF (Graph Exchange XML Format) is a language for describing
94
+ complex networks structures, their associated data and dynamics" [1]_.
95
+
96
+ Parameters
97
+ ----------
98
+ G : graph
99
+ A NetworkX graph
100
+ encoding : string (optional, default: 'utf-8')
101
+ Encoding for text data.
102
+ prettyprint : bool (optional, default: True)
103
+ If True use line breaks and indenting in output XML.
104
+ version : string (default: 1.2draft)
105
+ Version of GEFX File Format (see http://gexf.net/schema.html)
106
+ Supported values: "1.1draft", "1.2draft"
107
+
108
+
109
+ Examples
110
+ --------
111
+ >>> G = nx.path_graph(4)
112
+ >>> linefeed = chr(10) # linefeed=\n
113
+ >>> s = linefeed.join(nx.generate_gexf(G))
114
+ >>> for line in nx.generate_gexf(G): # doctest: +SKIP
115
+ ... print(line)
116
+
117
+ Notes
118
+ -----
119
+ This implementation does not support mixed graphs (directed and undirected
120
+ edges together).
121
+
122
+ The node id attribute is set to be the string of the node label.
123
+ If you want to specify an id use set it as node data, e.g.
124
+ node['a']['id']=1 to set the id of node 'a' to 1.
125
+
126
+ References
127
+ ----------
128
+ .. [1] GEXF File Format, https://gephi.org/gexf/format/
129
+ """
130
+ writer = GEXFWriter(encoding=encoding, prettyprint=prettyprint, version=version)
131
+ writer.add_graph(G)
132
+ yield from str(writer).splitlines()
133
+
134
+
135
+ @open_file(0, mode="rb")
136
+ @nx._dispatchable(graphs=None, returns_graph=True)
137
+ def read_gexf(path, node_type=None, relabel=False, version="1.2draft"):
138
+ """Read graph in GEXF format from path.
139
+
140
+ "GEXF (Graph Exchange XML Format) is a language for describing
141
+ complex networks structures, their associated data and dynamics" [1]_.
142
+
143
+ Parameters
144
+ ----------
145
+ path : file or string
146
+ File or file name to read.
147
+ File names ending in .gz or .bz2 will be decompressed.
148
+ node_type: Python type (default: None)
149
+ Convert node ids to this type if not None.
150
+ relabel : bool (default: False)
151
+ If True relabel the nodes to use the GEXF node "label" attribute
152
+ instead of the node "id" attribute as the NetworkX node label.
153
+ version : string (default: 1.2draft)
154
+ Version of GEFX File Format (see http://gexf.net/schema.html)
155
+ Supported values: "1.1draft", "1.2draft"
156
+
157
+ Returns
158
+ -------
159
+ graph: NetworkX graph
160
+ If no parallel edges are found a Graph or DiGraph is returned.
161
+ Otherwise a MultiGraph or MultiDiGraph is returned.
162
+
163
+ Notes
164
+ -----
165
+ This implementation does not support mixed graphs (directed and undirected
166
+ edges together).
167
+
168
+ References
169
+ ----------
170
+ .. [1] GEXF File Format, http://gexf.net/
171
+ """
172
+ reader = GEXFReader(node_type=node_type, version=version)
173
+ if relabel:
174
+ G = relabel_gexf_graph(reader(path))
175
+ else:
176
+ G = reader(path)
177
+ return G
178
+
179
+
180
+ class GEXF:
181
+ versions = {
182
+ "1.1draft": {
183
+ "NS_GEXF": "http://www.gexf.net/1.1draft",
184
+ "NS_VIZ": "http://www.gexf.net/1.1draft/viz",
185
+ "NS_XSI": "http://www.w3.org/2001/XMLSchema-instance",
186
+ "SCHEMALOCATION": " ".join(
187
+ [
188
+ "http://www.gexf.net/1.1draft",
189
+ "http://www.gexf.net/1.1draft/gexf.xsd",
190
+ ]
191
+ ),
192
+ "VERSION": "1.1",
193
+ },
194
+ "1.2draft": {
195
+ "NS_GEXF": "http://www.gexf.net/1.2draft",
196
+ "NS_VIZ": "http://www.gexf.net/1.2draft/viz",
197
+ "NS_XSI": "http://www.w3.org/2001/XMLSchema-instance",
198
+ "SCHEMALOCATION": " ".join(
199
+ [
200
+ "http://www.gexf.net/1.2draft",
201
+ "http://www.gexf.net/1.2draft/gexf.xsd",
202
+ ]
203
+ ),
204
+ "VERSION": "1.2",
205
+ },
206
+ }
207
+
208
+ def construct_types(self):
209
+ types = [
210
+ (int, "integer"),
211
+ (float, "float"),
212
+ (float, "double"),
213
+ (bool, "boolean"),
214
+ (list, "string"),
215
+ (dict, "string"),
216
+ (int, "long"),
217
+ (str, "liststring"),
218
+ (str, "anyURI"),
219
+ (str, "string"),
220
+ ]
221
+
222
+ # These additions to types allow writing numpy types
223
+ try:
224
+ import numpy as np
225
+ except ImportError:
226
+ pass
227
+ else:
228
+ # prepend so that python types are created upon read (last entry wins)
229
+ types = [
230
+ (np.float64, "float"),
231
+ (np.float32, "float"),
232
+ (np.float16, "float"),
233
+ (np.int_, "int"),
234
+ (np.int8, "int"),
235
+ (np.int16, "int"),
236
+ (np.int32, "int"),
237
+ (np.int64, "int"),
238
+ (np.uint8, "int"),
239
+ (np.uint16, "int"),
240
+ (np.uint32, "int"),
241
+ (np.uint64, "int"),
242
+ (np.int_, "int"),
243
+ (np.intc, "int"),
244
+ (np.intp, "int"),
245
+ ] + types
246
+
247
+ self.xml_type = dict(types)
248
+ self.python_type = dict(reversed(a) for a in types)
249
+
250
+ # http://www.w3.org/TR/xmlschema-2/#boolean
251
+ convert_bool = {
252
+ "true": True,
253
+ "false": False,
254
+ "True": True,
255
+ "False": False,
256
+ "0": False,
257
+ 0: False,
258
+ "1": True,
259
+ 1: True,
260
+ }
261
+
262
+ def set_version(self, version):
263
+ d = self.versions.get(version)
264
+ if d is None:
265
+ raise nx.NetworkXError(f"Unknown GEXF version {version}.")
266
+ self.NS_GEXF = d["NS_GEXF"]
267
+ self.NS_VIZ = d["NS_VIZ"]
268
+ self.NS_XSI = d["NS_XSI"]
269
+ self.SCHEMALOCATION = d["SCHEMALOCATION"]
270
+ self.VERSION = d["VERSION"]
271
+ self.version = version
272
+
273
+
274
+ class GEXFWriter(GEXF):
275
+ # class for writing GEXF format files
276
+ # use write_gexf() function
277
+ def __init__(
278
+ self, graph=None, encoding="utf-8", prettyprint=True, version="1.2draft"
279
+ ):
280
+ self.construct_types()
281
+ self.prettyprint = prettyprint
282
+ self.encoding = encoding
283
+ self.set_version(version)
284
+ self.xml = Element(
285
+ "gexf",
286
+ {
287
+ "xmlns": self.NS_GEXF,
288
+ "xmlns:xsi": self.NS_XSI,
289
+ "xsi:schemaLocation": self.SCHEMALOCATION,
290
+ "version": self.VERSION,
291
+ },
292
+ )
293
+
294
+ # Make meta element a non-graph element
295
+ # Also add lastmodifieddate as attribute, not tag
296
+ meta_element = Element("meta")
297
+ subelement_text = f"NetworkX {nx.__version__}"
298
+ SubElement(meta_element, "creator").text = subelement_text
299
+ meta_element.set("lastmodifieddate", time.strftime("%Y-%m-%d"))
300
+ self.xml.append(meta_element)
301
+
302
+ register_namespace("viz", self.NS_VIZ)
303
+
304
+ # counters for edge and attribute identifiers
305
+ self.edge_id = itertools.count()
306
+ self.attr_id = itertools.count()
307
+ self.all_edge_ids = set()
308
+ # default attributes are stored in dictionaries
309
+ self.attr = {}
310
+ self.attr["node"] = {}
311
+ self.attr["edge"] = {}
312
+ self.attr["node"]["dynamic"] = {}
313
+ self.attr["node"]["static"] = {}
314
+ self.attr["edge"]["dynamic"] = {}
315
+ self.attr["edge"]["static"] = {}
316
+
317
+ if graph is not None:
318
+ self.add_graph(graph)
319
+
320
+ def __str__(self):
321
+ if self.prettyprint:
322
+ self.indent(self.xml)
323
+ s = tostring(self.xml).decode(self.encoding)
324
+ return s
325
+
326
+ def add_graph(self, G):
327
+ # first pass through G collecting edge ids
328
+ for u, v, dd in G.edges(data=True):
329
+ eid = dd.get("id")
330
+ if eid is not None:
331
+ self.all_edge_ids.add(str(eid))
332
+ # set graph attributes
333
+ if G.graph.get("mode") == "dynamic":
334
+ mode = "dynamic"
335
+ else:
336
+ mode = "static"
337
+ # Add a graph element to the XML
338
+ if G.is_directed():
339
+ default = "directed"
340
+ else:
341
+ default = "undirected"
342
+ name = G.graph.get("name", "")
343
+ graph_element = Element("graph", defaultedgetype=default, mode=mode, name=name)
344
+ self.graph_element = graph_element
345
+ self.add_nodes(G, graph_element)
346
+ self.add_edges(G, graph_element)
347
+ self.xml.append(graph_element)
348
+
349
+ def add_nodes(self, G, graph_element):
350
+ nodes_element = Element("nodes")
351
+ for node, data in G.nodes(data=True):
352
+ node_data = data.copy()
353
+ node_id = str(node_data.pop("id", node))
354
+ kw = {"id": node_id}
355
+ label = str(node_data.pop("label", node))
356
+ kw["label"] = label
357
+ try:
358
+ pid = node_data.pop("pid")
359
+ kw["pid"] = str(pid)
360
+ except KeyError:
361
+ pass
362
+ try:
363
+ start = node_data.pop("start")
364
+ kw["start"] = str(start)
365
+ self.alter_graph_mode_timeformat(start)
366
+ except KeyError:
367
+ pass
368
+ try:
369
+ end = node_data.pop("end")
370
+ kw["end"] = str(end)
371
+ self.alter_graph_mode_timeformat(end)
372
+ except KeyError:
373
+ pass
374
+ # add node element with attributes
375
+ node_element = Element("node", **kw)
376
+ # add node element and attr subelements
377
+ default = G.graph.get("node_default", {})
378
+ node_data = self.add_parents(node_element, node_data)
379
+ if self.VERSION == "1.1":
380
+ node_data = self.add_slices(node_element, node_data)
381
+ else:
382
+ node_data = self.add_spells(node_element, node_data)
383
+ node_data = self.add_viz(node_element, node_data)
384
+ node_data = self.add_attributes("node", node_element, node_data, default)
385
+ nodes_element.append(node_element)
386
+ graph_element.append(nodes_element)
387
+
388
+ def add_edges(self, G, graph_element):
389
+ def edge_key_data(G):
390
+ # helper function to unify multigraph and graph edge iterator
391
+ if G.is_multigraph():
392
+ for u, v, key, data in G.edges(data=True, keys=True):
393
+ edge_data = data.copy()
394
+ edge_data.update(key=key)
395
+ edge_id = edge_data.pop("id", None)
396
+ if edge_id is None:
397
+ edge_id = next(self.edge_id)
398
+ while str(edge_id) in self.all_edge_ids:
399
+ edge_id = next(self.edge_id)
400
+ self.all_edge_ids.add(str(edge_id))
401
+ yield u, v, edge_id, edge_data
402
+ else:
403
+ for u, v, data in G.edges(data=True):
404
+ edge_data = data.copy()
405
+ edge_id = edge_data.pop("id", None)
406
+ if edge_id is None:
407
+ edge_id = next(self.edge_id)
408
+ while str(edge_id) in self.all_edge_ids:
409
+ edge_id = next(self.edge_id)
410
+ self.all_edge_ids.add(str(edge_id))
411
+ yield u, v, edge_id, edge_data
412
+
413
+ edges_element = Element("edges")
414
+ for u, v, key, edge_data in edge_key_data(G):
415
+ kw = {"id": str(key)}
416
+ try:
417
+ edge_label = edge_data.pop("label")
418
+ kw["label"] = str(edge_label)
419
+ except KeyError:
420
+ pass
421
+ try:
422
+ edge_weight = edge_data.pop("weight")
423
+ kw["weight"] = str(edge_weight)
424
+ except KeyError:
425
+ pass
426
+ try:
427
+ edge_type = edge_data.pop("type")
428
+ kw["type"] = str(edge_type)
429
+ except KeyError:
430
+ pass
431
+ try:
432
+ start = edge_data.pop("start")
433
+ kw["start"] = str(start)
434
+ self.alter_graph_mode_timeformat(start)
435
+ except KeyError:
436
+ pass
437
+ try:
438
+ end = edge_data.pop("end")
439
+ kw["end"] = str(end)
440
+ self.alter_graph_mode_timeformat(end)
441
+ except KeyError:
442
+ pass
443
+ source_id = str(G.nodes[u].get("id", u))
444
+ target_id = str(G.nodes[v].get("id", v))
445
+ edge_element = Element("edge", source=source_id, target=target_id, **kw)
446
+ default = G.graph.get("edge_default", {})
447
+ if self.VERSION == "1.1":
448
+ edge_data = self.add_slices(edge_element, edge_data)
449
+ else:
450
+ edge_data = self.add_spells(edge_element, edge_data)
451
+ edge_data = self.add_viz(edge_element, edge_data)
452
+ edge_data = self.add_attributes("edge", edge_element, edge_data, default)
453
+ edges_element.append(edge_element)
454
+ graph_element.append(edges_element)
455
+
456
+ def add_attributes(self, node_or_edge, xml_obj, data, default):
457
+ # Add attrvalues to node or edge
458
+ attvalues = Element("attvalues")
459
+ if len(data) == 0:
460
+ return data
461
+ mode = "static"
462
+ for k, v in data.items():
463
+ # rename generic multigraph key to avoid any name conflict
464
+ if k == "key":
465
+ k = "networkx_key"
466
+ val_type = type(v)
467
+ if val_type not in self.xml_type:
468
+ raise TypeError(f"attribute value type is not allowed: {val_type}")
469
+ if isinstance(v, list):
470
+ # dynamic data
471
+ for val, start, end in v:
472
+ val_type = type(val)
473
+ if start is not None or end is not None:
474
+ mode = "dynamic"
475
+ self.alter_graph_mode_timeformat(start)
476
+ self.alter_graph_mode_timeformat(end)
477
+ break
478
+ attr_id = self.get_attr_id(
479
+ str(k), self.xml_type[val_type], node_or_edge, default, mode
480
+ )
481
+ for val, start, end in v:
482
+ e = Element("attvalue")
483
+ e.attrib["for"] = attr_id
484
+ e.attrib["value"] = str(val)
485
+ # Handle nan, inf, -inf differently
486
+ if val_type == float:
487
+ if e.attrib["value"] == "inf":
488
+ e.attrib["value"] = "INF"
489
+ elif e.attrib["value"] == "nan":
490
+ e.attrib["value"] = "NaN"
491
+ elif e.attrib["value"] == "-inf":
492
+ e.attrib["value"] = "-INF"
493
+ if start is not None:
494
+ e.attrib["start"] = str(start)
495
+ if end is not None:
496
+ e.attrib["end"] = str(end)
497
+ attvalues.append(e)
498
+ else:
499
+ # static data
500
+ mode = "static"
501
+ attr_id = self.get_attr_id(
502
+ str(k), self.xml_type[val_type], node_or_edge, default, mode
503
+ )
504
+ e = Element("attvalue")
505
+ e.attrib["for"] = attr_id
506
+ if isinstance(v, bool):
507
+ e.attrib["value"] = str(v).lower()
508
+ else:
509
+ e.attrib["value"] = str(v)
510
+ # Handle float nan, inf, -inf differently
511
+ if val_type == float:
512
+ if e.attrib["value"] == "inf":
513
+ e.attrib["value"] = "INF"
514
+ elif e.attrib["value"] == "nan":
515
+ e.attrib["value"] = "NaN"
516
+ elif e.attrib["value"] == "-inf":
517
+ e.attrib["value"] = "-INF"
518
+ attvalues.append(e)
519
+ xml_obj.append(attvalues)
520
+ return data
521
+
522
+ def get_attr_id(self, title, attr_type, edge_or_node, default, mode):
523
+ # find the id of the attribute or generate a new id
524
+ try:
525
+ return self.attr[edge_or_node][mode][title]
526
+ except KeyError:
527
+ # generate new id
528
+ new_id = str(next(self.attr_id))
529
+ self.attr[edge_or_node][mode][title] = new_id
530
+ attr_kwargs = {"id": new_id, "title": title, "type": attr_type}
531
+ attribute = Element("attribute", **attr_kwargs)
532
+ # add subelement for data default value if present
533
+ default_title = default.get(title)
534
+ if default_title is not None:
535
+ default_element = Element("default")
536
+ default_element.text = str(default_title)
537
+ attribute.append(default_element)
538
+ # new insert it into the XML
539
+ attributes_element = None
540
+ for a in self.graph_element.findall("attributes"):
541
+ # find existing attributes element by class and mode
542
+ a_class = a.get("class")
543
+ a_mode = a.get("mode", "static")
544
+ if a_class == edge_or_node and a_mode == mode:
545
+ attributes_element = a
546
+ if attributes_element is None:
547
+ # create new attributes element
548
+ attr_kwargs = {"mode": mode, "class": edge_or_node}
549
+ attributes_element = Element("attributes", **attr_kwargs)
550
+ self.graph_element.insert(0, attributes_element)
551
+ attributes_element.append(attribute)
552
+ return new_id
553
+
554
+ def add_viz(self, element, node_data):
555
+ viz = node_data.pop("viz", False)
556
+ if viz:
557
+ color = viz.get("color")
558
+ if color is not None:
559
+ if self.VERSION == "1.1":
560
+ e = Element(
561
+ f"{{{self.NS_VIZ}}}color",
562
+ r=str(color.get("r")),
563
+ g=str(color.get("g")),
564
+ b=str(color.get("b")),
565
+ )
566
+ else:
567
+ e = Element(
568
+ f"{{{self.NS_VIZ}}}color",
569
+ r=str(color.get("r")),
570
+ g=str(color.get("g")),
571
+ b=str(color.get("b")),
572
+ a=str(color.get("a", 1.0)),
573
+ )
574
+ element.append(e)
575
+
576
+ size = viz.get("size")
577
+ if size is not None:
578
+ e = Element(f"{{{self.NS_VIZ}}}size", value=str(size))
579
+ element.append(e)
580
+
581
+ thickness = viz.get("thickness")
582
+ if thickness is not None:
583
+ e = Element(f"{{{self.NS_VIZ}}}thickness", value=str(thickness))
584
+ element.append(e)
585
+
586
+ shape = viz.get("shape")
587
+ if shape is not None:
588
+ if shape.startswith("http"):
589
+ e = Element(
590
+ f"{{{self.NS_VIZ}}}shape", value="image", uri=str(shape)
591
+ )
592
+ else:
593
+ e = Element(f"{{{self.NS_VIZ}}}shape", value=str(shape))
594
+ element.append(e)
595
+
596
+ position = viz.get("position")
597
+ if position is not None:
598
+ e = Element(
599
+ f"{{{self.NS_VIZ}}}position",
600
+ x=str(position.get("x")),
601
+ y=str(position.get("y")),
602
+ z=str(position.get("z")),
603
+ )
604
+ element.append(e)
605
+ return node_data
606
+
607
+ def add_parents(self, node_element, node_data):
608
+ parents = node_data.pop("parents", False)
609
+ if parents:
610
+ parents_element = Element("parents")
611
+ for p in parents:
612
+ e = Element("parent")
613
+ e.attrib["for"] = str(p)
614
+ parents_element.append(e)
615
+ node_element.append(parents_element)
616
+ return node_data
617
+
618
+ def add_slices(self, node_or_edge_element, node_or_edge_data):
619
+ slices = node_or_edge_data.pop("slices", False)
620
+ if slices:
621
+ slices_element = Element("slices")
622
+ for start, end in slices:
623
+ e = Element("slice", start=str(start), end=str(end))
624
+ slices_element.append(e)
625
+ node_or_edge_element.append(slices_element)
626
+ return node_or_edge_data
627
+
628
+ def add_spells(self, node_or_edge_element, node_or_edge_data):
629
+ spells = node_or_edge_data.pop("spells", False)
630
+ if spells:
631
+ spells_element = Element("spells")
632
+ for start, end in spells:
633
+ e = Element("spell")
634
+ if start is not None:
635
+ e.attrib["start"] = str(start)
636
+ self.alter_graph_mode_timeformat(start)
637
+ if end is not None:
638
+ e.attrib["end"] = str(end)
639
+ self.alter_graph_mode_timeformat(end)
640
+ spells_element.append(e)
641
+ node_or_edge_element.append(spells_element)
642
+ return node_or_edge_data
643
+
644
+ def alter_graph_mode_timeformat(self, start_or_end):
645
+ # If 'start' or 'end' appears, alter Graph mode to dynamic and
646
+ # set timeformat
647
+ if self.graph_element.get("mode") == "static":
648
+ if start_or_end is not None:
649
+ if isinstance(start_or_end, str):
650
+ timeformat = "date"
651
+ elif isinstance(start_or_end, float):
652
+ timeformat = "double"
653
+ elif isinstance(start_or_end, int):
654
+ timeformat = "long"
655
+ else:
656
+ raise nx.NetworkXError(
657
+ "timeformat should be of the type int, float or str"
658
+ )
659
+ self.graph_element.set("timeformat", timeformat)
660
+ self.graph_element.set("mode", "dynamic")
661
+
662
+ def write(self, fh):
663
+ # Serialize graph G in GEXF to the open fh
664
+ if self.prettyprint:
665
+ self.indent(self.xml)
666
+ document = ElementTree(self.xml)
667
+ document.write(fh, encoding=self.encoding, xml_declaration=True)
668
+
669
+ def indent(self, elem, level=0):
670
+ # in-place prettyprint formatter
671
+ i = "\n" + " " * level
672
+ if len(elem):
673
+ if not elem.text or not elem.text.strip():
674
+ elem.text = i + " "
675
+ if not elem.tail or not elem.tail.strip():
676
+ elem.tail = i
677
+ for elem in elem:
678
+ self.indent(elem, level + 1)
679
+ if not elem.tail or not elem.tail.strip():
680
+ elem.tail = i
681
+ else:
682
+ if level and (not elem.tail or not elem.tail.strip()):
683
+ elem.tail = i
684
+
685
+
686
+ class GEXFReader(GEXF):
687
+ # Class to read GEXF format files
688
+ # use read_gexf() function
689
+ def __init__(self, node_type=None, version="1.2draft"):
690
+ self.construct_types()
691
+ self.node_type = node_type
692
+ # assume simple graph and test for multigraph on read
693
+ self.simple_graph = True
694
+ self.set_version(version)
695
+
696
+ def __call__(self, stream):
697
+ self.xml = ElementTree(file=stream)
698
+ g = self.xml.find(f"{{{self.NS_GEXF}}}graph")
699
+ if g is not None:
700
+ return self.make_graph(g)
701
+ # try all the versions
702
+ for version in self.versions:
703
+ self.set_version(version)
704
+ g = self.xml.find(f"{{{self.NS_GEXF}}}graph")
705
+ if g is not None:
706
+ return self.make_graph(g)
707
+ raise nx.NetworkXError("No <graph> element in GEXF file.")
708
+
709
+ def make_graph(self, graph_xml):
710
+ # start with empty DiGraph or MultiDiGraph
711
+ edgedefault = graph_xml.get("defaultedgetype", None)
712
+ if edgedefault == "directed":
713
+ G = nx.MultiDiGraph()
714
+ else:
715
+ G = nx.MultiGraph()
716
+
717
+ # graph attributes
718
+ graph_name = graph_xml.get("name", "")
719
+ if graph_name != "":
720
+ G.graph["name"] = graph_name
721
+ graph_start = graph_xml.get("start")
722
+ if graph_start is not None:
723
+ G.graph["start"] = graph_start
724
+ graph_end = graph_xml.get("end")
725
+ if graph_end is not None:
726
+ G.graph["end"] = graph_end
727
+ graph_mode = graph_xml.get("mode", "")
728
+ if graph_mode == "dynamic":
729
+ G.graph["mode"] = "dynamic"
730
+ else:
731
+ G.graph["mode"] = "static"
732
+
733
+ # timeformat
734
+ self.timeformat = graph_xml.get("timeformat")
735
+ if self.timeformat == "date":
736
+ self.timeformat = "string"
737
+
738
+ # node and edge attributes
739
+ attributes_elements = graph_xml.findall(f"{{{self.NS_GEXF}}}attributes")
740
+ # dictionaries to hold attributes and attribute defaults
741
+ node_attr = {}
742
+ node_default = {}
743
+ edge_attr = {}
744
+ edge_default = {}
745
+ for a in attributes_elements:
746
+ attr_class = a.get("class")
747
+ if attr_class == "node":
748
+ na, nd = self.find_gexf_attributes(a)
749
+ node_attr.update(na)
750
+ node_default.update(nd)
751
+ G.graph["node_default"] = node_default
752
+ elif attr_class == "edge":
753
+ ea, ed = self.find_gexf_attributes(a)
754
+ edge_attr.update(ea)
755
+ edge_default.update(ed)
756
+ G.graph["edge_default"] = edge_default
757
+ else:
758
+ raise # unknown attribute class
759
+
760
+ # Hack to handle Gephi0.7beta bug
761
+ # add weight attribute
762
+ ea = {"weight": {"type": "double", "mode": "static", "title": "weight"}}
763
+ ed = {}
764
+ edge_attr.update(ea)
765
+ edge_default.update(ed)
766
+ G.graph["edge_default"] = edge_default
767
+
768
+ # add nodes
769
+ nodes_element = graph_xml.find(f"{{{self.NS_GEXF}}}nodes")
770
+ if nodes_element is not None:
771
+ for node_xml in nodes_element.findall(f"{{{self.NS_GEXF}}}node"):
772
+ self.add_node(G, node_xml, node_attr)
773
+
774
+ # add edges
775
+ edges_element = graph_xml.find(f"{{{self.NS_GEXF}}}edges")
776
+ if edges_element is not None:
777
+ for edge_xml in edges_element.findall(f"{{{self.NS_GEXF}}}edge"):
778
+ self.add_edge(G, edge_xml, edge_attr)
779
+
780
+ # switch to Graph or DiGraph if no parallel edges were found.
781
+ if self.simple_graph:
782
+ if G.is_directed():
783
+ G = nx.DiGraph(G)
784
+ else:
785
+ G = nx.Graph(G)
786
+ return G
787
+
788
+ def add_node(self, G, node_xml, node_attr, node_pid=None):
789
+ # add a single node with attributes to the graph
790
+
791
+ # get attributes and subattributues for node
792
+ data = self.decode_attr_elements(node_attr, node_xml)
793
+ data = self.add_parents(data, node_xml) # add any parents
794
+ if self.VERSION == "1.1":
795
+ data = self.add_slices(data, node_xml) # add slices
796
+ else:
797
+ data = self.add_spells(data, node_xml) # add spells
798
+ data = self.add_viz(data, node_xml) # add viz
799
+ data = self.add_start_end(data, node_xml) # add start/end
800
+
801
+ # find the node id and cast it to the appropriate type
802
+ node_id = node_xml.get("id")
803
+ if self.node_type is not None:
804
+ node_id = self.node_type(node_id)
805
+
806
+ # every node should have a label
807
+ node_label = node_xml.get("label")
808
+ data["label"] = node_label
809
+
810
+ # parent node id
811
+ node_pid = node_xml.get("pid", node_pid)
812
+ if node_pid is not None:
813
+ data["pid"] = node_pid
814
+
815
+ # check for subnodes, recursive
816
+ subnodes = node_xml.find(f"{{{self.NS_GEXF}}}nodes")
817
+ if subnodes is not None:
818
+ for node_xml in subnodes.findall(f"{{{self.NS_GEXF}}}node"):
819
+ self.add_node(G, node_xml, node_attr, node_pid=node_id)
820
+
821
+ G.add_node(node_id, **data)
822
+
823
+ def add_start_end(self, data, xml):
824
+ # start and end times
825
+ ttype = self.timeformat
826
+ node_start = xml.get("start")
827
+ if node_start is not None:
828
+ data["start"] = self.python_type[ttype](node_start)
829
+ node_end = xml.get("end")
830
+ if node_end is not None:
831
+ data["end"] = self.python_type[ttype](node_end)
832
+ return data
833
+
834
+ def add_viz(self, data, node_xml):
835
+ # add viz element for node
836
+ viz = {}
837
+ color = node_xml.find(f"{{{self.NS_VIZ}}}color")
838
+ if color is not None:
839
+ if self.VERSION == "1.1":
840
+ viz["color"] = {
841
+ "r": int(color.get("r")),
842
+ "g": int(color.get("g")),
843
+ "b": int(color.get("b")),
844
+ }
845
+ else:
846
+ viz["color"] = {
847
+ "r": int(color.get("r")),
848
+ "g": int(color.get("g")),
849
+ "b": int(color.get("b")),
850
+ "a": float(color.get("a", 1)),
851
+ }
852
+
853
+ size = node_xml.find(f"{{{self.NS_VIZ}}}size")
854
+ if size is not None:
855
+ viz["size"] = float(size.get("value"))
856
+
857
+ thickness = node_xml.find(f"{{{self.NS_VIZ}}}thickness")
858
+ if thickness is not None:
859
+ viz["thickness"] = float(thickness.get("value"))
860
+
861
+ shape = node_xml.find(f"{{{self.NS_VIZ}}}shape")
862
+ if shape is not None:
863
+ viz["shape"] = shape.get("shape")
864
+ if viz["shape"] == "image":
865
+ viz["shape"] = shape.get("uri")
866
+
867
+ position = node_xml.find(f"{{{self.NS_VIZ}}}position")
868
+ if position is not None:
869
+ viz["position"] = {
870
+ "x": float(position.get("x", 0)),
871
+ "y": float(position.get("y", 0)),
872
+ "z": float(position.get("z", 0)),
873
+ }
874
+
875
+ if len(viz) > 0:
876
+ data["viz"] = viz
877
+ return data
878
+
879
+ def add_parents(self, data, node_xml):
880
+ parents_element = node_xml.find(f"{{{self.NS_GEXF}}}parents")
881
+ if parents_element is not None:
882
+ data["parents"] = []
883
+ for p in parents_element.findall(f"{{{self.NS_GEXF}}}parent"):
884
+ parent = p.get("for")
885
+ data["parents"].append(parent)
886
+ return data
887
+
888
+ def add_slices(self, data, node_or_edge_xml):
889
+ slices_element = node_or_edge_xml.find(f"{{{self.NS_GEXF}}}slices")
890
+ if slices_element is not None:
891
+ data["slices"] = []
892
+ for s in slices_element.findall(f"{{{self.NS_GEXF}}}slice"):
893
+ start = s.get("start")
894
+ end = s.get("end")
895
+ data["slices"].append((start, end))
896
+ return data
897
+
898
+ def add_spells(self, data, node_or_edge_xml):
899
+ spells_element = node_or_edge_xml.find(f"{{{self.NS_GEXF}}}spells")
900
+ if spells_element is not None:
901
+ data["spells"] = []
902
+ ttype = self.timeformat
903
+ for s in spells_element.findall(f"{{{self.NS_GEXF}}}spell"):
904
+ start = self.python_type[ttype](s.get("start"))
905
+ end = self.python_type[ttype](s.get("end"))
906
+ data["spells"].append((start, end))
907
+ return data
908
+
909
+ def add_edge(self, G, edge_element, edge_attr):
910
+ # add an edge to the graph
911
+
912
+ # raise error if we find mixed directed and undirected edges
913
+ edge_direction = edge_element.get("type")
914
+ if G.is_directed() and edge_direction == "undirected":
915
+ raise nx.NetworkXError("Undirected edge found in directed graph.")
916
+ if (not G.is_directed()) and edge_direction == "directed":
917
+ raise nx.NetworkXError("Directed edge found in undirected graph.")
918
+
919
+ # Get source and target and recast type if required
920
+ source = edge_element.get("source")
921
+ target = edge_element.get("target")
922
+ if self.node_type is not None:
923
+ source = self.node_type(source)
924
+ target = self.node_type(target)
925
+
926
+ data = self.decode_attr_elements(edge_attr, edge_element)
927
+ data = self.add_start_end(data, edge_element)
928
+
929
+ if self.VERSION == "1.1":
930
+ data = self.add_slices(data, edge_element) # add slices
931
+ else:
932
+ data = self.add_spells(data, edge_element) # add spells
933
+
934
+ # GEXF stores edge ids as an attribute
935
+ # NetworkX uses them as keys in multigraphs
936
+ # if networkx_key is not specified as an attribute
937
+ edge_id = edge_element.get("id")
938
+ if edge_id is not None:
939
+ data["id"] = edge_id
940
+
941
+ # check if there is a 'multigraph_key' and use that as edge_id
942
+ multigraph_key = data.pop("networkx_key", None)
943
+ if multigraph_key is not None:
944
+ edge_id = multigraph_key
945
+
946
+ weight = edge_element.get("weight")
947
+ if weight is not None:
948
+ data["weight"] = float(weight)
949
+
950
+ edge_label = edge_element.get("label")
951
+ if edge_label is not None:
952
+ data["label"] = edge_label
953
+
954
+ if G.has_edge(source, target):
955
+ # seen this edge before - this is a multigraph
956
+ self.simple_graph = False
957
+ G.add_edge(source, target, key=edge_id, **data)
958
+ if edge_direction == "mutual":
959
+ G.add_edge(target, source, key=edge_id, **data)
960
+
961
+ def decode_attr_elements(self, gexf_keys, obj_xml):
962
+ # Use the key information to decode the attr XML
963
+ attr = {}
964
+ # look for outer '<attvalues>' element
965
+ attr_element = obj_xml.find(f"{{{self.NS_GEXF}}}attvalues")
966
+ if attr_element is not None:
967
+ # loop over <attvalue> elements
968
+ for a in attr_element.findall(f"{{{self.NS_GEXF}}}attvalue"):
969
+ key = a.get("for") # for is required
970
+ try: # should be in our gexf_keys dictionary
971
+ title = gexf_keys[key]["title"]
972
+ except KeyError as err:
973
+ raise nx.NetworkXError(f"No attribute defined for={key}.") from err
974
+ atype = gexf_keys[key]["type"]
975
+ value = a.get("value")
976
+ if atype == "boolean":
977
+ value = self.convert_bool[value]
978
+ else:
979
+ value = self.python_type[atype](value)
980
+ if gexf_keys[key]["mode"] == "dynamic":
981
+ # for dynamic graphs use list of three-tuples
982
+ # [(value1,start1,end1), (value2,start2,end2), etc]
983
+ ttype = self.timeformat
984
+ start = self.python_type[ttype](a.get("start"))
985
+ end = self.python_type[ttype](a.get("end"))
986
+ if title in attr:
987
+ attr[title].append((value, start, end))
988
+ else:
989
+ attr[title] = [(value, start, end)]
990
+ else:
991
+ # for static graphs just assign the value
992
+ attr[title] = value
993
+ return attr
994
+
995
+ def find_gexf_attributes(self, attributes_element):
996
+ # Extract all the attributes and defaults
997
+ attrs = {}
998
+ defaults = {}
999
+ mode = attributes_element.get("mode")
1000
+ for k in attributes_element.findall(f"{{{self.NS_GEXF}}}attribute"):
1001
+ attr_id = k.get("id")
1002
+ title = k.get("title")
1003
+ atype = k.get("type")
1004
+ attrs[attr_id] = {"title": title, "type": atype, "mode": mode}
1005
+ # check for the 'default' subelement of key element and add
1006
+ default = k.find(f"{{{self.NS_GEXF}}}default")
1007
+ if default is not None:
1008
+ if atype == "boolean":
1009
+ value = self.convert_bool[default.text]
1010
+ else:
1011
+ value = self.python_type[atype](default.text)
1012
+ defaults[title] = value
1013
+ return attrs, defaults
1014
+
1015
+
1016
+ def relabel_gexf_graph(G):
1017
+ """Relabel graph using "label" node keyword for node label.
1018
+
1019
+ Parameters
1020
+ ----------
1021
+ G : graph
1022
+ A NetworkX graph read from GEXF data
1023
+
1024
+ Returns
1025
+ -------
1026
+ H : graph
1027
+ A NetworkX graph with relabeled nodes
1028
+
1029
+ Raises
1030
+ ------
1031
+ NetworkXError
1032
+ If node labels are missing or not unique while relabel=True.
1033
+
1034
+ Notes
1035
+ -----
1036
+ This function relabels the nodes in a NetworkX graph with the
1037
+ "label" attribute. It also handles relabeling the specific GEXF
1038
+ node attributes "parents", and "pid".
1039
+ """
1040
+ # build mapping of node labels, do some error checking
1041
+ try:
1042
+ mapping = [(u, G.nodes[u]["label"]) for u in G]
1043
+ except KeyError as err:
1044
+ raise nx.NetworkXError(
1045
+ "Failed to relabel nodes: missing node labels found. Use relabel=False."
1046
+ ) from err
1047
+ x, y = zip(*mapping)
1048
+ if len(set(y)) != len(G):
1049
+ raise nx.NetworkXError(
1050
+ "Failed to relabel nodes: "
1051
+ "duplicate node labels found. "
1052
+ "Use relabel=False."
1053
+ )
1054
+ mapping = dict(mapping)
1055
+ H = nx.relabel_nodes(G, mapping)
1056
+ # relabel attributes
1057
+ for n in G:
1058
+ m = mapping[n]
1059
+ H.nodes[m]["id"] = n
1060
+ H.nodes[m].pop("label")
1061
+ if "pid" in H.nodes[m]:
1062
+ H.nodes[m]["pid"] = mapping[G.nodes[n]["pid"]]
1063
+ if "parents" in H.nodes[m]:
1064
+ H.nodes[m]["parents"] = [mapping[p] for p in G.nodes[n]["parents"]]
1065
+ return H
pythonProject/.venv/Lib/site-packages/networkx/readwrite/gml.py ADDED
@@ -0,0 +1,878 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Read graphs in GML format.
3
+
4
+ "GML, the Graph Modelling Language, is our proposal for a portable
5
+ file format for graphs. GML's key features are portability, simple
6
+ syntax, extensibility and flexibility. A GML file consists of a
7
+ hierarchical key-value lists. Graphs can be annotated with arbitrary
8
+ data structures. The idea for a common file format was born at the
9
+ GD'95; this proposal is the outcome of many discussions. GML is the
10
+ standard file format in the Graphlet graph editor system. It has been
11
+ overtaken and adapted by several other systems for drawing graphs."
12
+
13
+ GML files are stored using a 7-bit ASCII encoding with any extended
14
+ ASCII characters (iso8859-1) appearing as HTML character entities.
15
+ You will need to give some thought into how the exported data should
16
+ interact with different languages and even different Python versions.
17
+ Re-importing from gml is also a concern.
18
+
19
+ Without specifying a `stringizer`/`destringizer`, the code is capable of
20
+ writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
21
+ specification. For writing other data types, and for reading data other
22
+ than `str` you need to explicitly supply a `stringizer`/`destringizer`.
23
+
24
+ For additional documentation on the GML file format, please see the
25
+ `GML website <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
26
+
27
+ Several example graphs in GML format may be found on Mark Newman's
28
+ `Network data page <http://www-personal.umich.edu/~mejn/netdata/>`_.
29
+ """
30
+ import html.entities as htmlentitydefs
31
+ import re
32
+ import warnings
33
+ from ast import literal_eval
34
+ from collections import defaultdict
35
+ from enum import Enum
36
+ from io import StringIO
37
+ from typing import Any, NamedTuple
38
+
39
+ import networkx as nx
40
+ from networkx.exception import NetworkXError
41
+ from networkx.utils import open_file
42
+
43
+ __all__ = ["read_gml", "parse_gml", "generate_gml", "write_gml"]
44
+
45
+
46
+ def escape(text):
47
+ """Use XML character references to escape characters.
48
+
49
+ Use XML character references for unprintable or non-ASCII
50
+ characters, double quotes and ampersands in a string
51
+ """
52
+
53
+ def fixup(m):
54
+ ch = m.group(0)
55
+ return "&#" + str(ord(ch)) + ";"
56
+
57
+ text = re.sub('[^ -~]|[&"]', fixup, text)
58
+ return text if isinstance(text, str) else str(text)
59
+
60
+
61
+ def unescape(text):
62
+ """Replace XML character references with the referenced characters"""
63
+
64
+ def fixup(m):
65
+ text = m.group(0)
66
+ if text[1] == "#":
67
+ # Character reference
68
+ if text[2] == "x":
69
+ code = int(text[3:-1], 16)
70
+ else:
71
+ code = int(text[2:-1])
72
+ else:
73
+ # Named entity
74
+ try:
75
+ code = htmlentitydefs.name2codepoint[text[1:-1]]
76
+ except KeyError:
77
+ return text # leave unchanged
78
+ try:
79
+ return chr(code)
80
+ except (ValueError, OverflowError):
81
+ return text # leave unchanged
82
+
83
+ return re.sub("&(?:[0-9A-Za-z]+|#(?:[0-9]+|x[0-9A-Fa-f]+));", fixup, text)
84
+
85
+
86
+ def literal_destringizer(rep):
87
+ """Convert a Python literal to the value it represents.
88
+
89
+ Parameters
90
+ ----------
91
+ rep : string
92
+ A Python literal.
93
+
94
+ Returns
95
+ -------
96
+ value : object
97
+ The value of the Python literal.
98
+
99
+ Raises
100
+ ------
101
+ ValueError
102
+ If `rep` is not a Python literal.
103
+ """
104
+ if isinstance(rep, str):
105
+ orig_rep = rep
106
+ try:
107
+ return literal_eval(rep)
108
+ except SyntaxError as err:
109
+ raise ValueError(f"{orig_rep!r} is not a valid Python literal") from err
110
+ else:
111
+ raise ValueError(f"{rep!r} is not a string")
112
+
113
+
114
+ @open_file(0, mode="rb")
115
+ @nx._dispatchable(graphs=None, returns_graph=True)
116
+ def read_gml(path, label="label", destringizer=None):
117
+ """Read graph in GML format from `path`.
118
+
119
+ Parameters
120
+ ----------
121
+ path : filename or filehandle
122
+ The filename or filehandle to read from.
123
+
124
+ label : string, optional
125
+ If not None, the parsed nodes will be renamed according to node
126
+ attributes indicated by `label`. Default value: 'label'.
127
+
128
+ destringizer : callable, optional
129
+ A `destringizer` that recovers values stored as strings in GML. If it
130
+ cannot convert a string to a value, a `ValueError` is raised. Default
131
+ value : None.
132
+
133
+ Returns
134
+ -------
135
+ G : NetworkX graph
136
+ The parsed graph.
137
+
138
+ Raises
139
+ ------
140
+ NetworkXError
141
+ If the input cannot be parsed.
142
+
143
+ See Also
144
+ --------
145
+ write_gml, parse_gml
146
+ literal_destringizer
147
+
148
+ Notes
149
+ -----
150
+ GML files are stored using a 7-bit ASCII encoding with any extended
151
+ ASCII characters (iso8859-1) appearing as HTML character entities.
152
+ Without specifying a `stringizer`/`destringizer`, the code is capable of
153
+ writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
154
+ specification. For writing other data types, and for reading data other
155
+ than `str` you need to explicitly supply a `stringizer`/`destringizer`.
156
+
157
+ For additional documentation on the GML file format, please see the
158
+ `GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
159
+
160
+ See the module docstring :mod:`networkx.readwrite.gml` for more details.
161
+
162
+ Examples
163
+ --------
164
+ >>> G = nx.path_graph(4)
165
+ >>> nx.write_gml(G, "test.gml")
166
+
167
+ GML values are interpreted as strings by default:
168
+
169
+ >>> H = nx.read_gml("test.gml")
170
+ >>> H.nodes
171
+ NodeView(('0', '1', '2', '3'))
172
+
173
+ When a `destringizer` is provided, GML values are converted to the provided type.
174
+ For example, integer nodes can be recovered as shown below:
175
+
176
+ >>> J = nx.read_gml("test.gml", destringizer=int)
177
+ >>> J.nodes
178
+ NodeView((0, 1, 2, 3))
179
+
180
+ """
181
+
182
+ def filter_lines(lines):
183
+ for line in lines:
184
+ try:
185
+ line = line.decode("ascii")
186
+ except UnicodeDecodeError as err:
187
+ raise NetworkXError("input is not ASCII-encoded") from err
188
+ if not isinstance(line, str):
189
+ lines = str(lines)
190
+ if line and line[-1] == "\n":
191
+ line = line[:-1]
192
+ yield line
193
+
194
+ G = parse_gml_lines(filter_lines(path), label, destringizer)
195
+ return G
196
+
197
+
198
+ @nx._dispatchable(graphs=None, returns_graph=True)
199
+ def parse_gml(lines, label="label", destringizer=None):
200
+ """Parse GML graph from a string or iterable.
201
+
202
+ Parameters
203
+ ----------
204
+ lines : string or iterable of strings
205
+ Data in GML format.
206
+
207
+ label : string, optional
208
+ If not None, the parsed nodes will be renamed according to node
209
+ attributes indicated by `label`. Default value: 'label'.
210
+
211
+ destringizer : callable, optional
212
+ A `destringizer` that recovers values stored as strings in GML. If it
213
+ cannot convert a string to a value, a `ValueError` is raised. Default
214
+ value : None.
215
+
216
+ Returns
217
+ -------
218
+ G : NetworkX graph
219
+ The parsed graph.
220
+
221
+ Raises
222
+ ------
223
+ NetworkXError
224
+ If the input cannot be parsed.
225
+
226
+ See Also
227
+ --------
228
+ write_gml, read_gml
229
+
230
+ Notes
231
+ -----
232
+ This stores nested GML attributes as dictionaries in the NetworkX graph,
233
+ node, and edge attribute structures.
234
+
235
+ GML files are stored using a 7-bit ASCII encoding with any extended
236
+ ASCII characters (iso8859-1) appearing as HTML character entities.
237
+ Without specifying a `stringizer`/`destringizer`, the code is capable of
238
+ writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
239
+ specification. For writing other data types, and for reading data other
240
+ than `str` you need to explicitly supply a `stringizer`/`destringizer`.
241
+
242
+ For additional documentation on the GML file format, please see the
243
+ `GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
244
+
245
+ See the module docstring :mod:`networkx.readwrite.gml` for more details.
246
+ """
247
+
248
+ def decode_line(line):
249
+ if isinstance(line, bytes):
250
+ try:
251
+ line.decode("ascii")
252
+ except UnicodeDecodeError as err:
253
+ raise NetworkXError("input is not ASCII-encoded") from err
254
+ if not isinstance(line, str):
255
+ line = str(line)
256
+ return line
257
+
258
+ def filter_lines(lines):
259
+ if isinstance(lines, str):
260
+ lines = decode_line(lines)
261
+ lines = lines.splitlines()
262
+ yield from lines
263
+ else:
264
+ for line in lines:
265
+ line = decode_line(line)
266
+ if line and line[-1] == "\n":
267
+ line = line[:-1]
268
+ if line.find("\n") != -1:
269
+ raise NetworkXError("input line contains newline")
270
+ yield line
271
+
272
+ G = parse_gml_lines(filter_lines(lines), label, destringizer)
273
+ return G
274
+
275
+
276
+ class Pattern(Enum):
277
+ """encodes the index of each token-matching pattern in `tokenize`."""
278
+
279
+ KEYS = 0
280
+ REALS = 1
281
+ INTS = 2
282
+ STRINGS = 3
283
+ DICT_START = 4
284
+ DICT_END = 5
285
+ COMMENT_WHITESPACE = 6
286
+
287
+
288
+ class Token(NamedTuple):
289
+ category: Pattern
290
+ value: Any
291
+ line: int
292
+ position: int
293
+
294
+
295
+ LIST_START_VALUE = "_networkx_list_start"
296
+
297
+
298
+ def parse_gml_lines(lines, label, destringizer):
299
+ """Parse GML `lines` into a graph."""
300
+
301
+ def tokenize():
302
+ patterns = [
303
+ r"[A-Za-z][0-9A-Za-z_]*\b", # keys
304
+ # reals
305
+ r"[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*|INF)(?:[Ee][+-]?[0-9]+)?",
306
+ r"[+-]?[0-9]+", # ints
307
+ r'".*?"', # strings
308
+ r"\[", # dict start
309
+ r"\]", # dict end
310
+ r"#.*$|\s+", # comments and whitespaces
311
+ ]
312
+ tokens = re.compile("|".join(f"({pattern})" for pattern in patterns))
313
+ lineno = 0
314
+ multilines = [] # entries spread across multiple lines
315
+ for line in lines:
316
+ pos = 0
317
+
318
+ # deal with entries spread across multiple lines
319
+ #
320
+ # should we actually have to deal with escaped "s then do it here
321
+ if multilines:
322
+ multilines.append(line.strip())
323
+ if line[-1] == '"': # closing multiline entry
324
+ # multiline entries will be joined by space. cannot
325
+ # reintroduce newlines as this will break the tokenizer
326
+ line = " ".join(multilines)
327
+ multilines = []
328
+ else: # continued multiline entry
329
+ lineno += 1
330
+ continue
331
+ else:
332
+ if line.count('"') == 1: # opening multiline entry
333
+ if line.strip()[0] != '"' and line.strip()[-1] != '"':
334
+ # since we expect something like key "value", the " should not be found at ends
335
+ # otherwise tokenizer will pick up the formatting mistake.
336
+ multilines = [line.rstrip()]
337
+ lineno += 1
338
+ continue
339
+
340
+ length = len(line)
341
+
342
+ while pos < length:
343
+ match = tokens.match(line, pos)
344
+ if match is None:
345
+ m = f"cannot tokenize {line[pos:]} at ({lineno + 1}, {pos + 1})"
346
+ raise NetworkXError(m)
347
+ for i in range(len(patterns)):
348
+ group = match.group(i + 1)
349
+ if group is not None:
350
+ if i == 0: # keys
351
+ value = group.rstrip()
352
+ elif i == 1: # reals
353
+ value = float(group)
354
+ elif i == 2: # ints
355
+ value = int(group)
356
+ else:
357
+ value = group
358
+ if i != 6: # comments and whitespaces
359
+ yield Token(Pattern(i), value, lineno + 1, pos + 1)
360
+ pos += len(group)
361
+ break
362
+ lineno += 1
363
+ yield Token(None, None, lineno + 1, 1) # EOF
364
+
365
+ def unexpected(curr_token, expected):
366
+ category, value, lineno, pos = curr_token
367
+ value = repr(value) if value is not None else "EOF"
368
+ raise NetworkXError(f"expected {expected}, found {value} at ({lineno}, {pos})")
369
+
370
+ def consume(curr_token, category, expected):
371
+ if curr_token.category == category:
372
+ return next(tokens)
373
+ unexpected(curr_token, expected)
374
+
375
+ def parse_kv(curr_token):
376
+ dct = defaultdict(list)
377
+ while curr_token.category == Pattern.KEYS:
378
+ key = curr_token.value
379
+ curr_token = next(tokens)
380
+ category = curr_token.category
381
+ if category == Pattern.REALS or category == Pattern.INTS:
382
+ value = curr_token.value
383
+ curr_token = next(tokens)
384
+ elif category == Pattern.STRINGS:
385
+ value = unescape(curr_token.value[1:-1])
386
+ if destringizer:
387
+ try:
388
+ value = destringizer(value)
389
+ except ValueError:
390
+ pass
391
+ # Special handling for empty lists and tuples
392
+ if value == "()":
393
+ value = ()
394
+ if value == "[]":
395
+ value = []
396
+ curr_token = next(tokens)
397
+ elif category == Pattern.DICT_START:
398
+ curr_token, value = parse_dict(curr_token)
399
+ else:
400
+ # Allow for string convertible id and label values
401
+ if key in ("id", "label", "source", "target"):
402
+ try:
403
+ # String convert the token value
404
+ value = unescape(str(curr_token.value))
405
+ if destringizer:
406
+ try:
407
+ value = destringizer(value)
408
+ except ValueError:
409
+ pass
410
+ curr_token = next(tokens)
411
+ except Exception:
412
+ msg = (
413
+ "an int, float, string, '[' or string"
414
+ + " convertible ASCII value for node id or label"
415
+ )
416
+ unexpected(curr_token, msg)
417
+ # Special handling for nan and infinity. Since the gml language
418
+ # defines unquoted strings as keys, the numeric and string branches
419
+ # are skipped and we end up in this special branch, so we need to
420
+ # convert the current token value to a float for NAN and plain INF.
421
+ # +/-INF are handled in the pattern for 'reals' in tokenize(). This
422
+ # allows labels and values to be nan or infinity, but not keys.
423
+ elif curr_token.value in {"NAN", "INF"}:
424
+ value = float(curr_token.value)
425
+ curr_token = next(tokens)
426
+ else: # Otherwise error out
427
+ unexpected(curr_token, "an int, float, string or '['")
428
+ dct[key].append(value)
429
+
430
+ def clean_dict_value(value):
431
+ if not isinstance(value, list):
432
+ return value
433
+ if len(value) == 1:
434
+ return value[0]
435
+ if value[0] == LIST_START_VALUE:
436
+ return value[1:]
437
+ return value
438
+
439
+ dct = {key: clean_dict_value(value) for key, value in dct.items()}
440
+ return curr_token, dct
441
+
442
+ def parse_dict(curr_token):
443
+ # dict start
444
+ curr_token = consume(curr_token, Pattern.DICT_START, "'['")
445
+ # dict contents
446
+ curr_token, dct = parse_kv(curr_token)
447
+ # dict end
448
+ curr_token = consume(curr_token, Pattern.DICT_END, "']'")
449
+ return curr_token, dct
450
+
451
+ def parse_graph():
452
+ curr_token, dct = parse_kv(next(tokens))
453
+ if curr_token.category is not None: # EOF
454
+ unexpected(curr_token, "EOF")
455
+ if "graph" not in dct:
456
+ raise NetworkXError("input contains no graph")
457
+ graph = dct["graph"]
458
+ if isinstance(graph, list):
459
+ raise NetworkXError("input contains more than one graph")
460
+ return graph
461
+
462
+ tokens = tokenize()
463
+ graph = parse_graph()
464
+
465
+ directed = graph.pop("directed", False)
466
+ multigraph = graph.pop("multigraph", False)
467
+ if not multigraph:
468
+ G = nx.DiGraph() if directed else nx.Graph()
469
+ else:
470
+ G = nx.MultiDiGraph() if directed else nx.MultiGraph()
471
+ graph_attr = {k: v for k, v in graph.items() if k not in ("node", "edge")}
472
+ G.graph.update(graph_attr)
473
+
474
+ def pop_attr(dct, category, attr, i):
475
+ try:
476
+ return dct.pop(attr)
477
+ except KeyError as err:
478
+ raise NetworkXError(f"{category} #{i} has no {attr!r} attribute") from err
479
+
480
+ nodes = graph.get("node", [])
481
+ mapping = {}
482
+ node_labels = set()
483
+ for i, node in enumerate(nodes if isinstance(nodes, list) else [nodes]):
484
+ id = pop_attr(node, "node", "id", i)
485
+ if id in G:
486
+ raise NetworkXError(f"node id {id!r} is duplicated")
487
+ if label is not None and label != "id":
488
+ node_label = pop_attr(node, "node", label, i)
489
+ if node_label in node_labels:
490
+ raise NetworkXError(f"node label {node_label!r} is duplicated")
491
+ node_labels.add(node_label)
492
+ mapping[id] = node_label
493
+ G.add_node(id, **node)
494
+
495
+ edges = graph.get("edge", [])
496
+ for i, edge in enumerate(edges if isinstance(edges, list) else [edges]):
497
+ source = pop_attr(edge, "edge", "source", i)
498
+ target = pop_attr(edge, "edge", "target", i)
499
+ if source not in G:
500
+ raise NetworkXError(f"edge #{i} has undefined source {source!r}")
501
+ if target not in G:
502
+ raise NetworkXError(f"edge #{i} has undefined target {target!r}")
503
+ if not multigraph:
504
+ if not G.has_edge(source, target):
505
+ G.add_edge(source, target, **edge)
506
+ else:
507
+ arrow = "->" if directed else "--"
508
+ msg = f"edge #{i} ({source!r}{arrow}{target!r}) is duplicated"
509
+ raise nx.NetworkXError(msg)
510
+ else:
511
+ key = edge.pop("key", None)
512
+ if key is not None and G.has_edge(source, target, key):
513
+ arrow = "->" if directed else "--"
514
+ msg = f"edge #{i} ({source!r}{arrow}{target!r}, {key!r})"
515
+ msg2 = 'Hint: If multigraph add "multigraph 1" to file header.'
516
+ raise nx.NetworkXError(msg + " is duplicated\n" + msg2)
517
+ G.add_edge(source, target, key, **edge)
518
+
519
+ if label is not None and label != "id":
520
+ G = nx.relabel_nodes(G, mapping)
521
+ return G
522
+
523
+
524
+ def literal_stringizer(value):
525
+ """Convert a `value` to a Python literal in GML representation.
526
+
527
+ Parameters
528
+ ----------
529
+ value : object
530
+ The `value` to be converted to GML representation.
531
+
532
+ Returns
533
+ -------
534
+ rep : string
535
+ A double-quoted Python literal representing value. Unprintable
536
+ characters are replaced by XML character references.
537
+
538
+ Raises
539
+ ------
540
+ ValueError
541
+ If `value` cannot be converted to GML.
542
+
543
+ Notes
544
+ -----
545
+ The original value can be recovered using the
546
+ :func:`networkx.readwrite.gml.literal_destringizer` function.
547
+ """
548
+
549
+ def stringize(value):
550
+ if isinstance(value, int | bool) or value is None:
551
+ if value is True: # GML uses 1/0 for boolean values.
552
+ buf.write(str(1))
553
+ elif value is False:
554
+ buf.write(str(0))
555
+ else:
556
+ buf.write(str(value))
557
+ elif isinstance(value, str):
558
+ text = repr(value)
559
+ if text[0] != "u":
560
+ try:
561
+ value.encode("latin1")
562
+ except UnicodeEncodeError:
563
+ text = "u" + text
564
+ buf.write(text)
565
+ elif isinstance(value, float | complex | str | bytes):
566
+ buf.write(repr(value))
567
+ elif isinstance(value, list):
568
+ buf.write("[")
569
+ first = True
570
+ for item in value:
571
+ if not first:
572
+ buf.write(",")
573
+ else:
574
+ first = False
575
+ stringize(item)
576
+ buf.write("]")
577
+ elif isinstance(value, tuple):
578
+ if len(value) > 1:
579
+ buf.write("(")
580
+ first = True
581
+ for item in value:
582
+ if not first:
583
+ buf.write(",")
584
+ else:
585
+ first = False
586
+ stringize(item)
587
+ buf.write(")")
588
+ elif value:
589
+ buf.write("(")
590
+ stringize(value[0])
591
+ buf.write(",)")
592
+ else:
593
+ buf.write("()")
594
+ elif isinstance(value, dict):
595
+ buf.write("{")
596
+ first = True
597
+ for key, value in value.items():
598
+ if not first:
599
+ buf.write(",")
600
+ else:
601
+ first = False
602
+ stringize(key)
603
+ buf.write(":")
604
+ stringize(value)
605
+ buf.write("}")
606
+ elif isinstance(value, set):
607
+ buf.write("{")
608
+ first = True
609
+ for item in value:
610
+ if not first:
611
+ buf.write(",")
612
+ else:
613
+ first = False
614
+ stringize(item)
615
+ buf.write("}")
616
+ else:
617
+ msg = f"{value!r} cannot be converted into a Python literal"
618
+ raise ValueError(msg)
619
+
620
+ buf = StringIO()
621
+ stringize(value)
622
+ return buf.getvalue()
623
+
624
+
625
+ def generate_gml(G, stringizer=None):
626
+ r"""Generate a single entry of the graph `G` in GML format.
627
+
628
+ Parameters
629
+ ----------
630
+ G : NetworkX graph
631
+ The graph to be converted to GML.
632
+
633
+ stringizer : callable, optional
634
+ A `stringizer` which converts non-int/non-float/non-dict values into
635
+ strings. If it cannot convert a value into a string, it should raise a
636
+ `ValueError` to indicate that. Default value: None.
637
+
638
+ Returns
639
+ -------
640
+ lines: generator of strings
641
+ Lines of GML data. Newlines are not appended.
642
+
643
+ Raises
644
+ ------
645
+ NetworkXError
646
+ If `stringizer` cannot convert a value into a string, or the value to
647
+ convert is not a string while `stringizer` is None.
648
+
649
+ See Also
650
+ --------
651
+ literal_stringizer
652
+
653
+ Notes
654
+ -----
655
+ Graph attributes named 'directed', 'multigraph', 'node' or
656
+ 'edge', node attributes named 'id' or 'label', edge attributes
657
+ named 'source' or 'target' (or 'key' if `G` is a multigraph)
658
+ are ignored because these attribute names are used to encode the graph
659
+ structure.
660
+
661
+ GML files are stored using a 7-bit ASCII encoding with any extended
662
+ ASCII characters (iso8859-1) appearing as HTML character entities.
663
+ Without specifying a `stringizer`/`destringizer`, the code is capable of
664
+ writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
665
+ specification. For writing other data types, and for reading data other
666
+ than `str` you need to explicitly supply a `stringizer`/`destringizer`.
667
+
668
+ For additional documentation on the GML file format, please see the
669
+ `GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
670
+
671
+ See the module docstring :mod:`networkx.readwrite.gml` for more details.
672
+
673
+ Examples
674
+ --------
675
+ >>> G = nx.Graph()
676
+ >>> G.add_node("1")
677
+ >>> print("\n".join(nx.generate_gml(G)))
678
+ graph [
679
+ node [
680
+ id 0
681
+ label "1"
682
+ ]
683
+ ]
684
+ >>> G = nx.MultiGraph([("a", "b"), ("a", "b")])
685
+ >>> print("\n".join(nx.generate_gml(G)))
686
+ graph [
687
+ multigraph 1
688
+ node [
689
+ id 0
690
+ label "a"
691
+ ]
692
+ node [
693
+ id 1
694
+ label "b"
695
+ ]
696
+ edge [
697
+ source 0
698
+ target 1
699
+ key 0
700
+ ]
701
+ edge [
702
+ source 0
703
+ target 1
704
+ key 1
705
+ ]
706
+ ]
707
+ """
708
+ valid_keys = re.compile("^[A-Za-z][0-9A-Za-z_]*$")
709
+
710
+ def stringize(key, value, ignored_keys, indent, in_list=False):
711
+ if not isinstance(key, str):
712
+ raise NetworkXError(f"{key!r} is not a string")
713
+ if not valid_keys.match(key):
714
+ raise NetworkXError(f"{key!r} is not a valid key")
715
+ if not isinstance(key, str):
716
+ key = str(key)
717
+ if key not in ignored_keys:
718
+ if isinstance(value, int | bool):
719
+ if key == "label":
720
+ yield indent + key + ' "' + str(value) + '"'
721
+ elif value is True:
722
+ # python bool is an instance of int
723
+ yield indent + key + " 1"
724
+ elif value is False:
725
+ yield indent + key + " 0"
726
+ # GML only supports signed 32-bit integers
727
+ elif value < -(2**31) or value >= 2**31:
728
+ yield indent + key + ' "' + str(value) + '"'
729
+ else:
730
+ yield indent + key + " " + str(value)
731
+ elif isinstance(value, float):
732
+ text = repr(value).upper()
733
+ # GML matches INF to keys, so prepend + to INF. Use repr(float(*))
734
+ # instead of string literal to future proof against changes to repr.
735
+ if text == repr(float("inf")).upper():
736
+ text = "+" + text
737
+ else:
738
+ # GML requires that a real literal contain a decimal point, but
739
+ # repr may not output a decimal point when the mantissa is
740
+ # integral and hence needs fixing.
741
+ epos = text.rfind("E")
742
+ if epos != -1 and text.find(".", 0, epos) == -1:
743
+ text = text[:epos] + "." + text[epos:]
744
+ if key == "label":
745
+ yield indent + key + ' "' + text + '"'
746
+ else:
747
+ yield indent + key + " " + text
748
+ elif isinstance(value, dict):
749
+ yield indent + key + " ["
750
+ next_indent = indent + " "
751
+ for key, value in value.items():
752
+ yield from stringize(key, value, (), next_indent)
753
+ yield indent + "]"
754
+ elif isinstance(value, tuple) and key == "label":
755
+ yield indent + key + f" \"({','.join(repr(v) for v in value)})\""
756
+ elif isinstance(value, list | tuple) and key != "label" and not in_list:
757
+ if len(value) == 0:
758
+ yield indent + key + " " + f'"{value!r}"'
759
+ if len(value) == 1:
760
+ yield indent + key + " " + f'"{LIST_START_VALUE}"'
761
+ for val in value:
762
+ yield from stringize(key, val, (), indent, True)
763
+ else:
764
+ if stringizer:
765
+ try:
766
+ value = stringizer(value)
767
+ except ValueError as err:
768
+ raise NetworkXError(
769
+ f"{value!r} cannot be converted into a string"
770
+ ) from err
771
+ if not isinstance(value, str):
772
+ raise NetworkXError(f"{value!r} is not a string")
773
+ yield indent + key + ' "' + escape(value) + '"'
774
+
775
+ multigraph = G.is_multigraph()
776
+ yield "graph ["
777
+
778
+ # Output graph attributes
779
+ if G.is_directed():
780
+ yield " directed 1"
781
+ if multigraph:
782
+ yield " multigraph 1"
783
+ ignored_keys = {"directed", "multigraph", "node", "edge"}
784
+ for attr, value in G.graph.items():
785
+ yield from stringize(attr, value, ignored_keys, " ")
786
+
787
+ # Output node data
788
+ node_id = dict(zip(G, range(len(G))))
789
+ ignored_keys = {"id", "label"}
790
+ for node, attrs in G.nodes.items():
791
+ yield " node ["
792
+ yield " id " + str(node_id[node])
793
+ yield from stringize("label", node, (), " ")
794
+ for attr, value in attrs.items():
795
+ yield from stringize(attr, value, ignored_keys, " ")
796
+ yield " ]"
797
+
798
+ # Output edge data
799
+ ignored_keys = {"source", "target"}
800
+ kwargs = {"data": True}
801
+ if multigraph:
802
+ ignored_keys.add("key")
803
+ kwargs["keys"] = True
804
+ for e in G.edges(**kwargs):
805
+ yield " edge ["
806
+ yield " source " + str(node_id[e[0]])
807
+ yield " target " + str(node_id[e[1]])
808
+ if multigraph:
809
+ yield from stringize("key", e[2], (), " ")
810
+ for attr, value in e[-1].items():
811
+ yield from stringize(attr, value, ignored_keys, " ")
812
+ yield " ]"
813
+ yield "]"
814
+
815
+
816
+ @open_file(1, mode="wb")
817
+ def write_gml(G, path, stringizer=None):
818
+ """Write a graph `G` in GML format to the file or file handle `path`.
819
+
820
+ Parameters
821
+ ----------
822
+ G : NetworkX graph
823
+ The graph to be converted to GML.
824
+
825
+ path : filename or filehandle
826
+ The filename or filehandle to write. Files whose names end with .gz or
827
+ .bz2 will be compressed.
828
+
829
+ stringizer : callable, optional
830
+ A `stringizer` which converts non-int/non-float/non-dict values into
831
+ strings. If it cannot convert a value into a string, it should raise a
832
+ `ValueError` to indicate that. Default value: None.
833
+
834
+ Raises
835
+ ------
836
+ NetworkXError
837
+ If `stringizer` cannot convert a value into a string, or the value to
838
+ convert is not a string while `stringizer` is None.
839
+
840
+ See Also
841
+ --------
842
+ read_gml, generate_gml
843
+ literal_stringizer
844
+
845
+ Notes
846
+ -----
847
+ Graph attributes named 'directed', 'multigraph', 'node' or
848
+ 'edge', node attributes named 'id' or 'label', edge attributes
849
+ named 'source' or 'target' (or 'key' if `G` is a multigraph)
850
+ are ignored because these attribute names are used to encode the graph
851
+ structure.
852
+
853
+ GML files are stored using a 7-bit ASCII encoding with any extended
854
+ ASCII characters (iso8859-1) appearing as HTML character entities.
855
+ Without specifying a `stringizer`/`destringizer`, the code is capable of
856
+ writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
857
+ specification. For writing other data types, and for reading data other
858
+ than `str` you need to explicitly supply a `stringizer`/`destringizer`.
859
+
860
+ Note that while we allow non-standard GML to be read from a file, we make
861
+ sure to write GML format. In particular, underscores are not allowed in
862
+ attribute names.
863
+ For additional documentation on the GML file format, please see the
864
+ `GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
865
+
866
+ See the module docstring :mod:`networkx.readwrite.gml` for more details.
867
+
868
+ Examples
869
+ --------
870
+ >>> G = nx.path_graph(4)
871
+ >>> nx.write_gml(G, "test.gml")
872
+
873
+ Filenames ending in .gz or .bz2 will be compressed.
874
+
875
+ >>> nx.write_gml(G, "test.gml.gz")
876
+ """
877
+ for line in generate_gml(G, stringizer):
878
+ path.write((line + "\n").encode("ascii"))
pythonProject/.venv/Lib/site-packages/networkx/readwrite/graph6.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Original author: D. Eppstein, UC Irvine, August 12, 2003.
2
+ # The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain.
3
+ """Functions for reading and writing graphs in the *graph6* format.
4
+
5
+ The *graph6* file format is suitable for small graphs or large dense
6
+ graphs. For large sparse graphs, use the *sparse6* format.
7
+
8
+ For more information, see the `graph6`_ homepage.
9
+
10
+ .. _graph6: http://users.cecs.anu.edu.au/~bdm/data/formats.html
11
+
12
+ """
13
+ from itertools import islice
14
+
15
+ import networkx as nx
16
+ from networkx.exception import NetworkXError
17
+ from networkx.utils import not_implemented_for, open_file
18
+
19
+ __all__ = ["from_graph6_bytes", "read_graph6", "to_graph6_bytes", "write_graph6"]
20
+
21
+
22
+ def _generate_graph6_bytes(G, nodes, header):
23
+ """Yield bytes in the graph6 encoding of a graph.
24
+
25
+ `G` is an undirected simple graph. `nodes` is the list of nodes for
26
+ which the node-induced subgraph will be encoded; if `nodes` is the
27
+ list of all nodes in the graph, the entire graph will be
28
+ encoded. `header` is a Boolean that specifies whether to generate
29
+ the header ``b'>>graph6<<'`` before the remaining data.
30
+
31
+ This function generates `bytes` objects in the following order:
32
+
33
+ 1. the header (if requested),
34
+ 2. the encoding of the number of nodes,
35
+ 3. each character, one-at-a-time, in the encoding of the requested
36
+ node-induced subgraph,
37
+ 4. a newline character.
38
+
39
+ This function raises :exc:`ValueError` if the graph is too large for
40
+ the graph6 format (that is, greater than ``2 ** 36`` nodes).
41
+
42
+ """
43
+ n = len(G)
44
+ if n >= 2**36:
45
+ raise ValueError(
46
+ "graph6 is only defined if number of nodes is less than 2 ** 36"
47
+ )
48
+ if header:
49
+ yield b">>graph6<<"
50
+ for d in n_to_data(n):
51
+ yield str.encode(chr(d + 63))
52
+ # This generates the same as `(v in G[u] for u, v in combinations(G, 2))`,
53
+ # but in "column-major" order instead of "row-major" order.
54
+ bits = (nodes[j] in G[nodes[i]] for j in range(1, n) for i in range(j))
55
+ chunk = list(islice(bits, 6))
56
+ while chunk:
57
+ d = sum(b << 5 - i for i, b in enumerate(chunk))
58
+ yield str.encode(chr(d + 63))
59
+ chunk = list(islice(bits, 6))
60
+ yield b"\n"
61
+
62
+
63
+ @nx._dispatchable(graphs=None, returns_graph=True)
64
+ def from_graph6_bytes(bytes_in):
65
+ """Read a simple undirected graph in graph6 format from bytes.
66
+
67
+ Parameters
68
+ ----------
69
+ bytes_in : bytes
70
+ Data in graph6 format, without a trailing newline.
71
+
72
+ Returns
73
+ -------
74
+ G : Graph
75
+
76
+ Raises
77
+ ------
78
+ NetworkXError
79
+ If bytes_in is unable to be parsed in graph6 format
80
+
81
+ ValueError
82
+ If any character ``c`` in bytes_in does not satisfy
83
+ ``63 <= ord(c) < 127``.
84
+
85
+ Examples
86
+ --------
87
+ >>> G = nx.from_graph6_bytes(b"A_")
88
+ >>> sorted(G.edges())
89
+ [(0, 1)]
90
+
91
+ See Also
92
+ --------
93
+ read_graph6, write_graph6
94
+
95
+ References
96
+ ----------
97
+ .. [1] Graph6 specification
98
+ <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
99
+
100
+ """
101
+
102
+ def bits():
103
+ """Returns sequence of individual bits from 6-bit-per-value
104
+ list of data values."""
105
+ for d in data:
106
+ for i in [5, 4, 3, 2, 1, 0]:
107
+ yield (d >> i) & 1
108
+
109
+ if bytes_in.startswith(b">>graph6<<"):
110
+ bytes_in = bytes_in[10:]
111
+
112
+ data = [c - 63 for c in bytes_in]
113
+ if any(c > 63 for c in data):
114
+ raise ValueError("each input character must be in range(63, 127)")
115
+
116
+ n, data = data_to_n(data)
117
+ nd = (n * (n - 1) // 2 + 5) // 6
118
+ if len(data) != nd:
119
+ raise NetworkXError(
120
+ f"Expected {n * (n - 1) // 2} bits but got {len(data) * 6} in graph6"
121
+ )
122
+
123
+ G = nx.Graph()
124
+ G.add_nodes_from(range(n))
125
+ for (i, j), b in zip(((i, j) for j in range(1, n) for i in range(j)), bits()):
126
+ if b:
127
+ G.add_edge(i, j)
128
+
129
+ return G
130
+
131
+
132
+ @not_implemented_for("directed")
133
+ @not_implemented_for("multigraph")
134
+ def to_graph6_bytes(G, nodes=None, header=True):
135
+ """Convert a simple undirected graph to bytes in graph6 format.
136
+
137
+ Parameters
138
+ ----------
139
+ G : Graph (undirected)
140
+
141
+ nodes: list or iterable
142
+ Nodes are labeled 0...n-1 in the order provided. If None the ordering
143
+ given by ``G.nodes()`` is used.
144
+
145
+ header: bool
146
+ If True add '>>graph6<<' bytes to head of data.
147
+
148
+ Raises
149
+ ------
150
+ NetworkXNotImplemented
151
+ If the graph is directed or is a multigraph.
152
+
153
+ ValueError
154
+ If the graph has at least ``2 ** 36`` nodes; the graph6 format
155
+ is only defined for graphs of order less than ``2 ** 36``.
156
+
157
+ Examples
158
+ --------
159
+ >>> nx.to_graph6_bytes(nx.path_graph(2))
160
+ b'>>graph6<<A_\\n'
161
+
162
+ See Also
163
+ --------
164
+ from_graph6_bytes, read_graph6, write_graph6_bytes
165
+
166
+ Notes
167
+ -----
168
+ The returned bytes end with a newline character.
169
+
170
+ The format does not support edge or node labels, parallel edges or
171
+ self loops. If self loops are present they are silently ignored.
172
+
173
+ References
174
+ ----------
175
+ .. [1] Graph6 specification
176
+ <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
177
+
178
+ """
179
+ if nodes is not None:
180
+ G = G.subgraph(nodes)
181
+ H = nx.convert_node_labels_to_integers(G)
182
+ nodes = sorted(H.nodes())
183
+ return b"".join(_generate_graph6_bytes(H, nodes, header))
184
+
185
+
186
+ @open_file(0, mode="rb")
187
+ @nx._dispatchable(graphs=None, returns_graph=True)
188
+ def read_graph6(path):
189
+ """Read simple undirected graphs in graph6 format from path.
190
+
191
+ Parameters
192
+ ----------
193
+ path : file or string
194
+ File or filename to write.
195
+
196
+ Returns
197
+ -------
198
+ G : Graph or list of Graphs
199
+ If the file contains multiple lines then a list of graphs is returned
200
+
201
+ Raises
202
+ ------
203
+ NetworkXError
204
+ If the string is unable to be parsed in graph6 format
205
+
206
+ Examples
207
+ --------
208
+ You can read a graph6 file by giving the path to the file::
209
+
210
+ >>> import tempfile
211
+ >>> with tempfile.NamedTemporaryFile(delete=False) as f:
212
+ ... _ = f.write(b">>graph6<<A_\\n")
213
+ ... _ = f.seek(0)
214
+ ... G = nx.read_graph6(f.name)
215
+ >>> list(G.edges())
216
+ [(0, 1)]
217
+
218
+ You can also read a graph6 file by giving an open file-like object::
219
+
220
+ >>> import tempfile
221
+ >>> with tempfile.NamedTemporaryFile() as f:
222
+ ... _ = f.write(b">>graph6<<A_\\n")
223
+ ... _ = f.seek(0)
224
+ ... G = nx.read_graph6(f)
225
+ >>> list(G.edges())
226
+ [(0, 1)]
227
+
228
+ See Also
229
+ --------
230
+ from_graph6_bytes, write_graph6
231
+
232
+ References
233
+ ----------
234
+ .. [1] Graph6 specification
235
+ <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
236
+
237
+ """
238
+ glist = []
239
+ for line in path:
240
+ line = line.strip()
241
+ if not len(line):
242
+ continue
243
+ glist.append(from_graph6_bytes(line))
244
+ if len(glist) == 1:
245
+ return glist[0]
246
+ else:
247
+ return glist
248
+
249
+
250
+ @not_implemented_for("directed")
251
+ @not_implemented_for("multigraph")
252
+ @open_file(1, mode="wb")
253
+ def write_graph6(G, path, nodes=None, header=True):
254
+ """Write a simple undirected graph to a path in graph6 format.
255
+
256
+ Parameters
257
+ ----------
258
+ G : Graph (undirected)
259
+
260
+ path : str
261
+ The path naming the file to which to write the graph.
262
+
263
+ nodes: list or iterable
264
+ Nodes are labeled 0...n-1 in the order provided. If None the ordering
265
+ given by ``G.nodes()`` is used.
266
+
267
+ header: bool
268
+ If True add '>>graph6<<' string to head of data
269
+
270
+ Raises
271
+ ------
272
+ NetworkXNotImplemented
273
+ If the graph is directed or is a multigraph.
274
+
275
+ ValueError
276
+ If the graph has at least ``2 ** 36`` nodes; the graph6 format
277
+ is only defined for graphs of order less than ``2 ** 36``.
278
+
279
+ Examples
280
+ --------
281
+ You can write a graph6 file by giving the path to a file::
282
+
283
+ >>> import tempfile
284
+ >>> with tempfile.NamedTemporaryFile(delete=False) as f:
285
+ ... nx.write_graph6(nx.path_graph(2), f.name)
286
+ ... _ = f.seek(0)
287
+ ... print(f.read())
288
+ b'>>graph6<<A_\\n'
289
+
290
+ See Also
291
+ --------
292
+ from_graph6_bytes, read_graph6
293
+
294
+ Notes
295
+ -----
296
+ The function writes a newline character after writing the encoding
297
+ of the graph.
298
+
299
+ The format does not support edge or node labels, parallel edges or
300
+ self loops. If self loops are present they are silently ignored.
301
+
302
+ References
303
+ ----------
304
+ .. [1] Graph6 specification
305
+ <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
306
+
307
+ """
308
+ return write_graph6_file(G, path, nodes=nodes, header=header)
309
+
310
+
311
+ @not_implemented_for("directed")
312
+ @not_implemented_for("multigraph")
313
+ def write_graph6_file(G, f, nodes=None, header=True):
314
+ """Write a simple undirected graph to a file-like object in graph6 format.
315
+
316
+ Parameters
317
+ ----------
318
+ G : Graph (undirected)
319
+
320
+ f : file-like object
321
+ The file to write.
322
+
323
+ nodes: list or iterable
324
+ Nodes are labeled 0...n-1 in the order provided. If None the ordering
325
+ given by ``G.nodes()`` is used.
326
+
327
+ header: bool
328
+ If True add '>>graph6<<' string to head of data
329
+
330
+ Raises
331
+ ------
332
+ NetworkXNotImplemented
333
+ If the graph is directed or is a multigraph.
334
+
335
+ ValueError
336
+ If the graph has at least ``2 ** 36`` nodes; the graph6 format
337
+ is only defined for graphs of order less than ``2 ** 36``.
338
+
339
+ Examples
340
+ --------
341
+ You can write a graph6 file by giving an open file-like object::
342
+
343
+ >>> import tempfile
344
+ >>> with tempfile.NamedTemporaryFile() as f:
345
+ ... nx.write_graph6(nx.path_graph(2), f)
346
+ ... _ = f.seek(0)
347
+ ... print(f.read())
348
+ b'>>graph6<<A_\\n'
349
+
350
+ See Also
351
+ --------
352
+ from_graph6_bytes, read_graph6
353
+
354
+ Notes
355
+ -----
356
+ The function writes a newline character after writing the encoding
357
+ of the graph.
358
+
359
+ The format does not support edge or node labels, parallel edges or
360
+ self loops. If self loops are present they are silently ignored.
361
+
362
+ References
363
+ ----------
364
+ .. [1] Graph6 specification
365
+ <http://users.cecs.anu.edu.au/~bdm/data/formats.html>
366
+
367
+ """
368
+ if nodes is not None:
369
+ G = G.subgraph(nodes)
370
+ H = nx.convert_node_labels_to_integers(G)
371
+ nodes = sorted(H.nodes())
372
+ for b in _generate_graph6_bytes(H, nodes, header):
373
+ f.write(b)
374
+
375
+
376
+ def data_to_n(data):
377
+ """Read initial one-, four- or eight-unit value from graph6
378
+ integer sequence.
379
+
380
+ Return (value, rest of seq.)"""
381
+ if data[0] <= 62:
382
+ return data[0], data[1:]
383
+ if data[1] <= 62:
384
+ return (data[1] << 12) + (data[2] << 6) + data[3], data[4:]
385
+ return (
386
+ (data[2] << 30)
387
+ + (data[3] << 24)
388
+ + (data[4] << 18)
389
+ + (data[5] << 12)
390
+ + (data[6] << 6)
391
+ + data[7],
392
+ data[8:],
393
+ )
394
+
395
+
396
+ def n_to_data(n):
397
+ """Convert an integer to one-, four- or eight-unit graph6 sequence.
398
+
399
+ This function is undefined if `n` is not in ``range(2 ** 36)``.
400
+
401
+ """
402
+ if n <= 62:
403
+ return [n]
404
+ elif n <= 258047:
405
+ return [63, (n >> 12) & 0x3F, (n >> 6) & 0x3F, n & 0x3F]
406
+ else: # if n <= 68719476735:
407
+ return [
408
+ 63,
409
+ 63,
410
+ (n >> 30) & 0x3F,
411
+ (n >> 24) & 0x3F,
412
+ (n >> 18) & 0x3F,
413
+ (n >> 12) & 0x3F,
414
+ (n >> 6) & 0x3F,
415
+ n & 0x3F,
416
+ ]
pythonProject/.venv/Lib/site-packages/networkx/readwrite/graphml.py ADDED
@@ -0,0 +1,1052 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ *******
3
+ GraphML
4
+ *******
5
+ Read and write graphs in GraphML format.
6
+
7
+ .. warning::
8
+
9
+ This parser uses the standard xml library present in Python, which is
10
+ insecure - see :external+python:mod:`xml` for additional information.
11
+ Only parse GraphML files you trust.
12
+
13
+ This implementation does not support mixed graphs (directed and unidirected
14
+ edges together), hyperedges, nested graphs, or ports.
15
+
16
+ "GraphML is a comprehensive and easy-to-use file format for graphs. It
17
+ consists of a language core to describe the structural properties of a
18
+ graph and a flexible extension mechanism to add application-specific
19
+ data. Its main features include support of
20
+
21
+ * directed, undirected, and mixed graphs,
22
+ * hypergraphs,
23
+ * hierarchical graphs,
24
+ * graphical representations,
25
+ * references to external data,
26
+ * application-specific attribute data, and
27
+ * light-weight parsers.
28
+
29
+ Unlike many other file formats for graphs, GraphML does not use a
30
+ custom syntax. Instead, it is based on XML and hence ideally suited as
31
+ a common denominator for all kinds of services generating, archiving,
32
+ or processing graphs."
33
+
34
+ http://graphml.graphdrawing.org/
35
+
36
+ Format
37
+ ------
38
+ GraphML is an XML format. See
39
+ http://graphml.graphdrawing.org/specification.html for the specification and
40
+ http://graphml.graphdrawing.org/primer/graphml-primer.html
41
+ for examples.
42
+ """
43
+ import warnings
44
+ from collections import defaultdict
45
+
46
+ import networkx as nx
47
+ from networkx.utils import open_file
48
+
49
+ __all__ = [
50
+ "write_graphml",
51
+ "read_graphml",
52
+ "generate_graphml",
53
+ "write_graphml_xml",
54
+ "write_graphml_lxml",
55
+ "parse_graphml",
56
+ "GraphMLWriter",
57
+ "GraphMLReader",
58
+ ]
59
+
60
+
61
+ @open_file(1, mode="wb")
62
+ def write_graphml_xml(
63
+ G,
64
+ path,
65
+ encoding="utf-8",
66
+ prettyprint=True,
67
+ infer_numeric_types=False,
68
+ named_key_ids=False,
69
+ edge_id_from_attribute=None,
70
+ ):
71
+ """Write G in GraphML XML format to path
72
+
73
+ Parameters
74
+ ----------
75
+ G : graph
76
+ A networkx graph
77
+ path : file or string
78
+ File or filename to write.
79
+ Filenames ending in .gz or .bz2 will be compressed.
80
+ encoding : string (optional)
81
+ Encoding for text data.
82
+ prettyprint : bool (optional)
83
+ If True use line breaks and indenting in output XML.
84
+ infer_numeric_types : boolean
85
+ Determine if numeric types should be generalized.
86
+ For example, if edges have both int and float 'weight' attributes,
87
+ we infer in GraphML that both are floats.
88
+ named_key_ids : bool (optional)
89
+ If True use attr.name as value for key elements' id attribute.
90
+ edge_id_from_attribute : dict key (optional)
91
+ If provided, the graphml edge id is set by looking up the corresponding
92
+ edge data attribute keyed by this parameter. If `None` or the key does not exist in edge data,
93
+ the edge id is set by the edge key if `G` is a MultiGraph, else the edge id is left unset.
94
+
95
+ Examples
96
+ --------
97
+ >>> G = nx.path_graph(4)
98
+ >>> nx.write_graphml(G, "test.graphml")
99
+
100
+ Notes
101
+ -----
102
+ This implementation does not support mixed graphs (directed
103
+ and unidirected edges together) hyperedges, nested graphs, or ports.
104
+ """
105
+ writer = GraphMLWriter(
106
+ encoding=encoding,
107
+ prettyprint=prettyprint,
108
+ infer_numeric_types=infer_numeric_types,
109
+ named_key_ids=named_key_ids,
110
+ edge_id_from_attribute=edge_id_from_attribute,
111
+ )
112
+ writer.add_graph_element(G)
113
+ writer.dump(path)
114
+
115
+
116
+ @open_file(1, mode="wb")
117
+ def write_graphml_lxml(
118
+ G,
119
+ path,
120
+ encoding="utf-8",
121
+ prettyprint=True,
122
+ infer_numeric_types=False,
123
+ named_key_ids=False,
124
+ edge_id_from_attribute=None,
125
+ ):
126
+ """Write G in GraphML XML format to path
127
+
128
+ This function uses the LXML framework and should be faster than
129
+ the version using the xml library.
130
+
131
+ Parameters
132
+ ----------
133
+ G : graph
134
+ A networkx graph
135
+ path : file or string
136
+ File or filename to write.
137
+ Filenames ending in .gz or .bz2 will be compressed.
138
+ encoding : string (optional)
139
+ Encoding for text data.
140
+ prettyprint : bool (optional)
141
+ If True use line breaks and indenting in output XML.
142
+ infer_numeric_types : boolean
143
+ Determine if numeric types should be generalized.
144
+ For example, if edges have both int and float 'weight' attributes,
145
+ we infer in GraphML that both are floats.
146
+ named_key_ids : bool (optional)
147
+ If True use attr.name as value for key elements' id attribute.
148
+ edge_id_from_attribute : dict key (optional)
149
+ If provided, the graphml edge id is set by looking up the corresponding
150
+ edge data attribute keyed by this parameter. If `None` or the key does not exist in edge data,
151
+ the edge id is set by the edge key if `G` is a MultiGraph, else the edge id is left unset.
152
+
153
+ Examples
154
+ --------
155
+ >>> G = nx.path_graph(4)
156
+ >>> nx.write_graphml_lxml(G, "fourpath.graphml")
157
+
158
+ Notes
159
+ -----
160
+ This implementation does not support mixed graphs (directed
161
+ and unidirected edges together) hyperedges, nested graphs, or ports.
162
+ """
163
+ try:
164
+ import lxml.etree as lxmletree
165
+ except ImportError:
166
+ return write_graphml_xml(
167
+ G,
168
+ path,
169
+ encoding,
170
+ prettyprint,
171
+ infer_numeric_types,
172
+ named_key_ids,
173
+ edge_id_from_attribute,
174
+ )
175
+
176
+ writer = GraphMLWriterLxml(
177
+ path,
178
+ graph=G,
179
+ encoding=encoding,
180
+ prettyprint=prettyprint,
181
+ infer_numeric_types=infer_numeric_types,
182
+ named_key_ids=named_key_ids,
183
+ edge_id_from_attribute=edge_id_from_attribute,
184
+ )
185
+ writer.dump()
186
+
187
+
188
+ def generate_graphml(
189
+ G,
190
+ encoding="utf-8",
191
+ prettyprint=True,
192
+ named_key_ids=False,
193
+ edge_id_from_attribute=None,
194
+ ):
195
+ """Generate GraphML lines for G
196
+
197
+ Parameters
198
+ ----------
199
+ G : graph
200
+ A networkx graph
201
+ encoding : string (optional)
202
+ Encoding for text data.
203
+ prettyprint : bool (optional)
204
+ If True use line breaks and indenting in output XML.
205
+ named_key_ids : bool (optional)
206
+ If True use attr.name as value for key elements' id attribute.
207
+ edge_id_from_attribute : dict key (optional)
208
+ If provided, the graphml edge id is set by looking up the corresponding
209
+ edge data attribute keyed by this parameter. If `None` or the key does not exist in edge data,
210
+ the edge id is set by the edge key if `G` is a MultiGraph, else the edge id is left unset.
211
+
212
+ Examples
213
+ --------
214
+ >>> G = nx.path_graph(4)
215
+ >>> linefeed = chr(10) # linefeed = \n
216
+ >>> s = linefeed.join(nx.generate_graphml(G))
217
+ >>> for line in nx.generate_graphml(G): # doctest: +SKIP
218
+ ... print(line)
219
+
220
+ Notes
221
+ -----
222
+ This implementation does not support mixed graphs (directed and unidirected
223
+ edges together) hyperedges, nested graphs, or ports.
224
+ """
225
+ writer = GraphMLWriter(
226
+ encoding=encoding,
227
+ prettyprint=prettyprint,
228
+ named_key_ids=named_key_ids,
229
+ edge_id_from_attribute=edge_id_from_attribute,
230
+ )
231
+ writer.add_graph_element(G)
232
+ yield from str(writer).splitlines()
233
+
234
+
235
+ @open_file(0, mode="rb")
236
+ @nx._dispatchable(graphs=None, returns_graph=True)
237
+ def read_graphml(path, node_type=str, edge_key_type=int, force_multigraph=False):
238
+ """Read graph in GraphML format from path.
239
+
240
+ Parameters
241
+ ----------
242
+ path : file or string
243
+ File or filename to write.
244
+ Filenames ending in .gz or .bz2 will be compressed.
245
+
246
+ node_type: Python type (default: str)
247
+ Convert node ids to this type
248
+
249
+ edge_key_type: Python type (default: int)
250
+ Convert graphml edge ids to this type. Multigraphs use id as edge key.
251
+ Non-multigraphs add to edge attribute dict with name "id".
252
+
253
+ force_multigraph : bool (default: False)
254
+ If True, return a multigraph with edge keys. If False (the default)
255
+ return a multigraph when multiedges are in the graph.
256
+
257
+ Returns
258
+ -------
259
+ graph: NetworkX graph
260
+ If parallel edges are present or `force_multigraph=True` then
261
+ a MultiGraph or MultiDiGraph is returned. Otherwise a Graph/DiGraph.
262
+ The returned graph is directed if the file indicates it should be.
263
+
264
+ Notes
265
+ -----
266
+ Default node and edge attributes are not propagated to each node and edge.
267
+ They can be obtained from `G.graph` and applied to node and edge attributes
268
+ if desired using something like this:
269
+
270
+ >>> default_color = G.graph["node_default"]["color"] # doctest: +SKIP
271
+ >>> for node, data in G.nodes(data=True): # doctest: +SKIP
272
+ ... if "color" not in data:
273
+ ... data["color"] = default_color
274
+ >>> default_color = G.graph["edge_default"]["color"] # doctest: +SKIP
275
+ >>> for u, v, data in G.edges(data=True): # doctest: +SKIP
276
+ ... if "color" not in data:
277
+ ... data["color"] = default_color
278
+
279
+ This implementation does not support mixed graphs (directed and unidirected
280
+ edges together), hypergraphs, nested graphs, or ports.
281
+
282
+ For multigraphs the GraphML edge "id" will be used as the edge
283
+ key. If not specified then they "key" attribute will be used. If
284
+ there is no "key" attribute a default NetworkX multigraph edge key
285
+ will be provided.
286
+
287
+ Files with the yEd "yfiles" extension can be read. The type of the node's
288
+ shape is preserved in the `shape_type` node attribute.
289
+
290
+ yEd compressed files ("file.graphmlz" extension) can be read by renaming
291
+ the file to "file.graphml.gz".
292
+
293
+ """
294
+ reader = GraphMLReader(node_type, edge_key_type, force_multigraph)
295
+ # need to check for multiple graphs
296
+ glist = list(reader(path=path))
297
+ if len(glist) == 0:
298
+ # If no graph comes back, try looking for an incomplete header
299
+ header = b'<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
300
+ path.seek(0)
301
+ old_bytes = path.read()
302
+ new_bytes = old_bytes.replace(b"<graphml>", header)
303
+ glist = list(reader(string=new_bytes))
304
+ if len(glist) == 0:
305
+ raise nx.NetworkXError("file not successfully read as graphml")
306
+ return glist[0]
307
+
308
+
309
+ @nx._dispatchable(graphs=None, returns_graph=True)
310
+ def parse_graphml(
311
+ graphml_string, node_type=str, edge_key_type=int, force_multigraph=False
312
+ ):
313
+ """Read graph in GraphML format from string.
314
+
315
+ Parameters
316
+ ----------
317
+ graphml_string : string
318
+ String containing graphml information
319
+ (e.g., contents of a graphml file).
320
+
321
+ node_type: Python type (default: str)
322
+ Convert node ids to this type
323
+
324
+ edge_key_type: Python type (default: int)
325
+ Convert graphml edge ids to this type. Multigraphs use id as edge key.
326
+ Non-multigraphs add to edge attribute dict with name "id".
327
+
328
+ force_multigraph : bool (default: False)
329
+ If True, return a multigraph with edge keys. If False (the default)
330
+ return a multigraph when multiedges are in the graph.
331
+
332
+
333
+ Returns
334
+ -------
335
+ graph: NetworkX graph
336
+ If no parallel edges are found a Graph or DiGraph is returned.
337
+ Otherwise a MultiGraph or MultiDiGraph is returned.
338
+
339
+ Examples
340
+ --------
341
+ >>> G = nx.path_graph(4)
342
+ >>> linefeed = chr(10) # linefeed = \n
343
+ >>> s = linefeed.join(nx.generate_graphml(G))
344
+ >>> H = nx.parse_graphml(s)
345
+
346
+ Notes
347
+ -----
348
+ Default node and edge attributes are not propagated to each node and edge.
349
+ They can be obtained from `G.graph` and applied to node and edge attributes
350
+ if desired using something like this:
351
+
352
+ >>> default_color = G.graph["node_default"]["color"] # doctest: +SKIP
353
+ >>> for node, data in G.nodes(data=True): # doctest: +SKIP
354
+ ... if "color" not in data:
355
+ ... data["color"] = default_color
356
+ >>> default_color = G.graph["edge_default"]["color"] # doctest: +SKIP
357
+ >>> for u, v, data in G.edges(data=True): # doctest: +SKIP
358
+ ... if "color" not in data:
359
+ ... data["color"] = default_color
360
+
361
+ This implementation does not support mixed graphs (directed and unidirected
362
+ edges together), hypergraphs, nested graphs, or ports.
363
+
364
+ For multigraphs the GraphML edge "id" will be used as the edge
365
+ key. If not specified then they "key" attribute will be used. If
366
+ there is no "key" attribute a default NetworkX multigraph edge key
367
+ will be provided.
368
+
369
+ """
370
+ reader = GraphMLReader(node_type, edge_key_type, force_multigraph)
371
+ # need to check for multiple graphs
372
+ glist = list(reader(string=graphml_string))
373
+ if len(glist) == 0:
374
+ # If no graph comes back, try looking for an incomplete header
375
+ header = '<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
376
+ new_string = graphml_string.replace("<graphml>", header)
377
+ glist = list(reader(string=new_string))
378
+ if len(glist) == 0:
379
+ raise nx.NetworkXError("file not successfully read as graphml")
380
+ return glist[0]
381
+
382
+
383
+ class GraphML:
384
+ NS_GRAPHML = "http://graphml.graphdrawing.org/xmlns"
385
+ NS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
386
+ # xmlns:y="http://www.yworks.com/xml/graphml"
387
+ NS_Y = "http://www.yworks.com/xml/graphml"
388
+ SCHEMALOCATION = " ".join(
389
+ [
390
+ "http://graphml.graphdrawing.org/xmlns",
391
+ "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd",
392
+ ]
393
+ )
394
+
395
+ def construct_types(self):
396
+ types = [
397
+ (int, "integer"), # for Gephi GraphML bug
398
+ (str, "yfiles"),
399
+ (str, "string"),
400
+ (int, "int"),
401
+ (int, "long"),
402
+ (float, "float"),
403
+ (float, "double"),
404
+ (bool, "boolean"),
405
+ ]
406
+
407
+ # These additions to types allow writing numpy types
408
+ try:
409
+ import numpy as np
410
+ except:
411
+ pass
412
+ else:
413
+ # prepend so that python types are created upon read (last entry wins)
414
+ types = [
415
+ (np.float64, "float"),
416
+ (np.float32, "float"),
417
+ (np.float16, "float"),
418
+ (np.int_, "int"),
419
+ (np.int8, "int"),
420
+ (np.int16, "int"),
421
+ (np.int32, "int"),
422
+ (np.int64, "int"),
423
+ (np.uint8, "int"),
424
+ (np.uint16, "int"),
425
+ (np.uint32, "int"),
426
+ (np.uint64, "int"),
427
+ (np.int_, "int"),
428
+ (np.intc, "int"),
429
+ (np.intp, "int"),
430
+ ] + types
431
+
432
+ self.xml_type = dict(types)
433
+ self.python_type = dict(reversed(a) for a in types)
434
+
435
+ # This page says that data types in GraphML follow Java(TM).
436
+ # http://graphml.graphdrawing.org/primer/graphml-primer.html#AttributesDefinition
437
+ # true and false are the only boolean literals:
438
+ # http://en.wikibooks.org/wiki/Java_Programming/Literals#Boolean_Literals
439
+ convert_bool = {
440
+ # We use data.lower() in actual use.
441
+ "true": True,
442
+ "false": False,
443
+ # Include integer strings for convenience.
444
+ "0": False,
445
+ 0: False,
446
+ "1": True,
447
+ 1: True,
448
+ }
449
+
450
+ def get_xml_type(self, key):
451
+ """Wrapper around the xml_type dict that raises a more informative
452
+ exception message when a user attempts to use data of a type not
453
+ supported by GraphML."""
454
+ try:
455
+ return self.xml_type[key]
456
+ except KeyError as err:
457
+ raise TypeError(
458
+ f"GraphML does not support type {key} as data values."
459
+ ) from err
460
+
461
+
462
+ class GraphMLWriter(GraphML):
463
+ def __init__(
464
+ self,
465
+ graph=None,
466
+ encoding="utf-8",
467
+ prettyprint=True,
468
+ infer_numeric_types=False,
469
+ named_key_ids=False,
470
+ edge_id_from_attribute=None,
471
+ ):
472
+ self.construct_types()
473
+ from xml.etree.ElementTree import Element
474
+
475
+ self.myElement = Element
476
+
477
+ self.infer_numeric_types = infer_numeric_types
478
+ self.prettyprint = prettyprint
479
+ self.named_key_ids = named_key_ids
480
+ self.edge_id_from_attribute = edge_id_from_attribute
481
+ self.encoding = encoding
482
+ self.xml = self.myElement(
483
+ "graphml",
484
+ {
485
+ "xmlns": self.NS_GRAPHML,
486
+ "xmlns:xsi": self.NS_XSI,
487
+ "xsi:schemaLocation": self.SCHEMALOCATION,
488
+ },
489
+ )
490
+ self.keys = {}
491
+ self.attributes = defaultdict(list)
492
+ self.attribute_types = defaultdict(set)
493
+
494
+ if graph is not None:
495
+ self.add_graph_element(graph)
496
+
497
+ def __str__(self):
498
+ from xml.etree.ElementTree import tostring
499
+
500
+ if self.prettyprint:
501
+ self.indent(self.xml)
502
+ s = tostring(self.xml).decode(self.encoding)
503
+ return s
504
+
505
+ def attr_type(self, name, scope, value):
506
+ """Infer the attribute type of data named name. Currently this only
507
+ supports inference of numeric types.
508
+
509
+ If self.infer_numeric_types is false, type is used. Otherwise, pick the
510
+ most general of types found across all values with name and scope. This
511
+ means edges with data named 'weight' are treated separately from nodes
512
+ with data named 'weight'.
513
+ """
514
+ if self.infer_numeric_types:
515
+ types = self.attribute_types[(name, scope)]
516
+
517
+ if len(types) > 1:
518
+ types = {self.get_xml_type(t) for t in types}
519
+ if "string" in types:
520
+ return str
521
+ elif "float" in types or "double" in types:
522
+ return float
523
+ else:
524
+ return int
525
+ else:
526
+ return list(types)[0]
527
+ else:
528
+ return type(value)
529
+
530
+ def get_key(self, name, attr_type, scope, default):
531
+ keys_key = (name, attr_type, scope)
532
+ try:
533
+ return self.keys[keys_key]
534
+ except KeyError:
535
+ if self.named_key_ids:
536
+ new_id = name
537
+ else:
538
+ new_id = f"d{len(list(self.keys))}"
539
+
540
+ self.keys[keys_key] = new_id
541
+ key_kwargs = {
542
+ "id": new_id,
543
+ "for": scope,
544
+ "attr.name": name,
545
+ "attr.type": attr_type,
546
+ }
547
+ key_element = self.myElement("key", **key_kwargs)
548
+ # add subelement for data default value if present
549
+ if default is not None:
550
+ default_element = self.myElement("default")
551
+ default_element.text = str(default)
552
+ key_element.append(default_element)
553
+ self.xml.insert(0, key_element)
554
+ return new_id
555
+
556
+ def add_data(self, name, element_type, value, scope="all", default=None):
557
+ """
558
+ Make a data element for an edge or a node. Keep a log of the
559
+ type in the keys table.
560
+ """
561
+ if element_type not in self.xml_type:
562
+ raise nx.NetworkXError(
563
+ f"GraphML writer does not support {element_type} as data values."
564
+ )
565
+ keyid = self.get_key(name, self.get_xml_type(element_type), scope, default)
566
+ data_element = self.myElement("data", key=keyid)
567
+ data_element.text = str(value)
568
+ return data_element
569
+
570
+ def add_attributes(self, scope, xml_obj, data, default):
571
+ """Appends attribute data to edges or nodes, and stores type information
572
+ to be added later. See add_graph_element.
573
+ """
574
+ for k, v in data.items():
575
+ self.attribute_types[(str(k), scope)].add(type(v))
576
+ self.attributes[xml_obj].append([k, v, scope, default.get(k)])
577
+
578
+ def add_nodes(self, G, graph_element):
579
+ default = G.graph.get("node_default", {})
580
+ for node, data in G.nodes(data=True):
581
+ node_element = self.myElement("node", id=str(node))
582
+ self.add_attributes("node", node_element, data, default)
583
+ graph_element.append(node_element)
584
+
585
+ def add_edges(self, G, graph_element):
586
+ if G.is_multigraph():
587
+ for u, v, key, data in G.edges(data=True, keys=True):
588
+ edge_element = self.myElement(
589
+ "edge",
590
+ source=str(u),
591
+ target=str(v),
592
+ id=str(data.get(self.edge_id_from_attribute))
593
+ if self.edge_id_from_attribute
594
+ and self.edge_id_from_attribute in data
595
+ else str(key),
596
+ )
597
+ default = G.graph.get("edge_default", {})
598
+ self.add_attributes("edge", edge_element, data, default)
599
+ graph_element.append(edge_element)
600
+ else:
601
+ for u, v, data in G.edges(data=True):
602
+ if self.edge_id_from_attribute and self.edge_id_from_attribute in data:
603
+ # select attribute to be edge id
604
+ edge_element = self.myElement(
605
+ "edge",
606
+ source=str(u),
607
+ target=str(v),
608
+ id=str(data.get(self.edge_id_from_attribute)),
609
+ )
610
+ else:
611
+ # default: no edge id
612
+ edge_element = self.myElement("edge", source=str(u), target=str(v))
613
+ default = G.graph.get("edge_default", {})
614
+ self.add_attributes("edge", edge_element, data, default)
615
+ graph_element.append(edge_element)
616
+
617
+ def add_graph_element(self, G):
618
+ """
619
+ Serialize graph G in GraphML to the stream.
620
+ """
621
+ if G.is_directed():
622
+ default_edge_type = "directed"
623
+ else:
624
+ default_edge_type = "undirected"
625
+
626
+ graphid = G.graph.pop("id", None)
627
+ if graphid is None:
628
+ graph_element = self.myElement("graph", edgedefault=default_edge_type)
629
+ else:
630
+ graph_element = self.myElement(
631
+ "graph", edgedefault=default_edge_type, id=graphid
632
+ )
633
+ default = {}
634
+ data = {
635
+ k: v
636
+ for (k, v) in G.graph.items()
637
+ if k not in ["node_default", "edge_default"]
638
+ }
639
+ self.add_attributes("graph", graph_element, data, default)
640
+ self.add_nodes(G, graph_element)
641
+ self.add_edges(G, graph_element)
642
+
643
+ # self.attributes contains a mapping from XML Objects to a list of
644
+ # data that needs to be added to them.
645
+ # We postpone processing in order to do type inference/generalization.
646
+ # See self.attr_type
647
+ for xml_obj, data in self.attributes.items():
648
+ for k, v, scope, default in data:
649
+ xml_obj.append(
650
+ self.add_data(
651
+ str(k), self.attr_type(k, scope, v), str(v), scope, default
652
+ )
653
+ )
654
+ self.xml.append(graph_element)
655
+
656
+ def add_graphs(self, graph_list):
657
+ """Add many graphs to this GraphML document."""
658
+ for G in graph_list:
659
+ self.add_graph_element(G)
660
+
661
+ def dump(self, stream):
662
+ from xml.etree.ElementTree import ElementTree
663
+
664
+ if self.prettyprint:
665
+ self.indent(self.xml)
666
+ document = ElementTree(self.xml)
667
+ document.write(stream, encoding=self.encoding, xml_declaration=True)
668
+
669
+ def indent(self, elem, level=0):
670
+ # in-place prettyprint formatter
671
+ i = "\n" + level * " "
672
+ if len(elem):
673
+ if not elem.text or not elem.text.strip():
674
+ elem.text = i + " "
675
+ if not elem.tail or not elem.tail.strip():
676
+ elem.tail = i
677
+ for elem in elem:
678
+ self.indent(elem, level + 1)
679
+ if not elem.tail or not elem.tail.strip():
680
+ elem.tail = i
681
+ else:
682
+ if level and (not elem.tail or not elem.tail.strip()):
683
+ elem.tail = i
684
+
685
+
686
+ class IncrementalElement:
687
+ """Wrapper for _IncrementalWriter providing an Element like interface.
688
+
689
+ This wrapper does not intend to be a complete implementation but rather to
690
+ deal with those calls used in GraphMLWriter.
691
+ """
692
+
693
+ def __init__(self, xml, prettyprint):
694
+ self.xml = xml
695
+ self.prettyprint = prettyprint
696
+
697
+ def append(self, element):
698
+ self.xml.write(element, pretty_print=self.prettyprint)
699
+
700
+
701
+ class GraphMLWriterLxml(GraphMLWriter):
702
+ def __init__(
703
+ self,
704
+ path,
705
+ graph=None,
706
+ encoding="utf-8",
707
+ prettyprint=True,
708
+ infer_numeric_types=False,
709
+ named_key_ids=False,
710
+ edge_id_from_attribute=None,
711
+ ):
712
+ self.construct_types()
713
+ import lxml.etree as lxmletree
714
+
715
+ self.myElement = lxmletree.Element
716
+
717
+ self._encoding = encoding
718
+ self._prettyprint = prettyprint
719
+ self.named_key_ids = named_key_ids
720
+ self.edge_id_from_attribute = edge_id_from_attribute
721
+ self.infer_numeric_types = infer_numeric_types
722
+
723
+ self._xml_base = lxmletree.xmlfile(path, encoding=encoding)
724
+ self._xml = self._xml_base.__enter__()
725
+ self._xml.write_declaration()
726
+
727
+ # We need to have a xml variable that support insertion. This call is
728
+ # used for adding the keys to the document.
729
+ # We will store those keys in a plain list, and then after the graph
730
+ # element is closed we will add them to the main graphml element.
731
+ self.xml = []
732
+ self._keys = self.xml
733
+ self._graphml = self._xml.element(
734
+ "graphml",
735
+ {
736
+ "xmlns": self.NS_GRAPHML,
737
+ "xmlns:xsi": self.NS_XSI,
738
+ "xsi:schemaLocation": self.SCHEMALOCATION,
739
+ },
740
+ )
741
+ self._graphml.__enter__()
742
+ self.keys = {}
743
+ self.attribute_types = defaultdict(set)
744
+
745
+ if graph is not None:
746
+ self.add_graph_element(graph)
747
+
748
+ def add_graph_element(self, G):
749
+ """
750
+ Serialize graph G in GraphML to the stream.
751
+ """
752
+ if G.is_directed():
753
+ default_edge_type = "directed"
754
+ else:
755
+ default_edge_type = "undirected"
756
+
757
+ graphid = G.graph.pop("id", None)
758
+ if graphid is None:
759
+ graph_element = self._xml.element("graph", edgedefault=default_edge_type)
760
+ else:
761
+ graph_element = self._xml.element(
762
+ "graph", edgedefault=default_edge_type, id=graphid
763
+ )
764
+
765
+ # gather attributes types for the whole graph
766
+ # to find the most general numeric format needed.
767
+ # Then pass through attributes to create key_id for each.
768
+ graphdata = {
769
+ k: v
770
+ for k, v in G.graph.items()
771
+ if k not in ("node_default", "edge_default")
772
+ }
773
+ node_default = G.graph.get("node_default", {})
774
+ edge_default = G.graph.get("edge_default", {})
775
+ # Graph attributes
776
+ for k, v in graphdata.items():
777
+ self.attribute_types[(str(k), "graph")].add(type(v))
778
+ for k, v in graphdata.items():
779
+ element_type = self.get_xml_type(self.attr_type(k, "graph", v))
780
+ self.get_key(str(k), element_type, "graph", None)
781
+ # Nodes and data
782
+ for node, d in G.nodes(data=True):
783
+ for k, v in d.items():
784
+ self.attribute_types[(str(k), "node")].add(type(v))
785
+ for node, d in G.nodes(data=True):
786
+ for k, v in d.items():
787
+ T = self.get_xml_type(self.attr_type(k, "node", v))
788
+ self.get_key(str(k), T, "node", node_default.get(k))
789
+ # Edges and data
790
+ if G.is_multigraph():
791
+ for u, v, ekey, d in G.edges(keys=True, data=True):
792
+ for k, v in d.items():
793
+ self.attribute_types[(str(k), "edge")].add(type(v))
794
+ for u, v, ekey, d in G.edges(keys=True, data=True):
795
+ for k, v in d.items():
796
+ T = self.get_xml_type(self.attr_type(k, "edge", v))
797
+ self.get_key(str(k), T, "edge", edge_default.get(k))
798
+ else:
799
+ for u, v, d in G.edges(data=True):
800
+ for k, v in d.items():
801
+ self.attribute_types[(str(k), "edge")].add(type(v))
802
+ for u, v, d in G.edges(data=True):
803
+ for k, v in d.items():
804
+ T = self.get_xml_type(self.attr_type(k, "edge", v))
805
+ self.get_key(str(k), T, "edge", edge_default.get(k))
806
+
807
+ # Now add attribute keys to the xml file
808
+ for key in self.xml:
809
+ self._xml.write(key, pretty_print=self._prettyprint)
810
+
811
+ # The incremental_writer writes each node/edge as it is created
812
+ incremental_writer = IncrementalElement(self._xml, self._prettyprint)
813
+ with graph_element:
814
+ self.add_attributes("graph", incremental_writer, graphdata, {})
815
+ self.add_nodes(G, incremental_writer) # adds attributes too
816
+ self.add_edges(G, incremental_writer) # adds attributes too
817
+
818
+ def add_attributes(self, scope, xml_obj, data, default):
819
+ """Appends attribute data."""
820
+ for k, v in data.items():
821
+ data_element = self.add_data(
822
+ str(k), self.attr_type(str(k), scope, v), str(v), scope, default.get(k)
823
+ )
824
+ xml_obj.append(data_element)
825
+
826
+ def __str__(self):
827
+ return object.__str__(self)
828
+
829
+ def dump(self, stream=None):
830
+ self._graphml.__exit__(None, None, None)
831
+ self._xml_base.__exit__(None, None, None)
832
+
833
+
834
+ # default is lxml is present.
835
+ write_graphml = write_graphml_lxml
836
+
837
+
838
+ class GraphMLReader(GraphML):
839
+ """Read a GraphML document. Produces NetworkX graph objects."""
840
+
841
+ def __init__(self, node_type=str, edge_key_type=int, force_multigraph=False):
842
+ self.construct_types()
843
+ self.node_type = node_type
844
+ self.edge_key_type = edge_key_type
845
+ self.multigraph = force_multigraph # If False, test for multiedges
846
+ self.edge_ids = {} # dict mapping (u,v) tuples to edge id attributes
847
+
848
+ def __call__(self, path=None, string=None):
849
+ from xml.etree.ElementTree import ElementTree, fromstring
850
+
851
+ if path is not None:
852
+ self.xml = ElementTree(file=path)
853
+ elif string is not None:
854
+ self.xml = fromstring(string)
855
+ else:
856
+ raise ValueError("Must specify either 'path' or 'string' as kwarg")
857
+ (keys, defaults) = self.find_graphml_keys(self.xml)
858
+ for g in self.xml.findall(f"{{{self.NS_GRAPHML}}}graph"):
859
+ yield self.make_graph(g, keys, defaults)
860
+
861
+ def make_graph(self, graph_xml, graphml_keys, defaults, G=None):
862
+ # set default graph type
863
+ edgedefault = graph_xml.get("edgedefault", None)
864
+ if G is None:
865
+ if edgedefault == "directed":
866
+ G = nx.MultiDiGraph()
867
+ else:
868
+ G = nx.MultiGraph()
869
+ # set defaults for graph attributes
870
+ G.graph["node_default"] = {}
871
+ G.graph["edge_default"] = {}
872
+ for key_id, value in defaults.items():
873
+ key_for = graphml_keys[key_id]["for"]
874
+ name = graphml_keys[key_id]["name"]
875
+ python_type = graphml_keys[key_id]["type"]
876
+ if key_for == "node":
877
+ G.graph["node_default"].update({name: python_type(value)})
878
+ if key_for == "edge":
879
+ G.graph["edge_default"].update({name: python_type(value)})
880
+ # hyperedges are not supported
881
+ hyperedge = graph_xml.find(f"{{{self.NS_GRAPHML}}}hyperedge")
882
+ if hyperedge is not None:
883
+ raise nx.NetworkXError("GraphML reader doesn't support hyperedges")
884
+ # add nodes
885
+ for node_xml in graph_xml.findall(f"{{{self.NS_GRAPHML}}}node"):
886
+ self.add_node(G, node_xml, graphml_keys, defaults)
887
+ # add edges
888
+ for edge_xml in graph_xml.findall(f"{{{self.NS_GRAPHML}}}edge"):
889
+ self.add_edge(G, edge_xml, graphml_keys)
890
+ # add graph data
891
+ data = self.decode_data_elements(graphml_keys, graph_xml)
892
+ G.graph.update(data)
893
+
894
+ # switch to Graph or DiGraph if no parallel edges were found
895
+ if self.multigraph:
896
+ return G
897
+
898
+ G = nx.DiGraph(G) if G.is_directed() else nx.Graph(G)
899
+ # add explicit edge "id" from file as attribute in NX graph.
900
+ nx.set_edge_attributes(G, values=self.edge_ids, name="id")
901
+ return G
902
+
903
+ def add_node(self, G, node_xml, graphml_keys, defaults):
904
+ """Add a node to the graph."""
905
+ # warn on finding unsupported ports tag
906
+ ports = node_xml.find(f"{{{self.NS_GRAPHML}}}port")
907
+ if ports is not None:
908
+ warnings.warn("GraphML port tag not supported.")
909
+ # find the node by id and cast it to the appropriate type
910
+ node_id = self.node_type(node_xml.get("id"))
911
+ # get data/attributes for node
912
+ data = self.decode_data_elements(graphml_keys, node_xml)
913
+ G.add_node(node_id, **data)
914
+ # get child nodes
915
+ if node_xml.attrib.get("yfiles.foldertype") == "group":
916
+ graph_xml = node_xml.find(f"{{{self.NS_GRAPHML}}}graph")
917
+ self.make_graph(graph_xml, graphml_keys, defaults, G)
918
+
919
+ def add_edge(self, G, edge_element, graphml_keys):
920
+ """Add an edge to the graph."""
921
+ # warn on finding unsupported ports tag
922
+ ports = edge_element.find(f"{{{self.NS_GRAPHML}}}port")
923
+ if ports is not None:
924
+ warnings.warn("GraphML port tag not supported.")
925
+
926
+ # raise error if we find mixed directed and undirected edges
927
+ directed = edge_element.get("directed")
928
+ if G.is_directed() and directed == "false":
929
+ msg = "directed=false edge found in directed graph."
930
+ raise nx.NetworkXError(msg)
931
+ if (not G.is_directed()) and directed == "true":
932
+ msg = "directed=true edge found in undirected graph."
933
+ raise nx.NetworkXError(msg)
934
+
935
+ source = self.node_type(edge_element.get("source"))
936
+ target = self.node_type(edge_element.get("target"))
937
+ data = self.decode_data_elements(graphml_keys, edge_element)
938
+ # GraphML stores edge ids as an attribute
939
+ # NetworkX uses them as keys in multigraphs too if no key
940
+ # attribute is specified
941
+ edge_id = edge_element.get("id")
942
+ if edge_id:
943
+ # self.edge_ids is used by `make_graph` method for non-multigraphs
944
+ self.edge_ids[source, target] = edge_id
945
+ try:
946
+ edge_id = self.edge_key_type(edge_id)
947
+ except ValueError: # Could not convert.
948
+ pass
949
+ else:
950
+ edge_id = data.get("key")
951
+
952
+ if G.has_edge(source, target):
953
+ # mark this as a multigraph
954
+ self.multigraph = True
955
+
956
+ # Use add_edges_from to avoid error with add_edge when `'key' in data`
957
+ # Note there is only one edge here...
958
+ G.add_edges_from([(source, target, edge_id, data)])
959
+
960
+ def decode_data_elements(self, graphml_keys, obj_xml):
961
+ """Use the key information to decode the data XML if present."""
962
+ data = {}
963
+ for data_element in obj_xml.findall(f"{{{self.NS_GRAPHML}}}data"):
964
+ key = data_element.get("key")
965
+ try:
966
+ data_name = graphml_keys[key]["name"]
967
+ data_type = graphml_keys[key]["type"]
968
+ except KeyError as err:
969
+ raise nx.NetworkXError(f"Bad GraphML data: no key {key}") from err
970
+ text = data_element.text
971
+ # assume anything with subelements is a yfiles extension
972
+ if text is not None and len(list(data_element)) == 0:
973
+ if data_type == bool:
974
+ # Ignore cases.
975
+ # http://docs.oracle.com/javase/6/docs/api/java/lang/
976
+ # Boolean.html#parseBoolean%28java.lang.String%29
977
+ data[data_name] = self.convert_bool[text.lower()]
978
+ else:
979
+ data[data_name] = data_type(text)
980
+ elif len(list(data_element)) > 0:
981
+ # Assume yfiles as subelements, try to extract node_label
982
+ node_label = None
983
+ # set GenericNode's configuration as shape type
984
+ gn = data_element.find(f"{{{self.NS_Y}}}GenericNode")
985
+ if gn is not None:
986
+ data["shape_type"] = gn.get("configuration")
987
+ for node_type in ["GenericNode", "ShapeNode", "SVGNode", "ImageNode"]:
988
+ pref = f"{{{self.NS_Y}}}{node_type}/{{{self.NS_Y}}}"
989
+ geometry = data_element.find(f"{pref}Geometry")
990
+ if geometry is not None:
991
+ data["x"] = geometry.get("x")
992
+ data["y"] = geometry.get("y")
993
+ if node_label is None:
994
+ node_label = data_element.find(f"{pref}NodeLabel")
995
+ shape = data_element.find(f"{pref}Shape")
996
+ if shape is not None:
997
+ data["shape_type"] = shape.get("type")
998
+ if node_label is not None:
999
+ data["label"] = node_label.text
1000
+
1001
+ # check all the different types of edges available in yEd.
1002
+ for edge_type in [
1003
+ "PolyLineEdge",
1004
+ "SplineEdge",
1005
+ "QuadCurveEdge",
1006
+ "BezierEdge",
1007
+ "ArcEdge",
1008
+ ]:
1009
+ pref = f"{{{self.NS_Y}}}{edge_type}/{{{self.NS_Y}}}"
1010
+ edge_label = data_element.find(f"{pref}EdgeLabel")
1011
+ if edge_label is not None:
1012
+ break
1013
+ if edge_label is not None:
1014
+ data["label"] = edge_label.text
1015
+ elif text is None:
1016
+ data[data_name] = ""
1017
+ return data
1018
+
1019
+ def find_graphml_keys(self, graph_element):
1020
+ """Extracts all the keys and key defaults from the xml."""
1021
+ graphml_keys = {}
1022
+ graphml_key_defaults = {}
1023
+ for k in graph_element.findall(f"{{{self.NS_GRAPHML}}}key"):
1024
+ attr_id = k.get("id")
1025
+ attr_type = k.get("attr.type")
1026
+ attr_name = k.get("attr.name")
1027
+ yfiles_type = k.get("yfiles.type")
1028
+ if yfiles_type is not None:
1029
+ attr_name = yfiles_type
1030
+ attr_type = "yfiles"
1031
+ if attr_type is None:
1032
+ attr_type = "string"
1033
+ warnings.warn(f"No key type for id {attr_id}. Using string")
1034
+ if attr_name is None:
1035
+ raise nx.NetworkXError(f"Unknown key for id {attr_id}.")
1036
+ graphml_keys[attr_id] = {
1037
+ "name": attr_name,
1038
+ "type": self.python_type[attr_type],
1039
+ "for": k.get("for"),
1040
+ }
1041
+ # check for "default" sub-element of key element
1042
+ default = k.find(f"{{{self.NS_GRAPHML}}}default")
1043
+ if default is not None:
1044
+ # Handle default values identically to data element values
1045
+ python_type = graphml_keys[attr_id]["type"]
1046
+ if python_type == bool:
1047
+ graphml_key_defaults[attr_id] = self.convert_bool[
1048
+ default.text.lower()
1049
+ ]
1050
+ else:
1051
+ graphml_key_defaults[attr_id] = python_type(default.text)
1052
+ return graphml_keys, graphml_key_defaults
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ *********
3
+ JSON data
4
+ *********
5
+ Generate and parse JSON serializable data for NetworkX graphs.
6
+
7
+ These formats are suitable for use with the d3.js examples https://d3js.org/
8
+
9
+ The three formats that you can generate with NetworkX are:
10
+
11
+ - node-link like in the d3.js example https://bl.ocks.org/mbostock/4062045
12
+ - tree like in the d3.js example https://bl.ocks.org/mbostock/4063550
13
+ - adjacency like in the d3.js example https://bost.ocks.org/mike/miserables/
14
+ """
15
+ from networkx.readwrite.json_graph.node_link import *
16
+ from networkx.readwrite.json_graph.adjacency import *
17
+ from networkx.readwrite.json_graph.tree import *
18
+ from networkx.readwrite.json_graph.cytoscape import *
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/adjacency.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+ __all__ = ["adjacency_data", "adjacency_graph"]
4
+
5
+ _attrs = {"id": "id", "key": "key"}
6
+
7
+
8
+ def adjacency_data(G, attrs=_attrs):
9
+ """Returns data in adjacency format that is suitable for JSON serialization
10
+ and use in JavaScript documents.
11
+
12
+ Parameters
13
+ ----------
14
+ G : NetworkX graph
15
+
16
+ attrs : dict
17
+ A dictionary that contains two keys 'id' and 'key'. The corresponding
18
+ values provide the attribute names for storing NetworkX-internal graph
19
+ data. The values should be unique. Default value:
20
+ :samp:`dict(id='id', key='key')`.
21
+
22
+ If some user-defined graph data use these attribute names as data keys,
23
+ they may be silently dropped.
24
+
25
+ Returns
26
+ -------
27
+ data : dict
28
+ A dictionary with adjacency formatted data.
29
+
30
+ Raises
31
+ ------
32
+ NetworkXError
33
+ If values in attrs are not unique.
34
+
35
+ Examples
36
+ --------
37
+ >>> from networkx.readwrite import json_graph
38
+ >>> G = nx.Graph([(1, 2)])
39
+ >>> data = json_graph.adjacency_data(G)
40
+
41
+ To serialize with json
42
+
43
+ >>> import json
44
+ >>> s = json.dumps(data)
45
+
46
+ Notes
47
+ -----
48
+ Graph, node, and link attributes will be written when using this format
49
+ but attribute keys must be strings if you want to serialize the resulting
50
+ data with JSON.
51
+
52
+ The default value of attrs will be changed in a future release of NetworkX.
53
+
54
+ See Also
55
+ --------
56
+ adjacency_graph, node_link_data, tree_data
57
+ """
58
+ multigraph = G.is_multigraph()
59
+ id_ = attrs["id"]
60
+ # Allow 'key' to be omitted from attrs if the graph is not a multigraph.
61
+ key = None if not multigraph else attrs["key"]
62
+ if id_ == key:
63
+ raise nx.NetworkXError("Attribute names are not unique.")
64
+ data = {}
65
+ data["directed"] = G.is_directed()
66
+ data["multigraph"] = multigraph
67
+ data["graph"] = list(G.graph.items())
68
+ data["nodes"] = []
69
+ data["adjacency"] = []
70
+ for n, nbrdict in G.adjacency():
71
+ data["nodes"].append({**G.nodes[n], id_: n})
72
+ adj = []
73
+ if multigraph:
74
+ for nbr, keys in nbrdict.items():
75
+ for k, d in keys.items():
76
+ adj.append({**d, id_: nbr, key: k})
77
+ else:
78
+ for nbr, d in nbrdict.items():
79
+ adj.append({**d, id_: nbr})
80
+ data["adjacency"].append(adj)
81
+ return data
82
+
83
+
84
+ @nx._dispatchable(graphs=None, returns_graph=True)
85
+ def adjacency_graph(data, directed=False, multigraph=True, attrs=_attrs):
86
+ """Returns graph from adjacency data format.
87
+
88
+ Parameters
89
+ ----------
90
+ data : dict
91
+ Adjacency list formatted graph data
92
+
93
+ directed : bool
94
+ If True, and direction not specified in data, return a directed graph.
95
+
96
+ multigraph : bool
97
+ If True, and multigraph not specified in data, return a multigraph.
98
+
99
+ attrs : dict
100
+ A dictionary that contains two keys 'id' and 'key'. The corresponding
101
+ values provide the attribute names for storing NetworkX-internal graph
102
+ data. The values should be unique. Default value:
103
+ :samp:`dict(id='id', key='key')`.
104
+
105
+ Returns
106
+ -------
107
+ G : NetworkX graph
108
+ A NetworkX graph object
109
+
110
+ Examples
111
+ --------
112
+ >>> from networkx.readwrite import json_graph
113
+ >>> G = nx.Graph([(1, 2)])
114
+ >>> data = json_graph.adjacency_data(G)
115
+ >>> H = json_graph.adjacency_graph(data)
116
+
117
+ Notes
118
+ -----
119
+ The default value of attrs will be changed in a future release of NetworkX.
120
+
121
+ See Also
122
+ --------
123
+ adjacency_graph, node_link_data, tree_data
124
+ """
125
+ multigraph = data.get("multigraph", multigraph)
126
+ directed = data.get("directed", directed)
127
+ if multigraph:
128
+ graph = nx.MultiGraph()
129
+ else:
130
+ graph = nx.Graph()
131
+ if directed:
132
+ graph = graph.to_directed()
133
+ id_ = attrs["id"]
134
+ # Allow 'key' to be omitted from attrs if the graph is not a multigraph.
135
+ key = None if not multigraph else attrs["key"]
136
+ graph.graph = dict(data.get("graph", []))
137
+ mapping = []
138
+ for d in data["nodes"]:
139
+ node_data = d.copy()
140
+ node = node_data.pop(id_)
141
+ mapping.append(node)
142
+ graph.add_node(node)
143
+ graph.nodes[node].update(node_data)
144
+ for i, d in enumerate(data["adjacency"]):
145
+ source = mapping[i]
146
+ for tdata in d:
147
+ target_data = tdata.copy()
148
+ target = target_data.pop(id_)
149
+ if not multigraph:
150
+ graph.add_edge(source, target)
151
+ graph[source][target].update(target_data)
152
+ else:
153
+ ky = target_data.pop(key, None)
154
+ graph.add_edge(source, target, key=ky)
155
+ graph[source][target][ky].update(target_data)
156
+ return graph
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/cytoscape.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+ __all__ = ["cytoscape_data", "cytoscape_graph"]
4
+
5
+
6
+ def cytoscape_data(G, name="name", ident="id"):
7
+ """Returns data in Cytoscape JSON format (cyjs).
8
+
9
+ Parameters
10
+ ----------
11
+ G : NetworkX Graph
12
+ The graph to convert to cytoscape format
13
+ name : string
14
+ A string which is mapped to the 'name' node element in cyjs format.
15
+ Must not have the same value as `ident`.
16
+ ident : string
17
+ A string which is mapped to the 'id' node element in cyjs format.
18
+ Must not have the same value as `name`.
19
+
20
+ Returns
21
+ -------
22
+ data: dict
23
+ A dictionary with cyjs formatted data.
24
+
25
+ Raises
26
+ ------
27
+ NetworkXError
28
+ If the values for `name` and `ident` are identical.
29
+
30
+ See Also
31
+ --------
32
+ cytoscape_graph: convert a dictionary in cyjs format to a graph
33
+
34
+ References
35
+ ----------
36
+ .. [1] Cytoscape user's manual:
37
+ http://manual.cytoscape.org/en/stable/index.html
38
+
39
+ Examples
40
+ --------
41
+ >>> G = nx.path_graph(2)
42
+ >>> nx.cytoscape_data(G) # doctest: +SKIP
43
+ {'data': [],
44
+ 'directed': False,
45
+ 'multigraph': False,
46
+ 'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
47
+ {'data': {'id': '1', 'value': 1, 'name': '1'}}],
48
+ 'edges': [{'data': {'source': 0, 'target': 1}}]}}
49
+ """
50
+ if name == ident:
51
+ raise nx.NetworkXError("name and ident must be different.")
52
+
53
+ jsondata = {"data": list(G.graph.items())}
54
+ jsondata["directed"] = G.is_directed()
55
+ jsondata["multigraph"] = G.is_multigraph()
56
+ jsondata["elements"] = {"nodes": [], "edges": []}
57
+ nodes = jsondata["elements"]["nodes"]
58
+ edges = jsondata["elements"]["edges"]
59
+
60
+ for i, j in G.nodes.items():
61
+ n = {"data": j.copy()}
62
+ n["data"]["id"] = j.get(ident) or str(i)
63
+ n["data"]["value"] = i
64
+ n["data"]["name"] = j.get(name) or str(i)
65
+ nodes.append(n)
66
+
67
+ if G.is_multigraph():
68
+ for e in G.edges(keys=True):
69
+ n = {"data": G.adj[e[0]][e[1]][e[2]].copy()}
70
+ n["data"]["source"] = e[0]
71
+ n["data"]["target"] = e[1]
72
+ n["data"]["key"] = e[2]
73
+ edges.append(n)
74
+ else:
75
+ for e in G.edges():
76
+ n = {"data": G.adj[e[0]][e[1]].copy()}
77
+ n["data"]["source"] = e[0]
78
+ n["data"]["target"] = e[1]
79
+ edges.append(n)
80
+ return jsondata
81
+
82
+
83
+ @nx._dispatchable(graphs=None, returns_graph=True)
84
+ def cytoscape_graph(data, name="name", ident="id"):
85
+ """
86
+ Create a NetworkX graph from a dictionary in cytoscape JSON format.
87
+
88
+ Parameters
89
+ ----------
90
+ data : dict
91
+ A dictionary of data conforming to cytoscape JSON format.
92
+ name : string
93
+ A string which is mapped to the 'name' node element in cyjs format.
94
+ Must not have the same value as `ident`.
95
+ ident : string
96
+ A string which is mapped to the 'id' node element in cyjs format.
97
+ Must not have the same value as `name`.
98
+
99
+ Returns
100
+ -------
101
+ graph : a NetworkX graph instance
102
+ The `graph` can be an instance of `Graph`, `DiGraph`, `MultiGraph`, or
103
+ `MultiDiGraph` depending on the input data.
104
+
105
+ Raises
106
+ ------
107
+ NetworkXError
108
+ If the `name` and `ident` attributes are identical.
109
+
110
+ See Also
111
+ --------
112
+ cytoscape_data: convert a NetworkX graph to a dict in cyjs format
113
+
114
+ References
115
+ ----------
116
+ .. [1] Cytoscape user's manual:
117
+ http://manual.cytoscape.org/en/stable/index.html
118
+
119
+ Examples
120
+ --------
121
+ >>> data_dict = {
122
+ ... "data": [],
123
+ ... "directed": False,
124
+ ... "multigraph": False,
125
+ ... "elements": {
126
+ ... "nodes": [
127
+ ... {"data": {"id": "0", "value": 0, "name": "0"}},
128
+ ... {"data": {"id": "1", "value": 1, "name": "1"}},
129
+ ... ],
130
+ ... "edges": [{"data": {"source": 0, "target": 1}}],
131
+ ... },
132
+ ... }
133
+ >>> G = nx.cytoscape_graph(data_dict)
134
+ >>> G.name
135
+ ''
136
+ >>> G.nodes()
137
+ NodeView((0, 1))
138
+ >>> G.nodes(data=True)[0]
139
+ {'id': '0', 'value': 0, 'name': '0'}
140
+ >>> G.edges(data=True)
141
+ EdgeDataView([(0, 1, {'source': 0, 'target': 1})])
142
+ """
143
+ if name == ident:
144
+ raise nx.NetworkXError("name and ident must be different.")
145
+
146
+ multigraph = data.get("multigraph")
147
+ directed = data.get("directed")
148
+ if multigraph:
149
+ graph = nx.MultiGraph()
150
+ else:
151
+ graph = nx.Graph()
152
+ if directed:
153
+ graph = graph.to_directed()
154
+ graph.graph = dict(data.get("data"))
155
+ for d in data["elements"]["nodes"]:
156
+ node_data = d["data"].copy()
157
+ node = d["data"]["value"]
158
+
159
+ if d["data"].get(name):
160
+ node_data[name] = d["data"].get(name)
161
+ if d["data"].get(ident):
162
+ node_data[ident] = d["data"].get(ident)
163
+
164
+ graph.add_node(node)
165
+ graph.nodes[node].update(node_data)
166
+
167
+ for d in data["elements"]["edges"]:
168
+ edge_data = d["data"].copy()
169
+ sour = d["data"]["source"]
170
+ targ = d["data"]["target"]
171
+ if multigraph:
172
+ key = d["data"].get("key", 0)
173
+ graph.add_edge(sour, targ, key=key)
174
+ graph.edges[sour, targ, key].update(edge_data)
175
+ else:
176
+ graph.add_edge(sour, targ)
177
+ graph.edges[sour, targ].update(edge_data)
178
+ return graph
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/node_link.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import chain, count
2
+
3
+ import networkx as nx
4
+
5
+ __all__ = ["node_link_data", "node_link_graph"]
6
+
7
+
8
+ _attrs = {
9
+ "source": "source",
10
+ "target": "target",
11
+ "name": "id",
12
+ "key": "key",
13
+ "link": "links",
14
+ }
15
+
16
+
17
+ def _to_tuple(x):
18
+ """Converts lists to tuples, including nested lists.
19
+
20
+ All other non-list inputs are passed through unmodified. This function is
21
+ intended to be used to convert potentially nested lists from json files
22
+ into valid nodes.
23
+
24
+ Examples
25
+ --------
26
+ >>> _to_tuple([1, 2, [3, 4]])
27
+ (1, 2, (3, 4))
28
+ """
29
+ if not isinstance(x, tuple | list):
30
+ return x
31
+ return tuple(map(_to_tuple, x))
32
+
33
+
34
+ def node_link_data(
35
+ G,
36
+ *,
37
+ source="source",
38
+ target="target",
39
+ name="id",
40
+ key="key",
41
+ link="links",
42
+ ):
43
+ """Returns data in node-link format that is suitable for JSON serialization
44
+ and use in JavaScript documents.
45
+
46
+ Parameters
47
+ ----------
48
+ G : NetworkX graph
49
+ source : string
50
+ A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
51
+ target : string
52
+ A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
53
+ name : string
54
+ A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
55
+ key : string
56
+ A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
57
+ link : string
58
+ A string that provides the 'link' attribute name for storing NetworkX-internal graph data.
59
+
60
+ Returns
61
+ -------
62
+ data : dict
63
+ A dictionary with node-link formatted data.
64
+
65
+ Raises
66
+ ------
67
+ NetworkXError
68
+ If the values of 'source', 'target' and 'key' are not unique.
69
+
70
+ Examples
71
+ --------
72
+ >>> G = nx.Graph([("A", "B")])
73
+ >>> data1 = nx.node_link_data(G)
74
+ >>> data1
75
+ {'directed': False, 'multigraph': False, 'graph': {}, 'nodes': [{'id': 'A'}, {'id': 'B'}], 'links': [{'source': 'A', 'target': 'B'}]}
76
+
77
+ To serialize with JSON
78
+
79
+ >>> import json
80
+ >>> s1 = json.dumps(data1)
81
+ >>> s1
82
+ '{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, {"id": "B"}], "links": [{"source": "A", "target": "B"}]}'
83
+
84
+ A graph can also be serialized by passing `node_link_data` as an encoder function. The two methods are equivalent.
85
+
86
+ >>> s1 = json.dumps(G, default=nx.node_link_data)
87
+ >>> s1
88
+ '{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, {"id": "B"}], "links": [{"source": "A", "target": "B"}]}'
89
+
90
+ The attribute names for storing NetworkX-internal graph data can
91
+ be specified as keyword options.
92
+
93
+ >>> H = nx.gn_graph(2)
94
+ >>> data2 = nx.node_link_data(H, link="edges", source="from", target="to")
95
+ >>> data2
96
+ {'directed': True, 'multigraph': False, 'graph': {}, 'nodes': [{'id': 0}, {'id': 1}], 'edges': [{'from': 1, 'to': 0}]}
97
+
98
+ Notes
99
+ -----
100
+ Graph, node, and link attributes are stored in this format. Note that
101
+ attribute keys will be converted to strings in order to comply with JSON.
102
+
103
+ Attribute 'key' is only used for multigraphs.
104
+
105
+ To use `node_link_data` in conjunction with `node_link_graph`,
106
+ the keyword names for the attributes must match.
107
+
108
+
109
+ See Also
110
+ --------
111
+ node_link_graph, adjacency_data, tree_data
112
+ """
113
+ multigraph = G.is_multigraph()
114
+
115
+ # Allow 'key' to be omitted from attrs if the graph is not a multigraph.
116
+ key = None if not multigraph else key
117
+ if len({source, target, key}) < 3:
118
+ raise nx.NetworkXError("Attribute names are not unique.")
119
+ data = {
120
+ "directed": G.is_directed(),
121
+ "multigraph": multigraph,
122
+ "graph": G.graph,
123
+ "nodes": [{**G.nodes[n], name: n} for n in G],
124
+ }
125
+ if multigraph:
126
+ data[link] = [
127
+ {**d, source: u, target: v, key: k}
128
+ for u, v, k, d in G.edges(keys=True, data=True)
129
+ ]
130
+ else:
131
+ data[link] = [{**d, source: u, target: v} for u, v, d in G.edges(data=True)]
132
+ return data
133
+
134
+
135
+ @nx._dispatchable(graphs=None, returns_graph=True)
136
+ def node_link_graph(
137
+ data,
138
+ directed=False,
139
+ multigraph=True,
140
+ *,
141
+ source="source",
142
+ target="target",
143
+ name="id",
144
+ key="key",
145
+ link="links",
146
+ ):
147
+ """Returns graph from node-link data format.
148
+ Useful for de-serialization from JSON.
149
+
150
+ Parameters
151
+ ----------
152
+ data : dict
153
+ node-link formatted graph data
154
+
155
+ directed : bool
156
+ If True, and direction not specified in data, return a directed graph.
157
+
158
+ multigraph : bool
159
+ If True, and multigraph not specified in data, return a multigraph.
160
+
161
+ source : string
162
+ A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
163
+ target : string
164
+ A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
165
+ name : string
166
+ A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
167
+ key : string
168
+ A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
169
+ link : string
170
+ A string that provides the 'link' attribute name for storing NetworkX-internal graph data.
171
+
172
+ Returns
173
+ -------
174
+ G : NetworkX graph
175
+ A NetworkX graph object
176
+
177
+ Examples
178
+ --------
179
+
180
+ Create data in node-link format by converting a graph.
181
+
182
+ >>> G = nx.Graph([("A", "B")])
183
+ >>> data = nx.node_link_data(G)
184
+ >>> data
185
+ {'directed': False, 'multigraph': False, 'graph': {}, 'nodes': [{'id': 'A'}, {'id': 'B'}], 'links': [{'source': 'A', 'target': 'B'}]}
186
+
187
+ Revert data in node-link format to a graph.
188
+
189
+ >>> H = nx.node_link_graph(data)
190
+ >>> print(H.edges)
191
+ [('A', 'B')]
192
+
193
+ To serialize and deserialize a graph with JSON,
194
+
195
+ >>> import json
196
+ >>> d = json.dumps(node_link_data(G))
197
+ >>> H = node_link_graph(json.loads(d))
198
+ >>> print(G.edges, H.edges)
199
+ [('A', 'B')] [('A', 'B')]
200
+
201
+
202
+ Notes
203
+ -----
204
+ Attribute 'key' is only used for multigraphs.
205
+
206
+ To use `node_link_data` in conjunction with `node_link_graph`,
207
+ the keyword names for the attributes must match.
208
+
209
+ See Also
210
+ --------
211
+ node_link_data, adjacency_data, tree_data
212
+ """
213
+ multigraph = data.get("multigraph", multigraph)
214
+ directed = data.get("directed", directed)
215
+ if multigraph:
216
+ graph = nx.MultiGraph()
217
+ else:
218
+ graph = nx.Graph()
219
+ if directed:
220
+ graph = graph.to_directed()
221
+
222
+ # Allow 'key' to be omitted from attrs if the graph is not a multigraph.
223
+ key = None if not multigraph else key
224
+ graph.graph = data.get("graph", {})
225
+ c = count()
226
+ for d in data["nodes"]:
227
+ node = _to_tuple(d.get(name, next(c)))
228
+ nodedata = {str(k): v for k, v in d.items() if k != name}
229
+ graph.add_node(node, **nodedata)
230
+ for d in data[link]:
231
+ src = tuple(d[source]) if isinstance(d[source], list) else d[source]
232
+ tgt = tuple(d[target]) if isinstance(d[target], list) else d[target]
233
+ if not multigraph:
234
+ edgedata = {str(k): v for k, v in d.items() if k != source and k != target}
235
+ graph.add_edge(src, tgt, **edgedata)
236
+ else:
237
+ ky = d.get(key, None)
238
+ edgedata = {
239
+ str(k): v
240
+ for k, v in d.items()
241
+ if k != source and k != target and k != key
242
+ }
243
+ graph.add_edge(src, tgt, ky, **edgedata)
244
+ return graph
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/__init__.py ADDED
File without changes
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_adjacency.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import json
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.readwrite.json_graph import adjacency_data, adjacency_graph
8
+ from networkx.utils import graphs_equal
9
+
10
+
11
+ class TestAdjacency:
12
+ def test_graph(self):
13
+ G = nx.path_graph(4)
14
+ H = adjacency_graph(adjacency_data(G))
15
+ assert graphs_equal(G, H)
16
+
17
+ def test_graph_attributes(self):
18
+ G = nx.path_graph(4)
19
+ G.add_node(1, color="red")
20
+ G.add_edge(1, 2, width=7)
21
+ G.graph["foo"] = "bar"
22
+ G.graph[1] = "one"
23
+
24
+ H = adjacency_graph(adjacency_data(G))
25
+ assert graphs_equal(G, H)
26
+ assert H.graph["foo"] == "bar"
27
+ assert H.nodes[1]["color"] == "red"
28
+ assert H[1][2]["width"] == 7
29
+
30
+ d = json.dumps(adjacency_data(G))
31
+ H = adjacency_graph(json.loads(d))
32
+ assert graphs_equal(G, H)
33
+ assert H.graph["foo"] == "bar"
34
+ assert H.graph[1] == "one"
35
+ assert H.nodes[1]["color"] == "red"
36
+ assert H[1][2]["width"] == 7
37
+
38
+ def test_digraph(self):
39
+ G = nx.DiGraph()
40
+ nx.add_path(G, [1, 2, 3])
41
+ H = adjacency_graph(adjacency_data(G))
42
+ assert H.is_directed()
43
+ assert graphs_equal(G, H)
44
+
45
+ def test_multidigraph(self):
46
+ G = nx.MultiDiGraph()
47
+ nx.add_path(G, [1, 2, 3])
48
+ H = adjacency_graph(adjacency_data(G))
49
+ assert H.is_directed()
50
+ assert H.is_multigraph()
51
+ assert graphs_equal(G, H)
52
+
53
+ def test_multigraph(self):
54
+ G = nx.MultiGraph()
55
+ G.add_edge(1, 2, key="first")
56
+ G.add_edge(1, 2, key="second", color="blue")
57
+ H = adjacency_graph(adjacency_data(G))
58
+ assert graphs_equal(G, H)
59
+ assert H[1][2]["second"]["color"] == "blue"
60
+
61
+ def test_input_data_is_not_modified_when_building_graph(self):
62
+ G = nx.path_graph(4)
63
+ input_data = adjacency_data(G)
64
+ orig_data = copy.deepcopy(input_data)
65
+ # Ensure input is unmodified by deserialisation
66
+ assert graphs_equal(G, adjacency_graph(input_data))
67
+ assert input_data == orig_data
68
+
69
+ def test_adjacency_form_json_serialisable(self):
70
+ G = nx.path_graph(4)
71
+ H = adjacency_graph(json.loads(json.dumps(adjacency_data(G))))
72
+ assert graphs_equal(G, H)
73
+
74
+ def test_exception(self):
75
+ with pytest.raises(nx.NetworkXError):
76
+ G = nx.MultiDiGraph()
77
+ attrs = {"id": "node", "key": "node"}
78
+ adjacency_data(G, attrs)
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_cytoscape.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import json
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.readwrite.json_graph import cytoscape_data, cytoscape_graph
8
+
9
+
10
+ def test_graph():
11
+ G = nx.path_graph(4)
12
+ H = cytoscape_graph(cytoscape_data(G))
13
+ assert nx.is_isomorphic(G, H)
14
+
15
+
16
+ def test_input_data_is_not_modified_when_building_graph():
17
+ G = nx.path_graph(4)
18
+ input_data = cytoscape_data(G)
19
+ orig_data = copy.deepcopy(input_data)
20
+ # Ensure input is unmodified by cytoscape_graph (gh-4173)
21
+ cytoscape_graph(input_data)
22
+ assert input_data == orig_data
23
+
24
+
25
+ def test_graph_attributes():
26
+ G = nx.path_graph(4)
27
+ G.add_node(1, color="red")
28
+ G.add_edge(1, 2, width=7)
29
+ G.graph["foo"] = "bar"
30
+ G.graph[1] = "one"
31
+ G.add_node(3, name="node", id="123")
32
+
33
+ H = cytoscape_graph(cytoscape_data(G))
34
+ assert H.graph["foo"] == "bar"
35
+ assert H.nodes[1]["color"] == "red"
36
+ assert H[1][2]["width"] == 7
37
+ assert H.nodes[3]["name"] == "node"
38
+ assert H.nodes[3]["id"] == "123"
39
+
40
+ d = json.dumps(cytoscape_data(G))
41
+ H = cytoscape_graph(json.loads(d))
42
+ assert H.graph["foo"] == "bar"
43
+ assert H.graph[1] == "one"
44
+ assert H.nodes[1]["color"] == "red"
45
+ assert H[1][2]["width"] == 7
46
+ assert H.nodes[3]["name"] == "node"
47
+ assert H.nodes[3]["id"] == "123"
48
+
49
+
50
+ def test_digraph():
51
+ G = nx.DiGraph()
52
+ nx.add_path(G, [1, 2, 3])
53
+ H = cytoscape_graph(cytoscape_data(G))
54
+ assert H.is_directed()
55
+ assert nx.is_isomorphic(G, H)
56
+
57
+
58
+ def test_multidigraph():
59
+ G = nx.MultiDiGraph()
60
+ nx.add_path(G, [1, 2, 3])
61
+ H = cytoscape_graph(cytoscape_data(G))
62
+ assert H.is_directed()
63
+ assert H.is_multigraph()
64
+
65
+
66
+ def test_multigraph():
67
+ G = nx.MultiGraph()
68
+ G.add_edge(1, 2, key="first")
69
+ G.add_edge(1, 2, key="second", color="blue")
70
+ H = cytoscape_graph(cytoscape_data(G))
71
+ assert nx.is_isomorphic(G, H)
72
+ assert H[1][2]["second"]["color"] == "blue"
73
+
74
+
75
+ def test_exception():
76
+ with pytest.raises(nx.NetworkXError):
77
+ G = nx.MultiDiGraph()
78
+ cytoscape_data(G, name="foo", ident="foo")
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_node_link.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+ from networkx.readwrite.json_graph import node_link_data, node_link_graph
7
+
8
+
9
+ class TestNodeLink:
10
+ # TODO: To be removed when signature change complete
11
+ def test_custom_attrs_dep(self):
12
+ G = nx.path_graph(4)
13
+ G.add_node(1, color="red")
14
+ G.add_edge(1, 2, width=7)
15
+ G.graph[1] = "one"
16
+ G.graph["foo"] = "bar"
17
+
18
+ attrs = {
19
+ "source": "c_source",
20
+ "target": "c_target",
21
+ "name": "c_id",
22
+ "key": "c_key",
23
+ "link": "c_links",
24
+ }
25
+
26
+ H = node_link_graph(node_link_data(G, **attrs), multigraph=False, **attrs)
27
+ assert nx.is_isomorphic(G, H)
28
+ assert H.graph["foo"] == "bar"
29
+ assert H.nodes[1]["color"] == "red"
30
+ assert H[1][2]["width"] == 7
31
+
32
+ # provide only a partial dictionary of keywords.
33
+ # This is similar to an example in the doc string
34
+ attrs = {
35
+ "link": "c_links",
36
+ "source": "c_source",
37
+ "target": "c_target",
38
+ }
39
+ H = node_link_graph(node_link_data(G, **attrs), multigraph=False, **attrs)
40
+ assert nx.is_isomorphic(G, H)
41
+ assert H.graph["foo"] == "bar"
42
+ assert H.nodes[1]["color"] == "red"
43
+ assert H[1][2]["width"] == 7
44
+
45
+ def test_exception_dep(self):
46
+ with pytest.raises(nx.NetworkXError):
47
+ G = nx.MultiDiGraph()
48
+ node_link_data(G, name="node", source="node", target="node", key="node")
49
+
50
+ def test_graph(self):
51
+ G = nx.path_graph(4)
52
+ H = node_link_graph(node_link_data(G))
53
+ assert nx.is_isomorphic(G, H)
54
+
55
+ def test_graph_attributes(self):
56
+ G = nx.path_graph(4)
57
+ G.add_node(1, color="red")
58
+ G.add_edge(1, 2, width=7)
59
+ G.graph[1] = "one"
60
+ G.graph["foo"] = "bar"
61
+
62
+ H = node_link_graph(node_link_data(G))
63
+ assert H.graph["foo"] == "bar"
64
+ assert H.nodes[1]["color"] == "red"
65
+ assert H[1][2]["width"] == 7
66
+
67
+ d = json.dumps(node_link_data(G))
68
+ H = node_link_graph(json.loads(d))
69
+ assert H.graph["foo"] == "bar"
70
+ assert H.graph["1"] == "one"
71
+ assert H.nodes[1]["color"] == "red"
72
+ assert H[1][2]["width"] == 7
73
+
74
+ def test_digraph(self):
75
+ G = nx.DiGraph()
76
+ H = node_link_graph(node_link_data(G))
77
+ assert H.is_directed()
78
+
79
+ def test_multigraph(self):
80
+ G = nx.MultiGraph()
81
+ G.add_edge(1, 2, key="first")
82
+ G.add_edge(1, 2, key="second", color="blue")
83
+ H = node_link_graph(node_link_data(G))
84
+ assert nx.is_isomorphic(G, H)
85
+ assert H[1][2]["second"]["color"] == "blue"
86
+
87
+ def test_graph_with_tuple_nodes(self):
88
+ G = nx.Graph()
89
+ G.add_edge((0, 0), (1, 0), color=[255, 255, 0])
90
+ d = node_link_data(G)
91
+ dumped_d = json.dumps(d)
92
+ dd = json.loads(dumped_d)
93
+ H = node_link_graph(dd)
94
+ assert H.nodes[(0, 0)] == G.nodes[(0, 0)]
95
+ assert H[(0, 0)][(1, 0)]["color"] == [255, 255, 0]
96
+
97
+ def test_unicode_keys(self):
98
+ q = "qualité"
99
+ G = nx.Graph()
100
+ G.add_node(1, **{q: q})
101
+ s = node_link_data(G)
102
+ output = json.dumps(s, ensure_ascii=False)
103
+ data = json.loads(output)
104
+ H = node_link_graph(data)
105
+ assert H.nodes[1][q] == q
106
+
107
+ def test_exception(self):
108
+ with pytest.raises(nx.NetworkXError):
109
+ G = nx.MultiDiGraph()
110
+ attrs = {"name": "node", "source": "node", "target": "node", "key": "node"}
111
+ node_link_data(G, **attrs)
112
+
113
+ def test_string_ids(self):
114
+ q = "qualité"
115
+ G = nx.DiGraph()
116
+ G.add_node("A")
117
+ G.add_node(q)
118
+ G.add_edge("A", q)
119
+ data = node_link_data(G)
120
+ assert data["links"][0]["source"] == "A"
121
+ assert data["links"][0]["target"] == q
122
+ H = node_link_graph(data)
123
+ assert nx.is_isomorphic(G, H)
124
+
125
+ def test_custom_attrs(self):
126
+ G = nx.path_graph(4)
127
+ G.add_node(1, color="red")
128
+ G.add_edge(1, 2, width=7)
129
+ G.graph[1] = "one"
130
+ G.graph["foo"] = "bar"
131
+
132
+ attrs = {
133
+ "source": "c_source",
134
+ "target": "c_target",
135
+ "name": "c_id",
136
+ "key": "c_key",
137
+ "link": "c_links",
138
+ }
139
+
140
+ H = node_link_graph(node_link_data(G, **attrs), multigraph=False, **attrs)
141
+ assert nx.is_isomorphic(G, H)
142
+ assert H.graph["foo"] == "bar"
143
+ assert H.nodes[1]["color"] == "red"
144
+ assert H[1][2]["width"] == 7
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tests/test_tree.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+ from networkx.readwrite.json_graph import tree_data, tree_graph
7
+
8
+
9
+ def test_graph():
10
+ G = nx.DiGraph()
11
+ G.add_nodes_from([1, 2, 3], color="red")
12
+ G.add_edge(1, 2, foo=7)
13
+ G.add_edge(1, 3, foo=10)
14
+ G.add_edge(3, 4, foo=10)
15
+ H = tree_graph(tree_data(G, 1))
16
+ assert nx.is_isomorphic(G, H)
17
+
18
+
19
+ def test_graph_attributes():
20
+ G = nx.DiGraph()
21
+ G.add_nodes_from([1, 2, 3], color="red")
22
+ G.add_edge(1, 2, foo=7)
23
+ G.add_edge(1, 3, foo=10)
24
+ G.add_edge(3, 4, foo=10)
25
+ H = tree_graph(tree_data(G, 1))
26
+ assert H.nodes[1]["color"] == "red"
27
+
28
+ d = json.dumps(tree_data(G, 1))
29
+ H = tree_graph(json.loads(d))
30
+ assert H.nodes[1]["color"] == "red"
31
+
32
+
33
+ def test_exceptions():
34
+ with pytest.raises(TypeError, match="is not a tree."):
35
+ G = nx.complete_graph(3)
36
+ tree_data(G, 0)
37
+ with pytest.raises(TypeError, match="is not directed."):
38
+ G = nx.path_graph(3)
39
+ tree_data(G, 0)
40
+ with pytest.raises(TypeError, match="is not weakly connected."):
41
+ G = nx.path_graph(3, create_using=nx.DiGraph)
42
+ G.add_edge(2, 0)
43
+ G.add_node(3)
44
+ tree_data(G, 0)
45
+ with pytest.raises(nx.NetworkXError, match="must be different."):
46
+ G = nx.MultiDiGraph()
47
+ G.add_node(0)
48
+ tree_data(G, 0, ident="node", children="node")
pythonProject/.venv/Lib/site-packages/networkx/readwrite/json_graph/tree.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import chain
2
+
3
+ import networkx as nx
4
+
5
+ __all__ = ["tree_data", "tree_graph"]
6
+
7
+
8
+ def tree_data(G, root, ident="id", children="children"):
9
+ """Returns data in tree format that is suitable for JSON serialization
10
+ and use in JavaScript documents.
11
+
12
+ Parameters
13
+ ----------
14
+ G : NetworkX graph
15
+ G must be an oriented tree
16
+
17
+ root : node
18
+ The root of the tree
19
+
20
+ ident : string
21
+ Attribute name for storing NetworkX-internal graph data. `ident` must
22
+ have a different value than `children`. The default is 'id'.
23
+
24
+ children : string
25
+ Attribute name for storing NetworkX-internal graph data. `children`
26
+ must have a different value than `ident`. The default is 'children'.
27
+
28
+ Returns
29
+ -------
30
+ data : dict
31
+ A dictionary with node-link formatted data.
32
+
33
+ Raises
34
+ ------
35
+ NetworkXError
36
+ If `children` and `ident` attributes are identical.
37
+
38
+ Examples
39
+ --------
40
+ >>> from networkx.readwrite import json_graph
41
+ >>> G = nx.DiGraph([(1, 2)])
42
+ >>> data = json_graph.tree_data(G, root=1)
43
+
44
+ To serialize with json
45
+
46
+ >>> import json
47
+ >>> s = json.dumps(data)
48
+
49
+ Notes
50
+ -----
51
+ Node attributes are stored in this format but keys
52
+ for attributes must be strings if you want to serialize with JSON.
53
+
54
+ Graph and edge attributes are not stored.
55
+
56
+ See Also
57
+ --------
58
+ tree_graph, node_link_data, adjacency_data
59
+ """
60
+ if G.number_of_nodes() != G.number_of_edges() + 1:
61
+ raise TypeError("G is not a tree.")
62
+ if not G.is_directed():
63
+ raise TypeError("G is not directed.")
64
+ if not nx.is_weakly_connected(G):
65
+ raise TypeError("G is not weakly connected.")
66
+
67
+ if ident == children:
68
+ raise nx.NetworkXError("The values for `id` and `children` must be different.")
69
+
70
+ def add_children(n, G):
71
+ nbrs = G[n]
72
+ if len(nbrs) == 0:
73
+ return []
74
+ children_ = []
75
+ for child in nbrs:
76
+ d = {**G.nodes[child], ident: child}
77
+ c = add_children(child, G)
78
+ if c:
79
+ d[children] = c
80
+ children_.append(d)
81
+ return children_
82
+
83
+ return {**G.nodes[root], ident: root, children: add_children(root, G)}
84
+
85
+
86
+ @nx._dispatchable(graphs=None, returns_graph=True)
87
+ def tree_graph(data, ident="id", children="children"):
88
+ """Returns graph from tree data format.
89
+
90
+ Parameters
91
+ ----------
92
+ data : dict
93
+ Tree formatted graph data
94
+
95
+ ident : string
96
+ Attribute name for storing NetworkX-internal graph data. `ident` must
97
+ have a different value than `children`. The default is 'id'.
98
+
99
+ children : string
100
+ Attribute name for storing NetworkX-internal graph data. `children`
101
+ must have a different value than `ident`. The default is 'children'.
102
+
103
+ Returns
104
+ -------
105
+ G : NetworkX DiGraph
106
+
107
+ Examples
108
+ --------
109
+ >>> from networkx.readwrite import json_graph
110
+ >>> G = nx.DiGraph([(1, 2)])
111
+ >>> data = json_graph.tree_data(G, root=1)
112
+ >>> H = json_graph.tree_graph(data)
113
+
114
+ See Also
115
+ --------
116
+ tree_data, node_link_data, adjacency_data
117
+ """
118
+ graph = nx.DiGraph()
119
+
120
+ def add_children(parent, children_):
121
+ for data in children_:
122
+ child = data[ident]
123
+ graph.add_edge(parent, child)
124
+ grandchildren = data.get(children, [])
125
+ if grandchildren:
126
+ add_children(child, grandchildren)
127
+ nodedata = {
128
+ str(k): v for k, v in data.items() if k != ident and k != children
129
+ }
130
+ graph.add_node(child, **nodedata)
131
+
132
+ root = data[ident]
133
+ children_ = data.get(children, [])
134
+ nodedata = {str(k): v for k, v in data.items() if k != ident and k != children}
135
+ graph.add_node(root, **nodedata)
136
+ add_children(root, children_)
137
+ return graph
pythonProject/.venv/Lib/site-packages/networkx/readwrite/leda.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Read graphs in LEDA format.
3
+
4
+ LEDA is a C++ class library for efficient data types and algorithms.
5
+
6
+ Format
7
+ ------
8
+ See http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
9
+
10
+ """
11
+ # Original author: D. Eppstein, UC Irvine, August 12, 2003.
12
+ # The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain.
13
+
14
+ __all__ = ["read_leda", "parse_leda"]
15
+
16
+ import networkx as nx
17
+ from networkx.exception import NetworkXError
18
+ from networkx.utils import open_file
19
+
20
+
21
+ @open_file(0, mode="rb")
22
+ @nx._dispatchable(graphs=None, returns_graph=True)
23
+ def read_leda(path, encoding="UTF-8"):
24
+ """Read graph in LEDA format from path.
25
+
26
+ Parameters
27
+ ----------
28
+ path : file or string
29
+ File or filename to read. Filenames ending in .gz or .bz2 will be
30
+ uncompressed.
31
+
32
+ Returns
33
+ -------
34
+ G : NetworkX graph
35
+
36
+ Examples
37
+ --------
38
+ G=nx.read_leda('file.leda')
39
+
40
+ References
41
+ ----------
42
+ .. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
43
+ """
44
+ lines = (line.decode(encoding) for line in path)
45
+ G = parse_leda(lines)
46
+ return G
47
+
48
+
49
+ @nx._dispatchable(graphs=None, returns_graph=True)
50
+ def parse_leda(lines):
51
+ """Read graph in LEDA format from string or iterable.
52
+
53
+ Parameters
54
+ ----------
55
+ lines : string or iterable
56
+ Data in LEDA format.
57
+
58
+ Returns
59
+ -------
60
+ G : NetworkX graph
61
+
62
+ Examples
63
+ --------
64
+ G=nx.parse_leda(string)
65
+
66
+ References
67
+ ----------
68
+ .. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
69
+ """
70
+ if isinstance(lines, str):
71
+ lines = iter(lines.split("\n"))
72
+ lines = iter(
73
+ [
74
+ line.rstrip("\n")
75
+ for line in lines
76
+ if not (line.startswith(("#", "\n")) or line == "")
77
+ ]
78
+ )
79
+ for i in range(3):
80
+ next(lines)
81
+ # Graph
82
+ du = int(next(lines)) # -1=directed, -2=undirected
83
+ if du == -1:
84
+ G = nx.DiGraph()
85
+ else:
86
+ G = nx.Graph()
87
+
88
+ # Nodes
89
+ n = int(next(lines)) # number of nodes
90
+ node = {}
91
+ for i in range(1, n + 1): # LEDA counts from 1 to n
92
+ symbol = next(lines).rstrip().strip("|{}| ")
93
+ if symbol == "":
94
+ symbol = str(i) # use int if no label - could be trouble
95
+ node[i] = symbol
96
+
97
+ G.add_nodes_from([s for i, s in node.items()])
98
+
99
+ # Edges
100
+ m = int(next(lines)) # number of edges
101
+ for i in range(m):
102
+ try:
103
+ s, t, reversal, label = next(lines).split()
104
+ except BaseException as err:
105
+ raise NetworkXError(f"Too few fields in LEDA.GRAPH edge {i+1}") from err
106
+ # BEWARE: no handling of reversal edges
107
+ G.add_edge(node[int(s)], node[int(t)], label=label[2:-2])
108
+ return G
pythonProject/.venv/Lib/site-packages/networkx/readwrite/multiline_adjlist.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ *************************
3
+ Multi-line Adjacency List
4
+ *************************
5
+ Read and write NetworkX graphs as multi-line adjacency lists.
6
+
7
+ The multi-line adjacency list format is useful for graphs with
8
+ nodes that can be meaningfully represented as strings. With this format
9
+ simple edge data can be stored but node or graph data is not.
10
+
11
+ Format
12
+ ------
13
+ The first label in a line is the source node label followed by the node degree
14
+ d. The next d lines are target node labels and optional edge data.
15
+ That pattern repeats for all nodes in the graph.
16
+
17
+ The graph with edges a-b, a-c, d-e can be represented as the following
18
+ adjacency list (anything following the # in a line is a comment)::
19
+
20
+ # example.multiline-adjlist
21
+ a 2
22
+ b
23
+ c
24
+ d 1
25
+ e
26
+ """
27
+
28
+ __all__ = [
29
+ "generate_multiline_adjlist",
30
+ "write_multiline_adjlist",
31
+ "parse_multiline_adjlist",
32
+ "read_multiline_adjlist",
33
+ ]
34
+
35
+ import networkx as nx
36
+ from networkx.utils import open_file
37
+
38
+
39
+ def generate_multiline_adjlist(G, delimiter=" "):
40
+ """Generate a single line of the graph G in multiline adjacency list format.
41
+
42
+ Parameters
43
+ ----------
44
+ G : NetworkX graph
45
+
46
+ delimiter : string, optional
47
+ Separator for node labels
48
+
49
+ Returns
50
+ -------
51
+ lines : string
52
+ Lines of data in multiline adjlist format.
53
+
54
+ Examples
55
+ --------
56
+ >>> G = nx.lollipop_graph(4, 3)
57
+ >>> for line in nx.generate_multiline_adjlist(G):
58
+ ... print(line)
59
+ 0 3
60
+ 1 {}
61
+ 2 {}
62
+ 3 {}
63
+ 1 2
64
+ 2 {}
65
+ 3 {}
66
+ 2 1
67
+ 3 {}
68
+ 3 1
69
+ 4 {}
70
+ 4 1
71
+ 5 {}
72
+ 5 1
73
+ 6 {}
74
+ 6 0
75
+
76
+ See Also
77
+ --------
78
+ write_multiline_adjlist, read_multiline_adjlist
79
+ """
80
+ if G.is_directed():
81
+ if G.is_multigraph():
82
+ for s, nbrs in G.adjacency():
83
+ nbr_edges = [
84
+ (u, data)
85
+ for u, datadict in nbrs.items()
86
+ for key, data in datadict.items()
87
+ ]
88
+ deg = len(nbr_edges)
89
+ yield str(s) + delimiter + str(deg)
90
+ for u, d in nbr_edges:
91
+ if d is None:
92
+ yield str(u)
93
+ else:
94
+ yield str(u) + delimiter + str(d)
95
+ else: # directed single edges
96
+ for s, nbrs in G.adjacency():
97
+ deg = len(nbrs)
98
+ yield str(s) + delimiter + str(deg)
99
+ for u, d in nbrs.items():
100
+ if d is None:
101
+ yield str(u)
102
+ else:
103
+ yield str(u) + delimiter + str(d)
104
+ else: # undirected
105
+ if G.is_multigraph():
106
+ seen = set() # helper dict used to avoid duplicate edges
107
+ for s, nbrs in G.adjacency():
108
+ nbr_edges = [
109
+ (u, data)
110
+ for u, datadict in nbrs.items()
111
+ if u not in seen
112
+ for key, data in datadict.items()
113
+ ]
114
+ deg = len(nbr_edges)
115
+ yield str(s) + delimiter + str(deg)
116
+ for u, d in nbr_edges:
117
+ if d is None:
118
+ yield str(u)
119
+ else:
120
+ yield str(u) + delimiter + str(d)
121
+ seen.add(s)
122
+ else: # undirected single edges
123
+ seen = set() # helper dict used to avoid duplicate edges
124
+ for s, nbrs in G.adjacency():
125
+ nbr_edges = [(u, d) for u, d in nbrs.items() if u not in seen]
126
+ deg = len(nbr_edges)
127
+ yield str(s) + delimiter + str(deg)
128
+ for u, d in nbr_edges:
129
+ if d is None:
130
+ yield str(u)
131
+ else:
132
+ yield str(u) + delimiter + str(d)
133
+ seen.add(s)
134
+
135
+
136
+ @open_file(1, mode="wb")
137
+ def write_multiline_adjlist(G, path, delimiter=" ", comments="#", encoding="utf-8"):
138
+ """Write the graph G in multiline adjacency list format to path
139
+
140
+ Parameters
141
+ ----------
142
+ G : NetworkX graph
143
+
144
+ path : string or file
145
+ Filename or file handle to write to.
146
+ Filenames ending in .gz or .bz2 will be compressed.
147
+
148
+ comments : string, optional
149
+ Marker for comment lines
150
+
151
+ delimiter : string, optional
152
+ Separator for node labels
153
+
154
+ encoding : string, optional
155
+ Text encoding.
156
+
157
+ Examples
158
+ --------
159
+ >>> G = nx.path_graph(4)
160
+ >>> nx.write_multiline_adjlist(G, "test.adjlist")
161
+
162
+ The path can be a file handle or a string with the name of the file. If a
163
+ file handle is provided, it has to be opened in 'wb' mode.
164
+
165
+ >>> fh = open("test.adjlist", "wb")
166
+ >>> nx.write_multiline_adjlist(G, fh)
167
+
168
+ Filenames ending in .gz or .bz2 will be compressed.
169
+
170
+ >>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
171
+
172
+ See Also
173
+ --------
174
+ read_multiline_adjlist
175
+ """
176
+ import sys
177
+ import time
178
+
179
+ pargs = comments + " ".join(sys.argv)
180
+ header = (
181
+ f"{pargs}\n"
182
+ + comments
183
+ + f" GMT {time.asctime(time.gmtime())}\n"
184
+ + comments
185
+ + f" {G.name}\n"
186
+ )
187
+ path.write(header.encode(encoding))
188
+
189
+ for multiline in generate_multiline_adjlist(G, delimiter):
190
+ multiline += "\n"
191
+ path.write(multiline.encode(encoding))
192
+
193
+
194
+ @nx._dispatchable(graphs=None, returns_graph=True)
195
+ def parse_multiline_adjlist(
196
+ lines, comments="#", delimiter=None, create_using=None, nodetype=None, edgetype=None
197
+ ):
198
+ """Parse lines of a multiline adjacency list representation of a graph.
199
+
200
+ Parameters
201
+ ----------
202
+ lines : list or iterator of strings
203
+ Input data in multiline adjlist format
204
+
205
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
206
+ Graph type to create. If graph instance, then cleared before populated.
207
+
208
+ nodetype : Python type, optional
209
+ Convert nodes to this type.
210
+
211
+ edgetype : Python type, optional
212
+ Convert edges to this type.
213
+
214
+ comments : string, optional
215
+ Marker for comment lines
216
+
217
+ delimiter : string, optional
218
+ Separator for node labels. The default is whitespace.
219
+
220
+ Returns
221
+ -------
222
+ G: NetworkX graph
223
+ The graph corresponding to the lines in multiline adjacency list format.
224
+
225
+ Examples
226
+ --------
227
+ >>> lines = [
228
+ ... "1 2",
229
+ ... "2 {'weight':3, 'name': 'Frodo'}",
230
+ ... "3 {}",
231
+ ... "2 1",
232
+ ... "5 {'weight':6, 'name': 'Saruman'}",
233
+ ... ]
234
+ >>> G = nx.parse_multiline_adjlist(iter(lines), nodetype=int)
235
+ >>> list(G)
236
+ [1, 2, 3, 5]
237
+
238
+ """
239
+ from ast import literal_eval
240
+
241
+ G = nx.empty_graph(0, create_using)
242
+ for line in lines:
243
+ p = line.find(comments)
244
+ if p >= 0:
245
+ line = line[:p]
246
+ if not line:
247
+ continue
248
+ try:
249
+ (u, deg) = line.strip().split(delimiter)
250
+ deg = int(deg)
251
+ except BaseException as err:
252
+ raise TypeError(f"Failed to read node and degree on line ({line})") from err
253
+ if nodetype is not None:
254
+ try:
255
+ u = nodetype(u)
256
+ except BaseException as err:
257
+ raise TypeError(
258
+ f"Failed to convert node ({u}) to type {nodetype}"
259
+ ) from err
260
+ G.add_node(u)
261
+ for i in range(deg):
262
+ while True:
263
+ try:
264
+ line = next(lines)
265
+ except StopIteration as err:
266
+ msg = f"Failed to find neighbor for node ({u})"
267
+ raise TypeError(msg) from err
268
+ p = line.find(comments)
269
+ if p >= 0:
270
+ line = line[:p]
271
+ if line:
272
+ break
273
+ vlist = line.strip().split(delimiter)
274
+ numb = len(vlist)
275
+ if numb < 1:
276
+ continue # isolated node
277
+ v = vlist.pop(0)
278
+ data = "".join(vlist)
279
+ if nodetype is not None:
280
+ try:
281
+ v = nodetype(v)
282
+ except BaseException as err:
283
+ raise TypeError(
284
+ f"Failed to convert node ({v}) to type {nodetype}"
285
+ ) from err
286
+ if edgetype is not None:
287
+ try:
288
+ edgedata = {"weight": edgetype(data)}
289
+ except BaseException as err:
290
+ raise TypeError(
291
+ f"Failed to convert edge data ({data}) to type {edgetype}"
292
+ ) from err
293
+ else:
294
+ try: # try to evaluate
295
+ edgedata = literal_eval(data)
296
+ except:
297
+ edgedata = {}
298
+ G.add_edge(u, v, **edgedata)
299
+
300
+ return G
301
+
302
+
303
+ @open_file(0, mode="rb")
304
+ @nx._dispatchable(graphs=None, returns_graph=True)
305
+ def read_multiline_adjlist(
306
+ path,
307
+ comments="#",
308
+ delimiter=None,
309
+ create_using=None,
310
+ nodetype=None,
311
+ edgetype=None,
312
+ encoding="utf-8",
313
+ ):
314
+ """Read graph in multi-line adjacency list format from path.
315
+
316
+ Parameters
317
+ ----------
318
+ path : string or file
319
+ Filename or file handle to read.
320
+ Filenames ending in .gz or .bz2 will be uncompressed.
321
+
322
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
323
+ Graph type to create. If graph instance, then cleared before populated.
324
+
325
+ nodetype : Python type, optional
326
+ Convert nodes to this type.
327
+
328
+ edgetype : Python type, optional
329
+ Convert edge data to this type.
330
+
331
+ comments : string, optional
332
+ Marker for comment lines
333
+
334
+ delimiter : string, optional
335
+ Separator for node labels. The default is whitespace.
336
+
337
+ Returns
338
+ -------
339
+ G: NetworkX graph
340
+
341
+ Examples
342
+ --------
343
+ >>> G = nx.path_graph(4)
344
+ >>> nx.write_multiline_adjlist(G, "test.adjlist")
345
+ >>> G = nx.read_multiline_adjlist("test.adjlist")
346
+
347
+ The path can be a file or a string with the name of the file. If a
348
+ file s provided, it has to be opened in 'rb' mode.
349
+
350
+ >>> fh = open("test.adjlist", "rb")
351
+ >>> G = nx.read_multiline_adjlist(fh)
352
+
353
+ Filenames ending in .gz or .bz2 will be compressed.
354
+
355
+ >>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
356
+ >>> G = nx.read_multiline_adjlist("test.adjlist.gz")
357
+
358
+ The optional nodetype is a function to convert node strings to nodetype.
359
+
360
+ For example
361
+
362
+ >>> G = nx.read_multiline_adjlist("test.adjlist", nodetype=int)
363
+
364
+ will attempt to convert all nodes to integer type.
365
+
366
+ The optional edgetype is a function to convert edge data strings to
367
+ edgetype.
368
+
369
+ >>> G = nx.read_multiline_adjlist("test.adjlist")
370
+
371
+ The optional create_using parameter is a NetworkX graph container.
372
+ The default is Graph(), an undirected graph. To read the data as
373
+ a directed graph use
374
+
375
+ >>> G = nx.read_multiline_adjlist("test.adjlist", create_using=nx.DiGraph)
376
+
377
+ Notes
378
+ -----
379
+ This format does not store graph, node, or edge data.
380
+
381
+ See Also
382
+ --------
383
+ write_multiline_adjlist
384
+ """
385
+ lines = (line.decode(encoding) for line in path)
386
+ return parse_multiline_adjlist(
387
+ lines,
388
+ comments=comments,
389
+ delimiter=delimiter,
390
+ create_using=create_using,
391
+ nodetype=nodetype,
392
+ edgetype=edgetype,
393
+ )
pythonProject/.venv/Lib/site-packages/networkx/readwrite/p2g.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module provides the following: read and write of p2g format
3
+ used in metabolic pathway studies.
4
+
5
+ See https://web.archive.org/web/20080626113807/http://www.cs.purdue.edu/homes/koyuturk/pathway/ for a description.
6
+
7
+ The summary is included here:
8
+
9
+ A file that describes a uniquely labeled graph (with extension ".gr")
10
+ format looks like the following:
11
+
12
+
13
+ name
14
+ 3 4
15
+ a
16
+ 1 2
17
+ b
18
+
19
+ c
20
+ 0 2
21
+
22
+ "name" is simply a description of what the graph corresponds to. The
23
+ second line displays the number of nodes and number of edges,
24
+ respectively. This sample graph contains three nodes labeled "a", "b",
25
+ and "c". The rest of the graph contains two lines for each node. The
26
+ first line for a node contains the node label. After the declaration
27
+ of the node label, the out-edges of that node in the graph are
28
+ provided. For instance, "a" is linked to nodes 1 and 2, which are
29
+ labeled "b" and "c", while the node labeled "b" has no outgoing
30
+ edges. Observe that node labeled "c" has an outgoing edge to
31
+ itself. Indeed, self-loops are allowed. Node index starts from 0.
32
+
33
+ """
34
+ import networkx as nx
35
+ from networkx.utils import open_file
36
+
37
+
38
+ @open_file(1, mode="w")
39
+ def write_p2g(G, path, encoding="utf-8"):
40
+ """Write NetworkX graph in p2g format.
41
+
42
+ Notes
43
+ -----
44
+ This format is meant to be used with directed graphs with
45
+ possible self loops.
46
+ """
47
+ path.write((f"{G.name}\n").encode(encoding))
48
+ path.write((f"{G.order()} {G.size()}\n").encode(encoding))
49
+ nodes = list(G)
50
+ # make dictionary mapping nodes to integers
51
+ nodenumber = dict(zip(nodes, range(len(nodes))))
52
+ for n in nodes:
53
+ path.write((f"{n}\n").encode(encoding))
54
+ for nbr in G.neighbors(n):
55
+ path.write((f"{nodenumber[nbr]} ").encode(encoding))
56
+ path.write("\n".encode(encoding))
57
+
58
+
59
+ @open_file(0, mode="r")
60
+ @nx._dispatchable(graphs=None, returns_graph=True)
61
+ def read_p2g(path, encoding="utf-8"):
62
+ """Read graph in p2g format from path.
63
+
64
+ Returns
65
+ -------
66
+ MultiDiGraph
67
+
68
+ Notes
69
+ -----
70
+ If you want a DiGraph (with no self loops allowed and no edge data)
71
+ use D=nx.DiGraph(read_p2g(path))
72
+ """
73
+ lines = (line.decode(encoding) for line in path)
74
+ G = parse_p2g(lines)
75
+ return G
76
+
77
+
78
+ @nx._dispatchable(graphs=None, returns_graph=True)
79
+ def parse_p2g(lines):
80
+ """Parse p2g format graph from string or iterable.
81
+
82
+ Returns
83
+ -------
84
+ MultiDiGraph
85
+ """
86
+ description = next(lines).strip()
87
+ # are multiedges (parallel edges) allowed?
88
+ G = nx.MultiDiGraph(name=description, selfloops=True)
89
+ nnodes, nedges = map(int, next(lines).split())
90
+ nodelabel = {}
91
+ nbrs = {}
92
+ # loop over the nodes keeping track of node labels and out neighbors
93
+ # defer adding edges until all node labels are known
94
+ for i in range(nnodes):
95
+ n = next(lines).strip()
96
+ nodelabel[i] = n
97
+ G.add_node(n)
98
+ nbrs[n] = map(int, next(lines).split())
99
+ # now we know all of the node labels so we can add the edges
100
+ # with the correct labels
101
+ for n in G:
102
+ for nbr in nbrs[n]:
103
+ G.add_edge(n, nodelabel[nbr])
104
+ return G
pythonProject/.venv/Lib/site-packages/networkx/readwrite/pajek.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ *****
3
+ Pajek
4
+ *****
5
+ Read graphs in Pajek format.
6
+
7
+ This implementation handles directed and undirected graphs including
8
+ those with self loops and parallel edges.
9
+
10
+ Format
11
+ ------
12
+ See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
13
+ for format information.
14
+
15
+ """
16
+
17
+ import warnings
18
+
19
+ import networkx as nx
20
+ from networkx.utils import open_file
21
+
22
+ __all__ = ["read_pajek", "parse_pajek", "generate_pajek", "write_pajek"]
23
+
24
+
25
+ def generate_pajek(G):
26
+ """Generate lines in Pajek graph format.
27
+
28
+ Parameters
29
+ ----------
30
+ G : graph
31
+ A Networkx graph
32
+
33
+ References
34
+ ----------
35
+ See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
36
+ for format information.
37
+ """
38
+ if G.name == "":
39
+ name = "NetworkX"
40
+ else:
41
+ name = G.name
42
+ # Apparently many Pajek format readers can't process this line
43
+ # So we'll leave it out for now.
44
+ # yield '*network %s'%name
45
+
46
+ # write nodes with attributes
47
+ yield f"*vertices {G.order()}"
48
+ nodes = list(G)
49
+ # make dictionary mapping nodes to integers
50
+ nodenumber = dict(zip(nodes, range(1, len(nodes) + 1)))
51
+ for n in nodes:
52
+ # copy node attributes and pop mandatory attributes
53
+ # to avoid duplication.
54
+ na = G.nodes.get(n, {}).copy()
55
+ x = na.pop("x", 0.0)
56
+ y = na.pop("y", 0.0)
57
+ try:
58
+ id = int(na.pop("id", nodenumber[n]))
59
+ except ValueError as err:
60
+ err.args += (
61
+ (
62
+ "Pajek format requires 'id' to be an int()."
63
+ " Refer to the 'Relabeling nodes' section."
64
+ ),
65
+ )
66
+ raise
67
+ nodenumber[n] = id
68
+ shape = na.pop("shape", "ellipse")
69
+ s = " ".join(map(make_qstr, (id, n, x, y, shape)))
70
+ # only optional attributes are left in na.
71
+ for k, v in na.items():
72
+ if isinstance(v, str) and v.strip() != "":
73
+ s += f" {make_qstr(k)} {make_qstr(v)}"
74
+ else:
75
+ warnings.warn(
76
+ f"Node attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}."
77
+ )
78
+ yield s
79
+
80
+ # write edges with attributes
81
+ if G.is_directed():
82
+ yield "*arcs"
83
+ else:
84
+ yield "*edges"
85
+ for u, v, edgedata in G.edges(data=True):
86
+ d = edgedata.copy()
87
+ value = d.pop("weight", 1.0) # use 1 as default edge value
88
+ s = " ".join(map(make_qstr, (nodenumber[u], nodenumber[v], value)))
89
+ for k, v in d.items():
90
+ if isinstance(v, str) and v.strip() != "":
91
+ s += f" {make_qstr(k)} {make_qstr(v)}"
92
+ else:
93
+ warnings.warn(
94
+ f"Edge attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}."
95
+ )
96
+ yield s
97
+
98
+
99
+ @open_file(1, mode="wb")
100
+ def write_pajek(G, path, encoding="UTF-8"):
101
+ """Write graph in Pajek format to path.
102
+
103
+ Parameters
104
+ ----------
105
+ G : graph
106
+ A Networkx graph
107
+ path : file or string
108
+ File or filename to write.
109
+ Filenames ending in .gz or .bz2 will be compressed.
110
+
111
+ Examples
112
+ --------
113
+ >>> G = nx.path_graph(4)
114
+ >>> nx.write_pajek(G, "test.net")
115
+
116
+ Warnings
117
+ --------
118
+ Optional node attributes and edge attributes must be non-empty strings.
119
+ Otherwise it will not be written into the file. You will need to
120
+ convert those attributes to strings if you want to keep them.
121
+
122
+ References
123
+ ----------
124
+ See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
125
+ for format information.
126
+ """
127
+ for line in generate_pajek(G):
128
+ line += "\n"
129
+ path.write(line.encode(encoding))
130
+
131
+
132
+ @open_file(0, mode="rb")
133
+ @nx._dispatchable(graphs=None, returns_graph=True)
134
+ def read_pajek(path, encoding="UTF-8"):
135
+ """Read graph in Pajek format from path.
136
+
137
+ Parameters
138
+ ----------
139
+ path : file or string
140
+ File or filename to write.
141
+ Filenames ending in .gz or .bz2 will be uncompressed.
142
+
143
+ Returns
144
+ -------
145
+ G : NetworkX MultiGraph or MultiDiGraph.
146
+
147
+ Examples
148
+ --------
149
+ >>> G = nx.path_graph(4)
150
+ >>> nx.write_pajek(G, "test.net")
151
+ >>> G = nx.read_pajek("test.net")
152
+
153
+ To create a Graph instead of a MultiGraph use
154
+
155
+ >>> G1 = nx.Graph(G)
156
+
157
+ References
158
+ ----------
159
+ See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
160
+ for format information.
161
+ """
162
+ lines = (line.decode(encoding) for line in path)
163
+ return parse_pajek(lines)
164
+
165
+
166
+ @nx._dispatchable(graphs=None, returns_graph=True)
167
+ def parse_pajek(lines):
168
+ """Parse Pajek format graph from string or iterable.
169
+
170
+ Parameters
171
+ ----------
172
+ lines : string or iterable
173
+ Data in Pajek format.
174
+
175
+ Returns
176
+ -------
177
+ G : NetworkX graph
178
+
179
+ See Also
180
+ --------
181
+ read_pajek
182
+
183
+ """
184
+ import shlex
185
+
186
+ # multigraph=False
187
+ if isinstance(lines, str):
188
+ lines = iter(lines.split("\n"))
189
+ lines = iter([line.rstrip("\n") for line in lines])
190
+ G = nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes
191
+ labels = [] # in the order of the file, needed for matrix
192
+ while lines:
193
+ try:
194
+ l = next(lines)
195
+ except: # EOF
196
+ break
197
+ if l.lower().startswith("*network"):
198
+ try:
199
+ label, name = l.split(None, 1)
200
+ except ValueError:
201
+ # Line was not of the form: *network NAME
202
+ pass
203
+ else:
204
+ G.graph["name"] = name
205
+ elif l.lower().startswith("*vertices"):
206
+ nodelabels = {}
207
+ l, nnodes = l.split()
208
+ for i in range(int(nnodes)):
209
+ l = next(lines)
210
+ try:
211
+ splitline = [
212
+ x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
213
+ ]
214
+ except AttributeError:
215
+ splitline = shlex.split(str(l))
216
+ id, label = splitline[0:2]
217
+ labels.append(label)
218
+ G.add_node(label)
219
+ nodelabels[id] = label
220
+ G.nodes[label]["id"] = id
221
+ try:
222
+ x, y, shape = splitline[2:5]
223
+ G.nodes[label].update(
224
+ {"x": float(x), "y": float(y), "shape": shape}
225
+ )
226
+ except:
227
+ pass
228
+ extra_attr = zip(splitline[5::2], splitline[6::2])
229
+ G.nodes[label].update(extra_attr)
230
+ elif l.lower().startswith("*edges") or l.lower().startswith("*arcs"):
231
+ if l.lower().startswith("*edge"):
232
+ # switch from multidigraph to multigraph
233
+ G = nx.MultiGraph(G)
234
+ if l.lower().startswith("*arcs"):
235
+ # switch to directed with multiple arcs for each existing edge
236
+ G = G.to_directed()
237
+ for l in lines:
238
+ try:
239
+ splitline = [
240
+ x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
241
+ ]
242
+ except AttributeError:
243
+ splitline = shlex.split(str(l))
244
+
245
+ if len(splitline) < 2:
246
+ continue
247
+ ui, vi = splitline[0:2]
248
+ u = nodelabels.get(ui, ui)
249
+ v = nodelabels.get(vi, vi)
250
+ # parse the data attached to this edge and put in a dictionary
251
+ edge_data = {}
252
+ try:
253
+ # there should always be a single value on the edge?
254
+ w = splitline[2:3]
255
+ edge_data.update({"weight": float(w[0])})
256
+ except:
257
+ pass
258
+ # if there isn't, just assign a 1
259
+ # edge_data.update({'value':1})
260
+ extra_attr = zip(splitline[3::2], splitline[4::2])
261
+ edge_data.update(extra_attr)
262
+ # if G.has_edge(u,v):
263
+ # multigraph=True
264
+ G.add_edge(u, v, **edge_data)
265
+ elif l.lower().startswith("*matrix"):
266
+ G = nx.DiGraph(G)
267
+ adj_list = (
268
+ (labels[row], labels[col], {"weight": int(data)})
269
+ for (row, line) in enumerate(lines)
270
+ for (col, data) in enumerate(line.split())
271
+ if int(data) != 0
272
+ )
273
+ G.add_edges_from(adj_list)
274
+
275
+ return G
276
+
277
+
278
+ def make_qstr(t):
279
+ """Returns the string representation of t.
280
+ Add outer double-quotes if the string has a space.
281
+ """
282
+ if not isinstance(t, str):
283
+ t = str(t)
284
+ if " " in t:
285
+ t = f'"{t}"'
286
+ return t
pythonProject/.venv/Lib/site-packages/networkx/readwrite/sparse6.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Original author: D. Eppstein, UC Irvine, August 12, 2003.
2
+ # The original code at https://www.ics.uci.edu/~eppstein/PADS/ is public domain.
3
+ """Functions for reading and writing graphs in the *sparse6* format.
4
+
5
+ The *sparse6* file format is a space-efficient format for large sparse
6
+ graphs. For small graphs or large dense graphs, use the *graph6* file
7
+ format.
8
+
9
+ For more information, see the `sparse6`_ homepage.
10
+
11
+ .. _sparse6: https://users.cecs.anu.edu.au/~bdm/data/formats.html
12
+
13
+ """
14
+ import networkx as nx
15
+ from networkx.exception import NetworkXError
16
+ from networkx.readwrite.graph6 import data_to_n, n_to_data
17
+ from networkx.utils import not_implemented_for, open_file
18
+
19
+ __all__ = ["from_sparse6_bytes", "read_sparse6", "to_sparse6_bytes", "write_sparse6"]
20
+
21
+
22
+ def _generate_sparse6_bytes(G, nodes, header):
23
+ """Yield bytes in the sparse6 encoding of a graph.
24
+
25
+ `G` is an undirected simple graph. `nodes` is the list of nodes for
26
+ which the node-induced subgraph will be encoded; if `nodes` is the
27
+ list of all nodes in the graph, the entire graph will be
28
+ encoded. `header` is a Boolean that specifies whether to generate
29
+ the header ``b'>>sparse6<<'`` before the remaining data.
30
+
31
+ This function generates `bytes` objects in the following order:
32
+
33
+ 1. the header (if requested),
34
+ 2. the encoding of the number of nodes,
35
+ 3. each character, one-at-a-time, in the encoding of the requested
36
+ node-induced subgraph,
37
+ 4. a newline character.
38
+
39
+ This function raises :exc:`ValueError` if the graph is too large for
40
+ the graph6 format (that is, greater than ``2 ** 36`` nodes).
41
+
42
+ """
43
+ n = len(G)
44
+ if n >= 2**36:
45
+ raise ValueError(
46
+ "sparse6 is only defined if number of nodes is less than 2 ** 36"
47
+ )
48
+ if header:
49
+ yield b">>sparse6<<"
50
+ yield b":"
51
+ for d in n_to_data(n):
52
+ yield str.encode(chr(d + 63))
53
+
54
+ k = 1
55
+ while 1 << k < n:
56
+ k += 1
57
+
58
+ def enc(x):
59
+ """Big endian k-bit encoding of x"""
60
+ return [1 if (x & 1 << (k - 1 - i)) else 0 for i in range(k)]
61
+
62
+ edges = sorted((max(u, v), min(u, v)) for u, v in G.edges())
63
+ bits = []
64
+ curv = 0
65
+ for v, u in edges:
66
+ if v == curv: # current vertex edge
67
+ bits.append(0)
68
+ bits.extend(enc(u))
69
+ elif v == curv + 1: # next vertex edge
70
+ curv += 1
71
+ bits.append(1)
72
+ bits.extend(enc(u))
73
+ else: # skip to vertex v and then add edge to u
74
+ curv = v
75
+ bits.append(1)
76
+ bits.extend(enc(v))
77
+ bits.append(0)
78
+ bits.extend(enc(u))
79
+ if k < 6 and n == (1 << k) and ((-len(bits)) % 6) >= k and curv < (n - 1):
80
+ # Padding special case: small k, n=2^k,
81
+ # more than k bits of padding needed,
82
+ # current vertex is not (n-1) --
83
+ # appending 1111... would add a loop on (n-1)
84
+ bits.append(0)
85
+ bits.extend([1] * ((-len(bits)) % 6))
86
+ else:
87
+ bits.extend([1] * ((-len(bits)) % 6))
88
+
89
+ data = [
90
+ (bits[i + 0] << 5)
91
+ + (bits[i + 1] << 4)
92
+ + (bits[i + 2] << 3)
93
+ + (bits[i + 3] << 2)
94
+ + (bits[i + 4] << 1)
95
+ + (bits[i + 5] << 0)
96
+ for i in range(0, len(bits), 6)
97
+ ]
98
+
99
+ for d in data:
100
+ yield str.encode(chr(d + 63))
101
+ yield b"\n"
102
+
103
+
104
+ @nx._dispatchable(graphs=None, returns_graph=True)
105
+ def from_sparse6_bytes(string):
106
+ """Read an undirected graph in sparse6 format from string.
107
+
108
+ Parameters
109
+ ----------
110
+ string : string
111
+ Data in sparse6 format
112
+
113
+ Returns
114
+ -------
115
+ G : Graph
116
+
117
+ Raises
118
+ ------
119
+ NetworkXError
120
+ If the string is unable to be parsed in sparse6 format
121
+
122
+ Examples
123
+ --------
124
+ >>> G = nx.from_sparse6_bytes(b":A_")
125
+ >>> sorted(G.edges())
126
+ [(0, 1), (0, 1), (0, 1)]
127
+
128
+ See Also
129
+ --------
130
+ read_sparse6, write_sparse6
131
+
132
+ References
133
+ ----------
134
+ .. [1] Sparse6 specification
135
+ <https://users.cecs.anu.edu.au/~bdm/data/formats.html>
136
+
137
+ """
138
+ if string.startswith(b">>sparse6<<"):
139
+ string = string[11:]
140
+ if not string.startswith(b":"):
141
+ raise NetworkXError("Expected leading colon in sparse6")
142
+
143
+ chars = [c - 63 for c in string[1:]]
144
+ n, data = data_to_n(chars)
145
+ k = 1
146
+ while 1 << k < n:
147
+ k += 1
148
+
149
+ def parseData():
150
+ """Returns stream of pairs b[i], x[i] for sparse6 format."""
151
+ chunks = iter(data)
152
+ d = None # partial data word
153
+ dLen = 0 # how many unparsed bits are left in d
154
+
155
+ while 1:
156
+ if dLen < 1:
157
+ try:
158
+ d = next(chunks)
159
+ except StopIteration:
160
+ return
161
+ dLen = 6
162
+ dLen -= 1
163
+ b = (d >> dLen) & 1 # grab top remaining bit
164
+
165
+ x = d & ((1 << dLen) - 1) # partially built up value of x
166
+ xLen = dLen # how many bits included so far in x
167
+ while xLen < k: # now grab full chunks until we have enough
168
+ try:
169
+ d = next(chunks)
170
+ except StopIteration:
171
+ return
172
+ dLen = 6
173
+ x = (x << 6) + d
174
+ xLen += 6
175
+ x = x >> (xLen - k) # shift back the extra bits
176
+ dLen = xLen - k
177
+ yield b, x
178
+
179
+ v = 0
180
+
181
+ G = nx.MultiGraph()
182
+ G.add_nodes_from(range(n))
183
+
184
+ multigraph = False
185
+ for b, x in parseData():
186
+ if b == 1:
187
+ v += 1
188
+ # padding with ones can cause overlarge number here
189
+ if x >= n or v >= n:
190
+ break
191
+ elif x > v:
192
+ v = x
193
+ else:
194
+ if G.has_edge(x, v):
195
+ multigraph = True
196
+ G.add_edge(x, v)
197
+ if not multigraph:
198
+ G = nx.Graph(G)
199
+ return G
200
+
201
+
202
+ def to_sparse6_bytes(G, nodes=None, header=True):
203
+ """Convert an undirected graph to bytes in sparse6 format.
204
+
205
+ Parameters
206
+ ----------
207
+ G : Graph (undirected)
208
+
209
+ nodes: list or iterable
210
+ Nodes are labeled 0...n-1 in the order provided. If None the ordering
211
+ given by ``G.nodes()`` is used.
212
+
213
+ header: bool
214
+ If True add '>>sparse6<<' bytes to head of data.
215
+
216
+ Raises
217
+ ------
218
+ NetworkXNotImplemented
219
+ If the graph is directed.
220
+
221
+ ValueError
222
+ If the graph has at least ``2 ** 36`` nodes; the sparse6 format
223
+ is only defined for graphs of order less than ``2 ** 36``.
224
+
225
+ Examples
226
+ --------
227
+ >>> nx.to_sparse6_bytes(nx.path_graph(2))
228
+ b'>>sparse6<<:An\\n'
229
+
230
+ See Also
231
+ --------
232
+ to_sparse6_bytes, read_sparse6, write_sparse6_bytes
233
+
234
+ Notes
235
+ -----
236
+ The returned bytes end with a newline character.
237
+
238
+ The format does not support edge or node labels.
239
+
240
+ References
241
+ ----------
242
+ .. [1] Graph6 specification
243
+ <https://users.cecs.anu.edu.au/~bdm/data/formats.html>
244
+
245
+ """
246
+ if nodes is not None:
247
+ G = G.subgraph(nodes)
248
+ G = nx.convert_node_labels_to_integers(G, ordering="sorted")
249
+ return b"".join(_generate_sparse6_bytes(G, nodes, header))
250
+
251
+
252
+ @open_file(0, mode="rb")
253
+ @nx._dispatchable(graphs=None, returns_graph=True)
254
+ def read_sparse6(path):
255
+ """Read an undirected graph in sparse6 format from path.
256
+
257
+ Parameters
258
+ ----------
259
+ path : file or string
260
+ File or filename to write.
261
+
262
+ Returns
263
+ -------
264
+ G : Graph/Multigraph or list of Graphs/MultiGraphs
265
+ If the file contains multiple lines then a list of graphs is returned
266
+
267
+ Raises
268
+ ------
269
+ NetworkXError
270
+ If the string is unable to be parsed in sparse6 format
271
+
272
+ Examples
273
+ --------
274
+ You can read a sparse6 file by giving the path to the file::
275
+
276
+ >>> import tempfile
277
+ >>> with tempfile.NamedTemporaryFile(delete=False) as f:
278
+ ... _ = f.write(b">>sparse6<<:An\\n")
279
+ ... _ = f.seek(0)
280
+ ... G = nx.read_sparse6(f.name)
281
+ >>> list(G.edges())
282
+ [(0, 1)]
283
+
284
+ You can also read a sparse6 file by giving an open file-like object::
285
+
286
+ >>> import tempfile
287
+ >>> with tempfile.NamedTemporaryFile() as f:
288
+ ... _ = f.write(b">>sparse6<<:An\\n")
289
+ ... _ = f.seek(0)
290
+ ... G = nx.read_sparse6(f)
291
+ >>> list(G.edges())
292
+ [(0, 1)]
293
+
294
+ See Also
295
+ --------
296
+ read_sparse6, from_sparse6_bytes
297
+
298
+ References
299
+ ----------
300
+ .. [1] Sparse6 specification
301
+ <https://users.cecs.anu.edu.au/~bdm/data/formats.html>
302
+
303
+ """
304
+ glist = []
305
+ for line in path:
306
+ line = line.strip()
307
+ if not len(line):
308
+ continue
309
+ glist.append(from_sparse6_bytes(line))
310
+ if len(glist) == 1:
311
+ return glist[0]
312
+ else:
313
+ return glist
314
+
315
+
316
+ @not_implemented_for("directed")
317
+ @open_file(1, mode="wb")
318
+ def write_sparse6(G, path, nodes=None, header=True):
319
+ """Write graph G to given path in sparse6 format.
320
+
321
+ Parameters
322
+ ----------
323
+ G : Graph (undirected)
324
+
325
+ path : file or string
326
+ File or filename to write
327
+
328
+ nodes: list or iterable
329
+ Nodes are labeled 0...n-1 in the order provided. If None the ordering
330
+ given by G.nodes() is used.
331
+
332
+ header: bool
333
+ If True add '>>sparse6<<' string to head of data
334
+
335
+ Raises
336
+ ------
337
+ NetworkXError
338
+ If the graph is directed
339
+
340
+ Examples
341
+ --------
342
+ You can write a sparse6 file by giving the path to the file::
343
+
344
+ >>> import tempfile
345
+ >>> with tempfile.NamedTemporaryFile(delete=False) as f:
346
+ ... nx.write_sparse6(nx.path_graph(2), f.name)
347
+ ... print(f.read())
348
+ b'>>sparse6<<:An\\n'
349
+
350
+ You can also write a sparse6 file by giving an open file-like object::
351
+
352
+ >>> with tempfile.NamedTemporaryFile() as f:
353
+ ... nx.write_sparse6(nx.path_graph(2), f)
354
+ ... _ = f.seek(0)
355
+ ... print(f.read())
356
+ b'>>sparse6<<:An\\n'
357
+
358
+ See Also
359
+ --------
360
+ read_sparse6, from_sparse6_bytes
361
+
362
+ Notes
363
+ -----
364
+ The format does not support edge or node labels.
365
+
366
+ References
367
+ ----------
368
+ .. [1] Sparse6 specification
369
+ <https://users.cecs.anu.edu.au/~bdm/data/formats.html>
370
+
371
+ """
372
+ if nodes is not None:
373
+ G = G.subgraph(nodes)
374
+ G = nx.convert_node_labels_to_integers(G, ordering="sorted")
375
+ for b in _generate_sparse6_bytes(G, nodes, header):
376
+ path.write(b)
pythonProject/.venv/Lib/site-packages/networkx/readwrite/text.py ADDED
@@ -0,0 +1,950 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Text-based visual representations of graphs
3
+ """
4
+ import sys
5
+ import warnings
6
+ from collections import defaultdict
7
+
8
+ import networkx as nx
9
+ from networkx.utils import open_file
10
+
11
+ __all__ = ["forest_str", "generate_network_text", "write_network_text"]
12
+
13
+
14
+ class BaseGlyphs:
15
+ @classmethod
16
+ def as_dict(cls):
17
+ return {
18
+ a: getattr(cls, a)
19
+ for a in dir(cls)
20
+ if not a.startswith("_") and a != "as_dict"
21
+ }
22
+
23
+
24
+ class AsciiBaseGlyphs(BaseGlyphs):
25
+ empty: str = "+"
26
+ newtree_last: str = "+-- "
27
+ newtree_mid: str = "+-- "
28
+ endof_forest: str = " "
29
+ within_forest: str = ": "
30
+ within_tree: str = "| "
31
+
32
+
33
+ class AsciiDirectedGlyphs(AsciiBaseGlyphs):
34
+ last: str = "L-> "
35
+ mid: str = "|-> "
36
+ backedge: str = "<-"
37
+ vertical_edge: str = "!"
38
+
39
+
40
+ class AsciiUndirectedGlyphs(AsciiBaseGlyphs):
41
+ last: str = "L-- "
42
+ mid: str = "|-- "
43
+ backedge: str = "-"
44
+ vertical_edge: str = "|"
45
+
46
+
47
+ class UtfBaseGlyphs(BaseGlyphs):
48
+ # Notes on available box and arrow characters
49
+ # https://en.wikipedia.org/wiki/Box-drawing_character
50
+ # https://stackoverflow.com/questions/2701192/triangle-arrow
51
+ empty: str = "╙"
52
+ newtree_last: str = "╙── "
53
+ newtree_mid: str = "╟── "
54
+ endof_forest: str = " "
55
+ within_forest: str = "╎ "
56
+ within_tree: str = "│ "
57
+
58
+
59
+ class UtfDirectedGlyphs(UtfBaseGlyphs):
60
+ last: str = "└─╼ "
61
+ mid: str = "├─╼ "
62
+ backedge: str = "╾"
63
+ vertical_edge: str = "╽"
64
+
65
+
66
+ class UtfUndirectedGlyphs(UtfBaseGlyphs):
67
+ last: str = "└── "
68
+ mid: str = "├── "
69
+ backedge: str = "─"
70
+ vertical_edge: str = "│"
71
+
72
+
73
+ def generate_network_text(
74
+ graph,
75
+ with_labels=True,
76
+ sources=None,
77
+ max_depth=None,
78
+ ascii_only=False,
79
+ vertical_chains=False,
80
+ ):
81
+ """Generate lines in the "network text" format
82
+
83
+ This works via a depth-first traversal of the graph and writing a line for
84
+ each unique node encountered. Non-tree edges are written to the right of
85
+ each node, and connection to a non-tree edge is indicated with an ellipsis.
86
+ This representation works best when the input graph is a forest, but any
87
+ graph can be represented.
88
+
89
+ This notation is original to networkx, although it is simple enough that it
90
+ may be known in existing literature. See #5602 for details. The procedure
91
+ is summarized as follows:
92
+
93
+ 1. Given a set of source nodes (which can be specified, or automatically
94
+ discovered via finding the (strongly) connected components and choosing one
95
+ node with minimum degree from each), we traverse the graph in depth first
96
+ order.
97
+
98
+ 2. Each reachable node will be printed exactly once on it's own line.
99
+
100
+ 3. Edges are indicated in one of four ways:
101
+
102
+ a. a parent "L-style" connection on the upper left. This corresponds to
103
+ a traversal in the directed DFS tree.
104
+
105
+ b. a backref "<-style" connection shown directly on the right. For
106
+ directed graphs, these are drawn for any incoming edges to a node that
107
+ is not a parent edge. For undirected graphs, these are drawn for only
108
+ the non-parent edges that have already been represented (The edges that
109
+ have not been represented will be handled in the recursive case).
110
+
111
+ c. a child "L-style" connection on the lower right. Drawing of the
112
+ children are handled recursively.
113
+
114
+ d. if ``vertical_chains`` is true, and a parent node only has one child
115
+ a "vertical-style" edge is drawn between them.
116
+
117
+ 4. The children of each node (wrt the directed DFS tree) are drawn
118
+ underneath and to the right of it. In the case that a child node has already
119
+ been drawn the connection is replaced with an ellipsis ("...") to indicate
120
+ that there is one or more connections represented elsewhere.
121
+
122
+ 5. If a maximum depth is specified, an edge to nodes past this maximum
123
+ depth will be represented by an ellipsis.
124
+
125
+ 6. If a a node has a truthy "collapse" value, then we do not traverse past
126
+ that node.
127
+
128
+ Parameters
129
+ ----------
130
+ graph : nx.DiGraph | nx.Graph
131
+ Graph to represent
132
+
133
+ with_labels : bool | str
134
+ If True will use the "label" attribute of a node to display if it
135
+ exists otherwise it will use the node value itself. If given as a
136
+ string, then that attribute name will be used instead of "label".
137
+ Defaults to True.
138
+
139
+ sources : List
140
+ Specifies which nodes to start traversal from. Note: nodes that are not
141
+ reachable from one of these sources may not be shown. If unspecified,
142
+ the minimal set of nodes needed to reach all others will be used.
143
+
144
+ max_depth : int | None
145
+ The maximum depth to traverse before stopping. Defaults to None.
146
+
147
+ ascii_only : Boolean
148
+ If True only ASCII characters are used to construct the visualization
149
+
150
+ vertical_chains : Boolean
151
+ If True, chains of nodes will be drawn vertically when possible.
152
+
153
+ Yields
154
+ ------
155
+ str : a line of generated text
156
+
157
+ Examples
158
+ --------
159
+ >>> graph = nx.path_graph(10)
160
+ >>> graph.add_node("A")
161
+ >>> graph.add_node("B")
162
+ >>> graph.add_node("C")
163
+ >>> graph.add_node("D")
164
+ >>> graph.add_edge(9, "A")
165
+ >>> graph.add_edge(9, "B")
166
+ >>> graph.add_edge(9, "C")
167
+ >>> graph.add_edge("C", "D")
168
+ >>> graph.add_edge("C", "E")
169
+ >>> graph.add_edge("C", "F")
170
+ >>> nx.write_network_text(graph)
171
+ ╙── 0
172
+ └── 1
173
+ └── 2
174
+ └── 3
175
+ └── 4
176
+ └── 5
177
+ └── 6
178
+ └── 7
179
+ └── 8
180
+ └── 9
181
+ ├── A
182
+ ├── B
183
+ └── C
184
+ ├── D
185
+ ├── E
186
+ └── F
187
+ >>> nx.write_network_text(graph, vertical_chains=True)
188
+ ╙── 0
189
+
190
+ 1
191
+
192
+ 2
193
+
194
+ 3
195
+
196
+ 4
197
+
198
+ 5
199
+
200
+ 6
201
+
202
+ 7
203
+
204
+ 8
205
+
206
+ 9
207
+ ├── A
208
+ ├── B
209
+ └── C
210
+ ├── D
211
+ ├── E
212
+ └── F
213
+ """
214
+ from typing import Any, NamedTuple
215
+
216
+ class StackFrame(NamedTuple):
217
+ parent: Any
218
+ node: Any
219
+ indents: list
220
+ this_islast: bool
221
+ this_vertical: bool
222
+
223
+ collapse_attr = "collapse"
224
+
225
+ is_directed = graph.is_directed()
226
+
227
+ if is_directed:
228
+ glyphs = AsciiDirectedGlyphs if ascii_only else UtfDirectedGlyphs
229
+ succ = graph.succ
230
+ pred = graph.pred
231
+ else:
232
+ glyphs = AsciiUndirectedGlyphs if ascii_only else UtfUndirectedGlyphs
233
+ succ = graph.adj
234
+ pred = graph.adj
235
+
236
+ if isinstance(with_labels, str):
237
+ label_attr = with_labels
238
+ elif with_labels:
239
+ label_attr = "label"
240
+ else:
241
+ label_attr = None
242
+
243
+ if max_depth == 0:
244
+ yield glyphs.empty + " ..."
245
+ elif len(graph.nodes) == 0:
246
+ yield glyphs.empty
247
+ else:
248
+ # If the nodes to traverse are unspecified, find the minimal set of
249
+ # nodes that will reach the entire graph
250
+ if sources is None:
251
+ sources = _find_sources(graph)
252
+
253
+ # Populate the stack with each:
254
+ # 1. parent node in the DFS tree (or None for root nodes),
255
+ # 2. the current node in the DFS tree
256
+ # 2. a list of indentations indicating depth
257
+ # 3. a flag indicating if the node is the final one to be written.
258
+ # Reverse the stack so sources are popped in the correct order.
259
+ last_idx = len(sources) - 1
260
+ stack = [
261
+ StackFrame(None, node, [], (idx == last_idx), False)
262
+ for idx, node in enumerate(sources)
263
+ ][::-1]
264
+
265
+ num_skipped_children = defaultdict(lambda: 0)
266
+ seen_nodes = set()
267
+ while stack:
268
+ parent, node, indents, this_islast, this_vertical = stack.pop()
269
+
270
+ if node is not Ellipsis:
271
+ skip = node in seen_nodes
272
+ if skip:
273
+ # Mark that we skipped a parent's child
274
+ num_skipped_children[parent] += 1
275
+
276
+ if this_islast:
277
+ # If we reached the last child of a parent, and we skipped
278
+ # any of that parents children, then we should emit an
279
+ # ellipsis at the end after this.
280
+ if num_skipped_children[parent] and parent is not None:
281
+ # Append the ellipsis to be emitted last
282
+ next_islast = True
283
+ try_frame = StackFrame(
284
+ node, Ellipsis, indents, next_islast, False
285
+ )
286
+ stack.append(try_frame)
287
+
288
+ # Redo this frame, but not as a last object
289
+ next_islast = False
290
+ try_frame = StackFrame(
291
+ parent, node, indents, next_islast, this_vertical
292
+ )
293
+ stack.append(try_frame)
294
+ continue
295
+
296
+ if skip:
297
+ continue
298
+ seen_nodes.add(node)
299
+
300
+ if not indents:
301
+ # Top level items (i.e. trees in the forest) get different
302
+ # glyphs to indicate they are not actually connected
303
+ if this_islast:
304
+ this_vertical = False
305
+ this_prefix = indents + [glyphs.newtree_last]
306
+ next_prefix = indents + [glyphs.endof_forest]
307
+ else:
308
+ this_prefix = indents + [glyphs.newtree_mid]
309
+ next_prefix = indents + [glyphs.within_forest]
310
+
311
+ else:
312
+ # Non-top-level items
313
+ if this_vertical:
314
+ this_prefix = indents
315
+ next_prefix = indents
316
+ else:
317
+ if this_islast:
318
+ this_prefix = indents + [glyphs.last]
319
+ next_prefix = indents + [glyphs.endof_forest]
320
+ else:
321
+ this_prefix = indents + [glyphs.mid]
322
+ next_prefix = indents + [glyphs.within_tree]
323
+
324
+ if node is Ellipsis:
325
+ label = " ..."
326
+ suffix = ""
327
+ children = []
328
+ else:
329
+ if label_attr is not None:
330
+ label = str(graph.nodes[node].get(label_attr, node))
331
+ else:
332
+ label = str(node)
333
+
334
+ # Determine if we want to show the children of this node.
335
+ if collapse_attr is not None:
336
+ collapse = graph.nodes[node].get(collapse_attr, False)
337
+ else:
338
+ collapse = False
339
+
340
+ # Determine:
341
+ # (1) children to traverse into after showing this node.
342
+ # (2) parents to immediately show to the right of this node.
343
+ if is_directed:
344
+ # In the directed case we must show every successor node
345
+ # note: it may be skipped later, but we don't have that
346
+ # information here.
347
+ children = list(succ[node])
348
+ # In the directed case we must show every predecessor
349
+ # except for parent we directly traversed from.
350
+ handled_parents = {parent}
351
+ else:
352
+ # Showing only the unseen children results in a more
353
+ # concise representation for the undirected case.
354
+ children = [
355
+ child for child in succ[node] if child not in seen_nodes
356
+ ]
357
+
358
+ # In the undirected case, parents are also children, so we
359
+ # only need to immediately show the ones we can no longer
360
+ # traverse
361
+ handled_parents = {*children, parent}
362
+
363
+ if max_depth is not None and len(indents) == max_depth - 1:
364
+ # Use ellipsis to indicate we have reached maximum depth
365
+ if children:
366
+ children = [Ellipsis]
367
+ handled_parents = {parent}
368
+
369
+ if collapse:
370
+ # Collapsing a node is the same as reaching maximum depth
371
+ if children:
372
+ children = [Ellipsis]
373
+ handled_parents = {parent}
374
+
375
+ # The other parents are other predecessors of this node that
376
+ # are not handled elsewhere.
377
+ other_parents = [p for p in pred[node] if p not in handled_parents]
378
+ if other_parents:
379
+ if label_attr is not None:
380
+ other_parents_labels = ", ".join(
381
+ [
382
+ str(graph.nodes[p].get(label_attr, p))
383
+ for p in other_parents
384
+ ]
385
+ )
386
+ else:
387
+ other_parents_labels = ", ".join(
388
+ [str(p) for p in other_parents]
389
+ )
390
+ suffix = " ".join(["", glyphs.backedge, other_parents_labels])
391
+ else:
392
+ suffix = ""
393
+
394
+ # Emit the line for this node, this will be called for each node
395
+ # exactly once.
396
+ if this_vertical:
397
+ yield "".join(this_prefix + [glyphs.vertical_edge])
398
+
399
+ yield "".join(this_prefix + [label, suffix])
400
+
401
+ if vertical_chains:
402
+ if is_directed:
403
+ num_children = len(set(children))
404
+ else:
405
+ num_children = len(set(children) - {parent})
406
+ # The next node can be drawn vertically if it is the only
407
+ # remaining child of this node.
408
+ next_is_vertical = num_children == 1
409
+ else:
410
+ next_is_vertical = False
411
+
412
+ # Push children on the stack in reverse order so they are popped in
413
+ # the original order.
414
+ for idx, child in enumerate(children[::-1]):
415
+ next_islast = idx == 0
416
+ try_frame = StackFrame(
417
+ node, child, next_prefix, next_islast, next_is_vertical
418
+ )
419
+ stack.append(try_frame)
420
+
421
+
422
+ @open_file(1, "w")
423
+ def write_network_text(
424
+ graph,
425
+ path=None,
426
+ with_labels=True,
427
+ sources=None,
428
+ max_depth=None,
429
+ ascii_only=False,
430
+ end="\n",
431
+ vertical_chains=False,
432
+ ):
433
+ """Creates a nice text representation of a graph
434
+
435
+ This works via a depth-first traversal of the graph and writing a line for
436
+ each unique node encountered. Non-tree edges are written to the right of
437
+ each node, and connection to a non-tree edge is indicated with an ellipsis.
438
+ This representation works best when the input graph is a forest, but any
439
+ graph can be represented.
440
+
441
+ Parameters
442
+ ----------
443
+ graph : nx.DiGraph | nx.Graph
444
+ Graph to represent
445
+
446
+ path : string or file or callable or None
447
+ Filename or file handle for data output.
448
+ if a function, then it will be called for each generated line.
449
+ if None, this will default to "sys.stdout.write"
450
+
451
+ with_labels : bool | str
452
+ If True will use the "label" attribute of a node to display if it
453
+ exists otherwise it will use the node value itself. If given as a
454
+ string, then that attribute name will be used instead of "label".
455
+ Defaults to True.
456
+
457
+ sources : List
458
+ Specifies which nodes to start traversal from. Note: nodes that are not
459
+ reachable from one of these sources may not be shown. If unspecified,
460
+ the minimal set of nodes needed to reach all others will be used.
461
+
462
+ max_depth : int | None
463
+ The maximum depth to traverse before stopping. Defaults to None.
464
+
465
+ ascii_only : Boolean
466
+ If True only ASCII characters are used to construct the visualization
467
+
468
+ end : string
469
+ The line ending character
470
+
471
+ vertical_chains : Boolean
472
+ If True, chains of nodes will be drawn vertically when possible.
473
+
474
+ Examples
475
+ --------
476
+ >>> graph = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
477
+ >>> nx.write_network_text(graph)
478
+ ╙── 0
479
+ ├─╼ 1
480
+ │ ├─╼ 3
481
+ │ └─╼ 4
482
+ └─╼ 2
483
+ ├─╼ 5
484
+ └─╼ 6
485
+
486
+ >>> # A near tree with one non-tree edge
487
+ >>> graph.add_edge(5, 1)
488
+ >>> nx.write_network_text(graph)
489
+ ╙── 0
490
+ ├─╼ 1 ╾ 5
491
+ │ ├─╼ 3
492
+ │ └─╼ 4
493
+ └─╼ 2
494
+ ├─╼ 5
495
+ │ └─╼ ...
496
+ └─╼ 6
497
+
498
+ >>> graph = nx.cycle_graph(5)
499
+ >>> nx.write_network_text(graph)
500
+ ╙── 0
501
+ ├── 1
502
+ │ └── 2
503
+ │ └── 3
504
+ │ └── 4 ─ 0
505
+ └── ...
506
+
507
+ >>> graph = nx.cycle_graph(5, nx.DiGraph)
508
+ >>> nx.write_network_text(graph, vertical_chains=True)
509
+ ╙── 0 ╾ 4
510
+
511
+ 1
512
+
513
+ 2
514
+
515
+ 3
516
+
517
+ 4
518
+ └─╼ ...
519
+
520
+ >>> nx.write_network_text(graph, vertical_chains=True, ascii_only=True)
521
+ +-- 0 <- 4
522
+ !
523
+ 1
524
+ !
525
+ 2
526
+ !
527
+ 3
528
+ !
529
+ 4
530
+ L-> ...
531
+
532
+ >>> graph = nx.generators.barbell_graph(4, 2)
533
+ >>> nx.write_network_text(graph, vertical_chains=False)
534
+ ╙── 4
535
+ ├── 5
536
+ │ └── 6
537
+ │ ├── 7
538
+ │ │ ├── 8 ─ 6
539
+ │ │ │ └── 9 ─ 6, 7
540
+ │ │ └── ...
541
+ │ └── ...
542
+ └── 3
543
+ ├── 0
544
+ │ ├── 1 ─ 3
545
+ │ │ └── 2 ─ 0, 3
546
+ │ └── ...
547
+ └── ...
548
+ >>> nx.write_network_text(graph, vertical_chains=True)
549
+ ╙── 4
550
+ ├── 5
551
+ │ │
552
+ │ 6
553
+ │ ├── 7
554
+ │ │ ├── 8 ─ 6
555
+ │ │ │ │
556
+ │ │ │ 9 ─ 6, 7
557
+ │ │ └── ...
558
+ │ └── ...
559
+ └── 3
560
+ ├── 0
561
+ │ ├── 1 ─ 3
562
+ │ │ │
563
+ │ │ 2 ─ 0, 3
564
+ │ └── ...
565
+ └── ...
566
+
567
+ >>> graph = nx.complete_graph(5, create_using=nx.Graph)
568
+ >>> nx.write_network_text(graph)
569
+ ╙── 0
570
+ ├── 1
571
+ │ ├── 2 ─ 0
572
+ │ │ ├── 3 ─ 0, 1
573
+ │ │ │ └── 4 ─ 0, 1, 2
574
+ │ │ └── ...
575
+ │ └── ...
576
+ └── ...
577
+
578
+ >>> graph = nx.complete_graph(3, create_using=nx.DiGraph)
579
+ >>> nx.write_network_text(graph)
580
+ ╙── 0 ╾ 1, 2
581
+ ├─╼ 1 ╾ 2
582
+ │ ├─╼ 2 ╾ 0
583
+ │ │ └─╼ ...
584
+ │ └─╼ ...
585
+ └─╼ ...
586
+ """
587
+ if path is None:
588
+ # The path is unspecified, write to stdout
589
+ _write = sys.stdout.write
590
+ elif hasattr(path, "write"):
591
+ # The path is already an open file
592
+ _write = path.write
593
+ elif callable(path):
594
+ # The path is a custom callable
595
+ _write = path
596
+ else:
597
+ raise TypeError(type(path))
598
+
599
+ for line in generate_network_text(
600
+ graph,
601
+ with_labels=with_labels,
602
+ sources=sources,
603
+ max_depth=max_depth,
604
+ ascii_only=ascii_only,
605
+ vertical_chains=vertical_chains,
606
+ ):
607
+ _write(line + end)
608
+
609
+
610
+ def _find_sources(graph):
611
+ """
612
+ Determine a minimal set of nodes such that the entire graph is reachable
613
+ """
614
+ # For each connected part of the graph, choose at least
615
+ # one node as a starting point, preferably without a parent
616
+ if graph.is_directed():
617
+ # Choose one node from each SCC with minimum in_degree
618
+ sccs = list(nx.strongly_connected_components(graph))
619
+ # condensing the SCCs forms a dag, the nodes in this graph with
620
+ # 0 in-degree correspond to the SCCs from which the minimum set
621
+ # of nodes from which all other nodes can be reached.
622
+ scc_graph = nx.condensation(graph, sccs)
623
+ supernode_to_nodes = {sn: [] for sn in scc_graph.nodes()}
624
+ # Note: the order of mapping differs between pypy and cpython
625
+ # so we have to loop over graph nodes for consistency
626
+ mapping = scc_graph.graph["mapping"]
627
+ for n in graph.nodes:
628
+ sn = mapping[n]
629
+ supernode_to_nodes[sn].append(n)
630
+ sources = []
631
+ for sn in scc_graph.nodes():
632
+ if scc_graph.in_degree[sn] == 0:
633
+ scc = supernode_to_nodes[sn]
634
+ node = min(scc, key=lambda n: graph.in_degree[n])
635
+ sources.append(node)
636
+ else:
637
+ # For undirected graph, the entire graph will be reachable as
638
+ # long as we consider one node from every connected component
639
+ sources = [
640
+ min(cc, key=lambda n: graph.degree[n])
641
+ for cc in nx.connected_components(graph)
642
+ ]
643
+ sources = sorted(sources, key=lambda n: graph.degree[n])
644
+ return sources
645
+
646
+
647
+ def forest_str(graph, with_labels=True, sources=None, write=None, ascii_only=False):
648
+ """Creates a nice utf8 representation of a forest
649
+
650
+ This function has been superseded by
651
+ :func:`nx.readwrite.text.generate_network_text`, which should be used
652
+ instead.
653
+
654
+ Parameters
655
+ ----------
656
+ graph : nx.DiGraph | nx.Graph
657
+ Graph to represent (must be a tree, forest, or the empty graph)
658
+
659
+ with_labels : bool
660
+ If True will use the "label" attribute of a node to display if it
661
+ exists otherwise it will use the node value itself. Defaults to True.
662
+
663
+ sources : List
664
+ Mainly relevant for undirected forests, specifies which nodes to list
665
+ first. If unspecified the root nodes of each tree will be used for
666
+ directed forests; for undirected forests this defaults to the nodes
667
+ with the smallest degree.
668
+
669
+ write : callable
670
+ Function to use to write to, if None new lines are appended to
671
+ a list and returned. If set to the `print` function, lines will
672
+ be written to stdout as they are generated. If specified,
673
+ this function will return None. Defaults to None.
674
+
675
+ ascii_only : Boolean
676
+ If True only ASCII characters are used to construct the visualization
677
+
678
+ Returns
679
+ -------
680
+ str | None :
681
+ utf8 representation of the tree / forest
682
+
683
+ Examples
684
+ --------
685
+ >>> graph = nx.balanced_tree(r=2, h=3, create_using=nx.DiGraph)
686
+ >>> print(nx.forest_str(graph))
687
+ ╙── 0
688
+ ├─╼ 1
689
+ │ ├─╼ 3
690
+ │ │ ├─╼ 7
691
+ │ │ └─╼ 8
692
+ │ └─╼ 4
693
+ │ ├─╼ 9
694
+ │ └─╼ 10
695
+ └─╼ 2
696
+ ├─╼ 5
697
+ │ ├─╼ 11
698
+ │ └─╼ 12
699
+ └─╼ 6
700
+ ├─╼ 13
701
+ └─╼ 14
702
+
703
+
704
+ >>> graph = nx.balanced_tree(r=1, h=2, create_using=nx.Graph)
705
+ >>> print(nx.forest_str(graph))
706
+ ╙── 0
707
+ └── 1
708
+ └── 2
709
+
710
+ >>> print(nx.forest_str(graph, ascii_only=True))
711
+ +-- 0
712
+ L-- 1
713
+ L-- 2
714
+ """
715
+ msg = (
716
+ "\nforest_str is deprecated as of version 3.1 and will be removed "
717
+ "in version 3.3. Use generate_network_text or write_network_text "
718
+ "instead.\n"
719
+ )
720
+ warnings.warn(msg, DeprecationWarning)
721
+
722
+ if len(graph.nodes) > 0:
723
+ if not nx.is_forest(graph):
724
+ raise nx.NetworkXNotImplemented("input must be a forest or the empty graph")
725
+
726
+ printbuf = []
727
+ if write is None:
728
+ _write = printbuf.append
729
+ else:
730
+ _write = write
731
+
732
+ write_network_text(
733
+ graph,
734
+ _write,
735
+ with_labels=with_labels,
736
+ sources=sources,
737
+ ascii_only=ascii_only,
738
+ end="",
739
+ )
740
+
741
+ if write is None:
742
+ # Only return a string if the custom write function was not specified
743
+ return "\n".join(printbuf)
744
+
745
+
746
+ def _parse_network_text(lines):
747
+ """Reconstructs a graph from a network text representation.
748
+
749
+ This is mainly used for testing. Network text is for display, not
750
+ serialization, as such this cannot parse all network text representations
751
+ because node labels can be ambiguous with the glyphs and indentation used
752
+ to represent edge structure. Additionally, there is no way to determine if
753
+ disconnected graphs were originally directed or undirected.
754
+
755
+ Parameters
756
+ ----------
757
+ lines : list or iterator of strings
758
+ Input data in network text format
759
+
760
+ Returns
761
+ -------
762
+ G: NetworkX graph
763
+ The graph corresponding to the lines in network text format.
764
+ """
765
+ from itertools import chain
766
+ from typing import Any, NamedTuple, Union
767
+
768
+ class ParseStackFrame(NamedTuple):
769
+ node: Any
770
+ indent: int
771
+ has_vertical_child: int | None
772
+
773
+ initial_line_iter = iter(lines)
774
+
775
+ is_ascii = None
776
+ is_directed = None
777
+
778
+ ##############
779
+ # Initial Pass
780
+ ##############
781
+
782
+ # Do an initial pass over the lines to determine what type of graph it is.
783
+ # Remember what these lines were, so we can reiterate over them in the
784
+ # parsing pass.
785
+ initial_lines = []
786
+ try:
787
+ first_line = next(initial_line_iter)
788
+ except StopIteration:
789
+ ...
790
+ else:
791
+ initial_lines.append(first_line)
792
+ # The first character indicates if it is an ASCII or UTF graph
793
+ first_char = first_line[0]
794
+ if first_char in {
795
+ UtfBaseGlyphs.empty,
796
+ UtfBaseGlyphs.newtree_mid[0],
797
+ UtfBaseGlyphs.newtree_last[0],
798
+ }:
799
+ is_ascii = False
800
+ elif first_char in {
801
+ AsciiBaseGlyphs.empty,
802
+ AsciiBaseGlyphs.newtree_mid[0],
803
+ AsciiBaseGlyphs.newtree_last[0],
804
+ }:
805
+ is_ascii = True
806
+ else:
807
+ raise AssertionError(f"Unexpected first character: {first_char}")
808
+
809
+ if is_ascii:
810
+ directed_glyphs = AsciiDirectedGlyphs.as_dict()
811
+ undirected_glyphs = AsciiUndirectedGlyphs.as_dict()
812
+ else:
813
+ directed_glyphs = UtfDirectedGlyphs.as_dict()
814
+ undirected_glyphs = UtfUndirectedGlyphs.as_dict()
815
+
816
+ # For both directed / undirected glyphs, determine which glyphs never
817
+ # appear as substrings in the other undirected / directed glyphs. Glyphs
818
+ # with this property unambiguously indicates if a graph is directed /
819
+ # undirected.
820
+ directed_items = set(directed_glyphs.values())
821
+ undirected_items = set(undirected_glyphs.values())
822
+ unambiguous_directed_items = []
823
+ for item in directed_items:
824
+ other_items = undirected_items
825
+ other_supersets = [other for other in other_items if item in other]
826
+ if not other_supersets:
827
+ unambiguous_directed_items.append(item)
828
+ unambiguous_undirected_items = []
829
+ for item in undirected_items:
830
+ other_items = directed_items
831
+ other_supersets = [other for other in other_items if item in other]
832
+ if not other_supersets:
833
+ unambiguous_undirected_items.append(item)
834
+
835
+ for line in initial_line_iter:
836
+ initial_lines.append(line)
837
+ if any(item in line for item in unambiguous_undirected_items):
838
+ is_directed = False
839
+ break
840
+ elif any(item in line for item in unambiguous_directed_items):
841
+ is_directed = True
842
+ break
843
+
844
+ if is_directed is None:
845
+ # Not enough information to determine, choose undirected by default
846
+ is_directed = False
847
+
848
+ glyphs = directed_glyphs if is_directed else undirected_glyphs
849
+
850
+ # the backedge symbol by itself can be ambiguous, but with spaces around it
851
+ # becomes unambiguous.
852
+ backedge_symbol = " " + glyphs["backedge"] + " "
853
+
854
+ # Reconstruct an iterator over all of the lines.
855
+ parsing_line_iter = chain(initial_lines, initial_line_iter)
856
+
857
+ ##############
858
+ # Parsing Pass
859
+ ##############
860
+
861
+ edges = []
862
+ nodes = []
863
+ is_empty = None
864
+
865
+ noparent = object() # sentinel value
866
+
867
+ # keep a stack of previous nodes that could be parents of subsequent nodes
868
+ stack = [ParseStackFrame(noparent, -1, None)]
869
+
870
+ for line in parsing_line_iter:
871
+ if line == glyphs["empty"]:
872
+ # If the line is the empty glyph, we are done.
873
+ # There shouldn't be anything else after this.
874
+ is_empty = True
875
+ continue
876
+
877
+ if backedge_symbol in line:
878
+ # This line has one or more backedges, separate those out
879
+ node_part, backedge_part = line.split(backedge_symbol)
880
+ backedge_nodes = [u.strip() for u in backedge_part.split(", ")]
881
+ # Now the node can be parsed
882
+ node_part = node_part.rstrip()
883
+ prefix, node = node_part.rsplit(" ", 1)
884
+ node = node.strip()
885
+ # Add the backedges to the edge list
886
+ edges.extend([(u, node) for u in backedge_nodes])
887
+ else:
888
+ # No backedge, the tail of this line is the node
889
+ prefix, node = line.rsplit(" ", 1)
890
+ node = node.strip()
891
+
892
+ prev = stack.pop()
893
+
894
+ if node in glyphs["vertical_edge"]:
895
+ # Previous node is still the previous node, but we know it will
896
+ # have exactly one child, which will need to have its nesting level
897
+ # adjusted.
898
+ modified_prev = ParseStackFrame(
899
+ prev.node,
900
+ prev.indent,
901
+ True,
902
+ )
903
+ stack.append(modified_prev)
904
+ continue
905
+
906
+ # The length of the string before the node characters give us a hint
907
+ # about our nesting level. The only case where this doesn't work is
908
+ # when there are vertical chains, which is handled explicitly.
909
+ indent = len(prefix)
910
+ curr = ParseStackFrame(node, indent, None)
911
+
912
+ if prev.has_vertical_child:
913
+ # In this case we know prev must be the parent of our current line,
914
+ # so we don't have to search the stack. (which is good because the
915
+ # indentation check wouldn't work in this case).
916
+ ...
917
+ else:
918
+ # If the previous node nesting-level is greater than the current
919
+ # nodes nesting-level than the previous node was the end of a path,
920
+ # and is not our parent. We can safely pop nodes off the stack
921
+ # until we find one with a comparable nesting-level, which is our
922
+ # parent.
923
+ while curr.indent <= prev.indent:
924
+ prev = stack.pop()
925
+
926
+ if node == "...":
927
+ # The current previous node is no longer a valid parent,
928
+ # keep it popped from the stack.
929
+ stack.append(prev)
930
+ else:
931
+ # The previous and current nodes may still be parents, so add them
932
+ # back onto the stack.
933
+ stack.append(prev)
934
+ stack.append(curr)
935
+
936
+ # Add the node and the edge to its parent to the node / edge lists.
937
+ nodes.append(curr.node)
938
+ if prev.node is not noparent:
939
+ edges.append((prev.node, curr.node))
940
+
941
+ if is_empty:
942
+ # Sanity check
943
+ assert len(nodes) == 0
944
+
945
+ # Reconstruct the graph
946
+ cls = nx.DiGraph if is_directed else nx.Graph
947
+ new = cls()
948
+ new.add_nodes_from(nodes)
949
+ new.add_edges_from(edges)
950
+ return new