@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
@@ -22,40 +22,47 @@
22
22
  */
23
23
  import { INVALID_INDEX, } from "@graphty/graph-format";
24
24
  import { declareResolved, uniqueColumnName } from "../../common/attributes.js";
25
- import { AMBIGUOUS_GRAPH_NAME_CODE, ASPECT_ORDER_CODE, BAD_ASPECT_BLOCK_CODE, BAD_VALUE_CODE, COLUMN_RENAMED_CODE, COUNT_MISMATCH_CODE, DANGLING_REFERENCE_CODE, DIRECTION_FORCED_CODE, DIRECTION_REFUSED_CODE, DUPLICATE_ATTRIBUTE_CODE, DUPLICATE_EDGE_ID_CODE, DUPLICATE_NODE_CODE, EMPTY_INPUT_CODE, ENCODING_FALLBACK_CODE, GRAPH_NOT_FOUND_CODE, ID_MERGED_CODE, ID_TEXT_TYPE_CODE, INVALID_ENCODING_CODE, INVALID_UTF8_CODE, MISSING_ENDPOINT_CODE, MISSING_ID_CODE, MULTIPLE_GRAPHS_CODE, NO_GRAPH_CODE, OPTION_IGNORED_CODE, PARENT_CYCLE_CODE, PRECISION_CODE, ROLE_TAKEN_CODE, SINK_OPTION_CODE, STATUS_FAILED_CODE, STATUS_WARNING_CODE, STYLES_NOT_IMPORTED_CODE, SYNTAX_CODE, TOO_LARGE_CODE, UNKNOWN_ATTR_TYPE_CODE, UNKNOWN_ENCODING_CODE, UNKNOWN_PARENT_CODE, WIDENED_CODE, } from "../../common/codes.js";
25
+ import { AMBIGUOUS_GRAPH_NAME_CODE, ASPECT_ORDER_CODE, BAD_ASPECT_BLOCK_CODE, BAD_VALUE_CODE, COLUMN_RENAMED_CODE, COUNT_MISMATCH_CODE, DANGLING_REFERENCE_CODE, DIRECTION_FORCED_CODE, DIRECTION_REFUSED_CODE, DUPLICATE_ATTRIBUTE_CODE, DUPLICATE_EDGE_ID_CODE, DUPLICATE_NODE_CODE, ELEMENT_ISSUE, EMPTY_INPUT_CODE, ENCODING_FALLBACK_CODE, GRAPH_NOT_FOUND_CODE, ID_MERGED_CODE, ID_TEXT_TYPE_CODE, INPUT_ISSUE, INVALID_ENCODING_CODE, INVALID_UTF8_CODE, JSON_NONSTANDARD_NUMBER_CODE, MISSING_ENDPOINT_CODE, MISSING_ID_CODE, MULTI_ASPECT_FRAGMENT_CODE, MULTIPLE_GRAPHS_CODE, NO_GRAPH_CODE, OPTION_IGNORED_CODE, PARENT_CYCLE_CODE, PRECISION_CODE, ROLE_TAKEN_CODE, SINGLE_OBJECT_ASPECT_CODE, SINK_OPTION_CODE, STATUS_FAILED_CODE, STATUS_WARNING_CODE, STYLES_NOT_IMPORTED_CODE, SYNTAX_CODE, TOO_LARGE_CODE, UNKNOWN_ATTR_TYPE_CODE, UNKNOWN_ELEMENT_CODE, UNKNOWN_ENCODING_CODE, UNKNOWN_PARENT_CODE, WIDENED_CODE, } from "../../common/codes.js";
26
26
  import { DirectionResolver } from "../../common/direction.js";
27
27
  import { IdCoercer } from "../../common/ids.js";
28
28
  import { textChunks, throwIfAborted } from "../../common/input.js";
29
- import { cxId, CxStructure, declareFresh, ExactInteger, flipY, inexactLiteral, isRecord, JsonScanError, plainJson, positionDecl, reportTooDeep, scanAspects, zDecl, } from "../../common/json-elements.js";
30
- import { chooseGraph, reportSinkOptions, reportUnusedOptions, resolveImportOptions, } from "../../common/options.js";
29
+ import { cxId, CxStructure, declareFresh, ExactInteger, fitsF32, flipY, headText, inexactLiteral, isRecord, JsonScanError, keptPrecision, plainJson, positionDecl, reportSharedBlock, reportTooDeep, scanAspects, zDecl, } from "../../common/json-elements.js";
30
+ import { chooseGraph, graphChosen, reportSinkOptions, reportUnusedOptions, resolveImportOptions, } from "../../common/options.js";
31
+ import { agree, plural } from "../../common/plural.js";
31
32
  import { ImportReportBuilder } from "../../common/report.js";
32
33
  import { weightFromValue } from "../../common/weights.js";
33
- import { zAsOption } from "../cx2/importer.js";
34
+ import { ORIGINAL_ID_ATTRIBUTE, zAsOption } from "../cx2/importer.js";
34
35
  /** The format name. */
35
36
  const CX_FORMAT = "cx";
36
37
  /** The origin namespace of the per-element visual property columns. */
37
38
  const CX_BYPASS_NAMESPACE = "cx.bypass";
38
39
  /**
39
- * The issue codes the CX importer records (design section 1.2), by name: the codes shared with
40
- * the other importers (src/common/codes.ts) and the CX-specific ones. A key is the code without
40
+ * The issue codes the CX importer records, by name: the codes shared with
41
+ * the other importers and the CX-specific ones. A key is the code without
41
42
  * its severity and format prefixes.
43
+ * @category Built-in formats
42
44
  */
43
45
  export const CX_ISSUE = Object.freeze({
44
- /** The input is empty (fatal). */
46
+ ...INPUT_ISSUE,
47
+ ...ELEMENT_ISSUE,
48
+ /** The input is empty. The import stops. */
45
49
  EMPTY_INPUT: EMPTY_INPUT_CODE,
46
- /** The text is not JSON (fatal). */
50
+ /** The text is not valid JSON. The import stops. */
47
51
  SYNTAX: SYNTAX_CODE,
48
- /** Invalid UTF-8 (fatal). */
52
+ /** The input is not valid UTF-8. The import stops. */
49
53
  INVALID_UTF8: INVALID_UTF8_CODE,
50
- /** Invalid bytes in the encoding a BOM or the encoding option chose (fatal). */
54
+ /**
55
+ * Some bytes are not valid in the encoding that was chosen (by a byte order mark or the `encoding` option). The
56
+ * import stops.
57
+ */
51
58
  INVALID_ENCODING: INVALID_ENCODING_CODE,
52
59
  /** Bytes that are not UTF-8 were read as windows-1252. */
53
60
  ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
54
61
  /** An encoding the platform cannot decode was ignored. */
55
62
  UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
56
- /** The document is not a CX array, or it is CX2 (fatal; the message names CX2). */
63
+ /** The document is not a CX array, or it is CX2 (the message says so; read it as `cx2`). The import stops. */
57
64
  NOT_CX: "E_CX_NOT_CX",
58
- /** numberVerification holds another value than 2^48 - 1, or comes twice. */
65
+ /** NumberVerification holds another value than 2^48 - 1, or comes twice. */
59
66
  NUMBER_VERIFICATION: "W_CX_NUMBER_VERIFICATION",
60
67
  /** An old Cytoscape aspect name (visualProperties, subNetworks, ...) read under its cy name. */
61
68
  OLD_ASPECT_NAME: "W_CX_OLD_ASPECT_NAME",
@@ -63,11 +70,14 @@ export const CX_ISSUE = Object.freeze({
63
70
  GROUP_NODE_ADDED: "W_CX_GROUP_NODE_ADDED",
64
71
  /** Nodes or edges of the root network that no subnetwork holds: Cytoscape shows them in no network; not read. */
65
72
  ROOT_ONLY: "W_CX_ROOT_ONLY",
66
- /** The producer marked the document as failed (fatal). */
73
+ /** The program that wrote the file marked it as failed, so it is incomplete. The import stops. */
67
74
  STATUS_FAILED: STATUS_FAILED_CODE,
68
- /** The producer marked the document as successful with an error text. */
75
+ /** The program that wrote the file marked it as successful but added an error message; the message is shown. */
69
76
  STATUS_WARNING: STATUS_WARNING_CODE,
70
- /** A member of the array that is not a one-key aspect block, or an element that is not an object. */
77
+ /**
78
+ * A member of the file's top-level array that is not a block with one key (`{"nodes": [...]}`), or an element of a
79
+ * block that is not an object. It is skipped; the rest of the file is read.
80
+ */
71
81
  BAD_ASPECT_BLOCK: BAD_ASPECT_BLOCK_CODE,
72
82
  /** An aspect after the post-metadata or after the status, a third metaData. */
73
83
  ASPECT_ORDER: ASPECT_ORDER_CODE,
@@ -75,7 +85,10 @@ export const CX_ISSUE = Object.freeze({
75
85
  COUNT_MISMATCH: COUNT_MISMATCH_CODE,
76
86
  /** A value that does not parse as its data type; the cell is unset. */
77
87
  BAD_VALUE: BAD_VALUE_CODE,
78
- /** One attribute name with several data types: the column takes the wider one. */
88
+ /**
89
+ * An attribute's type was widened because a later value did not fit: an integer above 2^31 in an integer column,
90
+ * or two declared types for one attribute.
91
+ */
79
92
  WIDENED: WIDENED_CODE,
80
93
  /** A data type CX does not define; the value is kept as text. */
81
94
  UNKNOWN_ATTR_TYPE: UNKNOWN_ATTR_TYPE_CODE,
@@ -87,9 +100,9 @@ export const CX_ISSUE = Object.freeze({
87
100
  UNKNOWN_PARENT: UNKNOWN_PARENT_CODE,
88
101
  /** A group membership that would close a parent cycle; dropped. */
89
102
  PARENT_CYCLE: PARENT_CYCLE_CODE,
90
- /** The style rules of cyVisualProperties are not applied (issue #706). */
103
+ /** The style rules of cyVisualProperties are not applied; they are kept so a CX export writes them back. */
91
104
  STYLES_NOT_IMPORTED: STYLES_NOT_IMPORTED_CODE,
92
- /** A node without an @id. */
105
+ /** A node without an id (its `@id` key). The node is skipped. */
93
106
  MISSING_ID: MISSING_ID_CODE,
94
107
  /** An edge without s or t. */
95
108
  MISSING_ENDPOINT: MISSING_ENDPOINT_CODE,
@@ -99,37 +112,63 @@ export const CX_ISSUE = Object.freeze({
99
112
  DUPLICATE_EDGE_ID: DUPLICATE_EDGE_ID_CODE,
100
113
  /** An id that is not an integer. */
101
114
  INVALID_ID: "E_INVALID_ID",
115
+ /**
116
+ * A node or edge key CX does not define, an aspect graph-io keeps for writing back, or a table CX does not define;
117
+ * it is skipped.
118
+ */
119
+ UNKNOWN_ELEMENT: UNKNOWN_ELEMENT_CODE,
120
+ /** A member holding several aspects; each array-valued key is read as its own fragment. */
121
+ MULTI_ASPECT_FRAGMENT: MULTI_ASPECT_FRAGMENT_CODE,
122
+ /** An aspect written as one object, not an array of elements; read as one element. */
123
+ SINGLE_OBJECT_ASPECT: SINGLE_OBJECT_ASPECT_CODE,
124
+ /** The bare tokens NaN / Infinity / -Infinity (Python's json writes them), read as numbers. */
125
+ JSON_NONSTANDARD_NUMBER: JSON_NONSTANDARD_NUMBER_CODE,
102
126
  /** An edge endpoint naming no node of the graph (addMissingNodes false, the default). */
103
127
  UNKNOWN_NODE: "E_UNKNOWN_NODE",
104
128
  /** A weight that is not a number. */
105
129
  INVALID_WEIGHT: "E_INVALID_WEIGHT",
106
130
  /** An id spelled as a string or a non-integer literal; read as the integer. */
107
131
  ID_TEXT_TYPE: ID_TEXT_TYPE_CODE,
108
- /** An integer beyond 2^53: an id kept as its digits, a value stored as the nearest f64. */
132
+ /** An integer beyond 2^53 was stored as the nearest 64-bit float; pass `long: "string"` to keep every digit. */
109
133
  PRECISION: PRECISION_CODE,
110
134
  /** Two id texts merged under `ids: "number"`. */
111
135
  ID_MERGED: ID_MERGED_CODE,
112
- /** A collection read by import(): the other subnetworks are skipped. */
136
+ /**
137
+ * The file holds several graphs and only the first was read. It is not added when `graphIndex` or `graphName` chose the graph.
138
+ * `importAllGraphs()` reads every one.
139
+ */
113
140
  MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
114
- /** graphIndex or graphName names no subnetwork (fatal). */
141
+ /** `graphIndex` or `graphName` matches no subnetwork. The import stops. */
115
142
  GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
116
- /** graphName names several subnetworks (fatal). */
143
+ /** `graphName` matches several subnetworks. The import stops. */
117
144
  AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE,
118
- /** The input holds no graph (fatal). */
145
+ /** The input holds no graph. The import stops. */
119
146
  NO_GRAPH: NO_GRAPH_CODE,
120
- /** A column renamed because its name was taken. */
147
+ /**
148
+ * An attribute was renamed `<name>#<suffix>` because another attribute already has its name, for example
149
+ * two attributes declared with the same name.
150
+ */
121
151
  COLUMN_RENAMED: COLUMN_RENAMED_CODE,
122
- /** A column that lost its role because another column holds it. */
152
+ /**
153
+ * You read into a graph builder that already has an id, label or position attribute, so this file's one is kept as
154
+ * a plain attribute.
155
+ */
123
156
  ROLE_TAKEN: ROLE_TAKEN_CODE,
124
- /** The sink refused the direction. */
157
+ /**
158
+ * You read into a graph builder whose direction is already set, or which already holds edges, so the file is read
159
+ * with the builder's direction instead of its own.
160
+ */
125
161
  DIRECTION_REFUSED: DIRECTION_REFUSED_CODE,
126
- /** Edges forced to the policy's direction. */
162
+ /** Edges of the other direction were read with the direction `onMixedDirection` chose. */
127
163
  DIRECTION_FORCED: DIRECTION_FORCED_CODE,
128
- /** A common option CX has no use for. */
164
+ /** You set an option this format does not use; it had no effect. The message names the option. */
129
165
  OPTION_IGNORED: OPTION_IGNORED_CODE,
130
- /** A builder option the caller's sink does not honour. */
166
+ /**
167
+ * You read into your own graph builder, which was created with a different `addMissingNodes`, `duplicateEdges`,
168
+ * `selfLoops` or `weightDtype` than the option you passed; the builder's setting applies.
169
+ */
131
170
  SINK_OPTION: SINK_OPTION_CODE,
132
- /** The input is beyond a size limit (fatal). */
171
+ /** The input is larger than graph-io's size limit. The import stops. */
133
172
  TOO_LARGE: TOO_LARGE_CODE,
134
173
  });
135
174
  const USED_OPTIONS = new Set([
@@ -141,6 +180,7 @@ const USED_OPTIONS = new Set([
141
180
  "weightFrom",
142
181
  "weightDtype",
143
182
  "long",
183
+ "restoreMangledIds",
144
184
  "errorLimit",
145
185
  "signal",
146
186
  "onProgress",
@@ -156,12 +196,18 @@ const ABORT_CHECK_INTERVAL = 64;
156
196
  /** The numberVerification values a reader accepts: 2^48 - 1 and Java's Long.MAX_VALUE. */
157
197
  const NUMBER_VERIFICATION_VALUES = new Set(["281474976710655", "9223372036854775807"]);
158
198
  /** Old Cytoscape aspect names and the names they are read under. */
159
- const OLD_ASPECT_NAMES = {
160
- visualProperties: "cyVisualProperties",
161
- subNetworks: "cySubNetworks",
162
- networkRelations: "cyNetworkRelations",
163
- hiddenAttributes: "cyHiddenAttributes",
164
- };
199
+ const OLD_ASPECT_NAMES = new Map([
200
+ ["visualProperties", "cyVisualProperties"],
201
+ ["subNetworks", "cySubNetworks"],
202
+ ["networkRelations", "cyNetworkRelations"],
203
+ ["hiddenAttributes", "cyHiddenAttributes"],
204
+ ]);
205
+ /** The keys a nodes element defines. */
206
+ const NODE_KEYS = new Set(["@id", "n", "r"]);
207
+ /** The keys an edges element defines. */
208
+ const EDGE_KEYS = new Set(["@id", "s", "t", "i"]);
209
+ /** The tables cyTableColumn applies to. */
210
+ const TABLES = new Set(["node_table", "edge_table", "network_table"]);
165
211
  /** The aspects the importer reads into the graph (everything else is kept verbatim). */
166
212
  const READ_ASPECTS = new Set([
167
213
  "nodes",
@@ -200,6 +246,8 @@ const ALSO_KEPT = new Set([
200
246
  ]);
201
247
  /** Cytoscape's table-cell style aspects (both spellings): style rules, kept and reported, never read. */
202
248
  const TABLE_STYLE_ASPECTS = ["tableVisualProperties", "cyTableVisualProperties"];
249
+ /** The importer's own entries of meta.extra.cx, which an aspect of the same name would collide with. */
250
+ const OWN_EXTRA_KEYS = ["groups", "subnetwork"];
203
251
  /** The first keys that make a head CX for sure, and the other aspect names a CX document may start with. */
204
252
  const CX_FIRST_KEYS = ["numberVerification", "metaData"];
205
253
  const CX_ASPECT_KEYS = [
@@ -228,14 +276,14 @@ const CX_ASPECT_KEYS = [
228
276
  "functionTerms",
229
277
  ];
230
278
  /** The CX data types, with the Java reader's float aliases. */
231
- const SCALARS = {
232
- string: "string",
233
- boolean: "boolean",
234
- integer: "integer",
235
- long: "long",
236
- double: "double",
237
- float: "double",
238
- };
279
+ const SCALARS = new Map([
280
+ ["string", "string"],
281
+ ["boolean", "boolean"],
282
+ ["integer", "integer"],
283
+ ["long", "long"],
284
+ ["double", "double"],
285
+ ["float", "double"],
286
+ ]);
239
287
  /**
240
288
  * Resolve a `d` value.
241
289
  * @param d - the data type (undefined: string)
@@ -249,7 +297,7 @@ function cxType(d) {
249
297
  return null;
250
298
  }
251
299
  const list = d.startsWith("list_of_");
252
- const scalar = SCALARS[list ? d.slice("list_of_".length) : d];
300
+ const scalar = SCALARS.get(list ? d.slice("list_of_".length) : d);
253
301
  return scalar === undefined ? null : { scalar, list };
254
302
  }
255
303
  /**
@@ -367,7 +415,15 @@ function parseScalar(value, scalar, long, onPrecision) {
367
415
  if (value === "Infinity" || value === "-Infinity") {
368
416
  return Number(value);
369
417
  }
370
- return DECIMAL_TEXT.test(value) ? Number(value) : BAD;
418
+ if (!DECIMAL_TEXT.test(value)) {
419
+ return BAD;
420
+ }
421
+ const n = Number(value);
422
+ if (!Number.isFinite(n)) {
423
+ // beyond the range of a double: Java's parseDouble gives Infinity too, but say so
424
+ onPrecision(value);
425
+ }
426
+ return n;
371
427
  }
372
428
  }
373
429
  }
@@ -492,15 +548,16 @@ function aspect(doc, name) {
492
548
  * The inexact-literal bits of an element.
493
549
  * @param text - the element's JSON text
494
550
  * @param keys - the id keys to check, bit 1, 2, 4 in order
551
+ * @param depth - the depth of the element's own keys in the text (2: a member's single object)
495
552
  * @returns the bits
496
553
  */
497
- function inexactBits(text, keys) {
554
+ function inexactBits(text, keys, depth = 1) {
498
555
  if (!/[0-9][.eE]/.test(text)) {
499
556
  return 0;
500
557
  }
501
558
  let bits = 0;
502
559
  keys.forEach((key, i) => {
503
- if (inexactLiteral(text, key)) {
560
+ if (inexactLiteral(text, key, depth)) {
504
561
  bits |= 1 << i;
505
562
  }
506
563
  });
@@ -540,6 +597,85 @@ function checkNumberVerification(value, count, report, line) {
540
597
  : `numberVerification holds ${shown(raw === undefined ? null : digits)}, not 281474976710655`, { line, element: "numberVerification" });
541
598
  }
542
599
  }
600
+ /**
601
+ * Whether an aspect name is one CX (or NDEx, or Cytoscape) defines.
602
+ * @param name - the aspect name as written
603
+ * @returns true for a known aspect
604
+ */
605
+ function isCxAspect(name) {
606
+ return (READ_ASPECTS.has(name) ||
607
+ OLD_ASPECT_NAMES.has(name) ||
608
+ CX_ASPECT_KEYS.includes(name) ||
609
+ TABLE_STYLE_ASPECTS.includes(name));
610
+ }
611
+ /** The id keys of the aspects whose ids are checked for precision. */
612
+ const ID_KEYS = new Map([
613
+ ["nodes", ["@id"]],
614
+ ["edges", ["@id", "s", "t"]],
615
+ ]);
616
+ /**
617
+ * File an attribute element by its attribute name n; one without a usable name is reported and skipped.
618
+ * @param table - the aspect's attribute table
619
+ * @param name - the aspect
620
+ * @param held - the element
621
+ * @param report - the report
622
+ */
623
+ function fileAttribute(table, name, held, report) {
624
+ const { value, line } = held;
625
+ const n = isRecord(value) ? value.n : undefined;
626
+ if (n === undefined || n === null) {
627
+ report.error("missing-value", MISSING_ID_CODE, `a ${name} element has no attribute name n; skipped`, {
628
+ line,
629
+ element: name,
630
+ });
631
+ return;
632
+ }
633
+ if (typeof n !== "string" || n === "") {
634
+ report.error("validation-error", BAD_VALUE_CODE, `a ${name} element has the attribute name n ${shown(n)}, not a non-empty string; skipped`, { line, element: name });
635
+ return;
636
+ }
637
+ if (!table.has(n)) {
638
+ table.set(n, []);
639
+ }
640
+ table.get(n)?.push(held);
641
+ }
642
+ /**
643
+ * File one parsed aspect element: kept verbatim, checked (numberVerification), filed by attribute
644
+ * name (an attribute element without a usable name n is reported and skipped) or by aspect.
645
+ * @param state - the document being collected
646
+ * @param element - the element
647
+ */
648
+ function collectElement(state, element) {
649
+ const { doc, report } = state;
650
+ const { name, value, text, line, exact, depth } = element;
651
+ doc.structure.element(name, value, line);
652
+ if (ALSO_KEPT.has(name) || !READ_ASPECTS.has(name)) {
653
+ if (!doc.kept.has(name)) {
654
+ doc.kept.set(name, []);
655
+ }
656
+ // numberVerification holds Java's Long.MAX_VALUE in every Cytoscape file: checked, never warned
657
+ const onPrecision = name === "numberVerification" ? undefined : state.onKeptPrecision;
658
+ doc.kept.get(name)?.push(exact ? plainJson(value, onPrecision) : value);
659
+ }
660
+ if (!READ_ASPECTS.has(name) || name === "metaData" || name === "status") {
661
+ return;
662
+ }
663
+ if (name === "numberVerification") {
664
+ checkNumberVerification(value, ++state.verifications, report, line);
665
+ return;
666
+ }
667
+ const ids = ID_KEYS.get(name);
668
+ const held = { value, line, inexact: ids === undefined ? 0 : inexactBits(text, ids, depth) };
669
+ const table = attributeTable(doc, name);
670
+ if (table !== null) {
671
+ fileAttribute(table, name, held, report);
672
+ return;
673
+ }
674
+ if (!doc.aspects.has(name)) {
675
+ doc.aspects.set(name, []);
676
+ }
677
+ doc.aspects.get(name)?.push(held);
678
+ }
543
679
  /**
544
680
  * Read the whole document through the streaming scanner, checking its structure.
545
681
  * @param input - the input
@@ -548,76 +684,56 @@ function checkNumberVerification(value, count, report, line) {
548
684
  * @returns what was read
549
685
  */
550
686
  async function readDocument(input, report, options) {
551
- const structure = new CxStructure(report);
687
+ const structure = new CxStructure(report, (name) => OLD_ASPECT_NAMES.get(name) ?? name);
552
688
  const doc = {
553
689
  aspects: new Map(),
554
690
  attributes: { node: new Map(), edge: new Map(), network: new Map() },
555
691
  kept: new Map(),
556
692
  structure,
557
693
  };
694
+ const state = {
695
+ doc,
696
+ report,
697
+ verifications: 0,
698
+ onKeptPrecision: keptPrecision(report, "meta.extra.cx"),
699
+ };
558
700
  let sinceCheck = 0;
559
- let verifications = 0;
701
+ let members = 0;
702
+ let known = 0;
703
+ const unknownNames = new Set();
704
+ const lastMember = { block: -1 };
705
+ const countMember = (block, name) => {
706
+ if (block !== lastMember.block) {
707
+ lastMember.block = block;
708
+ members++;
709
+ }
710
+ if (name !== null && isCxAspect(name)) {
711
+ known++;
712
+ }
713
+ else if (name !== null) {
714
+ unknownNames.add(name);
715
+ }
716
+ };
560
717
  const canonical = (name, line) => {
561
- const renamed = OLD_ASPECT_NAMES[name];
718
+ const renamed = OLD_ASPECT_NAMES.get(name);
562
719
  if (renamed === undefined) {
563
720
  return name;
564
721
  }
565
722
  report.warnOnce("coercion", CX_ISSUE.OLD_ASPECT_NAME, `the old aspect name "${name}" is read as "${renamed}"`, { line, element: name }, `${CX_ISSUE.OLD_ASPECT_NAME}:${name}`);
566
723
  return renamed;
567
724
  };
568
- const collect = (name, value, text, line, exact) => {
569
- structure.element(name, value, line);
570
- if (ALSO_KEPT.has(name) || !READ_ASPECTS.has(name)) {
571
- if (!doc.kept.has(name)) {
572
- doc.kept.set(name, []);
573
- }
574
- doc.kept.get(name)?.push(exact ? plainJson(value) : value);
575
- }
576
- if (!READ_ASPECTS.has(name) || name === "metaData" || name === "status") {
577
- return;
578
- }
579
- if (name === "numberVerification") {
580
- checkNumberVerification(value, ++verifications, report, line);
581
- return;
582
- }
583
- let inexact = 0;
584
- if (name === "nodes") {
585
- inexact = inexactBits(text, ["@id"]);
586
- }
587
- else if (name === "edges") {
588
- inexact = inexactBits(text, ["@id", "s", "t"]);
589
- }
590
- const held = { value, line, inexact };
591
- const table = attributeTable(doc, name);
592
- if (table !== null) {
593
- if (!isRecord(value) || typeof value.n !== "string") {
594
- report.error("missing-value", MISSING_ID_CODE, `a ${name} element has no attribute name n; skipped`, {
595
- line,
596
- element: name,
597
- });
598
- return;
599
- }
600
- const key = value.n;
601
- if (!table.has(key)) {
602
- table.set(key, []);
603
- }
604
- table.get(key)?.push(held);
605
- return;
606
- }
607
- if (!doc.aspects.has(name)) {
608
- doc.aspects.set(name, []);
609
- }
610
- doc.aspects.get(name)?.push(held);
725
+ const collect = (name, value, text, line, exact, depth = 1) => {
726
+ collectElement(state, { name, value, text, line, exact, depth });
611
727
  };
612
728
  try {
613
- for await (const event of scanAspects(textChunks(input, report, options))) {
729
+ for await (const event of scanAspects(textChunks(input, report, options), report)) {
614
730
  if (++sinceCheck >= ABORT_CHECK_INTERVAL) {
615
731
  sinceCheck = 0;
616
732
  throwIfAborted(options.signal);
617
733
  }
618
734
  switch (event.kind) {
619
735
  case "root":
620
- report.fail(CX_ISSUE.NOT_CX, `a CX document is a JSON array of aspects; found ${isRecord(event.value) ? "an object" : shown(event.value)}`, { line: event.line });
736
+ report.fail(CX_ISSUE.NOT_CX, `a CX document is a JSON array of aspects; found ${event.object ? "an object" : shown(event.value)}`, { line: event.line });
621
737
  break;
622
738
  case "member": {
623
739
  const { value } = event;
@@ -626,27 +742,35 @@ async function readDocument(input, report, options) {
626
742
  }
627
743
  const keys = isRecord(value) ? Object.keys(value) : [];
628
744
  if (isRecord(value) && keys.length === 1 && isRecord(value[keys[0]])) {
745
+ countMember(event.block, keys[0]);
629
746
  const name = canonical(keys[0], event.line);
747
+ if (isCxAspect(name)) {
748
+ report.warning("validation-error", CX_ISSUE.SINGLE_OBJECT_ASPECT, `the "${keys[0]}" aspect is one object, not an array of elements; read as one element`, { line: event.line, element: keys[0] });
749
+ }
630
750
  structure.block(name, event.line);
631
- collect(name, value[keys[0]], "", event.line, event.exact);
751
+ collect(name, value[keys[0]], event.text, event.line, event.exact, 2);
632
752
  break;
633
753
  }
754
+ countMember(event.block, null);
634
755
  report.error("parse-error", BAD_ASPECT_BLOCK_CODE, `a member of the document is ${keys.length > 1 ? `an object with ${keys.length} keys (${keys.join(", ")})` : shown(value)}, not a one-key aspect fragment; skipped`, { line: event.line, element: keys[0] ?? null });
635
756
  break;
636
757
  }
637
758
  case "block":
759
+ countMember(event.block, event.aspect);
760
+ reportSharedBlock(report, event);
638
761
  structure.block(canonical(event.aspect, event.line), event.line);
639
762
  break;
640
763
  case "element": {
641
- const name = OLD_ASPECT_NAMES[event.aspect] ?? event.aspect;
764
+ const name = OLD_ASPECT_NAMES.get(event.aspect) ?? event.aspect;
642
765
  collect(name, event.value, event.text, event.line, event.exact);
643
766
  break;
644
767
  }
645
768
  case "deep":
769
+ countMember(event.block, event.aspect);
646
770
  reportTooDeep(report, event);
647
771
  break;
648
772
  case "extraKeys":
649
- report.error("parse-error", BAD_ASPECT_BLOCK_CODE, `the "${event.aspect}" fragment holds more keys (${event.keys.join(", ")}); a fragment has one key, the others are skipped`, { line: event.line, element: event.aspect });
773
+ report.error("parse-error", BAD_ASPECT_BLOCK_CODE, `the "${event.aspect}" fragment holds more keys (${event.keys.join(", ")}) whose values are not arrays; a fragment has one key, the others are skipped`, { line: event.line, element: event.aspect });
650
774
  break;
651
775
  default:
652
776
  break;
@@ -659,6 +783,15 @@ async function readDocument(input, report, options) {
659
783
  }
660
784
  throw err;
661
785
  }
786
+ if (members > 0 && known === 0) {
787
+ const found = [...unknownNames].slice(0, 5).map((n) => JSON.stringify(n));
788
+ const hint = unknownNames.has("data") ? ": Cytoscape.js elements?" : "";
789
+ const seen = found.length > 0 ? ` (found ${found.join(", ")}${hint})` : "";
790
+ report.fail(CX_ISSUE.NOT_CX, `no member of the array is a CX aspect${seen}; the input is not CX`);
791
+ }
792
+ if (structure.hasStatus && !structure.statusWellFormed()) {
793
+ report.error("validation-error", BAD_VALUE_CODE, `the status element is ${shown(structure.status ?? null)}, not an object with a boolean success; ignored`, { element: "status" });
794
+ }
662
795
  structure.checkCounts();
663
796
  return doc;
664
797
  }
@@ -676,11 +809,13 @@ function refId(raw) {
676
809
  }
677
810
  }
678
811
  /**
679
- * A member list: the ids of an array, or null for "all".
812
+ * A member list: the ids of an array, or null for "all" (an absent list is empty).
680
813
  * @param raw - the list
814
+ * @param onBad - told about a list that is neither an array nor "all" (BAD_VALUE) and about a
815
+ * member that is not an id (INVALID_ID)
681
816
  * @returns the set, or null for every element
682
817
  */
683
- function memberSet(raw) {
818
+ function memberSet(raw, onBad) {
684
819
  if (raw === "all") {
685
820
  return null;
686
821
  }
@@ -688,86 +823,64 @@ function memberSet(raw) {
688
823
  if (Array.isArray(raw)) {
689
824
  for (const item of raw) {
690
825
  const id = refId(item);
691
- if (id !== null) {
826
+ if (id === null) {
827
+ onBad?.(CX_ISSUE.INVALID_ID, `the member ${shown(item)} is not an id; ignored`);
828
+ }
829
+ else {
692
830
  out.add(id);
693
831
  }
694
832
  }
695
833
  }
834
+ else if (raw !== undefined) {
835
+ onBad?.(BAD_VALUE_CODE, `the member list ${shown(raw)} is neither a list of ids nor "all"; read as empty`);
836
+ }
696
837
  return out;
697
838
  }
698
839
  /**
699
- * The graphs of a document, in the order Cytoscape opens them.
700
- * @param doc - the document
701
- * @returns one plan per graph (at least one)
840
+ * The id of an element of a structural aspect (cySubNetworks, cyViews, cyNetworkRelations), or
841
+ * null with an issue: E_BAD_ASPECT_BLOCK for an element that is not an object, E_MISSING_ID or
842
+ * E_INVALID_ID for its id.
843
+ * @param held - the element
844
+ * @param name - the aspect
845
+ * @param key - the id key
846
+ * @param report - the report
847
+ * @returns the element and its id, or null
702
848
  */
703
- function graphPlans(doc) {
704
- const subs = new Map();
705
- for (const { value } of aspect(doc, "cySubNetworks")) {
706
- const id = isRecord(value) ? refId(value["@id"]) : null;
707
- if (id !== null && isRecord(value) && !subs.has(id)) {
708
- subs.set(id, { nodes: memberSet(value.nodes), edges: memberSet(value.edges) });
709
- }
710
- }
711
- const relationNames = new Map();
712
- const order = [];
713
- const views = new Map();
714
- const addView = (sub, view) => {
715
- const list = views.get(sub) ?? [];
716
- if (!list.includes(view)) {
717
- list.push(view);
718
- }
719
- views.set(sub, list);
720
- };
721
- for (const { value } of aspect(doc, "cyNetworkRelations")) {
722
- if (!isRecord(value)) {
723
- continue;
724
- }
725
- const child = refId(value.c);
726
- if (child === null) {
727
- continue;
728
- }
729
- if (value.r === "view") {
730
- const parent = refId(value.p);
731
- if (parent !== null) {
732
- addView(parent, child);
733
- }
734
- continue;
735
- }
736
- if (typeof value.name === "string") {
737
- relationNames.set(child, value.name);
738
- }
739
- if (subs.has(child) && !order.includes(child)) {
740
- order.push(child);
741
- }
849
+ function structuralId(held, name, key, report) {
850
+ const { value, line } = held;
851
+ if (!isRecord(value)) {
852
+ report.error("parse-error", BAD_ASPECT_BLOCK_CODE, `a ${name} element is ${shown(value)}, not an object; skipped`, {
853
+ line,
854
+ element: name,
855
+ });
856
+ return null;
742
857
  }
743
- for (const { value } of aspect(doc, "cyViews")) {
744
- const view = isRecord(value) ? refId(value["@id"]) : null;
745
- const sub = isRecord(value) ? refId(value.s) : null;
746
- if (view !== null && sub !== null) {
747
- addView(sub, view);
748
- }
858
+ if (value[key] === undefined || value[key] === null) {
859
+ report.error("missing-value", MISSING_ID_CODE, `a ${name} element has no ${key}; skipped`, {
860
+ line,
861
+ element: name,
862
+ });
863
+ return null;
749
864
  }
750
- for (const id of subs.keys()) {
751
- if (!order.includes(id)) {
752
- order.push(id);
753
- }
865
+ const id = refId(value[key]);
866
+ if (id === null) {
867
+ report.error("validation-error", CX_ISSUE.INVALID_ID, `a ${name} element has the ${key} ${shown(value[key])}, not an integer id; skipped`, {
868
+ line,
869
+ element: name,
870
+ });
871
+ return null;
754
872
  }
755
- const networkName = (sub) => {
756
- let found = null;
757
- for (const { value } of doc.attributes.network.get("name") ?? []) {
758
- if (!isRecord(value) || typeof value.v !== "string") {
759
- continue;
760
- }
761
- const scope = value.s === undefined ? null : refId(value.s);
762
- if (scope === sub) {
763
- return value.v;
764
- }
765
- if (scope === null) {
766
- found ?? (found = value.v);
767
- }
768
- }
769
- return found;
770
- };
873
+ return { value, id };
874
+ }
875
+ /**
876
+ * The graphs of a document, in the order Cytoscape opens them.
877
+ * @param doc - the document
878
+ * @param report - where malformed subnetworks, views and relations are recorded
879
+ * @returns one plan per graph (at least one)
880
+ */
881
+ function graphPlans(doc, report) {
882
+ const subs = readSubnetworks(doc, report);
883
+ const { relationNames, order, views } = readRelations(doc, subs, report);
771
884
  if (order.length <= 1) {
772
885
  const sub = order.length === 1 ? order[0] : null;
773
886
  const members = sub === null ? undefined : subs.get(sub);
@@ -778,10 +891,11 @@ function graphPlans(doc) {
778
891
  if (graphViews.length === 0) {
779
892
  graphViews = layoutViews(doc);
780
893
  }
894
+ const relationName = sub === null ? null : (relationNames.get(sub) ?? null);
781
895
  return [
782
896
  {
783
897
  index: 0,
784
- name: (sub === null ? null : (relationNames.get(sub) ?? null)) ?? networkName(sub),
898
+ name: relationName ?? networkName(doc, sub),
785
899
  subnetwork: sub,
786
900
  nodes: members?.nodes ?? null,
787
901
  edges: members?.edges ?? null,
@@ -791,13 +905,125 @@ function graphPlans(doc) {
791
905
  }
792
906
  return order.map((sub, index) => ({
793
907
  index,
794
- name: relationNames.get(sub) ?? networkName(sub),
908
+ name: relationNames.get(sub) ?? networkName(doc, sub),
795
909
  subnetwork: sub,
796
910
  nodes: subs.get(sub)?.nodes ?? null,
797
911
  edges: subs.get(sub)?.edges ?? null,
798
912
  views: views.get(sub) ?? [],
799
913
  }));
800
914
  }
915
+ /**
916
+ * The subnetworks a document declares (cySubNetworks), by id; a repeated id is reported and skipped.
917
+ * @param doc - the document
918
+ * @param report - the report
919
+ * @returns the subnetworks
920
+ */
921
+ function readSubnetworks(doc, report) {
922
+ const subs = new Map();
923
+ for (const held of aspect(doc, "cySubNetworks")) {
924
+ const sub = structuralId(held, "cySubNetworks", "@id", report);
925
+ if (sub === null) {
926
+ continue;
927
+ }
928
+ const { value, id } = sub;
929
+ if (subs.has(id)) {
930
+ report.error("validation-error", BAD_ASPECT_BLOCK_CODE, `a second cySubNetworks element has the @id ${String(id)}; skipped, the first is used`, { line: held.line, element: "cySubNetworks" });
931
+ continue;
932
+ }
933
+ const onBad = (code, message) => {
934
+ report.error("validation-error", code, `subnetwork ${String(id)}: ${message}`, {
935
+ line: held.line,
936
+ element: "cySubNetworks",
937
+ });
938
+ };
939
+ subs.set(id, { nodes: memberSet(value.nodes, onBad), edges: memberSet(value.edges, onBad) });
940
+ }
941
+ return subs;
942
+ }
943
+ /**
944
+ * The network relations of a document: each subnetwork's name, the order Cytoscape opens them in
945
+ * (the relations' order, then the other subnetworks), and each one's views.
946
+ * @param doc - the document
947
+ * @param subs - the subnetworks
948
+ * @param report - the report
949
+ * @returns the names, the order and the views
950
+ */
951
+ function readRelations(doc, subs, report) {
952
+ const relationNames = new Map();
953
+ const order = [];
954
+ const views = new Map();
955
+ const addView = (sub, view) => {
956
+ const list = views.get(sub) ?? [];
957
+ if (!list.includes(view)) {
958
+ list.push(view);
959
+ }
960
+ views.set(sub, list);
961
+ };
962
+ for (const held of aspect(doc, "cyNetworkRelations")) {
963
+ const relation = structuralId(held, "cyNetworkRelations", "c", report);
964
+ if (relation !== null) {
965
+ fileRelation(relation, subs, { relationNames, order, addView });
966
+ }
967
+ }
968
+ for (const held of aspect(doc, "cyViews")) {
969
+ const view = structuralId(held, "cyViews", "@id", report);
970
+ const sub = view === null ? null : refId(view.value.s);
971
+ if (view !== null && sub !== null) {
972
+ addView(sub, view.id);
973
+ }
974
+ }
975
+ order.push(...[...subs.keys()].filter((id) => !order.includes(id)));
976
+ return { relationNames, order, views };
977
+ }
978
+ /**
979
+ * File one network relation: a view of a subnetwork, or a subnetwork's name and its place in the order.
980
+ * @param relation - the relation element and its child id
981
+ * @param relation.value - the element
982
+ * @param relation.id - the child: a view or a subnetwork
983
+ * @param subs - the subnetworks
984
+ * @param out - where the relation is filed
985
+ * @param out.relationNames - each subnetwork's name
986
+ * @param out.order - the subnetworks in relation order
987
+ * @param out.addView - records a view of a subnetwork
988
+ */
989
+ function fileRelation(relation, subs, out) {
990
+ const { value, id: child } = relation;
991
+ if (value.r === "view") {
992
+ const parent = refId(value.p);
993
+ if (parent !== null) {
994
+ out.addView(parent, child);
995
+ }
996
+ return;
997
+ }
998
+ if (typeof value.name === "string") {
999
+ out.relationNames.set(child, value.name);
1000
+ }
1001
+ if (subs.has(child) && !out.order.includes(child)) {
1002
+ out.order.push(child);
1003
+ }
1004
+ }
1005
+ /**
1006
+ * A subnetwork's name from the network attributes: the one scoped to it, else the unscoped one.
1007
+ * @param doc - the document
1008
+ * @param sub - the subnetwork, or null
1009
+ * @returns the name, or null
1010
+ */
1011
+ function networkName(doc, sub) {
1012
+ let found = null;
1013
+ for (const { value } of doc.attributes.network.get("name") ?? []) {
1014
+ if (!isRecord(value) || typeof value.v !== "string") {
1015
+ continue;
1016
+ }
1017
+ const scope = value.s === undefined ? null : refId(value.s);
1018
+ if (scope === sub) {
1019
+ return value.v;
1020
+ }
1021
+ if (scope === null) {
1022
+ found ?? (found = value.v);
1023
+ }
1024
+ }
1025
+ return found;
1026
+ }
801
1027
  /**
802
1028
  * The distinct views the layout entries name, in order of appearance.
803
1029
  * @param doc - the document
@@ -838,10 +1064,14 @@ class CxReader {
838
1064
  this.rootNodes = new Set();
839
1065
  /** Every edge id of the root network. */
840
1066
  this.rootEdges = new Set();
841
- /** CX node id -> sink node index, for this graph. */
1067
+ /** CX node id (as written, before the `ids` option) -> sink node index, for this graph. */
842
1068
  this.nodeRows = new Map();
843
- /** CX edge id -> sink edge index, for this graph. */
1069
+ /** Sink node id -> sink node index: two CX ids the `ids` option makes one are one node. */
1070
+ this.sinkRows = new Map();
1071
+ /** CX edge id (as written) -> sink edge index, for this graph. */
844
1072
  this.edgeRows = new Map();
1073
+ /** CX node id -> the id given to the sink, for the nodes whose original id was restored. */
1074
+ this.sinkIds = new Map();
845
1075
  /** The subnetwork ids of the document. */
846
1076
  this.subnetworks = new Set();
847
1077
  this.dangling = new Map();
@@ -854,8 +1084,8 @@ class CxReader {
854
1084
  this.weighted = false;
855
1085
  /** The core field values written per row (n, r), to detect a differing name attribute. */
856
1086
  this.coreValues = new Map();
857
- /** The weight attribute values of the edges, by edge id (built once). */
858
- this.weights = null;
1087
+ /** The weight attribute values of the edges, by edge id. */
1088
+ this.weights = new Map();
859
1089
  this.sink = sink;
860
1090
  this.report = report;
861
1091
  this.options = options;
@@ -889,6 +1119,7 @@ class CxReader {
889
1119
  this.direction.setHeader(true);
890
1120
  this.checkMembers();
891
1121
  this.checkRootOnly();
1122
+ this.checkTableColumns();
892
1123
  const nodeColumns = this.declareAttributes("node");
893
1124
  this.readNodes();
894
1125
  this.readGroups();
@@ -901,7 +1132,7 @@ class CxReader {
901
1132
  this.readVisualProperties();
902
1133
  this.readProvenance();
903
1134
  for (const [kind, count] of this.dangling) {
904
- report.warning("validation-error", DANGLING_REFERENCE_CODE, `${count} ${kind}(s) name nothing; ignored`, {
1135
+ report.warning("validation-error", DANGLING_REFERENCE_CODE, `${count} ${kind}${plural(count)} ${agree(count, "names", "name")} nothing; ignored`, {
905
1136
  element: kind,
906
1137
  });
907
1138
  }
@@ -930,15 +1161,6 @@ class CxReader {
930
1161
  inGraph(id) {
931
1162
  return this.plan.nodes === null || this.plan.nodes.has(id);
932
1163
  }
933
- /**
934
- * The key of the node and edge row maps for a CX id as an aspect writes it: the id the `ids`
935
- * option makes of it, which is what idOf() stored the element under.
936
- * @param raw - the CX id
937
- * @returns the row key
938
- */
939
- key(raw) {
940
- return this.options.ids === "keep" ? raw : this.coercer.value(raw);
941
- }
942
1164
  /**
943
1165
  * Whether a scoped value applies to this graph: unscoped, or scoped to its subnetwork. A scope
944
1166
  * naming no subnetwork is counted as dangling.
@@ -959,41 +1181,97 @@ class CxReader {
959
1181
  return 0;
960
1182
  }
961
1183
  /**
962
- * The id of an element, by the CX id rule and the `ids` option.
1184
+ * The id of an element by the CX id rule, as the document's references name it (the `ids`
1185
+ * option applies only to the id the sink holds, sinkId()).
963
1186
  * @param raw - the parsed id
964
1187
  * @param inexact - whether the literal was not a plain integer
965
1188
  * @param element - the element name for issues
1189
+ * @param line - the element's line
966
1190
  * @param edgeId - whether this is an edge's own id (stored in the f64 id column, not kept as digits)
967
1191
  * @returns the id, or null when it was reported
968
1192
  */
969
- idOf(raw, inexact, element, edgeId = false) {
1193
+ idOf(raw, inexact, element, line, edgeId = false) {
970
1194
  try {
971
1195
  const parsed = cxId(raw, inexact);
972
1196
  if (parsed.note === "text") {
973
- this.report.warnOnce("coercion", ID_TEXT_TYPE_CODE, `${element}: the id ${shown(raw)} is not written as an integer; read as ${String(parsed.id)}`, { element });
1197
+ this.report.warnOnce("coercion", ID_TEXT_TYPE_CODE, `${element}: the id ${shown(raw)} is not written as an integer; read as ${String(parsed.id)}`, { line, element });
974
1198
  }
975
1199
  else if (parsed.note === "precision" && edgeId) {
976
1200
  // the edge is told apart by its digits, but the id column is f64
977
- this.report.warnOnce("precision", PRECISION_CODE, `${element}: the edge id ${String(parsed.id)} is beyond 2^53; the id column holds the nearest double`, { element }, `${PRECISION_CODE}:edge`);
1201
+ this.report.warnOnce("precision", PRECISION_CODE, `${element}: the edge id ${String(parsed.id)} is beyond 2^53; the id column holds the nearest double`, { line, element }, `${PRECISION_CODE}:edge`);
978
1202
  }
979
1203
  else if (parsed.note === "precision") {
980
1204
  this.idType = "mixed";
981
- this.report.warnOnce("precision", PRECISION_CODE, `${element}: the id ${String(parsed.id)} is beyond 2^53; kept as its digits (a string id)`, { element });
1205
+ this.report.warnOnce("precision", PRECISION_CODE, `${element}: the id ${String(parsed.id)} is beyond 2^53; kept as its digits (a string id)`, { line, element });
982
1206
  }
983
- return this.options.ids === "keep" ? parsed.id : this.coercer.value(parsed.id);
1207
+ return parsed.id;
984
1208
  }
985
1209
  catch (err) {
986
- this.report.recordError(err, { element });
1210
+ this.report.recordError(err, { line, element });
987
1211
  return null;
988
1212
  }
989
1213
  }
1214
+ /**
1215
+ * The id the sink holds a CX node under: its restored original id, else the `ids` option applied.
1216
+ * @param id - the CX id
1217
+ * @returns the sink id
1218
+ */
1219
+ sinkId(id) {
1220
+ const original = this.sinkIds.get(id);
1221
+ if (original !== undefined) {
1222
+ return original;
1223
+ }
1224
+ return this.options.ids === "keep" ? id : this.coercer.value(id);
1225
+ }
1226
+ /**
1227
+ * Add a node to the sink (once per sink id) and map its CX id to the row.
1228
+ * @param id - the CX id
1229
+ * @returns the row, and whether the sink id was new
1230
+ */
1231
+ addNode(id) {
1232
+ const sinkId = this.sinkId(id);
1233
+ const existing = this.sinkRows.get(sinkId);
1234
+ if (existing !== undefined) {
1235
+ this.nodeRows.set(id, existing);
1236
+ return { row: existing, added: false };
1237
+ }
1238
+ const row = this.sink.addNode(sinkId);
1239
+ this.sinkRows.set(sinkId, row);
1240
+ this.nodeRows.set(id, row);
1241
+ return { row, added: true };
1242
+ }
990
1243
  /**
991
1244
  * Record a long value beyond 2^53 (once).
992
1245
  * @param name - the attribute
993
1246
  * @param digits - the value
994
1247
  */
995
1248
  precision(name, digits) {
996
- this.report.warnOnce("precision", PRECISION_CODE, `"${name}": ${digits} is beyond 2^53; stored as the nearest double`, { element: name }, `${PRECISION_CODE}:value`);
1249
+ const overflow = !Number.isFinite(Number(digits));
1250
+ this.report.warnOnce("precision", PRECISION_CODE, overflow
1251
+ ? `"${name}": ${digits} is beyond the range of a double; stored as ${String(Number(digits))}`
1252
+ : `"${name}": ${digits} is beyond 2^53; stored as the nearest double`, { element: name }, `${PRECISION_CODE}:value`);
1253
+ }
1254
+ /**
1255
+ * Report the keys of a node or edge element CX does not define, once per key.
1256
+ * @param record - the element
1257
+ * @param known - the keys it defines
1258
+ * @param what - nodes or edges
1259
+ * @param line - its line
1260
+ */
1261
+ unknownKeys(record, known, what, line) {
1262
+ for (const key of Object.keys(record)) {
1263
+ if (!known.has(key)) {
1264
+ this.report.warnOnce("unsupported", UNKNOWN_ELEMENT_CODE, `the ${what} key "${key}" is not defined by CX; its values are not read`, { line, element: key }, `${UNKNOWN_ELEMENT_CODE}:${what}:${key}`);
1265
+ }
1266
+ }
1267
+ }
1268
+ /** Report cyTableColumn entries for a table CX does not define (once per table name). */
1269
+ checkTableColumns() {
1270
+ for (const { value, line } of aspect(this.doc, "cyTableColumn")) {
1271
+ if (isRecord(value) && typeof value.applies_to === "string" && !TABLES.has(value.applies_to)) {
1272
+ this.report.warnOnce("unsupported", UNKNOWN_ELEMENT_CODE, `a cyTableColumn entry applies to "${value.applies_to}", which is not node_table, edge_table or network_table; ignored`, { line, element: value.applies_to }, `${UNKNOWN_ELEMENT_CODE}:table:${value.applies_to}`);
1273
+ }
1274
+ }
997
1275
  }
998
1276
  /** Count the subnetwork members that name no node or edge. */
999
1277
  checkMembers() {
@@ -1037,7 +1315,7 @@ class CxReader {
1037
1315
  const nodes = lost("nodes", this.rootNodes);
1038
1316
  const edges = lost("edges", this.rootEdges);
1039
1317
  if (nodes + edges > 0) {
1040
- this.report.warning("unsupported", CX_ISSUE.ROOT_ONLY, `${nodes} node(s) and ${edges} edge(s) of the root network belong to no subnetwork (cySubNetworks); they are not read`, { element: "cySubNetworks" });
1318
+ this.report.warning("unsupported", CX_ISSUE.ROOT_ONLY, `${nodes} node${plural(nodes)} and ${edges} edge${plural(edges)} of the root network belong to no subnetwork (cySubNetworks); they are not read`, { element: "cySubNetworks" });
1041
1319
  }
1042
1320
  }
1043
1321
  /**
@@ -1063,7 +1341,8 @@ class CxReader {
1063
1341
  const declared = this.tableColumns(domain === "node" ? "node_table" : "edge_table");
1064
1342
  const names = new Set([...declared.keys(), ...this.doc.attributes[domain].keys()]);
1065
1343
  for (const name of names) {
1066
- if (name === "" || (domain === "edge" && name === this.options.weightFrom)) {
1344
+ if ((domain === "edge" && name === this.options.weightFrom) ||
1345
+ (domain === "node" && name === ORIGINAL_ID_ATTRIBUTE && this.options.restoreMangledIds)) {
1067
1346
  continue;
1068
1347
  }
1069
1348
  const types = [];
@@ -1144,7 +1423,10 @@ class CxReader {
1144
1423
  continue;
1145
1424
  }
1146
1425
  const type = cxType(value.d);
1147
- if (type !== null) {
1426
+ if (type === null) {
1427
+ this.report.warnOnce("unsupported", UNKNOWN_ATTR_TYPE_CODE, `cyTableColumn declares "${value.n}" with the data type ${shown(value.d)}, which CX does not define; its values are typed by their own d`, { element: value.n }, `${UNKNOWN_ATTR_TYPE_CODE}:table:${table}:${value.n}`);
1428
+ }
1429
+ else {
1148
1430
  out.set(value.n, type);
1149
1431
  }
1150
1432
  }
@@ -1196,14 +1478,6 @@ class CxReader {
1196
1478
  if (scope === 0) {
1197
1479
  continue;
1198
1480
  }
1199
- const target = refId(value.po);
1200
- const row = target === null ? undefined : rows.get(this.key(target));
1201
- if (row === undefined) {
1202
- if (target === null || !root.has(target)) {
1203
- this.dangle(`${domain} attribute target`);
1204
- }
1205
- continue;
1206
- }
1207
1481
  if (value.v === undefined) {
1208
1482
  this.report.error("missing-value", BAD_VALUE_CODE, `a ${domain}Attributes element for "${column.name}" has no v; skipped`, {
1209
1483
  line,
@@ -1211,18 +1485,29 @@ class CxReader {
1211
1485
  });
1212
1486
  continue;
1213
1487
  }
1214
- if (value.v === null) {
1215
- continue;
1216
- }
1217
- const previous = chosen.get(row);
1218
- if (previous !== undefined && previous.scope > scope) {
1219
- continue;
1220
- }
1221
- if (previous?.scope === scope &&
1222
- JSON.stringify(previous.value) !== JSON.stringify(plainJson(value.v))) {
1223
- this.report.warnOnce("validation-error", DUPLICATE_ATTRIBUTE_CODE, `${domain} ${shown(value.po)} has the attribute "${column.name}" twice; the later value wins`, { line, element: column.name }, `${DUPLICATE_ATTRIBUTE_CODE}:${domain}:${column.name}`);
1488
+ // po names one element, or (cxio's shared values) a list of them
1489
+ for (const raw of Array.isArray(value.po) ? value.po : [value.po]) {
1490
+ const target = refId(raw);
1491
+ const row = target === null ? undefined : rows.get(target);
1492
+ if (row === undefined) {
1493
+ if (target === null || !root.has(target)) {
1494
+ this.dangle(`${domain} attribute target`);
1495
+ }
1496
+ continue;
1497
+ }
1498
+ if (value.v === null) {
1499
+ continue;
1500
+ }
1501
+ const previous = chosen.get(row);
1502
+ if (previous !== undefined && previous.scope > scope) {
1503
+ continue;
1504
+ }
1505
+ if (previous?.scope === scope &&
1506
+ JSON.stringify(previous.value) !== JSON.stringify(plainJson(value.v))) {
1507
+ this.report.warnOnce("validation-error", DUPLICATE_ATTRIBUTE_CODE, `${domain} ${shown(raw)} has the attribute "${column.name}" twice; the later value wins`, { line, element: column.name }, `${DUPLICATE_ATTRIBUTE_CODE}:${domain}:${column.name}`);
1508
+ }
1509
+ chosen.set(row, { scope, value: value.v, d: value.d, line });
1224
1510
  }
1225
- chosen.set(row, { scope, value: value.v, d: value.d, line });
1226
1511
  }
1227
1512
  for (const [row, { value, d, line }] of chosen) {
1228
1513
  this.writeCell(domain, column, row, value, d, line);
@@ -1264,6 +1549,10 @@ class CxReader {
1264
1549
  }
1265
1550
  let { handle } = column;
1266
1551
  if (this.isStructural(domain, column.name)) {
1552
+ if (Array.isArray(raw)) {
1553
+ this.report.error("validation-error", BAD_VALUE_CODE, `${element}: ${shown(raw)} is a list; "${column.name}" holds one string, the cell is unset`, { line, element: column.name });
1554
+ return;
1555
+ }
1267
1556
  handle = this.coreColumn(domain, column.name);
1268
1557
  const prior = this.coreValue(domain, row, column.name);
1269
1558
  if (prior !== undefined && prior !== value) {
@@ -1329,7 +1618,8 @@ class CxReader {
1329
1618
  // ------------------------------------------------------------ nodes, groups, layout
1330
1619
  /** Read the nodes of this graph. */
1331
1620
  readNodes() {
1332
- const { report, sink } = this;
1621
+ const { report } = this;
1622
+ const originals = this.originalIds();
1333
1623
  for (const held of aspect(this.doc, "nodes")) {
1334
1624
  this.checkAbort();
1335
1625
  const { value, line } = held;
@@ -1346,36 +1636,61 @@ class CxReader {
1346
1636
  report.counts.skippedNodes++;
1347
1637
  continue;
1348
1638
  }
1639
+ this.unknownKeys(value, NODE_KEYS, "node", line);
1349
1640
  const element = `node ${shown(value["@id"])}`;
1350
- const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element);
1641
+ const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element, line);
1351
1642
  if (id === null) {
1352
1643
  report.counts.skippedNodes++;
1353
1644
  continue;
1354
1645
  }
1355
- // membership lists hold CX ids as written, before the ids option coerces them
1356
- if (!this.inGraph(refId(value["@id"]) ?? id)) {
1646
+ if (!this.inGraph(id)) {
1357
1647
  continue;
1358
1648
  }
1359
1649
  let row = this.nodeRows.get(id);
1360
- if (row !== undefined) {
1361
- report.warning("merged", DUPLICATE_NODE_CODE, `${element} is declared more than once; its fields are merged (the later values win)`, { line, element });
1362
- }
1363
- else {
1650
+ let added = false;
1651
+ if (row === undefined) {
1652
+ const original = originals.get(refId(value["@id"]) ?? id);
1653
+ if (original !== undefined) {
1654
+ this.idType = "mixed";
1655
+ this.sinkIds.set(id, original);
1656
+ }
1364
1657
  try {
1365
- row = sink.addNode(id);
1658
+ ({ row, added } = this.addNode(id));
1366
1659
  }
1367
1660
  catch (err) {
1368
1661
  report.recordError(err, { line, element });
1369
1662
  report.counts.skippedNodes++;
1370
1663
  continue;
1371
1664
  }
1372
- this.nodeRows.set(id, row);
1665
+ }
1666
+ if (added) {
1373
1667
  report.counts.nodes++;
1374
1668
  }
1669
+ else {
1670
+ report.warning("merged", DUPLICATE_NODE_CODE, `${element} is declared more than once; its fields are merged (the later values win)`, { line, element });
1671
+ }
1375
1672
  this.writeCore("node", row, "name", value.n, element, line);
1376
1673
  this.writeCore("node", row, "represents", value.r, element, line);
1377
1674
  }
1378
1675
  }
1676
+ /**
1677
+ * The original ids the exporter's `sanitizeIds: "mangle"` kept in the `graphty:originalId`
1678
+ * attribute, when they are to be restored.
1679
+ * @returns CX node id -> original id
1680
+ */
1681
+ originalIds() {
1682
+ const out = new Map();
1683
+ if (!this.options.restoreMangledIds) {
1684
+ return out;
1685
+ }
1686
+ for (const { value } of this.doc.attributes.node.get(ORIGINAL_ID_ATTRIBUTE) ?? []) {
1687
+ const po = isRecord(value) && this.scopePeek(value.s) !== 0 ? refId(value.po) : null;
1688
+ if (po !== null && isRecord(value) && typeof value.v === "string") {
1689
+ out.set(po, value.v);
1690
+ }
1691
+ }
1692
+ return out;
1693
+ }
1379
1694
  /**
1380
1695
  * Write a core string field (n, r, i).
1381
1696
  * @param domain - node or edge
@@ -1423,31 +1738,31 @@ class CxReader {
1423
1738
  }
1424
1739
  const parents = new Map();
1425
1740
  const collapsed = [];
1426
- for (const { value, line } of groups) {
1427
- if (!isRecord(value)) {
1741
+ for (const held of groups) {
1742
+ const group = structuralId(held, "cyGroups", "@id", this.report);
1743
+ if (group === null) {
1428
1744
  continue;
1429
1745
  }
1430
- const id = refId(value["@id"]);
1431
- if (id === null) {
1432
- this.report.error("missing-value", MISSING_ID_CODE, "a cyGroups element has no @id", {
1433
- line,
1434
- element: "cyGroups",
1435
- });
1436
- continue;
1746
+ const { value, id } = group;
1747
+ const { line } = held;
1748
+ if (value.nodes !== undefined && value.nodes !== null && !Array.isArray(value.nodes)) {
1749
+ this.report.error("validation-error", BAD_VALUE_CODE, `group ${String(id)}: nodes is ${shown(value.nodes)}, not a list of node ids; no member is read`, { line, element: String(id) });
1437
1750
  }
1438
- let groupRow = this.nodeRows.get(this.key(id));
1751
+ if (value.collapsed !== undefined && value.collapsed !== null && typeof value.collapsed !== "boolean") {
1752
+ this.report.error("validation-error", BAD_VALUE_CODE, `group ${String(id)}: collapsed is ${shown(value.collapsed)}, not a boolean; unset`, { line, element: String(id) });
1753
+ }
1754
+ let groupRow = this.nodeRows.get(id);
1439
1755
  if (groupRow === undefined) {
1440
1756
  if (this.rootNodes.has(id) || !this.hasMemberIn(value.nodes)) {
1441
1757
  continue;
1442
1758
  }
1443
1759
  try {
1444
- groupRow = this.sink.addNode(this.key(id));
1760
+ groupRow = this.addNode(id).row;
1445
1761
  }
1446
1762
  catch (err) {
1447
1763
  this.report.recordError(err, { line, element: String(id) });
1448
1764
  continue;
1449
1765
  }
1450
- this.nodeRows.set(this.key(id), groupRow);
1451
1766
  this.report.counts.nodes++;
1452
1767
  this.report.warning("coercion", CX_ISSUE.GROUP_NODE_ADDED, `group ${String(id)} is not a node; its group node is added`, { line, element: String(id) });
1453
1768
  }
@@ -1459,7 +1774,7 @@ class CxReader {
1459
1774
  }
1460
1775
  for (const raw of Array.isArray(value.nodes) ? value.nodes : []) {
1461
1776
  const member = refId(raw);
1462
- const row = member === null ? undefined : this.nodeRows.get(this.key(member));
1777
+ const row = member === null ? undefined : this.nodeRows.get(member);
1463
1778
  if (row === undefined) {
1464
1779
  if (member === null || !this.rootNodes.has(member)) {
1465
1780
  this.report.error("missing-value", UNKNOWN_PARENT_CODE, `group ${String(id)}: the member ${shown(raw)} is not a node`, { line, element: String(id) });
@@ -1499,7 +1814,7 @@ class CxReader {
1499
1814
  return (Array.isArray(raw) &&
1500
1815
  raw.some((item) => {
1501
1816
  const id = refId(item);
1502
- return id !== null && this.nodeRows.has(this.key(id));
1817
+ return id !== null && this.nodeRows.has(id);
1503
1818
  }));
1504
1819
  }
1505
1820
  /**
@@ -1562,6 +1877,7 @@ class CxReader {
1562
1877
  for (const { value, line } of entries) {
1563
1878
  this.checkAbort();
1564
1879
  if (!isRecord(value)) {
1880
+ this.report.error("parse-error", BAD_ASPECT_BLOCK_CODE, `a cartesianLayout element is ${shown(value)}, not an object; skipped`, { line, element: "cartesianLayout" });
1565
1881
  continue;
1566
1882
  }
1567
1883
  const view = value.view === undefined || value.view === null ? null : refId(value.view);
@@ -1577,14 +1893,21 @@ class CxReader {
1577
1893
  this.report.error("validation-error", BAD_VALUE_CODE, `a cartesianLayout element for node ${shown(value.node)} has no numeric x and y; skipped`, { line, element: "cartesianLayout" });
1578
1894
  continue;
1579
1895
  }
1896
+ if (!fitsF32(value.x) || !fitsF32(value.y)) {
1897
+ this.report.error("validation-error", BAD_VALUE_CODE, `a cartesianLayout element for node ${shown(value.node)} has the coordinates ${String(value.x)}, ${String(value.y)}, beyond what the f32 position column holds; skipped`, { line, element: "cartesianLayout" });
1898
+ continue;
1899
+ }
1580
1900
  const node = refId(value.node);
1581
- const row = node === null ? undefined : this.nodeRows.get(this.key(node));
1901
+ const row = node === null ? undefined : this.nodeRows.get(node);
1582
1902
  if (row === undefined) {
1583
1903
  if (node === null || !this.rootNodes.has(node)) {
1584
1904
  this.dangle("layout entry");
1585
1905
  }
1586
1906
  continue;
1587
1907
  }
1908
+ if (value.z !== undefined && value.z !== null && typeof value.z !== "number") {
1909
+ this.report.error("validation-error", BAD_VALUE_CODE, `a cartesianLayout element for node ${shown(value.node)} has the z ${shown(value.z)}, not a number; z is unset`, { line, element: "cartesianLayout" });
1910
+ }
1588
1911
  const key = `${slot}:${row}`;
1589
1912
  if (seen.has(key)) {
1590
1913
  this.report.warnOnce("validation-error", DUPLICATE_ATTRIBUTE_CODE, `node ${shown(value.node)} has two layout entries for one view; the later wins`, { line, element: "cartesianLayout" }, `${DUPLICATE_ATTRIBUTE_CODE}:layout`);
@@ -1652,6 +1975,7 @@ class CxReader {
1652
1975
  readEdges() {
1653
1976
  const { report, sink } = this;
1654
1977
  const members = this.plan.edges;
1978
+ this.weights = this.edgeWeights();
1655
1979
  for (const held of aspect(this.doc, "edges")) {
1656
1980
  this.checkAbort();
1657
1981
  const { value, line } = held;
@@ -1668,13 +1992,14 @@ class CxReader {
1668
1992
  report.counts.skippedEdges++;
1669
1993
  continue;
1670
1994
  }
1995
+ this.unknownKeys(value, EDGE_KEYS, "edge", line);
1671
1996
  const element = `edge ${shown(value["@id"])}`;
1672
- const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element, true);
1997
+ const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element, line, true);
1673
1998
  if (id === null) {
1674
1999
  report.counts.skippedEdges++;
1675
2000
  continue;
1676
2001
  }
1677
- if (members !== null && !members.has(refId(value["@id"]) ?? id)) {
2002
+ if (members !== null && !members.has(id)) {
1678
2003
  continue;
1679
2004
  }
1680
2005
  if (value.s === undefined || value.s === null || value.t === undefined || value.t === null) {
@@ -1685,8 +2010,8 @@ class CxReader {
1685
2010
  report.counts.skippedEdges++;
1686
2011
  continue;
1687
2012
  }
1688
- const s = this.idOf(value.s, (held.inexact & 2) !== 0, element);
1689
- const t = s === null ? null : this.idOf(value.t, (held.inexact & 4) !== 0, element);
2013
+ const s = this.idOf(value.s, (held.inexact & 2) !== 0, element, line);
2014
+ const t = s === null ? null : this.idOf(value.t, (held.inexact & 4) !== 0, element, line);
1690
2015
  if (s === null || t === null) {
1691
2016
  report.counts.skippedEdges++;
1692
2017
  continue;
@@ -1712,7 +2037,7 @@ class CxReader {
1712
2037
  const before = sink.edgeCount;
1713
2038
  let edge;
1714
2039
  try {
1715
- edge = this.direction.addEdge(s, t, "directed", weight, { line, element });
2040
+ edge = this.direction.addEdge(this.sinkId(s), this.sinkId(t), "directed", weight, { line, element });
1716
2041
  }
1717
2042
  catch (err) {
1718
2043
  report.recordError(err, { line, element });
@@ -1750,35 +2075,71 @@ class CxReader {
1750
2075
  return false;
1751
2076
  }
1752
2077
  try {
1753
- this.nodeRows.set(id, this.sink.addNode(id));
2078
+ if (this.addNode(id).added) {
2079
+ this.report.counts.nodes++;
2080
+ }
1754
2081
  }
1755
2082
  catch (err) {
1756
2083
  this.report.recordError(err, { line, element });
1757
2084
  return false;
1758
2085
  }
1759
- this.report.counts.nodes++;
1760
2086
  return true;
1761
2087
  }
1762
2088
  /**
1763
- * The weight of an edge: its weightFrom attribute, parsed as a number.
1764
- * @param id - the edge id
1765
- * @returns the weight, or undefined; E_INVALID_WEIGHT for a non-number
2089
+ * The weightFrom attribute values by edge id: this subnetwork's own value beats the unscoped
2090
+ * one, and of two in one scope the later wins (W_DUPLICATE_ATTRIBUTE when they differ), as
2091
+ * writeAttributes() rules every other attribute.
2092
+ * @returns the values (parsed JSON) by edge id
1766
2093
  */
1767
- weightOf(id) {
2094
+ edgeWeights() {
1768
2095
  const { weightFrom } = this.options;
2096
+ const chosen = new Map();
1769
2097
  if (weightFrom === null) {
1770
- return undefined;
2098
+ return new Map();
1771
2099
  }
1772
- if (this.weights === null) {
1773
- this.weights = new Map();
1774
- for (const { value } of this.doc.attributes.edge.get(weightFrom) ?? []) {
1775
- if (isRecord(value) && this.scopePeek(value.s) !== 0) {
1776
- const po = refId(value.po);
1777
- if (po !== null && value.v !== undefined && value.v !== null) {
1778
- this.weights.set(this.key(po), plainJson(value.v));
1779
- }
1780
- }
2100
+ for (const { value, line } of this.doc.attributes.edge.get(weightFrom) ?? []) {
2101
+ const scope = isRecord(value) ? this.scopePeek(value.s) : 0;
2102
+ if (!isRecord(value) || scope === 0 || value.v === undefined || value.v === null) {
2103
+ continue;
1781
2104
  }
2105
+ const v = plainJson(value.v);
2106
+ for (const raw of Array.isArray(value.po) ? value.po : [value.po]) {
2107
+ this.chooseWeight(chosen, raw, { scope, value: v }, line, weightFrom);
2108
+ }
2109
+ }
2110
+ return new Map([...chosen].map(([id, { value }]) => [id, value]));
2111
+ }
2112
+ /**
2113
+ * Keep one weight attribute element's value for one edge, unless a more specific scope already
2114
+ * gave it one; a second value in the same scope wins, with a warning.
2115
+ * @param chosen - the value chosen so far per edge
2116
+ * @param raw - the edge reference as written
2117
+ * @param weight - the value and the scope it applies in
2118
+ * @param weight.scope - the scope: higher is more specific
2119
+ * @param weight.value - the value
2120
+ * @param line - the element's line
2121
+ * @param weightFrom - the attribute name
2122
+ */
2123
+ chooseWeight(chosen, raw, weight, line, weightFrom) {
2124
+ const po = refId(raw);
2125
+ const previous = po === null ? undefined : chosen.get(po);
2126
+ if (po === null || (previous !== undefined && previous.scope > weight.scope)) {
2127
+ return;
2128
+ }
2129
+ if (previous?.scope === weight.scope && JSON.stringify(previous.value) !== JSON.stringify(weight.value)) {
2130
+ this.report.warnOnce("validation-error", DUPLICATE_ATTRIBUTE_CODE, `edge ${shown(raw)} has the attribute "${weightFrom}" twice; the later value wins`, { line, element: weightFrom }, `${DUPLICATE_ATTRIBUTE_CODE}:edge:${weightFrom}`);
2131
+ }
2132
+ chosen.set(po, weight);
2133
+ }
2134
+ /**
2135
+ * The weight of an edge: its weightFrom attribute, parsed as a number (the declared d is not
2136
+ * consulted: a value that is not a number is E_INVALID_WEIGHT whatever its type says).
2137
+ * @param id - the edge id
2138
+ * @returns the weight, or undefined; E_INVALID_WEIGHT for a non-number
2139
+ */
2140
+ weightOf(id) {
2141
+ if (this.options.weightFrom === null) {
2142
+ return undefined;
1782
2143
  }
1783
2144
  const raw = this.weights.get(id);
1784
2145
  if (typeof raw === "string" && isNullText(raw)) {
@@ -1792,17 +2153,29 @@ class CxReader {
1792
2153
  for (const [name, elements] of this.doc.attributes.network) {
1793
2154
  let chosen = null;
1794
2155
  let chosenScope = 0;
1795
- for (const { value } of elements) {
2156
+ for (const { value, line } of elements) {
1796
2157
  if (!isRecord(value)) {
1797
2158
  continue;
1798
2159
  }
1799
2160
  const scope = this.scopeOf(value.s);
1800
- if (scope !== 0 && scope >= chosenScope) {
2161
+ if (scope === 0) {
2162
+ continue;
2163
+ }
2164
+ if (value.v === undefined) {
2165
+ this.report.error("missing-value", BAD_VALUE_CODE, `a networkAttributes element for "${name}" has no v; skipped`, { line, element: name });
2166
+ continue;
2167
+ }
2168
+ if (chosen !== null &&
2169
+ scope === chosenScope &&
2170
+ JSON.stringify(plainJson(chosen.v)) !== JSON.stringify(plainJson(value.v))) {
2171
+ this.report.warnOnce("validation-error", DUPLICATE_ATTRIBUTE_CODE, `the network attribute "${name}" is given twice; the later value wins`, { line, element: name }, `${DUPLICATE_ATTRIBUTE_CODE}:network:${name}`);
2172
+ }
2173
+ if (scope >= chosenScope) {
1801
2174
  chosen = value;
1802
2175
  chosenScope = scope;
1803
2176
  }
1804
2177
  }
1805
- if (chosen === null || chosen.v === undefined || chosen.v === null || name === "") {
2178
+ if (chosen?.v === undefined || chosen.v === null) {
1806
2179
  continue;
1807
2180
  }
1808
2181
  const type = cxType(chosen.d);
@@ -1869,7 +2242,7 @@ class CxReader {
1869
2242
  const domain = value.properties_of === "nodes" ? "node" : "edge";
1870
2243
  const target = refId(value.applies_to);
1871
2244
  const rows = domain === "node" ? this.nodeRows : this.edgeRows;
1872
- const row = target === null ? undefined : rows.get(this.key(target));
2245
+ const row = target === null ? undefined : rows.get(target);
1873
2246
  if (row === undefined) {
1874
2247
  if (target === null || !(domain === "node" ? this.rootNodes : this.rootEdges).has(target)) {
1875
2248
  this.dangle(`${domain} visual property entry`);
@@ -1899,7 +2272,7 @@ class CxReader {
1899
2272
  name: property,
1900
2273
  dtype: "string",
1901
2274
  nullable: true,
1902
- origin: { format: CX_FORMAT, id: null, namespace: CX_BYPASS_NAMESPACE },
2275
+ origin: { format: CX_FORMAT, id: property, namespace: CX_BYPASS_NAMESPACE },
1903
2276
  }, this.report);
1904
2277
  for (const [row, text] of cells) {
1905
2278
  if (domain === "node") {
@@ -1924,12 +2297,12 @@ class CxReader {
1924
2297
  }
1925
2298
  if (defaults + mappings + dependencies + tableStyles > 0) {
1926
2299
  const parts = [
1927
- `cyVisualProperties: ${defaults} default(s), ${mappings} mapping(s), ${dependencies} dependenc(ies)`,
2300
+ `cyVisualProperties: ${defaults} default${plural(defaults)}, ${mappings} mapping${plural(mappings)}, ${dependencies} dependenc(ies)`,
1928
2301
  ];
1929
2302
  if (tableStyles > 0) {
1930
- parts.push(`${tableStyles} table style element(s)`);
2303
+ parts.push(`${tableStyles} table style element${plural(tableStyles)}`);
1931
2304
  }
1932
- this.report.warning("unsupported", STYLES_NOT_IMPORTED_CODE, `the file's style rules are not applied (${parts.join("; ")}); they are kept in meta.extra.cx (style import is issue #706)`, { element: "cyVisualProperties" });
2305
+ this.report.warning("unsupported", STYLES_NOT_IMPORTED_CODE, `the file's style rules are not applied (${parts.join("; ")}); they are kept in meta.extra.cx`, { element: "cyVisualProperties" });
1933
2306
  }
1934
2307
  }
1935
2308
  /**
@@ -1955,7 +2328,7 @@ class CxReader {
1955
2328
  continue;
1956
2329
  }
1957
2330
  const po = refId(value.po);
1958
- const row = po === null ? undefined : this.nodeRows.get(this.key(po));
2331
+ const row = po === null ? undefined : this.nodeRows.get(po);
1959
2332
  if (row === undefined) {
1960
2333
  if (po === null || !this.rootNodes.has(po)) {
1961
2334
  this.dangle("functionTerms entry");
@@ -1973,8 +2346,8 @@ class CxReader {
1973
2346
  }
1974
2347
  const node = refId(value.node);
1975
2348
  const edge = refId(value.edge);
1976
- const row = node === null ? undefined : this.nodeRows.get(this.key(node));
1977
- const edgeRow = edge === null ? undefined : this.edgeRows.get(this.key(edge));
2349
+ const row = node === null ? undefined : this.nodeRows.get(node);
2350
+ const edgeRow = edge === null ? undefined : this.edgeRows.get(edge);
1978
2351
  if (row === undefined || edgeRow === undefined) {
1979
2352
  if (node === null || !this.rootNodes.has(node) || edge === null || !this.rootEdges.has(edge)) {
1980
2353
  this.dangle("reifiedEdges entry");
@@ -2007,16 +2380,30 @@ class CxReader {
2007
2380
  return;
2008
2381
  }
2009
2382
  const fields = [...new Set(records.flatMap((r) => Object.keys(r)).filter((k) => k !== "@id"))];
2383
+ const names = new Set(["id"]);
2010
2384
  const decls = [
2011
2385
  { name: "id", dtype: "f64", nullable: true },
2012
- ...fields.map((field) => ({ name: field, dtype: "json", nullable: true })),
2386
+ ...fields.map((field) => {
2387
+ const column = uniqueColumnName(field, null, (n) => names.has(n));
2388
+ names.add(column);
2389
+ if (column !== field) {
2390
+ this.report.warning("coercion", COLUMN_RENAMED_CODE, `${name} column "${field}" renamed to "${column}": the table's own id column holds that name`, { element: field });
2391
+ }
2392
+ return { name: column, dtype: "json", nullable: true };
2393
+ }),
2013
2394
  ];
2014
2395
  const table = this.sink.addExtensionTable(name, decls);
2396
+ const precision = (digits) => {
2397
+ this.precision(name, digits);
2398
+ };
2015
2399
  for (const record of records) {
2016
2400
  const id = refId(record["@id"]);
2401
+ if (typeof id === "string") {
2402
+ precision(id);
2403
+ }
2017
2404
  this.sink.addExtensionRow(table, [
2018
2405
  id === null ? null : Number(id),
2019
- ...fields.map((field) => (record[field] === undefined ? null : plainJson(record[field]))),
2406
+ ...fields.map((field) => (record[field] === undefined ? null : plainJson(record[field], precision))),
2020
2407
  ]);
2021
2408
  }
2022
2409
  }
@@ -2041,7 +2428,7 @@ class CxReader {
2041
2428
  .map(Number);
2042
2429
  for (const raw of targets) {
2043
2430
  const target = refId(raw);
2044
- const row = target === null ? undefined : rows.get(this.key(target));
2431
+ const row = target === null ? undefined : rows.get(target);
2045
2432
  if (row === undefined) {
2046
2433
  if (target === null || !root.has(target)) {
2047
2434
  this.dangle(`${aspectName} entry`);
@@ -2101,12 +2488,19 @@ class CxReader {
2101
2488
  };
2102
2489
  const extra = Object.fromEntries(this.doc.kept);
2103
2490
  const groups = aspect(this.doc, "cyGroups").map((h) => plainJson(h.value));
2491
+ const own = {};
2104
2492
  if (groups.length > 0) {
2105
- extra.groups = groups;
2493
+ own.groups = groups;
2106
2494
  }
2107
2495
  if (this.plan.subnetwork !== null) {
2108
- extra.subnetwork = this.plan.subnetwork;
2496
+ own.subnetwork = this.plan.subnetwork;
2497
+ }
2498
+ for (const key of OWN_EXTRA_KEYS) {
2499
+ if (key in own && this.doc.kept.has(key)) {
2500
+ this.report.warning("unsupported", UNKNOWN_ELEMENT_CODE, `the aspect "${key}" is not kept: meta.extra.cx.${key} holds the importer's own ${key === "groups" ? "cyGroups" : "subnetwork"} data`, { element: key });
2501
+ }
2109
2502
  }
2503
+ Object.assign(extra, own);
2110
2504
  const { weightFrom } = this.options;
2111
2505
  const name = this.plan.name ?? describe("name");
2112
2506
  const description = describe("description");
@@ -2139,7 +2533,7 @@ async function run(input, options, sinkFor, pick) {
2139
2533
  const first = new ImportReportBuilder(CX_FORMAT, resolved.errorLimit);
2140
2534
  reportUnusedOptions(options, first, USED_OPTIONS);
2141
2535
  const doc = await readDocument(input, first, resolved);
2142
- const plans = graphPlans(doc);
2536
+ const plans = graphPlans(doc, first);
2143
2537
  const chosen = pick(plans, first);
2144
2538
  const reports = [];
2145
2539
  chosen.forEach((index, k) => {
@@ -2153,10 +2547,12 @@ async function run(input, options, sinkFor, pick) {
2153
2547
  return reports;
2154
2548
  }
2155
2549
  /**
2156
- * The CX version 1 importer plugin (design section 1.2).
2550
+ * The CX version 1 importer plugin.
2551
+ * @category Built-in formats
2157
2552
  */
2158
2553
  export const cxImporter = Object.freeze({
2159
2554
  format: CX_FORMAT,
2555
+ options: Object.freeze(["zAs"]),
2160
2556
  extensions: Object.freeze([".cx"]),
2161
2557
  mimeTypes: Object.freeze(["application/json"]),
2162
2558
  /**
@@ -2167,8 +2563,7 @@ export const cxImporter = Object.freeze({
2167
2563
  * @returns the confidence
2168
2564
  */
2169
2565
  sniff(head) {
2170
- const text = new TextDecoder("utf-8").decode(head.subarray(0, 1024)).replace(/^\uFEFF/, "");
2171
- const match = /^\s*\[\s*\{\s*"((?:[^"\\]|\\.)*)"\s*:/.exec(text);
2566
+ const match = /^\s*\[\s*\{\s*"((?:[^"\\]|\\.)*)"\s*:/.exec(headText(head));
2172
2567
  if (match === null) {
2173
2568
  return 0;
2174
2569
  }
@@ -2188,8 +2583,8 @@ export const cxImporter = Object.freeze({
2188
2583
  async import(input, sink, options) {
2189
2584
  const [report] = await run(input, options, () => sink, (plans, first) => {
2190
2585
  const index = chooseGraph(plans.map((p) => p.name), options, first);
2191
- if (plans.length > 1) {
2192
- first.warning("unsupported", MULTIPLE_GRAPHS_CODE, `the collection holds ${plans.length} subnetworks; graph ${index} is read and ${plans.length - 1} skipped (importAll() reads every one)`);
2586
+ if (plans.length > 1 && !graphChosen(options)) {
2587
+ first.warning("unsupported", MULTIPLE_GRAPHS_CODE, `the collection holds ${plans.length} subnetworks; graph ${index} is read and ${plans.length - 1} skipped (importAllGraphs() reads every one)`);
2193
2588
  }
2194
2589
  return [index];
2195
2590
  });
@@ -2218,7 +2613,7 @@ export const cxImporter = Object.freeze({
2218
2613
  const doc = await readDocument(input, report, resolved);
2219
2614
  const nodes = aspect(doc, "nodes").length;
2220
2615
  const edges = aspect(doc, "edges").length;
2221
- return graphPlans(doc).map((plan) => Object.freeze({
2616
+ return graphPlans(doc, report).map((plan) => Object.freeze({
2222
2617
  index: plan.index,
2223
2618
  name: plan.name,
2224
2619
  nodes: memberCount(plan.nodes, nodes),