@graphty/graph-io 0.3.25 → 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
@@ -9,30 +9,37 @@
9
9
  * groups, nested-network pointers); the subnetwork's CyCSV tables give its columns (LOCAL_ATTRS as
10
10
  * they are, the SHARED_ATTRS columns `cytables.xml` joins in, HIDDEN and app tables as hidden
11
11
  * columns under their namespace); its first view gives positions (y flipped) and the `graphics`
12
- * column, further views `position@<n>` columns. 2.x: one full XGMML file per network, with
12
+ * column, further views `position@<n>` columns. A table's `name` is the label (what Cytoscape
13
+ * shows), in a label column named `name`, and ids an exporter mangled are restored from the node
14
+ * table's `graphty:originalId` (restoreMangledIds). 2.x: one full XGMML file per network, with
13
15
  * selection and hidden state from `cysession.xml`. Everything is resolved by the XGMML emitter, so
14
16
  * both readers share every column rule. Styles are not read (issue #706): W_STYLES_NOT_IMPORTED.
15
17
  */
16
18
  import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
17
19
  import { declareResolved } from "../../common/attributes.js";
18
20
  import { IdCoercer } from "../../common/ids.js";
19
- import { decodeEntryName, readBytes, throwIfAborted } from "../../common/input.js";
20
- import { chooseGraph, reportSinkOptions, reportUnusedOptions, resolveImportOptions, } from "../../common/options.js";
21
+ import { decodeEntryName, throwIfAborted } from "../../common/input.js";
22
+ import { chooseGraph, graphChosen, reportSinkOptions, reportUnusedOptions, resolveImportOptions, } from "../../common/options.js";
23
+ import { agree, elementCount, plural } from "../../common/plural.js";
21
24
  import { ImportReportBuilder } from "../../common/report.js";
22
25
  import { startsLikeZip } from "../../common/zip.js";
23
26
  import { ImportError, } from "../../types.js";
27
+ import { ORIGINAL_ID_ATTRIBUTE } from "../graphml/constants.js";
24
28
  import { XGMML_ISSUE, XGMML_ORIGIN_NAMESPACE } from "../xgmml/constants.js";
25
29
  import { isCyTrue, } from "../xgmml/document.js";
26
- import { dialectOf, membersOf, XgmmlEmitter, } from "../xgmml/emit.js";
30
+ import { dialectOf, membersOf, parseCoordinate, XgmmlEmitter, } from "../xgmml/emit.js";
27
31
  import { parseXgmml, resolveSettings } from "../xgmml/importer.js";
28
32
  import { CYS_ISSUE, CYTOSCAPE_NAMESPACE, DEFAULT_MAX_UNCOMPRESSED, EXTENSIONS, FORMAT, HIDDEN_COLUMN, META_KEY, MIME_TYPES, SELECTED_COLUMN, VIEW_POSITION_PREFIX, } from "./constants.js";
29
- import { elementsNamed, listed, openSession, readXmlTree, urlDecode, } from "./session.js";
33
+ import { elementsNamed, entryLabel, listed, openSession, parseXmlTree, readXmlTree, urlDecode, } from "./session.js";
30
34
  import { cellAtt, readCyTable, virtualColumnsOf } from "./tables.js";
31
- /** The per-format defaults: SUIDs as written, edges directed unless they say otherwise, no weight. */
35
+ /**
36
+ * The per-format defaults: SUIDs as written, edges directed unless they say otherwise, the edge
37
+ * `weight` column as the weight (as for XGMML, the format of the session's networks).
38
+ */
32
39
  const FORMAT_DEFAULTS = {
33
40
  ids: "keep",
34
41
  defaultDirected: true,
35
- weightFrom: null,
42
+ weightFrom: "weight",
36
43
  addMissingNodes: false,
37
44
  };
38
45
  /** The common options the session importer reads. */
@@ -46,6 +53,7 @@ const USED_OPTIONS = new Set([
46
53
  "weightFrom",
47
54
  "weightDtype",
48
55
  "long",
56
+ "restoreMangledIds",
49
57
  "errorLimit",
50
58
  "signal",
51
59
  "onProgress",
@@ -101,24 +109,65 @@ async function prepare(input, sink, options) {
101
109
  * @returns the document and its dialect
102
110
  */
103
111
  async function parseEntry(session, entry, report, inner) {
104
- const bytes = await session.read(entry);
105
- const scratch = new ImportReportBuilder(FORMAT, Number.MAX_SAFE_INTEGER);
112
+ return (await parseOptionalEntry(session, entry, report, inner, false));
113
+ }
114
+ /**
115
+ * Read an XGMML entry, its issues and counts relayed into the import's report (the entry name
116
+ * in each message), parsing within what is left of the import's error limit. A fatal issue fails
117
+ * the import, unless the entry is optional (a view): then it is recorded as an error and the
118
+ * entry is skipped (null).
119
+ * @param session - the session
120
+ * @param entry - the entry
121
+ * @param report - the import's report
122
+ * @param inner - the options entries are read with
123
+ * @param optional - whether the import can go on without the entry
124
+ * @returns the document and its dialect, or null for an optional entry that cannot be read
125
+ */
126
+ async function parseOptionalEntry(session, entry, report, inner, optional) {
127
+ const scratch = new ImportReportBuilder(FORMAT, Math.max(0, report.errorLimit - report.errorCount));
106
128
  try {
129
+ // a damaged entry (bad CRC, truncated deflate) fails the scratch report like a parse error
130
+ const bytes = await session.read(entry, scratch);
107
131
  const doc = await parseXgmml(bytes, scratch, inner, undefined);
108
132
  const dialect = dialectOf(doc, scratch);
109
133
  relay(scratch.issues, report, entry.name);
134
+ addCounts(scratch, report);
110
135
  return { doc, dialect };
111
136
  }
112
137
  catch (err) {
113
138
  if (!(err instanceof ImportError)) {
114
139
  throw err;
115
140
  }
141
+ addCounts(scratch, report);
116
142
  const { issues } = scratch;
143
+ if (scratch.truncated) {
144
+ // the entry reached the import's error limit: relaying its errors aborts the import
145
+ relay(issues, report, entry.name);
146
+ }
117
147
  const fatal = issues.at(-1);
118
148
  relay(issues.slice(0, -1), report, entry.name);
119
- return report.fail(fatal?.code ?? CYS_ISSUE.CORRUPT, `${entry.name}: ${fatal?.message ?? err.message}`, {
120
- line: fatal?.line ?? null,
121
- });
149
+ const message = `${entryLabel(entry.name)}: ${fatal?.message ?? err.message}`;
150
+ const where = { line: fatal?.line ?? null };
151
+ if (fatal?.code === CYS_ISSUE.TOO_LARGE) {
152
+ // the byte budget is a limit of the whole import, not damage to one entry
153
+ report.error("unsupported", fatal.code, message);
154
+ throw report.abort(message, { code: fatal.code });
155
+ }
156
+ if (optional) {
157
+ report.error("parse-error", fatal?.code ?? CYS_ISSUE.CORRUPT, `${message}; the entry is not read`, where);
158
+ return null;
159
+ }
160
+ return report.fail(fatal?.code ?? CYS_ISSUE.CORRUPT, message, where);
161
+ }
162
+ }
163
+ /**
164
+ * Add an entry's element counts (the nodes skipped while parsing it) to the import's.
165
+ * @param from - the entry's report
166
+ * @param to - the import's report
167
+ */
168
+ function addCounts(from, to) {
169
+ for (const key of Object.keys(from.counts)) {
170
+ to.counts[key] += from.counts[key];
122
171
  }
123
172
  }
124
173
  /**
@@ -131,10 +180,10 @@ function relay(issues, report, entry) {
131
180
  for (const issue of issues) {
132
181
  const where = { line: issue.line, element: issue.element };
133
182
  if (issue.severity === "error") {
134
- report.error(issue.category, issue.code, `${entry}: ${issue.message}`, where);
183
+ report.error(issue.category, issue.code, `${entryLabel(entry)}: ${issue.message}`, where);
135
184
  }
136
185
  else {
137
- report.warning(issue.category, issue.code, `${entry}: ${issue.message}`, where);
186
+ report.warning(issue.category, issue.code, `${entryLabel(entry)}: ${issue.message}`, where);
138
187
  }
139
188
  }
140
189
  }
@@ -152,13 +201,22 @@ async function networks3(session, report, inner, docs) {
152
201
  const choices = [];
153
202
  for (const network of layout.networks) {
154
203
  const parsed = await parseEntry(session, network, report, inner);
204
+ if (parsed.dialect.view) {
205
+ report.fail(XGMML_ISSUE.VIEW_DOCUMENT, `${network.name}: this is a session view file (cy:view), not a network file; it holds no topology`, { line: parsed.doc.root.line });
206
+ }
155
207
  docs.set(network.name, parsed);
156
208
  for (const graph of registeredGraphs(parsed)) {
209
+ if (graph.id === null) {
210
+ const label = graph.label === null ? "" : ` ("${graph.label}")`;
211
+ report.error("missing-value", XGMML_ISSUE.MISSING_ID, `${network.name}: a registered subnetwork${label} has no id; its tables and views cannot be found and it is skipped`, { line: graph.line });
212
+ continue;
213
+ }
157
214
  const members = membersOf(parsed.doc, graph);
158
215
  choices.push({
159
216
  era: "3",
160
217
  network,
161
- graphId: graph.id ?? "",
218
+ graph,
219
+ graphId: graph.id,
162
220
  name: graph.label ?? graph.id,
163
221
  nodes: new Set(members.nodes).size,
164
222
  edges: members.edges.length,
@@ -171,12 +229,17 @@ async function networks3(session, report, inner, docs) {
171
229
  !layout.networks.some((n) => n.suid === t.network) &&
172
230
  !isGroupTable(t, session, docs));
173
231
  if (strayViews.length > 0 || strayTables.length > 0) {
174
- report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `${strayViews.length + strayTables.length} view(s) or table(s) belong to a network the session does not hold and were not read: ${listed([...strayViews, ...strayTables.map((t) => t.name)])}`);
232
+ report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `${strayViews.length + strayTables.length} view${plural(strayViews.length + strayTables.length)} or table${plural(strayViews.length + strayTables.length)} ${agree(strayViews.length + strayTables.length, "belongs", "belong")} to a network the session does not hold and were not read: ${listed([...strayViews, ...strayTables.map((t) => t.name)])}`);
175
233
  }
176
234
  if (layout.networkList === null) {
177
235
  return choices;
178
236
  }
179
- const tree = await readXmlTree(await session.read(layout.networkList), layout.networkList.name, report, inner);
237
+ const tree = await parseXmlTree(await session.read(layout.networkList, report), layout.networkList.name, report, inner);
238
+ if (typeof tree === "string") {
239
+ // the list only orders the networks: without it they keep the order of their entries
240
+ report.warning("unsupported", CYS_ISSUE.ENTRY_SKIPPED, `${layout.networkList.name}: ${tree}; it is not read and the networks are listed in entry order`);
241
+ return choices;
242
+ }
180
243
  const order = new Map();
181
244
  for (const node of elementsNamed(tree, "network")) {
182
245
  const id = node.attrs.get("id");
@@ -213,7 +276,9 @@ function isGroupTable(table, session, docs) {
213
276
  * @returns the graphs
214
277
  */
215
278
  function registeredGraphs(parsed) {
216
- return parsed.doc.root.subgraphs.filter((g) => isCyTrue(g.registered));
279
+ const subnetworks = parsed.doc.root.subgraphs.filter((g) => isCyTrue(g.registered));
280
+ // a registered root with no subnetworks (another tool's network file) is the network itself
281
+ return subnetworks.length === 0 && isCyTrue(parsed.doc.root.registered) ? [parsed.doc.root] : subnetworks;
217
282
  }
218
283
  /**
219
284
  * Import one registered subnetwork of a 3.x session.
@@ -223,97 +288,72 @@ function registeredGraphs(parsed) {
223
288
  * @param parsed - its network file, parsed for this import (its records are filled in)
224
289
  */
225
290
  async function import3(prepared, choice, sink, parsed) {
226
- const { report, inner, session } = prepared;
227
- const { layout } = session;
228
- const { doc, dialect } = parsed;
229
- const graph = doc.root.subgraphs.find((g) => g.id === choice.graphId);
230
- const members = membersOf(doc, graph);
291
+ const { graph } = choice;
292
+ const members = membersOf(parsed.doc, graph);
231
293
  const nodes = byId(members.nodes);
232
294
  const edges = byId(members.edges);
233
- const cache = new Map();
234
- const tableOf = (path) => {
235
- let pending = cache.get(path);
236
- if (pending === undefined) {
237
- const entry = layout.tables.find((t) => t.tablePath === path);
238
- pending =
239
- entry === undefined
240
- ? Promise.resolve(null)
241
- : session.read(entry).then((bytes) => readCyTable(bytes, path, entry.name, report, inner));
242
- cache.set(path, pending);
243
- }
244
- return pending;
245
- };
246
- const virtuals = layout.cytables === null ? [] : await readVirtuals(session, layout.cytables, report, inner);
247
- // a row of an element the file declares outside this network (a collapsed group's member, a
248
- // meta-edge) is Cytoscape's bookkeeping, not a stale row
249
- const declared = new Set([
250
- ...doc.nodes.flatMap((n) => (n.id === null ? [] : [n.id])),
251
- ...doc.edges.flatMap((e) => (e.id === null ? [] : [e.id])),
252
- ]);
253
- let unmatched = 0;
254
- for (const entry of layout.tables.filter((t) => t.network === choice.graphId && t.element !== null)) {
255
- throwIfAborted(inner.signal);
256
- const table = await tableOf(entry.tablePath);
257
- if (table === null) {
258
- continue;
259
- }
260
- const local = entry.namespace === "LOCAL_ATTRS";
261
- const shared = entry.namespace === "SHARED_ATTRS";
262
- const namespace = local ? null : entry.namespace;
263
- const hidden = !local && !shared;
264
- const target = (key) => {
265
- if (entry.element === "network") {
266
- return key === choice.graphId ? graph.atts : undefined;
267
- }
268
- return (entry.element === "node" ? nodes : edges).get(key)?.atts;
269
- };
270
- for (const [key, cells] of table.rows) {
271
- const atts = target(key);
272
- if (atts === undefined) {
273
- if (!declared.has(key)) {
274
- unmatched++;
275
- }
276
- continue;
277
- }
278
- const line = table.lines.get(key) ?? 0;
279
- for (let i = 1; i < table.columns.length && i < cells.length; i++) {
280
- const att = cellAtt(table.columns[i], cells[i], line, namespace, hidden);
281
- if (att !== null) {
282
- atts.push(att);
283
- }
284
- }
285
- }
286
- for (const virtual of await virtualColumnsOf(table, virtuals, tableOf, report)) {
287
- // a shared column named like a local one is renamed; the local one keeps the name
288
- const owned = table.columns.some((c) => c.name === virtual.column.name);
289
- let ns = namespace;
290
- if (local) {
291
- ns = owned ? "SHARED_ATTRS" : null;
292
- }
293
- for (const [key, text] of virtual.values) {
294
- const att = cellAtt(virtual.column, text, table.lines.get(key) ?? 0, ns, hidden);
295
- if (att !== null) {
296
- target(key)?.push(att);
297
- }
298
- }
299
- }
295
+ // the records are shared by every network of the file: what this import adds is undone after
296
+ const restore = saveRecords(graph, nodes, edges);
297
+ try {
298
+ await fill3(prepared, choice, sink, parsed, nodes, edges);
300
299
  }
301
- if (unmatched > 0) {
302
- report.warning("parse-error", CYS_ISSUE.TABLE_ROW, `${unmatched} table row(s) of network ${choice.graphId} name no node, edge or network of it (stale rows); they are not read`);
300
+ finally {
301
+ restore();
303
302
  }
304
- const views = layout.views.filter((v) => v.network === choice.graphId);
305
- let visualStyle = null;
306
- const extraViews = [];
307
- for (const [i, view] of views.entries()) {
308
- const viewDoc = (await parseEntry(session, view, report, inner)).doc;
309
- if (i === 0) {
310
- visualStyle = viewDoc.root.attrs.get("cy:visualStyle") ?? null;
311
- applyView(viewDoc, graph, nodes, edges, report, view.name);
303
+ }
304
+ /**
305
+ * The state of the records an import fills in (atts, coordinates, graphics), and a function that
306
+ * puts it back.
307
+ * @param graph - the subnetwork
308
+ * @param nodes - its node records
309
+ * @param edges - its edge records
310
+ * @returns the restore function
311
+ */
312
+ function saveRecords(graph, nodes, edges) {
313
+ const saved = [...nodes.values()].map((n) => ({
314
+ n,
315
+ atts: n.atts.length,
316
+ x: n.x,
317
+ y: n.y,
318
+ z: n.z,
319
+ graphics: n.graphics,
320
+ }));
321
+ const savedEdges = [...edges.values()].map((e) => ({ e, atts: e.atts.length, graphics: e.graphics }));
322
+ const graphAtts = graph.atts.length;
323
+ const graphGraphics = graph.graphics;
324
+ return () => {
325
+ for (const { n, atts, x, y, z, graphics } of saved) {
326
+ n.atts.length = atts;
327
+ Object.assign(n, { x, y, z, graphics });
312
328
  }
313
- else {
314
- extraViews.push(viewPositions(viewDoc, prepared.zAs));
329
+ for (const { e, atts, graphics } of savedEdges) {
330
+ e.atts.length = atts;
331
+ e.graphics = graphics;
315
332
  }
333
+ graph.atts.length = graphAtts;
334
+ graph.graphics = graphGraphics;
335
+ };
336
+ }
337
+ /**
338
+ * Fill one subnetwork's records from its tables and views and emit it.
339
+ * @param prepared - the prepared import
340
+ * @param choice - the subnetwork
341
+ * @param sink - the sink
342
+ * @param parsed - its network file
343
+ * @param nodes - its node records by id
344
+ * @param edges - its edge records by id
345
+ */
346
+ async function fill3(prepared, choice, sink, parsed, nodes, edges) {
347
+ const { report, inner } = prepared;
348
+ const { doc, dialect } = parsed;
349
+ const { graph } = choice;
350
+ const members = membersOf(doc, graph);
351
+ const unmatched = await applyTables(prepared, choice, doc, nodes, edges);
352
+ if (unmatched > 0) {
353
+ report.warning("parse-error", CYS_ISSUE.TABLE_ROW, `${unmatched} table row${plural(unmatched)} of network ${choice.graphId} ${agree(unmatched, "names", "name")} no node, edge or network in it (left over from deleted elements) and ${agree(unmatched, "is", "are")} not read`);
316
354
  }
355
+ const restoredIds = inner.restoreMangledIds ? restoreIds(members.nodes) : new Map();
356
+ const { visualStyle, extraViews, extraIds } = await readViews(prepared, choice, nodes, edges);
317
357
  const cyMeta = sessionMeta(prepared, {
318
358
  collection: doc.root.label ?? choice.network.title,
319
359
  network: choice.graphId,
@@ -327,27 +367,212 @@ async function import3(prepared, choice, sink, parsed) {
327
367
  entry: choice.network.name,
328
368
  graphName: localName(graph) ?? choice.name,
329
369
  metaExtra: { [META_KEY]: cyMeta },
330
- groupNodes: new Set(members.nodes
331
- .filter((n) => n.atts.some((a) => a.name === "__isGroup" && isCyTrue(a.value)))
332
- .flatMap((n) => (n.id === null ? [] : [n.id]))),
370
+ labelFromName: true,
371
+ restoredIds,
372
+ groupNodes: groupNodeIds(members.nodes),
333
373
  resolvePointer: pointerResolver(prepared, doc),
334
374
  onMissingMember: (group, member) => {
335
- const record = groups.find((g) => g.group === group);
336
- if (record === undefined) {
337
- groups.push({ group, members: [member] });
338
- }
339
- else if (!record.members.includes(member)) {
340
- record.members.push(member);
341
- }
375
+ addMissingMember(groups, group, member);
342
376
  },
343
377
  };
344
378
  const settings = { labelAliases: false, cytoscapeEscapes: false, zAs: prepared.zAs };
345
379
  new XgmmlEmitter(doc, dialect, sink, report, prepared.inner, settings, extras).emit(graph);
346
380
  if (groups.length > 0) {
347
- report.warning("unsupported", CYS_ISSUE.COLLAPSED_GROUP, `${groups.length} collapsed group(s) hold ${groups.reduce((n, g) => n + g.members.length, 0)} member(s) that are not in the network; they are listed in meta.extra.cytoscape.groups`);
381
+ const missing = groups.reduce((n, g) => n + g.members.length, 0);
382
+ report.warning("unsupported", CYS_ISSUE.COLLAPSED_GROUP, `${groups.length} collapsed group${plural(groups.length)} ${agree(groups.length, "holds", "hold")} ${missing} member${plural(missing)} that are not in the network; they are listed in meta.extra.cytoscape.groups`);
348
383
  }
349
384
  reportRootOnly(parsed, report, choice.network.name);
350
- writeViewPositions(sink, extraViews, views.slice(1).map((v) => v.view), report, prepared.inner);
385
+ writeViewPositions(sink, extraViews, extraIds, report, prepared.inner, restoredIds);
386
+ }
387
+ /**
388
+ * The ids of the nodes Cytoscape marks as groups.
389
+ * @param nodes - the network's node records
390
+ * @returns their ids
391
+ */
392
+ function groupNodeIds(nodes) {
393
+ return new Set(nodes
394
+ .filter((n) => n.atts.some((a) => a.name === "__isGroup" && isCyTrue(a.value)))
395
+ .flatMap((n) => (n.id === null ? [] : [n.id])));
396
+ }
397
+ /**
398
+ * Record a collapsed group's member that is not in the network.
399
+ * @param groups - the groups with missing members, added to
400
+ * @param group - the group's id
401
+ * @param member - the member's id
402
+ */
403
+ function addMissingMember(groups, group, member) {
404
+ const record = groups.find((g) => g.group === group);
405
+ if (record === undefined) {
406
+ groups.push({ group, members: [member] });
407
+ }
408
+ else if (!record.members.includes(member)) {
409
+ record.members.push(member);
410
+ }
411
+ }
412
+ /**
413
+ * Add the cells of a subnetwork's tables to its node, edge and network records.
414
+ * @param prepared - the prepared import
415
+ * @param choice - the subnetwork
416
+ * @param doc - its network file
417
+ * @param nodes - its node records by id
418
+ * @param edges - its edge records by id
419
+ * @returns the number of rows that name no element of the network
420
+ */
421
+ async function applyTables(prepared, choice, doc, nodes, edges) {
422
+ const { report, inner, session } = prepared;
423
+ const { layout } = session;
424
+ // each network reads its tables into its own report, so a shared table's issues, and a
425
+ // table that fails the import, land in every network that reads it.
426
+ // ponytail: a table shared by several networks is inflated and parsed once per network (the
427
+ // byte budget is charged once); cache the parse and relay its issues if importAll gets slow
428
+ const tables = new Map();
429
+ const tableOf = (path) => {
430
+ let pending = tables.get(path);
431
+ if (pending === undefined) {
432
+ const entry = layout.tables.find((t) => t.tablePath === path);
433
+ pending =
434
+ entry === undefined
435
+ ? Promise.resolve(null)
436
+ : session.read(entry, report).then((bytes) => readCyTable(bytes, path, entry.name, report, inner));
437
+ tables.set(path, pending);
438
+ }
439
+ return pending;
440
+ };
441
+ const ctx = {
442
+ choice,
443
+ graph: choice.graph,
444
+ nodes,
445
+ edges,
446
+ // a row of an element the file declares outside this network (a collapsed group's member, a
447
+ // meta-edge) is Cytoscape's bookkeeping, not a stale row
448
+ declared: new Set([
449
+ ...doc.nodes.flatMap((n) => (n.id === null ? [] : [n.id])),
450
+ ...doc.edges.flatMap((e) => (e.id === null ? [] : [e.id])),
451
+ ]),
452
+ virtuals: layout.cytables === null ? [] : await readVirtuals(session, layout.cytables, report, inner),
453
+ tableOf,
454
+ report,
455
+ };
456
+ let unmatched = 0;
457
+ for (const entry of layout.tables.filter((t) => t.network === choice.graphId && t.element !== null)) {
458
+ throwIfAborted(inner.signal);
459
+ const table = await tableOf(entry.tablePath);
460
+ if (table !== null) {
461
+ unmatched += await applyTable(ctx, entry, table);
462
+ }
463
+ }
464
+ return unmatched;
465
+ }
466
+ /**
467
+ * Add the cells of one table, and of the shared columns that extend it, to the records.
468
+ * @param ctx - the shared state
469
+ * @param entry - the table's entry
470
+ * @param table - the table
471
+ * @returns the number of rows that name no element of the network
472
+ */
473
+ async function applyTable(ctx, entry, table) {
474
+ const local = entry.namespace === "LOCAL_ATTRS";
475
+ const namespace = local ? null : entry.namespace;
476
+ const hidden = !local && entry.namespace !== "SHARED_ATTRS";
477
+ const target = (key) => {
478
+ if (entry.element === "network") {
479
+ return key === ctx.choice.graphId ? ctx.graph.atts : undefined;
480
+ }
481
+ return (entry.element === "node" ? ctx.nodes : ctx.edges).get(key)?.atts;
482
+ };
483
+ let unmatched = 0;
484
+ for (const [key, cells] of table.rows) {
485
+ const atts = target(key);
486
+ if (atts !== undefined) {
487
+ atts.push(...rowAtts(table, key, cells, namespace, hidden));
488
+ }
489
+ else if (!ctx.declared.has(key)) {
490
+ unmatched++;
491
+ }
492
+ }
493
+ for (const virtual of await virtualColumnsOf(table, ctx.virtuals, ctx.tableOf, ctx.report)) {
494
+ // a shared column named like a local one is renamed; the local one keeps the name
495
+ const owned = table.columns.some((c) => c.name === virtual.column.name);
496
+ const ns = local ? sharedNamespace(owned) : namespace;
497
+ for (const [key, text] of virtual.values) {
498
+ const atts = target(key);
499
+ if (atts !== undefined) {
500
+ pushAtt(atts, cellAtt(virtual.column, text, table.lines.get(key) ?? 0, ns, hidden));
501
+ }
502
+ }
503
+ }
504
+ return unmatched;
505
+ }
506
+ /**
507
+ * The attributes of one table row's cells (the first cell is the SUID key).
508
+ * @param table - the table
509
+ * @param key - the row's key
510
+ * @param cells - the row's cells
511
+ * @param namespace - the namespace the attributes are written in, or null
512
+ * @param hidden - whether the table is one of Cytoscape's hidden ones
513
+ * @returns the attributes of the cells that hold a value
514
+ */
515
+ function rowAtts(table, key, cells, namespace, hidden) {
516
+ const line = table.lines.get(key) ?? 0;
517
+ const out = [];
518
+ for (let i = 1; i < table.columns.length && i < cells.length; i++) {
519
+ pushAtt(out, cellAtt(table.columns[i], cells[i], line, namespace, hidden));
520
+ }
521
+ return out;
522
+ }
523
+ /**
524
+ * The namespace of a shared column in a local table: renamed when a local column has its name.
525
+ * @param owned - whether a local column has the shared column's name
526
+ * @returns "SHARED_ATTRS", or null for none
527
+ */
528
+ function sharedNamespace(owned) {
529
+ return owned ? "SHARED_ATTRS" : null;
530
+ }
531
+ /**
532
+ * Add an attribute to a record's list, unless there is none.
533
+ * @param atts - the record's attributes
534
+ * @param att - the attribute, or null
535
+ */
536
+ function pushAtt(atts, att) {
537
+ if (att !== null) {
538
+ atts.push(att);
539
+ }
540
+ }
541
+ /**
542
+ * Read a subnetwork's views: the first (the lowest view SUID, whatever the order of the entries)
543
+ * fills the records' coordinates and graphics, each further one gives positions only. A view that
544
+ * cannot be read is recorded and skipped.
545
+ * @param prepared - the prepared import
546
+ * @param choice - the subnetwork
547
+ * @param nodes - its node records by id
548
+ * @param edges - its edge records by id
549
+ * @returns the first view's style, and the positions and SUIDs of the further views
550
+ */
551
+ async function readViews(prepared, choice, nodes, edges) {
552
+ const { report, inner, session } = prepared;
553
+ const views = session.layout.views
554
+ .filter((v) => v.network === choice.graphId)
555
+ .sort((a, b) => a.view.length - b.view.length || (a.view < b.view ? -1 : Number(a.view > b.view)));
556
+ let visualStyle = null;
557
+ const extraViews = [];
558
+ const extraIds = [];
559
+ let first = true;
560
+ for (const view of views) {
561
+ const viewDoc = (await parseOptionalEntry(session, view, report, inner, true))?.doc;
562
+ if (viewDoc === undefined) {
563
+ continue;
564
+ }
565
+ if (first) {
566
+ first = false;
567
+ visualStyle = viewDoc.root.attrs.get("cy:visualStyle") ?? null;
568
+ applyView(viewDoc, choice.graph, nodes, edges, report, view.name);
569
+ }
570
+ else {
571
+ extraViews.push(viewPositions(viewDoc, prepared.zAs, nodes, report, view.name));
572
+ extraIds.push(view.view);
573
+ }
574
+ }
575
+ return { visualStyle, extraViews, extraIds };
351
576
  }
352
577
  /**
353
578
  * The virtual columns of `tables/cytables.xml`.
@@ -358,9 +583,20 @@ async function import3(prepared, choice, sink, parsed) {
358
583
  * @returns the virtual columns in document order
359
584
  */
360
585
  async function readVirtuals(session, entry, report, inner) {
361
- const tree = await readXmlTree(await session.read(entry), entry.name, report, inner);
586
+ const tree = await parseXmlTree(await session.read(entry, report), entry.name, report, inner);
587
+ if (typeof tree === "string") {
588
+ // only the shared columns are lost: every table's own columns are still read
589
+ report.error("parse-error", CYS_ISSUE.TABLE, `${entryLabel(entry.name)}: ${tree}; its virtual columns are not read`);
590
+ return [];
591
+ }
362
592
  const out = [];
363
593
  for (const node of elementsNamed(tree, "virtualColumn")) {
594
+ const missing = ["name", "targetTable", "sourceTable", "sourceColumn"].filter((k) => !node.attrs.has(k));
595
+ if (missing.length > 0) {
596
+ const named = node.attrs.has("name") ? ` "${node.attrs.get("name") ?? ""}"` : "";
597
+ report.error("parse-error", CYS_ISSUE.TABLE, `${entryLabel(entry.name)}: a virtual column${named} has no ${missing.join(", ")}; the column is not read`);
598
+ continue;
599
+ }
364
600
  const a = (key) => node.attrs.get(key) ?? "";
365
601
  out.push({
366
602
  name: a("name"),
@@ -400,12 +636,14 @@ function byId(records) {
400
636
  */
401
637
  function applyView(view, graph, nodes, edges, report, entry) {
402
638
  let dangling = 0;
639
+ const seen = new Set();
403
640
  for (const node of view.nodes) {
404
641
  const target = node.viewId === null ? undefined : nodes.get(node.viewId);
405
642
  if (target === undefined) {
406
643
  dangling++;
407
644
  continue;
408
645
  }
646
+ duplicateViewNode(seen, node, report, entry);
409
647
  target.x = node.x;
410
648
  target.y = node.y;
411
649
  target.z = node.z;
@@ -427,26 +665,71 @@ function applyView(view, graph, nodes, edges, report, entry) {
427
665
  graph.graphics = { ...(graph.graphics ?? {}), ...view.root.graphics };
428
666
  }
429
667
  if (dangling > 0) {
430
- report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `${entry}: ${dangling} view element(s) name no node or edge of the network; they are not read`);
668
+ report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `${entryLabel(entry)}: ${dangling} view element${plural(dangling)} ${agree(dangling, "names", "name")} no node or edge of the network; they are not read`);
431
669
  }
432
670
  }
433
671
  /**
434
- * The positions of a further view, by model node id: y flipped to y-up, z in the position only
435
- * under zAs "position".
672
+ * W_DUPLICATE_NODE for a view element that repeats a node an earlier one of the view gave.
673
+ * @param seen - the model node ids seen so far in the view
674
+ * @param node - the view element
675
+ * @param report - the report
676
+ * @param entry - the view entry name
677
+ */
678
+ function duplicateViewNode(seen, node, report, entry) {
679
+ const id = node.viewId;
680
+ if (seen.has(id)) {
681
+ report.warning("validation-error", XGMML_ISSUE.DUPLICATE_NODE, `${entryLabel(entry)}: the view gives node "${id}" twice; the later element is used`, { line: node.line, element: id });
682
+ }
683
+ seen.add(id);
684
+ }
685
+ /**
686
+ * The positions of a further view, by model node id, read by the first view's rules: y flipped
687
+ * to y-up, z in the position only under zAs "position", a coordinate that does not parse
688
+ * E_BAD_VALUE (the position is unset; a bad z is 0), an element naming no node of the network
689
+ * counted in one W_DANGLING_REFERENCE.
436
690
  * @param view - the view document
437
691
  * @param zAs - where z goes
692
+ * @param nodes - the network's node records by id
693
+ * @param report - the report
694
+ * @param entry - the view entry name
438
695
  * @returns the positions
439
696
  */
440
- function viewPositions(view, zAs) {
697
+ function viewPositions(view, zAs, nodes, report, entry) {
441
698
  const out = new Map();
699
+ const seen = new Set();
700
+ let dangling = 0;
701
+ const coordinate = (text, node) => {
702
+ if (text === null) {
703
+ return null;
704
+ }
705
+ const n = parseCoordinate(text);
706
+ if (n === null) {
707
+ report.error("validation-error", XGMML_ISSUE.BAD_VALUE, `${entryLabel(entry)}: graphics coordinate "${text}" is not a number`, {
708
+ line: node.line,
709
+ element: node.viewId,
710
+ });
711
+ }
712
+ return n;
713
+ };
442
714
  for (const node of view.nodes) {
443
- const x = Number(node.x);
444
- const y = Number(node.y);
445
- const z = zAs === "position" ? Number(node.z ?? 0) : 0;
446
- if (node.viewId !== null && node.x !== null && node.y !== null && Number.isFinite(x) && Number.isFinite(y)) {
447
- out.set(node.viewId, [x, y === 0 ? 0 : -y, Number.isFinite(z) ? z : 0]);
715
+ if (node.viewId === null || !nodes.has(node.viewId)) {
716
+ dangling++;
717
+ continue;
718
+ }
719
+ duplicateViewNode(seen, node, report, entry);
720
+ const x = coordinate(node.x, node);
721
+ const y = coordinate(node.y, node);
722
+ const z = zAs === "position" ? coordinate(node.z, node) : null;
723
+ if (x !== null && y !== null) {
724
+ out.set(node.viewId, [x, y === 0 ? 0 : -y, z ?? 0]);
725
+ }
726
+ else {
727
+ out.delete(node.viewId);
448
728
  }
449
729
  }
730
+ if (dangling > 0) {
731
+ report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `${entryLabel(entry)}: ${dangling} view element${plural(dangling)} ${agree(dangling, "names", "name")} no node of the network; they are not read`);
732
+ }
450
733
  return out;
451
734
  }
452
735
  /**
@@ -456,8 +739,9 @@ function viewPositions(view, zAs) {
456
739
  * @param viewIds - each view's SUID
457
740
  * @param report - the report
458
741
  * @param common - the id rule
742
+ * @param restoredIds - the original ids of mangled nodes, by written id
459
743
  */
460
- function writeViewPositions(sink, views, viewIds, report, common) {
744
+ function writeViewPositions(sink, views, viewIds, report, common, restoredIds) {
461
745
  const coercer = new IdCoercer(common.ids);
462
746
  views.forEach((positions, i) => {
463
747
  const name = `${VIEW_POSITION_PREFIX}${i + 2}`;
@@ -478,7 +762,7 @@ function writeViewPositions(sink, views, viewIds, report, common) {
478
762
  for (const [id, xyz] of positions) {
479
763
  let index;
480
764
  try {
481
- index = sink.indexOf(coercer.text(id));
765
+ index = sink.indexOf(coercer.text(restoredIds.get(id) ?? id));
482
766
  }
483
767
  catch {
484
768
  continue; // an id the id rule refuses was refused, and recorded, as a node already
@@ -489,6 +773,26 @@ function writeViewPositions(sink, views, viewIds, report, common) {
489
773
  }
490
774
  });
491
775
  }
776
+ /**
777
+ * The original ids an exporter's `sanitizeIds: "mangle"` kept in the node table's
778
+ * `graphty:originalId` column; each such cell is taken out of the node's columns.
779
+ * @param nodes - the node records, table cells merged in
780
+ * @returns the original id by written id
781
+ */
782
+ function restoreIds(nodes) {
783
+ const out = new Map();
784
+ for (const node of nodes) {
785
+ const at = node.atts.findIndex((a) => a.name === ORIGINAL_ID_ATTRIBUTE &&
786
+ (a.namespace ?? null) === null &&
787
+ a.value !== null &&
788
+ a.children.length === 0);
789
+ if (node.id !== null && at >= 0) {
790
+ out.set(node.id, node.atts[at].value);
791
+ node.atts.splice(at, 1);
792
+ }
793
+ }
794
+ return out;
795
+ }
492
796
  /**
493
797
  * The network's name from its own table (the LOCAL `name` att), else null.
494
798
  * @param graph - the subnetwork with its table atts
@@ -556,7 +860,7 @@ function reportRootOnly(parsed, report, entry) {
556
860
  const nodes = parsed.doc.nodes.filter((n) => !held.has(n)).length;
557
861
  const edges = parsed.doc.edges.filter((e) => !held.has(e)).length;
558
862
  if (nodes + edges > 0) {
559
- report.warning("unsupported", XGMML_ISSUE.ROOT_ONLY_ELEMENTS, `${entry}: ${nodes} node(s) and ${edges} edge(s) belong to no registered network (group meta-edges, collapsed group members) and were not read`);
863
+ report.warning("unsupported", XGMML_ISSUE.ROOT_ONLY_ELEMENTS, `${entryLabel(entry)}: ${elementCount(nodes, edges)} ${agree(nodes + edges, "belongs", "belong")} to none of the session's networks (group meta-edges or collapsed group members) and ${agree(nodes + edges, "was", "were")} not read`);
560
864
  }
561
865
  }
562
866
  // ---------------------------------------------------------------------------------------- 2.x
@@ -573,8 +877,11 @@ async function networks2(session, report, inner) {
573
877
  if (layout.cysession === null) {
574
878
  return report.fail(CYS_ISSUE.NOT_SESSION, "the session has no cysession.xml");
575
879
  }
576
- const tree = await readXmlTree(await session.read(layout.cysession), layout.cysession.name, report, inner);
880
+ const tree = await readXmlTree(await session.read(layout.cysession, report), layout.cysession.name, report, inner);
577
881
  const documentVersion = tree.attrs.get("documentVersion") ?? "";
882
+ if (documentVersion.length > 0 && !/^\d+(\.\d+)*$/.test(documentVersion.trim())) {
883
+ report.warning("validation-error", XGMML_ISSUE.DOCUMENT_VERSION, `${layout.cysession.name}: documentVersion "${documentVersion}" does not parse; the session is read as Cytoscape 2.x`);
884
+ }
578
885
  if (documentVersion.startsWith("3")) {
579
886
  report.fail(CYS_ISSUE.VERSION, `cysession.xml has documentVersion ${documentVersion} and no version marker: the 2011 Cytoscape 3.0 pre-release layout, which no released Cytoscape reads`);
580
887
  }
@@ -588,16 +895,28 @@ async function networks2(session, report, inner) {
588
895
  const missing = [];
589
896
  const named = new Set();
590
897
  for (const record of elementsNamed(tree, "network")) {
591
- const id = record.attrs.get("id") ?? "";
592
- const filename = record.attrs.get("filename") ?? `${id}.xgmml`;
593
- if (id === "Network Root") {
898
+ const given = record.attrs.get("id");
899
+ const filename = record.attrs.get("filename") ?? (given === undefined ? undefined : `${given}.xgmml`);
900
+ if (given === "Network Root") {
594
901
  continue;
595
902
  }
903
+ if (filename === undefined) {
904
+ sessionRecord(report, layout.cysession.name, "a <network> has neither an id nor a filename; it is not read");
905
+ continue;
906
+ }
907
+ const id = given ?? urlDecode(filename.replace(/\.xgmml$/i, ""));
908
+ if (given === undefined) {
909
+ sessionRecord(report, layout.cysession.name, `a <network> for ${filename} has no id; it is named "${id}" after its file`);
910
+ }
596
911
  const file = byDecoded.get(filename);
597
912
  if (file === undefined) {
598
913
  missing.push(filename);
599
914
  continue;
600
915
  }
916
+ if (named.has(file)) {
917
+ sessionRecord(report, layout.cysession.name, `the <network> "${id}" names ${filename}, which an earlier record already names; it is not read a second time`);
918
+ continue;
919
+ }
601
920
  named.add(file);
602
921
  const parent = record.children.find((c) => c.name === "parent")?.attrs.get("id") ?? null;
603
922
  choices.push({
@@ -609,14 +928,24 @@ async function networks2(session, report, inner) {
609
928
  });
610
929
  }
611
930
  if (missing.length > 0) {
612
- report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `cysession.xml names ${missing.length} network file(s) the session does not hold: ${listed(missing)}`);
931
+ report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `cysession.xml names ${missing.length} network file${plural(missing.length)} the session does not hold: ${listed(missing)}`);
613
932
  }
614
933
  const unnamed = order.filter((e) => !named.has(e)).map((e) => e.name);
615
934
  if (unnamed.length > 0) {
616
- report.warning("unsupported", CYS_ISSUE.ENTRY_SKIPPED, `${unnamed.length} network file(s) are not in cysession.xml's network tree and were not read: ${listed(unnamed)}`);
935
+ report.warning("unsupported", CYS_ISSUE.ENTRY_SKIPPED, `${unnamed.length} network file${plural(unnamed.length)} ${agree(unnamed.length, "is", "are")} not in cysession.xml's network tree and were not read: ${listed(unnamed)}`);
617
936
  }
618
937
  return choices.sort((a, b) => order.indexOf(a.file) - order.indexOf(b.file));
619
938
  }
939
+ /**
940
+ * W_CYS_SESSION_RECORD: a cysession.xml network record without an id, or one naming a file an
941
+ * earlier record names.
942
+ * @param report - the report
943
+ * @param entry - the cysession.xml entry name
944
+ * @param message - what is wrong and what is done
945
+ */
946
+ function sessionRecord(report, entry, message) {
947
+ report.warning("validation-error", CYS_ISSUE.SESSION_RECORD, `${entryLabel(entry)}: ${message}`);
948
+ }
620
949
  /**
621
950
  * Import one network of a 2.x session.
622
951
  * @param prepared - the prepared import
@@ -626,17 +955,21 @@ async function networks2(session, report, inner) {
626
955
  async function import2(prepared, choice, sink) {
627
956
  const { report, inner, session } = prepared;
628
957
  const { doc, dialect } = await parseEntry(session, choice.file, report, inner);
958
+ let unmatched = 0;
629
959
  for (const [list, column] of [
630
960
  ["selectedNodes", SELECTED_COLUMN],
631
961
  ["hiddenNodes", HIDDEN_COLUMN],
632
962
  ]) {
633
- mark(doc.nodes, idsIn(choice.record, list), column);
963
+ unmatched += mark(doc.nodes, idsIn(choice.record, list), column);
634
964
  }
635
965
  for (const [list, column] of [
636
966
  ["selectedEdges", SELECTED_COLUMN],
637
967
  ["hiddenEdges", HIDDEN_COLUMN],
638
968
  ]) {
639
- mark(doc.edges, idsIn(choice.record, list), column);
969
+ unmatched += mark(doc.edges, idsIn(choice.record, list), column);
970
+ }
971
+ if (unmatched > 0) {
972
+ report.warning("validation-error", CYS_ISSUE.DANGLING_REFERENCE, `cysession.xml lists ${unmatched} selected or hidden name${plural(unmatched)} that ${choice.file.name} does not hold; they are not read`);
640
973
  }
641
974
  const cyMeta = sessionMeta(prepared, {
642
975
  collection: null,
@@ -677,12 +1010,20 @@ function idsIn(record, list) {
677
1010
  * @param records - the node or edge records
678
1011
  * @param ids - the listed names
679
1012
  * @param column - the column
1013
+ * @returns how many listed names match no record
680
1014
  */
681
1015
  function mark(records, ids, column) {
682
1016
  if (ids.size === 0) {
683
- return;
1017
+ return 0;
684
1018
  }
1019
+ const matched = new Set();
685
1020
  for (const record of records) {
1021
+ if (record.id !== null && ids.has(record.id)) {
1022
+ matched.add(record.id);
1023
+ }
1024
+ if (record.label !== null && ids.has(record.label)) {
1025
+ matched.add(record.label);
1026
+ }
686
1027
  if ((record.id !== null && ids.has(record.id)) || (record.label !== null && ids.has(record.label))) {
687
1028
  record.atts.push({
688
1029
  name: column,
@@ -702,6 +1043,7 @@ function mark(records, ids, column) {
702
1043
  });
703
1044
  }
704
1045
  }
1046
+ return ids.size - matched.size;
705
1047
  }
706
1048
  /**
707
1049
  * The session facts of one network, and the once-per-import warnings about what is not read
@@ -718,10 +1060,10 @@ function sessionMeta(prepared, facts) {
718
1060
  const { layout } = prepared.session;
719
1061
  const { report } = prepared;
720
1062
  if (layout.styles.length > 0) {
721
- report.warning("unsupported", CYS_ISSUE.STYLES_NOT_IMPORTED, `the session's styles (${layout.styles.map((s) => s.path).join(", ")}) are not applied: style import is issue #706`);
1063
+ report.warning("unsupported", CYS_ISSUE.STYLES_NOT_IMPORTED, `the session's styles (${layout.styles.map((s) => s.path).join(", ")}) are not applied`);
722
1064
  }
723
1065
  if (layout.skipped.length > 0) {
724
- report.warning("unsupported", CYS_ISSUE.ENTRY_SKIPPED, `${layout.skipped.length} entries hold no graph data and were not read (apps, global tables, properties, images): ${listed(layout.skipped)}`);
1066
+ report.warning("unsupported", CYS_ISSUE.ENTRY_SKIPPED, `${layout.skipped.length} ${layout.skipped.length === 1 ? "entry holds" : "entries hold"} no graph data and ${agree(layout.skipped.length, "was", "were")} not read (apps, global tables, properties, images): ${listed(layout.skipped)}`);
725
1067
  }
726
1068
  return {
727
1069
  sessionVersion: layout.version,
@@ -758,10 +1100,12 @@ async function importChoice(prepared, index, sink) {
758
1100
  async function importCys(input, sink, options) {
759
1101
  const prepared = await prepare(input, sink, options);
760
1102
  const index = chooseGraph(prepared.choices.map((c) => c.name), options, prepared.report);
761
- if (prepared.choices.length > 1) {
762
- prepared.report.warning("unsupported", CYS_ISSUE.MULTIPLE_GRAPHS, `the session holds ${prepared.choices.length} networks; ${prepared.choices.length - 1} were not read (use importAll, graphIndex or graphName)`);
1103
+ if (prepared.choices.length > 1 && !graphChosen(options)) {
1104
+ prepared.report.warning("unsupported", CYS_ISSUE.MULTIPLE_GRAPHS, `the session holds ${prepared.choices.length} networks; only the first is read (importAllGraphs() reads every one; graphIndex or graphName chooses one)`);
763
1105
  }
764
- return importChoice(prepared, index, sink);
1106
+ const report = await importChoice(prepared, index, sink);
1107
+ prepared.session.done();
1108
+ return report;
765
1109
  }
766
1110
  /**
767
1111
  * Import every network of a session, each into its own sink with its own report.
@@ -771,17 +1115,19 @@ async function importCys(input, sink, options) {
771
1115
  * @returns one report per network
772
1116
  */
773
1117
  async function importAllCys(input, sinkFor, options) {
774
- const bytes = (await readBytes(input, { signal: options?.signal ?? null })) ?? input;
775
- const first = await prepare(bytes, null, options);
1118
+ // the archive is opened and its network files parsed once: one byte budget, one progress,
1119
+ // and every network's report starts from what reading the session recorded
1120
+ const first = await prepare(input, null, options);
1121
+ const opened = first.report.fork();
776
1122
  const reports = [];
777
1123
  for (let i = 0; i < first.choices.length; i++) {
778
1124
  const sink = sinkFor(i);
779
- // every network gets fresh records and a fresh report
780
- const prepared = i === 0 ? first : await prepare(bytes, null, options);
1125
+ const prepared = i === 0 ? first : { ...first, report: opened.fork() };
781
1126
  reportSinkOptions(sink, options, prepared.report, true);
782
1127
  reportUnusedOptions(options, prepared.report, USED_OPTIONS);
783
1128
  reports.push(await importChoice(prepared, i, sink));
784
1129
  }
1130
+ first.session.done();
785
1131
  return reports;
786
1132
  }
787
1133
  /**
@@ -799,8 +1145,18 @@ async function listCysGraphs(input, options) {
799
1145
  edges: choice.era === "3" ? choice.edges : null,
800
1146
  }));
801
1147
  }
802
- /** A session's marker or folder name in the head of the archive. */
803
- const SESSION_NAME = /CytoscapeSession|cysession\.xml|\d+\.\d+\.\d+\.version/;
1148
+ /**
1149
+ * A session's marker or folder name in the head of the archive, or an entry only a session has
1150
+ * (Cytoscape's app state, a network, view or table file), for a session whose folder was renamed.
1151
+ * The head is decoded whole, so a name can sit anywhere in it.
1152
+ */
1153
+ const SESSION_NAMES = [
1154
+ /CytoscapeSession|cysession\.xml/,
1155
+ /(?<!\d)\d+\.\d+\.\d+\.version/,
1156
+ /(?:^|\/)apps\/org\.cytoscape\./,
1157
+ /(?:^|\/)(?:networks|views)\/\d+-[^/]*\.xgmml/,
1158
+ /(?:^|\/)tables\/\d+-[^/]*\/[^/]+\.cytable/,
1159
+ ];
804
1160
  /**
805
1161
  * Confidence that a head of bytes is a Cytoscape session: 0.95 for a zip whose head names the
806
1162
  * session folder, its version marker or cysession.xml; 0 for any other zip, so an `.xlsx`, a
@@ -808,18 +1164,23 @@ const SESSION_NAME = /CytoscapeSession|cysession\.xml|\d+\.\d+\.\d+\.version/;
808
1164
  * start after a short stub (a self-extracting archive) within the head.
809
1165
  * @param head - the first bytes
810
1166
  * @returns the confidence
1167
+ * @category Plugin helpers
811
1168
  */
812
1169
  export function sniffCys(head) {
813
1170
  for (let at = 0; at + 4 <= head.byteLength; at++) {
814
1171
  if (startsLikeZip(head.subarray(at))) {
815
- return SESSION_NAME.test(decodeEntryName(head.subarray(at))) ? 0.95 : 0;
1172
+ return SESSION_NAMES.some((r) => r.test(decodeEntryName(head.subarray(at)))) ? 0.95 : 0;
816
1173
  }
817
1174
  }
818
1175
  return 0;
819
1176
  }
820
- /** The Cytoscape session importer. */
1177
+ /**
1178
+ * The Cytoscape session importer.
1179
+ * @category Built-in formats
1180
+ */
821
1181
  export const cysImporter = Object.freeze({
822
1182
  format: FORMAT,
1183
+ options: Object.freeze(["maxUncompressedBytes", "zAs"]),
823
1184
  extensions: EXTENSIONS,
824
1185
  mimeTypes: MIME_TYPES,
825
1186
  sniff: sniffCys,