@graphty/graph-io 0.3.25 → 0.3.27

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-DrXtu_96.js +56 -0
  4. package/dist/chunks/choose-DrXtu_96.js.map +1 -0
  5. package/dist/chunks/constants-ikG85dT8.js +231 -0
  6. package/dist/chunks/constants-ikG85dT8.js.map +1 -0
  7. package/dist/chunks/{escape-B-tkk_Cl.js → escape-D1HPTUrP.js} +390 -99
  8. package/dist/chunks/escape-D1HPTUrP.js.map +1 -0
  9. package/dist/chunks/export-CHd4tQE2.js +4333 -0
  10. package/dist/chunks/export-CHd4tQE2.js.map +1 -0
  11. package/dist/chunks/{importer-eUREhBGj.js → exporter-D23CKNqF.js} +4969 -3201
  12. package/dist/chunks/exporter-D23CKNqF.js.map +1 -0
  13. package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-DfWTgrti.js} +23 -21
  14. package/dist/chunks/exporter-DfWTgrti.js.map +1 -0
  15. package/dist/chunks/exporter-DhtW6FxK.js +3465 -0
  16. package/dist/chunks/exporter-DhtW6FxK.js.map +1 -0
  17. package/dist/chunks/{exporter-BGrsWamJ.js → exporter-IHv4nkCm.js} +38 -229
  18. package/dist/chunks/exporter-IHv4nkCm.js.map +1 -0
  19. package/dist/chunks/{importer-BWY2FFc5.js → importer-B1wmPQjr.js} +255 -77
  20. package/dist/chunks/importer-B1wmPQjr.js.map +1 -0
  21. package/dist/chunks/{importer-D_e7e7LX.js → importer-B2W_58pz.js} +399 -146
  22. package/dist/chunks/importer-B2W_58pz.js.map +1 -0
  23. package/dist/chunks/importer-BSALvkui.js +2811 -0
  24. package/dist/chunks/importer-BSALvkui.js.map +1 -0
  25. package/dist/chunks/{importer-vi3bSdtw.js → importer-BVRE--Un.js} +510 -77
  26. package/dist/chunks/importer-BVRE--Un.js.map +1 -0
  27. package/dist/chunks/{importer-DET1tg6s.js → importer-CIAzt-wo.js} +1689 -312
  28. package/dist/chunks/importer-CIAzt-wo.js.map +1 -0
  29. package/dist/chunks/{importer-DIrbbnAf.js → importer-DpEW5zeG.js} +585 -226
  30. package/dist/chunks/importer-DpEW5zeG.js.map +1 -0
  31. package/dist/chunks/{importer-zGLo8Dg_.js → importer-z_FL4VMb.js} +488 -161
  32. package/dist/chunks/importer-z_FL4VMb.js.map +1 -0
  33. package/dist/chunks/json-elements-BAmNBZok.js +1225 -0
  34. package/dist/chunks/json-elements-BAmNBZok.js.map +1 -0
  35. package/dist/chunks/ontology-export-Bab4eUA8.js +357 -0
  36. package/dist/chunks/ontology-export-Bab4eUA8.js.map +1 -0
  37. package/dist/chunks/{records-dbRkxwaq.js → records-CdlvL4RO.js} +298 -114
  38. package/dist/chunks/records-CdlvL4RO.js.map +1 -0
  39. package/dist/chunks/{text-Dr0Ifpag.js → text-DzXjAiTl.js} +29 -11
  40. package/dist/chunks/text-DzXjAiTl.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 +952 -262
  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 +344 -87
  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 +565 -158
  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 +21 -3
  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 +39 -22
  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 +869 -189
  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 +3 -1
  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 +402 -85
  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 +146 -33
  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 +682 -287
  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 +227 -80
  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 +101 -27
  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 +110 -28
  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 +239 -86
  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 +67 -35
  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 +186 -54
  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 +21 -22
  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 +8 -3
  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 +40 -6
  468. package/src/common/direction.ts +78 -51
  469. package/src/common/escape.ts +39 -22
  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 +1035 -199
  475. package/src/common/line-format.ts +249 -0
  476. package/src/common/lists.ts +7 -2
  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 +479 -90
  487. package/src/common/zip.ts +169 -7
  488. package/src/formats/csv/exporter.ts +202 -47
  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 +890 -302
  496. package/src/formats/cx/index.ts +4 -3
  497. package/src/formats/cx2/exporter.ts +62 -339
  498. package/src/formats/cx2/importer.ts +318 -87
  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 +142 -34
  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 +113 -28
  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 +321 -102
  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 +90 -42
  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 +216 -55
  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 +36 -28
  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 +12 -4
  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
@@ -50,20 +50,26 @@ import {
50
50
  PRECISION_CODE,
51
51
  } from "../../common/attributes.js";
52
52
  import {
53
+ BAD_VALUE_CODE,
53
54
  COUNT_HINT_CODE,
55
+ COUNT_MISMATCH_CODE,
54
56
  DUPLICATE_ATTRIBUTE_CODE,
55
57
  DUPLICATE_EDGE_ID_CODE,
56
58
  DUPLICATE_NODE_CODE,
59
+ EMPTY_INPUT_CODE,
57
60
  ID_MERGED_CODE,
58
61
  MISSING_ENDPOINT_CODE,
59
62
  MISSING_ID_CODE,
63
+ MULTIPLE_GRAPHS_CODE,
60
64
  NO_GRAPH_CODE,
65
+ PARENT_CYCLE_CODE,
61
66
  STRAY_TEXT_CODE,
62
67
  UNKNOWN_ELEMENT_CODE,
63
68
  UNKNOWN_PARENT_CODE,
69
+ UNKNOWN_XML_ATTRIBUTE_CODE,
64
70
  XML_SYNTAX_CODE,
65
71
  } from "../../common/codes.js";
66
- import { type DeclaredTypeSpec, parseDecimalText } from "../../common/declared-types.js";
72
+ import { type DeclaredTypeSpec, overflowsToInfinity, parseDecimalText } from "../../common/declared-types.js";
67
73
  import { DirectionResolver, type EdgeKind } from "../../common/direction.js";
68
74
  import { IdCoercer } from "../../common/ids.js";
69
75
  import { textChunks, throwIfAborted } from "../../common/input.js";
@@ -80,8 +86,10 @@ import { isWeightField, parseWeightText } from "../../common/weights.js";
80
86
  import {
81
87
  isWhitespace,
82
88
  localName,
89
+ sniffXmlText,
83
90
  tokenizeXml,
84
91
  xmlDeclaredEncoding,
92
+ XmlEmptyInputError,
85
93
  type XmlHandler,
86
94
  XmlSyntaxError,
87
95
  } from "../../common/xml.js";
@@ -102,50 +110,111 @@ import {
102
110
  TIME_REPRESENTATIONS,
103
111
  } from "./schema.js";
104
112
 
105
- /** The format-specific options of the GEXF importer. */
106
- export interface GexfImportOptions {
113
+ /**
114
+ * The format-specific options of the GEXF importer.
115
+ * @category Built-in formats
116
+ */
117
+ export interface GexfImportOptions extends CommonImportOptions {
107
118
  /**
108
- * Whether the viz namespace elements (color, position, size, shape, thickness) are imported as
109
- * role columns (default true); false ignores them and records one `unsupported` warning.
119
+ * Whether to read the visual attributes of Gephi's viz namespace (color, position, size, shape,
120
+ * thickness) into node and edge attributes; false skips them, with one W_GEXF_VIZ_SKIPPED
121
+ * warning.
122
+ * @defaultValue true
110
123
  */
111
124
  viz?: boolean | undefined;
112
125
  }
113
126
 
114
- /** Issue code: the document is not a GEXF document (no `<gexf>` root). */
127
+ /**
128
+ * The document is not a GEXF document (no `<gexf>` root).
129
+ * @category Issue and loss codes
130
+ */
115
131
  export const NOT_GEXF_CODE = "E_NOT_GEXF";
116
- /** Issue code: the graph declares edges but no `<nodes>` section. */
132
+ /**
133
+ * The graph declares edges but no `<nodes>` section.
134
+ * @category Issue and loss codes
135
+ */
117
136
  export const MISSING_NODES_CODE = "E_GEXF_MISSING_NODES";
118
- /** Issue code: an edge `type` outside directed / undirected / mutual. */
137
+ /**
138
+ * An edge `type` outside directed / undirected / mutual.
139
+ * @category Issue and loss codes
140
+ */
119
141
  export const EDGE_TYPE_CODE = "E_GEXF_EDGE_TYPE";
120
- /** Issue code: an `<attributes>` group without a class, or with an unknown one. */
142
+ /**
143
+ * An `<attributes>` group without a class, or with an unknown one.
144
+ * @category Issue and loss codes
145
+ */
121
146
  export const ATTRIBUTES_CLASS_CODE = "E_GEXF_ATTRIBUTES_CLASS";
122
- /** Issue code: an `<attribute>` without an id. */
147
+ /**
148
+ * An `<attribute>` without an id.
149
+ * @category Issue and loss codes
150
+ */
123
151
  export const ATTRIBUTE_ID_CODE = "E_GEXF_ATTRIBUTE_ID";
124
- /** Issue code: a graph header value (`defaultedgetype`, `mode`, `timeformat`, ...) outside its set. */
152
+ /**
153
+ * A graph header value (`defaultedgetype`, `mode`, `timeformat`, ...) outside its set.
154
+ * @category Issue and loss codes
155
+ */
125
156
  export const HEADER_VALUE_CODE = "W_GEXF_HEADER_VALUE";
126
- /** Issue code: an `<attribute>` without a type (read as string). */
157
+ /**
158
+ * An `<attribute>` without a type (read as string).
159
+ * @category Issue and loss codes
160
+ */
127
161
  export const ATTRIBUTE_TYPE_CODE = "W_GEXF_ATTRIBUTE_TYPE";
128
- /** Issue code: an `<attvalue>` naming an attribute the document never declares (once per id). */
162
+ /**
163
+ * An `<attvalue>` naming an attribute the document never declares (once per id).
164
+ * @category Issue and loss codes
165
+ */
129
166
  export const UNKNOWN_ATTRIBUTE_CODE = "W_GEXF_UNKNOWN_ATTRIBUTE";
130
- /** Issue code: an `<attvalue>` without a `for` or a `value`. */
167
+ /**
168
+ * An `<attvalue>` without a `for` or a `value`.
169
+ * @category Issue and loss codes
170
+ */
131
171
  export const ATTVALUE_SHAPE_CODE = "W_GEXF_ATTVALUE_SHAPE";
132
- /** Issue code: a timed value on an attribute declared in a static group (stored as dynamic anyway). */
172
+ /**
173
+ * A timed value on an attribute declared in a static group (stored as dynamic anyway).
174
+ * @category Issue and loss codes
175
+ */
133
176
  export const TIMED_STATIC_CODE = "W_GEXF_TIMED_VALUE_ON_STATIC";
134
- /** Issue code: the `weight` XML attribute is present but `weightFrom` is null. */
177
+ /**
178
+ * The `weight` XML attribute is present but `weightFrom` is null.
179
+ * @category Issue and loss codes
180
+ */
135
181
  export const WEIGHT_IGNORED_CODE = "W_GEXF_WEIGHT_IGNORED";
136
- /** Issue code: viz elements skipped under `viz: false`. */
182
+ /**
183
+ * Viz elements skipped under `viz: false`.
184
+ * @category Issue and loss codes
185
+ */
137
186
  export const VIZ_SKIPPED_CODE = "W_GEXF_VIZ_SKIPPED";
138
- /** Issue code: a 1.2 dynamic viz element (its bounds are dropped, the value kept). */
187
+ /**
188
+ * A 1.2 dynamic viz element (its bounds are dropped, the value kept).
189
+ * @category Issue and loss codes
190
+ */
139
191
  export const VIZ_DYNAMIC_CODE = "W_GEXF_VIZ_DYNAMIC_DROPPED";
140
192
  /**
141
- * Issue code: `startopen` / `endopen` on a `<spell>` stored closed. No longer recorded: the open
142
- * bits of each spell are kept in the spells.open column. Kept so the exported code table is stable.
193
+ * `startopen` / `endopen` on a `<spell>` stored closed. No longer recorded: the open bits of each spell are kept in
194
+ * the spells.open column. Kept so the exported code table is stable.
195
+ * @category Issue and loss codes
143
196
  */
144
197
  export const SPELL_OPEN_CODE = "W_GEXF_SPELL_OPEN_DROPPED";
145
- /** Issue code: a viz element with a value that does not parse (the element is skipped). */
198
+ /**
199
+ * A viz element with a value that does not parse (the element is skipped).
200
+ * @category Issue and loss codes
201
+ */
146
202
  export const VIZ_VALUE_CODE = "W_GEXF_VIZ_VALUE";
147
- /** Issue code: both `start` and `startopen` (or `end` and `endopen`) on one element; the closed bound wins. */
203
+ /**
204
+ * Both `start` and `startopen` (or `end` and `endopen`) on one element; the closed bound wins.
205
+ * @category Issue and loss codes
206
+ */
148
207
  export const OPEN_BOUND_CONFLICT_CODE = "W_GEXF_OPEN_BOUND_CONFLICT";
208
+ /**
209
+ * Both `timestamp` and `start` / `end` on one element or value; `start` / `end` win.
210
+ * @category Issue and loss codes
211
+ */
212
+ export const TIMESTAMP_CONFLICT_CODE = "W_GEXF_TIMESTAMP_CONFLICT";
213
+ /**
214
+ * A value outside the `<options>` its attribute declares (the value is kept).
215
+ * @category Issue and loss codes
216
+ */
217
+ export const VALUE_OUTSIDE_OPTIONS_CODE = "W_GEXF_VALUE_OUTSIDE_OPTIONS";
149
218
 
150
219
  export {
151
220
  DUPLICATE_NODE_CODE,
@@ -156,6 +225,85 @@ export {
156
225
  UNKNOWN_PARENT_CODE,
157
226
  };
158
227
 
228
+ /** The GEXF versions graph-io knows, and the version a namespace implies when `version` is missing. */
229
+ const VERSIONS: ReadonlySet<string> = new Set(["1.0", "1.1", "1.2", "1.3"]);
230
+
231
+ /**
232
+ * The version a GEXF namespace names (`http://www.gexf.net/1.2draft`, `http://gexf.net/1.3`).
233
+ * @param ns - the root's namespace
234
+ * @returns the version, or null
235
+ */
236
+ function namespaceVersion(ns: string | null): string | null {
237
+ const match = ns === null ? null : /gexf\.net\/(1\.[0-3])/.exec(ns);
238
+ return match === null ? null : match[1];
239
+ }
240
+
241
+ /**
242
+ * Whether a namespace is a GEXF viz namespace (`http://www.gexf.net/1.2draft/viz`, the 1.1
243
+ * triple-slash `http://gexf.net/1.1draft///viz`, `http://gexf.net/1.3/viz`, GEXF 1.0's `http://www.gephi.org/gexf/viz`).
244
+ * @param ns - the element's namespace
245
+ * @returns true for a viz namespace
246
+ */
247
+ function isVizNamespace(ns: string | null): boolean {
248
+ return ns !== null && /(gexf\.net\/.*viz|gephi\.org\/gexf\/viz)\/?$/.test(ns);
249
+ }
250
+
251
+ /** Distinct unknown attribute names reported one by one per element kind; the rest are counted in one issue. */
252
+ const MAX_UNKNOWN_ATTRIBUTE_NAMES = 16;
253
+
254
+ /** The XML attributes the importer reads on each element other than `<node>` / `<edge>`. */
255
+ const ELEMENT_ATTRIBUTES: Readonly<Record<string, ReadonlySet<string>>> = {
256
+ gexf: new Set(["version", "variant"]),
257
+ meta: new Set(["lastmodifieddate"]),
258
+ graph: new Set([
259
+ "defaultedgetype",
260
+ "mode",
261
+ "type",
262
+ "timeformat",
263
+ "timerepresentation",
264
+ "idtype",
265
+ "start",
266
+ "end",
267
+ "timestamp",
268
+ "startopen",
269
+ "endopen",
270
+ ]),
271
+ // `type` is GEXF 1.0's name for `mode` (static | dynamic)
272
+ attributes: new Set(["class", "mode", "type", "start", "end", "startopen", "endopen"]),
273
+ attribute: new Set(["id", "title", "type"]),
274
+ attvalue: new Set(["for", "value", "start", "end", "timestamp", "startopen", "endopen", "id"]),
275
+ spell: new Set(["start", "end", "timestamp", "startopen", "endopen"]),
276
+ parent: new Set(["for"]),
277
+ nodes: new Set(["count"]),
278
+ edges: new Set(["count"]),
279
+ };
280
+
281
+ /** A `count` hint as written: decimal digits only (as GraphML's parse.nodes). */
282
+ const COUNT_TEXT = /^\d+$/;
283
+
284
+ /** An xs:date / xs:dateTime text (`lastmodifieddate`). */
285
+ const DATE_TEXT = /^-?\d{4,}-\d{2}-\d{2}(.*)$/s;
286
+
287
+ /** The time of an xs:dateTime, after its date. */
288
+ const TIME_TEXT = /^T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?/;
289
+
290
+ /** The zone that may end an xs:date or xs:dateTime. */
291
+ const ZONE_TEXT = /^(?:Z|[+-]\d{2}:\d{2})?$/;
292
+
293
+ /**
294
+ * Whether a text is an xs:date or xs:dateTime.
295
+ * @param text - the text
296
+ * @returns true when it is
297
+ */
298
+ function isDateText(text: string): boolean {
299
+ const m = DATE_TEXT.exec(text);
300
+ if (m === null) {
301
+ return false;
302
+ }
303
+ const time = TIME_TEXT.exec(m[1])?.[0] ?? "";
304
+ return ZONE_TEXT.test(m[1].slice(time.length));
305
+ }
306
+
159
307
  const EDGE_TYPES: ReadonlySet<string> = new Set(["directed", "undirected", "mutual"]);
160
308
 
161
309
  /**
@@ -407,6 +555,8 @@ interface EdgeFrame {
407
555
  index: number;
408
556
  mirror: number;
409
557
  readonly where: MutableLocation;
558
+ /** The `weight` XML attribute as written, or null, for the clash with a weight attvalue. */
559
+ xmlWeight: string | null;
410
560
  }
411
561
 
412
562
  /** A location whose fields are overwritten per element (see EdgeFrame). */
@@ -502,8 +652,25 @@ class GexfReader implements XmlHandler {
502
652
  index: INVALID_INDEX,
503
653
  mirror: INVALID_INDEX,
504
654
  where: { line: 0, element: null },
655
+ xmlWeight: null,
505
656
  };
506
657
 
658
+ /** The `for` ids of the static values of the open `<attvalues>`, for the duplicate check. */
659
+ private readonly attvalueIds = new Set<string>();
660
+
661
+ /** The open `<nodes>` / `<edges>` sections: their count hint and the elements read directly in them. */
662
+ private readonly sections: {
663
+ readonly domain: "node" | "edge";
664
+ readonly hint: number | null;
665
+ seen: number;
666
+ readonly line: number;
667
+ }[] = [];
668
+
669
+ /** Unknown attribute names reported per element kind, and how many more were not listed. */
670
+ private readonly unknownNames = new Map<string, Set<string>>();
671
+
672
+ private readonly unknownOverflow = new Map<string, number>();
673
+
507
674
  /** The one `<attvalue>` location, reused per value (its fields are copied into every issue). */
508
675
  private readonly valueWhere: MutableLocation = { line: 0, element: null };
509
676
 
@@ -572,15 +739,28 @@ class GexfReader implements XmlHandler {
572
739
 
573
740
  // ---------------------------------------------------------------- events
574
741
 
575
- start(name: string, attrs: ReadonlyMap<string, string>, line: number): void {
742
+ start(name: string, attrs: ReadonlyMap<string, string>, line: number, ns: string | null = ""): void {
576
743
  const parent = this.ctx.length === 0 ? null : this.ctx[this.ctx.length - 1];
577
744
  const local = localName(name);
745
+ if (
746
+ ns === null &&
747
+ parent !== null &&
748
+ parent !== Ctx.Skip &&
749
+ parent !== Ctx.MetaText &&
750
+ parent !== Ctx.AttributeText &&
751
+ !name.startsWith("viz:")
752
+ ) {
753
+ // an undeclared prefix or a name that is not namespace-well-formed (<:node>, <a:b:node>)
754
+ this.unknownElement(name, line);
755
+ return;
756
+ }
578
757
  switch (parent) {
579
758
  case null:
580
759
  if (local !== "gexf") {
581
760
  this.report.fail(NOT_GEXF_CODE, `the root element is <${name}>, not <gexf>`, { line });
582
761
  }
583
- this.readRoot(attrs);
762
+ this.checkAttributes("gexf", attrs, line);
763
+ this.readRoot(attrs, ns, line);
584
764
  this.ctx.push(Ctx.Gexf);
585
765
  return;
586
766
  case Ctx.Gexf:
@@ -602,7 +782,7 @@ class GexfReader implements XmlHandler {
602
782
  this.startInNodes(name, local, attrs, line);
603
783
  return;
604
784
  case Ctx.Node:
605
- this.startInNode(name, local, attrs, line);
785
+ this.startInNode(name, local, attrs, line, ns);
606
786
  return;
607
787
  case Ctx.Attvalues:
608
788
  this.startInAttvalues(name, local, attrs, line);
@@ -617,7 +797,7 @@ class GexfReader implements XmlHandler {
617
797
  this.startInEdges(name, local, attrs, line);
618
798
  return;
619
799
  case Ctx.Edge:
620
- this.startInEdge(name, local, attrs, line);
800
+ this.startInEdge(name, local, attrs, line, ns);
621
801
  return;
622
802
  case Ctx.MetaText:
623
803
  case Ctx.AttributeText:
@@ -658,6 +838,10 @@ class GexfReader implements XmlHandler {
658
838
  case Ctx.Node:
659
839
  this.nodeStack.pop();
660
840
  return;
841
+ case Ctx.Nodes:
842
+ case Ctx.Edges:
843
+ this.finishSection();
844
+ return;
661
845
  case Ctx.Edge:
662
846
  this.edge = null;
663
847
  return;
@@ -668,8 +852,6 @@ class GexfReader implements XmlHandler {
668
852
  this.finishSpells();
669
853
  return;
670
854
  case Ctx.Gexf:
671
- case Ctx.Nodes:
672
- case Ctx.Edges:
673
855
  case Ctx.Attvalues:
674
856
  case Ctx.Skip:
675
857
  case undefined:
@@ -717,18 +899,42 @@ class GexfReader implements XmlHandler {
717
899
  if (!this.graphSeen) {
718
900
  this.report.fail(NO_GRAPH_CODE, "the document has no <graph> element");
719
901
  }
902
+ for (const [kind, more] of this.unknownOverflow) {
903
+ this.report.warning(
904
+ "unsupported",
905
+ UNKNOWN_XML_ATTRIBUTE_CODE,
906
+ `${more} more distinct attribute names on <${kind}> are not GEXF attributes and are not kept (not listed one by one)`,
907
+ { element: kind },
908
+ );
909
+ }
720
910
  }
721
911
 
722
912
  // ---------------------------------------------------------------- root, meta, graph
723
913
 
724
914
  /**
725
- * Record the root's version.
915
+ * Record the root's version: as written when known; the namespace's when absent; an unknown
916
+ * one is kept as written and reported.
726
917
  * @param attrs - the `<gexf>` attributes
918
+ * @param ns - the root's namespace
919
+ * @param line - the line
727
920
  */
728
- private readRoot(attrs: ReadonlyMap<string, string>): void {
921
+ private readRoot(attrs: ReadonlyMap<string, string>, ns: string | null, line: number): void {
729
922
  const version = attrs.get("version");
730
923
  if (version !== undefined && version.length > 0) {
731
924
  this.meta.sourceVersion = version;
925
+ if (!VERSIONS.has(version)) {
926
+ this.report.warning(
927
+ "validation-error",
928
+ HEADER_VALUE_CODE,
929
+ `<gexf version="${version}"> is not a GEXF version graph-io knows (1.0 to 1.3); read as GEXF 1.3`,
930
+ { line, element: "version" },
931
+ );
932
+ }
933
+ return;
934
+ }
935
+ const implied = namespaceVersion(ns);
936
+ if (implied !== null) {
937
+ this.meta.sourceVersion = implied;
732
938
  }
733
939
  }
734
940
 
@@ -742,21 +948,36 @@ class GexfReader implements XmlHandler {
742
948
  private startInGexf(name: string, local: string, attrs: ReadonlyMap<string, string>, line: number): void {
743
949
  switch (local) {
744
950
  case "meta": {
951
+ this.checkAttributes("meta", attrs, line);
745
952
  const modified = attrs.get("lastmodifieddate");
746
953
  if (modified !== undefined && modified.length > 0) {
747
954
  this.meta.modified = modified;
955
+ if (!isDateText(modified.trim())) {
956
+ this.report.warning(
957
+ "validation-error",
958
+ HEADER_VALUE_CODE,
959
+ `<meta lastmodifieddate="${modified}"> is not an xs:date; kept as written`,
960
+ { line, element: "lastmodifieddate" },
961
+ );
962
+ }
748
963
  }
749
964
  this.ctx.push(Ctx.Meta);
750
965
  return;
751
966
  }
752
967
  case "graph":
968
+ this.checkAttributes("graph", attrs, line);
753
969
  if (this.graphSeen) {
754
- this.report.fail(NO_GRAPH_CODE, "a second <graph> element; a GEXF document has one graph", {
755
- line,
756
- });
970
+ // the schema allows one graph; merging is what the GraphML importer does too
971
+ this.report.warnOnce(
972
+ "unsupported",
973
+ MULTIPLE_GRAPHS_CODE,
974
+ "a second <graph> element; its nodes and edges are merged into the first and its header is ignored",
975
+ { line },
976
+ );
977
+ } else {
978
+ this.graphSeen = true;
979
+ this.readHeader(attrs, line);
757
980
  }
758
- this.graphSeen = true;
759
- this.readHeader(attrs, line);
760
981
  this.ctx.push(Ctx.Graph);
761
982
  return;
762
983
  default:
@@ -803,9 +1024,11 @@ class GexfReader implements XmlHandler {
803
1024
  this.metaText = "";
804
1025
  }
805
1026
 
806
- /** Close `<meta>`: nothing more to do (the patch is applied at `<graph>`). */
1027
+ /** Close `<meta>`: the patch is applied with the graph header, or now when `<meta>` follows the graph. */
807
1028
  private finishMeta(): void {
808
- // the meta patch is applied when the graph header is read
1029
+ if (this.graphSeen) {
1030
+ this.sink.setMeta(this.meta);
1031
+ }
809
1032
  }
810
1033
 
811
1034
  /**
@@ -855,7 +1078,7 @@ class GexfReader implements XmlHandler {
855
1078
  const idType = attrs.get("idtype");
856
1079
  if (idType !== undefined) {
857
1080
  if (idType === "string") {
858
- // informational only: Gephi writes idtype="string" unconditionally, so honouring it
1081
+ // informational only: Gephi writes idtype="string" unconditionally, so honoring it
859
1082
  // would turn every Gephi export's integer ids into text; the canonical rule of
860
1083
  // design section 4.1 applies and the exporter's check() reports W_ID_TEXT_TYPE
861
1084
  patch.idType = "string";
@@ -908,8 +1131,7 @@ class GexfReader implements XmlHandler {
908
1131
  return;
909
1132
  case "nodes":
910
1133
  this.nodesSeen = true;
911
- this.reserve(attrs, "node", line);
912
- this.ctx.push(Ctx.Nodes);
1134
+ this.beginSection(attrs, "node", line);
913
1135
  return;
914
1136
  case "edges":
915
1137
  if (!this.nodesSeen) {
@@ -921,37 +1143,65 @@ class GexfReader implements XmlHandler {
921
1143
  );
922
1144
  }
923
1145
  this.resolveParents();
924
- this.reserve(attrs, "edge", line);
925
- this.ctx.push(Ctx.Edges);
1146
+ this.beginSection(attrs, "edge", line);
926
1147
  return;
927
1148
  default:
928
1149
  this.unknownElement(name, line);
929
1150
  }
930
1151
  }
931
1152
 
1153
+ /**
1154
+ * Open a `<nodes>` / `<edges>` section: its count hint is reserved and later compared with the
1155
+ * elements the section holds.
1156
+ * @param attrs - the section's attributes
1157
+ * @param domain - node or edge
1158
+ * @param line - the line
1159
+ */
1160
+ private beginSection(attrs: ReadonlyMap<string, string>, domain: "node" | "edge", line: number): void {
1161
+ this.checkAttributes(`${domain}s`, attrs, line);
1162
+ this.sections.push({ domain, hint: this.reserve(attrs, domain, line), seen: 0, line });
1163
+ this.ctx.push(domain === "node" ? Ctx.Nodes : Ctx.Edges);
1164
+ }
1165
+
1166
+ /** Close a `<nodes>` / `<edges>` section: a count hint that disagrees is reported. */
1167
+ private finishSection(): void {
1168
+ const section = this.sections.pop();
1169
+ if (section?.hint === undefined || section.hint === null || section.hint === section.seen) {
1170
+ return;
1171
+ }
1172
+ this.report.warning(
1173
+ "validation-error",
1174
+ COUNT_MISMATCH_CODE,
1175
+ `<${section.domain}s count="${section.hint}"> but the section holds ${section.seen} <${section.domain}> elements (nested ones included)`,
1176
+ { line: section.line, element: "count" },
1177
+ );
1178
+ }
1179
+
932
1180
  /**
933
1181
  * Pass a section's `count` hint to the sink; a hint the sink cannot reserve is a warning.
934
1182
  * @param attrs - the `<nodes>` or `<edges>` attributes
935
1183
  * @param domain - which count it is
936
1184
  * @param line - the line
1185
+ * @returns the hint, or null when absent or ignored
937
1186
  */
938
- private reserve(attrs: ReadonlyMap<string, string>, domain: "node" | "edge", line: number): void {
1187
+ private reserve(attrs: ReadonlyMap<string, string>, domain: "node" | "edge", line: number): number | null {
939
1188
  const text = attrs.get("count");
940
1189
  if (text === undefined) {
941
- return;
1190
+ return null;
942
1191
  }
943
- const count = Number(text);
944
- if (!Number.isInteger(count) || count < 0 || count > MAX_COUNT) {
1192
+ // decimal digits only: Number() would read "" as 0, "0x10" as 16 and "1e3" as 1000
1193
+ const count = COUNT_TEXT.test(text) ? Number(text) : Number.NaN;
1194
+ if (!Number.isSafeInteger(count) || count > MAX_COUNT) {
945
1195
  this.report.warning(
946
1196
  "validation-error",
947
1197
  COUNT_HINT_CODE,
948
1198
  `<${domain}s count="${text}"> is not a count the sink can reserve; ignored`,
949
1199
  { line, element: "count" },
950
1200
  );
951
- return;
1201
+ return null;
952
1202
  }
953
1203
  if (count === 0) {
954
- return;
1204
+ return count;
955
1205
  }
956
1206
  try {
957
1207
  if (domain === "node") {
@@ -968,6 +1218,7 @@ class GexfReader implements XmlHandler {
968
1218
  element: "count",
969
1219
  });
970
1220
  }
1221
+ return count;
971
1222
  }
972
1223
 
973
1224
  // ---------------------------------------------------------------- attribute declarations
@@ -978,6 +1229,7 @@ class GexfReader implements XmlHandler {
978
1229
  * @param line - the line
979
1230
  */
980
1231
  private beginAttributes(attrs: ReadonlyMap<string, string>, line: number): void {
1232
+ this.checkAttributes("attributes", attrs, line);
981
1233
  const cls = attrs.get("class");
982
1234
  const where = { line };
983
1235
  let domain: "node" | "edge" | null = null;
@@ -993,7 +1245,7 @@ class GexfReader implements XmlHandler {
993
1245
  where,
994
1246
  );
995
1247
  }
996
- const mode = attrs.get("mode");
1248
+ const mode = attrs.get("mode") ?? attrs.get("type");
997
1249
  if (mode !== undefined && mode !== "static" && mode !== "dynamic") {
998
1250
  this.warnHeader("mode", mode, where);
999
1251
  }
@@ -1013,6 +1265,7 @@ class GexfReader implements XmlHandler {
1013
1265
  this.unknownElement(name, line);
1014
1266
  return;
1015
1267
  }
1268
+ this.checkAttributes("attribute", attrs, line);
1016
1269
  const group = this.attributeGroup;
1017
1270
  if (group === null || group.domain === null) {
1018
1271
  // the group's class was refused; its attributes are dropped with it
@@ -1099,6 +1352,14 @@ class GexfReader implements XmlHandler {
1099
1352
  );
1100
1353
  return;
1101
1354
  }
1355
+ if (this.unknownAttributes.has(`${domain}:${id}`)) {
1356
+ report.warning(
1357
+ "missing-value",
1358
+ UNKNOWN_ATTRIBUTE_CODE,
1359
+ `${domain} attribute "${id}" is declared after values that use it; those values were dropped (declare <attributes> before <nodes>)`,
1360
+ where,
1361
+ );
1362
+ }
1102
1363
  const title = attrs.get("title") ?? null;
1103
1364
  const type = attrs.get("type") ?? null;
1104
1365
  if (type === null) {
@@ -1110,7 +1371,15 @@ class GexfReader implements XmlHandler {
1110
1371
  );
1111
1372
  }
1112
1373
  const name = title !== null && title.length > 0 ? title : id;
1113
- if (domain === "edge" && isWeightField(name, options.weightFrom)) {
1374
+ if (domain === "edge" && this.weightAttr !== null && isWeightField(name, options.weightFrom)) {
1375
+ // the first attribute of that title stays THE weight; this one becomes a plain column
1376
+ report.warning(
1377
+ "validation-error",
1378
+ DUPLICATE_ATTRIBUTE_CODE,
1379
+ `edge attribute "${id}" is also titled ${name}; attribute "${this.weightAttr.id}" is the weight, "${id}" is kept as a column`,
1380
+ where,
1381
+ );
1382
+ } else if (domain === "edge" && isWeightField(name, options.weightFrom)) {
1114
1383
  const declared = declareAttribute({ format: GEXF_FORMAT, id, title, type, long: options.long });
1115
1384
  for (const issue of declared.issues) {
1116
1385
  report.warning(issue.category, issue.code, issue.message, where);
@@ -1121,7 +1390,9 @@ class GexfReader implements XmlHandler {
1121
1390
  }
1122
1391
  const declaredNames = domain === "node" ? this.declaredNodeNames : this.declaredEdgeNames;
1123
1392
  const reserved = domain === "node" ? RESERVED_NODE_NAMES : RESERVED_EDGE_NAMES;
1124
- const taken = (candidate: string): boolean => reserved.has(candidate) || declaredNames.has(candidate);
1393
+ const weightName = domain === "edge" && this.weightAttr !== null ? options.weightFrom : null;
1394
+ const taken = (candidate: string): boolean =>
1395
+ reserved.has(candidate) || declaredNames.has(candidate) || candidate === weightName;
1125
1396
  const input: AttributeDeclarationInput = {
1126
1397
  format: GEXF_FORMAT,
1127
1398
  id,
@@ -1197,7 +1468,9 @@ class GexfReader implements XmlHandler {
1197
1468
  const enclosing = this.nodeStack.length === 0 ? INVALID_INDEX : this.nodeStack[this.nodeStack.length - 1].index;
1198
1469
  const idText = attrs.get("id");
1199
1470
  const where: IssueLocation = { line, element: idText ?? attrs.get("label") ?? null };
1471
+ this.countInSection("node");
1200
1472
  this.checkAttributeNames(attrs, NODE_ATTRIBUTES, "node", where);
1473
+ // GEXF types ids as xs:string: id="" is a legal id (the exporter writes it), only a missing one is not
1201
1474
  if (idText === undefined) {
1202
1475
  report.error("missing-value", MISSING_ID_CODE, "<node> without an id", where);
1203
1476
  report.counts.skippedNodes++;
@@ -1205,6 +1478,15 @@ class GexfReader implements XmlHandler {
1205
1478
  this.ctx.push(Ctx.Node);
1206
1479
  return;
1207
1480
  }
1481
+ if (this.meta.idType === "integer" && !/^\s*[+-]?\d+\s*$/.test(idText)) {
1482
+ report.warnOnce(
1483
+ "validation-error",
1484
+ HEADER_VALUE_CODE,
1485
+ `node id "${idText}" is not an integer although the graph declares idtype="integer"; ids follow the ids option`,
1486
+ where,
1487
+ `${HEADER_VALUE_CODE}:idtype`,
1488
+ );
1489
+ }
1208
1490
  let index: number;
1209
1491
  try {
1210
1492
  const id = this.coerceId(idText, where);
@@ -1230,11 +1512,16 @@ class GexfReader implements XmlHandler {
1230
1512
  }
1231
1513
  this.nodeStack.push({ index, where });
1232
1514
  this.ctx.push(Ctx.Node);
1515
+ // three independent parts: a bad pid must not cost the label or the lifetime
1233
1516
  try {
1234
1517
  const label = attrs.get("label");
1235
1518
  if (label !== undefined) {
1236
1519
  this.nodeColumns.label.set(sink, report, index, label, where);
1237
1520
  }
1521
+ } catch (err) {
1522
+ report.recordError(err, where);
1523
+ }
1524
+ try {
1238
1525
  const pid = attrs.get("pid");
1239
1526
  if (pid !== undefined) {
1240
1527
  this.parentRefs.push({
@@ -1246,28 +1533,61 @@ class GexfReader implements XmlHandler {
1246
1533
  } else if (enclosing !== INVALID_INDEX) {
1247
1534
  this.parentRefs.push({ child: index, parentId: null, parentIndex: enclosing, where });
1248
1535
  }
1536
+ } catch (err) {
1537
+ report.recordError(err, where);
1538
+ }
1539
+ try {
1249
1540
  this.writeLifetime(attrs, index, this.nodeColumns, where);
1250
1541
  } catch (err) {
1251
1542
  report.recordError(err, where);
1252
1543
  }
1253
1544
  }
1254
1545
 
1546
+ /**
1547
+ * Count a `<node>` / `<edge>` element in the open sections of its kind, for the count hint
1548
+ * check: a `<nodes count>` counts the nodes nested in its nodes too.
1549
+ * @param domain - node or edge
1550
+ */
1551
+ private countInSection(domain: "node" | "edge"): void {
1552
+ for (const section of this.sections) {
1553
+ if (section.domain === domain) {
1554
+ section.seen++;
1555
+ }
1556
+ }
1557
+ }
1558
+
1255
1559
  /**
1256
1560
  * A child of `<node>`.
1257
1561
  * @param name - the element name
1258
1562
  * @param local - its local name
1259
1563
  * @param attrs - its attributes
1260
1564
  * @param line - the line
1565
+ * @param ns - the element's namespace
1261
1566
  */
1262
- private startInNode(name: string, local: string, attrs: ReadonlyMap<string, string>, line: number): void {
1567
+ private startInNode(
1568
+ name: string,
1569
+ local: string,
1570
+ attrs: ReadonlyMap<string, string>,
1571
+ line: number,
1572
+ ns: string | null,
1573
+ ): void {
1263
1574
  const frame = this.nodeStack[this.nodeStack.length - 1];
1264
1575
  if (frame.index === INVALID_INDEX && local !== "nodes") {
1265
1576
  // the node was skipped; its children are too
1266
1577
  this.ctx.push(Ctx.Skip);
1267
1578
  return;
1268
1579
  }
1580
+ if (frame.index === INVALID_INDEX) {
1581
+ this.report.error(
1582
+ "missing-value",
1583
+ UNKNOWN_PARENT_CODE,
1584
+ "the nodes nested in this skipped <node> lose their parent; they are read as top-level nodes",
1585
+ { line },
1586
+ );
1587
+ }
1269
1588
  switch (local) {
1270
1589
  case "attvalues":
1590
+ this.attvalueIds.clear();
1271
1591
  this.ctx.push(Ctx.Attvalues);
1272
1592
  return;
1273
1593
  case "parents":
@@ -1281,13 +1601,16 @@ class GexfReader implements XmlHandler {
1281
1601
  this.ctx.push(Ctx.Spells);
1282
1602
  return;
1283
1603
  case "nodes":
1284
- this.reserve(attrs, "node", line);
1285
- this.ctx.push(Ctx.Nodes);
1604
+ this.beginSection(attrs, "node", line);
1286
1605
  return;
1287
1606
  case "color":
1288
1607
  case "position":
1289
1608
  case "size":
1290
1609
  case "shape":
1610
+ if (!isViz(name, ns)) {
1611
+ this.unknownElement(name, line);
1612
+ return;
1613
+ }
1291
1614
  this.writeViz(name, local, attrs, "node", frame.index, frame.where);
1292
1615
  this.ctx.push(Ctx.Skip);
1293
1616
  return;
@@ -1309,10 +1632,16 @@ class GexfReader implements XmlHandler {
1309
1632
  return;
1310
1633
  }
1311
1634
  this.ctx.push(Ctx.Skip);
1635
+ this.checkAttributes("parent", attrs, line);
1312
1636
  const frame = this.nodeStack[this.nodeStack.length - 1];
1313
1637
  const text = attrs.get("for");
1314
1638
  if (text === undefined) {
1315
- this.report.warning("missing-value", ATTVALUE_SHAPE_CODE, "<parent> without a for attribute", frame.where);
1639
+ this.report.error(
1640
+ "missing-value",
1641
+ UNKNOWN_PARENT_CODE,
1642
+ "<parent> without a for attribute names no parent",
1643
+ frame.where,
1644
+ );
1316
1645
  return;
1317
1646
  }
1318
1647
  try {
@@ -1341,6 +1670,20 @@ class GexfReader implements XmlHandler {
1341
1670
  const lists = this.parentsRefs;
1342
1671
  this.parentRefs = [];
1343
1672
  this.parentsRefs = [];
1673
+ // each node's parent so far, as the parent column holds it: a link closes a cycle when the
1674
+ // child is the parent or one of its ancestors. A repeated link (a duplicate node declaring
1675
+ // the same pid) is not a cycle, which an undirected union-find would wrongly report.
1676
+ // ponytail: O(depth) per link, so O(n * depth); fine for real hierarchies, add path
1677
+ // compression over an ancestry index if a deep chain ever shows up in a profile.
1678
+ const parentOf = new Map<number, number>();
1679
+ const isAncestorOrSelf = (candidate: number, of: number): boolean => {
1680
+ for (let at: number | undefined = of; at !== undefined; at = parentOf.get(at)) {
1681
+ if (at === candidate) {
1682
+ return true;
1683
+ }
1684
+ }
1685
+ return false;
1686
+ };
1344
1687
  for (const ref of refs) {
1345
1688
  let parent = ref.parentIndex;
1346
1689
  if (ref.parentId !== null) {
@@ -1355,8 +1698,21 @@ class GexfReader implements XmlHandler {
1355
1698
  continue;
1356
1699
  }
1357
1700
  }
1701
+ if (parentOf.get(ref.child) === parent) {
1702
+ continue;
1703
+ }
1704
+ if (isAncestorOrSelf(ref.child, parent)) {
1705
+ report.error(
1706
+ "validation-error",
1707
+ PARENT_CYCLE_CODE,
1708
+ `the parent link of node "${ref.where.element ?? "?"}" would close a parent cycle; it is dropped`,
1709
+ ref.where,
1710
+ );
1711
+ continue;
1712
+ }
1358
1713
  try {
1359
1714
  this.parentColumn.set(sink, report, ref.child, parent, ref.where);
1715
+ parentOf.set(ref.child, parent);
1360
1716
  } catch (err) {
1361
1717
  report.recordError(err, ref.where);
1362
1718
  }
@@ -1417,7 +1773,9 @@ class GexfReader implements XmlHandler {
1417
1773
  const { where } = this.edgeFrame;
1418
1774
  where.line = line;
1419
1775
  where.element = idText ?? edgeElement(sourceText, targetText);
1776
+ this.countInSection("edge");
1420
1777
  this.checkAttributeNames(attrs, EDGE_ATTRIBUTES, "edge", where);
1778
+ // source="" names the node whose id is "" (legal in GEXF), so only a missing endpoint is E_MISSING_ENDPOINT
1421
1779
  if (sourceText === undefined || targetText === undefined) {
1422
1780
  report.error(
1423
1781
  "missing-value",
@@ -1444,18 +1802,15 @@ class GexfReader implements XmlHandler {
1444
1802
  }
1445
1803
  kind = type as EdgeKind;
1446
1804
  }
1447
- if (idText !== undefined) {
1448
- if (this.edgeIds.has(idText)) {
1449
- report.error(
1450
- "validation-error",
1451
- DUPLICATE_EDGE_ID_CODE,
1452
- `edge id "${idText}" is declared more than once; the edge is skipped`,
1453
- where,
1454
- );
1455
- report.counts.skippedEdges++;
1456
- return;
1457
- }
1458
- this.edgeIds.add(idText);
1805
+ if (idText !== undefined && this.edgeIds.has(idText)) {
1806
+ report.error(
1807
+ "validation-error",
1808
+ DUPLICATE_EDGE_ID_CODE,
1809
+ `edge id "${idText}" is declared more than once; the edge is skipped`,
1810
+ where,
1811
+ );
1812
+ report.counts.skippedEdges++;
1813
+ return;
1459
1814
  }
1460
1815
  let index: number;
1461
1816
  try {
@@ -1482,9 +1837,14 @@ class GexfReader implements XmlHandler {
1482
1837
  report.counts.skippedEdges++;
1483
1838
  return;
1484
1839
  }
1840
+ // the id is claimed only by an edge that was added, so a rejected edge does not cost the next
1841
+ if (idText !== undefined) {
1842
+ this.edgeIds.add(idText);
1843
+ }
1485
1844
  const frame = this.edgeFrame;
1486
1845
  frame.index = index;
1487
1846
  frame.mirror = this.resolver.lastMirror;
1847
+ frame.xmlWeight = attrs.get("weight") ?? null;
1488
1848
  this.edge = frame;
1489
1849
  try {
1490
1850
  if (idText !== undefined) {
@@ -1525,7 +1885,28 @@ class GexfReader implements XmlHandler {
1525
1885
  );
1526
1886
  return undefined;
1527
1887
  }
1528
- return parseWeightText(text);
1888
+ // a blank weight="" is an absent weight (common/weights.ts), as a blank weight attvalue is
1889
+ const weight = parseWeightText(text, this.report);
1890
+ this.checkOverflow(text, where);
1891
+ return weight;
1892
+ }
1893
+
1894
+ /**
1895
+ * Report (once) a weight text too large for a double, stored as Infinity; `INF` itself is a
1896
+ * legal weight (design section 3.7) and is not reported.
1897
+ * @param text - the weight text
1898
+ * @param where - the location
1899
+ */
1900
+ private checkOverflow(text: string, where: IssueLocation): void {
1901
+ if (overflowsToInfinity(text)) {
1902
+ this.report.warnOnce(
1903
+ "precision",
1904
+ PRECISION_CODE,
1905
+ `weight ${text.trim()} is beyond the double range and is stored as Infinity`,
1906
+ where,
1907
+ `${PRECISION_CODE}:weight`,
1908
+ );
1909
+ }
1529
1910
  }
1530
1911
 
1531
1912
  /**
@@ -1534,8 +1915,15 @@ class GexfReader implements XmlHandler {
1534
1915
  * @param local - its local name
1535
1916
  * @param attrs - its attributes
1536
1917
  * @param line - the line
1918
+ * @param ns - the element's namespace
1537
1919
  */
1538
- private startInEdge(name: string, local: string, attrs: ReadonlyMap<string, string>, line: number): void {
1920
+ private startInEdge(
1921
+ name: string,
1922
+ local: string,
1923
+ attrs: ReadonlyMap<string, string>,
1924
+ line: number,
1925
+ ns: string | null,
1926
+ ): void {
1539
1927
  const { edge } = this;
1540
1928
  if (edge === null) {
1541
1929
  this.ctx.push(Ctx.Skip);
@@ -1543,6 +1931,7 @@ class GexfReader implements XmlHandler {
1543
1931
  }
1544
1932
  switch (local) {
1545
1933
  case "attvalues":
1934
+ this.attvalueIds.clear();
1546
1935
  this.ctx.push(Ctx.Attvalues);
1547
1936
  return;
1548
1937
  case "spells":
@@ -1554,6 +1943,10 @@ class GexfReader implements XmlHandler {
1554
1943
  case "color":
1555
1944
  case "thickness":
1556
1945
  case "shape":
1946
+ if (!isViz(name, ns)) {
1947
+ this.unknownElement(name, line);
1948
+ return;
1949
+ }
1557
1950
  this.writeViz(name, local, attrs, "edge", edge.index, edge.where);
1558
1951
  this.ctx.push(Ctx.Skip);
1559
1952
  return;
@@ -1578,6 +1971,7 @@ class GexfReader implements XmlHandler {
1578
1971
  return;
1579
1972
  }
1580
1973
  this.ctx.push(Ctx.Skip);
1974
+ this.checkAttributes("attvalue", attrs, line);
1581
1975
  const { report, edge } = this;
1582
1976
  const domain: "node" | "edge" = edge === null ? "node" : "edge";
1583
1977
  let index: number;
@@ -1611,6 +2005,17 @@ class GexfReader implements XmlHandler {
1611
2005
  );
1612
2006
  return;
1613
2007
  }
2008
+ if (!isTimed(attrs)) {
2009
+ if (this.attvalueIds.has(forId)) {
2010
+ report.warning(
2011
+ "validation-error",
2012
+ DUPLICATE_ATTRIBUTE_CODE,
2013
+ `a second static <attvalue for="${forId}"> on one ${domain}; the later value "${value}" is kept`,
2014
+ { ...valueWhere, element: forId },
2015
+ );
2016
+ }
2017
+ this.attvalueIds.add(forId);
2018
+ }
1614
2019
  try {
1615
2020
  if (domain === "edge" && this.weightAttr !== null && this.weightAttr.id === forId) {
1616
2021
  if (isTimed(attrs)) {
@@ -1656,12 +2061,19 @@ class GexfReader implements XmlHandler {
1656
2061
  if (edge === null) {
1657
2062
  return;
1658
2063
  }
1659
- const weight = parseWeightText(text);
2064
+ const weight = parseWeightText(text, this.report);
1660
2065
  if (weight === undefined) {
1661
- throw new GraphFormatError("E_INVALID_WEIGHT", "an edge weight cannot be blank", {
1662
- value: text,
1663
- line: where.line ?? null,
1664
- });
2066
+ // blank is absent (common/weights.ts), as a blank weight="" XML attribute is
2067
+ return;
2068
+ }
2069
+ this.checkOverflow(text, where);
2070
+ if (edge.xmlWeight !== null) {
2071
+ this.report.warning(
2072
+ "validation-error",
2073
+ DUPLICATE_ATTRIBUTE_CODE,
2074
+ `the edge has both weight="${edge.xmlWeight}" and a weight attvalue "${text}"; the attvalue is kept`,
2075
+ where,
2076
+ );
1665
2077
  }
1666
2078
  sink.setEdgeWeight(edge.index, weight);
1667
2079
  if (edge.mirror !== INVALID_INDEX) {
@@ -1705,10 +2117,44 @@ class GexfReader implements XmlHandler {
1705
2117
  `long value ${text.trim()} of "${attribute.name}" exceeds 2^53 and was rounded`,
1706
2118
  where,
1707
2119
  );
2120
+ } else if (typeof value === "number" && overflowsToInfinity(text)) {
2121
+ this.report.warnOnce(
2122
+ "precision",
2123
+ PRECISION_CODE,
2124
+ `values of "${attribute.name}" beyond the double range (${text.trim()}) are stored as Infinity`,
2125
+ where,
2126
+ `${PRECISION_CODE}:overflow:${attribute.name}`,
2127
+ );
1708
2128
  }
2129
+ this.checkOptions(attribute, value, where);
1709
2130
  this.setCell(domain, attribute.handle, index, value);
1710
2131
  }
1711
2132
 
2133
+ /**
2134
+ * Report (once per attribute) a value outside the `<options>` the attribute declares; the
2135
+ * value is kept.
2136
+ * @param attribute - the declaration
2137
+ * @param value - the parsed value (a list checks each item)
2138
+ * @param where - the value's location
2139
+ */
2140
+ private checkOptions(attribute: TrackedAttribute, value: unknown, where: IssueLocation): void {
2141
+ const { options } = attribute.valueDecl;
2142
+ if (options === undefined || options.length === 0) {
2143
+ return;
2144
+ }
2145
+ const items = Array.isArray(value) ? (value as unknown[]) : [value];
2146
+ const outside = items.find((item) => !options.includes(item));
2147
+ if (typeof outside === "string" || typeof outside === "number" || typeof outside === "boolean") {
2148
+ this.report.warnOnce(
2149
+ "validation-error",
2150
+ VALUE_OUTSIDE_OPTIONS_CODE,
2151
+ `"${outside}" is not among the options of attribute "${attribute.name}"; the value is kept`,
2152
+ where,
2153
+ `${VALUE_OUTSIDE_OPTIONS_CODE}:${attribute.name}`,
2154
+ );
2155
+ }
2156
+ }
2157
+
1712
2158
  /**
1713
2159
  * Append a timed value to the attribute's temporal table (design section 5.10), creating the
1714
2160
  * table on first use.
@@ -1762,6 +2208,9 @@ class GexfReader implements XmlHandler {
1762
2208
  }
1763
2209
  }
1764
2210
  const row = this.temporalRow(attrs, index, value, where);
2211
+ if (row === null) {
2212
+ return;
2213
+ }
1765
2214
  if (temporalValue) {
1766
2215
  row.push(valueText ?? undefined);
1767
2216
  }
@@ -1799,14 +2248,15 @@ class GexfReader implements XmlHandler {
1799
2248
  ),
1800
2249
  );
1801
2250
  }
1802
- const value = parseWeightText(text);
2251
+ const value = parseWeightText(text, this.report);
1803
2252
  if (value === undefined) {
1804
- throw new GraphFormatError("E_INVALID_WEIGHT", "a timed edge weight cannot be blank", {
1805
- value: text,
1806
- line: where.line ?? null,
1807
- });
2253
+ // blank is absent (common/weights.ts): no timed value
2254
+ return;
2255
+ }
2256
+ const row = this.temporalRow(attrs, index, value, where);
2257
+ if (row !== null) {
2258
+ sink.addExtensionRow(weightAttr.table, row);
1808
2259
  }
1809
- sink.addExtensionRow(weightAttr.table, this.temporalRow(attrs, index, value, where));
1810
2260
  }
1811
2261
 
1812
2262
  /**
@@ -1815,15 +2265,18 @@ class GexfReader implements XmlHandler {
1815
2265
  * @param index - the element's index
1816
2266
  * @param value - the parsed value
1817
2267
  * @param where - the value's location
1818
- * @returns the row, without the optional value text
2268
+ * @returns the row, without the optional value text; null when the interval is inverted (recorded)
1819
2269
  */
1820
2270
  private temporalRow(
1821
2271
  attrs: ReadonlyMap<string, string>,
1822
2272
  index: number,
1823
2273
  value: unknown,
1824
2274
  where: IssueLocation,
1825
- ): unknown[] {
2275
+ ): unknown[] | null {
1826
2276
  const bounds = this.readBounds(attrs, where);
2277
+ if (bounds === null) {
2278
+ return null;
2279
+ }
1827
2280
  return [
1828
2281
  index,
1829
2282
  bounds.start.value,
@@ -1841,12 +2294,12 @@ class GexfReader implements XmlHandler {
1841
2294
  * section 5.1); missing bounds are unbounded.
1842
2295
  * @param attrs - the attributes
1843
2296
  * @param where - the location, for the conflict warning
1844
- * @returns the bounds and the open bits
2297
+ * @returns the bounds and the open bits; null (recorded as E_BAD_VALUE) when the interval is inverted
1845
2298
  */
1846
2299
  private readBounds(
1847
2300
  attrs: ReadonlyMap<string, string>,
1848
2301
  where: IssueLocation,
1849
- ): { start: TemporalValue; end: TemporalValue; open: number } {
2302
+ ): { start: TemporalValue; end: TemporalValue; open: number } | null {
1850
2303
  let start: TemporalValue = { value: -Infinity, text: null };
1851
2304
  let end: TemporalValue = { value: Infinity, text: null };
1852
2305
  let open = 0;
@@ -1854,6 +2307,14 @@ class GexfReader implements XmlHandler {
1854
2307
  if (timestamp !== undefined) {
1855
2308
  start = parseTimeText(timestamp, this.timeFormat);
1856
2309
  end = start;
2310
+ if (attrs.has("start") || attrs.has("end") || attrs.has("startopen") || attrs.has("endopen")) {
2311
+ this.report.warnOnce(
2312
+ "validation-error",
2313
+ TIMESTAMP_CONFLICT_CODE,
2314
+ "both timestamp and start / end are given; start / end set the interval and the timestamp is kept as written",
2315
+ where,
2316
+ );
2317
+ }
1857
2318
  }
1858
2319
  const startText = attrs.get("start");
1859
2320
  const startOpen = attrs.get("startopen");
@@ -1877,6 +2338,15 @@ class GexfReader implements XmlHandler {
1877
2338
  end = parseTimeText(endOpen, this.timeFormat);
1878
2339
  open |= OPEN_END;
1879
2340
  }
2341
+ if (start.value > end.value) {
2342
+ this.report.error(
2343
+ "validation-error",
2344
+ BAD_VALUE_CODE,
2345
+ `the interval starts after it ends (${start.text ?? String(start.value)} > ${end.text ?? String(end.value)}); it is not kept`,
2346
+ where,
2347
+ );
2348
+ return null;
2349
+ }
1880
2350
  return { start, end, open };
1881
2351
  }
1882
2352
 
@@ -1919,9 +2389,13 @@ class GexfReader implements XmlHandler {
1919
2389
  attrs.has("endopen")
1920
2390
  ) {
1921
2391
  const bounds = this.readBounds(attrs, where);
2392
+ if (bounds === null) {
2393
+ return;
2394
+ }
1922
2395
  const timestamp = attrs.get("timestamp");
1923
2396
  if (timestamp !== undefined) {
1924
- columns.timestamp.set(sink, report, index, bounds.start, where);
2397
+ // parsed again: beside start / startopen, bounds.start is the start, not the timestamp
2398
+ columns.timestamp.set(sink, report, index, parseTimeText(timestamp, this.timeFormat), where);
1925
2399
  }
1926
2400
  if (attrs.has("start") || attrs.has("startopen")) {
1927
2401
  columns.start.set(sink, report, index, bounds.start, where);
@@ -1969,10 +2443,20 @@ class GexfReader implements XmlHandler {
1969
2443
  line: where.line ?? null,
1970
2444
  });
1971
2445
  }
1972
- pairs.push([
2446
+ const pair = [
1973
2447
  parseTimeText(items[0], this.timeFormat).value,
1974
2448
  parseTimeText(items[1], this.timeFormat).value,
1975
- ]);
2449
+ ];
2450
+ if (pair[0] > pair[1]) {
2451
+ this.report.error(
2452
+ "validation-error",
2453
+ BAD_VALUE_CODE,
2454
+ `interval "${part.trim()}" starts after it ends; it is not kept`,
2455
+ where,
2456
+ );
2457
+ continue;
2458
+ }
2459
+ pairs.push(pair);
1976
2460
  }
1977
2461
  return pairs;
1978
2462
  }
@@ -1990,9 +2474,13 @@ class GexfReader implements XmlHandler {
1990
2474
  return;
1991
2475
  }
1992
2476
  this.ctx.push(Ctx.Skip);
2477
+ this.checkAttributes("spell", attrs, line);
1993
2478
  const where = this.spellsWhere ?? { line };
1994
2479
  try {
1995
2480
  const bounds = this.readBounds(attrs, where);
2481
+ if (bounds === null) {
2482
+ return;
2483
+ }
1996
2484
  this.spellPairs?.push([bounds.start.value, bounds.end.value]);
1997
2485
  this.spellOpen.push(bounds.open);
1998
2486
  } catch (err) {
@@ -2123,34 +2611,59 @@ class GexfReader implements XmlHandler {
2123
2611
  }
2124
2612
 
2125
2613
  /**
2126
- * Report XML attributes the importer does not read (once per attribute name and domain).
2614
+ * Report XML attributes the importer does not read (once per attribute name and element kind;
2615
+ * beyond MAX_UNKNOWN_ATTRIBUTE_NAMES distinct names per kind the rest are counted and reported
2616
+ * once at the end). Namespace declarations and `xml:` / `xsi:` attributes are not data.
2127
2617
  * @param attrs - the element's attributes
2128
2618
  * @param known - the attribute names read
2129
- * @param domain - node or edge
2619
+ * @param kind - the element name
2130
2620
  * @param where - the element's location
2131
2621
  */
2132
2622
  private checkAttributeNames(
2133
2623
  attrs: ReadonlyMap<string, string>,
2134
2624
  known: ReadonlySet<string>,
2135
- domain: "node" | "edge",
2625
+ kind: string,
2136
2626
  where: IssueLocation,
2137
2627
  ): void {
2628
+ if (attrs.size === 0) {
2629
+ return;
2630
+ }
2138
2631
  for (const key of attrs.keys()) {
2139
- if (!known.has(key)) {
2140
- const seen = `${domain}@${key}`;
2141
- if (!this.unknownAttributes.has(seen)) {
2142
- this.unknownAttributes.add(seen);
2143
- this.report.warning(
2144
- "unsupported",
2145
- UNKNOWN_ELEMENT_CODE,
2146
- `attribute ${key} of <${domain}> is not GEXF and was ignored`,
2147
- { ...where, element: key },
2148
- );
2149
- }
2632
+ if (known.has(key) || key === "xmlns" || /^(xmlns|xml|xsi):/.test(key)) {
2633
+ continue;
2634
+ }
2635
+ let names = this.unknownNames.get(kind);
2636
+ if (names === undefined) {
2637
+ names = new Set();
2638
+ this.unknownNames.set(kind, names);
2639
+ }
2640
+ if (names.has(key)) {
2641
+ continue;
2642
+ }
2643
+ names.add(key);
2644
+ if (names.size > MAX_UNKNOWN_ATTRIBUTE_NAMES) {
2645
+ this.unknownOverflow.set(kind, (this.unknownOverflow.get(kind) ?? 0) + 1);
2646
+ continue;
2150
2647
  }
2648
+ this.report.warning(
2649
+ "unsupported",
2650
+ UNKNOWN_XML_ATTRIBUTE_CODE,
2651
+ `attribute ${key} of <${kind}> is not GEXF and was ignored`,
2652
+ { ...where, element: key },
2653
+ );
2151
2654
  }
2152
2655
  }
2153
2656
 
2657
+ /**
2658
+ * Report the XML attributes the importer does not read on an element other than node / edge.
2659
+ * @param kind - the element's local name (a key of ELEMENT_ATTRIBUTES)
2660
+ * @param attrs - its attributes
2661
+ * @param line - the line
2662
+ */
2663
+ private checkAttributes(kind: string, attrs: ReadonlyMap<string, string>, line: number): void {
2664
+ this.checkAttributeNames(attrs, ELEMENT_ATTRIBUTES[kind] ?? new Set(), kind, { line });
2665
+ }
2666
+
2154
2667
  /**
2155
2668
  * Write one cell of a domain.
2156
2669
  * @param domain - node or edge
@@ -2195,6 +2708,17 @@ class GexfReader implements XmlHandler {
2195
2708
  }
2196
2709
  }
2197
2710
 
2711
+ /**
2712
+ * Whether a node / edge child named like a viz element is one: in a viz namespace, or written
2713
+ * with the `viz:` prefix the document never declares (hand-written files often omit it).
2714
+ * @param name - the element name as written
2715
+ * @param ns - its namespace
2716
+ * @returns true for a viz element
2717
+ */
2718
+ function isViz(name: string, ns: string | null): boolean {
2719
+ return isVizNamespace(ns) || (ns === null && name.startsWith("viz:"));
2720
+ }
2721
+
2198
2722
  /**
2199
2723
  * The element text of an edge without an id, for issues.
2200
2724
  * @param source - the source text, or undefined
@@ -2267,7 +2791,7 @@ function parseColor(attrs: ReadonlyMap<string, string>): number[] {
2267
2791
  if (hex !== undefined && !attrs.has("r")) {
2268
2792
  const digits = hex.startsWith("#") ? hex.slice(1) : hex;
2269
2793
  if (!/^[0-9a-fA-F]{6}$/.test(digits)) {
2270
- throw new GraphFormatError("E_COLUMN_TYPE", `viz hex="${hex}" is not a #RRGGBB colour`, {
2794
+ throw new GraphFormatError("E_COLUMN_TYPE", `viz hex="${hex}" is not a #RRGGBB color`, {
2271
2795
  attribute: "hex",
2272
2796
  });
2273
2797
  }
@@ -2293,7 +2817,7 @@ const XML_START = /^\uFEFF?\s*</;
2293
2817
  * not start with markup
2294
2818
  */
2295
2819
  function sniffGexf(head: Uint8Array): number {
2296
- const text = new TextDecoder("utf-8", { fatal: false }).decode(head);
2820
+ const text = sniffXmlText(head);
2297
2821
  // Only a document that starts as markup: a JSON or CSV value may mention a `<gexf>` tag.
2298
2822
  if (!XML_START.test(text)) {
2299
2823
  return 0;
@@ -2323,9 +2847,13 @@ function resolveGexfOptions(options: (GexfImportOptions & CommonImportOptions) |
2323
2847
  return viz;
2324
2848
  }
2325
2849
 
2326
- /** The GEXF importer (design section 8.4). */
2850
+ /**
2851
+ * The GEXF importer.
2852
+ * @category Built-in formats
2853
+ */
2327
2854
  export const gexfImporter: GraphImporter<GexfImportOptions> = Object.freeze({
2328
2855
  format: GEXF_FORMAT,
2856
+ options: Object.freeze(["viz"]),
2329
2857
  extensions: Object.freeze([".gexf"]),
2330
2858
  mimeTypes: Object.freeze(["application/gexf+xml", "application/xml", "text/xml"]),
2331
2859
  sniff: sniffGexf,
@@ -2355,10 +2883,13 @@ export const gexfImporter: GraphImporter<GexfImportOptions> = Object.freeze({
2355
2883
  const reader = new GexfReader(sink, report, resolved, viz);
2356
2884
  try {
2357
2885
  await tokenizeXml(
2358
- textChunks(input, report, { ...resolved, declaredEncoding: xmlDeclaredEncoding }),
2886
+ textChunks(input, report, { ...resolved, declaredEncoding: xmlDeclaredEncoding, xml: true }),
2359
2887
  reader,
2360
2888
  );
2361
2889
  } catch (err) {
2890
+ if (err instanceof XmlEmptyInputError) {
2891
+ report.fail(EMPTY_INPUT_CODE, "the input is empty (no markup at all)", { line: err.line });
2892
+ }
2362
2893
  if (err instanceof XmlSyntaxError) {
2363
2894
  report.fail(XML_SYNTAX_CODE, err.message, { line: err.line });
2364
2895
  }