@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
@@ -5,8 +5,8 @@
5
5
  * the ids the format cannot hold and hands the exporter the originals to write into its
6
6
  * `graphty:originalId` attribute).
7
7
  *
8
- * Every exporter's check() calls checkCapabilities() first and appends its format-specific notes
9
- * (design section 8.5 names W_GML_RECORD_NUMBER_TYPE and W_OPEN_INTERVAL); export() calls
8
+ * Every exporter's check() calls checkCapabilities() first and appends its format-specific notes;
9
+ * export() calls
10
10
  * sanitizeIds() before writing anything.
11
11
  */
12
12
 
@@ -15,12 +15,14 @@ import {
15
15
  type Dtype,
16
16
  GraphFormatError,
17
17
  type GraphSnapshot,
18
+ type IdCoercion,
18
19
  INVALID_INDEX,
19
20
  type NodeId,
20
21
  } from "@graphty/graph-format";
21
22
 
22
23
  import { type ExportCapabilities, type LossNote } from "../types.js";
23
24
  import {
25
+ COLUMN_DROPPED_CODE,
24
26
  COLUMN_RENAMED_LOSS_CODE,
25
27
  EMPTY_COLUMN_DROPPED_CODE,
26
28
  ID_TEXT_COLLISION_CODE,
@@ -29,6 +31,7 @@ import {
29
31
  MIXED_DIRECTION_CODE,
30
32
  MUTUAL_AS_UNDIRECTED_CODE,
31
33
  MUTUAL_EXPANDED_CODE,
34
+ NONFINITE_AS_NULL_CODE,
32
35
  OPTIONS_GAINED_CODE,
33
36
  PARENTS_DROPPED_CODE,
34
37
  ROLE_ASSUMED_CODE,
@@ -38,33 +41,67 @@ import {
38
41
  TEMPORAL_TEXT_DROPPED_CODE,
39
42
  TEXT_INFERRED_CODE,
40
43
  WEIGHT_KEY_CLASH_CODE,
44
+ WEIGHTS_DROPPED_CODE,
41
45
  XML_ILLEGAL_CHAR_CODE,
42
46
  } from "./codes.js";
47
+ import { coerceIdText } from "./ids.js";
43
48
  import { type ResolvedExportOptions } from "./options.js";
49
+ import { agree, plural } from "./plural.js";
50
+ import { explicitWeights } from "./weights.js";
44
51
 
45
- /** The shared LossNote codes of checkCapabilities(). */
52
+ /** The code of node ids the format cannot write under `sanitizeIds: "error"`. */
53
+ const ID_CHARSET_REFUSAL = "E_ID_CHARSET";
54
+
55
+ /**
56
+ * The shared LossNote codes of checkCapabilities().
57
+ * @category Issue and loss codes
58
+ */
46
59
  export const LOSS = Object.freeze({
47
- /** The format has no mixed direction; expanded pairs are written per onMixedDirection. */
60
+ /**
61
+ * A node or edge attribute is not written: the format has no attributes, or none of this kind
62
+ * (`CheckExtras.attributes` false).
63
+ */
64
+ COLUMN_DROPPED: COLUMN_DROPPED_CODE,
65
+ /**
66
+ * The graph has both directed and undirected edges, the format holds one direction per file, and
67
+ * `onMixedDirection` is "directed" or "undirected": every edge is written with that direction.
68
+ */
48
69
  MIXED_DIRECTION: "W_MIXED_DIRECTION",
49
- /** The format has no mixed direction and onMixedDirection is "error": export() will throw E_DIRECTED. */
70
+ /**
71
+ * The graph has both directed and undirected edges and `onMixedDirection` is "error". An import stops; a save to a
72
+ * format that holds one direction per file fails with E_DIRECTED. Pass "directed" or "undirected" to read or write
73
+ * it anyway.
74
+ */
50
75
  MIXED_DIRECTION_ERROR: MIXED_DIRECTION_CODE,
51
76
  /** Parallel edges in a format without them. */
52
77
  MULTI_EDGES: "W_MULTI_EDGES",
53
78
  /** Self-loops in a format without them. */
54
79
  SELF_LOOPS: "W_SELF_LOOPS",
55
- /** The format requires edge ids and the snapshot has none; canonical e0..e{E-1} are generated. */
80
+ /**
81
+ * The format needs an id on every edge and the graph has none, so the file gets ids e0, e1, e2 and so on, which
82
+ * read back as edge ids.
83
+ */
56
84
  EDGE_IDS_GENERATED: "W_EDGE_IDS_GENERATED",
57
- /** The snapshot has an edge id column and the format cannot write one. */
85
+ /** The graph has edge ids and the format has no place for them, so they are not written. */
58
86
  EDGE_IDS_DROPPED: "W_EDGE_IDS_DROPPED",
59
87
  /** Ids outside the format's charset are rewritten (sanitizeIds "mangle"). */
60
88
  ID_MANGLED: "W_ID_MANGLED",
61
- /** Ids outside the format's charset and sanitizeIds "error": export() will throw E_INVALID_ID. */
62
- ID_CHARSET: "E_ID_CHARSET",
89
+ /**
90
+ * Node ids the format cannot write, under `sanitizeIds: "error"`; the save fails with E_INVALID_ID. Pass
91
+ * `sanitizeIds: "mangle"` to rewrite them.
92
+ */
93
+ ID_CHARSET: ID_CHARSET_REFUSAL,
63
94
  /** The format numbers nodes 1..N; ids that are not their 1-based index are kept as labels only. */
64
95
  ID_RENUMBERED: "W_ID_RENUMBERED",
65
- /** A column dtype the format does not keep as declared. */
96
+ /**
97
+ * An attribute's type is one the format cannot store as it is (for example, a 32-bit float in a format that has only 64-bit
98
+ * numbers), so it reads back with the nearest type the format has.
99
+ */
66
100
  DTYPE: "W_DTYPE_UNSUPPORTED",
67
- /** A multi-component column in a format without strides. */
101
+ /**
102
+ * An attribute with several numbers per element (other than a position) is written as separate values or a list,
103
+ * and does not read back as one attribute.
104
+ */
68
105
  COMPONENTS: "W_COMPONENTS_FLATTENED",
69
106
  /** A list column in a format without lists. */
70
107
  LIST: "W_LIST_UNSUPPORTED",
@@ -78,9 +115,9 @@ export const LOSS = Object.freeze({
78
115
  HIERARCHY: "W_HIERARCHY_DROPPED",
79
116
  /** A start / end / timestamp column in a format without temporal support. */
80
117
  TEMPORAL: TEMPORAL_DROPPED_CODE,
81
- /** A `<column>.text` companion (design section 5.1) the format cannot carry. */
118
+ /** A `<column>.text` companion the format cannot carry. */
82
119
  TEMPORAL_TEXT: TEMPORAL_TEXT_DROPPED_CODE,
83
- /** A role column the format has no slot for; written as a plain attribute, the role lost. */
120
+ /** An attribute with a role the format has no place for is written as a plain attribute; the role is lost. */
84
121
  ROLE: ROLE_DROPPED_CODE,
85
122
  /** A parents (multi-parent) column in a format with single containment only. */
86
123
  PARENTS: PARENTS_DROPPED_CODE,
@@ -90,31 +127,55 @@ export const LOSS = Object.freeze({
90
127
  MUTUAL_AS_UNDIRECTED: MUTUAL_AS_UNDIRECTED_CODE,
91
128
  /** Node ids whose written text reads back as the other type under the importer's id rule. */
92
129
  ID_TEXT_TYPE: ID_TEXT_TYPE_CODE,
93
- /** Two node ids share one written text; export() throws E_INVALID_ID. */
130
+ /**
131
+ * Two node ids would be written as the same text (the number 5 and the text "5"); the save fails with
132
+ * E_INVALID_ID.
133
+ */
94
134
  ID_TEXT_COLLISION: ID_TEXT_COLLISION_CODE,
95
- /** A plain column named like the importer's weight key reads back as THE weight. */
135
+ /**
136
+ * An attribute without the weight role is named like the attribute graph-io reads weights from (for example, `weight`), so
137
+ * it reads back as the edge weight.
138
+ */
96
139
  WEIGHT_KEY_CLASH: WEIGHT_KEY_CLASH_CODE,
97
- /** A role-less column whose written name the importer maps to a role. */
140
+ /**
141
+ * An attribute without a role is written where the format keeps a role (for example, a `name` column as the label), and
142
+ * reads back with that role.
143
+ */
98
144
  ROLE_ASSUMED: ROLE_ASSUMED_CODE,
99
- /** A role column written into the format's slot reads back under the importer's fixed name. */
145
+ /**
146
+ * An attribute with a role (for example, the label) is written where the format keeps that role, and reads back under the
147
+ * name the format's importer gives it.
148
+ */
100
149
  COLUMN_NAME_CHANGED: COLUMN_RENAMED_LOSS_CODE,
101
150
  /** A declared column whose every row is unset vanishes through a format without declarations. */
102
151
  EMPTY_COLUMN: EMPTY_COLUMN_DROPPED_CODE,
103
- /** A string / dict column that reads back as the other storage class (the dictionary heuristic). */
152
+ /**
153
+ * A text attribute reads back as a dictionary attribute, or the reverse, because the importer chooses by how often
154
+ * its values repeat. The values are the same.
155
+ */
104
156
  STORAGE_CLASS: STORAGE_CLASS_CODE,
105
- /** An f64 column whose set values are all integral reads back as i32 through an untyped format. */
157
+ /**
158
+ * A number attribute whose values are all whole numbers reads back as integers, because the format does not record
159
+ * the type.
160
+ */
106
161
  INTEGRAL_F64: INTEGRAL_F64_CODE,
107
- /** Text cells that read back as numbers or booleans under the 5.1 grammar. */
162
+ /**
163
+ * A text value that reads back as a number or a boolean, because the format does not record that it was text (the
164
+ * text "42" reads back as the number 42).
165
+ */
108
166
  TEXT_INFERRED: TEXT_INFERRED_CODE,
109
- /** A string cell holding a character XML 1.0 forbids; export() throws E_COLUMN_TYPE. */
167
+ /** A text value holds a character XML 1.0 forbids (most control characters); the save fails with E_COLUMN_TYPE. */
110
168
  XML_ILLEGAL_CHAR: XML_ILLEGAL_CHAR_CODE,
111
- /** A dict column without declared options gains one on re-import. */
169
+ /**
170
+ * A text attribute stored as a dictionary, without a declared list of allowed values, reads back with its distinct
171
+ * values as that list.
172
+ */
112
173
  OPTIONS_GAINED: OPTIONS_GAINED_CODE,
113
174
  /** A spells column in a format without spells. */
114
175
  SPELLS: "W_SPELLS_DROPPED",
115
176
  /** Dynamic attribute values (extension tables) in a format without them. */
116
177
  DYNAMIC_VALUES: "W_DYNAMIC_VALUES_DROPPED",
117
- /** An open-interval column in a format without open intervals (design section 5.1). */
178
+ /** An open-interval column in a format without open intervals. */
118
179
  OPEN_INTERVAL: "W_OPEN_INTERVAL",
119
180
  /** Graph-level attributes in a format without them. */
120
181
  GRAPH_ATTRIBUTES: "W_GRAPH_ATTRIBUTES_DROPPED",
@@ -124,9 +185,54 @@ export const LOSS = Object.freeze({
124
185
  VIZ: "W_VIZ_DROPPED",
125
186
  /** An extension table the format cannot carry. */
126
187
  EXTENSION_TABLE: "W_EXTENSION_TABLE_DROPPED",
188
+ /** A NaN or infinite number the format cannot spell; not written (or written as null), it reads back unset. */
189
+ NONFINITE_AS_NULL: NONFINITE_AS_NULL_CODE,
190
+ /**
191
+ * The graph has edge weights and the file does not keep them (the format has no weights, or an option turned them
192
+ * off): every edge reads back with the default weight 1.
193
+ */
194
+ WEIGHTS_DROPPED: WEIGHTS_DROPPED_CODE,
127
195
  });
128
196
 
129
- /** The capabilities of a format that keeps nothing beyond plain topology; the base every exporter overrides. */
197
+ /**
198
+ * The error a save throws for the first `E_` note of a check() result, or null when there is none.
199
+ * An exporter calls it before it writes anything, so every format refuses a save the same way: an
200
+ * id note (E_ID_CHARSET, E_ID_TEXT_COLLISION) throws E_INVALID_ID, E_MIXED_DIRECTION throws
201
+ * E_DIRECTED, E_XML_ILLEGAL_CHAR throws E_COLUMN_TYPE, and any other note E_UNSUPPORTED. Map your
202
+ * own note codes onto these with `codes`. The error's `details.code` is the note's code.
203
+ * @param notes - what check() returned
204
+ * @param codes - the error code for each of your own note codes, such as
205
+ * `{ E_PAIRS_BAD_ID: "E_INVALID_ID" }`
206
+ * @returns the error to throw, or null
207
+ * @category Writing a format
208
+ */
209
+ export function refusedSave(
210
+ notes: readonly LossNote[],
211
+ codes: Readonly<Record<string, "E_INVALID_ID" | "E_DIRECTED" | "E_COLUMN_TYPE" | "E_UNSUPPORTED">> = {},
212
+ ): GraphFormatError | null {
213
+ const note = notes.find((n) => n.code.startsWith("E_"));
214
+ if (note === undefined) {
215
+ return null;
216
+ }
217
+ const code = codes[note.code] ?? REFUSAL_CODES[note.code] ?? "E_UNSUPPORTED";
218
+ return new GraphFormatError(code, note.message, { code: note.code, column: note.column, count: note.count });
219
+ }
220
+
221
+ /** The error codes a refused save throws. */
222
+ type RefusalCode = "E_INVALID_ID" | "E_DIRECTED" | "E_COLUMN_TYPE" | "E_UNSUPPORTED";
223
+
224
+ /** The error code a save throws for each shared refusal note. */
225
+ const REFUSAL_CODES: Readonly<Record<string, RefusalCode>> = {
226
+ [ID_CHARSET_REFUSAL]: "E_INVALID_ID",
227
+ [ID_TEXT_COLLISION_CODE]: "E_INVALID_ID",
228
+ [MIXED_DIRECTION_CODE]: "E_DIRECTED",
229
+ [XML_ILLEGAL_CHAR_CODE]: "E_COLUMN_TYPE",
230
+ };
231
+
232
+ /**
233
+ * The capabilities of a format that keeps nothing beyond plain topology; the base every exporter overrides.
234
+ * @category Plugin helpers
235
+ */
130
236
  export const NO_CAPABILITIES: ExportCapabilities = Object.freeze({
131
237
  mixedDirection: false,
132
238
  multiEdges: false,
@@ -151,21 +257,22 @@ export const NO_CAPABILITIES: ExportCapabilities = Object.freeze({
151
257
  * conservative NO_CAPABILITIES value, so an exporter states what it keeps and nothing is assumed.
152
258
  * @param supported - the fields the format supports
153
259
  * @returns a frozen table
260
+ * @category Writing a format
154
261
  */
155
262
  export function capabilities(supported: Partial<ExportCapabilities>): ExportCapabilities {
156
263
  return Object.freeze({ ...NO_CAPABILITIES, ...supported, dtypes: Object.freeze([...(supported.dtypes ?? [])]) });
157
264
  }
158
265
 
159
266
  /**
160
- * Format facts checkCapabilities() needs that the 12.4 table does not carry.
161
- * Consumed by the per-format importers and exporters under src/formats.
267
+ * Format facts checkCapabilities() needs that the capabilities table does not carry.
162
268
  * @public
269
+ * @category Writing a format
163
270
  */
164
271
  export interface CheckExtras {
165
272
  /** Whether the format can write open intervals (GEXF 1.2 startopen / endopen); default false. */
166
273
  readonly openIntervals?: boolean | undefined;
167
274
  /**
168
- * Whether the format carries the `<column>.text` companions of design section 5.1 (the lexical
275
+ * Whether the format carries the `<column>.text` companion columns (the lexical
169
276
  * form of a temporal value); default false, and every companion is then reported as
170
277
  * W_TEMPORAL_TEXT_DROPPED.
171
278
  */
@@ -189,6 +296,34 @@ export interface CheckExtras {
189
296
  * importer gives it). Defaults to none.
190
297
  */
191
298
  readonly roleNames?: Readonly<Record<string, string>> | undefined;
299
+ /**
300
+ * False for a format that writes no node or edge attributes except the ones whose role is in
301
+ * `roles`: every other attribute column is reported once as W_COLUMN_DROPPED, instead of the
302
+ * type and role notes a written column gets. Default true. `nodeAttributes` and
303
+ * `edgeAttributes` override it for one kind.
304
+ */
305
+ readonly attributes?: boolean | undefined;
306
+ /** Like `attributes`, for node attributes only: false for a format that writes no node attributes. */
307
+ readonly nodeAttributes?: boolean | undefined;
308
+ /** Like `attributes`, for edge attributes only: false for a format that writes no edge attributes. */
309
+ readonly edgeAttributes?: boolean | undefined;
310
+ /**
311
+ * Columns the format writes although it writes no other attributes: a column a format option
312
+ * names, such as an edge label column. Keyed by `node` or `edge`. They get no W_COLUMN_DROPPED
313
+ * note; the type notes of a written column still apply.
314
+ */
315
+ readonly writtenColumns?: Readonly<Partial<Record<"node" | "edge", readonly string[]>>> | undefined;
316
+ /**
317
+ * False for a format (or an option) that writes no edge weights: a weighted graph gets one
318
+ * W_WEIGHTS_DROPPED note. Default true.
319
+ */
320
+ readonly weights?: boolean | undefined;
321
+ /**
322
+ * How the format's importer turns id text back into ids ("canonical" for most text formats), so
323
+ * an id that comes back with another type (the text "7" as the number 7) is reported as
324
+ * W_ID_TEXT_TYPE. Omit it when the format records each id's type.
325
+ */
326
+ readonly idsReadBack?: IdCoercion | undefined;
192
327
  }
193
328
 
194
329
  /** The roles checkCapabilities() treats structurally rather than as attribute columns. */
@@ -198,7 +333,7 @@ const TEMPORAL_ROLES: ReadonlySet<string> = new Set(["start", "end", "timestamp"
198
333
  const HIERARCHY_ROLES: ReadonlySet<string> = new Set(["parent", "parents"]);
199
334
 
200
335
  /**
201
- * The generic pre-flight of design section 8.5: compare a snapshot with a format's capabilities
336
+ * The generic pre-flight check: compare a snapshot with a format's capabilities
202
337
  * and return one LossNote per gap, with the shared codes of LOSS. Format-specific notes are the
203
338
  * exporter's to append.
204
339
  * @param snapshot - the snapshot about to be exported
@@ -206,6 +341,7 @@ const HIERARCHY_ROLES: ReadonlySet<string> = new Set(["parent", "parents"]);
206
341
  * @param options - the resolved common export options
207
342
  * @param extras - format facts the capabilities table does not carry
208
343
  * @returns the notes, empty when the export is exact
344
+ * @category Writing a format
209
345
  */
210
346
  export function checkCapabilities(
211
347
  snapshot: GraphSnapshot,
@@ -224,14 +360,14 @@ export function checkCapabilities(
224
360
  if (options.onMixedDirection === "error") {
225
361
  note(
226
362
  LOSS.MIXED_DIRECTION_ERROR,
227
- `${mixed} undirected edge(s) in a directed graph; the format has no mixed direction and onMixedDirection is "error"`,
363
+ `${mixed} undirected edge${plural(mixed)} in a directed graph, and the format holds one direction per file, so the save fails unless onMixedDirection is "directed" or "undirected"`,
228
364
  null,
229
365
  mixed,
230
366
  );
231
367
  } else {
232
368
  note(
233
369
  LOSS.MIXED_DIRECTION,
234
- `${mixed} undirected edge(s) in a directed graph written as ${options.onMixedDirection}`,
370
+ `${mixed} undirected edge${plural(mixed)} in a directed graph ${agree(mixed, "is", "are")} written as ${options.onMixedDirection} (onMixedDirection), and the format holds one direction per file`,
235
371
  null,
236
372
  mixed,
237
373
  );
@@ -240,12 +376,17 @@ export function checkCapabilities(
240
376
 
241
377
  if (snapshot.flags.multigraph && !caps.multiEdges) {
242
378
  const parallel = countParallelArcs(snapshot);
243
- note(LOSS.MULTI_EDGES, `${parallel} parallel edge(s); the format has no parallel edges`, null, parallel);
379
+ note(
380
+ LOSS.MULTI_EDGES,
381
+ `${parallel} parallel edge${plural(parallel)}; the format has no parallel edges`,
382
+ null,
383
+ parallel,
384
+ );
244
385
  }
245
386
  if (snapshot.selfLoopCount > 0 && !caps.selfLoops) {
246
387
  note(
247
388
  LOSS.SELF_LOOPS,
248
- `${snapshot.selfLoopCount} self-loop(s); the format has no self-loops`,
389
+ `${snapshot.selfLoopCount} self-loop${plural(snapshot.selfLoopCount)}; the format has no self-loops`,
249
390
  null,
250
391
  snapshot.selfLoopCount,
251
392
  );
@@ -255,7 +396,7 @@ export function checkCapabilities(
255
396
  if (caps.edgeIds === "required" && edgeIds === null && snapshot.edgeCount > 0) {
256
397
  note(
257
398
  LOSS.EDGE_IDS_GENERATED,
258
- "the format requires edge ids; canonical e0..e{E-1} are generated and not written back",
399
+ "the format requires edge ids, and the graph has none; the edges are written with generated ids, which read back as a new edge id column",
259
400
  null,
260
401
  snapshot.edgeCount,
261
402
  );
@@ -274,28 +415,47 @@ export function checkCapabilities(
274
415
  note(
275
416
  LOSS.ID_RENUMBERED,
276
417
  options.sanitizeIds === "mangle"
277
- ? `${unrepresentable} node id(s) are not their 1-based index; nodes are numbered 1..N, the originals kept in the exporter's originalId attribute (restored by restoreMangledIds) and as labels of the nodes without a label value`
278
- : `${unrepresentable} node id(s) are not their 1-based index; nodes are numbered 1..N and ids kept as labels of the nodes without a label value (a node with one loses its id)`,
418
+ ? `${unrepresentable} node id${plural(unrepresentable)} ${agree(unrepresentable, "is", "are")} not their 1-based index; nodes are numbered 1..N, the original ids are written too, and an import with restoreMangledIds: true reads them back as the ids; they are also the labels of nodes without a label value`
419
+ : `${unrepresentable} node id${plural(unrepresentable)} ${agree(unrepresentable, "is", "are")} not their 1-based index; nodes are numbered 1..N and ids kept as labels of the nodes without a label value (a node with one loses its id)`,
279
420
  null,
280
421
  unrepresentable,
281
422
  );
282
423
  } else if (options.sanitizeIds === "mangle") {
283
424
  note(
284
425
  LOSS.ID_MANGLED,
285
- `${unrepresentable} node id(s) outside the ${caps.idCharset} charset are rewritten; originals kept in the originalId attribute`,
426
+ `${unrepresentable} node id${plural(unrepresentable)} ${charsetText(caps.idCharset, unrepresentable)}, so they are rewritten; the original ids are written too, and an import with restoreMangledIds: true reads them back`,
286
427
  null,
287
428
  unrepresentable,
288
429
  );
289
430
  } else {
290
431
  note(
291
432
  LOSS.ID_CHARSET,
292
- `${unrepresentable} node id(s) outside the ${caps.idCharset} charset; export() will throw unless sanitizeIds is "mangle"`,
433
+ `${unrepresentable} node id${plural(unrepresentable)} ${charsetText(caps.idCharset, unrepresentable)}, so the save fails unless sanitizeIds is "mangle"`,
293
434
  null,
294
435
  unrepresentable,
295
436
  );
296
437
  }
297
438
  }
298
439
 
440
+ if (extras.idsReadBack !== undefined) {
441
+ let retyped = 0;
442
+ for (let i = 0; i < snapshot.nodeCount; i++) {
443
+ const id = snapshot.ids.idOf(i);
444
+ if (coerceIdText(String(id), extras.idsReadBack) !== id) {
445
+ retyped++;
446
+ }
447
+ }
448
+ if (retyped > 0) {
449
+ note(
450
+ LOSS.ID_TEXT_TYPE,
451
+ `${retyped} node id${plural(retyped)} ${agree(retyped, "reads", "read")} back with another type (a number as text, or text as a number)`,
452
+ null,
453
+ retyped,
454
+ );
455
+ }
456
+ }
457
+
458
+ checkWeights(snapshot, extras, note);
299
459
  checkColumns(snapshot.nodes, "node", caps, extras, note);
300
460
  checkColumns(snapshot.edges, "edge", caps, extras, note);
301
461
  const graphColumns = snapshot.graph.names();
@@ -305,7 +465,7 @@ export function checkCapabilities(
305
465
  } else {
306
466
  note(
307
467
  LOSS.GRAPH_ATTRIBUTES,
308
- `${graphColumns.length} graph attribute(s) cannot be written`,
468
+ `${graphColumns.length} graph attribute${plural(graphColumns.length)} cannot be written`,
309
469
  null,
310
470
  graphColumns.length,
311
471
  );
@@ -317,7 +477,7 @@ export function checkCapabilities(
317
477
  if (caps.temporal !== "dynamic-values") {
318
478
  note(
319
479
  LOSS.DYNAMIC_VALUES,
320
- `dynamic values of "${name}" (${table.rowCount} row(s)) cannot be written`,
480
+ `dynamic values of "${name}" (${table.rowCount} row${plural(table.rowCount)}) cannot be written`,
321
481
  name,
322
482
  table.rowCount,
323
483
  );
@@ -325,7 +485,7 @@ export function checkCapabilities(
325
485
  } else {
326
486
  note(
327
487
  LOSS.EXTENSION_TABLE,
328
- `extension table "${name}" (${table.rowCount} row(s)) cannot be written`,
488
+ `extension table "${name}" (${table.rowCount} row${plural(table.rowCount)}) cannot be written`,
329
489
  name,
330
490
  table.rowCount,
331
491
  );
@@ -334,9 +494,69 @@ export function checkCapabilities(
334
494
  return notes;
335
495
  }
336
496
 
497
+ /**
498
+ * The ids a charset cannot hold, as a message phrase.
499
+ * @param charset - the format's id charset
500
+ * @param n - how many ids, for the verb
501
+ * @returns the phrase
502
+ */
503
+ function charsetText(charset: ExportCapabilities["idCharset"], n: number): string {
504
+ const are = agree(n, "is", "are");
505
+ switch (charset) {
506
+ case "nmtoken":
507
+ return `${are} not XML name tokens (letters, digits and . - _ : only; no spaces)`;
508
+ case "integer":
509
+ return `${are} not integers`;
510
+ default:
511
+ return `cannot be written as ${charset} ids`;
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Whether a format writes the attributes of one kind (CheckExtras.nodeAttributes / edgeAttributes, else attributes).
517
+ * @param domain - "node", "edge" or "graph"
518
+ * @param extras - format extras
519
+ * @returns false when it writes none, undefined when the extras do not say
520
+ */
521
+ function writesAttributes(domain: string, extras: CheckExtras): boolean | undefined {
522
+ if (domain === "node") {
523
+ return extras.nodeAttributes ?? extras.attributes;
524
+ }
525
+ if (domain === "edge") {
526
+ return extras.edgeAttributes ?? extras.attributes;
527
+ }
528
+ return extras.attributes;
529
+ }
530
+
531
+ /**
532
+ * The note of a format (or an option) that writes no weights, for a weighted graph.
533
+ * @param snapshot - the snapshot
534
+ * @param extras - format extras
535
+ * @param note - the recorder
536
+ */
537
+ function checkWeights(snapshot: GraphSnapshot, extras: CheckExtras, note: NoteFn): void {
538
+ if (extras.weights === false && explicitWeights(snapshot).weighted) {
539
+ note(
540
+ LOSS.WEIGHTS_DROPPED,
541
+ `the edge weights are not written; ${snapshot.edgeCount} edge${plural(snapshot.edgeCount)} ${agree(snapshot.edgeCount, "reads", "read")} back with weight 1`,
542
+ null,
543
+ snapshot.edgeCount,
544
+ );
545
+ }
546
+ }
547
+
337
548
  /** A note-recording callback. */
338
549
  type NoteFn = (code: string, message: string, column?: string | null, count?: number | null) => void;
339
550
 
551
+ /** How messages name what a column of a role holds. */
552
+ const ROLE_WORDS: Readonly<Record<string, string>> = {
553
+ label: "labels",
554
+ weight: "weights",
555
+ id: "ids",
556
+ position: "positions",
557
+ originalId: "original ids",
558
+ };
559
+
340
560
  /**
341
561
  * The per-column checks of checkCapabilities().
342
562
  * @param table - the table
@@ -364,7 +584,7 @@ function checkColumns(
364
584
  if (fixed !== undefined && fixed !== name) {
365
585
  note(
366
586
  LOSS.COLUMN_NAME_CHANGED,
367
- `${label} (${role}) is written into the format's ${role} slot and reads back as "${fixed}"`,
587
+ `${label} (the ${ROLE_WORDS[role] ?? role}) is written as the format's own ${role} and reads back as "${fixed}"`,
368
588
  name,
369
589
  column.length - column.nullCount,
370
590
  );
@@ -373,11 +593,20 @@ function checkColumns(
373
593
  if (domain === "edge" && role === "id") {
374
594
  continue;
375
595
  }
596
+ const named = extras.writtenColumns?.[domain as "node" | "edge"]?.includes(name) ?? false;
597
+ if (
598
+ writesAttributes(domain, extras) === false &&
599
+ !named &&
600
+ !(role !== null && (extras.roles?.has(role) ?? false))
601
+ ) {
602
+ note(LOSS.COLUMN_DROPPED, `${label} is not written`, name, column.length - column.nullCount);
603
+ continue;
604
+ }
376
605
  if (role === "timeText") {
377
606
  if (extras.temporalText !== true) {
378
607
  note(
379
608
  LOSS.TEMPORAL_TEXT,
380
- `${label} (the lexical form of a temporal column, design section 5.1) cannot be written`,
609
+ `${label} (the original text of a date or time column) cannot be written`,
381
610
  name,
382
611
  column.length - column.nullCount,
383
612
  );
@@ -509,6 +738,7 @@ function checkDtype(column: Column, label: string, caps: ExportCapabilities, not
509
738
  * expanded pair count once; an expanded self-loop has no mirror and counts once).
510
739
  * @param snapshot - the snapshot
511
740
  * @returns the count; 0 for an undirected snapshot or one without the directed role
741
+ * @category Plugin helpers
512
742
  */
513
743
  export function countMixedEdges(snapshot: GraphSnapshot): number {
514
744
  if (!snapshot.directed) {
@@ -537,6 +767,7 @@ export function countMixedEdges(snapshot: GraphSnapshot): number {
537
767
  * over the forward CSR; an undirected parallel edge is counted in both rows.
538
768
  * @param snapshot - the snapshot
539
769
  * @returns the count
770
+ * @category Plugin helpers
540
771
  */
541
772
  export function countParallelArcs(snapshot: GraphSnapshot): number {
542
773
  const { rowPtr, colIdx, nodeCount } = snapshot;
@@ -555,15 +786,15 @@ export function countParallelArcs(snapshot: GraphSnapshot): number {
555
786
 
556
787
  /**
557
788
  * The id charsets of ExportCapabilities.
558
- * Consumed by the per-format importers and exporters under src/formats.
559
789
  * @public
790
+ * @category Plugin helpers
560
791
  */
561
792
  export type IdCharset = ExportCapabilities["idCharset"];
562
793
 
563
794
  /**
564
- * The ids to write for every node after sanitising (design section 8.5).
565
- * Consumed by the per-format importers and exporters under src/formats.
795
+ * The ids to write for every node after sanitising.
566
796
  * @public
797
+ * @category Writing a format
567
798
  */
568
799
  export interface SanitizedIds {
569
800
  /** The charset applied. */
@@ -616,6 +847,7 @@ const NAME_CHAR_RANGES: readonly (readonly [number, number])[] = [
616
847
  * Whether a code point is an XML NameChar.
617
848
  * @param cp - the code point
618
849
  * @returns true when it may appear in an NMTOKEN
850
+ * @category Plugin helpers
619
851
  */
620
852
  export function isNameChar(cp: number): boolean {
621
853
  for (const [lo, hi] of NAME_CHAR_RANGES) {
@@ -633,6 +865,7 @@ export function isNameChar(cp: number): boolean {
633
865
  * Whether a text is an XML NMTOKEN (one or more NameChars; GraphML node and edge ids).
634
866
  * @param text - the text
635
867
  * @returns true for a non-empty text of NameChars
868
+ * @category Plugin helpers
636
869
  */
637
870
  export function isNmtoken(text: string): boolean {
638
871
  if (text.length === 0) {
@@ -652,6 +885,7 @@ export function isNmtoken(text: string): boolean {
652
885
  * @param id - the id
653
886
  * @param charset - the charset
654
887
  * @returns true when the id needs no rewriting ("dense-1-based" is never true: see sanitizeIds)
888
+ * @category Plugin helpers
655
889
  */
656
890
  export function isRepresentableId(id: NodeId, charset: IdCharset): boolean {
657
891
  switch (charset) {
@@ -676,6 +910,7 @@ export function isRepresentableId(id: NodeId, charset: IdCharset): boolean {
676
910
  * @param snapshot - the snapshot
677
911
  * @param charset - the charset
678
912
  * @returns the count
913
+ * @category Plugin helpers
679
914
  */
680
915
  export function countUnrepresentableIds(snapshot: GraphSnapshot, charset: IdCharset): number {
681
916
  const { ids } = snapshot;
@@ -704,7 +939,7 @@ export function countUnrepresentableIds(snapshot: GraphSnapshot, charset: IdChar
704
939
  }
705
940
 
706
941
  /**
707
- * Sanitise the node ids for a charset (design section 8.5). Under "error" any id the charset
942
+ * Sanitise the node ids for a charset. Under "error" any id the charset
708
943
  * cannot hold is E_INVALID_ID (details.reason "charset") and nothing is written; under "mangle"
709
944
  * such ids are rewritten deterministically and uniquely: NMTOKEN by replacing every other
710
945
  * character with `_` (an empty result becomes `_`) and suffixing `_2`, `_3`... on collision;
@@ -716,6 +951,7 @@ export function countUnrepresentableIds(snapshot: GraphSnapshot, charset: IdChar
716
951
  * @param charset - the charset
717
952
  * @param mode - the resolved sanitizeIds option
718
953
  * @returns the ids to write
954
+ * @category Writing a format
719
955
  */
720
956
  export function sanitizeIds(snapshot: GraphSnapshot, charset: IdCharset, mode: "error" | "mangle"): SanitizedIds {
721
957
  const { ids } = snapshot;
@@ -753,7 +989,7 @@ export function sanitizeIds(snapshot: GraphSnapshot, charset: IdCharset, mode: "
753
989
  const first = ids.idOf(bad[0]);
754
990
  throw new GraphFormatError(
755
991
  "E_INVALID_ID",
756
- `${bad.length} node id(s) cannot be written as ${charset} (first: ${JSON.stringify(first)} at index ${bad[0]}); pass sanitizeIds: "mangle" to rewrite them`,
992
+ `${bad.length} node id${plural(bad.length)} ${charsetText(charset, bad.length)} (first: ${JSON.stringify(first)} at index ${bad[0]}); pass sanitizeIds: "mangle" to rewrite them`,
757
993
  { reason: "charset", charset, count: bad.length, id: first, index: bad[0] },
758
994
  );
759
995
  }
@@ -806,6 +1042,7 @@ export function sanitizeIds(snapshot: GraphSnapshot, charset: IdCharset, mode: "
806
1042
  * Rewrite a text as an NMTOKEN: every character that is not a NameChar becomes `_`.
807
1043
  * @param text - the text
808
1044
  * @returns a non-empty NMTOKEN
1045
+ * @category Plugin helpers
809
1046
  */
810
1047
  export function mangleNmtoken(text: string): string {
811
1048
  let out = "";