@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
@@ -0,0 +1,3465 @@
1
+ import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
2
+ import { S as SINK_OPTION_CODE, O as OPTION_IGNORED_CODE, R as ROLE_TAKEN_CODE, ay as RENAMED_CODE, I as ID_MERGED_CODE, i as DANGLING_REFERENCE_CODE, g as UNKNOWN_ELEMENT_CODE, o as DUPLICATE_ATTRIBUTE_CODE, n as DUPLICATE_NODE_CODE, a3 as BAD_VALUE_CODE, t as MISSING_ID_CODE, w as UNKNOWN_ENCODING_CODE, x as ENCODING_FALLBACK_CODE, y as INVALID_ENCODING_CODE, z as INVALID_UTF8_CODE, E as EMPTY_INPUT_CODE, F as ELEMENT_ISSUE, G as INPUT_ISSUE, al as resolveImportOptions, a8 as ImportReportBuilder, ao as reportSinkOptions, an as reportUnusedOptions, am as throwIfAborted, b as plural, a1 as declareResolved, ar as IdCoercer, aq as DirectionResolver, $ as agree, az as LineReader, c as capabilities, L as LOSS, au as ROLE_ASSUMED_CODE, aA as NODE_ORDER_CODE, aB as GRAPH_COLUMN_AS_METADATA_CODE, aC as TYPEDEF_NODES_CODE, aD as RELATION_ASSUMED_CODE, at as COLUMN_AS_PROPERTY_VALUE_CODE, r as resolveExportOptions, p as pairFolding, e as explicitWeights, av as formatNumber } from "./export-BTI4yQgy.js";
3
+ import { S as SYNONYM_SCOPES, O as OBO_NODE_COLUMNS, o as oboColumnDecl, P as PLACEHOLDER_COLUMN, f as framelessNodes, m as movedNodes, s as slotColumn, c as capabilityNotes, e as emptyColumnNotes, a as cellOf, F as FRAME_KINDS$1, U as UNWRITTEN_ROLES, n as notePropertyColumn, b as fitsVocabulary, i as isQualifierRecord, t as textOf } from "./ontology-export-B9Tcf37D.js";
4
+ import { j as joinText, e as encodeChunks } from "./writer-DxjgHwSe.js";
5
+ const ESCAPES = Object.freeze({
6
+ n: "\n",
7
+ t: " ",
8
+ // the guides read `\W` as a space; the 1.4 BNF (and fastobo) as the letter W (design 4.1)
9
+ W: " "
10
+ });
11
+ function unescapeObo(text) {
12
+ if (!text.includes("\\")) {
13
+ return text;
14
+ }
15
+ let out = "";
16
+ for (let i = 0; i < text.length; i++) {
17
+ const ch = text[i];
18
+ if (ch !== "\\") {
19
+ out += ch;
20
+ continue;
21
+ }
22
+ i++;
23
+ if (i < text.length) {
24
+ const next = text[i];
25
+ out += ESCAPES[next] ?? next;
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ function isEscaped(text, index) {
31
+ let run = 0;
32
+ for (let i = index - 1; i >= 0 && text[i] === "\\"; i--) {
33
+ run++;
34
+ }
35
+ return run % 2 === 1;
36
+ }
37
+ function splitFormFeeds(line) {
38
+ if (!line.includes("\f")) {
39
+ return [line];
40
+ }
41
+ const pieces = [];
42
+ let quoted = false;
43
+ let start = 0;
44
+ for (let i = 0; i < line.length; i += line[i] === "\\" ? 2 : 1) {
45
+ const ch = line[i];
46
+ if (ch === '"') {
47
+ quoted = !quoted;
48
+ } else if (ch === "\f" && !quoted) {
49
+ pieces.push(line.slice(start, i));
50
+ start = i + 1;
51
+ }
52
+ }
53
+ pieces.push(line.slice(start));
54
+ return pieces;
55
+ }
56
+ function endsWithContinuation(line) {
57
+ return line.endsWith("\\") && !isEscaped(line, line.length - 1);
58
+ }
59
+ function splitTagValue(line) {
60
+ for (let i = 0; i < line.length; i++) {
61
+ if (line[i] === "\\") {
62
+ i++;
63
+ continue;
64
+ }
65
+ if (line[i] === ":") {
66
+ return { tag: unescapeObo(line.slice(0, i).trim()), rest: line.slice(i + 1).trimStart() };
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ function stripComment(rest) {
72
+ let quoted = false;
73
+ for (let i = 0; i < rest.length; i++) {
74
+ const ch = rest[i];
75
+ if (ch === "\\") {
76
+ i++;
77
+ continue;
78
+ }
79
+ const atStart = i === 0 || rest[i - 1] === " " || rest[i - 1] === " ";
80
+ if (ch === '"' && (quoted || atStart)) {
81
+ quoted = !quoted;
82
+ } else if (ch === "!" && !quoted && atStart) {
83
+ return rest.slice(0, i).trimEnd();
84
+ }
85
+ }
86
+ return rest.trimEnd();
87
+ }
88
+ function splitQualifiers(value) {
89
+ if (!value.endsWith("}")) {
90
+ return { value, qualifiers: null, badBlock: null };
91
+ }
92
+ const blocks = braceBlocks(value);
93
+ const parsed = [];
94
+ let end = value.length;
95
+ let badBlock = null;
96
+ while (end > 0 && value[end - 1] === "}" && !isEscaped(value, end - 1)) {
97
+ const open = blocks.get(end - 1) ?? -1;
98
+ const block = open < 0 ? null : parseQualifiers(value.slice(open + 1, end - 1));
99
+ if (block === null) {
100
+ badBlock = value.slice(Math.max(open, 0), end);
101
+ break;
102
+ }
103
+ parsed.push(block);
104
+ end = open;
105
+ while (end > 0 && /\s/.test(value[end - 1])) {
106
+ end--;
107
+ }
108
+ }
109
+ let qualifiers = null;
110
+ for (let i = parsed.length - 1; i >= 0; i--) {
111
+ qualifiers ??= /* @__PURE__ */ Object.create(null);
112
+ for (const [name, v] of Object.entries(parsed[i])) {
113
+ addQualifier(qualifiers, name, v);
114
+ }
115
+ }
116
+ return { value: value.slice(0, end), qualifiers, badBlock };
117
+ }
118
+ function braceBlocks(value) {
119
+ const blocks = /* @__PURE__ */ new Map();
120
+ let quoted = false;
121
+ let open = -1;
122
+ for (let i = 0; i < value.length; i++) {
123
+ const ch = value[i];
124
+ if (ch === "\\") {
125
+ i++;
126
+ continue;
127
+ }
128
+ if (ch === '"') {
129
+ quoted = !quoted;
130
+ } else if (!quoted && ch === "{") {
131
+ open = i;
132
+ } else if (!quoted && ch === "}") {
133
+ blocks.set(i, open);
134
+ open = -1;
135
+ }
136
+ }
137
+ return blocks;
138
+ }
139
+ function unclosedBlock(value) {
140
+ let quoted = false;
141
+ let open = -1;
142
+ for (let i = 0; i < value.length; i += value[i] === "\\" ? 2 : 1) {
143
+ const ch = value[i];
144
+ if (ch === "\\") {
145
+ continue;
146
+ }
147
+ if (ch === '"' && open < 0) {
148
+ quoted = !quoted;
149
+ } else if (!quoted && ch === "{") {
150
+ open = i;
151
+ } else if (!quoted && ch === "}") {
152
+ open = -1;
153
+ }
154
+ }
155
+ return open;
156
+ }
157
+ function addQualifier(record, name, value) {
158
+ const values = Array.isArray(value) ? value : [value];
159
+ if (!Object.prototype.hasOwnProperty.call(record, name)) {
160
+ record[name] = Array.isArray(value) ? [...value] : value;
161
+ return;
162
+ }
163
+ const before = record[name];
164
+ if (Array.isArray(before)) {
165
+ for (const v of values) {
166
+ before.push(v);
167
+ }
168
+ } else {
169
+ record[name] = [before, ...values];
170
+ }
171
+ }
172
+ function parseQualifiers(inner) {
173
+ const out = /* @__PURE__ */ Object.create(null);
174
+ let i = 0;
175
+ const n = inner.length;
176
+ let any = false;
177
+ while (i < n) {
178
+ while (i < n && (inner[i] === " " || inner[i] === " " || inner[i] === ",")) {
179
+ i++;
180
+ }
181
+ if (i >= n) {
182
+ break;
183
+ }
184
+ const eq = inner.indexOf("=", i);
185
+ if (eq < 0) {
186
+ return null;
187
+ }
188
+ const name = unescapeObo(inner.slice(i, eq).trim());
189
+ if (name.length === 0 || name.includes('"')) {
190
+ return null;
191
+ }
192
+ i = eq + 1;
193
+ while (i < n && (inner[i] === " " || inner[i] === " ")) {
194
+ i++;
195
+ }
196
+ let value;
197
+ if (inner[i] === '"') {
198
+ const end = closingQuote(inner, i + 1);
199
+ if (end < 0) {
200
+ return null;
201
+ }
202
+ value = unescapeObo(inner.slice(i + 1, end));
203
+ i = end + 1;
204
+ } else {
205
+ let end = i;
206
+ while (end < n && inner[end] !== ",") {
207
+ end += inner[end] === "\\" ? 2 : 1;
208
+ }
209
+ value = unescapeObo(inner.slice(i, end).trim());
210
+ i = end;
211
+ }
212
+ addQualifier(out, name, value);
213
+ any = true;
214
+ }
215
+ return any ? out : null;
216
+ }
217
+ function closingQuote(text, from) {
218
+ for (let i = from; i < text.length; i++) {
219
+ if (text[i] === "\\") {
220
+ i++;
221
+ } else if (text[i] === '"') {
222
+ return i;
223
+ }
224
+ }
225
+ return -1;
226
+ }
227
+ function tokenize(value) {
228
+ const tokens = [];
229
+ const n = value.length;
230
+ let i = 0;
231
+ while (i < n) {
232
+ const ch = value[i];
233
+ if (ch === " " || ch === " ") {
234
+ i++;
235
+ continue;
236
+ }
237
+ if (ch === '"') {
238
+ const end2 = closingQuote(value, i + 1);
239
+ const stop = end2 < 0 ? n : end2;
240
+ let after = stop + 1;
241
+ if (end2 >= 0 && value[after] === "@") {
242
+ while (after < n && value[after] !== " " && value[after] !== " ") {
243
+ after++;
244
+ }
245
+ }
246
+ const suffix = end2 < 0 ? "" : value.slice(stop + 1, after);
247
+ tokens.push({
248
+ kind: "quoted",
249
+ text: unescapeObo(value.slice(i + 1, stop)) + suffix,
250
+ unterminated: end2 < 0
251
+ });
252
+ i = after;
253
+ continue;
254
+ }
255
+ if (ch === "[") {
256
+ const end2 = closingBracket(value, i + 1);
257
+ const stop = end2 < 0 ? n : end2;
258
+ tokens.push({ kind: "list", text: value.slice(i + 1, stop), unterminated: end2 < 0 });
259
+ i = stop + 1;
260
+ continue;
261
+ }
262
+ let end = i;
263
+ while (end < n && value[end] !== " " && value[end] !== " ") {
264
+ end += value[end] === "\\" ? 2 : 1;
265
+ }
266
+ tokens.push({ kind: "word", text: unescapeObo(value.slice(i, Math.min(end, n))), unterminated: false });
267
+ i = end;
268
+ }
269
+ return tokens;
270
+ }
271
+ function closingBracket(text, from) {
272
+ let quoted = false;
273
+ for (let i = from; i < text.length; i++) {
274
+ const ch = text[i];
275
+ if (ch === "\\") {
276
+ i++;
277
+ } else if (ch === '"') {
278
+ quoted = !quoted;
279
+ } else if (ch === "]" && !quoted) {
280
+ return i;
281
+ }
282
+ }
283
+ return -1;
284
+ }
285
+ function parseXref(raw) {
286
+ const { value, qualifiers } = splitQualifiers(raw.trim());
287
+ let quote = -1;
288
+ for (let i = 0; i < value.length; i++) {
289
+ if (value[i] === "\\") {
290
+ i++;
291
+ } else if (value[i] === '"') {
292
+ quote = i;
293
+ break;
294
+ }
295
+ }
296
+ const id = unescapeObo((quote < 0 ? value : value.slice(0, quote)).trim());
297
+ if (id.length === 0) {
298
+ return null;
299
+ }
300
+ let description = null;
301
+ let unterminated = false;
302
+ if (quote >= 0) {
303
+ const end = closingQuote(value, quote + 1);
304
+ unterminated = end < 0;
305
+ description = unescapeObo(value.slice(quote + 1, end < 0 ? value.length : end));
306
+ }
307
+ return { id, description, qualifiers, unterminated };
308
+ }
309
+ function parseXrefList(inner) {
310
+ const out = [];
311
+ let quoted = false;
312
+ let depth = 0;
313
+ let start = 0;
314
+ const flush = (end) => {
315
+ const xref = parseXref(inner.slice(start, end));
316
+ if (xref !== null) {
317
+ out.push(xref);
318
+ }
319
+ };
320
+ for (let i = 0; i < inner.length; i++) {
321
+ const ch = inner[i];
322
+ if (ch === "\\") {
323
+ i++;
324
+ } else if (ch === '"') {
325
+ quoted = !quoted;
326
+ } else if (!quoted && ch === "{") {
327
+ depth++;
328
+ } else if (!quoted && ch === "}") {
329
+ depth = Math.max(0, depth - 1);
330
+ } else if (!quoted && depth === 0 && ch === ",") {
331
+ flush(i);
332
+ start = i + 1;
333
+ }
334
+ }
335
+ flush(inner.length);
336
+ return out;
337
+ }
338
+ function hasStrayBrace(value) {
339
+ let quoted = false;
340
+ let listed = false;
341
+ for (let i = 0; i < value.length; i++) {
342
+ const ch = value[i];
343
+ if (ch === "\\") {
344
+ i++;
345
+ } else if (ch === '"') {
346
+ quoted = !quoted;
347
+ } else if (!quoted && (ch === "[" || ch === "]")) {
348
+ listed = ch === "[";
349
+ } else if (!quoted && !listed && (ch === "{" || ch === "}")) {
350
+ return true;
351
+ }
352
+ }
353
+ return false;
354
+ }
355
+ const LINE_ENDS = /\r\n|\r|\f/g;
356
+ function hasLineEnd(text) {
357
+ return text.includes("\r") || text.includes("\f");
358
+ }
359
+ function escapeOboQuoted(text) {
360
+ return text.replaceAll(LINE_ENDS, "\n").replaceAll(/[\\"\n\t]/g, (ch) => {
361
+ switch (ch) {
362
+ case "\n":
363
+ return String.raw`\n`;
364
+ case " ":
365
+ return String.raw`\t`;
366
+ default:
367
+ return `\\${ch}`;
368
+ }
369
+ });
370
+ }
371
+ function escapeOboValue(text) {
372
+ return escapeOboQuoted(text).replaceAll(/[!{}[\],]/g, (ch) => `\\${ch}`);
373
+ }
374
+ function isOboWord(text) {
375
+ return text.length > 0 && !NOT_IN_WORD.test(text);
376
+ }
377
+ const NOT_IN_WORD = /[\s!{}\p{Cc}]/u;
378
+ function escapeOboWord(word) {
379
+ const out = word.replaceAll(/[\\"]/g, (ch) => `\\${ch}`);
380
+ return out.startsWith("[") ? `\\${out}` : out;
381
+ }
382
+ function isQualifierName(name) {
383
+ return name.length > 0 && name === name.trim() && !/[=",{}[\]!\\\n\r\t\f]/.test(name);
384
+ }
385
+ function qualifierBlock(pairs) {
386
+ if (pairs.length === 0) {
387
+ return "";
388
+ }
389
+ const items = pairs.map(([name, value]) => `${name}="${escapeOboQuoted(value)}"`);
390
+ return ` {${items.join(", ")}}`;
391
+ }
392
+ const OBO_ISSUE = Object.freeze({
393
+ ...INPUT_ISSUE,
394
+ ...ELEMENT_ISSUE,
395
+ /** The input is empty or whitespace. The import stops. */
396
+ EMPTY_INPUT: EMPTY_INPUT_CODE,
397
+ /** The input is not valid UTF-8. The import stops. */
398
+ INVALID_UTF8: INVALID_UTF8_CODE,
399
+ /**
400
+ * Some bytes are not valid in the encoding that was chosen (by a byte order mark or the `encoding` option). The
401
+ * import stops.
402
+ */
403
+ INVALID_ENCODING: INVALID_ENCODING_CODE,
404
+ /** Bytes that are not UTF-8 were read as windows-1252. */
405
+ ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
406
+ /** A declared encoding the platform cannot decode was ignored. */
407
+ UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
408
+ /** A frame without an `id` clause; the frame is skipped. */
409
+ MISSING_ID: MISSING_ID_CODE,
410
+ /** A clause whose value cannot be read (a boolean other than true / false, a relationship with one or three values); the clause is skipped. */
411
+ BAD_VALUE: BAD_VALUE_CODE,
412
+ /** Two frames have the same id, so they were merged into one node. */
413
+ DUPLICATE_NODE: DUPLICATE_NODE_CODE,
414
+ /** A tag that may appear once per frame appears twice; the first value is kept. */
415
+ DUPLICATE_ATTRIBUTE: DUPLICATE_ATTRIBUTE_CODE,
416
+ /** An unknown tag (kept in `obo.unrecognized`) or frame type (skipped), once per name. */
417
+ UNKNOWN_ELEMENT: UNKNOWN_ELEMENT_CODE,
418
+ /** A target no frame declares: a placeholder node was made, or the edge dropped under addMissingNodes false. */
419
+ DANGLING_REFERENCE: DANGLING_REFERENCE_CODE,
420
+ /** A line without a colon, an unterminated quote, a def without its xref list, a qualifier block that does not parse, an unescaped brace. */
421
+ SYNTAX: "W_OBO_SYNTAX",
422
+ /**
423
+ * Nothing in the input looks like OBO: there is no frame header and no header tag. It may be an HTML page, a JSON
424
+ * or GML file, or UTF-16 text without a byte order mark. The import stops.
425
+ */
426
+ NOT_OBO: "E_OBO_NOT_OBO",
427
+ /**
428
+ * 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.
429
+ * The file is read accepting the tags of every version.
430
+ */
431
+ FORMAT_VERSION: "W_OBO_FORMAT_VERSION",
432
+ /** The frame's `id` is not its first clause; it is used anyway. */
433
+ ID_NOT_FIRST: "W_OBO_ID_NOT_FIRST",
434
+ /** 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. */
435
+ SYNONYM_SCOPE: "W_OBO_SYNONYM_SCOPE",
436
+ /** A relation, subset or synonym type that nothing declares. */
437
+ UNDECLARED: "W_OBO_UNDECLARED",
438
+ /** One id for a Term and a Typedef (or an Instance); the Term (the first node frame) is the node. */
439
+ ID_KIND_CLASH: "W_OBO_ID_KIND_CLASH",
440
+ /** A frame has only one `intersection_of` or `union_of` clause, where OBO needs at least two. */
441
+ CARDINALITY: "W_OBO_CARDINALITY",
442
+ /** An obsolete term with is_a / relationship, or replaced_by / consider on a term that is not obsolete. */
443
+ OBSOLETION: "W_OBO_OBSOLETION",
444
+ /** An OBO 1.0 / 1.2 tag read as its 1.4 meaning (exact_synonym, xref_analog, use_term, typeref, version). */
445
+ DEPRECATED_TAG: "W_OBO_DEPRECATED_TAG",
446
+ /** A backslash line continuation (deprecated in 1.4). */
447
+ DEPRECATED_SYNTAX: "W_OBO_DEPRECATED_SYNTAX",
448
+ /**
449
+ * A header clause whose meaning graph-io does not apply (`import`, `id-mapping`, the `treat-xrefs` macros,
450
+ * `owl-axioms`); it is kept in `snapshot.meta.extra.obo.header`.
451
+ */
452
+ HEADER_NOT_APPLIED: "W_OBO_HEADER_NOT_APPLIED",
453
+ /** Obsolete terms and their edges left out under obsolete: "drop". */
454
+ OBSOLETE_DROPPED: "W_OBO_OBSOLETE_DROPPED",
455
+ /**
456
+ * Two different id texts became the same number because `ids` is "number" (for example, "042" and "42"), so their nodes
457
+ * were merged.
458
+ */
459
+ ID_MERGED: ID_MERGED_CODE,
460
+ /** An OBO attribute was renamed `<name>#obo` because another attribute already has its name. */
461
+ COLUMN_RENAMED: RENAMED_CODE,
462
+ /**
463
+ * You read into a graph builder that already has an id, label or position attribute, so this file's one is kept as
464
+ * a plain attribute.
465
+ */
466
+ ROLE_TAKEN: ROLE_TAKEN_CODE,
467
+ /** You set an option this format does not use; it had no effect. The message names the option. */
468
+ OPTION_IGNORED: OPTION_IGNORED_CODE,
469
+ /**
470
+ * You read into your own graph builder, which was created with a different `addMissingNodes`, `duplicateEdges`,
471
+ * `selfLoops` or `weightDtype` than the option you passed; the builder's setting applies.
472
+ */
473
+ SINK_OPTION: SINK_OPTION_CODE
474
+ });
475
+ const USED_OPTIONS = /* @__PURE__ */ new Set([
476
+ "ids",
477
+ "addMissingNodes",
478
+ "duplicateEdges",
479
+ "selfLoops",
480
+ "onMixedDirection",
481
+ "weightDtype",
482
+ "errorLimit",
483
+ "signal",
484
+ "onProgress",
485
+ "encoding",
486
+ "restoreMangledIds"
487
+ ]);
488
+ const ORIGINAL_ID$1 = "graphty:originalId";
489
+ const DEFAULTS = { ids: "keep", defaultDirected: true, weightFrom: null };
490
+ const ABORT_CHECK_INTERVAL = 64;
491
+ const SNIFF_BYTES = 4096;
492
+ const FRAME_KINDS = /* @__PURE__ */ new Set(["Term", "Typedef", "Instance"]);
493
+ const NOT_APPLIED_HEADER = /^(import|typeref|id-mapping|default-relationship-id-prefix|owl-axioms|treat-xrefs-as-.*)$/;
494
+ const DEPRECATED_HEADER = /* @__PURE__ */ new Map([
495
+ ["typeref", "import"],
496
+ ["version", "data-version"]
497
+ ]);
498
+ const DEPRECATED_TAGS = /* @__PURE__ */ new Map([
499
+ ["exact_synonym", ["synonym", "EXACT"]],
500
+ ["narrow_synonym", ["synonym", "NARROW"]],
501
+ ["broad_synonym", ["synonym", "BROAD"]],
502
+ ["related_synonym", ["synonym", "RELATED"]],
503
+ ["xref_analog", ["xref", null]],
504
+ ["xref_unk", ["xref", null]],
505
+ ["xref_unknown", ["xref", null]],
506
+ ["use_term", ["consider", null]]
507
+ ]);
508
+ const BUILTIN_RELATIONS$1 = /* @__PURE__ */ new Set([
509
+ "is_a",
510
+ "instance_of",
511
+ "disjoint_from",
512
+ "inverse_of",
513
+ "union_of",
514
+ "intersection_of"
515
+ ]);
516
+ const TEXT_TAGS = /* @__PURE__ */ new Set([
517
+ "name",
518
+ "namespace",
519
+ "comment",
520
+ "created_by",
521
+ "creation_date",
522
+ "domain",
523
+ "range",
524
+ "inverse_of"
525
+ ]);
526
+ const BOOL_TAGS = new Set(
527
+ Object.keys(OBO_NODE_COLUMNS).filter((name) => OBO_NODE_COLUMNS[name].dtype === "bool")
528
+ );
529
+ const ID_LIST_TAGS = /* @__PURE__ */ new Set([
530
+ "alt_id",
531
+ "subset",
532
+ "replaced_by",
533
+ "consider",
534
+ "union_of",
535
+ "equivalent_to",
536
+ "disjoint_from",
537
+ "transitive_over",
538
+ "disjoint_over"
539
+ ]);
540
+ const SINGLE_HEADER_TAGS = /* @__PURE__ */ new Set([
541
+ "format-version",
542
+ "data-version",
543
+ "version",
544
+ "date",
545
+ "saved-by",
546
+ "auto-generated-by",
547
+ "default-namespace",
548
+ "ontology",
549
+ "default-relationship-id-prefix"
550
+ ]);
551
+ const KNOWN_VERSION = /^(GO_)?1\.[024]$/;
552
+ const ID_VALUE_TAGS = /* @__PURE__ */ new Set([
553
+ ...ID_LIST_TAGS,
554
+ "is_a",
555
+ "instance_of",
556
+ "relationship",
557
+ "intersection_of"
558
+ ]);
559
+ const MAX_TALLIES_PER_CODE = 100;
560
+ function hasControl(text) {
561
+ for (const ch of text) {
562
+ if (ch < " " && ch !== " " && ch !== "\n" && ch !== "\f" && ch !== "\r" || ch === "") {
563
+ return true;
564
+ }
565
+ }
566
+ return false;
567
+ }
568
+ function isTagName(tag) {
569
+ return tag.length > 0 && !hasControl(tag) && !/[\s{}"<>[\]()]/.test(tag);
570
+ }
571
+ function ownRecord() {
572
+ return /* @__PURE__ */ Object.create(null);
573
+ }
574
+ function append(map, key, value) {
575
+ if (Object.hasOwn(map, key)) {
576
+ map[key].push(value);
577
+ } else {
578
+ map[key] = [value];
579
+ }
580
+ }
581
+ const TYPEDEF_TAGS = /* @__PURE__ */ new Set([
582
+ "domain",
583
+ "range",
584
+ "inverse_of",
585
+ "transitive_over",
586
+ "disjoint_over",
587
+ "holds_over_chain",
588
+ "equivalent_to_chain",
589
+ "expand_assertion_to",
590
+ "expand_expression_to",
591
+ "is_cyclic",
592
+ "is_reflexive",
593
+ "is_symmetric",
594
+ "is_anti_symmetric",
595
+ "is_asymmetric",
596
+ "is_transitive",
597
+ "is_functional",
598
+ "is_inverse_functional",
599
+ "is_metadata_tag",
600
+ "is_class_level"
601
+ ]);
602
+ function resolveOboOptions$1(options) {
603
+ const obsolete = options?.obsolete ?? "keep";
604
+ const typedefs = options?.typedefs ?? "metadata";
605
+ if (obsolete !== "keep" && obsolete !== "drop") {
606
+ throw unsupported("obsolete", obsolete, ["keep", "drop"]);
607
+ }
608
+ if (typedefs !== "metadata" && typedefs !== "nodes") {
609
+ throw unsupported("typedefs", typedefs, ["metadata", "nodes"]);
610
+ }
611
+ return { obsolete, typedefs };
612
+ }
613
+ function unsupported(option, found, supported) {
614
+ return new GraphFormatError(
615
+ "E_UNSUPPORTED",
616
+ `option ${option}: ${JSON.stringify(found)} is not one of ${supported.join(", ")}`,
617
+ {
618
+ option,
619
+ found,
620
+ supported
621
+ }
622
+ );
623
+ }
624
+ class OboReader {
625
+ /**
626
+ * Create a reader.
627
+ * @param report - the report
628
+ * @param signal - the cancellation signal
629
+ * @param typedefsAsNodes - whether `[Typedef]` frames are nodes
630
+ */
631
+ constructor(report, signal, typedefsAsNodes) {
632
+ this.header = ownRecord();
633
+ this.frameSeen = false;
634
+ this.tagClauses = 0;
635
+ this.firstBadLine = null;
636
+ this.version = null;
637
+ this.defaultNamespace = null;
638
+ this.subsets = /* @__PURE__ */ new Set();
639
+ this.synonymTypes = /* @__PURE__ */ new Map();
640
+ this.nodes = /* @__PURE__ */ new Map();
641
+ this.typedefs = /* @__PURE__ */ new Map();
642
+ this.tallies = /* @__PURE__ */ new Map();
643
+ this.frame = null;
644
+ this.unknownFrames = [];
645
+ this.framesSinceCheck = 0;
646
+ this.significant = false;
647
+ this.report = report;
648
+ this.signal = signal;
649
+ this.typedefsAsNodes = typedefsAsNodes;
650
+ }
651
+ /**
652
+ * Read one logical line.
653
+ * @param text - the line, continuation joined
654
+ * @param line - its 1-based number
655
+ */
656
+ line(text, line) {
657
+ const t = text.trim();
658
+ if (t.length === 0) {
659
+ return;
660
+ }
661
+ this.significant = true;
662
+ if (t.startsWith("!")) {
663
+ return;
664
+ }
665
+ if (t.startsWith("[") && this.frameHeader(t, line)) {
666
+ return;
667
+ }
668
+ const tv = splitTagValue(stripComment(t));
669
+ if (tv === null || tv.tag.length === 0) {
670
+ this.firstBadLine ??= line;
671
+ const what = tv === null ? "a line without a colon was skipped" : "a line without a tag was skipped";
672
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, what, "line", line);
673
+ return;
674
+ }
675
+ const clause = { tag: tv.tag, value: stripComment(tv.rest), line };
676
+ if (this.frame === null) {
677
+ if (isTagName(tv.tag)) {
678
+ this.tagClauses++;
679
+ } else {
680
+ this.firstBadLine ??= line;
681
+ }
682
+ this.headerClause(clause);
683
+ } else {
684
+ this.frame.clauses.push(clause);
685
+ }
686
+ }
687
+ /**
688
+ * Start a frame at a line opening with `[`: a well-formed header, or one damaged so that only a
689
+ * frame header can be meant (a known frame name, or no colon: `[Term`, `[Term] extra`), with a
690
+ * warning, so the clauses after it never merge into the frame before.
691
+ * @param t - the trimmed line
692
+ * @param line - its number
693
+ * @returns false when the line is not a frame header
694
+ */
695
+ frameHeader(t, line) {
696
+ const close = t.indexOf("]");
697
+ const name = (close < 0 ? t.slice(1) : t.slice(1, close)).trim();
698
+ const after = close < 0 ? "" : t.slice(close + 1).trim();
699
+ const clean = close >= 0 && (after.length === 0 || after.startsWith("!"));
700
+ if (!clean && !FRAME_KINDS.has(name) && t.includes(":")) {
701
+ return false;
702
+ }
703
+ if (!clean) {
704
+ const what = close < 0 ? "a frame header without its closing ] starts the frame" : "text after a frame header was ignored";
705
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, what, `[${name}]`, line);
706
+ }
707
+ const proof = FRAME_KINDS.has(name) || clean && /^[A-Za-z]/.test(name) && isTagName(name);
708
+ if (!proof) {
709
+ this.firstBadLine ??= line;
710
+ }
711
+ this.startFrame(name, line, proof);
712
+ return true;
713
+ }
714
+ /**
715
+ * Start a frame, finishing the one before.
716
+ * @param name - the frame type
717
+ * @param line - the header's line
718
+ * @param proof - whether the header shows the input is OBO
719
+ */
720
+ startFrame(name, line, proof) {
721
+ this.finishFrame();
722
+ this.frameSeen ||= proof;
723
+ const kind = FRAME_KINDS.has(name) ? name : null;
724
+ if (kind === null) {
725
+ this.tally(
726
+ "validation-error",
727
+ OBO_ISSUE.UNKNOWN_ELEMENT,
728
+ "frames of an unknown type are not nodes; kept in meta.extra.obo.unknownFrames",
729
+ `[${name}]`,
730
+ line
731
+ );
732
+ }
733
+ this.frame = { kind, name, line, clauses: [] };
734
+ }
735
+ /**
736
+ * Record one header clause.
737
+ * @param clause - the clause
738
+ */
739
+ headerClause(clause) {
740
+ append(this.header, clause.tag, clause.value);
741
+ const { tag } = clause;
742
+ if (SINGLE_HEADER_TAGS.has(tag) && this.header[tag].length > 1) {
743
+ this.tally(
744
+ "validation-error",
745
+ OBO_ISSUE.DUPLICATE_ATTRIBUTE,
746
+ "a single-valued header tag given twice; the first is kept",
747
+ tag,
748
+ clause.line
749
+ );
750
+ }
751
+ const tokens = tokenize(splitQualifiers(clause.value).value);
752
+ const first = tokens.length > 0 && tokens[0].kind === "word" ? tokens[0].text : null;
753
+ if (tag === "format-version") {
754
+ this.version ??= unescapeObo(clause.value).trim();
755
+ } else if (tag === "default-namespace") {
756
+ this.defaultNamespace ??= first;
757
+ } else if ((tag === "subsetdef" || tag === "synonymtypedef") && first === null) {
758
+ this.tally(
759
+ "parse-error",
760
+ OBO_ISSUE.SYNTAX,
761
+ "a declaration whose name is not a word declares nothing",
762
+ tag,
763
+ clause.line
764
+ );
765
+ } else if (tag === "subsetdef" && first !== null) {
766
+ this.subsets.add(first);
767
+ } else if (tag === "synonymtypedef" && first !== null) {
768
+ const scope = tokens.slice(1).find((t) => t.kind === "word" && SYNONYM_SCOPES.has(t.text));
769
+ this.synonymTypes.set(first, scope?.text ?? null);
770
+ }
771
+ const now = DEPRECATED_HEADER.get(tag);
772
+ if (now !== void 0) {
773
+ this.tally("coercion", OBO_ISSUE.DEPRECATED_TAG, `the 1.0 header tag is read as ${now}`, tag, clause.line);
774
+ }
775
+ if (NOT_APPLIED_HEADER.test(tag)) {
776
+ this.tally(
777
+ "unsupported",
778
+ OBO_ISSUE.HEADER_NOT_APPLIED,
779
+ "kept in meta.extra.obo.header, not applied",
780
+ tag,
781
+ clause.line
782
+ );
783
+ }
784
+ }
785
+ /** Finish the frame being read: find its id and merge its clauses into the record of that id. */
786
+ finishFrame() {
787
+ const { frame } = this;
788
+ this.frame = null;
789
+ if (frame === null) {
790
+ return;
791
+ }
792
+ if (frame.kind === null) {
793
+ const clauses = ownRecord();
794
+ for (const clause of frame.clauses) {
795
+ append(clauses, clause.tag, clause.value);
796
+ }
797
+ this.unknownFrames.push({ type: frame.name, clauses });
798
+ return;
799
+ }
800
+ if (++this.framesSinceCheck >= ABORT_CHECK_INTERVAL) {
801
+ this.framesSinceCheck = 0;
802
+ throwIfAborted(this.signal);
803
+ }
804
+ const idAt = frame.clauses.findIndex((c) => c.tag === "id");
805
+ const idParts = idAt < 0 ? null : splitQualifiers(frame.clauses[idAt].value);
806
+ const rawId = idParts === null ? "" : idParts.value.trim();
807
+ const id = unescapeObo(rawId).trim();
808
+ const wouldBeNode = frame.kind !== "Typedef" || this.typedefsAsNodes;
809
+ if (id.length === 0) {
810
+ this.report.error(
811
+ "missing-value",
812
+ OBO_ISSUE.MISSING_ID,
813
+ `a [${frame.kind}] frame has no id; it is skipped`,
814
+ {
815
+ line: frame.line
816
+ }
817
+ );
818
+ if (wouldBeNode) {
819
+ this.report.counts.skippedNodes++;
820
+ }
821
+ return;
822
+ }
823
+ if (tokenize(rawId).length > 1 || hasControl(id)) {
824
+ this.report.error(
825
+ "validation-error",
826
+ OBO_ISSUE.BAD_VALUE,
827
+ `a [${frame.kind}] frame's id ${JSON.stringify(id)} holds unescaped whitespace or a control character; the frame is skipped`,
828
+ { line: frame.clauses[idAt].line, element: id }
829
+ );
830
+ if (wouldBeNode) {
831
+ this.report.counts.skippedNodes++;
832
+ }
833
+ return;
834
+ }
835
+ if (idAt > 0) {
836
+ this.tally(
837
+ "validation-error",
838
+ OBO_ISSUE.ID_NOT_FIRST,
839
+ "the id clause is not the frame's first; it is used",
840
+ "id",
841
+ frame.clauses[idAt].line
842
+ );
843
+ }
844
+ const target = this.recordFor(frame.kind, id, frame.line);
845
+ this.extraQualifiers(target, "id", id, idParts?.qualifiers ?? null);
846
+ for (let i = 0; i < frame.clauses.length; i++) {
847
+ const clause = frame.clauses[i];
848
+ if (clause.tag === "id") {
849
+ if (i !== idAt) {
850
+ this.tally(
851
+ "validation-error",
852
+ OBO_ISSUE.DUPLICATE_ATTRIBUTE,
853
+ "a frame names a second id; the first is kept",
854
+ "id",
855
+ clause.line
856
+ );
857
+ }
858
+ continue;
859
+ }
860
+ this.applyClause(target, clause);
861
+ }
862
+ if (target.kind === "Typedef" && !Object.hasOwn(target.raw, "id")) {
863
+ target.raw.id = [id];
864
+ }
865
+ }
866
+ /**
867
+ * The record a frame merges into: the existing one of its id, or a new one.
868
+ * @param kind - the frame type
869
+ * @param id - the id
870
+ * @param line - the frame's line
871
+ * @returns the record
872
+ */
873
+ recordFor(kind, id, line) {
874
+ const map = kind === "Typedef" ? this.typedefs : this.nodes;
875
+ const existing = map.get(id);
876
+ if (existing !== void 0) {
877
+ if (existing.kind !== kind) {
878
+ this.tally(
879
+ "validation-error",
880
+ OBO_ISSUE.ID_KIND_CLASH,
881
+ `an id names both a ${existing.kind} and an ${kind}; the ${existing.kind} is the node`,
882
+ id,
883
+ line
884
+ );
885
+ } else {
886
+ this.tally(
887
+ "merged",
888
+ OBO_ISSUE.DUPLICATE_NODE,
889
+ `[${kind}] frames with this id are merged (spec 4.1.1)`,
890
+ id,
891
+ line
892
+ );
893
+ }
894
+ return existing;
895
+ }
896
+ const created = {
897
+ id,
898
+ kind,
899
+ line,
900
+ values: /* @__PURE__ */ new Map(),
901
+ lists: /* @__PURE__ */ new Map(),
902
+ edges: [],
903
+ seen: /* @__PURE__ */ new Set(),
904
+ raw: ownRecord()
905
+ };
906
+ map.set(id, created);
907
+ return created;
908
+ }
909
+ /**
910
+ * A clause's tag (a 1.0 tag read as its current one), the synonym scope that tag implies, its
911
+ * qualifier block and its value, with the syntax warnings about stray braces recorded.
912
+ * @param clause - the clause
913
+ * @returns the parts
914
+ */
915
+ clauseParts(clause) {
916
+ let { tag } = clause;
917
+ let scope = null;
918
+ const deprecated = DEPRECATED_TAGS.get(tag);
919
+ if (deprecated !== void 0) {
920
+ this.tally(
921
+ "coercion",
922
+ OBO_ISSUE.DEPRECATED_TAG,
923
+ `the 1.0 tag is read as ${deprecated[0]}`,
924
+ tag,
925
+ clause.line
926
+ );
927
+ [tag, scope] = deprecated;
928
+ }
929
+ let split = splitQualifiers(clause.value);
930
+ const open = ID_VALUE_TAGS.has(tag) ? unclosedBlock(split.value) : -1;
931
+ if (open >= 0) {
932
+ this.tally(
933
+ "parse-error",
934
+ OBO_ISSUE.SYNTAX,
935
+ "a qualifier block without its closing brace was dropped",
936
+ tag,
937
+ clause.line
938
+ );
939
+ split = splitQualifiers(split.value.slice(0, open).trimEnd());
940
+ }
941
+ const { qualifiers } = split;
942
+ if (split.badBlock !== null) {
943
+ this.tally(
944
+ "parse-error",
945
+ OBO_ISSUE.SYNTAX,
946
+ "a closing brace that is not a qualifier block was kept as text",
947
+ tag,
948
+ clause.line
949
+ );
950
+ } else if (hasStrayBrace(split.value)) {
951
+ this.tally(
952
+ "parse-error",
953
+ OBO_ISSUE.SYNTAX,
954
+ "an unescaped brace inside a value was kept as text",
955
+ tag,
956
+ clause.line
957
+ );
958
+ }
959
+ return { tag, scope, qualifiers, value: split.value };
960
+ }
961
+ /**
962
+ * A def, expand_assertion_to or expand_expression_to clause: quoted text and its xref list.
963
+ * @param record - the record
964
+ * @param tag - the tag
965
+ * @param value - the clause value
966
+ * @param tokens - its tokens
967
+ * @param qualifiers - its qualifier block
968
+ * @param line - its line
969
+ */
970
+ definition(record, tag, value, tokens, qualifiers, line) {
971
+ if (tag === "def" && record.values.has("def")) {
972
+ this.single(record, "def", null, line);
973
+ return;
974
+ }
975
+ const text = this.quotedWithXrefs(record, tag, value, tokens, line);
976
+ if (tag === "def") {
977
+ this.single(record, "def", text.text, line, () => {
978
+ record.values.set("def.xrefs", text.xrefs);
979
+ });
980
+ this.extraQualifiers(record, tag, text.text, qualifiers);
981
+ } else {
982
+ this.push(record, tag, withQualifiers({ template: text.text, xrefs: text.xrefs }, qualifiers));
983
+ }
984
+ }
985
+ /**
986
+ * Apply one clause to a record.
987
+ * @param record - the record
988
+ * @param clause - the clause
989
+ */
990
+ applyClause(record, clause) {
991
+ const key = `${clause.tag}\0${clause.value}`;
992
+ if (record.seen.has(key)) {
993
+ return;
994
+ }
995
+ record.seen.add(key);
996
+ if (record.kind === "Typedef") {
997
+ append(record.raw, clause.tag, clause.value);
998
+ }
999
+ const { tag, scope, qualifiers, value } = this.clauseParts(clause);
1000
+ const where = { line: clause.line, element: record.id };
1001
+ if (TYPEDEF_TAGS.has(tag) && record.kind !== "Typedef") {
1002
+ this.unrecognized(record, clause);
1003
+ return;
1004
+ }
1005
+ if (TEXT_TAGS.has(tag)) {
1006
+ this.text(record, tag, value, qualifiers, clause.line);
1007
+ return;
1008
+ }
1009
+ if (BOOL_TAGS.has(tag)) {
1010
+ const text = unescapeObo(value).trim();
1011
+ if (text !== "true" && text !== "false") {
1012
+ this.report.error(
1013
+ "validation-error",
1014
+ OBO_ISSUE.BAD_VALUE,
1015
+ `${tag}: ${JSON.stringify(text)} is not true or false; the clause is skipped`,
1016
+ where
1017
+ );
1018
+ return;
1019
+ }
1020
+ this.single(record, tag, text === "true", clause.line);
1021
+ this.extraQualifiers(record, tag, text, qualifiers);
1022
+ return;
1023
+ }
1024
+ const tokens = tokenize(value);
1025
+ if (ID_LIST_TAGS.has(tag)) {
1026
+ const id = this.oneWord(tokens, tag, where);
1027
+ if (id !== null) {
1028
+ this.push(record, tag, id);
1029
+ this.extraQualifiers(record, tag, id, qualifiers);
1030
+ if (tag === "subset" && !this.subsets.has(id)) {
1031
+ this.tally(
1032
+ "validation-error",
1033
+ OBO_ISSUE.UNDECLARED,
1034
+ "a subset no subsetdef declares",
1035
+ id,
1036
+ clause.line
1037
+ );
1038
+ }
1039
+ }
1040
+ return;
1041
+ }
1042
+ switch (tag) {
1043
+ case "is_a":
1044
+ case "instance_of": {
1045
+ if (tag === "instance_of" && record.kind !== "Instance") {
1046
+ this.unrecognized(record, clause);
1047
+ return;
1048
+ }
1049
+ const target = this.oneWord(tokens, tag, where);
1050
+ if (target !== null) {
1051
+ record.edges.push({ relation: tag, target, qualifiers, line: clause.line });
1052
+ }
1053
+ return;
1054
+ }
1055
+ case "relationship": {
1056
+ if (tokens.length !== 2 || tokens.some((t) => t.kind !== "word")) {
1057
+ this.report.error(
1058
+ "validation-error",
1059
+ OBO_ISSUE.BAD_VALUE,
1060
+ `relationship needs a relation and a target, found ${JSON.stringify(unescapeObo(value))}; the clause is skipped`,
1061
+ where
1062
+ );
1063
+ return;
1064
+ }
1065
+ record.edges.push({ relation: tokens[0].text, target: tokens[1].text, qualifiers, line: clause.line });
1066
+ return;
1067
+ }
1068
+ case "intersection_of": {
1069
+ if (tokens.length < 1 || tokens.length > 2 || tokens.some((t) => t.kind !== "word")) {
1070
+ this.report.error(
1071
+ "validation-error",
1072
+ OBO_ISSUE.BAD_VALUE,
1073
+ `intersection_of needs a class, or a relation and a class, found ${JSON.stringify(unescapeObo(value))}; the clause is skipped`,
1074
+ where
1075
+ );
1076
+ return;
1077
+ }
1078
+ const item = tokens.length === 1 ? { relation: null, target: tokens[0].text } : { relation: tokens[0].text, target: tokens[1].text };
1079
+ this.push(record, tag, withQualifiers(item, qualifiers));
1080
+ return;
1081
+ }
1082
+ case "holds_over_chain":
1083
+ case "equivalent_to_chain": {
1084
+ if (tokens.length !== 2 || tokens.some((t) => t.kind !== "word")) {
1085
+ this.report.error(
1086
+ "validation-error",
1087
+ OBO_ISSUE.BAD_VALUE,
1088
+ `${tag} needs two relations; the clause is skipped`,
1089
+ where
1090
+ );
1091
+ return;
1092
+ }
1093
+ this.push(record, tag, [tokens[0].text, tokens[1].text]);
1094
+ this.extraQualifiers(record, tag, `${tokens[0].text} ${tokens[1].text}`, qualifiers);
1095
+ return;
1096
+ }
1097
+ case "def":
1098
+ case "expand_assertion_to":
1099
+ case "expand_expression_to":
1100
+ this.definition(record, tag, value, tokens, qualifiers, clause.line);
1101
+ return;
1102
+ case "synonym":
1103
+ this.synonym(record, tokens, scope, qualifiers, clause.line);
1104
+ return;
1105
+ case "xref": {
1106
+ const xref = parseXref(value);
1107
+ if (xref === null) {
1108
+ this.report.error(
1109
+ "validation-error",
1110
+ OBO_ISSUE.BAD_VALUE,
1111
+ "xref has no id; the clause is skipped",
1112
+ where
1113
+ );
1114
+ return;
1115
+ }
1116
+ this.push(record, "xref", xref.id);
1117
+ this.describe(record, [xref], tag, clause.line);
1118
+ this.extraQualifiers(record, tag, xref.id, qualifiers ?? xref.qualifiers);
1119
+ return;
1120
+ }
1121
+ case "property_value":
1122
+ this.propertyValue(record, tokens, qualifiers, where);
1123
+ return;
1124
+ default:
1125
+ this.unrecognized(record, clause);
1126
+ }
1127
+ }
1128
+ /**
1129
+ * A single-valued text clause (name, comment, ...), warning about a quote that never closes
1130
+ * (stripComment() then keeps a `!` comment after it as text).
1131
+ * @param record - the record
1132
+ * @param tag - the tag
1133
+ * @param value - the raw value
1134
+ * @param qualifiers - its qualifier block
1135
+ * @param line - its line
1136
+ */
1137
+ text(record, tag, value, qualifiers, line) {
1138
+ if (tokenize(value).some((t) => t.kind === "quoted" && t.unterminated)) {
1139
+ this.tally(
1140
+ "parse-error",
1141
+ OBO_ISSUE.SYNTAX,
1142
+ "an unterminated quote; the text after it, a comment included, is kept",
1143
+ tag,
1144
+ line
1145
+ );
1146
+ }
1147
+ const text = unescapeObo(value).trim();
1148
+ this.single(record, tag, text, line);
1149
+ this.extraQualifiers(record, tag, text, qualifiers);
1150
+ }
1151
+ /**
1152
+ * Read a value that must be one word.
1153
+ * @param tokens - the value's tokens
1154
+ * @param tag - the tag, for the message
1155
+ * @param where - the location
1156
+ * @returns the word, or null after reporting E_BAD_VALUE
1157
+ */
1158
+ oneWord(tokens, tag, where) {
1159
+ if (tokens.length === 1 && tokens[0].kind === "word") {
1160
+ return tokens[0].text;
1161
+ }
1162
+ const found = tokens.map((t) => t.text).join(" ");
1163
+ this.report.error(
1164
+ "validation-error",
1165
+ OBO_ISSUE.BAD_VALUE,
1166
+ `${tag} needs one id, found ${JSON.stringify(found)}; the clause is skipped`,
1167
+ where
1168
+ );
1169
+ return null;
1170
+ }
1171
+ /**
1172
+ * Read `"text" [xrefs]` (def, expand_*), reporting the forms the grammar does not allow.
1173
+ * @param record - the record (xref descriptions go to its `xref.descriptions`)
1174
+ * @param tag - the tag
1175
+ * @param value - the raw value
1176
+ * @param tokens - its tokens
1177
+ * @param line - the line
1178
+ * @returns the text and the xref ids
1179
+ */
1180
+ quotedWithXrefs(record, tag, value, tokens, line) {
1181
+ if (tokens.length === 0 || tokens[0].kind !== "quoted") {
1182
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "the text is not quoted; the whole value is kept", tag, line);
1183
+ return { text: unescapeObo(value).trim(), xrefs: [] };
1184
+ }
1185
+ if (tokens[0].unterminated) {
1186
+ this.tally(
1187
+ "parse-error",
1188
+ OBO_ISSUE.SYNTAX,
1189
+ "an unterminated quote; the text runs to the end of the line",
1190
+ tag,
1191
+ line
1192
+ );
1193
+ return { text: tokens[0].text, xrefs: [] };
1194
+ }
1195
+ const list = tokens[1];
1196
+ if (list?.kind !== "list") {
1197
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "the xref list is missing", tag, line);
1198
+ return { text: tokens[0].text, xrefs: [] };
1199
+ }
1200
+ if (list.unterminated) {
1201
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "an xref list without its closing ]; kept as read", tag, line);
1202
+ }
1203
+ if (tokens.length > 2) {
1204
+ this.tally("parse-error", OBO_ISSUE.SYNTAX, "text after the xref list was ignored", tag, line);
1205
+ }
1206
+ const xrefs = parseXrefList(list.text);
1207
+ this.describe(record, xrefs, tag, line);
1208
+ this.xrefQualifiers(record, tag, xrefs);
1209
+ return { text: tokens[0].text, xrefs: xrefs.map((x) => x.id) };
1210
+ }
1211
+ /**
1212
+ * Read a synonym: `"text" SCOPE? TYPE? [xrefs]` (the scope optional in 1.2).
1213
+ * @param record - the record
1214
+ * @param tokens - the value's tokens
1215
+ * @param fixedScope - the scope of a 1.0 tag (exact_synonym), or null
1216
+ * @param qualifiers - the clause's qualifiers
1217
+ * @param line - the line
1218
+ */
1219
+ synonym(record, tokens, fixedScope, qualifiers, line) {
1220
+ if (tokens.length === 0 || tokens[0].kind !== "quoted") {
1221
+ this.report.error(
1222
+ "validation-error",
1223
+ OBO_ISSUE.BAD_VALUE,
1224
+ "a synonym needs its quoted text; the clause is skipped",
1225
+ {
1226
+ line,
1227
+ element: record.id
1228
+ }
1229
+ );
1230
+ return;
1231
+ }
1232
+ if (tokens[0].unterminated) {
1233
+ this.tally(
1234
+ "parse-error",
1235
+ OBO_ISSUE.SYNTAX,
1236
+ "an unterminated quote; the text runs to the end of the line",
1237
+ "synonym",
1238
+ line
1239
+ );
1240
+ }
1241
+ const words = tokens.slice(1).filter((t) => t.kind === "word").map((t) => t.text);
1242
+ const list = tokens.slice(1).find((t) => t.kind === "list");
1243
+ let scope = fixedScope;
1244
+ let type = null;
1245
+ if (fixedScope === null) {
1246
+ if (words.length > 0 && SYNONYM_SCOPES.has(words[0])) {
1247
+ [scope] = words;
1248
+ type = words[1] ?? null;
1249
+ } else if (words.length > 0 && this.synonymTypes.has(words[0])) {
1250
+ [type] = words;
1251
+ scope = "RELATED";
1252
+ } else if (words.length > 0) {
1253
+ [type] = words;
1254
+ this.tally(
1255
+ "validation-error",
1256
+ OBO_ISSUE.SYNONYM_SCOPE,
1257
+ `a scope that is not EXACT, BROAD, NARROW or RELATED was kept as the type, scope null`,
1258
+ words[0],
1259
+ line
1260
+ );
1261
+ } else {
1262
+ scope = "RELATED";
1263
+ }
1264
+ if (scope === "RELATED" && (words.length === 0 || words[0] === type) && !this.allowsMissingScope()) {
1265
+ this.tally(
1266
+ "validation-error",
1267
+ OBO_ISSUE.SYNONYM_SCOPE,
1268
+ "a synonym without a scope (1.2 only) is read as RELATED",
1269
+ "synonym",
1270
+ line
1271
+ );
1272
+ }
1273
+ }
1274
+ if (type !== null) {
1275
+ const declared = this.synonymTypes.get(type);
1276
+ if (declared === void 0) {
1277
+ if (scope !== null) {
1278
+ this.tally(
1279
+ "validation-error",
1280
+ OBO_ISSUE.UNDECLARED,
1281
+ "a synonym type no synonymtypedef declares",
1282
+ type,
1283
+ line
1284
+ );
1285
+ }
1286
+ } else if (declared !== null) {
1287
+ scope = declared;
1288
+ }
1289
+ }
1290
+ if (words.length > 2 || list === void 0) {
1291
+ this.tally(
1292
+ "parse-error",
1293
+ OBO_ISSUE.SYNTAX,
1294
+ list === void 0 ? "a synonym without its xref list" : "extra words after the synonym type",
1295
+ "synonym",
1296
+ line
1297
+ );
1298
+ }
1299
+ const listAt = list === void 0 ? -1 : tokens.indexOf(list);
1300
+ const stray = tokens.some(
1301
+ (t, k) => k > 0 && (t.kind === "quoted" || t.kind === "list" && k !== listAt || listAt >= 0 && k > listAt)
1302
+ );
1303
+ if (stray) {
1304
+ this.tally(
1305
+ "parse-error",
1306
+ OBO_ISSUE.SYNTAX,
1307
+ "a second quoted string, a second xref list or text after the list was ignored",
1308
+ "synonym",
1309
+ line
1310
+ );
1311
+ }
1312
+ if (list?.unterminated === true) {
1313
+ this.tally(
1314
+ "parse-error",
1315
+ OBO_ISSUE.SYNTAX,
1316
+ "an xref list without its closing ]; kept as read",
1317
+ "synonym",
1318
+ line
1319
+ );
1320
+ }
1321
+ const xrefs = list === void 0 ? [] : parseXrefList(list.text);
1322
+ this.describe(record, xrefs, "synonym", line);
1323
+ this.xrefQualifiers(record, "synonym", xrefs);
1324
+ this.push(
1325
+ record,
1326
+ "synonym",
1327
+ withQualifiers({ text: tokens[0].text, scope, type, xrefs: xrefs.map((x) => x.id) }, qualifiers)
1328
+ );
1329
+ }
1330
+ /**
1331
+ * Whether the file's version lets a synonym omit its scope (1.0 and 1.2).
1332
+ * @returns true for a 1.2 or 1.0 file
1333
+ */
1334
+ allowsMissingScope() {
1335
+ return this.version !== null && /^(1\.[0-2]|GO_1\.[0-2])\b/.test(this.version);
1336
+ }
1337
+ /**
1338
+ * Read a property_value: `relation "literal" datatype` or `relation id`.
1339
+ * @param record - the record
1340
+ * @param tokens - the value's tokens
1341
+ * @param qualifiers - the clause's qualifiers
1342
+ * @param where - the location
1343
+ */
1344
+ propertyValue(record, tokens, qualifiers, where) {
1345
+ if (tokens.length < 2 || tokens.length > 3 || tokens[0].kind !== "word" || tokens[1].kind === "list") {
1346
+ this.report.error(
1347
+ "validation-error",
1348
+ OBO_ISSUE.BAD_VALUE,
1349
+ "property_value needs a relation and a value; the clause is skipped",
1350
+ where
1351
+ );
1352
+ return;
1353
+ }
1354
+ if (tokens.some((t) => t.unterminated)) {
1355
+ this.tally(
1356
+ "parse-error",
1357
+ OBO_ISSUE.SYNTAX,
1358
+ "an unterminated quote; the text runs to the end of the line",
1359
+ "property_value",
1360
+ where.line
1361
+ );
1362
+ }
1363
+ let datatype = tokens.length === 3 ? tokens[2].text : null;
1364
+ if (tokens.length === 3 && tokens[2].kind !== "word") {
1365
+ this.tally(
1366
+ "parse-error",
1367
+ OBO_ISSUE.SYNTAX,
1368
+ "a datatype that is not a word was ignored",
1369
+ "property_value",
1370
+ where.line
1371
+ );
1372
+ datatype = null;
1373
+ }
1374
+ this.push(
1375
+ record,
1376
+ "property_value",
1377
+ withQualifiers({ relation: tokens[0].text, value: tokens[1].text, datatype }, qualifiers)
1378
+ );
1379
+ }
1380
+ /**
1381
+ * Set a single-valued column, keeping the first value of an id.
1382
+ * @param record - the record
1383
+ * @param column - the column
1384
+ * @param value - the value
1385
+ * @param line - the line
1386
+ * @param also - more to set together with the value
1387
+ */
1388
+ single(record, column, value, line, also) {
1389
+ if (record.values.has(column)) {
1390
+ this.tally(
1391
+ "validation-error",
1392
+ OBO_ISSUE.DUPLICATE_ATTRIBUTE,
1393
+ "a single-valued tag given twice for one id; the first is kept",
1394
+ column,
1395
+ line
1396
+ );
1397
+ return;
1398
+ }
1399
+ record.values.set(column, value);
1400
+ also?.();
1401
+ }
1402
+ /**
1403
+ * Append to a list column, without repeats (merged frames take the union).
1404
+ * @param record - the record
1405
+ * @param column - the column
1406
+ * @param value - the item
1407
+ */
1408
+ push(record, column, value) {
1409
+ let list = record.lists.get(column);
1410
+ if (list === void 0) {
1411
+ list = [];
1412
+ record.lists.set(column, list);
1413
+ }
1414
+ if (typeof value === "object" || !list.includes(value)) {
1415
+ list.push(value);
1416
+ }
1417
+ }
1418
+ /**
1419
+ * Record xref descriptions in `xref.descriptions` (the first description of an id is kept),
1420
+ * warning about a description whose quote never closes.
1421
+ * @param target - the record
1422
+ * @param xrefs - the xrefs
1423
+ * @param tag - the clause's tag
1424
+ * @param line - the clause's line
1425
+ */
1426
+ describe(target, xrefs, tag, line) {
1427
+ for (const xref of xrefs) {
1428
+ if (xref.unterminated) {
1429
+ this.tally(
1430
+ "parse-error",
1431
+ OBO_ISSUE.SYNTAX,
1432
+ "an xref description whose quote never closes runs to the end",
1433
+ tag,
1434
+ line
1435
+ );
1436
+ }
1437
+ if (xref.description !== null) {
1438
+ let map = target.values.get("xref.descriptions");
1439
+ if (map === void 0) {
1440
+ map = ownRecord();
1441
+ target.values.set("xref.descriptions", map);
1442
+ }
1443
+ if (!Object.hasOwn(map, xref.id)) {
1444
+ map[xref.id] = xref.description;
1445
+ }
1446
+ }
1447
+ }
1448
+ }
1449
+ /**
1450
+ * Keep the per-xref qualifiers of an xref list (OBO 1.2: `[A:1 {source="x"}]`) in
1451
+ * `obo.qualifiers` under `<tag>.xrefs`.
1452
+ * @param record - the record
1453
+ * @param tag - the clause's tag
1454
+ * @param xrefs - the list's xrefs
1455
+ */
1456
+ xrefQualifiers(record, tag, xrefs) {
1457
+ for (const xref of xrefs) {
1458
+ this.extraQualifiers(record, `${tag}.xrefs`, xref.id, xref.qualifiers);
1459
+ }
1460
+ }
1461
+ /**
1462
+ * Keep the qualifiers of a clause whose column has no place for them in `obo.qualifiers`.
1463
+ * @param record - the record
1464
+ * @param tag - the tag
1465
+ * @param value - the clause's value as stored
1466
+ * @param qualifiers - the qualifiers, or null
1467
+ */
1468
+ extraQualifiers(record, tag, value, qualifiers) {
1469
+ if (qualifiers === null) {
1470
+ return;
1471
+ }
1472
+ const map = record.values.get("obo.qualifiers") ?? {};
1473
+ (map[tag] ??= []).push({ value, qualifiers });
1474
+ record.values.set("obo.qualifiers", map);
1475
+ }
1476
+ /**
1477
+ * Keep an unknown clause in `obo.unrecognized`.
1478
+ * @param record - the record
1479
+ * @param clause - the clause
1480
+ */
1481
+ unrecognized(record, clause) {
1482
+ let map = record.values.get("obo.unrecognized");
1483
+ if (map === void 0) {
1484
+ map = ownRecord();
1485
+ record.values.set("obo.unrecognized", map);
1486
+ }
1487
+ append(map, clause.tag, unescapeObo(clause.value));
1488
+ this.tally(
1489
+ "validation-error",
1490
+ OBO_ISSUE.UNKNOWN_ELEMENT,
1491
+ "an unknown tag was kept in obo.unrecognized",
1492
+ clause.tag,
1493
+ clause.line
1494
+ );
1495
+ }
1496
+ /**
1497
+ * Count a warning, recorded once per code and element at the end.
1498
+ * @param category - the category
1499
+ * @param code - the code
1500
+ * @param what - what happened
1501
+ * @param element - the tag, id or name it is about
1502
+ * @param line - the first line
1503
+ */
1504
+ tally(category, code, what, element, line) {
1505
+ const key = `${code}\0${element}\0${what}`;
1506
+ const found = this.tallies.get(key);
1507
+ if (found === void 0) {
1508
+ this.tallies.set(key, { category, code, element, what, line, count: 1 });
1509
+ } else {
1510
+ found.count++;
1511
+ }
1512
+ }
1513
+ /**
1514
+ * Record the counted warnings, in the order they were first seen; past MAX_TALLIES_PER_CODE
1515
+ * of one code, the rest are summed up in one warning.
1516
+ */
1517
+ flushTallies() {
1518
+ const listed = /* @__PURE__ */ new Map();
1519
+ const over = /* @__PURE__ */ new Map();
1520
+ for (const t of this.tallies.values()) {
1521
+ const n = (listed.get(t.code) ?? 0) + 1;
1522
+ listed.set(t.code, n);
1523
+ if (n > MAX_TALLIES_PER_CODE) {
1524
+ const rest = over.get(t.code) ?? [];
1525
+ rest.push(t);
1526
+ over.set(t.code, rest);
1527
+ continue;
1528
+ }
1529
+ this.report.warning(
1530
+ t.category,
1531
+ t.code,
1532
+ `${t.element}: ${t.what} (${t.count} time${plural(t.count)}, first at line ${t.line})`,
1533
+ {
1534
+ line: t.line,
1535
+ element: t.element
1536
+ }
1537
+ );
1538
+ }
1539
+ for (const [code, rest] of over) {
1540
+ this.report.warning(
1541
+ rest[0].category,
1542
+ code,
1543
+ `${rest.length} more element${plural(rest.length)} with this warning, not listed (first: ${rest[0].element}: ${rest[0].what}, line ${rest[0].line})`,
1544
+ { line: rest[0].line }
1545
+ );
1546
+ }
1547
+ this.tallies.clear();
1548
+ }
1549
+ /**
1550
+ * The first line that makes the input not OBO: null unless no frame header and no plausible
1551
+ * header tag was read while some line was neither (an HTML page, a JSON, GML, DOT or CSV file,
1552
+ * UTF-16 read without its BOM). A file of only comments is OBO, if an empty one.
1553
+ * @returns the line, or null
1554
+ */
1555
+ notObo() {
1556
+ return this.frameSeen || this.tagClauses > 0 ? null : this.firstBadLine;
1557
+ }
1558
+ /**
1559
+ * Warn about the header once the file is read: a missing or unknown format-version (the
1560
+ * synonym-scope rule depends on it), a date that is not a real calendar date.
1561
+ */
1562
+ checkHeader() {
1563
+ if (this.version === null) {
1564
+ const empty = this.frameSeen ? "" : " and the file has no frames: nothing was read";
1565
+ this.tally(
1566
+ "validation-error",
1567
+ OBO_ISSUE.FORMAT_VERSION,
1568
+ `the header has no format-version (required by 1.2 and 1.4)${empty}; read as the 1.0 / 1.2 / 1.4 union`,
1569
+ "format-version",
1570
+ 1
1571
+ );
1572
+ } else if (!KNOWN_VERSION.test(this.version)) {
1573
+ this.tally(
1574
+ "validation-error",
1575
+ OBO_ISSUE.FORMAT_VERSION,
1576
+ `format-version ${JSON.stringify(this.version)} is not 1.0, 1.2 or 1.4; read as 1.4`,
1577
+ "format-version",
1578
+ 1
1579
+ );
1580
+ }
1581
+ const date = this.header.date?.[0];
1582
+ if (date !== void 0 && /^\d{2}:\d{2}:\d{4}\s+\d{2}:\d{2}$/.test(date.trim()) && oboDate$1(date.trim()) === null) {
1583
+ this.tally(
1584
+ "validation-error",
1585
+ OBO_ISSUE.SYNTAX,
1586
+ "the header date is not a real calendar date; created is null",
1587
+ "date",
1588
+ 1
1589
+ );
1590
+ }
1591
+ }
1592
+ /**
1593
+ * The header as GraphMeta fields (design 4.2): name from `ontology`, the version, `created` from
1594
+ * the `dd:MM:yyyy HH:mm` date, `creator` from `saved-by`.
1595
+ * @returns the metadata patch, without `extra`
1596
+ */
1597
+ meta() {
1598
+ const first = (tag) => {
1599
+ const values = this.header[tag];
1600
+ return values === void 0 ? null : unescapeObo(values[0]).trim();
1601
+ };
1602
+ return {
1603
+ name: first("ontology"),
1604
+ sourceFormat: "obo",
1605
+ sourceVersion: this.version,
1606
+ created: oboDate$1(first("date")),
1607
+ creator: first("saved-by")
1608
+ };
1609
+ }
1610
+ /**
1611
+ * The raw header clauses.
1612
+ * @returns the header by tag
1613
+ */
1614
+ headerRecord() {
1615
+ return this.header;
1616
+ }
1617
+ /**
1618
+ * The namespace a frame without one gets.
1619
+ * @returns the default namespace, or null
1620
+ */
1621
+ namespaceDefault() {
1622
+ return this.defaultNamespace;
1623
+ }
1624
+ }
1625
+ function withQualifiers(item, qualifiers) {
1626
+ return qualifiers === null ? item : { ...item, qualifiers };
1627
+ }
1628
+ function oboDate$1(text) {
1629
+ const m = text === null ? null : /^(\d{2}):(\d{2}):(\d{4})\s+(\d{2}):(\d{2})$/.exec(text);
1630
+ if (m === null) {
1631
+ return null;
1632
+ }
1633
+ const [, dd, mm, yyyy, hh, min] = m;
1634
+ const day = new Date(Date.UTC(Number(yyyy), Number(mm) - 1, Number(dd)));
1635
+ if (Number(mm) < 1 || Number(mm) > 12 || day.getUTCDate() !== Number(dd) || Number(hh) > 23 || Number(min) > 59) {
1636
+ return null;
1637
+ }
1638
+ return `${yyyy}-${mm}-${dd}T${hh}:${min}`;
1639
+ }
1640
+ function planGraph(reader, report, addMissingNodes, obo) {
1641
+ const records = [...reader.nodes.values()];
1642
+ for (const [id, typedef] of reader.typedefs) {
1643
+ const node = reader.nodes.get(id);
1644
+ if (node !== void 0) {
1645
+ reader.tally(
1646
+ "validation-error",
1647
+ OBO_ISSUE.ID_KIND_CLASH,
1648
+ `an id names both a ${node.kind} and a Typedef; the ${node.kind} is the node`,
1649
+ id,
1650
+ typedef.line
1651
+ );
1652
+ } else if (obo.typedefs === "nodes") {
1653
+ records.push(typedef);
1654
+ }
1655
+ }
1656
+ for (const record of [...reader.nodes.values(), ...reader.typedefs.values()]) {
1657
+ for (const tag of ["intersection_of", "union_of"]) {
1658
+ if (record.lists.get(tag)?.length === 1) {
1659
+ reader.tally(
1660
+ "validation-error",
1661
+ OBO_ISSUE.CARDINALITY,
1662
+ "a single clause (a class expression needs two or more)",
1663
+ tag,
1664
+ record.line
1665
+ );
1666
+ }
1667
+ }
1668
+ }
1669
+ const dropped = /* @__PURE__ */ new Set();
1670
+ const kept = /* @__PURE__ */ new Set();
1671
+ for (const record of records) {
1672
+ const obsolete = record.values.get("is_obsolete") === true;
1673
+ (obo.obsolete === "drop" && obsolete ? dropped : kept).add(record.id);
1674
+ if (obsolete && record.edges.length > 0) {
1675
+ reader.tally(
1676
+ "validation-error",
1677
+ OBO_ISSUE.OBSOLETION,
1678
+ "an obsolete term has is_a or relationship clauses; they are kept",
1679
+ "is_obsolete",
1680
+ record.edges[0].line
1681
+ );
1682
+ }
1683
+ if (!obsolete && (record.lists.has("replaced_by") || record.lists.has("consider"))) {
1684
+ reader.tally(
1685
+ "validation-error",
1686
+ OBO_ISSUE.OBSOLETION,
1687
+ "replaced_by or consider on a term that is not obsolete; kept",
1688
+ "replaced_by",
1689
+ record.line
1690
+ );
1691
+ }
1692
+ }
1693
+ const declared = new Set(reader.typedefs.keys());
1694
+ for (const typedef of reader.typedefs.values()) {
1695
+ for (const xref of typedef.lists.get("xref") ?? []) {
1696
+ declared.add(xref);
1697
+ }
1698
+ }
1699
+ const dangling = [];
1700
+ const danglingSet = /* @__PURE__ */ new Set();
1701
+ let danglingEdges = 0;
1702
+ let droppedEdges = 0;
1703
+ const edges = [];
1704
+ for (const record of records) {
1705
+ for (const edge of record.edges) {
1706
+ if (dropped.has(record.id) || dropped.has(edge.target)) {
1707
+ droppedEdges++;
1708
+ continue;
1709
+ }
1710
+ if (!BUILTIN_RELATIONS$1.has(edge.relation) && !declared.has(edge.relation)) {
1711
+ reader.tally(
1712
+ "validation-error",
1713
+ OBO_ISSUE.UNDECLARED,
1714
+ "a relation no Typedef declares",
1715
+ edge.relation,
1716
+ edge.line
1717
+ );
1718
+ }
1719
+ if (!kept.has(edge.target)) {
1720
+ if (!danglingSet.has(edge.target)) {
1721
+ danglingSet.add(edge.target);
1722
+ dangling.push(edge.target);
1723
+ }
1724
+ if (!addMissingNodes) {
1725
+ danglingEdges++;
1726
+ report.counts.skippedEdges++;
1727
+ continue;
1728
+ }
1729
+ }
1730
+ edges.push({ source: record.id, edge });
1731
+ }
1732
+ }
1733
+ reader.flushTallies();
1734
+ reportDangling(records, dangling, addMissingNodes, danglingEdges, report, new Set(reader.typedefs.keys()));
1735
+ if (dropped.size > 0) {
1736
+ report.warning(
1737
+ "merged",
1738
+ OBO_ISSUE.OBSOLETE_DROPPED,
1739
+ `${dropped.size} obsolete term${plural(dropped.size)} and ${droppedEdges} edge${plural(droppedEdges)} to or from them were left out (obsolete: "drop")`,
1740
+ { element: [...dropped][0] }
1741
+ );
1742
+ }
1743
+ return {
1744
+ nodes: records.filter((record) => kept.has(record.id)),
1745
+ placeholders: addMissingNodes ? dangling : [],
1746
+ edges
1747
+ };
1748
+ }
1749
+ function reportDangling(records, dangling, addMissingNodes, droppedEdges, report, typedefs) {
1750
+ if (dangling.length === 0) {
1751
+ return;
1752
+ }
1753
+ const altOf = /* @__PURE__ */ new Map();
1754
+ for (const record of records) {
1755
+ for (const alt of record.lists.get("alt_id") ?? []) {
1756
+ altOf.set(alt, record.id);
1757
+ }
1758
+ }
1759
+ const shown = dangling.slice(0, 5).map((id) => {
1760
+ const primary = altOf.get(id);
1761
+ if (typedefs.has(id)) {
1762
+ return `${id} (a Typedef, kept as metadata, not a node)`;
1763
+ }
1764
+ return primary === void 0 ? id : `${id} (an alt_id of ${primary})`;
1765
+ });
1766
+ const more = dangling.length > 5 ? `, ... (${dangling.length - 5} more)` : "";
1767
+ const action = addMissingNodes ? "each became a placeholder node (graphty.placeholder)" : `${droppedEdges} edge${plural(droppedEdges)} to them ${agree(droppedEdges, "was", "were")} dropped (addMissingNodes false)`;
1768
+ report.warning(
1769
+ "validation-error",
1770
+ OBO_ISSUE.DANGLING_REFERENCE,
1771
+ `${dangling.length} target${plural(dangling.length)} no frame declares: ${shown.join(", ")}${more}; ${action}`,
1772
+ { element: dangling[0] }
1773
+ );
1774
+ }
1775
+ class Pusher {
1776
+ /**
1777
+ * Create a pusher.
1778
+ * @param sink - the sink
1779
+ * @param report - the report
1780
+ * @param options - the resolved common options
1781
+ */
1782
+ constructor(sink, report, options) {
1783
+ this.restored = /* @__PURE__ */ new Map();
1784
+ this.since = 0;
1785
+ this.sink = sink;
1786
+ this.report = report;
1787
+ this.coercer = new IdCoercer(options.ids);
1788
+ this.signal = options.signal;
1789
+ }
1790
+ /**
1791
+ * An id text under the `ids` rule, reporting a merge.
1792
+ * @param text - the id as written
1793
+ * @returns the node id
1794
+ */
1795
+ id(text) {
1796
+ const id = this.coercer.text(this.restored.get(text) ?? text);
1797
+ const merge = this.coercer.lastMerge;
1798
+ if (merge !== null) {
1799
+ this.report.warning(
1800
+ "coercion",
1801
+ OBO_ISSUE.ID_MERGED,
1802
+ `id text ${JSON.stringify(merge.text)} merged with ${JSON.stringify(merge.previousText)} as ${merge.id}`,
1803
+ { element: text }
1804
+ );
1805
+ }
1806
+ return id;
1807
+ }
1808
+ /**
1809
+ * Add a node, counting it, or record why it was refused.
1810
+ * @param text - the id as written
1811
+ * @param line - the line, when known
1812
+ * @returns the node index, or -1 when refused
1813
+ */
1814
+ node(text, line) {
1815
+ this.tick();
1816
+ try {
1817
+ const id = this.id(text);
1818
+ const known = this.sink.indexOf(id) !== INVALID_INDEX;
1819
+ const index = this.sink.addNode(id);
1820
+ if (!known) {
1821
+ this.report.counts.nodes++;
1822
+ }
1823
+ return index;
1824
+ } catch (err) {
1825
+ this.report.recordError(err, { line, element: text });
1826
+ this.report.counts.skippedNodes++;
1827
+ return -1;
1828
+ }
1829
+ }
1830
+ /** Check the cancellation signal every ABORT_CHECK_INTERVAL pushes. */
1831
+ tick() {
1832
+ if (++this.since >= ABORT_CHECK_INTERVAL) {
1833
+ this.since = 0;
1834
+ throwIfAborted(this.signal);
1835
+ }
1836
+ }
1837
+ }
1838
+ function writeGraph(reader, sink, report, options, obo) {
1839
+ const plan2 = planGraph(reader, report, options.addMissingNodes, obo);
1840
+ const push = new Pusher(sink, report, options);
1841
+ if (options.restoreMangledIds) {
1842
+ restoreIds(plan2.nodes, push.restored);
1843
+ }
1844
+ const namespace = reader.namespaceDefault();
1845
+ const used = /* @__PURE__ */ new Set(["type"]);
1846
+ for (const record of plan2.nodes) {
1847
+ for (const name of [...record.values.keys(), ...record.lists.keys()]) {
1848
+ used.add(name);
1849
+ }
1850
+ }
1851
+ if (namespace !== null) {
1852
+ used.add("namespace");
1853
+ }
1854
+ const columns = /* @__PURE__ */ new Map();
1855
+ for (const name of plan2.nodes.length > 0 ? Object.keys(OBO_NODE_COLUMNS) : []) {
1856
+ if (used.has(name)) {
1857
+ columns.set(name, declareResolved(sink, "node", oboColumnDecl("node", name), report).handle);
1858
+ }
1859
+ }
1860
+ sink.reserve(plan2.nodes.length + plan2.placeholders.length, plan2.edges.length);
1861
+ for (const record of plan2.nodes) {
1862
+ const index = push.node(record.id, record.line);
1863
+ if (index < 0) {
1864
+ continue;
1865
+ }
1866
+ const cells = [["type", record.kind], ...record.values, ...record.lists];
1867
+ if (namespace !== null && !record.values.has("namespace")) {
1868
+ cells.push(["namespace", namespace]);
1869
+ }
1870
+ for (const [name, value] of cells) {
1871
+ const handle = columns.get(name);
1872
+ if (handle !== void 0) {
1873
+ sink.setNodeValue(handle, index, value);
1874
+ }
1875
+ }
1876
+ }
1877
+ if (plan2.placeholders.length > 0) {
1878
+ const placeholder = declareResolved(sink, "node", oboColumnDecl("node", PLACEHOLDER_COLUMN), report).handle;
1879
+ for (const text of plan2.placeholders) {
1880
+ const index = push.node(text);
1881
+ if (index >= 0) {
1882
+ sink.setNodeValue(placeholder, index, true);
1883
+ }
1884
+ }
1885
+ }
1886
+ writeEdges(plan2, push, options);
1887
+ }
1888
+ function restoreIds(records, restored) {
1889
+ const taken = new Set(records.map((r) => r.id));
1890
+ for (const record of records) {
1891
+ const values = record.lists.get("property_value");
1892
+ const at = values?.findIndex((v) => v.relation === ORIGINAL_ID$1) ?? -1;
1893
+ if (values === void 0 || at < 0) {
1894
+ continue;
1895
+ }
1896
+ const original = originalText(values[at].value);
1897
+ if (taken.has(original)) {
1898
+ continue;
1899
+ }
1900
+ taken.add(original);
1901
+ restored.set(record.id, original);
1902
+ values.splice(at, 1);
1903
+ if (values.length === 0) {
1904
+ record.lists.delete("property_value");
1905
+ }
1906
+ }
1907
+ }
1908
+ function originalText(value) {
1909
+ try {
1910
+ const parsed = JSON.parse(value);
1911
+ return typeof parsed === "string" ? parsed : value;
1912
+ } catch {
1913
+ return value;
1914
+ }
1915
+ }
1916
+ function writeEdges(plan2, push, options) {
1917
+ const { sink, report } = push;
1918
+ const direction = new DirectionResolver(sink, report, options.onMixedDirection);
1919
+ direction.setHeader(true);
1920
+ if (plan2.edges.length === 0) {
1921
+ return;
1922
+ }
1923
+ const relation = declareResolved(sink, "edge", oboColumnDecl("edge", "relation"), report).handle;
1924
+ const qualifiers = plan2.edges.some((e) => e.edge.qualifiers !== null) ? declareResolved(sink, "edge", oboColumnDecl("edge", "qualifiers"), report).handle : null;
1925
+ for (const { source, edge } of plan2.edges) {
1926
+ push.tick();
1927
+ const before = sink.edgeCount;
1928
+ try {
1929
+ const e = direction.addEdge(push.id(source), push.id(edge.target), "directed", void 0, {
1930
+ line: edge.line,
1931
+ element: source
1932
+ });
1933
+ report.counts.edges += sink.edgeCount - before;
1934
+ if (e < 0) {
1935
+ continue;
1936
+ }
1937
+ sink.setEdgeValue(relation, e, edge.relation);
1938
+ if (qualifiers !== null && edge.qualifiers !== null) {
1939
+ sink.setEdgeValue(qualifiers, e, edge.qualifiers);
1940
+ }
1941
+ } catch (err) {
1942
+ report.recordError(err, { line: edge.line, element: source });
1943
+ report.counts.skippedEdges++;
1944
+ }
1945
+ }
1946
+ }
1947
+ async function readLines(lines, reader) {
1948
+ const parts = [];
1949
+ let partLine = 0;
1950
+ const feed = (text, line) => {
1951
+ for (const piece of splitFormFeeds(text)) {
1952
+ reader.line(piece, line);
1953
+ }
1954
+ };
1955
+ for await (const physical of lines) {
1956
+ const { line } = lines;
1957
+ if (parts.length > 0) {
1958
+ const head = physical.trimStart();
1959
+ if (head.length === 0 || head.startsWith("[")) {
1960
+ reader.tally(
1961
+ "parse-error",
1962
+ OBO_ISSUE.SYNTAX,
1963
+ "a line continuation before a frame header or a blank line was ignored",
1964
+ "\\",
1965
+ partLine
1966
+ );
1967
+ feed(parts.join(""), partLine);
1968
+ parts.length = 0;
1969
+ } else {
1970
+ reader.tally(
1971
+ "coercion",
1972
+ OBO_ISSUE.DEPRECATED_SYNTAX,
1973
+ "a backslash line continuation was joined (deprecated in 1.4)",
1974
+ "\\",
1975
+ partLine
1976
+ );
1977
+ }
1978
+ }
1979
+ if (endsWithContinuation(physical) && stripComment(physical) === physical) {
1980
+ parts.push(physical.slice(0, -1));
1981
+ partLine = line;
1982
+ continue;
1983
+ }
1984
+ if (parts.length > 0) {
1985
+ parts.push(physical);
1986
+ feed(parts.join(""), line);
1987
+ parts.length = 0;
1988
+ } else {
1989
+ feed(physical, line);
1990
+ }
1991
+ }
1992
+ if (parts.length > 0) {
1993
+ reader.tally(
1994
+ "coercion",
1995
+ OBO_ISSUE.DEPRECATED_SYNTAX,
1996
+ "a backslash line continuation was joined (deprecated in 1.4)",
1997
+ "\\",
1998
+ partLine
1999
+ );
2000
+ feed(parts.join(""), partLine);
2001
+ }
2002
+ }
2003
+ function sniffObo(head) {
2004
+ let text = new TextDecoder("utf-8").decode(head.subarray(0, SNIFF_BYTES));
2005
+ if (text.charCodeAt(0) === 65279) {
2006
+ text = text.slice(1);
2007
+ }
2008
+ const lines = text.split(/\r\n|\r|\n|\f/);
2009
+ if (lines.length > 1 && head.byteLength >= SNIFF_BYTES) {
2010
+ lines.pop();
2011
+ }
2012
+ let tagLines2 = 0;
2013
+ for (const raw of lines) {
2014
+ const line = raw.trim();
2015
+ if (line.length === 0 || line.startsWith("!")) {
2016
+ continue;
2017
+ }
2018
+ if (/^\[(Term|Typedef|Instance)\]\s*(!.*)?$/.test(line)) {
2019
+ return 0.9;
2020
+ }
2021
+ if (tagLines2 === 0 && /^format-version\s*:/.test(line)) {
2022
+ return 0.9;
2023
+ }
2024
+ if (/^[A-Za-z][A-Za-z0-9_-]*\s*:(\s|$)/.test(line)) {
2025
+ tagLines2++;
2026
+ continue;
2027
+ }
2028
+ return 0;
2029
+ }
2030
+ return tagLines2 > 0 ? 0.4 : 0;
2031
+ }
2032
+ const oboImporter = Object.freeze({
2033
+ format: "obo",
2034
+ options: Object.freeze(["obsolete", "typedefs"]),
2035
+ extensions: Object.freeze([".obo"]),
2036
+ mimeTypes: Object.freeze(["text/obo", "application/obo"]),
2037
+ /**
2038
+ * Confidence that the head is an OBO file.
2039
+ * @param head - the first bytes
2040
+ * @returns the confidence
2041
+ */
2042
+ sniff(head) {
2043
+ return sniffObo(head);
2044
+ },
2045
+ /**
2046
+ * Read an OBO file into the sink.
2047
+ * @param input - the text, bytes or stream
2048
+ * @param sink - the sink
2049
+ * @param options - format-specific and common options
2050
+ * @returns the import report; ImportError on a fatal error or beyond the error limit
2051
+ */
2052
+ async import(input, sink, options) {
2053
+ const resolved = resolveImportOptions(options, DEFAULTS);
2054
+ const obo = resolveOboOptions$1(options);
2055
+ const report = new ImportReportBuilder("obo", resolved.errorLimit);
2056
+ reportSinkOptions(sink, options, report, true);
2057
+ reportUnusedOptions(options, report, USED_OPTIONS);
2058
+ const reader = new OboReader(report, resolved.signal, obo.typedefs === "nodes");
2059
+ await readLines(new LineReader(input, report, resolved), reader);
2060
+ reader.finishFrame();
2061
+ if (!reader.significant) {
2062
+ report.fail(OBO_ISSUE.EMPTY_INPUT, "the input is empty");
2063
+ }
2064
+ const notObo = reader.notObo();
2065
+ if (notObo !== null) {
2066
+ report.fail(
2067
+ OBO_ISSUE.NOT_OBO,
2068
+ `the input is not OBO: it has no frame header and no header tag, and line ${notObo} is not a tag-value line`,
2069
+ { line: notObo }
2070
+ );
2071
+ }
2072
+ throwIfAborted(resolved.signal);
2073
+ reader.checkHeader();
2074
+ writeGraph(reader, sink, report, resolved, obo);
2075
+ const typedefs = ownRecord();
2076
+ for (const [id, record] of reader.typedefs) {
2077
+ typedefs[id] = record.raw;
2078
+ }
2079
+ const extra = { header: reader.headerRecord(), typedefs };
2080
+ if (reader.unknownFrames.length > 0) {
2081
+ extra.unknownFrames = reader.unknownFrames;
2082
+ }
2083
+ sink.setMeta({ ...reader.meta(), extra: { obo: extra } });
2084
+ throwIfAborted(resolved.signal);
2085
+ return report.finish();
2086
+ }
2087
+ });
2088
+ const OBO_LOSS = Object.freeze({
2089
+ /** A node column outside the OBO vocabulary (or one whose values a tag cannot carry exactly) reads back inside the `property_value` column. */
2090
+ COLUMN_AS_PROPERTY_VALUE: COLUMN_AS_PROPERTY_VALUE_CODE,
2091
+ /** An edge column (or the explicit weights) reads back inside the `qualifiers` column. */
2092
+ EDGE_COLUMN_AS_QUALIFIER: "W_OBO_EDGE_COLUMN_AS_QUALIFIER",
2093
+ /** Every edge is written from its source to its target, so an undirected graph reads back as directed. */
2094
+ UNDIRECTED_AS_DIRECTED: "W_OBO_UNDIRECTED_AS_DIRECTED",
2095
+ /** Two identical clauses on one frame (parallel edges with one relation and the same qualifiers, a repeated list item) read back as one. */
2096
+ DUPLICATE_CLAUSE: "W_OBO_DUPLICATE_CLAUSE",
2097
+ /** An edge without a relation is written with the `relation` option (default `is_a`). */
2098
+ RELATION_ASSUMED: RELATION_ASSUMED_CODE,
2099
+ /** A relation that is not an OBO id (empty, or holding a space, `!`, `{` or `}`) is written with `_` in place of those characters. */
2100
+ RELATION_RENAMED: "W_OBO_RELATION_RENAMED",
2101
+ /** The graph name is not an ontology id; the header `ontology` is written with `_` in place of the other characters. */
2102
+ ONTOLOGY_NAME: "W_OBO_ONTOLOGY_NAME",
2103
+ /** `[Typedef]` frames read back as nodes only under the importer's `typedefs: "nodes"`; by default they are metadata. */
2104
+ TYPEDEF_NODES: TYPEDEF_NODES_CODE,
2105
+ /** Edges are written in the frame of their source node, so they read back grouped by source, in node order. */
2106
+ EDGE_ORDER: "W_OBO_EDGE_ORDER",
2107
+ /**
2108
+ * A graph attribute is written as a header `property_value` and reads back in `snapshot.meta.extra.obo.header`,
2109
+ * not as a graph attribute.
2110
+ */
2111
+ GRAPH_COLUMN_AS_METADATA: GRAPH_COLUMN_AS_METADATA_CODE,
2112
+ /** A carriage return or form feed in a text cannot be written; it is written as a line feed. */
2113
+ LINE_END: "W_OBO_LINE_END",
2114
+ /** The nodes read back in a different order. */
2115
+ NODE_ORDER: NODE_ORDER_CODE,
2116
+ /** A mutual pair is written as two clauses without its mark. */
2117
+ MUTUAL_EXPANDED: LOSS.MUTUAL_EXPANDED,
2118
+ /**
2119
+ * The undirected edges of a mixed graph are written the way `onMixedDirection` chose ("directed" or "undirected").
2120
+ */
2121
+ MIXED_DIRECTION: LOSS.MIXED_DIRECTION,
2122
+ /**
2123
+ * The graph has both directed and undirected edges and `onMixedDirection` is "error". An import stops; a save to a
2124
+ * format that holds one direction per file fails with E_DIRECTED. Pass "directed" or "undirected" to read or write
2125
+ * it anyway.
2126
+ */
2127
+ MIXED_DIRECTION_ERROR: LOSS.MIXED_DIRECTION_ERROR,
2128
+ /** OBO has no edge ids. */
2129
+ EDGE_IDS_DROPPED: LOSS.EDGE_IDS_DROPPED,
2130
+ /** OBO has no positions. */
2131
+ POSITIONS_DROPPED: LOSS.POSITIONS,
2132
+ /** OBO has no containment. */
2133
+ HIERARCHY_DROPPED: LOSS.HIERARCHY,
2134
+ /** OBO has no time. */
2135
+ TEMPORAL_DROPPED: LOSS.TEMPORAL,
2136
+ /** OBO has no visual columns. */
2137
+ VIZ_DROPPED: LOSS.VIZ,
2138
+ /** An attribute with a role the format has no place for is written as a plain attribute; the role is lost. */
2139
+ ROLE_DROPPED: LOSS.ROLE,
2140
+ /**
2141
+ * An attribute without a role is written where the format keeps a role (for example, a `name` column as the label), and
2142
+ * reads back with that role.
2143
+ */
2144
+ ROLE_ASSUMED: ROLE_ASSUMED_CODE,
2145
+ /**
2146
+ * An attribute with a role (for example, the label) is written where the format keeps that role, and reads back under the
2147
+ * name the format's importer gives it.
2148
+ */
2149
+ COLUMN_NAME_CHANGED: LOSS.COLUMN_NAME_CHANGED,
2150
+ /**
2151
+ * An OBO attribute stored as text where graph-io uses a dictionary (or the other way round); it reads back with
2152
+ * graph-io's usual type. The values are the same.
2153
+ */
2154
+ DTYPE_UNSUPPORTED: LOSS.DTYPE,
2155
+ /** A column without a value on any written element reads back absent. */
2156
+ EMPTY_COLUMN_DROPPED: LOSS.EMPTY_COLUMN,
2157
+ /** A declared default: OBO has none. */
2158
+ DEFAULT_DROPPED: LOSS.DEFAULT,
2159
+ /** Declared options: OBO has no enumerations. */
2160
+ OPTIONS_DROPPED: LOSS.OPTIONS,
2161
+ /** An extension table OBO cannot carry. */
2162
+ EXTENSION_TABLE_DROPPED: LOSS.EXTENSION_TABLE,
2163
+ /**
2164
+ * Node ids the format cannot write, under `sanitizeIds: "error"`; the save fails with E_INVALID_ID. Pass
2165
+ * `sanitizeIds: "mangle"` to rewrite them.
2166
+ */
2167
+ ID_CHARSET: LOSS.ID_CHARSET,
2168
+ /** Node ids OBO cannot write as they are, under sanitizeIds "mangle": rewritten, the original kept. */
2169
+ ID_MANGLED: LOSS.ID_MANGLED,
2170
+ /** Numeric ids are written as text and read back as strings. */
2171
+ ID_TEXT_TYPE: LOSS.ID_TEXT_TYPE,
2172
+ /**
2173
+ * Two node ids would be written as the same text (the number 5 and the text "5"); the save fails with
2174
+ * E_INVALID_ID.
2175
+ */
2176
+ ID_TEXT_COLLISION: LOSS.ID_TEXT_COLLISION
2177
+ });
2178
+ const OBO_CAPABILITIES = capabilities({
2179
+ mixedDirection: false,
2180
+ multiEdges: true,
2181
+ selfLoops: true,
2182
+ edgeIds: "none",
2183
+ idCharset: "any",
2184
+ dtypes: [],
2185
+ components: false,
2186
+ lists: false,
2187
+ json: false,
2188
+ defaults: false,
2189
+ options: false,
2190
+ hierarchy: false,
2191
+ temporal: "none",
2192
+ graphAttributes: false,
2193
+ positions: false,
2194
+ viz: false
2195
+ });
2196
+ const ORIGINAL_ID = "graphty:originalId";
2197
+ const RELATION_COLUMN = "relation";
2198
+ const QUALIFIERS_COLUMN = "qualifiers";
2199
+ const WEIGHT_QUALIFIER = "weight";
2200
+ const BUILTIN_RELATIONS = /* @__PURE__ */ new Set([
2201
+ "is_a",
2202
+ "instance_of",
2203
+ "disjoint_from",
2204
+ "inverse_of",
2205
+ "union_of",
2206
+ "intersection_of"
2207
+ ]);
2208
+ const EDGES = "\0edges";
2209
+ const UNRECOGNIZED = "obo.unrecognized";
2210
+ const TAG_ORDER = [
2211
+ "is_anonymous",
2212
+ "name",
2213
+ "namespace",
2214
+ "alt_id",
2215
+ "def",
2216
+ "comment",
2217
+ "subset",
2218
+ "synonym",
2219
+ "xref",
2220
+ "builtin",
2221
+ "property_value",
2222
+ "domain",
2223
+ "range",
2224
+ "holds_over_chain",
2225
+ "is_anti_symmetric",
2226
+ "is_cyclic",
2227
+ "is_reflexive",
2228
+ "is_symmetric",
2229
+ "is_asymmetric",
2230
+ "is_transitive",
2231
+ "is_functional",
2232
+ "is_inverse_functional",
2233
+ EDGES,
2234
+ "intersection_of",
2235
+ "union_of",
2236
+ "equivalent_to",
2237
+ "disjoint_from",
2238
+ "inverse_of",
2239
+ "transitive_over",
2240
+ "equivalent_to_chain",
2241
+ "disjoint_over",
2242
+ "created_by",
2243
+ "creation_date",
2244
+ "is_obsolete",
2245
+ "replaced_by",
2246
+ "consider",
2247
+ "expand_assertion_to",
2248
+ "expand_expression_to",
2249
+ "is_metadata_tag",
2250
+ "is_class_level",
2251
+ UNRECOGNIZED
2252
+ ];
2253
+ const HEADER_ORDER = [
2254
+ "format-version",
2255
+ "data-version",
2256
+ "version",
2257
+ "date",
2258
+ "saved-by",
2259
+ "auto-generated-by",
2260
+ "import",
2261
+ "subsetdef",
2262
+ "synonymtypedef",
2263
+ "idspace",
2264
+ "default-relationship-id-prefix",
2265
+ "id-mapping",
2266
+ "default-namespace",
2267
+ "namespace-id-rule",
2268
+ "remark",
2269
+ "ontology"
2270
+ ];
2271
+ const APPLIED_TAGS = /* @__PURE__ */ new Set([
2272
+ "id",
2273
+ "is_a",
2274
+ "instance_of",
2275
+ "relationship",
2276
+ "intersection_of",
2277
+ "holds_over_chain",
2278
+ "equivalent_to_chain",
2279
+ "def",
2280
+ "expand_assertion_to",
2281
+ "expand_expression_to",
2282
+ "synonym",
2283
+ "xref",
2284
+ "property_value",
2285
+ ...TEXT_TAGS,
2286
+ ...BOOL_TAGS,
2287
+ ...ID_LIST_TAGS,
2288
+ ...DEPRECATED_TAGS.keys()
2289
+ ]);
2290
+ const TAG_COLUMNS = new Set(
2291
+ Object.keys(OBO_NODE_COLUMNS).filter((name) => name !== "type" && name !== "name" && name !== "propertyType")
2292
+ );
2293
+ const XSD = Object.freeze({
2294
+ string: "xsd:string",
2295
+ dict: "xsd:string",
2296
+ bool: "xsd:boolean",
2297
+ i32: "xsd:integer",
2298
+ u32: "xsd:integer",
2299
+ u8: "xsd:integer",
2300
+ f32: "xsd:double",
2301
+ f64: "xsd:double"
2302
+ });
2303
+ function isTrimmed(text) {
2304
+ return text.length > 0 && text === text.trim();
2305
+ }
2306
+ function arrayOf(value, item, nonEmpty = true) {
2307
+ return Array.isArray(value) && (!nonEmpty || value.length > 0) && value.every(item);
2308
+ }
2309
+ function isXrefId(value) {
2310
+ return typeof value === "string" && value.length > 0 && isTrimmed(value);
2311
+ }
2312
+ function isWord(value) {
2313
+ return typeof value === "string" && isOboWord(value);
2314
+ }
2315
+ function hasKeys(value, keys) {
2316
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2317
+ return false;
2318
+ }
2319
+ const own = Object.keys(value);
2320
+ const record = value;
2321
+ if (Object.hasOwn(record, "qualifiers")) {
2322
+ if (!isQualifierRecord(record.qualifiers, isQualifierName)) {
2323
+ return false;
2324
+ }
2325
+ return own.length === keys.length + 1 && keys.every((k) => Object.hasOwn(record, k));
2326
+ }
2327
+ return own.length === keys.length && keys.every((k) => Object.hasOwn(record, k));
2328
+ }
2329
+ function wordOf(text) {
2330
+ const out = text.replaceAll(new RegExp(NOT_IN_WORD.source, "gu"), "_");
2331
+ return out.length === 0 ? "_" : out;
2332
+ }
2333
+ function qualifierPairs(record) {
2334
+ const out = [];
2335
+ for (const [name, value] of Object.entries(record)) {
2336
+ for (const item of Array.isArray(value) ? value : [value]) {
2337
+ out.push([name, item]);
2338
+ }
2339
+ }
2340
+ return out;
2341
+ }
2342
+ function resolveOboOptions(options) {
2343
+ const relation = options?.relation ?? "is_a";
2344
+ if (typeof relation !== "string" || !isOboWord(relation)) {
2345
+ throw new GraphFormatError("E_UNSUPPORTED", `option relation: ${JSON.stringify(relation)} is not an OBO id`, {
2346
+ option: "relation",
2347
+ found: relation
2348
+ });
2349
+ }
2350
+ const ontology = options?.ontology ?? null;
2351
+ if (ontology !== null && (typeof ontology !== "string" || !/^[A-Za-z0-9_.\-/]+$/.test(ontology))) {
2352
+ throw new GraphFormatError(
2353
+ "E_UNSUPPORTED",
2354
+ `option ontology: ${JSON.stringify(ontology)} is not an ontology id (letters, digits, _ . - /)`,
2355
+ { option: "ontology", found: ontology }
2356
+ );
2357
+ }
2358
+ return { relation, ontology };
2359
+ }
2360
+ function planIds(snapshot, mode, note) {
2361
+ const n = snapshot.nodeCount;
2362
+ const ids = new Array(n);
2363
+ const bad = [];
2364
+ let numeric = 0;
2365
+ for (let i = 0; i < n; i++) {
2366
+ const id = snapshot.ids.idOf(i);
2367
+ if (typeof id === "number") {
2368
+ numeric++;
2369
+ }
2370
+ ids[i] = String(id);
2371
+ if (!isOboWord(ids[i])) {
2372
+ bad.push(i);
2373
+ }
2374
+ }
2375
+ if (numeric > 0) {
2376
+ note(
2377
+ LOSS.ID_TEXT_TYPE,
2378
+ `${numeric} numeric node id${plural(numeric)} ${agree(numeric, "is", "are")} written as text and read back as strings`,
2379
+ null,
2380
+ numeric
2381
+ );
2382
+ }
2383
+ const originals = /* @__PURE__ */ new Map();
2384
+ let fatal = null;
2385
+ if (bad.length > 0 && mode === "error") {
2386
+ fatal = refuseIds(snapshot, ids, bad, note);
2387
+ } else if (bad.length > 0) {
2388
+ mangleIds(ids, bad, originals, note);
2389
+ }
2390
+ return { ids, originals, fatal: fatal ?? collidingIds(snapshot, ids, note) };
2391
+ }
2392
+ function refuseIds(snapshot, ids, bad, note) {
2393
+ note(
2394
+ LOSS.ID_CHARSET,
2395
+ `${bad.length} node id${plural(bad.length)} ${agree(bad.length, "is", "are")} not OBO ids (empty, or holding whitespace, a control character, "!", "{" or "}"); the save fails unless sanitizeIds is "mangle"`,
2396
+ null,
2397
+ bad.length
2398
+ );
2399
+ return new GraphFormatError(
2400
+ "E_INVALID_ID",
2401
+ `${bad.length} node id${plural(bad.length)} cannot be written as OBO ids (first: ${JSON.stringify(ids[bad[0]])}); pass sanitizeIds: "mangle" to rewrite them`,
2402
+ { reason: "charset", count: bad.length, id: snapshot.ids.idOf(bad[0]), index: bad[0] }
2403
+ );
2404
+ }
2405
+ function mangleIds(ids, bad, originals, note) {
2406
+ const badSet = new Set(bad);
2407
+ const used = new Set(ids.filter((_, i) => !badSet.has(i)));
2408
+ for (const i of bad) {
2409
+ const base = wordOf(ids[i]);
2410
+ let candidate = base;
2411
+ let k = 2;
2412
+ while (used.has(candidate)) {
2413
+ candidate = `${base}_${k}`;
2414
+ k++;
2415
+ }
2416
+ used.add(candidate);
2417
+ originals.set(i, ids[i]);
2418
+ ids[i] = candidate;
2419
+ }
2420
+ note(
2421
+ LOSS.ID_MANGLED,
2422
+ `${bad.length} node id${plural(bad.length)} that ${agree(bad.length, "is", "are")} not OBO ids ${agree(bad.length, "is", "are")} rewritten with "_"; the originals are kept as ${ORIGINAL_ID} property values (JSON text; an import with restoreMangledIds: true reads them back as the ids)`,
2423
+ null,
2424
+ bad.length
2425
+ );
2426
+ }
2427
+ function collidingIds(snapshot, ids, note) {
2428
+ const seen = /* @__PURE__ */ new Map();
2429
+ for (let i = 0; i < ids.length; i++) {
2430
+ const before = seen.get(ids[i]);
2431
+ if (before !== void 0) {
2432
+ note(
2433
+ LOSS.ID_TEXT_COLLISION,
2434
+ `node ids ${JSON.stringify(snapshot.ids.idOf(before))} and ${JSON.stringify(snapshot.ids.idOf(i))} have the same text; the save fails`,
2435
+ null,
2436
+ 1
2437
+ );
2438
+ return new GraphFormatError("E_INVALID_ID", "two node ids have the same text; OBO keeps ids as text", {
2439
+ reason: "collision"
2440
+ });
2441
+ }
2442
+ seen.set(ids[i], i);
2443
+ }
2444
+ return null;
2445
+ }
2446
+ function planEdges(snapshot, folding, common, note) {
2447
+ const { src, dst } = snapshot.edgeList();
2448
+ const written2 = [];
2449
+ for (let e = 0; e < snapshot.edgeCount; e++) {
2450
+ if (!folding.folded(e)) {
2451
+ written2.push(e);
2452
+ }
2453
+ }
2454
+ const edgeStart = new Uint32Array(snapshot.nodeCount + 1);
2455
+ for (const e of written2) {
2456
+ edgeStart[src[e] + 1]++;
2457
+ }
2458
+ for (let i = 0; i < snapshot.nodeCount; i++) {
2459
+ edgeStart[i + 1] += edgeStart[i];
2460
+ }
2461
+ const fill = edgeStart.slice(0, snapshot.nodeCount);
2462
+ const edgeOrder = new Uint32Array(written2.length);
2463
+ for (const e of written2) {
2464
+ edgeOrder[fill[src[e]]++] = e;
2465
+ }
2466
+ let moved = 0;
2467
+ for (let k = 0; k < edgeOrder.length; k++) {
2468
+ if (edgeOrder[k] !== written2[k]) {
2469
+ moved++;
2470
+ }
2471
+ }
2472
+ if (moved > 0) {
2473
+ note(
2474
+ OBO_LOSS.EDGE_ORDER,
2475
+ `${moved} edge${plural(moved)} ${agree(moved, "reads", "read")} back at another position: edges are written on their source's frame, grouped by source in node order`,
2476
+ null,
2477
+ moved
2478
+ );
2479
+ }
2480
+ if (!snapshot.directed) {
2481
+ note(
2482
+ OBO_LOSS.UNDIRECTED_AS_DIRECTED,
2483
+ `the snapshot is undirected; every edge is written from its source to its target and reads back directed (${written2.length} edge${plural(written2.length)})`,
2484
+ null,
2485
+ written2.length
2486
+ );
2487
+ } else if (common.onMixedDirection !== "error") {
2488
+ const undirected = written2.filter((e) => !folding.sourceDirected(e)).length;
2489
+ if (undirected > 0) {
2490
+ note(
2491
+ OBO_LOSS.UNDIRECTED_AS_DIRECTED,
2492
+ `${undirected} undirected edge${plural(undirected)} ${agree(undirected, "is", "are")} written as one directed clause each; OBO has no undirected edge`,
2493
+ null,
2494
+ undirected
2495
+ );
2496
+ }
2497
+ }
2498
+ if (folding.mutualCount > 0) {
2499
+ note(
2500
+ LOSS.MUTUAL_EXPANDED,
2501
+ `${folding.mutualCount} mutual pair${plural(folding.mutualCount)} ${agree(folding.mutualCount, "is", "are")} written as two clauses; the mutual mark is lost`,
2502
+ null,
2503
+ folding.mutualCount
2504
+ );
2505
+ }
2506
+ return { written: written2, edgeStart, edgeOrder, dst };
2507
+ }
2508
+ function planKinds(snapshot, frameless) {
2509
+ const kinds = new Array(snapshot.nodeCount).fill("Term");
2510
+ const type = snapshot.nodes.get("type");
2511
+ if (type === null || type.dtype !== "dict" && type.dtype !== "string") {
2512
+ return { kinds, type: null };
2513
+ }
2514
+ for (let i = 0; i < snapshot.nodeCount; i++) {
2515
+ if (frameless[i] === 1) {
2516
+ if (type.isSet(i)) {
2517
+ return { kinds: kinds.fill("Term"), type: null };
2518
+ }
2519
+ continue;
2520
+ }
2521
+ const value = cellOf(type, i);
2522
+ if (typeof value !== "string" || !FRAME_KINDS$1.has(value)) {
2523
+ return { kinds: kinds.fill("Term"), type: null };
2524
+ }
2525
+ kinds[i] = value;
2526
+ }
2527
+ return { kinds, type };
2528
+ }
2529
+ function tagCarries(name, column, rows, kinds, scopes) {
2530
+ const spec = OBO_NODE_COLUMNS[name];
2531
+ if (!fitsVocabulary(spec.dtype, column) || column.meta.components > 1) {
2532
+ return false;
2533
+ }
2534
+ const typedefOnly = TYPEDEF_TAGS.has(name);
2535
+ const valid = (v) => {
2536
+ if (TEXT_TAGS.has(name)) {
2537
+ return typeof v === "string" && isTrimmed(v);
2538
+ }
2539
+ if (BOOL_TAGS.has(name)) {
2540
+ return typeof v === "boolean";
2541
+ }
2542
+ if (ID_LIST_TAGS.has(name)) {
2543
+ return arrayOf(v, isWord);
2544
+ }
2545
+ switch (name) {
2546
+ case "def":
2547
+ return typeof v === "string";
2548
+ case "def.xrefs":
2549
+ return arrayOf(v, isXrefId, false);
2550
+ case "xref":
2551
+ return arrayOf(v, isXrefId);
2552
+ case "xref.descriptions":
2553
+ return typeof v === "object" && v !== null && !Array.isArray(v) && Object.values(v).every((d) => typeof d === "string");
2554
+ case "synonym":
2555
+ return arrayOf(
2556
+ v,
2557
+ (s) => hasKeys(s, ["text", "scope", "type", "xrefs"]) && typeof s.text === "string" && typeof s.scope === "string" && SYNONYM_SCOPES.has(s.scope) && (s.type === null || isWord(s.type)) && (s.type === null || (scopes.get(s.type) ?? s.scope) === s.scope) && arrayOf(s.xrefs, isXrefId, false)
2558
+ );
2559
+ case "intersection_of":
2560
+ return arrayOf(
2561
+ v,
2562
+ (x) => hasKeys(x, ["relation", "target"]) && (x.relation === null || isWord(x.relation)) && isWord(x.target)
2563
+ );
2564
+ case "property_value":
2565
+ return arrayOf(
2566
+ v,
2567
+ (x) => hasKeys(x, ["relation", "value", "datatype"]) && isWord(x.relation) && x.relation !== ORIGINAL_ID && typeof x.value === "string" && (x.datatype === null || isWord(x.datatype))
2568
+ );
2569
+ case "holds_over_chain":
2570
+ case "equivalent_to_chain":
2571
+ return arrayOf(v, (pair) => arrayOf(pair, isWord) && pair.length === 2);
2572
+ case "expand_assertion_to":
2573
+ case "expand_expression_to":
2574
+ return arrayOf(
2575
+ v,
2576
+ (x) => hasKeys(x, ["template", "xrefs"]) && typeof x.template === "string" && arrayOf(x.xrefs, isXrefId, false)
2577
+ );
2578
+ case "obo.qualifiers":
2579
+ return typeof v === "object" && v !== null && !Array.isArray(v) && Object.values(v).every(
2580
+ (entries) => arrayOf(
2581
+ entries,
2582
+ (entry) => hasKeys(entry, ["value"]) && Object.hasOwn(entry, "qualifiers") && typeof entry.value === "string" && isQualifierRecord(entry.qualifiers, isQualifierName)
2583
+ )
2584
+ );
2585
+ default:
2586
+ return false;
2587
+ }
2588
+ };
2589
+ for (const i of rows) {
2590
+ if (!column.isSet(i)) {
2591
+ continue;
2592
+ }
2593
+ if (typedefOnly && kinds[i] !== "Typedef") {
2594
+ return false;
2595
+ }
2596
+ if (!valid(cellOf(column, i))) {
2597
+ return false;
2598
+ }
2599
+ }
2600
+ return true;
2601
+ }
2602
+ function unrecognizedCarried(column, rows, kinds) {
2603
+ if (column.dtype !== "json") {
2604
+ return false;
2605
+ }
2606
+ for (const i of rows) {
2607
+ const v = cellOf(column, i);
2608
+ if (v === void 0) {
2609
+ continue;
2610
+ }
2611
+ if (typeof v !== "object" || v === null || Array.isArray(v)) {
2612
+ return false;
2613
+ }
2614
+ for (const [tag, values] of Object.entries(v)) {
2615
+ const applied = (APPLIED_TAGS.has(tag) || TYPEDEF_TAGS.has(tag)) && !(TYPEDEF_TAGS.has(tag) && kinds[i] !== "Typedef") && !(tag === "instance_of" && kinds[i] !== "Instance");
2616
+ if (applied || tag.length === 0 || !isTrimmed(tag) || !arrayOf(values, (t) => typeof t === "string" && isTrimmed(t))) {
2617
+ return false;
2618
+ }
2619
+ }
2620
+ }
2621
+ return true;
2622
+ }
2623
+ function synonymTypeScopes(header) {
2624
+ const out = /* @__PURE__ */ new Map();
2625
+ for (const raw of header.synonymtypedef ?? []) {
2626
+ const tokens = tokenize(raw);
2627
+ if (tokens.length > 0 && tokens[0].kind === "word") {
2628
+ const scope = tokens.slice(1).find((t) => t.kind === "word" && SYNONYM_SCOPES.has(t.text));
2629
+ out.set(tokens[0].text, scope?.text ?? null);
2630
+ }
2631
+ }
2632
+ return out;
2633
+ }
2634
+ function keptObo(snapshot) {
2635
+ const raw = snapshot.meta.extra.obo;
2636
+ const record = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
2637
+ const clauses = (value) => {
2638
+ const out = {};
2639
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
2640
+ for (const [tag, values] of Object.entries(value)) {
2641
+ if (Array.isArray(values)) {
2642
+ out[tag] = values.filter((v) => typeof v === "string");
2643
+ }
2644
+ }
2645
+ }
2646
+ return out;
2647
+ };
2648
+ const typedefs = {};
2649
+ if (typeof record.typedefs === "object" && record.typedefs !== null && !Array.isArray(record.typedefs)) {
2650
+ for (const [id, frame] of Object.entries(record.typedefs)) {
2651
+ typedefs[id] = clauses(frame);
2652
+ }
2653
+ }
2654
+ const unknownFrames = Array.isArray(record.unknownFrames) ? record.unknownFrames.flatMap(
2655
+ (f) => typeof f === "object" && f !== null && typeof f.type === "string" ? [{ type: f.type, clauses: clauses(f.clauses) }] : []
2656
+ ) : [];
2657
+ return { header: clauses(record.header), typedefs, unknownFrames };
2658
+ }
2659
+ function plan(snapshot, options) {
2660
+ const common = resolveExportOptions(options);
2661
+ const obo = resolveOboOptions(options);
2662
+ const notes = [];
2663
+ const note = (code, message, column = null, count = null) => {
2664
+ notes.push(Object.freeze({ code, message, column, count }));
2665
+ };
2666
+ let fatal = null;
2667
+ const { ids, originals, fatal: idFatal } = planIds(snapshot, common.sanitizeIds, note);
2668
+ fatal ??= idFatal;
2669
+ const folding = pairFolding(snapshot);
2670
+ const { written: written2, edgeStart, edgeOrder, dst } = planEdges(snapshot, folding, common, note);
2671
+ const frameless = framelessNodes(snapshot, written2, {
2672
+ outgoingAllowed: false,
2673
+ changed: (i) => originals.has(i)
2674
+ });
2675
+ const moved = movedNodes(
2676
+ frameless,
2677
+ Array.from(edgeOrder, (e) => dst[e])
2678
+ );
2679
+ if (moved > 0) {
2680
+ note(
2681
+ NODE_ORDER_CODE,
2682
+ `${moved} node${plural(moved)} ${agree(moved, "reads", "read")} back at another position: placeholder nodes have no frame and read back after the written nodes`,
2683
+ null,
2684
+ moved
2685
+ );
2686
+ }
2687
+ const rows = [];
2688
+ for (let i = 0; i < snapshot.nodeCount; i++) {
2689
+ if (frameless[i] === 0) {
2690
+ rows.push(i);
2691
+ }
2692
+ }
2693
+ const { kinds, type } = planKinds(snapshot, frameless);
2694
+ const typedefNodes = rows.filter((i) => kinds[i] === "Typedef").length;
2695
+ if (typedefNodes > 0) {
2696
+ note(
2697
+ OBO_LOSS.TYPEDEF_NODES,
2698
+ `${typedefNodes} node${plural(typedefNodes)} ${agree(typedefNodes, "is", "are")} written as [Typedef] frames; they read back as nodes only when imported with typedefs: "nodes", and then after the other nodes, with the [Typedef] frames that declare the relations`,
2699
+ "type",
2700
+ typedefNodes
2701
+ );
2702
+ }
2703
+ const kept = keptObo(snapshot);
2704
+ const scopes = synonymTypeScopes(kept.header);
2705
+ const labelSlot = slotColumn(snapshot.nodes, "label", "name");
2706
+ const labelColumn = labelSlot.column;
2707
+ const label = labelColumn !== null && rows.every((i) => !labelColumn.isSet(i) || isTrimmed(String(labelColumn.value(i)))) ? labelColumn : null;
2708
+ const tags = planTags(snapshot, rows, kinds, scopes, label);
2709
+ fatal ??= capabilityNotes(snapshot, common, {
2710
+ caps: OBO_CAPABILITIES,
2711
+ label,
2712
+ labelAssumed: labelSlot.assumed,
2713
+ edgeSlot: "a qualifier",
2714
+ note
2715
+ });
2716
+ oboColumnNotes(snapshot, label, tags, note);
2717
+ const properties = planProperties(snapshot, rows, frameless, label, tags, type, note);
2718
+ const relationSlot = slotColumn(snapshot.edges, "kind", RELATION_COLUMN);
2719
+ const relations = planRelations(snapshot, written2, relationSlot.column, obo.relation, note);
2720
+ if (relationSlot.assumed && relationSlot.column !== null) {
2721
+ note(
2722
+ ROLE_ASSUMED_CODE,
2723
+ `edge column "${RELATION_COLUMN}" has no role, so it is taken as the relation of each edge: it is written as each line's relation, and reads back with the relation role ("kind")`,
2724
+ RELATION_COLUMN,
2725
+ relationSlot.column.length - relationSlot.column.nullCount
2726
+ );
2727
+ }
2728
+ const weights = explicitWeights(snapshot);
2729
+ const { qualifiers, edgeColumns } = planEdgeColumns(snapshot, written2, relationSlot.column, weights, note);
2730
+ emptyColumnNotes(snapshot, rows, frameless, written2, note);
2731
+ const p = {
2732
+ notes,
2733
+ fatal,
2734
+ options: obo,
2735
+ ids,
2736
+ originals,
2737
+ frameless,
2738
+ kinds,
2739
+ label,
2740
+ tags,
2741
+ properties,
2742
+ edgeStart,
2743
+ edgeOrder,
2744
+ dst,
2745
+ relations,
2746
+ qualifiers,
2747
+ edgeColumns,
2748
+ weights,
2749
+ header: [],
2750
+ tail: []
2751
+ };
2752
+ let stats = simulate(p, rows);
2753
+ if (!stats.qualifiersPlaced) {
2754
+ tags.delete("obo.qualifiers");
2755
+ const column = snapshot.nodes.get("obo.qualifiers");
2756
+ if (column !== null) {
2757
+ properties.push({ column, relation: "obo.qualifiers" });
2758
+ notePropertyColumn(note, column, rows, "property_value lines (relation obo.qualifiers)");
2759
+ }
2760
+ stats = simulate(p, rows);
2761
+ }
2762
+ if (stats.duplicates > 0) {
2763
+ note(
2764
+ OBO_LOSS.DUPLICATE_CLAUSE,
2765
+ `${stats.duplicates} clause${plural(stats.duplicates)} ${agree(stats.duplicates, "repeats", "repeat")} another clause of their frame word for word (parallel edges with one relation and the same qualifiers, a repeated list item) and read back as one`,
2766
+ null,
2767
+ stats.duplicates
2768
+ );
2769
+ }
2770
+ const header = planHeader(snapshot, kept.header, obo, rows, tags, note, stats);
2771
+ const tail = planTail({
2772
+ kept,
2773
+ rows,
2774
+ kinds,
2775
+ ids,
2776
+ properties,
2777
+ relations,
2778
+ edges: edgeOrder,
2779
+ xref: tags.get("xref") ?? null
2780
+ });
2781
+ if (stats.lineEnds > 0) {
2782
+ note(
2783
+ OBO_LOSS.LINE_END,
2784
+ `${stats.lineEnds} text${plural(stats.lineEnds)} ${agree(stats.lineEnds, "holds", "hold")} a carriage return or form feed, which OBO cannot carry; written as a line feed`,
2785
+ null,
2786
+ stats.lineEnds
2787
+ );
2788
+ }
2789
+ return { ...p, header, tail };
2790
+ }
2791
+ function planTags(snapshot, rows, kinds, scopes, label) {
2792
+ const tags = /* @__PURE__ */ new Map();
2793
+ for (const name of TAG_COLUMNS) {
2794
+ const column = snapshot.nodes.get(name);
2795
+ if (column === null || column === label || column.meta.role !== null && column.meta.role !== "label") {
2796
+ continue;
2797
+ }
2798
+ if (name === UNRECOGNIZED ? unrecognizedCarried(column, rows, kinds) : tagCarries(name, column, rows, kinds, scopes)) {
2799
+ tags.set(name, column);
2800
+ }
2801
+ }
2802
+ const def = tags.get("def");
2803
+ const defXrefs = tags.get("def.xrefs");
2804
+ if (defXrefs !== void 0 && (def === void 0 || rows.some((i) => def.isSet(i) !== defXrefs.isSet(i)))) {
2805
+ tags.delete("def.xrefs");
2806
+ }
2807
+ if (def !== void 0 && !tags.has("def.xrefs") && snapshot.nodes.get("def.xrefs") !== null) {
2808
+ tags.delete("def");
2809
+ }
2810
+ const descriptions = tags.get("xref.descriptions");
2811
+ if (descriptions !== void 0 && !rows.every((i) => describedXrefsWritten(tags, descriptions, i))) {
2812
+ tags.delete("xref.descriptions");
2813
+ }
2814
+ return tags;
2815
+ }
2816
+ function describedXrefsWritten(tags, descriptions, row) {
2817
+ const map = cellOf(descriptions, row);
2818
+ if (map === void 0) {
2819
+ return true;
2820
+ }
2821
+ const written2 = /* @__PURE__ */ new Set();
2822
+ const add = (values) => {
2823
+ if (Array.isArray(values)) {
2824
+ for (const v of values) {
2825
+ if (typeof v === "string") {
2826
+ written2.add(v);
2827
+ }
2828
+ }
2829
+ }
2830
+ };
2831
+ for (const name of ["def.xrefs", "xref"]) {
2832
+ const column = tags.get(name);
2833
+ if (column !== void 0) {
2834
+ add(cellOf(column, row));
2835
+ }
2836
+ }
2837
+ for (const name of ["synonym", "expand_assertion_to", "expand_expression_to"]) {
2838
+ const column = tags.get(name);
2839
+ const items = column === void 0 ? void 0 : cellOf(column, row);
2840
+ if (Array.isArray(items)) {
2841
+ for (const item of items) {
2842
+ add(item.xrefs);
2843
+ }
2844
+ }
2845
+ }
2846
+ return Object.keys(map).every((id) => written2.has(id));
2847
+ }
2848
+ function oboColumnNotes(snapshot, label, tags, note) {
2849
+ for (const [name, column] of [...tags, ...label === null ? [] : [["name", label]]]) {
2850
+ const want = name === "name" ? "string" : OBO_NODE_COLUMNS[name].dtype;
2851
+ if ((want === "string" || want === "dict") && column.dtype !== want || column.dtype === "list" && column.child.dtype !== "string") {
2852
+ const held = column.dtype === "list" ? `${column.child.dtype} items` : column.dtype;
2853
+ note(
2854
+ LOSS.DTYPE,
2855
+ `node column "${column.meta.name}" holds ${held}; it reads back as ${column.dtype === "list" ? "string items" : want}`,
2856
+ column.meta.name,
2857
+ column.length - column.nullCount
2858
+ );
2859
+ }
2860
+ }
2861
+ for (const column of snapshot.graph) {
2862
+ note(
2863
+ GRAPH_COLUMN_AS_METADATA_CODE,
2864
+ `graph column "${column.meta.name}" is written as a header property_value and reads back in meta.extra.obo.header, not as a column`,
2865
+ column.meta.name,
2866
+ column.length - column.nullCount
2867
+ );
2868
+ }
2869
+ }
2870
+ function planProperties(snapshot, rows, frameless, label, tags, type, note) {
2871
+ const out = [];
2872
+ const slotted = /* @__PURE__ */ new Set([
2873
+ ...tags.values(),
2874
+ ...label === null ? [] : [label],
2875
+ ...type === null ? [] : [type]
2876
+ ]);
2877
+ for (const column of snapshot.nodes) {
2878
+ const { role, name } = column.meta;
2879
+ if (slotted.has(column) || role !== null && UNWRITTEN_ROLES.has(role)) {
2880
+ continue;
2881
+ }
2882
+ if (name === PLACEHOLDER_COLUMN && rows.every((i) => !column.isSet(i)) && frameless.includes(1)) {
2883
+ continue;
2884
+ }
2885
+ const relation = isOboWord(name) && name !== ORIGINAL_ID ? name : wordOf(name.replaceAll(":", "_"));
2886
+ out.push({ column, relation });
2887
+ notePropertyColumn(note, column, rows, `property_value lines (relation ${relation})`);
2888
+ }
2889
+ return out;
2890
+ }
2891
+ function planRelations(snapshot, written2, relation, fallback, note) {
2892
+ const out = new Array(snapshot.edgeCount).fill(fallback);
2893
+ let assumed = 0;
2894
+ let renamed = 0;
2895
+ for (const e of written2) {
2896
+ const value = relation === null ? void 0 : cellOf(relation, e);
2897
+ if (typeof value !== "string") {
2898
+ assumed++;
2899
+ continue;
2900
+ }
2901
+ if (isOboWord(value)) {
2902
+ out[e] = value;
2903
+ } else {
2904
+ out[e] = wordOf(value);
2905
+ renamed++;
2906
+ }
2907
+ }
2908
+ if (assumed > 0) {
2909
+ note(
2910
+ OBO_LOSS.RELATION_ASSUMED,
2911
+ `${assumed} edge${plural(assumed)} ${agree(assumed, "has", "have")} no relation; written as ${fallback} (an ontology reads is_a as subclassing)`,
2912
+ relation?.meta.name ?? null,
2913
+ assumed
2914
+ );
2915
+ }
2916
+ if (renamed > 0) {
2917
+ note(
2918
+ OBO_LOSS.RELATION_RENAMED,
2919
+ `${renamed} relation${plural(renamed)} ${agree(renamed, "is", "are")} not OBO ids; written with "_" in place of the space, "!", "{" or "}"`,
2920
+ relation?.meta.name ?? null,
2921
+ renamed
2922
+ );
2923
+ }
2924
+ return out;
2925
+ }
2926
+ function planEdgeColumns(snapshot, written2, relation, weights, note) {
2927
+ const qualifiers = qualifierRecords(snapshot, written2);
2928
+ const edgeColumns = [];
2929
+ const count = (column) => written2.filter((e) => column.isSet(e)).length;
2930
+ const merged = qualifiers === null ? "" : " (merged with its values)";
2931
+ for (const column of snapshot.edges) {
2932
+ const { role, name } = column.meta;
2933
+ if (column === relation || column === qualifiers || role === "id" || role !== null && UNWRITTEN_ROLES.has(role)) {
2934
+ continue;
2935
+ }
2936
+ const written22 = isQualifierName(name) ? name : wordOf(name.replaceAll(/[=",[\]\\]/g, "_"));
2937
+ edgeColumns.push({ column, name: written22 });
2938
+ const n = count(column);
2939
+ if (n > 0) {
2940
+ note(
2941
+ OBO_LOSS.EDGE_COLUMN_AS_QUALIFIER,
2942
+ `edge column "${name}" is written as the qualifier ${written22} and reads back inside the qualifiers column${merged}`,
2943
+ name,
2944
+ n
2945
+ );
2946
+ }
2947
+ }
2948
+ const explicit = weights.weighted ? written2.filter((e) => weights.isExplicit(e)).length : 0;
2949
+ if (explicit > 0) {
2950
+ note(
2951
+ OBO_LOSS.EDGE_COLUMN_AS_QUALIFIER,
2952
+ `${explicit} explicit edge weight${plural(explicit)} ${agree(explicit, "is", "are")} written as the qualifier ${WEIGHT_QUALIFIER} and read back inside the qualifiers column, not as weights`,
2953
+ snapshot.edges.byRole("weight")?.meta.name ?? null,
2954
+ explicit
2955
+ );
2956
+ }
2957
+ return { qualifiers, edgeColumns };
2958
+ }
2959
+ function qualifierRecords(snapshot, written2) {
2960
+ const qualifiers = snapshot.edges.get(QUALIFIERS_COLUMN);
2961
+ if (qualifiers?.dtype !== "json" || qualifiers.meta.role !== null) {
2962
+ return null;
2963
+ }
2964
+ const records = written2.every(
2965
+ (e) => !qualifiers.isSet(e) || isQualifierRecord(cellOf(qualifiers, e), isQualifierName)
2966
+ );
2967
+ return records ? qualifiers : null;
2968
+ }
2969
+ function planHeader(snapshot, kept, obo, rows, tags, note, stats) {
2970
+ const { values, filled } = headerFields(snapshot, kept, obo.ontology, note);
2971
+ const namespace = tags.get("namespace");
2972
+ if (values.has("default-namespace") && (namespace === void 0 || rows.some((i) => !namespace.isSet(i)))) {
2973
+ values.delete("default-namespace");
2974
+ }
2975
+ const lines = [];
2976
+ const tagsInOrder = [
2977
+ ...HEADER_ORDER.filter((t) => filled.has(t)),
2978
+ ...[...values.keys()].filter((t) => !filled.has(t))
2979
+ ];
2980
+ for (const tag of tagsInOrder) {
2981
+ for (const value of values.get(tag) ?? []) {
2982
+ lines.push(`${escapeTag(tag)}: ${value}`);
2983
+ }
2984
+ }
2985
+ if (lines.length === 0 && rows.length === 0) {
2986
+ lines.push("format-version: 1.4");
2987
+ }
2988
+ for (const column of snapshot.graph) {
2989
+ const relation = isOboWord(column.meta.name) ? column.meta.name : wordOf(column.meta.name);
2990
+ for (const line of propertyLines({ column, relation }, 0, stats)) {
2991
+ lines.push(`property_value: ${line}`);
2992
+ }
2993
+ }
2994
+ return lines;
2995
+ }
2996
+ function headerFields(snapshot, kept, ontologyOption, note) {
2997
+ const { meta } = snapshot;
2998
+ const values = new Map(Object.entries(kept).map(([tag, v]) => [tag, v.map(rawValue)]));
2999
+ const filled = /* @__PURE__ */ new Set();
3000
+ const fill = (tag, value) => {
3001
+ if (value !== null && !values.has(tag)) {
3002
+ values.set(tag, [value]);
3003
+ filled.add(tag);
3004
+ }
3005
+ };
3006
+ if (meta.sourceFormat !== "obo") {
3007
+ fill("format-version", "1.4");
3008
+ fill("date", oboDate(meta.created));
3009
+ fill("saved-by", meta.creator === null ? null : escapeOboValue(meta.creator));
3010
+ }
3011
+ const ontology = ontologyField(ontologyOption, meta.name, values.has("ontology"), note);
3012
+ if (ontology !== null) {
3013
+ if (!values.has("ontology")) {
3014
+ filled.add("ontology");
3015
+ }
3016
+ values.set("ontology", [ontology]);
3017
+ }
3018
+ return { values, filled };
3019
+ }
3020
+ function ontologyField(option, name, kept, note) {
3021
+ if (option !== null) {
3022
+ return option;
3023
+ }
3024
+ if (name === null || name.length === 0 || kept) {
3025
+ return null;
3026
+ }
3027
+ if (/^[A-Za-z0-9_.\-/]+$/.test(name)) {
3028
+ return name;
3029
+ }
3030
+ const slug = name.replaceAll(/[^A-Za-z0-9_.-]/g, "_");
3031
+ note(
3032
+ OBO_LOSS.ONTOLOGY_NAME,
3033
+ `the graph name ${JSON.stringify(name)} is not an ontology id; written as ${slug}`,
3034
+ null,
3035
+ null
3036
+ );
3037
+ return slug;
3038
+ }
3039
+ function rawValue(raw) {
3040
+ return raw.replaceAll(/\r\n|\r|\n|\f/g, String.raw`\n`);
3041
+ }
3042
+ function escapeTag(tag) {
3043
+ return escapeOboValue(tag).replaceAll(":", String.raw`\:`);
3044
+ }
3045
+ function oboDate(iso) {
3046
+ const m = iso === null ? null : /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?/.exec(iso);
3047
+ if (m === null) {
3048
+ return null;
3049
+ }
3050
+ return `${m[3]}:${m[2]}:${m[1]} ${m[4] ?? "00"}:${m[5] ?? "00"}`;
3051
+ }
3052
+ function planTail(input) {
3053
+ const { kept, rows, properties, relations } = input;
3054
+ const lines = [];
3055
+ const declared = nodeTypedefs(input);
3056
+ keptTypedefLines(kept.typedefs, declared, lines);
3057
+ const declare = (relation, metadata) => {
3058
+ if (declared.has(relation)) {
3059
+ return;
3060
+ }
3061
+ declared.add(relation);
3062
+ lines.push("", "[Typedef]", `id: ${escapeOboWord(relation)}`, `name: ${escapeOboValue(relation)}`);
3063
+ if (metadata) {
3064
+ lines.push("is_metadata_tag: true");
3065
+ }
3066
+ };
3067
+ const used = /* @__PURE__ */ new Set();
3068
+ for (const e of input.edges) {
3069
+ used.add(relations[e]);
3070
+ }
3071
+ for (const { column, relation } of properties) {
3072
+ if (rows.some((i) => column.isSet(i))) {
3073
+ declare(relation, true);
3074
+ }
3075
+ }
3076
+ for (const relation of used) {
3077
+ declare(relation, false);
3078
+ }
3079
+ for (const frame of kept.unknownFrames) {
3080
+ lines.push("", `[${frame.type.replaceAll(/[\]\r\n]/g, "_")}]`, ...clauseLines(frame.clauses));
3081
+ }
3082
+ return lines;
3083
+ }
3084
+ function nodeTypedefs(input) {
3085
+ const { rows, kinds, ids, xref } = input;
3086
+ const declared = new Set(BUILTIN_RELATIONS);
3087
+ for (const i of rows) {
3088
+ if (kinds[i] !== "Typedef") {
3089
+ continue;
3090
+ }
3091
+ declared.add(ids[i]);
3092
+ const xrefs = xref === null ? void 0 : cellOf(xref, i);
3093
+ for (const x of Array.isArray(xrefs) ? xrefs : []) {
3094
+ declared.add(String(x));
3095
+ }
3096
+ }
3097
+ return declared;
3098
+ }
3099
+ function keptTypedefLines(typedefs, declared, lines) {
3100
+ for (const [id, clauses] of Object.entries(typedefs)) {
3101
+ if (declared.has(id) && !BUILTIN_RELATIONS.has(id)) {
3102
+ continue;
3103
+ }
3104
+ declared.add(id);
3105
+ lines.push("", "[Typedef]", `id: ${escapeOboWord(id)}`, ...clauseLines(clauses, "id"));
3106
+ for (const xref of clauses.xref ?? []) {
3107
+ declared.add(xref.trim());
3108
+ }
3109
+ }
3110
+ }
3111
+ function clauseLines(clauses, skip = null) {
3112
+ return Object.entries(clauses).filter(([tag]) => tag !== skip).flatMap(([tag, values]) => values.map((value) => `${escapeTag(tag)}: ${rawValue(value)}`));
3113
+ }
3114
+ class QualifierCursor {
3115
+ /**
3116
+ * Create a cursor.
3117
+ * @param cell - the frame's `obo.qualifiers` cell, or undefined
3118
+ */
3119
+ constructor(cell) {
3120
+ this.next = /* @__PURE__ */ new Map();
3121
+ this.entries = cell ?? {};
3122
+ }
3123
+ /**
3124
+ * The qualifier block of a clause: the next entry of its tag when that entry is for this value.
3125
+ * @param tag - the tag (or `<tag>.xrefs` for an xref inside a list)
3126
+ * @param value - the clause's value as the column holds it
3127
+ * @returns the block with a leading space, or ""
3128
+ */
3129
+ block(tag, value) {
3130
+ const list = Object.hasOwn(this.entries, tag) ? this.entries[tag] : void 0;
3131
+ const k = this.next.get(tag) ?? 0;
3132
+ if (list === void 0 || k >= list.length || list[k].value !== value) {
3133
+ return "";
3134
+ }
3135
+ this.next.set(tag, k + 1);
3136
+ return qualifierBlock(qualifierPairs(list[k].qualifiers));
3137
+ }
3138
+ /**
3139
+ * Whether every entry found its clause.
3140
+ * @returns true when all were placed
3141
+ */
3142
+ placed() {
3143
+ return Object.entries(this.entries).every(([tag, list]) => (this.next.get(tag) ?? 0) === list.length);
3144
+ }
3145
+ }
3146
+ function counted(s, text) {
3147
+ if (hasLineEnd(text)) {
3148
+ s.lineEnds++;
3149
+ }
3150
+ return text;
3151
+ }
3152
+ function xrefText(f, id, listTag) {
3153
+ let out = escapeOboValue(counted(f.stats, id));
3154
+ const descriptions = f.p.tags.get("xref.descriptions");
3155
+ const map = descriptions === void 0 ? void 0 : cellOf(descriptions, f.row);
3156
+ if (map !== void 0 && Object.hasOwn(map, id) && !f.described.has(id)) {
3157
+ f.described.add(id);
3158
+ out += ` "${escapeOboQuoted(counted(f.stats, map[id]))}"`;
3159
+ }
3160
+ if (listTag !== null) {
3161
+ out += f.cursor.block(listTag, id);
3162
+ }
3163
+ return out;
3164
+ }
3165
+ function xrefList(f, ids, tag) {
3166
+ const field = `${tag}.xrefs`;
3167
+ return `[${ids.map((id) => xrefText(f, id, field)).join(", ")}]`;
3168
+ }
3169
+ function propertyLines(prop, row, stats) {
3170
+ const { column, relation } = prop;
3171
+ const value = cellOf(column, row);
3172
+ if (value === void 0) {
3173
+ return [];
3174
+ }
3175
+ const head = escapeOboWord(relation);
3176
+ const one = (v, dtype) => {
3177
+ let text;
3178
+ let xsd = XSD[dtype] ?? "xsd:string";
3179
+ if (typeof v === "number" && xsd === "xsd:double") {
3180
+ text = xsdDouble(v, dtype);
3181
+ } else if (typeof v === "string" || typeof v === "boolean" || typeof v === "number") {
3182
+ text = textOf(v, dtype);
3183
+ } else {
3184
+ text = JSON.stringify(v) ?? "null";
3185
+ xsd = "xsd:string";
3186
+ }
3187
+ return `${head} "${escapeOboQuoted(counted(stats, text))}" ${xsd}`;
3188
+ };
3189
+ if (column.dtype === "list") {
3190
+ return value.map((item) => one(item, column.child.dtype));
3191
+ }
3192
+ if (column.dtype === "json" || column.meta.components > 1) {
3193
+ return [one(value, "json")];
3194
+ }
3195
+ return [one(value, column.dtype)];
3196
+ }
3197
+ function xsdDouble(v, dtype) {
3198
+ if (Number.isNaN(v)) {
3199
+ return "NaN";
3200
+ }
3201
+ if (!Number.isFinite(v)) {
3202
+ return v > 0 ? "INF" : "-INF";
3203
+ }
3204
+ return formatNumber(v, dtype === "f32" ? "f32" : "f64");
3205
+ }
3206
+ function edgeQualifiers(f, e) {
3207
+ const { p } = f;
3208
+ const pairs = [];
3209
+ if (p.qualifiers !== null) {
3210
+ const record = cellOf(p.qualifiers, e);
3211
+ if (record !== void 0) {
3212
+ pairs.push(...qualifierPairs(record));
3213
+ }
3214
+ }
3215
+ for (const { column, name } of p.edgeColumns) {
3216
+ pairs.push(...qualifierTexts(column, e).map((text) => [name, text]));
3217
+ }
3218
+ const weight = p.weights.text(e);
3219
+ if (weight !== null) {
3220
+ pairs.push([WEIGHT_QUALIFIER, weight]);
3221
+ }
3222
+ for (const [, value] of pairs) {
3223
+ counted(f.stats, value);
3224
+ }
3225
+ return qualifierBlock(pairs);
3226
+ }
3227
+ function qualifierTexts(column, e) {
3228
+ const value = cellOf(column, e);
3229
+ if (value === void 0) {
3230
+ return [];
3231
+ }
3232
+ if (column.dtype === "list") {
3233
+ return value.map((item) => textOf(item, column.child.dtype));
3234
+ }
3235
+ return [column.dtype === "json" ? JSON.stringify(value) ?? "null" : textOf(value, column.dtype)];
3236
+ }
3237
+ function tagLines(f, tag, column, out) {
3238
+ const value = cellOf(column, f.row);
3239
+ if (value === void 0) {
3240
+ return;
3241
+ }
3242
+ const { cursor, stats } = f;
3243
+ const push = (text, stored, raw = false) => {
3244
+ out.push(`${tag}: ${raw ? text : escapeOboValue(counted(stats, text))}${cursor.block(tag, stored)}`);
3245
+ };
3246
+ if (TEXT_TAGS.has(tag)) {
3247
+ push(value, value);
3248
+ return;
3249
+ }
3250
+ if (BOOL_TAGS.has(tag)) {
3251
+ push(value ? "true" : "false", value ? "true" : "false", true);
3252
+ return;
3253
+ }
3254
+ if (ID_LIST_TAGS.has(tag)) {
3255
+ for (const id of value) {
3256
+ push(escapeOboWord(id), id, true);
3257
+ }
3258
+ return;
3259
+ }
3260
+ if (Object.hasOwn(STRUCTURED_TAGS, tag)) {
3261
+ STRUCTURED_TAGS[tag](f, tag, value, out);
3262
+ }
3263
+ }
3264
+ function blockOf(qualifiers) {
3265
+ return qualifiers === void 0 ? "" : qualifierBlock(qualifierPairs(qualifiers));
3266
+ }
3267
+ const STRUCTURED_TAGS = {
3268
+ def(f, _tag, value, out) {
3269
+ const xrefs = f.p.tags.get("def.xrefs");
3270
+ const ids = xrefs === void 0 ? [] : cellOf(xrefs, f.row) ?? [];
3271
+ const text = value;
3272
+ out.push(
3273
+ `def: "${escapeOboQuoted(counted(f.stats, text))}" ${xrefList(f, ids, "def")}${f.cursor.block("def", text)}`
3274
+ );
3275
+ },
3276
+ xref(f, _tag, value, out) {
3277
+ for (const id of value) {
3278
+ out.push(`xref: ${xrefText(f, id, null)}${f.cursor.block("xref", id)}`);
3279
+ }
3280
+ },
3281
+ synonym(f, _tag, value, out) {
3282
+ for (const s of value) {
3283
+ const type = s.type === null ? "" : ` ${escapeOboWord(s.type)}`;
3284
+ out.push(
3285
+ `synonym: "${escapeOboQuoted(counted(f.stats, s.text))}" ${s.scope}${type} ${xrefList(f, s.xrefs, "synonym")}${blockOf(s.qualifiers)}`
3286
+ );
3287
+ }
3288
+ },
3289
+ intersection_of(_f, _tag, value, out) {
3290
+ for (const x of value) {
3291
+ const relation = x.relation === null ? "" : `${escapeOboWord(x.relation)} `;
3292
+ out.push(`intersection_of: ${relation}${escapeOboWord(x.target)}${blockOf(x.qualifiers)}`);
3293
+ }
3294
+ },
3295
+ property_value(f, _tag, value, out) {
3296
+ for (const x of value) {
3297
+ const datatype = x.datatype === null ? "" : ` ${escapeOboWord(x.datatype)}`;
3298
+ const literal = x.datatype === null && isOboWord(x.value) ? escapeOboWord(x.value) : `"${escapeOboQuoted(counted(f.stats, x.value))}"${datatype}`;
3299
+ out.push(`property_value: ${escapeOboWord(x.relation)} ${literal}${blockOf(x.qualifiers)}`);
3300
+ }
3301
+ },
3302
+ holds_over_chain: chainLines,
3303
+ equivalent_to_chain: chainLines,
3304
+ expand_assertion_to: expansionLines,
3305
+ expand_expression_to: expansionLines,
3306
+ [UNRECOGNIZED](f, _tag, value, out) {
3307
+ for (const [unknown, values] of Object.entries(value)) {
3308
+ for (const v of values) {
3309
+ out.push(`${escapeTag(unknown)}: ${escapeOboValue(counted(f.stats, v))}`);
3310
+ }
3311
+ }
3312
+ }
3313
+ };
3314
+ function chainLines(f, tag, value, out) {
3315
+ for (const [a, b] of value) {
3316
+ const pair = `${a} ${b}`;
3317
+ out.push(`${tag}: ${escapeOboWord(a)} ${escapeOboWord(b)}${f.cursor.block(tag, pair)}`);
3318
+ }
3319
+ }
3320
+ function expansionLines(f, tag, value, out) {
3321
+ for (const x of value) {
3322
+ out.push(
3323
+ `${tag}: "${escapeOboQuoted(counted(f.stats, x.template))}" ${xrefList(f, x.xrefs, tag)}${blockOf(x.qualifiers)}`
3324
+ );
3325
+ }
3326
+ }
3327
+ function frameLines(p, row, stats) {
3328
+ const qualifiersColumn = p.tags.get("obo.qualifiers");
3329
+ const f = {
3330
+ p,
3331
+ row,
3332
+ cursor: new QualifierCursor(qualifiersColumn === void 0 ? void 0 : cellOf(qualifiersColumn, row)),
3333
+ described: /* @__PURE__ */ new Set(),
3334
+ stats
3335
+ };
3336
+ const out = [`[${p.kinds[row]}]`, `id: ${escapeOboWord(p.ids[row])}`];
3337
+ for (const tag of TAG_ORDER) {
3338
+ if (tag === "name") {
3339
+ nameLine(f, out);
3340
+ } else if (tag === "property_value") {
3341
+ propertyValueLines(f, out);
3342
+ } else if (tag === EDGES) {
3343
+ edgeLines(f, out);
3344
+ } else {
3345
+ const column = p.tags.get(tag);
3346
+ if (column !== void 0) {
3347
+ tagLines(f, tag, column, out);
3348
+ }
3349
+ }
3350
+ }
3351
+ stats.qualifiersPlaced &&= f.cursor.placed();
3352
+ return out;
3353
+ }
3354
+ function nameLine(f, out) {
3355
+ const name = f.p.label === null ? void 0 : cellOf(f.p.label, f.row);
3356
+ if (typeof name === "string") {
3357
+ out.push(`name: ${escapeOboValue(counted(f.stats, name))}${f.cursor.block("name", name)}`);
3358
+ }
3359
+ }
3360
+ function propertyValueLines(f, out) {
3361
+ const { p, row } = f;
3362
+ const original = p.originals.get(row);
3363
+ if (original !== void 0) {
3364
+ out.push(`property_value: ${ORIGINAL_ID} "${escapeOboQuoted(JSON.stringify(original))}" xsd:string`);
3365
+ }
3366
+ const column = p.tags.get("property_value");
3367
+ if (column !== void 0) {
3368
+ tagLines(f, "property_value", column, out);
3369
+ }
3370
+ for (const prop of p.properties) {
3371
+ for (const line of propertyLines(prop, row, f.stats)) {
3372
+ out.push(`property_value: ${line}`);
3373
+ }
3374
+ }
3375
+ }
3376
+ function edgeLines(f, out) {
3377
+ const { p, row } = f;
3378
+ for (let k = p.edgeStart[row]; k < p.edgeStart[row + 1]; k++) {
3379
+ const e = p.edgeOrder[k];
3380
+ const target = escapeOboWord(p.ids[p.dst[e]]);
3381
+ const relation = p.relations[e];
3382
+ const block = edgeQualifiers(f, e);
3383
+ if (relation === "is_a" || relation === "instance_of" && p.kinds[row] === "Instance") {
3384
+ out.push(`${relation}: ${target}${block}`);
3385
+ } else {
3386
+ out.push(`relationship: ${escapeOboWord(relation)} ${target}${block}`);
3387
+ }
3388
+ }
3389
+ }
3390
+ function simulate(p, rows) {
3391
+ const stats = { lineEnds: 0, qualifiersPlaced: true, duplicates: 0 };
3392
+ for (const i of rows) {
3393
+ const lines = frameLines(p, i, stats);
3394
+ const seen = /* @__PURE__ */ new Set();
3395
+ for (let k = 2; k < lines.length; k++) {
3396
+ if (seen.has(lines[k])) {
3397
+ stats.duplicates++;
3398
+ }
3399
+ seen.add(lines[k]);
3400
+ }
3401
+ }
3402
+ return stats;
3403
+ }
3404
+ function* write(p) {
3405
+ if (p.fatal !== null) {
3406
+ throw p.fatal;
3407
+ }
3408
+ yield p.header.map((line) => `${line}
3409
+ `).join("");
3410
+ const stats = { lineEnds: 0, qualifiersPlaced: true };
3411
+ for (let i = 0; i < p.ids.length; i++) {
3412
+ if (p.frameless[i] === 0) {
3413
+ yield `
3414
+ ${frameLines(p, i, stats).join("\n")}
3415
+ `;
3416
+ }
3417
+ }
3418
+ if (p.tail.length > 0) {
3419
+ yield `${p.tail.join("\n")}
3420
+ `;
3421
+ }
3422
+ }
3423
+ function* written(snapshot, options) {
3424
+ yield* write(plan(snapshot, options));
3425
+ }
3426
+ const oboExporter = Object.freeze({
3427
+ format: "obo",
3428
+ options: Object.freeze(["ontology", "relation"]),
3429
+ capabilities: OBO_CAPABILITIES,
3430
+ /**
3431
+ * Pre-flight: what export() would lose, without writing anything.
3432
+ * @param snapshot - the snapshot
3433
+ * @param options - format-specific and common options
3434
+ * @returns the notes, empty when the export is exact
3435
+ */
3436
+ check(snapshot, options) {
3437
+ return Object.freeze([...plan(snapshot, options).notes]);
3438
+ },
3439
+ /**
3440
+ * Write the file as UTF-8 chunks.
3441
+ * @param snapshot - the snapshot
3442
+ * @param options - format-specific and common options
3443
+ * @returns the chunks
3444
+ */
3445
+ export(snapshot, options) {
3446
+ return encodeChunks(written(snapshot, options));
3447
+ },
3448
+ /**
3449
+ * Write the file as one string.
3450
+ * @param snapshot - the snapshot
3451
+ * @param options - format-specific and common options
3452
+ * @returns the file
3453
+ */
3454
+ exportToString(snapshot, options) {
3455
+ return joinText(written(snapshot, options));
3456
+ }
3457
+ });
3458
+ export {
3459
+ OBO_CAPABILITIES as O,
3460
+ OBO_ISSUE as a,
3461
+ OBO_LOSS as b,
3462
+ oboImporter as c,
3463
+ oboExporter as o
3464
+ };
3465
+ //# sourceMappingURL=exporter-BMqk5oxW.js.map