@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
@@ -0,0 +1,1138 @@
1
+ /**
2
+ * The `graphty.styles` block: the rules of a graph's styles written into a file so that importing the file back into
3
+ * graphty rebuilds the same rules. The block is JSON; the per-element values its rules read are written beside it as
4
+ * element attributes named `graphty.style.<target>.<key>` (or, in a format with no per-element attributes, inside
5
+ * the block). A block is file input like any other: it is size-limited, validated member by member, never keyed into
6
+ * a plain object, and a block that fails any check is ignored with a reason code, never thrown.
7
+ */
8
+
9
+ import type { GraphSnapshot } from "@graphty/graph-format";
10
+
11
+ import type { LossNote } from "../../types.js";
12
+ import { STYLE_ISSUE } from "../codes.js";
13
+ import { toolDefaults } from "../defaults.js";
14
+ import { featuresOf, heldEdgeRows, styleFingerprint } from "../plan/features.js";
15
+ import { plainCell, refSource, resolveRules, within } from "../plan/resolve.js";
16
+ import type {
17
+ ColumnRef,
18
+ ContinuousPoint,
19
+ ExtractedStyles,
20
+ FileStyles,
21
+ Mapping,
22
+ Predicate,
23
+ Scalar,
24
+ StyleBlockIgnoredReason,
25
+ StyleColumn,
26
+ StyleFingerprint,
27
+ StyleNote,
28
+ StyleRule,
29
+ StyleRuleFacts,
30
+ StyleTarget,
31
+ StyleTool,
32
+ StyleValue,
33
+ } from "../types.js";
34
+ import { STYLE_PROPERTIES } from "../vocabulary.js";
35
+
36
+ /**
37
+ * The name of the block in every format: a graph attribute, a key, an aspect or an archive entry.
38
+ * @category Styles
39
+ */
40
+ export const STYLE_BLOCK_NAME = "graphty.styles";
41
+
42
+ /**
43
+ * The longest block graph-io reads, in characters; a longer one is ignored (`TOO_LARGE`).
44
+ * @category Styles
45
+ */
46
+ export const MAX_STYLE_BLOCK_LENGTH = 16 * 1024 * 1024;
47
+
48
+ /** The deepest nesting of JSON values a block may have. */
49
+ const MAX_DEPTH = 64;
50
+ /** The most rules a block may hold. */
51
+ const MAX_RULES = 4096;
52
+ /** The longest id, key, property name or source field. */
53
+ const MAX_NAME = 256;
54
+ /** Keys that would reach an object's prototype; a block holding one anywhere is ignored. */
55
+ const FORBIDDEN_KEYS: ReadonlySet<string> = new Set(["__proto__", "constructor", "prototype"]);
56
+ const TARGETS: readonly StyleTarget[] = ["node", "edge"];
57
+ const COMPUTED: ReadonlySet<string> = new Set(["degree", "indegree", "outdegree"]);
58
+
59
+ /**
60
+ * The name of the element attribute holding one style column: `graphty.style.<target>.<key>`.
61
+ * @param target - nodes or edges
62
+ * @param key - the style column's key (`class`, `member.3`, `exceptions.fill`)
63
+ * @returns the attribute name
64
+ * @category Styles
65
+ */
66
+ export function styleTag(target: StyleTarget, key: string): string {
67
+ return `graphty.style.${target}.${key}`;
68
+ }
69
+
70
+ /**
71
+ * The content of a `graphty.styles` block.
72
+ * @category Styles
73
+ */
74
+ export interface StyleBlock {
75
+ /** The block's version: 1. */
76
+ readonly version: 1;
77
+ /** The format, dialect and tool the styles were described with. */
78
+ readonly source: FileStyles["source"];
79
+ /** The unit of every length. */
80
+ readonly units: FileStyles["units"];
81
+ /** The graph's own style. */
82
+ readonly graph: FileStyles["graph"];
83
+ /** The rules, bottom to top, with their `ext` members. */
84
+ readonly rules: readonly StyleRule[];
85
+ /** The keys of the style columns the rules read, per target. */
86
+ readonly columns: {
87
+ /** Node style column keys. */
88
+ readonly node: readonly string[];
89
+ /** Edge style column keys. */
90
+ readonly edge: readonly string[];
91
+ };
92
+ /**
93
+ * The text of each node's id, in the order the inline style columns list the nodes, so they are matched to a graph
94
+ * read back in another order; absent without inline columns.
95
+ */
96
+ readonly order?: readonly string[];
97
+ /**
98
+ * The keys of the style columns that hold booleans, per target, so a format that has no booleans (GML writes 0
99
+ * and 1) reads them back as booleans; absent when none does.
100
+ */
101
+ readonly booleans?: {
102
+ /** Node style column keys. */
103
+ readonly node: readonly string[];
104
+ /** Edge style column keys. */
105
+ readonly edge: readonly string[];
106
+ };
107
+ /** The style columns themselves, for a format with no per-element attributes; absent otherwise. */
108
+ readonly values?: {
109
+ /** Node style columns by key. */
110
+ readonly node: ReadonlyMap<string, StyleColumn>;
111
+ /** Edge style columns by key. */
112
+ readonly edge: ReadonlyMap<string, StyleColumn>;
113
+ };
114
+ /**
115
+ * A hash of the values of every data column the rules read, per target, by the column's name when the block was
116
+ * written; absent in a block written without its graph. A column the file reads back under another name (a
117
+ * format writes its label column as its own `label`) is found again by its values.
118
+ */
119
+ readonly data?: {
120
+ /** Node data column hashes by column name. */
121
+ readonly node: ReadonlyMap<string, string>;
122
+ /** Edge data column hashes by column name. */
123
+ readonly edge: ReadonlyMap<string, string>;
124
+ };
125
+ /** The graph the block describes. */
126
+ readonly fingerprint: StyleFingerprint;
127
+ }
128
+
129
+ /**
130
+ * Options of `writeStyleBlock()`.
131
+ * @category Styles
132
+ */
133
+ export interface WriteStyleBlockOptions {
134
+ /** Write the style columns inside the block, for a format with no per-element attributes; defaults to false. */
135
+ readonly inlineColumns?: boolean;
136
+ /**
137
+ * The graph being written: with it, the block records a hash of each data column the rules read, so the rules
138
+ * still find their columns in a file that renames them.
139
+ */
140
+ readonly snapshot?: GraphSnapshot;
141
+ }
142
+
143
+ /** The JSON escapes a reader of Cytoscape's convention would change, as `\u` escapes that mean the same. */
144
+ const SAFE_ESCAPES: Readonly<Record<string, string>> = {
145
+ "\\": String.raw`\u005c`,
146
+ n: String.raw`\u000a`,
147
+ t: String.raw`\u0009`,
148
+ };
149
+
150
+ /**
151
+ * Writes a graph's styles as a `graphty.styles` block. Notes are not written. The output is deterministic: the same
152
+ * styles always give the same text. A backslash, newline or tab inside a string is written as a `\u` escape, so a
153
+ * format that decodes `\n` and `\t` in attribute values (Cytoscape's convention) reads the block back unchanged.
154
+ * @param extracted - the styles
155
+ * @param options - whether to write the style columns inside the block
156
+ * @returns the block's JSON text
157
+ * @category Styles
158
+ */
159
+ export function writeStyleBlock(extracted: ExtractedStyles, options: WriteStyleBlockOptions = {}): string {
160
+ const { styles, columns, fingerprint } = extracted;
161
+ const out: Record<string, unknown> = {
162
+ version: 1,
163
+ source: styles.source,
164
+ units: styles.units,
165
+ graph: styles.graph,
166
+ rules: styles.rules,
167
+ columns: { node: Array.from(columns.node.keys()), edge: Array.from(columns.edge.keys()) },
168
+ };
169
+ const booleanKeys = (byKey: ReadonlyMap<string, StyleColumn>): string[] =>
170
+ Array.from(byKey)
171
+ .filter(([, col]) => col.some((x) => x !== null) && col.every((x) => x === null || typeof x === "boolean"))
172
+ .map(([key]) => key);
173
+ const booleans = { node: booleanKeys(columns.node), edge: booleanKeys(columns.edge) };
174
+ if (booleans.node.length > 0 || booleans.edge.length > 0) {
175
+ out.booleans = booleans;
176
+ }
177
+ if (options.inlineColumns === true) {
178
+ if (options.snapshot !== undefined) {
179
+ const { ids, nodeCount } = options.snapshot;
180
+ out.order = Array.from({ length: nodeCount }, (_unused, i) => String(ids.idOf(i)));
181
+ }
182
+ // an edge's values are listed once per edge the file holds
183
+ const held = options.snapshot === undefined ? null : heldEdgeRows(options.snapshot);
184
+ out.values = {
185
+ node: Object.fromEntries(columns.node),
186
+ edge: Object.fromEntries(
187
+ Array.from(columns.edge, ([key, col]) => [key, held === null ? col : held.map((e) => col[e] ?? null)]),
188
+ ),
189
+ };
190
+ }
191
+ const { snapshot } = options;
192
+ if (snapshot !== undefined) {
193
+ const hashes = (target: StyleTarget): Record<string, string> => {
194
+ const table = target === "node" ? snapshot.nodes : snapshot.edges;
195
+ const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
196
+ // a name the reader refuses as a key is left out: its rules then find the column by its name alone
197
+ const read = dataNames(styles.rules.filter((r) => r.target === target))
198
+ .filter((c) => table.has(c) && !FORBIDDEN_KEYS.has(c) && c.length <= MAX_NAME)
199
+ .slice(0, MAX_RULES);
200
+ return Object.fromEntries(read.map((c) => [c, columnHash(table, c, n)]));
201
+ };
202
+ out.data = { node: hashes("node"), edge: hashes("edge") };
203
+ }
204
+ out.fingerprint = {
205
+ nodes: fingerprint.nodes,
206
+ edges: fingerprint.heldEdges ?? fingerprint.edges,
207
+ ids: fingerprint.ids,
208
+ };
209
+ return JSON.stringify(out).replaceAll(/\\(.)/g, (escape, c: string) => SAFE_ESCAPES[c] ?? escape);
210
+ }
211
+
212
+ /**
213
+ * The loss when graph-io would ignore a block it has just written: one longer than `MAX_STYLE_BLOCK_LENGTH`, or one
214
+ * past another limit the reader enforces (too many rules, a name too long). The format's own styles are still
215
+ * written, so importing the file back infers the rules again from them.
216
+ * @param text - the block's text, as `writeStyleBlock()` wrote it
217
+ * @param rules - how many rules the block holds
218
+ * @param check - "size" to check only the length: a block rewritten with its columns inline, whose rules were
219
+ * checked already
220
+ * @returns one `W_STYLE_RULES_NOT_KEPT` note naming the reason, or none when the block reads back
221
+ * @category Styles
222
+ */
223
+ export function styleBlockLosses(text: string, rules: number, check: "all" | "size" = "all"): LossNote[] {
224
+ let reason: StyleBlockIgnoredReason | null = null;
225
+ if (text.length > MAX_STYLE_BLOCK_LENGTH) {
226
+ reason = "TOO_LARGE";
227
+ } else if (check === "all") {
228
+ const top = JSON.parse(text) as { fingerprint: { nodes: number; edges: number; ids: string } };
229
+ const { nodes, edges, ids } = top.fingerprint;
230
+ const parsed = parseStyleBlock(text, { nodes, edges, ids });
231
+ reason = parsed.ok ? null : parsed.reason;
232
+ }
233
+ if (reason === null) {
234
+ return [];
235
+ }
236
+ return [
237
+ Object.freeze({
238
+ code: STYLE_ISSUE.RULES_NOT_KEPT,
239
+ message: `the ${STYLE_BLOCK_NAME} block would not read back (${reason}): the styles are written as the format's own, and importing the file infers the rules again`,
240
+ column: null,
241
+ count: rules,
242
+ }),
243
+ ];
244
+ }
245
+
246
+ /**
247
+ * What `parseStyleBlock()` found: a valid block, or the reason it is ignored.
248
+ * @category Styles
249
+ */
250
+ export type ParsedStyleBlock =
251
+ | {
252
+ /** The block is valid and describes this graph. */
253
+ readonly ok: true;
254
+ /** The block. */
255
+ readonly block: StyleBlock;
256
+ }
257
+ | {
258
+ /** The block is ignored. */
259
+ readonly ok: false;
260
+ /** Why. */
261
+ readonly reason: StyleBlockIgnoredReason;
262
+ };
263
+
264
+ /** Thrown inside the validator only; turned into an `INVALID` result before parseStyleBlock() returns. */
265
+ class Invalid extends Error {}
266
+
267
+ /**
268
+ * Fails validation: throws `Invalid`, which parseStyleBlock() turns into an `INVALID` result.
269
+ * @throws Invalid always
270
+ */
271
+ function invalid(): never {
272
+ throw new Invalid();
273
+ }
274
+
275
+ /**
276
+ * Reads a `graphty.styles` block as untrusted input. It never throws: a block that is not text, too large, not JSON,
277
+ * of another version, nested too deep, holding a key that reaches a prototype (`__proto__`), shaped wrongly or
278
+ * written for a different graph is ignored with the reason.
279
+ * @param text - the block's text, as the file holds it
280
+ * @param fingerprint - the graph being imported (`styleFingerprint(snapshot)`)
281
+ * @returns the block, or why it is ignored
282
+ * @category Styles
283
+ */
284
+ export function parseStyleBlock(text: unknown, fingerprint: StyleFingerprint): ParsedStyleBlock {
285
+ if (typeof text !== "string") {
286
+ return { ok: false, reason: "INVALID" };
287
+ }
288
+ if (text.length > MAX_STYLE_BLOCK_LENGTH) {
289
+ return { ok: false, reason: "TOO_LARGE" };
290
+ }
291
+ let json: unknown;
292
+ try {
293
+ json = JSON.parse(text);
294
+ } catch {
295
+ return { ok: false, reason: "NOT_JSON" };
296
+ }
297
+ if (!safeTree(json)) {
298
+ return { ok: false, reason: "INVALID" };
299
+ }
300
+ const top = isObject(json) ? json : null;
301
+ if (top === null) {
302
+ return { ok: false, reason: "INVALID" };
303
+ }
304
+ if (top.version !== 1) {
305
+ return { ok: false, reason: "VERSION" };
306
+ }
307
+ // a block for another graph says so, whatever else in it would not fit this one
308
+ const fp = isObject(top.fingerprint) ? top.fingerprint : null;
309
+ if (
310
+ fp !== null &&
311
+ (fp.nodes !== fingerprint.nodes ||
312
+ fp.edges !== (fingerprint.heldEdges ?? fingerprint.edges) ||
313
+ fp.ids !== fingerprint.ids)
314
+ ) {
315
+ return { ok: false, reason: "FINGERPRINT" };
316
+ }
317
+ let block: StyleBlock;
318
+ try {
319
+ block = readBlock(top);
320
+ } catch (err) {
321
+ if (err instanceof Invalid) {
322
+ return { ok: false, reason: "INVALID" };
323
+ }
324
+ throw err;
325
+ }
326
+ const f = block.fingerprint;
327
+ if (
328
+ f.nodes !== fingerprint.nodes ||
329
+ f.edges !== (fingerprint.heldEdges ?? fingerprint.edges) ||
330
+ f.ids !== fingerprint.ids
331
+ ) {
332
+ return { ok: false, reason: "FINGERPRINT" };
333
+ }
334
+ return { ok: true, block };
335
+ }
336
+
337
+ /**
338
+ * Whether a parsed JSON value is at most `MAX_DEPTH` deep and holds no key that reaches a prototype. Walked with an
339
+ * explicit stack, never by recursion.
340
+ * @param root - the parsed value
341
+ * @returns true when safe
342
+ */
343
+ function safeTree(root: unknown): boolean {
344
+ const stack: [unknown, number][] = [[root, 0]];
345
+ while (stack.length > 0) {
346
+ const [v, depth] = stack.pop() as [unknown, number];
347
+ if (v === null || typeof v !== "object") {
348
+ continue;
349
+ }
350
+ if (depth >= MAX_DEPTH || (!Array.isArray(v) && Object.keys(v).some((key) => FORBIDDEN_KEYS.has(key)))) {
351
+ return false;
352
+ }
353
+ const items: unknown[] = Array.isArray(v) ? v : Object.values(v);
354
+ for (const item of items) {
355
+ if (item !== null && typeof item === "object") {
356
+ stack.push([item, depth + 1]);
357
+ }
358
+ }
359
+ }
360
+ return true;
361
+ }
362
+
363
+ /**
364
+ * Whether a value is a plain JSON object (not an array, not null).
365
+ * @param v - the value
366
+ * @returns true for an object
367
+ */
368
+ function isObject(v: unknown): v is Record<string, unknown> {
369
+ return v !== null && typeof v === "object" && !Array.isArray(v);
370
+ }
371
+
372
+ /**
373
+ * A short string, or invalid.
374
+ * @param v - the value
375
+ * @returns the string
376
+ */
377
+ function name(v: unknown): string {
378
+ return typeof v === "string" && v.length <= MAX_NAME ? v : invalid();
379
+ }
380
+
381
+ /**
382
+ * A short string or null, or invalid.
383
+ * @param v - the value
384
+ * @returns the string or null
385
+ */
386
+ function nameOrNull(v: unknown): string | null {
387
+ return v === null || v === undefined ? null : name(v);
388
+ }
389
+
390
+ /**
391
+ * Whether a value is a scalar: a string, a finite number or a boolean.
392
+ * @param v - the value
393
+ * @returns true for a scalar
394
+ */
395
+ function isScalar(v: unknown): v is Scalar {
396
+ return typeof v === "string" || typeof v === "boolean" || (typeof v === "number" && Number.isFinite(v));
397
+ }
398
+
399
+ /**
400
+ * A scalar, or invalid.
401
+ * @param v - the value
402
+ * @returns the scalar
403
+ */
404
+ function scalar(v: unknown): Scalar {
405
+ return isScalar(v) ? v : invalid();
406
+ }
407
+
408
+ /**
409
+ * An array, or invalid.
410
+ * @param v - the value
411
+ * @param max - the most items allowed
412
+ * @returns the array
413
+ */
414
+ function array(v: unknown, max: number): readonly unknown[] {
415
+ return Array.isArray(v) && v.length <= max ? v : invalid();
416
+ }
417
+
418
+ /**
419
+ * The whole block.
420
+ * @param top - the parsed object
421
+ * @returns the block
422
+ */
423
+ function readBlock(top: Record<string, unknown>): StyleBlock {
424
+ const source = isObject(top.source) ? top.source : invalid();
425
+ const units = isObject(top.units) ? top.units : invalid();
426
+ const length = units.length === "px" || units.length === "relative" ? units.length : invalid();
427
+ const fp = isObject(top.fingerprint) ? top.fingerprint : invalid();
428
+ const count = (v: unknown): number => (typeof v === "number" && Number.isSafeInteger(v) && v >= 0 ? v : invalid());
429
+ const cols = isObject(top.columns) ? top.columns : invalid();
430
+ const block: StyleBlock = {
431
+ version: 1,
432
+ source: { format: name(source.format), dialect: nameOrNull(source.dialect), tool: nameOrNull(source.tool) },
433
+ units: { length },
434
+ graph: values(top.graph ?? {}),
435
+ rules: array(top.rules, MAX_RULES).map(readRule),
436
+ columns: {
437
+ node: array(cols.node, MAX_RULES).map(name),
438
+ edge: array(cols.edge, MAX_RULES).map(name),
439
+ },
440
+ fingerprint: { nodes: count(fp.nodes), edges: count(fp.edges), ids: name(fp.ids) },
441
+ };
442
+ const read = top.data === undefined ? block : { ...block, data: readData(top.data) };
443
+ const withData = top.booleans === undefined ? read : { ...read, booleans: readBooleans(top.booleans) };
444
+ if (top.values === undefined) {
445
+ return withData;
446
+ }
447
+ const order = top.order === undefined ? undefined : readOrder(top.order, block.fingerprint.nodes);
448
+ const inline = readInline(top.values, block);
449
+ return { ...withData, ...(order === undefined ? {} : { order }), values: inline };
450
+ }
451
+
452
+ /**
453
+ * The boolean style columns of a block, by target.
454
+ * @param v - the parsed `booleans` member
455
+ * @returns the column keys
456
+ */
457
+ function readBooleans(v: unknown): NonNullable<StyleBlock["booleans"]> {
458
+ const booleans = isObject(v) ? v : invalid();
459
+ return {
460
+ node: array(booleans.node ?? [], MAX_RULES).map(name),
461
+ edge: array(booleans.edge ?? [], MAX_RULES).map(name),
462
+ };
463
+ }
464
+
465
+ /**
466
+ * The node ids of a block, in the order its inline values list the nodes.
467
+ * @param v - the parsed `order` member
468
+ * @param nodes - the node count of its fingerprint
469
+ * @returns the ids
470
+ */
471
+ function readOrder(v: unknown, nodes: number): string[] {
472
+ const order = array(v, nodes).map((id) => (typeof id === "string" ? id : invalid()));
473
+ return order.length === nodes ? order : invalid();
474
+ }
475
+
476
+ /**
477
+ * The style columns written inside a block: one value per node or edge for each column the block names.
478
+ * @param v - the parsed `values` member
479
+ * @param block - the block read so far
480
+ * @returns the columns, by target
481
+ */
482
+ function readInline(v: unknown, block: StyleBlock): { node: Map<string, StyleColumn>; edge: Map<string, StyleColumn> } {
483
+ const values = isObject(v) ? v : invalid();
484
+ const inline = { node: new Map<string, StyleColumn>(), edge: new Map<string, StyleColumn>() };
485
+ for (const target of TARGETS) {
486
+ const n = target === "node" ? block.fingerprint.nodes : block.fingerprint.edges;
487
+ const byKey = isObject(values[target]) ? values[target] : invalid();
488
+ for (const key of block.columns[target]) {
489
+ const col = array(Object.hasOwn(byKey, key) ? byKey[key] : invalid(), n);
490
+ if (col.length !== n) {
491
+ invalid();
492
+ }
493
+ inline[target].set(
494
+ key,
495
+ col.map((x) => (x === null ? null : scalar(x))),
496
+ );
497
+ }
498
+ }
499
+ return inline;
500
+ }
501
+
502
+ /**
503
+ * The data column hashes of a block.
504
+ * @param v - the parsed `data` member
505
+ * @returns the hashes by column name, per target
506
+ */
507
+ function readData(v: unknown): NonNullable<StyleBlock["data"]> {
508
+ const d = isObject(v) ? v : invalid();
509
+ const byTarget = (t: unknown): Map<string, string> => {
510
+ const o = isObject(t) ? t : invalid();
511
+ const keys = Object.keys(o);
512
+ if (keys.length > MAX_RULES) {
513
+ invalid();
514
+ }
515
+ return new Map(keys.map((k) => [name(k), name(o[k])]));
516
+ };
517
+ return { node: byTarget(d.node), edge: byTarget(d.edge) };
518
+ }
519
+
520
+ /**
521
+ * A cell's value as text: a list as JSON, no value as a character no text holds.
522
+ * @param v - the cell
523
+ * @returns the text
524
+ */
525
+ function cellText(v: unknown): string {
526
+ if (v === null) {
527
+ return "\u0001";
528
+ }
529
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
530
+ return String(v);
531
+ }
532
+ return JSON.stringify(v);
533
+ }
534
+
535
+ /**
536
+ * A hash of a column's values as text, by element index: the text of a value, so a format that reads the number 7
537
+ * back as "7" keeps the hash.
538
+ * @param table - the node or edge table
539
+ * @param column - the column's name
540
+ * @param n - the element count
541
+ * @returns the hash
542
+ */
543
+ function columnHash(table: GraphSnapshot["nodes"], column: string, n: number): string {
544
+ let h1 = 0x811c9dc5;
545
+ let h2 = 0x01000193 ^ 0x5bd1e995;
546
+ for (let i = 0; i < n; i++) {
547
+ const text = `${cellText(plainCell(table.value(column, i)))}\u0000`;
548
+ for (let c = 0; c < text.length; c++) {
549
+ const code = text.charCodeAt(c); // NOSONAR(S7758): hashes UTF-16 code units; code points would change every written hash
550
+ h1 = Math.imul(h1 ^ code, 0x01000193);
551
+ h2 = Math.imul(h2 ^ code, 0x01000193) ^ (h2 >>> 13);
552
+ }
553
+ }
554
+ const hex = (h: number): string => (h >>> 0).toString(16).padStart(8, "0");
555
+ return hex(h1) + hex(h2);
556
+ }
557
+
558
+ /**
559
+ * Every data column some rule reads, in first-read order. A rule's `ext` is the consumer's own and is not read.
560
+ * @param rules - the rules
561
+ * @returns the column names
562
+ */
563
+ function dataNames(rules: readonly StyleRule[]): string[] {
564
+ const out = new Set<string>();
565
+ for (const { ext: _ext, ...rule } of rules) {
566
+ JSON.stringify(rule, (_k, v: unknown) => {
567
+ if (isObject(v) && typeof v.data === "string" && Object.keys(v).length === 1) {
568
+ out.add(v.data);
569
+ }
570
+ return v;
571
+ });
572
+ }
573
+ return [...out];
574
+ }
575
+
576
+ /**
577
+ * A rule with its data column references renamed; its `ext` is kept untouched.
578
+ * @param rule - the rule
579
+ * @param rename - the new name of each column that moved
580
+ * @returns the rule
581
+ */
582
+ function renameData(rule: StyleRule, rename: ReadonlyMap<string, string>): StyleRule {
583
+ const { ext, ...rest } = rule;
584
+ const moved = JSON.parse(
585
+ JSON.stringify(rest, (_k, v: unknown) =>
586
+ isObject(v) && typeof v.data === "string" && Object.keys(v).length === 1 && rename.has(v.data)
587
+ ? { data: rename.get(v.data) }
588
+ : v,
589
+ ),
590
+ ) as StyleRule;
591
+ return ext === undefined ? moved : { ...moved, ext };
592
+ }
593
+
594
+ /**
595
+ * Where each data column a block's rules read is in the imported graph: under its own name when its values are the
596
+ * same, else the first column holding the same values (a format that renamed it), else under its own name.
597
+ * @param block - the block
598
+ * @param snapshot - the graph
599
+ * @param target - nodes or edges
600
+ * @returns the new name of each column that moved
601
+ */
602
+ function movedColumns(block: StyleBlock, snapshot: GraphSnapshot, target: StyleTarget): Map<string, string> {
603
+ const moved = new Map<string, string>();
604
+ const want = block.data?.[target];
605
+ if (want === undefined || want.size === 0) {
606
+ return moved;
607
+ }
608
+ const table = target === "node" ? snapshot.nodes : snapshot.edges;
609
+ const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
610
+ let have: Map<string, string> | null = null;
611
+ for (const [column, hash] of want) {
612
+ if (table.has(column) && columnHash(table, column, n) === hash) {
613
+ continue;
614
+ }
615
+ have ??= new Map(Array.from(table.names(), (c) => [c, columnHash(table, c, n)]));
616
+ for (const [c, h] of have) {
617
+ if (h === hash) {
618
+ moved.set(column, c);
619
+ break;
620
+ }
621
+ }
622
+ }
623
+ return moved;
624
+ }
625
+
626
+ /**
627
+ * A record of style values, copied into a null-prototype object.
628
+ * @param v - the parsed record
629
+ * @returns the copy
630
+ */
631
+ function values(v: unknown): Record<string, StyleValue> {
632
+ const o = isObject(v) ? v : invalid();
633
+ const out = Object.create(null) as Record<string, StyleValue>;
634
+ for (const key of Object.keys(o)) {
635
+ out[name(key)] = scalar(o[key]);
636
+ }
637
+ return out;
638
+ }
639
+
640
+ /**
641
+ * One rule.
642
+ * @param v - the parsed rule
643
+ * @returns the rule
644
+ */
645
+ function readRule(v: unknown): StyleRule {
646
+ const r = isObject(v) ? v : invalid();
647
+ const target = r.target === "node" || r.target === "edge" ? r.target : invalid();
648
+ const rule: {
649
+ -readonly [K in keyof StyleRule]: StyleRule[K];
650
+ } = {
651
+ id: name(r.id),
652
+ target,
653
+ role: name(r.role),
654
+ origin: name(r.origin),
655
+ where: predicate(r.where),
656
+ };
657
+ if (r.set !== undefined) {
658
+ rule.set = values(r.set);
659
+ }
660
+ if (r.map !== undefined) {
661
+ const m = isObject(r.map) ? r.map : invalid();
662
+ const map = Object.create(null) as Record<string, Mapping>;
663
+ for (const key of Object.keys(m)) {
664
+ map[name(key)] = mapping(m[key]);
665
+ }
666
+ rule.map = map;
667
+ }
668
+ if (r.facts !== undefined) {
669
+ rule.facts = facts(r.facts);
670
+ }
671
+ if (r.ext !== undefined) {
672
+ // carried untouched for the consumer that wrote it; safeTree() already checked its keys and depth
673
+ rule.ext = isObject(r.ext) ? r.ext : invalid();
674
+ }
675
+ return rule;
676
+ }
677
+
678
+ /**
679
+ * A column reference.
680
+ * @param v - the parsed reference
681
+ * @returns the reference
682
+ */
683
+ function columnRef(v: unknown): ColumnRef {
684
+ const o = isObject(v) ? v : invalid();
685
+ if (typeof o.data === "string") {
686
+ return { data: name(o.data) };
687
+ }
688
+ if (typeof o.style === "string") {
689
+ return { style: name(o.style) };
690
+ }
691
+ if (typeof o.computed === "string" && COMPUTED.has(o.computed)) {
692
+ return { computed: o.computed as "degree" | "indegree" | "outdegree" };
693
+ }
694
+ return invalid();
695
+ }
696
+
697
+ /**
698
+ * A predicate. A predicate of a kind this version does not know is kept as it is and matches nothing.
699
+ * @param v - the parsed predicate
700
+ * @returns the predicate
701
+ */
702
+ function predicate(v: unknown): Predicate {
703
+ const o = isObject(v) ? v : invalid();
704
+ if ("all" in o) {
705
+ return o.all === true ? { all: true } : invalid();
706
+ }
707
+ if ("has" in o) {
708
+ return { has: columnRef(o.has) };
709
+ }
710
+ if ("eq" in o) {
711
+ const t = array(o.eq, 2);
712
+ return t.length === 2 ? { eq: [columnRef(t[0]), scalar(t[1])] } : invalid();
713
+ }
714
+ if ("in" in o) {
715
+ const t = array(o.in, 2);
716
+ return t.length === 2 ? { in: [columnRef(t[0]), array(t[1], 65536).map(scalar)] } : invalid();
717
+ }
718
+ if ("and" in o) {
719
+ return { and: array(o.and, 64).map(predicate) };
720
+ }
721
+ if ("or" in o) {
722
+ return { or: array(o.or, 64).map(predicate) };
723
+ }
724
+ if ("not" in o) {
725
+ return { not: predicate(o.not) };
726
+ }
727
+ return o as Predicate;
728
+ }
729
+
730
+ /**
731
+ * A mapping. A mapping of a kind this version does not know is kept and gives no value.
732
+ * @param v - the parsed mapping
733
+ * @returns the mapping
734
+ */
735
+ function mapping(v: unknown): Mapping {
736
+ const o = isObject(v) ? v : invalid();
737
+ const by = columnRef(o.by);
738
+ switch (o.kind) {
739
+ case "passthrough":
740
+ return { kind: "passthrough", by };
741
+ case "discrete":
742
+ return {
743
+ kind: "discrete",
744
+ by,
745
+ table: array(o.table, 1 << 20).map((pair) => {
746
+ const p = array(pair, 2);
747
+ return p.length === 2 ? ([scalar(p[0]), scalar(p[1])] as const) : invalid();
748
+ }),
749
+ };
750
+ case "continuous":
751
+ return {
752
+ kind: "continuous",
753
+ by,
754
+ points: array(o.points, 4096).map((pt): ContinuousPoint => {
755
+ const q = isObject(pt) ? pt : invalid();
756
+ return {
757
+ at: typeof q.at === "number" && Number.isFinite(q.at) ? q.at : invalid(),
758
+ below: scalar(q.below),
759
+ value: scalar(q.value),
760
+ above: scalar(q.above),
761
+ };
762
+ }),
763
+ };
764
+ default:
765
+ return { ...o, kind: name(o.kind), by } as unknown as Mapping;
766
+ }
767
+ }
768
+
769
+ /**
770
+ * A rule's facts: the known members, each checked; anything else is left out.
771
+ * @param v - the parsed facts
772
+ * @returns the facts
773
+ */
774
+ function facts(v: unknown): StyleRuleFacts {
775
+ const o = isObject(v) ? v : invalid();
776
+ const count = (x: unknown): number | undefined =>
777
+ typeof x === "number" && Number.isSafeInteger(x) && x >= 0 ? x : undefined;
778
+ const out: { -readonly [K in keyof StyleRuleFacts]: StyleRuleFacts[K] } = {};
779
+ const elements = count(o.elements);
780
+ const classes = count(o.classes);
781
+ const distinctValues = count(o.distinctValues);
782
+ if (elements !== undefined) {
783
+ out.elements = elements;
784
+ }
785
+ if (classes !== undefined) {
786
+ out.classes = classes;
787
+ }
788
+ if (distinctValues !== undefined) {
789
+ out.distinctValues = distinctValues;
790
+ }
791
+ if (typeof o.reason === "string") {
792
+ out.reason = name(o.reason);
793
+ }
794
+ if (o.names !== undefined) {
795
+ out.names = array(o.names, 1 << 20).map((pair) => {
796
+ const p = array(pair, 2);
797
+ return p.length === 2 ? ([scalar(p[0]), String(scalar(p[1]))] as const) : invalid();
798
+ });
799
+ }
800
+ return out;
801
+ }
802
+
803
+ /**
804
+ * The style columns a block's rules read, from the block itself or from the tagged element attributes of the
805
+ * snapshot; null when one is missing.
806
+ * @param block - the block
807
+ * @param snapshot - the graph
808
+ * @param target - nodes or edges
809
+ * @returns the columns by key, or null
810
+ */
811
+ function blockColumns(
812
+ block: StyleBlock,
813
+ snapshot: GraphSnapshot,
814
+ target: StyleTarget,
815
+ ): Map<string, StyleColumn> | null {
816
+ const out = new Map<string, StyleColumn>();
817
+ const table = target === "node" ? snapshot.nodes : snapshot.edges;
818
+ const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
819
+ for (const key of block.columns[target]) {
820
+ const inline = block.values?.[target].get(key);
821
+ if (inline !== undefined) {
822
+ out.set(key, target === "node" ? inNodeOrder(block, snapshot, inline) : onEdgeRows(snapshot, inline));
823
+ continue;
824
+ }
825
+ const tag = styleTag(target, key);
826
+ if (!table.has(tag)) {
827
+ return null;
828
+ }
829
+ const col = Array.from({ length: n }, (_, i): StyleValue | null => {
830
+ const v = plainCell(table.value(tag, i));
831
+ return isScalar(v) ? v : null;
832
+ });
833
+ out.set(key, target === "edge" ? withPairValues(snapshot, col) : col);
834
+ }
835
+ return out;
836
+ }
837
+
838
+ /**
839
+ * The style columns the block lists as booleans, as booleans: a format with no booleans (GML) holds them as 0 and 1
840
+ * (or their text).
841
+ * @param block - the block
842
+ * @param columns - the style columns per target, changed in place
843
+ */
844
+ function asBooleans(block: StyleBlock, columns: Record<StyleTarget, Map<string, StyleColumn>>): void {
845
+ for (const target of TARGETS) {
846
+ for (const key of block.booleans?.[target] ?? []) {
847
+ const col = columns[target].get(key);
848
+ if (col !== undefined) {
849
+ columns[target].set(key, col.map(asBoolean));
850
+ }
851
+ }
852
+ }
853
+ }
854
+
855
+ /**
856
+ * A value of a boolean style column as a boolean: 1 and 0, and their text, as GML and untyped formats hold them.
857
+ * @param v - the value read
858
+ * @returns the boolean, or the value when it is neither
859
+ */
860
+ function asBoolean(v: StyleValue | null): StyleValue | null {
861
+ if (v === 1 || v === "1" || v === "true") {
862
+ return true;
863
+ }
864
+ return v === 0 || v === "0" || v === "false" ? false : v;
865
+ }
866
+
867
+ /**
868
+ * Inline node values in the order of the graph read back: by each node's id, when the block lists the order it wrote
869
+ * them in and the file read its nodes back in another.
870
+ * @param block - the block
871
+ * @param snapshot - the graph
872
+ * @param col - the values in the block's order
873
+ * @returns the values per node row
874
+ */
875
+ function inNodeOrder(block: StyleBlock, snapshot: GraphSnapshot, col: StyleColumn): StyleColumn {
876
+ const { order } = block;
877
+ if (order === undefined || order.every((id, i) => id === String(snapshot.ids.idOf(i)))) {
878
+ return col;
879
+ }
880
+ const at = new Map<string, number>();
881
+ order.forEach((id, i) => {
882
+ if (!at.has(id)) {
883
+ at.set(id, i);
884
+ }
885
+ });
886
+ return Array.from(
887
+ { length: snapshot.nodeCount },
888
+ (_unused, i) => col[at.get(String(snapshot.ids.idOf(i))) ?? -1] ?? null,
889
+ );
890
+ }
891
+
892
+ /**
893
+ * Inline edge values, listed once per edge the file holds, per edge row: both halves of an undirected edge in a
894
+ * directed graph take the edge's value.
895
+ * @param snapshot - the graph
896
+ * @param col - the values per held edge
897
+ * @returns the values per edge row
898
+ */
899
+ function onEdgeRows(snapshot: GraphSnapshot, col: StyleColumn): StyleColumn {
900
+ const held = heldEdgeRows(snapshot);
901
+ if (held.length === snapshot.edgeCount) {
902
+ return col;
903
+ }
904
+ const out = new Array<StyleValue | null>(snapshot.edgeCount).fill(null);
905
+ held.forEach((e, k) => {
906
+ out[e] = col[k] ?? null;
907
+ });
908
+ return withPairValues(snapshot, out);
909
+ }
910
+
911
+ /**
912
+ * Per-edge values with each half of an undirected edge in a directed graph given its other half's value where it has
913
+ * none: the file holds the edge once, and the format reads its values onto one half.
914
+ * @param snapshot - the graph
915
+ * @param col - the values per edge row
916
+ * @returns the values
917
+ */
918
+ function withPairValues(snapshot: GraphSnapshot, col: (StyleValue | null)[]): (StyleValue | null)[] {
919
+ const pair = snapshot.edges.byRole("pair");
920
+ if (pair === null) {
921
+ return col;
922
+ }
923
+ for (let i = 0; i < col.length; i++) {
924
+ if (col[i] === null && pair.isSet(i)) {
925
+ col[i] = col[pair.value(i) as number] ?? null;
926
+ }
927
+ }
928
+ return col;
929
+ }
930
+
931
+ /** The color property whose alpha carries an opacity, and that opacity, per target. */
932
+ const ALPHA_SLOT: Readonly<Record<StyleTarget, readonly [string, string]>> = {
933
+ node: ["fill", "fillOpacity"],
934
+ edge: ["color", "opacity"],
935
+ };
936
+
937
+ /**
938
+ * The native values with opacity 1 wherever the color is opaque (it has a color and no opacity) and the rules give a
939
+ * translucent opacity: a format that writes the opacity as the color's alpha states 1 with an opaque color.
940
+ * @param values - the native values of one target
941
+ * @param target - nodes or edges
942
+ * @param rules - the block's rules
943
+ * @param src - the values the rules read
944
+ * @param n - the element count
945
+ * @returns the values, the opacity column replaced where it changes
946
+ */
947
+ function opaqueColors(
948
+ values: Partial<Record<string, StyleColumn>>,
949
+ target: StyleTarget,
950
+ rules: readonly StyleRule[],
951
+ src: ReturnType<typeof refSource>,
952
+ n: number,
953
+ ): Partial<Record<string, StyleColumn>> {
954
+ const [colorProperty, opacityProperty] = ALPHA_SLOT[target];
955
+ const color = values[colorProperty];
956
+ if (color === undefined) {
957
+ return values;
958
+ }
959
+ const native = values[opacityProperty];
960
+ const resolved = resolveRules(rules, target, opacityProperty, src);
961
+ let out: (StyleValue | null)[] | null = null;
962
+ for (let i = 0; i < n; i++) {
963
+ const r = resolved[i];
964
+ if ((color[i] ?? null) !== null && (native?.[i] ?? null) === null && r !== null && r !== 1) {
965
+ out ??= native === undefined ? new Array<StyleValue | null>(n).fill(null) : [...native];
966
+ out[i] = 1;
967
+ }
968
+ }
969
+ return out === null ? values : { ...values, [opacityProperty]: out };
970
+ }
971
+
972
+ /**
973
+ * A name not yet taken: the base, else the base with `#2`, `#3` and so on.
974
+ * @param taken - whether a name is taken
975
+ * @param base - the preferred name
976
+ * @returns the name
977
+ */
978
+ function freeName(taken: (name: string) => boolean, base: string): string {
979
+ let name = base;
980
+ let k = 2;
981
+ while (taken(name)) {
982
+ name = `${base}#${String(k)}`;
983
+ k++;
984
+ }
985
+ return name;
986
+ }
987
+
988
+ /**
989
+ * Whether a property's values are text: an empty text draws what no text draws, so a format that holds no "unset"
990
+ * (CyCSV) reads one back as "".
991
+ * @param target - nodes or edges
992
+ * @param property - the property
993
+ * @returns true for text
994
+ */
995
+ function textProperty(target: StyleTarget, property: string): boolean {
996
+ return STYLE_PROPERTIES[target].get(property) === "text";
997
+ }
998
+
999
+ /**
1000
+ * The elements whose native value differs from what the rules give: another program edited them. Where the rules
1001
+ * give no value, the program's own drawing of an unset property, and an empty text, are no edit.
1002
+ * @param values - the native values
1003
+ * @param resolved - what the rules give
1004
+ * @param unset - what the writing program draws for the unset property, or undefined
1005
+ * @param text - whether the property is text
1006
+ * @returns the edited values, null where an element is not edited; null when none is
1007
+ */
1008
+ function editedValues(
1009
+ values: readonly (StyleValue | null)[],
1010
+ resolved: readonly (StyleValue | null)[],
1011
+ unset: StyleValue | undefined,
1012
+ text: boolean,
1013
+ ): (StyleValue | null)[] | null {
1014
+ const empty = text ? "" : undefined;
1015
+ let exceptions: (StyleValue | null)[] | null = null;
1016
+ for (let i = 0; i < resolved.length; i++) {
1017
+ const t = values[i] ?? null;
1018
+ const unchanged = resolved[i] === null && (t === unset || t === empty);
1019
+ if (t !== null && !within(t, resolved[i]) && !unchanged) {
1020
+ exceptions ??= new Array<StyleValue | null>(resolved.length).fill(null);
1021
+ exceptions[i] = t;
1022
+ }
1023
+ }
1024
+ return exceptions;
1025
+ }
1026
+
1027
+ /**
1028
+ * The passthrough mappings of the elements whose native value differs from what the rules give, each over a new
1029
+ * exceptions column.
1030
+ * @param own - the native per-element values by property
1031
+ * @param rules - the block's rules
1032
+ * @param target - nodes or edges
1033
+ * @param src - what the rules read
1034
+ * @param columns - the target's style columns, added to
1035
+ * @param unsetOf - what the writing program draws for an unset property
1036
+ * @returns the mappings by property, empty when nothing was edited
1037
+ */
1038
+ function exceptionMappings(
1039
+ own: Partial<Record<string, StyleColumn>>,
1040
+ rules: readonly StyleRule[],
1041
+ target: StyleTarget,
1042
+ src: ReturnType<typeof refSource>,
1043
+ columns: Map<string, StyleColumn>,
1044
+ unsetOf: (property: string) => StyleValue | undefined,
1045
+ ): Record<string, Mapping> {
1046
+ const map = Object.create(null) as Record<string, Mapping>;
1047
+ // in one order whatever order the reader found the properties in, so a file written again is the same
1048
+ for (const [property, values] of Object.entries(own).sort(([a], [b]) => (a < b ? -1 : 1))) {
1049
+ if (values === undefined) {
1050
+ continue;
1051
+ }
1052
+ const resolved = resolveRules(rules, target, property, src);
1053
+ const exceptions = editedValues(values, resolved, unsetOf(property), textProperty(target, property));
1054
+ if (exceptions !== null) {
1055
+ const key = freeName((k) => columns.has(k), `exceptions.${property}`);
1056
+ columns.set(key, exceptions);
1057
+ map[property] = { kind: "passthrough", by: { style: key } };
1058
+ }
1059
+ }
1060
+ return map;
1061
+ }
1062
+
1063
+ /**
1064
+ * The styles a valid block describes, read against the graph it was imported into. The block's rules are the
1065
+ * file's declared rules and read the tagged per-element values. Where the file's own native values are given, they
1066
+ * are the truth: an element whose value differs from what the rules give (another program edited it) is written
1067
+ * into one exceptions rule on top, so the styles stay exact and, with no edit, are the block's rules unchanged.
1068
+ * @param block - a block `parseStyleBlock()` accepted for this graph
1069
+ * @param snapshot - the graph
1070
+ * @param format - the format the file was read as
1071
+ * @param truth - the native per-element values per target and property, when the format has any
1072
+ * @param notes - notes to carry (the reader's own)
1073
+ * @param tool - the program whose drawing the native values describe: where the rules give an element no value and
1074
+ * the file holds only what that program draws for an unset property (a font written with "plain" style), it is not
1075
+ * an edit
1076
+ * @param opacityInColor - whether the format writes a fill's and an edge's opacity as its color's alpha: a native
1077
+ * color with no native opacity then states opacity 1 where the rules give a translucent one
1078
+ * @returns the styles, or null when a style column the rules read is missing from the file
1079
+ * @category Styles
1080
+ */
1081
+ export function blockStyles(
1082
+ block: StyleBlock,
1083
+ snapshot: GraphSnapshot,
1084
+ format: string,
1085
+ truth: Partial<Record<StyleTarget, Partial<Record<string, StyleColumn>>>> = {},
1086
+ notes: readonly StyleNote[] = [],
1087
+ tool: StyleTool | null = null,
1088
+ opacityInColor = false,
1089
+ ): ExtractedStyles | null {
1090
+ const node = blockColumns(block, snapshot, "node");
1091
+ const edge = blockColumns(block, snapshot, "edge");
1092
+ if (node === null || edge === null) {
1093
+ return null;
1094
+ }
1095
+ const columns = { node, edge };
1096
+ asBooleans(block, columns);
1097
+ const features = { node: featuresOf(snapshot, "node"), edge: featuresOf(snapshot, "edge") };
1098
+ // a data column the file holds under another name is read there
1099
+ const moved = { node: movedColumns(block, snapshot, "node"), edge: movedColumns(block, snapshot, "edge") };
1100
+ const blockRules = block.rules.map((r) => (moved[r.target].size === 0 ? r : renameData(r, moved[r.target])));
1101
+ const rules: StyleRule[] = [...blockRules];
1102
+ for (const target of TARGETS) {
1103
+ const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
1104
+ const src = refSource(target, n, columns[target], features[target], snapshot);
1105
+ const own = opacityInColor ? opaqueColors(truth[target] ?? {}, target, blockRules, src, n) : truth[target];
1106
+ const unsetOf = (property: string): StyleValue | undefined =>
1107
+ (
1108
+ (tool === null ? null : toolDefaults(tool))?.[target]?.[property as never] as
1109
+ { readonly value: StyleValue } | undefined
1110
+ )?.value;
1111
+ const map = exceptionMappings(own ?? {}, blockRules, target, src, columns[target], unsetOf);
1112
+ if (Object.keys(map).length > 0) {
1113
+ // the block's own rules may hold an exceptions rule already (a file graphty wrote from an edited one)
1114
+ const id = freeName((x) => rules.some((r) => r.id === x), `${target}.exceptions`);
1115
+ rules.push({
1116
+ id,
1117
+ target,
1118
+ role: "exceptions",
1119
+ origin: "inferred",
1120
+ where: { all: true },
1121
+ map,
1122
+ });
1123
+ }
1124
+ }
1125
+ return {
1126
+ styles: {
1127
+ version: 1,
1128
+ source: { format, dialect: "graphty", tool: "graphty" },
1129
+ units: block.units,
1130
+ graph: block.graph,
1131
+ rules,
1132
+ notes,
1133
+ },
1134
+ columns,
1135
+ features,
1136
+ fingerprint: styleFingerprint(snapshot),
1137
+ };
1138
+ }