@graphty/graph-io 0.0.0 → 0.2.1

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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -28
  3. package/dist/chunks/children-CL3Cy0ez.js +238 -0
  4. package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
  5. package/dist/chunks/escape-DyI8JofU.js +938 -0
  6. package/dist/chunks/escape-DyI8JofU.js.map +1 -0
  7. package/dist/chunks/importer-CQnJuWJw.js +2987 -0
  8. package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
  9. package/dist/chunks/importer-CpCpfbxr.js +2015 -0
  10. package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
  11. package/dist/chunks/importer-DbnGYr3_.js +2342 -0
  12. package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
  13. package/dist/chunks/importer-GozH8DkN.js +3050 -0
  14. package/dist/chunks/importer-GozH8DkN.js.map +1 -0
  15. package/dist/chunks/records-CGpxszm1.js +605 -0
  16. package/dist/chunks/records-CGpxszm1.js.map +1 -0
  17. package/dist/chunks/text-CajMdVFy.js +189 -0
  18. package/dist/chunks/text-CajMdVFy.js.map +1 -0
  19. package/dist/chunks/writer-DxSKC7TL.js +2842 -0
  20. package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
  21. package/dist/csv.d.ts +1 -0
  22. package/dist/csv.js +1702 -0
  23. package/dist/csv.js.map +1 -0
  24. package/dist/dot.d.ts +1 -0
  25. package/dist/dot.js +8 -0
  26. package/dist/dot.js.map +1 -0
  27. package/dist/gexf.d.ts +1 -0
  28. package/dist/gexf.js +3466 -0
  29. package/dist/gexf.js.map +1 -0
  30. package/dist/gml.d.ts +1 -0
  31. package/dist/gml.js +2647 -0
  32. package/dist/gml.js.map +1 -0
  33. package/dist/graph-io.d.ts +1 -0
  34. package/dist/graph-io.js +790 -0
  35. package/dist/graph-io.js.map +1 -0
  36. package/dist/graphml.d.ts +1 -0
  37. package/dist/graphml.js +8 -0
  38. package/dist/graphml.js.map +1 -0
  39. package/dist/json.d.ts +1 -0
  40. package/dist/json.js +11 -0
  41. package/dist/json.js.map +1 -0
  42. package/dist/neo4j.d.ts +1 -0
  43. package/dist/neo4j.js +2046 -0
  44. package/dist/neo4j.js.map +1 -0
  45. package/dist/pajek.d.ts +1 -0
  46. package/dist/pajek.js +8 -0
  47. package/dist/pajek.js.map +1 -0
  48. package/dist/src/children.d.ts +134 -0
  49. package/dist/src/children.d.ts.map +1 -0
  50. package/dist/src/children.js +274 -0
  51. package/dist/src/children.js.map +1 -0
  52. package/dist/src/common/attributes.d.ts +229 -0
  53. package/dist/src/common/attributes.d.ts.map +1 -0
  54. package/dist/src/common/attributes.js +368 -0
  55. package/dist/src/common/attributes.js.map +1 -0
  56. package/dist/src/common/codes.d.ts +105 -0
  57. package/dist/src/common/codes.d.ts.map +1 -0
  58. package/dist/src/common/codes.js +107 -0
  59. package/dist/src/common/codes.js.map +1 -0
  60. package/dist/src/common/declared-types.d.ts +84 -0
  61. package/dist/src/common/declared-types.d.ts.map +1 -0
  62. package/dist/src/common/declared-types.js +326 -0
  63. package/dist/src/common/declared-types.js.map +1 -0
  64. package/dist/src/common/direction.d.ts +206 -0
  65. package/dist/src/common/direction.d.ts.map +1 -0
  66. package/dist/src/common/direction.js +370 -0
  67. package/dist/src/common/direction.js.map +1 -0
  68. package/dist/src/common/escape.d.ts +92 -0
  69. package/dist/src/common/escape.d.ts.map +1 -0
  70. package/dist/src/common/escape.js +212 -0
  71. package/dist/src/common/escape.js.map +1 -0
  72. package/dist/src/common/export.d.ts +249 -0
  73. package/dist/src/common/export.d.ts.map +1 -0
  74. package/dist/src/common/export.js +594 -0
  75. package/dist/src/common/export.js.map +1 -0
  76. package/dist/src/common/format.d.ts +59 -0
  77. package/dist/src/common/format.d.ts.map +1 -0
  78. package/dist/src/common/format.js +106 -0
  79. package/dist/src/common/format.js.map +1 -0
  80. package/dist/src/common/ids.d.ts +83 -0
  81. package/dist/src/common/ids.d.ts.map +1 -0
  82. package/dist/src/common/ids.js +158 -0
  83. package/dist/src/common/ids.js.map +1 -0
  84. package/dist/src/common/input.d.ts +100 -0
  85. package/dist/src/common/input.d.ts.map +1 -0
  86. package/dist/src/common/input.js +335 -0
  87. package/dist/src/common/input.js.map +1 -0
  88. package/dist/src/common/lists.d.ts +34 -0
  89. package/dist/src/common/lists.d.ts.map +1 -0
  90. package/dist/src/common/lists.js +185 -0
  91. package/dist/src/common/lists.js.map +1 -0
  92. package/dist/src/common/options.d.ts +108 -0
  93. package/dist/src/common/options.d.ts.map +1 -0
  94. package/dist/src/common/options.js +265 -0
  95. package/dist/src/common/options.js.map +1 -0
  96. package/dist/src/common/report.d.ts +187 -0
  97. package/dist/src/common/report.d.ts.map +1 -0
  98. package/dist/src/common/report.js +274 -0
  99. package/dist/src/common/report.js.map +1 -0
  100. package/dist/src/common/temporal.d.ts +71 -0
  101. package/dist/src/common/temporal.d.ts.map +1 -0
  102. package/dist/src/common/temporal.js +266 -0
  103. package/dist/src/common/temporal.js.map +1 -0
  104. package/dist/src/common/text.d.ts +104 -0
  105. package/dist/src/common/text.d.ts.map +1 -0
  106. package/dist/src/common/text.js +255 -0
  107. package/dist/src/common/text.js.map +1 -0
  108. package/dist/src/common/weights.d.ts +77 -0
  109. package/dist/src/common/weights.d.ts.map +1 -0
  110. package/dist/src/common/weights.js +156 -0
  111. package/dist/src/common/weights.js.map +1 -0
  112. package/dist/src/common/writer.d.ts +51 -0
  113. package/dist/src/common/writer.d.ts.map +1 -0
  114. package/dist/src/common/writer.js +108 -0
  115. package/dist/src/common/writer.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +245 -0
  117. package/dist/src/common/xml.d.ts.map +1 -0
  118. package/dist/src/common/xml.js +942 -0
  119. package/dist/src/common/xml.js.map +1 -0
  120. package/dist/src/formats/csv/exporter.d.ts +70 -0
  121. package/dist/src/formats/csv/exporter.d.ts.map +1 -0
  122. package/dist/src/formats/csv/exporter.js +682 -0
  123. package/dist/src/formats/csv/exporter.js.map +1 -0
  124. package/dist/src/formats/csv/header.d.ts +66 -0
  125. package/dist/src/formats/csv/header.d.ts.map +1 -0
  126. package/dist/src/formats/csv/header.js +152 -0
  127. package/dist/src/formats/csv/header.js.map +1 -0
  128. package/dist/src/formats/csv/importer.d.ts +82 -0
  129. package/dist/src/formats/csv/importer.d.ts.map +1 -0
  130. package/dist/src/formats/csv/importer.js +849 -0
  131. package/dist/src/formats/csv/importer.js.map +1 -0
  132. package/dist/src/formats/csv/index.d.ts +60 -0
  133. package/dist/src/formats/csv/index.d.ts.map +1 -0
  134. package/dist/src/formats/csv/index.js +63 -0
  135. package/dist/src/formats/csv/index.js.map +1 -0
  136. package/dist/src/formats/csv/records.d.ts +188 -0
  137. package/dist/src/formats/csv/records.d.ts.map +1 -0
  138. package/dist/src/formats/csv/records.js +702 -0
  139. package/dist/src/formats/csv/records.js.map +1 -0
  140. package/dist/src/formats/csv/values.d.ts +105 -0
  141. package/dist/src/formats/csv/values.d.ts.map +1 -0
  142. package/dist/src/formats/csv/values.js +192 -0
  143. package/dist/src/formats/csv/values.js.map +1 -0
  144. package/dist/src/formats/dot/exporter.d.ts +52 -0
  145. package/dist/src/formats/dot/exporter.d.ts.map +1 -0
  146. package/dist/src/formats/dot/exporter.js +836 -0
  147. package/dist/src/formats/dot/exporter.js.map +1 -0
  148. package/dist/src/formats/dot/importer.d.ts +102 -0
  149. package/dist/src/formats/dot/importer.d.ts.map +1 -0
  150. package/dist/src/formats/dot/importer.js +1291 -0
  151. package/dist/src/formats/dot/importer.js.map +1 -0
  152. package/dist/src/formats/dot/index.d.ts +7 -0
  153. package/dist/src/formats/dot/index.d.ts.map +1 -0
  154. package/dist/src/formats/dot/index.js +7 -0
  155. package/dist/src/formats/dot/index.js.map +1 -0
  156. package/dist/src/formats/dot/names.d.ts +29 -0
  157. package/dist/src/formats/dot/names.d.ts.map +1 -0
  158. package/dist/src/formats/dot/names.js +28 -0
  159. package/dist/src/formats/dot/names.js.map +1 -0
  160. package/dist/src/formats/dot/tokenizer.d.ts +114 -0
  161. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
  162. package/dist/src/formats/dot/tokenizer.js +341 -0
  163. package/dist/src/formats/dot/tokenizer.js.map +1 -0
  164. package/dist/src/formats/gexf/exporter.d.ts +56 -0
  165. package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
  166. package/dist/src/formats/gexf/exporter.js +1395 -0
  167. package/dist/src/formats/gexf/exporter.js.map +1 -0
  168. package/dist/src/formats/gexf/importer.d.ts +73 -0
  169. package/dist/src/formats/gexf/importer.d.ts.map +1 -0
  170. package/dist/src/formats/gexf/importer.js +1880 -0
  171. package/dist/src/formats/gexf/importer.js.map +1 -0
  172. package/dist/src/formats/gexf/index.d.ts +96 -0
  173. package/dist/src/formats/gexf/index.d.ts.map +1 -0
  174. package/dist/src/formats/gexf/index.js +97 -0
  175. package/dist/src/formats/gexf/index.js.map +1 -0
  176. package/dist/src/formats/gexf/schema.d.ts +135 -0
  177. package/dist/src/formats/gexf/schema.d.ts.map +1 -0
  178. package/dist/src/formats/gexf/schema.js +323 -0
  179. package/dist/src/formats/gexf/schema.js.map +1 -0
  180. package/dist/src/formats/gml/exporter.d.ts +69 -0
  181. package/dist/src/formats/gml/exporter.d.ts.map +1 -0
  182. package/dist/src/formats/gml/exporter.js +1093 -0
  183. package/dist/src/formats/gml/exporter.js.map +1 -0
  184. package/dist/src/formats/gml/importer.d.ts +66 -0
  185. package/dist/src/formats/gml/importer.d.ts.map +1 -0
  186. package/dist/src/formats/gml/importer.js +1331 -0
  187. package/dist/src/formats/gml/importer.js.map +1 -0
  188. package/dist/src/formats/gml/index.d.ts +85 -0
  189. package/dist/src/formats/gml/index.d.ts.map +1 -0
  190. package/dist/src/formats/gml/index.js +88 -0
  191. package/dist/src/formats/gml/index.js.map +1 -0
  192. package/dist/src/formats/gml/syntax.d.ts +186 -0
  193. package/dist/src/formats/gml/syntax.d.ts.map +1 -0
  194. package/dist/src/formats/gml/syntax.js +467 -0
  195. package/dist/src/formats/gml/syntax.js.map +1 -0
  196. package/dist/src/formats/graphml/constants.d.ts +169 -0
  197. package/dist/src/formats/graphml/constants.d.ts.map +1 -0
  198. package/dist/src/formats/graphml/constants.js +165 -0
  199. package/dist/src/formats/graphml/constants.js.map +1 -0
  200. package/dist/src/formats/graphml/exporter.d.ts +34 -0
  201. package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
  202. package/dist/src/formats/graphml/exporter.js +1176 -0
  203. package/dist/src/formats/graphml/exporter.js.map +1 -0
  204. package/dist/src/formats/graphml/importer.d.ts +31 -0
  205. package/dist/src/formats/graphml/importer.d.ts.map +1 -0
  206. package/dist/src/formats/graphml/importer.js +1607 -0
  207. package/dist/src/formats/graphml/importer.js.map +1 -0
  208. package/dist/src/formats/graphml/index.d.ts +8 -0
  209. package/dist/src/formats/graphml/index.d.ts.map +1 -0
  210. package/dist/src/formats/graphml/index.js +8 -0
  211. package/dist/src/formats/graphml/index.js.map +1 -0
  212. package/dist/src/formats/graphml/tree.d.ts +72 -0
  213. package/dist/src/formats/graphml/tree.d.ts.map +1 -0
  214. package/dist/src/formats/graphml/tree.js +290 -0
  215. package/dist/src/formats/graphml/tree.js.map +1 -0
  216. package/dist/src/formats/json/dialect.d.ts +125 -0
  217. package/dist/src/formats/json/dialect.d.ts.map +1 -0
  218. package/dist/src/formats/json/dialect.js +262 -0
  219. package/dist/src/formats/json/dialect.js.map +1 -0
  220. package/dist/src/formats/json/exporter.d.ts +89 -0
  221. package/dist/src/formats/json/exporter.d.ts.map +1 -0
  222. package/dist/src/formats/json/exporter.js +1358 -0
  223. package/dist/src/formats/json/exporter.js.map +1 -0
  224. package/dist/src/formats/json/importer.d.ts +108 -0
  225. package/dist/src/formats/json/importer.d.ts.map +1 -0
  226. package/dist/src/formats/json/importer.js +1838 -0
  227. package/dist/src/formats/json/importer.js.map +1 -0
  228. package/dist/src/formats/json/index.d.ts +8 -0
  229. package/dist/src/formats/json/index.d.ts.map +1 -0
  230. package/dist/src/formats/json/index.js +8 -0
  231. package/dist/src/formats/json/index.js.map +1 -0
  232. package/dist/src/formats/neo4j/exporter.d.ts +68 -0
  233. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
  234. package/dist/src/formats/neo4j/exporter.js +1055 -0
  235. package/dist/src/formats/neo4j/exporter.js.map +1 -0
  236. package/dist/src/formats/neo4j/header.d.ts +52 -0
  237. package/dist/src/formats/neo4j/header.d.ts.map +1 -0
  238. package/dist/src/formats/neo4j/header.js +131 -0
  239. package/dist/src/formats/neo4j/header.js.map +1 -0
  240. package/dist/src/formats/neo4j/importer.d.ts +73 -0
  241. package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
  242. package/dist/src/formats/neo4j/importer.js +932 -0
  243. package/dist/src/formats/neo4j/importer.js.map +1 -0
  244. package/dist/src/formats/neo4j/index.d.ts +79 -0
  245. package/dist/src/formats/neo4j/index.d.ts.map +1 -0
  246. package/dist/src/formats/neo4j/index.js +83 -0
  247. package/dist/src/formats/neo4j/index.js.map +1 -0
  248. package/dist/src/formats/pajek/exporter.d.ts +58 -0
  249. package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
  250. package/dist/src/formats/pajek/exporter.js +825 -0
  251. package/dist/src/formats/pajek/exporter.js.map +1 -0
  252. package/dist/src/formats/pajek/importer.d.ts +88 -0
  253. package/dist/src/formats/pajek/importer.d.ts.map +1 -0
  254. package/dist/src/formats/pajek/importer.js +1047 -0
  255. package/dist/src/formats/pajek/importer.js.map +1 -0
  256. package/dist/src/formats/pajek/index.d.ts +7 -0
  257. package/dist/src/formats/pajek/index.d.ts.map +1 -0
  258. package/dist/src/formats/pajek/index.js +7 -0
  259. package/dist/src/formats/pajek/index.js.map +1 -0
  260. package/dist/src/formats/pajek/syntax.d.ts +112 -0
  261. package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
  262. package/dist/src/formats/pajek/syntax.js +269 -0
  263. package/dist/src/formats/pajek/syntax.js.map +1 -0
  264. package/dist/src/index.d.ts +35 -0
  265. package/dist/src/index.d.ts.map +1 -0
  266. package/dist/src/index.js +39 -0
  267. package/dist/src/index.js.map +1 -0
  268. package/dist/src/registry.d.ts +207 -0
  269. package/dist/src/registry.d.ts.map +1 -0
  270. package/dist/src/registry.js +481 -0
  271. package/dist/src/registry.js.map +1 -0
  272. package/dist/src/sniff.d.ts +104 -0
  273. package/dist/src/sniff.d.ts.map +1 -0
  274. package/dist/src/sniff.js +357 -0
  275. package/dist/src/sniff.js.map +1 -0
  276. package/dist/src/types.d.ts +238 -0
  277. package/dist/src/types.d.ts.map +1 -0
  278. package/dist/src/types.js +29 -0
  279. package/dist/src/types.js.map +1 -0
  280. package/dist/tsconfig.build.tsbuildinfo +1 -0
  281. package/package.json +122 -7
  282. package/src/children.ts +335 -0
  283. package/src/common/attributes.ts +520 -0
  284. package/src/common/codes.ts +153 -0
  285. package/src/common/declared-types.ts +374 -0
  286. package/src/common/direction.ts +518 -0
  287. package/src/common/escape.ts +231 -0
  288. package/src/common/export.ts +817 -0
  289. package/src/common/format.ts +111 -0
  290. package/src/common/ids.ts +176 -0
  291. package/src/common/input.ts +378 -0
  292. package/src/common/lists.ts +196 -0
  293. package/src/common/options.ts +377 -0
  294. package/src/common/report.ts +352 -0
  295. package/src/common/temporal.ts +302 -0
  296. package/src/common/text.ts +294 -0
  297. package/src/common/weights.ts +202 -0
  298. package/src/common/writer.ts +123 -0
  299. package/src/common/xml.ts +1053 -0
  300. package/src/formats/csv/exporter.ts +894 -0
  301. package/src/formats/csv/header.ts +172 -0
  302. package/src/formats/csv/importer.ts +1104 -0
  303. package/src/formats/csv/index.ts +88 -0
  304. package/src/formats/csv/records.ts +813 -0
  305. package/src/formats/csv/values.ts +224 -0
  306. package/src/formats/dot/exporter.ts +1014 -0
  307. package/src/formats/dot/importer.ts +1549 -0
  308. package/src/formats/dot/index.ts +7 -0
  309. package/src/formats/dot/names.ts +40 -0
  310. package/src/formats/dot/tokenizer.ts +384 -0
  311. package/src/formats/gexf/exporter.ts +1696 -0
  312. package/src/formats/gexf/importer.ts +2333 -0
  313. package/src/formats/gexf/index.ts +142 -0
  314. package/src/formats/gexf/schema.ts +361 -0
  315. package/src/formats/gml/exporter.ts +1404 -0
  316. package/src/formats/gml/importer.ts +1591 -0
  317. package/src/formats/gml/index.ts +128 -0
  318. package/src/formats/gml/syntax.ts +545 -0
  319. package/src/formats/graphml/constants.ts +225 -0
  320. package/src/formats/graphml/exporter.ts +1458 -0
  321. package/src/formats/graphml/importer.ts +2027 -0
  322. package/src/formats/graphml/index.ts +8 -0
  323. package/src/formats/graphml/tree.ts +318 -0
  324. package/src/formats/json/dialect.ts +317 -0
  325. package/src/formats/json/exporter.ts +1616 -0
  326. package/src/formats/json/importer.ts +2271 -0
  327. package/src/formats/json/index.ts +8 -0
  328. package/src/formats/neo4j/exporter.ts +1287 -0
  329. package/src/formats/neo4j/header.ts +156 -0
  330. package/src/formats/neo4j/importer.ts +1220 -0
  331. package/src/formats/neo4j/index.ts +116 -0
  332. package/src/formats/pajek/exporter.ts +1000 -0
  333. package/src/formats/pajek/importer.ts +1311 -0
  334. package/src/formats/pajek/index.ts +7 -0
  335. package/src/formats/pajek/syntax.ts +307 -0
  336. package/src/index.ts +244 -0
  337. package/src/registry.ts +617 -0
  338. package/src/sniff.ts +397 -0
  339. package/src/types.ts +262 -0
@@ -0,0 +1,2987 @@
1
+ import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
2
+ import { a9 as capabilities, L as LOSS, au as joinText, ah as encodeChunks, aa as checkCapabilities, aB as resolveExportOptions, aG as countMixedEdges, ai as explicitWeights, aw as pairFolding, ak as formatF32, J as OPTION_IGNORED_CODE, W as SINK_OPTION_CODE, H as HYPEREDGE_CODE, y as MULTIPLE_GRAPHS_CODE, n as ID_MERGED_CODE, i as DUPLICATE_EDGE_ID_CODE, k as DUPLICATE_NODE_CODE, a3 as UNKNOWN_PARENT_CODE, M as MISSING_ENDPOINT_CODE, w as MISSING_ID_CODE, Z as SYNTAX_CODE, m as EMPTY_INPUT_CODE, aC as resolveImportOptions, I as ImportReportBuilder, ay as readText, az as reportSinkOptions, aA as reportUnusedOptions, a as throwIfAborted, s as IdCoercer, l as DirectionResolver, aH as weightFromValue, aI as uniqueColumnName } from "./writer-DxSKC7TL.js";
3
+ const JSON_DIALECTS = Object.freeze([
4
+ "node-link",
5
+ "d3",
6
+ "jgf",
7
+ "cytoscape",
8
+ "graphology",
9
+ "vis"
10
+ ]);
11
+ const META_KEY = "json";
12
+ function isJsonObject(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+ function hasKey(record, key) {
16
+ return Object.prototype.hasOwnProperty.call(record, key);
17
+ }
18
+ function isJsonDialect(value) {
19
+ return typeof value === "string" && JSON_DIALECTS.includes(value);
20
+ }
21
+ function sniffJsonDialect(root) {
22
+ if (Array.isArray(root)) {
23
+ const first = firstJsonObject(root);
24
+ return root.length === 0 || first !== null && isJsonObject(first.data) ? "cytoscape" : null;
25
+ }
26
+ if (!isJsonObject(root)) {
27
+ return null;
28
+ }
29
+ if (hasKey(root, "elements")) {
30
+ return "cytoscape";
31
+ }
32
+ if (isJsonObject(root.graph) && (hasKey(root.graph, "nodes") || hasKey(root.graph, "edges"))) {
33
+ return "jgf";
34
+ }
35
+ if (Array.isArray(root.graphs)) {
36
+ return "jgf";
37
+ }
38
+ if (!hasKey(root, "nodes") && !hasKey(root, "edges") && !hasKey(root, "links")) {
39
+ return null;
40
+ }
41
+ const firstNode = firstJsonObject(root.nodes);
42
+ const firstEdge = firstJsonObject(hasKey(root, "edges") ? root.edges : root.links);
43
+ if (isJsonObject(root.options) && (hasKey(root.options, "type") || hasKey(root.options, "multi"))) {
44
+ return "graphology";
45
+ }
46
+ if (firstNode !== null && hasKey(firstNode, "key") && !hasKey(firstNode, "id")) {
47
+ return "graphology";
48
+ }
49
+ if (firstEdge !== null && !hasKey(firstEdge, "from")) {
50
+ if (hasKey(firstEdge, "undirected") || isJsonObject(firstEdge.attributes)) {
51
+ return "graphology";
52
+ }
53
+ }
54
+ if (firstEdge !== null && hasKey(firstEdge, "from") && hasKey(firstEdge, "to") && !hasKey(firstEdge, "source")) {
55
+ return "vis";
56
+ }
57
+ const bare = !hasKey(root, "directed") && !hasKey(root, "multigraph") && !hasKey(root, "graph");
58
+ return bare && hasKey(root, "links") ? "d3" : "node-link";
59
+ }
60
+ function firstJsonObject(value) {
61
+ if (!Array.isArray(value)) {
62
+ return null;
63
+ }
64
+ const found = value.find((item) => isJsonObject(item));
65
+ return isJsonObject(found) ? found : null;
66
+ }
67
+ function shapeMetaOf(meta) {
68
+ const raw = meta.extra[META_KEY];
69
+ if (!isJsonObject(raw)) {
70
+ return {};
71
+ }
72
+ const text = (key) => typeof raw[key] === "string" ? raw[key] : void 0;
73
+ const flag = (key) => typeof raw[key] === "boolean" ? raw[key] : void 0;
74
+ const { nodeIdKey } = raw;
75
+ return {
76
+ dialect: isJsonDialect(raw.dialect) ? raw.dialect : void 0,
77
+ edgesKey: text("edgesKey"),
78
+ nodeIdKey: nodeIdKey === null || typeof nodeIdKey === "string" ? nodeIdKey : void 0,
79
+ indexLinks: flag("indexLinks"),
80
+ sourceKey: text("sourceKey"),
81
+ targetKey: text("targetKey"),
82
+ id: text("id"),
83
+ type: text("type"),
84
+ allowSelfLoops: flag("allowSelfLoops"),
85
+ cytoscape: isJsonObject(raw.cytoscape) ? raw.cytoscape : void 0
86
+ };
87
+ }
88
+ const DIALECT_DEFAULT_DIRECTED = Object.freeze({
89
+ "node-link": false,
90
+ d3: false,
91
+ jgf: true,
92
+ cytoscape: true,
93
+ graphology: true,
94
+ vis: false
95
+ });
96
+ const JSON_DTYPES = Object.freeze(["f64", "i32", "bool", "string"]);
97
+ const COMMON = {
98
+ multiEdges: true,
99
+ selfLoops: true,
100
+ idCharset: "any",
101
+ dtypes: JSON_DTYPES,
102
+ components: false,
103
+ lists: false,
104
+ json: true,
105
+ defaults: false,
106
+ options: false,
107
+ temporal: "none",
108
+ positions: false,
109
+ viz: false
110
+ };
111
+ const TABLES = Object.freeze({
112
+ "node-link": capabilities({
113
+ ...COMMON,
114
+ mixedDirection: false,
115
+ edgeIds: "none",
116
+ hierarchy: false,
117
+ graphAttributes: true
118
+ }),
119
+ d3: capabilities({
120
+ ...COMMON,
121
+ mixedDirection: false,
122
+ edgeIds: "none",
123
+ hierarchy: false,
124
+ graphAttributes: false
125
+ }),
126
+ jgf: capabilities({
127
+ ...COMMON,
128
+ mixedDirection: true,
129
+ edgeIds: "optional",
130
+ hierarchy: false,
131
+ graphAttributes: true
132
+ }),
133
+ cytoscape: capabilities({
134
+ ...COMMON,
135
+ positions: true,
136
+ mixedDirection: false,
137
+ edgeIds: "required",
138
+ hierarchy: true,
139
+ graphAttributes: true
140
+ }),
141
+ graphology: capabilities({
142
+ ...COMMON,
143
+ mixedDirection: true,
144
+ edgeIds: "optional",
145
+ hierarchy: false,
146
+ graphAttributes: true
147
+ }),
148
+ vis: capabilities({
149
+ ...COMMON,
150
+ mixedDirection: false,
151
+ edgeIds: "optional",
152
+ hierarchy: false,
153
+ graphAttributes: false
154
+ })
155
+ });
156
+ function dialectCapabilities(dialect) {
157
+ return TABLES[dialect];
158
+ }
159
+ const CYTOSCAPE_ELEMENT_KEYS = /* @__PURE__ */ new Set([
160
+ "selected",
161
+ "selectable",
162
+ "locked",
163
+ "grabbable",
164
+ "pannable",
165
+ "removed",
166
+ "scratch",
167
+ "renderedPosition"
168
+ ]);
169
+ const CYTOSCAPE_STRUCTURAL_KEYS = /* @__PURE__ */ new Set(["data", "group", "position", "classes"]);
170
+ const SUFFIX = Object.freeze({
171
+ /** A Cytoscape `data` key or a JGF / graphology attribute that collides with a structural column. */
172
+ data: "#data",
173
+ /** An element-level key the dialect does not define (Cytoscape, JGF, graphology). */
174
+ element: "#element"
175
+ });
176
+ const NODE_LINK_SOURCE_KEYS = Object.freeze(["source", "src", "from"]);
177
+ const NODE_LINK_TARGET_KEYS = Object.freeze(["target", "dst", "to"]);
178
+ const POSITION_COLUMN = "position";
179
+ const CLASSES_COLUMN = "classes";
180
+ const PARENT_COLUMN = "parent";
181
+ const JSON_LOSS = Object.freeze({
182
+ /** Non-finite numbers (columns, weights) are written as null. */
183
+ NONFINITE_AS_NULL: "W_NONFINITE_AS_NULL",
184
+ /** Cytoscape, vis and d3 carry no direction; the file re-imports with the dialect's default direction. */
185
+ DIRECTION_DROPPED: "W_DIRECTION_DROPPED",
186
+ /** GEXF mutual pairs are written as two directed edges. */
187
+ MUTUAL_EXPANDED: LOSS.MUTUAL_EXPANDED,
188
+ /** JGF keys its nodes by string; numeric ids re-import as text unless ids: "canonical". */
189
+ NUMERIC_IDS_STRINGIFIED: "W_NUMERIC_IDS_STRINGIFIED",
190
+ /** JGF: two ids have the same text; export() throws E_INVALID_ID. */
191
+ ID_TEXT_COLLISION: LOSS.ID_TEXT_COLLISION,
192
+ /** JGF: integer-like id keys are enumerated first and ascending by JSON parsers; node order changes on re-import. */
193
+ NODE_ORDER: "W_NODE_ORDER",
194
+ /** A column named like a reserved key of the dialect (id, source, target, ...) is skipped. */
195
+ RESERVED_KEY: "W_RESERVED_KEY",
196
+ /** A plain edge column named like the weight key reads back as THE weight (or is skipped when weights are written). */
197
+ WEIGHT_KEY_CLASH: LOSS.WEIGHT_KEY_CLASH,
198
+ /** A position column without a slot (every dialect but Cytoscape) is a plain array attribute; the role is lost. */
199
+ POSITIONS_DROPPED: LOSS.POSITIONS,
200
+ /** Cytoscape positions are 2D; non-zero z values are dropped. */
201
+ POSITION_Z_DROPPED: "W_POSITION_Z_DROPPED",
202
+ /** Cytoscape has a single parent; a `parents` list column cannot be written. */
203
+ PARENTS_DROPPED: LOSS.PARENTS,
204
+ /** node-link / d3 have no edge id slot; the id column is written as a plain attribute. */
205
+ EDGE_IDS_DROPPED: LOSS.EDGE_IDS_DROPPED,
206
+ /** An f64 column of integral values reads back as i32 (JSON declares no types). */
207
+ INTEGRAL_F64_AS_I32: LOSS.INTEGRAL_F64,
208
+ /** A role column the dialect has no slot for is a plain attribute; the role is lost. */
209
+ ROLE_DROPPED: LOSS.ROLE,
210
+ /** A column without a set cell is not written (JSON declares no columns). */
211
+ EMPTY_COLUMN_DROPPED: LOSS.EMPTY_COLUMN
212
+ });
213
+ const SLOT_ROLES = Object.freeze({
214
+ "node-link": /* @__PURE__ */ new Set(),
215
+ d3: /* @__PURE__ */ new Set(),
216
+ jgf: /* @__PURE__ */ new Set(["label", "kind"]),
217
+ cytoscape: /* @__PURE__ */ new Set(["classes"]),
218
+ graphology: /* @__PURE__ */ new Set(),
219
+ vis: /* @__PURE__ */ new Set()
220
+ });
221
+ const STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
222
+ "directed",
223
+ "pair",
224
+ "mutual",
225
+ "weight",
226
+ "timeText",
227
+ "originalId"
228
+ ]);
229
+ const TEMPORAL_ROLES = /* @__PURE__ */ new Set(["start", "end", "timestamp", "timestamps", "spells", "open"]);
230
+ const NESTED_DIALECTS = /* @__PURE__ */ new Set(["jgf", "cytoscape", "graphology"]);
231
+ function resolve(snapshot, options) {
232
+ const o = options ?? {};
233
+ const common = resolveExportOptions(options);
234
+ const shape = shapeMetaOf(snapshot.meta);
235
+ let dialect;
236
+ if (o.dialect === void 0) {
237
+ dialect = shape.dialect ?? "node-link";
238
+ } else if (isJsonDialect(o.dialect)) {
239
+ ({ dialect } = o);
240
+ } else {
241
+ throw unsupported("dialect", o.dialect, JSON_DIALECTS);
242
+ }
243
+ const indent = o.indent ?? 0;
244
+ if (!Number.isInteger(indent) || indent < 0 || indent > 16) {
245
+ throw unsupported("indent", indent, ["an integer 0..16"]);
246
+ }
247
+ const sameDialect = shape.dialect === dialect;
248
+ const recorded = (value, fallback) => sameDialect && value !== void 0 ? value : fallback;
249
+ const edgesKey = keyOption$1("edgesKey", o.edgesKey) ?? recorded(shape.edgesKey, dialect === "d3" ? "links" : "edges");
250
+ let nodeIdKey;
251
+ if (o.nodeIdKey !== void 0) {
252
+ nodeIdKey = keyOption$1("nodeIdKey", o.nodeIdKey);
253
+ } else if (sameDialect && shape.nodeIdKey !== void 0) {
254
+ ({ nodeIdKey } = shape);
255
+ } else {
256
+ nodeIdKey = "id";
257
+ }
258
+ let indexLinks = o.indexLinks ?? recorded(shape.indexLinks, false);
259
+ if (typeof indexLinks !== "boolean") {
260
+ throw unsupported("indexLinks", indexLinks, ["true", "false"]);
261
+ }
262
+ if (nodeIdKey === null) {
263
+ indexLinks = true;
264
+ }
265
+ const sourceKey = keyOption$1("sourceKey", o.sourceKey) ?? recorded(shape.sourceKey, dialect === "vis" ? "from" : "source");
266
+ const targetKey = keyOption$1("targetKey", o.targetKey) ?? recorded(shape.targetKey, dialect === "vis" ? "to" : "target");
267
+ const { weightOrigin } = snapshot.meta;
268
+ const weightKey = keyOption$1("weightKey", o.weightKey) ?? (weightOrigin !== null && weightOrigin.format === "json" && weightOrigin.id !== null ? weightOrigin.id : "weight");
269
+ return { common, dialect, indent, edgesKey, nodeIdKey, indexLinks, sourceKey, targetKey, weightKey, shape };
270
+ }
271
+ function keyOption$1(name, value) {
272
+ if (value === void 0) {
273
+ return null;
274
+ }
275
+ if (typeof value !== "string" || value.length === 0) {
276
+ throw unsupported(name, value, ["a non-empty key"]);
277
+ }
278
+ return value;
279
+ }
280
+ function unsupported(name, found, supported) {
281
+ const shown = typeof found === "string" ? JSON.stringify(found) : String(found);
282
+ return new GraphFormatError("E_UNSUPPORTED", `option ${name}: ${shown} is not one of ${supported.join(", ")}`, {
283
+ option: name,
284
+ found: typeof found === "string" || typeof found === "number" ? found : typeof found,
285
+ supported: [...supported]
286
+ });
287
+ }
288
+ function numberText(value, f32, nonfinite) {
289
+ if (!Number.isFinite(value)) {
290
+ nonfinite.count++;
291
+ return "null";
292
+ }
293
+ if (f32) {
294
+ return formatF32(value);
295
+ }
296
+ return Object.is(value, -0) ? "0" : String(value);
297
+ }
298
+ function valueText(value, f32, nonfinite) {
299
+ switch (typeof value) {
300
+ case "number":
301
+ return numberText(value, f32, nonfinite);
302
+ case "boolean":
303
+ return value ? "true" : "false";
304
+ case "string":
305
+ return JSON.stringify(value);
306
+ case "undefined":
307
+ return "null";
308
+ }
309
+ if (value === null) {
310
+ return "null";
311
+ }
312
+ if (Array.isArray(value) || ArrayBuffer.isView(value)) {
313
+ const items = Array.from(value, (item) => valueText(item, f32, nonfinite));
314
+ return `[${items.join(",")}]`;
315
+ }
316
+ const text = JSON.stringify(value);
317
+ return text === void 0 ? "null" : text;
318
+ }
319
+ function cellText(column, row, ids, nonfinite) {
320
+ switch (column.dtype) {
321
+ case "list":
322
+ return valueText(column.sliceOf(row), column.child.dtype === "f32", nonfinite);
323
+ case "json":
324
+ return valueText(column.values[row], false, nonfinite);
325
+ case "u32":
326
+ if (column.meta.refersTo === "node" && column.meta.components === 1) {
327
+ const index = column.data[row];
328
+ return index === INVALID_INDEX ? "null" : JSON.stringify(ids.idOf(index));
329
+ }
330
+ return valueText(column.value(row), false, nonfinite);
331
+ case "f32":
332
+ return valueText(column.value(row), true, nonfinite);
333
+ default:
334
+ return valueText(column.value(row), false, nonfinite);
335
+ }
336
+ }
337
+ function countNonFinite(column) {
338
+ let data;
339
+ switch (column.dtype) {
340
+ case "f32":
341
+ case "f64":
342
+ ({ data } = column);
343
+ break;
344
+ case "list":
345
+ if (column.child.dtype !== "f32" && column.child.dtype !== "f64") {
346
+ return 0;
347
+ }
348
+ ({ data } = column.child);
349
+ break;
350
+ default:
351
+ return 0;
352
+ }
353
+ let count = 0;
354
+ for (let i = 0; i < data.length; i++) {
355
+ if (!Number.isFinite(data[i])) {
356
+ count++;
357
+ }
358
+ }
359
+ return count;
360
+ }
361
+ class JsonWriter {
362
+ /**
363
+ * Create a writer.
364
+ * @param indent - spaces per level; 0 for compact output
365
+ */
366
+ constructor(indent) {
367
+ this.parts = [];
368
+ this.depth = 0;
369
+ this.firstAtDepth = [];
370
+ this.indent = indent;
371
+ }
372
+ /**
373
+ * Open an object or an array.
374
+ * @param bracket - "{" or "["
375
+ */
376
+ open(bracket) {
377
+ this.parts.push(bracket);
378
+ this.depth++;
379
+ this.firstAtDepth.push(true);
380
+ }
381
+ /**
382
+ * Close the innermost object or array.
383
+ * @param bracket - "}" or "]"
384
+ */
385
+ close(bracket) {
386
+ const wasEmpty = this.firstAtDepth.pop() ?? true;
387
+ this.depth--;
388
+ if (!wasEmpty) {
389
+ this.parts.push(this.newline());
390
+ }
391
+ this.parts.push(bracket);
392
+ }
393
+ /**
394
+ * Start an object member: the separator and the quoted key.
395
+ * @param name - the key
396
+ */
397
+ key(name) {
398
+ this.separator();
399
+ this.parts.push(JSON.stringify(name), this.indent > 0 ? ": " : ":");
400
+ }
401
+ /** Start an array element: the separator only. */
402
+ item() {
403
+ this.separator();
404
+ }
405
+ /**
406
+ * Append raw JSON text (a value).
407
+ * @param text - the text
408
+ */
409
+ raw(text) {
410
+ this.parts.push(text);
411
+ }
412
+ /**
413
+ * Write a member with a value in one call.
414
+ * @param name - the key
415
+ * @param text - the value's JSON text
416
+ */
417
+ member(name, text) {
418
+ this.key(name);
419
+ this.parts.push(text);
420
+ }
421
+ /**
422
+ * Hand out the text accumulated since the last take.
423
+ * @returns the text
424
+ */
425
+ take() {
426
+ const text = this.parts.length === 1 ? this.parts[0] : this.parts.join("");
427
+ this.parts = [];
428
+ return text;
429
+ }
430
+ /** The comma and newline before a member or element. */
431
+ separator() {
432
+ const top = this.firstAtDepth.length - 1;
433
+ if (this.firstAtDepth[top]) {
434
+ this.firstAtDepth[top] = false;
435
+ } else {
436
+ this.parts.push(",");
437
+ }
438
+ this.parts.push(this.newline());
439
+ }
440
+ /**
441
+ * A newline plus the current indentation, or nothing in compact mode.
442
+ * @returns the text
443
+ */
444
+ newline() {
445
+ return this.indent > 0 ? `
446
+ ${" ".repeat(this.depth * this.indent)}` : "";
447
+ }
448
+ }
449
+ function plan(snapshot, resolved) {
450
+ const { dialect } = resolved;
451
+ const caps = dialectCapabilities(dialect);
452
+ const notes = checkCapabilities(snapshot, caps, resolved.common, {
453
+ roles: SLOT_ROLES[dialect],
454
+ positionDtype: "f32"
455
+ }).filter((n) => n.code !== LOSS.POSITIONS && n.code !== LOSS.EDGE_IDS_DROPPED);
456
+ const note = (code, message, column = null, count = null) => {
457
+ notes.push(Object.freeze({ code, message, column, count }));
458
+ };
459
+ const ctx = {
460
+ snapshot,
461
+ resolved,
462
+ dialect,
463
+ caps,
464
+ notes,
465
+ note,
466
+ reserved: reservedKeys(snapshot, resolved),
467
+ nonfinite: { count: 0 }
468
+ };
469
+ const nodes = planTable(ctx, snapshot.nodes, "node");
470
+ const edges = planTable(ctx, snapshot.edges, "edge");
471
+ const graph = caps.graphAttributes ? planTable(ctx, snapshot.graph, "graph") : [];
472
+ withdrawSlotNotes(notes, [...nodes, ...edges]);
473
+ const weights = planWeights(ctx, edges);
474
+ if (ctx.nonfinite.count > 0) {
475
+ note(
476
+ JSON_LOSS.NONFINITE_AS_NULL,
477
+ `${ctx.nonfinite.count} non-finite number(s) are written as null`,
478
+ null,
479
+ ctx.nonfinite.count
480
+ );
481
+ }
482
+ const { folding, directed } = planDirection(ctx);
483
+ const multigraph = snapshot.flags.multigraph || snapshot.meta.declaredMultigraph === true;
484
+ if (dialect === "jgf") {
485
+ jgfIdNotes(snapshot, note);
486
+ }
487
+ const edgeIds = snapshot.edges.byRole("id");
488
+ if (dialect === "cytoscape" && edgeIds !== null && edgeIds.nullCount > 0) {
489
+ note(
490
+ LOSS.EDGE_IDS_GENERATED,
491
+ `${edgeIds.nullCount} edge(s) have no id; canonical e<index> ids are generated for them`,
492
+ edgeIds.meta.name,
493
+ edgeIds.nullCount
494
+ );
495
+ }
496
+ return { resolved, notes, nodes, edges, graph, edgeIds, weights, folding, directed, multigraph };
497
+ }
498
+ function withdrawSlotNotes(notes, plans) {
499
+ for (const plan2 of plans) {
500
+ if (plan2.slot !== "classes") {
501
+ continue;
502
+ }
503
+ const at = notes.findIndex((n) => n.code === LOSS.LIST && n.column === plan2.column.meta.name);
504
+ if (at >= 0) {
505
+ notes.splice(at, 1);
506
+ }
507
+ }
508
+ }
509
+ function reservedKeys(snapshot, resolved) {
510
+ const { dialect } = resolved;
511
+ const node = /* @__PURE__ */ new Set();
512
+ const edge = new Set(snapshot.flags.weighted ? [resolved.weightKey] : []);
513
+ const graph = /* @__PURE__ */ new Set();
514
+ switch (dialect) {
515
+ case "node-link":
516
+ case "d3":
517
+ if (resolved.nodeIdKey !== null) {
518
+ node.add(resolved.nodeIdKey);
519
+ }
520
+ edge.add(resolved.sourceKey).add(resolved.targetKey);
521
+ break;
522
+ case "vis":
523
+ node.add(resolved.nodeIdKey ?? "id");
524
+ edge.add(resolved.sourceKey).add(resolved.targetKey);
525
+ break;
526
+ case "cytoscape":
527
+ node.add("id").add("parent");
528
+ edge.add("id").add("source").add("target");
529
+ break;
530
+ case "jgf":
531
+ case "graphology":
532
+ break;
533
+ default: {
534
+ const name = dialect;
535
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, { option: "dialect", found: name });
536
+ }
537
+ }
538
+ return { node, edge, graph };
539
+ }
540
+ function planTable(ctx, table, domain) {
541
+ const { dialect, note } = ctx;
542
+ const plans = [];
543
+ const reserved = ctx.reserved[domain];
544
+ const used = /* @__PURE__ */ new Set();
545
+ for (const column of table) {
546
+ const { meta } = column;
547
+ const { role } = meta;
548
+ const setRows = column.length - column.nullCount;
549
+ if (role !== null && (STRUCTURAL_ROLES.has(role) || TEMPORAL_ROLES.has(role))) {
550
+ continue;
551
+ }
552
+ if (domain !== "graph") {
553
+ ctx.nonfinite.count += countNonFinite(column);
554
+ }
555
+ const slotted = planSlot(ctx, column, domain);
556
+ if (slotted !== null) {
557
+ if (slotted !== SKIPPED) {
558
+ plans.push(slotted);
559
+ }
560
+ continue;
561
+ }
562
+ let key = meta.name;
563
+ let slot = "attribute";
564
+ if (NESTED_DIALECTS.has(dialect) && domain !== "graph") {
565
+ if (key.endsWith(SUFFIX.element)) {
566
+ key = key.slice(0, -SUFFIX.element.length);
567
+ slot = "element";
568
+ } else if (dialect === "cytoscape" && CYTOSCAPE_ELEMENT_KEYS.has(key)) {
569
+ slot = "element";
570
+ } else if (key.endsWith(SUFFIX.data)) {
571
+ key = key.slice(0, -SUFFIX.data.length);
572
+ }
573
+ }
574
+ if (slot === "attribute" && reserved.has(key)) {
575
+ note(
576
+ JSON_LOSS.RESERVED_KEY,
577
+ `${domain} column "${meta.name}" cannot be written: "${key}" is a reserved key of ${dialect}`,
578
+ meta.name,
579
+ setRows
580
+ );
581
+ continue;
582
+ }
583
+ const slotKey = `${slot}:${key}`;
584
+ if (used.has(slotKey)) {
585
+ note(
586
+ JSON_LOSS.RESERVED_KEY,
587
+ `${domain} column "${meta.name}" cannot be written: key "${key}" is already used by another column`,
588
+ meta.name,
589
+ setRows
590
+ );
591
+ continue;
592
+ }
593
+ used.add(slotKey);
594
+ if (setRows === 0 && domain !== "graph") {
595
+ note(
596
+ LOSS.EMPTY_COLUMN,
597
+ `${domain} column "${meta.name}" has no set cell and is not written: JSON writes values, never declarations`,
598
+ meta.name,
599
+ 0
600
+ );
601
+ }
602
+ inferenceNotes(ctx, column, domain, setRows);
603
+ plans.push({ column, key, slot });
604
+ }
605
+ return plans;
606
+ }
607
+ const SKIPPED = /* @__PURE__ */ Symbol("skipped");
608
+ function planSlot(ctx, column, domain) {
609
+ const { dialect, note } = ctx;
610
+ const { meta } = column;
611
+ const { role } = meta;
612
+ const setRows = column.length - column.nullCount;
613
+ if (role === "parent" || role === "parents") {
614
+ if (dialect === "cytoscape" && domain === "node") {
615
+ if (role === "parent" && column.dtype === "u32" && meta.refersTo === "node") {
616
+ return { column, key: "parent", slot: "parent" };
617
+ }
618
+ note(
619
+ LOSS.PARENTS,
620
+ `node column "${meta.name}" (${role}) cannot be written: Cytoscape has a single parent`,
621
+ meta.name,
622
+ setRows
623
+ );
624
+ }
625
+ return SKIPPED;
626
+ }
627
+ if (role === "position" && domain === "node") {
628
+ if (dialect === "cytoscape" && (column.dtype === "f32" || column.dtype === "f64") && meta.components >= 2) {
629
+ const z = countNonZeroZ(column);
630
+ if (z > 0) {
631
+ note(
632
+ JSON_LOSS.POSITION_Z_DROPPED,
633
+ `${z} position(s) have a non-zero z; Cytoscape positions are 2D`,
634
+ meta.name,
635
+ z
636
+ );
637
+ }
638
+ return { column, key: "position", slot: "cytoscapePosition" };
639
+ }
640
+ note(
641
+ LOSS.POSITIONS,
642
+ `node column "${meta.name}" (position) is written as a plain array attribute; its role is lost on re-import`,
643
+ meta.name,
644
+ setRows
645
+ );
646
+ return { column, key: meta.name, slot: "position" };
647
+ }
648
+ if (role === "id" && domain === "edge") {
649
+ if (dialect === "node-link" || dialect === "d3") {
650
+ note(
651
+ LOSS.EDGE_IDS_DROPPED,
652
+ `edge column "${meta.name}" (id) is written as a plain attribute; ${dialect} has no edge id`,
653
+ meta.name,
654
+ setRows
655
+ );
656
+ return null;
657
+ }
658
+ return SKIPPED;
659
+ }
660
+ if (dialect === "cytoscape" && role === "classes" && column.dtype === "list" && column.child.dtype === "string") {
661
+ return { column, key: "classes", slot: "classes" };
662
+ }
663
+ if (dialect === "jgf" && role === "label" && (column.dtype === "string" || column.dtype === "dict")) {
664
+ return { column, key: "label", slot: "label" };
665
+ }
666
+ if (dialect === "jgf" && role === "kind" && domain === "edge" && (column.dtype === "string" || column.dtype === "dict")) {
667
+ return { column, key: "relation", slot: "relation" };
668
+ }
669
+ return null;
670
+ }
671
+ function inferenceNotes(ctx, column, domain, setRows) {
672
+ if (column.dtype !== "f64" || column.meta.components > 1 || setRows === 0) {
673
+ return;
674
+ }
675
+ const { data } = column;
676
+ for (let r = 0; r < column.length; r++) {
677
+ if (column.isSet(r) && !Number.isInteger(data[r])) {
678
+ return;
679
+ }
680
+ }
681
+ ctx.note(
682
+ LOSS.INTEGRAL_F64,
683
+ `${domain} column "${column.meta.name}" is f64 with integral values only; JSON declares no types and it reads back as i32`,
684
+ column.meta.name,
685
+ setRows
686
+ );
687
+ }
688
+ function planWeights(ctx, edges) {
689
+ const { snapshot, nonfinite } = ctx;
690
+ const weights = explicitWeights(snapshot);
691
+ if (!weights.weighted) {
692
+ const clash = edges.find((p) => p.slot === "attribute" && p.key === ctx.resolved.weightKey);
693
+ if (clash !== void 0) {
694
+ ctx.note(
695
+ LOSS.WEIGHT_KEY_CLASH,
696
+ `edge column "${clash.column.meta.name}" is written under "${clash.key}", the key the importer reads THE weight from; it reads back as the weight, not as a column`,
697
+ clash.column.meta.name,
698
+ clash.column.length - clash.column.nullCount
699
+ );
700
+ }
701
+ return () => null;
702
+ }
703
+ for (let e = 0; e < snapshot.edgeCount; e++) {
704
+ if (weights.isExplicit(e) && !Number.isFinite(weights.value(e))) {
705
+ nonfinite.count++;
706
+ }
707
+ }
708
+ return (e) => {
709
+ if (!weights.isExplicit(e)) {
710
+ return null;
711
+ }
712
+ return Number.isFinite(weights.value(e)) ? weights.text(e) : "null";
713
+ };
714
+ }
715
+ function planDirection(ctx) {
716
+ const { snapshot, dialect, caps, note } = ctx;
717
+ const folding = pairFolding(snapshot);
718
+ if (folding.mutualCount > 0) {
719
+ note(
720
+ LOSS.MUTUAL_EXPANDED,
721
+ `${folding.mutualCount} mutual pair(s) are written as two directed edges; the mutual mark is lost`,
722
+ null,
723
+ folding.mutualCount
724
+ );
725
+ }
726
+ let { directed } = snapshot;
727
+ if (!caps.mixedDirection && countMixedEdges(snapshot) > 0 && ctx.resolved.common.onMixedDirection === "undirected") {
728
+ directed = false;
729
+ }
730
+ if (dialect === "cytoscape" || dialect === "vis" || dialect === "d3") {
731
+ const assumed = DIALECT_DEFAULT_DIRECTED[dialect];
732
+ if (directed !== assumed) {
733
+ note(
734
+ JSON_LOSS.DIRECTION_DROPPED,
735
+ `${dialect} has no direction flag; the ${directed ? "directed" : "undirected"} graph re-imports as ${assumed ? "directed" : "undirected"} unless defaultDirected is passed`
736
+ );
737
+ }
738
+ }
739
+ return { folding, directed };
740
+ }
741
+ function countNonZeroZ(column) {
742
+ if (column.dtype !== "f32" && column.dtype !== "f64" || column.meta.components < 3) {
743
+ return 0;
744
+ }
745
+ const { components } = column.meta;
746
+ let count = 0;
747
+ for (let r = 0; r < column.length; r++) {
748
+ if (column.isSet(r) && column.data[r * components + 2] !== 0) {
749
+ count++;
750
+ }
751
+ }
752
+ return count;
753
+ }
754
+ function jgfIdNotes(snapshot, note) {
755
+ const { ids } = snapshot;
756
+ const seen = /* @__PURE__ */ new Set();
757
+ let numeric = 0;
758
+ let collisions = 0;
759
+ let indexLike = 0;
760
+ let lastIndex = -1;
761
+ let ordered = true;
762
+ let sawOther = false;
763
+ for (let i = 0; i < ids.size; i++) {
764
+ const id = ids.idOf(i);
765
+ const text = String(id);
766
+ if (typeof id === "number") {
767
+ numeric++;
768
+ }
769
+ if (seen.has(text)) {
770
+ collisions++;
771
+ }
772
+ seen.add(text);
773
+ if (isArrayIndexKey(text)) {
774
+ indexLike++;
775
+ const n = Number(text);
776
+ if (sawOther || n <= lastIndex) {
777
+ ordered = false;
778
+ }
779
+ lastIndex = n;
780
+ } else {
781
+ sawOther = true;
782
+ }
783
+ }
784
+ if (numeric > 0) {
785
+ note(
786
+ JSON_LOSS.NUMERIC_IDS_STRINGIFIED,
787
+ `${numeric} numeric node id(s) become JGF object keys (strings); pass ids: "canonical" on re-import`,
788
+ null,
789
+ numeric
790
+ );
791
+ }
792
+ if (collisions > 0) {
793
+ note(
794
+ JSON_LOSS.ID_TEXT_COLLISION,
795
+ `${collisions} node id(s) share their text with another id; export() will throw`,
796
+ null,
797
+ collisions
798
+ );
799
+ }
800
+ if (!ordered) {
801
+ note(
802
+ JSON_LOSS.NODE_ORDER,
803
+ `${indexLike} integer-like node id(s) are enumerated first and ascending by JSON parsers; node order changes on re-import`,
804
+ null,
805
+ indexLike
806
+ );
807
+ }
808
+ }
809
+ function isArrayIndexKey(text) {
810
+ return /^(0|[1-9][0-9]*)$/.test(text) && Number(text) < 4294967295;
811
+ }
812
+ function* write(snapshot, p) {
813
+ const { dialect } = p.resolved;
814
+ if (p.notes.some((n) => n.code === LOSS.MIXED_DIRECTION_ERROR)) {
815
+ throw new GraphFormatError(
816
+ "E_DIRECTED",
817
+ `${dialect} has no mixed direction; pass onMixedDirection "directed" or "undirected"`,
818
+ { reason: "mixed direction", dialect }
819
+ );
820
+ }
821
+ if (p.notes.some((n) => n.code === JSON_LOSS.ID_TEXT_COLLISION)) {
822
+ throw new GraphFormatError("E_INVALID_ID", "two node ids have the same text; JGF keys nodes by text", {
823
+ reason: "collision"
824
+ });
825
+ }
826
+ switch (dialect) {
827
+ case "node-link":
828
+ case "d3":
829
+ yield* writeNodeLink(snapshot, p);
830
+ return;
831
+ case "vis":
832
+ yield* writeVis(snapshot, p);
833
+ return;
834
+ case "graphology":
835
+ yield* writeGraphology(snapshot, p);
836
+ return;
837
+ case "jgf":
838
+ yield* writeJgf(snapshot, p);
839
+ return;
840
+ case "cytoscape":
841
+ yield* writeCytoscape(snapshot, p);
842
+ return;
843
+ default: {
844
+ const name = dialect;
845
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, { option: "dialect", found: name });
846
+ }
847
+ }
848
+ }
849
+ function writeSlots(c, plans, row, slots) {
850
+ for (const item of plans) {
851
+ if (!slots.has(item.slot) || !item.column.isSet(row)) {
852
+ continue;
853
+ }
854
+ let text;
855
+ switch (item.slot) {
856
+ case "position": {
857
+ const value = item.column.value(row);
858
+ const dims = dimsOf(item.column);
859
+ const values = Array.from(value).slice(0, dims);
860
+ text = valueText(values, item.column.dtype === "f32", c.nonfinite);
861
+ break;
862
+ }
863
+ case "classes":
864
+ text = JSON.stringify(item.column.sliceOf(row).join(" "));
865
+ break;
866
+ default:
867
+ text = cellText(item.column, row, c.ids, c.nonfinite);
868
+ break;
869
+ }
870
+ c.w.member(item.key, text);
871
+ }
872
+ }
873
+ const ATTRIBUTE_SLOTS = /* @__PURE__ */ new Set(["attribute", "position"]);
874
+ const ELEMENT_SLOTS = /* @__PURE__ */ new Set(["element", "classes"]);
875
+ function dimsOf(column) {
876
+ const dims = column.meta.extra.sourceDims;
877
+ if (typeof dims === "number" && dims >= 1 && dims <= column.meta.components) {
878
+ return dims;
879
+ }
880
+ return column.meta.components;
881
+ }
882
+ function anySet(plans, row, slots) {
883
+ return plans.some((item) => slots.has(item.slot) && item.column.isSet(row));
884
+ }
885
+ function idText(id) {
886
+ return typeof id === "number" && Object.is(id, -0) ? "0" : JSON.stringify(id);
887
+ }
888
+ function edgeIdText(p, e, c) {
889
+ const column = p.edgeIds;
890
+ if (column === null || !column.isSet(e)) {
891
+ return null;
892
+ }
893
+ return cellText(column, e, c.ids, c.nonfinite);
894
+ }
895
+ function edgeDisposition(p, e) {
896
+ const { folding } = p;
897
+ if (folding.folded(e)) {
898
+ return "skip";
899
+ }
900
+ if (!p.directed) {
901
+ return "undirected";
902
+ }
903
+ if (folding.sourceDirected(e)) {
904
+ return "directed";
905
+ }
906
+ if (dialectCapabilities(p.resolved.dialect).mixedDirection) {
907
+ return "undirected";
908
+ }
909
+ return p.resolved.common.onMixedDirection === "directed" ? "directed" : "undirected";
910
+ }
911
+ function writeGraphObject(c, p) {
912
+ c.w.open("{");
913
+ for (const item of p.graph) {
914
+ if (item.column.isSet(0)) {
915
+ c.w.member(item.key, cellText(item.column, 0, c.ids, c.nonfinite));
916
+ }
917
+ }
918
+ c.w.close("}");
919
+ }
920
+ function endpoints(snapshot, p, e) {
921
+ const list = snapshot.edgeList();
922
+ const u = list.src[e];
923
+ const v = list.dst[e];
924
+ if (p.resolved.indexLinks) {
925
+ return [String(u), String(v)];
926
+ }
927
+ return [idText(snapshot.ids.idOf(u)), idText(snapshot.ids.idOf(v))];
928
+ }
929
+ function* writeNodeLink(snapshot, p) {
930
+ const { resolved } = p;
931
+ const c = { w: new JsonWriter(resolved.indent), ids: snapshot.ids, nonfinite: { count: 0 } };
932
+ const { w } = c;
933
+ w.open("{");
934
+ if (resolved.dialect !== "d3") {
935
+ w.member("directed", p.directed ? "true" : "false");
936
+ w.member("multigraph", p.multigraph ? "true" : "false");
937
+ w.key("graph");
938
+ writeGraphObject(c, p);
939
+ }
940
+ w.key("nodes");
941
+ w.open("[");
942
+ yield w.take();
943
+ for (let i = 0; i < snapshot.nodeCount; i++) {
944
+ w.item();
945
+ w.open("{");
946
+ if (resolved.nodeIdKey !== null) {
947
+ w.member(resolved.nodeIdKey, idText(snapshot.ids.idOf(i)));
948
+ }
949
+ writeSlots(c, p.nodes, i, ATTRIBUTE_SLOTS);
950
+ w.close("}");
951
+ yield w.take();
952
+ }
953
+ w.close("]");
954
+ w.key(resolved.edgesKey);
955
+ w.open("[");
956
+ for (let e = 0; e < snapshot.edgeCount; e++) {
957
+ const disposition = edgeDisposition(p, e);
958
+ if (disposition === "skip") {
959
+ continue;
960
+ }
961
+ const [s, t] = endpoints(snapshot, p, e);
962
+ w.item();
963
+ w.open("{");
964
+ w.member(resolved.sourceKey, s);
965
+ w.member(resolved.targetKey, t);
966
+ const weight = p.weights(e);
967
+ if (weight !== null) {
968
+ w.member(resolved.weightKey, weight);
969
+ }
970
+ writeSlots(c, p.edges, e, ATTRIBUTE_SLOTS);
971
+ w.close("}");
972
+ yield w.take();
973
+ }
974
+ w.close("]");
975
+ w.close("}");
976
+ yield w.take();
977
+ }
978
+ function* writeVis(snapshot, p) {
979
+ const { resolved } = p;
980
+ const c = { w: new JsonWriter(resolved.indent), ids: snapshot.ids, nonfinite: { count: 0 } };
981
+ const { w } = c;
982
+ const nodeIdKey = resolved.nodeIdKey ?? "id";
983
+ w.open("{");
984
+ w.key("nodes");
985
+ w.open("[");
986
+ yield w.take();
987
+ for (let i = 0; i < snapshot.nodeCount; i++) {
988
+ w.item();
989
+ w.open("{");
990
+ w.member(nodeIdKey, idText(snapshot.ids.idOf(i)));
991
+ writeSlots(c, p.nodes, i, ATTRIBUTE_SLOTS);
992
+ w.close("}");
993
+ yield w.take();
994
+ }
995
+ w.close("]");
996
+ w.key("edges");
997
+ w.open("[");
998
+ for (let e = 0; e < snapshot.edgeCount; e++) {
999
+ if (edgeDisposition(p, e) === "skip") {
1000
+ continue;
1001
+ }
1002
+ const list = snapshot.edgeList();
1003
+ w.item();
1004
+ w.open("{");
1005
+ const id = edgeIdText(p, e, c);
1006
+ if (id !== null) {
1007
+ w.member("id", id);
1008
+ }
1009
+ w.member(resolved.sourceKey, idText(snapshot.ids.idOf(list.src[e])));
1010
+ w.member(resolved.targetKey, idText(snapshot.ids.idOf(list.dst[e])));
1011
+ const weight = p.weights(e);
1012
+ if (weight !== null) {
1013
+ w.member(resolved.weightKey, weight);
1014
+ }
1015
+ writeSlots(c, p.edges, e, ATTRIBUTE_SLOTS);
1016
+ w.close("}");
1017
+ yield w.take();
1018
+ }
1019
+ w.close("]");
1020
+ w.close("}");
1021
+ yield w.take();
1022
+ }
1023
+ function* writeGraphology(snapshot, p) {
1024
+ const { resolved } = p;
1025
+ const c = { w: new JsonWriter(resolved.indent), ids: snapshot.ids, nonfinite: { count: 0 } };
1026
+ const { w } = c;
1027
+ let type;
1028
+ if (!p.directed) {
1029
+ type = "undirected";
1030
+ } else {
1031
+ type = countMixedEdges(snapshot) > 0 ? "mixed" : "directed";
1032
+ }
1033
+ w.open("{");
1034
+ w.key("attributes");
1035
+ writeGraphObject(c, p);
1036
+ w.key("options");
1037
+ w.open("{");
1038
+ w.member("type", JSON.stringify(type));
1039
+ if (snapshot.meta.declaredMultigraph !== null || p.multigraph) {
1040
+ w.member("multi", p.multigraph ? "true" : "false");
1041
+ }
1042
+ if (resolved.shape.allowSelfLoops !== void 0) {
1043
+ w.member("allowSelfLoops", !resolved.shape.allowSelfLoops && snapshot.selfLoopCount === 0 ? "false" : "true");
1044
+ }
1045
+ w.close("}");
1046
+ w.key("nodes");
1047
+ w.open("[");
1048
+ yield w.take();
1049
+ for (let i = 0; i < snapshot.nodeCount; i++) {
1050
+ w.item();
1051
+ w.open("{");
1052
+ w.member("key", idText(snapshot.ids.idOf(i)));
1053
+ if (anySet(p.nodes, i, ATTRIBUTE_SLOTS)) {
1054
+ w.key("attributes");
1055
+ w.open("{");
1056
+ writeSlots(c, p.nodes, i, ATTRIBUTE_SLOTS);
1057
+ w.close("}");
1058
+ }
1059
+ writeSlots(c, p.nodes, i, ELEMENT_SLOTS);
1060
+ w.close("}");
1061
+ yield w.take();
1062
+ }
1063
+ w.close("]");
1064
+ w.key("edges");
1065
+ w.open("[");
1066
+ for (let e = 0; e < snapshot.edgeCount; e++) {
1067
+ const disposition = edgeDisposition(p, e);
1068
+ if (disposition === "skip") {
1069
+ continue;
1070
+ }
1071
+ const list = snapshot.edgeList();
1072
+ w.item();
1073
+ w.open("{");
1074
+ const id = edgeIdText(p, e, c);
1075
+ if (id !== null) {
1076
+ w.member("key", id);
1077
+ }
1078
+ w.member("source", idText(snapshot.ids.idOf(list.src[e])));
1079
+ w.member("target", idText(snapshot.ids.idOf(list.dst[e])));
1080
+ if (type === "mixed" && disposition === "undirected") {
1081
+ w.member("undirected", "true");
1082
+ }
1083
+ const weight = p.weights(e);
1084
+ if (weight !== null || anySet(p.edges, e, ATTRIBUTE_SLOTS)) {
1085
+ w.key("attributes");
1086
+ w.open("{");
1087
+ if (weight !== null) {
1088
+ w.member(resolved.weightKey, weight);
1089
+ }
1090
+ writeSlots(c, p.edges, e, ATTRIBUTE_SLOTS);
1091
+ w.close("}");
1092
+ }
1093
+ writeSlots(c, p.edges, e, ELEMENT_SLOTS);
1094
+ w.close("}");
1095
+ yield w.take();
1096
+ }
1097
+ w.close("]");
1098
+ w.close("}");
1099
+ yield w.take();
1100
+ }
1101
+ function* writeJgf(snapshot, p) {
1102
+ const { resolved } = p;
1103
+ const c = { w: new JsonWriter(resolved.indent), ids: snapshot.ids, nonfinite: { count: 0 } };
1104
+ const { w } = c;
1105
+ const labelSlots = /* @__PURE__ */ new Set(["label"]);
1106
+ const relationSlots = /* @__PURE__ */ new Set(["relation"]);
1107
+ w.open("{");
1108
+ w.key("graph");
1109
+ w.open("{");
1110
+ if (resolved.shape.id !== void 0) {
1111
+ w.member("id", JSON.stringify(resolved.shape.id));
1112
+ }
1113
+ if (snapshot.meta.name !== null) {
1114
+ w.member("label", JSON.stringify(snapshot.meta.name));
1115
+ }
1116
+ if (resolved.shape.type !== void 0) {
1117
+ w.member("type", JSON.stringify(resolved.shape.type));
1118
+ }
1119
+ w.member("directed", p.directed ? "true" : "false");
1120
+ if (p.graph.some((item) => item.column.isSet(0))) {
1121
+ w.key("metadata");
1122
+ writeGraphObject(c, p);
1123
+ }
1124
+ w.key("nodes");
1125
+ w.open("{");
1126
+ yield w.take();
1127
+ for (let i = 0; i < snapshot.nodeCount; i++) {
1128
+ w.key(String(snapshot.ids.idOf(i)));
1129
+ w.open("{");
1130
+ writeSlots(c, p.nodes, i, labelSlots);
1131
+ if (anySet(p.nodes, i, ATTRIBUTE_SLOTS)) {
1132
+ w.key("metadata");
1133
+ w.open("{");
1134
+ writeSlots(c, p.nodes, i, ATTRIBUTE_SLOTS);
1135
+ w.close("}");
1136
+ }
1137
+ writeSlots(c, p.nodes, i, ELEMENT_SLOTS);
1138
+ w.close("}");
1139
+ yield w.take();
1140
+ }
1141
+ w.close("}");
1142
+ w.key("edges");
1143
+ w.open("[");
1144
+ for (let e = 0; e < snapshot.edgeCount; e++) {
1145
+ const disposition = edgeDisposition(p, e);
1146
+ if (disposition === "skip") {
1147
+ continue;
1148
+ }
1149
+ const list = snapshot.edgeList();
1150
+ w.item();
1151
+ w.open("{");
1152
+ const id = edgeIdText(p, e, c);
1153
+ if (id !== null) {
1154
+ w.member("id", id);
1155
+ }
1156
+ w.member("source", JSON.stringify(String(snapshot.ids.idOf(list.src[e]))));
1157
+ w.member("target", JSON.stringify(String(snapshot.ids.idOf(list.dst[e]))));
1158
+ writeSlots(c, p.edges, e, relationSlots);
1159
+ if (p.directed && disposition === "undirected") {
1160
+ w.member("directed", "false");
1161
+ }
1162
+ writeSlots(c, p.edges, e, labelSlots);
1163
+ const weight = p.weights(e);
1164
+ if (weight !== null || anySet(p.edges, e, ATTRIBUTE_SLOTS)) {
1165
+ w.key("metadata");
1166
+ w.open("{");
1167
+ if (weight !== null) {
1168
+ w.member(resolved.weightKey, weight);
1169
+ }
1170
+ writeSlots(c, p.edges, e, ATTRIBUTE_SLOTS);
1171
+ w.close("}");
1172
+ }
1173
+ writeSlots(c, p.edges, e, ELEMENT_SLOTS);
1174
+ w.close("}");
1175
+ yield w.take();
1176
+ }
1177
+ w.close("]");
1178
+ w.close("}");
1179
+ w.close("}");
1180
+ yield w.take();
1181
+ }
1182
+ function* writeCytoscape(snapshot, p) {
1183
+ const { resolved } = p;
1184
+ const c = { w: new JsonWriter(resolved.indent), ids: snapshot.ids, nonfinite: { count: 0 } };
1185
+ const { w } = c;
1186
+ const parentSlots = /* @__PURE__ */ new Set(["parent"]);
1187
+ const positionSlots = /* @__PURE__ */ new Set(["cytoscapePosition"]);
1188
+ w.open("{");
1189
+ w.key("elements");
1190
+ w.open("{");
1191
+ w.key("nodes");
1192
+ w.open("[");
1193
+ yield w.take();
1194
+ for (let i = 0; i < snapshot.nodeCount; i++) {
1195
+ w.item();
1196
+ w.open("{");
1197
+ w.key("data");
1198
+ w.open("{");
1199
+ w.member("id", idText(snapshot.ids.idOf(i)));
1200
+ writeSlots(c, p.nodes, i, parentSlots);
1201
+ writeSlots(c, p.nodes, i, ATTRIBUTE_SLOTS);
1202
+ w.close("}");
1203
+ for (const item of p.nodes) {
1204
+ if (positionSlots.has(item.slot) && item.column.isSet(i)) {
1205
+ const value = item.column.value(i);
1206
+ const f32 = item.column.dtype === "f32";
1207
+ w.key("position");
1208
+ w.open("{");
1209
+ w.member("x", numberText(value[0], f32, c.nonfinite));
1210
+ w.member("y", numberText(value[1], f32, c.nonfinite));
1211
+ w.close("}");
1212
+ }
1213
+ }
1214
+ writeSlots(c, p.nodes, i, ELEMENT_SLOTS);
1215
+ w.close("}");
1216
+ yield w.take();
1217
+ }
1218
+ w.close("]");
1219
+ w.key("edges");
1220
+ w.open("[");
1221
+ const generated = edgeIdGenerator(snapshot, p);
1222
+ for (let e = 0; e < snapshot.edgeCount; e++) {
1223
+ if (edgeDisposition(p, e) === "skip") {
1224
+ continue;
1225
+ }
1226
+ const list = snapshot.edgeList();
1227
+ w.item();
1228
+ w.open("{");
1229
+ w.key("data");
1230
+ w.open("{");
1231
+ w.member("id", edgeIdText(p, e, c) ?? generated(e));
1232
+ w.member("source", idText(snapshot.ids.idOf(list.src[e])));
1233
+ w.member("target", idText(snapshot.ids.idOf(list.dst[e])));
1234
+ const weight = p.weights(e);
1235
+ if (weight !== null) {
1236
+ w.member(resolved.weightKey, weight);
1237
+ }
1238
+ writeSlots(c, p.edges, e, ATTRIBUTE_SLOTS);
1239
+ w.close("}");
1240
+ writeSlots(c, p.edges, e, ELEMENT_SLOTS);
1241
+ w.close("}");
1242
+ yield w.take();
1243
+ }
1244
+ w.close("]");
1245
+ w.close("}");
1246
+ if (p.graph.some((item) => item.column.isSet(0))) {
1247
+ w.key("data");
1248
+ writeGraphObject(c, p);
1249
+ }
1250
+ const extra = resolved.shape.cytoscape;
1251
+ if (extra !== void 0) {
1252
+ for (const key of Object.keys(extra)) {
1253
+ if (key !== "elements" && key !== "data") {
1254
+ w.member(key, valueText(extra[key], false, c.nonfinite));
1255
+ }
1256
+ }
1257
+ }
1258
+ w.close("}");
1259
+ yield w.take();
1260
+ }
1261
+ function edgeIdGenerator(snapshot, p) {
1262
+ const used = /* @__PURE__ */ new Set();
1263
+ for (let i = 0; i < snapshot.nodeCount; i++) {
1264
+ used.add(String(snapshot.ids.idOf(i)));
1265
+ }
1266
+ const column = p.edgeIds;
1267
+ if (column !== null) {
1268
+ for (let e = 0; e < snapshot.edgeCount; e++) {
1269
+ if (column.isSet(e)) {
1270
+ used.add(String(column.value(e)));
1271
+ }
1272
+ }
1273
+ }
1274
+ return (e) => {
1275
+ let candidate = `e${e}`;
1276
+ for (let k = 2; used.has(candidate); k++) {
1277
+ candidate = `e${e}_${k}`;
1278
+ }
1279
+ used.add(candidate);
1280
+ return JSON.stringify(candidate);
1281
+ };
1282
+ }
1283
+ const jsonExporter = Object.freeze({
1284
+ format: "json",
1285
+ capabilities: dialectCapabilities("node-link"),
1286
+ /**
1287
+ * Pre-flight: every loss of the selected dialect, without writing anything.
1288
+ * @param snapshot - the snapshot
1289
+ * @param options - format-specific and common options
1290
+ * @returns the notes, empty when the export is exact
1291
+ */
1292
+ check(snapshot, options) {
1293
+ return Object.freeze([...plan(snapshot, resolve(snapshot, options)).notes]);
1294
+ },
1295
+ /**
1296
+ * Write the document as UTF-8 chunks.
1297
+ * @param snapshot - the snapshot
1298
+ * @param options - format-specific and common options
1299
+ * @returns the chunks
1300
+ */
1301
+ export(snapshot, options) {
1302
+ return encodeChunks(write(snapshot, plan(snapshot, resolve(snapshot, options))));
1303
+ },
1304
+ /**
1305
+ * Write the document as one string.
1306
+ * @param snapshot - the snapshot
1307
+ * @param options - format-specific and common options
1308
+ * @returns the document
1309
+ */
1310
+ exportToString(snapshot, options) {
1311
+ return joinText(write(snapshot, plan(snapshot, resolve(snapshot, options))));
1312
+ }
1313
+ });
1314
+ function jsonCapabilities(dialect) {
1315
+ return dialectCapabilities(dialect);
1316
+ }
1317
+ const JSON_ISSUE = Object.freeze({
1318
+ /** The text is empty or whitespace (fatal). */
1319
+ EMPTY_INPUT: EMPTY_INPUT_CODE,
1320
+ /** JSON.parse refused the text (fatal). */
1321
+ SYNTAX: SYNTAX_CODE,
1322
+ /** No dialect matches the document's top-level shape. */
1323
+ DIALECT: "E_JSON_DIALECT",
1324
+ /** A section (nodes, edges, elements, graph) has the wrong JSON type. */
1325
+ SHAPE: "E_JSON_SHAPE",
1326
+ /** A node-link document lacks its nodes or its edges array, or a Cytoscape document its elements. */
1327
+ MISSING_SECTION: "E_MISSING_SECTION",
1328
+ /** A node record or an element is not an object. */
1329
+ BAD_ELEMENT: "E_BAD_ELEMENT",
1330
+ /** A node record has no id. */
1331
+ MISSING_ID: MISSING_ID_CODE,
1332
+ /** A node id is a JSON boolean or null (legal in NetworkX, not a NodeId); coerced only under ids "string". */
1333
+ UNSUPPORTED_ID: "E_UNSUPPORTED_ID",
1334
+ /** An edge record has no source or no target. */
1335
+ MISSING_ENDPOINT: MISSING_ENDPOINT_CODE,
1336
+ /** An index endpoint is not an integer below the node count, or names a skipped node. */
1337
+ BAD_INDEX: "E_BAD_INDEX",
1338
+ /** A declared field has the wrong JSON type (JGF label / relation / metadata, Cytoscape position / classes). */
1339
+ BAD_VALUE: "E_BAD_VALUE",
1340
+ /** A graph-level flag (`directed`, `multigraph`, graphology `options`) has the wrong type; the default is used. */
1341
+ BAD_FLAG: "W_BAD_FLAG",
1342
+ /** A Cytoscape `data.parent` names an unknown node. */
1343
+ UNKNOWN_PARENT: UNKNOWN_PARENT_CODE,
1344
+ /** A node id repeated by a later record; the records are merged (the later attributes win). */
1345
+ DUPLICATE_NODE: DUPLICATE_NODE_CODE,
1346
+ /** An edge id (Cytoscape data.id, graphology key, vis id) repeated by a later edge; the edge is skipped. */
1347
+ DUPLICATE_EDGE_ID: DUPLICATE_EDGE_ID_CODE,
1348
+ /** Two distinct id texts merged into one number under ids "number". */
1349
+ ID_MERGED: ID_MERGED_CODE,
1350
+ /** Edge ids of mixed JSON types were stored as text. */
1351
+ EDGE_ID_STRINGIFIED: "W_EDGE_ID_STRINGIFIED",
1352
+ /** A JGF `graphs` array holds more than one graph; only `graphIndex` is read. */
1353
+ MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
1354
+ /** JGF hyperedges under the "error" policy. */
1355
+ HYPEREDGE: HYPEREDGE_CODE,
1356
+ /** JGF hyperedges skipped under the default "skip" policy. */
1357
+ HYPEREDGES_SKIPPED: "W_HYPEREDGES_SKIPPED",
1358
+ /** A JGF hyperedge with neither a nodes array nor source / target arrays. */
1359
+ HYPEREDGE_SHAPE: "E_HYPEREDGE_SHAPE",
1360
+ /** The nodes have no id key at all; array positions became the ids. */
1361
+ POSITIONAL_NODES: "W_POSITIONAL_NODES",
1362
+ /** A builder-policy option (addMissingNodes, duplicateEdges, selfLoops, weightDtype) differs from the sink's (the shared W_SINK_OPTION). */
1363
+ SINK_OPTION: SINK_OPTION_CODE,
1364
+ /** A common option the dialect has no use for (nodeIdFrom outside node-link, long, restoreMangledIds). */
1365
+ OPTION_IGNORED: OPTION_IGNORED_CODE
1366
+ });
1367
+ const USED_OPTIONS = /* @__PURE__ */ new Set([
1368
+ "ids",
1369
+ "nodeIdFrom",
1370
+ "addMissingNodes",
1371
+ "duplicateEdges",
1372
+ "selfLoops",
1373
+ "onMixedDirection",
1374
+ "defaultDirected",
1375
+ "weightFrom",
1376
+ "weightDtype",
1377
+ "hyperedges",
1378
+ "errorLimit",
1379
+ "signal",
1380
+ "onProgress"
1381
+ ]);
1382
+ const FORMAT_DEFAULTS = { ids: "keep", defaultDirected: false, weightFrom: "weight" };
1383
+ const SNIFF_BYTES = 4096;
1384
+ const ABORT_CHECK_INTERVAL = 64;
1385
+ const SNIFF_KEYS = ['"nodes"', '"links"', '"edges"', '"elements"', '"graph"', '"graphs"'];
1386
+ const BOM = String.fromCharCode(65279);
1387
+ const JGF_EDGE_KEYS = /* @__PURE__ */ new Set([
1388
+ "id",
1389
+ "source",
1390
+ "target",
1391
+ "relation",
1392
+ "directed",
1393
+ "label",
1394
+ "metadata"
1395
+ ]);
1396
+ const JGF_HYPEREDGE_KEYS = /* @__PURE__ */ new Set([...JGF_EDGE_KEYS, "nodes"]);
1397
+ const JGF_NODE_KEYS = /* @__PURE__ */ new Set(["label", "metadata"]);
1398
+ const GRAPHOLOGY_NODE_KEYS = /* @__PURE__ */ new Set(["key", "attributes"]);
1399
+ const GRAPHOLOGY_EDGE_KEYS = /* @__PURE__ */ new Set(["key", "source", "target", "attributes", "undirected"]);
1400
+ const VIS_SOURCE_KEYS = Object.freeze(["from"]);
1401
+ const VIS_TARGET_KEYS = Object.freeze(["to"]);
1402
+ function resolveJsonOptions(options) {
1403
+ const o = options ?? {};
1404
+ const dialect = o.dialect ?? "auto";
1405
+ if (dialect !== "auto" && !isJsonDialect(dialect)) {
1406
+ throw unsupportedOption("dialect", dialect, [...JSON_DIALECTS, "auto"]);
1407
+ }
1408
+ const indexLinks = o.indexLinks ?? "auto";
1409
+ if (indexLinks !== "auto" && typeof indexLinks !== "boolean") {
1410
+ throw unsupportedOption("indexLinks", indexLinks, ["true", "false", "auto"]);
1411
+ }
1412
+ const graphIndex = o.graphIndex ?? 0;
1413
+ if (!Number.isInteger(graphIndex) || graphIndex < 0) {
1414
+ throw unsupportedOption("graphIndex", graphIndex, ["a non-negative integer"]);
1415
+ }
1416
+ return {
1417
+ dialect,
1418
+ nodeIdKey: keyOption("nodeIdKey", o.nodeIdKey),
1419
+ edgesKey: keyOption("edgesKey", o.edgesKey),
1420
+ sourceKey: keyOption("sourceKey", o.sourceKey),
1421
+ targetKey: keyOption("targetKey", o.targetKey),
1422
+ indexLinks,
1423
+ graphIndex
1424
+ };
1425
+ }
1426
+ function keyOption(name, value) {
1427
+ if (value === void 0) {
1428
+ return null;
1429
+ }
1430
+ if (typeof value !== "string" || value.length === 0) {
1431
+ throw unsupportedOption(name, value, ["a non-empty key"]);
1432
+ }
1433
+ return value;
1434
+ }
1435
+ function unsupportedOption(name, found, supported) {
1436
+ return new GraphFormatError(
1437
+ "E_UNSUPPORTED",
1438
+ `option ${name}: ${describe(found)} is not one of ${supported.join(", ")}`,
1439
+ {
1440
+ option: name,
1441
+ found: typeof found === "string" ? found : typeof found,
1442
+ supported: [...supported]
1443
+ }
1444
+ );
1445
+ }
1446
+ function describe(value) {
1447
+ if (value === null) {
1448
+ return "null";
1449
+ }
1450
+ if (Array.isArray(value)) {
1451
+ return "array";
1452
+ }
1453
+ switch (typeof value) {
1454
+ case "string":
1455
+ case "number":
1456
+ case "boolean":
1457
+ return JSON.stringify(value);
1458
+ default:
1459
+ return typeof value;
1460
+ }
1461
+ }
1462
+ class AttributeWriter {
1463
+ /**
1464
+ * Create a writer.
1465
+ * @param sink - the sink
1466
+ * @param domain - node or edge
1467
+ */
1468
+ constructor(sink, domain) {
1469
+ this.handles = /* @__PURE__ */ new Map();
1470
+ this.reservedNames = /* @__PURE__ */ new Set();
1471
+ this.sink = sink;
1472
+ this.domain = domain;
1473
+ }
1474
+ /**
1475
+ * Declare a structural column up front; attribute keys with its name are suffixed from now on.
1476
+ * @param decl - the declaration
1477
+ * @returns the handle
1478
+ */
1479
+ declare(decl) {
1480
+ this.reservedNames.add(decl.name);
1481
+ const handle = this.domain === "node" ? this.sink.declareNodeColumn(decl) : this.sink.declareEdgeColumn(decl);
1482
+ this.handles.set(decl.name, handle);
1483
+ return handle;
1484
+ }
1485
+ /**
1486
+ * Declare a structural column only when the file uses it.
1487
+ * @param decl - the declaration
1488
+ * @param present - whether any element carries the field
1489
+ * @returns the handle, or INVALID_INDEX when not declared
1490
+ */
1491
+ declareIf(decl, present) {
1492
+ return present ? this.declare(decl) : INVALID_INDEX;
1493
+ }
1494
+ /**
1495
+ * Whether a name is taken in the sink's table (for the deterministic rename rule).
1496
+ * @param name - the column name
1497
+ * @returns true when a column of that name exists
1498
+ */
1499
+ taken(name) {
1500
+ return this.lookup(name) !== INVALID_INDEX;
1501
+ }
1502
+ /**
1503
+ * Write one attribute cell by its source key; null and undefined leave the row unset.
1504
+ * @param row - the node or edge index
1505
+ * @param key - the source key
1506
+ * @param value - the JSON value
1507
+ * @param suffix - the suffix applied when the key collides with a structural column
1508
+ */
1509
+ write(row, key, value, suffix) {
1510
+ if (value === void 0 || value === null) {
1511
+ return;
1512
+ }
1513
+ const name = this.reservedNames.has(key) ? `${key}${suffix}` : key;
1514
+ const cached = this.handles.get(name);
1515
+ if (cached !== void 0) {
1516
+ this.set(cached, row, value);
1517
+ return;
1518
+ }
1519
+ this.set(name, row, value);
1520
+ const handle = this.lookup(name);
1521
+ if (handle !== INVALID_INDEX) {
1522
+ this.handles.set(name, handle);
1523
+ }
1524
+ }
1525
+ /**
1526
+ * Write through a handle or a name.
1527
+ * @param column - the handle or name
1528
+ * @param row - the row
1529
+ * @param value - the value
1530
+ */
1531
+ set(column, row, value) {
1532
+ if (this.domain === "node") {
1533
+ this.sink.setNodeValue(column, row, value);
1534
+ } else {
1535
+ this.sink.setEdgeValue(column, row, value);
1536
+ }
1537
+ }
1538
+ /**
1539
+ * Look a column up by name.
1540
+ * @param name - the column name
1541
+ * @returns the handle, or INVALID_INDEX
1542
+ */
1543
+ lookup(name) {
1544
+ return this.domain === "node" ? this.sink.nodeColumn(name) : this.sink.edgeColumn(name);
1545
+ }
1546
+ }
1547
+ class ImportContext {
1548
+ /**
1549
+ * Create the context.
1550
+ * @param sink - the sink
1551
+ * @param report - the report
1552
+ * @param options - the resolved common options
1553
+ * @param json - the resolved format options
1554
+ * @param explicitDefaultDirected - whether the caller passed defaultDirected
1555
+ */
1556
+ constructor(sink, report, options, json, explicitDefaultDirected) {
1557
+ this.elementsSinceCheck = 0;
1558
+ this.fileDirected = false;
1559
+ this.sink = sink;
1560
+ this.report = report;
1561
+ this.options = options;
1562
+ this.json = json;
1563
+ this.ids = new IdCoercer(options.ids);
1564
+ this.direction = new DirectionResolver(sink, report, options.onMixedDirection);
1565
+ this.nodes = new AttributeWriter(sink, "node");
1566
+ this.edges = new AttributeWriter(sink, "edge");
1567
+ this.explicitDefaultDirected = explicitDefaultDirected;
1568
+ }
1569
+ /**
1570
+ * Report a `nodeIdFrom` other than "id" for a dialect whose ids are unambiguous.
1571
+ * @param dialect - the dialect
1572
+ * @param idField - where the dialect's ids come from, for the message
1573
+ */
1574
+ reportNodeIdFrom(dialect, idField) {
1575
+ if (this.options.nodeIdFrom !== "id") {
1576
+ this.report.warning(
1577
+ "unsupported",
1578
+ JSON_ISSUE.OPTION_IGNORED,
1579
+ `nodeIdFrom "${this.options.nodeIdFrom}" does not apply to ${dialect}; ids are read from ${idField}`,
1580
+ { element: "nodeIdFrom" }
1581
+ );
1582
+ }
1583
+ }
1584
+ /**
1585
+ * The direction assumed when the file declares none: the caller's `defaultDirected` when given,
1586
+ * else the dialect's convention.
1587
+ * @param dialect - the dialect
1588
+ * @returns the direction
1589
+ */
1590
+ defaultDirected(dialect) {
1591
+ return this.explicitDefaultDirected ? this.options.defaultDirected : DIALECT_DEFAULT_DIRECTED[dialect];
1592
+ }
1593
+ /**
1594
+ * Rule 1 of design section 8.4: set the sink's direction from the file's header (or the
1595
+ * dialect's default) before the first edge, remembering the file's direction for uniformKind().
1596
+ * @param directed - the file's direction
1597
+ */
1598
+ setHeader(directed) {
1599
+ this.fileDirected = directed;
1600
+ this.direction.setHeader(directed);
1601
+ }
1602
+ /**
1603
+ * The edge kind every edge of a dialect without per-edge direction has: the file's direction
1604
+ * (the resolver expands it when the sink's direction differs).
1605
+ * @returns the kind
1606
+ */
1607
+ uniformKind() {
1608
+ return this.fileDirected ? "directed" : "undirected";
1609
+ }
1610
+ /**
1611
+ * Coerce a node id value per the `ids` option, reporting what cannot be an id. A JSON boolean or
1612
+ * null is `unsupported` unless `ids` is "string" (design section 8.5); anything the rule rejects
1613
+ * is recorded through the per-element catch.
1614
+ * @param raw - the JSON value; undefined when the record has no id key
1615
+ * @param element - the element name for the issue
1616
+ * @returns the id, or null when the value was reported and the element must be skipped
1617
+ */
1618
+ coerceId(raw, element) {
1619
+ if (raw === void 0) {
1620
+ this.report.error("missing-value", JSON_ISSUE.MISSING_ID, `${element} has no id`, { element });
1621
+ return null;
1622
+ }
1623
+ if ((typeof raw === "boolean" || raw === null) && this.options.ids !== "string") {
1624
+ const kind = raw === null ? "null" : "boolean";
1625
+ this.report.error(
1626
+ "unsupported",
1627
+ JSON_ISSUE.UNSUPPORTED_ID,
1628
+ `${element}: a JSON ${kind} is not a node id (pass ids: "string" to coerce it)`,
1629
+ { element }
1630
+ );
1631
+ return null;
1632
+ }
1633
+ let id;
1634
+ try {
1635
+ id = this.ids.value(this.options.ids === "string" && typeof raw === "number" ? String(raw) : raw);
1636
+ } catch (err) {
1637
+ this.report.recordError(err, { element });
1638
+ return null;
1639
+ }
1640
+ const merge = this.ids.lastMerge;
1641
+ if (merge !== null) {
1642
+ this.report.warning(
1643
+ "coercion",
1644
+ JSON_ISSUE.ID_MERGED,
1645
+ `id text ${JSON.stringify(merge.text)} merged with ${JSON.stringify(merge.previousText)} as ${merge.id}`,
1646
+ { element }
1647
+ );
1648
+ }
1649
+ return id;
1650
+ }
1651
+ /**
1652
+ * Coerce an id that must be valid for the caller to proceed (hyperedge members): the rejections
1653
+ * of coerceId() are thrown instead of recorded.
1654
+ * @param raw - the JSON value
1655
+ * @param element - the element name
1656
+ * @returns the id
1657
+ */
1658
+ requireId(raw, element) {
1659
+ if ((typeof raw === "boolean" || raw === null) && this.options.ids !== "string") {
1660
+ const kind = raw === null ? "null" : "boolean";
1661
+ throw new GraphFormatError("E_INVALID_ID", `${element}: a JSON ${kind} is not a node id`, {
1662
+ reason: "unsupported id"
1663
+ });
1664
+ }
1665
+ return this.ids.value(this.options.ids === "string" && typeof raw === "number" ? String(raw) : raw);
1666
+ }
1667
+ /**
1668
+ * Add a node, counting it or recording the failure.
1669
+ * @param id - the node id
1670
+ * @param element - the element name
1671
+ * @returns the node index, or -1 when the sink refused the node
1672
+ */
1673
+ pushNode(id, element) {
1674
+ this.checkAbort();
1675
+ const existing = this.sink.indexOf(id);
1676
+ if (existing !== INVALID_INDEX) {
1677
+ this.report.warning(
1678
+ "merged",
1679
+ JSON_ISSUE.DUPLICATE_NODE,
1680
+ `${element}: node ${JSON.stringify(id)} already exists; its attributes are merged (the later values win)`,
1681
+ { element }
1682
+ );
1683
+ return existing;
1684
+ }
1685
+ let index;
1686
+ try {
1687
+ index = this.sink.addNode(id);
1688
+ } catch (err) {
1689
+ this.skip(err, "node", element);
1690
+ return -1;
1691
+ }
1692
+ this.report.counts.nodes++;
1693
+ return index;
1694
+ }
1695
+ /**
1696
+ * Push one edge through the direction resolver, counting every logical edge the sink gained
1697
+ * (both halves of an expanded edge, and the mirrors of an in-place expansion).
1698
+ * @param source - the source id
1699
+ * @param target - the target id
1700
+ * @param kind - the edge's direction in the file
1701
+ * @param weight - the weight, or undefined
1702
+ * @param element - the element name for issues
1703
+ * @returns the primary edge index
1704
+ */
1705
+ pushEdge(source, target, kind, weight, element) {
1706
+ this.checkAbort();
1707
+ const before = this.sink.edgeCount;
1708
+ let created = this.sink.indexOf(source) === INVALID_INDEX ? 1 : 0;
1709
+ if (source !== target && this.sink.indexOf(target) === INVALID_INDEX) {
1710
+ created++;
1711
+ }
1712
+ const edge = this.direction.addEdge(source, target, kind, weight, { element });
1713
+ this.report.counts.edges += this.sink.edgeCount - before;
1714
+ this.report.counts.nodes += created;
1715
+ return edge;
1716
+ }
1717
+ /**
1718
+ * Check the cancellation signal every ABORT_CHECK_INTERVAL pushed elements, so an abort raised
1719
+ * while the whole in-memory document is being walked rejects promptly.
1720
+ */
1721
+ checkAbort() {
1722
+ if (++this.elementsSinceCheck >= ABORT_CHECK_INTERVAL) {
1723
+ this.elementsSinceCheck = 0;
1724
+ throwIfAborted(this.options.signal);
1725
+ }
1726
+ }
1727
+ /**
1728
+ * Record a per-element failure and count the skipped element.
1729
+ * @param err - the thrown value
1730
+ * @param domain - which counter to bump
1731
+ * @param element - the element name
1732
+ */
1733
+ skip(err, domain, element) {
1734
+ this.report.recordError(err, { element });
1735
+ if (domain === "node") {
1736
+ this.report.counts.skippedNodes++;
1737
+ } else {
1738
+ this.report.counts.skippedEdges++;
1739
+ }
1740
+ }
1741
+ /**
1742
+ * Report an element that is not an object and count it as skipped.
1743
+ * @param domain - node or edge
1744
+ * @param element - the element name
1745
+ * @param what - what was expected
1746
+ */
1747
+ badElement(domain, element, what = "an object") {
1748
+ this.report.error("validation-error", JSON_ISSUE.BAD_ELEMENT, `${element} is not ${what}`, { element });
1749
+ this.countSkipped(domain);
1750
+ }
1751
+ /**
1752
+ * Count a skipped element whose issue was already recorded.
1753
+ * @param domain - node or edge
1754
+ */
1755
+ countSkipped(domain) {
1756
+ if (domain === "node") {
1757
+ this.report.counts.skippedNodes++;
1758
+ } else {
1759
+ this.report.counts.skippedEdges++;
1760
+ }
1761
+ }
1762
+ /**
1763
+ * Report an edge record without an endpoint and count it as skipped.
1764
+ * @param element - the element name
1765
+ * @param field - the missing field
1766
+ */
1767
+ missingEndpoint(element, field) {
1768
+ this.report.error("missing-value", JSON_ISSUE.MISSING_ENDPOINT, `${element} has no ${field}`, { element });
1769
+ this.report.counts.skippedEdges++;
1770
+ }
1771
+ /**
1772
+ * Record the shape metadata, the source format and further metadata fields in one setMeta()
1773
+ * call (the sink replaces `extra` as a whole).
1774
+ * @param shape - the shape record
1775
+ * @param patch - further metadata fields
1776
+ */
1777
+ setMeta(shape, patch = {}) {
1778
+ const extra = {};
1779
+ for (const [key, value] of Object.entries(shape)) {
1780
+ if (value !== void 0) {
1781
+ extra[key] = value;
1782
+ }
1783
+ }
1784
+ this.sink.setMeta({ sourceFormat: "json", ...patch, extra: { [META_KEY]: extra } });
1785
+ }
1786
+ /**
1787
+ * Record which source field the weight came from, so the exporter writes it back under the same
1788
+ * key (design section 3.7, `meta.weightOrigin`).
1789
+ * @returns the metadata patch, empty for an unweighted import
1790
+ */
1791
+ weightOriginPatch() {
1792
+ const { weightFrom } = this.options;
1793
+ if (weightFrom === null) {
1794
+ return {};
1795
+ }
1796
+ return { weightOrigin: { format: "json", id: weightFrom, title: null, type: null, namespace: null } };
1797
+ }
1798
+ /**
1799
+ * Write the graph-level attributes of a dict (NetworkX `graph`, JGF `metadata`, graphology
1800
+ * `attributes`, Cytoscape `data`), one column per key.
1801
+ * @param dict - the dict, or anything else (then reported)
1802
+ * @param what - the dict's name for the issue
1803
+ */
1804
+ writeGraphDict(dict, what) {
1805
+ if (dict === void 0 || dict === null) {
1806
+ return;
1807
+ }
1808
+ if (!isJsonObject(dict)) {
1809
+ this.report.warning("validation-error", JSON_ISSUE.BAD_FLAG, `${what} is not an object; ignored`, {
1810
+ element: what
1811
+ });
1812
+ return;
1813
+ }
1814
+ for (const key of Object.keys(dict)) {
1815
+ const value = dict[key];
1816
+ if (value === void 0 || value === null) {
1817
+ continue;
1818
+ }
1819
+ try {
1820
+ this.sink.setGraphValue(key, value);
1821
+ } catch (err) {
1822
+ this.report.recordError(err, { element: `${what}.${key}` });
1823
+ }
1824
+ }
1825
+ }
1826
+ /**
1827
+ * Read the weight field of an edge record.
1828
+ * @param record - the record holding the attributes
1829
+ * @returns the weight, or undefined when absent or null; E_INVALID_WEIGHT otherwise
1830
+ */
1831
+ weightOf(record) {
1832
+ const { weightFrom } = this.options;
1833
+ if (weightFrom === null || !hasKey(record, weightFrom)) {
1834
+ return void 0;
1835
+ }
1836
+ return weightFromValue(record[weightFrom]);
1837
+ }
1838
+ /**
1839
+ * Write the attributes of a nested dict plus the element-level keys the dialect does not
1840
+ * define (kept with the `#element` suffix).
1841
+ * @param writer - the table writer
1842
+ * @param row - the row
1843
+ * @param record - the element record
1844
+ * @param dict - the nested attribute dict
1845
+ * @param structural - the element keys that are not attributes
1846
+ * @param weightFrom - the weight key to skip in the dict, or null
1847
+ */
1848
+ writeNested(writer, row, record, dict, structural, weightFrom) {
1849
+ for (const key of Object.keys(dict)) {
1850
+ if (key !== weightFrom) {
1851
+ writer.write(row, key, dict[key], SUFFIX.data);
1852
+ }
1853
+ }
1854
+ for (const key of Object.keys(record)) {
1855
+ if (!structural.has(key)) {
1856
+ writer.write(row, `${key}${SUFFIX.element}`, record[key], SUFFIX.data);
1857
+ }
1858
+ }
1859
+ }
1860
+ /**
1861
+ * Write a spec-typed string field (JGF label / relation), reporting a value of another type.
1862
+ * @param writer - the table writer
1863
+ * @param column - the declared column, or INVALID_INDEX when the file has no such field
1864
+ * @param row - the row
1865
+ * @param value - the value
1866
+ * @param field - the field name
1867
+ * @param element - the element name
1868
+ */
1869
+ writeStringField(writer, column, row, value, field, element) {
1870
+ if (column === INVALID_INDEX || value === void 0 || value === null) {
1871
+ return;
1872
+ }
1873
+ if (typeof value !== "string") {
1874
+ this.report.error(
1875
+ "validation-error",
1876
+ JSON_ISSUE.BAD_VALUE,
1877
+ `${element}: ${field} must be a string, found ${describe(value)}`,
1878
+ { element }
1879
+ );
1880
+ return;
1881
+ }
1882
+ writer.set(column, row, value);
1883
+ }
1884
+ /**
1885
+ * Declare an edge id column with role "id" from a scan of the file's edge ids: f64 when every
1886
+ * id is a number, string otherwise (numbers are then stored as their text and reported once).
1887
+ * @param edges - the edge records
1888
+ * @param read - how to read an edge's raw id
1889
+ * @param name - the column name
1890
+ * @param unique - whether uniqueness is enforced at freeze
1891
+ * @returns the column, or null when no edge has an id
1892
+ */
1893
+ declareEdgeIds(edges, read, name, unique) {
1894
+ let numbers = 0;
1895
+ let strings = 0;
1896
+ for (const edge of edges) {
1897
+ if (!isJsonObject(edge)) {
1898
+ continue;
1899
+ }
1900
+ const raw = read(edge);
1901
+ if (typeof raw === "number") {
1902
+ numbers++;
1903
+ } else if (typeof raw === "string") {
1904
+ strings++;
1905
+ }
1906
+ }
1907
+ if (numbers + strings === 0) {
1908
+ return null;
1909
+ }
1910
+ const dtype = strings === 0 ? "f64" : "string";
1911
+ const columnName = uniqueColumnName(name, "id", (candidate) => this.edges.taken(candidate));
1912
+ const handle = this.edges.declare({ name: columnName, dtype, role: "id", nullable: true, unique });
1913
+ const stringify = strings > 0 && numbers > 0;
1914
+ const seen = unique ? /* @__PURE__ */ new Set() : null;
1915
+ if (stringify) {
1916
+ this.report.warning(
1917
+ "coercion",
1918
+ JSON_ISSUE.EDGE_ID_STRINGIFIED,
1919
+ `edge ids mix numbers and strings; ${numbers} numeric id(s) stored as text`,
1920
+ { element: columnName }
1921
+ );
1922
+ }
1923
+ return { handle, stringify, seen };
1924
+ }
1925
+ /**
1926
+ * The value an edge id column stores for a raw id, checked BEFORE the edge is pushed so a bad
1927
+ * id skips the edge without touching the sink (design section 11.1).
1928
+ * @param column - the column, or null when the file has no edge ids
1929
+ * @param raw - the raw id value
1930
+ * @returns the value to store, or null when there is nothing to store
1931
+ */
1932
+ edgeIdValue(column, raw) {
1933
+ if (column === null || raw === void 0 || raw === null) {
1934
+ return null;
1935
+ }
1936
+ let value;
1937
+ if (typeof raw === "number") {
1938
+ value = column.stringify ? String(raw) : raw;
1939
+ } else if (typeof raw === "string") {
1940
+ value = raw;
1941
+ } else {
1942
+ throw new GraphFormatError(
1943
+ "E_COLUMN_TYPE",
1944
+ `an edge id must be a string or a number, found ${describe(raw)}`,
1945
+ { found: typeof raw }
1946
+ );
1947
+ }
1948
+ if (column.seen !== null) {
1949
+ const text = String(value);
1950
+ if (column.seen.has(text)) {
1951
+ throw new GraphFormatError(
1952
+ JSON_ISSUE.DUPLICATE_EDGE_ID,
1953
+ `edge id ${JSON.stringify(value)} is declared more than once; the edge is skipped`,
1954
+ { id: value }
1955
+ );
1956
+ }
1957
+ column.seen.add(text);
1958
+ }
1959
+ return value;
1960
+ }
1961
+ /**
1962
+ * Write an edge id value from edgeIdValue().
1963
+ * @param column - the column, or null
1964
+ * @param edge - the edge index
1965
+ * @param value - the value, or null for none
1966
+ */
1967
+ setEdgeId(column, edge, value) {
1968
+ if (column !== null && value !== null) {
1969
+ this.edges.set(column.handle, edge, value);
1970
+ }
1971
+ }
1972
+ }
1973
+ function parseDocument(text, report) {
1974
+ if (text.trim().length === 0) {
1975
+ report.fail(JSON_ISSUE.EMPTY_INPUT, "the input is empty");
1976
+ }
1977
+ try {
1978
+ return JSON.parse(text);
1979
+ } catch (err) {
1980
+ const message = err instanceof Error ? err.message : String(err);
1981
+ return report.fail(JSON_ISSUE.SYNTAX, `invalid JSON: ${message}`);
1982
+ }
1983
+ }
1984
+ function detectDialect(root, forced, report) {
1985
+ if (forced !== "auto") {
1986
+ return forced;
1987
+ }
1988
+ const dialect = sniffJsonDialect(root);
1989
+ if (dialect !== null) {
1990
+ return dialect;
1991
+ }
1992
+ if (Array.isArray(root)) {
1993
+ return report.fail(
1994
+ JSON_ISSUE.DIALECT,
1995
+ "a top-level array is only read as Cytoscape elements (objects with a data record)"
1996
+ );
1997
+ }
1998
+ if (!isJsonObject(root)) {
1999
+ return report.fail(JSON_ISSUE.DIALECT, `the document is a JSON ${describe(root)}, not a graph object`);
2000
+ }
2001
+ return report.fail(
2002
+ JSON_ISSUE.DIALECT,
2003
+ "no known dialect: expected nodes / links / edges (node-link), elements (Cytoscape) or graph (JGF)"
2004
+ );
2005
+ }
2006
+ function arraySection(value, what, report) {
2007
+ if (value === void 0 || value === null) {
2008
+ return null;
2009
+ }
2010
+ if (!Array.isArray(value)) {
2011
+ return report.fail(JSON_ISSUE.SHAPE, `${what} must be an array, found ${describe(value)}`, { element: what });
2012
+ }
2013
+ return value;
2014
+ }
2015
+ function flagOf(value, what, fallback, report) {
2016
+ if (value === void 0 || value === null) {
2017
+ return fallback;
2018
+ }
2019
+ if (typeof value === "boolean") {
2020
+ return value;
2021
+ }
2022
+ report.warning(
2023
+ "validation-error",
2024
+ JSON_ISSUE.BAD_FLAG,
2025
+ `${what} is ${describe(value)}, not a boolean; ${fallback} assumed`,
2026
+ { element: what }
2027
+ );
2028
+ return fallback;
2029
+ }
2030
+ function endpointOf(record, explicit, defaults) {
2031
+ if (explicit !== null) {
2032
+ return { key: hasKey(record, explicit) ? explicit : null, value: record[explicit] };
2033
+ }
2034
+ for (const key of defaults) {
2035
+ if (hasKey(record, key)) {
2036
+ return { key, value: record[key] };
2037
+ }
2038
+ }
2039
+ return { key: null, value: void 0 };
2040
+ }
2041
+ function anyHas(items, key) {
2042
+ return items.some((item) => isJsonObject(item) && item[key] !== void 0 && item[key] !== null);
2043
+ }
2044
+ function isIndexBelow(value, bound) {
2045
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value < bound;
2046
+ }
2047
+ function importNodeLink(ctx, root, dialect) {
2048
+ const { report, json } = ctx;
2049
+ let { edgesKey } = json;
2050
+ if (edgesKey === null) {
2051
+ edgesKey = hasKey(root, "edges") || !hasKey(root, "links") ? "edges" : "links";
2052
+ }
2053
+ const nodes = arraySection(root.nodes, "nodes", report);
2054
+ const edges = arraySection(root[edgesKey], edgesKey, report);
2055
+ if (nodes === null) {
2056
+ report.error(
2057
+ "missing-value",
2058
+ JSON_ISSUE.MISSING_SECTION,
2059
+ "the document has no nodes array; nodes come from the edges",
2060
+ { element: "nodes" }
2061
+ );
2062
+ }
2063
+ if (edges === null) {
2064
+ report.error(
2065
+ "missing-value",
2066
+ JSON_ISSUE.MISSING_SECTION,
2067
+ `the document has no ${edgesKey} array; the graph has no edges`,
2068
+ { element: edgesKey }
2069
+ );
2070
+ }
2071
+ const directed = flagOf(root.directed, "directed", ctx.defaultDirected(dialect), report);
2072
+ const multigraph = hasKey(root, "multigraph") ? flagOf(root.multigraph, "multigraph", false, report) : null;
2073
+ ctx.setHeader(directed);
2074
+ ctx.writeGraphDict(root.graph, "graph");
2075
+ const nodeList = nodes ?? [];
2076
+ const edgeList = edges ?? [];
2077
+ ctx.sink.reserve(nodeList.length, edgeList.length);
2078
+ let { nodeIdKey } = json;
2079
+ let positional = ctx.options.nodeIdFrom === "index";
2080
+ if (!positional && nodeIdKey === null) {
2081
+ const candidates = ctx.options.nodeIdFrom === "label" ? ["label", "name", "id"] : ["id", "name"];
2082
+ nodeIdKey = candidates.find((key) => anyHas(nodeList, key)) ?? null;
2083
+ if (ctx.options.nodeIdFrom === "label" && nodeIdKey === "id") {
2084
+ report.warning(
2085
+ "unsupported",
2086
+ JSON_ISSUE.OPTION_IGNORED,
2087
+ 'nodeIdFrom "label": no node has a label or name key; ids are read from "id"',
2088
+ { element: "nodeIdFrom" }
2089
+ );
2090
+ }
2091
+ if (nodeIdKey === null) {
2092
+ if (nodeList.length > 0) {
2093
+ positional = true;
2094
+ report.warning(
2095
+ "missing-value",
2096
+ JSON_ISSUE.POSITIONAL_NODES,
2097
+ "no node has an id or name key; array positions are the node ids",
2098
+ { element: "nodes" }
2099
+ );
2100
+ } else {
2101
+ nodeIdKey = "id";
2102
+ }
2103
+ }
2104
+ }
2105
+ if (positional) {
2106
+ nodeIdKey = null;
2107
+ }
2108
+ let indexLinks;
2109
+ if (positional) {
2110
+ indexLinks = true;
2111
+ } else if (json.indexLinks === "auto") {
2112
+ indexLinks = nodeIdKey !== null && looksIndexLinked(nodeList, edgeList, nodeIdKey, json);
2113
+ } else {
2114
+ ({ indexLinks } = json);
2115
+ }
2116
+ const positionIds = indexLinks ? [] : null;
2117
+ for (let i = 0; i < nodeList.length; i++) {
2118
+ const element = `nodes[${i}]`;
2119
+ const record = nodeList[i];
2120
+ let pushed = null;
2121
+ if (!isJsonObject(record)) {
2122
+ ctx.badElement("node", element);
2123
+ } else {
2124
+ let id;
2125
+ if (nodeIdKey === null) {
2126
+ id = i;
2127
+ } else {
2128
+ id = ctx.coerceId(hasKey(record, nodeIdKey) ? record[nodeIdKey] : void 0, element);
2129
+ }
2130
+ if (id === null) {
2131
+ ctx.countSkipped("node");
2132
+ } else {
2133
+ const index = ctx.pushNode(id, element);
2134
+ if (index >= 0) {
2135
+ pushed = id;
2136
+ writeFlat(ctx, ctx.nodes, index, record, id, (key) => key !== nodeIdKey);
2137
+ }
2138
+ }
2139
+ }
2140
+ positionIds?.push(pushed);
2141
+ }
2142
+ throwIfAborted(ctx.options.signal);
2143
+ const endpointKeys = importNodeLinkEdges(ctx, edgeList, edgesKey, positionIds);
2144
+ ctx.setMeta(
2145
+ {
2146
+ dialect,
2147
+ edgesKey,
2148
+ nodeIdKey,
2149
+ indexLinks,
2150
+ sourceKey: endpointKeys.source ?? void 0,
2151
+ targetKey: endpointKeys.target ?? void 0
2152
+ },
2153
+ { declaredMultigraph: multigraph, ...ctx.weightOriginPatch() }
2154
+ );
2155
+ }
2156
+ function importNodeLinkEdges(ctx, edgeList, edgesKey, positionIds) {
2157
+ const { json } = ctx;
2158
+ const kind = ctx.uniformKind();
2159
+ let sourceKey = null;
2160
+ let targetKey = null;
2161
+ for (let i = 0; i < edgeList.length; i++) {
2162
+ const element = `${edgesKey}[${i}]`;
2163
+ const record = edgeList[i];
2164
+ if (!isJsonObject(record)) {
2165
+ ctx.badElement("edge", element);
2166
+ continue;
2167
+ }
2168
+ const source = endpointOf(record, json.sourceKey, NODE_LINK_SOURCE_KEYS);
2169
+ const target = endpointOf(record, json.targetKey, NODE_LINK_TARGET_KEYS);
2170
+ if (source.key === null || target.key === null) {
2171
+ ctx.missingEndpoint(element, source.key === null ? "source" : "target");
2172
+ continue;
2173
+ }
2174
+ sourceKey ??= source.key;
2175
+ targetKey ??= target.key;
2176
+ try {
2177
+ const u = resolveEndpoint(ctx, source.value, positionIds, element, "source");
2178
+ const v = resolveEndpoint(ctx, target.value, positionIds, element, "target");
2179
+ if (u === null || v === null) {
2180
+ ctx.countSkipped("edge");
2181
+ continue;
2182
+ }
2183
+ const weight = ctx.weightOf(record);
2184
+ const edge = ctx.pushEdge(u, v, kind, weight, element);
2185
+ const { weightFrom } = ctx.options;
2186
+ for (const key of Object.keys(record)) {
2187
+ if (key !== source.key && key !== target.key && key !== weightFrom) {
2188
+ ctx.edges.write(edge, key, record[key], SUFFIX.data);
2189
+ }
2190
+ }
2191
+ } catch (err) {
2192
+ ctx.skip(err, "edge", element);
2193
+ }
2194
+ }
2195
+ return { source: sourceKey, target: targetKey };
2196
+ }
2197
+ function writeFlat(ctx, writer, index, record, id, keep) {
2198
+ try {
2199
+ for (const key of Object.keys(record)) {
2200
+ if (keep(key)) {
2201
+ writer.write(index, key, record[key], SUFFIX.data);
2202
+ }
2203
+ }
2204
+ } catch (err) {
2205
+ ctx.report.recordError(err, { element: String(id) });
2206
+ }
2207
+ }
2208
+ function looksIndexLinked(nodes, edges, nodeIdKey, json) {
2209
+ if (edges.length === 0 || nodes.length === 0) {
2210
+ return false;
2211
+ }
2212
+ for (const node of nodes) {
2213
+ if (isJsonObject(node) && typeof node[nodeIdKey] === "number") {
2214
+ return false;
2215
+ }
2216
+ }
2217
+ let seen = 0;
2218
+ for (const edge of edges) {
2219
+ if (!isJsonObject(edge)) {
2220
+ continue;
2221
+ }
2222
+ const s = endpointOf(edge, json.sourceKey, NODE_LINK_SOURCE_KEYS).value;
2223
+ const t = endpointOf(edge, json.targetKey, NODE_LINK_TARGET_KEYS).value;
2224
+ if (!isIndexBelow(s, Infinity) || !isIndexBelow(t, Infinity)) {
2225
+ return false;
2226
+ }
2227
+ seen++;
2228
+ }
2229
+ return seen > 0;
2230
+ }
2231
+ function resolveEndpoint(ctx, raw, positionIds, element, field) {
2232
+ if (positionIds === null) {
2233
+ return ctx.coerceId(raw, `${element}.${field}`);
2234
+ }
2235
+ if (!isIndexBelow(raw, positionIds.length)) {
2236
+ ctx.report.error(
2237
+ "validation-error",
2238
+ JSON_ISSUE.BAD_INDEX,
2239
+ `${element}: ${field} ${describe(raw)} is not a node index below ${positionIds.length}`,
2240
+ { element }
2241
+ );
2242
+ return null;
2243
+ }
2244
+ const id = positionIds[raw];
2245
+ if (id === null) {
2246
+ ctx.report.error("missing-value", JSON_ISSUE.BAD_INDEX, `${element}: ${field} names a node that was skipped`, {
2247
+ element
2248
+ });
2249
+ }
2250
+ return id;
2251
+ }
2252
+ function importVis(ctx, root) {
2253
+ const { report, json } = ctx;
2254
+ const nodes = arraySection(root.nodes, "nodes", report) ?? [];
2255
+ const edges = arraySection(root.edges, "edges", report) ?? [];
2256
+ ctx.setHeader(ctx.defaultDirected("vis"));
2257
+ ctx.sink.reserve(nodes.length, edges.length);
2258
+ const nodeIdKey = json.nodeIdKey ?? "id";
2259
+ ctx.reportNodeIdFrom("vis", `the ${JSON.stringify(nodeIdKey)} key`);
2260
+ for (let i = 0; i < nodes.length; i++) {
2261
+ const element = `nodes[${i}]`;
2262
+ const record = nodes[i];
2263
+ if (!isJsonObject(record)) {
2264
+ ctx.badElement("node", element);
2265
+ continue;
2266
+ }
2267
+ const id = ctx.coerceId(hasKey(record, nodeIdKey) ? record[nodeIdKey] : void 0, element);
2268
+ if (id === null) {
2269
+ ctx.countSkipped("node");
2270
+ continue;
2271
+ }
2272
+ const index = ctx.pushNode(id, element);
2273
+ if (index >= 0) {
2274
+ writeFlat(ctx, ctx.nodes, index, record, id, (key) => key !== nodeIdKey);
2275
+ }
2276
+ }
2277
+ throwIfAborted(ctx.options.signal);
2278
+ const ids = ctx.declareEdgeIds(edges, (edge) => edge.id, "id", true);
2279
+ const kind = ctx.uniformKind();
2280
+ let sourceKey = null;
2281
+ let targetKey = null;
2282
+ for (let i = 0; i < edges.length; i++) {
2283
+ const element = `edges[${i}]`;
2284
+ const record = edges[i];
2285
+ if (!isJsonObject(record)) {
2286
+ ctx.badElement("edge", element);
2287
+ continue;
2288
+ }
2289
+ const source = endpointOf(record, json.sourceKey, VIS_SOURCE_KEYS);
2290
+ const target = endpointOf(record, json.targetKey, VIS_TARGET_KEYS);
2291
+ if (source.key === null || target.key === null) {
2292
+ ctx.missingEndpoint(element, source.key === null ? "from" : "to");
2293
+ continue;
2294
+ }
2295
+ sourceKey ??= source.key;
2296
+ targetKey ??= target.key;
2297
+ try {
2298
+ const u = ctx.coerceId(source.value, `${element}.from`);
2299
+ const v = ctx.coerceId(target.value, `${element}.to`);
2300
+ if (u === null || v === null) {
2301
+ ctx.countSkipped("edge");
2302
+ continue;
2303
+ }
2304
+ const idValue = ctx.edgeIdValue(ids, record.id);
2305
+ const edge = ctx.pushEdge(u, v, kind, ctx.weightOf(record), element);
2306
+ ctx.setEdgeId(ids, edge, idValue);
2307
+ const { weightFrom } = ctx.options;
2308
+ for (const key of Object.keys(record)) {
2309
+ if (key !== source.key && key !== target.key && key !== "id" && key !== weightFrom) {
2310
+ ctx.edges.write(edge, key, record[key], SUFFIX.data);
2311
+ }
2312
+ }
2313
+ } catch (err) {
2314
+ ctx.skip(err, "edge", element);
2315
+ }
2316
+ }
2317
+ ctx.setMeta(
2318
+ { dialect: "vis", nodeIdKey, sourceKey: sourceKey ?? void 0, targetKey: targetKey ?? void 0 },
2319
+ ctx.weightOriginPatch()
2320
+ );
2321
+ }
2322
+ function importGraphology(ctx, root) {
2323
+ const { report } = ctx;
2324
+ const nodes = arraySection(root.nodes, "nodes", report) ?? [];
2325
+ const edges = arraySection(root.edges, "edges", report) ?? [];
2326
+ const options = isJsonObject(root.options) ? root.options : {};
2327
+ if (hasKey(root, "options") && !isJsonObject(root.options)) {
2328
+ report.warning("validation-error", JSON_ISSUE.BAD_FLAG, "options is not an object; ignored", {
2329
+ element: "options"
2330
+ });
2331
+ }
2332
+ let type;
2333
+ if (options.type === "directed" || options.type === "undirected" || options.type === "mixed") {
2334
+ ({ type } = options);
2335
+ } else {
2336
+ if (options.type !== void 0 && options.type !== null) {
2337
+ report.warning(
2338
+ "validation-error",
2339
+ JSON_ISSUE.BAD_FLAG,
2340
+ `options.type ${describe(options.type)} is not directed, undirected or mixed; mixed assumed`,
2341
+ { element: "options.type" }
2342
+ );
2343
+ }
2344
+ type = "mixed";
2345
+ }
2346
+ let directed;
2347
+ if (type === "mixed") {
2348
+ const objects = edges.filter((edge) => isJsonObject(edge));
2349
+ const undirectedEdges = objects.filter((edge) => edge.undirected === true).length;
2350
+ directed = objects.length === 0 ? ctx.defaultDirected("graphology") : undirectedEdges < objects.length;
2351
+ } else {
2352
+ directed = type === "directed";
2353
+ }
2354
+ ctx.setHeader(directed);
2355
+ const multi = hasKey(options, "multi") ? flagOf(options.multi, "options.multi", false, report) : null;
2356
+ const allowSelfLoops = hasKey(options, "allowSelfLoops") ? flagOf(options.allowSelfLoops, "options.allowSelfLoops", true, report) : void 0;
2357
+ ctx.writeGraphDict(root.attributes, "attributes");
2358
+ ctx.reportNodeIdFrom("graphology", "the node key");
2359
+ ctx.sink.reserve(nodes.length, edges.length);
2360
+ for (let i = 0; i < nodes.length; i++) {
2361
+ const element = `nodes[${i}]`;
2362
+ const record = nodes[i];
2363
+ if (!isJsonObject(record)) {
2364
+ ctx.badElement("node", element);
2365
+ continue;
2366
+ }
2367
+ const id = ctx.coerceId(hasKey(record, "key") ? record.key : void 0, element);
2368
+ if (id === null) {
2369
+ ctx.countSkipped("node");
2370
+ continue;
2371
+ }
2372
+ pushNestedNode(ctx, id, record, "attributes", GRAPHOLOGY_NODE_KEYS, element);
2373
+ }
2374
+ throwIfAborted(ctx.options.signal);
2375
+ const ids = ctx.declareEdgeIds(edges, (edge) => edge.key, "key", true);
2376
+ for (let i = 0; i < edges.length; i++) {
2377
+ const element = `edges[${i}]`;
2378
+ const record = edges[i];
2379
+ if (!isJsonObject(record)) {
2380
+ ctx.badElement("edge", element);
2381
+ continue;
2382
+ }
2383
+ if (!hasKey(record, "source") || !hasKey(record, "target")) {
2384
+ ctx.missingEndpoint(element, hasKey(record, "source") ? "target" : "source");
2385
+ continue;
2386
+ }
2387
+ try {
2388
+ const u = ctx.coerceId(record.source, `${element}.source`);
2389
+ const v = ctx.coerceId(record.target, `${element}.target`);
2390
+ if (u === null || v === null) {
2391
+ ctx.countSkipped("edge");
2392
+ continue;
2393
+ }
2394
+ let kind;
2395
+ if (type === "mixed") {
2396
+ kind = flagOf(record.undirected, `${element}.undirected`, false, report) ? "undirected" : "directed";
2397
+ } else {
2398
+ kind = type;
2399
+ }
2400
+ const attributes = isJsonObject(record.attributes) ? record.attributes : {};
2401
+ const idValue = ctx.edgeIdValue(ids, record.key);
2402
+ const edge = ctx.pushEdge(u, v, kind, ctx.weightOf(attributes), element);
2403
+ ctx.setEdgeId(ids, edge, idValue);
2404
+ ctx.writeNested(ctx.edges, edge, record, attributes, GRAPHOLOGY_EDGE_KEYS, ctx.options.weightFrom);
2405
+ } catch (err) {
2406
+ ctx.skip(err, "edge", element);
2407
+ }
2408
+ }
2409
+ ctx.setMeta({ dialect: "graphology", allowSelfLoops }, { declaredMultigraph: multi, ...ctx.weightOriginPatch() });
2410
+ }
2411
+ function pushNestedNode(ctx, id, record, dictKey, structural, element) {
2412
+ const index = ctx.pushNode(id, element);
2413
+ if (index < 0) {
2414
+ return index;
2415
+ }
2416
+ try {
2417
+ const dict = record[dictKey];
2418
+ if (dict !== void 0 && dict !== null && !isJsonObject(dict)) {
2419
+ ctx.report.error(
2420
+ "validation-error",
2421
+ JSON_ISSUE.BAD_VALUE,
2422
+ `${element}: ${dictKey} must be an object, found ${describe(dict)}`,
2423
+ { element }
2424
+ );
2425
+ }
2426
+ ctx.writeNested(ctx.nodes, index, record, isJsonObject(dict) ? dict : {}, structural, null);
2427
+ } catch (err) {
2428
+ ctx.report.recordError(err, { element: String(id) });
2429
+ }
2430
+ return index;
2431
+ }
2432
+ function importJgf(ctx, root) {
2433
+ const { report } = ctx;
2434
+ const graph = jgfGraphOf(ctx, root);
2435
+ const edges = arraySection(graph.edges, "graph.edges", report) ?? [];
2436
+ const hyperedges = arraySection(graph.hyperedges, "graph.hyperedges", report) ?? [];
2437
+ const nodesRaw = graph.nodes;
2438
+ if (nodesRaw !== void 0 && nodesRaw !== null && !isJsonObject(nodesRaw) && !Array.isArray(nodesRaw)) {
2439
+ report.fail(
2440
+ JSON_ISSUE.SHAPE,
2441
+ `graph.nodes must be an object keyed by id or an array, found ${describe(nodesRaw)}`
2442
+ );
2443
+ }
2444
+ const nodeRecords = Array.isArray(nodesRaw) ? nodesRaw : Object.values(nodesRaw ?? {});
2445
+ let directed;
2446
+ if (typeof graph.directed === "boolean") {
2447
+ ({ directed } = graph);
2448
+ } else if (ctx.explicitDefaultDirected) {
2449
+ directed = ctx.options.defaultDirected;
2450
+ } else {
2451
+ if (graph.directed !== void 0 && graph.directed !== null) {
2452
+ report.warning(
2453
+ "validation-error",
2454
+ JSON_ISSUE.BAD_FLAG,
2455
+ `graph.directed is ${describe(graph.directed)}, not a boolean`,
2456
+ { element: "graph.directed" }
2457
+ );
2458
+ }
2459
+ const objects = edges.filter((edge) => isJsonObject(edge));
2460
+ directed = objects.length === 0 || !objects.every((edge) => edge.directed === false);
2461
+ }
2462
+ ctx.setHeader(directed);
2463
+ ctx.writeGraphDict(graph.metadata, "graph.metadata");
2464
+ const shape = {
2465
+ dialect: "jgf",
2466
+ id: typeof graph.id === "string" ? graph.id : void 0,
2467
+ type: typeof graph.type === "string" ? graph.type : void 0
2468
+ };
2469
+ const label = typeof graph.label === "string" ? graph.label : null;
2470
+ ctx.reportNodeIdFrom("jgf", "the node keys");
2471
+ ctx.sink.reserve(nodeRecords.length, edges.length);
2472
+ const labelColumn = ctx.nodes.declareIf(
2473
+ { name: "label", dtype: "string", role: "label", nullable: true },
2474
+ anyHas(nodeRecords, "label")
2475
+ );
2476
+ if (isJsonObject(nodesRaw)) {
2477
+ for (const key of Object.keys(nodesRaw)) {
2478
+ const element = `nodes[${JSON.stringify(key)}]`;
2479
+ const record = nodesRaw[key];
2480
+ if (record !== null && !isJsonObject(record)) {
2481
+ ctx.badElement("node", element);
2482
+ continue;
2483
+ }
2484
+ const id = ctx.coerceId(key, element);
2485
+ if (id === null) {
2486
+ ctx.countSkipped("node");
2487
+ continue;
2488
+ }
2489
+ pushJgfNode(ctx, id, record ?? {}, labelColumn, element);
2490
+ }
2491
+ } else {
2492
+ for (let i = 0; i < nodeRecords.length; i++) {
2493
+ const element = `nodes[${i}]`;
2494
+ const record = nodeRecords[i];
2495
+ if (!isJsonObject(record)) {
2496
+ ctx.badElement("node", element);
2497
+ continue;
2498
+ }
2499
+ const id = ctx.coerceId(hasKey(record, "id") ? record.id : void 0, element);
2500
+ if (id === null) {
2501
+ ctx.countSkipped("node");
2502
+ continue;
2503
+ }
2504
+ pushJgfNode(ctx, id, record, labelColumn, element);
2505
+ }
2506
+ }
2507
+ throwIfAborted(ctx.options.signal);
2508
+ const ids = ctx.declareEdgeIds([...edges, ...hyperedges], (edge) => edge.id, "id", false);
2509
+ const relationColumn = ctx.edges.declareIf(
2510
+ { name: "relation", dtype: "string", role: "kind", nullable: true },
2511
+ anyHas(edges, "relation") || anyHas(hyperedges, "relation")
2512
+ );
2513
+ const edgeLabelColumn = ctx.edges.declareIf(
2514
+ { name: "label", dtype: "string", role: "label", nullable: true },
2515
+ anyHas(edges, "label") || anyHas(hyperedges, "label")
2516
+ );
2517
+ const writeJgfEdge = (record, u, v, kind, element, structural = JGF_EDGE_KEYS) => {
2518
+ const { metadata } = record;
2519
+ const dict = isJsonObject(metadata) ? metadata : {};
2520
+ const idValue = ctx.edgeIdValue(ids, record.id);
2521
+ const edge = ctx.pushEdge(u, v, kind, ctx.weightOf(dict), element);
2522
+ ctx.setEdgeId(ids, edge, idValue);
2523
+ ctx.writeStringField(ctx.edges, relationColumn, edge, record.relation, "relation", element);
2524
+ ctx.writeStringField(ctx.edges, edgeLabelColumn, edge, record.label, "label", element);
2525
+ if (metadata !== void 0 && metadata !== null && !isJsonObject(metadata)) {
2526
+ report.error("validation-error", JSON_ISSUE.BAD_VALUE, `${element}: metadata must be an object`, {
2527
+ element
2528
+ });
2529
+ }
2530
+ ctx.writeNested(ctx.edges, edge, record, dict, structural, ctx.options.weightFrom);
2531
+ };
2532
+ for (let i = 0; i < edges.length; i++) {
2533
+ const element = `edges[${i}]`;
2534
+ const record = edges[i];
2535
+ if (!isJsonObject(record)) {
2536
+ ctx.badElement("edge", element);
2537
+ continue;
2538
+ }
2539
+ if (!hasKey(record, "source") || !hasKey(record, "target")) {
2540
+ ctx.missingEndpoint(element, hasKey(record, "source") ? "target" : "source");
2541
+ continue;
2542
+ }
2543
+ try {
2544
+ const u = ctx.coerceId(record.source, `${element}.source`);
2545
+ const v = ctx.coerceId(record.target, `${element}.target`);
2546
+ if (u === null || v === null) {
2547
+ ctx.countSkipped("edge");
2548
+ continue;
2549
+ }
2550
+ const edgeDirected = flagOf(record.directed, `${element}.directed`, directed, report);
2551
+ writeJgfEdge(record, u, v, edgeDirected ? "directed" : "undirected", element);
2552
+ } catch (err) {
2553
+ ctx.skip(err, "edge", element);
2554
+ }
2555
+ }
2556
+ importHyperedges(ctx, hyperedges, directed, writeJgfEdge);
2557
+ ctx.setMeta(shape, { name: label, ...ctx.weightOriginPatch() });
2558
+ }
2559
+ function jgfGraphOf(ctx, root) {
2560
+ const { report } = ctx;
2561
+ if (isJsonObject(root.graph)) {
2562
+ return root.graph;
2563
+ }
2564
+ const graphs = arraySection(root.graphs, "graphs", report) ?? [];
2565
+ if (graphs.length === 0) {
2566
+ report.fail(JSON_ISSUE.SHAPE, "a JGF document needs a graph object or a non-empty graphs array");
2567
+ }
2568
+ if (graphs.length > 1) {
2569
+ report.warning(
2570
+ "unsupported",
2571
+ JSON_ISSUE.MULTIPLE_GRAPHS,
2572
+ `the document holds ${graphs.length} graphs; only graphs[${ctx.json.graphIndex}] is read`,
2573
+ { element: "graphs" }
2574
+ );
2575
+ }
2576
+ if (ctx.json.graphIndex >= graphs.length) {
2577
+ report.fail(JSON_ISSUE.SHAPE, `graphIndex ${ctx.json.graphIndex} is beyond the ${graphs.length} graph(s)`);
2578
+ }
2579
+ const graph = graphs[ctx.json.graphIndex];
2580
+ if (!isJsonObject(graph)) {
2581
+ return report.fail(JSON_ISSUE.SHAPE, `graphs[${ctx.json.graphIndex}] is not an object`);
2582
+ }
2583
+ return graph;
2584
+ }
2585
+ function pushJgfNode(ctx, id, record, labelColumn, element) {
2586
+ const index = ctx.pushNode(id, element);
2587
+ if (index < 0) {
2588
+ return;
2589
+ }
2590
+ try {
2591
+ ctx.writeStringField(ctx.nodes, labelColumn, index, record.label, "label", element);
2592
+ const { metadata } = record;
2593
+ if (metadata !== void 0 && metadata !== null && !isJsonObject(metadata)) {
2594
+ ctx.report.error("validation-error", JSON_ISSUE.BAD_VALUE, `${element}: metadata must be an object`, {
2595
+ element
2596
+ });
2597
+ }
2598
+ ctx.writeNested(ctx.nodes, index, record, isJsonObject(metadata) ? metadata : {}, JGF_NODE_KEYS, null);
2599
+ } catch (err) {
2600
+ ctx.report.recordError(err, { element: String(id) });
2601
+ }
2602
+ }
2603
+ function importHyperedges(ctx, hyperedges, directed, push) {
2604
+ if (hyperedges.length === 0) {
2605
+ return;
2606
+ }
2607
+ const { report } = ctx;
2608
+ const policy = ctx.options.hyperedges;
2609
+ if (policy === "error") {
2610
+ report.error("unsupported", JSON_ISSUE.HYPEREDGE, `${hyperedges.length} hyperedge(s) (hyperedges: "error")`, {
2611
+ element: "hyperedges"
2612
+ });
2613
+ throw report.abort("hyperedges refused", { code: JSON_ISSUE.HYPEREDGE, count: hyperedges.length });
2614
+ }
2615
+ if (policy === "skip") {
2616
+ report.warning("unsupported", JSON_ISSUE.HYPEREDGES_SKIPPED, `${hyperedges.length} hyperedge(s) skipped`, {
2617
+ element: "hyperedges"
2618
+ });
2619
+ report.loss(
2620
+ JSON_ISSUE.HYPEREDGES_SKIPPED,
2621
+ `${hyperedges.length} hyperedge(s) were not imported`,
2622
+ null,
2623
+ hyperedges.length
2624
+ );
2625
+ return;
2626
+ }
2627
+ for (let i = 0; i < hyperedges.length; i++) {
2628
+ const element = `hyperedges[${i}]`;
2629
+ const record = hyperedges[i];
2630
+ if (!isJsonObject(record)) {
2631
+ ctx.badElement("edge", element);
2632
+ continue;
2633
+ }
2634
+ try {
2635
+ if (Array.isArray(record.nodes)) {
2636
+ const members = record.nodes.map((raw) => ctx.requireId(raw, element));
2637
+ if (members.length < 2) {
2638
+ throw new GraphFormatError(
2639
+ "E_INVALID_ID",
2640
+ `${element}: an undirected hyperedge needs two or more nodes`,
2641
+ { reason: "hyperedge shape" }
2642
+ );
2643
+ }
2644
+ const kind = directed ? "directed" : "undirected";
2645
+ if (policy === "star") {
2646
+ for (let k = 1; k < members.length; k++) {
2647
+ push(record, members[0], members[k], kind, element, JGF_HYPEREDGE_KEYS);
2648
+ }
2649
+ } else {
2650
+ for (let a = 0; a < members.length; a++) {
2651
+ for (let b = a + 1; b < members.length; b++) {
2652
+ push(record, members[a], members[b], kind, element, JGF_HYPEREDGE_KEYS);
2653
+ }
2654
+ }
2655
+ }
2656
+ } else if (Array.isArray(record.source) && Array.isArray(record.target)) {
2657
+ const sources = record.source.map((raw) => ctx.requireId(raw, element));
2658
+ const targets = record.target.map((raw) => ctx.requireId(raw, element));
2659
+ if (sources.length === 0 || targets.length === 0) {
2660
+ throw new GraphFormatError(
2661
+ "E_INVALID_ID",
2662
+ `${element}: a directed hyperedge needs sources and targets`,
2663
+ { reason: "hyperedge shape" }
2664
+ );
2665
+ }
2666
+ for (const s of sources) {
2667
+ for (const t of targets) {
2668
+ push(record, s, t, "directed", element, JGF_HYPEREDGE_KEYS);
2669
+ }
2670
+ }
2671
+ } else {
2672
+ report.error(
2673
+ "validation-error",
2674
+ JSON_ISSUE.HYPEREDGE_SHAPE,
2675
+ `${element} has neither a nodes array nor source / target arrays`,
2676
+ { element }
2677
+ );
2678
+ ctx.countSkipped("edge");
2679
+ }
2680
+ } catch (err) {
2681
+ ctx.skip(err, "edge", element);
2682
+ }
2683
+ }
2684
+ }
2685
+ function importCytoscape(ctx, root) {
2686
+ const { report } = ctx;
2687
+ let elements;
2688
+ let extra;
2689
+ if (Array.isArray(root)) {
2690
+ elements = root;
2691
+ } else if (isJsonObject(root)) {
2692
+ ({ elements } = root);
2693
+ ctx.writeGraphDict(root.data, "data");
2694
+ const rest = {};
2695
+ for (const key of Object.keys(root)) {
2696
+ if (key !== "elements" && key !== "data") {
2697
+ rest[key] = root[key];
2698
+ }
2699
+ }
2700
+ if (Object.keys(rest).length > 0) {
2701
+ extra = rest;
2702
+ }
2703
+ } else {
2704
+ report.fail(JSON_ISSUE.SHAPE, `a Cytoscape document must be an object or an array, found ${describe(root)}`);
2705
+ }
2706
+ const { nodes, edges } = cytoscapeSections(ctx, elements);
2707
+ ctx.setHeader(ctx.defaultDirected("cytoscape"));
2708
+ ctx.reportNodeIdFrom("cytoscape", "data.id");
2709
+ ctx.sink.reserve(nodes.length, edges.length);
2710
+ const dataHas = (items, key) => items.some((item) => isJsonObject(item) && isJsonObject(item.data) && item.data[key] !== void 0);
2711
+ const positionColumn = ctx.nodes.declareIf(
2712
+ {
2713
+ name: POSITION_COLUMN,
2714
+ dtype: "f32",
2715
+ components: 3,
2716
+ role: "position",
2717
+ mutable: true,
2718
+ nullable: true,
2719
+ extra: { sourceDims: 2, units: "file" },
2720
+ origin: { format: "json", namespace: "cytoscape" }
2721
+ },
2722
+ anyHas(nodes, "position")
2723
+ );
2724
+ const classesColumn = ctx.nodes.declareIf(
2725
+ { name: CLASSES_COLUMN, dtype: "list", itemDtype: "string", role: "classes", nullable: true },
2726
+ anyHas(nodes, "classes")
2727
+ );
2728
+ const parentColumn = ctx.nodes.declareIf(
2729
+ { name: PARENT_COLUMN, dtype: "u32", role: "parent", refersTo: "node", nullable: true },
2730
+ dataHas(nodes, "parent")
2731
+ );
2732
+ const edgeClassesColumn = ctx.edges.declareIf(
2733
+ { name: CLASSES_COLUMN, dtype: "list", itemDtype: "string", role: "classes", nullable: true },
2734
+ anyHas(edges, "classes")
2735
+ );
2736
+ const parents = [];
2737
+ const point = [0, 0, 0];
2738
+ for (let i = 0; i < nodes.length; i++) {
2739
+ const element = `nodes[${i}]`;
2740
+ const record = nodes[i];
2741
+ if (!isJsonObject(record) || !isJsonObject(record.data)) {
2742
+ ctx.badElement("node", element, "an element with a data object");
2743
+ continue;
2744
+ }
2745
+ const { data } = record;
2746
+ const id = ctx.coerceId(hasKey(data, "id") ? data.id : void 0, element);
2747
+ if (id === null) {
2748
+ ctx.countSkipped("node");
2749
+ continue;
2750
+ }
2751
+ const index = ctx.pushNode(id, element);
2752
+ if (index < 0) {
2753
+ continue;
2754
+ }
2755
+ try {
2756
+ for (const key of Object.keys(data)) {
2757
+ if (key === "id") {
2758
+ continue;
2759
+ }
2760
+ if (key === "parent") {
2761
+ const raw = data.parent;
2762
+ if (raw !== void 0 && raw !== null) {
2763
+ const parent = ctx.coerceId(raw, `${element}.data.parent`);
2764
+ if (parent !== null) {
2765
+ parents.push({ index, parent, element });
2766
+ }
2767
+ }
2768
+ continue;
2769
+ }
2770
+ ctx.nodes.write(index, key, data[key], SUFFIX.data);
2771
+ }
2772
+ const { position } = record;
2773
+ if (position !== void 0 && position !== null) {
2774
+ if (isJsonObject(position) && typeof position.x === "number" && typeof position.y === "number") {
2775
+ point[0] = position.x;
2776
+ point[1] = position.y;
2777
+ ctx.nodes.set(positionColumn, index, point);
2778
+ } else {
2779
+ report.error(
2780
+ "validation-error",
2781
+ JSON_ISSUE.BAD_VALUE,
2782
+ `${element}: position must be an object with numeric x and y`,
2783
+ { element }
2784
+ );
2785
+ }
2786
+ }
2787
+ writeClasses(ctx, ctx.nodes, classesColumn, index, record.classes, element);
2788
+ writeElementKeys(ctx.nodes, index, record);
2789
+ } catch (err) {
2790
+ report.recordError(err, { element: String(id) });
2791
+ }
2792
+ }
2793
+ for (const { index, parent, element } of parents) {
2794
+ const parentIndex = ctx.sink.indexOf(parent);
2795
+ if (parentIndex === INVALID_INDEX) {
2796
+ report.error(
2797
+ "missing-value",
2798
+ JSON_ISSUE.UNKNOWN_PARENT,
2799
+ `${element}: parent ${JSON.stringify(parent)} is not a node`,
2800
+ { element }
2801
+ );
2802
+ continue;
2803
+ }
2804
+ ctx.nodes.set(parentColumn, index, parentIndex);
2805
+ }
2806
+ throwIfAborted(ctx.options.signal);
2807
+ importCytoscapeEdges(ctx, edges, edgeClassesColumn);
2808
+ ctx.setMeta({ dialect: "cytoscape", cytoscape: extra }, ctx.weightOriginPatch());
2809
+ }
2810
+ function importCytoscapeEdges(ctx, edges, edgeClassesColumn) {
2811
+ const ids = ctx.declareEdgeIds(edges, (edge) => isJsonObject(edge.data) ? edge.data.id : void 0, "id", true);
2812
+ const kind = ctx.uniformKind();
2813
+ for (let i = 0; i < edges.length; i++) {
2814
+ const element = `edges[${i}]`;
2815
+ const record = edges[i];
2816
+ if (!isJsonObject(record) || !isJsonObject(record.data)) {
2817
+ ctx.badElement("edge", element, "an element with a data object");
2818
+ continue;
2819
+ }
2820
+ const { data } = record;
2821
+ if (!hasKey(data, "source") || !hasKey(data, "target")) {
2822
+ ctx.missingEndpoint(element, `data.${hasKey(data, "source") ? "target" : "source"}`);
2823
+ continue;
2824
+ }
2825
+ try {
2826
+ const u = ctx.coerceId(data.source, `${element}.data.source`);
2827
+ const v = ctx.coerceId(data.target, `${element}.data.target`);
2828
+ if (u === null || v === null) {
2829
+ ctx.countSkipped("edge");
2830
+ continue;
2831
+ }
2832
+ const idValue = ctx.edgeIdValue(ids, data.id);
2833
+ const edge = ctx.pushEdge(u, v, kind, ctx.weightOf(data), element);
2834
+ ctx.setEdgeId(ids, edge, idValue);
2835
+ const { weightFrom } = ctx.options;
2836
+ for (const key of Object.keys(data)) {
2837
+ if (key !== "id" && key !== "source" && key !== "target" && key !== weightFrom) {
2838
+ ctx.edges.write(edge, key, data[key], SUFFIX.data);
2839
+ }
2840
+ }
2841
+ writeClasses(ctx, ctx.edges, edgeClassesColumn, edge, record.classes, element);
2842
+ writeElementKeys(ctx.edges, edge, record);
2843
+ } catch (err) {
2844
+ ctx.skip(err, "edge", element);
2845
+ }
2846
+ }
2847
+ }
2848
+ function cytoscapeSections(ctx, elements) {
2849
+ const { report } = ctx;
2850
+ if (Array.isArray(elements)) {
2851
+ return {
2852
+ nodes: elements.filter((item) => isJsonObject(item) && isNodeElement(item)),
2853
+ edges: elements.filter((item) => isJsonObject(item) && !isNodeElement(item))
2854
+ };
2855
+ }
2856
+ if (isJsonObject(elements)) {
2857
+ return {
2858
+ nodes: arraySection(elements.nodes, "elements.nodes", report) ?? [],
2859
+ edges: arraySection(elements.edges, "elements.edges", report) ?? []
2860
+ };
2861
+ }
2862
+ if (elements === void 0 || elements === null) {
2863
+ report.error("missing-value", JSON_ISSUE.MISSING_SECTION, "the document has no elements", {
2864
+ element: "elements"
2865
+ });
2866
+ return { nodes: [], edges: [] };
2867
+ }
2868
+ return report.fail(JSON_ISSUE.SHAPE, `elements must be an object or an array, found ${describe(elements)}`);
2869
+ }
2870
+ function isNodeElement(element) {
2871
+ if (element.group === "nodes") {
2872
+ return true;
2873
+ }
2874
+ if (element.group === "edges") {
2875
+ return false;
2876
+ }
2877
+ const { data } = element;
2878
+ return !(isJsonObject(data) && hasKey(data, "source") && hasKey(data, "target"));
2879
+ }
2880
+ function writeClasses(ctx, writer, column, row, raw, element) {
2881
+ if (column === INVALID_INDEX || raw === void 0 || raw === null) {
2882
+ return;
2883
+ }
2884
+ let classes;
2885
+ if (typeof raw === "string") {
2886
+ classes = raw.split(/\s+/).filter((c) => c.length > 0);
2887
+ } else if (Array.isArray(raw) && raw.every((c) => typeof c === "string")) {
2888
+ classes = raw;
2889
+ } else {
2890
+ ctx.report.error(
2891
+ "validation-error",
2892
+ JSON_ISSUE.BAD_VALUE,
2893
+ `${element}: classes must be a string or an array of strings`,
2894
+ { element }
2895
+ );
2896
+ return;
2897
+ }
2898
+ writer.set(column, row, classes);
2899
+ }
2900
+ function writeElementKeys(writer, row, record) {
2901
+ for (const key of Object.keys(record)) {
2902
+ if (CYTOSCAPE_STRUCTURAL_KEYS.has(key)) {
2903
+ continue;
2904
+ }
2905
+ const name = CYTOSCAPE_ELEMENT_KEYS.has(key) ? key : `${key}${SUFFIX.element}`;
2906
+ writer.write(row, name, record[key], SUFFIX.data);
2907
+ }
2908
+ }
2909
+ const jsonImporter = Object.freeze({
2910
+ format: "json",
2911
+ extensions: Object.freeze([".json"]),
2912
+ mimeTypes: Object.freeze(["application/json"]),
2913
+ /**
2914
+ * Confidence that the head is a JSON graph document: 0 unless it starts with `{` or `[`, 0.5
2915
+ * for any JSON, 0.9 when a graph key (nodes, links, edges, elements, graph, graphs) appears in
2916
+ * the head.
2917
+ * @param head - the first bytes
2918
+ * @returns the confidence
2919
+ */
2920
+ sniff(head) {
2921
+ const text = new TextDecoder("utf-8").decode(head.subarray(0, SNIFF_BYTES));
2922
+ const trimmed = (text.startsWith(BOM) ? text.slice(1) : text).trimStart();
2923
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
2924
+ return 0;
2925
+ }
2926
+ return SNIFF_KEYS.some((key) => trimmed.includes(key)) ? 0.9 : 0.5;
2927
+ },
2928
+ /**
2929
+ * Read a JSON graph document into the sink.
2930
+ * @param input - the text, bytes or stream
2931
+ * @param sink - the sink
2932
+ * @param options - format-specific and common options
2933
+ * @returns the import report; ImportError on a fatal error or beyond the error limit
2934
+ */
2935
+ async import(input, sink, options) {
2936
+ const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
2937
+ const json = resolveJsonOptions(options);
2938
+ const report = new ImportReportBuilder("json", resolved.errorLimit);
2939
+ const text = await readText(input, report, resolved);
2940
+ const root = parseDocument(text, report);
2941
+ const dialect = detectDialect(root, json.dialect, report);
2942
+ const ctx = new ImportContext(sink, report, resolved, json, options?.defaultDirected !== void 0);
2943
+ reportSinkOptions(sink, options, report);
2944
+ reportUnusedOptions(options, report, USED_OPTIONS);
2945
+ if (dialect === "cytoscape") {
2946
+ importCytoscape(ctx, root);
2947
+ throwIfAborted(resolved.signal);
2948
+ return report.finish();
2949
+ }
2950
+ const doc = isJsonObject(root) ? root : report.fail(JSON_ISSUE.SHAPE, `a ${dialect} document must be a JSON object, found ${describe(root)}`);
2951
+ switch (dialect) {
2952
+ case "node-link":
2953
+ case "d3":
2954
+ importNodeLink(ctx, doc, dialect);
2955
+ break;
2956
+ case "jgf":
2957
+ importJgf(ctx, doc);
2958
+ break;
2959
+ case "graphology":
2960
+ importGraphology(ctx, doc);
2961
+ break;
2962
+ case "vis":
2963
+ importVis(ctx, doc);
2964
+ break;
2965
+ default: {
2966
+ const name = dialect;
2967
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {
2968
+ option: "dialect",
2969
+ found: name
2970
+ });
2971
+ }
2972
+ }
2973
+ throwIfAborted(resolved.signal);
2974
+ return report.finish();
2975
+ }
2976
+ });
2977
+ export {
2978
+ JSON_DIALECTS as J,
2979
+ JSON_ISSUE as a,
2980
+ JSON_LOSS as b,
2981
+ jsonExporter as c,
2982
+ dialectCapabilities as d,
2983
+ jsonImporter as e,
2984
+ jsonCapabilities as j,
2985
+ sniffJsonDialect as s
2986
+ };
2987
+ //# sourceMappingURL=importer-CQnJuWJw.js.map