@graphty/graph-io 0.3.25 → 0.3.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (581) hide show
  1. package/README.md +49 -414
  2. package/dist/chunks/children-CL3Cy0ez.js.map +1 -1
  3. package/dist/chunks/choose-DrXtu_96.js +56 -0
  4. package/dist/chunks/choose-DrXtu_96.js.map +1 -0
  5. package/dist/chunks/constants-ikG85dT8.js +231 -0
  6. package/dist/chunks/constants-ikG85dT8.js.map +1 -0
  7. package/dist/chunks/{escape-B-tkk_Cl.js → escape-D1HPTUrP.js} +390 -99
  8. package/dist/chunks/escape-D1HPTUrP.js.map +1 -0
  9. package/dist/chunks/export-CHd4tQE2.js +4333 -0
  10. package/dist/chunks/export-CHd4tQE2.js.map +1 -0
  11. package/dist/chunks/{importer-eUREhBGj.js → exporter-D23CKNqF.js} +4969 -3201
  12. package/dist/chunks/exporter-D23CKNqF.js.map +1 -0
  13. package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-DfWTgrti.js} +23 -21
  14. package/dist/chunks/exporter-DfWTgrti.js.map +1 -0
  15. package/dist/chunks/exporter-DhtW6FxK.js +3465 -0
  16. package/dist/chunks/exporter-DhtW6FxK.js.map +1 -0
  17. package/dist/chunks/{exporter-BGrsWamJ.js → exporter-IHv4nkCm.js} +38 -229
  18. package/dist/chunks/exporter-IHv4nkCm.js.map +1 -0
  19. package/dist/chunks/{importer-BWY2FFc5.js → importer-B1wmPQjr.js} +255 -77
  20. package/dist/chunks/importer-B1wmPQjr.js.map +1 -0
  21. package/dist/chunks/{importer-D_e7e7LX.js → importer-B2W_58pz.js} +399 -146
  22. package/dist/chunks/importer-B2W_58pz.js.map +1 -0
  23. package/dist/chunks/importer-BSALvkui.js +2811 -0
  24. package/dist/chunks/importer-BSALvkui.js.map +1 -0
  25. package/dist/chunks/{importer-vi3bSdtw.js → importer-BVRE--Un.js} +510 -77
  26. package/dist/chunks/importer-BVRE--Un.js.map +1 -0
  27. package/dist/chunks/{importer-DET1tg6s.js → importer-CIAzt-wo.js} +1689 -312
  28. package/dist/chunks/importer-CIAzt-wo.js.map +1 -0
  29. package/dist/chunks/{importer-DIrbbnAf.js → importer-DpEW5zeG.js} +585 -226
  30. package/dist/chunks/importer-DpEW5zeG.js.map +1 -0
  31. package/dist/chunks/{importer-zGLo8Dg_.js → importer-z_FL4VMb.js} +488 -161
  32. package/dist/chunks/importer-z_FL4VMb.js.map +1 -0
  33. package/dist/chunks/json-elements-BAmNBZok.js +1225 -0
  34. package/dist/chunks/json-elements-BAmNBZok.js.map +1 -0
  35. package/dist/chunks/ontology-export-Bab4eUA8.js +357 -0
  36. package/dist/chunks/ontology-export-Bab4eUA8.js.map +1 -0
  37. package/dist/chunks/{records-dbRkxwaq.js → records-CdlvL4RO.js} +298 -114
  38. package/dist/chunks/records-CdlvL4RO.js.map +1 -0
  39. package/dist/chunks/{text-Dr0Ifpag.js → text-DzXjAiTl.js} +29 -11
  40. package/dist/chunks/text-DzXjAiTl.js.map +1 -0
  41. package/dist/chunks/{writer-C7flM-Ih.js → writer-DxjgHwSe.js} +19 -1
  42. package/dist/chunks/writer-DxjgHwSe.js.map +1 -0
  43. package/dist/csv.js +952 -262
  44. package/dist/csv.js.map +1 -1
  45. package/dist/cx.js +6 -3
  46. package/dist/cx2.js +2 -2
  47. package/dist/cys.js +6 -3
  48. package/dist/dot.js +1 -1
  49. package/dist/gexf.js +554 -122
  50. package/dist/gexf.js.map +1 -1
  51. package/dist/gml.js +344 -87
  52. package/dist/gml.js.map +1 -1
  53. package/dist/graph-io.js +1105 -213
  54. package/dist/graph-io.js.map +1 -1
  55. package/dist/graphml.js +3 -2
  56. package/dist/graphml.js.map +1 -1
  57. package/dist/json.js +3 -2
  58. package/dist/neo4j.js +565 -158
  59. package/dist/neo4j.js.map +1 -1
  60. package/dist/obo.js +6 -3
  61. package/dist/pajek.js +1 -1
  62. package/dist/src/children.d.ts +18 -6
  63. package/dist/src/children.d.ts.map +1 -1
  64. package/dist/src/children.js +6 -3
  65. package/dist/src/children.js.map +1 -1
  66. package/dist/src/common/attributes.d.ts +29 -11
  67. package/dist/src/common/attributes.d.ts.map +1 -1
  68. package/dist/src/common/attributes.js +25 -7
  69. package/dist/src/common/attributes.js.map +1 -1
  70. package/dist/src/common/cell-budget.d.ts +9 -2
  71. package/dist/src/common/cell-budget.d.ts.map +1 -1
  72. package/dist/src/common/cell-budget.js +11 -3
  73. package/dist/src/common/cell-budget.js.map +1 -1
  74. package/dist/src/common/choose.d.ts +19 -0
  75. package/dist/src/common/choose.d.ts.map +1 -0
  76. package/dist/src/common/choose.js +72 -0
  77. package/dist/src/common/choose.js.map +1 -0
  78. package/dist/src/common/codes.d.ts +456 -66
  79. package/dist/src/common/codes.d.ts.map +1 -1
  80. package/dist/src/common/codes.js +458 -66
  81. package/dist/src/common/codes.js.map +1 -1
  82. package/dist/src/common/compare.d.ts +81 -0
  83. package/dist/src/common/compare.d.ts.map +1 -0
  84. package/dist/src/common/compare.js +394 -0
  85. package/dist/src/common/compare.js.map +1 -0
  86. package/dist/src/common/cx-export.d.ts +123 -0
  87. package/dist/src/common/cx-export.d.ts.map +1 -0
  88. package/dist/src/common/cx-export.js +334 -0
  89. package/dist/src/common/cx-export.js.map +1 -0
  90. package/dist/src/common/declared-types.d.ts +22 -2
  91. package/dist/src/common/declared-types.d.ts.map +1 -1
  92. package/dist/src/common/declared-types.js +21 -3
  93. package/dist/src/common/declared-types.js.map +1 -1
  94. package/dist/src/common/direction.d.ts +74 -50
  95. package/dist/src/common/direction.d.ts.map +1 -1
  96. package/dist/src/common/direction.js +47 -27
  97. package/dist/src/common/direction.js.map +1 -1
  98. package/dist/src/common/escape.d.ts +21 -9
  99. package/dist/src/common/escape.d.ts.map +1 -1
  100. package/dist/src/common/escape.js +39 -22
  101. package/dist/src/common/escape.js.map +1 -1
  102. package/dist/src/common/export.d.ts +144 -32
  103. package/dist/src/common/export.d.ts.map +1 -1
  104. package/dist/src/common/export.js +216 -42
  105. package/dist/src/common/export.js.map +1 -1
  106. package/dist/src/common/format.d.ts +23 -1
  107. package/dist/src/common/format.d.ts.map +1 -1
  108. package/dist/src/common/format.js +26 -1
  109. package/dist/src/common/format.js.map +1 -1
  110. package/dist/src/common/ids.d.ts +9 -2
  111. package/dist/src/common/ids.d.ts.map +1 -1
  112. package/dist/src/common/ids.js +6 -2
  113. package/dist/src/common/ids.js.map +1 -1
  114. package/dist/src/common/input.d.ts +122 -28
  115. package/dist/src/common/input.d.ts.map +1 -1
  116. package/dist/src/common/input.js +890 -124
  117. package/dist/src/common/input.js.map +1 -1
  118. package/dist/src/common/json-elements.d.ts +144 -20
  119. package/dist/src/common/json-elements.d.ts.map +1 -1
  120. package/dist/src/common/json-elements.js +869 -189
  121. package/dist/src/common/json-elements.js.map +1 -1
  122. package/dist/src/common/line-format.d.ts +90 -0
  123. package/dist/src/common/line-format.d.ts.map +1 -0
  124. package/dist/src/common/line-format.js +185 -0
  125. package/dist/src/common/line-format.js.map +1 -0
  126. package/dist/src/common/lists.d.ts +6 -1
  127. package/dist/src/common/lists.d.ts.map +1 -1
  128. package/dist/src/common/lists.js +3 -1
  129. package/dist/src/common/lists.js.map +1 -1
  130. package/dist/src/common/ontology-export.d.ts +167 -0
  131. package/dist/src/common/ontology-export.d.ts.map +1 -0
  132. package/dist/src/common/ontology-export.js +359 -0
  133. package/dist/src/common/ontology-export.js.map +1 -0
  134. package/dist/src/common/ontology.d.ts +25 -3
  135. package/dist/src/common/ontology.d.ts.map +1 -1
  136. package/dist/src/common/ontology.js +30 -9
  137. package/dist/src/common/ontology.js.map +1 -1
  138. package/dist/src/common/options.d.ts +62 -12
  139. package/dist/src/common/options.d.ts.map +1 -1
  140. package/dist/src/common/options.js +89 -11
  141. package/dist/src/common/options.js.map +1 -1
  142. package/dist/src/common/plural.d.ts +31 -0
  143. package/dist/src/common/plural.d.ts.map +1 -0
  144. package/dist/src/common/plural.js +46 -0
  145. package/dist/src/common/plural.js.map +1 -0
  146. package/dist/src/common/report.d.ts +72 -14
  147. package/dist/src/common/report.d.ts.map +1 -1
  148. package/dist/src/common/report.js +146 -22
  149. package/dist/src/common/report.js.map +1 -1
  150. package/dist/src/common/temporal.d.ts +22 -5
  151. package/dist/src/common/temporal.d.ts.map +1 -1
  152. package/dist/src/common/temporal.js +13 -2
  153. package/dist/src/common/temporal.js.map +1 -1
  154. package/dist/src/common/text.d.ts +39 -23
  155. package/dist/src/common/text.d.ts.map +1 -1
  156. package/dist/src/common/text.js +51 -21
  157. package/dist/src/common/text.js.map +1 -1
  158. package/dist/src/common/weights.d.ts +37 -4
  159. package/dist/src/common/weights.d.ts.map +1 -1
  160. package/dist/src/common/weights.js +61 -2
  161. package/dist/src/common/weights.js.map +1 -1
  162. package/dist/src/common/writer.d.ts +19 -3
  163. package/dist/src/common/writer.d.ts.map +1 -1
  164. package/dist/src/common/writer.js +30 -2
  165. package/dist/src/common/writer.js.map +1 -1
  166. package/dist/src/common/xml.d.ts +125 -9
  167. package/dist/src/common/xml.d.ts.map +1 -1
  168. package/dist/src/common/xml.js +402 -85
  169. package/dist/src/common/xml.js.map +1 -1
  170. package/dist/src/common/zip.d.ts +44 -4
  171. package/dist/src/common/zip.d.ts.map +1 -1
  172. package/dist/src/common/zip.js +134 -5
  173. package/dist/src/common/zip.js.map +1 -1
  174. package/dist/src/formats/csv/exporter.d.ts +80 -24
  175. package/dist/src/formats/csv/exporter.d.ts.map +1 -1
  176. package/dist/src/formats/csv/exporter.js +146 -33
  177. package/dist/src/formats/csv/exporter.js.map +1 -1
  178. package/dist/src/formats/csv/header.d.ts +38 -9
  179. package/dist/src/formats/csv/header.d.ts.map +1 -1
  180. package/dist/src/formats/csv/header.js +46 -10
  181. package/dist/src/formats/csv/header.js.map +1 -1
  182. package/dist/src/formats/csv/importer.d.ts +187 -37
  183. package/dist/src/formats/csv/importer.d.ts.map +1 -1
  184. package/dist/src/formats/csv/importer.js +835 -204
  185. package/dist/src/formats/csv/importer.js.map +1 -1
  186. package/dist/src/formats/csv/index.d.ts +100 -19
  187. package/dist/src/formats/csv/index.d.ts.map +1 -1
  188. package/dist/src/formats/csv/index.js +94 -21
  189. package/dist/src/formats/csv/index.js.map +1 -1
  190. package/dist/src/formats/csv/records.d.ts +95 -10
  191. package/dist/src/formats/csv/records.d.ts.map +1 -1
  192. package/dist/src/formats/csv/records.js +333 -122
  193. package/dist/src/formats/csv/records.js.map +1 -1
  194. package/dist/src/formats/csv/values.d.ts +1 -0
  195. package/dist/src/formats/csv/values.d.ts.map +1 -1
  196. package/dist/src/formats/csv/values.js +1 -0
  197. package/dist/src/formats/csv/values.js.map +1 -1
  198. package/dist/src/formats/cx/exporter.d.ts +102 -0
  199. package/dist/src/formats/cx/exporter.d.ts.map +1 -0
  200. package/dist/src/formats/cx/exporter.js +1217 -0
  201. package/dist/src/formats/cx/exporter.js.map +1 -0
  202. package/dist/src/formats/cx/importer.d.ts +79 -32
  203. package/dist/src/formats/cx/importer.d.ts.map +1 -1
  204. package/dist/src/formats/cx/importer.js +682 -287
  205. package/dist/src/formats/cx/importer.js.map +1 -1
  206. package/dist/src/formats/cx/index.d.ts +4 -3
  207. package/dist/src/formats/cx/index.d.ts.map +1 -1
  208. package/dist/src/formats/cx/index.js +4 -3
  209. package/dist/src/formats/cx/index.js.map +1 -1
  210. package/dist/src/formats/cx2/exporter.d.ts +29 -11
  211. package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
  212. package/dist/src/formats/cx2/exporter.js +39 -268
  213. package/dist/src/formats/cx2/exporter.js.map +1 -1
  214. package/dist/src/formats/cx2/importer.d.ts +78 -28
  215. package/dist/src/formats/cx2/importer.d.ts.map +1 -1
  216. package/dist/src/formats/cx2/importer.js +227 -80
  217. package/dist/src/formats/cx2/importer.js.map +1 -1
  218. package/dist/src/formats/cx2/index.d.ts +2 -1
  219. package/dist/src/formats/cx2/index.d.ts.map +1 -1
  220. package/dist/src/formats/cx2/index.js +2 -1
  221. package/dist/src/formats/cx2/index.js.map +1 -1
  222. package/dist/src/formats/cys/constants.d.ts +66 -17
  223. package/dist/src/formats/cys/constants.d.ts.map +1 -1
  224. package/dist/src/formats/cys/constants.js +59 -18
  225. package/dist/src/formats/cys/constants.js.map +1 -1
  226. package/dist/src/formats/cys/exporter.d.ts +118 -0
  227. package/dist/src/formats/cys/exporter.d.ts.map +1 -0
  228. package/dist/src/formats/cys/exporter.js +1072 -0
  229. package/dist/src/formats/cys/exporter.js.map +1 -0
  230. package/dist/src/formats/cys/importer.d.ts +22 -8
  231. package/dist/src/formats/cys/importer.d.ts.map +1 -1
  232. package/dist/src/formats/cys/importer.js +510 -149
  233. package/dist/src/formats/cys/importer.js.map +1 -1
  234. package/dist/src/formats/cys/index.d.ts +4 -2
  235. package/dist/src/formats/cys/index.d.ts.map +1 -1
  236. package/dist/src/formats/cys/index.js +4 -2
  237. package/dist/src/formats/cys/index.js.map +1 -1
  238. package/dist/src/formats/cys/session.d.ts +46 -7
  239. package/dist/src/formats/cys/session.d.ts.map +1 -1
  240. package/dist/src/formats/cys/session.js +101 -27
  241. package/dist/src/formats/cys/session.js.map +1 -1
  242. package/dist/src/formats/cys/tables.d.ts +9 -2
  243. package/dist/src/formats/cys/tables.d.ts.map +1 -1
  244. package/dist/src/formats/cys/tables.js +29 -7
  245. package/dist/src/formats/cys/tables.js.map +1 -1
  246. package/dist/src/formats/dot/exporter.d.ts +42 -14
  247. package/dist/src/formats/dot/exporter.d.ts.map +1 -1
  248. package/dist/src/formats/dot/exporter.js +56 -29
  249. package/dist/src/formats/dot/exporter.js.map +1 -1
  250. package/dist/src/formats/dot/importer.d.ts +91 -29
  251. package/dist/src/formats/dot/importer.d.ts.map +1 -1
  252. package/dist/src/formats/dot/importer.js +190 -39
  253. package/dist/src/formats/dot/importer.js.map +1 -1
  254. package/dist/src/formats/dot/index.d.ts +2 -1
  255. package/dist/src/formats/dot/index.d.ts.map +1 -1
  256. package/dist/src/formats/dot/index.js +2 -1
  257. package/dist/src/formats/dot/index.js.map +1 -1
  258. package/dist/src/formats/dot/names.d.ts +44 -11
  259. package/dist/src/formats/dot/names.d.ts.map +1 -1
  260. package/dist/src/formats/dot/names.js +44 -11
  261. package/dist/src/formats/dot/names.js.map +1 -1
  262. package/dist/src/formats/dot/tokenizer.d.ts +9 -2
  263. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
  264. package/dist/src/formats/dot/tokenizer.js +20 -5
  265. package/dist/src/formats/dot/tokenizer.js.map +1 -1
  266. package/dist/src/formats/gexf/exporter.d.ts +46 -15
  267. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  268. package/dist/src/formats/gexf/exporter.js +45 -15
  269. package/dist/src/formats/gexf/exporter.js.map +1 -1
  270. package/dist/src/formats/gexf/importer.d.ts +87 -23
  271. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  272. package/dist/src/formats/gexf/importer.js +471 -90
  273. package/dist/src/formats/gexf/importer.js.map +1 -1
  274. package/dist/src/formats/gexf/index.d.ts +84 -21
  275. package/dist/src/formats/gexf/index.d.ts.map +1 -1
  276. package/dist/src/formats/gexf/index.js +80 -23
  277. package/dist/src/formats/gexf/index.js.map +1 -1
  278. package/dist/src/formats/gexf/schema.d.ts +67 -15
  279. package/dist/src/formats/gexf/schema.d.ts.map +1 -1
  280. package/dist/src/formats/gexf/schema.js +59 -13
  281. package/dist/src/formats/gexf/schema.js.map +1 -1
  282. package/dist/src/formats/gml/exporter.d.ts +69 -21
  283. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  284. package/dist/src/formats/gml/exporter.js +74 -29
  285. package/dist/src/formats/gml/exporter.js.map +1 -1
  286. package/dist/src/formats/gml/importer.d.ts +116 -25
  287. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  288. package/dist/src/formats/gml/importer.js +290 -61
  289. package/dist/src/formats/gml/importer.js.map +1 -1
  290. package/dist/src/formats/gml/index.d.ts +87 -24
  291. package/dist/src/formats/gml/index.d.ts.map +1 -1
  292. package/dist/src/formats/gml/index.js +79 -26
  293. package/dist/src/formats/gml/index.js.map +1 -1
  294. package/dist/src/formats/gml/syntax.d.ts +67 -18
  295. package/dist/src/formats/gml/syntax.d.ts.map +1 -1
  296. package/dist/src/formats/gml/syntax.js +110 -28
  297. package/dist/src/formats/gml/syntax.js.map +1 -1
  298. package/dist/src/formats/graphml/constants.d.ts +174 -44
  299. package/dist/src/formats/graphml/constants.d.ts.map +1 -1
  300. package/dist/src/formats/graphml/constants.js +165 -44
  301. package/dist/src/formats/graphml/constants.js.map +1 -1
  302. package/dist/src/formats/graphml/exporter.d.ts +17 -9
  303. package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
  304. package/dist/src/formats/graphml/exporter.js +33 -12
  305. package/dist/src/formats/graphml/exporter.js.map +1 -1
  306. package/dist/src/formats/graphml/importer.d.ts +19 -7
  307. package/dist/src/formats/graphml/importer.d.ts.map +1 -1
  308. package/dist/src/formats/graphml/importer.js +466 -39
  309. package/dist/src/formats/graphml/importer.js.map +1 -1
  310. package/dist/src/formats/graphml/index.d.ts +2 -1
  311. package/dist/src/formats/graphml/index.d.ts.map +1 -1
  312. package/dist/src/formats/graphml/index.js +2 -1
  313. package/dist/src/formats/graphml/index.js.map +1 -1
  314. package/dist/src/formats/graphml/tree.d.ts +6 -2
  315. package/dist/src/formats/graphml/tree.d.ts.map +1 -1
  316. package/dist/src/formats/graphml/tree.js +6 -2
  317. package/dist/src/formats/graphml/tree.js.map +1 -1
  318. package/dist/src/formats/graphml/yfiles.d.ts +8 -3
  319. package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
  320. package/dist/src/formats/graphml/yfiles.js +36 -15
  321. package/dist/src/formats/graphml/yfiles.js.map +1 -1
  322. package/dist/src/formats/json/dialect.d.ts +88 -24
  323. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  324. package/dist/src/formats/json/dialect.js +109 -19
  325. package/dist/src/formats/json/dialect.js.map +1 -1
  326. package/dist/src/formats/json/exporter.d.ts +134 -22
  327. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  328. package/dist/src/formats/json/exporter.js +135 -54
  329. package/dist/src/formats/json/exporter.js.map +1 -1
  330. package/dist/src/formats/json/importer.d.ts +173 -52
  331. package/dist/src/formats/json/importer.d.ts.map +1 -1
  332. package/dist/src/formats/json/importer.js +899 -220
  333. package/dist/src/formats/json/importer.js.map +1 -1
  334. package/dist/src/formats/json/index.d.ts +3 -2
  335. package/dist/src/formats/json/index.d.ts.map +1 -1
  336. package/dist/src/formats/json/index.js +3 -2
  337. package/dist/src/formats/json/index.js.map +1 -1
  338. package/dist/src/formats/json/obographs-export.d.ts +68 -0
  339. package/dist/src/formats/json/obographs-export.d.ts.map +1 -0
  340. package/dist/src/formats/json/obographs-export.js +941 -0
  341. package/dist/src/formats/json/obographs-export.js.map +1 -0
  342. package/dist/src/formats/json/obographs.d.ts +26 -0
  343. package/dist/src/formats/json/obographs.d.ts.map +1 -1
  344. package/dist/src/formats/json/obographs.js +297 -80
  345. package/dist/src/formats/json/obographs.js.map +1 -1
  346. package/dist/src/formats/neo4j/exporter.d.ts +109 -21
  347. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
  348. package/dist/src/formats/neo4j/exporter.js +239 -86
  349. package/dist/src/formats/neo4j/exporter.js.map +1 -1
  350. package/dist/src/formats/neo4j/header.d.ts +17 -4
  351. package/dist/src/formats/neo4j/header.d.ts.map +1 -1
  352. package/dist/src/formats/neo4j/header.js +36 -4
  353. package/dist/src/formats/neo4j/header.js.map +1 -1
  354. package/dist/src/formats/neo4j/importer.d.ts +107 -27
  355. package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
  356. package/dist/src/formats/neo4j/importer.js +415 -63
  357. package/dist/src/formats/neo4j/importer.js.map +1 -1
  358. package/dist/src/formats/neo4j/index.d.ts +73 -23
  359. package/dist/src/formats/neo4j/index.d.ts.map +1 -1
  360. package/dist/src/formats/neo4j/index.js +65 -25
  361. package/dist/src/formats/neo4j/index.js.map +1 -1
  362. package/dist/src/formats/obo/exporter.d.ts +150 -0
  363. package/dist/src/formats/obo/exporter.d.ts.map +1 -0
  364. package/dist/src/formats/obo/exporter.js +1736 -0
  365. package/dist/src/formats/obo/exporter.js.map +1 -0
  366. package/dist/src/formats/obo/importer.d.ts +95 -19
  367. package/dist/src/formats/obo/importer.d.ts.map +1 -1
  368. package/dist/src/formats/obo/importer.js +475 -104
  369. package/dist/src/formats/obo/importer.js.map +1 -1
  370. package/dist/src/formats/obo/index.d.ts +6 -3
  371. package/dist/src/formats/obo/index.d.ts.map +1 -1
  372. package/dist/src/formats/obo/index.js +6 -3
  373. package/dist/src/formats/obo/index.js.map +1 -1
  374. package/dist/src/formats/obo/syntax.d.ts +108 -4
  375. package/dist/src/formats/obo/syntax.d.ts.map +1 -1
  376. package/dist/src/formats/obo/syntax.js +211 -34
  377. package/dist/src/formats/obo/syntax.js.map +1 -1
  378. package/dist/src/formats/pajek/exporter.d.ts +54 -16
  379. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  380. package/dist/src/formats/pajek/exporter.js +67 -35
  381. package/dist/src/formats/pajek/exporter.js.map +1 -1
  382. package/dist/src/formats/pajek/importer.d.ts +84 -22
  383. package/dist/src/formats/pajek/importer.d.ts.map +1 -1
  384. package/dist/src/formats/pajek/importer.js +303 -83
  385. package/dist/src/formats/pajek/importer.js.map +1 -1
  386. package/dist/src/formats/pajek/index.d.ts +2 -1
  387. package/dist/src/formats/pajek/index.d.ts.map +1 -1
  388. package/dist/src/formats/pajek/index.js +2 -1
  389. package/dist/src/formats/pajek/index.js.map +1 -1
  390. package/dist/src/formats/pajek/syntax.d.ts +77 -14
  391. package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
  392. package/dist/src/formats/pajek/syntax.js +186 -54
  393. package/dist/src/formats/pajek/syntax.js.map +1 -1
  394. package/dist/src/formats/xgmml/columns.d.ts +1 -0
  395. package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
  396. package/dist/src/formats/xgmml/columns.js +4 -0
  397. package/dist/src/formats/xgmml/columns.js.map +1 -1
  398. package/dist/src/formats/xgmml/constants.d.ts +186 -56
  399. package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
  400. package/dist/src/formats/xgmml/constants.js +181 -57
  401. package/dist/src/formats/xgmml/constants.js.map +1 -1
  402. package/dist/src/formats/xgmml/document.d.ts +65 -11
  403. package/dist/src/formats/xgmml/document.d.ts.map +1 -1
  404. package/dist/src/formats/xgmml/document.js +157 -28
  405. package/dist/src/formats/xgmml/document.js.map +1 -1
  406. package/dist/src/formats/xgmml/emit.d.ts +61 -3
  407. package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
  408. package/dist/src/formats/xgmml/emit.js +129 -26
  409. package/dist/src/formats/xgmml/emit.js.map +1 -1
  410. package/dist/src/formats/xgmml/exporter.d.ts +13 -6
  411. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
  412. package/dist/src/formats/xgmml/exporter.js +21 -22
  413. package/dist/src/formats/xgmml/exporter.js.map +1 -1
  414. package/dist/src/formats/xgmml/importer.d.ts +33 -12
  415. package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
  416. package/dist/src/formats/xgmml/importer.js +35 -72
  417. package/dist/src/formats/xgmml/importer.js.map +1 -1
  418. package/dist/src/formats/xgmml/index.d.ts +1 -0
  419. package/dist/src/formats/xgmml/index.d.ts.map +1 -1
  420. package/dist/src/formats/xgmml/index.js +1 -0
  421. package/dist/src/formats/xgmml/index.js.map +1 -1
  422. package/dist/src/formats/xgmml/values.d.ts +8 -1
  423. package/dist/src/formats/xgmml/values.d.ts.map +1 -1
  424. package/dist/src/formats/xgmml/values.js +8 -3
  425. package/dist/src/formats/xgmml/values.js.map +1 -1
  426. package/dist/src/index.d.ts +23 -18
  427. package/dist/src/index.d.ts.map +1 -1
  428. package/dist/src/index.js +28 -16
  429. package/dist/src/index.js.map +1 -1
  430. package/dist/src/registry.d.ts +478 -92
  431. package/dist/src/registry.d.ts.map +1 -1
  432. package/dist/src/registry.js +807 -88
  433. package/dist/src/registry.js.map +1 -1
  434. package/dist/src/sniff.d.ts +61 -19
  435. package/dist/src/sniff.d.ts.map +1 -1
  436. package/dist/src/sniff.js +121 -29
  437. package/dist/src/sniff.js.map +1 -1
  438. package/dist/src/types.d.ts +362 -112
  439. package/dist/src/types.d.ts.map +1 -1
  440. package/dist/src/types.js +28 -7
  441. package/dist/src/types.js.map +1 -1
  442. package/dist/xgmml.js +2 -2
  443. package/docs/samples/README.md +25 -0
  444. package/docs/samples/got-edges.csv +353 -0
  445. package/docs/samples/got-network.graphml +1384 -0
  446. package/docs/samples/got-nodes.csv +108 -0
  447. package/docs/samples/got.cx +930 -0
  448. package/docs/samples/got.cx2 +465 -0
  449. package/docs/samples/got.gexf +471 -0
  450. package/docs/samples/got.gml +2650 -0
  451. package/docs/samples/got.json +2546 -0
  452. package/docs/samples/got.net +461 -0
  453. package/docs/samples/movies-nodes.csv +9 -0
  454. package/docs/samples/movies-rels.csv +8 -0
  455. package/docs/samples/networks.cys +0 -0
  456. package/docs/samples/proteins.xgmml +9 -0
  457. package/docs/samples/teams.gv +24 -0
  458. package/docs/samples/vehicles.obo +25 -0
  459. package/package.json +9 -4
  460. package/src/children.ts +18 -6
  461. package/src/common/attributes.ts +29 -11
  462. package/src/common/cell-budget.ts +11 -3
  463. package/src/common/choose.ts +89 -0
  464. package/src/common/codes.ts +490 -66
  465. package/src/common/compare.ts +472 -0
  466. package/src/common/cx-export.ts +432 -0
  467. package/src/common/declared-types.ts +40 -6
  468. package/src/common/direction.ts +78 -51
  469. package/src/common/escape.ts +39 -22
  470. package/src/common/export.ts +284 -47
  471. package/src/common/format.ts +30 -1
  472. package/src/common/ids.ts +14 -3
  473. package/src/common/input.ts +1057 -134
  474. package/src/common/json-elements.ts +1035 -199
  475. package/src/common/line-format.ts +249 -0
  476. package/src/common/lists.ts +7 -2
  477. package/src/common/ontology-export.ts +461 -0
  478. package/src/common/ontology.ts +31 -9
  479. package/src/common/options.ts +110 -15
  480. package/src/common/plural.ts +49 -0
  481. package/src/common/report.ts +168 -25
  482. package/src/common/temporal.ts +22 -5
  483. package/src/common/text.ts +62 -23
  484. package/src/common/weights.ts +74 -4
  485. package/src/common/writer.ts +37 -3
  486. package/src/common/xml.ts +479 -90
  487. package/src/common/zip.ts +169 -7
  488. package/src/formats/csv/exporter.ts +202 -47
  489. package/src/formats/csv/header.ts +50 -11
  490. package/src/formats/csv/importer.ts +1133 -245
  491. package/src/formats/csv/index.ts +106 -19
  492. package/src/formats/csv/records.ts +396 -122
  493. package/src/formats/csv/values.ts +1 -0
  494. package/src/formats/cx/exporter.ts +1483 -0
  495. package/src/formats/cx/importer.ts +890 -302
  496. package/src/formats/cx/index.ts +4 -3
  497. package/src/formats/cx2/exporter.ts +62 -339
  498. package/src/formats/cx2/importer.ts +318 -87
  499. package/src/formats/cx2/index.ts +2 -1
  500. package/src/formats/cys/constants.ts +62 -17
  501. package/src/formats/cys/exporter.ts +1368 -0
  502. package/src/formats/cys/importer.ts +659 -157
  503. package/src/formats/cys/index.ts +4 -2
  504. package/src/formats/cys/session.ts +142 -34
  505. package/src/formats/cys/tables.ts +47 -9
  506. package/src/formats/dot/exporter.ts +83 -37
  507. package/src/formats/dot/importer.ts +248 -54
  508. package/src/formats/dot/index.ts +2 -1
  509. package/src/formats/dot/names.ts +44 -11
  510. package/src/formats/dot/tokenizer.ts +25 -6
  511. package/src/formats/gexf/exporter.ts +57 -18
  512. package/src/formats/gexf/importer.ts +636 -105
  513. package/src/formats/gexf/index.ts +90 -21
  514. package/src/formats/gexf/schema.ts +67 -15
  515. package/src/formats/gml/exporter.ts +90 -36
  516. package/src/formats/gml/importer.ts +388 -67
  517. package/src/formats/gml/index.ts +85 -24
  518. package/src/formats/gml/syntax.ts +113 -28
  519. package/src/formats/graphml/constants.ts +178 -44
  520. package/src/formats/graphml/exporter.ts +52 -18
  521. package/src/formats/graphml/importer.ts +640 -60
  522. package/src/formats/graphml/index.ts +2 -1
  523. package/src/formats/graphml/tree.ts +6 -2
  524. package/src/formats/graphml/yfiles.ts +37 -15
  525. package/src/formats/json/dialect.ts +128 -29
  526. package/src/formats/json/exporter.ts +205 -68
  527. package/src/formats/json/importer.ts +1163 -253
  528. package/src/formats/json/index.ts +3 -1
  529. package/src/formats/json/obographs-export.ts +1197 -0
  530. package/src/formats/json/obographs.ts +353 -92
  531. package/src/formats/neo4j/exporter.ts +321 -102
  532. package/src/formats/neo4j/header.ts +44 -6
  533. package/src/formats/neo4j/importer.ts +512 -73
  534. package/src/formats/neo4j/index.ts +75 -23
  535. package/src/formats/obo/exporter.ts +2210 -0
  536. package/src/formats/obo/importer.ts +622 -109
  537. package/src/formats/obo/index.ts +6 -3
  538. package/src/formats/obo/syntax.ts +232 -37
  539. package/src/formats/pajek/exporter.ts +90 -42
  540. package/src/formats/pajek/importer.ts +407 -93
  541. package/src/formats/pajek/index.ts +2 -1
  542. package/src/formats/pajek/syntax.ts +216 -55
  543. package/src/formats/xgmml/columns.ts +10 -0
  544. package/src/formats/xgmml/constants.ts +183 -56
  545. package/src/formats/xgmml/document.ts +212 -34
  546. package/src/formats/xgmml/emit.ts +175 -34
  547. package/src/formats/xgmml/exporter.ts +36 -28
  548. package/src/formats/xgmml/importer.ts +64 -86
  549. package/src/formats/xgmml/index.ts +1 -0
  550. package/src/formats/xgmml/values.ts +12 -4
  551. package/src/index.ts +81 -11
  552. package/src/registry.ts +1121 -135
  553. package/src/sniff.ts +163 -32
  554. package/src/types.ts +385 -114
  555. package/dist/chunks/escape-B-tkk_Cl.js.map +0 -1
  556. package/dist/chunks/export-Bh60Dv-n.js +0 -582
  557. package/dist/chunks/export-Bh60Dv-n.js.map +0 -1
  558. package/dist/chunks/exporter-BGrsWamJ.js.map +0 -1
  559. package/dist/chunks/exporter-DIeGJXAZ.js.map +0 -1
  560. package/dist/chunks/importer-BWY2FFc5.js.map +0 -1
  561. package/dist/chunks/importer-DD-xv9_Y.js +0 -1471
  562. package/dist/chunks/importer-DD-xv9_Y.js.map +0 -1
  563. package/dist/chunks/importer-DET1tg6s.js.map +0 -1
  564. package/dist/chunks/importer-DEcKhsmQ.js +0 -1614
  565. package/dist/chunks/importer-DEcKhsmQ.js.map +0 -1
  566. package/dist/chunks/importer-DIrbbnAf.js.map +0 -1
  567. package/dist/chunks/importer-D_e7e7LX.js.map +0 -1
  568. package/dist/chunks/importer-eUREhBGj.js.map +0 -1
  569. package/dist/chunks/importer-vi3bSdtw.js.map +0 -1
  570. package/dist/chunks/importer-zGLo8Dg_.js.map +0 -1
  571. package/dist/chunks/json-elements-DDS8N2Dd.js +0 -779
  572. package/dist/chunks/json-elements-DDS8N2Dd.js.map +0 -1
  573. package/dist/chunks/ontology-BnrJ4I98.js +0 -113
  574. package/dist/chunks/ontology-BnrJ4I98.js.map +0 -1
  575. package/dist/chunks/records-dbRkxwaq.js.map +0 -1
  576. package/dist/chunks/report-B1z4WT9e.js +0 -2372
  577. package/dist/chunks/report-B1z4WT9e.js.map +0 -1
  578. package/dist/chunks/text-Dr0Ifpag.js.map +0 -1
  579. package/dist/chunks/weights-Dzba96G3.js +0 -176
  580. package/dist/chunks/weights-Dzba96G3.js.map +0 -1
  581. package/dist/chunks/writer-C7flM-Ih.js.map +0 -1
@@ -0,0 +1,1368 @@
1
+ /**
2
+ * The Cytoscape session exporter: writes a snapshot as a `.cys` file that Cytoscape Desktop 3.x
3
+ * opens as a session holding one network (one collection with one registered subnetwork).
4
+ *
5
+ * The archive holds what Cytoscape's session reader needs and nothing more:
6
+ * - `CytoscapeSession/3.0.0.version`, the empty marker that makes Cytoscape read a 3.x session;
7
+ * - `networks/<root>-<name>.xgmml`, the topology: one root network holding one registered
8
+ * subnetwork, its nodes and edges by SUID, each edge with `cy:directed`;
9
+ * - `tables/<subnetwork>-<name>/LOCAL_ATTRS-org.cytoscape.model.Cy{Node,Edge,Network}-*.cytable`,
10
+ * every column as a CyCSV table keyed by SUID (a session network file carries no attributes:
11
+ * Cytoscape ignores atts on the nodes and edges of a 3.x session), with the label in `name`,
12
+ * which Cytoscape's default style shows; plus the root network's own table with its name;
13
+ * - `views/<subnetwork>-<view>-<name>.xgmml`, the node coordinates (y negated back to screen
14
+ * coordinates), only when the snapshot has positions: graph-io computes no layout.
15
+ *
16
+ * Styles, the network list, properties and thumbnails are not written; Cytoscape gives the view
17
+ * its default style. Entries are stored, not compressed. Every SUID comes from one counter, above
18
+ * the node ids that are kept as SUIDs; Cytoscape renumbers them all on load.
19
+ */
20
+
21
+ import { type Column, GraphFormatError, type GraphSnapshot, type NodeId } from "@graphty/graph-format";
22
+
23
+ import { DictHeuristic } from "../../common/attributes.js";
24
+ import { type PairFolding, pairFolding } from "../../common/direction.js";
25
+ import { escapeXmlAttribute, quoteCsvCell } from "../../common/escape.js";
26
+ import { capabilities, checkCapabilities, LOSS } from "../../common/export.js";
27
+ import { formatDecimal, formatF32, formatF64 } from "../../common/format.js";
28
+ import { type ResolvedExportOptions, resolveExportOptions } from "../../common/options.js";
29
+ import { agree, plural } from "../../common/plural.js";
30
+ import { type ExplicitWeights, explicitWeights } from "../../common/weights.js";
31
+ import { hasIllegalXmlChar } from "../../common/xml.js";
32
+ import { writeZip, type ZipWriteEntry } from "../../common/zip.js";
33
+ import { type CommonExportOptions, type ExportCapabilities, type GraphExporter, type LossNote } from "../../types.js";
34
+ import { ORIGINAL_ID_ATTRIBUTE } from "../graphml/constants.js";
35
+ import {
36
+ CY_NAMESPACE,
37
+ EDGE_ID_COLUMN,
38
+ GRAPHICS_COLUMN,
39
+ NESTED_NETWORK_COLUMN,
40
+ NETWORK_POINTER_COLUMN,
41
+ NETWORKS_COLUMN,
42
+ PARENT_COLUMN,
43
+ PARENTS_COLUMN,
44
+ POSITION_COLUMN,
45
+ SUBGRAPH_COLUMN,
46
+ XGMML_NAMESPACE,
47
+ XLINK_NAMESPACE,
48
+ Z_COLUMN,
49
+ } from "../xgmml/constants.js";
50
+ import { FORMAT } from "./constants.js";
51
+
52
+ /**
53
+ * The options of the Cytoscape session exporter: the common export options; it has none of its own. The
54
+ * collection and network names come from the snapshot's `meta.name` (default "Network").
55
+ * @category Built-in formats
56
+ */
57
+ export type CysExportOptions = CommonExportOptions;
58
+
59
+ /**
60
+ * The loss notes the session exporter's check() returns, by name. A key is the code without its
61
+ * severity and format prefixes.
62
+ * @category Built-in formats
63
+ */
64
+ export const CYS_LOSS = Object.freeze({
65
+ /** CyCSV has no unset text cell: an unset cell of a text column reads back as "". */
66
+ UNSET_AS_EMPTY_STRING: "W_CYS_UNSET_AS_EMPTY_STRING",
67
+ /**
68
+ * List cells CyCSV cannot hold exactly: an unset or empty list of text reads back as [""], an
69
+ * empty list of numbers or booleans reads back unset, trailing empty text items vanish, and an
70
+ * item holding a newline splits in two.
71
+ */
72
+ LIST_ITEMS: "W_CYS_LIST_ITEMS",
73
+ /** A text cell starting with "=" is a formula to Cytoscape (an error cell there); graph-io reads it back as text. */
74
+ TEXT_AS_EQUATION: "W_CYS_TEXT_AS_EQUATION",
75
+ /** A nested (json) column is written as a text column holding its JSON text. */
76
+ JSON_AS_STRING: "W_CYS_JSON_AS_STRING",
77
+ /** A position that cannot be written as it is: another shape, a non-finite coordinate, a z read back in the z column. */
78
+ POSITION: "W_CYS_POSITION",
79
+ /**
80
+ * An attribute with a role (for example, the label) is written where the format keeps that role, and reads back under the
81
+ * name the format's importer gives it.
82
+ */
83
+ COLUMN_NAME_CHANGED: LOSS.COLUMN_NAME_CHANGED,
84
+ /**
85
+ * An attribute without a role is written where the format keeps a role (for example, a `name` column as the label), and
86
+ * reads back with that role.
87
+ */
88
+ ROLE_ASSUMED: LOSS.ROLE_ASSUMED,
89
+ /** A mutual pair is written as two directed edges without its mark. */
90
+ MUTUAL_EXPANDED: LOSS.MUTUAL_EXPANDED,
91
+ /** Node ids that are numbers read back as their text. */
92
+ ID_TEXT_TYPE: LOSS.ID_TEXT_TYPE,
93
+ /**
94
+ * Two node ids would be written as the same text (the number 5 and the text "5"); the save fails with
95
+ * E_INVALID_ID.
96
+ */
97
+ ID_TEXT_COLLISION: LOSS.ID_TEXT_COLLISION,
98
+ /**
99
+ * Node ids the format cannot write, under `sanitizeIds: "error"`; the save fails with E_INVALID_ID. Pass
100
+ * `sanitizeIds: "mangle"` to rewrite them.
101
+ */
102
+ ID_CHARSET: LOSS.ID_CHARSET,
103
+ /** Node ids that are not positive integers under sanitizeIds "mangle": renumbered, originals kept. */
104
+ ID_MANGLED: LOSS.ID_MANGLED,
105
+ /** Edges without a usable id get generated SUIDs. */
106
+ EDGE_IDS_GENERATED: LOSS.EDGE_IDS_GENERATED,
107
+ /**
108
+ * An attribute type Cytoscape stores as a wider one (a 32-bit float as Double, a byte as Integer), or a label that
109
+ * is not text and is written as text; it reads back with the Cytoscape type.
110
+ */
111
+ DTYPE_UNSUPPORTED: LOSS.DTYPE,
112
+ /** A column whose every cell is unset (and which is not text) vanishes. */
113
+ EMPTY_COLUMN_DROPPED: LOSS.EMPTY_COLUMN,
114
+ /**
115
+ * A text attribute reads back as a dictionary attribute, or the reverse, because the importer chooses by how often
116
+ * its values repeat. The values are the same.
117
+ */
118
+ STORAGE_CLASS_CHANGED: LOSS.STORAGE_CLASS,
119
+ /** A plain `weight` edge column reads back as the edge weight. */
120
+ WEIGHT_KEY_CLASH: LOSS.WEIGHT_KEY_CLASH,
121
+ /** A parent / parents column: groups are not written. */
122
+ HIERARCHY_DROPPED: LOSS.HIERARCHY,
123
+ /** A start / end / timestamp column: sessions have no time. */
124
+ TEMPORAL_DROPPED: LOSS.TEMPORAL,
125
+ /** An attribute with a role the format has no place for is written as a plain attribute; the role is lost. */
126
+ ROLE_DROPPED: LOSS.ROLE,
127
+ /** An extension table the session cannot carry. */
128
+ EXTENSION_TABLE_DROPPED: LOSS.EXTENSION_TABLE,
129
+ });
130
+
131
+ /**
132
+ * What a Cytoscape session keeps: mixed direction, parallel edges and self-loops, integer SUIDs
133
+ * for nodes and edges, String / Double / Integer / Long / Boolean columns and lists of them,
134
+ * graph columns and positions. Groups, defaults, styles and time are not written.
135
+ * @category Built-in formats
136
+ */
137
+ export const CYS_CAPABILITIES: ExportCapabilities = capabilities({
138
+ mixedDirection: true,
139
+ multiEdges: true,
140
+ selfLoops: true,
141
+ edgeIds: "required",
142
+ idCharset: "integer",
143
+ dtypes: ["string", "dict", "f64", "i32", "bool", "list"],
144
+ components: false,
145
+ lists: true,
146
+ json: false,
147
+ defaults: false,
148
+ options: false,
149
+ hierarchy: false,
150
+ temporal: "none",
151
+ graphAttributes: true,
152
+ positions: true,
153
+ viz: false,
154
+ });
155
+
156
+ /** The roles with a slot (the label as `name`, the edge id as the SUID, positions in the view) and their read-back names. */
157
+ const SLOT_ROLES: ReadonlySet<string> = new Set(["label", "id", "position"]);
158
+ const ROLE_NAMES: Readonly<Record<string, string>> = Object.freeze({
159
+ label: "name",
160
+ id: EDGE_ID_COLUMN,
161
+ position: POSITION_COLUMN,
162
+ });
163
+
164
+ /** Roles whose columns are not written as table columns (structure, or reported dropped by checkCapabilities). */
165
+ const SKIPPED_ROLES: ReadonlySet<string> = new Set([
166
+ "directed",
167
+ "pair",
168
+ "mutual",
169
+ "weight",
170
+ "timeText",
171
+ "parent",
172
+ "parents",
173
+ "start",
174
+ "end",
175
+ "timestamp",
176
+ "timestamps",
177
+ "spells",
178
+ "open",
179
+ "spellsOpen",
180
+ "position",
181
+ "color",
182
+ "size",
183
+ "shape",
184
+ "thickness",
185
+ ]);
186
+
187
+ /** A table of the session. */
188
+ type Domain = "node" | "edge" | "network";
189
+
190
+ /** The Java classes of the columns Cytoscape creates in every table, by lower-case name. */
191
+ const BUILT_IN: Readonly<Record<Domain, ReadonlyMap<string, string>>> = {
192
+ node: new Map([
193
+ ["name", "java.lang.String"],
194
+ ["shared name", "java.lang.String"],
195
+ ["selected", "java.lang.Boolean"],
196
+ ]),
197
+ edge: new Map([
198
+ ["name", "java.lang.String"],
199
+ ["shared name", "java.lang.String"],
200
+ ["interaction", "java.lang.String"],
201
+ ["shared interaction", "java.lang.String"],
202
+ ["selected", "java.lang.Boolean"],
203
+ ]),
204
+ network: new Map([
205
+ ["name", "java.lang.String"],
206
+ ["shared name", "java.lang.String"],
207
+ ["selected", "java.lang.Boolean"],
208
+ ["__annotations", "java.util.List<java.lang.String>"],
209
+ ]),
210
+ };
211
+
212
+ /** Column names graph-io's session importer gives its own columns; a table column of such a name reads back renamed. */
213
+ const IMPORTER_NAMES: Readonly<Record<Domain, ReadonlySet<string>>> = {
214
+ node: new Set([
215
+ POSITION_COLUMN,
216
+ Z_COLUMN,
217
+ GRAPHICS_COLUMN,
218
+ PARENT_COLUMN,
219
+ PARENTS_COLUMN,
220
+ SUBGRAPH_COLUMN,
221
+ NESTED_NETWORK_COLUMN,
222
+ NETWORK_POINTER_COLUMN,
223
+ NETWORKS_COLUMN,
224
+ ]),
225
+ edge: new Set([EDGE_ID_COLUMN, GRAPHICS_COLUMN]),
226
+ network: new Set([GRAPHICS_COLUMN]),
227
+ };
228
+
229
+ const STRING = "java.lang.String";
230
+ const DOUBLE = "java.lang.Double";
231
+ const INTEGER = "java.lang.Integer";
232
+ const LONG = "java.lang.Long";
233
+ const BOOLEAN = "java.lang.Boolean";
234
+ const I32_MAX = 2147483647;
235
+
236
+ /** The folder every entry lives under (Cytoscape needs one; the name is free). */
237
+ const ROOT_FOLDER = "CytoscapeSession/";
238
+
239
+ /** The default network name. */
240
+ const DEFAULT_NAME = "Network";
241
+
242
+ /** A record-a-note callback. */
243
+ type NoteFn = (code: string, message: string, column?: string | null, count?: number | null) => void;
244
+
245
+ /** One column of a CyCSV table. */
246
+ interface TableColumn {
247
+ /** The written name. */
248
+ readonly name: string;
249
+ /** The Java class text (`java.lang.Double`, `java.util.List<java.lang.String>`). */
250
+ readonly javaClass: string;
251
+ /** The cell text of a row (node, edge or network), or null for an unset cell (written empty). */
252
+ readonly cell: (row: number) => string | null;
253
+ }
254
+
255
+ /** A column to place in a table before its name is settled. */
256
+ interface Candidate {
257
+ /** The name it wants. */
258
+ readonly name: string;
259
+ /** The snapshot column it came from, or null for one the exporter makes. */
260
+ readonly source: Column | null;
261
+ /** The Java class text. */
262
+ readonly javaClass: string;
263
+ /** The cell text of a row, or null for an unset cell. */
264
+ readonly cell: (row: number) => string | null;
265
+ }
266
+
267
+ /** Everything check() and export() agree on. */
268
+ interface Plan {
269
+ readonly notes: LossNote[];
270
+ readonly fatal: GraphFormatError | null;
271
+ /** The SUID of each node. */
272
+ readonly nodeSuids: readonly number[];
273
+ /** The SUID of each edge (meaningless for a folded mirror). */
274
+ readonly edgeSuids: readonly number[];
275
+ readonly rootSuid: number;
276
+ readonly subSuid: number;
277
+ /** The view's SUID, or null when nothing is placed. */
278
+ readonly viewSuid: number | null;
279
+ /** The first SUID of the view's nodes. */
280
+ readonly viewNodeBase: number;
281
+ readonly name: string;
282
+ readonly folding: PairFolding;
283
+ readonly nodeTable: readonly TableColumn[];
284
+ readonly edgeTable: readonly TableColumn[];
285
+ readonly networkTable: readonly TableColumn[];
286
+ readonly rootTable: readonly TableColumn[];
287
+ readonly position: Column | null;
288
+ /** The z column written as the view's z, or null. */
289
+ readonly z: Column | null;
290
+ /** Whether a position's own z is written as the view's z (no z column). */
291
+ readonly positionZ: boolean;
292
+ }
293
+
294
+ // ============================================================ ids
295
+
296
+ /** Positive integer text a Long holds exactly. */
297
+ const SUID_TEXT = /^[1-9]\d*$/;
298
+
299
+ /**
300
+ * The SUID a node id can keep, or null: a positive safe integer, or its canonical digits.
301
+ * @param id - the id
302
+ * @returns the SUID, or null
303
+ */
304
+ function suidOf(id: NodeId): number | null {
305
+ if (typeof id === "number") {
306
+ return Number.isSafeInteger(id) && id > 0 ? id : null;
307
+ }
308
+ if (SUID_TEXT.test(id)) {
309
+ const n = Number(id);
310
+ return Number.isSafeInteger(n) ? n : null;
311
+ }
312
+ return null;
313
+ }
314
+
315
+ /** The node SUIDs and what the id rule found. */
316
+ interface NodeSuids {
317
+ /** The kept SUID of each node, or 0 for a node that needs a new one. */
318
+ readonly kept: number[];
319
+ /** The nodes that need a new SUID. */
320
+ readonly bad: number[];
321
+ }
322
+
323
+ /**
324
+ * The SUIDs node ids keep (the first of two ids naming one SUID keeps it).
325
+ * @param snapshot - the snapshot
326
+ * @returns the kept SUIDs and the nodes without one
327
+ */
328
+ function keptNodeSuids(snapshot: GraphSnapshot): NodeSuids {
329
+ const kept: number[] = [];
330
+ const bad: number[] = [];
331
+ const used = new Set<number>();
332
+ for (let i = 0; i < snapshot.nodeCount; i++) {
333
+ const suid = suidOf(snapshot.ids.idOf(i));
334
+ if (suid === null || used.has(suid)) {
335
+ kept.push(0);
336
+ bad.push(i);
337
+ } else {
338
+ used.add(suid);
339
+ kept.push(suid);
340
+ }
341
+ }
342
+ return { kept, bad };
343
+ }
344
+
345
+ /**
346
+ * The edge SUIDs the id role column keeps: positive integers, distinct, and not node SUIDs.
347
+ * @param snapshot - the snapshot
348
+ * @param nodeSuids - the SUIDs nodes keep
349
+ * @returns the kept SUID of each edge, 0 where one is generated
350
+ */
351
+ function keptEdgeSuids(snapshot: GraphSnapshot, nodeSuids: ReadonlySet<number>): number[] {
352
+ const column = snapshot.edges.byRole("id");
353
+ const out = new Array<number>(snapshot.edgeCount).fill(0);
354
+ if (column === null) {
355
+ return out;
356
+ }
357
+ const used = new Set<number>();
358
+ for (let e = 0; e < snapshot.edgeCount; e++) {
359
+ const value = column.isSet(e) ? column.value(e) : null;
360
+ const suid = typeof value === "number" || typeof value === "string" ? suidOf(value) : null;
361
+ if (suid !== null && !used.has(suid) && !nodeSuids.has(suid)) {
362
+ used.add(suid);
363
+ out[e] = suid;
364
+ }
365
+ }
366
+ return out;
367
+ }
368
+
369
+ // ============================================================ columns
370
+
371
+ /**
372
+ * The Java class of a scalar dtype.
373
+ * @param column - the column (for the long origin and the u32 range)
374
+ * @param dtype - the dtype (a list's item dtype)
375
+ * @returns the class text
376
+ */
377
+ function scalarClass(column: Column, dtype: string | null): string {
378
+ switch (dtype) {
379
+ case "bool":
380
+ return BOOLEAN;
381
+ case "i32":
382
+ case "u8":
383
+ return INTEGER;
384
+ case "u32":
385
+ return maxValue(column) > I32_MAX ? LONG : INTEGER;
386
+ case "f32":
387
+ case "f64":
388
+ return /long$/i.test(column.meta.origin?.type ?? "") && allSafeIntegers(column) ? LONG : DOUBLE;
389
+ default:
390
+ return STRING;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * The items of a set cell: a list's items, or the scalar itself.
396
+ * @param column - the column
397
+ * @param row - the row
398
+ * @returns the values
399
+ */
400
+ function itemsOf(column: Column, row: number): unknown[] {
401
+ return column.dtype === "list" ? [...column.sliceOf(row)] : [column.value(row)];
402
+ }
403
+
404
+ /**
405
+ * Whether every set value of a numeric column (or of its list items) is a safe integer.
406
+ * @param column - the column
407
+ * @returns true when Long can carry every value
408
+ */
409
+ function allSafeIntegers(column: Column): boolean {
410
+ for (let r = 0; r < column.length; r++) {
411
+ if (column.isSet(r) && !itemsOf(column, r).every((v) => typeof v === "number" && Number.isSafeInteger(v))) {
412
+ return false;
413
+ }
414
+ }
415
+ return true;
416
+ }
417
+
418
+ /**
419
+ * The largest value of a numeric column (or of its list items).
420
+ * @param column - the column
421
+ * @returns the maximum, 0 when empty
422
+ */
423
+ function maxValue(column: Column): number {
424
+ let max = 0;
425
+ for (let r = 0; r < column.length; r++) {
426
+ if (column.isSet(r)) {
427
+ for (const v of itemsOf(column, r)) {
428
+ if (typeof v === "number" && v > max) {
429
+ max = v;
430
+ }
431
+ }
432
+ }
433
+ }
434
+ return max;
435
+ }
436
+
437
+ /**
438
+ * The CyCSV text of one value of a class.
439
+ * @param value - the value
440
+ * @param javaClass - the scalar class
441
+ * @param dtype - the dtype it came from
442
+ * @returns the text Cytoscape's `valueOf` reads back
443
+ */
444
+ function valueText(value: unknown, javaClass: string, dtype: string | null): string {
445
+ if (javaClass === BOOLEAN) {
446
+ return value === true ? "true" : "false";
447
+ }
448
+ if (javaClass === DOUBLE) {
449
+ return formatDecimal(value as number, dtype === "f32" ? "f32" : "f64");
450
+ }
451
+ if (javaClass === INTEGER || javaClass === LONG) {
452
+ return String(value);
453
+ }
454
+ return textOf(value);
455
+ }
456
+
457
+ /**
458
+ * A value as text: strings as they are, numbers in their shortest form, anything else as JSON.
459
+ * @param value - the value
460
+ * @returns the text
461
+ */
462
+ function textOf(value: unknown): string {
463
+ if (typeof value === "string") {
464
+ return value;
465
+ }
466
+ if (typeof value === "number") {
467
+ return formatF64(value);
468
+ }
469
+ if (typeof value === "boolean") {
470
+ return value ? "true" : "false";
471
+ }
472
+ if (ArrayBuffer.isView(value)) {
473
+ return JSON.stringify(Array.from(value as unknown as ArrayLike<number>).map(finiteOrText));
474
+ }
475
+ return JSON.stringify(value) ?? "";
476
+ }
477
+
478
+ /**
479
+ * A number for JSON text (JSON has no NaN or infinities: those are written as their names).
480
+ * @param v - the number
481
+ * @returns the number or its name
482
+ */
483
+ function finiteOrText(v: number): number | string {
484
+ return Number.isFinite(v) ? v : String(v);
485
+ }
486
+
487
+ /**
488
+ * A snapshot column as a table candidate: its class and cell texts.
489
+ * @param column - the column
490
+ * @param name - the name it wants
491
+ * @returns the candidate
492
+ */
493
+ function candidateOf(column: Column, name: string): Candidate {
494
+ const { meta } = column;
495
+ if (column.dtype === "list") {
496
+ const item = meta.itemDtype;
497
+ const itemClass = scalarClass(column, item === "json" ? null : item);
498
+ return {
499
+ name,
500
+ source: column,
501
+ javaClass: `java.util.List<${itemClass}>`,
502
+ cell: (row) =>
503
+ column.isSet(row)
504
+ ? [...column.sliceOf(row)].map((v) => valueText(v, itemClass, item)).join("\n")
505
+ : null,
506
+ };
507
+ }
508
+ const javaClass = meta.dtype === "json" || meta.components > 1 ? STRING : scalarClass(column, meta.dtype);
509
+ return {
510
+ name,
511
+ source: column,
512
+ javaClass,
513
+ cell: (row) => (column.isSet(row) ? valueText(column.value(row), javaClass, meta.dtype) : null),
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Settle the names of a table's columns: case-insensitively unique, `SUID` and Cytoscape's own
519
+ * columns of another class renamed `<name>#2`, `#3`...
520
+ * @param domain - the table
521
+ * @param candidates - the columns in order
522
+ * @param note - the recorder
523
+ * @returns the table's columns (the SUID key first)
524
+ */
525
+ function settleNames(domain: Domain, candidates: readonly Candidate[], note: NoteFn): TableColumn[] {
526
+ const taken = new Set<string>(["suid"]);
527
+ const out: TableColumn[] = [];
528
+ for (const c of candidates) {
529
+ const lower = c.name.toLowerCase();
530
+ const builtIn = BUILT_IN[domain].get(lower);
531
+ let { name } = c;
532
+ const clash = builtIn !== undefined && builtIn !== c.javaClass ? builtIn : null;
533
+ if (taken.has(lower) || clash !== null) {
534
+ let k = 2;
535
+ while (taken.has(`${lower}#${k}`)) {
536
+ k++;
537
+ }
538
+ name = `${c.name}#${k}`;
539
+ const why = renameReason(lower, clash);
540
+ const set = c.source === null ? null : c.source.length - c.source.nullCount;
541
+ note(LOSS.COLUMN_NAME_CHANGED, `${domain} column "${c.name}" is written as "${name}": ${why}`, c.name, set);
542
+ } else if (c.source !== null && IMPORTER_NAMES[domain].has(c.name)) {
543
+ note(
544
+ LOSS.COLUMN_NAME_CHANGED,
545
+ `${domain} column "${c.name}" is written to the session's table and reads back as "${c.name}#2": graph-io's session reader makes a "${c.name}" column of its own`,
546
+ c.name,
547
+ c.source.length - c.source.nullCount,
548
+ );
549
+ }
550
+ taken.add(name.toLowerCase());
551
+ out.push({ name, javaClass: c.javaClass, cell: c.cell });
552
+ }
553
+ return out;
554
+ }
555
+
556
+ /**
557
+ * Why a column is renamed.
558
+ * @param lower - the column name, lower-cased
559
+ * @param clash - the Java class of Cytoscape's own column of that name when it holds another, else null
560
+ * @returns the reason
561
+ */
562
+ function renameReason(lower: string, clash: string | null): string {
563
+ if (lower === "suid") {
564
+ return "SUID is Cytoscape's key column";
565
+ }
566
+ if (clash !== null) {
567
+ return `Cytoscape's own "${lower}" column holds ${clash}`;
568
+ }
569
+ return "its name is taken (Cytoscape column names ignore case)";
570
+ }
571
+
572
+ /**
573
+ * Java's `String.split("\n")`: trailing empty items are removed.
574
+ * @param text - the cell
575
+ * @returns the items
576
+ */
577
+ function javaSplit(text: string): string[] {
578
+ if (!text.includes("\n")) {
579
+ return [text];
580
+ }
581
+ const items = text.split("\n");
582
+ while (items.at(-1) === "") {
583
+ items.pop();
584
+ }
585
+ return items;
586
+ }
587
+
588
+ /**
589
+ * The notes of one written column: what CyCSV cannot hold of its cells.
590
+ * @param domain - the table
591
+ * @param c - the candidate
592
+ * @param rows - the row count
593
+ * @param note - the recorder
594
+ */
595
+ function cellNotes(domain: Domain, c: Candidate, rows: number, note: NoteFn): void {
596
+ const { source } = c;
597
+ if (source === null) {
598
+ return;
599
+ }
600
+ const label = `${domain} column "${source.meta.name}"`;
601
+ const { name } = source.meta;
602
+ const text = c.javaClass === STRING;
603
+ const { unset, equations, lists, set, heuristic } = cellCounts(c, source, rows);
604
+ if (source.dtype === "json") {
605
+ note(CYS_LOSS.JSON_AS_STRING, `${label} holds nested values; written as their JSON text`, name, set);
606
+ }
607
+ if (set === 0 && (!text || rows === 0)) {
608
+ note(LOSS.EMPTY_COLUMN, `${label} has no value; it is not in the re-import`, name, null);
609
+ return;
610
+ }
611
+ if (text && unset > 0) {
612
+ note(
613
+ CYS_LOSS.UNSET_AS_EMPTY_STRING,
614
+ `${label}: ${unset} unset cell${plural(unset)} ${agree(unset, "is", "are")} written empty and read back as ""`,
615
+ name,
616
+ unset,
617
+ );
618
+ }
619
+ if (lists > 0) {
620
+ note(
621
+ CYS_LOSS.LIST_ITEMS,
622
+ `${label}: ${lists} list cell${plural(lists)} ${agree(lists, "reads", "read")} back changed (an unset or empty list of text as [""], an empty list of numbers unset, trailing empty items dropped, items holding a newline split)`,
623
+ name,
624
+ lists,
625
+ );
626
+ }
627
+ if (equations > 0) {
628
+ note(
629
+ CYS_LOSS.TEXT_AS_EQUATION,
630
+ `${label}: ${equations} cell${plural(equations)} start with "=", which Cytoscape reads as a formula (an error cell); graph-io reads them back as text`,
631
+ name,
632
+ equations,
633
+ );
634
+ }
635
+ if (text && (source.dtype === "string" || source.dtype === "dict")) {
636
+ const readsAs = heuristic.decide();
637
+ if (readsAs !== source.dtype) {
638
+ note(LOSS.STORAGE_CLASS, `${label} reads back as ${readsAs} (the cardinality heuristic)`, name, null);
639
+ }
640
+ }
641
+ }
642
+
643
+ /**
644
+ * Count what CyCSV changes in a column's cells: unset cells, text read as a formula, lists that
645
+ * read back changed; and feed the text cells to the dict heuristic.
646
+ * @param c - the candidate
647
+ * @param source - its column
648
+ * @param rows - the row count
649
+ * @returns the counts and the heuristic
650
+ */
651
+ function cellCounts(
652
+ c: Candidate,
653
+ source: Column,
654
+ rows: number,
655
+ ): { unset: number; equations: number; lists: number; set: number; heuristic: DictHeuristic } {
656
+ const text = c.javaClass === STRING;
657
+ const stringList = c.javaClass === `java.util.List<${STRING}>`;
658
+ const counts = { unset: 0, equations: 0, lists: 0, set: 0, heuristic: new DictHeuristic() };
659
+ for (let r = 0; r < rows; r++) {
660
+ const cell = c.cell(r);
661
+ if (cell === null) {
662
+ counts.unset++;
663
+ } else {
664
+ counts.set++;
665
+ }
666
+ const written = cell ?? "";
667
+ if (text && !counts.heuristic.decided) {
668
+ counts.heuristic.observe(written);
669
+ }
670
+ if (written.startsWith("=")) {
671
+ counts.equations++;
672
+ } else if (source.dtype === "list" && listChanges(source, r, cell, stringList)) {
673
+ counts.lists++;
674
+ }
675
+ }
676
+ return counts;
677
+ }
678
+
679
+ /**
680
+ * Whether a list cell reads back changed from the text CyCSV writes for it.
681
+ * @param source - the list column
682
+ * @param r - the row
683
+ * @param cell - the written text, or null when unset
684
+ * @param stringList - whether Cytoscape reads the column as a list of text
685
+ * @returns true when it changes
686
+ */
687
+ function listChanges(
688
+ source: Extract<Column, { dtype: "list" }>,
689
+ r: number,
690
+ cell: string | null,
691
+ stringList: boolean,
692
+ ): boolean {
693
+ const written = cell ?? "";
694
+ let back: string[] | null = null;
695
+ if (stringList) {
696
+ back = written.length === 0 ? [""] : javaSplit(written);
697
+ } else if (written.length > 0) {
698
+ back = javaSplit(written).filter((item) => item.length > 0);
699
+ }
700
+ const items = cell === null ? null : [...source.sliceOf(r)].map((v) => valueText(v, "", null));
701
+ if (back === null || items === null) {
702
+ return back !== items;
703
+ }
704
+ return back.length !== items.length || (stringList && back.some((b, i) => b !== items[i]));
705
+ }
706
+
707
+ /**
708
+ * Whether a column is the z slot (a role-less f64 `z` column, as the importer makes it).
709
+ * @param column - the column
710
+ * @returns true for the z column
711
+ */
712
+ function isZ(column: Column): boolean {
713
+ return column.meta.name === Z_COLUMN && column.meta.dtype === "f64" && column.meta.role === null;
714
+ }
715
+
716
+ /**
717
+ * The candidates of the node or edge table: the label as `name`, then the exporter's own columns,
718
+ * then every other column in order.
719
+ * @param snapshot - the snapshot
720
+ * @param domain - node or edge
721
+ * @param own - the exporter's columns after the label
722
+ * @param skip - columns written elsewhere
723
+ * @param note - the recorder
724
+ * @returns the candidates
725
+ */
726
+ function elementCandidates(
727
+ snapshot: GraphSnapshot,
728
+ domain: "node" | "edge",
729
+ own: readonly Candidate[],
730
+ skip: (column: Column) => boolean,
731
+ note: NoteFn,
732
+ ): Candidate[] {
733
+ const table = domain === "node" ? snapshot.nodes : snapshot.edges;
734
+ const label = table.byRole("label");
735
+ const out: Candidate[] = [];
736
+ if (label !== null) {
737
+ const c = candidateOf(label, "name");
738
+ out.push({ ...c, javaClass: STRING, cell: (row) => (label.isSet(row) ? textOf(label.value(row)) : null) });
739
+ if (label.dtype !== "string" && label.dtype !== "dict") {
740
+ note(
741
+ LOSS.DTYPE,
742
+ `${domain} label column "${label.meta.name}" (${label.dtype}) is written as text and reads back as string`,
743
+ label.meta.name,
744
+ label.length - label.nullCount,
745
+ );
746
+ }
747
+ }
748
+ out.push(...own);
749
+ for (const column of table) {
750
+ const { role } = column.meta;
751
+ if (column === label || (role !== null && (SKIPPED_ROLES.has(role) || role === "id")) || skip(column)) {
752
+ continue;
753
+ }
754
+ out.push(candidateOf(column, column.meta.name));
755
+ if (
756
+ label === null &&
757
+ role === null &&
758
+ column.meta.name === "name" &&
759
+ scalarClass(column, column.dtype) === STRING
760
+ ) {
761
+ note(
762
+ LOSS.ROLE_ASSUMED,
763
+ `${domain} column "name" is Cytoscape's name and reads back as the label`,
764
+ "name",
765
+ column.length - column.nullCount,
766
+ );
767
+ }
768
+ }
769
+ return out;
770
+ }
771
+
772
+ /**
773
+ * Settle a table and record the notes of its cells.
774
+ * @param domain - the table
775
+ * @param candidates - its columns
776
+ * @param rows - its row count
777
+ * @param note - the recorder
778
+ * @returns the columns
779
+ */
780
+ function planTable(domain: Domain, candidates: readonly Candidate[], rows: number, note: NoteFn): TableColumn[] {
781
+ for (const c of candidates) {
782
+ cellNotes(domain, c, rows, note);
783
+ }
784
+ return settleNames(domain, candidates, note);
785
+ }
786
+
787
+ // ============================================================ the plan
788
+
789
+ /** The SUIDs of the nodes and edges, and what the id rule found. */
790
+ interface PlannedIds {
791
+ readonly nodeSuids: readonly number[];
792
+ readonly edgeSuids: readonly number[];
793
+ /** The next free SUID. */
794
+ readonly next: number;
795
+ /** E_INVALID_ID when the ids cannot be written, else null. */
796
+ readonly fatal: GraphFormatError | null;
797
+ /** Whether nodes were renumbered (the originals are written). */
798
+ readonly mangled: boolean;
799
+ }
800
+
801
+ /**
802
+ * Note the node ids whose text changes on the way back: numbers read back as text, and the error
803
+ * for two ids with the same text.
804
+ * @param snapshot - the snapshot
805
+ * @param note - the recorder
806
+ * @returns the error the save throws for a collision, or null
807
+ */
808
+ function idTextNotes(snapshot: GraphSnapshot, note: NoteFn): GraphFormatError | null {
809
+ let collisions = 0;
810
+ let numeric = 0;
811
+ const texts = new Set<string>();
812
+ for (let i = 0; i < snapshot.nodeCount; i++) {
813
+ const id = snapshot.ids.idOf(i);
814
+ numeric += typeof id === "number" ? 1 : 0;
815
+ const text = String(id);
816
+ if (texts.has(text)) {
817
+ collisions++;
818
+ }
819
+ texts.add(text);
820
+ }
821
+ let fatal: GraphFormatError | null = null;
822
+ if (collisions > 0) {
823
+ note(
824
+ LOSS.ID_TEXT_COLLISION,
825
+ `${collisions} node id${plural(collisions)} ${agree(collisions, "shares", "share")} their text with another id (a number and a string); the save fails with E_INVALID_ID`,
826
+ null,
827
+ collisions,
828
+ );
829
+ fatal = new GraphFormatError(
830
+ "E_INVALID_ID",
831
+ `${collisions} node id${plural(collisions)} ${agree(collisions, "shares", "share")} their text with another id`,
832
+ { reason: "collision", count: collisions },
833
+ );
834
+ }
835
+ if (numeric > 0) {
836
+ note(
837
+ LOSS.ID_TEXT_TYPE,
838
+ `${numeric} numeric node id${plural(numeric)} ${agree(numeric, "reads", "read")} back as their text`,
839
+ null,
840
+ numeric,
841
+ );
842
+ }
843
+ return fatal;
844
+ }
845
+
846
+ /**
847
+ * Note the node ids that are not positive integers: renumbered under "mangle", else refused.
848
+ * @param snapshot - the snapshot
849
+ * @param bad - the indices of those ids
850
+ * @param mangle - whether sanitizeIds is "mangle"
851
+ * @param note - the recorder
852
+ * @returns the error the save throws, or null under "mangle"
853
+ */
854
+ function badIdNotes(
855
+ snapshot: GraphSnapshot,
856
+ bad: readonly number[],
857
+ mangle: boolean,
858
+ note: NoteFn,
859
+ ): GraphFormatError | null {
860
+ if (mangle) {
861
+ note(
862
+ LOSS.ID_MANGLED,
863
+ `${bad.length} node id${plural(bad.length)} that ${agree(bad.length, "is", "are")} not positive integers ${agree(bad.length, "is", "are")} renumbered, since Cytoscape ids are positive integers; the originals are kept in the "${ORIGINAL_ID_ATTRIBUTE}" column (restored by restoreMangledIds)`,
864
+ null,
865
+ bad.length,
866
+ );
867
+ return null;
868
+ }
869
+ note(
870
+ LOSS.ID_CHARSET,
871
+ `${bad.length} node id${plural(bad.length)} ${agree(bad.length, "is", "are")} not positive integers, which Cytoscape requires; the save fails unless sanitizeIds is "mangle"`,
872
+ null,
873
+ bad.length,
874
+ );
875
+ const first = snapshot.ids.idOf(bad[0]);
876
+ return new GraphFormatError(
877
+ "E_INVALID_ID",
878
+ `${bad.length} node id${plural(bad.length)} cannot be written as Cytoscape ids, which are positive integers (first: ${JSON.stringify(first)} at index ${bad[0]}); pass sanitizeIds: "mangle" to rewrite them`,
879
+ { reason: "charset", charset: "integer", count: bad.length, index: bad[0] },
880
+ );
881
+ }
882
+
883
+ /**
884
+ * The SUIDs: node ids that are positive integers keep them, the others get new ones (or refuse
885
+ * under sanitizeIds "error"); edge ids likewise, from the id role column. Every new SUID comes
886
+ * from one counter above the kept ones.
887
+ * @param snapshot - the snapshot
888
+ * @param common - the resolved common options
889
+ * @param note - the recorder
890
+ * @returns the SUIDs
891
+ */
892
+ function planIds(snapshot: GraphSnapshot, common: ResolvedExportOptions, note: NoteFn): PlannedIds {
893
+ const { kept, bad } = keptNodeSuids(snapshot);
894
+ const collision = idTextNotes(snapshot, note);
895
+ const charset = bad.length > 0 ? badIdNotes(snapshot, bad, common.sanitizeIds === "mangle", note) : null;
896
+ const fatal = collision ?? charset;
897
+ const nodeSet = new Set(kept.filter((s) => s > 0));
898
+ const edgeKept = keptEdgeSuids(snapshot, nodeSet);
899
+ let next = 1;
900
+ for (const s of [...nodeSet, ...edgeKept]) {
901
+ next = Math.max(next, s + 1);
902
+ }
903
+ const nodeSuids = kept.map((s) => (s > 0 ? s : next++));
904
+ const idColumn = snapshot.edges.byRole("id");
905
+ let generated = 0;
906
+ const edgeSuids = edgeKept.map((s) => {
907
+ if (s > 0) {
908
+ return s;
909
+ }
910
+ generated++;
911
+ return next++;
912
+ });
913
+ if (idColumn !== null && generated > 0) {
914
+ note(
915
+ LOSS.EDGE_IDS_GENERATED,
916
+ `${generated} edge${plural(generated)} ${agree(generated, "has", "have")} no distinct positive integer id in "${idColumn.meta.name}"; they are written with new positive integer ids`,
917
+ idColumn.meta.name,
918
+ generated,
919
+ );
920
+ }
921
+ if (idColumn !== null && idColumn.dtype !== "string") {
922
+ note(
923
+ LOSS.DTYPE,
924
+ `edge id column "${idColumn.meta.name}" is ${idColumn.dtype}; edge ids read back as text`,
925
+ idColumn.meta.name,
926
+ snapshot.edgeCount - idColumn.nullCount,
927
+ );
928
+ }
929
+ return { nodeSuids, edgeSuids, next, fatal, mangled: common.sanitizeIds === "mangle" && bad.length > 0 };
930
+ }
931
+
932
+ /**
933
+ * The columns of the network's own table: its name (from meta.name unless a text graph column
934
+ * holds it), its description, and the graph columns.
935
+ * @param snapshot - the snapshot
936
+ * @param name - the network name
937
+ * @returns the candidates
938
+ */
939
+ function networkCandidates(snapshot: GraphSnapshot, name: string): Candidate[] {
940
+ const out: Candidate[] = [];
941
+ const graphName = snapshot.graph.get("name");
942
+ if (graphName === null || (graphName.dtype !== "string" && graphName.dtype !== "dict")) {
943
+ out.push({ name: "name", source: null, javaClass: STRING, cell: () => name });
944
+ }
945
+ const { description } = snapshot.meta;
946
+ if (description !== null && snapshot.graph.get("description") === null) {
947
+ out.push({ name: "description", source: null, javaClass: STRING, cell: () => description });
948
+ }
949
+ for (const column of snapshot.graph) {
950
+ out.push(candidateOf(column, column.meta.name));
951
+ }
952
+ return out;
953
+ }
954
+
955
+ /**
956
+ * Plan an export: the notes, the fatal condition, the SUIDs and the tables.
957
+ * @param snapshot - the snapshot
958
+ * @param common - the resolved common options
959
+ * @returns the plan
960
+ */
961
+ function plan(snapshot: GraphSnapshot, common: ResolvedExportOptions): Plan {
962
+ const notes: LossNote[] = [];
963
+ const note: NoteFn = (code, message, column = null, count = null) => {
964
+ notes.push(Object.freeze({ code, message, column, count }));
965
+ };
966
+ for (const gen of checkCapabilities(snapshot, CYS_CAPABILITIES, common, {
967
+ roles: SLOT_ROLES,
968
+ roleNames: ROLE_NAMES,
969
+ positionDtype: "f32",
970
+ })) {
971
+ // json is reported per column below; ids by the SUID rule (positive integers)
972
+ if (gen.code !== LOSS.JSON && gen.code !== LOSS.ID_CHARSET && gen.code !== LOSS.ID_MANGLED) {
973
+ notes.push(gen);
974
+ }
975
+ }
976
+
977
+ const ids = planIds(snapshot, common, note);
978
+ const { nodeSuids, edgeSuids, mangled, fatal } = ids;
979
+ let { next } = ids;
980
+ const rootSuid = next++;
981
+ const subSuid = next++;
982
+
983
+ // direction
984
+ const folding = pairFolding(snapshot);
985
+ if (folding.mutualCount > 0) {
986
+ note(
987
+ LOSS.MUTUAL_EXPANDED,
988
+ `${folding.mutualCount} mutual pair${plural(folding.mutualCount)} ${agree(folding.mutualCount, "is", "are")} written as two directed edges; the mutual mark is lost`,
989
+ null,
990
+ folding.mutualCount,
991
+ );
992
+ }
993
+
994
+ // positions
995
+ const position = snapshot.nodes.byRole("position");
996
+ const z = [...snapshot.nodes].find(isZ) ?? null;
997
+ const positionZ = position !== null && z === null;
998
+ if (position !== null) {
999
+ positionNotes(position, z, note);
1000
+ }
1001
+ const placed =
1002
+ (position !== null && position.nullCount < position.length) || (z !== null && z.nullCount < z.length);
1003
+ const viewSuid = placed ? next++ : null;
1004
+ const viewNodeBase = next;
1005
+
1006
+ // node table
1007
+ const nodeOwn: Candidate[] = mangled
1008
+ ? [
1009
+ {
1010
+ name: ORIGINAL_ID_ATTRIBUTE,
1011
+ source: null,
1012
+ javaClass: STRING,
1013
+ // every node carries its original, so an empty id ("") is told apart from an unset cell
1014
+ cell: (row) => String(snapshot.ids.idOf(row)),
1015
+ },
1016
+ ]
1017
+ : [];
1018
+ const nodeTable = planTable(
1019
+ "node",
1020
+ elementCandidates(snapshot, "node", nodeOwn, (c) => placed && c === z, note),
1021
+ snapshot.nodeCount,
1022
+ note,
1023
+ );
1024
+
1025
+ // edge table
1026
+ const weights: ExplicitWeights = explicitWeights(snapshot);
1027
+ const plainWeight = snapshot.edges.get("weight");
1028
+ if (plainWeight !== null && plainWeight.meta.role === null && !weights.weighted) {
1029
+ note(LOSS.WEIGHT_KEY_CLASH, `edge column "weight" reads back as the edge weight`, "weight", null);
1030
+ }
1031
+ const edgeOwn: Candidate[] = weights.weighted
1032
+ ? [
1033
+ {
1034
+ name: "weight",
1035
+ source: null,
1036
+ javaClass: DOUBLE,
1037
+ cell: (e) => (weights.isExplicit(e) ? formatDecimal(weights.value(e)) : null),
1038
+ },
1039
+ ]
1040
+ : [];
1041
+ const edgeRows = snapshot.edgeCount;
1042
+ const edgeTable = planTable(
1043
+ "edge",
1044
+ elementCandidates(snapshot, "edge", edgeOwn, () => false, note),
1045
+ edgeRows,
1046
+ note,
1047
+ );
1048
+
1049
+ const name = snapshot.meta.name ?? DEFAULT_NAME;
1050
+ const networkTable = planTable("network", networkCandidates(snapshot, name), 1, note);
1051
+ const rootTable: TableColumn[] = [{ name: "name", javaClass: STRING, cell: () => name }];
1052
+ return {
1053
+ notes,
1054
+ fatal,
1055
+ nodeSuids,
1056
+ edgeSuids,
1057
+ rootSuid,
1058
+ subSuid,
1059
+ viewSuid,
1060
+ viewNodeBase,
1061
+ name,
1062
+ folding,
1063
+ nodeTable,
1064
+ edgeTable,
1065
+ networkTable,
1066
+ rootTable,
1067
+ position,
1068
+ z: placed ? z : null,
1069
+ positionZ,
1070
+ };
1071
+ }
1072
+
1073
+ /**
1074
+ * The notes of the position column: its shape, non-finite coordinates, and a z that reads back
1075
+ * in the z column.
1076
+ * @param position - the position column
1077
+ * @param z - the z column, or null
1078
+ * @param note - the recorder
1079
+ */
1080
+ function positionNotes(position: Column, z: Column | null, note: NoteFn): void {
1081
+ const { name, components } = position.meta;
1082
+ if (components !== 3) {
1083
+ note(
1084
+ CYS_LOSS.POSITION,
1085
+ `position column "${name}" has ${components} component${plural(components)} and reads back with 3`,
1086
+ name,
1087
+ null,
1088
+ );
1089
+ }
1090
+ let nonFinite = 0;
1091
+ let depth = 0;
1092
+ for (let r = 0; r < position.length; r++) {
1093
+ if (!position.isSet(r)) {
1094
+ continue;
1095
+ }
1096
+ const xyz = Array.from(position.value(r) as ArrayLike<number>);
1097
+ if (xyz.slice(0, 2).some((v) => !Number.isFinite(v))) {
1098
+ nonFinite++;
1099
+ } else if ((xyz[2] ?? 0) !== 0) {
1100
+ depth++;
1101
+ }
1102
+ }
1103
+ if (nonFinite > 0) {
1104
+ note(
1105
+ CYS_LOSS.POSITION,
1106
+ `${nonFinite} position${plural(nonFinite)} with a non-finite coordinate ${agree(nonFinite, "is", "are")} not written`,
1107
+ name,
1108
+ nonFinite,
1109
+ );
1110
+ }
1111
+ if (depth > 0) {
1112
+ note(
1113
+ CYS_LOSS.POSITION,
1114
+ z === null
1115
+ ? `${depth} position${plural(depth)} ${agree(depth, "has", "have")} a z; it is written as the view's z and reads back in the z column`
1116
+ : `${depth} position${plural(depth)} ${agree(depth, "has", "have")} a z, but the view's z holds the z column; the position z is lost`,
1117
+ name,
1118
+ depth,
1119
+ );
1120
+ }
1121
+ }
1122
+
1123
+ // ============================================================ writing
1124
+
1125
+ /**
1126
+ * Cytoscape's `SessionUtil.escape`: Java's `URLEncoder.encode(text, "UTF-8")` (letters, digits
1127
+ * and `.-*_` kept, space as `+`, everything else `%XX`), then `-` as `%2D`.
1128
+ * @param text - the text
1129
+ * @returns the escaped text
1130
+ * @category Plugin helpers
1131
+ */
1132
+ export function sessionEscape(text: string): string {
1133
+ const wellFormed = text.replaceAll(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "�");
1134
+ return encodeURIComponent(wellFormed)
1135
+ .replaceAll(/[!~'()]/g, (c) => `%${(c.codePointAt(0) ?? 0).toString(16).toUpperCase()}`)
1136
+ .replaceAll("%20", "+")
1137
+ .replaceAll("-", "%2D");
1138
+ }
1139
+
1140
+ /**
1141
+ * One CyCSV table (schema version 1): the key column SUID, then the columns.
1142
+ * @param title - the table title
1143
+ * @param columns - the columns
1144
+ * @param rows - the SUID of each row, with its row index
1145
+ * @yields the table text
1146
+ * @returns nothing
1147
+ */
1148
+ function* cyTable(
1149
+ title: string,
1150
+ columns: readonly TableColumn[],
1151
+ rows: Iterable<readonly [number, number]>,
1152
+ ): Generator<string, void, undefined> {
1153
+ const line = (cells: readonly string[]): string => `${cells.map((c) => quoteCsvCell(c)).join(",")}\n`;
1154
+ yield line(["CyCSV-Version", "1"]);
1155
+ yield line(["SUID", ...columns.map((c) => c.name)]);
1156
+ yield line([LONG, ...columns.map((c) => c.javaClass)]);
1157
+ yield line(["", ...columns.map(() => "mutable")]);
1158
+ yield line([title, ""]);
1159
+ for (const [suid, row] of rows) {
1160
+ yield line([String(suid), ...columns.map((c) => c.cell(row) ?? "")]);
1161
+ }
1162
+ }
1163
+
1164
+ /** The XML declaration and the namespaces of a session XGMML document. */
1165
+ const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
1166
+ const NAMESPACES = `xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xlink="${XLINK_NAMESPACE}" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cy="${CY_NAMESPACE}" xmlns="${XGMML_NAMESPACE}"`;
1167
+
1168
+ /**
1169
+ * The network file: the root network holding the one registered subnetwork, nodes and edges by
1170
+ * SUID. No attributes: a 3.x session keeps them in the tables.
1171
+ * @param snapshot - the snapshot
1172
+ * @param p - the plan
1173
+ * @yields the document's text
1174
+ * @returns nothing
1175
+ */
1176
+ function* networkFile(snapshot: GraphSnapshot, p: Plan): Generator<string, void, undefined> {
1177
+ const label = !hasIllegalXmlChar(p.name) ? ` label="${escapeXmlAttribute(p.name)}"` : "";
1178
+ yield XML_DECLARATION;
1179
+ yield `<graph id="${p.rootSuid}"${label} directed="${snapshot.directed ? "1" : "0"}" cy:view="0" cy:registered="0" cy:documentVersion="3.0" ${NAMESPACES}>\n`;
1180
+ yield ` <att>\n <graph id="${p.subSuid}"${label} cy:registered="1">\n`;
1181
+ for (let i = 0; i < snapshot.nodeCount; i++) {
1182
+ yield ` <node id="${p.nodeSuids[i]}"/>\n`;
1183
+ }
1184
+ const { src, dst } = snapshot.edgeList();
1185
+ for (let e = 0; e < snapshot.edgeCount; e++) {
1186
+ if (p.folding.folded(e)) {
1187
+ continue;
1188
+ }
1189
+ const directed = snapshot.directed && p.folding.sourceDirected(e);
1190
+ yield ` <edge id="${p.edgeSuids[e]}" source="${p.nodeSuids[src[e]]}" target="${p.nodeSuids[dst[e]]}" cy:directed="${directed ? "1" : "0"}"/>\n`;
1191
+ }
1192
+ yield " </graph>\n </att>\n</graph>\n";
1193
+ }
1194
+
1195
+ /**
1196
+ * The view file: one view node per placed node, x and y (y negated back to screen coordinates)
1197
+ * and z.
1198
+ * @param snapshot - the snapshot
1199
+ * @param p - the plan
1200
+ * @param viewSuid - the view's SUID
1201
+ * @yields the document's text
1202
+ * @returns nothing
1203
+ */
1204
+ function* viewFile(snapshot: GraphSnapshot, p: Plan, viewSuid: number): Generator<string, void, undefined> {
1205
+ yield XML_DECLARATION;
1206
+ yield `<graph id="${viewSuid}" label="${viewSuid}" cy:view="1" cy:networkId="${p.subSuid}" cy:visualStyle="default" cy:rendererId="org.cytoscape.ding" cy:documentVersion="3.0" ${NAMESPACES}>\n`;
1207
+ let view = p.viewNodeBase;
1208
+ for (let i = 0; i < snapshot.nodeCount; i++) {
1209
+ const coords = viewCoords(p, i);
1210
+ if (coords.length > 0) {
1211
+ yield ` <node id="${view++}" cy:nodeId="${p.nodeSuids[i]}">\n <graphics ${coords.join(" ")}/>\n </node>\n`;
1212
+ }
1213
+ }
1214
+ yield "</graph>\n";
1215
+ }
1216
+
1217
+ /**
1218
+ * The graphics attributes of one node's view: x and y (y down, as Cytoscape draws), and z.
1219
+ * @param p - the plan
1220
+ * @param i - the node
1221
+ * @returns the attributes, none when the node has no position
1222
+ */
1223
+ function viewCoords(p: Plan, i: number): string[] {
1224
+ const coords: string[] = [];
1225
+ if (p.position?.isSet(i) === true) {
1226
+ const [x, y, z] = Array.from(p.position.value(i) as ArrayLike<number>);
1227
+ if (Number.isFinite(x) && Number.isFinite(y)) {
1228
+ coords.push(`x="${formatF32(x)}"`, `y="${formatF32(y === 0 ? 0 : -y)}"`);
1229
+ }
1230
+ if (
1231
+ Number.isFinite(x) &&
1232
+ Number.isFinite(y) &&
1233
+ p.positionZ &&
1234
+ z !== undefined &&
1235
+ z !== 0 &&
1236
+ Number.isFinite(z)
1237
+ ) {
1238
+ coords.push(`z="${formatF32(z)}"`);
1239
+ }
1240
+ }
1241
+ if (p.z?.isSet(i) === true) {
1242
+ coords.push(`z="${formatDecimal(p.z.value(i) as number)}"`);
1243
+ }
1244
+ return coords;
1245
+ }
1246
+
1247
+ /**
1248
+ * The text of string parts, UTF-8 encoded.
1249
+ * @param parts - the parts
1250
+ * @returns the bytes
1251
+ */
1252
+ function encode(parts: Iterable<string>): Uint8Array {
1253
+ let text = "";
1254
+ for (const part of parts) {
1255
+ text += part;
1256
+ }
1257
+ return new TextEncoder().encode(text);
1258
+ }
1259
+
1260
+ /**
1261
+ * The archive's entries, each built when the zip writer asks for it.
1262
+ * @param snapshot - the snapshot
1263
+ * @param p - the plan
1264
+ * @yields the entries
1265
+ * @returns nothing
1266
+ */
1267
+ function* entries(snapshot: GraphSnapshot, p: Plan): Generator<ZipWriteEntry, void, undefined> {
1268
+ if (p.fatal !== null) {
1269
+ throw p.fatal;
1270
+ }
1271
+ const name = sessionEscape(p.name);
1272
+ const tables = `${ROOT_FOLDER}tables/${p.subSuid}-${name}/LOCAL_ATTRS-org.cytoscape.model.`;
1273
+ const rowsOf = (suids: readonly number[], skip?: (row: number) => boolean): [number, number][] =>
1274
+ suids.flatMap((suid, row) => (skip?.(row) === true ? [] : [[suid, row] as [number, number]]));
1275
+ const nodeTable = `${p.name} default node`;
1276
+ const edgeTable = `${p.name} default edge`;
1277
+ const networkTable = `${p.name} default network`;
1278
+ yield { name: `${ROOT_FOLDER}3.0.0.version`, data: new Uint8Array(0) };
1279
+ yield { name: `${ROOT_FOLDER}networks/${p.rootSuid}-${name}.xgmml`, data: encode(networkFile(snapshot, p)) };
1280
+ yield {
1281
+ name: `${tables}CyNode-${sessionEscape(nodeTable)}.cytable`,
1282
+ data: encode(cyTable(nodeTable, p.nodeTable, rowsOf(p.nodeSuids))),
1283
+ };
1284
+ yield {
1285
+ name: `${tables}CyEdge-${sessionEscape(edgeTable)}.cytable`,
1286
+ data: encode(
1287
+ cyTable(
1288
+ edgeTable,
1289
+ p.edgeTable,
1290
+ rowsOf(p.edgeSuids, (e) => p.folding.folded(e)),
1291
+ ),
1292
+ ),
1293
+ };
1294
+ yield {
1295
+ name: `${tables}CyNetwork-${sessionEscape(networkTable)}.cytable`,
1296
+ data: encode(cyTable(networkTable, p.networkTable, [[p.subSuid, 0]])),
1297
+ };
1298
+ yield {
1299
+ name: `${ROOT_FOLDER}tables/${p.rootSuid}-${name}/LOCAL_ATTRS-org.cytoscape.model.CyNetwork-${p.rootSuid}+default+network.cytable`,
1300
+ data: encode(cyTable(`${p.rootSuid} default network`, p.rootTable, [[p.rootSuid, 0]])),
1301
+ };
1302
+ if (p.viewSuid !== null) {
1303
+ yield {
1304
+ name: `${ROOT_FOLDER}views/${p.subSuid}-${p.viewSuid}-${name}.xgmml`,
1305
+ data: encode(viewFile(snapshot, p, p.viewSuid)),
1306
+ };
1307
+ }
1308
+ }
1309
+
1310
+ /**
1311
+ * The archive as byte chunks.
1312
+ * @param snapshot - the snapshot
1313
+ * @param options - the options
1314
+ * @yields the bytes
1315
+ * @returns nothing
1316
+ */
1317
+ async function* write(
1318
+ snapshot: GraphSnapshot,
1319
+ options: CysExportOptions | undefined,
1320
+ ): AsyncGenerator<Uint8Array, void, undefined> {
1321
+ const p = plan(snapshot, resolveExportOptions(options));
1322
+ await Promise.resolve();
1323
+ yield* writeZip(entries(snapshot, p));
1324
+ }
1325
+
1326
+ /**
1327
+ * The Cytoscape session exporter.
1328
+ * @category Built-in formats
1329
+ */
1330
+ export const cysExporter: GraphExporter<CysExportOptions> = Object.freeze({
1331
+ format: FORMAT,
1332
+ options: Object.freeze([]),
1333
+ capabilities: CYS_CAPABILITIES,
1334
+
1335
+ /**
1336
+ * Pre-flight: what export() would lose, without writing anything.
1337
+ * @param snapshot - the snapshot
1338
+ * @param options - the common options
1339
+ * @returns the notes, empty when the export is exact
1340
+ */
1341
+ check(snapshot: GraphSnapshot, options?: CysExportOptions): readonly LossNote[] {
1342
+ return Object.freeze([...plan(snapshot, resolveExportOptions(options)).notes]);
1343
+ },
1344
+
1345
+ /**
1346
+ * Write the session as zip bytes (stored entries, one at a time).
1347
+ * @param snapshot - the snapshot
1348
+ * @param options - the common options
1349
+ * @returns the bytes
1350
+ */
1351
+ export(snapshot: GraphSnapshot, options?: CysExportOptions): AsyncIterable<Uint8Array> {
1352
+ return write(snapshot, options);
1353
+ },
1354
+
1355
+ /**
1356
+ * A session is a zip archive, not text: always rejects with E_UNSUPPORTED (use exportGraphToBytes() or exportGraphToBlob()).
1357
+ * @returns never
1358
+ */
1359
+ exportToString(): Promise<string> {
1360
+ return Promise.reject(
1361
+ new GraphFormatError(
1362
+ "E_UNSUPPORTED",
1363
+ "a Cytoscape session is a zip file, not text; use exportGraphToBytes() or exportGraphToBlob()",
1364
+ { reason: "binary" },
1365
+ ),
1366
+ );
1367
+ },
1368
+ });