@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
@@ -27,8 +27,8 @@
27
27
  */
28
28
  import { GraphFormatError, INVALID_INDEX, MAX_COUNT, } from "@graphty/graph-format";
29
29
  import { declareAttribute, declareCompanion, declareResolved, losesPrecision, parseDeclaredTemporal, parseDeclaredValue, PRECISION_CODE, } from "../../common/attributes.js";
30
- import { COUNT_HINT_CODE, DUPLICATE_ATTRIBUTE_CODE, DUPLICATE_EDGE_ID_CODE, DUPLICATE_NODE_CODE, ID_MERGED_CODE, MISSING_ENDPOINT_CODE, MISSING_ID_CODE, NO_GRAPH_CODE, STRAY_TEXT_CODE, UNKNOWN_ELEMENT_CODE, UNKNOWN_PARENT_CODE, XML_SYNTAX_CODE, } from "../../common/codes.js";
31
- import { parseDecimalText } from "../../common/declared-types.js";
30
+ import { BAD_VALUE_CODE, COUNT_HINT_CODE, COUNT_MISMATCH_CODE, DUPLICATE_ATTRIBUTE_CODE, DUPLICATE_EDGE_ID_CODE, DUPLICATE_NODE_CODE, EMPTY_INPUT_CODE, ID_MERGED_CODE, MISSING_ENDPOINT_CODE, MISSING_ID_CODE, MULTIPLE_GRAPHS_CODE, NO_GRAPH_CODE, PARENT_CYCLE_CODE, STRAY_TEXT_CODE, UNKNOWN_ELEMENT_CODE, UNKNOWN_PARENT_CODE, UNKNOWN_XML_ATTRIBUTE_CODE, XML_SYNTAX_CODE, } from "../../common/codes.js";
31
+ import { overflowsToInfinity, parseDecimalText } from "../../common/declared-types.js";
32
32
  import { DirectionResolver } from "../../common/direction.js";
33
33
  import { IdCoercer } from "../../common/ids.js";
34
34
  import { textChunks, throwIfAborted } from "../../common/input.js";
@@ -37,44 +37,169 @@ import { reportSinkOptions, reportUnusedOptions, resolveImportOptions, } from ".
37
37
  import { ImportReportBuilder } from "../../common/report.js";
38
38
  import { parseTimeText, timeTextCompanion } from "../../common/temporal.js";
39
39
  import { isWeightField, parseWeightText } from "../../common/weights.js";
40
- import { isWhitespace, localName, tokenizeXml, xmlDeclaredEncoding, XmlSyntaxError, } from "../../common/xml.js";
40
+ import { isWhitespace, localName, sniffXmlText, tokenizeXml, xmlDeclaredEncoding, XmlEmptyInputError, XmlSyntaxError, } from "../../common/xml.js";
41
41
  import { EDGE_DECLS, GEXF_FORMAT, GRAPH_MODES, NODE_DECLS, OPEN_END, OPEN_START, RESERVED_EDGE_NAMES, RESERVED_NODE_NAMES, temporalTableDecls, temporalTableName, TIME_FORMATS, TIME_REPRESENTATIONS, } from "./schema.js";
42
- /** Issue code: the document is not a GEXF document (no `<gexf>` root). */
42
+ /**
43
+ * The document is not a GEXF document (no `<gexf>` root).
44
+ * @category Issue and loss codes
45
+ */
43
46
  export const NOT_GEXF_CODE = "E_NOT_GEXF";
44
- /** Issue code: the graph declares edges but no `<nodes>` section. */
47
+ /**
48
+ * The graph declares edges but no `<nodes>` section.
49
+ * @category Issue and loss codes
50
+ */
45
51
  export const MISSING_NODES_CODE = "E_GEXF_MISSING_NODES";
46
- /** Issue code: an edge `type` outside directed / undirected / mutual. */
52
+ /**
53
+ * An edge `type` outside directed / undirected / mutual.
54
+ * @category Issue and loss codes
55
+ */
47
56
  export const EDGE_TYPE_CODE = "E_GEXF_EDGE_TYPE";
48
- /** Issue code: an `<attributes>` group without a class, or with an unknown one. */
57
+ /**
58
+ * An `<attributes>` group without a class, or with an unknown one.
59
+ * @category Issue and loss codes
60
+ */
49
61
  export const ATTRIBUTES_CLASS_CODE = "E_GEXF_ATTRIBUTES_CLASS";
50
- /** Issue code: an `<attribute>` without an id. */
62
+ /**
63
+ * An `<attribute>` without an id.
64
+ * @category Issue and loss codes
65
+ */
51
66
  export const ATTRIBUTE_ID_CODE = "E_GEXF_ATTRIBUTE_ID";
52
- /** Issue code: a graph header value (`defaultedgetype`, `mode`, `timeformat`, ...) outside its set. */
67
+ /**
68
+ * A graph header value (`defaultedgetype`, `mode`, `timeformat`, ...) outside its set.
69
+ * @category Issue and loss codes
70
+ */
53
71
  export const HEADER_VALUE_CODE = "W_GEXF_HEADER_VALUE";
54
- /** Issue code: an `<attribute>` without a type (read as string). */
72
+ /**
73
+ * An `<attribute>` without a type (read as string).
74
+ * @category Issue and loss codes
75
+ */
55
76
  export const ATTRIBUTE_TYPE_CODE = "W_GEXF_ATTRIBUTE_TYPE";
56
- /** Issue code: an `<attvalue>` naming an attribute the document never declares (once per id). */
77
+ /**
78
+ * An `<attvalue>` naming an attribute the document never declares (once per id).
79
+ * @category Issue and loss codes
80
+ */
57
81
  export const UNKNOWN_ATTRIBUTE_CODE = "W_GEXF_UNKNOWN_ATTRIBUTE";
58
- /** Issue code: an `<attvalue>` without a `for` or a `value`. */
82
+ /**
83
+ * An `<attvalue>` without a `for` or a `value`.
84
+ * @category Issue and loss codes
85
+ */
59
86
  export const ATTVALUE_SHAPE_CODE = "W_GEXF_ATTVALUE_SHAPE";
60
- /** Issue code: a timed value on an attribute declared in a static group (stored as dynamic anyway). */
87
+ /**
88
+ * A timed value on an attribute declared in a static group (stored as dynamic anyway).
89
+ * @category Issue and loss codes
90
+ */
61
91
  export const TIMED_STATIC_CODE = "W_GEXF_TIMED_VALUE_ON_STATIC";
62
- /** Issue code: the `weight` XML attribute is present but `weightFrom` is null. */
92
+ /**
93
+ * The `weight` XML attribute is present but `weightFrom` is null.
94
+ * @category Issue and loss codes
95
+ */
63
96
  export const WEIGHT_IGNORED_CODE = "W_GEXF_WEIGHT_IGNORED";
64
- /** Issue code: viz elements skipped under `viz: false`. */
97
+ /**
98
+ * Viz elements skipped under `viz: false`.
99
+ * @category Issue and loss codes
100
+ */
65
101
  export const VIZ_SKIPPED_CODE = "W_GEXF_VIZ_SKIPPED";
66
- /** Issue code: a 1.2 dynamic viz element (its bounds are dropped, the value kept). */
102
+ /**
103
+ * A 1.2 dynamic viz element (its bounds are dropped, the value kept).
104
+ * @category Issue and loss codes
105
+ */
67
106
  export const VIZ_DYNAMIC_CODE = "W_GEXF_VIZ_DYNAMIC_DROPPED";
68
107
  /**
69
- * Issue code: `startopen` / `endopen` on a `<spell>` stored closed. No longer recorded: the open
70
- * bits of each spell are kept in the spells.open column. Kept so the exported code table is stable.
108
+ * `startopen` / `endopen` on a `<spell>` stored closed. No longer recorded: the open bits of each spell are kept in
109
+ * the spells.open column. Kept so the exported code table is stable.
110
+ * @category Issue and loss codes
71
111
  */
72
112
  export const SPELL_OPEN_CODE = "W_GEXF_SPELL_OPEN_DROPPED";
73
- /** Issue code: a viz element with a value that does not parse (the element is skipped). */
113
+ /**
114
+ * A viz element with a value that does not parse (the element is skipped).
115
+ * @category Issue and loss codes
116
+ */
74
117
  export const VIZ_VALUE_CODE = "W_GEXF_VIZ_VALUE";
75
- /** Issue code: both `start` and `startopen` (or `end` and `endopen`) on one element; the closed bound wins. */
118
+ /**
119
+ * Both `start` and `startopen` (or `end` and `endopen`) on one element; the closed bound wins.
120
+ * @category Issue and loss codes
121
+ */
76
122
  export const OPEN_BOUND_CONFLICT_CODE = "W_GEXF_OPEN_BOUND_CONFLICT";
123
+ /**
124
+ * Both `timestamp` and `start` / `end` on one element or value; `start` / `end` win.
125
+ * @category Issue and loss codes
126
+ */
127
+ export const TIMESTAMP_CONFLICT_CODE = "W_GEXF_TIMESTAMP_CONFLICT";
128
+ /**
129
+ * A value outside the `<options>` its attribute declares (the value is kept).
130
+ * @category Issue and loss codes
131
+ */
132
+ export const VALUE_OUTSIDE_OPTIONS_CODE = "W_GEXF_VALUE_OUTSIDE_OPTIONS";
77
133
  export { DUPLICATE_NODE_CODE, ID_MERGED_CODE, MISSING_ENDPOINT_CODE, MISSING_ID_CODE, NO_GRAPH_CODE, UNKNOWN_PARENT_CODE, };
134
+ /** The GEXF versions graph-io knows, and the version a namespace implies when `version` is missing. */
135
+ const VERSIONS = new Set(["1.0", "1.1", "1.2", "1.3"]);
136
+ /**
137
+ * The version a GEXF namespace names (`http://www.gexf.net/1.2draft`, `http://gexf.net/1.3`).
138
+ * @param ns - the root's namespace
139
+ * @returns the version, or null
140
+ */
141
+ function namespaceVersion(ns) {
142
+ const match = ns === null ? null : /gexf\.net\/(1\.[0-3])/.exec(ns);
143
+ return match === null ? null : match[1];
144
+ }
145
+ /**
146
+ * Whether a namespace is a GEXF viz namespace (`http://www.gexf.net/1.2draft/viz`, the 1.1
147
+ * triple-slash `http://gexf.net/1.1draft///viz`, `http://gexf.net/1.3/viz`, GEXF 1.0's `http://www.gephi.org/gexf/viz`).
148
+ * @param ns - the element's namespace
149
+ * @returns true for a viz namespace
150
+ */
151
+ function isVizNamespace(ns) {
152
+ return ns !== null && /(gexf\.net\/.*viz|gephi\.org\/gexf\/viz)\/?$/.test(ns);
153
+ }
154
+ /** Distinct unknown attribute names reported one by one per element kind; the rest are counted in one issue. */
155
+ const MAX_UNKNOWN_ATTRIBUTE_NAMES = 16;
156
+ /** The XML attributes the importer reads on each element other than `<node>` / `<edge>`. */
157
+ const ELEMENT_ATTRIBUTES = {
158
+ gexf: new Set(["version", "variant"]),
159
+ meta: new Set(["lastmodifieddate"]),
160
+ graph: new Set([
161
+ "defaultedgetype",
162
+ "mode",
163
+ "type",
164
+ "timeformat",
165
+ "timerepresentation",
166
+ "idtype",
167
+ "start",
168
+ "end",
169
+ "timestamp",
170
+ "startopen",
171
+ "endopen",
172
+ ]),
173
+ // `type` is GEXF 1.0's name for `mode` (static | dynamic)
174
+ attributes: new Set(["class", "mode", "type", "start", "end", "startopen", "endopen"]),
175
+ attribute: new Set(["id", "title", "type"]),
176
+ attvalue: new Set(["for", "value", "start", "end", "timestamp", "startopen", "endopen", "id"]),
177
+ spell: new Set(["start", "end", "timestamp", "startopen", "endopen"]),
178
+ parent: new Set(["for"]),
179
+ nodes: new Set(["count"]),
180
+ edges: new Set(["count"]),
181
+ };
182
+ /** A `count` hint as written: decimal digits only (as GraphML's parse.nodes). */
183
+ const COUNT_TEXT = /^\d+$/;
184
+ /** An xs:date / xs:dateTime text (`lastmodifieddate`). */
185
+ const DATE_TEXT = /^-?\d{4,}-\d{2}-\d{2}(.*)$/s;
186
+ /** The time of an xs:dateTime, after its date. */
187
+ const TIME_TEXT = /^T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?/;
188
+ /** The zone that may end an xs:date or xs:dateTime. */
189
+ const ZONE_TEXT = /^(?:Z|[+-]\d{2}:\d{2})?$/;
190
+ /**
191
+ * Whether a text is an xs:date or xs:dateTime.
192
+ * @param text - the text
193
+ * @returns true when it is
194
+ */
195
+ function isDateText(text) {
196
+ const m = DATE_TEXT.exec(text);
197
+ if (m === null) {
198
+ return false;
199
+ }
200
+ const time = TIME_TEXT.exec(m[1])?.[0] ?? "";
201
+ return ZONE_TEXT.test(m[1].slice(time.length));
202
+ }
78
203
  const EDGE_TYPES = new Set(["directed", "undirected", "mutual"]);
79
204
  /**
80
205
  * An edge type as a keyword of {@link EDGE_TYPES}: the schema spells them in lower case, but files
@@ -274,7 +399,15 @@ class GexfReader {
274
399
  index: INVALID_INDEX,
275
400
  mirror: INVALID_INDEX,
276
401
  where: { line: 0, element: null },
402
+ xmlWeight: null,
277
403
  };
404
+ /** The `for` ids of the static values of the open `<attvalues>`, for the duplicate check. */
405
+ this.attvalueIds = new Set();
406
+ /** The open `<nodes>` / `<edges>` sections: their count hint and the elements read directly in them. */
407
+ this.sections = [];
408
+ /** Unknown attribute names reported per element kind, and how many more were not listed. */
409
+ this.unknownNames = new Map();
410
+ this.unknownOverflow = new Map();
278
411
  /** The one `<attvalue>` location, reused per value (its fields are copied into every issue). */
279
412
  this.valueWhere = { line: 0, element: null };
280
413
  this.attributeGroup = null;
@@ -321,15 +454,26 @@ class GexfReader {
321
454
  };
322
455
  }
323
456
  // ---------------------------------------------------------------- events
324
- start(name, attrs, line) {
457
+ start(name, attrs, line, ns = "") {
325
458
  const parent = this.ctx.length === 0 ? null : this.ctx[this.ctx.length - 1];
326
459
  const local = localName(name);
460
+ if (ns === null &&
461
+ parent !== null &&
462
+ parent !== Ctx.Skip &&
463
+ parent !== Ctx.MetaText &&
464
+ parent !== Ctx.AttributeText &&
465
+ !name.startsWith("viz:")) {
466
+ // an undeclared prefix or a name that is not namespace-well-formed (<:node>, <a:b:node>)
467
+ this.unknownElement(name, line);
468
+ return;
469
+ }
327
470
  switch (parent) {
328
471
  case null:
329
472
  if (local !== "gexf") {
330
473
  this.report.fail(NOT_GEXF_CODE, `the root element is <${name}>, not <gexf>`, { line });
331
474
  }
332
- this.readRoot(attrs);
475
+ this.checkAttributes("gexf", attrs, line);
476
+ this.readRoot(attrs, ns, line);
333
477
  this.ctx.push(Ctx.Gexf);
334
478
  return;
335
479
  case Ctx.Gexf:
@@ -351,7 +495,7 @@ class GexfReader {
351
495
  this.startInNodes(name, local, attrs, line);
352
496
  return;
353
497
  case Ctx.Node:
354
- this.startInNode(name, local, attrs, line);
498
+ this.startInNode(name, local, attrs, line, ns);
355
499
  return;
356
500
  case Ctx.Attvalues:
357
501
  this.startInAttvalues(name, local, attrs, line);
@@ -366,7 +510,7 @@ class GexfReader {
366
510
  this.startInEdges(name, local, attrs, line);
367
511
  return;
368
512
  case Ctx.Edge:
369
- this.startInEdge(name, local, attrs, line);
513
+ this.startInEdge(name, local, attrs, line, ns);
370
514
  return;
371
515
  case Ctx.MetaText:
372
516
  case Ctx.AttributeText:
@@ -406,6 +550,10 @@ class GexfReader {
406
550
  case Ctx.Node:
407
551
  this.nodeStack.pop();
408
552
  return;
553
+ case Ctx.Nodes:
554
+ case Ctx.Edges:
555
+ this.finishSection();
556
+ return;
409
557
  case Ctx.Edge:
410
558
  this.edge = null;
411
559
  return;
@@ -416,8 +564,6 @@ class GexfReader {
416
564
  this.finishSpells();
417
565
  return;
418
566
  case Ctx.Gexf:
419
- case Ctx.Nodes:
420
- case Ctx.Edges:
421
567
  case Ctx.Attvalues:
422
568
  case Ctx.Skip:
423
569
  case undefined:
@@ -458,16 +604,30 @@ class GexfReader {
458
604
  if (!this.graphSeen) {
459
605
  this.report.fail(NO_GRAPH_CODE, "the document has no <graph> element");
460
606
  }
607
+ for (const [kind, more] of this.unknownOverflow) {
608
+ this.report.warning("unsupported", UNKNOWN_XML_ATTRIBUTE_CODE, `${more} more distinct attribute names on <${kind}> are not GEXF attributes and are not kept (not listed one by one)`, { element: kind });
609
+ }
461
610
  }
462
611
  // ---------------------------------------------------------------- root, meta, graph
463
612
  /**
464
- * Record the root's version.
613
+ * Record the root's version: as written when known; the namespace's when absent; an unknown
614
+ * one is kept as written and reported.
465
615
  * @param attrs - the `<gexf>` attributes
616
+ * @param ns - the root's namespace
617
+ * @param line - the line
466
618
  */
467
- readRoot(attrs) {
619
+ readRoot(attrs, ns, line) {
468
620
  const version = attrs.get("version");
469
621
  if (version !== undefined && version.length > 0) {
470
622
  this.meta.sourceVersion = version;
623
+ if (!VERSIONS.has(version)) {
624
+ this.report.warning("validation-error", HEADER_VALUE_CODE, `<gexf version="${version}"> is not a GEXF version graph-io knows (1.0 to 1.3); read as GEXF 1.3`, { line, element: "version" });
625
+ }
626
+ return;
627
+ }
628
+ const implied = namespaceVersion(ns);
629
+ if (implied !== null) {
630
+ this.meta.sourceVersion = implied;
471
631
  }
472
632
  }
473
633
  /**
@@ -480,21 +640,27 @@ class GexfReader {
480
640
  startInGexf(name, local, attrs, line) {
481
641
  switch (local) {
482
642
  case "meta": {
643
+ this.checkAttributes("meta", attrs, line);
483
644
  const modified = attrs.get("lastmodifieddate");
484
645
  if (modified !== undefined && modified.length > 0) {
485
646
  this.meta.modified = modified;
647
+ if (!isDateText(modified.trim())) {
648
+ this.report.warning("validation-error", HEADER_VALUE_CODE, `<meta lastmodifieddate="${modified}"> is not an xs:date; kept as written`, { line, element: "lastmodifieddate" });
649
+ }
486
650
  }
487
651
  this.ctx.push(Ctx.Meta);
488
652
  return;
489
653
  }
490
654
  case "graph":
655
+ this.checkAttributes("graph", attrs, line);
491
656
  if (this.graphSeen) {
492
- this.report.fail(NO_GRAPH_CODE, "a second <graph> element; a GEXF document has one graph", {
493
- line,
494
- });
657
+ // the schema allows one graph; merging is what the GraphML importer does too
658
+ this.report.warnOnce("unsupported", MULTIPLE_GRAPHS_CODE, "a second <graph> element; its nodes and edges are merged into the first and its header is ignored", { line });
659
+ }
660
+ else {
661
+ this.graphSeen = true;
662
+ this.readHeader(attrs, line);
495
663
  }
496
- this.graphSeen = true;
497
- this.readHeader(attrs, line);
498
664
  this.ctx.push(Ctx.Graph);
499
665
  return;
500
666
  default:
@@ -538,9 +704,11 @@ class GexfReader {
538
704
  this.metaField = null;
539
705
  this.metaText = "";
540
706
  }
541
- /** Close `<meta>`: nothing more to do (the patch is applied at `<graph>`). */
707
+ /** Close `<meta>`: the patch is applied with the graph header, or now when `<meta>` follows the graph. */
542
708
  finishMeta() {
543
- // the meta patch is applied when the graph header is read
709
+ if (this.graphSeen) {
710
+ this.sink.setMeta(this.meta);
711
+ }
544
712
  }
545
713
  /**
546
714
  * Read `<graph defaultedgetype mode timeformat timerepresentation idtype start end timestamp>`
@@ -594,7 +762,7 @@ class GexfReader {
594
762
  const idType = attrs.get("idtype");
595
763
  if (idType !== undefined) {
596
764
  if (idType === "string") {
597
- // informational only: Gephi writes idtype="string" unconditionally, so honouring it
765
+ // informational only: Gephi writes idtype="string" unconditionally, so honoring it
598
766
  // would turn every Gephi export's integer ids into text; the canonical rule of
599
767
  // design section 4.1 applies and the exporter's check() reports W_ID_TEXT_TYPE
600
768
  patch.idType = "string";
@@ -642,39 +810,59 @@ class GexfReader {
642
810
  return;
643
811
  case "nodes":
644
812
  this.nodesSeen = true;
645
- this.reserve(attrs, "node", line);
646
- this.ctx.push(Ctx.Nodes);
813
+ this.beginSection(attrs, "node", line);
647
814
  return;
648
815
  case "edges":
649
816
  if (!this.nodesSeen) {
650
817
  this.report.error("validation-error", MISSING_NODES_CODE, "the graph declares <edges> but no <nodes>", { line });
651
818
  }
652
819
  this.resolveParents();
653
- this.reserve(attrs, "edge", line);
654
- this.ctx.push(Ctx.Edges);
820
+ this.beginSection(attrs, "edge", line);
655
821
  return;
656
822
  default:
657
823
  this.unknownElement(name, line);
658
824
  }
659
825
  }
826
+ /**
827
+ * Open a `<nodes>` / `<edges>` section: its count hint is reserved and later compared with the
828
+ * elements the section holds.
829
+ * @param attrs - the section's attributes
830
+ * @param domain - node or edge
831
+ * @param line - the line
832
+ */
833
+ beginSection(attrs, domain, line) {
834
+ this.checkAttributes(`${domain}s`, attrs, line);
835
+ this.sections.push({ domain, hint: this.reserve(attrs, domain, line), seen: 0, line });
836
+ this.ctx.push(domain === "node" ? Ctx.Nodes : Ctx.Edges);
837
+ }
838
+ /** Close a `<nodes>` / `<edges>` section: a count hint that disagrees is reported. */
839
+ finishSection() {
840
+ const section = this.sections.pop();
841
+ if (section?.hint === undefined || section.hint === null || section.hint === section.seen) {
842
+ return;
843
+ }
844
+ this.report.warning("validation-error", COUNT_MISMATCH_CODE, `<${section.domain}s count="${section.hint}"> but the section holds ${section.seen} <${section.domain}> elements (nested ones included)`, { line: section.line, element: "count" });
845
+ }
660
846
  /**
661
847
  * Pass a section's `count` hint to the sink; a hint the sink cannot reserve is a warning.
662
848
  * @param attrs - the `<nodes>` or `<edges>` attributes
663
849
  * @param domain - which count it is
664
850
  * @param line - the line
851
+ * @returns the hint, or null when absent or ignored
665
852
  */
666
853
  reserve(attrs, domain, line) {
667
854
  const text = attrs.get("count");
668
855
  if (text === undefined) {
669
- return;
856
+ return null;
670
857
  }
671
- const count = Number(text);
672
- if (!Number.isInteger(count) || count < 0 || count > MAX_COUNT) {
858
+ // decimal digits only: Number() would read "" as 0, "0x10" as 16 and "1e3" as 1000
859
+ const count = COUNT_TEXT.test(text) ? Number(text) : Number.NaN;
860
+ if (!Number.isSafeInteger(count) || count > MAX_COUNT) {
673
861
  this.report.warning("validation-error", COUNT_HINT_CODE, `<${domain}s count="${text}"> is not a count the sink can reserve; ignored`, { line, element: "count" });
674
- return;
862
+ return null;
675
863
  }
676
864
  if (count === 0) {
677
- return;
865
+ return count;
678
866
  }
679
867
  try {
680
868
  if (domain === "node") {
@@ -693,6 +881,7 @@ class GexfReader {
693
881
  element: "count",
694
882
  });
695
883
  }
884
+ return count;
696
885
  }
697
886
  // ---------------------------------------------------------------- attribute declarations
698
887
  /**
@@ -701,6 +890,7 @@ class GexfReader {
701
890
  * @param line - the line
702
891
  */
703
892
  beginAttributes(attrs, line) {
893
+ this.checkAttributes("attributes", attrs, line);
704
894
  const cls = attrs.get("class");
705
895
  const where = { line };
706
896
  let domain = null;
@@ -712,7 +902,7 @@ class GexfReader {
712
902
  ? "<attributes> without a class"
713
903
  : `<attributes class="${cls}"> is neither node nor edge`, where);
714
904
  }
715
- const mode = attrs.get("mode");
905
+ const mode = attrs.get("mode") ?? attrs.get("type");
716
906
  if (mode !== undefined && mode !== "static" && mode !== "dynamic") {
717
907
  this.warnHeader("mode", mode, where);
718
908
  }
@@ -731,6 +921,7 @@ class GexfReader {
731
921
  this.unknownElement(name, line);
732
922
  return;
733
923
  }
924
+ this.checkAttributes("attribute", attrs, line);
734
925
  const group = this.attributeGroup;
735
926
  if (group === null || group.domain === null) {
736
927
  // the group's class was refused; its attributes are dropped with it
@@ -810,13 +1001,20 @@ class GexfReader {
810
1001
  report.warning("validation-error", DUPLICATE_ATTRIBUTE_CODE, `${domain} attribute "${id}" is declared twice; the first declaration is kept`, where);
811
1002
  return;
812
1003
  }
1004
+ if (this.unknownAttributes.has(`${domain}:${id}`)) {
1005
+ report.warning("missing-value", UNKNOWN_ATTRIBUTE_CODE, `${domain} attribute "${id}" is declared after values that use it; those values were dropped (declare <attributes> before <nodes>)`, where);
1006
+ }
813
1007
  const title = attrs.get("title") ?? null;
814
1008
  const type = attrs.get("type") ?? null;
815
1009
  if (type === null) {
816
1010
  report.warning("validation-error", ATTRIBUTE_TYPE_CODE, `attribute "${id}" declares no type; read as string`, where);
817
1011
  }
818
1012
  const name = title !== null && title.length > 0 ? title : id;
819
- if (domain === "edge" && isWeightField(name, options.weightFrom)) {
1013
+ if (domain === "edge" && this.weightAttr !== null && isWeightField(name, options.weightFrom)) {
1014
+ // the first attribute of that title stays THE weight; this one becomes a plain column
1015
+ report.warning("validation-error", DUPLICATE_ATTRIBUTE_CODE, `edge attribute "${id}" is also titled ${name}; attribute "${this.weightAttr.id}" is the weight, "${id}" is kept as a column`, where);
1016
+ }
1017
+ else if (domain === "edge" && isWeightField(name, options.weightFrom)) {
820
1018
  const declared = declareAttribute({ format: GEXF_FORMAT, id, title, type, long: options.long });
821
1019
  for (const issue of declared.issues) {
822
1020
  report.warning(issue.category, issue.code, issue.message, where);
@@ -827,7 +1025,8 @@ class GexfReader {
827
1025
  }
828
1026
  const declaredNames = domain === "node" ? this.declaredNodeNames : this.declaredEdgeNames;
829
1027
  const reserved = domain === "node" ? RESERVED_NODE_NAMES : RESERVED_EDGE_NAMES;
830
- const taken = (candidate) => reserved.has(candidate) || declaredNames.has(candidate);
1028
+ const weightName = domain === "edge" && this.weightAttr !== null ? options.weightFrom : null;
1029
+ const taken = (candidate) => reserved.has(candidate) || declaredNames.has(candidate) || candidate === weightName;
831
1030
  const input = {
832
1031
  format: GEXF_FORMAT,
833
1032
  id,
@@ -900,7 +1099,9 @@ class GexfReader {
900
1099
  const enclosing = this.nodeStack.length === 0 ? INVALID_INDEX : this.nodeStack[this.nodeStack.length - 1].index;
901
1100
  const idText = attrs.get("id");
902
1101
  const where = { line, element: idText ?? attrs.get("label") ?? null };
1102
+ this.countInSection("node");
903
1103
  this.checkAttributeNames(attrs, NODE_ATTRIBUTES, "node", where);
1104
+ // GEXF types ids as xs:string: id="" is a legal id (the exporter writes it), only a missing one is not
904
1105
  if (idText === undefined) {
905
1106
  report.error("missing-value", MISSING_ID_CODE, "<node> without an id", where);
906
1107
  report.counts.skippedNodes++;
@@ -908,6 +1109,9 @@ class GexfReader {
908
1109
  this.ctx.push(Ctx.Node);
909
1110
  return;
910
1111
  }
1112
+ if (this.meta.idType === "integer" && !/^\s*[+-]?\d+\s*$/.test(idText)) {
1113
+ report.warnOnce("validation-error", HEADER_VALUE_CODE, `node id "${idText}" is not an integer although the graph declares idtype="integer"; ids follow the ids option`, where, `${HEADER_VALUE_CODE}:idtype`);
1114
+ }
911
1115
  let index;
912
1116
  try {
913
1117
  const id = this.coerceId(idText, where);
@@ -930,11 +1134,17 @@ class GexfReader {
930
1134
  }
931
1135
  this.nodeStack.push({ index, where });
932
1136
  this.ctx.push(Ctx.Node);
1137
+ // three independent parts: a bad pid must not cost the label or the lifetime
933
1138
  try {
934
1139
  const label = attrs.get("label");
935
1140
  if (label !== undefined) {
936
1141
  this.nodeColumns.label.set(sink, report, index, label, where);
937
1142
  }
1143
+ }
1144
+ catch (err) {
1145
+ report.recordError(err, where);
1146
+ }
1147
+ try {
938
1148
  const pid = attrs.get("pid");
939
1149
  if (pid !== undefined) {
940
1150
  this.parentRefs.push({
@@ -947,28 +1157,50 @@ class GexfReader {
947
1157
  else if (enclosing !== INVALID_INDEX) {
948
1158
  this.parentRefs.push({ child: index, parentId: null, parentIndex: enclosing, where });
949
1159
  }
1160
+ }
1161
+ catch (err) {
1162
+ report.recordError(err, where);
1163
+ }
1164
+ try {
950
1165
  this.writeLifetime(attrs, index, this.nodeColumns, where);
951
1166
  }
952
1167
  catch (err) {
953
1168
  report.recordError(err, where);
954
1169
  }
955
1170
  }
1171
+ /**
1172
+ * Count a `<node>` / `<edge>` element in the open sections of its kind, for the count hint
1173
+ * check: a `<nodes count>` counts the nodes nested in its nodes too.
1174
+ * @param domain - node or edge
1175
+ */
1176
+ countInSection(domain) {
1177
+ for (const section of this.sections) {
1178
+ if (section.domain === domain) {
1179
+ section.seen++;
1180
+ }
1181
+ }
1182
+ }
956
1183
  /**
957
1184
  * A child of `<node>`.
958
1185
  * @param name - the element name
959
1186
  * @param local - its local name
960
1187
  * @param attrs - its attributes
961
1188
  * @param line - the line
1189
+ * @param ns - the element's namespace
962
1190
  */
963
- startInNode(name, local, attrs, line) {
1191
+ startInNode(name, local, attrs, line, ns) {
964
1192
  const frame = this.nodeStack[this.nodeStack.length - 1];
965
1193
  if (frame.index === INVALID_INDEX && local !== "nodes") {
966
1194
  // the node was skipped; its children are too
967
1195
  this.ctx.push(Ctx.Skip);
968
1196
  return;
969
1197
  }
1198
+ if (frame.index === INVALID_INDEX) {
1199
+ this.report.error("missing-value", UNKNOWN_PARENT_CODE, "the nodes nested in this skipped <node> lose their parent; they are read as top-level nodes", { line });
1200
+ }
970
1201
  switch (local) {
971
1202
  case "attvalues":
1203
+ this.attvalueIds.clear();
972
1204
  this.ctx.push(Ctx.Attvalues);
973
1205
  return;
974
1206
  case "parents":
@@ -982,13 +1214,16 @@ class GexfReader {
982
1214
  this.ctx.push(Ctx.Spells);
983
1215
  return;
984
1216
  case "nodes":
985
- this.reserve(attrs, "node", line);
986
- this.ctx.push(Ctx.Nodes);
1217
+ this.beginSection(attrs, "node", line);
987
1218
  return;
988
1219
  case "color":
989
1220
  case "position":
990
1221
  case "size":
991
1222
  case "shape":
1223
+ if (!isViz(name, ns)) {
1224
+ this.unknownElement(name, line);
1225
+ return;
1226
+ }
992
1227
  this.writeViz(name, local, attrs, "node", frame.index, frame.where);
993
1228
  this.ctx.push(Ctx.Skip);
994
1229
  return;
@@ -1009,10 +1244,11 @@ class GexfReader {
1009
1244
  return;
1010
1245
  }
1011
1246
  this.ctx.push(Ctx.Skip);
1247
+ this.checkAttributes("parent", attrs, line);
1012
1248
  const frame = this.nodeStack[this.nodeStack.length - 1];
1013
1249
  const text = attrs.get("for");
1014
1250
  if (text === undefined) {
1015
- this.report.warning("missing-value", ATTVALUE_SHAPE_CODE, "<parent> without a for attribute", frame.where);
1251
+ this.report.error("missing-value", UNKNOWN_PARENT_CODE, "<parent> without a for attribute names no parent", frame.where);
1016
1252
  return;
1017
1253
  }
1018
1254
  try {
@@ -1040,6 +1276,20 @@ class GexfReader {
1040
1276
  const lists = this.parentsRefs;
1041
1277
  this.parentRefs = [];
1042
1278
  this.parentsRefs = [];
1279
+ // each node's parent so far, as the parent column holds it: a link closes a cycle when the
1280
+ // child is the parent or one of its ancestors. A repeated link (a duplicate node declaring
1281
+ // the same pid) is not a cycle, which an undirected union-find would wrongly report.
1282
+ // ponytail: O(depth) per link, so O(n * depth); fine for real hierarchies, add path
1283
+ // compression over an ancestry index if a deep chain ever shows up in a profile.
1284
+ const parentOf = new Map();
1285
+ const isAncestorOrSelf = (candidate, of) => {
1286
+ for (let at = of; at !== undefined; at = parentOf.get(at)) {
1287
+ if (at === candidate) {
1288
+ return true;
1289
+ }
1290
+ }
1291
+ return false;
1292
+ };
1043
1293
  for (const ref of refs) {
1044
1294
  let parent = ref.parentIndex;
1045
1295
  if (ref.parentId !== null) {
@@ -1049,8 +1299,16 @@ class GexfReader {
1049
1299
  continue;
1050
1300
  }
1051
1301
  }
1302
+ if (parentOf.get(ref.child) === parent) {
1303
+ continue;
1304
+ }
1305
+ if (isAncestorOrSelf(ref.child, parent)) {
1306
+ report.error("validation-error", PARENT_CYCLE_CODE, `the parent link of node "${ref.where.element ?? "?"}" would close a parent cycle; it is dropped`, ref.where);
1307
+ continue;
1308
+ }
1052
1309
  try {
1053
1310
  this.parentColumn.set(sink, report, ref.child, parent, ref.where);
1311
+ parentOf.set(ref.child, parent);
1054
1312
  }
1055
1313
  catch (err) {
1056
1314
  report.recordError(err, ref.where);
@@ -1105,7 +1363,9 @@ class GexfReader {
1105
1363
  const { where } = this.edgeFrame;
1106
1364
  where.line = line;
1107
1365
  where.element = idText ?? edgeElement(sourceText, targetText);
1366
+ this.countInSection("edge");
1108
1367
  this.checkAttributeNames(attrs, EDGE_ATTRIBUTES, "edge", where);
1368
+ // source="" names the node whose id is "" (legal in GEXF), so only a missing endpoint is E_MISSING_ENDPOINT
1109
1369
  if (sourceText === undefined || targetText === undefined) {
1110
1370
  report.error("missing-value", MISSING_ENDPOINT_CODE, `<edge> without a ${sourceText === undefined ? "source" : "target"}`, where);
1111
1371
  report.counts.skippedEdges++;
@@ -1122,13 +1382,10 @@ class GexfReader {
1122
1382
  }
1123
1383
  kind = type;
1124
1384
  }
1125
- if (idText !== undefined) {
1126
- if (this.edgeIds.has(idText)) {
1127
- report.error("validation-error", DUPLICATE_EDGE_ID_CODE, `edge id "${idText}" is declared more than once; the edge is skipped`, where);
1128
- report.counts.skippedEdges++;
1129
- return;
1130
- }
1131
- this.edgeIds.add(idText);
1385
+ if (idText !== undefined && this.edgeIds.has(idText)) {
1386
+ report.error("validation-error", DUPLICATE_EDGE_ID_CODE, `edge id "${idText}" is declared more than once; the edge is skipped`, where);
1387
+ report.counts.skippedEdges++;
1388
+ return;
1132
1389
  }
1133
1390
  let index;
1134
1391
  try {
@@ -1156,9 +1413,14 @@ class GexfReader {
1156
1413
  report.counts.skippedEdges++;
1157
1414
  return;
1158
1415
  }
1416
+ // the id is claimed only by an edge that was added, so a rejected edge does not cost the next
1417
+ if (idText !== undefined) {
1418
+ this.edgeIds.add(idText);
1419
+ }
1159
1420
  const frame = this.edgeFrame;
1160
1421
  frame.index = index;
1161
1422
  frame.mirror = this.resolver.lastMirror;
1423
+ frame.xmlWeight = attrs.get("weight") ?? null;
1162
1424
  this.edge = frame;
1163
1425
  try {
1164
1426
  if (idText !== undefined) {
@@ -1194,7 +1456,21 @@ class GexfReader {
1194
1456
  this.report.warnOnce("coercion", WEIGHT_IGNORED_CODE, "the weight attribute is ignored because weightFrom is null; edges are unweighted", where);
1195
1457
  return undefined;
1196
1458
  }
1197
- return parseWeightText(text);
1459
+ // a blank weight="" is an absent weight (common/weights.ts), as a blank weight attvalue is
1460
+ const weight = parseWeightText(text, this.report);
1461
+ this.checkOverflow(text, where);
1462
+ return weight;
1463
+ }
1464
+ /**
1465
+ * Report (once) a weight text too large for a double, stored as Infinity; `INF` itself is a
1466
+ * legal weight (design section 3.7) and is not reported.
1467
+ * @param text - the weight text
1468
+ * @param where - the location
1469
+ */
1470
+ checkOverflow(text, where) {
1471
+ if (overflowsToInfinity(text)) {
1472
+ this.report.warnOnce("precision", PRECISION_CODE, `weight ${text.trim()} is beyond the double range and is stored as Infinity`, where, `${PRECISION_CODE}:weight`);
1473
+ }
1198
1474
  }
1199
1475
  /**
1200
1476
  * A child of `<edge>`.
@@ -1202,8 +1478,9 @@ class GexfReader {
1202
1478
  * @param local - its local name
1203
1479
  * @param attrs - its attributes
1204
1480
  * @param line - the line
1481
+ * @param ns - the element's namespace
1205
1482
  */
1206
- startInEdge(name, local, attrs, line) {
1483
+ startInEdge(name, local, attrs, line, ns) {
1207
1484
  const { edge } = this;
1208
1485
  if (edge === null) {
1209
1486
  this.ctx.push(Ctx.Skip);
@@ -1211,6 +1488,7 @@ class GexfReader {
1211
1488
  }
1212
1489
  switch (local) {
1213
1490
  case "attvalues":
1491
+ this.attvalueIds.clear();
1214
1492
  this.ctx.push(Ctx.Attvalues);
1215
1493
  return;
1216
1494
  case "spells":
@@ -1222,6 +1500,10 @@ class GexfReader {
1222
1500
  case "color":
1223
1501
  case "thickness":
1224
1502
  case "shape":
1503
+ if (!isViz(name, ns)) {
1504
+ this.unknownElement(name, line);
1505
+ return;
1506
+ }
1225
1507
  this.writeViz(name, local, attrs, "edge", edge.index, edge.where);
1226
1508
  this.ctx.push(Ctx.Skip);
1227
1509
  return;
@@ -1244,6 +1526,7 @@ class GexfReader {
1244
1526
  return;
1245
1527
  }
1246
1528
  this.ctx.push(Ctx.Skip);
1529
+ this.checkAttributes("attvalue", attrs, line);
1247
1530
  const { report, edge } = this;
1248
1531
  const domain = edge === null ? "node" : "edge";
1249
1532
  let index;
@@ -1273,6 +1556,12 @@ class GexfReader {
1273
1556
  report.warning("missing-value", ATTVALUE_SHAPE_CODE, `<attvalue for="${forId}"> without a value attribute`, valueWhere);
1274
1557
  return;
1275
1558
  }
1559
+ if (!isTimed(attrs)) {
1560
+ if (this.attvalueIds.has(forId)) {
1561
+ report.warning("validation-error", DUPLICATE_ATTRIBUTE_CODE, `a second static <attvalue for="${forId}"> on one ${domain}; the later value "${value}" is kept`, { ...valueWhere, element: forId });
1562
+ }
1563
+ this.attvalueIds.add(forId);
1564
+ }
1276
1565
  try {
1277
1566
  if (domain === "edge" && this.weightAttr !== null && this.weightAttr.id === forId) {
1278
1567
  if (isTimed(attrs)) {
@@ -1315,12 +1604,14 @@ class GexfReader {
1315
1604
  if (edge === null) {
1316
1605
  return;
1317
1606
  }
1318
- const weight = parseWeightText(text);
1607
+ const weight = parseWeightText(text, this.report);
1319
1608
  if (weight === undefined) {
1320
- throw new GraphFormatError("E_INVALID_WEIGHT", "an edge weight cannot be blank", {
1321
- value: text,
1322
- line: where.line ?? null,
1323
- });
1609
+ // blank is absent (common/weights.ts), as a blank weight="" XML attribute is
1610
+ return;
1611
+ }
1612
+ this.checkOverflow(text, where);
1613
+ if (edge.xmlWeight !== null) {
1614
+ this.report.warning("validation-error", DUPLICATE_ATTRIBUTE_CODE, `the edge has both weight="${edge.xmlWeight}" and a weight attvalue "${text}"; the attvalue is kept`, where);
1324
1615
  }
1325
1616
  sink.setEdgeWeight(edge.index, weight);
1326
1617
  if (edge.mirror !== INVALID_INDEX) {
@@ -1353,8 +1644,30 @@ class GexfReader {
1353
1644
  if (losesPrecision(spec, text)) {
1354
1645
  this.report.warning("precision", PRECISION_CODE, `long value ${text.trim()} of "${attribute.name}" exceeds 2^53 and was rounded`, where);
1355
1646
  }
1647
+ else if (typeof value === "number" && overflowsToInfinity(text)) {
1648
+ this.report.warnOnce("precision", PRECISION_CODE, `values of "${attribute.name}" beyond the double range (${text.trim()}) are stored as Infinity`, where, `${PRECISION_CODE}:overflow:${attribute.name}`);
1649
+ }
1650
+ this.checkOptions(attribute, value, where);
1356
1651
  this.setCell(domain, attribute.handle, index, value);
1357
1652
  }
1653
+ /**
1654
+ * Report (once per attribute) a value outside the `<options>` the attribute declares; the
1655
+ * value is kept.
1656
+ * @param attribute - the declaration
1657
+ * @param value - the parsed value (a list checks each item)
1658
+ * @param where - the value's location
1659
+ */
1660
+ checkOptions(attribute, value, where) {
1661
+ const { options } = attribute.valueDecl;
1662
+ if (options === undefined || options.length === 0) {
1663
+ return;
1664
+ }
1665
+ const items = Array.isArray(value) ? value : [value];
1666
+ const outside = items.find((item) => !options.includes(item));
1667
+ if (typeof outside === "string" || typeof outside === "number" || typeof outside === "boolean") {
1668
+ this.report.warnOnce("validation-error", VALUE_OUTSIDE_OPTIONS_CODE, `"${outside}" is not among the options of attribute "${attribute.name}"; the value is kept`, where, `${VALUE_OUTSIDE_OPTIONS_CODE}:${attribute.name}`);
1669
+ }
1670
+ }
1358
1671
  /**
1359
1672
  * Append a timed value to the attribute's temporal table (design section 5.10), creating the
1360
1673
  * table on first use.
@@ -1389,6 +1702,9 @@ class GexfReader {
1389
1702
  }
1390
1703
  }
1391
1704
  const row = this.temporalRow(attrs, index, value, where);
1705
+ if (row === null) {
1706
+ return;
1707
+ }
1392
1708
  if (temporalValue) {
1393
1709
  row.push(valueText ?? undefined);
1394
1710
  }
@@ -1413,14 +1729,15 @@ class GexfReader {
1413
1729
  origin: { format: GEXF_FORMAT, id: weightAttr.id, type: weightAttr.spec.declared },
1414
1730
  }, false));
1415
1731
  }
1416
- const value = parseWeightText(text);
1732
+ const value = parseWeightText(text, this.report);
1417
1733
  if (value === undefined) {
1418
- throw new GraphFormatError("E_INVALID_WEIGHT", "a timed edge weight cannot be blank", {
1419
- value: text,
1420
- line: where.line ?? null,
1421
- });
1734
+ // blank is absent (common/weights.ts): no timed value
1735
+ return;
1736
+ }
1737
+ const row = this.temporalRow(attrs, index, value, where);
1738
+ if (row !== null) {
1739
+ sink.addExtensionRow(weightAttr.table, row);
1422
1740
  }
1423
- sink.addExtensionRow(weightAttr.table, this.temporalRow(attrs, index, value, where));
1424
1741
  }
1425
1742
  /**
1426
1743
  * The row of a temporal table for one timed value: element, bounds, value, bound texts, open bits.
@@ -1428,10 +1745,13 @@ class GexfReader {
1428
1745
  * @param index - the element's index
1429
1746
  * @param value - the parsed value
1430
1747
  * @param where - the value's location
1431
- * @returns the row, without the optional value text
1748
+ * @returns the row, without the optional value text; null when the interval is inverted (recorded)
1432
1749
  */
1433
1750
  temporalRow(attrs, index, value, where) {
1434
1751
  const bounds = this.readBounds(attrs, where);
1752
+ if (bounds === null) {
1753
+ return null;
1754
+ }
1435
1755
  return [
1436
1756
  index,
1437
1757
  bounds.start.value,
@@ -1448,7 +1768,7 @@ class GexfReader {
1448
1768
  * section 5.1); missing bounds are unbounded.
1449
1769
  * @param attrs - the attributes
1450
1770
  * @param where - the location, for the conflict warning
1451
- * @returns the bounds and the open bits
1771
+ * @returns the bounds and the open bits; null (recorded as E_BAD_VALUE) when the interval is inverted
1452
1772
  */
1453
1773
  readBounds(attrs, where) {
1454
1774
  let start = { value: -Infinity, text: null };
@@ -1458,6 +1778,9 @@ class GexfReader {
1458
1778
  if (timestamp !== undefined) {
1459
1779
  start = parseTimeText(timestamp, this.timeFormat);
1460
1780
  end = start;
1781
+ if (attrs.has("start") || attrs.has("end") || attrs.has("startopen") || attrs.has("endopen")) {
1782
+ this.report.warnOnce("validation-error", TIMESTAMP_CONFLICT_CODE, "both timestamp and start / end are given; start / end set the interval and the timestamp is kept as written", where);
1783
+ }
1461
1784
  }
1462
1785
  const startText = attrs.get("start");
1463
1786
  const startOpen = attrs.get("startopen");
@@ -1483,6 +1806,10 @@ class GexfReader {
1483
1806
  end = parseTimeText(endOpen, this.timeFormat);
1484
1807
  open |= OPEN_END;
1485
1808
  }
1809
+ if (start.value > end.value) {
1810
+ this.report.error("validation-error", BAD_VALUE_CODE, `the interval starts after it ends (${start.text ?? String(start.value)} > ${end.text ?? String(end.value)}); it is not kept`, where);
1811
+ return null;
1812
+ }
1486
1813
  return { start, end, open };
1487
1814
  }
1488
1815
  /**
@@ -1510,9 +1837,13 @@ class GexfReader {
1510
1837
  attrs.has("startopen") ||
1511
1838
  attrs.has("endopen")) {
1512
1839
  const bounds = this.readBounds(attrs, where);
1840
+ if (bounds === null) {
1841
+ return;
1842
+ }
1513
1843
  const timestamp = attrs.get("timestamp");
1514
1844
  if (timestamp !== undefined) {
1515
- columns.timestamp.set(sink, report, index, bounds.start, where);
1845
+ // parsed again: beside start / startopen, bounds.start is the start, not the timestamp
1846
+ columns.timestamp.set(sink, report, index, parseTimeText(timestamp, this.timeFormat), where);
1516
1847
  }
1517
1848
  if (attrs.has("start") || attrs.has("startopen")) {
1518
1849
  columns.start.set(sink, report, index, bounds.start, where);
@@ -1553,10 +1884,15 @@ class GexfReader {
1553
1884
  line: where.line ?? null,
1554
1885
  });
1555
1886
  }
1556
- pairs.push([
1887
+ const pair = [
1557
1888
  parseTimeText(items[0], this.timeFormat).value,
1558
1889
  parseTimeText(items[1], this.timeFormat).value,
1559
- ]);
1890
+ ];
1891
+ if (pair[0] > pair[1]) {
1892
+ this.report.error("validation-error", BAD_VALUE_CODE, `interval "${part.trim()}" starts after it ends; it is not kept`, where);
1893
+ continue;
1894
+ }
1895
+ pairs.push(pair);
1560
1896
  }
1561
1897
  return pairs;
1562
1898
  }
@@ -1573,9 +1909,13 @@ class GexfReader {
1573
1909
  return;
1574
1910
  }
1575
1911
  this.ctx.push(Ctx.Skip);
1912
+ this.checkAttributes("spell", attrs, line);
1576
1913
  const where = this.spellsWhere ?? { line };
1577
1914
  try {
1578
1915
  const bounds = this.readBounds(attrs, where);
1916
+ if (bounds === null) {
1917
+ return;
1918
+ }
1579
1919
  this.spellPairs?.push([bounds.start.value, bounds.end.value]);
1580
1920
  this.spellOpen.push(bounds.open);
1581
1921
  }
@@ -1691,23 +2031,47 @@ class GexfReader {
1691
2031
  this.ctx.push(Ctx.Skip);
1692
2032
  }
1693
2033
  /**
1694
- * Report XML attributes the importer does not read (once per attribute name and domain).
2034
+ * Report XML attributes the importer does not read (once per attribute name and element kind;
2035
+ * beyond MAX_UNKNOWN_ATTRIBUTE_NAMES distinct names per kind the rest are counted and reported
2036
+ * once at the end). Namespace declarations and `xml:` / `xsi:` attributes are not data.
1695
2037
  * @param attrs - the element's attributes
1696
2038
  * @param known - the attribute names read
1697
- * @param domain - node or edge
2039
+ * @param kind - the element name
1698
2040
  * @param where - the element's location
1699
2041
  */
1700
- checkAttributeNames(attrs, known, domain, where) {
2042
+ checkAttributeNames(attrs, known, kind, where) {
2043
+ if (attrs.size === 0) {
2044
+ return;
2045
+ }
1701
2046
  for (const key of attrs.keys()) {
1702
- if (!known.has(key)) {
1703
- const seen = `${domain}@${key}`;
1704
- if (!this.unknownAttributes.has(seen)) {
1705
- this.unknownAttributes.add(seen);
1706
- this.report.warning("unsupported", UNKNOWN_ELEMENT_CODE, `attribute ${key} of <${domain}> is not GEXF and was ignored`, { ...where, element: key });
1707
- }
2047
+ if (known.has(key) || key === "xmlns" || /^(xmlns|xml|xsi):/.test(key)) {
2048
+ continue;
2049
+ }
2050
+ let names = this.unknownNames.get(kind);
2051
+ if (names === undefined) {
2052
+ names = new Set();
2053
+ this.unknownNames.set(kind, names);
1708
2054
  }
2055
+ if (names.has(key)) {
2056
+ continue;
2057
+ }
2058
+ names.add(key);
2059
+ if (names.size > MAX_UNKNOWN_ATTRIBUTE_NAMES) {
2060
+ this.unknownOverflow.set(kind, (this.unknownOverflow.get(kind) ?? 0) + 1);
2061
+ continue;
2062
+ }
2063
+ this.report.warning("unsupported", UNKNOWN_XML_ATTRIBUTE_CODE, `attribute ${key} of <${kind}> is not GEXF and was ignored`, { ...where, element: key });
1709
2064
  }
1710
2065
  }
2066
+ /**
2067
+ * Report the XML attributes the importer does not read on an element other than node / edge.
2068
+ * @param kind - the element's local name (a key of ELEMENT_ATTRIBUTES)
2069
+ * @param attrs - its attributes
2070
+ * @param line - the line
2071
+ */
2072
+ checkAttributes(kind, attrs, line) {
2073
+ this.checkAttributeNames(attrs, ELEMENT_ATTRIBUTES[kind] ?? new Set(), kind, { line });
2074
+ }
1711
2075
  /**
1712
2076
  * Write one cell of a domain.
1713
2077
  * @param domain - node or edge
@@ -1745,6 +2109,16 @@ class GexfReader {
1745
2109
  }
1746
2110
  }
1747
2111
  }
2112
+ /**
2113
+ * Whether a node / edge child named like a viz element is one: in a viz namespace, or written
2114
+ * with the `viz:` prefix the document never declares (hand-written files often omit it).
2115
+ * @param name - the element name as written
2116
+ * @param ns - its namespace
2117
+ * @returns true for a viz element
2118
+ */
2119
+ function isViz(name, ns) {
2120
+ return isVizNamespace(ns) || (ns === null && name.startsWith("viz:"));
2121
+ }
1748
2122
  /**
1749
2123
  * The element text of an edge without an id, for issues.
1750
2124
  * @param source - the source text, or undefined
@@ -1811,7 +2185,7 @@ function parseColor(attrs) {
1811
2185
  if (hex !== undefined && !attrs.has("r")) {
1812
2186
  const digits = hex.startsWith("#") ? hex.slice(1) : hex;
1813
2187
  if (!/^[0-9a-fA-F]{6}$/.test(digits)) {
1814
- throw new GraphFormatError("E_COLUMN_TYPE", `viz hex="${hex}" is not a #RRGGBB colour`, {
2188
+ throw new GraphFormatError("E_COLUMN_TYPE", `viz hex="${hex}" is not a #RRGGBB color`, {
1815
2189
  attribute: "hex",
1816
2190
  });
1817
2191
  }
@@ -1836,7 +2210,7 @@ const XML_START = /^\uFEFF?\s*</;
1836
2210
  * not start with markup
1837
2211
  */
1838
2212
  function sniffGexf(head) {
1839
- const text = new TextDecoder("utf-8", { fatal: false }).decode(head);
2213
+ const text = sniffXmlText(head);
1840
2214
  // Only a document that starts as markup: a JSON or CSV value may mention a `<gexf>` tag.
1841
2215
  if (!XML_START.test(text)) {
1842
2216
  return 0;
@@ -1864,9 +2238,13 @@ function resolveGexfOptions(options) {
1864
2238
  }
1865
2239
  return viz;
1866
2240
  }
1867
- /** The GEXF importer (design section 8.4). */
2241
+ /**
2242
+ * The GEXF importer.
2243
+ * @category Built-in formats
2244
+ */
1868
2245
  export const gexfImporter = Object.freeze({
1869
2246
  format: GEXF_FORMAT,
2247
+ options: Object.freeze(["viz"]),
1870
2248
  extensions: Object.freeze([".gexf"]),
1871
2249
  mimeTypes: Object.freeze(["application/gexf+xml", "application/xml", "text/xml"]),
1872
2250
  sniff: sniffGexf,
@@ -1891,9 +2269,12 @@ export const gexfImporter = Object.freeze({
1891
2269
  reportUnusedOptions(options, report, USED_OPTIONS);
1892
2270
  const reader = new GexfReader(sink, report, resolved, viz);
1893
2271
  try {
1894
- await tokenizeXml(textChunks(input, report, { ...resolved, declaredEncoding: xmlDeclaredEncoding }), reader);
2272
+ await tokenizeXml(textChunks(input, report, { ...resolved, declaredEncoding: xmlDeclaredEncoding, xml: true }), reader);
1895
2273
  }
1896
2274
  catch (err) {
2275
+ if (err instanceof XmlEmptyInputError) {
2276
+ report.fail(EMPTY_INPUT_CODE, "the input is empty (no markup at all)", { line: err.line });
2277
+ }
1897
2278
  if (err instanceof XmlSyntaxError) {
1898
2279
  report.fail(XML_SYNTAX_CODE, err.message, { line: err.line });
1899
2280
  }