@graphty/graph-io 0.3.42 → 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-CpRyHZ3H.js → exporter-quPUCBiC.js} +1807 -288
  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 +295 -414
  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 +23 -7
  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 +441 -265
  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 +30 -13
  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 +543 -323
  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-CpRyHZ3H.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
  }
@@ -1687,7 +1735,8 @@ function arraySection(value: unknown, what: string, report: ImportReportBuilder)
1687
1735
  if (!Array.isArray(value)) {
1688
1736
  return report.fail(JSON_ISSUE.SHAPE, `${what} must be an array, found ${describe(value)}`, { element: what });
1689
1737
  }
1690
- return value;
1738
+ const items: readonly unknown[] = value;
1739
+ return items;
1691
1740
  }
1692
1741
 
1693
1742
  /**
@@ -1773,10 +1822,16 @@ function reportUnreadKeys(ctx: ImportContext, record: JsonRecord, known: Readonl
1773
1822
  }
1774
1823
 
1775
1824
  /** The keys of a vis.js document the reader reads. */
1776
- const VIS_DOCUMENT_KEYS: ReadonlySet<string> = new Set(["nodes", "edges"]);
1825
+ const VIS_DOCUMENT_KEYS: ReadonlySet<string> = new Set(["nodes", "edges", STYLE_BLOCK_NAME]);
1777
1826
 
1778
1827
  /** The keys of a graphology serialization the reader reads. */
1779
- 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
+ ]);
1780
1835
 
1781
1836
  /** The keys of a JGF graph object the reader reads. */
1782
1837
  const JGF_GRAPH_KEYS: ReadonlySet<string> = new Set([
@@ -1820,10 +1875,61 @@ function isIndexBelow(value: unknown, bound: number): boolean {
1820
1875
  */
1821
1876
  function importNodeLink(ctx: ImportContext, root: JsonRecord, dialect: "node-link" | "d3"): void {
1822
1877
  const { report, json } = ctx;
1823
- let { edgesKey } = json;
1824
- if (edgesKey === null) {
1825
- 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);
1826
1899
  }
1900
+ if (indexLinks && !positional && json.indexLinks === "auto") {
1901
+ reportAmbiguousIndexLinks(ctx, nodeList, edgeList, nodeIdKey);
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;
1827
1933
  const nodes = arraySection(root.nodes, "nodes", report);
1828
1934
  const edges = arraySection(root[edgesKey], edgesKey, report);
1829
1935
  if (nodes === null) {
@@ -1843,101 +1949,100 @@ function importNodeLink(ctx: ImportContext, root: JsonRecord, dialect: "node-lin
1843
1949
  { element: edgesKey },
1844
1950
  );
1845
1951
  }
1846
- const directed = flagOf(root.directed, "directed", ctx.defaultDirected(dialect), report);
1847
- const multigraph = hasKey(root, "multigraph") ? flagOf(root.multigraph, "multigraph", false, report) : null;
1848
- ctx.setHeader(directed);
1849
- ctx.writeGraphDict(root.graph, "graph");
1850
- reportUnreadKeys(ctx, root, new Set(["nodes", edgesKey, "directed", "multigraph", "graph"]), "");
1851
-
1852
- const nodeList = nodes ?? [];
1853
- const edgeList = edges ?? [];
1854
- ctx.sink.reserve(nodeList.length, edgeList.length);
1952
+ return { nodeList: nodes ?? [], edgeList: edges ?? [] };
1953
+ }
1855
1954
 
1856
- // the id key: the caller's, else "id" when any node has it, else "name" (d3); nodes without any
1857
- // id key are positional (nodeIdFrom "index", or a d3 v3 file whose nodes carry no id at all)
1858
- let { nodeIdKey } = json;
1859
- let positional = ctx.options.nodeIdFrom === "index";
1860
- if (!positional && nodeIdKey === null) {
1861
- const candidates = ctx.options.nodeIdFrom === "label" ? ["label", "name", "id"] : ["id", "name"];
1862
- nodeIdKey = candidates.find((key) => anyHas(nodeList, key)) ?? null;
1863
- if (ctx.options.nodeIdFrom === "label" && (nodeIdKey === "id" || nodeIdKey === "name")) {
1864
- report.warning(
1865
- "unsupported",
1866
- JSON_ISSUE.OPTION_IGNORED,
1867
- nodeIdKey === "id"
1868
- ? 'nodeIdFrom "label": no node has a label or name key; ids are read from "id"'
1869
- : 'nodeIdFrom "label": no node has a "label" key; ids are read from "name"',
1870
- { element: "nodeIdFrom" },
1871
- );
1872
- }
1873
- if (nodeIdKey === null) {
1874
- if (nodeList.length > 0) {
1875
- positional = true;
1876
- report.warning(
1877
- "missing-value",
1878
- JSON_ISSUE.POSITIONAL_NODES,
1879
- "no node has an id or name key; array positions are the node ids",
1880
- { element: "nodes" },
1881
- );
1882
- } else {
1883
- nodeIdKey = "id";
1884
- }
1885
- }
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;
1886
1966
  }
1887
- if (positional) {
1888
- nodeIdKey = null;
1967
+ if (json.nodeIdKey !== null) {
1968
+ return json.nodeIdKey;
1889
1969
  }
1890
- let indexLinks: boolean;
1891
- if (positional) {
1892
- indexLinks = true;
1893
- } else if (json.indexLinks === "auto") {
1894
- indexLinks = nodeIdKey !== null && looksIndexLinked(nodeList, edgeList, nodeIdKey, json);
1895
- } else {
1896
- ({ 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
+ );
1897
1982
  }
1898
- const positionIds: (NodeId | null)[] | null = indexLinks ? [] : null;
1899
- if (indexLinks && !positional && json.indexLinks === "auto" && nodeIdKey !== null) {
1900
- reportAmbiguousIndexLinks(ctx, nodeList, edgeList, nodeIdKey);
1983
+ if (found !== null || nodeList.length === 0) {
1984
+ return found ?? "id";
1901
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
+ }
1902
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;
1903
2010
  for (let i = 0; i < nodeList.length; i++) {
1904
2011
  const element = `nodes[${i}]`;
1905
2012
  const record = nodeList[i];
1906
2013
  let pushed: NodeId | null = null;
1907
- if (!isJsonObject(record)) {
1908
- ctx.badElement("node", element);
2014
+ if (isJsonObject(record)) {
2015
+ pushed = pushNodeLinkNode(ctx, record, i, nodeIdKey);
1909
2016
  } else {
1910
- let id: NodeId | null;
1911
- if (nodeIdKey === null) {
1912
- id = i;
1913
- } else {
1914
- id = ctx.coerceId(hasKey(record, nodeIdKey) ? record[nodeIdKey] : undefined, element);
1915
- }
1916
- if (id === null) {
1917
- ctx.countSkipped("node");
1918
- } else {
1919
- const index = ctx.pushNode(id, element);
1920
- if (index >= 0) {
1921
- pushed = id;
1922
- writeFlat(ctx.nodes, index, record, id, (key) => key !== nodeIdKey);
1923
- }
1924
- }
2017
+ ctx.badElement("node", element);
1925
2018
  }
1926
2019
  positionIds?.push(pushed);
1927
2020
  }
1928
- throwIfAborted(ctx.options.signal);
1929
- const endpointKeys = importNodeLinkEdges(ctx, edgeList, edgesKey, positionIds, multigraph);
1930
- ctx.setMeta(
1931
- {
1932
- dialect,
1933
- edgesKey,
1934
- nodeIdKey,
1935
- indexLinks,
1936
- sourceKey: endpointKeys.source ?? undefined,
1937
- targetKey: endpointKeys.target ?? undefined,
1938
- },
1939
- { declaredMultigraph: multigraph, ...ctx.weightOriginPatch() },
1940
- );
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;
1941
2046
  }
1942
2047
 
1943
2048
  /**
@@ -2275,6 +2380,7 @@ function importVis(ctx: ImportContext, root: JsonRecord): void {
2275
2380
  { element: "arrows" },
2276
2381
  );
2277
2382
  }
2383
+ ctx.styleSource = { dialect: "vis", block: blockOf(root), options: root.options, groups: root.groups };
2278
2384
  ctx.setMeta(
2279
2385
  { dialect: "vis", nodeIdKey, sourceKey: sourceKey ?? undefined, targetKey: targetKey ?? undefined },
2280
2386
  ctx.weightOriginPatch(),
@@ -2681,58 +2787,34 @@ function importGraphology(ctx: ImportContext, root: JsonRecord): void {
2681
2787
  });
2682
2788
  }
2683
2789
  const type = graphologyType(ctx, options);
2684
- ctx.setHeader(graphologyDirected(ctx, type, edges));
2790
+ ctx.setHeader(type === "mixed" ? mostlyDirected(ctx, edges) : type === "directed");
2685
2791
  const multi = hasKey(options, "multi") ? flagOf(options.multi, "options.multi", false, report) : null;
2686
2792
  const allowSelfLoops = hasKey(options, "allowSelfLoops")
2687
2793
  ? flagOf(options.allowSelfLoops, "options.allowSelfLoops", true, report)
2688
2794
  : undefined;
2689
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 };
2690
2801
  ctx.reportNodeIdFrom("graphology", "the node key");
2691
2802
  ctx.sink.reserve(nodes.length, edges.length);
2692
2803
 
2693
- for (let i = 0; i < nodes.length; i++) {
2694
- pushGraphologyNode(ctx, nodes[i], `nodes[${i}]`);
2695
- }
2804
+ importGraphologyNodes(ctx, nodes, gephi);
2696
2805
  throwIfAborted(ctx.options.signal);
2697
-
2698
- const reading: GraphologyReading = {
2699
- ids: ctx.declareEdgeIds(edges, (edge) => edge.key, "key", true),
2700
- type,
2701
- allowSelfLoops,
2702
- // what the edges do against the declared options: graphology itself refuses such a document
2703
- violations: { selfLoops: 0, parallels: 0, flags: 0 },
2704
- pairs: multi === false ? new Set<string>() : null,
2705
- };
2706
- for (let i = 0; i < edges.length; i++) {
2707
- readGraphologyEdge(ctx, reading, edges[i], `edges[${i}]`);
2708
- }
2709
- reportGraphologyViolations(ctx, reading.violations, type);
2806
+ const violations = importGraphologyEdges(ctx, edges, { type, gephi, multi, allowSelfLoops });
2807
+ reportGraphologyViolations(ctx, violations, type);
2710
2808
  ctx.setMeta({ dialect: "graphology", allowSelfLoops }, { declaredMultigraph: multi, ...ctx.weightOriginPatch() });
2711
2809
  }
2712
2810
 
2713
- /** The graph types a graphology document declares in `options.type`. */
2714
- type GraphologyType = "directed" | "undirected" | "mixed";
2715
-
2716
- /** The state of a graphology read, shared by its edges. */
2717
- interface GraphologyReading {
2718
- /** The edge id column, from declareEdgeIds(). */
2719
- readonly ids: EdgeIdColumn | null;
2720
- readonly type: GraphologyType;
2721
- /** options.allowSelfLoops, or undefined when absent. */
2722
- readonly allowSelfLoops: boolean | undefined;
2723
- /** The edges that contradict the declared options (reportGraphologyViolations()). */
2724
- readonly violations: { selfLoops: number; parallels: number; flags: number };
2725
- /** The pairs seen so far under multi false, or null. */
2726
- readonly pairs: Set<string> | null;
2727
- }
2728
-
2729
2811
  /**
2730
- * 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).
2731
2813
  * @param ctx - the context
2732
- * @param options - the options object
2814
+ * @param options - the document's options
2733
2815
  * @returns the type
2734
2816
  */
2735
- function graphologyType(ctx: ImportContext, options: JsonRecord): GraphologyType {
2817
+ function graphologyType(ctx: ImportContext, options: JsonRecord): "directed" | "undirected" | "mixed" {
2736
2818
  if (options.type === "directed" || options.type === "undirected" || options.type === "mixed") {
2737
2819
  return options.type;
2738
2820
  }
@@ -2748,127 +2830,170 @@ function graphologyType(ctx: ImportContext, options: JsonRecord): GraphologyType
2748
2830
  }
2749
2831
 
2750
2832
  /**
2751
- * The header direction of a graphology document: its type, or for "mixed" directed unless every
2752
- * 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.
2753
2834
  * @param ctx - the context
2754
- * @param type - options.type
2755
2835
  * @param edges - the edge records
2756
- * @returns whether the header is directed
2836
+ * @returns true for directed
2757
2837
  */
2758
- function graphologyDirected(ctx: ImportContext, type: GraphologyType, edges: readonly unknown[]): boolean {
2759
- if (type !== "mixed") {
2760
- return type === "directed";
2761
- }
2838
+ function mostlyDirected(ctx: ImportContext, edges: readonly unknown[]): boolean {
2762
2839
  const objects = edges.filter((edge) => isJsonObject(edge)) as JsonRecord[];
2763
2840
  const undirectedEdges = objects.filter((edge) => edge.undirected === true).length;
2764
2841
  return objects.length === 0 ? ctx.defaultDirected("graphology") : undirectedEdges < objects.length;
2765
2842
  }
2766
2843
 
2767
2844
  /**
2768
- * Push one graphology node record: `key` the id, `attributes` the columns.
2845
+ * Read the graphology node records.
2769
2846
  * @param ctx - the context
2770
- * @param record - the node record
2771
- * @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)
2772
2849
  */
2773
- function pushGraphologyNode(ctx: ImportContext, record: unknown, element: string): void {
2774
- if (!isJsonObject(record)) {
2775
- ctx.badElement("node", element);
2776
- 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);
2777
2865
  }
2778
- const id = ctx.coerceId(hasKey(record, "key") ? record.key : undefined, element);
2779
- if (id === null) {
2780
- ctx.countSkipped("node");
2781
- 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
+ }
2782
2918
  }
2783
- pushNestedNode(ctx, id, record, "attributes", GRAPHOLOGY_NODE_KEYS, element);
2919
+ return violations;
2784
2920
  }
2785
2921
 
2786
2922
  /**
2787
- * 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.
2788
2924
  * @param ctx - the context
2789
- * @param reading - the read's state
2790
2925
  * @param record - the edge record
2791
2926
  * @param element - the element name
2927
+ * @returns the record, or null when it was reported and is skipped
2792
2928
  */
2793
- function readGraphologyEdge(ctx: ImportContext, reading: GraphologyReading, record: unknown, element: string): void {
2929
+ function graphologyEdgeRecord(ctx: ImportContext, record: unknown, element: string): JsonRecord | null {
2794
2930
  if (!isJsonObject(record)) {
2795
2931
  ctx.badElement("edge", element);
2796
- return;
2932
+ return null;
2797
2933
  }
2798
2934
  if (!present(record, "source") || !present(record, "target")) {
2799
2935
  ctx.missingEndpoint(element, present(record, "source") ? "target" : "source");
2800
- return;
2801
- }
2802
- try {
2803
- const u = ctx.coerceId(record.source, `${element}.source`);
2804
- const v = ctx.coerceId(record.target, `${element}.target`);
2805
- if (u === null || v === null) {
2806
- ctx.countSkipped("edge");
2807
- return;
2808
- }
2809
- const kind = graphologyEdgeKind(ctx, reading, record, element);
2810
- const attributes = graphologyEdgeAttributes(ctx, record, element);
2811
- const idValue = ctx.edgeIdValue(reading.ids, record.key);
2812
- const edge = ctx.pushEdge(u, v, kind, ctx.weightOf(attributes), element);
2813
- ctx.setEdgeId(reading.ids, edge, idValue);
2814
- ctx.writeNested(ctx.edges, edge, record, attributes, GRAPHOLOGY_EDGE_KEYS, ctx.options.weightFrom, element);
2815
- if (reading.allowSelfLoops === false && u === v) {
2816
- reading.violations.selfLoops++;
2817
- }
2818
- if (reading.pairs !== null) {
2819
- const pair = `${kind} ${pairKey(u, v, kind === "directed")}`;
2820
- reading.violations.parallels += reading.pairs.has(pair) ? 1 : 0;
2821
- reading.pairs.add(pair);
2822
- }
2823
- } catch (err) {
2824
- ctx.skip(err, "edge", element);
2936
+ return null;
2825
2937
  }
2938
+ return record;
2826
2939
  }
2827
2940
 
2828
2941
  /**
2829
- * The direction of one graphology edge: its `undirected` flag under type "mixed", else the type
2830
- * (a contrary flag is counted as a violation).
2942
+ * Add one graphology edge with its id and attributes.
2831
2943
  * @param ctx - the context
2832
- * @param reading - the read's state
2833
2944
  * @param record - the edge record
2834
2945
  * @param element - the element name
2835
- * @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
2836
2950
  */
2837
- function graphologyEdgeKind(
2951
+ function pushGraphologyEdge(
2838
2952
  ctx: ImportContext,
2839
- reading: GraphologyReading,
2840
2953
  record: JsonRecord,
2841
2954
  element: string,
2842
- ): EdgeKind {
2843
- const { type } = reading;
2844
- if (type === "mixed") {
2845
- 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;
2846
2966
  }
2847
- if (typeof record.undirected === "boolean" && record.undirected !== (type === "undirected")) {
2848
- 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
+ }
2849
2975
  }
2850
- return type;
2851
- }
2852
-
2853
- /**
2854
- * The `attributes` object of a graphology edge; any other non-null value is reported and read as
2855
- * no attributes.
2856
- * @param ctx - the context
2857
- * @param record - the edge record
2858
- * @param element - the element name
2859
- * @returns the attributes
2860
- */
2861
- function graphologyEdgeAttributes(ctx: ImportContext, record: JsonRecord, element: string): JsonRecord {
2862
- const { attributes } = record;
2863
- if (attributes !== undefined && attributes !== null && !isJsonObject(attributes)) {
2864
- ctx.report.error(
2976
+ const { attributes: rawAttributes } = record;
2977
+ if (rawAttributes !== undefined && rawAttributes !== null && !isJsonObject(rawAttributes)) {
2978
+ report.error(
2865
2979
  "validation-error",
2866
2980
  JSON_ISSUE.BAD_VALUE,
2867
- `${element}: attributes must be an object, found ${describe(attributes)}`,
2981
+ `${element}: attributes must be an object, found ${describe(rawAttributes)}`,
2868
2982
  { element },
2869
2983
  );
2870
2984
  }
2871
- 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")}`;
2872
2997
  }
2873
2998
 
2874
2999
  /**
@@ -2918,6 +3043,7 @@ function reportGraphologyViolations(
2918
3043
  * @param dictKey - the key of the nested dict
2919
3044
  * @param structural - the element keys that are not attributes
2920
3045
  * @param element - the element name
3046
+ * @param plain - write the element-level keys under their own names
2921
3047
  * @returns the node index, or -1 when skipped
2922
3048
  */
2923
3049
  function pushNestedNode(
@@ -2927,6 +3053,7 @@ function pushNestedNode(
2927
3053
  dictKey: string,
2928
3054
  structural: ReadonlySet<string>,
2929
3055
  element: string,
3056
+ plain = false,
2930
3057
  ): number {
2931
3058
  const index = ctx.pushNode(id, element);
2932
3059
  if (index < 0) {
@@ -2942,7 +3069,7 @@ function pushNestedNode(
2942
3069
  { element },
2943
3070
  );
2944
3071
  }
2945
- 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 });
2946
3073
  } catch (err) {
2947
3074
  ctx.report.recordError(err, { element: String(id) });
2948
3075
  }
@@ -2989,7 +3116,7 @@ function importJgf(ctx: ImportContext, root: JsonRecord): void {
2989
3116
  anyHas(nodeRecords, "label"),
2990
3117
  );
2991
3118
  if (isJsonObject(nodesRaw)) {
2992
- for (const key of Object.keys(nodesRaw)) {
3119
+ for (const key of jgfNodeKeys(ctx, root, graph, nodesRaw)) {
2993
3120
  pushJgfKeyedNode(ctx, key, nodesRaw[key], labelColumn);
2994
3121
  }
2995
3122
  } else {
@@ -3126,7 +3253,7 @@ function jgfEdgeWriter(ctx: ImportContext, edges: readonly unknown[], hyperedges
3126
3253
  element,
3127
3254
  });
3128
3255
  }
3129
- 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 });
3130
3257
  };
3131
3258
  }
3132
3259
 
@@ -3167,6 +3294,26 @@ function readJgfEdge(
3167
3294
  }
3168
3295
  }
3169
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
+
3170
3317
  /**
3171
3318
  * The graph object of a JGF document: `graph`, or the graph of `graphs` that graphIndex /
3172
3319
  * graphName choose (chooseGraph()).
@@ -3344,7 +3491,7 @@ function pushJgfNode(
3344
3491
  element,
3345
3492
  });
3346
3493
  }
3347
- 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);
3348
3495
  } catch (err) {
3349
3496
  ctx.report.recordError(err, { element: String(id) });
3350
3497
  }
@@ -3561,69 +3708,63 @@ function distinctMembers(ctx: ImportContext, members: readonly NodeId[], element
3561
3708
  function importCytoscape(ctx: ImportContext, root: unknown): void {
3562
3709
  const { elements, extra } = cytoscapeRoot(ctx, root);
3563
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
+ };
3564
3720
  ctx.setHeader(ctx.defaultDirected("cytoscape"));
3565
3721
  ctx.reportNodeIdFrom("cytoscape", "data.id");
3566
3722
  ctx.sink.reserve(nodes.length, edges.length);
3567
3723
 
3568
- const dataHas = (items: readonly unknown[], key: string): boolean =>
3569
- items.some((item) => isJsonObject(item) && isJsonObject(item.data) && item.data[key] !== undefined);
3570
- const reading: CytoscapeNodeReading = {
3571
- positionColumn: ctx.nodes.declareIf(
3572
- {
3573
- name: POSITION_COLUMN,
3574
- dtype: "f64",
3575
- components: 3,
3576
- role: "position",
3577
- mutable: true,
3578
- nullable: true,
3579
- // 3D layouts written by Cytoscape-compatible tools carry a z
3580
- extra: { sourceDims: nodes.some(hasPositionZ) ? 3 : 2, units: "file" },
3581
- origin: { format: "json", namespace: "cytoscape" },
3582
- },
3583
- anyHas(nodes, "position"),
3584
- ),
3585
- classesColumn: ctx.nodes.declareIf(
3586
- { name: CLASSES_COLUMN, dtype: "list", itemDtype: "string", role: "classes", nullable: true },
3587
- anyHas(nodes, "classes"),
3588
- ),
3589
- parents: [],
3590
- point: [0, 0, 0],
3591
- };
3592
- const parentColumn = ctx.nodes.declareIf(
3593
- { name: PARENT_COLUMN, dtype: "u32", role: "parent", refersTo: "node", nullable: true },
3594
- dataHas(nodes, "parent"),
3595
- );
3724
+ const columns = cytoscapeNodeColumns(ctx, nodes);
3596
3725
  const edgeClassesColumn = ctx.edges.declareIf(
3597
3726
  { name: CLASSES_COLUMN, dtype: "list", itemDtype: "string", role: "classes", nullable: true },
3598
3727
  anyHas(edges, "classes"),
3599
3728
  );
3600
-
3729
+ const parents: CytoscapeParentRef[] = [];
3601
3730
  for (let i = 0; i < nodes.length; i++) {
3602
- 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
+ }
3603
3740
  }
3604
- resolveCytoscapeParents(ctx, reading.parents, parentColumn);
3741
+ resolveCytoscapeParents(ctx, parents, columns.parent);
3605
3742
  throwIfAborted(ctx.options.signal);
3606
3743
 
3607
3744
  importCytoscapeEdges(ctx, edges, edgeClassesColumn);
3608
3745
  ctx.setMeta({ dialect: "cytoscape", cytoscape: extra }, ctx.weightOriginPatch());
3609
3746
  }
3610
3747
 
3611
- /** The state of a read of Cytoscape node elements. */
3612
- interface CytoscapeNodeReading {
3613
- readonly positionColumn: ColumnHandle;
3614
- readonly classesColumn: ColumnHandle;
3615
- /** The parent links, resolved once every node is known. */
3616
- readonly parents: { index: number; parent: NodeId; element: string }[];
3617
- /** A scratch position. */
3618
- 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;
3619
3760
  }
3620
3761
 
3621
3762
  /**
3622
- * The elements of a Cytoscape document (the document itself when it is an array) and, for an
3623
- * 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.
3624
3765
  * @param ctx - the context
3625
- * @param root - the document
3626
- * @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)
3627
3768
  */
3628
3769
  function cytoscapeRoot(ctx: ImportContext, root: unknown): { elements: unknown; extra?: Record<string, unknown> } {
3629
3770
  if (Array.isArray(root)) {
@@ -3635,32 +3776,73 @@ function cytoscapeRoot(ctx: ImportContext, root: unknown): { elements: unknown;
3635
3776
  `a Cytoscape document must be an object or an array, found ${describe(root)}`,
3636
3777
  );
3637
3778
  }
3638
- ctx.writeGraphDict(root.data, "data");
3779
+ const object = root as JsonRecord;
3780
+ ctx.writeGraphDict(object.data, "data");
3639
3781
  const rest: Record<string, unknown> = {};
3640
- for (const key of Object.keys(root)) {
3641
- if (key !== "elements" && key !== "data") {
3642
- 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];
3643
3785
  }
3644
3786
  }
3645
- 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 };
3646
3788
  }
3647
3789
 
3648
3790
  /**
3649
- * Read one Cytoscape node element: `data.id` the id, the other `data` keys attributes (`parent`
3650
- * 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.
3651
3827
  * @param ctx - the context
3652
- * @param reading - the read's state
3653
3828
  * @param record - the element
3829
+ * @param data - its data object
3830
+ * @param raw - its raw id
3654
3831
  * @param element - the element name
3832
+ * @param columns - the node columns
3833
+ * @param parents - receives its parent link
3655
3834
  */
3656
- 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 {
3657
3844
  const { report } = ctx;
3658
- if (!isJsonObject(record) || !isJsonObject(record.data)) {
3659
- ctx.badElement("node", element, "an element with a data object");
3660
- return;
3661
- }
3662
- const { data } = record;
3663
- const id = ctx.coerceId(hasKey(data, "id") ? data.id : undefined, element);
3845
+ const id = ctx.coerceId(raw, element);
3664
3846
  if (id === null) {
3665
3847
  ctx.countSkipped("node");
3666
3848
  return;
@@ -3678,9 +3860,18 @@ function readCytoscapeNode(ctx: ImportContext, reading: CytoscapeNodeReading, re
3678
3860
  );
3679
3861
  }
3680
3862
  try {
3681
- writeCytoscapeData(ctx, reading, index, data, element);
3682
- writeCytoscapePosition(ctx, reading, index, record.position, element);
3683
- 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);
3684
3875
  writeElementKeys(ctx.nodes, index, record, element);
3685
3876
  } catch (err) {
3686
3877
  report.recordError(err, { element: String(id) });
@@ -3688,51 +3879,17 @@ function readCytoscapeNode(ctx: ImportContext, reading: CytoscapeNodeReading, re
3688
3879
  }
3689
3880
 
3690
3881
  /**
3691
- * Write a Cytoscape node's `data` keys as attributes; `parent` is kept as a link to resolve later.
3692
- * @param ctx - the context
3693
- * @param reading - the read's state
3694
- * @param index - the node index
3695
- * @param data - the element's data object
3696
- * @param element - the element name
3697
- */
3698
- function writeCytoscapeData(
3699
- ctx: ImportContext,
3700
- reading: CytoscapeNodeReading,
3701
- index: number,
3702
- data: JsonRecord,
3703
- element: string,
3704
- ): void {
3705
- for (const key of Object.keys(data)) {
3706
- if (key === "id") {
3707
- continue;
3708
- }
3709
- if (key !== "parent") {
3710
- ctx.nodes.write(index, key, data[key], SUFFIX.data, element);
3711
- continue;
3712
- }
3713
- const raw = data.parent;
3714
- if (raw === undefined || raw === null) {
3715
- continue;
3716
- }
3717
- const parent = ctx.coerceId(raw, `${element}.data.parent`);
3718
- if (parent !== null) {
3719
- reading.parents.push({ index, parent, element });
3720
- }
3721
- }
3722
- }
3723
-
3724
- /**
3725
- * Write a Cytoscape node's `position` ({ x, y, z? }, y flipped to grow upward); any other value is
3726
- * 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).
3727
3884
  * @param ctx - the context
3728
- * @param reading - the read's state
3729
- * @param index - the node index
3885
+ * @param column - the position column
3886
+ * @param index - the node
3730
3887
  * @param position - the element's position
3731
3888
  * @param element - the element name
3732
3889
  */
3733
3890
  function writeCytoscapePosition(
3734
3891
  ctx: ImportContext,
3735
- reading: CytoscapeNodeReading,
3892
+ column: ColumnHandle,
3736
3893
  index: number,
3737
3894
  position: unknown,
3738
3895
  element: string,
@@ -3742,12 +3899,7 @@ function writeCytoscapePosition(
3742
3899
  }
3743
3900
  const z = isJsonObject(position) ? (position.z ?? 0) : 0;
3744
3901
  if (isJsonObject(position) && isCoordinate(position.x) && isCoordinate(position.y) && isCoordinate(z)) {
3745
- const { point } = reading;
3746
- point[0] = position.x;
3747
- // Cytoscape's y grows downward; positions are stored y-up (the exporter flips back)
3748
- point[1] = flipY(position.y);
3749
- point[2] = z;
3750
- ctx.nodes.set(reading.positionColumn, index, point);
3902
+ ctx.nodes.set(column, index, [position.x, flipY(position.y), z]);
3751
3903
  } else {
3752
3904
  ctx.report.error(
3753
3905
  "validation-error",
@@ -3783,7 +3935,11 @@ function resolveCytoscapeParents(
3783
3935
  // the last link per child (a duplicate node's later parent wins), child -> its link
3784
3936
  const linkOf = new Map<number, ParentLink>();
3785
3937
  for (let order = 0; order < parents.length; order++) {
3786
- 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
+ }
3787
3943
  const parentIndex = ctx.sink.indexOf(parent);
3788
3944
  if (parentIndex === INVALID_INDEX) {
3789
3945
  report.error(
@@ -3897,8 +4053,8 @@ function importCytoscapeEdge(
3897
4053
  ): void {
3898
4054
  const { ids, kind, edgeClassesColumn } = shared;
3899
4055
  try {
3900
- const u = ctx.coerceId(data.source, `${element}.data.source`);
3901
- 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`);
3902
4058
  if (u === null || v === null) {
3903
4059
  ctx.countSkipped("edge");
3904
4060
  return;
@@ -3963,6 +4119,49 @@ function cytoscapeSections(
3963
4119
  return report.fail(JSON_ISSUE.SHAPE, `elements must be an object or an array, found ${describe(elements)}`);
3964
4120
  }
3965
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
+
3966
4165
  /**
3967
4166
  * Whether a Cytoscape element's position has a numeric z.
3968
4167
  * @param element - the element
@@ -4073,6 +4272,7 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4073
4272
  "typedefs",
4074
4273
  ]),
4075
4274
  extensions: Object.freeze([".json"]),
4275
+ positions: jsonPositions,
4076
4276
  mimeTypes: Object.freeze(["application/json"]),
4077
4277
 
4078
4278
  /**
@@ -4112,7 +4312,7 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4112
4312
  const report = new ImportReportBuilder("json", resolved.errorLimit);
4113
4313
  const text = await readText(input, report, { ...resolved, bomlessUtf16: true });
4114
4314
  const { root, dialect } = documentOf(parseDocument(text, report), json, report);
4115
- readGraph(root, dialect, sink, report, resolved, json, options);
4315
+ readGraph({ root, dialect, text }, sink, report, resolved, json, options);
4116
4316
  return report.finish();
4117
4317
  },
4118
4318
 
@@ -4158,17 +4358,35 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4158
4358
  const reports: ImportReport[] = [];
4159
4359
  for (let i = 0; i < Math.max(graphs, 1); i++) {
4160
4360
  const report = i === 0 ? first : new ImportReportBuilder("json", resolved.errorLimit);
4161
- 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
+ );
4162
4369
  reports.push(report.finish());
4163
4370
  }
4164
4371
  return reports;
4165
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,
4166
4382
  });
4167
4383
 
4168
4384
  /**
4169
4385
  * Read one graph of a parsed document into a sink.
4170
- * @param root - the parsed document
4171
- * @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
4172
4390
  * @param sink - the sink
4173
4391
  * @param report - the graph's report
4174
4392
  * @param resolved - the resolved common options
@@ -4176,19 +4394,21 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
4176
4394
  * @param options - the caller's options, for the sink and unused-option checks
4177
4395
  */
4178
4396
  function readGraph(
4179
- root: unknown,
4180
- dialect: JsonImportDialect,
4397
+ doc: { readonly root: unknown; readonly dialect: JsonImportDialect; readonly text: string },
4181
4398
  sink: GraphSink,
4182
4399
  report: ImportReportBuilder,
4183
4400
  resolved: ResolvedImportOptions,
4184
4401
  json: ResolvedJsonOptions,
4185
4402
  options: (JsonImportOptions & CommonImportOptions) | undefined,
4186
4403
  ): void {
4404
+ const { root, dialect, text } = doc;
4187
4405
  const ctx = new ImportContext(sink, report, resolved, json, options?.defaultDirected !== undefined);
4406
+ ctx.text = text;
4188
4407
  // the obographs reader refuses missing endpoints itself, so addMissingNodes false holds on any sink
4189
4408
  reportSinkOptions(sink, options, report, dialect === "obographs");
4190
4409
  reportUnusedOptions(options, report, USED_OPTIONS);
4191
4410
  readDialect(ctx, root, dialect);
4411
+ report.keepForStyles(ctx.styleSource ?? { dialect, block: blockOf(root) });
4192
4412
  ctx.reportDangling();
4193
4413
  ctx.nodes.reportNullOnly();
4194
4414
  ctx.edges.reportNullOnly();