@graphty/graph-io 0.3.24 → 0.3.26

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-BbJI55dx.js +56 -0
  4. package/dist/chunks/choose-BbJI55dx.js.map +1 -0
  5. package/dist/chunks/constants-BCsFsMj4.js +231 -0
  6. package/dist/chunks/constants-BCsFsMj4.js.map +1 -0
  7. package/dist/chunks/{escape-B-tkk_Cl.js → escape-D0g6NNUy.js} +382 -92
  8. package/dist/chunks/escape-D0g6NNUy.js.map +1 -0
  9. package/dist/chunks/export-BTI4yQgy.js +4333 -0
  10. package/dist/chunks/export-BTI4yQgy.js.map +1 -0
  11. package/dist/chunks/exporter-BMqk5oxW.js +3465 -0
  12. package/dist/chunks/exporter-BMqk5oxW.js.map +1 -0
  13. package/dist/chunks/{importer-eUREhBGj.js → exporter-Cra2GYcN.js} +4969 -3201
  14. package/dist/chunks/exporter-Cra2GYcN.js.map +1 -0
  15. package/dist/chunks/{exporter-BGrsWamJ.js → exporter-Dxh231ZF.js} +38 -229
  16. package/dist/chunks/exporter-Dxh231ZF.js.map +1 -0
  17. package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-tne475KK.js} +22 -20
  18. package/dist/chunks/exporter-tne475KK.js.map +1 -0
  19. package/dist/chunks/importer-90YWhJoF.js +2811 -0
  20. package/dist/chunks/importer-90YWhJoF.js.map +1 -0
  21. package/dist/chunks/{importer-D_e7e7LX.js → importer-Bcl1d4xH.js} +398 -145
  22. package/dist/chunks/importer-Bcl1d4xH.js.map +1 -0
  23. package/dist/chunks/{importer-BWY2FFc5.js → importer-BfIsp1f9.js} +255 -77
  24. package/dist/chunks/importer-BfIsp1f9.js.map +1 -0
  25. package/dist/chunks/{importer-zGLo8Dg_.js → importer-C_TEA1zJ.js} +484 -157
  26. package/dist/chunks/importer-C_TEA1zJ.js.map +1 -0
  27. package/dist/chunks/{importer-DET1tg6s.js → importer-DCjOvO7U.js} +1687 -310
  28. package/dist/chunks/importer-DCjOvO7U.js.map +1 -0
  29. package/dist/chunks/{importer-vi3bSdtw.js → importer-DfVu6j59.js} +509 -76
  30. package/dist/chunks/importer-DfVu6j59.js.map +1 -0
  31. package/dist/chunks/{importer-DIrbbnAf.js → importer-Dku_VAIY.js} +585 -226
  32. package/dist/chunks/importer-Dku_VAIY.js.map +1 -0
  33. package/dist/chunks/json-elements-3tnyt5uZ.js +1225 -0
  34. package/dist/chunks/json-elements-3tnyt5uZ.js.map +1 -0
  35. package/dist/chunks/ontology-export-B9Tcf37D.js +357 -0
  36. package/dist/chunks/ontology-export-B9Tcf37D.js.map +1 -0
  37. package/dist/chunks/{records-dbRkxwaq.js → records-CQXT__mY.js} +298 -114
  38. package/dist/chunks/records-CQXT__mY.js.map +1 -0
  39. package/dist/chunks/{text-Dr0Ifpag.js → text-DmcUs5_Q.js} +29 -11
  40. package/dist/chunks/text-DmcUs5_Q.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 +951 -261
  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 +342 -85
  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 +564 -157
  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 +19 -1
  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 +33 -17
  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 +868 -188
  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 +2 -0
  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 +400 -83
  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 +145 -32
  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 +680 -285
  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 +226 -79
  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 +98 -24
  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 +108 -26
  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 +238 -85
  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 +66 -34
  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 +183 -51
  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 +20 -21
  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 +7 -2
  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 +38 -4
  468. package/src/common/direction.ts +78 -51
  469. package/src/common/escape.ts +33 -17
  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 +1034 -198
  475. package/src/common/line-format.ts +249 -0
  476. package/src/common/lists.ts +6 -1
  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 +477 -88
  487. package/src/common/zip.ts +169 -7
  488. package/src/formats/csv/exporter.ts +201 -46
  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 +888 -300
  496. package/src/formats/cx/index.ts +4 -3
  497. package/src/formats/cx2/exporter.ts +61 -338
  498. package/src/formats/cx2/importer.ts +317 -86
  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 +139 -31
  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 +111 -26
  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 +320 -101
  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 +89 -41
  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 +213 -52
  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 +35 -27
  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 +11 -3
  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,1483 @@
1
+ /**
2
+ * The CX version 1 exporter (design/graph-io/cytoscape-and-obo/design.md section 1.2): writes a
3
+ * snapshot as the NDEx form of a CX document -- one network with at most one view, no
4
+ * `cySubNetworks` -- which NDEx, ndex2 and Cytoscape's CX import all read.
5
+ *
6
+ * Aspects, in the order Cytoscape and NDEx write them: `numberVerification`, the pre-`metaData`
7
+ * (element counts, `idCounter` on nodes, edges, citations and supports), `nodes` (`n` the label,
8
+ * `r` the `represents` column), `edges` (`i` the `interaction` column), `nodeAttributes`,
9
+ * `edgeAttributes` and `networkAttributes` (values as JSON strings, the spec's form), `cyTableColumn`
10
+ * (so a column no element fills is still declared), `cartesianLayout` (y negated back to screen
11
+ * coordinates), `cyGroups` from the parent / parents column, `cyVisualProperties` from the
12
+ * `cx.bypass` columns, the provenance aspects a CX import created, the style rules and unknown
13
+ * aspects a CX import kept, and `status`.
14
+ *
15
+ * What CX cannot hold is announced by check() before anything is written: every edge is directed
16
+ * (W_CX_UNDIRECTED_AS_DIRECTED, W_MUTUAL_EXPANDED), node ids are integers (E_ID_CHARSET unless
17
+ * `sanitizeIds: "mangle"`, which keeps the original in the `graphty:originalId` attribute the
18
+ * importer restores), nested values are written as JSON text (W_CX_JSON_AS_STRING), and the generic
19
+ * notes of checkCapabilities(). NaN and the infinities are written as "NaN" / "Infinity" in double
20
+ * attributes, which read back as they were.
21
+ */
22
+
23
+ import { type Column, GraphFormatError, type GraphSnapshot, type NodeId } from "@graphty/graph-format";
24
+
25
+ import { childrenCsr } from "../../children.js";
26
+ import {
27
+ aspectBlock,
28
+ type CxNoteFn,
29
+ declaredType,
30
+ directionNotes,
31
+ nextNodeId,
32
+ noteList,
33
+ planEdgeIds,
34
+ planNodeIds,
35
+ stringify,
36
+ type WrittenIds,
37
+ } from "../../common/cx-export.js";
38
+ import { type PairFolding, pairFolding } from "../../common/direction.js";
39
+ import { capabilities, checkCapabilities, LOSS } from "../../common/export.js";
40
+ import { isRecord, POSITION_COLUMN } from "../../common/json-elements.js";
41
+ import { type ResolvedExportOptions, resolveExportOptions } from "../../common/options.js";
42
+ import { plural } from "../../common/plural.js";
43
+ import { type ExplicitWeights, explicitWeights } from "../../common/weights.js";
44
+ import { encodeChunks, joinText } from "../../common/writer.js";
45
+ import { type CommonExportOptions, type ExportCapabilities, type GraphExporter, type LossNote } from "../../types.js";
46
+ import { ORIGINAL_ID_ATTRIBUTE } from "../cx2/importer.js";
47
+
48
+ /** The format name. */
49
+ const CX_FORMAT = "cx";
50
+
51
+ /**
52
+ * The options of the CX exporter: the common export options; it has none of its own.
53
+ * @category Built-in formats
54
+ */
55
+ export type CxExportOptions = CommonExportOptions;
56
+
57
+ /**
58
+ * The loss notes the CX exporter's check() returns, by name. A key is the code without its
59
+ * severity and format prefixes.
60
+ * @category Built-in formats
61
+ */
62
+ export const CX_LOSS = Object.freeze({
63
+ /**
64
+ * Every edge is written as directed, so an undirected graph, or the undirected edges of a mixed graph, read back
65
+ * as directed.
66
+ */
67
+ UNDIRECTED_AS_DIRECTED: "W_CX_UNDIRECTED_AS_DIRECTED",
68
+ /** A nested (json) column is written as a string attribute holding its JSON text. */
69
+ JSON_AS_STRING: "W_CX_JSON_AS_STRING",
70
+ /** A mutual pair is written as two directed edges without its mark. */
71
+ MUTUAL_EXPANDED: LOSS.MUTUAL_EXPANDED,
72
+ /**
73
+ * An attribute named `weight` without the weight role reads back as the edge weight, or is not written when the
74
+ * graph has weights of its own.
75
+ */
76
+ WEIGHT_KEY_CLASH: LOSS.WEIGHT_KEY_CLASH,
77
+ /** A start / end / timestamp column: CX has no time. */
78
+ TEMPORAL_DROPPED: LOSS.TEMPORAL,
79
+ /** An attribute with a role the format has no place for is written as a plain attribute; the role is lost. */
80
+ ROLE_DROPPED: LOSS.ROLE,
81
+ /**
82
+ * An attribute without a role is written where the format keeps a role (for example, a `name` column as the label), and
83
+ * reads back with that role.
84
+ */
85
+ ROLE_ASSUMED: LOSS.ROLE_ASSUMED,
86
+ /**
87
+ * An attribute type CX stores as another (a 32-bit float as double, an unsigned integer as long, a byte as
88
+ * integer, a dictionary as string); it reads back with that type.
89
+ */
90
+ DTYPE_UNSUPPORTED: LOSS.DTYPE,
91
+ /** A multi-component column (a second view's `position@2`, a vector) is written as a list of doubles. */
92
+ COMPONENTS_FLATTENED: LOSS.COMPONENTS,
93
+ /** A declared default: CX has none. */
94
+ DEFAULT_DROPPED: LOSS.DEFAULT,
95
+ /** Declared options: CX has no enumerations. */
96
+ OPTIONS_DROPPED: LOSS.OPTIONS,
97
+ /**
98
+ * Node ids the format cannot write, under `sanitizeIds: "error"`; the save fails with E_INVALID_ID. Pass
99
+ * `sanitizeIds: "mangle"` to rewrite them.
100
+ */
101
+ ID_CHARSET: LOSS.ID_CHARSET,
102
+ /** Node ids that are not integers under sanitizeIds "mangle": renumbered, originals kept. */
103
+ ID_MANGLED: LOSS.ID_MANGLED,
104
+ /** Edges without a usable integer id get generated ids. */
105
+ EDGE_IDS_GENERATED: LOSS.EDGE_IDS_GENERATED,
106
+ /**
107
+ * An attribute with a role (for example, the label) is written where the format keeps that role, and reads back under the
108
+ * name the format's importer gives it.
109
+ */
110
+ COLUMN_NAME_CHANGED: LOSS.COLUMN_NAME_CHANGED,
111
+ /** An extension table other than the `cx:citations` / `cx:supports` tables a CX import creates. */
112
+ EXTENSION_TABLE_DROPPED: LOSS.EXTENSION_TABLE,
113
+ /** A position, stacking order or weight that is NaN or infinite: CX spells no such number there. */
114
+ NONFINITE_AS_NULL: LOSS.NONFINITE_AS_NULL,
115
+ /** Visual columns (color, size, shape, thickness roles): CX keeps style as visual properties, not roles. */
116
+ VIZ_DROPPED: LOSS.VIZ,
117
+ });
118
+
119
+ /**
120
+ * What CX version 1 keeps: directed multigraphs with self-loops, integer node ids, required
121
+ * integer edge ids, typed string / double / integer / boolean attributes and lists of them, network
122
+ * attributes, the position role (one view), and containment as `cyGroups`. f32, u32, u8 and dict
123
+ * columns are written as the nearest declared type and read back as it.
124
+ * @category Built-in formats
125
+ */
126
+ export const CX_CAPABILITIES: ExportCapabilities = capabilities({
127
+ mixedDirection: false,
128
+ multiEdges: true,
129
+ selfLoops: true,
130
+ edgeIds: "required",
131
+ idCharset: "integer",
132
+ dtypes: ["string", "f64", "i32", "bool"],
133
+ components: false,
134
+ lists: true,
135
+ json: false,
136
+ defaults: false,
137
+ options: false,
138
+ hierarchy: true,
139
+ temporal: "none",
140
+ graphAttributes: true,
141
+ positions: true,
142
+ viz: false,
143
+ });
144
+
145
+ /** The roles CX has a slot for, and the column names its importer gives them. */
146
+ const SLOT_ROLES: ReadonlySet<string> = new Set(["label", "position", "id"]);
147
+ const ROLE_NAMES: Readonly<Record<string, string>> = Object.freeze({
148
+ label: "name",
149
+ position: POSITION_COLUMN,
150
+ id: "id",
151
+ parent: "parent",
152
+ });
153
+
154
+ /** Roles whose columns are never written as attributes. */
155
+ const SKIPPED_ROLES: ReadonlySet<string> = new Set([
156
+ "directed",
157
+ "pair",
158
+ "mutual",
159
+ "weight",
160
+ "timeText",
161
+ "originalId",
162
+ "parent",
163
+ "parents",
164
+ "start",
165
+ "end",
166
+ "timestamp",
167
+ "timestamps",
168
+ "spells",
169
+ "open",
170
+ "spellsOpen",
171
+ "position",
172
+ "color",
173
+ "size",
174
+ "shape",
175
+ "thickness",
176
+ "id",
177
+ ]);
178
+
179
+ /** The origin namespace of the per-element visual property columns of a CX import. */
180
+ const BYPASS_NAMESPACE = "cx.bypass";
181
+
182
+ /** The extension tables a CX import creates from the citations and supports aspects. */
183
+ const PROVENANCE_TABLES: Readonly<Record<string, string>> = Object.freeze({
184
+ "cx:citations": "citations",
185
+ "cx:supports": "supports",
186
+ });
187
+
188
+ /**
189
+ * Kept aspects never written back: they describe the collection, views and SUIDs of the file the
190
+ * snapshot came from, or are written by the exporter itself.
191
+ */
192
+ const NEVER_WRITTEN: ReadonlySet<string> = new Set([
193
+ "nodes",
194
+ "edges",
195
+ "nodeAttributes",
196
+ "edgeAttributes",
197
+ "networkAttributes",
198
+ "cartesianLayout",
199
+ "cyGroups",
200
+ "cyVisualProperties",
201
+ "cyTableColumn",
202
+ "cySubNetworks",
203
+ "cyNetworkRelations",
204
+ "cyViews",
205
+ "CX Element ID",
206
+ "citations",
207
+ "supports",
208
+ "nodeCitations",
209
+ "edgeCitations",
210
+ "nodeSupports",
211
+ "edgeSupports",
212
+ "functionTerms",
213
+ "reifiedEdges",
214
+ "numberVerification",
215
+ "metaData",
216
+ "status",
217
+ "groups",
218
+ "subnetwork",
219
+ ]);
220
+
221
+ /** The weight attribute name (the CX importer's weightFrom default). */
222
+ const WEIGHT_ATTRIBUTE = "weight";
223
+
224
+ /** The dtypes a plain `weight` column may have to be written as the weight. */
225
+ const NUMERIC_DTYPES: ReadonlySet<string> = new Set(["f32", "f64", "i32", "u32", "u8"]);
226
+
227
+ /** One attribute column as it will be written. */
228
+ interface AttributePlan {
229
+ readonly column: Column;
230
+ /** The attribute name written in n. */
231
+ readonly name: string;
232
+ /** The declared type text. */
233
+ readonly d: string;
234
+ }
235
+
236
+ /** A core field (n, r, i) and the column it comes from. */
237
+ interface CorePlan {
238
+ readonly key: "n" | "r" | "i";
239
+ readonly column: Column;
240
+ }
241
+
242
+ /** The edge weights as written: explicit weights or a plain numeric `weight` column. */
243
+ interface WeightPlan {
244
+ /**
245
+ * The weight of an edge, or undefined when none is written.
246
+ * @param e - the edge index
247
+ * @returns the value
248
+ */
249
+ value(e: number): number | undefined;
250
+ }
251
+
252
+ /** Everything export() and check() need, computed once. */
253
+ interface Plan {
254
+ readonly notes: LossNote[];
255
+ readonly fatal: GraphFormatError | null;
256
+ readonly ids: WrittenIds | null;
257
+ readonly folding: PairFolding;
258
+ readonly edgeIds: readonly number[];
259
+ readonly weight: WeightPlan | null;
260
+ readonly nodeCore: readonly CorePlan[];
261
+ readonly edgeCore: readonly CorePlan[];
262
+ readonly nodeAttrs: readonly AttributePlan[];
263
+ readonly edgeAttrs: readonly AttributePlan[];
264
+ readonly graphAttrs: readonly AttributePlan[];
265
+ readonly position: Column | null;
266
+ readonly positionZ: boolean;
267
+ readonly z: Column | null;
268
+ readonly bypass: {
269
+ readonly node: readonly Column[];
270
+ readonly edge: readonly Column[];
271
+ readonly graph: readonly Column[];
272
+ };
273
+ readonly collapsed: Column | null;
274
+ readonly links: readonly LinkPlan[];
275
+ readonly functionTerm: Column | null;
276
+ readonly reifiedEdge: Column | null;
277
+ }
278
+
279
+ /** A citations / supports list column and the aspect it is written to. */
280
+ interface LinkPlan {
281
+ readonly aspect: string;
282
+ readonly key: "citations" | "supports";
283
+ readonly domain: "node" | "edge";
284
+ readonly column: Column;
285
+ }
286
+
287
+ /**
288
+ * Whether a column was created by the CX importer in an origin namespace.
289
+ * @param column - the column
290
+ * @param namespace - the namespace
291
+ * @returns true when it was
292
+ */
293
+ function fromCx(column: Column, namespace: string): boolean {
294
+ return column.meta.origin?.format === CX_FORMAT && column.meta.origin.namespace === namespace;
295
+ }
296
+
297
+ /**
298
+ * Whether a column is a bypass column of a CX import.
299
+ * @param column - the column
300
+ * @returns true for the cx.bypass namespace
301
+ */
302
+ function isBypass(column: Column): boolean {
303
+ return column.meta.origin?.namespace === BYPASS_NAMESPACE;
304
+ }
305
+
306
+ /**
307
+ * Whether a column is the z (stacking order) column.
308
+ * @param column - the column
309
+ * @returns true for the column marked by a Cytoscape-family importer
310
+ */
311
+ function isZ(column: Column): boolean {
312
+ return column.meta.extra.cytoscape === "z";
313
+ }
314
+
315
+ /**
316
+ * Whether a column is a provenance list column (citations / supports) of a CX import.
317
+ * @param column - the column
318
+ * @returns true for one
319
+ */
320
+ function isLink(column: Column): boolean {
321
+ return (
322
+ fromCx(column, "provenance") &&
323
+ column.meta.dtype === "list" &&
324
+ (column.meta.name === "citations" || column.meta.name === "supports")
325
+ );
326
+ }
327
+
328
+ /**
329
+ * Whether a column holds a text the core fields (n, r, i) cannot hold as itself.
330
+ * @param column - the column
331
+ * @returns true for every dtype but string (dict and json have their own notes)
332
+ */
333
+ function coreNeedsText(column: Column): boolean {
334
+ const { dtype } = column.meta;
335
+ return dtype !== "string" && dtype !== "dict" && dtype !== "json";
336
+ }
337
+
338
+ /**
339
+ * The notes of checkCapabilities(), adapted to CX: json columns are written as JSON text, the id
340
+ * notes are counted by planNodeIds(), the provenance tables and columns a CX import made are
341
+ * written back.
342
+ * @param snapshot - the snapshot
343
+ * @param common - the resolved common options
344
+ * @param several - whether the parents column gives some node several parents
345
+ * @param own - the functionTerm and reifiedEdge columns written as their own aspects
346
+ * @param notes - the notes so far
347
+ * @param note - records a note
348
+ * @returns the error export() throws for mixed direction, or null
349
+ */
350
+ function genericNotes(
351
+ snapshot: GraphSnapshot,
352
+ common: ResolvedExportOptions,
353
+ several: boolean,
354
+ own: readonly (Column | null)[],
355
+ notes: LossNote[],
356
+ note: CxNoteFn,
357
+ ): GraphFormatError | null {
358
+ let fatal: GraphFormatError | null = null;
359
+ const edgeLabel = snapshot.edges.byRole("label")?.meta.name;
360
+ const ownNames = new Set(own.filter((c): c is Column => c !== null).map((c) => c.meta.name));
361
+ for (const gen of checkCapabilities(snapshot, CX_CAPABILITIES, common, {
362
+ roles: SLOT_ROLES,
363
+ // a parents column reads back as parents only when some node has several parents
364
+ roleNames: several ? { ...ROLE_NAMES, parents: "parents" } : ROLE_NAMES,
365
+ })) {
366
+ if (writtenOtherwise(gen, ownNames)) {
367
+ continue;
368
+ }
369
+ if (gen.code === LOSS.JSON) {
370
+ const which = gen.column === null ? "a column" : `column "${gen.column}"`;
371
+ note(
372
+ CX_LOSS.JSON_AS_STRING,
373
+ `${which} holds nested values; written as a string attribute holding their JSON text`,
374
+ gen.column,
375
+ gen.count,
376
+ );
377
+ continue;
378
+ }
379
+ if (isEdgeLabelRename(gen, edgeLabel)) {
380
+ // only nodes have a label slot (n); an edge label is a plain attribute
381
+ note(
382
+ LOSS.ROLE,
383
+ `edge column "${edgeLabel ?? ""}" (label) is written as a plain attribute; CX edges have no label slot and the role is lost`,
384
+ gen.column,
385
+ gen.count,
386
+ );
387
+ continue;
388
+ }
389
+ if (gen.code === LOSS.MIXED_DIRECTION_ERROR && fatal === null) {
390
+ fatal = new GraphFormatError("E_DIRECTED", gen.message, { reason: "mixed direction" });
391
+ }
392
+ notes.push(gen);
393
+ }
394
+ if (edgeLabel === "name") {
395
+ note(
396
+ LOSS.ROLE,
397
+ `edge column "name" (label) is written as a plain attribute; CX edges have no label slot and the role is lost`,
398
+ "name",
399
+ null,
400
+ );
401
+ }
402
+ return fatal;
403
+ }
404
+
405
+ /**
406
+ * Whether a generic note is about something CX writes another way: the functionTerm and
407
+ * reifiedEdge columns (their own aspects), ids (counted by planNodeIds(), since CX also keeps
408
+ * integer ids beyond 2^53, which the generic rule refuses) and the provenance tables.
409
+ * @param gen - the generic note
410
+ * @param ownNames - the names of the columns written as their own aspects
411
+ * @returns true when the note does not apply
412
+ */
413
+ function writtenOtherwise(gen: LossNote, ownNames: ReadonlySet<string>): boolean {
414
+ if (gen.column !== null && ownNames.has(gen.column) && (gen.code === LOSS.JSON || gen.code === LOSS.DTYPE)) {
415
+ return true;
416
+ }
417
+ if (gen.code === LOSS.ID_CHARSET || gen.code === LOSS.ID_MANGLED) {
418
+ return true;
419
+ }
420
+ return gen.code === LOSS.EXTENSION_TABLE && gen.column !== null && gen.column in PROVENANCE_TABLES;
421
+ }
422
+
423
+ /**
424
+ * Whether a generic note is the rename of the edge label column.
425
+ * @param gen - the generic note
426
+ * @param edgeLabel - the edge label column's name, if any
427
+ * @returns true when it is
428
+ */
429
+ function isEdgeLabelRename(gen: LossNote, edgeLabel: string | undefined): boolean {
430
+ return gen.code === LOSS.COLUMN_NAME_CHANGED && gen.column === edgeLabel && gen.message.startsWith("edge column");
431
+ }
432
+
433
+ /**
434
+ * The node positions to write: the position column, the z column (when it fits the layout), whether
435
+ * the position's third coordinate is written as z, and the count of non-finite values left out.
436
+ * @param snapshot - the snapshot
437
+ * @param note - records a note
438
+ * @returns the position plan
439
+ */
440
+ function planPositions(
441
+ snapshot: GraphSnapshot,
442
+ note: CxNoteFn,
443
+ ): { position: Column | null; z: Column | null; positionZ: boolean; nonfinite: number } {
444
+ const position = snapshot.nodes.byRole("position");
445
+ const zColumn = [...snapshot.nodes].find(isZ) ?? null;
446
+ const z = zColumn !== null && zFitsLayout(zColumn, position) ? zColumn : null;
447
+ if (position === null) {
448
+ return { position, z, positionZ: false, nonfinite: 0 };
449
+ }
450
+ let nonfinite = nonFiniteCells(position, (v) => [v[0], v[1]]);
451
+ const depth = nonFiniteCells(position, (v) => (v[2] === 0 ? [] : [Number.NaN]));
452
+ const positionZ = zColumn === null && (position.meta.extra.sourceDims === 3 || depth > 0);
453
+ if (positionZ) {
454
+ nonfinite += nonFiniteCells(position, (v) => (Number.isFinite(v[0]) && Number.isFinite(v[1]) ? [v[2]] : []));
455
+ note(
456
+ LOSS.COMPONENTS,
457
+ `node column "${position.meta.name}" (position) has a third coordinate; CX writes it as the layout's z, which reads back in the z column (a stacking order), not in the position`,
458
+ position.meta.name,
459
+ depth,
460
+ );
461
+ }
462
+ return { position, z, positionZ, nonfinite };
463
+ }
464
+
465
+ /**
466
+ * Note the graph columns that read back as the graph's name or description.
467
+ * @param snapshot - the snapshot
468
+ * @param note - records a note
469
+ */
470
+ function graphColumnNotes(snapshot: GraphSnapshot, note: CxNoteFn): void {
471
+ for (const c of snapshot.graph) {
472
+ const named = c.meta.name === "name" || c.meta.name === "description";
473
+ if (!isBypass(c) && named && (c.dtype === "string" || c.dtype === "dict")) {
474
+ note(
475
+ LOSS.ROLE_ASSUMED,
476
+ `graph column "${c.meta.name}" is written as the network attribute ${c.meta.name}, which reads back as the graph's ${c.meta.name}, not as a column`,
477
+ c.meta.name,
478
+ null,
479
+ );
480
+ }
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Plan an export: the notes, the fatal condition and what is written.
486
+ * @param snapshot - the snapshot
487
+ * @param common - the resolved common options
488
+ * @returns the plan
489
+ */
490
+ function plan(snapshot: GraphSnapshot, common: ResolvedExportOptions): Plan {
491
+ const { notes, note } = noteList();
492
+ const functionTerm =
493
+ [...snapshot.nodes].find((c) => fromCx(c, "provenance") && c.meta.name === "functionTerm") ?? null;
494
+ const reifiedEdge = [...snapshot.nodes].find((c) => fromCx(c, "reifiedEdges") && c.dtype === "u32") ?? null;
495
+ const children = childrenCsr(snapshot);
496
+ const several = children.column?.meta.role === "parents" && severalParents(children.column);
497
+ let fatal = genericNotes(snapshot, common, several, [functionTerm, reifiedEdge], notes, note);
498
+
499
+ const folding = pairFolding(snapshot);
500
+ directionNotes(snapshot, folding, common, CX_LOSS.UNDIRECTED_AS_DIRECTED, note);
501
+ const planned = planNodeIds(snapshot, common, "CX", ORIGINAL_ID_ATTRIBUTE, note);
502
+ const ids = planned instanceof GraphFormatError ? null : planned;
503
+ if (planned instanceof GraphFormatError) {
504
+ fatal ??= planned;
505
+ }
506
+ const edgeIds = planEdgeIds(snapshot, note);
507
+ if (snapshot.nodes.has(ORIGINAL_ID_ATTRIBUTE)) {
508
+ note(
509
+ LOSS.ROLE_ASSUMED,
510
+ `node column "${ORIGINAL_ID_ATTRIBUTE}" is the importer's record of mangled ids: it reads back as the node ids unless restoreMangledIds is false`,
511
+ ORIGINAL_ID_ATTRIBUTE,
512
+ null,
513
+ );
514
+ }
515
+
516
+ // nodes: n, r, the z column, the groups' collapsed flags, provenance
517
+ const positions = planPositions(snapshot, note);
518
+ const { position, z, positionZ } = positions;
519
+ let { nonfinite } = positions;
520
+ const collapsed = [...snapshot.nodes].find((c) => fromCx(c, "cyGroups") && c.dtype === "bool") ?? null;
521
+ const nodeCore = corePlans(snapshot, note);
522
+ const edgeCore: CorePlan[] = [];
523
+ const interaction = snapshot.edges.get("interaction");
524
+ if (interaction !== null && (interaction.meta.role === null || !SKIPPED_ROLES.has(interaction.meta.role))) {
525
+ edgeCore.push({ key: "i", column: interaction });
526
+ coreTextNote(interaction, "edge", note);
527
+ }
528
+
529
+ const coreColumns = new Set<Column>([...nodeCore, ...edgeCore].map((c) => c.column));
530
+ const special = (c: Column): boolean =>
531
+ coreColumns.has(c) ||
532
+ isBypass(c) ||
533
+ isLink(c) ||
534
+ c === z ||
535
+ c === collapsed ||
536
+ c === functionTerm ||
537
+ c === reifiedEdge ||
538
+ (c.meta.role !== null && SKIPPED_ROLES.has(c.meta.role));
539
+
540
+ const label = snapshot.nodes.byRole("label");
541
+ const renamedName = label !== null && label.meta.name !== "name" ? snapshot.nodes.get("name") : null;
542
+ if (renamedName !== null) {
543
+ note(
544
+ LOSS.COLUMN_NAME_CHANGED,
545
+ `node column "name" is written as the attribute "name#2": the label column "${label?.meta.name ?? ""}" is written as n, which reads back as "name"`,
546
+ "name",
547
+ renamedName.length - renamedName.nullCount,
548
+ );
549
+ }
550
+ const nodeAttrs = attributePlans(snapshot.nodes, special, (c) => (c === renamedName ? "name#2" : c.meta.name));
551
+
552
+ const weight = planWeight(snapshot, folding, note);
553
+ const plainWeight = snapshot.edges.get(WEIGHT_ATTRIBUTE);
554
+ const edgeAttrs = attributePlans(
555
+ snapshot.edges,
556
+ (c) => special(c) || (c === plainWeight && c.meta.role === null),
557
+ (c) => c.meta.name,
558
+ );
559
+ nonfinite += weight.nonfinite;
560
+
561
+ graphColumnNotes(snapshot, note);
562
+ const graphAttrs = attributePlans(snapshot.graph, isBypass, (c) => c.meta.name);
563
+
564
+ if (children.column !== null && children.column.meta.role === "parents" && !several) {
565
+ note(
566
+ LOSS.COLUMN_NAME_CHANGED,
567
+ `node column "${children.column.meta.name}" (parents) holds at most one parent per node; it reads back as the single-parent column "parent"`,
568
+ children.column.meta.name,
569
+ children.column.length - children.column.nullCount,
570
+ );
571
+ }
572
+
573
+ if (nonfinite > 0) {
574
+ note(
575
+ LOSS.NONFINITE_AS_NULL,
576
+ `${nonfinite} NaN or infinite position${plural(nonfinite)}, stacking orders or weights cannot be written in CX; they read back unset`,
577
+ null,
578
+ nonfinite,
579
+ );
580
+ }
581
+ return {
582
+ notes,
583
+ fatal,
584
+ ids,
585
+ folding,
586
+ edgeIds,
587
+ weight: weight.plan,
588
+ nodeCore,
589
+ edgeCore,
590
+ nodeAttrs,
591
+ edgeAttrs,
592
+ graphAttrs,
593
+ position,
594
+ positionZ,
595
+ z,
596
+ bypass: {
597
+ node: [...snapshot.nodes].filter(isBypass),
598
+ edge: [...snapshot.edges].filter(isBypass),
599
+ graph: [...snapshot.graph].filter(isBypass),
600
+ },
601
+ collapsed,
602
+ links: linkPlans(snapshot),
603
+ functionTerm,
604
+ reifiedEdge,
605
+ };
606
+ }
607
+
608
+ /**
609
+ * The node core fields: n from the label column (else a plain `name` column, which then reads back
610
+ * as the label), r from a `represents` column.
611
+ * @param snapshot - the snapshot
612
+ * @param note - records a note
613
+ * @returns the plans
614
+ */
615
+ function corePlans(snapshot: GraphSnapshot, note: CxNoteFn): CorePlan[] {
616
+ const out: CorePlan[] = [];
617
+ const label = snapshot.nodes.byRole("label");
618
+ const plainName = snapshot.nodes.get("name");
619
+ const n = label ?? (plainName !== null && plainName.meta.role === null ? plainName : null);
620
+ if (n !== null) {
621
+ out.push({ key: "n", column: n });
622
+ coreTextNote(n, "node", note);
623
+ if (n !== label) {
624
+ note(
625
+ LOSS.ROLE_ASSUMED,
626
+ `node column "name" is written as n and reads back as the label`,
627
+ "name",
628
+ n.length - n.nullCount,
629
+ );
630
+ }
631
+ }
632
+ const represents = snapshot.nodes.get("represents");
633
+ if (
634
+ represents !== null &&
635
+ represents !== n &&
636
+ (represents.meta.role === null || !SKIPPED_ROLES.has(represents.meta.role))
637
+ ) {
638
+ out.push({ key: "r", column: represents });
639
+ coreTextNote(represents, "node", note);
640
+ }
641
+ return out;
642
+ }
643
+
644
+ /**
645
+ * The note of a core field column that is not text: n, r and i are strings in CX.
646
+ * @param column - the column
647
+ * @param domain - node or edge
648
+ * @param note - records a note
649
+ */
650
+ function coreTextNote(column: Column, domain: string, note: CxNoteFn): void {
651
+ if (coreNeedsText(column)) {
652
+ note(
653
+ LOSS.DTYPE,
654
+ `${domain} column "${column.meta.name}" is ${column.dtype}; CX writes it as text and it reads back as string`,
655
+ column.meta.name,
656
+ column.length - column.nullCount,
657
+ );
658
+ }
659
+ }
660
+
661
+ /**
662
+ * The attribute plans of one table.
663
+ * @param table - the table
664
+ * @param skip - columns not written as attributes
665
+ * @param rename - the written name of a column
666
+ * @returns the plans
667
+ */
668
+ function attributePlans(
669
+ table: Iterable<Column>,
670
+ skip: (column: Column) => boolean,
671
+ rename: (column: Column) => string,
672
+ ): AttributePlan[] {
673
+ return [...table]
674
+ .filter((c) => !skip(c))
675
+ .map((column) => ({ column, name: rename(column), d: declaredType(column) }));
676
+ }
677
+
678
+ /**
679
+ * The weights to write: the explicit weights, else a plain numeric `weight` column (which then
680
+ * reads back as THE weight); a plain `weight` column that is not written is noted.
681
+ * @param snapshot - the snapshot
682
+ * @param folding - the pair folding
683
+ * @param note - records a note
684
+ * @returns the plan, or null for no weight attribute, and the count of non-finite weights skipped
685
+ */
686
+ function planWeight(
687
+ snapshot: GraphSnapshot,
688
+ folding: PairFolding,
689
+ note: CxNoteFn,
690
+ ): { plan: WeightPlan | null; nonfinite: number } {
691
+ const weights: ExplicitWeights = explicitWeights(snapshot);
692
+ const plain = snapshot.edges.get(WEIGHT_ATTRIBUTE);
693
+ const clash = plain !== null && plain.meta.role === null;
694
+ let plan: WeightPlan | null = null;
695
+ if (weights.weighted) {
696
+ plan = { value: (e) => (weights.isExplicit(e) ? weights.value(e) : undefined) };
697
+ if (clash) {
698
+ note(
699
+ LOSS.WEIGHT_KEY_CLASH,
700
+ `edge column "${WEIGHT_ATTRIBUTE}" is not written: the explicit weights are written under that name`,
701
+ WEIGHT_ATTRIBUTE,
702
+ null,
703
+ );
704
+ }
705
+ } else if (clash) {
706
+ const numeric = NUMERIC_DTYPES.has(plain.dtype);
707
+ note(
708
+ LOSS.WEIGHT_KEY_CLASH,
709
+ numeric
710
+ ? `edge column "${WEIGHT_ATTRIBUTE}" is written as the weight attribute and reads back as the edge weight`
711
+ : `edge column "${WEIGHT_ATTRIBUTE}" is ${plain.dtype}, and CX reads that attribute as the edge weight; it is not written`,
712
+ WEIGHT_ATTRIBUTE,
713
+ null,
714
+ );
715
+ if (numeric) {
716
+ plan = { value: (e) => (plain.isSet(e) ? (plain.value(e) as number) : undefined) };
717
+ }
718
+ }
719
+ return { plan, nonfinite: plan === null ? 0 : nanWeights(snapshot.edgeCount, plan, folding) };
720
+ }
721
+
722
+ /**
723
+ * The written weights that are NaN, which is not a weight on re-import (the infinities are).
724
+ * @param edgeCount - the number of edges
725
+ * @param plan - the weight plan
726
+ * @param folding - the pair folding
727
+ * @returns the count
728
+ */
729
+ function nanWeights(edgeCount: number, plan: WeightPlan, folding: PairFolding): number {
730
+ let nan = 0;
731
+ for (let e = 0; e < edgeCount; e++) {
732
+ const w = plan.value(e);
733
+ if (!folding.folded(e) && w !== undefined && Number.isNaN(w)) {
734
+ nan++;
735
+ }
736
+ }
737
+ return nan;
738
+ }
739
+
740
+ /**
741
+ * Whether the z column can go into cartesianLayout: every set z is finite and its node has a
742
+ * finite position (a layout entry needs x and y). Otherwise it is written as an ordinary attribute.
743
+ * @param z - the z column
744
+ * @param position - the position column
745
+ * @returns true when every z fits a layout entry
746
+ */
747
+ function zFitsLayout(z: Column, position: Column | null): boolean {
748
+ for (let i = 0; i < z.length; i++) {
749
+ if (!z.isSet(i)) {
750
+ continue;
751
+ }
752
+ const value = z.value(i);
753
+ const point = position?.isSet(i) === true ? (position.value(i) as ArrayLike<number>) : null;
754
+ if (
755
+ typeof value !== "number" ||
756
+ !Number.isFinite(value) ||
757
+ point === null ||
758
+ !Number.isFinite(point[0]) ||
759
+ !Number.isFinite(point[1])
760
+ ) {
761
+ return false;
762
+ }
763
+ }
764
+ return true;
765
+ }
766
+
767
+ /**
768
+ * Count the set cells of a multi-component column with a non-finite component among those picked.
769
+ * @param column - the column
770
+ * @param pick - the components that are written
771
+ * @returns the count
772
+ */
773
+ function nonFiniteCells(column: Column, pick: (value: ArrayLike<number>) => number[]): number {
774
+ let count = 0;
775
+ for (let i = 0; i < column.length; i++) {
776
+ if (column.isSet(i) && pick(column.value(i) as ArrayLike<number>).some((v) => !Number.isFinite(v))) {
777
+ count++;
778
+ }
779
+ }
780
+ return count;
781
+ }
782
+
783
+ /**
784
+ * Whether a parents column gives some node more than one parent.
785
+ * @param column - the parents list column
786
+ * @returns true when one does
787
+ */
788
+ function severalParents(column: Column): boolean {
789
+ for (let i = 0; i < column.length; i++) {
790
+ if (column.isSet(i) && (column.value(i) as ArrayLike<unknown>).length > 1) {
791
+ return true;
792
+ }
793
+ }
794
+ return false;
795
+ }
796
+
797
+ /**
798
+ * The citations / supports list columns of a CX import and the aspects they go to.
799
+ * @param snapshot - the snapshot
800
+ * @returns the plans
801
+ */
802
+ function linkPlans(snapshot: GraphSnapshot): LinkPlan[] {
803
+ const out: LinkPlan[] = [];
804
+ for (const [domain, table] of [
805
+ ["node", snapshot.nodes],
806
+ ["edge", snapshot.edges],
807
+ ] as const) {
808
+ for (const column of table) {
809
+ if (isLink(column)) {
810
+ const key = column.meta.name as "citations" | "supports";
811
+ out.push({ aspect: `${domain}${key === "citations" ? "Citations" : "Supports"}`, key, domain, column });
812
+ }
813
+ }
814
+ }
815
+ return out;
816
+ }
817
+
818
+ // ============================================================ writing
819
+
820
+ /**
821
+ * The text of a scalar the way Cytoscape writes CX values: numbers by their shortest decimal
822
+ * ("-0", "NaN", "Infinity" included), booleans as "true" / "false".
823
+ * @param value - the value
824
+ * @returns the text
825
+ */
826
+ function scalarText(value: unknown): string {
827
+ if (typeof value === "number") {
828
+ return Object.is(value, -0) ? "-0" : String(value);
829
+ }
830
+ if (typeof value === "string") {
831
+ return value;
832
+ }
833
+ return typeof value === "boolean" ? String(value) : JSON.stringify(value);
834
+ }
835
+
836
+ /**
837
+ * The v of one cell: a JSON string, or an array of strings for a list type.
838
+ * @param column - the column
839
+ * @param d - its declared type
840
+ * @param row - the row
841
+ * @returns the value, or undefined for an unset cell
842
+ */
843
+ function cellValue(column: Column, d: string, row: number): unknown {
844
+ if (!column.isSet(row)) {
845
+ return undefined;
846
+ }
847
+ const value = column.value(row);
848
+ if (column.meta.dtype === "json") {
849
+ return JSON.stringify(value);
850
+ }
851
+ if (column.meta.dtype === "list" || column.meta.components > 1) {
852
+ const items = Array.from(value as ArrayLike<unknown>);
853
+ return d === "string" ? JSON.stringify(items) : items.map(scalarText);
854
+ }
855
+ return scalarText(value);
856
+ }
857
+
858
+ /**
859
+ * The text a core field (n, r, i) holds for one row.
860
+ * @param column - the column
861
+ * @param row - the row
862
+ * @returns the text, or undefined for an unset cell
863
+ */
864
+ function coreText(column: Column, row: number): string | undefined {
865
+ if (!column.isSet(row)) {
866
+ return undefined;
867
+ }
868
+ const value = column.value(row);
869
+ if (column.meta.dtype === "json" || column.meta.dtype === "list" || column.meta.components > 1) {
870
+ return JSON.stringify(column.meta.dtype === "json" ? value : Array.from(value as ArrayLike<unknown>));
871
+ }
872
+ return scalarText(value);
873
+ }
874
+
875
+ /** One aspect to write: its name, its elements (produced twice: once to count them) and its idCounter. */
876
+ interface Aspect {
877
+ readonly name: string;
878
+ readonly elements: () => Iterable<unknown>;
879
+ readonly idCounter?: NodeId | undefined;
880
+ }
881
+
882
+ /**
883
+ * The written edges: every logical edge but the folded halves of pairs.
884
+ * @param snapshot - the snapshot
885
+ * @param folding - the pair folding
886
+ * @returns the edge indexes
887
+ */
888
+ function writtenEdges(snapshot: GraphSnapshot, folding: PairFolding): number[] {
889
+ const out: number[] = [];
890
+ for (let e = 0; e < snapshot.edgeCount; e++) {
891
+ if (!folding.folded(e)) {
892
+ out.push(e);
893
+ }
894
+ }
895
+ return out;
896
+ }
897
+
898
+ /**
899
+ * Build every aspect of the document, in the order Cytoscape and NDEx write them.
900
+ * @param snapshot - the snapshot
901
+ * @param p - the plan
902
+ * @param ids - the written node ids
903
+ * @returns the aspects (those without elements included; the writer skips them)
904
+ */
905
+ function aspects(snapshot: GraphSnapshot, p: Plan, ids: WrittenIds): Aspect[] {
906
+ const { src, dst } = snapshot.edgeList();
907
+ const edges = writtenEdges(snapshot, p.folding);
908
+ const nodeRange = (): number[] => Array.from({ length: snapshot.nodeCount }, (_, i) => i);
909
+ const nodeCore = (key: "n" | "r"): Column | null => p.nodeCore.find((c) => c.key === key)?.column ?? null;
910
+ const n = nodeCore("n");
911
+ const r = nodeCore("r");
912
+ const i = p.edgeCore[0]?.column ?? null;
913
+ const kept =
914
+ snapshot.meta.sourceFormat === CX_FORMAT && isRecord(snapshot.meta.extra.cx) ? snapshot.meta.extra.cx : {};
915
+
916
+ const out: Aspect[] = [
917
+ {
918
+ name: "nodes",
919
+ idCounter: nextNodeId(ids, snapshot.nodeCount),
920
+ *elements() {
921
+ for (let row = 0; row < snapshot.nodeCount; row++) {
922
+ const element: Record<string, unknown> = { "@id": ids.idAt(row) };
923
+ const name = n === null ? undefined : coreText(n, row);
924
+ const represents = r === null ? undefined : coreText(r, row);
925
+ if (name !== undefined) {
926
+ element.n = name;
927
+ }
928
+ if (represents !== undefined) {
929
+ element.r = represents;
930
+ }
931
+ yield element;
932
+ }
933
+ },
934
+ },
935
+ {
936
+ name: "edges",
937
+ idCounter: edges.reduce((max, e) => Math.max(max, p.edgeIds[e] + 1), 0),
938
+ *elements() {
939
+ for (const e of edges) {
940
+ const element: Record<string, unknown> = {
941
+ "@id": p.edgeIds[e],
942
+ s: ids.idAt(src[e]),
943
+ t: ids.idAt(dst[e]),
944
+ };
945
+ const interaction = i === null ? undefined : coreText(i, e);
946
+ if (interaction !== undefined) {
947
+ element.i = interaction;
948
+ }
949
+ yield element;
950
+ }
951
+ },
952
+ },
953
+ {
954
+ name: "nodeAttributes",
955
+ *elements() {
956
+ for (const a of p.nodeAttrs) {
957
+ for (let row = 0; row < snapshot.nodeCount; row++) {
958
+ const v = cellValue(a.column, a.d, row);
959
+ if (v !== undefined) {
960
+ yield { po: ids.idAt(row), n: a.name, v, d: a.d };
961
+ }
962
+ }
963
+ }
964
+ for (let row = 0; row < snapshot.nodeCount; row++) {
965
+ if (ids.isChanged(row)) {
966
+ yield {
967
+ po: ids.idAt(row),
968
+ n: ORIGINAL_ID_ATTRIBUTE,
969
+ v: String(ids.originalAt(row)),
970
+ d: "string",
971
+ };
972
+ }
973
+ }
974
+ },
975
+ },
976
+ {
977
+ name: "edgeAttributes",
978
+ *elements() {
979
+ for (const a of p.edgeAttrs) {
980
+ for (const e of edges) {
981
+ const v = cellValue(a.column, a.d, e);
982
+ if (v !== undefined) {
983
+ yield { po: p.edgeIds[e], n: a.name, v, d: a.d };
984
+ }
985
+ }
986
+ }
987
+ if (p.weight !== null) {
988
+ for (const e of edges) {
989
+ const w = p.weight.value(e);
990
+ if (w !== undefined && !Number.isNaN(w)) {
991
+ yield { po: p.edgeIds[e], n: WEIGHT_ATTRIBUTE, v: scalarText(w), d: "double" };
992
+ }
993
+ }
994
+ }
995
+ },
996
+ },
997
+ {
998
+ name: "networkAttributes",
999
+ *elements() {
1000
+ for (const a of p.graphAttrs) {
1001
+ const v = cellValue(a.column, a.d, 0);
1002
+ if (v !== undefined) {
1003
+ yield { n: a.name, v, d: a.d };
1004
+ }
1005
+ }
1006
+ const { meta } = snapshot;
1007
+ for (const [key, value] of [
1008
+ ["name", meta.name],
1009
+ ["description", meta.description],
1010
+ ] as const) {
1011
+ if (value !== null && !p.graphAttrs.some((a) => a.name === key)) {
1012
+ yield { n: key, v: value, d: "string" };
1013
+ }
1014
+ }
1015
+ },
1016
+ },
1017
+ { name: "cyTableColumn", elements: () => tableColumns(p, ids) },
1018
+ { name: "cartesianLayout", elements: () => layoutElements(p, ids, nodeRange()) },
1019
+ { name: "cyGroups", elements: () => groupElements(snapshot, p, ids, edges) },
1020
+ { name: "cyVisualProperties", elements: () => visualProperties(p, ids, nodeRange(), edges, kept) },
1021
+ ];
1022
+ out.push(...provenanceAspects(snapshot, p, ids, edges));
1023
+ for (const [name, value] of Object.entries(kept)) {
1024
+ if (!NEVER_WRITTEN.has(name) && Array.isArray(value)) {
1025
+ out.push({ name, elements: () => keptElements(value, kept.subnetwork, STYLE_ASPECTS.has(name)) });
1026
+ }
1027
+ }
1028
+ return out;
1029
+ }
1030
+
1031
+ /**
1032
+ * The cyTableColumn entries: every written column with its type, so a column no element fills is
1033
+ * still declared on re-import.
1034
+ * @param p - the plan
1035
+ * @param ids - the written ids
1036
+ * @yields the entries
1037
+ */
1038
+ function* tableColumns(p: Plan, ids: WrittenIds): Generator<unknown, void, undefined> {
1039
+ for (const { key, column } of [...p.nodeCore, ...p.edgeCore]) {
1040
+ const n = { n: "name", r: "represents", i: "interaction" }[key];
1041
+ if (n !== undefined && column !== null) {
1042
+ yield { applies_to: key === "i" ? "edge_table" : "node_table", n, d: "string" };
1043
+ }
1044
+ }
1045
+ for (const a of p.nodeAttrs) {
1046
+ yield { applies_to: "node_table", n: a.name, d: a.d };
1047
+ }
1048
+ if (ids.changed > 0) {
1049
+ yield { applies_to: "node_table", n: ORIGINAL_ID_ATTRIBUTE, d: "string" };
1050
+ }
1051
+ for (const a of p.edgeAttrs) {
1052
+ yield { applies_to: "edge_table", n: a.name, d: a.d };
1053
+ }
1054
+ if (p.weight !== null) {
1055
+ yield { applies_to: "edge_table", n: WEIGHT_ATTRIBUTE, d: "double" };
1056
+ }
1057
+ for (const a of p.graphAttrs) {
1058
+ yield { applies_to: "network_table", n: a.name, d: a.d };
1059
+ }
1060
+ }
1061
+
1062
+ /** Kept aspects whose view references are rewritten to the one view of the written file. */
1063
+ const STYLE_ASPECTS: ReadonlySet<string> = new Set(["tableVisualProperties", "cyTableVisualProperties"]);
1064
+
1065
+ /**
1066
+ * The elements of a kept aspect: those of the snapshot's own subnetwork (or unscoped), with the
1067
+ * scope dropped; style elements get their view references rewritten to view 0.
1068
+ * @param elements - the kept elements
1069
+ * @param subnetwork - the subnetwork the snapshot was read from, if any
1070
+ * @param style - whether the aspect is a style aspect
1071
+ * @yields the elements to write
1072
+ */
1073
+ function* keptElements(
1074
+ elements: readonly unknown[],
1075
+ subnetwork: unknown,
1076
+ style: boolean,
1077
+ ): Generator<unknown, void, undefined> {
1078
+ for (const element of elements) {
1079
+ if (!isRecord(element)) {
1080
+ yield element;
1081
+ continue;
1082
+ }
1083
+ if (element.s !== undefined && element.s !== null && element.s !== subnetwork) {
1084
+ continue;
1085
+ }
1086
+ const { s: _scope, ...rest } = element;
1087
+ yield style ? rewriteView(rest) : rest;
1088
+ }
1089
+ }
1090
+
1091
+ /**
1092
+ * A style element with its view and subnetwork references rewritten to 0, the written network.
1093
+ * @param element - the element
1094
+ * @returns the rewritten element
1095
+ */
1096
+ function rewriteView(element: Record<string, unknown>): Record<string, unknown> {
1097
+ const out = { ...element };
1098
+ if ("view" in out) {
1099
+ out.view = 0;
1100
+ }
1101
+ if (typeof out.applies_to === "number") {
1102
+ out.applies_to = 0;
1103
+ }
1104
+ return out;
1105
+ }
1106
+
1107
+ /**
1108
+ * The cartesianLayout entries: x / y (y negated back to screen coordinates) and z.
1109
+ * @param p - the plan
1110
+ * @param ids - the written ids
1111
+ * @param rows - every node row
1112
+ * @yields the entries
1113
+ */
1114
+ function* layoutElements(p: Plan, ids: WrittenIds, rows: readonly number[]): Generator<unknown, void, undefined> {
1115
+ const { position } = p;
1116
+ if (position === null) {
1117
+ return;
1118
+ }
1119
+ for (const row of rows) {
1120
+ if (!position.isSet(row)) {
1121
+ continue;
1122
+ }
1123
+ const point = position.value(row) as ArrayLike<number>;
1124
+ if (!Number.isFinite(point[0]) || !Number.isFinite(point[1])) {
1125
+ continue;
1126
+ }
1127
+ const entry: Record<string, unknown> = { node: ids.idAt(row), x: point[0], y: point[1] === 0 ? 0 : -point[1] };
1128
+ if (p.z?.isSet(row) === true) {
1129
+ entry.z = p.z.value(row);
1130
+ } else if (p.positionZ && Number.isFinite(point[2])) {
1131
+ entry.z = point[2];
1132
+ }
1133
+ yield entry;
1134
+ }
1135
+ }
1136
+
1137
+ /**
1138
+ * The cyGroups entries: one per node that has members (or a collapsed flag), its members, and the
1139
+ * edges with both ends (internal) or one end (external) among them.
1140
+ * @param snapshot - the snapshot
1141
+ * @param p - the plan
1142
+ * @param ids - the written ids
1143
+ * @param edges - the written edges
1144
+ * @yields the entries
1145
+ */
1146
+ function* groupElements(
1147
+ snapshot: GraphSnapshot,
1148
+ p: Plan,
1149
+ ids: WrittenIds,
1150
+ edges: readonly number[],
1151
+ ): Generator<unknown, void, undefined> {
1152
+ const children = childrenCsr(snapshot);
1153
+ const groupsOf = new Map<number, number[]>();
1154
+ const groups: number[] = [];
1155
+ for (let g = 0; g < snapshot.nodeCount; g++) {
1156
+ const members = children.childrenOf(g);
1157
+ if (members.length > 0 || p.collapsed?.isSet(g) === true) {
1158
+ groups.push(g);
1159
+ }
1160
+ for (const c of members) {
1161
+ groupsOf.set(c, [...(groupsOf.get(c) ?? []), g]);
1162
+ }
1163
+ }
1164
+ if (groups.length === 0) {
1165
+ return;
1166
+ }
1167
+ const { internal, external } = groupEdges(snapshot, p, groupsOf, edges);
1168
+ const label = p.nodeCore.find((c) => c.key === "n")?.column ?? null;
1169
+ for (const g of groups) {
1170
+ const entry: Record<string, unknown> = { "@id": ids.idAt(g) };
1171
+ const name = label === null ? undefined : coreText(label, g);
1172
+ if (name !== undefined) {
1173
+ entry.n = name;
1174
+ }
1175
+ entry.nodes = Array.from(children.childrenOf(g), (c) => ids.idAt(c));
1176
+ entry.internal_edges = internal.get(g) ?? [];
1177
+ entry.external_edges = external.get(g) ?? [];
1178
+ if (p.collapsed?.isSet(g) === true) {
1179
+ entry.collapsed = p.collapsed.value(g);
1180
+ }
1181
+ yield entry;
1182
+ }
1183
+ }
1184
+
1185
+ /**
1186
+ * The written edges of each group: internal (both ends among its members) and external (one end).
1187
+ * @param snapshot - the snapshot
1188
+ * @param p - the plan
1189
+ * @param groupsOf - the groups each member node is in
1190
+ * @param edges - the written edges
1191
+ * @returns the edge ids by group
1192
+ */
1193
+ function groupEdges(
1194
+ snapshot: GraphSnapshot,
1195
+ p: Plan,
1196
+ groupsOf: ReadonlyMap<number, number[]>,
1197
+ edges: readonly number[],
1198
+ ): { internal: Map<number, number[]>; external: Map<number, number[]> } {
1199
+ const internal = new Map<number, number[]>();
1200
+ const external = new Map<number, number[]>();
1201
+ const add = (table: Map<number, number[]>, g: number, id: number): void => {
1202
+ table.set(g, [...(table.get(g) ?? []), id]);
1203
+ };
1204
+ const { src, dst } = snapshot.edgeList();
1205
+ for (const e of edges) {
1206
+ const ofSource = groupsOf.get(src[e]) ?? [];
1207
+ const ofTarget = groupsOf.get(dst[e]) ?? [];
1208
+ for (const g of ofSource) {
1209
+ add(ofTarget.includes(g) ? internal : external, g, p.edgeIds[e]);
1210
+ }
1211
+ for (const g of ofTarget.filter((t) => !ofSource.includes(t))) {
1212
+ add(external, g, p.edgeIds[e]);
1213
+ }
1214
+ }
1215
+ return { internal, external };
1216
+ }
1217
+
1218
+ /**
1219
+ * The visual property values of one row: property -> value text.
1220
+ * @param columns - the bypass columns
1221
+ * @param row - the row
1222
+ * @returns the properties, or null when none is set
1223
+ */
1224
+ function bypassValues(columns: readonly Column[], row: number): Record<string, string> | null {
1225
+ let out: Record<string, string> | null = null;
1226
+ for (const column of columns) {
1227
+ if (column.isSet(row)) {
1228
+ // the visual property's own name: a column renamed for a clash with an attribute keeps it in origin.id
1229
+ const property = column.meta.origin?.id;
1230
+ out ??= {};
1231
+ out[typeof property === "string" && property.length > 0 ? property : column.meta.name] = scalarText(
1232
+ column.value(row),
1233
+ );
1234
+ }
1235
+ }
1236
+ return out;
1237
+ }
1238
+
1239
+ /**
1240
+ * The cyVisualProperties entries: the network values, the style rules a CX import kept (its own
1241
+ * view's, rewritten to view 0) and the per-element values.
1242
+ * @param p - the plan
1243
+ * @param ids - the written ids
1244
+ * @param rows - every node row
1245
+ * @param edges - the written edges
1246
+ * @param kept - meta.extra.cx of a CX import, else empty
1247
+ * @yields the entries
1248
+ */
1249
+ function* visualProperties(
1250
+ p: Plan,
1251
+ ids: WrittenIds,
1252
+ rows: readonly number[],
1253
+ edges: readonly number[],
1254
+ kept: Record<string, unknown>,
1255
+ ): Generator<unknown, void, undefined> {
1256
+ const network = bypassValues(p.bypass.graph, 0);
1257
+ if (network !== null) {
1258
+ yield { properties_of: "network", properties: network };
1259
+ }
1260
+ const view = ownView(kept);
1261
+ for (const entry of Array.isArray(kept.cyVisualProperties) ? (kept.cyVisualProperties as unknown[]) : []) {
1262
+ if (
1263
+ isRecord(entry) &&
1264
+ !["nodes", "edges", "network"].includes(String(entry.properties_of)) &&
1265
+ (view === null || entry.view === undefined || entry.view === null || entry.view === view)
1266
+ ) {
1267
+ yield rewriteView(entry);
1268
+ }
1269
+ }
1270
+ for (const row of rows) {
1271
+ const properties = bypassValues(p.bypass.node, row);
1272
+ if (properties !== null) {
1273
+ yield { properties_of: "nodes", applies_to: ids.idAt(row), properties };
1274
+ }
1275
+ }
1276
+ for (const e of edges) {
1277
+ const properties = bypassValues(p.bypass.edge, e);
1278
+ if (properties !== null) {
1279
+ yield { properties_of: "edges", applies_to: p.edgeIds[e], properties };
1280
+ }
1281
+ }
1282
+ }
1283
+
1284
+ /**
1285
+ * The view of the subnetwork a CX import read (the one its style rules belong to).
1286
+ * @param kept - meta.extra.cx
1287
+ * @returns the view id, or null when the file named none
1288
+ */
1289
+ function ownView(kept: Record<string, unknown>): unknown {
1290
+ const sub = kept.subnetwork;
1291
+ for (const r of Array.isArray(kept.cyNetworkRelations) ? (kept.cyNetworkRelations as unknown[]) : []) {
1292
+ if (isRecord(r) && r.r === "view" && (sub === undefined || r.p === sub)) {
1293
+ return r.c;
1294
+ }
1295
+ }
1296
+ for (const v of Array.isArray(kept.cyViews) ? (kept.cyViews as unknown[]) : []) {
1297
+ if (isRecord(v) && (sub === undefined || v.s === sub)) {
1298
+ return v["@id"];
1299
+ }
1300
+ }
1301
+ return null;
1302
+ }
1303
+
1304
+ /**
1305
+ * The provenance aspects a CX import turned into extension tables and columns, written back:
1306
+ * citations, supports, their node / edge links, functionTerms and reifiedEdges.
1307
+ * @param snapshot - the snapshot
1308
+ * @param p - the plan
1309
+ * @param ids - the written ids
1310
+ * @param edges - the written edges
1311
+ * @returns the aspects
1312
+ */
1313
+ function provenanceAspects(snapshot: GraphSnapshot, p: Plan, ids: WrittenIds, edges: readonly number[]): Aspect[] {
1314
+ const out: Aspect[] = [];
1315
+ for (const [table, aspect] of Object.entries(PROVENANCE_TABLES)) {
1316
+ const ext = snapshot.extensions.get(table);
1317
+ if (ext === undefined) {
1318
+ continue;
1319
+ }
1320
+ const idColumn = ext.get("id");
1321
+ const fields = [...ext].filter((c) => c !== idColumn);
1322
+ let max = -1;
1323
+ for (let row = 0; row < ext.rowCount; row++) {
1324
+ if (idColumn?.isSet(row) === true) {
1325
+ max = Math.max(max, Number(idColumn.value(row)));
1326
+ }
1327
+ }
1328
+ out.push({
1329
+ name: aspect,
1330
+ idCounter: max + 1,
1331
+ *elements() {
1332
+ for (let row = 0; row < ext.rowCount; row++) {
1333
+ const element: Record<string, unknown> = {};
1334
+ if (idColumn?.isSet(row) === true) {
1335
+ element["@id"] = idColumn.value(row);
1336
+ }
1337
+ for (const field of fields) {
1338
+ if (field.isSet(row)) {
1339
+ element[field.meta.name] = field.value(row);
1340
+ }
1341
+ }
1342
+ yield element;
1343
+ }
1344
+ },
1345
+ });
1346
+ }
1347
+ for (const link of p.links) {
1348
+ out.push({
1349
+ name: link.aspect,
1350
+ *elements() {
1351
+ const rows = link.domain === "node" ? Array.from({ length: snapshot.nodeCount }, (_, i) => i) : edges;
1352
+ for (const row of rows) {
1353
+ if (link.column.isSet(row)) {
1354
+ const po = link.domain === "node" ? ids.idAt(row) : p.edgeIds[row];
1355
+ yield { po: [po], [link.key]: Array.from(link.column.value(row) as ArrayLike<unknown>) };
1356
+ }
1357
+ }
1358
+ },
1359
+ });
1360
+ }
1361
+ const terms = p.functionTerm;
1362
+ if (terms !== null) {
1363
+ out.push({
1364
+ name: "functionTerms",
1365
+ *elements() {
1366
+ for (let row = 0; row < snapshot.nodeCount; row++) {
1367
+ const term = terms.isSet(row) ? terms.value(row) : null;
1368
+ if (isRecord(term)) {
1369
+ yield { po: ids.idAt(row), ...term };
1370
+ }
1371
+ }
1372
+ },
1373
+ });
1374
+ }
1375
+ const reified = p.reifiedEdge;
1376
+ if (reified !== null) {
1377
+ out.push({
1378
+ name: "reifiedEdges",
1379
+ *elements() {
1380
+ for (let row = 0; row < snapshot.nodeCount; row++) {
1381
+ const edge = reified.isSet(row) ? (reified.value(row) as number) : -1;
1382
+ if (edge >= 0 && edge < snapshot.edgeCount) {
1383
+ yield { node: ids.idAt(row), edge: p.edgeIds[edge] };
1384
+ }
1385
+ }
1386
+ },
1387
+ });
1388
+ }
1389
+ return out;
1390
+ }
1391
+
1392
+ /**
1393
+ * Write the document as text parts.
1394
+ * @param snapshot - the snapshot
1395
+ * @param p - the plan
1396
+ * @yields the document's text
1397
+ * @returns nothing
1398
+ */
1399
+ function* write(snapshot: GraphSnapshot, p: Plan): Generator<string, void, undefined> {
1400
+ if (p.fatal !== null) {
1401
+ throw p.fatal;
1402
+ }
1403
+ const { ids } = p;
1404
+ if (ids === null) {
1405
+ throw new GraphFormatError("E_INVALID_ID", "node ids cannot be written as CX integers", { reason: "charset" });
1406
+ }
1407
+ const all = aspects(snapshot, p, ids);
1408
+ const written: { aspect: Aspect; count: number }[] = [];
1409
+ for (const aspect of all) {
1410
+ let count = 0;
1411
+ for (const _ of aspect.elements()) {
1412
+ count++;
1413
+ }
1414
+ if (count > 0 || aspect.name === "nodes") {
1415
+ written.push({ aspect, count });
1416
+ }
1417
+ }
1418
+ const metaData = written.map(({ aspect, count }) => ({
1419
+ name: aspect.name,
1420
+ version: "1.0",
1421
+ ...(aspect.idCounter === undefined ? {} : { idCounter: aspect.idCounter }),
1422
+ elementCount: count,
1423
+ consistencyGroup: 1,
1424
+ }));
1425
+ yield '[{"numberVerification":[{"longNumber":281474976710655}]},\n';
1426
+ yield `{"metaData":${stringify(metaData)}},\n`;
1427
+ for (const { aspect } of written) {
1428
+ yield* aspectBlock(aspect.name, mapIterable(aspect.elements(), stringify));
1429
+ }
1430
+ yield '{"status":[{"error":"","success":true}]}]\n';
1431
+ }
1432
+
1433
+ /**
1434
+ * Map an iterable lazily.
1435
+ * @param items - the items
1436
+ * @param fn - the mapping
1437
+ * @yields the mapped items
1438
+ */
1439
+ function* mapIterable<T, U>(items: Iterable<T>, fn: (item: T) => U): Generator<U, void, undefined> {
1440
+ for (const item of items) {
1441
+ yield fn(item);
1442
+ }
1443
+ }
1444
+
1445
+ /**
1446
+ * The CX version 1 exporter plugin.
1447
+ * @category Built-in formats
1448
+ */
1449
+ export const cxExporter: GraphExporter<CxExportOptions> = Object.freeze({
1450
+ format: CX_FORMAT,
1451
+ options: Object.freeze([]),
1452
+ capabilities: CX_CAPABILITIES,
1453
+
1454
+ /**
1455
+ * Pre-flight: what export() would lose, without writing anything.
1456
+ * @param snapshot - the snapshot
1457
+ * @param options - the common options
1458
+ * @returns the notes, empty when the export is exact
1459
+ */
1460
+ check(snapshot: GraphSnapshot, options?: CxExportOptions): readonly LossNote[] {
1461
+ return Object.freeze([...plan(snapshot, resolveExportOptions(options)).notes]);
1462
+ },
1463
+
1464
+ /**
1465
+ * Write the document as UTF-8 chunks.
1466
+ * @param snapshot - the snapshot
1467
+ * @param options - the common options
1468
+ * @returns the chunks
1469
+ */
1470
+ export(snapshot: GraphSnapshot, options?: CxExportOptions): AsyncIterable<Uint8Array> {
1471
+ return encodeChunks(write(snapshot, plan(snapshot, resolveExportOptions(options))));
1472
+ },
1473
+
1474
+ /**
1475
+ * Write the document as one string.
1476
+ * @param snapshot - the snapshot
1477
+ * @param options - the common options
1478
+ * @returns the document
1479
+ */
1480
+ exportToString(snapshot: GraphSnapshot, options?: CxExportOptions): Promise<string> {
1481
+ return joinText(write(snapshot, plan(snapshot, resolveExportOptions(options))));
1482
+ },
1483
+ });