@graphty/graph-io 0.3.43 → 0.3.44

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 (674) hide show
  1. package/dist/chunks/approximations-3Q4TsdTh.js +40 -0
  2. package/dist/chunks/approximations-3Q4TsdTh.js.map +1 -0
  3. package/dist/chunks/block-JQiwViEt.js +2777 -0
  4. package/dist/chunks/block-JQiwViEt.js.map +1 -0
  5. package/dist/chunks/{choose-DvpbdRuz.js → choose-BemygfH8.js} +3 -2
  6. package/dist/chunks/{choose-DvpbdRuz.js.map → choose-BemygfH8.js.map} +1 -1
  7. package/dist/chunks/{constants-fT3E8JYY.js → constants-BFUR_CtU.js} +6 -2
  8. package/dist/chunks/constants-BFUR_CtU.js.map +1 -0
  9. package/dist/chunks/convert-BcQ8_jMo.js +59 -0
  10. package/dist/chunks/convert-BcQ8_jMo.js.map +1 -0
  11. package/dist/chunks/emit-DCwFLts5.js +3456 -0
  12. package/dist/chunks/emit-DCwFLts5.js.map +1 -0
  13. package/dist/chunks/{escape-B62ZGM_a.js → escape-B1rMftd9.js} +43 -20
  14. package/dist/chunks/escape-B1rMftd9.js.map +1 -0
  15. package/dist/chunks/{export-rKLeGQMo.js → export-D7IGzTG4.js} +196 -792
  16. package/dist/chunks/export-D7IGzTG4.js.map +1 -0
  17. package/dist/chunks/{exporter-BWwEuOPT.js → exporter-BtnqYuuj.js} +22 -12
  18. package/dist/chunks/exporter-BtnqYuuj.js.map +1 -0
  19. package/dist/chunks/{exporter-yLxKBvLE.js → exporter-ChPyhBOv.js} +4 -3
  20. package/dist/chunks/{exporter-yLxKBvLE.js.map → exporter-ChPyhBOv.js.map} +1 -1
  21. package/dist/chunks/{exporter-8hLNemFk.js → exporter-KEPxYUXD.js} +133 -74
  22. package/dist/chunks/exporter-KEPxYUXD.js.map +1 -0
  23. package/dist/chunks/{exporter-RVtXVetw.js → exporter-quPUCBiC.js} +1805 -287
  24. package/dist/chunks/exporter-quPUCBiC.js.map +1 -0
  25. package/dist/chunks/format-GDmO6m6Q.js +676 -0
  26. package/dist/chunks/format-GDmO6m6Q.js.map +1 -0
  27. package/dist/chunks/generic-BL06jrJf.js +1347 -0
  28. package/dist/chunks/generic-BL06jrJf.js.map +1 -0
  29. package/dist/chunks/{importer-WrGRZb0N.js → importer-B7r2owVf.js} +449 -102
  30. package/dist/chunks/importer-B7r2owVf.js.map +1 -0
  31. package/dist/chunks/{importer-Ht7LNg8L.js → importer-BFmOP96W.js} +116 -296
  32. package/dist/chunks/importer-BFmOP96W.js.map +1 -0
  33. package/dist/chunks/{importer-BbNb7eNg.js → importer-C2nzGhdV.js} +1390 -129
  34. package/dist/chunks/importer-C2nzGhdV.js.map +1 -0
  35. package/dist/chunks/{importer-Dz7tI8Sa.js → importer-CbX2_rNF.js} +617 -115
  36. package/dist/chunks/importer-CbX2_rNF.js.map +1 -0
  37. package/dist/chunks/{importer-D794XUTe.js → importer-CdzeN6X4.js} +31 -8
  38. package/dist/chunks/importer-CdzeN6X4.js.map +1 -0
  39. package/dist/chunks/{importer-xZ-3suY1.js → importer-ChKNC_GN.js} +791 -135
  40. package/dist/chunks/importer-ChKNC_GN.js.map +1 -0
  41. package/dist/chunks/{importer-C98dpSH4.js → importer-dwoYpSqb.js} +141 -82
  42. package/dist/chunks/importer-dwoYpSqb.js.map +1 -0
  43. package/dist/chunks/index-B9fdNpTA.js +4310 -0
  44. package/dist/chunks/index-B9fdNpTA.js.map +1 -0
  45. package/dist/chunks/{json-elements-DKSqpNW1.js → json-elements-C4FwgnH5.js} +123 -32
  46. package/dist/chunks/json-elements-C4FwgnH5.js.map +1 -0
  47. package/dist/chunks/lexicon-B-knUi-l.js +508 -0
  48. package/dist/chunks/lexicon-B-knUi-l.js.map +1 -0
  49. package/dist/chunks/{ontology-export-CtRXjg_i.js → ontology-export-BSpbY5FL.js} +3 -2
  50. package/dist/chunks/{ontology-export-CtRXjg_i.js.map → ontology-export-BSpbY5FL.js.map} +1 -1
  51. package/dist/chunks/{records-Cu4bxYOm.js → records-Nwz_rbNt.js} +2 -2
  52. package/dist/chunks/{records-Cu4bxYOm.js.map → records-Nwz_rbNt.js.map} +1 -1
  53. package/dist/chunks/styles-Brt9JMfs.js +690 -0
  54. package/dist/chunks/styles-Brt9JMfs.js.map +1 -0
  55. package/dist/chunks/styles-_N-C8S-U.js +2102 -0
  56. package/dist/chunks/styles-_N-C8S-U.js.map +1 -0
  57. package/dist/chunks/styles-yed-DAfcIkf7.js +842 -0
  58. package/dist/chunks/styles-yed-DAfcIkf7.js.map +1 -0
  59. package/dist/chunks/{text-DHOJXnED.js → text-CgzMqHQb.js} +2 -2
  60. package/dist/chunks/{text-DHOJXnED.js.map → text-CgzMqHQb.js.map} +1 -1
  61. package/dist/chunks/units-XMZFIFe1.js +19 -0
  62. package/dist/chunks/units-XMZFIFe1.js.map +1 -0
  63. package/dist/csv.js +5 -4
  64. package/dist/csv.js.map +1 -1
  65. package/dist/cx.js +1 -1
  66. package/dist/cx2.js +2 -2
  67. package/dist/cys.js +1 -1
  68. package/dist/dot.js +5 -2
  69. package/dist/dot.js.map +1 -1
  70. package/dist/gexf.js +292 -412
  71. package/dist/gexf.js.map +1 -1
  72. package/dist/gml.js +8 -3031
  73. package/dist/gml.js.map +1 -1
  74. package/dist/graph-io.js +354 -240
  75. package/dist/graph-io.js.map +1 -1
  76. package/dist/graphml.js +4 -2
  77. package/dist/graphml.js.map +1 -1
  78. package/dist/json.js +1 -1
  79. package/dist/neo4j.js +4 -3
  80. package/dist/neo4j.js.map +1 -1
  81. package/dist/obo.js +1 -1
  82. package/dist/pajek.js +4 -2
  83. package/dist/positions.d.ts +1 -0
  84. package/dist/positions.js +427 -0
  85. package/dist/positions.js.map +1 -0
  86. package/dist/src/common/codes.d.ts +6 -0
  87. package/dist/src/common/codes.d.ts.map +1 -1
  88. package/dist/src/common/codes.js +6 -0
  89. package/dist/src/common/codes.js.map +1 -1
  90. package/dist/src/common/escape.d.ts.map +1 -1
  91. package/dist/src/common/escape.js +4 -2
  92. package/dist/src/common/escape.js.map +1 -1
  93. package/dist/src/common/export.d.ts +10 -0
  94. package/dist/src/common/export.d.ts.map +1 -1
  95. package/dist/src/common/export.js +123 -100
  96. package/dist/src/common/export.js.map +1 -1
  97. package/dist/src/common/input.d.ts.map +1 -1
  98. package/dist/src/common/input.js +8 -1
  99. package/dist/src/common/input.js.map +1 -1
  100. package/dist/src/common/json-elements.d.ts +12 -9
  101. package/dist/src/common/json-elements.d.ts.map +1 -1
  102. package/dist/src/common/json-elements.js +160 -37
  103. package/dist/src/common/json-elements.js.map +1 -1
  104. package/dist/src/common/notes.d.ts +42 -0
  105. package/dist/src/common/notes.d.ts.map +1 -0
  106. package/dist/src/common/notes.js +72 -0
  107. package/dist/src/common/notes.js.map +1 -0
  108. package/dist/src/common/report.d.ts +18 -0
  109. package/dist/src/common/report.d.ts.map +1 -1
  110. package/dist/src/common/report.js +36 -1
  111. package/dist/src/common/report.js.map +1 -1
  112. package/dist/src/common/units.d.ts +24 -0
  113. package/dist/src/common/units.d.ts.map +1 -0
  114. package/dist/src/common/units.js +26 -0
  115. package/dist/src/common/units.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +18 -0
  117. package/dist/src/common/xml.d.ts.map +1 -1
  118. package/dist/src/common/xml.js +42 -5
  119. package/dist/src/common/xml.js.map +1 -1
  120. package/dist/src/formats/cx/exporter.d.ts +6 -0
  121. package/dist/src/formats/cx/exporter.d.ts.map +1 -1
  122. package/dist/src/formats/cx/exporter.js +71 -8
  123. package/dist/src/formats/cx/exporter.js.map +1 -1
  124. package/dist/src/formats/cx/importer.d.ts +5 -2
  125. package/dist/src/formats/cx/importer.d.ts.map +1 -1
  126. package/dist/src/formats/cx/importer.js +102 -72
  127. package/dist/src/formats/cx/importer.js.map +1 -1
  128. package/dist/src/formats/cx/styles.d.ts +102 -0
  129. package/dist/src/formats/cx/styles.d.ts.map +1 -0
  130. package/dist/src/formats/cx/styles.js +225 -0
  131. package/dist/src/formats/cx/styles.js.map +1 -0
  132. package/dist/src/formats/cx2/exporter.d.ts +5 -0
  133. package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
  134. package/dist/src/formats/cx2/exporter.js +181 -71
  135. package/dist/src/formats/cx2/exporter.js.map +1 -1
  136. package/dist/src/formats/cx2/importer.d.ts +4 -1
  137. package/dist/src/formats/cx2/importer.d.ts.map +1 -1
  138. package/dist/src/formats/cx2/importer.js +19 -1
  139. package/dist/src/formats/cx2/importer.js.map +1 -1
  140. package/dist/src/formats/cx2/styles.d.ts +18 -0
  141. package/dist/src/formats/cx2/styles.d.ts.map +1 -0
  142. package/dist/src/formats/cx2/styles.js +24 -0
  143. package/dist/src/formats/cx2/styles.js.map +1 -0
  144. package/dist/src/formats/cys/constants.d.ts +2 -0
  145. package/dist/src/formats/cys/constants.d.ts.map +1 -1
  146. package/dist/src/formats/cys/constants.js +2 -0
  147. package/dist/src/formats/cys/constants.js.map +1 -1
  148. package/dist/src/formats/cys/exporter.d.ts +6 -2
  149. package/dist/src/formats/cys/exporter.d.ts.map +1 -1
  150. package/dist/src/formats/cys/exporter.js +215 -53
  151. package/dist/src/formats/cys/exporter.js.map +1 -1
  152. package/dist/src/formats/cys/importer.d.ts +2 -1
  153. package/dist/src/formats/cys/importer.d.ts.map +1 -1
  154. package/dist/src/formats/cys/importer.js +119 -6
  155. package/dist/src/formats/cys/importer.js.map +1 -1
  156. package/dist/src/formats/cys/session.d.ts +2 -0
  157. package/dist/src/formats/cys/session.d.ts.map +1 -1
  158. package/dist/src/formats/cys/session.js +5 -0
  159. package/dist/src/formats/cys/session.js.map +1 -1
  160. package/dist/src/formats/cys/styles.d.ts +38 -0
  161. package/dist/src/formats/cys/styles.d.ts.map +1 -0
  162. package/dist/src/formats/cys/styles.js +213 -0
  163. package/dist/src/formats/cys/styles.js.map +1 -0
  164. package/dist/src/formats/cys/tables.d.ts +8 -3
  165. package/dist/src/formats/cys/tables.d.ts.map +1 -1
  166. package/dist/src/formats/cys/tables.js +88 -34
  167. package/dist/src/formats/cys/tables.js.map +1 -1
  168. package/dist/src/formats/dot/exporter.d.ts +2 -0
  169. package/dist/src/formats/dot/exporter.d.ts.map +1 -1
  170. package/dist/src/formats/dot/exporter.js +195 -81
  171. package/dist/src/formats/dot/exporter.js.map +1 -1
  172. package/dist/src/formats/dot/importer.d.ts.map +1 -1
  173. package/dist/src/formats/dot/importer.js +100 -20
  174. package/dist/src/formats/dot/importer.js.map +1 -1
  175. package/dist/src/formats/dot/index.d.ts +1 -0
  176. package/dist/src/formats/dot/index.d.ts.map +1 -1
  177. package/dist/src/formats/dot/index.js +1 -0
  178. package/dist/src/formats/dot/index.js.map +1 -1
  179. package/dist/src/formats/dot/positions.d.ts +14 -0
  180. package/dist/src/formats/dot/positions.d.ts.map +1 -0
  181. package/dist/src/formats/dot/positions.js +70 -0
  182. package/dist/src/formats/dot/positions.js.map +1 -0
  183. package/dist/src/formats/dot/styles.d.ts +82 -0
  184. package/dist/src/formats/dot/styles.d.ts.map +1 -0
  185. package/dist/src/formats/dot/styles.js +1484 -0
  186. package/dist/src/formats/dot/styles.js.map +1 -0
  187. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  188. package/dist/src/formats/gexf/exporter.js +20 -5
  189. package/dist/src/formats/gexf/exporter.js.map +1 -1
  190. package/dist/src/formats/gexf/importer.d.ts +8 -1
  191. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  192. package/dist/src/formats/gexf/importer.js +251 -131
  193. package/dist/src/formats/gexf/importer.js.map +1 -1
  194. package/dist/src/formats/gexf/index.d.ts +8 -0
  195. package/dist/src/formats/gexf/index.d.ts.map +1 -1
  196. package/dist/src/formats/gexf/index.js +10 -2
  197. package/dist/src/formats/gexf/index.js.map +1 -1
  198. package/dist/src/formats/gexf/schema.d.ts +5 -0
  199. package/dist/src/formats/gexf/schema.d.ts.map +1 -1
  200. package/dist/src/formats/gexf/schema.js +5 -0
  201. package/dist/src/formats/gexf/schema.js.map +1 -1
  202. package/dist/src/formats/gexf/styles.d.ts +74 -0
  203. package/dist/src/formats/gexf/styles.d.ts.map +1 -0
  204. package/dist/src/formats/gexf/styles.js +604 -0
  205. package/dist/src/formats/gexf/styles.js.map +1 -0
  206. package/dist/src/formats/gml/exporter.d.ts +1 -5
  207. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  208. package/dist/src/formats/gml/exporter.js +19 -8
  209. package/dist/src/formats/gml/exporter.js.map +1 -1
  210. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  211. package/dist/src/formats/gml/importer.js +4 -0
  212. package/dist/src/formats/gml/importer.js.map +1 -1
  213. package/dist/src/formats/gml/index.d.ts +2 -1
  214. package/dist/src/formats/gml/index.d.ts.map +1 -1
  215. package/dist/src/formats/gml/index.js +2 -1
  216. package/dist/src/formats/gml/index.js.map +1 -1
  217. package/dist/src/formats/gml/positions.d.ts +14 -0
  218. package/dist/src/formats/gml/positions.d.ts.map +1 -0
  219. package/dist/src/formats/gml/positions.js +46 -0
  220. package/dist/src/formats/gml/positions.js.map +1 -0
  221. package/dist/src/formats/gml/styles.d.ts +59 -0
  222. package/dist/src/formats/gml/styles.d.ts.map +1 -0
  223. package/dist/src/formats/gml/styles.js +1056 -0
  224. package/dist/src/formats/gml/styles.js.map +1 -0
  225. package/dist/src/formats/gml/syntax.d.ts +11 -0
  226. package/dist/src/formats/gml/syntax.d.ts.map +1 -1
  227. package/dist/src/formats/gml/syntax.js +151 -62
  228. package/dist/src/formats/gml/syntax.js.map +1 -1
  229. package/dist/src/formats/graphml/constants.d.ts +4 -0
  230. package/dist/src/formats/graphml/constants.d.ts.map +1 -1
  231. package/dist/src/formats/graphml/constants.js +5 -1
  232. package/dist/src/formats/graphml/constants.js.map +1 -1
  233. package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
  234. package/dist/src/formats/graphml/exporter.js +133 -65
  235. package/dist/src/formats/graphml/exporter.js.map +1 -1
  236. package/dist/src/formats/graphml/importer.d.ts +4 -1
  237. package/dist/src/formats/graphml/importer.d.ts.map +1 -1
  238. package/dist/src/formats/graphml/importer.js +100 -44
  239. package/dist/src/formats/graphml/importer.js.map +1 -1
  240. package/dist/src/formats/graphml/index.d.ts +1 -0
  241. package/dist/src/formats/graphml/index.d.ts.map +1 -1
  242. package/dist/src/formats/graphml/index.js +1 -0
  243. package/dist/src/formats/graphml/index.js.map +1 -1
  244. package/dist/src/formats/graphml/styles-plain.d.ts +19 -0
  245. package/dist/src/formats/graphml/styles-plain.d.ts.map +1 -0
  246. package/dist/src/formats/graphml/styles-plain.js +187 -0
  247. package/dist/src/formats/graphml/styles-plain.js.map +1 -0
  248. package/dist/src/formats/graphml/styles-yed.d.ts +179 -0
  249. package/dist/src/formats/graphml/styles-yed.d.ts.map +1 -0
  250. package/dist/src/formats/graphml/styles-yed.js +1086 -0
  251. package/dist/src/formats/graphml/styles-yed.js.map +1 -0
  252. package/dist/src/formats/graphml/styles-yfiles3.d.ts +19 -0
  253. package/dist/src/formats/graphml/styles-yfiles3.d.ts.map +1 -0
  254. package/dist/src/formats/graphml/styles-yfiles3.js +508 -0
  255. package/dist/src/formats/graphml/styles-yfiles3.js.map +1 -0
  256. package/dist/src/formats/graphml/tree.d.ts.map +1 -1
  257. package/dist/src/formats/graphml/tree.js +34 -19
  258. package/dist/src/formats/graphml/tree.js.map +1 -1
  259. package/dist/src/formats/graphml/yfiles.d.ts +16 -2
  260. package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
  261. package/dist/src/formats/graphml/yfiles.js +83 -12
  262. package/dist/src/formats/graphml/yfiles.js.map +1 -1
  263. package/dist/src/formats/json/exporter.d.ts +5 -0
  264. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  265. package/dist/src/formats/json/exporter.js +68 -25
  266. package/dist/src/formats/json/exporter.js.map +1 -1
  267. package/dist/src/formats/json/importer.d.ts +22 -3
  268. package/dist/src/formats/json/importer.d.ts.map +1 -1
  269. package/dist/src/formats/json/importer.js +439 -264
  270. package/dist/src/formats/json/importer.js.map +1 -1
  271. package/dist/src/formats/json/positions.d.ts +16 -0
  272. package/dist/src/formats/json/positions.d.ts.map +1 -0
  273. package/dist/src/formats/json/positions.js +44 -0
  274. package/dist/src/formats/json/positions.js.map +1 -0
  275. package/dist/src/formats/json/styles.d.ts +102 -0
  276. package/dist/src/formats/json/styles.d.ts.map +1 -0
  277. package/dist/src/formats/json/styles.js +1753 -0
  278. package/dist/src/formats/json/styles.js.map +1 -0
  279. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
  280. package/dist/src/formats/neo4j/exporter.js +2 -1
  281. package/dist/src/formats/neo4j/exporter.js.map +1 -1
  282. package/dist/src/formats/pajek/exporter.d.ts +2 -0
  283. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  284. package/dist/src/formats/pajek/exporter.js +142 -76
  285. package/dist/src/formats/pajek/exporter.js.map +1 -1
  286. package/dist/src/formats/pajek/importer.d.ts +4 -0
  287. package/dist/src/formats/pajek/importer.d.ts.map +1 -1
  288. package/dist/src/formats/pajek/importer.js +60 -6
  289. package/dist/src/formats/pajek/importer.js.map +1 -1
  290. package/dist/src/formats/pajek/index.d.ts +2 -0
  291. package/dist/src/formats/pajek/index.d.ts.map +1 -1
  292. package/dist/src/formats/pajek/index.js +2 -0
  293. package/dist/src/formats/pajek/index.js.map +1 -1
  294. package/dist/src/formats/pajek/styles.d.ts +50 -0
  295. package/dist/src/formats/pajek/styles.d.ts.map +1 -0
  296. package/dist/src/formats/pajek/styles.js +569 -0
  297. package/dist/src/formats/pajek/styles.js.map +1 -0
  298. package/dist/src/formats/pajek/syntax.d.ts +6 -0
  299. package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
  300. package/dist/src/formats/pajek/syntax.js +6 -0
  301. package/dist/src/formats/pajek/syntax.js.map +1 -1
  302. package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
  303. package/dist/src/formats/xgmml/columns.js +34 -9
  304. package/dist/src/formats/xgmml/columns.js.map +1 -1
  305. package/dist/src/formats/xgmml/constants.d.ts +2 -0
  306. package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
  307. package/dist/src/formats/xgmml/constants.js +3 -1
  308. package/dist/src/formats/xgmml/constants.js.map +1 -1
  309. package/dist/src/formats/xgmml/document.d.ts +12 -8
  310. package/dist/src/formats/xgmml/document.d.ts.map +1 -1
  311. package/dist/src/formats/xgmml/document.js +56 -13
  312. package/dist/src/formats/xgmml/document.js.map +1 -1
  313. package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
  314. package/dist/src/formats/xgmml/emit.js +12 -6
  315. package/dist/src/formats/xgmml/emit.js.map +1 -1
  316. package/dist/src/formats/xgmml/exporter.d.ts +3 -1
  317. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
  318. package/dist/src/formats/xgmml/exporter.js +22 -9
  319. package/dist/src/formats/xgmml/exporter.js.map +1 -1
  320. package/dist/src/formats/xgmml/importer.d.ts +2 -1
  321. package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
  322. package/dist/src/formats/xgmml/importer.js +31 -24
  323. package/dist/src/formats/xgmml/importer.js.map +1 -1
  324. package/dist/src/formats/xgmml/styles.d.ts +104 -0
  325. package/dist/src/formats/xgmml/styles.d.ts.map +1 -0
  326. package/dist/src/formats/xgmml/styles.js +310 -0
  327. package/dist/src/formats/xgmml/styles.js.map +1 -0
  328. package/dist/src/index.d.ts +9 -7
  329. package/dist/src/index.d.ts.map +1 -1
  330. package/dist/src/index.js +7 -7
  331. package/dist/src/index.js.map +1 -1
  332. package/dist/src/positions/codes.d.ts +31 -0
  333. package/dist/src/positions/codes.d.ts.map +1 -0
  334. package/dist/src/positions/codes.js +26 -0
  335. package/dist/src/positions/codes.js.map +1 -0
  336. package/dist/src/positions/convert.d.ts +58 -0
  337. package/dist/src/positions/convert.d.ts.map +1 -0
  338. package/dist/src/positions/convert.js +107 -0
  339. package/dist/src/positions/convert.js.map +1 -0
  340. package/dist/src/positions/extract.d.ts +27 -0
  341. package/dist/src/positions/extract.d.ts.map +1 -0
  342. package/dist/src/positions/extract.js +526 -0
  343. package/dist/src/positions/extract.js.map +1 -0
  344. package/dist/src/positions/index.d.ts +10 -0
  345. package/dist/src/positions/index.d.ts.map +1 -0
  346. package/dist/src/positions/index.js +9 -0
  347. package/dist/src/positions/index.js.map +1 -0
  348. package/dist/src/positions/types.d.ts +208 -0
  349. package/dist/src/positions/types.d.ts.map +1 -0
  350. package/dist/src/positions/types.js +10 -0
  351. package/dist/src/positions/types.js.map +1 -0
  352. package/dist/src/registry.d.ts +30 -0
  353. package/dist/src/registry.d.ts.map +1 -1
  354. package/dist/src/registry.js +130 -9
  355. package/dist/src/registry.js.map +1 -1
  356. package/dist/src/styles/annotations.d.ts +132 -0
  357. package/dist/src/styles/annotations.d.ts.map +1 -0
  358. package/dist/src/styles/annotations.js +320 -0
  359. package/dist/src/styles/annotations.js.map +1 -0
  360. package/dist/src/styles/budget.d.ts +45 -0
  361. package/dist/src/styles/budget.d.ts.map +1 -0
  362. package/dist/src/styles/budget.js +58 -0
  363. package/dist/src/styles/budget.js.map +1 -0
  364. package/dist/src/styles/canon/bends.d.ts +32 -0
  365. package/dist/src/styles/canon/bends.d.ts.map +1 -0
  366. package/dist/src/styles/canon/bends.js +74 -0
  367. package/dist/src/styles/canon/bends.js.map +1 -0
  368. package/dist/src/styles/canon/color-tables.d.ts +22 -0
  369. package/dist/src/styles/canon/color-tables.d.ts.map +1 -0
  370. package/dist/src/styles/canon/color-tables.js +369 -0
  371. package/dist/src/styles/canon/color-tables.js.map +1 -0
  372. package/dist/src/styles/canon/color.d.ts +66 -0
  373. package/dist/src/styles/canon/color.d.ts.map +1 -0
  374. package/dist/src/styles/canon/color.js +548 -0
  375. package/dist/src/styles/canon/color.js.map +1 -0
  376. package/dist/src/styles/canon/enums.d.ts +25 -0
  377. package/dist/src/styles/canon/enums.d.ts.map +1 -0
  378. package/dist/src/styles/canon/enums.js +187 -0
  379. package/dist/src/styles/canon/enums.js.map +1 -0
  380. package/dist/src/styles/canon/graphviz.d.ts +48 -0
  381. package/dist/src/styles/canon/graphviz.d.ts.map +1 -0
  382. package/dist/src/styles/canon/graphviz.js +160 -0
  383. package/dist/src/styles/canon/graphviz.js.map +1 -0
  384. package/dist/src/styles/canon/index.d.ts +13 -0
  385. package/dist/src/styles/canon/index.d.ts.map +1 -0
  386. package/dist/src/styles/canon/index.js +13 -0
  387. package/dist/src/styles/canon/index.js.map +1 -0
  388. package/dist/src/styles/canon/number.d.ts +59 -0
  389. package/dist/src/styles/canon/number.d.ts.map +1 -0
  390. package/dist/src/styles/canon/number.js +142 -0
  391. package/dist/src/styles/canon/number.js.map +1 -0
  392. package/dist/src/styles/canon/site.d.ts +51 -0
  393. package/dist/src/styles/canon/site.d.ts.map +1 -0
  394. package/dist/src/styles/canon/site.js +63 -0
  395. package/dist/src/styles/canon/site.js.map +1 -0
  396. package/dist/src/styles/canon/text.d.ts +61 -0
  397. package/dist/src/styles/canon/text.d.ts.map +1 -0
  398. package/dist/src/styles/canon/text.js +199 -0
  399. package/dist/src/styles/canon/text.js.map +1 -0
  400. package/dist/src/styles/codes.d.ts +95 -0
  401. package/dist/src/styles/codes.d.ts.map +1 -0
  402. package/dist/src/styles/codes.js +97 -0
  403. package/dist/src/styles/codes.js.map +1 -0
  404. package/dist/src/styles/cyjs/cascade.d.ts +134 -0
  405. package/dist/src/styles/cyjs/cascade.d.ts.map +1 -0
  406. package/dist/src/styles/cyjs/cascade.js +2252 -0
  407. package/dist/src/styles/cyjs/cascade.js.map +1 -0
  408. package/dist/src/styles/cyjs/defaults-table.d.ts +25 -0
  409. package/dist/src/styles/cyjs/defaults-table.d.ts.map +1 -0
  410. package/dist/src/styles/cyjs/defaults-table.js +76 -0
  411. package/dist/src/styles/cyjs/defaults-table.js.map +1 -0
  412. package/dist/src/styles/cyjs/emit.d.ts +82 -0
  413. package/dist/src/styles/cyjs/emit.d.ts.map +1 -0
  414. package/dist/src/styles/cyjs/emit.js +790 -0
  415. package/dist/src/styles/cyjs/emit.js.map +1 -0
  416. package/dist/src/styles/cyjs/index.d.ts +13 -0
  417. package/dist/src/styles/cyjs/index.d.ts.map +1 -0
  418. package/dist/src/styles/cyjs/index.js +13 -0
  419. package/dist/src/styles/cyjs/index.js.map +1 -0
  420. package/dist/src/styles/cyjs/live.d.ts +46 -0
  421. package/dist/src/styles/cyjs/live.d.ts.map +1 -0
  422. package/dist/src/styles/cyjs/live.js +108 -0
  423. package/dist/src/styles/cyjs/live.js.map +1 -0
  424. package/dist/src/styles/cyjs/mappers.d.ts +147 -0
  425. package/dist/src/styles/cyjs/mappers.d.ts.map +1 -0
  426. package/dist/src/styles/cyjs/mappers.js +744 -0
  427. package/dist/src/styles/cyjs/mappers.js.map +1 -0
  428. package/dist/src/styles/cyjs/selector.d.ts +113 -0
  429. package/dist/src/styles/cyjs/selector.d.ts.map +1 -0
  430. package/dist/src/styles/cyjs/selector.js +611 -0
  431. package/dist/src/styles/cyjs/selector.js.map +1 -0
  432. package/dist/src/styles/cyjs/stylesheet-text.d.ts +37 -0
  433. package/dist/src/styles/cyjs/stylesheet-text.d.ts.map +1 -0
  434. package/dist/src/styles/cyjs/stylesheet-text.js +181 -0
  435. package/dist/src/styles/cyjs/stylesheet-text.js.map +1 -0
  436. package/dist/src/styles/cytoscape/emit.d.ts +125 -0
  437. package/dist/src/styles/cytoscape/emit.d.ts.map +1 -0
  438. package/dist/src/styles/cytoscape/emit.js +1025 -0
  439. package/dist/src/styles/cytoscape/emit.js.map +1 -0
  440. package/dist/src/styles/cytoscape/index.d.ts +10 -0
  441. package/dist/src/styles/cytoscape/index.d.ts.map +1 -0
  442. package/dist/src/styles/cytoscape/index.js +10 -0
  443. package/dist/src/styles/cytoscape/index.js.map +1 -0
  444. package/dist/src/styles/cytoscape/lexicon.d.ts +49 -0
  445. package/dist/src/styles/cytoscape/lexicon.d.ts.map +1 -0
  446. package/dist/src/styles/cytoscape/lexicon.js +603 -0
  447. package/dist/src/styles/cytoscape/lexicon.js.map +1 -0
  448. package/dist/src/styles/cytoscape/mappings.d.ts +171 -0
  449. package/dist/src/styles/cytoscape/mappings.d.ts.map +1 -0
  450. package/dist/src/styles/cytoscape/mappings.js +460 -0
  451. package/dist/src/styles/cytoscape/mappings.js.map +1 -0
  452. package/dist/src/styles/cytoscape/resolve.d.ts +137 -0
  453. package/dist/src/styles/cytoscape/resolve.d.ts.map +1 -0
  454. package/dist/src/styles/cytoscape/resolve.js +381 -0
  455. package/dist/src/styles/cytoscape/resolve.js.map +1 -0
  456. package/dist/src/styles/cytoscape/translate.d.ts +50 -0
  457. package/dist/src/styles/cytoscape/translate.d.ts.map +1 -0
  458. package/dist/src/styles/cytoscape/translate.js +156 -0
  459. package/dist/src/styles/cytoscape/translate.js.map +1 -0
  460. package/dist/src/styles/defaults.d.ts +90 -0
  461. package/dist/src/styles/defaults.d.ts.map +1 -0
  462. package/dist/src/styles/defaults.js +414 -0
  463. package/dist/src/styles/defaults.js.map +1 -0
  464. package/dist/src/styles/index.d.ts +15 -0
  465. package/dist/src/styles/index.d.ts.map +1 -0
  466. package/dist/src/styles/index.js +14 -0
  467. package/dist/src/styles/index.js.map +1 -0
  468. package/dist/src/styles/internal.d.ts +17 -0
  469. package/dist/src/styles/internal.d.ts.map +1 -0
  470. package/dist/src/styles/internal.js +16 -0
  471. package/dist/src/styles/internal.js.map +1 -0
  472. package/dist/src/styles/io/approximations.d.ts +24 -0
  473. package/dist/src/styles/io/approximations.d.ts.map +1 -0
  474. package/dist/src/styles/io/approximations.js +51 -0
  475. package/dist/src/styles/io/approximations.js.map +1 -0
  476. package/dist/src/styles/io/block.d.ts +166 -0
  477. package/dist/src/styles/io/block.d.ts.map +1 -0
  478. package/dist/src/styles/io/block.js +930 -0
  479. package/dist/src/styles/io/block.js.map +1 -0
  480. package/dist/src/styles/io/generic.d.ts +88 -0
  481. package/dist/src/styles/io/generic.d.ts.map +1 -0
  482. package/dist/src/styles/io/generic.js +516 -0
  483. package/dist/src/styles/io/generic.js.map +1 -0
  484. package/dist/src/styles/io/index.d.ts +8 -0
  485. package/dist/src/styles/io/index.d.ts.map +1 -0
  486. package/dist/src/styles/io/index.js +7 -0
  487. package/dist/src/styles/io/index.js.map +1 -0
  488. package/dist/src/styles/io/writer.d.ts +80 -0
  489. package/dist/src/styles/io/writer.d.ts.map +1 -0
  490. package/dist/src/styles/io/writer.js +596 -0
  491. package/dist/src/styles/io/writer.js.map +1 -0
  492. package/dist/src/styles/notes.d.ts +47 -0
  493. package/dist/src/styles/notes.d.ts.map +1 -0
  494. package/dist/src/styles/notes.js +140 -0
  495. package/dist/src/styles/notes.js.map +1 -0
  496. package/dist/src/styles/plan/classes.d.ts +31 -0
  497. package/dist/src/styles/plan/classes.d.ts.map +1 -0
  498. package/dist/src/styles/plan/classes.js +139 -0
  499. package/dist/src/styles/plan/classes.js.map +1 -0
  500. package/dist/src/styles/plan/continuous.d.ts +32 -0
  501. package/dist/src/styles/plan/continuous.d.ts.map +1 -0
  502. package/dist/src/styles/plan/continuous.js +165 -0
  503. package/dist/src/styles/plan/continuous.js.map +1 -0
  504. package/dist/src/styles/plan/discrete.d.ts +39 -0
  505. package/dist/src/styles/plan/discrete.d.ts.map +1 -0
  506. package/dist/src/styles/plan/discrete.js +127 -0
  507. package/dist/src/styles/plan/discrete.js.map +1 -0
  508. package/dist/src/styles/plan/families.d.ts +31 -0
  509. package/dist/src/styles/plan/families.d.ts.map +1 -0
  510. package/dist/src/styles/plan/families.js +95 -0
  511. package/dist/src/styles/plan/families.js.map +1 -0
  512. package/dist/src/styles/plan/features.d.ts +58 -0
  513. package/dist/src/styles/plan/features.d.ts.map +1 -0
  514. package/dist/src/styles/plan/features.js +185 -0
  515. package/dist/src/styles/plan/features.js.map +1 -0
  516. package/dist/src/styles/plan/index.d.ts +7 -0
  517. package/dist/src/styles/plan/index.d.ts.map +1 -0
  518. package/dist/src/styles/plan/index.js +7 -0
  519. package/dist/src/styles/plan/index.js.map +1 -0
  520. package/dist/src/styles/plan/plan.d.ts +71 -0
  521. package/dist/src/styles/plan/plan.d.ts.map +1 -0
  522. package/dist/src/styles/plan/plan.js +893 -0
  523. package/dist/src/styles/plan/plan.js.map +1 -0
  524. package/dist/src/styles/plan/resolve.d.ts +137 -0
  525. package/dist/src/styles/plan/resolve.d.ts.map +1 -0
  526. package/dist/src/styles/plan/resolve.js +587 -0
  527. package/dist/src/styles/plan/resolve.js.map +1 -0
  528. package/dist/src/styles/types.d.ts +702 -0
  529. package/dist/src/styles/types.d.ts.map +1 -0
  530. package/dist/src/styles/types.js +10 -0
  531. package/dist/src/styles/types.js.map +1 -0
  532. package/dist/src/styles/vocabulary.d.ts +41 -0
  533. package/dist/src/styles/vocabulary.d.ts.map +1 -0
  534. package/dist/src/styles/vocabulary.js +140 -0
  535. package/dist/src/styles/vocabulary.js.map +1 -0
  536. package/dist/src/types.d.ts +64 -1
  537. package/dist/src/types.d.ts.map +1 -1
  538. package/dist/src/types.js.map +1 -1
  539. package/dist/styles.d.ts +1 -0
  540. package/dist/styles.js +322 -0
  541. package/dist/styles.js.map +1 -0
  542. package/dist/xgmml.js +5 -4
  543. package/dist/xgmml.js.map +1 -1
  544. package/package.json +13 -2
  545. package/src/common/codes.ts +7 -0
  546. package/src/common/escape.ts +5 -2
  547. package/src/common/export.ts +179 -140
  548. package/src/common/input.ts +8 -4
  549. package/src/common/json-elements.ts +169 -38
  550. package/src/common/notes.ts +94 -0
  551. package/src/common/report.ts +41 -1
  552. package/src/common/units.ts +42 -0
  553. package/src/common/xml.ts +45 -5
  554. package/src/formats/cx/exporter.ts +95 -11
  555. package/src/formats/cx/importer.ts +126 -90
  556. package/src/formats/cx/styles.ts +304 -0
  557. package/src/formats/cx2/exporter.ts +234 -77
  558. package/src/formats/cx2/importer.ts +19 -1
  559. package/src/formats/cx2/styles.ts +29 -0
  560. package/src/formats/cys/constants.ts +3 -0
  561. package/src/formats/cys/exporter.ts +246 -60
  562. package/src/formats/cys/importer.ts +125 -9
  563. package/src/formats/cys/session.ts +6 -0
  564. package/src/formats/cys/styles.ts +254 -0
  565. package/src/formats/cys/tables.ts +117 -48
  566. package/src/formats/dot/exporter.ts +262 -126
  567. package/src/formats/dot/importer.ts +111 -20
  568. package/src/formats/dot/index.ts +1 -0
  569. package/src/formats/dot/positions.ts +77 -0
  570. package/src/formats/dot/styles.ts +1741 -0
  571. package/src/formats/gexf/exporter.ts +27 -11
  572. package/src/formats/gexf/importer.ts +308 -158
  573. package/src/formats/gexf/index.ts +10 -0
  574. package/src/formats/gexf/schema.ts +5 -0
  575. package/src/formats/gexf/styles.ts +719 -0
  576. package/src/formats/gml/exporter.ts +33 -14
  577. package/src/formats/gml/importer.ts +4 -0
  578. package/src/formats/gml/index.ts +2 -1
  579. package/src/formats/gml/positions.ts +52 -0
  580. package/src/formats/gml/styles.ts +1248 -0
  581. package/src/formats/gml/syntax.ts +173 -62
  582. package/src/formats/graphml/constants.ts +5 -0
  583. package/src/formats/graphml/exporter.ts +163 -71
  584. package/src/formats/graphml/importer.ts +119 -45
  585. package/src/formats/graphml/index.ts +1 -0
  586. package/src/formats/graphml/styles-plain.ts +236 -0
  587. package/src/formats/graphml/styles-yed.ts +1221 -0
  588. package/src/formats/graphml/styles-yfiles3.ts +573 -0
  589. package/src/formats/graphml/tree.ts +36 -20
  590. package/src/formats/graphml/yfiles.ts +89 -13
  591. package/src/formats/json/exporter.ts +76 -26
  592. package/src/formats/json/importer.ts +541 -322
  593. package/src/formats/json/positions.ts +50 -0
  594. package/src/formats/json/styles.ts +2082 -0
  595. package/src/formats/neo4j/exporter.ts +2 -1
  596. package/src/formats/pajek/exporter.ts +195 -106
  597. package/src/formats/pajek/importer.ts +78 -9
  598. package/src/formats/pajek/index.ts +2 -0
  599. package/src/formats/pajek/styles.ts +685 -0
  600. package/src/formats/pajek/syntax.ts +7 -0
  601. package/src/formats/xgmml/columns.ts +39 -9
  602. package/src/formats/xgmml/constants.ts +3 -0
  603. package/src/formats/xgmml/document.ts +54 -12
  604. package/src/formats/xgmml/emit.ts +11 -6
  605. package/src/formats/xgmml/exporter.ts +32 -13
  606. package/src/formats/xgmml/importer.ts +43 -29
  607. package/src/formats/xgmml/styles.ts +400 -0
  608. package/src/index.ts +16 -0
  609. package/src/positions/codes.ts +32 -0
  610. package/src/positions/convert.ts +123 -0
  611. package/src/positions/extract.ts +619 -0
  612. package/src/positions/index.ts +23 -0
  613. package/src/positions/types.ts +221 -0
  614. package/src/registry.ts +179 -8
  615. package/src/styles/annotations.ts +478 -0
  616. package/src/styles/budget.ts +60 -0
  617. package/src/styles/canon/bends.ts +84 -0
  618. package/src/styles/canon/color-tables.ts +386 -0
  619. package/src/styles/canon/color.ts +624 -0
  620. package/src/styles/canon/enums.ts +211 -0
  621. package/src/styles/canon/graphviz.ts +189 -0
  622. package/src/styles/canon/index.ts +13 -0
  623. package/src/styles/canon/number.ts +152 -0
  624. package/src/styles/canon/site.ts +87 -0
  625. package/src/styles/canon/text.ts +221 -0
  626. package/src/styles/codes.ts +104 -0
  627. package/src/styles/cyjs/cascade.ts +2670 -0
  628. package/src/styles/cyjs/defaults-table.ts +82 -0
  629. package/src/styles/cyjs/emit.ts +951 -0
  630. package/src/styles/cyjs/index.ts +37 -0
  631. package/src/styles/cyjs/live.ts +147 -0
  632. package/src/styles/cyjs/mappers.ts +879 -0
  633. package/src/styles/cyjs/selector.ts +778 -0
  634. package/src/styles/cyjs/stylesheet-text.ts +214 -0
  635. package/src/styles/cytoscape/emit.ts +1264 -0
  636. package/src/styles/cytoscape/index.ts +40 -0
  637. package/src/styles/cytoscape/lexicon.ts +656 -0
  638. package/src/styles/cytoscape/mappings.ts +603 -0
  639. package/src/styles/cytoscape/resolve.ts +521 -0
  640. package/src/styles/cytoscape/translate.ts +237 -0
  641. package/src/styles/defaults.ts +504 -0
  642. package/src/styles/index.ts +57 -0
  643. package/src/styles/internal.ts +17 -0
  644. package/src/styles/io/approximations.ts +47 -0
  645. package/src/styles/io/block.ts +1138 -0
  646. package/src/styles/io/generic.ts +687 -0
  647. package/src/styles/io/index.ts +18 -0
  648. package/src/styles/io/writer.ts +745 -0
  649. package/src/styles/notes.ts +165 -0
  650. package/src/styles/plan/classes.ts +166 -0
  651. package/src/styles/plan/continuous.ts +218 -0
  652. package/src/styles/plan/discrete.ts +165 -0
  653. package/src/styles/plan/families.ts +115 -0
  654. package/src/styles/plan/features.ts +234 -0
  655. package/src/styles/plan/index.ts +7 -0
  656. package/src/styles/plan/plan.ts +1129 -0
  657. package/src/styles/plan/resolve.ts +685 -0
  658. package/src/styles/types.ts +814 -0
  659. package/src/styles/vocabulary.ts +172 -0
  660. package/src/types.ts +76 -1
  661. package/dist/chunks/constants-fT3E8JYY.js.map +0 -1
  662. package/dist/chunks/escape-B62ZGM_a.js.map +0 -1
  663. package/dist/chunks/export-rKLeGQMo.js.map +0 -1
  664. package/dist/chunks/exporter-8hLNemFk.js.map +0 -1
  665. package/dist/chunks/exporter-BWwEuOPT.js.map +0 -1
  666. package/dist/chunks/exporter-RVtXVetw.js.map +0 -1
  667. package/dist/chunks/importer-BbNb7eNg.js.map +0 -1
  668. package/dist/chunks/importer-C98dpSH4.js.map +0 -1
  669. package/dist/chunks/importer-D794XUTe.js.map +0 -1
  670. package/dist/chunks/importer-Dz7tI8Sa.js.map +0 -1
  671. package/dist/chunks/importer-Ht7LNg8L.js.map +0 -1
  672. package/dist/chunks/importer-WrGRZb0N.js.map +0 -1
  673. package/dist/chunks/importer-xZ-3suY1.js.map +0 -1
  674. package/dist/chunks/json-elements-DKSqpNW1.js.map +0 -1
@@ -79,6 +79,7 @@ import { readText, throwIfAborted } from "../../common/input.js";
79
79
  import {
80
80
  findDuplicateKeys,
81
81
  flipY,
82
+ keysInTextOrder,
82
83
  MAYBE_INEXACT_EXPONENT,
83
84
  MAYBE_UNSAFE_INTEGER,
84
85
  rewriteNumbers,
@@ -97,6 +98,7 @@ import { agree, plural } from "../../common/plural.js";
97
98
  import { ImportReportBuilder } from "../../common/report.js";
98
99
  import { weightFromValue } from "../../common/weights.js";
99
100
  import { headBytes, sniffJsonDialectHead } from "../../sniff.js";
101
+ import { STYLE_BLOCK_NAME } from "../../styles/io/block.js";
100
102
  import {
101
103
  type CommonImportOptions,
102
104
  type GraphChoiceOptions,
@@ -125,6 +127,8 @@ import {
125
127
  SUFFIX,
126
128
  } from "./dialect.js";
127
129
  import { importObographs } from "./obographs.js";
130
+ import { jsonPositions } from "./positions.js";
131
+ import { jsonStyles, type JsonStyleSource } from "./styles.js";
128
132
 
129
133
  /**
130
134
  * The format-specific options of the JSON importer. `graphIndex` / `graphName` choose one graph of
@@ -386,6 +390,9 @@ const JGF_EDGE_KEYS: ReadonlySet<string> = new Set([
386
390
  /** The JGF hyperedge keys that are not metadata. */
387
391
  const JGF_HYPEREDGE_KEYS: ReadonlySet<string> = new Set([...JGF_EDGE_KEYS, "nodes"]);
388
392
 
393
+ /** A key JavaScript orders as an array index: digits with no leading zero, below 2^32 - 1. */
394
+ const ARRAY_INDEX = /^(?:0|[1-9]\d{0,9})$/;
395
+
389
396
  /** The JGF node keys that are not metadata. */
390
397
  const JGF_NODE_KEYS: ReadonlySet<string> = new Set(["label", "metadata"]);
391
398
 
@@ -395,6 +402,12 @@ const GRAPHOLOGY_NODE_KEYS: ReadonlySet<string> = new Set(["key", "attributes"])
395
402
  /** The graphology edge keys that are not attributes. */
396
403
  const GRAPHOLOGY_EDGE_KEYS: ReadonlySet<string> = new Set(["key", "source", "target", "attributes", "undirected"]);
397
404
 
405
+ /** The structural keys of a node in Gephi's JSON export. */
406
+ const GEPHI_NODE_KEYS: ReadonlySet<string> = new Set(["id", "attributes"]);
407
+
408
+ /** The structural keys of an edge in Gephi's JSON export. */
409
+ const GEPHI_EDGE_KEYS: ReadonlySet<string> = new Set([...GRAPHOLOGY_EDGE_KEYS, "id"]);
410
+
398
411
  /** The vis.js endpoint keys. */
399
412
  const VIS_SOURCE_KEYS: readonly string[] = Object.freeze(["from"]);
400
413
  const VIS_TARGET_KEYS: readonly string[] = Object.freeze(["to"]);
@@ -897,6 +910,9 @@ export class ImportContext {
897
910
  /** Whether the caller passed `defaultDirected` explicitly (then it beats the dialect's convention). */
898
911
  readonly explicitDefaultDirected: boolean;
899
912
 
913
+ /** The document's text, for an order JSON.parse does not keep (a JGF nodes object's integer-like keys). */
914
+ text = "";
915
+
900
916
  /** Nodes and edges pushed since the signal was last checked. */
901
917
  private elementsSinceCheck = 0;
902
918
 
@@ -1098,6 +1114,34 @@ export class ImportContext {
1098
1114
  /** Set when the document has no node section: every node comes from an edge (E_MISSING_SECTION says so). */
1099
1115
  nodesFromEdges = false;
1100
1116
 
1117
+ /** What the dialect's reader keeps for the style reader (`report.keepForStyles()`), or null. */
1118
+ styleSource: JsonStyleSource | null = null;
1119
+
1120
+ /**
1121
+ * A Cytoscape.js endpoint or parent id: Cytoscape.js holds every id as text, so an endpoint `0` names the node
1122
+ * `"0"` and an endpoint `"0"` the node `0` when the file has no node of the endpoint's own type.
1123
+ * @param raw - the JSON value
1124
+ * @param element - the element name for issues
1125
+ * @returns the id, or null when the value was reported
1126
+ */
1127
+ textualId(raw: unknown, element: string): NodeId | null {
1128
+ const id = this.coerceId(raw, element);
1129
+ if (id === null || this.sink.indexOf(id) !== INVALID_INDEX) {
1130
+ return id;
1131
+ }
1132
+ let other: unknown = null;
1133
+ if (typeof raw === "number" && Number.isFinite(raw)) {
1134
+ other = String(raw);
1135
+ } else if (typeof raw === "string" && /^-?\d+(?:\.\d+)?$/.test(raw)) {
1136
+ other = Number(raw);
1137
+ }
1138
+ if (other === null) {
1139
+ return id;
1140
+ }
1141
+ const alt = this.ids.value(other);
1142
+ return this.sink.indexOf(alt) === INVALID_INDEX ? id : alt;
1143
+ }
1144
+
1101
1145
  /** Dangling endpoints whose id, as the other JSON type, is a node (`"1"` next to the node 1). */
1102
1146
  private readonly nearMatches: NodeId[] = [];
1103
1147
 
@@ -1280,8 +1324,11 @@ export class ImportContext {
1280
1324
  * @param record - the element record
1281
1325
  * @param dict - the nested attribute dict
1282
1326
  * @param structural - the element keys that are not attributes
1283
- * @param weightFrom - the weight key to skip in the dict, or null
1284
1327
  * @param element - the element name for issues
1328
+ * @param how - `weightFrom`: the weight key to skip in the dict; `plain`: write the element-level keys under their
1329
+ * own names, not with the `#element` suffix
1330
+ * @param how.weightFrom - the weight key to skip in the dict, or null (the default)
1331
+ * @param how.plain - write the element-level keys under their own names (default false)
1285
1332
  */
1286
1333
  writeNested(
1287
1334
  writer: AttributeWriter,
@@ -1289,9 +1336,10 @@ export class ImportContext {
1289
1336
  record: JsonRecord,
1290
1337
  dict: JsonRecord,
1291
1338
  structural: ReadonlySet<string>,
1292
- weightFrom: string | null,
1293
1339
  element: string,
1340
+ how: { readonly weightFrom?: string | null; readonly plain?: boolean } = {},
1294
1341
  ): void {
1342
+ const { weightFrom = null, plain = false } = how;
1295
1343
  for (const key of Object.keys(dict)) {
1296
1344
  if (key !== weightFrom) {
1297
1345
  writer.write(row, key, dict[key], SUFFIX.data, element);
@@ -1299,7 +1347,7 @@ export class ImportContext {
1299
1347
  }
1300
1348
  for (const key of Object.keys(record)) {
1301
1349
  if (!structural.has(key)) {
1302
- writer.write(row, `${key}${SUFFIX.element}`, record[key], SUFFIX.data, element);
1350
+ writer.write(row, plain ? key : `${key}${SUFFIX.element}`, record[key], SUFFIX.data, element);
1303
1351
  }
1304
1352
  }
1305
1353
  }
@@ -1774,10 +1822,16 @@ function reportUnreadKeys(ctx: ImportContext, record: JsonRecord, known: Readonl
1774
1822
  }
1775
1823
 
1776
1824
  /** The keys of a vis.js document the reader reads. */
1777
- const VIS_DOCUMENT_KEYS: ReadonlySet<string> = new Set(["nodes", "edges"]);
1825
+ const VIS_DOCUMENT_KEYS: ReadonlySet<string> = new Set(["nodes", "edges", STYLE_BLOCK_NAME]);
1778
1826
 
1779
1827
  /** The keys of a graphology serialization the reader reads. */
1780
- const GRAPHOLOGY_DOCUMENT_KEYS: ReadonlySet<string> = new Set(["nodes", "edges", "options", "attributes"]);
1828
+ const GRAPHOLOGY_DOCUMENT_KEYS: ReadonlySet<string> = new Set([
1829
+ "nodes",
1830
+ "edges",
1831
+ "options",
1832
+ "attributes",
1833
+ STYLE_BLOCK_NAME,
1834
+ ]);
1781
1835
 
1782
1836
  /** The keys of a JGF graph object the reader reads. */
1783
1837
  const JGF_GRAPH_KEYS: ReadonlySet<string> = new Set([
@@ -1821,10 +1875,61 @@ function isIndexBelow(value: unknown, bound: number): boolean {
1821
1875
  */
1822
1876
  function importNodeLink(ctx: ImportContext, root: JsonRecord, dialect: "node-link" | "d3"): void {
1823
1877
  const { report, json } = ctx;
1824
- let { edgesKey } = json;
1825
- if (edgesKey === null) {
1826
- edgesKey = hasKey(root, "edges") || !hasKey(root, "links") ? "edges" : "links";
1878
+ const edgesKey = json.edgesKey ?? (hasKey(root, "edges") || !hasKey(root, "links") ? "edges" : "links");
1879
+ const { nodeList, edgeList } = nodeLinkSections(ctx, root, edgesKey);
1880
+ const directed = flagOf(root.directed, "directed", ctx.defaultDirected(dialect), report);
1881
+ const multigraph = hasKey(root, "multigraph") ? flagOf(root.multigraph, "multigraph", false, report) : null;
1882
+ ctx.setHeader(directed);
1883
+ ctx.writeGraphDict(root.graph, "graph");
1884
+ reportUnreadKeys(ctx, root, new Set(["nodes", edgesKey, "directed", "multigraph", "graph", STYLE_BLOCK_NAME]), "");
1885
+ // NetworkX's node_link_data writes all three keys; its drawing defaults then apply
1886
+ const networkx = hasKey(root, "directed") && hasKey(root, "multigraph") && hasKey(root, "graph");
1887
+ ctx.styleSource = { dialect, block: blockOf(root), networkx };
1888
+ ctx.sink.reserve(nodeList.length, edgeList.length);
1889
+
1890
+ const nodeIdKey = nodeLinkIdKey(ctx, nodeList);
1891
+ const positional = nodeIdKey === null;
1892
+ let indexLinks: boolean;
1893
+ if (positional) {
1894
+ indexLinks = true;
1895
+ } else if (json.indexLinks === "auto") {
1896
+ indexLinks = looksIndexLinked(nodeList, edgeList, nodeIdKey, json);
1897
+ } else {
1898
+ ({ indexLinks } = json);
1899
+ }
1900
+ if (indexLinks && !positional && json.indexLinks === "auto") {
1901
+ reportAmbiguousIndexLinks(ctx, nodeList, edgeList, nodeIdKey);
1827
1902
  }
1903
+ const positionIds = importNodeLinkNodes(ctx, nodeList, nodeIdKey, indexLinks);
1904
+ throwIfAborted(ctx.options.signal);
1905
+ const endpointKeys = importNodeLinkEdges(ctx, edgeList, edgesKey, positionIds, multigraph);
1906
+ ctx.setMeta(
1907
+ {
1908
+ dialect,
1909
+ edgesKey,
1910
+ nodeIdKey,
1911
+ indexLinks,
1912
+ sourceKey: endpointKeys.source ?? undefined,
1913
+ targetKey: endpointKeys.target ?? undefined,
1914
+ },
1915
+ { declaredMultigraph: multigraph, ...ctx.weightOriginPatch() },
1916
+ );
1917
+ }
1918
+
1919
+ /**
1920
+ * The node and edge arrays of a node-link or d3 document; a missing one is reported (without nodes, nodes come from
1921
+ * the edges).
1922
+ * @param ctx - the context
1923
+ * @param root - the document
1924
+ * @param edgesKey - the key of the edges
1925
+ * @returns the arrays, empty when missing
1926
+ */
1927
+ function nodeLinkSections(
1928
+ ctx: ImportContext,
1929
+ root: JsonRecord,
1930
+ edgesKey: string,
1931
+ ): { nodeList: readonly unknown[]; edgeList: readonly unknown[] } {
1932
+ const { report } = ctx;
1828
1933
  const nodes = arraySection(root.nodes, "nodes", report);
1829
1934
  const edges = arraySection(root[edgesKey], edgesKey, report);
1830
1935
  if (nodes === null) {
@@ -1844,101 +1949,100 @@ function importNodeLink(ctx: ImportContext, root: JsonRecord, dialect: "node-lin
1844
1949
  { element: edgesKey },
1845
1950
  );
1846
1951
  }
1847
- const directed = flagOf(root.directed, "directed", ctx.defaultDirected(dialect), report);
1848
- const multigraph = hasKey(root, "multigraph") ? flagOf(root.multigraph, "multigraph", false, report) : null;
1849
- ctx.setHeader(directed);
1850
- ctx.writeGraphDict(root.graph, "graph");
1851
- reportUnreadKeys(ctx, root, new Set(["nodes", edgesKey, "directed", "multigraph", "graph"]), "");
1852
-
1853
- const nodeList = nodes ?? [];
1854
- const edgeList = edges ?? [];
1855
- ctx.sink.reserve(nodeList.length, edgeList.length);
1952
+ return { nodeList: nodes ?? [], edgeList: edges ?? [] };
1953
+ }
1856
1954
 
1857
- // the id key: the caller's, else "id" when any node has it, else "name" (d3); nodes without any
1858
- // id key are positional (nodeIdFrom "index", or a d3 v3 file whose nodes carry no id at all)
1859
- let { nodeIdKey } = json;
1860
- let positional = ctx.options.nodeIdFrom === "index";
1861
- if (!positional && nodeIdKey === null) {
1862
- const candidates = ctx.options.nodeIdFrom === "label" ? ["label", "name", "id"] : ["id", "name"];
1863
- nodeIdKey = candidates.find((key) => anyHas(nodeList, key)) ?? null;
1864
- if (ctx.options.nodeIdFrom === "label" && (nodeIdKey === "id" || nodeIdKey === "name")) {
1865
- report.warning(
1866
- "unsupported",
1867
- JSON_ISSUE.OPTION_IGNORED,
1868
- nodeIdKey === "id"
1869
- ? 'nodeIdFrom "label": no node has a label or name key; ids are read from "id"'
1870
- : 'nodeIdFrom "label": no node has a "label" key; ids are read from "name"',
1871
- { element: "nodeIdFrom" },
1872
- );
1873
- }
1874
- if (nodeIdKey === null) {
1875
- if (nodeList.length > 0) {
1876
- positional = true;
1877
- report.warning(
1878
- "missing-value",
1879
- JSON_ISSUE.POSITIONAL_NODES,
1880
- "no node has an id or name key; array positions are the node ids",
1881
- { element: "nodes" },
1882
- );
1883
- } else {
1884
- nodeIdKey = "id";
1885
- }
1886
- }
1955
+ /**
1956
+ * The key node ids are read from: the caller's, else "id" when any node has it, else "name" (d3); null when the nodes
1957
+ * are positional (nodeIdFrom "index", or a d3 v3 file whose nodes carry no id at all).
1958
+ * @param ctx - the context
1959
+ * @param nodeList - the node records
1960
+ * @returns the key, or null for positional nodes
1961
+ */
1962
+ function nodeLinkIdKey(ctx: ImportContext, nodeList: readonly unknown[]): string | null {
1963
+ const { report, json } = ctx;
1964
+ if (ctx.options.nodeIdFrom === "index") {
1965
+ return null;
1887
1966
  }
1888
- if (positional) {
1889
- nodeIdKey = null;
1967
+ if (json.nodeIdKey !== null) {
1968
+ return json.nodeIdKey;
1890
1969
  }
1891
- let indexLinks: boolean;
1892
- if (positional) {
1893
- indexLinks = true;
1894
- } else if (json.indexLinks === "auto") {
1895
- indexLinks = nodeIdKey !== null && looksIndexLinked(nodeList, edgeList, nodeIdKey, json);
1896
- } else {
1897
- ({ indexLinks } = json);
1970
+ const byLabel = ctx.options.nodeIdFrom === "label";
1971
+ const candidates = byLabel ? ["label", "name", "id"] : ["id", "name"];
1972
+ const found = candidates.find((key) => anyHas(nodeList, key)) ?? null;
1973
+ if (byLabel && (found === "id" || found === "name")) {
1974
+ report.warning(
1975
+ "unsupported",
1976
+ JSON_ISSUE.OPTION_IGNORED,
1977
+ found === "id"
1978
+ ? 'nodeIdFrom "label": no node has a label or name key; ids are read from "id"'
1979
+ : 'nodeIdFrom "label": no node has a "label" key; ids are read from "name"',
1980
+ { element: "nodeIdFrom" },
1981
+ );
1898
1982
  }
1899
- const positionIds: (NodeId | null)[] | null = indexLinks ? [] : null;
1900
- if (indexLinks && !positional && json.indexLinks === "auto" && nodeIdKey !== null) {
1901
- reportAmbiguousIndexLinks(ctx, nodeList, edgeList, nodeIdKey);
1983
+ if (found !== null || nodeList.length === 0) {
1984
+ return found ?? "id";
1902
1985
  }
1986
+ report.warning(
1987
+ "missing-value",
1988
+ JSON_ISSUE.POSITIONAL_NODES,
1989
+ "no node has an id or name key; array positions are the node ids",
1990
+ { element: "nodes" },
1991
+ );
1992
+ return null;
1993
+ }
1903
1994
 
1995
+ /**
1996
+ * Read the node-link / d3 node records.
1997
+ * @param ctx - the context
1998
+ * @param nodeList - the node records
1999
+ * @param nodeIdKey - the key ids are read from, or null for positional nodes
2000
+ * @param indexLinks - whether edges name their endpoints by array position
2001
+ * @returns the ids by array position under index links (null where a node was not added), else null
2002
+ */
2003
+ function importNodeLinkNodes(
2004
+ ctx: ImportContext,
2005
+ nodeList: readonly unknown[],
2006
+ nodeIdKey: string | null,
2007
+ indexLinks: boolean,
2008
+ ): (NodeId | null)[] | null {
2009
+ const positionIds: (NodeId | null)[] | null = indexLinks ? [] : null;
1904
2010
  for (let i = 0; i < nodeList.length; i++) {
1905
2011
  const element = `nodes[${i}]`;
1906
2012
  const record = nodeList[i];
1907
2013
  let pushed: NodeId | null = null;
1908
- if (!isJsonObject(record)) {
1909
- ctx.badElement("node", element);
2014
+ if (isJsonObject(record)) {
2015
+ pushed = pushNodeLinkNode(ctx, record, i, nodeIdKey);
1910
2016
  } else {
1911
- let id: NodeId | null;
1912
- if (nodeIdKey === null) {
1913
- id = i;
1914
- } else {
1915
- id = ctx.coerceId(hasKey(record, nodeIdKey) ? record[nodeIdKey] : undefined, element);
1916
- }
1917
- if (id === null) {
1918
- ctx.countSkipped("node");
1919
- } else {
1920
- const index = ctx.pushNode(id, element);
1921
- if (index >= 0) {
1922
- pushed = id;
1923
- writeFlat(ctx.nodes, index, record, id, (key) => key !== nodeIdKey);
1924
- }
1925
- }
2017
+ ctx.badElement("node", element);
1926
2018
  }
1927
2019
  positionIds?.push(pushed);
1928
2020
  }
1929
- throwIfAborted(ctx.options.signal);
1930
- const endpointKeys = importNodeLinkEdges(ctx, edgeList, edgesKey, positionIds, multigraph);
1931
- ctx.setMeta(
1932
- {
1933
- dialect,
1934
- edgesKey,
1935
- nodeIdKey,
1936
- indexLinks,
1937
- sourceKey: endpointKeys.source ?? undefined,
1938
- targetKey: endpointKeys.target ?? undefined,
1939
- },
1940
- { declaredMultigraph: multigraph, ...ctx.weightOriginPatch() },
1941
- );
2021
+ return positionIds;
2022
+ }
2023
+
2024
+ /**
2025
+ * Add one node-link / d3 node and its other keys as attributes.
2026
+ * @param ctx - the context
2027
+ * @param record - the node record
2028
+ * @param i - its array position
2029
+ * @param nodeIdKey - the key its id is read from, or null for positional nodes
2030
+ * @returns its id, or null when it was not added
2031
+ */
2032
+ function pushNodeLinkNode(ctx: ImportContext, record: JsonRecord, i: number, nodeIdKey: string | null): NodeId | null {
2033
+ const element = `nodes[${i}]`;
2034
+ const raw = nodeIdKey !== null && hasKey(record, nodeIdKey) ? record[nodeIdKey] : undefined;
2035
+ const id = nodeIdKey === null ? i : ctx.coerceId(raw, element);
2036
+ if (id === null) {
2037
+ ctx.countSkipped("node");
2038
+ return null;
2039
+ }
2040
+ const index = ctx.pushNode(id, element);
2041
+ if (index < 0) {
2042
+ return null;
2043
+ }
2044
+ writeFlat(ctx.nodes, index, record, id, (key) => key !== nodeIdKey);
2045
+ return id;
1942
2046
  }
1943
2047
 
1944
2048
  /**
@@ -2276,6 +2380,7 @@ function importVis(ctx: ImportContext, root: JsonRecord): void {
2276
2380
  { element: "arrows" },
2277
2381
  );
2278
2382
  }
2383
+ ctx.styleSource = { dialect: "vis", block: blockOf(root), options: root.options, groups: root.groups };
2279
2384
  ctx.setMeta(
2280
2385
  { dialect: "vis", nodeIdKey, sourceKey: sourceKey ?? undefined, targetKey: targetKey ?? undefined },
2281
2386
  ctx.weightOriginPatch(),
@@ -2682,58 +2787,34 @@ function importGraphology(ctx: ImportContext, root: JsonRecord): void {
2682
2787
  });
2683
2788
  }
2684
2789
  const type = graphologyType(ctx, options);
2685
- ctx.setHeader(graphologyDirected(ctx, type, edges));
2790
+ ctx.setHeader(type === "mixed" ? mostlyDirected(ctx, edges) : type === "directed");
2686
2791
  const multi = hasKey(options, "multi") ? flagOf(options.multi, "options.multi", false, report) : null;
2687
2792
  const allowSelfLoops = hasKey(options, "allowSelfLoops")
2688
2793
  ? flagOf(options.allowSelfLoops, "options.allowSelfLoops", true, report)
2689
2794
  : undefined;
2690
2795
  ctx.writeGraphDict(root.attributes, "attributes");
2796
+ // Gephi's JSON export (for sigma.js) names nodes and edges by `id` and puts sigma's display keys (`label`, `x`,
2797
+ // `y`, `color`, `size`) beside `attributes`; they are read as attributes, not as unknown element keys
2798
+ const firstNode = nodes.find((n) => isJsonObject(n));
2799
+ const gephi = isJsonObject(firstNode) && !hasKey(firstNode, "key") && hasKey(firstNode, "id");
2800
+ ctx.styleSource = { dialect: "graphology", block: blockOf(root), gephi };
2691
2801
  ctx.reportNodeIdFrom("graphology", "the node key");
2692
2802
  ctx.sink.reserve(nodes.length, edges.length);
2693
2803
 
2694
- for (let i = 0; i < nodes.length; i++) {
2695
- pushGraphologyNode(ctx, nodes[i], `nodes[${i}]`);
2696
- }
2804
+ importGraphologyNodes(ctx, nodes, gephi);
2697
2805
  throwIfAborted(ctx.options.signal);
2698
-
2699
- const reading: GraphologyReading = {
2700
- ids: ctx.declareEdgeIds(edges, (edge) => edge.key, "key", true),
2701
- type,
2702
- allowSelfLoops,
2703
- // what the edges do against the declared options: graphology itself refuses such a document
2704
- violations: { selfLoops: 0, parallels: 0, flags: 0 },
2705
- pairs: multi === false ? new Set<string>() : null,
2706
- };
2707
- for (let i = 0; i < edges.length; i++) {
2708
- readGraphologyEdge(ctx, reading, edges[i], `edges[${i}]`);
2709
- }
2710
- reportGraphologyViolations(ctx, reading.violations, type);
2806
+ const violations = importGraphologyEdges(ctx, edges, { type, gephi, multi, allowSelfLoops });
2807
+ reportGraphologyViolations(ctx, violations, type);
2711
2808
  ctx.setMeta({ dialect: "graphology", allowSelfLoops }, { declaredMultigraph: multi, ...ctx.weightOriginPatch() });
2712
2809
  }
2713
2810
 
2714
- /** The graph types a graphology document declares in `options.type`. */
2715
- type GraphologyType = "directed" | "undirected" | "mixed";
2716
-
2717
- /** The state of a graphology read, shared by its edges. */
2718
- interface GraphologyReading {
2719
- /** The edge id column, from declareEdgeIds(). */
2720
- readonly ids: EdgeIdColumn | null;
2721
- readonly type: GraphologyType;
2722
- /** options.allowSelfLoops, or undefined when absent. */
2723
- readonly allowSelfLoops: boolean | undefined;
2724
- /** The edges that contradict the declared options (reportGraphologyViolations()). */
2725
- readonly violations: { selfLoops: number; parallels: number; flags: number };
2726
- /** The pairs seen so far under multi false, or null. */
2727
- readonly pairs: Set<string> | null;
2728
- }
2729
-
2730
2811
  /**
2731
- * A graphology document's `options.type`; anything else is reported and read as "mixed".
2812
+ * A graphology document's `options.type`: "mixed" when absent or not one of the three (reported).
2732
2813
  * @param ctx - the context
2733
- * @param options - the options object
2814
+ * @param options - the document's options
2734
2815
  * @returns the type
2735
2816
  */
2736
- function graphologyType(ctx: ImportContext, options: JsonRecord): GraphologyType {
2817
+ function graphologyType(ctx: ImportContext, options: JsonRecord): "directed" | "undirected" | "mixed" {
2737
2818
  if (options.type === "directed" || options.type === "undirected" || options.type === "mixed") {
2738
2819
  return options.type;
2739
2820
  }
@@ -2749,127 +2830,170 @@ function graphologyType(ctx: ImportContext, options: JsonRecord): GraphologyType
2749
2830
  }
2750
2831
 
2751
2832
  /**
2752
- * The header direction of a graphology document: its type, or for "mixed" directed unless every
2753
- * edge object says undirected (the dialect default when there is none).
2833
+ * The header direction of a mixed graphology graph: directed unless every edge is flagged undirected.
2754
2834
  * @param ctx - the context
2755
- * @param type - options.type
2756
2835
  * @param edges - the edge records
2757
- * @returns whether the header is directed
2836
+ * @returns true for directed
2758
2837
  */
2759
- function graphologyDirected(ctx: ImportContext, type: GraphologyType, edges: readonly unknown[]): boolean {
2760
- if (type !== "mixed") {
2761
- return type === "directed";
2762
- }
2838
+ function mostlyDirected(ctx: ImportContext, edges: readonly unknown[]): boolean {
2763
2839
  const objects = edges.filter((edge) => isJsonObject(edge)) as JsonRecord[];
2764
2840
  const undirectedEdges = objects.filter((edge) => edge.undirected === true).length;
2765
2841
  return objects.length === 0 ? ctx.defaultDirected("graphology") : undirectedEdges < objects.length;
2766
2842
  }
2767
2843
 
2768
2844
  /**
2769
- * Push one graphology node record: `key` the id, `attributes` the columns.
2845
+ * Read the graphology node records.
2770
2846
  * @param ctx - the context
2771
- * @param record - the node record
2772
- * @param element - the element name
2847
+ * @param nodes - the node records
2848
+ * @param gephi - whether Gephi wrote them (ids under `id`, display keys beside the attributes)
2773
2849
  */
2774
- function pushGraphologyNode(ctx: ImportContext, record: unknown, element: string): void {
2775
- if (!isJsonObject(record)) {
2776
- ctx.badElement("node", element);
2777
- return;
2850
+ function importGraphologyNodes(ctx: ImportContext, nodes: readonly unknown[], gephi: boolean): void {
2851
+ const nodeKey = gephi ? "id" : "key";
2852
+ for (let i = 0; i < nodes.length; i++) {
2853
+ const element = `nodes[${i}]`;
2854
+ const record = nodes[i];
2855
+ if (!isJsonObject(record)) {
2856
+ ctx.badElement("node", element);
2857
+ continue;
2858
+ }
2859
+ const id = ctx.coerceId(hasKey(record, nodeKey) ? record[nodeKey] : undefined, element);
2860
+ if (id === null) {
2861
+ ctx.countSkipped("node");
2862
+ continue;
2863
+ }
2864
+ pushNestedNode(ctx, id, record, "attributes", gephi ? GEPHI_NODE_KEYS : GRAPHOLOGY_NODE_KEYS, element, gephi);
2778
2865
  }
2779
- const id = ctx.coerceId(hasKey(record, "key") ? record.key : undefined, element);
2780
- if (id === null) {
2781
- ctx.countSkipped("node");
2782
- return;
2866
+ }
2867
+
2868
+ /** What a graphology document declares about its edges. */
2869
+ interface GraphologyEdgeOptions {
2870
+ /** `options.type`. */
2871
+ readonly type: "directed" | "undirected" | "mixed";
2872
+ /** Whether Gephi wrote the document. */
2873
+ readonly gephi: boolean;
2874
+ /** `options.multi`, or null when absent. */
2875
+ readonly multi: boolean | null;
2876
+ /** `options.allowSelfLoops`, or undefined when absent. */
2877
+ readonly allowSelfLoops: boolean | undefined;
2878
+ }
2879
+
2880
+ /** How many edges contradict a graphology document's declared options. */
2881
+ interface GraphologyViolations {
2882
+ selfLoops: number;
2883
+ parallels: number;
2884
+ flags: number;
2885
+ }
2886
+
2887
+ /**
2888
+ * Read the graphology edge records.
2889
+ * @param ctx - the context
2890
+ * @param edges - the edge records
2891
+ * @param declared - what the document declares
2892
+ * @returns the edges that contradict the declared options: graphology itself refuses such a document
2893
+ */
2894
+ function importGraphologyEdges(
2895
+ ctx: ImportContext,
2896
+ edges: readonly unknown[],
2897
+ declared: GraphologyEdgeOptions,
2898
+ ): GraphologyViolations {
2899
+ const { gephi } = declared;
2900
+ const ids = ctx.declareEdgeIds(edges, (edge) => (gephi && !hasKey(edge, "key") ? edge.id : edge.key), "key", true);
2901
+ const violations: GraphologyViolations = { selfLoops: 0, parallels: 0, flags: 0 };
2902
+ const pairs = declared.multi === false ? new Set<string>() : null;
2903
+ for (let i = 0; i < edges.length; i++) {
2904
+ const element = `edges[${i}]`;
2905
+ const record = graphologyEdgeRecord(ctx, edges[i], element);
2906
+ if (record === null) {
2907
+ continue;
2908
+ }
2909
+ try {
2910
+ const pushed = pushGraphologyEdge(ctx, record, element, ids, declared, violations);
2911
+ if (pushed !== null && pairs !== null) {
2912
+ violations.parallels += pairs.has(pushed) ? 1 : 0;
2913
+ pairs.add(pushed);
2914
+ }
2915
+ } catch (err) {
2916
+ ctx.skip(err, "edge", element);
2917
+ }
2783
2918
  }
2784
- pushNestedNode(ctx, id, record, "attributes", GRAPHOLOGY_NODE_KEYS, element);
2919
+ return violations;
2785
2920
  }
2786
2921
 
2787
2922
  /**
2788
- * Read one graphology edge record, counting what it does against the declared options.
2923
+ * One graphology edge record, when it is an object with both endpoints.
2789
2924
  * @param ctx - the context
2790
- * @param reading - the read's state
2791
2925
  * @param record - the edge record
2792
2926
  * @param element - the element name
2927
+ * @returns the record, or null when it was reported and is skipped
2793
2928
  */
2794
- function readGraphologyEdge(ctx: ImportContext, reading: GraphologyReading, record: unknown, element: string): void {
2929
+ function graphologyEdgeRecord(ctx: ImportContext, record: unknown, element: string): JsonRecord | null {
2795
2930
  if (!isJsonObject(record)) {
2796
2931
  ctx.badElement("edge", element);
2797
- return;
2932
+ return null;
2798
2933
  }
2799
2934
  if (!present(record, "source") || !present(record, "target")) {
2800
2935
  ctx.missingEndpoint(element, present(record, "source") ? "target" : "source");
2801
- return;
2802
- }
2803
- try {
2804
- const u = ctx.coerceId(record.source, `${element}.source`);
2805
- const v = ctx.coerceId(record.target, `${element}.target`);
2806
- if (u === null || v === null) {
2807
- ctx.countSkipped("edge");
2808
- return;
2809
- }
2810
- const kind = graphologyEdgeKind(ctx, reading, record, element);
2811
- const attributes = graphologyEdgeAttributes(ctx, record, element);
2812
- const idValue = ctx.edgeIdValue(reading.ids, record.key);
2813
- const edge = ctx.pushEdge(u, v, kind, ctx.weightOf(attributes), element);
2814
- ctx.setEdgeId(reading.ids, edge, idValue);
2815
- ctx.writeNested(ctx.edges, edge, record, attributes, GRAPHOLOGY_EDGE_KEYS, ctx.options.weightFrom, element);
2816
- if (reading.allowSelfLoops === false && u === v) {
2817
- reading.violations.selfLoops++;
2818
- }
2819
- if (reading.pairs !== null) {
2820
- const pair = `${kind} ${pairKey(u, v, kind === "directed")}`;
2821
- reading.violations.parallels += reading.pairs.has(pair) ? 1 : 0;
2822
- reading.pairs.add(pair);
2823
- }
2824
- } catch (err) {
2825
- ctx.skip(err, "edge", element);
2936
+ return null;
2826
2937
  }
2938
+ return record;
2827
2939
  }
2828
2940
 
2829
2941
  /**
2830
- * The direction of one graphology edge: its `undirected` flag under type "mixed", else the type
2831
- * (a contrary flag is counted as a violation).
2942
+ * Add one graphology edge with its id and attributes.
2832
2943
  * @param ctx - the context
2833
- * @param reading - the read's state
2834
2944
  * @param record - the edge record
2835
2945
  * @param element - the element name
2836
- * @returns the edge's kind
2946
+ * @param ids - the edge id column
2947
+ * @param declared - what the document declares
2948
+ * @param violations - counts the edges that contradict the declared options
2949
+ * @returns the edge's kind and endpoint pair, or null when it was skipped
2837
2950
  */
2838
- function graphologyEdgeKind(
2951
+ function pushGraphologyEdge(
2839
2952
  ctx: ImportContext,
2840
- reading: GraphologyReading,
2841
2953
  record: JsonRecord,
2842
2954
  element: string,
2843
- ): EdgeKind {
2844
- const { type } = reading;
2845
- if (type === "mixed") {
2846
- return flagOf(record.undirected, `${element}.undirected`, false, ctx.report) ? "undirected" : "directed";
2955
+ ids: Parameters<ImportContext["edgeIdValue"]>[0],
2956
+ declared: GraphologyEdgeOptions,
2957
+ violations: GraphologyViolations,
2958
+ ): string | null {
2959
+ const { report } = ctx;
2960
+ const { type, gephi } = declared;
2961
+ const u = ctx.coerceId(record.source, `${element}.source`);
2962
+ const v = ctx.coerceId(record.target, `${element}.target`);
2963
+ if (u === null || v === null) {
2964
+ ctx.countSkipped("edge");
2965
+ return null;
2847
2966
  }
2848
- if (typeof record.undirected === "boolean" && record.undirected !== (type === "undirected")) {
2849
- reading.violations.flags++;
2967
+ let kind: EdgeKind;
2968
+ if (type === "mixed") {
2969
+ kind = flagOf(record.undirected, `${element}.undirected`, false, report) ? "undirected" : "directed";
2970
+ } else {
2971
+ kind = type;
2972
+ if (typeof record.undirected === "boolean" && record.undirected !== (type === "undirected")) {
2973
+ violations.flags++;
2974
+ }
2850
2975
  }
2851
- return type;
2852
- }
2853
-
2854
- /**
2855
- * The `attributes` object of a graphology edge; any other non-null value is reported and read as
2856
- * no attributes.
2857
- * @param ctx - the context
2858
- * @param record - the edge record
2859
- * @param element - the element name
2860
- * @returns the attributes
2861
- */
2862
- function graphologyEdgeAttributes(ctx: ImportContext, record: JsonRecord, element: string): JsonRecord {
2863
- const { attributes } = record;
2864
- if (attributes !== undefined && attributes !== null && !isJsonObject(attributes)) {
2865
- ctx.report.error(
2976
+ const { attributes: rawAttributes } = record;
2977
+ if (rawAttributes !== undefined && rawAttributes !== null && !isJsonObject(rawAttributes)) {
2978
+ report.error(
2866
2979
  "validation-error",
2867
2980
  JSON_ISSUE.BAD_VALUE,
2868
- `${element}: attributes must be an object, found ${describe(attributes)}`,
2981
+ `${element}: attributes must be an object, found ${describe(rawAttributes)}`,
2869
2982
  { element },
2870
2983
  );
2871
2984
  }
2872
- return isJsonObject(attributes) ? attributes : {};
2985
+ const attributes = isJsonObject(rawAttributes) ? rawAttributes : {};
2986
+ const idValue = ctx.edgeIdValue(ids, record.key);
2987
+ const edge = ctx.pushEdge(u, v, kind, ctx.weightOf(attributes), element);
2988
+ ctx.setEdgeId(ids, edge, idValue);
2989
+ ctx.writeNested(ctx.edges, edge, record, attributes, gephi ? GEPHI_EDGE_KEYS : GRAPHOLOGY_EDGE_KEYS, element, {
2990
+ weightFrom: ctx.options.weightFrom,
2991
+ plain: gephi,
2992
+ });
2993
+ if (declared.allowSelfLoops === false && u === v) {
2994
+ violations.selfLoops++;
2995
+ }
2996
+ return `${kind} ${pairKey(u, v, kind === "directed")}`;
2873
2997
  }
2874
2998
 
2875
2999
  /**
@@ -2919,6 +3043,7 @@ function reportGraphologyViolations(
2919
3043
  * @param dictKey - the key of the nested dict
2920
3044
  * @param structural - the element keys that are not attributes
2921
3045
  * @param element - the element name
3046
+ * @param plain - write the element-level keys under their own names
2922
3047
  * @returns the node index, or -1 when skipped
2923
3048
  */
2924
3049
  function pushNestedNode(
@@ -2928,6 +3053,7 @@ function pushNestedNode(
2928
3053
  dictKey: string,
2929
3054
  structural: ReadonlySet<string>,
2930
3055
  element: string,
3056
+ plain = false,
2931
3057
  ): number {
2932
3058
  const index = ctx.pushNode(id, element);
2933
3059
  if (index < 0) {
@@ -2943,7 +3069,7 @@ function pushNestedNode(
2943
3069
  { element },
2944
3070
  );
2945
3071
  }
2946
- ctx.writeNested(ctx.nodes, index, record, isJsonObject(dict) ? dict : {}, structural, null, element);
3072
+ ctx.writeNested(ctx.nodes, index, record, isJsonObject(dict) ? dict : {}, structural, element, { plain });
2947
3073
  } catch (err) {
2948
3074
  ctx.report.recordError(err, { element: String(id) });
2949
3075
  }
@@ -2990,7 +3116,7 @@ function importJgf(ctx: ImportContext, root: JsonRecord): void {
2990
3116
  anyHas(nodeRecords, "label"),
2991
3117
  );
2992
3118
  if (isJsonObject(nodesRaw)) {
2993
- for (const key of Object.keys(nodesRaw)) {
3119
+ for (const key of jgfNodeKeys(ctx, root, graph, nodesRaw)) {
2994
3120
  pushJgfKeyedNode(ctx, key, nodesRaw[key], labelColumn);
2995
3121
  }
2996
3122
  } else {
@@ -3127,7 +3253,7 @@ function jgfEdgeWriter(ctx: ImportContext, edges: readonly unknown[], hyperedges
3127
3253
  element,
3128
3254
  });
3129
3255
  }
3130
- ctx.writeNested(ctx.edges, edge, record, dict, structural, ctx.options.weightFrom, element);
3256
+ ctx.writeNested(ctx.edges, edge, record, dict, structural, element, { weightFrom: ctx.options.weightFrom });
3131
3257
  };
3132
3258
  }
3133
3259
 
@@ -3168,6 +3294,26 @@ function readJgfEdge(
3168
3294
  }
3169
3295
  }
3170
3296
 
3297
+ /**
3298
+ * The keys of a JGF nodes object in the file's order, which is the nodes' order: JSON.parse puts integer-like keys
3299
+ * first, so then they are read from the text.
3300
+ * @param ctx - the context, holding the text
3301
+ * @param root - the document
3302
+ * @param graph - the graph object read
3303
+ * @param nodes - its nodes object
3304
+ * @returns the keys
3305
+ */
3306
+ function jgfNodeKeys(ctx: ImportContext, root: JsonRecord, graph: JsonRecord, nodes: JsonRecord): string[] {
3307
+ const keys = Object.keys(nodes);
3308
+ if (!keys.some((k) => ARRAY_INDEX.test(k))) {
3309
+ return keys;
3310
+ }
3311
+ const path = isJsonObject(root.graph)
3312
+ ? ["graph", "nodes"]
3313
+ : ["graphs", (root.graphs as unknown[]).indexOf(graph), "nodes"];
3314
+ return keysInTextOrder(ctx.text, path) ?? keys;
3315
+ }
3316
+
3171
3317
  /**
3172
3318
  * The graph object of a JGF document: `graph`, or the graph of `graphs` that graphIndex /
3173
3319
  * graphName choose (chooseGraph()).
@@ -3345,7 +3491,7 @@ function pushJgfNode(
3345
3491
  element,
3346
3492
  });
3347
3493
  }
3348
- ctx.writeNested(ctx.nodes, index, record, isJsonObject(metadata) ? metadata : {}, JGF_NODE_KEYS, null, element);
3494
+ ctx.writeNested(ctx.nodes, index, record, isJsonObject(metadata) ? metadata : {}, JGF_NODE_KEYS, element);
3349
3495
  } catch (err) {
3350
3496
  ctx.report.recordError(err, { element: String(id) });
3351
3497
  }
@@ -3562,69 +3708,63 @@ function distinctMembers(ctx: ImportContext, members: readonly NodeId[], element
3562
3708
  function importCytoscape(ctx: ImportContext, root: unknown): void {
3563
3709
  const { elements, extra } = cytoscapeRoot(ctx, root);
3564
3710
  const { nodes, edges } = cytoscapeSections(ctx, elements);
3711
+ const generatedIds = cytoscapeGeneratedIds(nodes);
3712
+ ctx.styleSource = {
3713
+ dialect: "cytoscape",
3714
+ block: blockOf(root),
3715
+ elements: elements ?? [],
3716
+ style: isJsonObject(root) ? root.style : undefined,
3717
+ generatedBy: isJsonObject(root) && typeof root.generated_by === "string" ? root.generated_by : null,
3718
+ generatedIds,
3719
+ };
3565
3720
  ctx.setHeader(ctx.defaultDirected("cytoscape"));
3566
3721
  ctx.reportNodeIdFrom("cytoscape", "data.id");
3567
3722
  ctx.sink.reserve(nodes.length, edges.length);
3568
3723
 
3569
- const dataHas = (items: readonly unknown[], key: string): boolean =>
3570
- items.some((item) => isJsonObject(item) && isJsonObject(item.data) && item.data[key] !== undefined);
3571
- const reading: CytoscapeNodeReading = {
3572
- positionColumn: ctx.nodes.declareIf(
3573
- {
3574
- name: POSITION_COLUMN,
3575
- dtype: "f64",
3576
- components: 3,
3577
- role: "position",
3578
- mutable: true,
3579
- nullable: true,
3580
- // 3D layouts written by Cytoscape-compatible tools carry a z
3581
- extra: { sourceDims: nodes.some(hasPositionZ) ? 3 : 2, units: "file" },
3582
- origin: { format: "json", namespace: "cytoscape" },
3583
- },
3584
- anyHas(nodes, "position"),
3585
- ),
3586
- classesColumn: ctx.nodes.declareIf(
3587
- { name: CLASSES_COLUMN, dtype: "list", itemDtype: "string", role: "classes", nullable: true },
3588
- anyHas(nodes, "classes"),
3589
- ),
3590
- parents: [],
3591
- point: [0, 0, 0],
3592
- };
3593
- const parentColumn = ctx.nodes.declareIf(
3594
- { name: PARENT_COLUMN, dtype: "u32", role: "parent", refersTo: "node", nullable: true },
3595
- dataHas(nodes, "parent"),
3596
- );
3724
+ const columns = cytoscapeNodeColumns(ctx, nodes);
3597
3725
  const edgeClassesColumn = ctx.edges.declareIf(
3598
3726
  { name: CLASSES_COLUMN, dtype: "list", itemDtype: "string", role: "classes", nullable: true },
3599
3727
  anyHas(edges, "classes"),
3600
3728
  );
3601
-
3729
+ const parents: CytoscapeParentRef[] = [];
3602
3730
  for (let i = 0; i < nodes.length; i++) {
3603
- readCytoscapeNode(ctx, reading, nodes[i], `nodes[${i}]`);
3731
+ const element = `nodes[${i}]`;
3732
+ const record = nodes[i];
3733
+ if (isJsonObject(record) && isJsonObject(record.data)) {
3734
+ // Cytoscape.js gives a node without data.id an id of its own
3735
+ const raw = record.data.id === undefined ? generatedIds.get(record) : record.data.id;
3736
+ importCytoscapeNode(ctx, record, record.data, raw, element, columns, parents);
3737
+ } else {
3738
+ ctx.badElement("node", element, "an element with a data object");
3739
+ }
3604
3740
  }
3605
- resolveCytoscapeParents(ctx, reading.parents, parentColumn);
3741
+ resolveCytoscapeParents(ctx, parents, columns.parent);
3606
3742
  throwIfAborted(ctx.options.signal);
3607
3743
 
3608
3744
  importCytoscapeEdges(ctx, edges, edgeClassesColumn);
3609
3745
  ctx.setMeta({ dialect: "cytoscape", cytoscape: extra }, ctx.weightOriginPatch());
3610
3746
  }
3611
3747
 
3612
- /** The state of a read of Cytoscape node elements. */
3613
- interface CytoscapeNodeReading {
3614
- readonly positionColumn: ColumnHandle;
3615
- readonly classesColumn: ColumnHandle;
3616
- /** The parent links, resolved once every node is known. */
3617
- readonly parents: { index: number; parent: NodeId; element: string }[];
3618
- /** A scratch position. */
3619
- readonly point: [number, number, number];
3748
+ /** A Cytoscape node's `data.parent`, resolved once every node is known. */
3749
+ interface CytoscapeParentRef {
3750
+ readonly index: number;
3751
+ readonly parent: NodeId;
3752
+ readonly element: string;
3753
+ }
3754
+
3755
+ /** The node columns a Cytoscape document fills besides its data. */
3756
+ interface CytoscapeNodeColumns {
3757
+ readonly position: ColumnHandle;
3758
+ readonly classes: ColumnHandle;
3759
+ readonly parent: ColumnHandle;
3620
3760
  }
3621
3761
 
3622
3762
  /**
3623
- * The elements of a Cytoscape document (the document itself when it is an array) and, for an
3624
- * object, its graph `data` written and its other top-level keys kept.
3763
+ * A Cytoscape document's elements, and its other top-level keys (kept in meta); its `data` is written as graph
3764
+ * attributes.
3625
3765
  * @param ctx - the context
3626
- * @param root - the document
3627
- * @returns the elements and the other top-level keys, if any
3766
+ * @param root - the document: an object, or an array of elements
3767
+ * @returns the elements and the other keys (undefined when there are none)
3628
3768
  */
3629
3769
  function cytoscapeRoot(ctx: ImportContext, root: unknown): { elements: unknown; extra?: Record<string, unknown> } {
3630
3770
  if (Array.isArray(root)) {
@@ -3636,32 +3776,73 @@ function cytoscapeRoot(ctx: ImportContext, root: unknown): { elements: unknown;
3636
3776
  `a Cytoscape document must be an object or an array, found ${describe(root)}`,
3637
3777
  );
3638
3778
  }
3639
- ctx.writeGraphDict(root.data, "data");
3779
+ const object = root as JsonRecord;
3780
+ ctx.writeGraphDict(object.data, "data");
3640
3781
  const rest: Record<string, unknown> = {};
3641
- for (const key of Object.keys(root)) {
3642
- if (key !== "elements" && key !== "data") {
3643
- rest[key] = root[key];
3782
+ for (const key of Object.keys(object)) {
3783
+ if (key !== "elements" && key !== "data" && key !== STYLE_BLOCK_NAME) {
3784
+ rest[key] = object[key];
3644
3785
  }
3645
3786
  }
3646
- return Object.keys(rest).length > 0 ? { elements: root.elements, extra: rest } : { elements: root.elements };
3787
+ return Object.keys(rest).length > 0 ? { elements: object.elements, extra: rest } : { elements: object.elements };
3647
3788
  }
3648
3789
 
3649
3790
  /**
3650
- * Read one Cytoscape node element: `data.id` the id, the other `data` keys attributes (`parent`
3651
- * kept for later), `position`, `classes` and the element-level keys.
3791
+ * Declare the position, classes and parent columns the node records use.
3792
+ * @param ctx - the context
3793
+ * @param nodes - the node records
3794
+ * @returns the columns
3795
+ */
3796
+ function cytoscapeNodeColumns(ctx: ImportContext, nodes: readonly unknown[]): CytoscapeNodeColumns {
3797
+ const dataHas = (items: readonly unknown[], key: string): boolean =>
3798
+ items.some((item) => isJsonObject(item) && isJsonObject(item.data) && item.data[key] !== undefined);
3799
+ return {
3800
+ position: ctx.nodes.declareIf(
3801
+ {
3802
+ name: POSITION_COLUMN,
3803
+ dtype: "f64",
3804
+ components: 3,
3805
+ role: "position",
3806
+ mutable: true,
3807
+ nullable: true,
3808
+ // 3D layouts written by Cytoscape-compatible tools carry a z
3809
+ extra: { sourceDims: nodes.some(hasPositionZ) ? 3 : 2, units: "file" },
3810
+ origin: { format: "json", namespace: "cytoscape" },
3811
+ },
3812
+ anyHas(nodes, "position"),
3813
+ ),
3814
+ classes: ctx.nodes.declareIf(
3815
+ { name: CLASSES_COLUMN, dtype: "list", itemDtype: "string", role: "classes", nullable: true },
3816
+ anyHas(nodes, "classes"),
3817
+ ),
3818
+ parent: ctx.nodes.declareIf(
3819
+ { name: PARENT_COLUMN, dtype: "u32", role: "parent", refersTo: "node", nullable: true },
3820
+ dataHas(nodes, "parent"),
3821
+ ),
3822
+ };
3823
+ }
3824
+
3825
+ /**
3826
+ * Add one Cytoscape node: its data as attributes (its parent kept for later), its position, classes and element keys.
3652
3827
  * @param ctx - the context
3653
- * @param reading - the read's state
3654
3828
  * @param record - the element
3829
+ * @param data - its data object
3830
+ * @param raw - its raw id
3655
3831
  * @param element - the element name
3832
+ * @param columns - the node columns
3833
+ * @param parents - receives its parent link
3656
3834
  */
3657
- function readCytoscapeNode(ctx: ImportContext, reading: CytoscapeNodeReading, record: unknown, element: string): void {
3835
+ function importCytoscapeNode(
3836
+ ctx: ImportContext,
3837
+ record: JsonRecord,
3838
+ data: JsonRecord,
3839
+ raw: unknown,
3840
+ element: string,
3841
+ columns: CytoscapeNodeColumns,
3842
+ parents: CytoscapeParentRef[],
3843
+ ): void {
3658
3844
  const { report } = ctx;
3659
- if (!isJsonObject(record) || !isJsonObject(record.data)) {
3660
- ctx.badElement("node", element, "an element with a data object");
3661
- return;
3662
- }
3663
- const { data } = record;
3664
- const id = ctx.coerceId(hasKey(data, "id") ? data.id : undefined, element);
3845
+ const id = ctx.coerceId(raw, element);
3665
3846
  if (id === null) {
3666
3847
  ctx.countSkipped("node");
3667
3848
  return;
@@ -3679,9 +3860,18 @@ function readCytoscapeNode(ctx: ImportContext, reading: CytoscapeNodeReading, re
3679
3860
  );
3680
3861
  }
3681
3862
  try {
3682
- writeCytoscapeData(ctx, reading, index, data, element);
3683
- writeCytoscapePosition(ctx, reading, index, record.position, element);
3684
- writeClasses(ctx, ctx.nodes, reading.classesColumn, index, record.classes, element);
3863
+ for (const key of Object.keys(data).filter((k) => k !== "id")) {
3864
+ if (key !== "parent") {
3865
+ ctx.nodes.write(index, key, data[key], SUFFIX.data, element);
3866
+ continue;
3867
+ }
3868
+ const parent = data.parent === null ? null : ctx.coerceId(data.parent, `${element}.data.parent`);
3869
+ if (parent !== null) {
3870
+ parents.push({ index, parent, element });
3871
+ }
3872
+ }
3873
+ writeCytoscapePosition(ctx, columns.position, index, record.position, element);
3874
+ writeClasses(ctx, ctx.nodes, columns.classes, index, record.classes, element);
3685
3875
  writeElementKeys(ctx.nodes, index, record, element);
3686
3876
  } catch (err) {
3687
3877
  report.recordError(err, { element: String(id) });
@@ -3689,51 +3879,17 @@ function readCytoscapeNode(ctx: ImportContext, reading: CytoscapeNodeReading, re
3689
3879
  }
3690
3880
 
3691
3881
  /**
3692
- * Write a Cytoscape node's `data` keys as attributes; `parent` is kept as a link to resolve later.
3693
- * @param ctx - the context
3694
- * @param reading - the read's state
3695
- * @param index - the node index
3696
- * @param data - the element's data object
3697
- * @param element - the element name
3698
- */
3699
- function writeCytoscapeData(
3700
- ctx: ImportContext,
3701
- reading: CytoscapeNodeReading,
3702
- index: number,
3703
- data: JsonRecord,
3704
- element: string,
3705
- ): void {
3706
- for (const key of Object.keys(data)) {
3707
- if (key === "id") {
3708
- continue;
3709
- }
3710
- if (key !== "parent") {
3711
- ctx.nodes.write(index, key, data[key], SUFFIX.data, element);
3712
- continue;
3713
- }
3714
- const raw = data.parent;
3715
- if (raw === undefined || raw === null) {
3716
- continue;
3717
- }
3718
- const parent = ctx.coerceId(raw, `${element}.data.parent`);
3719
- if (parent !== null) {
3720
- reading.parents.push({ index, parent, element });
3721
- }
3722
- }
3723
- }
3724
-
3725
- /**
3726
- * Write a Cytoscape node's `position` ({ x, y, z? }, y flipped to grow upward); any other value is
3727
- * reported.
3882
+ * Write a Cytoscape node's position, y flipped (Cytoscape's y grows downward; positions are stored y-up and the
3883
+ * exporter flips back).
3728
3884
  * @param ctx - the context
3729
- * @param reading - the read's state
3730
- * @param index - the node index
3885
+ * @param column - the position column
3886
+ * @param index - the node
3731
3887
  * @param position - the element's position
3732
3888
  * @param element - the element name
3733
3889
  */
3734
3890
  function writeCytoscapePosition(
3735
3891
  ctx: ImportContext,
3736
- reading: CytoscapeNodeReading,
3892
+ column: ColumnHandle,
3737
3893
  index: number,
3738
3894
  position: unknown,
3739
3895
  element: string,
@@ -3743,12 +3899,7 @@ function writeCytoscapePosition(
3743
3899
  }
3744
3900
  const z = isJsonObject(position) ? (position.z ?? 0) : 0;
3745
3901
  if (isJsonObject(position) && isCoordinate(position.x) && isCoordinate(position.y) && isCoordinate(z)) {
3746
- const { point } = reading;
3747
- point[0] = position.x;
3748
- // Cytoscape's y grows downward; positions are stored y-up (the exporter flips back)
3749
- point[1] = flipY(position.y);
3750
- point[2] = z;
3751
- ctx.nodes.set(reading.positionColumn, index, point);
3902
+ ctx.nodes.set(column, index, [position.x, flipY(position.y), z]);
3752
3903
  } else {
3753
3904
  ctx.report.error(
3754
3905
  "validation-error",
@@ -3784,7 +3935,11 @@ function resolveCytoscapeParents(
3784
3935
  // the last link per child (a duplicate node's later parent wins), child -> its link
3785
3936
  const linkOf = new Map<number, ParentLink>();
3786
3937
  for (let order = 0; order < parents.length; order++) {
3787
- const { index, parent, element } = parents[order];
3938
+ const { index, element } = parents[order];
3939
+ let { parent } = parents[order];
3940
+ if (ctx.sink.indexOf(parent) === INVALID_INDEX) {
3941
+ parent = ctx.textualId(parent, element) ?? parent;
3942
+ }
3788
3943
  const parentIndex = ctx.sink.indexOf(parent);
3789
3944
  if (parentIndex === INVALID_INDEX) {
3790
3945
  report.error(
@@ -3898,8 +4053,8 @@ function importCytoscapeEdge(
3898
4053
  ): void {
3899
4054
  const { ids, kind, edgeClassesColumn } = shared;
3900
4055
  try {
3901
- const u = ctx.coerceId(data.source, `${element}.data.source`);
3902
- const v = ctx.coerceId(data.target, `${element}.data.target`);
4056
+ const u = ctx.textualId(data.source, `${element}.data.source`);
4057
+ const v = ctx.textualId(data.target, `${element}.data.target`);
3903
4058
  if (u === null || v === null) {
3904
4059
  ctx.countSkipped("edge");
3905
4060
  return;
@@ -3964,6 +4119,49 @@ function cytoscapeSections(
3964
4119
  return report.fail(JSON_ISSUE.SHAPE, `elements must be an object or an array, found ${describe(elements)}`);
3965
4120
  }
3966
4121
 
4122
+ /**
4123
+ * The ids Cytoscape.js would give the nodes without a `data.id`: `n<k>` by their place among the nodes, made unique
4124
+ * against every id the file states.
4125
+ * @param nodes - the node elements
4126
+ * @returns the generated id of each node element without one
4127
+ */
4128
+ function cytoscapeGeneratedIds(nodes: readonly unknown[]): Map<object, string> {
4129
+ const out = new Map<object, string>();
4130
+ const missing = nodes.filter(
4131
+ (n): n is JsonRecord => isJsonObject(n) && isJsonObject(n.data) && n.data.id === undefined,
4132
+ );
4133
+ if (missing.length === 0) {
4134
+ return out;
4135
+ }
4136
+ const taken = new Set<string>();
4137
+ for (const n of nodes) {
4138
+ const id = isJsonObject(n) && isJsonObject(n.data) ? n.data.id : undefined;
4139
+ if (typeof id === "string" || typeof id === "number") {
4140
+ taken.add(String(id));
4141
+ }
4142
+ }
4143
+ for (let k = 0; k < missing.length; k++) {
4144
+ let id = `n${String(k)}`;
4145
+ let j = 2;
4146
+ while (taken.has(id)) {
4147
+ id = `n${String(k)}_${String(j)}`;
4148
+ j++;
4149
+ }
4150
+ taken.add(id);
4151
+ out.set(missing[k], id);
4152
+ }
4153
+ return out;
4154
+ }
4155
+
4156
+ /**
4157
+ * A `graphty.styles` block a document holds as a top-level key, or null.
4158
+ * @param root - the document
4159
+ * @returns the block's text
4160
+ */
4161
+ function blockOf(root: unknown): string | null {
4162
+ return isJsonObject(root) && typeof root[STYLE_BLOCK_NAME] === "string" ? root[STYLE_BLOCK_NAME] : null;
4163
+ }
4164
+
3967
4165
  /**
3968
4166
  * Whether a Cytoscape element's position has a numeric z.
3969
4167
  * @param element - the element
@@ -4074,6 +4272,7 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4074
4272
  "typedefs",
4075
4273
  ]),
4076
4274
  extensions: Object.freeze([".json"]),
4275
+ positions: jsonPositions,
4077
4276
  mimeTypes: Object.freeze(["application/json"]),
4078
4277
 
4079
4278
  /**
@@ -4113,7 +4312,7 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4113
4312
  const report = new ImportReportBuilder("json", resolved.errorLimit);
4114
4313
  const text = await readText(input, report, { ...resolved, bomlessUtf16: true });
4115
4314
  const { root, dialect } = documentOf(parseDocument(text, report), json, report);
4116
- readGraph(root, dialect, sink, report, resolved, json, options);
4315
+ readGraph({ root, dialect, text }, sink, report, resolved, json, options);
4117
4316
  return report.finish();
4118
4317
  },
4119
4318
 
@@ -4159,17 +4358,35 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4159
4358
  const reports: ImportReport[] = [];
4160
4359
  for (let i = 0; i < Math.max(graphs, 1); i++) {
4161
4360
  const report = i === 0 ? first : new ImportReportBuilder("json", resolved.errorLimit);
4162
- readGraph(root, dialect, sinkFor(i), report, resolved, { ...json, graphIndex: i, all: true }, options);
4361
+ readGraph(
4362
+ { root, dialect, text },
4363
+ sinkFor(i),
4364
+ report,
4365
+ resolved,
4366
+ { ...json, graphIndex: i, all: true },
4367
+ options,
4368
+ );
4163
4369
  reports.push(report.finish());
4164
4370
  }
4165
4371
  return reports;
4166
4372
  },
4373
+
4374
+ /**
4375
+ * The document's styles, read by its dialect's reader (Cytoscape.js stylesheets, graphology and vis.js keys,
4376
+ * NetworkX's defaults); a `graphty.styles` block gives back the rules graphty wrote.
4377
+ * @param snapshot - the graph the import built
4378
+ * @param context - the work budget and what the import kept
4379
+ * @returns the styles, or null when the dialect has none
4380
+ */
4381
+ styles: jsonStyles,
4167
4382
  });
4168
4383
 
4169
4384
  /**
4170
4385
  * Read one graph of a parsed document into a sink.
4171
- * @param root - the parsed document
4172
- * @param dialect - its dialect
4386
+ * @param doc - the parsed document, its dialect and its text
4387
+ * @param doc.root - the parsed document
4388
+ * @param doc.dialect - its dialect
4389
+ * @param doc.text - the document's text
4173
4390
  * @param sink - the sink
4174
4391
  * @param report - the graph's report
4175
4392
  * @param resolved - the resolved common options
@@ -4177,19 +4394,21 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4177
4394
  * @param options - the caller's options, for the sink and unused-option checks
4178
4395
  */
4179
4396
  function readGraph(
4180
- root: unknown,
4181
- dialect: JsonImportDialect,
4397
+ doc: { readonly root: unknown; readonly dialect: JsonImportDialect; readonly text: string },
4182
4398
  sink: GraphSink,
4183
4399
  report: ImportReportBuilder,
4184
4400
  resolved: ResolvedImportOptions,
4185
4401
  json: ResolvedJsonOptions,
4186
4402
  options: (JsonImportOptions & CommonImportOptions) | undefined,
4187
4403
  ): void {
4404
+ const { root, dialect, text } = doc;
4188
4405
  const ctx = new ImportContext(sink, report, resolved, json, options?.defaultDirected !== undefined);
4406
+ ctx.text = text;
4189
4407
  // the obographs reader refuses missing endpoints itself, so addMissingNodes false holds on any sink
4190
4408
  reportSinkOptions(sink, options, report, dialect === "obographs");
4191
4409
  reportUnusedOptions(options, report, USED_OPTIONS);
4192
4410
  readDialect(ctx, root, dialect);
4411
+ report.keepForStyles(ctx.styleSource ?? { dialect, block: blockOf(root) });
4193
4412
  ctx.reportDangling();
4194
4413
  ctx.nodes.reportNullOnly();
4195
4414
  ctx.edges.reportNullOnly();