@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
@@ -45,17 +45,22 @@ import {
45
45
  DUPLICATE_ATTRIBUTE_CODE,
46
46
  DUPLICATE_EDGE_ID_CODE,
47
47
  DUPLICATE_NODE_CODE,
48
+ ELEMENT_ISSUE,
48
49
  EMPTY_INPUT_CODE,
49
50
  ENCODING_FALLBACK_CODE,
50
51
  ID_MERGED_CODE,
51
52
  ID_TEXT_TYPE_CODE,
53
+ INPUT_ISSUE,
52
54
  INVALID_ENCODING_CODE,
53
55
  INVALID_UTF8_CODE,
56
+ JSON_NONSTANDARD_NUMBER_CODE,
54
57
  MISSING_ENDPOINT_CODE,
55
58
  MISSING_ID_CODE,
59
+ MULTI_ASPECT_FRAGMENT_CODE,
56
60
  OPTION_IGNORED_CODE,
57
61
  PRECISION_CODE,
58
62
  ROLE_TAKEN_CODE,
63
+ SINGLE_OBJECT_ASPECT_CODE,
59
64
  SINK_OPTION_CODE,
60
65
  STATUS_FAILED_CODE,
61
66
  STATUS_WARNING_CODE,
@@ -74,14 +79,19 @@ import {
74
79
  CxStructure,
75
80
  declareFresh,
76
81
  ExactInteger,
82
+ fitsF32,
77
83
  flipY,
84
+ headText,
78
85
  inexactLiteral,
79
86
  isRecord,
80
87
  JsonScanError,
88
+ keptPrecision,
81
89
  plainJson,
82
90
  positionDecl,
91
+ reportSharedBlock,
83
92
  reportTooDeep,
84
93
  scanAspects,
94
+ setOwn,
85
95
  zDecl,
86
96
  } from "../../common/json-elements.js";
87
97
  import {
@@ -91,55 +101,78 @@ import {
91
101
  type ResolvedImportOptions,
92
102
  resolveImportOptions,
93
103
  } from "../../common/options.js";
104
+ import { agree, plural } from "../../common/plural.js";
94
105
  import { ImportReportBuilder } from "../../common/report.js";
95
106
  import { weightFromValue } from "../../common/weights.js";
96
107
  import { type CommonImportOptions, type GraphImporter, type ImportInput, type ImportReport } from "../../types.js";
97
108
 
98
- /** The format name. */
109
+ /**
110
+ * The format name.
111
+ * @category Plugin helpers
112
+ */
99
113
  export const CX2_FORMAT = "cx2";
100
114
 
101
- /** The node attribute the exporter's `sanitizeIds: "mangle"` keeps an original id in. */
115
+ /**
116
+ * The node attribute the exporter's `sanitizeIds: "mangle"` keeps an original id in.
117
+ * @category Plugin helpers
118
+ */
102
119
  export const ORIGINAL_ID_ATTRIBUTE = "graphty:originalId";
103
120
 
104
- /** The origin namespace of the per-element visual property columns. */
121
+ /**
122
+ * The origin namespace of the per-element visual property columns.
123
+ * @category Plugin helpers
124
+ */
105
125
  export const BYPASS_NAMESPACE = "cx2.bypass";
106
126
 
107
- /** The format-specific options of the CX2 importer. */
108
- export interface Cx2ImportOptions {
127
+ /**
128
+ * The format-specific options of the CX2 importer.
129
+ * @category Built-in formats
130
+ */
131
+ export interface Cx2ImportOptions extends CommonImportOptions {
109
132
  /**
110
- * Where a node's `z` goes: "column" (default) keeps it in the f64 node column `z` (Cytoscape
111
- * writes a stacking order there); "position" makes it the third component of the position.
133
+ * Where Cytoscape's `z` value (a drawing order, not a depth) goes: "column" keeps it as a node
134
+ * attribute named `z`; "position" makes it the third coordinate of the position.
135
+ * @defaultValue "column"
112
136
  */
113
137
  zAs?: "column" | "position" | undefined;
114
138
  }
115
139
 
116
140
  /**
117
- * The issue codes the CX2 importer records (design section 1.3), by name: the codes shared with
118
- * the other importers (src/common/codes.ts) and the CX2-specific ones. A key is the code without
141
+ * The issue codes the CX2 importer records, by name: the codes shared with
142
+ * the other importers and the CX2-specific ones. A key is the code without
119
143
  * its severity and format prefixes.
144
+ * @category Built-in formats
120
145
  */
121
146
  export const CX2_ISSUE = Object.freeze({
122
- /** The input is empty (fatal). */
147
+ ...INPUT_ISSUE,
148
+ ...ELEMENT_ISSUE,
149
+ /** The input is empty. The import stops. */
123
150
  EMPTY_INPUT: EMPTY_INPUT_CODE,
124
- /** The text is not JSON (fatal). */
151
+ /** The text is not valid JSON. The import stops. */
125
152
  SYNTAX: SYNTAX_CODE,
126
- /** Invalid UTF-8 (fatal). */
153
+ /** The input is not valid UTF-8. The import stops. */
127
154
  INVALID_UTF8: INVALID_UTF8_CODE,
128
- /** Invalid bytes in the encoding a BOM or the encoding option chose (fatal). */
155
+ /**
156
+ * Some bytes are not valid in the encoding that was chosen (by a byte order mark or the `encoding` option). The
157
+ * import stops.
158
+ */
129
159
  INVALID_ENCODING: INVALID_ENCODING_CODE,
130
160
  /** Bytes that are not UTF-8 were read as windows-1252. */
131
161
  ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
132
162
  /** An encoding the platform cannot decode was ignored. */
133
163
  UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
134
- /** The document is not an array or does not start with a descriptor holding CXVersion (fatal). */
164
+ /**
165
+ * The document is not a JSON array, or its first element is not the CX2 descriptor with `CXVersion`. The import
166
+ * stops.
167
+ */
135
168
  NO_DESCRIPTOR: "E_CX2_NO_DESCRIPTOR",
136
- /** CXVersion names a major version other than 2 (fatal); "1.x" says the file is CX1. */
169
+ /** `CXVersion` names a version other than 2 ("1.x" means the file is CX1; read it as `cx`). The import stops. */
137
170
  VERSION: "E_CX2_VERSION",
138
171
  /** CXVersion is a 2.x other than "2.0" (or not a string); the document is read. */
139
172
  MINOR_VERSION: "W_CX2_MINOR_VERSION",
140
173
  /** The document has no status block, or a malformed one. */
141
174
  NO_STATUS: "E_CX2_NO_STATUS",
142
- /** The producer marked the document as failed (fatal). */
175
+ /** The program that wrote the file marked it as failed, so it is incomplete. The import stops. */
143
176
  STATUS_FAILED: STATUS_FAILED_CODE,
144
177
  /** The producer marked the document as successful with an error text. */
145
178
  STATUS_WARNING: STATUS_WARNING_CODE,
@@ -163,9 +196,18 @@ export const CX2_ISSUE = Object.freeze({
163
196
  ALIAS_BYPASSED: "W_CX2_ALIAS_BYPASSED",
164
197
  /** A CX1 cartesianLayout aspect next to node coordinates; kept, not applied. */
165
198
  LEGACY_LAYOUT: "W_CX2_LEGACY_LAYOUT",
166
- /** The file's style rules are not applied (issue #706). */
199
+ /** An aspect CX2 defines as an array of elements written as one object; read as one element. */
200
+ SINGLE_OBJECT_ASPECT: SINGLE_OBJECT_ASPECT_CODE,
201
+ /** A member holding several aspects; each array-valued key is read as its own block. */
202
+ MULTI_ASPECT_FRAGMENT: MULTI_ASPECT_FRAGMENT_CODE,
203
+ /** The bare tokens NaN / Infinity / -Infinity (Python's json writes them), read as numbers. */
204
+ JSON_NONSTANDARD_NUMBER: JSON_NONSTANDARD_NUMBER_CODE,
205
+ /** The file's style rules are not applied; they are kept so a CX2 export writes them back. */
167
206
  STYLES_NOT_IMPORTED: STYLES_NOT_IMPORTED_CODE,
168
- /** A member of the top-level array that is not a one-key aspect block, or an element that is not an object. */
207
+ /**
208
+ * A member of the file's top-level array that is not a block with one key (`{"nodes": [...]}`), or an element of a
209
+ * block that is not an object. It is skipped; the rest of the file is read.
210
+ */
169
211
  BAD_ASPECT_BLOCK: BAD_ASPECT_BLOCK_CODE,
170
212
  /** An aspect out of its place (after the post-metadata or the status, a third metaData, late declarations). */
171
213
  ASPECT_ORDER: ASPECT_ORDER_CODE,
@@ -197,25 +239,37 @@ export const CX2_ISSUE = Object.freeze({
197
239
  INVALID_WEIGHT: "E_INVALID_WEIGHT",
198
240
  /** An id spelled as a string or a non-integer literal; read as the integer. */
199
241
  ID_TEXT_TYPE: ID_TEXT_TYPE_CODE,
200
- /** An integer beyond 2^53: an id kept as its digits, a value stored as the nearest f64. */
242
+ /** An integer beyond 2^53 was stored as the nearest 64-bit float; pass `long: "string"` to keep every digit. */
201
243
  PRECISION: PRECISION_CODE,
202
244
  /** Two id texts merged under `ids: "number"`. */
203
245
  ID_MERGED: ID_MERGED_CODE,
204
246
  /** An element key CX2 does not define. */
205
247
  UNKNOWN_ELEMENT: UNKNOWN_ELEMENT_CODE,
206
- /** A column renamed because its name was taken. */
248
+ /**
249
+ * An attribute was renamed `<name>#<suffix>` because another attribute already has its name, for example
250
+ * two attributes declared with the same name.
251
+ */
207
252
  COLUMN_RENAMED: COLUMN_RENAMED_CODE,
208
- /** A column that lost its role because another column holds it. */
253
+ /**
254
+ * You read into a graph builder that already has an id, label or position attribute, so this file's one is kept as
255
+ * a plain attribute.
256
+ */
209
257
  ROLE_TAKEN: ROLE_TAKEN_CODE,
210
- /** The sink refused the direction. */
258
+ /**
259
+ * You read into a graph builder whose direction is already set, or which already holds edges, so the file is read
260
+ * with the builder's direction instead of its own.
261
+ */
211
262
  DIRECTION_REFUSED: DIRECTION_REFUSED_CODE,
212
- /** Edges forced to the policy's direction. */
263
+ /** Edges of the other direction were read with the direction `onMixedDirection` chose. */
213
264
  DIRECTION_FORCED: DIRECTION_FORCED_CODE,
214
- /** A common option CX2 has no use for. */
265
+ /** You set an option this format does not use; it had no effect. The message names the option. */
215
266
  OPTION_IGNORED: OPTION_IGNORED_CODE,
216
- /** A builder option the caller's sink does not honour. */
267
+ /**
268
+ * You read into your own graph builder, which was created with a different `addMissingNodes`, `duplicateEdges`,
269
+ * `selfLoops` or `weightDtype` than the option you passed; the builder's setting applies.
270
+ */
217
271
  SINK_OPTION: SINK_OPTION_CODE,
218
- /** The input is beyond a size limit (fatal). */
272
+ /** The input is larger than graph-io's size limit. The import stops. */
219
273
  TOO_LARGE: TOO_LARGE_CODE,
220
274
  });
221
275
 
@@ -265,6 +319,18 @@ const STYLE_ASPECTS: readonly string[] = [
265
319
  /** The aspects the stream structure owns; never kept as opaque. */
266
320
  const STRUCTURE_ASPECTS: ReadonlySet<string> = new Set(["metaData", "status"]);
267
321
 
322
+ /** The aspects CX2 defines as an array of elements, which a reader expects in that form. */
323
+ const ARRAY_ASPECTS: ReadonlySet<string> = new Set([
324
+ "nodes",
325
+ "edges",
326
+ "attributeDeclarations",
327
+ "networkAttributes",
328
+ "nodeBypasses",
329
+ "edgeBypasses",
330
+ "status",
331
+ "metaData",
332
+ ]);
333
+
268
334
  /** The canonical CX2 scalar types. */
269
335
  type Cx2Scalar = "string" | "long" | "integer" | "double" | "boolean";
270
336
 
@@ -279,26 +345,27 @@ interface Cx2Type {
279
345
  }
280
346
 
281
347
  /** The type spellings CX2 defines, plus the Python spellings ndex2 writes. */
282
- const SCALARS: Readonly<Record<string, Cx2Scalar>> = {
283
- string: "string",
284
- long: "long",
285
- integer: "integer",
286
- double: "double",
287
- boolean: "boolean",
288
- str: "string",
289
- int: "integer",
290
- bool: "boolean",
291
- float: "double",
292
- };
348
+ const SCALARS: ReadonlyMap<string, Cx2Scalar> = new Map([
349
+ ["string", "string"],
350
+ ["long", "long"],
351
+ ["integer", "integer"],
352
+ ["double", "double"],
353
+ ["boolean", "boolean"],
354
+ ["str", "string"],
355
+ ["int", "integer"],
356
+ ["bool", "boolean"],
357
+ ["float", "double"],
358
+ ]);
293
359
 
294
360
  /**
295
361
  * Resolve a declared type text.
296
362
  * @param d - the `d` value
297
363
  * @returns the type, or null when CX2 does not define it
364
+ * @category Plugin helpers
298
365
  */
299
366
  export function cx2Type(d: string): Cx2Type | null {
300
367
  const list = d.startsWith("list_of_");
301
- const scalar = SCALARS[list ? d.slice("list_of_".length) : d] as Cx2Scalar | undefined;
368
+ const scalar = SCALARS.get(list ? d.slice("list_of_".length) : d);
302
369
  if (scalar === undefined) {
303
370
  return null;
304
371
  }
@@ -357,6 +424,7 @@ function convertScalar(
357
424
  return typeof value === "boolean" ? value : BAD;
358
425
  case "double":
359
426
  if (value instanceof ExactInteger) {
427
+ onPrecision(value.digits);
360
428
  return Number(value.digits);
361
429
  }
362
430
  return typeof value === "number" ? value : BAD;
@@ -460,15 +528,16 @@ interface Cx2Document {
460
528
  * The inexact-literal bits of an element.
461
529
  * @param text - the element's JSON text
462
530
  * @param keys - the id keys to check, bit 1, 2, 4 in order
531
+ * @param depth - the depth of the element's own keys in the text (2: a member's single object)
463
532
  * @returns the bits
464
533
  */
465
- function inexactBits(text: string, keys: readonly string[]): number {
534
+ function inexactBits(text: string, keys: readonly string[], depth = 1): number {
466
535
  if (!/[0-9][.eE]/.test(text)) {
467
536
  return 0;
468
537
  }
469
538
  let bits = 0;
470
539
  keys.forEach((key, i) => {
471
- if (inexactLiteral(text, key)) {
540
+ if (inexactLiteral(text, key, depth)) {
472
541
  bits |= 1 << i;
473
542
  }
474
543
  });
@@ -504,16 +573,17 @@ async function readDocument(
504
573
  let descriptor = false;
505
574
  let elementsSeen = false;
506
575
  let sinceCheck = 0;
507
- const collect = (aspect: string, value: unknown, text: string, line: number, exact: boolean): void => {
576
+ const opaquePrecision = keptPrecision(report, "meta.extra.cx2.opaque");
577
+ const collect = (aspect: string, value: unknown, text: string, line: number, exact: boolean, depth = 1): void => {
508
578
  structure.element(aspect, value, line);
509
579
  switch (aspect) {
510
580
  case "nodes":
511
581
  elementsSeen = true;
512
- doc.nodes.push({ value, line, inexact: inexactBits(text, ["id"]) });
582
+ doc.nodes.push({ value, line, inexact: inexactBits(text, ["id"], depth) });
513
583
  return;
514
584
  case "edges":
515
585
  elementsSeen = true;
516
- doc.edges.push({ value, line, inexact: inexactBits(text, ["id", "s", "t"]) });
586
+ doc.edges.push({ value, line, inexact: inexactBits(text, ["id", "s", "t"], depth) });
517
587
  return;
518
588
  case "attributeDeclarations":
519
589
  if (elementsSeen) {
@@ -525,7 +595,8 @@ async function readDocument(
525
595
  `${ASPECT_ORDER_CODE}:declarations`,
526
596
  );
527
597
  }
528
- doc.declarations.push({ value: plainJson(value), line, inexact: 0 });
598
+ // not plainJson(): a long default beyond 2^53 keeps its digits for long: "string"
599
+ doc.declarations.push({ value, line, inexact: 0 });
529
600
  return;
530
601
  case "networkAttributes":
531
602
  elementsSeen = true;
@@ -547,11 +618,11 @@ async function readDocument(
547
618
  if (!doc.opaque.has(aspect)) {
548
619
  doc.opaque.set(aspect, []);
549
620
  }
550
- doc.opaque.get(aspect)?.push(exact ? plainJson(value, () => undefined) : value);
621
+ doc.opaque.get(aspect)?.push(exact ? plainJson(value, opaquePrecision) : value);
551
622
  }
552
623
  };
553
624
  try {
554
- for await (const event of scanAspects(textChunks(input, report, options))) {
625
+ for await (const event of scanAspects(textChunks(input, report, options), report)) {
555
626
  if (++sinceCheck >= ABORT_CHECK_INTERVAL) {
556
627
  sinceCheck = 0;
557
628
  throwIfAborted(options.signal);
@@ -560,7 +631,7 @@ async function readDocument(
560
631
  case "root":
561
632
  report.fail(
562
633
  CX2_ISSUE.NO_DESCRIPTOR,
563
- `a CX2 document is a JSON array; found ${isRecord(event.value) ? "an object" : shown(event.value)}`,
634
+ `a CX2 document is a JSON array; found ${event.object ? "an object" : shown(event.value)}`,
564
635
  { line: event.line },
565
636
  );
566
637
  break;
@@ -571,10 +642,11 @@ async function readDocument(
571
642
  break;
572
643
  }
573
644
  memberAspect(event.value, event.line, structure, report, (aspect, value) => {
574
- collect(aspect, value, JSON.stringify(value) ?? "", event.line, event.exact);
645
+ collect(aspect, value, event.text, event.line, event.exact, 2);
575
646
  });
576
647
  break;
577
648
  case "block":
649
+ reportSharedBlock(report, event);
578
650
  if (!descriptor) {
579
651
  report.fail(
580
652
  CX2_ISSUE.NO_DESCRIPTOR,
@@ -597,7 +669,7 @@ async function readDocument(
597
669
  report.error(
598
670
  "parse-error",
599
671
  BAD_ASPECT_BLOCK_CODE,
600
- `the "${event.aspect}" block holds more keys (${event.keys.join(", ")}); an aspect block has one key, the others are skipped`,
672
+ `the "${event.aspect}" block holds more keys (${event.keys.join(", ")}) whose values are not arrays; an aspect block has one key, the others are skipped`,
601
673
  { line: event.line, element: event.aspect },
602
674
  );
603
675
  break;
@@ -635,7 +707,8 @@ function readDescriptor(value: unknown, line: number, doc: Cx2Document, report:
635
707
  }
636
708
  const raw = value.CXVersion;
637
709
  const text = typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
638
- const match = /^([0-9]+)(?:\.([0-9]+))?$/.exec(text);
710
+ // the major version: "2.0", "2.1", "2.0.1", "2.1-beta", " 2.0" are all CX2
711
+ const match = /^\s*(\d+)(?=$|[.\s-])/.exec(text);
639
712
  const major = match === null ? NaN : Number(match[1]);
640
713
  if (major === 1) {
641
714
  report.fail(
@@ -695,6 +768,14 @@ function memberAspect(
695
768
  ): void {
696
769
  const keys = isRecord(value) ? Object.keys(value) : [];
697
770
  if (isRecord(value) && keys.length === 1 && isRecord(value[keys[0]])) {
771
+ if (ARRAY_ASPECTS.has(keys[0])) {
772
+ report.warning(
773
+ "validation-error",
774
+ CX2_ISSUE.SINGLE_OBJECT_ASPECT,
775
+ `the "${keys[0]}" aspect is one object, not an array of elements; read as one element`,
776
+ { line, element: keys[0] },
777
+ );
778
+ }
698
779
  structure.block(keys[0], line);
699
780
  collect(keys[0], value[keys[0]]);
700
781
  return;
@@ -832,7 +913,21 @@ interface Declarations {
832
913
  }
833
914
 
834
915
  /** The domain key of attributeDeclarations for each table. */
835
- const DECLARATION_KEYS = { nodes: "node", edges: "edge", networkAttributes: "network" } as const;
916
+ const DECLARATION_KEYS: ReadonlyMap<string, "node" | "edge" | "network"> = new Map([
917
+ ["nodes", "node"],
918
+ ["edges", "edge"],
919
+ ["networkAttributes", "network"],
920
+ ]);
921
+
922
+ /**
923
+ * Whether an object has an own property (never one of Object.prototype, as `in` would find).
924
+ * @param record - the object
925
+ * @param key - the key
926
+ * @returns true for an own property
927
+ */
928
+ function hasOwn(record: Record<string, unknown>, key: string): boolean {
929
+ return Object.hasOwn(record, key);
930
+ }
836
931
 
837
932
  // ============================================================ the build
838
933
 
@@ -865,6 +960,9 @@ class Cx2Reader {
865
960
  /** CX node id -> the id the sink holds it under (differs when a mangled id is restored). */
866
961
  private readonly sinkIds = new Map<NodeId, NodeId>();
867
962
 
963
+ /** The original ids restored so far: a second node with one keeps its CX id. */
964
+ private readonly restored = new Set<NodeId>();
965
+
868
966
  /** CX edge id -> sink edge index. */
869
967
  private readonly edgeRows = new Map<NodeId, number>();
870
968
 
@@ -926,7 +1024,7 @@ class Cx2Reader {
926
1024
  report.warnOnce(
927
1025
  "missing-value",
928
1026
  CX2_ISSUE.PARTIAL_LAYOUT,
929
- `${this.nodeRows.size - this.positioned.size} of ${this.nodeRows.size} node(s) have no coordinates; their positions are unset`,
1027
+ `${this.nodeRows.size - this.positioned.size} of ${this.nodeRows.size} node${plural(this.nodeRows.size)} ${agree(this.nodeRows.size - this.positioned.size, "has", "have")} no coordinates; their positions are unset`,
930
1028
  { element: "nodes" },
931
1029
  );
932
1030
  }
@@ -940,7 +1038,7 @@ class Cx2Reader {
940
1038
  report.warning(
941
1039
  "validation-error",
942
1040
  DANGLING_REFERENCE_CODE,
943
- `${count} ${kind} name(s) no element; ignored`,
1041
+ `${count} ${kind}${plural(count)} ${agree(count, "names", "name")} no element; ignored`,
944
1042
  { element: kind },
945
1043
  );
946
1044
  }
@@ -967,13 +1065,21 @@ class Cx2Reader {
967
1065
  continue;
968
1066
  }
969
1067
  for (const [key, table] of Object.entries(held.value)) {
970
- const domain = DECLARATION_KEYS[key as keyof typeof DECLARATION_KEYS] as
971
- | "node"
972
- | "edge"
973
- | "network"
974
- | undefined;
1068
+ const domain = DECLARATION_KEYS.get(key);
975
1069
  if (domain === undefined) {
976
- this.decls.other[key] = table;
1070
+ report.warning(
1071
+ "unsupported",
1072
+ UNKNOWN_ELEMENT_CODE,
1073
+ `attributeDeclarations holds the table "${key}", which CX2 does not define; kept in meta.extra.cx2.declarations, not applied`,
1074
+ { line: held.line, element: key },
1075
+ );
1076
+ setOwn(
1077
+ this.decls.other,
1078
+ key,
1079
+ plainJson(table, (digits) => {
1080
+ this.precision(key, digits);
1081
+ }),
1082
+ );
977
1083
  continue;
978
1084
  }
979
1085
  if (!isRecord(table)) {
@@ -1012,7 +1118,12 @@ class Cx2Reader {
1012
1118
  }
1013
1119
  for (const [key, raw] of Object.entries(value.v)) {
1014
1120
  const resolved = table.resolve(key);
1015
- if ((resolved !== null && resolved.decl.type !== null) || raw === null || raw === undefined) {
1121
+ if (
1122
+ key === "" ||
1123
+ (resolved !== null && resolved.decl.type !== null) ||
1124
+ raw === null ||
1125
+ raw === undefined
1126
+ ) {
1016
1127
  continue;
1017
1128
  }
1018
1129
  const name = resolved?.decl.name ?? key;
@@ -1060,6 +1171,15 @@ class Cx2Reader {
1060
1171
  const { report } = this;
1061
1172
  const table = this.decls[domain];
1062
1173
  const where = { line, element: name };
1174
+ if (name === "") {
1175
+ report.error(
1176
+ "validation-error",
1177
+ BAD_VALUE_CODE,
1178
+ `a ${domain} attribute declaration has an empty name; ignored`,
1179
+ where,
1180
+ );
1181
+ return;
1182
+ }
1063
1183
  if (!isRecord(raw)) {
1064
1184
  report.error(
1065
1185
  "validation-error",
@@ -1101,6 +1221,14 @@ class Cx2Reader {
1101
1221
  }
1102
1222
  return;
1103
1223
  }
1224
+ if (raw.a !== undefined && raw.a !== null && typeof raw.a !== "string") {
1225
+ report.error(
1226
+ "validation-error",
1227
+ BAD_VALUE_CODE,
1228
+ `the alias ${shown(raw.a)} of "${name}" is not a string; ignored`,
1229
+ where,
1230
+ );
1231
+ }
1104
1232
  let alias = typeof raw.a === "string" ? raw.a : null;
1105
1233
  let hasDefault = raw.v !== undefined && raw.v !== null;
1106
1234
  if (domain === "network" && (raw.a !== undefined || raw.v !== undefined)) {
@@ -1147,7 +1275,9 @@ class Cx2Reader {
1147
1275
  type: Cx2Type,
1148
1276
  where: { line?: number; element: string },
1149
1277
  ): unknown {
1150
- const converted = convertValue(value, type, this.options.long, () => undefined);
1278
+ const converted = convertValue(value, type, this.options.long, (digits) => {
1279
+ this.precision(name, digits);
1280
+ });
1151
1281
  if (converted !== BAD) {
1152
1282
  return converted;
1153
1283
  }
@@ -1281,10 +1411,11 @@ class Cx2Reader {
1281
1411
  * @param raw - the parsed id
1282
1412
  * @param inexact - whether the literal was not a plain integer
1283
1413
  * @param element - the element name for issues
1414
+ * @param line - the element's line
1284
1415
  * @param edgeId - whether this is an edge's own id (stored in the f64 id column, not kept as digits)
1285
1416
  * @returns the id, or null when it was reported
1286
1417
  */
1287
- private idOf(raw: unknown, inexact: boolean, element: string, edgeId = false): NodeId | null {
1418
+ private idOf(raw: unknown, inexact: boolean, element: string, line: number, edgeId = false): NodeId | null {
1288
1419
  let id: NodeId;
1289
1420
  try {
1290
1421
  const parsed = cxId(raw, inexact);
@@ -1293,7 +1424,7 @@ class Cx2Reader {
1293
1424
  "coercion",
1294
1425
  ID_TEXT_TYPE_CODE,
1295
1426
  `${element}: the id ${shown(raw)} is not written as an integer; read as ${String(parsed.id)}`,
1296
- { element },
1427
+ { line, element },
1297
1428
  );
1298
1429
  } else if (parsed.note === "precision" && edgeId) {
1299
1430
  // the edge is told apart by its digits, but the id column is f64
@@ -1301,7 +1432,7 @@ class Cx2Reader {
1301
1432
  "precision",
1302
1433
  PRECISION_CODE,
1303
1434
  `${element}: the edge id ${String(parsed.id)} is beyond 2^53; the id column holds the nearest double`,
1304
- { element },
1435
+ { line, element },
1305
1436
  `${PRECISION_CODE}:edge`,
1306
1437
  );
1307
1438
  } else if (parsed.note === "precision") {
@@ -1310,12 +1441,12 @@ class Cx2Reader {
1310
1441
  "precision",
1311
1442
  PRECISION_CODE,
1312
1443
  `${element}: the id ${String(parsed.id)} is beyond 2^53; kept as its digits (a string id)`,
1313
- { element },
1444
+ { line, element },
1314
1445
  );
1315
1446
  }
1316
1447
  id = this.options.ids === "keep" ? parsed.id : this.coercer.value(parsed.id);
1317
1448
  } catch (err) {
1318
- this.report.recordError(err, { element });
1449
+ this.report.recordError(err, { line, element });
1319
1450
  return null;
1320
1451
  }
1321
1452
  return id;
@@ -1377,7 +1508,7 @@ class Cx2Reader {
1377
1508
  return;
1378
1509
  }
1379
1510
  const element = `node ${shown(value.id)}`;
1380
- const cx = this.idOf(value.id, (held.inexact & 1) !== 0, element);
1511
+ const cx = this.idOf(value.id, (held.inexact & 1) !== 0, element, line);
1381
1512
  if (cx === null) {
1382
1513
  report.counts.skippedNodes++;
1383
1514
  return;
@@ -1398,7 +1529,19 @@ class Cx2Reader {
1398
1529
  { line, element },
1399
1530
  );
1400
1531
  } else {
1401
- const original = this.originalIdOf(attrs);
1532
+ let original = this.originalIdOf(attrs, element, line);
1533
+ if (original !== null && this.restored.has(original)) {
1534
+ report.warning(
1535
+ "merged",
1536
+ DUPLICATE_NODE_CODE,
1537
+ `${element}: its original id ${shown(original)} is already another node's; it keeps its CX id`,
1538
+ { line, element },
1539
+ );
1540
+ original = null;
1541
+ }
1542
+ if (original !== null) {
1543
+ this.restored.add(original);
1544
+ }
1402
1545
  this.idTypeOf(original ?? cx);
1403
1546
  try {
1404
1547
  row = sink.addNode(original ?? cx);
@@ -1420,14 +1563,24 @@ class Cx2Reader {
1420
1563
  /**
1421
1564
  * The original id the exporter's `sanitizeIds: "mangle"` kept, when it is to be restored.
1422
1565
  * @param attrs - the node's v
1566
+ * @param element - the element name for issues
1567
+ * @param line - its line
1423
1568
  * @returns the original id, or null
1424
1569
  */
1425
- private originalIdOf(attrs: Record<string, unknown> | null): NodeId | null {
1570
+ private originalIdOf(attrs: Record<string, unknown> | null, element: string, line: number): NodeId | null {
1426
1571
  if (attrs === null || !this.options.restoreMangledIds) {
1427
1572
  return null;
1428
1573
  }
1429
1574
  const alias = this.decls.node.byName.get(ORIGINAL_ID_ATTRIBUTE)?.alias ?? null;
1430
1575
  const raw = attrs[ORIGINAL_ID_ATTRIBUTE] ?? (alias === null ? undefined : attrs[alias]);
1576
+ if (raw !== undefined && raw !== null && typeof raw !== "string") {
1577
+ this.report.error(
1578
+ "validation-error",
1579
+ BAD_VALUE_CODE,
1580
+ `${element}: ${ORIGINAL_ID_ATTRIBUTE} is ${shown(raw)}, not a string; the node keeps its CX id`,
1581
+ { line, element },
1582
+ );
1583
+ }
1431
1584
  return typeof raw === "string" ? raw : null;
1432
1585
  }
1433
1586
 
@@ -1485,7 +1638,14 @@ class Cx2Reader {
1485
1638
  `${CX2_ISSUE.PARTIAL_LAYOUT}:z`,
1486
1639
  );
1487
1640
  }
1488
- if (x !== null && y !== null) {
1641
+ if (x !== null && y !== null && (!fitsF32(x) || !fitsF32(y))) {
1642
+ this.report.error(
1643
+ "validation-error",
1644
+ BAD_VALUE_CODE,
1645
+ `${element}: the coordinates ${String(x)}, ${String(y)} are beyond what the f32 position column holds; the position is unset`,
1646
+ { line, element },
1647
+ );
1648
+ } else if (x !== null && y !== null) {
1489
1649
  this.setPosition(row, x, y, z);
1490
1650
  }
1491
1651
  if (z !== null && this.zAs === "column") {
@@ -1533,11 +1693,14 @@ class Cx2Reader {
1533
1693
  this.report.warning(
1534
1694
  "unsupported",
1535
1695
  CX2_ISSUE.LEGACY_LAYOUT,
1536
- `a CX1 cartesianLayout aspect (${cartesianLayout.length} element(s)) next to node coordinates; the node coordinates are used, the aspect is kept in meta.extra.cx2`,
1696
+ `a CX1 cartesianLayout aspect (${cartesianLayout.length} element${plural(cartesianLayout.length)}) next to node coordinates; the node coordinates are used, the aspect is kept in meta.extra.cx2`,
1537
1697
  { element: "cartesianLayout" },
1538
1698
  );
1539
1699
  return;
1540
1700
  }
1701
+ // the first view named is the layout; a CX1 file with several views keeps the others
1702
+ let view: unknown;
1703
+ const seen = new Set<number>();
1541
1704
  for (const held of cartesianLayout) {
1542
1705
  const entry = held.value;
1543
1706
  if (!isRecord(entry) || typeof entry.x !== "number" || typeof entry.y !== "number") {
@@ -1554,6 +1717,31 @@ class Cx2Reader {
1554
1717
  this.dangle("cartesianLayout entry");
1555
1718
  continue;
1556
1719
  }
1720
+ view ??= entry.view ?? null;
1721
+ if ((entry.view ?? null) !== view) {
1722
+ this.doc.opaque.set(
1723
+ "cartesianLayout",
1724
+ cartesianLayout.map((h) => plainJson(h.value)),
1725
+ );
1726
+ this.report.warnOnce(
1727
+ "validation-error",
1728
+ DUPLICATE_ATTRIBUTE_CODE,
1729
+ `the cartesianLayout aspect names several views; view ${shown(view)} is read, the aspect is kept in meta.extra.cx2`,
1730
+ { line: held.line, element: "cartesianLayout" },
1731
+ `${DUPLICATE_ATTRIBUTE_CODE}:layout:view`,
1732
+ );
1733
+ continue;
1734
+ }
1735
+ if (seen.has(row)) {
1736
+ this.report.warnOnce(
1737
+ "validation-error",
1738
+ DUPLICATE_ATTRIBUTE_CODE,
1739
+ `node ${shown(entry.node)} has two cartesianLayout entries; the later wins`,
1740
+ { line: held.line, element: "cartesianLayout" },
1741
+ `${DUPLICATE_ATTRIBUTE_CODE}:layout`,
1742
+ );
1743
+ }
1744
+ seen.add(row);
1557
1745
  const z = typeof entry.z === "number" ? entry.z : null;
1558
1746
  this.setPosition(row, entry.x, entry.y, z);
1559
1747
  if (z !== null && this.zAs === "column") {
@@ -1609,6 +1797,15 @@ class Cx2Reader {
1609
1797
  const table = this.decls[domain];
1610
1798
  for (const key of Object.keys(attrs)) {
1611
1799
  const value = attrs[key];
1800
+ if (key === "") {
1801
+ report.error(
1802
+ "validation-error",
1803
+ BAD_VALUE_CODE,
1804
+ `${element}: v holds an attribute with an empty name; skipped`,
1805
+ { line, element },
1806
+ );
1807
+ continue;
1808
+ }
1612
1809
  const resolved = table.resolve(key);
1613
1810
  const name = resolved?.decl.name ?? key;
1614
1811
  if (domain === "node" && this.options.restoreMangledIds && name === ORIGINAL_ID_ATTRIBUTE) {
@@ -1632,7 +1829,7 @@ class Cx2Reader {
1632
1829
  resolved.decl.alias !== null &&
1633
1830
  resolved.decl.alias !== key
1634
1831
  ) {
1635
- if (resolved.decl.alias in attrs) {
1832
+ if (hasOwn(attrs, resolved.decl.alias)) {
1636
1833
  report.warning(
1637
1834
  "validation-error",
1638
1835
  DUPLICATE_ATTRIBUTE_CODE,
@@ -1741,9 +1938,9 @@ class Cx2Reader {
1741
1938
  return;
1742
1939
  }
1743
1940
  }
1744
- const id = this.idOf(value.id, (held.inexact & 1) !== 0, element, true);
1745
- const s = id === null ? null : this.idOf(value.s, (held.inexact & 2) !== 0, element);
1746
- const t = s === null ? null : this.idOf(value.t, (held.inexact & 4) !== 0, element);
1941
+ const id = this.idOf(value.id, (held.inexact & 1) !== 0, element, line, true);
1942
+ const s = id === null ? null : this.idOf(value.s, (held.inexact & 2) !== 0, element, line);
1943
+ const t = s === null ? null : this.idOf(value.t, (held.inexact & 4) !== 0, element, line);
1747
1944
  if (id === null || s === null || t === null) {
1748
1945
  report.counts.skippedEdges++;
1749
1946
  return;
@@ -1765,6 +1962,12 @@ class Cx2Reader {
1765
1962
  return;
1766
1963
  }
1767
1964
  const attrs = isRecord(value.v) ? value.v : null;
1965
+ if (value.v !== undefined && value.v !== null && attrs === null) {
1966
+ report.error("validation-error", BAD_VALUE_CODE, `${element}: v is ${shown(value.v)}, not an object`, {
1967
+ line,
1968
+ element,
1969
+ });
1970
+ }
1768
1971
  let weight: number | undefined;
1769
1972
  const weightKey = this.weightKey(attrs);
1770
1973
  try {
@@ -1805,10 +2008,10 @@ class Cx2Reader {
1805
2008
  return null;
1806
2009
  }
1807
2010
  const alias = this.decls.edge.byName.get(weightFrom)?.alias ?? null;
1808
- if (alias !== null && alias in attrs) {
2011
+ if (alias !== null && hasOwn(attrs, alias)) {
1809
2012
  return alias;
1810
2013
  }
1811
- return weightFrom in attrs ? weightFrom : null;
2014
+ return hasOwn(attrs, weightFrom) ? weightFrom : null;
1812
2015
  }
1813
2016
 
1814
2017
  /**
@@ -1938,7 +2141,7 @@ class Cx2Reader {
1938
2141
  */
1939
2142
  private readBypasses(domain: "node" | "edge", entries: readonly Held[]): void {
1940
2143
  const rows = domain === "node" ? this.nodeRows : this.edgeRows;
1941
- const columns = new Map<string, { rows: number[]; values: unknown[] }>();
2144
+ const columns = new Map<string, { rows: number[]; values: unknown[]; at: Map<number, number> }>();
1942
2145
  for (const held of entries) {
1943
2146
  this.checkAbort();
1944
2147
  const { value, line } = held;
@@ -1960,13 +2163,38 @@ class Cx2Reader {
1960
2163
  if (raw === null || raw === undefined) {
1961
2164
  continue;
1962
2165
  }
2166
+ if (property === "") {
2167
+ this.report.error(
2168
+ "validation-error",
2169
+ BAD_VALUE_CODE,
2170
+ `a ${domain}Bypasses element holds a visual property with an empty name; skipped`,
2171
+ { line, element: `${domain}Bypasses` },
2172
+ );
2173
+ continue;
2174
+ }
1963
2175
  let column = columns.get(property);
1964
2176
  if (column === undefined) {
1965
- column = { rows: [], values: [] };
2177
+ column = { rows: [], values: [], at: new Map() };
1966
2178
  columns.set(property, column);
1967
2179
  }
1968
- column.rows.push(row);
1969
- column.values.push(plainJson(raw));
2180
+ const plain = plainJson(raw);
2181
+ const index = column.at.get(row);
2182
+ if (index === undefined) {
2183
+ column.at.set(row, column.rows.length);
2184
+ column.rows.push(row);
2185
+ column.values.push(plain);
2186
+ continue;
2187
+ }
2188
+ if (JSON.stringify(column.values[index]) !== JSON.stringify(plain)) {
2189
+ this.report.warnOnce(
2190
+ "validation-error",
2191
+ DUPLICATE_ATTRIBUTE_CODE,
2192
+ `a ${domain} has the visual property "${property}" in two ${domain}Bypasses elements; the later wins`,
2193
+ { line, element: property },
2194
+ `${DUPLICATE_ATTRIBUTE_CODE}:bypass:${domain}:${property}`,
2195
+ );
2196
+ }
2197
+ column.values[index] = plain;
1970
2198
  }
1971
2199
  }
1972
2200
  for (const [property, { rows: targets, values }] of columns) {
@@ -2005,7 +2233,7 @@ class Cx2Reader {
2005
2233
  report.warning(
2006
2234
  "unsupported",
2007
2235
  STYLES_NOT_IMPORTED_CODE,
2008
- `the file's style rules are not applied (${styles.join("; ")}); they are kept in meta.extra.cx2.opaque (style import is issue #706)`,
2236
+ `the file's style rules are not applied (${styles.join("; ")}); they are kept in meta.extra.cx2.opaque`,
2009
2237
  { element: STYLE_ASPECTS.filter((a) => doc.opaque.has(a)).join(",") },
2010
2238
  );
2011
2239
  }
@@ -2118,15 +2346,16 @@ function describeStyle(aspect: string, elements: readonly unknown[]): string {
2118
2346
  const defaults = isRecord(first.default)
2119
2347
  ? Object.values(first.default).reduce<number>((n, v) => n + (isRecord(v) ? Object.keys(v).length : 0), 0)
2120
2348
  : 0;
2121
- return `visualProperties: ${defaults} default(s), ${count("nodeMapping")} node mapping(s), ${count("edgeMapping")} edge mapping(s)`;
2349
+ return `visualProperties: ${defaults} default${plural(defaults)}, ${count("nodeMapping")} node mapping${plural(count("nodeMapping"))}, ${count("edgeMapping")} edge mapping${plural(count("edgeMapping"))}`;
2122
2350
  }
2123
- return `${aspect}: ${elements.length} element(s)`;
2351
+ return `${aspect}: ${elements.length} element${plural(elements.length)}`;
2124
2352
  }
2125
2353
 
2126
2354
  /**
2127
2355
  * Resolve the zAs option.
2128
2356
  * @param value - the option
2129
2357
  * @returns the mode; E_UNSUPPORTED for any other value
2358
+ * @category Plugin helpers
2130
2359
  */
2131
2360
  export function zAsOption(value: unknown): "column" | "position" {
2132
2361
  if (value === undefined) {
@@ -2142,10 +2371,12 @@ export function zAsOption(value: unknown): "column" | "position" {
2142
2371
  }
2143
2372
 
2144
2373
  /**
2145
- * The CX2 importer plugin (design section 1.3).
2374
+ * The CX2 importer plugin.
2375
+ * @category Built-in formats
2146
2376
  */
2147
2377
  export const cx2Importer: GraphImporter<Cx2ImportOptions> = Object.freeze({
2148
2378
  format: CX2_FORMAT,
2379
+ options: Object.freeze(["zAs"]),
2149
2380
  extensions: Object.freeze([".cx2"]),
2150
2381
  mimeTypes: Object.freeze(["application/json"]),
2151
2382
 
@@ -2156,7 +2387,7 @@ export const cx2Importer: GraphImporter<Cx2ImportOptions> = Object.freeze({
2156
2387
  * @returns the confidence
2157
2388
  */
2158
2389
  sniff(head: Uint8Array): number {
2159
- const text = new TextDecoder("utf-8").decode(head.subarray(0, 1024)).replace(/^\uFEFF/, "");
2390
+ const text = headText(head);
2160
2391
  return /^\s*\[\s*\{(?:\s*"[^"]*"\s*:\s*(?:true|false|"[^"]*"|[0-9.]+)\s*,)*\s*"CXVersion"\s*:/.test(text)
2161
2392
  ? 0.97
2162
2393
  : 0;