@graphty/graph-io 0.3.25 → 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
@@ -20,21 +20,30 @@
20
20
  */
21
21
  import { GraphFormatError, INVALID_INDEX, } from "@graphty/graph-format";
22
22
  import { declareResolved, RENAMED_CODE, ROLE_TAKEN_CODE } from "../../common/attributes.js";
23
- import { BAD_VALUE_CODE, DANGLING_REFERENCE_CODE, DUPLICATE_ATTRIBUTE_CODE, DUPLICATE_NODE_CODE, EMPTY_INPUT_CODE, ENCODING_FALLBACK_CODE, INVALID_ENCODING_CODE, INVALID_UTF8_CODE, MISSING_ID_CODE, OPTION_IGNORED_CODE, UNKNOWN_ELEMENT_CODE, UNKNOWN_ENCODING_CODE, } from "../../common/codes.js";
23
+ import { BAD_VALUE_CODE, DANGLING_REFERENCE_CODE, DUPLICATE_ATTRIBUTE_CODE, DUPLICATE_NODE_CODE, ELEMENT_ISSUE, EMPTY_INPUT_CODE, ENCODING_FALLBACK_CODE, INPUT_ISSUE, INVALID_ENCODING_CODE, INVALID_UTF8_CODE, MISSING_ID_CODE, OPTION_IGNORED_CODE, UNKNOWN_ELEMENT_CODE, UNKNOWN_ENCODING_CODE, } from "../../common/codes.js";
24
24
  import { DirectionResolver } from "../../common/direction.js";
25
25
  import { ID_MERGED_CODE, IdCoercer } from "../../common/ids.js";
26
26
  import { LineReader, throwIfAborted } from "../../common/input.js";
27
27
  import { OBO_NODE_COLUMNS, oboColumnDecl, PLACEHOLDER_COLUMN, SYNONYM_SCOPES } from "../../common/ontology.js";
28
28
  import { reportSinkOptions, reportUnusedOptions, resolveImportOptions, SINK_OPTION_CODE, } from "../../common/options.js";
29
+ import { agree, plural } from "../../common/plural.js";
29
30
  import { ImportReportBuilder } from "../../common/report.js";
30
- import { endsWithContinuation, hasStrayBrace, parseXref, parseXrefList, splitQualifiers, splitTagValue, stripComment, tokenize, unescapeObo, } from "./syntax.js";
31
- /** Issue codes of the OBO importer (design section 4.3). */
31
+ import { endsWithContinuation, hasStrayBrace, parseXref, parseXrefList, splitFormFeeds, splitQualifiers, splitTagValue, stripComment, tokenize, unclosedBlock, unescapeObo, } from "./syntax.js";
32
+ /**
33
+ * Issue codes of the OBO importer.
34
+ * @category Built-in formats
35
+ */
32
36
  export const OBO_ISSUE = Object.freeze({
33
- /** The input is empty or whitespace (fatal). */
37
+ ...INPUT_ISSUE,
38
+ ...ELEMENT_ISSUE,
39
+ /** The input is empty or whitespace. The import stops. */
34
40
  EMPTY_INPUT: EMPTY_INPUT_CODE,
35
- /** The input holds invalid UTF-8 (fatal). */
41
+ /** The input is not valid UTF-8. The import stops. */
36
42
  INVALID_UTF8: INVALID_UTF8_CODE,
37
- /** Invalid bytes in the encoding a BOM or the encoding option chose (fatal). */
43
+ /**
44
+ * Some bytes are not valid in the encoding that was chosen (by a byte order mark or the `encoding` option). The
45
+ * import stops.
46
+ */
38
47
  INVALID_ENCODING: INVALID_ENCODING_CODE,
39
48
  /** Bytes that are not UTF-8 were read as windows-1252. */
40
49
  ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
@@ -44,9 +53,9 @@ export const OBO_ISSUE = Object.freeze({
44
53
  MISSING_ID: MISSING_ID_CODE,
45
54
  /** A clause whose value cannot be read (a boolean other than true / false, a relationship with one or three values); the clause is skipped. */
46
55
  BAD_VALUE: BAD_VALUE_CODE,
47
- /** Two frames with one id were merged (spec 4.1.1). */
56
+ /** Two frames have the same id, so they were merged into one node. */
48
57
  DUPLICATE_NODE: DUPLICATE_NODE_CODE,
49
- /** A single-valued tag given twice for one id (a cardinality violation); the first is kept. */
58
+ /** A tag that may appear once per frame appears twice; the first value is kept. */
50
59
  DUPLICATE_ATTRIBUTE: DUPLICATE_ATTRIBUTE_CODE,
51
60
  /** An unknown tag (kept in `obo.unrecognized`) or frame type (skipped), once per name. */
52
61
  UNKNOWN_ELEMENT: UNKNOWN_ELEMENT_CODE,
@@ -54,6 +63,16 @@ export const OBO_ISSUE = Object.freeze({
54
63
  DANGLING_REFERENCE: DANGLING_REFERENCE_CODE,
55
64
  /** A line without a colon, an unterminated quote, a def without its xref list, a qualifier block that does not parse, an unescaped brace. */
56
65
  SYNTAX: "W_OBO_SYNTAX",
66
+ /**
67
+ * Nothing in the input looks like OBO: there is no frame header and no header tag. It may be an HTML page, a JSON
68
+ * or GML file, or UTF-16 text without a byte order mark. The import stops.
69
+ */
70
+ NOT_OBO: "E_OBO_NOT_OBO",
71
+ /**
72
+ * The header has no `format-version` (OBO 1.2 and 1.4 require one), or names a version other than 1.0, 1.2 or 1.4.
73
+ * The file is read accepting the tags of every version.
74
+ */
75
+ FORMAT_VERSION: "W_OBO_FORMAT_VERSION",
57
76
  /** The frame's `id` is not its first clause; it is used anyway. */
58
77
  ID_NOT_FIRST: "W_OBO_ID_NOT_FIRST",
59
78
  /** A synonym without a scope in a file that does not say 1.2, or with a scope that is not one of the four. */
@@ -62,7 +81,7 @@ export const OBO_ISSUE = Object.freeze({
62
81
  UNDECLARED: "W_OBO_UNDECLARED",
63
82
  /** One id for a Term and a Typedef (or an Instance); the Term (the first node frame) is the node. */
64
83
  ID_KIND_CLASH: "W_OBO_ID_KIND_CLASH",
65
- /** Fewer than two `intersection_of` or `union_of` clauses on a frame. */
84
+ /** A frame has only one `intersection_of` or `union_of` clause, where OBO needs at least two. */
66
85
  CARDINALITY: "W_OBO_CARDINALITY",
67
86
  /** An obsolete term with is_a / relationship, or replaced_by / consider on a term that is not obsolete. */
68
87
  OBSOLETION: "W_OBO_OBSOLETION",
@@ -70,19 +89,31 @@ export const OBO_ISSUE = Object.freeze({
70
89
  DEPRECATED_TAG: "W_OBO_DEPRECATED_TAG",
71
90
  /** A backslash line continuation (deprecated in 1.4). */
72
91
  DEPRECATED_SYNTAX: "W_OBO_DEPRECATED_SYNTAX",
73
- /** A header clause kept in `meta.extra.obo.header` whose meaning is not applied (import, id-mapping, the treat-xrefs macros, owl-axioms). */
92
+ /**
93
+ * A header clause whose meaning graph-io does not apply (`import`, `id-mapping`, the `treat-xrefs` macros,
94
+ * `owl-axioms`); it is kept in `snapshot.meta.extra.obo.header`.
95
+ */
74
96
  HEADER_NOT_APPLIED: "W_OBO_HEADER_NOT_APPLIED",
75
97
  /** Obsolete terms and their edges left out under obsolete: "drop". */
76
98
  OBSOLETE_DROPPED: "W_OBO_OBSOLETE_DROPPED",
77
- /** Two distinct id texts became one number under ids "number". */
99
+ /**
100
+ * Two different id texts became the same number because `ids` is "number" (for example, "042" and "42"), so their nodes
101
+ * were merged.
102
+ */
78
103
  ID_MERGED: ID_MERGED_CODE,
79
- /** A vocabulary column renamed `<name>#obo` because the sink already holds the name. */
104
+ /** An OBO attribute was renamed `<name>#obo` because another attribute already has its name. */
80
105
  COLUMN_RENAMED: RENAMED_CODE,
81
- /** A vocabulary column declared without its role because the sink already holds it. */
106
+ /**
107
+ * You read into a graph builder that already has an id, label or position attribute, so this file's one is kept as
108
+ * a plain attribute.
109
+ */
82
110
  ROLE_TAKEN: ROLE_TAKEN_CODE,
83
- /** A common option the format has no use for (defaultDirected, weightFrom, nodeIdFrom, ...). */
111
+ /** You set an option this format does not use; it had no effect. The message names the option. */
84
112
  OPTION_IGNORED: OPTION_IGNORED_CODE,
85
- /** A builder-policy option the caller passed that the caller's sink does not use. */
113
+ /**
114
+ * You read into your own graph builder, which was created with a different `addMissingNodes`, `duplicateEdges`,
115
+ * `selfLoops` or `weightDtype` than the option you passed; the builder's setting applies.
116
+ */
86
117
  SINK_OPTION: SINK_OPTION_CODE,
87
118
  });
88
119
  /** The common options the OBO importer reads. */
@@ -97,7 +128,10 @@ const USED_OPTIONS = new Set([
97
128
  "signal",
98
129
  "onProgress",
99
130
  "encoding",
131
+ "restoreMangledIds",
100
132
  ]);
133
+ /** The property_value relation under which the exporter keeps an id it rewrote (`sanitizeIds: "mangle"`). */
134
+ const ORIGINAL_ID = "graphty:originalId";
101
135
  const DEFAULTS = { ids: "keep", defaultDirected: true, weightFrom: null };
102
136
  /** Frames between two checks of the cancellation signal. */
103
137
  const ABORT_CHECK_INTERVAL = 64;
@@ -111,8 +145,11 @@ const DEPRECATED_HEADER = new Map([
111
145
  ["typeref", "import"],
112
146
  ["version", "data-version"],
113
147
  ]);
114
- /** The 1.0 / 1.2 frame tags read as their 1.4 meaning: tag to [1.4 tag, synonym scope]. */
115
- const DEPRECATED_TAGS = new Map([
148
+ /**
149
+ * The 1.0 / 1.2 frame tags read as their 1.4 meaning: tag to [1.4 tag, synonym scope].
150
+ * @category Plugin helpers
151
+ */
152
+ export const DEPRECATED_TAGS = new Map([
116
153
  ["exact_synonym", ["synonym", "EXACT"]],
117
154
  ["narrow_synonym", ["synonym", "NARROW"]],
118
155
  ["broad_synonym", ["synonym", "BROAD"]],
@@ -131,8 +168,11 @@ const BUILTIN_RELATIONS = new Set([
131
168
  "union_of",
132
169
  "intersection_of",
133
170
  ]);
134
- /** Single-valued text tags (first value kept). */
135
- const TEXT_TAGS = new Set([
171
+ /**
172
+ * Single-valued text tags (first value kept).
173
+ * @category Plugin helpers
174
+ */
175
+ export const TEXT_TAGS = new Set([
136
176
  "name",
137
177
  "namespace",
138
178
  "comment",
@@ -142,10 +182,16 @@ const TEXT_TAGS = new Set([
142
182
  "range",
143
183
  "inverse_of",
144
184
  ]);
145
- /** Single-valued boolean tags. */
146
- const BOOL_TAGS = new Set(Object.keys(OBO_NODE_COLUMNS).filter((name) => OBO_NODE_COLUMNS[name].dtype === "bool"));
147
- /** Tags whose value is one id, collected in a list column. */
148
- const ID_LIST_TAGS = new Set([
185
+ /**
186
+ * Single-valued boolean tags.
187
+ * @category Plugin helpers
188
+ */
189
+ export const BOOL_TAGS = new Set(Object.keys(OBO_NODE_COLUMNS).filter((name) => OBO_NODE_COLUMNS[name].dtype === "bool"));
190
+ /**
191
+ * Tags whose value is one id, collected in a list column.
192
+ * @category Plugin helpers
193
+ */
194
+ export const ID_LIST_TAGS = new Set([
149
195
  "alt_id",
150
196
  "subset",
151
197
  "replaced_by",
@@ -156,8 +202,80 @@ const ID_LIST_TAGS = new Set([
156
202
  "transitive_over",
157
203
  "disjoint_over",
158
204
  ]);
159
- /** Tags that only a Typedef frame may carry (elsewhere they are unrecognized). */
160
- const TYPEDEF_TAGS = new Set([
205
+ /** Header tags the specification allows once (the first is kept). */
206
+ const SINGLE_HEADER_TAGS = new Set([
207
+ "format-version",
208
+ "data-version",
209
+ "version",
210
+ "date",
211
+ "saved-by",
212
+ "auto-generated-by",
213
+ "default-namespace",
214
+ "ontology",
215
+ "default-relationship-id-prefix",
216
+ ]);
217
+ /** The format versions the importer knows (it reads their union). */
218
+ const KNOWN_VERSION = /^(GO_)?1\.[024]$/;
219
+ /** Tags whose value is an id, where a qualifier block cut short by a truncated file is dropped. */
220
+ const ID_VALUE_TAGS = new Set([
221
+ ...ID_LIST_TAGS,
222
+ "is_a",
223
+ "instance_of",
224
+ "relationship",
225
+ "intersection_of",
226
+ ]);
227
+ /** Warnings listed per code before the rest are summed up in one (a file of 50k unknown tags). */
228
+ const MAX_TALLIES_PER_CODE = 100;
229
+ /**
230
+ * Whether a text holds a control character other than tab, line feed, form feed (a line end in
231
+ * the 1.4 grammar, text inside quotes) and carriage return.
232
+ * @param text - the text
233
+ * @returns true when it does
234
+ */
235
+ function hasControl(text) {
236
+ for (const ch of text) {
237
+ if ((ch < " " && ch !== "\t" && ch !== "\n" && ch !== "\f" && ch !== "\r") || ch === "\x7f") {
238
+ return true;
239
+ }
240
+ }
241
+ return false;
242
+ }
243
+ /**
244
+ * Whether a tag could name an OBO tag: no whitespace, control characters or the punctuation of
245
+ * other formats (`{`, `"`, `<`, brackets, parentheses).
246
+ * @param tag - the tag
247
+ * @returns true for a plausible tag
248
+ */
249
+ function isTagName(tag) {
250
+ return tag.length > 0 && !hasControl(tag) && !/[\s{}"<>[\]()]/.test(tag);
251
+ }
252
+ /**
253
+ * A record with no prototype, so a key named after an Object.prototype member (`__proto__`,
254
+ * `constructor`, `toString`) is an own key like any other.
255
+ * @returns the empty record
256
+ */
257
+ function ownRecord() {
258
+ return Object.create(null);
259
+ }
260
+ /**
261
+ * Append a value to the list of a key in a null-prototype record.
262
+ * @param map - the record
263
+ * @param key - the key
264
+ * @param value - the value
265
+ */
266
+ function append(map, key, value) {
267
+ if (Object.hasOwn(map, key)) {
268
+ map[key].push(value);
269
+ }
270
+ else {
271
+ map[key] = [value];
272
+ }
273
+ }
274
+ /**
275
+ * Tags that only a Typedef frame may carry (elsewhere they are unrecognized).
276
+ * @category Plugin helpers
277
+ */
278
+ export const TYPEDEF_TAGS = new Set([
161
279
  "domain",
162
280
  "range",
163
281
  "inverse_of",
@@ -214,10 +332,17 @@ class OboReader {
214
332
  * Create a reader.
215
333
  * @param report - the report
216
334
  * @param signal - the cancellation signal
335
+ * @param typedefsAsNodes - whether `[Typedef]` frames are nodes
217
336
  */
218
- constructor(report, signal) {
337
+ constructor(report, signal, typedefsAsNodes) {
219
338
  /** Header clauses by tag, raw, in order. */
220
- this.header = {};
339
+ this.header = ownRecord();
340
+ /** Whether any frame header was read. */
341
+ this.frameSeen = false;
342
+ /** Header clauses whose tag could be an OBO tag. */
343
+ this.tagClauses = 0;
344
+ /** The first line that is not a frame header, a comment or a tag-value line with a plausible tag. */
345
+ this.firstBadLine = null;
221
346
  this.version = null;
222
347
  this.defaultNamespace = null;
223
348
  this.subsets = new Set();
@@ -237,6 +362,7 @@ class OboReader {
237
362
  this.significant = false;
238
363
  this.report = report;
239
364
  this.signal = signal;
365
+ this.typedefsAsNodes = typedefsAsNodes;
240
366
  }
241
367
  /**
242
368
  * Read one logical line.
@@ -252,32 +378,71 @@ class OboReader {
252
378
  if (t.startsWith("!")) {
253
379
  return;
254
380
  }
255
- const header = /^\[([^\]]*)\]\s*(?:!.*)?$/.exec(t);
256
- if (header !== null) {
257
- this.startFrame(header[1].trim(), line);
381
+ if (t.startsWith("[") && this.frameHeader(t, line)) {
258
382
  return;
259
383
  }
260
384
  // the comment goes first, so a colon inside it never makes `foo ! a:b` a tag-value line
261
385
  const tv = splitTagValue(stripComment(t));
262
- if (tv === null) {
263
- this.tally("parse-error", OBO_ISSUE.SYNTAX, "a line without a colon was skipped", "line", line);
386
+ if (tv === null || tv.tag.length === 0) {
387
+ this.firstBadLine ?? (this.firstBadLine = line);
388
+ const what = tv === null ? "a line without a colon was skipped" : "a line without a tag was skipped";
389
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, what, "line", line);
264
390
  return;
265
391
  }
266
392
  const clause = { tag: tv.tag, value: stripComment(tv.rest), line };
267
393
  if (this.frame === null) {
394
+ if (isTagName(tv.tag)) {
395
+ this.tagClauses++;
396
+ }
397
+ else {
398
+ this.firstBadLine ?? (this.firstBadLine = line);
399
+ }
268
400
  this.headerClause(clause);
269
401
  }
270
402
  else {
271
403
  this.frame.clauses.push(clause);
272
404
  }
273
405
  }
406
+ /**
407
+ * Start a frame at a line opening with `[`: a well-formed header, or one damaged so that only a
408
+ * frame header can be meant (a known frame name, or no colon: `[Term`, `[Term] extra`), with a
409
+ * warning, so the clauses after it never merge into the frame before.
410
+ * @param t - the trimmed line
411
+ * @param line - its number
412
+ * @returns false when the line is not a frame header
413
+ */
414
+ frameHeader(t, line) {
415
+ const close = t.indexOf("]");
416
+ const name = (close < 0 ? t.slice(1) : t.slice(1, close)).trim();
417
+ const after = close < 0 ? "" : t.slice(close + 1).trim();
418
+ const clean = close >= 0 && (after.length === 0 || after.startsWith("!"));
419
+ if (!clean && !FRAME_KINDS.has(name) && t.includes(":")) {
420
+ return false;
421
+ }
422
+ if (!clean) {
423
+ const what = close < 0
424
+ ? "a frame header without its closing ] starts the frame"
425
+ : "text after a frame header was ignored";
426
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, what, `[${name}]`, line);
427
+ }
428
+ // a known frame name, or a clean header with a name: anything else (`[`, `[{"id": 1}]`, a JSON
429
+ // array) is read as a damaged header but is no proof the input is OBO
430
+ const proof = FRAME_KINDS.has(name) || (clean && /^[A-Za-z]/.test(name) && isTagName(name));
431
+ if (!proof) {
432
+ this.firstBadLine ?? (this.firstBadLine = line);
433
+ }
434
+ this.startFrame(name, line, proof);
435
+ return true;
436
+ }
274
437
  /**
275
438
  * Start a frame, finishing the one before.
276
439
  * @param name - the frame type
277
440
  * @param line - the header's line
441
+ * @param proof - whether the header shows the input is OBO
278
442
  */
279
- startFrame(name, line) {
443
+ startFrame(name, line, proof) {
280
444
  this.finishFrame();
445
+ this.frameSeen || (this.frameSeen = proof);
281
446
  const kind = FRAME_KINDS.has(name) ? name : null;
282
447
  if (kind === null) {
283
448
  this.tally("validation-error", OBO_ISSUE.UNKNOWN_ELEMENT, "frames of an unknown type are not nodes; kept in meta.extra.obo.unknownFrames", `[${name}]`, line);
@@ -289,9 +454,11 @@ class OboReader {
289
454
  * @param clause - the clause
290
455
  */
291
456
  headerClause(clause) {
292
- var _a, _b;
293
- ((_a = this.header)[_b = clause.tag] ?? (_a[_b] = [])).push(clause.value);
457
+ append(this.header, clause.tag, clause.value);
294
458
  const { tag } = clause;
459
+ if (SINGLE_HEADER_TAGS.has(tag) && this.header[tag].length > 1) {
460
+ this.tally("validation-error", OBO_ISSUE.DUPLICATE_ATTRIBUTE, "a single-valued header tag given twice; the first is kept", tag, clause.line);
461
+ }
295
462
  const tokens = tokenize(splitQualifiers(clause.value).value);
296
463
  const first = tokens.length > 0 && tokens[0].kind === "word" ? tokens[0].text : null;
297
464
  if (tag === "format-version") {
@@ -300,6 +467,9 @@ class OboReader {
300
467
  else if (tag === "default-namespace") {
301
468
  this.defaultNamespace ?? (this.defaultNamespace = first);
302
469
  }
470
+ else if ((tag === "subsetdef" || tag === "synonymtypedef") && first === null) {
471
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "a declaration whose name is not a word declares nothing", tag, clause.line);
472
+ }
303
473
  else if (tag === "subsetdef" && first !== null) {
304
474
  this.subsets.add(first);
305
475
  }
@@ -317,7 +487,6 @@ class OboReader {
317
487
  }
318
488
  /** Finish the frame being read: find its id and merge its clauses into the record of that id. */
319
489
  finishFrame() {
320
- var _a, _b;
321
490
  const { frame } = this;
322
491
  this.frame = null;
323
492
  if (frame === null) {
@@ -325,9 +494,9 @@ class OboReader {
325
494
  }
326
495
  if (frame.kind === null) {
327
496
  // the guides: an unrecognized frame must survive, so its clauses are kept, raw
328
- const clauses = {};
497
+ const clauses = ownRecord();
329
498
  for (const clause of frame.clauses) {
330
- (clauses[_a = clause.tag] ?? (clauses[_a] = [])).push(clause.value);
499
+ append(clauses, clause.tag, clause.value);
331
500
  }
332
501
  this.unknownFrames.push({ type: frame.name, clauses });
333
502
  return;
@@ -337,18 +506,34 @@ class OboReader {
337
506
  throwIfAborted(this.signal);
338
507
  }
339
508
  const idAt = frame.clauses.findIndex((c) => c.tag === "id");
340
- const id = idAt < 0 ? "" : unescapeObo(splitQualifiers(frame.clauses[idAt].value).value).trim();
509
+ const idParts = idAt < 0 ? null : splitQualifiers(frame.clauses[idAt].value);
510
+ const rawId = idParts === null ? "" : idParts.value.trim();
511
+ const id = unescapeObo(rawId).trim();
512
+ // a Typedef is metadata unless typedefs "nodes": it would never have been a node
513
+ const wouldBeNode = frame.kind !== "Typedef" || this.typedefsAsNodes;
341
514
  if (id.length === 0) {
342
515
  this.report.error("missing-value", OBO_ISSUE.MISSING_ID, `a [${frame.kind}] frame has no id; it is skipped`, {
343
516
  line: frame.line,
344
517
  });
345
- this.report.counts.skippedNodes++;
518
+ if (wouldBeNode) {
519
+ this.report.counts.skippedNodes++;
520
+ }
521
+ return;
522
+ }
523
+ if (tokenize(rawId).length > 1 || hasControl(id)) {
524
+ // a reference cannot name such an id (is_a needs one word), so the frame is refused
525
+ // rather than made a node no edge can reach
526
+ this.report.error("validation-error", OBO_ISSUE.BAD_VALUE, `a [${frame.kind}] frame's id ${JSON.stringify(id)} holds unescaped whitespace or a control character; the frame is skipped`, { line: frame.clauses[idAt].line, element: id });
527
+ if (wouldBeNode) {
528
+ this.report.counts.skippedNodes++;
529
+ }
346
530
  return;
347
531
  }
348
532
  if (idAt > 0) {
349
533
  this.tally("validation-error", OBO_ISSUE.ID_NOT_FIRST, "the id clause is not the frame's first; it is used", "id", frame.clauses[idAt].line);
350
534
  }
351
- const record = this.recordFor(frame.kind, id, frame.line);
535
+ const target = this.recordFor(frame.kind, id, frame.line);
536
+ this.extraQualifiers(target, "id", id, idParts?.qualifiers ?? null);
352
537
  for (let i = 0; i < frame.clauses.length; i++) {
353
538
  const clause = frame.clauses[i];
354
539
  if (clause.tag === "id") {
@@ -357,10 +542,10 @@ class OboReader {
357
542
  }
358
543
  continue;
359
544
  }
360
- this.applyClause(record, clause);
545
+ this.applyClause(target, clause);
361
546
  }
362
- if (record.kind === "Typedef") {
363
- (_b = record.raw).id ?? (_b.id = [id]);
547
+ if (target.kind === "Typedef" && !Object.hasOwn(target.raw, "id")) {
548
+ target.raw.id = [id];
364
549
  }
365
550
  }
366
551
  /**
@@ -378,14 +563,11 @@ class OboReader {
378
563
  this.tally("validation-error", OBO_ISSUE.ID_KIND_CLASH, `an id names both a ${existing.kind} and an ${kind}; the ${existing.kind} is the node`, id, line);
379
564
  }
380
565
  else {
381
- this.report.warning("merged", OBO_ISSUE.DUPLICATE_NODE, `two [${kind}] frames have the id ${id}; they are merged (spec 4.1.1)`, {
382
- line,
383
- element: id,
384
- });
566
+ this.tally("merged", OBO_ISSUE.DUPLICATE_NODE, `[${kind}] frames with this id are merged (spec 4.1.1)`, id, line);
385
567
  }
386
568
  return existing;
387
569
  }
388
- const record = {
570
+ const created = {
389
571
  id,
390
572
  kind,
391
573
  line,
@@ -393,10 +575,10 @@ class OboReader {
393
575
  lists: new Map(),
394
576
  edges: [],
395
577
  seen: new Set(),
396
- raw: {},
578
+ raw: ownRecord(),
397
579
  };
398
- map.set(id, record);
399
- return record;
580
+ map.set(id, created);
581
+ return created;
400
582
  }
401
583
  /**
402
584
  * A clause's tag (a 1.0 tag read as its current one), the synonym scope that tag implies, its
@@ -412,7 +594,12 @@ class OboReader {
412
594
  this.tally("coercion", OBO_ISSUE.DEPRECATED_TAG, `the 1.0 tag is read as ${deprecated[0]}`, tag, clause.line);
413
595
  [tag, scope] = deprecated;
414
596
  }
415
- const split = splitQualifiers(clause.value);
597
+ let split = splitQualifiers(clause.value);
598
+ const open = ID_VALUE_TAGS.has(tag) ? unclosedBlock(split.value) : -1;
599
+ if (open >= 0) {
600
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "a qualifier block without its closing brace was dropped", tag, clause.line);
601
+ split = splitQualifiers(split.value.slice(0, open).trimEnd());
602
+ }
416
603
  const { qualifiers } = split;
417
604
  if (split.badBlock !== null) {
418
605
  this.tally("parse-error", OBO_ISSUE.SYNTAX, "a closing brace that is not a qualifier block was kept as text", tag, clause.line);
@@ -454,24 +641,23 @@ class OboReader {
454
641
  * @param clause - the clause
455
642
  */
456
643
  applyClause(record, clause) {
457
- var _a, _b;
458
644
  const key = `${clause.tag}\u0000${clause.value}`;
459
645
  if (record.seen.has(key)) {
460
646
  return;
461
647
  }
462
648
  record.seen.add(key);
463
649
  if (record.kind === "Typedef") {
464
- ((_a = record.raw)[_b = clause.tag] ?? (_a[_b] = [])).push(clause.value);
650
+ append(record.raw, clause.tag, clause.value);
465
651
  }
466
652
  const { tag, scope, qualifiers, value } = this.clauseParts(clause);
653
+ // a control character kept in the value is the shared W_CONTROL_CHARACTER of the text check
467
654
  const where = { line: clause.line, element: record.id };
468
655
  if (TYPEDEF_TAGS.has(tag) && record.kind !== "Typedef") {
469
656
  this.unrecognized(record, clause);
470
657
  return;
471
658
  }
472
659
  if (TEXT_TAGS.has(tag)) {
473
- this.single(record, tag, unescapeObo(value).trim(), clause.line);
474
- this.extraQualifiers(record, tag, unescapeObo(value).trim(), qualifiers);
660
+ this.text(record, tag, value, qualifiers, clause.line);
475
661
  return;
476
662
  }
477
663
  if (BOOL_TAGS.has(tag)) {
@@ -553,7 +739,7 @@ class OboReader {
553
739
  return;
554
740
  }
555
741
  this.push(record, "xref", xref.id);
556
- this.describe(record, [xref]);
742
+ this.describe(record, [xref], tag, clause.line);
557
743
  this.extraQualifiers(record, tag, xref.id, qualifiers ?? xref.qualifiers);
558
744
  return;
559
745
  }
@@ -564,6 +750,23 @@ class OboReader {
564
750
  this.unrecognized(record, clause);
565
751
  }
566
752
  }
753
+ /**
754
+ * A single-valued text clause (name, comment, ...), warning about a quote that never closes
755
+ * (stripComment() then keeps a `!` comment after it as text).
756
+ * @param record - the record
757
+ * @param tag - the tag
758
+ * @param value - the raw value
759
+ * @param qualifiers - its qualifier block
760
+ * @param line - its line
761
+ */
762
+ text(record, tag, value, qualifiers, line) {
763
+ if (tokenize(value).some((t) => t.kind === "quoted" && t.unterminated)) {
764
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "an unterminated quote; the text after it, a comment included, is kept", tag, line);
765
+ }
766
+ const text = unescapeObo(value).trim();
767
+ this.single(record, tag, text, line);
768
+ this.extraQualifiers(record, tag, text, qualifiers);
769
+ }
567
770
  /**
568
771
  * Read a value that must be one word.
569
772
  * @param tokens - the value's tokens
@@ -602,8 +805,14 @@ class OboReader {
602
805
  this.tally("parse-error", OBO_ISSUE.SYNTAX, "the xref list is missing", tag, line);
603
806
  return { text: tokens[0].text, xrefs: [] };
604
807
  }
808
+ if (list.unterminated) {
809
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "an xref list without its closing ]; kept as read", tag, line);
810
+ }
811
+ if (tokens.length > 2) {
812
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "text after the xref list was ignored", tag, line);
813
+ }
605
814
  const xrefs = parseXrefList(list.text);
606
- this.describe(record, xrefs);
815
+ this.describe(record, xrefs, tag, line);
607
816
  this.xrefQualifiers(record, tag, xrefs);
608
817
  return { text: tokens[0].text, xrefs: xrefs.map((x) => x.id) };
609
818
  }
@@ -668,8 +877,16 @@ class OboReader {
668
877
  if (words.length > 2 || list === undefined) {
669
878
  this.tally("parse-error", OBO_ISSUE.SYNTAX, list === undefined ? "a synonym without its xref list" : "extra words after the synonym type", "synonym", line);
670
879
  }
880
+ const listAt = list === undefined ? -1 : tokens.indexOf(list);
881
+ const stray = tokens.some((t, k) => k > 0 && (t.kind === "quoted" || (t.kind === "list" && k !== listAt) || (listAt >= 0 && k > listAt)));
882
+ if (stray) {
883
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "a second quoted string, a second xref list or text after the list was ignored", "synonym", line);
884
+ }
885
+ if (list?.unterminated === true) {
886
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "an xref list without its closing ]; kept as read", "synonym", line);
887
+ }
671
888
  const xrefs = list === undefined ? [] : parseXrefList(list.text);
672
- this.describe(record, xrefs);
889
+ this.describe(record, xrefs, "synonym", line);
673
890
  this.xrefQualifiers(record, "synonym", xrefs);
674
891
  this.push(record, "synonym", withQualifiers({ text: tokens[0].text, scope, type, xrefs: xrefs.map((x) => x.id) }, qualifiers));
675
892
  }
@@ -692,7 +909,14 @@ class OboReader {
692
909
  this.report.error("validation-error", OBO_ISSUE.BAD_VALUE, "property_value needs a relation and a value; the clause is skipped", where);
693
910
  return;
694
911
  }
695
- const datatype = tokens.length === 3 ? tokens[2].text : null;
912
+ if (tokens.some((t) => t.unterminated)) {
913
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "an unterminated quote; the text runs to the end of the line", "property_value", where.line);
914
+ }
915
+ let datatype = tokens.length === 3 ? tokens[2].text : null;
916
+ if (tokens.length === 3 && tokens[2].kind !== "word") {
917
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "a datatype that is not a word was ignored", "property_value", where.line);
918
+ datatype = null;
919
+ }
696
920
  this.push(record, "property_value", withQualifiers({ relation: tokens[0].text, value: tokens[1].text, datatype }, qualifiers));
697
921
  }
698
922
  /**
@@ -729,17 +953,27 @@ class OboReader {
729
953
  }
730
954
  }
731
955
  /**
732
- * Record xref descriptions in `xref.descriptions`.
733
- * @param record - the record
956
+ * Record xref descriptions in `xref.descriptions` (the first description of an id is kept),
957
+ * warning about a description whose quote never closes.
958
+ * @param target - the record
734
959
  * @param xrefs - the xrefs
960
+ * @param tag - the clause's tag
961
+ * @param line - the clause's line
735
962
  */
736
- describe(record, xrefs) {
737
- var _a;
963
+ describe(target, xrefs, tag, line) {
738
964
  for (const xref of xrefs) {
965
+ if (xref.unterminated) {
966
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "an xref description whose quote never closes runs to the end", tag, line);
967
+ }
739
968
  if (xref.description !== null) {
740
- const map = (record.values.get("xref.descriptions") ?? {});
741
- map[_a = xref.id] ?? (map[_a] = xref.description);
742
- record.values.set("xref.descriptions", map);
969
+ let map = target.values.get("xref.descriptions");
970
+ if (map === undefined) {
971
+ map = ownRecord();
972
+ target.values.set("xref.descriptions", map);
973
+ }
974
+ if (!Object.hasOwn(map, xref.id)) {
975
+ map[xref.id] = xref.description;
976
+ }
743
977
  }
744
978
  }
745
979
  }
@@ -766,7 +1000,7 @@ class OboReader {
766
1000
  if (qualifiers === null) {
767
1001
  return;
768
1002
  }
769
- const map = (record.values.get("obo.qualifiers") ?? {});
1003
+ const map = record.values.get("obo.qualifiers") ?? {};
770
1004
  (map[tag] ?? (map[tag] = [])).push({ value, qualifiers });
771
1005
  record.values.set("obo.qualifiers", map);
772
1006
  }
@@ -776,10 +1010,12 @@ class OboReader {
776
1010
  * @param clause - the clause
777
1011
  */
778
1012
  unrecognized(record, clause) {
779
- var _a;
780
- const map = (record.values.get("obo.unrecognized") ?? {});
781
- (map[_a = clause.tag] ?? (map[_a] = [])).push(unescapeObo(clause.value));
782
- record.values.set("obo.unrecognized", map);
1013
+ let map = record.values.get("obo.unrecognized");
1014
+ if (map === undefined) {
1015
+ map = ownRecord();
1016
+ record.values.set("obo.unrecognized", map);
1017
+ }
1018
+ append(map, clause.tag, unescapeObo(clause.value));
783
1019
  this.tally("validation-error", OBO_ISSUE.UNKNOWN_ELEMENT, "an unknown tag was kept in obo.unrecognized", clause.tag, clause.line);
784
1020
  }
785
1021
  /**
@@ -800,16 +1036,60 @@ class OboReader {
800
1036
  found.count++;
801
1037
  }
802
1038
  }
803
- /** Record the counted warnings, in the order they were first seen. */
1039
+ /**
1040
+ * Record the counted warnings, in the order they were first seen; past MAX_TALLIES_PER_CODE
1041
+ * of one code, the rest are summed up in one warning.
1042
+ */
804
1043
  flushTallies() {
1044
+ const listed = new Map();
1045
+ const over = new Map();
805
1046
  for (const t of this.tallies.values()) {
806
- this.report.warning(t.category, t.code, `${t.element}: ${t.what} (${t.count} time(s), first at line ${t.line})`, {
1047
+ const n = (listed.get(t.code) ?? 0) + 1;
1048
+ listed.set(t.code, n);
1049
+ if (n > MAX_TALLIES_PER_CODE) {
1050
+ const rest = over.get(t.code) ?? [];
1051
+ rest.push(t);
1052
+ over.set(t.code, rest);
1053
+ continue;
1054
+ }
1055
+ this.report.warning(t.category, t.code, `${t.element}: ${t.what} (${t.count} time${plural(t.count)}, first at line ${t.line})`, {
807
1056
  line: t.line,
808
1057
  element: t.element,
809
1058
  });
810
1059
  }
1060
+ for (const [code, rest] of over) {
1061
+ this.report.warning(rest[0].category, code, `${rest.length} more element${plural(rest.length)} with this warning, not listed (first: ${rest[0].element}: ${rest[0].what}, line ${rest[0].line})`, { line: rest[0].line });
1062
+ }
811
1063
  this.tallies.clear();
812
1064
  }
1065
+ /**
1066
+ * The first line that makes the input not OBO: null unless no frame header and no plausible
1067
+ * header tag was read while some line was neither (an HTML page, a JSON, GML, DOT or CSV file,
1068
+ * UTF-16 read without its BOM). A file of only comments is OBO, if an empty one.
1069
+ * @returns the line, or null
1070
+ */
1071
+ notObo() {
1072
+ return this.frameSeen || this.tagClauses > 0 ? null : this.firstBadLine;
1073
+ }
1074
+ /**
1075
+ * Warn about the header once the file is read: a missing or unknown format-version (the
1076
+ * synonym-scope rule depends on it), a date that is not a real calendar date.
1077
+ */
1078
+ checkHeader() {
1079
+ if (this.version === null) {
1080
+ const empty = this.frameSeen ? "" : " and the file has no frames: nothing was read";
1081
+ this.tally("validation-error", OBO_ISSUE.FORMAT_VERSION, `the header has no format-version (required by 1.2 and 1.4)${empty}; read as the 1.0 / 1.2 / 1.4 union`, "format-version", 1);
1082
+ }
1083
+ else if (!KNOWN_VERSION.test(this.version)) {
1084
+ this.tally("validation-error", OBO_ISSUE.FORMAT_VERSION, `format-version ${JSON.stringify(this.version)} is not 1.0, 1.2 or 1.4; read as 1.4`, "format-version", 1);
1085
+ }
1086
+ const date = this.header.date?.[0];
1087
+ if (date !== undefined &&
1088
+ /^\d{2}:\d{2}:\d{4}\s+\d{2}:\d{2}$/.test(date.trim()) &&
1089
+ oboDate(date.trim()) === null) {
1090
+ this.tally("validation-error", OBO_ISSUE.SYNTAX, "the header date is not a real calendar date; created is null", "date", 1);
1091
+ }
1092
+ }
813
1093
  /**
814
1094
  * The header as GraphMeta fields (design 4.2): name from `ontology`, the version, `created` from
815
1095
  * the `dd:MM:yyyy HH:mm` date, `creator` from `saved-by`.
@@ -863,7 +1143,9 @@ function oboDate(text) {
863
1143
  return null;
864
1144
  }
865
1145
  const [, dd, mm, yyyy, hh, min] = m;
866
- if (Number(mm) < 1 || Number(mm) > 12 || Number(dd) < 1 || Number(dd) > 31 || Number(hh) > 23 || Number(min) > 59) {
1146
+ // a real calendar date: 31:02 and 31:04 do not survive the round trip through Date
1147
+ const day = new Date(Date.UTC(Number(yyyy), Number(mm) - 1, Number(dd)));
1148
+ if (Number(mm) < 1 || Number(mm) > 12 || day.getUTCDate() !== Number(dd) || Number(hh) > 23 || Number(min) > 59) {
867
1149
  return null;
868
1150
  }
869
1151
  return `${yyyy}-${mm}-${dd}T${hh}:${min}`;
@@ -946,9 +1228,9 @@ function planGraph(reader, report, addMissingNodes, obo) {
946
1228
  }
947
1229
  }
948
1230
  reader.flushTallies();
949
- reportDangling(records, dangling, addMissingNodes, danglingEdges, report);
1231
+ reportDangling(records, dangling, addMissingNodes, danglingEdges, report, new Set(reader.typedefs.keys()));
950
1232
  if (dropped.size > 0) {
951
- report.warning("merged", OBO_ISSUE.OBSOLETE_DROPPED, `${dropped.size} obsolete term(s) and ${droppedEdges} edge(s) to or from them were left out (obsolete: "drop")`, { element: [...dropped][0] });
1233
+ report.warning("merged", OBO_ISSUE.OBSOLETE_DROPPED, `${dropped.size} obsolete term${plural(dropped.size)} and ${droppedEdges} edge${plural(droppedEdges)} to or from them were left out (obsolete: "drop")`, { element: [...dropped][0] });
952
1234
  }
953
1235
  return {
954
1236
  nodes: records.filter((record) => kept.has(record.id)),
@@ -964,8 +1246,9 @@ function planGraph(reader, report, addMissingNodes, obo) {
964
1246
  * @param addMissingNodes - whether they became placeholder nodes
965
1247
  * @param droppedEdges - the edges dropped under addMissingNodes false
966
1248
  * @param report - the report
1249
+ * @param typedefs - the ids of the Typedef frames (kept as metadata, so not nodes)
967
1250
  */
968
- function reportDangling(records, dangling, addMissingNodes, droppedEdges, report) {
1251
+ function reportDangling(records, dangling, addMissingNodes, droppedEdges, report, typedefs) {
969
1252
  if (dangling.length === 0) {
970
1253
  return;
971
1254
  }
@@ -977,13 +1260,16 @@ function reportDangling(records, dangling, addMissingNodes, droppedEdges, report
977
1260
  }
978
1261
  const shown = dangling.slice(0, 5).map((id) => {
979
1262
  const primary = altOf.get(id);
1263
+ if (typedefs.has(id)) {
1264
+ return `${id} (a Typedef, kept as metadata, not a node)`;
1265
+ }
980
1266
  return primary === undefined ? id : `${id} (an alt_id of ${primary})`;
981
1267
  });
982
1268
  const more = dangling.length > 5 ? `, ... (${dangling.length - 5} more)` : "";
983
1269
  const action = addMissingNodes
984
1270
  ? "each became a placeholder node (graphty.placeholder)"
985
- : `${droppedEdges} edge(s) to them were dropped (addMissingNodes false)`;
986
- report.warning("validation-error", OBO_ISSUE.DANGLING_REFERENCE, `${dangling.length} target(s) no frame declares: ${shown.join(", ")}${more}; ${action}`, { element: dangling[0] });
1271
+ : `${droppedEdges} edge${plural(droppedEdges)} to them ${agree(droppedEdges, "was", "were")} dropped (addMissingNodes false)`;
1272
+ report.warning("validation-error", OBO_ISSUE.DANGLING_REFERENCE, `${dangling.length} target${plural(dangling.length)} no frame declares: ${shown.join(", ")}${more}; ${action}`, { element: dangling[0] });
987
1273
  }
988
1274
  /** Pushes ids into a sink: the id coercion rule and the cancellation check. */
989
1275
  class Pusher {
@@ -994,6 +1280,8 @@ class Pusher {
994
1280
  * @param options - the resolved common options
995
1281
  */
996
1282
  constructor(sink, report, options) {
1283
+ /** The original ids restored under restoreMangledIds, by the id the file wrote. */
1284
+ this.restored = new Map();
997
1285
  this.since = 0;
998
1286
  this.sink = sink;
999
1287
  this.report = report;
@@ -1006,7 +1294,7 @@ class Pusher {
1006
1294
  * @returns the node id
1007
1295
  */
1008
1296
  id(text) {
1009
- const id = this.coercer.text(text);
1297
+ const id = this.coercer.text(this.restored.get(text) ?? text);
1010
1298
  const merge = this.coercer.lastMerge;
1011
1299
  if (merge !== null) {
1012
1300
  this.report.warning("coercion", OBO_ISSUE.ID_MERGED, `id text ${JSON.stringify(merge.text)} merged with ${JSON.stringify(merge.previousText)} as ${merge.id}`, { element: text });
@@ -1056,6 +1344,9 @@ class Pusher {
1056
1344
  function writeGraph(reader, sink, report, options, obo) {
1057
1345
  const plan = planGraph(reader, report, options.addMissingNodes, obo);
1058
1346
  const push = new Pusher(sink, report, options);
1347
+ if (options.restoreMangledIds) {
1348
+ restoreIds(plan.nodes, push.restored);
1349
+ }
1059
1350
  const namespace = reader.namespaceDefault();
1060
1351
  // columns, declared only when used, in the vocabulary's order
1061
1352
  const used = new Set(["type"]);
@@ -1101,6 +1392,49 @@ function writeGraph(reader, sink, report, options, obo) {
1101
1392
  }
1102
1393
  writeEdges(plan, push, options);
1103
1394
  }
1395
+ /**
1396
+ * Take back the ids the exporter rewrote under `sanitizeIds: "mangle"`: a frame's
1397
+ * `property_value: graphty:originalId "..."` names its original id, which replaces the written one
1398
+ * wherever the file names it (the property value itself is dropped). An original that another
1399
+ * frame already uses as its id is not restored.
1400
+ * @param records - the node records
1401
+ * @param restored - receives written id to original id
1402
+ */
1403
+ function restoreIds(records, restored) {
1404
+ const taken = new Set(records.map((r) => r.id));
1405
+ for (const record of records) {
1406
+ const values = record.lists.get("property_value");
1407
+ const at = values?.findIndex((v) => v.relation === ORIGINAL_ID) ?? -1;
1408
+ if (values === undefined || at < 0) {
1409
+ continue;
1410
+ }
1411
+ const original = originalText(values[at].value);
1412
+ if (taken.has(original)) {
1413
+ continue;
1414
+ }
1415
+ taken.add(original);
1416
+ restored.set(record.id, original);
1417
+ values.splice(at, 1);
1418
+ if (values.length === 0) {
1419
+ record.lists.delete("property_value");
1420
+ }
1421
+ }
1422
+ }
1423
+ /**
1424
+ * The original id of a `graphty:originalId` property value: the exporter writes it as JSON text (so
1425
+ * every character survives); a value that is not a JSON string is the id as it stands.
1426
+ * @param value - the property value
1427
+ * @returns the original id
1428
+ */
1429
+ function originalText(value) {
1430
+ try {
1431
+ const parsed = JSON.parse(value);
1432
+ return typeof parsed === "string" ? parsed : value;
1433
+ }
1434
+ catch {
1435
+ return value;
1436
+ }
1437
+ }
1104
1438
  /**
1105
1439
  * Push the planned edges through the direction resolver, with their relation and qualifiers.
1106
1440
  * @param plan - the plan
@@ -1141,6 +1475,54 @@ function writeEdges(plan, push, options) {
1141
1475
  }
1142
1476
  }
1143
1477
  }
1478
+ /**
1479
+ * Feed the physical lines to the reader as logical lines: backslash continuations joined (the parts
1480
+ * collected and joined once, so a value continued over many lines costs its length), form feeds
1481
+ * outside quotes split. A backslash inside a `!` comment never continues a line, and a continuation
1482
+ * never swallows a frame header or a blank line.
1483
+ * @param lines - the line reader
1484
+ * @param reader - the OBO reader
1485
+ */
1486
+ async function readLines(lines, reader) {
1487
+ const parts = [];
1488
+ let partLine = 0;
1489
+ const feed = (text, line) => {
1490
+ for (const piece of splitFormFeeds(text)) {
1491
+ reader.line(piece, line);
1492
+ }
1493
+ };
1494
+ for await (const physical of lines) {
1495
+ const { line } = lines;
1496
+ if (parts.length > 0) {
1497
+ const head = physical.trimStart();
1498
+ if (head.length === 0 || head.startsWith("[")) {
1499
+ reader.tally("parse-error", OBO_ISSUE.SYNTAX, "a line continuation before a frame header or a blank line was ignored", "\\", partLine);
1500
+ feed(parts.join(""), partLine);
1501
+ parts.length = 0;
1502
+ }
1503
+ else {
1504
+ reader.tally("coercion", OBO_ISSUE.DEPRECATED_SYNTAX, "a backslash line continuation was joined (deprecated in 1.4)", "\\", partLine);
1505
+ }
1506
+ }
1507
+ if (endsWithContinuation(physical) && stripComment(physical) === physical) {
1508
+ parts.push(physical.slice(0, -1));
1509
+ partLine = line;
1510
+ continue;
1511
+ }
1512
+ if (parts.length > 0) {
1513
+ parts.push(physical);
1514
+ feed(parts.join(""), line);
1515
+ parts.length = 0;
1516
+ }
1517
+ else {
1518
+ feed(physical, line);
1519
+ }
1520
+ }
1521
+ if (parts.length > 0) {
1522
+ reader.tally("coercion", OBO_ISSUE.DEPRECATED_SYNTAX, "a backslash line continuation was joined (deprecated in 1.4)", "\\", partLine);
1523
+ feed(parts.join(""), partLine);
1524
+ }
1525
+ }
1144
1526
  /**
1145
1527
  * Confidence that a head is OBO (design 1.5): after a BOM, blank lines and `!` comment lines, 0.9
1146
1528
  * when the first significant line is `format-version:` or a `[Term]` / `[Typedef]` / `[Instance]`
@@ -1179,10 +1561,12 @@ function sniffObo(head) {
1179
1561
  return tagLines > 0 ? 0.4 : 0;
1180
1562
  }
1181
1563
  /**
1182
- * The OBO importer plugin (design section 4).
1564
+ * The OBO importer plugin.
1565
+ * @category Built-in formats
1183
1566
  */
1184
1567
  export const oboImporter = Object.freeze({
1185
1568
  format: "obo",
1569
+ options: Object.freeze(["obsolete", "typedefs"]),
1186
1570
  extensions: Object.freeze([".obo"]),
1187
1571
  mimeTypes: Object.freeze(["text/obo", "application/obo"]),
1188
1572
  /**
@@ -1206,33 +1590,20 @@ export const oboImporter = Object.freeze({
1206
1590
  const report = new ImportReportBuilder("obo", resolved.errorLimit);
1207
1591
  reportSinkOptions(sink, options, report, true);
1208
1592
  reportUnusedOptions(options, report, USED_OPTIONS);
1209
- const reader = new OboReader(report, resolved.signal);
1210
- const lines = new LineReader(input, report, resolved);
1211
- let pending = null;
1212
- for await (const physical of lines) {
1213
- const { line } = lines;
1214
- const text = pending === null ? physical : pending + physical;
1215
- pending = null;
1216
- if (endsWithContinuation(text)) {
1217
- reader.tally("coercion", OBO_ISSUE.DEPRECATED_SYNTAX, "a backslash line continuation was joined (deprecated in 1.4)", "\\", line);
1218
- pending = text.slice(0, -1);
1219
- continue;
1220
- }
1221
- // the 1.4 grammar counts form feed as a line end; LineReader does not
1222
- for (const piece of text.includes("\f") ? text.split("\f") : [text]) {
1223
- reader.line(piece, line);
1224
- }
1225
- }
1226
- if (pending !== null) {
1227
- reader.line(pending, lines.line);
1228
- }
1593
+ const reader = new OboReader(report, resolved.signal, obo.typedefs === "nodes");
1594
+ await readLines(new LineReader(input, report, resolved), reader);
1229
1595
  reader.finishFrame();
1230
1596
  if (!reader.significant) {
1231
1597
  report.fail(OBO_ISSUE.EMPTY_INPUT, "the input is empty");
1232
1598
  }
1599
+ const notObo = reader.notObo();
1600
+ if (notObo !== null) {
1601
+ report.fail(OBO_ISSUE.NOT_OBO, `the input is not OBO: it has no frame header and no header tag, and line ${notObo} is not a tag-value line`, { line: notObo });
1602
+ }
1233
1603
  throwIfAborted(resolved.signal);
1604
+ reader.checkHeader();
1234
1605
  writeGraph(reader, sink, report, resolved, obo);
1235
- const typedefs = {};
1606
+ const typedefs = ownRecord();
1236
1607
  for (const [id, record] of reader.typedefs) {
1237
1608
  typedefs[id] = record.raw;
1238
1609
  }