@graphty/graph-io 0.3.24 → 0.3.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (581) hide show
  1. package/README.md +49 -414
  2. package/dist/chunks/children-CL3Cy0ez.js.map +1 -1
  3. package/dist/chunks/choose-BbJI55dx.js +56 -0
  4. package/dist/chunks/choose-BbJI55dx.js.map +1 -0
  5. package/dist/chunks/constants-BCsFsMj4.js +231 -0
  6. package/dist/chunks/constants-BCsFsMj4.js.map +1 -0
  7. package/dist/chunks/{escape-B-tkk_Cl.js → escape-D0g6NNUy.js} +382 -92
  8. package/dist/chunks/escape-D0g6NNUy.js.map +1 -0
  9. package/dist/chunks/export-BTI4yQgy.js +4333 -0
  10. package/dist/chunks/export-BTI4yQgy.js.map +1 -0
  11. package/dist/chunks/exporter-BMqk5oxW.js +3465 -0
  12. package/dist/chunks/exporter-BMqk5oxW.js.map +1 -0
  13. package/dist/chunks/{importer-eUREhBGj.js → exporter-Cra2GYcN.js} +4969 -3201
  14. package/dist/chunks/exporter-Cra2GYcN.js.map +1 -0
  15. package/dist/chunks/{exporter-BGrsWamJ.js → exporter-Dxh231ZF.js} +38 -229
  16. package/dist/chunks/exporter-Dxh231ZF.js.map +1 -0
  17. package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-tne475KK.js} +22 -20
  18. package/dist/chunks/exporter-tne475KK.js.map +1 -0
  19. package/dist/chunks/importer-90YWhJoF.js +2811 -0
  20. package/dist/chunks/importer-90YWhJoF.js.map +1 -0
  21. package/dist/chunks/{importer-D_e7e7LX.js → importer-Bcl1d4xH.js} +398 -145
  22. package/dist/chunks/importer-Bcl1d4xH.js.map +1 -0
  23. package/dist/chunks/{importer-BWY2FFc5.js → importer-BfIsp1f9.js} +255 -77
  24. package/dist/chunks/importer-BfIsp1f9.js.map +1 -0
  25. package/dist/chunks/{importer-zGLo8Dg_.js → importer-C_TEA1zJ.js} +484 -157
  26. package/dist/chunks/importer-C_TEA1zJ.js.map +1 -0
  27. package/dist/chunks/{importer-DET1tg6s.js → importer-DCjOvO7U.js} +1687 -310
  28. package/dist/chunks/importer-DCjOvO7U.js.map +1 -0
  29. package/dist/chunks/{importer-vi3bSdtw.js → importer-DfVu6j59.js} +509 -76
  30. package/dist/chunks/importer-DfVu6j59.js.map +1 -0
  31. package/dist/chunks/{importer-DIrbbnAf.js → importer-Dku_VAIY.js} +585 -226
  32. package/dist/chunks/importer-Dku_VAIY.js.map +1 -0
  33. package/dist/chunks/json-elements-3tnyt5uZ.js +1225 -0
  34. package/dist/chunks/json-elements-3tnyt5uZ.js.map +1 -0
  35. package/dist/chunks/ontology-export-B9Tcf37D.js +357 -0
  36. package/dist/chunks/ontology-export-B9Tcf37D.js.map +1 -0
  37. package/dist/chunks/{records-dbRkxwaq.js → records-CQXT__mY.js} +298 -114
  38. package/dist/chunks/records-CQXT__mY.js.map +1 -0
  39. package/dist/chunks/{text-Dr0Ifpag.js → text-DmcUs5_Q.js} +29 -11
  40. package/dist/chunks/text-DmcUs5_Q.js.map +1 -0
  41. package/dist/chunks/{writer-C7flM-Ih.js → writer-DxjgHwSe.js} +19 -1
  42. package/dist/chunks/writer-DxjgHwSe.js.map +1 -0
  43. package/dist/csv.js +951 -261
  44. package/dist/csv.js.map +1 -1
  45. package/dist/cx.js +6 -3
  46. package/dist/cx2.js +2 -2
  47. package/dist/cys.js +6 -3
  48. package/dist/dot.js +1 -1
  49. package/dist/gexf.js +554 -122
  50. package/dist/gexf.js.map +1 -1
  51. package/dist/gml.js +342 -85
  52. package/dist/gml.js.map +1 -1
  53. package/dist/graph-io.js +1105 -213
  54. package/dist/graph-io.js.map +1 -1
  55. package/dist/graphml.js +3 -2
  56. package/dist/graphml.js.map +1 -1
  57. package/dist/json.js +3 -2
  58. package/dist/neo4j.js +564 -157
  59. package/dist/neo4j.js.map +1 -1
  60. package/dist/obo.js +6 -3
  61. package/dist/pajek.js +1 -1
  62. package/dist/src/children.d.ts +18 -6
  63. package/dist/src/children.d.ts.map +1 -1
  64. package/dist/src/children.js +6 -3
  65. package/dist/src/children.js.map +1 -1
  66. package/dist/src/common/attributes.d.ts +29 -11
  67. package/dist/src/common/attributes.d.ts.map +1 -1
  68. package/dist/src/common/attributes.js +25 -7
  69. package/dist/src/common/attributes.js.map +1 -1
  70. package/dist/src/common/cell-budget.d.ts +9 -2
  71. package/dist/src/common/cell-budget.d.ts.map +1 -1
  72. package/dist/src/common/cell-budget.js +11 -3
  73. package/dist/src/common/cell-budget.js.map +1 -1
  74. package/dist/src/common/choose.d.ts +19 -0
  75. package/dist/src/common/choose.d.ts.map +1 -0
  76. package/dist/src/common/choose.js +72 -0
  77. package/dist/src/common/choose.js.map +1 -0
  78. package/dist/src/common/codes.d.ts +456 -66
  79. package/dist/src/common/codes.d.ts.map +1 -1
  80. package/dist/src/common/codes.js +458 -66
  81. package/dist/src/common/codes.js.map +1 -1
  82. package/dist/src/common/compare.d.ts +81 -0
  83. package/dist/src/common/compare.d.ts.map +1 -0
  84. package/dist/src/common/compare.js +394 -0
  85. package/dist/src/common/compare.js.map +1 -0
  86. package/dist/src/common/cx-export.d.ts +123 -0
  87. package/dist/src/common/cx-export.d.ts.map +1 -0
  88. package/dist/src/common/cx-export.js +334 -0
  89. package/dist/src/common/cx-export.js.map +1 -0
  90. package/dist/src/common/declared-types.d.ts +22 -2
  91. package/dist/src/common/declared-types.d.ts.map +1 -1
  92. package/dist/src/common/declared-types.js +19 -1
  93. package/dist/src/common/declared-types.js.map +1 -1
  94. package/dist/src/common/direction.d.ts +74 -50
  95. package/dist/src/common/direction.d.ts.map +1 -1
  96. package/dist/src/common/direction.js +47 -27
  97. package/dist/src/common/direction.js.map +1 -1
  98. package/dist/src/common/escape.d.ts +21 -9
  99. package/dist/src/common/escape.d.ts.map +1 -1
  100. package/dist/src/common/escape.js +33 -17
  101. package/dist/src/common/escape.js.map +1 -1
  102. package/dist/src/common/export.d.ts +144 -32
  103. package/dist/src/common/export.d.ts.map +1 -1
  104. package/dist/src/common/export.js +216 -42
  105. package/dist/src/common/export.js.map +1 -1
  106. package/dist/src/common/format.d.ts +23 -1
  107. package/dist/src/common/format.d.ts.map +1 -1
  108. package/dist/src/common/format.js +26 -1
  109. package/dist/src/common/format.js.map +1 -1
  110. package/dist/src/common/ids.d.ts +9 -2
  111. package/dist/src/common/ids.d.ts.map +1 -1
  112. package/dist/src/common/ids.js +6 -2
  113. package/dist/src/common/ids.js.map +1 -1
  114. package/dist/src/common/input.d.ts +122 -28
  115. package/dist/src/common/input.d.ts.map +1 -1
  116. package/dist/src/common/input.js +890 -124
  117. package/dist/src/common/input.js.map +1 -1
  118. package/dist/src/common/json-elements.d.ts +144 -20
  119. package/dist/src/common/json-elements.d.ts.map +1 -1
  120. package/dist/src/common/json-elements.js +868 -188
  121. package/dist/src/common/json-elements.js.map +1 -1
  122. package/dist/src/common/line-format.d.ts +90 -0
  123. package/dist/src/common/line-format.d.ts.map +1 -0
  124. package/dist/src/common/line-format.js +185 -0
  125. package/dist/src/common/line-format.js.map +1 -0
  126. package/dist/src/common/lists.d.ts +6 -1
  127. package/dist/src/common/lists.d.ts.map +1 -1
  128. package/dist/src/common/lists.js +2 -0
  129. package/dist/src/common/lists.js.map +1 -1
  130. package/dist/src/common/ontology-export.d.ts +167 -0
  131. package/dist/src/common/ontology-export.d.ts.map +1 -0
  132. package/dist/src/common/ontology-export.js +359 -0
  133. package/dist/src/common/ontology-export.js.map +1 -0
  134. package/dist/src/common/ontology.d.ts +25 -3
  135. package/dist/src/common/ontology.d.ts.map +1 -1
  136. package/dist/src/common/ontology.js +30 -9
  137. package/dist/src/common/ontology.js.map +1 -1
  138. package/dist/src/common/options.d.ts +62 -12
  139. package/dist/src/common/options.d.ts.map +1 -1
  140. package/dist/src/common/options.js +89 -11
  141. package/dist/src/common/options.js.map +1 -1
  142. package/dist/src/common/plural.d.ts +31 -0
  143. package/dist/src/common/plural.d.ts.map +1 -0
  144. package/dist/src/common/plural.js +46 -0
  145. package/dist/src/common/plural.js.map +1 -0
  146. package/dist/src/common/report.d.ts +72 -14
  147. package/dist/src/common/report.d.ts.map +1 -1
  148. package/dist/src/common/report.js +146 -22
  149. package/dist/src/common/report.js.map +1 -1
  150. package/dist/src/common/temporal.d.ts +22 -5
  151. package/dist/src/common/temporal.d.ts.map +1 -1
  152. package/dist/src/common/temporal.js +13 -2
  153. package/dist/src/common/temporal.js.map +1 -1
  154. package/dist/src/common/text.d.ts +39 -23
  155. package/dist/src/common/text.d.ts.map +1 -1
  156. package/dist/src/common/text.js +51 -21
  157. package/dist/src/common/text.js.map +1 -1
  158. package/dist/src/common/weights.d.ts +37 -4
  159. package/dist/src/common/weights.d.ts.map +1 -1
  160. package/dist/src/common/weights.js +61 -2
  161. package/dist/src/common/weights.js.map +1 -1
  162. package/dist/src/common/writer.d.ts +19 -3
  163. package/dist/src/common/writer.d.ts.map +1 -1
  164. package/dist/src/common/writer.js +30 -2
  165. package/dist/src/common/writer.js.map +1 -1
  166. package/dist/src/common/xml.d.ts +125 -9
  167. package/dist/src/common/xml.d.ts.map +1 -1
  168. package/dist/src/common/xml.js +400 -83
  169. package/dist/src/common/xml.js.map +1 -1
  170. package/dist/src/common/zip.d.ts +44 -4
  171. package/dist/src/common/zip.d.ts.map +1 -1
  172. package/dist/src/common/zip.js +134 -5
  173. package/dist/src/common/zip.js.map +1 -1
  174. package/dist/src/formats/csv/exporter.d.ts +80 -24
  175. package/dist/src/formats/csv/exporter.d.ts.map +1 -1
  176. package/dist/src/formats/csv/exporter.js +145 -32
  177. package/dist/src/formats/csv/exporter.js.map +1 -1
  178. package/dist/src/formats/csv/header.d.ts +38 -9
  179. package/dist/src/formats/csv/header.d.ts.map +1 -1
  180. package/dist/src/formats/csv/header.js +46 -10
  181. package/dist/src/formats/csv/header.js.map +1 -1
  182. package/dist/src/formats/csv/importer.d.ts +187 -37
  183. package/dist/src/formats/csv/importer.d.ts.map +1 -1
  184. package/dist/src/formats/csv/importer.js +835 -204
  185. package/dist/src/formats/csv/importer.js.map +1 -1
  186. package/dist/src/formats/csv/index.d.ts +100 -19
  187. package/dist/src/formats/csv/index.d.ts.map +1 -1
  188. package/dist/src/formats/csv/index.js +94 -21
  189. package/dist/src/formats/csv/index.js.map +1 -1
  190. package/dist/src/formats/csv/records.d.ts +95 -10
  191. package/dist/src/formats/csv/records.d.ts.map +1 -1
  192. package/dist/src/formats/csv/records.js +333 -122
  193. package/dist/src/formats/csv/records.js.map +1 -1
  194. package/dist/src/formats/csv/values.d.ts +1 -0
  195. package/dist/src/formats/csv/values.d.ts.map +1 -1
  196. package/dist/src/formats/csv/values.js +1 -0
  197. package/dist/src/formats/csv/values.js.map +1 -1
  198. package/dist/src/formats/cx/exporter.d.ts +102 -0
  199. package/dist/src/formats/cx/exporter.d.ts.map +1 -0
  200. package/dist/src/formats/cx/exporter.js +1217 -0
  201. package/dist/src/formats/cx/exporter.js.map +1 -0
  202. package/dist/src/formats/cx/importer.d.ts +79 -32
  203. package/dist/src/formats/cx/importer.d.ts.map +1 -1
  204. package/dist/src/formats/cx/importer.js +680 -285
  205. package/dist/src/formats/cx/importer.js.map +1 -1
  206. package/dist/src/formats/cx/index.d.ts +4 -3
  207. package/dist/src/formats/cx/index.d.ts.map +1 -1
  208. package/dist/src/formats/cx/index.js +4 -3
  209. package/dist/src/formats/cx/index.js.map +1 -1
  210. package/dist/src/formats/cx2/exporter.d.ts +29 -11
  211. package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
  212. package/dist/src/formats/cx2/exporter.js +39 -268
  213. package/dist/src/formats/cx2/exporter.js.map +1 -1
  214. package/dist/src/formats/cx2/importer.d.ts +78 -28
  215. package/dist/src/formats/cx2/importer.d.ts.map +1 -1
  216. package/dist/src/formats/cx2/importer.js +226 -79
  217. package/dist/src/formats/cx2/importer.js.map +1 -1
  218. package/dist/src/formats/cx2/index.d.ts +2 -1
  219. package/dist/src/formats/cx2/index.d.ts.map +1 -1
  220. package/dist/src/formats/cx2/index.js +2 -1
  221. package/dist/src/formats/cx2/index.js.map +1 -1
  222. package/dist/src/formats/cys/constants.d.ts +66 -17
  223. package/dist/src/formats/cys/constants.d.ts.map +1 -1
  224. package/dist/src/formats/cys/constants.js +59 -18
  225. package/dist/src/formats/cys/constants.js.map +1 -1
  226. package/dist/src/formats/cys/exporter.d.ts +118 -0
  227. package/dist/src/formats/cys/exporter.d.ts.map +1 -0
  228. package/dist/src/formats/cys/exporter.js +1072 -0
  229. package/dist/src/formats/cys/exporter.js.map +1 -0
  230. package/dist/src/formats/cys/importer.d.ts +22 -8
  231. package/dist/src/formats/cys/importer.d.ts.map +1 -1
  232. package/dist/src/formats/cys/importer.js +510 -149
  233. package/dist/src/formats/cys/importer.js.map +1 -1
  234. package/dist/src/formats/cys/index.d.ts +4 -2
  235. package/dist/src/formats/cys/index.d.ts.map +1 -1
  236. package/dist/src/formats/cys/index.js +4 -2
  237. package/dist/src/formats/cys/index.js.map +1 -1
  238. package/dist/src/formats/cys/session.d.ts +46 -7
  239. package/dist/src/formats/cys/session.d.ts.map +1 -1
  240. package/dist/src/formats/cys/session.js +98 -24
  241. package/dist/src/formats/cys/session.js.map +1 -1
  242. package/dist/src/formats/cys/tables.d.ts +9 -2
  243. package/dist/src/formats/cys/tables.d.ts.map +1 -1
  244. package/dist/src/formats/cys/tables.js +29 -7
  245. package/dist/src/formats/cys/tables.js.map +1 -1
  246. package/dist/src/formats/dot/exporter.d.ts +42 -14
  247. package/dist/src/formats/dot/exporter.d.ts.map +1 -1
  248. package/dist/src/formats/dot/exporter.js +56 -29
  249. package/dist/src/formats/dot/exporter.js.map +1 -1
  250. package/dist/src/formats/dot/importer.d.ts +91 -29
  251. package/dist/src/formats/dot/importer.d.ts.map +1 -1
  252. package/dist/src/formats/dot/importer.js +190 -39
  253. package/dist/src/formats/dot/importer.js.map +1 -1
  254. package/dist/src/formats/dot/index.d.ts +2 -1
  255. package/dist/src/formats/dot/index.d.ts.map +1 -1
  256. package/dist/src/formats/dot/index.js +2 -1
  257. package/dist/src/formats/dot/index.js.map +1 -1
  258. package/dist/src/formats/dot/names.d.ts +44 -11
  259. package/dist/src/formats/dot/names.d.ts.map +1 -1
  260. package/dist/src/formats/dot/names.js +44 -11
  261. package/dist/src/formats/dot/names.js.map +1 -1
  262. package/dist/src/formats/dot/tokenizer.d.ts +9 -2
  263. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
  264. package/dist/src/formats/dot/tokenizer.js +20 -5
  265. package/dist/src/formats/dot/tokenizer.js.map +1 -1
  266. package/dist/src/formats/gexf/exporter.d.ts +46 -15
  267. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  268. package/dist/src/formats/gexf/exporter.js +45 -15
  269. package/dist/src/formats/gexf/exporter.js.map +1 -1
  270. package/dist/src/formats/gexf/importer.d.ts +87 -23
  271. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  272. package/dist/src/formats/gexf/importer.js +471 -90
  273. package/dist/src/formats/gexf/importer.js.map +1 -1
  274. package/dist/src/formats/gexf/index.d.ts +84 -21
  275. package/dist/src/formats/gexf/index.d.ts.map +1 -1
  276. package/dist/src/formats/gexf/index.js +80 -23
  277. package/dist/src/formats/gexf/index.js.map +1 -1
  278. package/dist/src/formats/gexf/schema.d.ts +67 -15
  279. package/dist/src/formats/gexf/schema.d.ts.map +1 -1
  280. package/dist/src/formats/gexf/schema.js +59 -13
  281. package/dist/src/formats/gexf/schema.js.map +1 -1
  282. package/dist/src/formats/gml/exporter.d.ts +69 -21
  283. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  284. package/dist/src/formats/gml/exporter.js +74 -29
  285. package/dist/src/formats/gml/exporter.js.map +1 -1
  286. package/dist/src/formats/gml/importer.d.ts +116 -25
  287. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  288. package/dist/src/formats/gml/importer.js +290 -61
  289. package/dist/src/formats/gml/importer.js.map +1 -1
  290. package/dist/src/formats/gml/index.d.ts +87 -24
  291. package/dist/src/formats/gml/index.d.ts.map +1 -1
  292. package/dist/src/formats/gml/index.js +79 -26
  293. package/dist/src/formats/gml/index.js.map +1 -1
  294. package/dist/src/formats/gml/syntax.d.ts +67 -18
  295. package/dist/src/formats/gml/syntax.d.ts.map +1 -1
  296. package/dist/src/formats/gml/syntax.js +108 -26
  297. package/dist/src/formats/gml/syntax.js.map +1 -1
  298. package/dist/src/formats/graphml/constants.d.ts +174 -44
  299. package/dist/src/formats/graphml/constants.d.ts.map +1 -1
  300. package/dist/src/formats/graphml/constants.js +165 -44
  301. package/dist/src/formats/graphml/constants.js.map +1 -1
  302. package/dist/src/formats/graphml/exporter.d.ts +17 -9
  303. package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
  304. package/dist/src/formats/graphml/exporter.js +33 -12
  305. package/dist/src/formats/graphml/exporter.js.map +1 -1
  306. package/dist/src/formats/graphml/importer.d.ts +19 -7
  307. package/dist/src/formats/graphml/importer.d.ts.map +1 -1
  308. package/dist/src/formats/graphml/importer.js +466 -39
  309. package/dist/src/formats/graphml/importer.js.map +1 -1
  310. package/dist/src/formats/graphml/index.d.ts +2 -1
  311. package/dist/src/formats/graphml/index.d.ts.map +1 -1
  312. package/dist/src/formats/graphml/index.js +2 -1
  313. package/dist/src/formats/graphml/index.js.map +1 -1
  314. package/dist/src/formats/graphml/tree.d.ts +6 -2
  315. package/dist/src/formats/graphml/tree.d.ts.map +1 -1
  316. package/dist/src/formats/graphml/tree.js +6 -2
  317. package/dist/src/formats/graphml/tree.js.map +1 -1
  318. package/dist/src/formats/graphml/yfiles.d.ts +8 -3
  319. package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
  320. package/dist/src/formats/graphml/yfiles.js +36 -15
  321. package/dist/src/formats/graphml/yfiles.js.map +1 -1
  322. package/dist/src/formats/json/dialect.d.ts +88 -24
  323. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  324. package/dist/src/formats/json/dialect.js +109 -19
  325. package/dist/src/formats/json/dialect.js.map +1 -1
  326. package/dist/src/formats/json/exporter.d.ts +134 -22
  327. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  328. package/dist/src/formats/json/exporter.js +135 -54
  329. package/dist/src/formats/json/exporter.js.map +1 -1
  330. package/dist/src/formats/json/importer.d.ts +173 -52
  331. package/dist/src/formats/json/importer.d.ts.map +1 -1
  332. package/dist/src/formats/json/importer.js +899 -220
  333. package/dist/src/formats/json/importer.js.map +1 -1
  334. package/dist/src/formats/json/index.d.ts +3 -2
  335. package/dist/src/formats/json/index.d.ts.map +1 -1
  336. package/dist/src/formats/json/index.js +3 -2
  337. package/dist/src/formats/json/index.js.map +1 -1
  338. package/dist/src/formats/json/obographs-export.d.ts +68 -0
  339. package/dist/src/formats/json/obographs-export.d.ts.map +1 -0
  340. package/dist/src/formats/json/obographs-export.js +941 -0
  341. package/dist/src/formats/json/obographs-export.js.map +1 -0
  342. package/dist/src/formats/json/obographs.d.ts +26 -0
  343. package/dist/src/formats/json/obographs.d.ts.map +1 -1
  344. package/dist/src/formats/json/obographs.js +297 -80
  345. package/dist/src/formats/json/obographs.js.map +1 -1
  346. package/dist/src/formats/neo4j/exporter.d.ts +109 -21
  347. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
  348. package/dist/src/formats/neo4j/exporter.js +238 -85
  349. package/dist/src/formats/neo4j/exporter.js.map +1 -1
  350. package/dist/src/formats/neo4j/header.d.ts +17 -4
  351. package/dist/src/formats/neo4j/header.d.ts.map +1 -1
  352. package/dist/src/formats/neo4j/header.js +36 -4
  353. package/dist/src/formats/neo4j/header.js.map +1 -1
  354. package/dist/src/formats/neo4j/importer.d.ts +107 -27
  355. package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
  356. package/dist/src/formats/neo4j/importer.js +415 -63
  357. package/dist/src/formats/neo4j/importer.js.map +1 -1
  358. package/dist/src/formats/neo4j/index.d.ts +73 -23
  359. package/dist/src/formats/neo4j/index.d.ts.map +1 -1
  360. package/dist/src/formats/neo4j/index.js +65 -25
  361. package/dist/src/formats/neo4j/index.js.map +1 -1
  362. package/dist/src/formats/obo/exporter.d.ts +150 -0
  363. package/dist/src/formats/obo/exporter.d.ts.map +1 -0
  364. package/dist/src/formats/obo/exporter.js +1736 -0
  365. package/dist/src/formats/obo/exporter.js.map +1 -0
  366. package/dist/src/formats/obo/importer.d.ts +95 -19
  367. package/dist/src/formats/obo/importer.d.ts.map +1 -1
  368. package/dist/src/formats/obo/importer.js +475 -104
  369. package/dist/src/formats/obo/importer.js.map +1 -1
  370. package/dist/src/formats/obo/index.d.ts +6 -3
  371. package/dist/src/formats/obo/index.d.ts.map +1 -1
  372. package/dist/src/formats/obo/index.js +6 -3
  373. package/dist/src/formats/obo/index.js.map +1 -1
  374. package/dist/src/formats/obo/syntax.d.ts +108 -4
  375. package/dist/src/formats/obo/syntax.d.ts.map +1 -1
  376. package/dist/src/formats/obo/syntax.js +211 -34
  377. package/dist/src/formats/obo/syntax.js.map +1 -1
  378. package/dist/src/formats/pajek/exporter.d.ts +54 -16
  379. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  380. package/dist/src/formats/pajek/exporter.js +66 -34
  381. package/dist/src/formats/pajek/exporter.js.map +1 -1
  382. package/dist/src/formats/pajek/importer.d.ts +84 -22
  383. package/dist/src/formats/pajek/importer.d.ts.map +1 -1
  384. package/dist/src/formats/pajek/importer.js +303 -83
  385. package/dist/src/formats/pajek/importer.js.map +1 -1
  386. package/dist/src/formats/pajek/index.d.ts +2 -1
  387. package/dist/src/formats/pajek/index.d.ts.map +1 -1
  388. package/dist/src/formats/pajek/index.js +2 -1
  389. package/dist/src/formats/pajek/index.js.map +1 -1
  390. package/dist/src/formats/pajek/syntax.d.ts +77 -14
  391. package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
  392. package/dist/src/formats/pajek/syntax.js +183 -51
  393. package/dist/src/formats/pajek/syntax.js.map +1 -1
  394. package/dist/src/formats/xgmml/columns.d.ts +1 -0
  395. package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
  396. package/dist/src/formats/xgmml/columns.js +4 -0
  397. package/dist/src/formats/xgmml/columns.js.map +1 -1
  398. package/dist/src/formats/xgmml/constants.d.ts +186 -56
  399. package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
  400. package/dist/src/formats/xgmml/constants.js +181 -57
  401. package/dist/src/formats/xgmml/constants.js.map +1 -1
  402. package/dist/src/formats/xgmml/document.d.ts +65 -11
  403. package/dist/src/formats/xgmml/document.d.ts.map +1 -1
  404. package/dist/src/formats/xgmml/document.js +157 -28
  405. package/dist/src/formats/xgmml/document.js.map +1 -1
  406. package/dist/src/formats/xgmml/emit.d.ts +61 -3
  407. package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
  408. package/dist/src/formats/xgmml/emit.js +129 -26
  409. package/dist/src/formats/xgmml/emit.js.map +1 -1
  410. package/dist/src/formats/xgmml/exporter.d.ts +13 -6
  411. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
  412. package/dist/src/formats/xgmml/exporter.js +20 -21
  413. package/dist/src/formats/xgmml/exporter.js.map +1 -1
  414. package/dist/src/formats/xgmml/importer.d.ts +33 -12
  415. package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
  416. package/dist/src/formats/xgmml/importer.js +35 -72
  417. package/dist/src/formats/xgmml/importer.js.map +1 -1
  418. package/dist/src/formats/xgmml/index.d.ts +1 -0
  419. package/dist/src/formats/xgmml/index.d.ts.map +1 -1
  420. package/dist/src/formats/xgmml/index.js +1 -0
  421. package/dist/src/formats/xgmml/index.js.map +1 -1
  422. package/dist/src/formats/xgmml/values.d.ts +8 -1
  423. package/dist/src/formats/xgmml/values.d.ts.map +1 -1
  424. package/dist/src/formats/xgmml/values.js +7 -2
  425. package/dist/src/formats/xgmml/values.js.map +1 -1
  426. package/dist/src/index.d.ts +23 -18
  427. package/dist/src/index.d.ts.map +1 -1
  428. package/dist/src/index.js +28 -16
  429. package/dist/src/index.js.map +1 -1
  430. package/dist/src/registry.d.ts +478 -92
  431. package/dist/src/registry.d.ts.map +1 -1
  432. package/dist/src/registry.js +807 -88
  433. package/dist/src/registry.js.map +1 -1
  434. package/dist/src/sniff.d.ts +61 -19
  435. package/dist/src/sniff.d.ts.map +1 -1
  436. package/dist/src/sniff.js +121 -29
  437. package/dist/src/sniff.js.map +1 -1
  438. package/dist/src/types.d.ts +362 -112
  439. package/dist/src/types.d.ts.map +1 -1
  440. package/dist/src/types.js +28 -7
  441. package/dist/src/types.js.map +1 -1
  442. package/dist/xgmml.js +2 -2
  443. package/docs/samples/README.md +25 -0
  444. package/docs/samples/got-edges.csv +353 -0
  445. package/docs/samples/got-network.graphml +1384 -0
  446. package/docs/samples/got-nodes.csv +108 -0
  447. package/docs/samples/got.cx +930 -0
  448. package/docs/samples/got.cx2 +465 -0
  449. package/docs/samples/got.gexf +471 -0
  450. package/docs/samples/got.gml +2650 -0
  451. package/docs/samples/got.json +2546 -0
  452. package/docs/samples/got.net +461 -0
  453. package/docs/samples/movies-nodes.csv +9 -0
  454. package/docs/samples/movies-rels.csv +8 -0
  455. package/docs/samples/networks.cys +0 -0
  456. package/docs/samples/proteins.xgmml +9 -0
  457. package/docs/samples/teams.gv +24 -0
  458. package/docs/samples/vehicles.obo +25 -0
  459. package/package.json +9 -4
  460. package/src/children.ts +18 -6
  461. package/src/common/attributes.ts +29 -11
  462. package/src/common/cell-budget.ts +11 -3
  463. package/src/common/choose.ts +89 -0
  464. package/src/common/codes.ts +490 -66
  465. package/src/common/compare.ts +472 -0
  466. package/src/common/cx-export.ts +432 -0
  467. package/src/common/declared-types.ts +38 -4
  468. package/src/common/direction.ts +78 -51
  469. package/src/common/escape.ts +33 -17
  470. package/src/common/export.ts +284 -47
  471. package/src/common/format.ts +30 -1
  472. package/src/common/ids.ts +14 -3
  473. package/src/common/input.ts +1057 -134
  474. package/src/common/json-elements.ts +1034 -198
  475. package/src/common/line-format.ts +249 -0
  476. package/src/common/lists.ts +6 -1
  477. package/src/common/ontology-export.ts +461 -0
  478. package/src/common/ontology.ts +31 -9
  479. package/src/common/options.ts +110 -15
  480. package/src/common/plural.ts +49 -0
  481. package/src/common/report.ts +168 -25
  482. package/src/common/temporal.ts +22 -5
  483. package/src/common/text.ts +62 -23
  484. package/src/common/weights.ts +74 -4
  485. package/src/common/writer.ts +37 -3
  486. package/src/common/xml.ts +477 -88
  487. package/src/common/zip.ts +169 -7
  488. package/src/formats/csv/exporter.ts +201 -46
  489. package/src/formats/csv/header.ts +50 -11
  490. package/src/formats/csv/importer.ts +1133 -245
  491. package/src/formats/csv/index.ts +106 -19
  492. package/src/formats/csv/records.ts +396 -122
  493. package/src/formats/csv/values.ts +1 -0
  494. package/src/formats/cx/exporter.ts +1483 -0
  495. package/src/formats/cx/importer.ts +888 -300
  496. package/src/formats/cx/index.ts +4 -3
  497. package/src/formats/cx2/exporter.ts +61 -338
  498. package/src/formats/cx2/importer.ts +317 -86
  499. package/src/formats/cx2/index.ts +2 -1
  500. package/src/formats/cys/constants.ts +62 -17
  501. package/src/formats/cys/exporter.ts +1368 -0
  502. package/src/formats/cys/importer.ts +659 -157
  503. package/src/formats/cys/index.ts +4 -2
  504. package/src/formats/cys/session.ts +139 -31
  505. package/src/formats/cys/tables.ts +47 -9
  506. package/src/formats/dot/exporter.ts +83 -37
  507. package/src/formats/dot/importer.ts +248 -54
  508. package/src/formats/dot/index.ts +2 -1
  509. package/src/formats/dot/names.ts +44 -11
  510. package/src/formats/dot/tokenizer.ts +25 -6
  511. package/src/formats/gexf/exporter.ts +57 -18
  512. package/src/formats/gexf/importer.ts +636 -105
  513. package/src/formats/gexf/index.ts +90 -21
  514. package/src/formats/gexf/schema.ts +67 -15
  515. package/src/formats/gml/exporter.ts +90 -36
  516. package/src/formats/gml/importer.ts +388 -67
  517. package/src/formats/gml/index.ts +85 -24
  518. package/src/formats/gml/syntax.ts +111 -26
  519. package/src/formats/graphml/constants.ts +178 -44
  520. package/src/formats/graphml/exporter.ts +52 -18
  521. package/src/formats/graphml/importer.ts +640 -60
  522. package/src/formats/graphml/index.ts +2 -1
  523. package/src/formats/graphml/tree.ts +6 -2
  524. package/src/formats/graphml/yfiles.ts +37 -15
  525. package/src/formats/json/dialect.ts +128 -29
  526. package/src/formats/json/exporter.ts +205 -68
  527. package/src/formats/json/importer.ts +1163 -253
  528. package/src/formats/json/index.ts +3 -1
  529. package/src/formats/json/obographs-export.ts +1197 -0
  530. package/src/formats/json/obographs.ts +353 -92
  531. package/src/formats/neo4j/exporter.ts +320 -101
  532. package/src/formats/neo4j/header.ts +44 -6
  533. package/src/formats/neo4j/importer.ts +512 -73
  534. package/src/formats/neo4j/index.ts +75 -23
  535. package/src/formats/obo/exporter.ts +2210 -0
  536. package/src/formats/obo/importer.ts +622 -109
  537. package/src/formats/obo/index.ts +6 -3
  538. package/src/formats/obo/syntax.ts +232 -37
  539. package/src/formats/pajek/exporter.ts +89 -41
  540. package/src/formats/pajek/importer.ts +407 -93
  541. package/src/formats/pajek/index.ts +2 -1
  542. package/src/formats/pajek/syntax.ts +213 -52
  543. package/src/formats/xgmml/columns.ts +10 -0
  544. package/src/formats/xgmml/constants.ts +183 -56
  545. package/src/formats/xgmml/document.ts +212 -34
  546. package/src/formats/xgmml/emit.ts +175 -34
  547. package/src/formats/xgmml/exporter.ts +35 -27
  548. package/src/formats/xgmml/importer.ts +64 -86
  549. package/src/formats/xgmml/index.ts +1 -0
  550. package/src/formats/xgmml/values.ts +11 -3
  551. package/src/index.ts +81 -11
  552. package/src/registry.ts +1121 -135
  553. package/src/sniff.ts +163 -32
  554. package/src/types.ts +385 -114
  555. package/dist/chunks/escape-B-tkk_Cl.js.map +0 -1
  556. package/dist/chunks/export-Bh60Dv-n.js +0 -582
  557. package/dist/chunks/export-Bh60Dv-n.js.map +0 -1
  558. package/dist/chunks/exporter-BGrsWamJ.js.map +0 -1
  559. package/dist/chunks/exporter-DIeGJXAZ.js.map +0 -1
  560. package/dist/chunks/importer-BWY2FFc5.js.map +0 -1
  561. package/dist/chunks/importer-DD-xv9_Y.js +0 -1471
  562. package/dist/chunks/importer-DD-xv9_Y.js.map +0 -1
  563. package/dist/chunks/importer-DET1tg6s.js.map +0 -1
  564. package/dist/chunks/importer-DEcKhsmQ.js +0 -1614
  565. package/dist/chunks/importer-DEcKhsmQ.js.map +0 -1
  566. package/dist/chunks/importer-DIrbbnAf.js.map +0 -1
  567. package/dist/chunks/importer-D_e7e7LX.js.map +0 -1
  568. package/dist/chunks/importer-eUREhBGj.js.map +0 -1
  569. package/dist/chunks/importer-vi3bSdtw.js.map +0 -1
  570. package/dist/chunks/importer-zGLo8Dg_.js.map +0 -1
  571. package/dist/chunks/json-elements-DDS8N2Dd.js +0 -779
  572. package/dist/chunks/json-elements-DDS8N2Dd.js.map +0 -1
  573. package/dist/chunks/ontology-BnrJ4I98.js +0 -113
  574. package/dist/chunks/ontology-BnrJ4I98.js.map +0 -1
  575. package/dist/chunks/records-dbRkxwaq.js.map +0 -1
  576. package/dist/chunks/report-B1z4WT9e.js +0 -2372
  577. package/dist/chunks/report-B1z4WT9e.js.map +0 -1
  578. package/dist/chunks/text-Dr0Ifpag.js.map +0 -1
  579. package/dist/chunks/weights-Dzba96G3.js +0 -176
  580. package/dist/chunks/weights-Dzba96G3.js.map +0 -1
  581. package/dist/chunks/writer-C7flM-Ih.js.map +0 -1
@@ -0,0 +1,1197 @@
1
+ /**
2
+ * The `obographs` dialect of the JSON exporter: writes a snapshot as an OBO Graphs document
3
+ * (`{ "graphs": [{ "id", "meta", "nodes", "edges", ... }] }`), the JSON form the Gene Ontology and
4
+ * the OBO Foundry publish, and the form the JSON importer's `obographs` dialect reads back.
5
+ *
6
+ * Nodes carry `lbl` from the label column, `type` from the `type` column (Term as CLASS, Instance
7
+ * as INDIVIDUAL, Typedef as PROPERTY) and a `meta` built from the OBO vocabulary columns
8
+ * (src/common/ontology.ts: definition, comments, subsets, xrefs, synonyms, deprecated, and the
9
+ * basicPropertyValues of namespace, alt_id, created_by, creation_date, replaced_by, consider and
10
+ * property_value); any other node column becomes basicPropertyValues named after the column. Edges
11
+ * are `{ sub, pred, obj }` with the edge's `meta` column and its other columns in `meta`. Ids are
12
+ * written as IRIs (`GO:0008150` as `http://purl.obolibrary.org/obo/GO_0008150`, a relation by the
13
+ * IRI of the property whose shorthand it is) whenever the importer compacts the IRI back to the
14
+ * same id. The graph metadata, the PROPERTY nodes, the property edges and the axioms an OBO Graphs
15
+ * import kept in `meta.extra.obographs` are written back.
16
+ */
17
+
18
+ import { type Column, GraphFormatError, type GraphSnapshot, type NodeId } from "@graphty/graph-format";
19
+
20
+ import {
21
+ COLUMN_AS_PROPERTY_VALUE_CODE,
22
+ DIRECTION_DROPPED_CODE,
23
+ GRAPH_COLUMN_AS_METADATA_CODE,
24
+ NODE_ORDER_CODE,
25
+ NONFINITE_AS_NULL_CODE,
26
+ RELATION_ASSUMED_CODE,
27
+ TYPEDEF_NODES_CODE,
28
+ } from "../../common/codes.js";
29
+ import { type PairFolding, pairFolding } from "../../common/direction.js";
30
+ import { LOSS } from "../../common/export.js";
31
+ import {
32
+ compactOboIri,
33
+ OBO_IN_OWL,
34
+ OBO_NODE_COLUMNS,
35
+ OBO_PURL,
36
+ OBOGRAPHS_PREDICATE_TAGS,
37
+ PLACEHOLDER_COLUMN,
38
+ SYNONYM_SCOPES,
39
+ } from "../../common/ontology.js";
40
+ import {
41
+ capabilityNotes,
42
+ cellOf,
43
+ emptyColumnNotes,
44
+ fitsVocabulary,
45
+ framelessNodes,
46
+ movedNodes,
47
+ type NoteFn,
48
+ notePropertyColumn,
49
+ slotColumn,
50
+ textOf,
51
+ UNWRITTEN_ROLES,
52
+ valuesOf,
53
+ } from "../../common/ontology-export.js";
54
+ import { type ResolvedExportOptions } from "../../common/options.js";
55
+ import { agree, plural } from "../../common/plural.js";
56
+ import { type ExplicitWeights, explicitWeights } from "../../common/weights.js";
57
+ import { type LossNote } from "../../types.js";
58
+ import { dialectCapabilities, exponentIfUnsafe, isJsonObject } from "./dialect.js";
59
+ import { BUILTIN_PREDICATES, FRAME_TYPES, META_KEYS, NODE_KEYS, PROPERTY_PREDICATES } from "./obographs.js";
60
+
61
+ /**
62
+ * The loss codes only the `obographs` dialect records.
63
+ * @category Built-in formats
64
+ */
65
+ export const OBOGRAPHS_LOSS = Object.freeze({
66
+ /** An edge column (or the explicit weights) is written into the edge's `meta` and reads back inside the `meta` column. */
67
+ EDGE_COLUMN_AS_META: "W_OBOGRAPHS_EDGE_COLUMN_AS_META",
68
+ /** A node id or relation is written as an IRI the importer's default `oboIds: "curie"` reads back as a different id. */
69
+ ID_CHANGED: "W_OBOGRAPHS_ID_CHANGED",
70
+ /** A `property_value` with an xsd datatype: basicPropertyValues have no datatype, so it reads back without one. */
71
+ DATATYPE_DROPPED: "W_OBOGRAPHS_DATATYPE_DROPPED",
72
+ });
73
+
74
+ /**
75
+ * The codes the dialect shares with the other exporters, under the names JSON_LOSS gives them.
76
+ * @category Built-in formats
77
+ */
78
+ export const OBOGRAPHS_SHARED_LOSS = Object.freeze({
79
+ /** A node column outside the OBO vocabulary reads back inside the `property_value` column. */
80
+ COLUMN_AS_PROPERTY_VALUE: COLUMN_AS_PROPERTY_VALUE_CODE,
81
+ /** An edge without a relation is written with the predicate `is_a`. */
82
+ RELATION_ASSUMED: RELATION_ASSUMED_CODE,
83
+ /** Typedef nodes are written as PROPERTY nodes, which read back as nodes only under `typedefs: "nodes"`. */
84
+ TYPEDEF_NODES: TYPEDEF_NODES_CODE,
85
+ /** A graph attribute is written into the graph's `meta` and reads back in `snapshot.meta.extra.obographs`. */
86
+ GRAPH_COLUMN_AS_METADATA: GRAPH_COLUMN_AS_METADATA_CODE,
87
+ });
88
+
89
+ /** The settings of one export. */
90
+ interface ObographsSettings {
91
+ readonly common: ResolvedExportOptions;
92
+ readonly indent: string;
93
+ /** The ontology IRI unprefixed ids are written under, or null for the kept or derived one. */
94
+ readonly ontologyIri: string | null;
95
+ }
96
+
97
+ /** A planned export: the notes and the writer. */
98
+ interface ObographsPlan {
99
+ readonly notes: LossNote[];
100
+ /** The document's text parts. */
101
+ write(): Generator<string, void, undefined>;
102
+ }
103
+
104
+ /** The node type of each OBO frame type. */
105
+ const NODE_TYPES: Readonly<Record<string, string>> = Object.freeze({
106
+ Term: "CLASS",
107
+ Instance: "INDIVIDUAL",
108
+ Typedef: "PROPERTY",
109
+ });
110
+
111
+ /** The basicPropertyValues predicate of each OBO tag (the reverse of OBOGRAPHS_PREDICATE_TAGS). */
112
+ const TAG_PREDICATES: ReadonlyMap<string, string> = new Map(
113
+ [...OBOGRAPHS_PREDICATE_TAGS].map(([pred, tag]) => [tag, pred] as const),
114
+ );
115
+
116
+ /** The OBO Graphs pred of the relations the importer reads from a built-in pred. */
117
+ const REVERSE_PREDICATES: Readonly<Record<string, string>> = Object.freeze({ is_a: "is_a", instance_of: "type" });
118
+
119
+ /** The vocabulary columns the dialect has a place for; every other node column is property values. */
120
+ const PLACED: ReadonlySet<string> = new Set([
121
+ "type",
122
+ "propertyType",
123
+ "def",
124
+ "def.xrefs",
125
+ "comment",
126
+ "subset",
127
+ "xref",
128
+ "synonym",
129
+ "is_obsolete",
130
+ "namespace",
131
+ "created_by",
132
+ "creation_date",
133
+ "alt_id",
134
+ "replaced_by",
135
+ "consider",
136
+ "property_value",
137
+ "obo.unrecognized",
138
+ ]);
139
+
140
+ /** The basicPropertyValues columns, in the order they are written. */
141
+ const PROPERTY_TAGS: readonly string[] = [
142
+ "namespace",
143
+ "alt_id",
144
+ "created_by",
145
+ "creation_date",
146
+ "replaced_by",
147
+ "consider",
148
+ ];
149
+
150
+ /** An IRI with a scheme. */
151
+ const IRI = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
152
+
153
+ /** A CURIE whose prefix the OBO PURL mapping keeps (no underscore). */
154
+ const CURIE = /^([A-Za-z][A-Za-z0-9.-]*):(.+)$/;
155
+
156
+ /** The keyed OBO Graphs records an import kept. */
157
+ interface Kept {
158
+ readonly ids: "curie" | "iri";
159
+ readonly graph: Record<string, unknown>;
160
+ readonly document: Record<string, unknown>;
161
+ readonly properties: Record<string, Record<string, unknown>>;
162
+ readonly propertyEdges: unknown[];
163
+ }
164
+
165
+ /**
166
+ * The records an OBO Graphs import kept under `meta.extra.obographs`.
167
+ * @param snapshot - the snapshot
168
+ * @returns the records, empty when the snapshot did not come from OBO Graphs
169
+ */
170
+ function keptOf(snapshot: GraphSnapshot): Kept {
171
+ const raw = snapshot.meta.extra.obographs;
172
+ const r = isJsonObject(raw) ? raw : {};
173
+ const properties: Record<string, Record<string, unknown>> = {};
174
+ if (isJsonObject(r.properties)) {
175
+ for (const [iri, entry] of Object.entries(r.properties)) {
176
+ if (isJsonObject(entry)) {
177
+ properties[iri] = entry;
178
+ }
179
+ }
180
+ }
181
+ return {
182
+ ids: r.ids === "iri" ? "iri" : "curie",
183
+ graph: isJsonObject(r.graph) ? r.graph : {},
184
+ document: isJsonObject(r.document) ? r.document : {},
185
+ properties,
186
+ propertyEdges: Array.isArray(r.propertyEdges) ? r.propertyEdges : [],
187
+ };
188
+ }
189
+
190
+ /**
191
+ * The shorthand a property's meta declares (the importer names the property by it).
192
+ * @param meta - the property's meta
193
+ * @returns the shorthand, or null
194
+ */
195
+ function shorthandOf(meta: unknown): string | null {
196
+ if (!isJsonObject(meta) || !Array.isArray(meta.basicPropertyValues)) {
197
+ return null;
198
+ }
199
+ for (const pv of meta.basicPropertyValues) {
200
+ if (
201
+ isJsonObject(pv) &&
202
+ typeof pv.pred === "string" &&
203
+ OBOGRAPHS_PREDICATE_TAGS.get(pv.pred) === "shorthand" &&
204
+ typeof pv.val === "string"
205
+ ) {
206
+ return pv.val;
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+
212
+ /**
213
+ * The ontology IRI unprefixed ids are written under: the option, the kept graph id, else the OBO
214
+ * PURL of the ontology id (the kept OBO header's, or the graph name's).
215
+ * @param snapshot - the snapshot
216
+ * @param kept - the kept records
217
+ * @param option - the ontologyIri option, or null
218
+ * @returns the IRI
219
+ */
220
+ function ontologyIriOf(snapshot: GraphSnapshot, kept: Kept, option: string | null): string {
221
+ if (option !== null) {
222
+ return option;
223
+ }
224
+ if (typeof kept.graph.id === "string") {
225
+ return kept.graph.id;
226
+ }
227
+ const { obo } = snapshot.meta.extra;
228
+ const header = isJsonObject(obo) && isJsonObject(obo.header) ? obo.header : {};
229
+ const ontology =
230
+ Array.isArray(header.ontology) && typeof header.ontology[0] === "string" ? header.ontology[0].trim() : null;
231
+ const name = ontology ?? snapshot.meta.name;
232
+ const id = name === null || name.length === 0 ? "graph" : name.replaceAll(/[^A-Za-z0-9_.\-/]/g, "_");
233
+ return `${OBO_PURL}${id}.owl`;
234
+ }
235
+
236
+ /**
237
+ * JSON text of a value with -0 kept and the non-finite numbers written as null (counted).
238
+ * @param value - the value
239
+ * @param nonfinite - the counter
240
+ * @param nonfinite.count - the count
241
+ * @returns the JSON text
242
+ */
243
+ function json(value: unknown, nonfinite: { count: number }): string {
244
+ if (typeof value === "number") {
245
+ if (!Number.isFinite(value)) {
246
+ nonfinite.count++;
247
+ return "null";
248
+ }
249
+ return Object.is(value, -0) ? "-0" : exponentIfUnsafe(JSON.stringify(value));
250
+ }
251
+ if (Array.isArray(value)) {
252
+ return `[${value.map((v: unknown) => json(v, nonfinite)).join(",")}]`;
253
+ }
254
+ if (isJsonObject(value)) {
255
+ return `{${Object.entries(value)
256
+ .filter(([, v]) => v !== undefined)
257
+ .map(([k, v]) => `${JSON.stringify(k)}:${json(v, nonfinite)}`)
258
+ .join(",")}}`;
259
+ }
260
+ return JSON.stringify(value) ?? "null";
261
+ }
262
+
263
+ /**
264
+ * Whether a value is a non-empty array of strings.
265
+ * @param value - the value
266
+ * @param empty - whether an empty array is allowed
267
+ * @returns true for such an array
268
+ */
269
+ function strings(value: unknown, empty = false): value is string[] {
270
+ return Array.isArray(value) && (empty || value.length > 0) && value.every((v) => typeof v === "string");
271
+ }
272
+
273
+ /** A synonym as the vocabulary holds it. */
274
+ interface Synonym {
275
+ readonly text: string | null;
276
+ readonly scope: string | null;
277
+ readonly type: string | null;
278
+ readonly xrefs: string[];
279
+ /** The synonym's own `meta`, kept by the importer as it was. */
280
+ readonly meta?: unknown;
281
+ }
282
+
283
+ /** One OBO Graphs export: the plan (ids, columns, notes) and the writer. */
284
+ /**
285
+ * The basicPropertyValue texts a property column gives one node: one per item of a list, JSON text
286
+ * for nested values, none when unset.
287
+ * @param column - the column
288
+ * @param i - the node
289
+ * @returns the texts
290
+ */
291
+ function propertyTexts(column: Column, i: number): string[] {
292
+ const v = cellOf(column, i);
293
+ if (v === undefined) {
294
+ return [];
295
+ }
296
+ if (column.dtype === "list") {
297
+ return (v as readonly unknown[]).map((item) => textOf(item, column.child.dtype));
298
+ }
299
+ const nested = column.dtype === "json" || column.meta.components > 1;
300
+ return [nested ? (JSON.stringify(v) ?? "null") : textOf(v, column.dtype)];
301
+ }
302
+
303
+ /**
304
+ * The graph columns as basicPropertyValues of the graph's meta.
305
+ * @param snapshot - the snapshot
306
+ * @returns one record per value
307
+ */
308
+ function graphPropertyValues(snapshot: GraphSnapshot): { pred: string; val: string }[] {
309
+ const values: { pred: string; val: string }[] = [];
310
+ for (const column of snapshot.graph) {
311
+ const dtype = column.dtype === "list" ? column.child.dtype : column.dtype;
312
+ for (const item of valuesOf(cellOf(column, 0))) {
313
+ values.push({ pred: column.meta.name, val: typeof item === "string" ? item : textOf(item, dtype) });
314
+ }
315
+ }
316
+ return values;
317
+ }
318
+
319
+ /**
320
+ * Note a placed node column whose dtype reads back as another.
321
+ * @param note - records a note
322
+ * @param want - the dtype the column reads back as
323
+ * @param column - the column
324
+ */
325
+ function dtypeNote(note: NoteFn, want: string, column: Column): void {
326
+ const listItems = column.dtype === "list" && column.child.dtype !== "string";
327
+ if (((want === "string" || want === "dict") && column.dtype !== want) || listItems) {
328
+ note(
329
+ LOSS.DTYPE,
330
+ `node column "${column.meta.name}" holds ${listItems ? "non-string items" : column.dtype}; it reads back as ${listItems ? "string items" : want}`,
331
+ column.meta.name,
332
+ column.length - column.nullCount,
333
+ );
334
+ }
335
+ }
336
+
337
+ /**
338
+ * The property values of the written nodes that carry an xsd datatype.
339
+ * @param pv - the property_value column
340
+ * @param rows - the written nodes
341
+ * @returns the count
342
+ */
343
+ function countDatatypes(pv: Column, rows: readonly number[]): number {
344
+ let datatypes = 0;
345
+ for (const i of rows) {
346
+ for (const item of (cellOf(pv, i) ?? []) as { datatype: unknown }[]) {
347
+ datatypes += item.datatype === null ? 0 : 1;
348
+ }
349
+ }
350
+ return datatypes;
351
+ }
352
+
353
+ class ObographsExport {
354
+ readonly notes: LossNote[] = [];
355
+
356
+ private readonly note: NoteFn = (code, message, column = null, count = null) => {
357
+ this.notes.push(Object.freeze({ code, message, column, count }));
358
+ };
359
+
360
+ private readonly kept: Kept;
361
+
362
+ private readonly ontologyIri: string;
363
+
364
+ /** IRI to the shorthand the importer names it by. */
365
+ private readonly shorthands = new Map<string, string>();
366
+
367
+ /** Shorthand to the IRI of its property. */
368
+ private readonly shorthandIris = new Map<string, string>();
369
+
370
+ /** The IRI written for each node. */
371
+ private readonly iris: string[] = [];
372
+
373
+ /** The nodes whose IRI reads back as another id. */
374
+ private readonly changedRows = new Set<number>();
375
+
376
+ /** The Typedef nodes written with a shorthand, by node. */
377
+ private readonly typedefShorthands = new Map<number, string>();
378
+
379
+ private readonly written: number[] = [];
380
+
381
+ private rows: number[] = [];
382
+
383
+ private frameless: Uint8Array = new Uint8Array(0);
384
+
385
+ private label: Column | null = null;
386
+
387
+ private readonly placed = new Map<string, Column>();
388
+
389
+ private readonly properties: Column[] = [];
390
+
391
+ private metaColumn: Column | null = null;
392
+
393
+ private readonly edgeColumns: Column[] = [];
394
+
395
+ private readonly preds = new Map<number, string>();
396
+
397
+ private readonly weights: ExplicitWeights;
398
+
399
+ private readonly folding: PairFolding;
400
+
401
+ private readonly fatal: GraphFormatError | null;
402
+
403
+ private readonly nonfinite = { count: 0 };
404
+
405
+ private readonly indent: string;
406
+
407
+ /** The edges' endpoints. */
408
+ private readonly ends: { readonly src: ArrayLike<number>; readonly dst: ArrayLike<number> };
409
+
410
+ /**
411
+ * Plan the export.
412
+ * @param snapshot - the snapshot
413
+ * @param settings - the resolved settings
414
+ */
415
+ constructor(
416
+ private readonly snapshot: GraphSnapshot,
417
+ settings: ObographsSettings,
418
+ ) {
419
+ this.kept = keptOf(snapshot);
420
+ this.ontologyIri = ontologyIriOf(snapshot, this.kept, settings.ontologyIri);
421
+ this.indent = settings.indent !== "" ? `\n${settings.indent}` : "";
422
+ for (const [iri, entry] of Object.entries(this.kept.properties)) {
423
+ const shorthand = shorthandOf(entry.meta);
424
+ if (shorthand !== null) {
425
+ this.shorthands.set(iri, shorthand);
426
+ this.shorthandIris.set(shorthand, iri);
427
+ }
428
+ }
429
+ this.folding = pairFolding(snapshot);
430
+ this.ends = snapshot.edgeList();
431
+ for (let e = 0; e < snapshot.edgeCount; e++) {
432
+ if (!this.folding.folded(e)) {
433
+ this.written.push(e);
434
+ }
435
+ }
436
+ this.weights = explicitWeights(snapshot);
437
+ this.planIds();
438
+ this.planNodes();
439
+ this.fatal =
440
+ capabilityNotes(snapshot, settings.common, {
441
+ caps: dialectCapabilities("obographs"),
442
+ label: this.label,
443
+ labelAssumed: slotColumn(snapshot.nodes, "label", "name").assumed,
444
+ edgeSlot: "edge meta",
445
+ note: this.note,
446
+ }) ?? this.emptyIdError();
447
+ this.planColumnNotes();
448
+ this.planEdges(settings.common);
449
+ emptyColumnNotes(snapshot, this.rows, this.frameless, this.written, this.note);
450
+ // a dry run counts the non-finite numbers the document writes as null
451
+ if (this.fatal === null) {
452
+ const parts = this.write();
453
+ while (parts.next().done !== true) {
454
+ // only the counts the writing leaves behind are wanted
455
+ }
456
+ }
457
+ if (this.nonfinite.count > 0) {
458
+ this.note(
459
+ NONFINITE_AS_NULL_CODE,
460
+ `${this.nonfinite.count} NaN or infinite value${plural(this.nonfinite.count)} ${agree(this.nonfinite.count, "is", "are")} written as null`,
461
+ null,
462
+ this.nonfinite.count,
463
+ );
464
+ }
465
+ }
466
+
467
+ /**
468
+ * The id the importer reads an IRI back as: the shorthand of the property it names, else the
469
+ * compacted IRI.
470
+ * @param iri - the IRI
471
+ * @returns the id
472
+ */
473
+ private reread(iri: string): string {
474
+ return this.shorthands.get(iri) ?? compactOboIri(iri);
475
+ }
476
+
477
+ /**
478
+ * An id as an IRI: an IRI as it is, a CURIE as its OBO PURL, anything else under the
479
+ * ontology IRI.
480
+ * @param id - the id
481
+ * @returns the IRI
482
+ */
483
+ private expand(id: string): string {
484
+ if (IRI.test(id)) {
485
+ return id;
486
+ }
487
+ const curie = CURIE.exec(id);
488
+ if (curie !== null && !curie[1].includes("_")) {
489
+ return `${OBO_PURL}${curie[1]}_${curie[2]}`;
490
+ }
491
+ return `${this.ontologyIri}#${id}`;
492
+ }
493
+
494
+ /**
495
+ * The IRI to write for an id: the first form that reads back as the id (the property of a
496
+ * shorthand, the expanded IRI, the id itself); the id as it stands for a snapshot read with
497
+ * oboIds "iri".
498
+ * @param id - the id
499
+ * @returns the IRI and whether it reads back as the id
500
+ */
501
+ private iriOf(id: string): { iri: string; exact: boolean } {
502
+ const candidates =
503
+ this.kept.ids === "iri"
504
+ ? [id]
505
+ : [this.shorthandIris.get(id), this.expand(id), id].filter((c): c is string => c !== undefined);
506
+ const found = candidates.find((c) => this.reread(c) === id);
507
+ return found === undefined ? { iri: candidates[0], exact: false } : { iri: found, exact: true };
508
+ }
509
+
510
+ /**
511
+ * The pred to write for a relation: `is_a`, `type` for instance_of, the property of a
512
+ * shorthand, the expanded IRI or the relation itself, whichever reads back as the relation.
513
+ * @param r - the relation
514
+ * @returns the pred and whether it reads back as the relation
515
+ */
516
+ private predOf(r: string): { pred: string; exact: boolean } {
517
+ const reverse = REVERSE_PREDICATES[r];
518
+ const candidates = [reverse, this.shorthandIris.get(r), this.expand(r), r].filter(
519
+ (c): c is string => c !== undefined && !PROPERTY_PREDICATES.has(c),
520
+ );
521
+ const reread = (c: string): string => BUILTIN_PREDICATES[c] ?? this.shorthands.get(c) ?? compactOboIri(c);
522
+ const found =
523
+ this.kept.ids === "iri"
524
+ ? candidates.find((c) => c === r && reread(c) === r)
525
+ : candidates.find((c) => reread(c) === r);
526
+ if (found !== undefined) {
527
+ return { pred: found, exact: true };
528
+ }
529
+ return { pred: this.kept.ids === "iri" ? r : candidates[0], exact: false };
530
+ }
531
+
532
+ /**
533
+ * An empty node id: OBO Graphs gives every node a non-empty id, and the importer skips a node
534
+ * without one, so the export is refused.
535
+ * @returns the error, or null when every id has text
536
+ */
537
+ private emptyIdError(): GraphFormatError | null {
538
+ const { snapshot } = this;
539
+ let count = 0;
540
+ let first = -1;
541
+ for (let i = 0; i < snapshot.nodeCount; i++) {
542
+ if (String(snapshot.ids.idOf(i)) === "") {
543
+ count++;
544
+ first = first < 0 ? i : first;
545
+ }
546
+ }
547
+ if (count === 0) {
548
+ return null;
549
+ }
550
+ const message = `${count} node id${plural(count)} ${agree(count, "is", "are")} empty; an OBO Graphs node needs an id, so the save fails`;
551
+ this.note(LOSS.ID_CHARSET, message, null, count);
552
+ return new GraphFormatError("E_INVALID_ID", message, { reason: "charset", count, id: "", index: first });
553
+ }
554
+
555
+ /** The IRI of every node, the Typedefs named by a shorthand, and the numeric-id note. */
556
+ private planIds(): void {
557
+ const { snapshot } = this;
558
+ const typeColumn = snapshot.nodes.get("type");
559
+ let numeric = 0;
560
+ for (let i = 0; i < snapshot.nodeCount; i++) {
561
+ const id: NodeId = snapshot.ids.idOf(i);
562
+ numeric += typeof id === "number" ? 1 : 0;
563
+ const text = String(id);
564
+ let { iri, exact } = this.iriOf(text);
565
+ const typedef = typeColumn !== null && typeColumn.isSet(i) && typeColumn.value(i) === "Typedef";
566
+ if (!exact && typedef && !IRI.test(text) && this.kept.ids === "curie") {
567
+ // a Typedef named by its shorthand: written under an IRI with the shorthand declared
568
+ iri = this.expand(text);
569
+ this.shorthands.set(iri, text);
570
+ this.shorthandIris.set(text, iri);
571
+ this.typedefShorthands.set(i, text);
572
+ exact = true;
573
+ }
574
+ this.iris.push(iri);
575
+ if (!exact) {
576
+ this.changedRows.add(i);
577
+ }
578
+ }
579
+ if (numeric > 0) {
580
+ this.note(
581
+ LOSS.ID_TEXT_TYPE,
582
+ `${numeric} numeric node id${plural(numeric)} ${agree(numeric, "is", "are")} written as text and read back as strings`,
583
+ null,
584
+ numeric,
585
+ );
586
+ }
587
+ }
588
+
589
+ /** The written nodes (placeholders aside), the node order, and the columns with a place. */
590
+ private planNodes(): void {
591
+ const { snapshot } = this;
592
+ const { src, dst } = this.ends;
593
+ this.frameless = framelessNodes(snapshot, this.written, {
594
+ outgoingAllowed: true,
595
+ changed: (i) => this.changedRows.has(i),
596
+ });
597
+ const moved = movedNodes(
598
+ this.frameless,
599
+ this.written.flatMap((e) => [src[e], dst[e]]),
600
+ );
601
+ if (moved > 0) {
602
+ this.note(
603
+ NODE_ORDER_CODE,
604
+ `${moved} node${plural(moved)} ${agree(moved, "reads", "read")} back at another position: placeholder nodes are not written and read back after the written nodes`,
605
+ null,
606
+ moved,
607
+ );
608
+ }
609
+ this.rows = [];
610
+ for (let i = 0; i < snapshot.nodeCount; i++) {
611
+ if (this.frameless[i] === 0) {
612
+ this.rows.push(i);
613
+ }
614
+ }
615
+ this.label = slotColumn(snapshot.nodes, "label", "name").column;
616
+ this.placeColumns();
617
+ this.planProperties();
618
+ }
619
+
620
+ /** The node columns written in their own places of a node (def, synonyms, xrefs...), not as property values. */
621
+ private placeColumns(): void {
622
+ const { snapshot } = this;
623
+ const reproducible = (id: unknown): boolean => typeof id === "string" && this.iriOf(id).exact;
624
+ for (const name of PLACED) {
625
+ const column = snapshot.nodes.get(name);
626
+ if (
627
+ column !== null &&
628
+ column !== this.label &&
629
+ column.meta.role === null &&
630
+ carries(name, column, this.rows, reproducible)
631
+ ) {
632
+ this.placed.set(name, column);
633
+ }
634
+ }
635
+ const def = this.placed.get("def");
636
+ const defXrefs = this.placed.get("def.xrefs");
637
+ if (
638
+ defXrefs !== undefined &&
639
+ (def === undefined || this.rows.some((i) => def.isSet(i) !== defXrefs.isSet(i)))
640
+ ) {
641
+ this.placed.delete("def.xrefs");
642
+ }
643
+ if (def !== undefined && !this.placed.has("def.xrefs") && snapshot.nodes.get("def.xrefs") !== null) {
644
+ this.placed.delete("def");
645
+ }
646
+ }
647
+
648
+ /** The node columns written as property values, each with its note. */
649
+ private planProperties(): void {
650
+ const { snapshot } = this;
651
+ const slotted = new Set<Column>([...this.placed.values(), ...(this.label === null ? [] : [this.label])]);
652
+ for (const column of snapshot.nodes) {
653
+ const { role, name } = column.meta;
654
+ if (slotted.has(column) || (role !== null && UNWRITTEN_ROLES.has(role))) {
655
+ continue;
656
+ }
657
+ if (name === PLACEHOLDER_COLUMN && this.rows.every((i) => !column.isSet(i))) {
658
+ continue;
659
+ }
660
+ this.properties.push(column);
661
+ notePropertyColumn(this.note, column, this.rows, `basicPropertyValues (pred ${JSON.stringify(name)})`);
662
+ }
663
+ }
664
+
665
+ /** The notes about node and graph columns: Typedef nodes, dtypes, datatypes, graph columns. */
666
+ private planColumnNotes(): void {
667
+ const { snapshot, rows, label, note } = this;
668
+ const type = this.placed.get("type");
669
+ const typedefs =
670
+ type === undefined ? 0 : rows.filter((i) => type.isSet(i) && type.value(i) === "Typedef").length;
671
+ if (typedefs > 0) {
672
+ note(
673
+ TYPEDEF_NODES_CODE,
674
+ `${typedefs} node${plural(typedefs)} ${agree(typedefs, "is", "are")} written as PROPERTY nodes; they read back as nodes only when imported with typedefs: "nodes", and then their edges with them`,
675
+ "type",
676
+ typedefs,
677
+ );
678
+ }
679
+ for (const [name, column] of [...this.placed, ...(label === null ? [] : ([["name", label]] as const))]) {
680
+ dtypeNote(note, name === "name" ? "string" : OBO_NODE_COLUMNS[name].dtype, column);
681
+ }
682
+ const pv = this.placed.get("property_value");
683
+ const datatypes = pv === undefined ? 0 : countDatatypes(pv, rows);
684
+ if (datatypes > 0) {
685
+ note(
686
+ OBOGRAPHS_LOSS.DATATYPE_DROPPED,
687
+ `${datatypes} property value${plural(datatypes)} ${agree(datatypes, "carries", "carry")} an xsd datatype; OBO Graphs basicPropertyValues have none, so they read back without it`,
688
+ "property_value",
689
+ datatypes,
690
+ );
691
+ }
692
+ for (const column of snapshot.graph) {
693
+ note(
694
+ GRAPH_COLUMN_AS_METADATA_CODE,
695
+ `graph column "${column.meta.name}" is written as a basicPropertyValue of the graph's meta and reads back in meta.extra.obographs, not as a column`,
696
+ column.meta.name,
697
+ column.length - column.nullCount,
698
+ );
699
+ }
700
+ }
701
+
702
+ /**
703
+ * The edges' preds, meta and other columns, and their notes (relation, direction, ids that
704
+ * change).
705
+ * @param common - the common options
706
+ */
707
+ private planEdges(common: ResolvedExportOptions): void {
708
+ const { snapshot, written, note } = this;
709
+ const slot = slotColumn(snapshot.edges, "kind", "relation");
710
+ const relation = slot.column;
711
+ if (slot.assumed && relation !== null) {
712
+ note(
713
+ LOSS.ROLE_ASSUMED,
714
+ `edge column "relation" has no role, so it is taken as the relation of each edge: it is written as each edge's pred, and reads back with the relation role ("kind")`,
715
+ "relation",
716
+ relation.length - relation.nullCount,
717
+ );
718
+ }
719
+ const meta = snapshot.edges.get("meta");
720
+ const metaOk =
721
+ meta !== null &&
722
+ meta.dtype === "json" &&
723
+ meta.meta.role === null &&
724
+ written.every((e) => !meta.isSet(e) || isJsonObject(cellOf(meta, e)));
725
+ this.metaColumn = metaOk ? meta : null;
726
+ for (const column of snapshot.edges) {
727
+ const { role } = column.meta;
728
+ if (
729
+ column === relation ||
730
+ column === this.metaColumn ||
731
+ role === "id" ||
732
+ (role !== null && UNWRITTEN_ROLES.has(role))
733
+ ) {
734
+ continue;
735
+ }
736
+ this.edgeColumns.push(column);
737
+ const count = written.filter((e) => column.isSet(e)).length;
738
+ if (count > 0) {
739
+ note(
740
+ OBOGRAPHS_LOSS.EDGE_COLUMN_AS_META,
741
+ `edge column "${column.meta.name}" is written into each edge's meta and reads back inside the meta column`,
742
+ column.meta.name,
743
+ count,
744
+ );
745
+ }
746
+ }
747
+ const { weights } = this;
748
+ const weighted = weights.weighted ? written.filter((e) => weights.isExplicit(e)).length : 0;
749
+ if (weighted > 0) {
750
+ note(
751
+ OBOGRAPHS_LOSS.EDGE_COLUMN_AS_META,
752
+ `${weighted} explicit edge weight${plural(weighted)} ${agree(weighted, "is", "are")} written as "weight" in each edge's meta and read back inside the meta column, not as weights`,
753
+ snapshot.edges.byRole("weight")?.meta.name ?? null,
754
+ weighted,
755
+ );
756
+ }
757
+ this.planPreds(relation);
758
+ this.directionNotes(common);
759
+ }
760
+
761
+ /**
762
+ * The pred of every written edge, with the relation and id notes.
763
+ * @param relation - the relation column, or null
764
+ */
765
+ private planPreds(relation: Column | null): void {
766
+ let assumed = 0;
767
+ let relationChanges = 0;
768
+ for (const e of this.written) {
769
+ const value = relation === null ? undefined : cellOf(relation, e);
770
+ if (typeof value !== "string") {
771
+ assumed++;
772
+ this.preds.set(e, "is_a");
773
+ continue;
774
+ }
775
+ const { pred, exact } = this.predOf(value);
776
+ this.preds.set(e, pred);
777
+ relationChanges += exact ? 0 : 1;
778
+ }
779
+ if (assumed > 0) {
780
+ this.note(
781
+ RELATION_ASSUMED_CODE,
782
+ `${assumed} edge${plural(assumed)} ${agree(assumed, "has", "have")} no relation; written with the pred is_a (an ontology reads is_a as subclassing)`,
783
+ relation?.meta.name ?? null,
784
+ assumed,
785
+ );
786
+ }
787
+ const idChanges = this.rows.filter((i) => this.changedRows.has(i)).length;
788
+ if (idChanges + relationChanges > 0) {
789
+ this.note(
790
+ OBOGRAPHS_LOSS.ID_CHANGED,
791
+ `${idChanges} node id${plural(idChanges)} and ${relationChanges} relation${plural(relationChanges)} are written as IRIs the importer reads back as other ids under its default oboIds "curie"`,
792
+ null,
793
+ idChanges + relationChanges,
794
+ );
795
+ }
796
+ }
797
+
798
+ /**
799
+ * The direction notes: every OBO Graphs edge is directed.
800
+ * @param common - the common options
801
+ */
802
+ private directionNotes(common: ResolvedExportOptions): void {
803
+ const { snapshot, written, folding, note } = this;
804
+ if (!snapshot.directed) {
805
+ note(
806
+ DIRECTION_DROPPED_CODE,
807
+ `the snapshot is undirected; OBO Graphs edges are directed and read back directed (${written.length} edge${plural(written.length)})`,
808
+ null,
809
+ written.length,
810
+ );
811
+ } else if (common.onMixedDirection !== "error") {
812
+ const undirected = written.filter((e) => !folding.sourceDirected(e)).length;
813
+ if (undirected > 0) {
814
+ note(
815
+ DIRECTION_DROPPED_CODE,
816
+ `${undirected} undirected edge${plural(undirected)} ${agree(undirected, "is", "are")} written as one directed edge each`,
817
+ null,
818
+ undirected,
819
+ );
820
+ }
821
+ }
822
+ if (folding.mutualCount > 0) {
823
+ note(
824
+ LOSS.MUTUAL_EXPANDED,
825
+ `${folding.mutualCount} mutual pair${plural(folding.mutualCount)} ${agree(folding.mutualCount, "is", "are")} written as two edges; the mutual mark is lost`,
826
+ null,
827
+ folding.mutualCount,
828
+ );
829
+ }
830
+ }
831
+
832
+ /**
833
+ * A placed column's cell.
834
+ * @param name - the vocabulary name
835
+ * @param i - the node
836
+ * @returns the value, or undefined
837
+ */
838
+ private at(name: string, i: number): unknown {
839
+ const column = this.placed.get(name);
840
+ return column === undefined ? undefined : cellOf(column, i);
841
+ }
842
+
843
+ /**
844
+ * One node record as JSON.
845
+ * @param i - the node
846
+ * @returns the JSON text
847
+ */
848
+ private nodeRecord(i: number): string {
849
+ const node: Record<string, unknown> = { id: this.iris[i] };
850
+ const lbl = this.label === null ? undefined : cellOf(this.label, i);
851
+ if (typeof lbl === "string") {
852
+ node.lbl = lbl;
853
+ }
854
+ // without a type column every node is a class; the type also marks the document as OBO Graphs
855
+ const untyped = this.snapshot.nodes.get("type") === null ? "Term" : undefined;
856
+ const type = this.placed.has("type") ? this.at("type", i) : untyped;
857
+ if (typeof type === "string") {
858
+ node.type = NODE_TYPES[type] ?? type;
859
+ }
860
+ const propertyType = this.at("propertyType", i);
861
+ if (propertyType !== undefined) {
862
+ node.propertyType = propertyType;
863
+ }
864
+ const meta = this.nodeMeta(i);
865
+ const extra = this.at("obo.unrecognized", i);
866
+ if (isJsonObject(extra)) {
867
+ for (const [key, value] of Object.entries(extra)) {
868
+ if (key.startsWith("meta.")) {
869
+ meta[key.slice(5)] = value;
870
+ } else {
871
+ node[key] = value;
872
+ }
873
+ }
874
+ }
875
+ if (Object.keys(meta).length > 0) {
876
+ node.meta = meta;
877
+ }
878
+ return json(node, this.nonfinite);
879
+ }
880
+
881
+ /**
882
+ * One node's meta: definition, comments, subsets, xrefs, synonyms, deprecated and the
883
+ * basicPropertyValues.
884
+ * @param i - the node
885
+ * @returns the meta record
886
+ */
887
+ private nodeMeta(i: number): Record<string, unknown> {
888
+ const meta: Record<string, unknown> = {};
889
+ const def = this.at("def", i);
890
+ if (typeof def === "string") {
891
+ meta.definition = { val: def, xrefs: this.at("def.xrefs", i) ?? [] };
892
+ }
893
+ const comment = this.at("comment", i);
894
+ if (typeof comment === "string") {
895
+ meta.comments = [comment];
896
+ }
897
+ const subsets = this.at("subset", i);
898
+ if (Array.isArray(subsets)) {
899
+ meta.subsets = subsets.map((s) => this.iriOf(String(s)).iri);
900
+ }
901
+ const xrefs = this.at("xref", i);
902
+ if (Array.isArray(xrefs)) {
903
+ meta.xrefs = xrefs.map((val) => ({ val }));
904
+ }
905
+ const synonyms = this.at("synonym", i);
906
+ if (Array.isArray(synonyms)) {
907
+ meta.synonyms = (synonyms as Synonym[]).map((s) => this.synonymRecord(s));
908
+ }
909
+ const obsolete = this.at("is_obsolete", i);
910
+ if (typeof obsolete === "boolean") {
911
+ meta.deprecated = obsolete;
912
+ }
913
+ const values = this.propertyValues(i);
914
+ if (values.length > 0) {
915
+ meta.basicPropertyValues = values;
916
+ }
917
+ return meta;
918
+ }
919
+
920
+ /**
921
+ * One synonym record.
922
+ * @param s - the synonym
923
+ * @returns the record
924
+ */
925
+ private synonymRecord(s: Synonym): Record<string, unknown> {
926
+ const out: Record<string, unknown> = {
927
+ pred: s.scope === null ? "hasSynonym" : `has${s.scope[0]}${s.scope.slice(1).toLowerCase()}Synonym`,
928
+ };
929
+ if (s.text !== null) {
930
+ out.val = s.text;
931
+ }
932
+ if (s.xrefs.length > 0) {
933
+ out.xrefs = s.xrefs;
934
+ }
935
+ if (s.type !== null) {
936
+ out.synonymType = this.iriOf(s.type).iri;
937
+ }
938
+ if (s.meta !== undefined) {
939
+ out.meta = s.meta;
940
+ }
941
+ return out;
942
+ }
943
+
944
+ /**
945
+ * One node's basicPropertyValues: the tags OBO Graphs writes as annotations, the Typedef
946
+ * shorthand, the property values, then the columns outside the vocabulary.
947
+ * @param i - the node
948
+ * @returns the records
949
+ */
950
+ private propertyValues(i: number): Record<string, unknown>[] {
951
+ const values: Record<string, unknown>[] = [];
952
+ for (const tag of PROPERTY_TAGS) {
953
+ const v = this.at(tag, i);
954
+ const pred = TAG_PREDICATES.get(tag) ?? tag;
955
+ for (const item of valuesOf(v)) {
956
+ const text = String(item);
957
+ values.push({ pred, val: tag === "replaced_by" || tag === "consider" ? this.iriOf(text).iri : text });
958
+ }
959
+ }
960
+ const shorthand = this.typedefShorthands.get(i);
961
+ if (shorthand !== undefined) {
962
+ values.push({ pred: `${OBO_IN_OWL}shorthand`, val: shorthand });
963
+ }
964
+ for (const item of (this.at("property_value", i) ?? []) as { relation: string; value: string }[]) {
965
+ values.push({ pred: this.iriOf(item.relation).iri, val: item.value });
966
+ }
967
+ for (const column of this.properties) {
968
+ for (const val of propertyTexts(column, i)) {
969
+ values.push({ pred: column.meta.name, val });
970
+ }
971
+ }
972
+ return values;
973
+ }
974
+
975
+ /**
976
+ * One edge record as JSON.
977
+ * @param e - the edge
978
+ * @returns the JSON text
979
+ */
980
+ private edgeRecord(e: number): string {
981
+ const { src, dst } = this.ends;
982
+ const edge: Record<string, unknown> = {
983
+ sub: this.iris[src[e]],
984
+ pred: this.preds.get(e),
985
+ obj: this.iris[dst[e]],
986
+ };
987
+ const base = this.metaColumn === null ? undefined : cellOf(this.metaColumn, e);
988
+ const meta: Record<string, unknown> = isJsonObject(base) ? { ...base } : {};
989
+ for (const column of this.edgeColumns) {
990
+ const v = cellOf(column, e);
991
+ if (v !== undefined) {
992
+ meta[column.meta.name] = v;
993
+ }
994
+ }
995
+ if (this.weights.weighted && this.weights.isExplicit(e)) {
996
+ meta.weight = this.weights.value(e);
997
+ }
998
+ if (Object.keys(meta).length > 0 || base !== undefined) {
999
+ edge.meta = meta;
1000
+ }
1001
+ return json(edge, this.nonfinite);
1002
+ }
1003
+
1004
+ /**
1005
+ * The graph record up to its nodes: the id, the label and the meta (the graph columns as
1006
+ * basicPropertyValues).
1007
+ * @returns the JSON text without its closing brace
1008
+ */
1009
+ private graphHead(): string {
1010
+ const { kept, snapshot } = this;
1011
+ const graph: Record<string, unknown> = {
1012
+ id: typeof kept.graph.id === "string" ? kept.graph.id : this.ontologyIri,
1013
+ };
1014
+ const named = snapshot.meta.name !== null && kept.graph.id === undefined;
1015
+ const lbl = kept.graph.lbl ?? (named ? snapshot.meta.name : undefined);
1016
+ if (lbl !== undefined) {
1017
+ graph.lbl = lbl;
1018
+ }
1019
+ const meta: Record<string, unknown> = isJsonObject(kept.graph.meta) ? { ...kept.graph.meta } : {};
1020
+ const values: unknown[] = Array.isArray(meta.basicPropertyValues)
1021
+ ? [...(meta.basicPropertyValues as unknown[])]
1022
+ : [];
1023
+ values.push(...graphPropertyValues(snapshot));
1024
+ if (values.length > 0) {
1025
+ meta.basicPropertyValues = values;
1026
+ }
1027
+ if (Object.keys(meta).length > 0) {
1028
+ graph.meta = meta;
1029
+ }
1030
+ return json(graph, this.nonfinite).slice(0, -1);
1031
+ }
1032
+
1033
+ /**
1034
+ * The graph keys after the edges (the kept axioms and other keys).
1035
+ * @returns the JSON text from the comma to the closing brace
1036
+ */
1037
+ private graphTail(): string {
1038
+ const rest: Record<string, unknown> = {};
1039
+ for (const [key, value] of Object.entries(this.kept.graph)) {
1040
+ if (key !== "id" && key !== "lbl" && key !== "meta" && key !== "nodes" && key !== "edges") {
1041
+ rest[key] = value;
1042
+ }
1043
+ }
1044
+ const text = json(rest, this.nonfinite);
1045
+ return text === "{}" ? "}" : `,${text.slice(1)}`;
1046
+ }
1047
+
1048
+ /**
1049
+ * The kept PROPERTY nodes no written node stands for.
1050
+ * @returns their JSON texts
1051
+ */
1052
+ private propertyNodes(): string[] {
1053
+ const writtenIris = new Set(this.rows.map((i) => this.iris[i]));
1054
+ const out: string[] = [];
1055
+ for (const [iri, entry] of Object.entries(this.kept.properties)) {
1056
+ if (writtenIris.has(iri)) {
1057
+ continue;
1058
+ }
1059
+ const node: Record<string, unknown> = { id: iri, lbl: entry.lbl, type: "PROPERTY" };
1060
+ node.propertyType = entry.propertyType;
1061
+ node.meta = entry.meta;
1062
+ out.push(json(node, this.nonfinite));
1063
+ }
1064
+ return out;
1065
+ }
1066
+
1067
+ /**
1068
+ * The document's text parts.
1069
+ * @yields the graph head, one part per node and edge, the tail
1070
+ * @returns nothing
1071
+ */
1072
+ *write(): Generator<string, void, undefined> {
1073
+ if (this.fatal !== null) {
1074
+ throw this.fatal;
1075
+ }
1076
+ const { indent } = this;
1077
+ yield `{"graphs":[${this.graphHead()},"nodes":[`;
1078
+ let first = true;
1079
+ const item = (text: string): string => {
1080
+ const out = `${first ? "" : ","}${indent}${text}`;
1081
+ first = false;
1082
+ return out;
1083
+ };
1084
+ for (const i of this.rows) {
1085
+ yield item(this.nodeRecord(i));
1086
+ }
1087
+ for (const node of this.propertyNodes()) {
1088
+ yield item(node);
1089
+ }
1090
+ yield `],"edges":[`;
1091
+ first = true;
1092
+ for (const e of this.written) {
1093
+ yield item(this.edgeRecord(e));
1094
+ }
1095
+ for (const edge of this.kept.propertyEdges) {
1096
+ yield item(json(edge, this.nonfinite));
1097
+ }
1098
+ yield `]${this.graphTail()}]`;
1099
+ const document = json(this.kept.document, this.nonfinite);
1100
+ yield document === "{}" ? "}\n" : `,${document.slice(1)}\n`;
1101
+ }
1102
+ }
1103
+
1104
+ /**
1105
+ * Plan an OBO Graphs export.
1106
+ * @param snapshot - the snapshot
1107
+ * @param settings - the resolved settings
1108
+ * @returns the notes and the writer
1109
+ * @category Plugin helpers
1110
+ */
1111
+ export function planObographs(snapshot: GraphSnapshot, settings: ObographsSettings): ObographsPlan {
1112
+ const plan = new ObographsExport(snapshot, settings);
1113
+ return { notes: plan.notes, write: () => plan.write() };
1114
+ }
1115
+
1116
+ /**
1117
+ * Whether a vocabulary column is written into its OBO Graphs place and read back unchanged on
1118
+ * every written node.
1119
+ * @param name - the column name (a PLACED name)
1120
+ * @param column - the column
1121
+ * @param rows - the written nodes
1122
+ * @param reproducible - whether an id is written as an IRI that reads back as itself
1123
+ * @returns true when the place carries the column
1124
+ */
1125
+ function carries(
1126
+ name: string,
1127
+ column: Column,
1128
+ rows: readonly number[],
1129
+ reproducible: (id: unknown) => boolean,
1130
+ ): boolean {
1131
+ const spec = OBO_NODE_COLUMNS[name];
1132
+ if (!fitsVocabulary(spec.dtype, column) || column.meta.components > 1) {
1133
+ return false;
1134
+ }
1135
+ const valid = (v: unknown): boolean => {
1136
+ switch (name) {
1137
+ case "type":
1138
+ return typeof v === "string" && !Object.hasOwn(FRAME_TYPES, v);
1139
+ case "propertyType":
1140
+ case "def":
1141
+ case "comment":
1142
+ case "namespace":
1143
+ case "created_by":
1144
+ case "creation_date":
1145
+ return typeof v === "string";
1146
+ case "is_obsolete":
1147
+ return typeof v === "boolean";
1148
+ case "def.xrefs":
1149
+ return strings(v, true);
1150
+ case "xref":
1151
+ case "alt_id":
1152
+ return strings(v);
1153
+ case "subset":
1154
+ case "replaced_by":
1155
+ case "consider":
1156
+ return strings(v) && v.every(reproducible);
1157
+ case "synonym":
1158
+ return (
1159
+ Array.isArray(v) &&
1160
+ v.length > 0 &&
1161
+ v.every(
1162
+ (s) =>
1163
+ isJsonObject(s) &&
1164
+ Object.keys(s).length === ("meta" in s ? 5 : 4) &&
1165
+ (s.text === null || typeof s.text === "string") &&
1166
+ (s.scope === null || (typeof s.scope === "string" && SYNONYM_SCOPES.has(s.scope))) &&
1167
+ (s.type === null || reproducible(s.type)) &&
1168
+ strings(s.xrefs, true),
1169
+ )
1170
+ );
1171
+ case "property_value":
1172
+ return (
1173
+ Array.isArray(v) &&
1174
+ v.length > 0 &&
1175
+ v.every(
1176
+ (x) =>
1177
+ isJsonObject(x) &&
1178
+ Object.keys(x).length === 3 &&
1179
+ reproducible(x.relation) &&
1180
+ !OBOGRAPHS_PREDICATE_TAGS.has(String(x.relation)) &&
1181
+ typeof x.value === "string" &&
1182
+ (x.datatype === null || typeof x.datatype === "string"),
1183
+ )
1184
+ );
1185
+ case "obo.unrecognized":
1186
+ return (
1187
+ isJsonObject(v) &&
1188
+ Object.keys(v).every((key) =>
1189
+ key.startsWith("meta.") ? !META_KEYS.has(key.slice(5)) && key.length > 5 : !NODE_KEYS.has(key),
1190
+ )
1191
+ );
1192
+ default:
1193
+ return false;
1194
+ }
1195
+ };
1196
+ return rows.every((i) => !column.isSet(i) || valid(cellOf(column, i)));
1197
+ }