test_gexf.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. import io
  2. import time
  3. import pytest
  4. import networkx as nx
  5. def test_gexf_v1_3(tmp_path):
  6. """'Basic graph' example from https://gexf.net/schema.html"""
  7. # GEXF file from published example
  8. data = """<?xml version="1.0" encoding="UTF-8"?>
  9. <gexf xmlns="http://gexf.net/1.3" version="1.3">
  10. <graph mode="static" defaultedgetype="directed">
  11. <nodes>
  12. <node id="0" label="Hello" />
  13. <node id="1" label="Word" />
  14. </nodes>
  15. <edges>
  16. <edge source="0" target="1" />
  17. </edges>
  18. </graph>
  19. </gexf>
  20. """
  21. with open(fname := (tmp_path / "basic.gexf"), "w") as fh:
  22. fh.write(data)
  23. # Expected output based on xml input
  24. expected = nx.DiGraph([("0", "1")])
  25. nx.set_node_attributes(expected, {"0": "Hello", "1": "Word"}, name="label")
  26. expected.graph = {"mode": "static", "edge_default": {}}
  27. # Load example with version explicitly set
  28. G = nx.read_gexf(fname, version="1.3")
  29. assert nx.utils.graphs_equal(G, expected)
  30. # And with the "default" version
  31. G = nx.read_gexf(fname)
  32. assert nx.utils.graphs_equal(G, expected)
  33. @pytest.mark.parametrize("time_attr", ("start", "end"))
  34. @pytest.mark.parametrize("dyn_attr", ("static", "dynamic"))
  35. def test_dynamic_graph_has_timeformat(time_attr, dyn_attr, tmp_path):
  36. """Ensure that graphs which have a 'start' or 'stop' attribute get a
  37. 'timeformat' attribute upon parsing. See gh-7914."""
  38. G = nx.MultiGraph(mode=dyn_attr)
  39. G.add_node(0)
  40. G.nodes[0][time_attr] = 1
  41. # Write out
  42. fname = tmp_path / "foo.gexf"
  43. nx.write_gexf(G, fname)
  44. # Check that timeformat is added to saved data
  45. with open(fname) as fh:
  46. assert 'timeformat="long"' in fh.read()
  47. # Round-trip
  48. H = nx.read_gexf(fname)
  49. # If any node has a "start" or "end" attr, it is considered dynamic
  50. # regardless of the graph "mode" attr
  51. assert H.graph["mode"] == "dynamic"
  52. assert nx.utils.nodes_equal(G.edges, H.edges)
  53. class TestGEXF:
  54. @classmethod
  55. def setup_class(cls):
  56. cls.simple_directed_data = """<?xml version="1.0" encoding="UTF-8"?>
  57. <gexf xmlns="http://www.gexf.net/1.2draft" version="1.2">
  58. <graph mode="static" defaultedgetype="directed">
  59. <nodes>
  60. <node id="0" label="Hello" />
  61. <node id="1" label="Word" />
  62. </nodes>
  63. <edges>
  64. <edge id="0" source="0" target="1" />
  65. </edges>
  66. </graph>
  67. </gexf>
  68. """
  69. cls.simple_directed_graph = nx.DiGraph()
  70. cls.simple_directed_graph.add_node("0", label="Hello")
  71. cls.simple_directed_graph.add_node("1", label="World")
  72. cls.simple_directed_graph.add_edge("0", "1", id="0")
  73. cls.simple_directed_fh = io.BytesIO(cls.simple_directed_data.encode("UTF-8"))
  74. cls.attribute_data = """<?xml version="1.0" encoding="UTF-8"?>\
  75. <gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi="http://www.w3.\
  76. org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gexf.net/\
  77. 1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
  78. <meta lastmodifieddate="2009-03-20">
  79. <creator>Gephi.org</creator>
  80. <description>A Web network</description>
  81. </meta>
  82. <graph defaultedgetype="directed">
  83. <attributes class="node">
  84. <attribute id="0" title="url" type="string"/>
  85. <attribute id="1" title="indegree" type="integer"/>
  86. <attribute id="2" title="frog" type="boolean">
  87. <default>true</default>
  88. </attribute>
  89. </attributes>
  90. <nodes>
  91. <node id="0" label="Gephi">
  92. <attvalues>
  93. <attvalue for="0" value="https://gephi.org"/>
  94. <attvalue for="1" value="1"/>
  95. <attvalue for="2" value="false"/>
  96. </attvalues>
  97. </node>
  98. <node id="1" label="Webatlas">
  99. <attvalues>
  100. <attvalue for="0" value="http://webatlas.fr"/>
  101. <attvalue for="1" value="2"/>
  102. <attvalue for="2" value="false"/>
  103. </attvalues>
  104. </node>
  105. <node id="2" label="RTGI">
  106. <attvalues>
  107. <attvalue for="0" value="http://rtgi.fr"/>
  108. <attvalue for="1" value="1"/>
  109. <attvalue for="2" value="true"/>
  110. </attvalues>
  111. </node>
  112. <node id="3" label="BarabasiLab">
  113. <attvalues>
  114. <attvalue for="0" value="http://barabasilab.com"/>
  115. <attvalue for="1" value="1"/>
  116. <attvalue for="2" value="true"/>
  117. </attvalues>
  118. </node>
  119. </nodes>
  120. <edges>
  121. <edge id="0" source="0" target="1" label="foo"/>
  122. <edge id="1" source="0" target="2"/>
  123. <edge id="2" source="1" target="0"/>
  124. <edge id="3" source="2" target="1"/>
  125. <edge id="4" source="0" target="3"/>
  126. </edges>
  127. </graph>
  128. </gexf>
  129. """
  130. cls.attribute_graph = nx.DiGraph()
  131. cls.attribute_graph.graph["node_default"] = {"frog": True}
  132. cls.attribute_graph.add_node(
  133. "0", label="Gephi", url="https://gephi.org", indegree=1, frog=False
  134. )
  135. cls.attribute_graph.add_node(
  136. "1", label="Webatlas", url="http://webatlas.fr", indegree=2, frog=False
  137. )
  138. cls.attribute_graph.add_node(
  139. "2", label="RTGI", url="http://rtgi.fr", indegree=1, frog=True
  140. )
  141. cls.attribute_graph.add_node(
  142. "3",
  143. label="BarabasiLab",
  144. url="http://barabasilab.com",
  145. indegree=1,
  146. frog=True,
  147. )
  148. cls.attribute_graph.add_edge("0", "1", id="0", label="foo")
  149. cls.attribute_graph.add_edge("0", "2", id="1")
  150. cls.attribute_graph.add_edge("1", "0", id="2")
  151. cls.attribute_graph.add_edge("2", "1", id="3")
  152. cls.attribute_graph.add_edge("0", "3", id="4")
  153. cls.attribute_fh = io.BytesIO(cls.attribute_data.encode("UTF-8"))
  154. cls.simple_undirected_data = """<?xml version="1.0" encoding="UTF-8"?>
  155. <gexf xmlns="http://www.gexf.net/1.2draft" version="1.2">
  156. <graph mode="static" defaultedgetype="undirected">
  157. <nodes>
  158. <node id="0" label="Hello" />
  159. <node id="1" label="Word" />
  160. </nodes>
  161. <edges>
  162. <edge id="0" source="0" target="1" />
  163. </edges>
  164. </graph>
  165. </gexf>
  166. """
  167. cls.simple_undirected_graph = nx.Graph()
  168. cls.simple_undirected_graph.add_node("0", label="Hello")
  169. cls.simple_undirected_graph.add_node("1", label="World")
  170. cls.simple_undirected_graph.add_edge("0", "1", id="0")
  171. cls.simple_undirected_fh = io.BytesIO(
  172. cls.simple_undirected_data.encode("UTF-8")
  173. )
  174. def test_read_simple_directed_graphml(self):
  175. G = self.simple_directed_graph
  176. H = nx.read_gexf(self.simple_directed_fh)
  177. assert sorted(G.nodes()) == sorted(H.nodes())
  178. assert sorted(G.edges()) == sorted(H.edges())
  179. assert sorted(G.edges(data=True)) == sorted(H.edges(data=True))
  180. self.simple_directed_fh.seek(0)
  181. def test_write_read_simple_directed_graphml(self):
  182. G = self.simple_directed_graph
  183. fh = io.BytesIO()
  184. nx.write_gexf(G, fh)
  185. fh.seek(0)
  186. H = nx.read_gexf(fh)
  187. assert sorted(G.nodes()) == sorted(H.nodes())
  188. assert sorted(G.edges()) == sorted(H.edges())
  189. assert sorted(G.edges(data=True)) == sorted(H.edges(data=True))
  190. self.simple_directed_fh.seek(0)
  191. def test_read_simple_undirected_graphml(self):
  192. G = self.simple_undirected_graph
  193. H = nx.read_gexf(self.simple_undirected_fh)
  194. assert sorted(G.nodes()) == sorted(H.nodes())
  195. assert sorted(sorted(e) for e in G.edges()) == sorted(
  196. sorted(e) for e in H.edges()
  197. )
  198. self.simple_undirected_fh.seek(0)
  199. def test_read_attribute_graphml(self):
  200. G = self.attribute_graph
  201. H = nx.read_gexf(self.attribute_fh)
  202. assert sorted(G.nodes(True)) == sorted(H.nodes(data=True))
  203. ge = sorted(G.edges(data=True))
  204. he = sorted(H.edges(data=True))
  205. for a, b in zip(ge, he):
  206. assert a == b
  207. self.attribute_fh.seek(0)
  208. def test_directed_edge_in_undirected(self):
  209. s = """<?xml version="1.0" encoding="UTF-8"?>
  210. <gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
  211. <graph mode="static" defaultedgetype="undirected" name="">
  212. <nodes>
  213. <node id="0" label="Hello" />
  214. <node id="1" label="Word" />
  215. </nodes>
  216. <edges>
  217. <edge id="0" source="0" target="1" type="directed"/>
  218. </edges>
  219. </graph>
  220. </gexf>
  221. """
  222. fh = io.BytesIO(s.encode("UTF-8"))
  223. pytest.raises(nx.NetworkXError, nx.read_gexf, fh)
  224. def test_undirected_edge_in_directed(self):
  225. s = """<?xml version="1.0" encoding="UTF-8"?>
  226. <gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
  227. <graph mode="static" defaultedgetype="directed" name="">
  228. <nodes>
  229. <node id="0" label="Hello" />
  230. <node id="1" label="Word" />
  231. </nodes>
  232. <edges>
  233. <edge id="0" source="0" target="1" type="undirected"/>
  234. </edges>
  235. </graph>
  236. </gexf>
  237. """
  238. fh = io.BytesIO(s.encode("UTF-8"))
  239. pytest.raises(nx.NetworkXError, nx.read_gexf, fh)
  240. def test_key_raises(self):
  241. s = """<?xml version="1.0" encoding="UTF-8"?>
  242. <gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
  243. <graph mode="static" defaultedgetype="directed" name="">
  244. <nodes>
  245. <node id="0" label="Hello">
  246. <attvalues>
  247. <attvalue for='0' value='1'/>
  248. </attvalues>
  249. </node>
  250. <node id="1" label="Word" />
  251. </nodes>
  252. <edges>
  253. <edge id="0" source="0" target="1" type="undirected"/>
  254. </edges>
  255. </graph>
  256. </gexf>
  257. """
  258. fh = io.BytesIO(s.encode("UTF-8"))
  259. pytest.raises(nx.NetworkXError, nx.read_gexf, fh)
  260. def test_relabel(self):
  261. s = """<?xml version="1.0" encoding="UTF-8"?>
  262. <gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
  263. <graph mode="static" defaultedgetype="directed" name="">
  264. <nodes>
  265. <node id="0" label="Hello" />
  266. <node id="1" label="Word" />
  267. </nodes>
  268. <edges>
  269. <edge id="0" source="0" target="1"/>
  270. </edges>
  271. </graph>
  272. </gexf>
  273. """
  274. fh = io.BytesIO(s.encode("UTF-8"))
  275. G = nx.read_gexf(fh, relabel=True)
  276. assert sorted(G.nodes()) == ["Hello", "Word"]
  277. def test_default_attribute(self):
  278. G = nx.Graph()
  279. G.add_node(1, label="1", color="green")
  280. nx.add_path(G, [0, 1, 2, 3])
  281. G.add_edge(1, 2, foo=3)
  282. G.graph["node_default"] = {"color": "yellow"}
  283. G.graph["edge_default"] = {"foo": 7}
  284. fh = io.BytesIO()
  285. nx.write_gexf(G, fh)
  286. fh.seek(0)
  287. H = nx.read_gexf(fh, node_type=int)
  288. assert sorted(G.nodes()) == sorted(H.nodes())
  289. assert sorted(sorted(e) for e in G.edges()) == sorted(
  290. sorted(e) for e in H.edges()
  291. )
  292. # Reading a gexf graph always sets mode attribute to either
  293. # 'static' or 'dynamic'. Remove the mode attribute from the
  294. # read graph for the sake of comparing remaining attributes.
  295. del H.graph["mode"]
  296. assert G.graph == H.graph
  297. def test_serialize_ints_to_strings(self):
  298. G = nx.Graph()
  299. G.add_node(1, id=7, label=77)
  300. fh = io.BytesIO()
  301. nx.write_gexf(G, fh)
  302. fh.seek(0)
  303. H = nx.read_gexf(fh, node_type=int)
  304. assert list(H) == [7]
  305. assert H.nodes[7]["label"] == "77"
  306. def test_write_with_node_attributes(self):
  307. # Addresses #673.
  308. G = nx.Graph()
  309. G.add_edges_from([(0, 1), (1, 2), (2, 3)])
  310. for i in range(4):
  311. G.nodes[i]["id"] = i
  312. G.nodes[i]["label"] = i
  313. G.nodes[i]["pid"] = i
  314. G.nodes[i]["start"] = i
  315. G.nodes[i]["end"] = i + 1
  316. expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi\
  317. ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=\
  318. "http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/\
  319. gexf.xsd" version="1.2">
  320. <meta lastmodifieddate="{time.strftime("%Y-%m-%d")}">
  321. <creator>NetworkX {nx.__version__}</creator>
  322. </meta>
  323. <graph defaultedgetype="undirected" mode="dynamic" name="" timeformat="long">
  324. <nodes>
  325. <node id="0" label="0" pid="0" start="0" end="1" />
  326. <node id="1" label="1" pid="1" start="1" end="2" />
  327. <node id="2" label="2" pid="2" start="2" end="3" />
  328. <node id="3" label="3" pid="3" start="3" end="4" />
  329. </nodes>
  330. <edges>
  331. <edge source="0" target="1" id="0" />
  332. <edge source="1" target="2" id="1" />
  333. <edge source="2" target="3" id="2" />
  334. </edges>
  335. </graph>
  336. </gexf>"""
  337. obtained = "\n".join(nx.generate_gexf(G))
  338. assert expected == obtained
  339. def test_edge_id_construct(self):
  340. G = nx.Graph()
  341. G.add_edges_from([(0, 1, {"id": 0}), (1, 2, {"id": 2}), (2, 3)])
  342. expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi\
  343. ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.\
  344. gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
  345. <meta lastmodifieddate="{time.strftime("%Y-%m-%d")}">
  346. <creator>NetworkX {nx.__version__}</creator>
  347. </meta>
  348. <graph defaultedgetype="undirected" mode="static" name="">
  349. <nodes>
  350. <node id="0" label="0" />
  351. <node id="1" label="1" />
  352. <node id="2" label="2" />
  353. <node id="3" label="3" />
  354. </nodes>
  355. <edges>
  356. <edge source="0" target="1" id="0" />
  357. <edge source="1" target="2" id="2" />
  358. <edge source="2" target="3" id="1" />
  359. </edges>
  360. </graph>
  361. </gexf>"""
  362. obtained = "\n".join(nx.generate_gexf(G))
  363. assert expected == obtained
  364. def test_numpy_type(self):
  365. np = pytest.importorskip("numpy")
  366. G = nx.path_graph(4)
  367. nx.set_node_attributes(G, {n: n for n in np.arange(4)}, "number")
  368. G[0][1]["edge-number"] = np.float64(1.1)
  369. expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft"\
  370. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation\
  371. ="http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd"\
  372. version="1.2">
  373. <meta lastmodifieddate="{time.strftime("%Y-%m-%d")}">
  374. <creator>NetworkX {nx.__version__}</creator>
  375. </meta>
  376. <graph defaultedgetype="undirected" mode="static" name="">
  377. <attributes mode="static" class="edge">
  378. <attribute id="1" title="edge-number" type="float" />
  379. </attributes>
  380. <attributes mode="static" class="node">
  381. <attribute id="0" title="number" type="int" />
  382. </attributes>
  383. <nodes>
  384. <node id="0" label="0">
  385. <attvalues>
  386. <attvalue for="0" value="0" />
  387. </attvalues>
  388. </node>
  389. <node id="1" label="1">
  390. <attvalues>
  391. <attvalue for="0" value="1" />
  392. </attvalues>
  393. </node>
  394. <node id="2" label="2">
  395. <attvalues>
  396. <attvalue for="0" value="2" />
  397. </attvalues>
  398. </node>
  399. <node id="3" label="3">
  400. <attvalues>
  401. <attvalue for="0" value="3" />
  402. </attvalues>
  403. </node>
  404. </nodes>
  405. <edges>
  406. <edge source="0" target="1" id="0">
  407. <attvalues>
  408. <attvalue for="1" value="1.1" />
  409. </attvalues>
  410. </edge>
  411. <edge source="1" target="2" id="1" />
  412. <edge source="2" target="3" id="2" />
  413. </edges>
  414. </graph>
  415. </gexf>"""
  416. obtained = "\n".join(nx.generate_gexf(G))
  417. assert expected == obtained
  418. def test_bool(self):
  419. G = nx.Graph()
  420. G.add_node(1, testattr=True)
  421. fh = io.BytesIO()
  422. nx.write_gexf(G, fh)
  423. fh.seek(0)
  424. H = nx.read_gexf(fh, node_type=int)
  425. assert H.nodes[1]["testattr"]
  426. # Test for NaN, INF and -INF
  427. def test_specials(self):
  428. from math import isnan
  429. inf, nan = float("inf"), float("nan")
  430. G = nx.Graph()
  431. G.add_node(1, testattr=inf, strdata="inf", key="a")
  432. G.add_node(2, testattr=nan, strdata="nan", key="b")
  433. G.add_node(3, testattr=-inf, strdata="-inf", key="c")
  434. fh = io.BytesIO()
  435. nx.write_gexf(G, fh)
  436. fh.seek(0)
  437. filetext = fh.read()
  438. fh.seek(0)
  439. H = nx.read_gexf(fh, node_type=int)
  440. assert b"INF" in filetext
  441. assert b"NaN" in filetext
  442. assert b"-INF" in filetext
  443. assert H.nodes[1]["testattr"] == inf
  444. assert isnan(H.nodes[2]["testattr"])
  445. assert H.nodes[3]["testattr"] == -inf
  446. assert H.nodes[1]["strdata"] == "inf"
  447. assert H.nodes[2]["strdata"] == "nan"
  448. assert H.nodes[3]["strdata"] == "-inf"
  449. assert H.nodes[1]["networkx_key"] == "a"
  450. assert H.nodes[2]["networkx_key"] == "b"
  451. assert H.nodes[3]["networkx_key"] == "c"
  452. def test_simple_list(self):
  453. G = nx.Graph()
  454. list_value = [(1, 2, 3), (9, 1, 2)]
  455. G.add_node(1, key=list_value)
  456. fh = io.BytesIO()
  457. nx.write_gexf(G, fh)
  458. fh.seek(0)
  459. H = nx.read_gexf(fh, node_type=int)
  460. assert H.nodes[1]["networkx_key"] == list_value
  461. def test_dynamic_mode(self):
  462. G = nx.Graph()
  463. G.add_node(1, label="1", color="green")
  464. G.graph["mode"] = "dynamic"
  465. fh = io.BytesIO()
  466. nx.write_gexf(G, fh)
  467. fh.seek(0)
  468. H = nx.read_gexf(fh, node_type=int)
  469. assert sorted(G.nodes()) == sorted(H.nodes())
  470. assert sorted(sorted(e) for e in G.edges()) == sorted(
  471. sorted(e) for e in H.edges()
  472. )
  473. def test_multigraph_with_missing_attributes(self):
  474. G = nx.MultiGraph()
  475. G.add_node(0, label="1", color="green")
  476. G.add_node(1, label="2", color="green")
  477. G.add_edge(0, 1, id="0", weight=3, type="undirected", start=0, end=1)
  478. G.add_edge(0, 1, id="1", label="foo", start=0, end=1)
  479. G.add_edge(0, 1)
  480. fh = io.BytesIO()
  481. nx.write_gexf(G, fh)
  482. fh.seek(0)
  483. H = nx.read_gexf(fh, node_type=int)
  484. assert sorted(G.nodes()) == sorted(H.nodes())
  485. assert sorted(sorted(e) for e in G.edges()) == sorted(
  486. sorted(e) for e in H.edges()
  487. )
  488. def test_missing_viz_attributes(self):
  489. G = nx.Graph()
  490. G.add_node(0, label="1", color="green")
  491. G.nodes[0]["viz"] = {"size": 54}
  492. G.nodes[0]["viz"]["position"] = {"x": 0, "y": 1, "z": 0}
  493. G.nodes[0]["viz"]["color"] = {"r": 0, "g": 0, "b": 256}
  494. G.nodes[0]["viz"]["shape"] = "http://random.url"
  495. G.nodes[0]["viz"]["thickness"] = 2
  496. fh = io.BytesIO()
  497. nx.write_gexf(G, fh, version="1.1draft")
  498. fh.seek(0)
  499. H = nx.read_gexf(fh, node_type=int)
  500. assert sorted(G.nodes()) == sorted(H.nodes())
  501. assert sorted(sorted(e) for e in G.edges()) == sorted(
  502. sorted(e) for e in H.edges()
  503. )
  504. # Test missing alpha value for version >draft1.1 - set default alpha value
  505. # to 1.0 instead of `None` when writing for better general compatibility
  506. fh = io.BytesIO()
  507. # G.nodes[0]["viz"]["color"] does not have an alpha value explicitly defined
  508. # so the default is used instead
  509. nx.write_gexf(G, fh, version="1.2draft")
  510. fh.seek(0)
  511. H = nx.read_gexf(fh, node_type=int)
  512. assert H.nodes[0]["viz"]["color"]["a"] == 1.0
  513. # Second graph for the other branch
  514. G = nx.Graph()
  515. G.add_node(0, label="1", color="green")
  516. G.nodes[0]["viz"] = {"size": 54}
  517. G.nodes[0]["viz"]["position"] = {"x": 0, "y": 1, "z": 0}
  518. G.nodes[0]["viz"]["color"] = {"r": 0, "g": 0, "b": 256, "a": 0.5}
  519. G.nodes[0]["viz"]["shape"] = "ftp://random.url"
  520. G.nodes[0]["viz"]["thickness"] = 2
  521. fh = io.BytesIO()
  522. nx.write_gexf(G, fh)
  523. fh.seek(0)
  524. H = nx.read_gexf(fh, node_type=int)
  525. assert sorted(G.nodes()) == sorted(H.nodes())
  526. assert sorted(sorted(e) for e in G.edges()) == sorted(
  527. sorted(e) for e in H.edges()
  528. )
  529. def test_slice_and_spell(self):
  530. # Test spell first, so version = 1.2
  531. G = nx.Graph()
  532. G.add_node(0, label="1", color="green")
  533. G.nodes[0]["spells"] = [(1, 2)]
  534. fh = io.BytesIO()
  535. nx.write_gexf(G, fh)
  536. fh.seek(0)
  537. H = nx.read_gexf(fh, node_type=int)
  538. assert sorted(G.nodes()) == sorted(H.nodes())
  539. assert sorted(sorted(e) for e in G.edges()) == sorted(
  540. sorted(e) for e in H.edges()
  541. )
  542. G = nx.Graph()
  543. G.add_node(0, label="1", color="green")
  544. G.nodes[0]["slices"] = [(1, 2)]
  545. fh = io.BytesIO()
  546. nx.write_gexf(G, fh, version="1.1draft")
  547. fh.seek(0)
  548. H = nx.read_gexf(fh, node_type=int)
  549. assert sorted(G.nodes()) == sorted(H.nodes())
  550. assert sorted(sorted(e) for e in G.edges()) == sorted(
  551. sorted(e) for e in H.edges()
  552. )
  553. def test_add_parent(self):
  554. G = nx.Graph()
  555. G.add_node(0, label="1", color="green", parents=[1, 2])
  556. fh = io.BytesIO()
  557. nx.write_gexf(G, fh)
  558. fh.seek(0)
  559. H = nx.read_gexf(fh, node_type=int)
  560. assert sorted(G.nodes()) == sorted(H.nodes())
  561. assert sorted(sorted(e) for e in G.edges()) == sorted(
  562. sorted(e) for e in H.edges()
  563. )