@graphty/graph-io 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -28
  3. package/dist/chunks/children-CL3Cy0ez.js +238 -0
  4. package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
  5. package/dist/chunks/escape-DyI8JofU.js +938 -0
  6. package/dist/chunks/escape-DyI8JofU.js.map +1 -0
  7. package/dist/chunks/importer-CQnJuWJw.js +2987 -0
  8. package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
  9. package/dist/chunks/importer-CpCpfbxr.js +2015 -0
  10. package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
  11. package/dist/chunks/importer-DbnGYr3_.js +2342 -0
  12. package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
  13. package/dist/chunks/importer-GozH8DkN.js +3050 -0
  14. package/dist/chunks/importer-GozH8DkN.js.map +1 -0
  15. package/dist/chunks/records-CGpxszm1.js +605 -0
  16. package/dist/chunks/records-CGpxszm1.js.map +1 -0
  17. package/dist/chunks/text-CajMdVFy.js +189 -0
  18. package/dist/chunks/text-CajMdVFy.js.map +1 -0
  19. package/dist/chunks/writer-DxSKC7TL.js +2842 -0
  20. package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
  21. package/dist/csv.d.ts +1 -0
  22. package/dist/csv.js +1702 -0
  23. package/dist/csv.js.map +1 -0
  24. package/dist/dot.d.ts +1 -0
  25. package/dist/dot.js +8 -0
  26. package/dist/dot.js.map +1 -0
  27. package/dist/gexf.d.ts +1 -0
  28. package/dist/gexf.js +3466 -0
  29. package/dist/gexf.js.map +1 -0
  30. package/dist/gml.d.ts +1 -0
  31. package/dist/gml.js +2647 -0
  32. package/dist/gml.js.map +1 -0
  33. package/dist/graph-io.d.ts +1 -0
  34. package/dist/graph-io.js +790 -0
  35. package/dist/graph-io.js.map +1 -0
  36. package/dist/graphml.d.ts +1 -0
  37. package/dist/graphml.js +8 -0
  38. package/dist/graphml.js.map +1 -0
  39. package/dist/json.d.ts +1 -0
  40. package/dist/json.js +11 -0
  41. package/dist/json.js.map +1 -0
  42. package/dist/neo4j.d.ts +1 -0
  43. package/dist/neo4j.js +2046 -0
  44. package/dist/neo4j.js.map +1 -0
  45. package/dist/pajek.d.ts +1 -0
  46. package/dist/pajek.js +8 -0
  47. package/dist/pajek.js.map +1 -0
  48. package/dist/src/children.d.ts +134 -0
  49. package/dist/src/children.d.ts.map +1 -0
  50. package/dist/src/children.js +274 -0
  51. package/dist/src/children.js.map +1 -0
  52. package/dist/src/common/attributes.d.ts +229 -0
  53. package/dist/src/common/attributes.d.ts.map +1 -0
  54. package/dist/src/common/attributes.js +368 -0
  55. package/dist/src/common/attributes.js.map +1 -0
  56. package/dist/src/common/codes.d.ts +105 -0
  57. package/dist/src/common/codes.d.ts.map +1 -0
  58. package/dist/src/common/codes.js +107 -0
  59. package/dist/src/common/codes.js.map +1 -0
  60. package/dist/src/common/declared-types.d.ts +84 -0
  61. package/dist/src/common/declared-types.d.ts.map +1 -0
  62. package/dist/src/common/declared-types.js +326 -0
  63. package/dist/src/common/declared-types.js.map +1 -0
  64. package/dist/src/common/direction.d.ts +206 -0
  65. package/dist/src/common/direction.d.ts.map +1 -0
  66. package/dist/src/common/direction.js +370 -0
  67. package/dist/src/common/direction.js.map +1 -0
  68. package/dist/src/common/escape.d.ts +92 -0
  69. package/dist/src/common/escape.d.ts.map +1 -0
  70. package/dist/src/common/escape.js +212 -0
  71. package/dist/src/common/escape.js.map +1 -0
  72. package/dist/src/common/export.d.ts +249 -0
  73. package/dist/src/common/export.d.ts.map +1 -0
  74. package/dist/src/common/export.js +594 -0
  75. package/dist/src/common/export.js.map +1 -0
  76. package/dist/src/common/format.d.ts +59 -0
  77. package/dist/src/common/format.d.ts.map +1 -0
  78. package/dist/src/common/format.js +106 -0
  79. package/dist/src/common/format.js.map +1 -0
  80. package/dist/src/common/ids.d.ts +83 -0
  81. package/dist/src/common/ids.d.ts.map +1 -0
  82. package/dist/src/common/ids.js +158 -0
  83. package/dist/src/common/ids.js.map +1 -0
  84. package/dist/src/common/input.d.ts +100 -0
  85. package/dist/src/common/input.d.ts.map +1 -0
  86. package/dist/src/common/input.js +335 -0
  87. package/dist/src/common/input.js.map +1 -0
  88. package/dist/src/common/lists.d.ts +34 -0
  89. package/dist/src/common/lists.d.ts.map +1 -0
  90. package/dist/src/common/lists.js +185 -0
  91. package/dist/src/common/lists.js.map +1 -0
  92. package/dist/src/common/options.d.ts +108 -0
  93. package/dist/src/common/options.d.ts.map +1 -0
  94. package/dist/src/common/options.js +265 -0
  95. package/dist/src/common/options.js.map +1 -0
  96. package/dist/src/common/report.d.ts +187 -0
  97. package/dist/src/common/report.d.ts.map +1 -0
  98. package/dist/src/common/report.js +274 -0
  99. package/dist/src/common/report.js.map +1 -0
  100. package/dist/src/common/temporal.d.ts +71 -0
  101. package/dist/src/common/temporal.d.ts.map +1 -0
  102. package/dist/src/common/temporal.js +266 -0
  103. package/dist/src/common/temporal.js.map +1 -0
  104. package/dist/src/common/text.d.ts +104 -0
  105. package/dist/src/common/text.d.ts.map +1 -0
  106. package/dist/src/common/text.js +255 -0
  107. package/dist/src/common/text.js.map +1 -0
  108. package/dist/src/common/weights.d.ts +77 -0
  109. package/dist/src/common/weights.d.ts.map +1 -0
  110. package/dist/src/common/weights.js +156 -0
  111. package/dist/src/common/weights.js.map +1 -0
  112. package/dist/src/common/writer.d.ts +51 -0
  113. package/dist/src/common/writer.d.ts.map +1 -0
  114. package/dist/src/common/writer.js +108 -0
  115. package/dist/src/common/writer.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +245 -0
  117. package/dist/src/common/xml.d.ts.map +1 -0
  118. package/dist/src/common/xml.js +942 -0
  119. package/dist/src/common/xml.js.map +1 -0
  120. package/dist/src/formats/csv/exporter.d.ts +70 -0
  121. package/dist/src/formats/csv/exporter.d.ts.map +1 -0
  122. package/dist/src/formats/csv/exporter.js +682 -0
  123. package/dist/src/formats/csv/exporter.js.map +1 -0
  124. package/dist/src/formats/csv/header.d.ts +66 -0
  125. package/dist/src/formats/csv/header.d.ts.map +1 -0
  126. package/dist/src/formats/csv/header.js +152 -0
  127. package/dist/src/formats/csv/header.js.map +1 -0
  128. package/dist/src/formats/csv/importer.d.ts +82 -0
  129. package/dist/src/formats/csv/importer.d.ts.map +1 -0
  130. package/dist/src/formats/csv/importer.js +849 -0
  131. package/dist/src/formats/csv/importer.js.map +1 -0
  132. package/dist/src/formats/csv/index.d.ts +60 -0
  133. package/dist/src/formats/csv/index.d.ts.map +1 -0
  134. package/dist/src/formats/csv/index.js +63 -0
  135. package/dist/src/formats/csv/index.js.map +1 -0
  136. package/dist/src/formats/csv/records.d.ts +188 -0
  137. package/dist/src/formats/csv/records.d.ts.map +1 -0
  138. package/dist/src/formats/csv/records.js +702 -0
  139. package/dist/src/formats/csv/records.js.map +1 -0
  140. package/dist/src/formats/csv/values.d.ts +105 -0
  141. package/dist/src/formats/csv/values.d.ts.map +1 -0
  142. package/dist/src/formats/csv/values.js +192 -0
  143. package/dist/src/formats/csv/values.js.map +1 -0
  144. package/dist/src/formats/dot/exporter.d.ts +52 -0
  145. package/dist/src/formats/dot/exporter.d.ts.map +1 -0
  146. package/dist/src/formats/dot/exporter.js +836 -0
  147. package/dist/src/formats/dot/exporter.js.map +1 -0
  148. package/dist/src/formats/dot/importer.d.ts +102 -0
  149. package/dist/src/formats/dot/importer.d.ts.map +1 -0
  150. package/dist/src/formats/dot/importer.js +1291 -0
  151. package/dist/src/formats/dot/importer.js.map +1 -0
  152. package/dist/src/formats/dot/index.d.ts +7 -0
  153. package/dist/src/formats/dot/index.d.ts.map +1 -0
  154. package/dist/src/formats/dot/index.js +7 -0
  155. package/dist/src/formats/dot/index.js.map +1 -0
  156. package/dist/src/formats/dot/names.d.ts +29 -0
  157. package/dist/src/formats/dot/names.d.ts.map +1 -0
  158. package/dist/src/formats/dot/names.js +28 -0
  159. package/dist/src/formats/dot/names.js.map +1 -0
  160. package/dist/src/formats/dot/tokenizer.d.ts +114 -0
  161. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
  162. package/dist/src/formats/dot/tokenizer.js +341 -0
  163. package/dist/src/formats/dot/tokenizer.js.map +1 -0
  164. package/dist/src/formats/gexf/exporter.d.ts +56 -0
  165. package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
  166. package/dist/src/formats/gexf/exporter.js +1395 -0
  167. package/dist/src/formats/gexf/exporter.js.map +1 -0
  168. package/dist/src/formats/gexf/importer.d.ts +73 -0
  169. package/dist/src/formats/gexf/importer.d.ts.map +1 -0
  170. package/dist/src/formats/gexf/importer.js +1880 -0
  171. package/dist/src/formats/gexf/importer.js.map +1 -0
  172. package/dist/src/formats/gexf/index.d.ts +96 -0
  173. package/dist/src/formats/gexf/index.d.ts.map +1 -0
  174. package/dist/src/formats/gexf/index.js +97 -0
  175. package/dist/src/formats/gexf/index.js.map +1 -0
  176. package/dist/src/formats/gexf/schema.d.ts +135 -0
  177. package/dist/src/formats/gexf/schema.d.ts.map +1 -0
  178. package/dist/src/formats/gexf/schema.js +323 -0
  179. package/dist/src/formats/gexf/schema.js.map +1 -0
  180. package/dist/src/formats/gml/exporter.d.ts +69 -0
  181. package/dist/src/formats/gml/exporter.d.ts.map +1 -0
  182. package/dist/src/formats/gml/exporter.js +1093 -0
  183. package/dist/src/formats/gml/exporter.js.map +1 -0
  184. package/dist/src/formats/gml/importer.d.ts +66 -0
  185. package/dist/src/formats/gml/importer.d.ts.map +1 -0
  186. package/dist/src/formats/gml/importer.js +1331 -0
  187. package/dist/src/formats/gml/importer.js.map +1 -0
  188. package/dist/src/formats/gml/index.d.ts +85 -0
  189. package/dist/src/formats/gml/index.d.ts.map +1 -0
  190. package/dist/src/formats/gml/index.js +88 -0
  191. package/dist/src/formats/gml/index.js.map +1 -0
  192. package/dist/src/formats/gml/syntax.d.ts +186 -0
  193. package/dist/src/formats/gml/syntax.d.ts.map +1 -0
  194. package/dist/src/formats/gml/syntax.js +467 -0
  195. package/dist/src/formats/gml/syntax.js.map +1 -0
  196. package/dist/src/formats/graphml/constants.d.ts +169 -0
  197. package/dist/src/formats/graphml/constants.d.ts.map +1 -0
  198. package/dist/src/formats/graphml/constants.js +165 -0
  199. package/dist/src/formats/graphml/constants.js.map +1 -0
  200. package/dist/src/formats/graphml/exporter.d.ts +34 -0
  201. package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
  202. package/dist/src/formats/graphml/exporter.js +1176 -0
  203. package/dist/src/formats/graphml/exporter.js.map +1 -0
  204. package/dist/src/formats/graphml/importer.d.ts +31 -0
  205. package/dist/src/formats/graphml/importer.d.ts.map +1 -0
  206. package/dist/src/formats/graphml/importer.js +1607 -0
  207. package/dist/src/formats/graphml/importer.js.map +1 -0
  208. package/dist/src/formats/graphml/index.d.ts +8 -0
  209. package/dist/src/formats/graphml/index.d.ts.map +1 -0
  210. package/dist/src/formats/graphml/index.js +8 -0
  211. package/dist/src/formats/graphml/index.js.map +1 -0
  212. package/dist/src/formats/graphml/tree.d.ts +72 -0
  213. package/dist/src/formats/graphml/tree.d.ts.map +1 -0
  214. package/dist/src/formats/graphml/tree.js +290 -0
  215. package/dist/src/formats/graphml/tree.js.map +1 -0
  216. package/dist/src/formats/json/dialect.d.ts +125 -0
  217. package/dist/src/formats/json/dialect.d.ts.map +1 -0
  218. package/dist/src/formats/json/dialect.js +262 -0
  219. package/dist/src/formats/json/dialect.js.map +1 -0
  220. package/dist/src/formats/json/exporter.d.ts +89 -0
  221. package/dist/src/formats/json/exporter.d.ts.map +1 -0
  222. package/dist/src/formats/json/exporter.js +1358 -0
  223. package/dist/src/formats/json/exporter.js.map +1 -0
  224. package/dist/src/formats/json/importer.d.ts +108 -0
  225. package/dist/src/formats/json/importer.d.ts.map +1 -0
  226. package/dist/src/formats/json/importer.js +1838 -0
  227. package/dist/src/formats/json/importer.js.map +1 -0
  228. package/dist/src/formats/json/index.d.ts +8 -0
  229. package/dist/src/formats/json/index.d.ts.map +1 -0
  230. package/dist/src/formats/json/index.js +8 -0
  231. package/dist/src/formats/json/index.js.map +1 -0
  232. package/dist/src/formats/neo4j/exporter.d.ts +68 -0
  233. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
  234. package/dist/src/formats/neo4j/exporter.js +1055 -0
  235. package/dist/src/formats/neo4j/exporter.js.map +1 -0
  236. package/dist/src/formats/neo4j/header.d.ts +52 -0
  237. package/dist/src/formats/neo4j/header.d.ts.map +1 -0
  238. package/dist/src/formats/neo4j/header.js +131 -0
  239. package/dist/src/formats/neo4j/header.js.map +1 -0
  240. package/dist/src/formats/neo4j/importer.d.ts +73 -0
  241. package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
  242. package/dist/src/formats/neo4j/importer.js +932 -0
  243. package/dist/src/formats/neo4j/importer.js.map +1 -0
  244. package/dist/src/formats/neo4j/index.d.ts +79 -0
  245. package/dist/src/formats/neo4j/index.d.ts.map +1 -0
  246. package/dist/src/formats/neo4j/index.js +83 -0
  247. package/dist/src/formats/neo4j/index.js.map +1 -0
  248. package/dist/src/formats/pajek/exporter.d.ts +58 -0
  249. package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
  250. package/dist/src/formats/pajek/exporter.js +825 -0
  251. package/dist/src/formats/pajek/exporter.js.map +1 -0
  252. package/dist/src/formats/pajek/importer.d.ts +88 -0
  253. package/dist/src/formats/pajek/importer.d.ts.map +1 -0
  254. package/dist/src/formats/pajek/importer.js +1047 -0
  255. package/dist/src/formats/pajek/importer.js.map +1 -0
  256. package/dist/src/formats/pajek/index.d.ts +7 -0
  257. package/dist/src/formats/pajek/index.d.ts.map +1 -0
  258. package/dist/src/formats/pajek/index.js +7 -0
  259. package/dist/src/formats/pajek/index.js.map +1 -0
  260. package/dist/src/formats/pajek/syntax.d.ts +112 -0
  261. package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
  262. package/dist/src/formats/pajek/syntax.js +269 -0
  263. package/dist/src/formats/pajek/syntax.js.map +1 -0
  264. package/dist/src/index.d.ts +35 -0
  265. package/dist/src/index.d.ts.map +1 -0
  266. package/dist/src/index.js +39 -0
  267. package/dist/src/index.js.map +1 -0
  268. package/dist/src/registry.d.ts +207 -0
  269. package/dist/src/registry.d.ts.map +1 -0
  270. package/dist/src/registry.js +481 -0
  271. package/dist/src/registry.js.map +1 -0
  272. package/dist/src/sniff.d.ts +104 -0
  273. package/dist/src/sniff.d.ts.map +1 -0
  274. package/dist/src/sniff.js +357 -0
  275. package/dist/src/sniff.js.map +1 -0
  276. package/dist/src/types.d.ts +238 -0
  277. package/dist/src/types.d.ts.map +1 -0
  278. package/dist/src/types.js +29 -0
  279. package/dist/src/types.js.map +1 -0
  280. package/dist/tsconfig.build.tsbuildinfo +1 -0
  281. package/package.json +122 -7
  282. package/src/children.ts +335 -0
  283. package/src/common/attributes.ts +520 -0
  284. package/src/common/codes.ts +153 -0
  285. package/src/common/declared-types.ts +374 -0
  286. package/src/common/direction.ts +518 -0
  287. package/src/common/escape.ts +231 -0
  288. package/src/common/export.ts +817 -0
  289. package/src/common/format.ts +111 -0
  290. package/src/common/ids.ts +176 -0
  291. package/src/common/input.ts +378 -0
  292. package/src/common/lists.ts +196 -0
  293. package/src/common/options.ts +377 -0
  294. package/src/common/report.ts +352 -0
  295. package/src/common/temporal.ts +302 -0
  296. package/src/common/text.ts +294 -0
  297. package/src/common/weights.ts +202 -0
  298. package/src/common/writer.ts +123 -0
  299. package/src/common/xml.ts +1053 -0
  300. package/src/formats/csv/exporter.ts +894 -0
  301. package/src/formats/csv/header.ts +172 -0
  302. package/src/formats/csv/importer.ts +1104 -0
  303. package/src/formats/csv/index.ts +88 -0
  304. package/src/formats/csv/records.ts +813 -0
  305. package/src/formats/csv/values.ts +224 -0
  306. package/src/formats/dot/exporter.ts +1014 -0
  307. package/src/formats/dot/importer.ts +1549 -0
  308. package/src/formats/dot/index.ts +7 -0
  309. package/src/formats/dot/names.ts +40 -0
  310. package/src/formats/dot/tokenizer.ts +384 -0
  311. package/src/formats/gexf/exporter.ts +1696 -0
  312. package/src/formats/gexf/importer.ts +2333 -0
  313. package/src/formats/gexf/index.ts +142 -0
  314. package/src/formats/gexf/schema.ts +361 -0
  315. package/src/formats/gml/exporter.ts +1404 -0
  316. package/src/formats/gml/importer.ts +1591 -0
  317. package/src/formats/gml/index.ts +128 -0
  318. package/src/formats/gml/syntax.ts +545 -0
  319. package/src/formats/graphml/constants.ts +225 -0
  320. package/src/formats/graphml/exporter.ts +1458 -0
  321. package/src/formats/graphml/importer.ts +2027 -0
  322. package/src/formats/graphml/index.ts +8 -0
  323. package/src/formats/graphml/tree.ts +318 -0
  324. package/src/formats/json/dialect.ts +317 -0
  325. package/src/formats/json/exporter.ts +1616 -0
  326. package/src/formats/json/importer.ts +2271 -0
  327. package/src/formats/json/index.ts +8 -0
  328. package/src/formats/neo4j/exporter.ts +1287 -0
  329. package/src/formats/neo4j/header.ts +156 -0
  330. package/src/formats/neo4j/importer.ts +1220 -0
  331. package/src/formats/neo4j/index.ts +116 -0
  332. package/src/formats/pajek/exporter.ts +1000 -0
  333. package/src/formats/pajek/importer.ts +1311 -0
  334. package/src/formats/pajek/index.ts +7 -0
  335. package/src/formats/pajek/syntax.ts +307 -0
  336. package/src/index.ts +244 -0
  337. package/src/registry.ts +617 -0
  338. package/src/sniff.ts +397 -0
  339. package/src/types.ts +262 -0
@@ -0,0 +1,1607 @@
1
+ /**
2
+ * The GraphML importer (design sections 8.4 and 5.1; research note 07 section 2.3): a single-pass
3
+ * SAX-style reader over the streaming XML tokenizer that pushes scalars into the sink as elements
4
+ * close. `<key>` declarations become typed columns up front (boolean / int / long / float / double
5
+ * / string, with `<default>`, for the node, edge and graph domains; `for="all"` declares in all
6
+ * three with the same origin.id); `edgedefault` and per-edge `directed` go through the common
7
+ * DirectionResolver; a nested `<graph>` inside a node becomes the `parent` column (containment,
8
+ * never topology); `<port>` declarations and `<locator>` elements are reported and dropped while
9
+ * `sourceport` / `targetport` edge attributes are kept as role columns; hyperedges follow the
10
+ * `hyperedges` option (refuse, skip with a report entry, star, clique); yFiles `yfiles.type` keys
11
+ * become `json` columns holding the nested XML as a tree (origin.namespace "yfiles"), reported as
12
+ * a loss note because the structure, not the bytes, is preserved.
13
+ *
14
+ * Ids are coerced with the common rule (`ids: "canonical"` by default, design section 4.1); the
15
+ * edge attribute whose `attr.name` is `weightFrom` ("weight" by default) is THE weight and is
16
+ * passed to `addEdge` rather than stored as a column, with its declaration kept in
17
+ * `meta.weightOrigin`; a node key named `graphty:originalId` restores ids the exporter mangled.
18
+ */
19
+ import { GraphFormatError, INVALID_INDEX, MAX_COUNT, } from "@graphty/graph-format";
20
+ import { declareAttribute, declareResolved, losesPrecision, parseDeclaredValue, PRECISION_CODE, takenIn, uniqueColumnName, } from "../../common/attributes.js";
21
+ import { DirectionResolver } from "../../common/direction.js";
22
+ import { IdCoercer } from "../../common/ids.js";
23
+ import { textChunks, throwIfAborted } from "../../common/input.js";
24
+ import { reportSinkOptions, reportUnusedOptions, resolveImportOptions, } from "../../common/options.js";
25
+ import { ImportReportBuilder } from "../../common/report.js";
26
+ import { isWeightField, parseWeightText } from "../../common/weights.js";
27
+ import { isWhitespace, localName, tokenizeXml, XmlSyntaxError } from "../../common/xml.js";
28
+ import { EDGE_ID_COLUMN, EXTENSIONS, FORMAT, GRAPHML_ISSUE, GRAPHML_LOSS, GRAPHML_NAMESPACE, HYPEREDGE_HUB_COLUMN, LABEL_COLUMN, META_KEY, MIME_TYPES, ORIGINAL_ID_ATTRIBUTE, ORIGINAL_ID_COLUMN, PARENT_COLUMN, RESERVED_EDGE_NAMES, RESERVED_NODE_NAMES, SOURCE_PORT_COLUMN, TARGET_PORT_COLUMN, XSI_NAMESPACE, } from "./constants.js";
29
+ import { XmlTreeBuilder } from "./tree.js";
30
+ /** The per-format defaults of design section 8.4: text-cell ids, undirected when edgedefault is missing, weight key. */
31
+ const FORMAT_DEFAULTS = { ids: "canonical", defaultDirected: false, weightFrom: "weight" };
32
+ /** Bytes inspected by sniff(). */
33
+ const SNIFF_BYTES = 4096;
34
+ const YFILES_MODES = new Set(["json", "skip"]);
35
+ /** Elements between two checks of the cancellation signal. */
36
+ const ABORT_CHECK_INTERVAL = 64;
37
+ /** The common options the GraphML importer reads (the rest is reported by reportUnusedOptions). */
38
+ const USED_OPTIONS = new Set([
39
+ "ids",
40
+ "addMissingNodes",
41
+ "duplicateEdges",
42
+ "selfLoops",
43
+ "onMixedDirection",
44
+ "defaultDirected",
45
+ "weightFrom",
46
+ "weightDtype",
47
+ "long",
48
+ "restoreMangledIds",
49
+ "hyperedges",
50
+ "errorLimit",
51
+ "signal",
52
+ "onProgress",
53
+ ]);
54
+ /** Where the reader is, by the innermost open element. */
55
+ var Ctx;
56
+ (function (Ctx) {
57
+ Ctx[Ctx["Graphml"] = 0] = "Graphml";
58
+ Ctx[Ctx["Desc"] = 1] = "Desc";
59
+ Ctx[Ctx["Key"] = 2] = "Key";
60
+ Ctx[Ctx["Default"] = 3] = "Default";
61
+ Ctx[Ctx["Graph"] = 4] = "Graph";
62
+ Ctx[Ctx["Node"] = 5] = "Node";
63
+ Ctx[Ctx["Edge"] = 6] = "Edge";
64
+ Ctx[Ctx["Hyperedge"] = 7] = "Hyperedge";
65
+ Ctx[Ctx["Data"] = 8] = "Data";
66
+ Ctx[Ctx["Capture"] = 9] = "Capture";
67
+ Ctx[Ctx["Skip"] = 10] = "Skip";
68
+ })(Ctx || (Ctx = {}));
69
+ /**
70
+ * A growable bit set over node indices, marking the indices declared by a `<node>` element so a
71
+ * second declaration is reported and a node created by an edge reference is not.
72
+ */
73
+ class IndexFlags {
74
+ constructor() {
75
+ this.words = new Uint32Array(64);
76
+ }
77
+ /**
78
+ * Whether an index is marked.
79
+ * @param index - the node index
80
+ * @returns true when set
81
+ */
82
+ has(index) {
83
+ const w = index >>> 5;
84
+ return w < this.words.length && ((this.words[w] >>> (index & 31)) & 1) === 1;
85
+ }
86
+ /**
87
+ * Mark an index.
88
+ * @param index - the node index
89
+ */
90
+ add(index) {
91
+ const w = index >>> 5;
92
+ if (w >= this.words.length) {
93
+ let size = this.words.length * 2;
94
+ while (size <= w) {
95
+ size *= 2;
96
+ }
97
+ const grown = new Uint32Array(size);
98
+ grown.set(this.words);
99
+ this.words = grown;
100
+ }
101
+ this.words[w] |= 1 << (index & 31);
102
+ }
103
+ }
104
+ /**
105
+ * The event handler that turns the tokenizer's events into sink calls.
106
+ */
107
+ class GraphmlReader {
108
+ /**
109
+ * Create a reader for one import call.
110
+ * @param sink - the sink
111
+ * @param report - the report
112
+ * @param options - the resolved common options
113
+ * @param yfilesMode - the resolved yfiles option
114
+ */
115
+ constructor(sink, report, options, yfilesMode) {
116
+ this.ctx = [];
117
+ this.keys = new Map();
118
+ this.graphs = [];
119
+ this.nodes = [];
120
+ this.declared = new IndexFlags();
121
+ this.edgeIds = new Set();
122
+ this.elementsSinceCheck = 0;
123
+ /** Mangled id text -> the original id restored from `graphty:originalId`, for edge endpoints. */
124
+ this.restored = new Map();
125
+ this.graphColumnNames = new Set();
126
+ this.pendingKey = null;
127
+ this.edge = null;
128
+ this.hyperedge = null;
129
+ this.content = {
130
+ key: null,
131
+ pendingKey: null,
132
+ domain: "node",
133
+ text: "",
134
+ tree: null,
135
+ line: 0,
136
+ };
137
+ this.descTarget = null;
138
+ this.descText = "";
139
+ this.description = null;
140
+ this.graphSeen = false;
141
+ this.graphId = null;
142
+ this.edgedefault = null;
143
+ this.namespaces = {};
144
+ this.weightOrigin = null;
145
+ this.yfilesLossNoted = false;
146
+ this.hyperedgeCount = 0;
147
+ this.edgeIdHandle = INVALID_INDEX;
148
+ this.sourcePortHandle = INVALID_INDEX;
149
+ this.targetPortHandle = INVALID_INDEX;
150
+ this.parentHandle = INVALID_INDEX;
151
+ this.hubHandle = INVALID_INDEX;
152
+ this.sink = sink;
153
+ this.report = report;
154
+ this.options = options;
155
+ this.yfilesMode = yfilesMode;
156
+ this.coercer = new IdCoercer(options.ids);
157
+ this.direction = new DirectionResolver(sink, report, options.onMixedDirection);
158
+ }
159
+ // ------------------------------------------------------------------ tokenizer events
160
+ /**
161
+ * An element starts.
162
+ * @param name - the element name
163
+ * @param attrs - its attributes
164
+ * @param line - the line
165
+ */
166
+ start(name, attrs, line) {
167
+ const parent = this.ctx.length === 0 ? null : this.ctx[this.ctx.length - 1];
168
+ switch (parent) {
169
+ case Ctx.Capture:
170
+ this.captureStart(name, attrs);
171
+ return;
172
+ case Ctx.Skip:
173
+ case Ctx.Desc:
174
+ this.ctx.push(Ctx.Skip);
175
+ return;
176
+ case Ctx.Data:
177
+ case Ctx.Default:
178
+ this.captureStart(name, attrs);
179
+ return;
180
+ default:
181
+ break;
182
+ }
183
+ const local = localName(name);
184
+ switch (parent) {
185
+ case null:
186
+ if (local !== "graphml") {
187
+ this.report.fail(GRAPHML_ISSUE.NOT_GRAPHML, `the root element is <${name}>, not <graphml>`, {
188
+ line,
189
+ });
190
+ }
191
+ this.readRoot(attrs);
192
+ this.ctx.push(Ctx.Graphml);
193
+ return;
194
+ case Ctx.Graphml:
195
+ this.startInGraphml(name, local, attrs, line);
196
+ return;
197
+ case Ctx.Key:
198
+ this.startInKey(name, local, line);
199
+ return;
200
+ case Ctx.Graph:
201
+ this.startInGraph(name, local, attrs, line);
202
+ return;
203
+ case Ctx.Node:
204
+ this.startInNode(name, local, attrs, line);
205
+ return;
206
+ case Ctx.Edge:
207
+ this.startInEdge(name, local, attrs, line);
208
+ return;
209
+ case Ctx.Hyperedge:
210
+ this.startInHyperedge(name, local, attrs, line);
211
+ return;
212
+ default:
213
+ this.unknownElement(name, line);
214
+ }
215
+ }
216
+ /**
217
+ * An element ends.
218
+ * @param name - the element name
219
+ * @param _line - the line (unused: issues of an element cite its start line)
220
+ */
221
+ end(name, _line) {
222
+ const ctx = this.ctx.pop();
223
+ switch (ctx) {
224
+ case Ctx.Capture:
225
+ this.captureEnd();
226
+ return;
227
+ case Ctx.Skip:
228
+ case Ctx.Graphml:
229
+ case undefined:
230
+ return;
231
+ case Ctx.Desc:
232
+ this.finishDesc();
233
+ return;
234
+ case Ctx.Key:
235
+ this.finishKey();
236
+ return;
237
+ case Ctx.Default:
238
+ this.finishDefault();
239
+ return;
240
+ case Ctx.Data:
241
+ this.finishData();
242
+ return;
243
+ case Ctx.Node:
244
+ this.finishNode();
245
+ return;
246
+ case Ctx.Edge:
247
+ this.finishEdge();
248
+ return;
249
+ case Ctx.Hyperedge:
250
+ this.finishHyperedge();
251
+ return;
252
+ case Ctx.Graph:
253
+ this.graphs.pop();
254
+ return;
255
+ default: {
256
+ const value = ctx;
257
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown reader state ${String(value)}`, { name });
258
+ }
259
+ }
260
+ }
261
+ /**
262
+ * Character data.
263
+ * @param text - the text
264
+ * @param line - the line
265
+ */
266
+ text(text, line) {
267
+ const ctx = this.ctx.length === 0 ? null : this.ctx[this.ctx.length - 1];
268
+ switch (ctx) {
269
+ case Ctx.Capture:
270
+ this.content.tree?.text(text);
271
+ return;
272
+ case Ctx.Data:
273
+ case Ctx.Default:
274
+ if (this.content.tree === null) {
275
+ this.content.text += text;
276
+ }
277
+ else {
278
+ this.content.tree.text(text);
279
+ }
280
+ return;
281
+ case Ctx.Desc:
282
+ this.descText += text;
283
+ return;
284
+ case Ctx.Skip:
285
+ return;
286
+ default:
287
+ if (!isWhitespace(text)) {
288
+ this.report.warnOnce("validation-error", GRAPHML_ISSUE.STRAY_TEXT, "text where GraphML allows only elements was ignored", { line });
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * After the last event: the document must have held a graph; the metadata is written.
294
+ */
295
+ finish() {
296
+ if (!this.graphSeen) {
297
+ this.report.fail(GRAPHML_ISSUE.NO_GRAPH, "the document has no <graph> element");
298
+ }
299
+ const meta = { graphId: this.graphId, edgedefault: this.edgedefault, namespaces: this.namespaces };
300
+ this.sink.setMeta({
301
+ sourceFormat: FORMAT,
302
+ description: this.description ?? undefined,
303
+ weightOrigin: this.weightOrigin === null
304
+ ? undefined
305
+ : {
306
+ format: FORMAT,
307
+ id: this.weightOrigin.id,
308
+ title: this.weightOrigin.title,
309
+ type: this.weightOrigin.type,
310
+ namespace: null,
311
+ },
312
+ extra: { [META_KEY]: meta },
313
+ });
314
+ }
315
+ // ------------------------------------------------------------------ structure
316
+ /**
317
+ * Record the root element's namespace declarations.
318
+ * @param attrs - the root attributes
319
+ */
320
+ readRoot(attrs) {
321
+ for (const [key, value] of attrs) {
322
+ if (key.startsWith("xmlns:") && value !== GRAPHML_NAMESPACE && value !== XSI_NAMESPACE) {
323
+ this.namespaces[key.slice(6)] = value;
324
+ }
325
+ }
326
+ }
327
+ /**
328
+ * A child of `<graphml>`.
329
+ * @param name - the element name
330
+ * @param local - its local name
331
+ * @param attrs - its attributes
332
+ * @param line - the line
333
+ */
334
+ startInGraphml(name, local, attrs, line) {
335
+ switch (local) {
336
+ case "desc":
337
+ this.beginDesc("graph");
338
+ return;
339
+ case "key":
340
+ this.beginKey(attrs, line);
341
+ return;
342
+ case "graph":
343
+ this.beginGraph(attrs, line, INVALID_INDEX);
344
+ return;
345
+ case "data":
346
+ this.beginData(attrs, line, "graph");
347
+ return;
348
+ default:
349
+ this.unknownElement(name, line);
350
+ }
351
+ }
352
+ /**
353
+ * A child of `<key>`.
354
+ * @param name - the element name
355
+ * @param local - its local name
356
+ * @param line - the line
357
+ */
358
+ startInKey(name, local, line) {
359
+ switch (local) {
360
+ case "desc":
361
+ this.beginDesc("key");
362
+ return;
363
+ case "default":
364
+ this.content.key = null;
365
+ this.content.pendingKey = this.pendingKey;
366
+ this.content.text = "";
367
+ this.content.tree = null;
368
+ this.content.line = line;
369
+ this.ctx.push(Ctx.Default);
370
+ return;
371
+ default:
372
+ this.unknownElement(name, line);
373
+ }
374
+ }
375
+ /**
376
+ * A child of `<graph>`.
377
+ * @param name - the element name
378
+ * @param local - its local name
379
+ * @param attrs - its attributes
380
+ * @param line - the line
381
+ */
382
+ startInGraph(name, local, attrs, line) {
383
+ switch (local) {
384
+ case "desc":
385
+ if (this.graphs.length === 1) {
386
+ this.beginDesc("graph");
387
+ }
388
+ else {
389
+ this.ctx.push(Ctx.Skip);
390
+ }
391
+ return;
392
+ case "data":
393
+ if (this.graphs.length === 1) {
394
+ this.beginData(attrs, line, "graph");
395
+ }
396
+ else {
397
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.NESTED_GRAPH_DATA, "data of a nested <graph> cannot be kept; only the top-level graph has attributes", { line });
398
+ this.ctx.push(Ctx.Skip);
399
+ }
400
+ return;
401
+ case "node":
402
+ this.beginNode(attrs, line);
403
+ return;
404
+ case "edge":
405
+ this.beginEdge(attrs, line);
406
+ return;
407
+ case "hyperedge":
408
+ this.beginHyperedge(attrs, line);
409
+ return;
410
+ case "locator":
411
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.LOCATOR_DROPPED, "<locator> references are not followed", { line });
412
+ this.ctx.push(Ctx.Skip);
413
+ return;
414
+ default:
415
+ this.unknownElement(name, line);
416
+ }
417
+ }
418
+ /**
419
+ * A child of `<node>`.
420
+ * @param name - the element name
421
+ * @param local - its local name
422
+ * @param attrs - its attributes
423
+ * @param line - the line
424
+ */
425
+ startInNode(name, local, attrs, line) {
426
+ switch (local) {
427
+ case "desc":
428
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.DESC_DROPPED, "node and edge <desc> texts are not kept", {
429
+ line,
430
+ });
431
+ this.ctx.push(Ctx.Skip);
432
+ return;
433
+ case "data":
434
+ this.beginData(attrs, line, "node");
435
+ return;
436
+ case "port":
437
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.PORT_DECLARATION, "<port> declarations are not kept; sourceport / targetport edge attributes are", { line });
438
+ this.ctx.push(Ctx.Skip);
439
+ return;
440
+ case "graph": {
441
+ const node = this.nodes[this.nodes.length - 1];
442
+ this.materializeNode(node);
443
+ this.beginGraph(attrs, line, node.index);
444
+ return;
445
+ }
446
+ case "locator":
447
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.LOCATOR_DROPPED, "<locator> references are not followed", { line });
448
+ this.ctx.push(Ctx.Skip);
449
+ return;
450
+ default:
451
+ this.unknownElement(name, line);
452
+ }
453
+ }
454
+ /**
455
+ * A child of `<edge>`.
456
+ * @param name - the element name
457
+ * @param local - its local name
458
+ * @param attrs - its attributes
459
+ * @param line - the line
460
+ */
461
+ startInEdge(name, local, attrs, line) {
462
+ switch (local) {
463
+ case "desc":
464
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.DESC_DROPPED, "node and edge <desc> texts are not kept", {
465
+ line,
466
+ });
467
+ this.ctx.push(Ctx.Skip);
468
+ return;
469
+ case "data":
470
+ this.beginData(attrs, line, "edge");
471
+ return;
472
+ default:
473
+ this.unknownElement(name, line);
474
+ }
475
+ }
476
+ /**
477
+ * A child of `<hyperedge>` (under the star / clique policies).
478
+ * @param name - the element name
479
+ * @param local - its local name
480
+ * @param attrs - its attributes
481
+ * @param line - the line
482
+ */
483
+ startInHyperedge(name, local, attrs, line) {
484
+ switch (local) {
485
+ case "endpoint":
486
+ this.readEndpoint(attrs, line);
487
+ this.ctx.push(Ctx.Skip);
488
+ return;
489
+ case "data":
490
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.HYPEREDGE_DATA_DROPPED, "hyperedge data are not kept by the star / clique expansions", { line });
491
+ this.ctx.push(Ctx.Skip);
492
+ return;
493
+ case "desc":
494
+ this.ctx.push(Ctx.Skip);
495
+ return;
496
+ default:
497
+ this.unknownElement(name, line);
498
+ }
499
+ }
500
+ /**
501
+ * Report an element the schema does not define at this place and skip its subtree.
502
+ * @param name - the element name
503
+ * @param line - the line
504
+ */
505
+ unknownElement(name, line) {
506
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.UNKNOWN_ELEMENT, `element <${name}> is not GraphML and was skipped`, {
507
+ line,
508
+ element: name,
509
+ });
510
+ this.ctx.push(Ctx.Skip);
511
+ }
512
+ /**
513
+ * Open a `<desc>`.
514
+ * @param target - what the text describes
515
+ */
516
+ beginDesc(target) {
517
+ this.descTarget = target;
518
+ this.descText = "";
519
+ this.ctx.push(Ctx.Desc);
520
+ }
521
+ /** Close a `<desc>`. */
522
+ finishDesc() {
523
+ const text = this.descText.trim();
524
+ if (this.descTarget === "key" && this.pendingKey !== null) {
525
+ this.pendingKey.desc = text;
526
+ }
527
+ else if (this.descTarget === "graph" && text.length > 0) {
528
+ this.description = text;
529
+ }
530
+ this.descTarget = null;
531
+ this.descText = "";
532
+ }
533
+ // ------------------------------------------------------------------ graphs
534
+ /**
535
+ * Open a `<graph>`: the top-level one sets the sink's direction (rule 1 of design section
536
+ * 8.4); a nested one records its container as the parent of its nodes.
537
+ * @param attrs - the graph attributes
538
+ * @param line - the line
539
+ * @param parent - the containing node's index, or INVALID_INDEX
540
+ */
541
+ beginGraph(attrs, line, parent) {
542
+ const top = this.graphs.length === 0;
543
+ const edgedefault = attrs.get("edgedefault");
544
+ let directed;
545
+ if (edgedefault === "directed" || edgedefault === "undirected") {
546
+ directed = edgedefault === "directed";
547
+ }
548
+ else if (edgedefault === undefined) {
549
+ directed = top ? this.options.defaultDirected : this.graphs[this.graphs.length - 1].directed;
550
+ this.report.warning("validation-error", GRAPHML_ISSUE.EDGEDEFAULT_MISSING, `<graph> has no edgedefault; read as ${directed ? "directed" : "undirected"}`, { line });
551
+ }
552
+ else {
553
+ directed = top ? this.options.defaultDirected : this.graphs[this.graphs.length - 1].directed;
554
+ this.report.error("validation-error", GRAPHML_ISSUE.INVALID_EDGEDEFAULT, `edgedefault "${edgedefault}" is neither directed nor undirected; read as ${directed ? "directed" : "undirected"}`, { line });
555
+ }
556
+ if (top && this.graphSeen) {
557
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.MULTIPLE_GRAPHS, "the document holds more than one top-level <graph>; their nodes and edges are merged into one graph", { line, element: attrs.get("id") ?? null });
558
+ }
559
+ else if (top) {
560
+ this.graphSeen = true;
561
+ this.graphId = attrs.get("id") ?? null;
562
+ this.edgedefault = directed ? "directed" : "undirected";
563
+ this.direction.setHeader(directed, { line });
564
+ const nodes = this.hint(attrs.get("parse.nodes"), "parse.nodes", line);
565
+ const edges = this.hint(attrs.get("parse.edges"), "parse.edges", line);
566
+ if (nodes !== null || edges !== null) {
567
+ try {
568
+ this.sink.reserve(nodes ?? undefined, edges ?? undefined);
569
+ }
570
+ catch (err) {
571
+ if (!(err instanceof GraphFormatError)) {
572
+ throw err;
573
+ }
574
+ this.report.warning("validation-error", GRAPHML_ISSUE.COUNT_HINT, `count hint refused: ${err.message}`, {
575
+ line,
576
+ });
577
+ }
578
+ }
579
+ }
580
+ this.graphs.push({ directed, parent });
581
+ this.ctx.push(Ctx.Graph);
582
+ }
583
+ /**
584
+ * A `parse.nodes` / `parse.edges` hint the sink can reserve; anything else is reported and ignored.
585
+ * @param text - the attribute text, or undefined
586
+ * @param name - the attribute name
587
+ * @param line - the line
588
+ * @returns the count, or null
589
+ */
590
+ hint(text, name, line) {
591
+ if (text === undefined) {
592
+ return null;
593
+ }
594
+ const n = hint(text);
595
+ if (n === null || n > MAX_COUNT) {
596
+ this.report.warning("validation-error", GRAPHML_ISSUE.COUNT_HINT, `<graph ${name}="${text}"> is not a count the sink can reserve; ignored`, { line, element: name });
597
+ return null;
598
+ }
599
+ return n;
600
+ }
601
+ // ------------------------------------------------------------------ keys
602
+ /**
603
+ * Open a `<key>`; the column is declared at `</key>` once its default is known.
604
+ * @param attrs - the key attributes
605
+ * @param line - the line
606
+ */
607
+ beginKey(attrs, line) {
608
+ const id = attrs.get("id") ?? null;
609
+ const forText = attrs.get("for") ?? "all";
610
+ let domains;
611
+ switch (forText) {
612
+ case "all":
613
+ domains = ["graph", "node", "edge"];
614
+ break;
615
+ case "graph":
616
+ case "node":
617
+ case "edge":
618
+ domains = [forText];
619
+ break;
620
+ case "hyperedge":
621
+ case "port":
622
+ case "endpoint":
623
+ this.report.warning("unsupported", GRAPHML_ISSUE.KEY_DOMAIN_UNSUPPORTED, `key "${id ?? ""}" is declared for ${forText} elements, which are not kept`, { line, element: id });
624
+ domains = [];
625
+ break;
626
+ default:
627
+ this.report.error("validation-error", GRAPHML_ISSUE.KEY_FOR_INVALID, `key "${id ?? ""}" has an unknown for="${forText}"`, { line, element: id });
628
+ domains = [];
629
+ }
630
+ this.pendingKey = {
631
+ id,
632
+ attrName: attrs.get("attr.name") ?? null,
633
+ attrType: attrs.get("attr.type") ?? null,
634
+ yfilesType: attrs.get("yfiles.type") ?? null,
635
+ domains,
636
+ line,
637
+ desc: "",
638
+ defaultText: null,
639
+ defaultTree: undefined,
640
+ };
641
+ this.ctx.push(Ctx.Key);
642
+ }
643
+ /** Close a `<key>`: declare its columns. */
644
+ finishKey() {
645
+ const key = this.pendingKey;
646
+ this.pendingKey = null;
647
+ if (key === null) {
648
+ return;
649
+ }
650
+ const where = { line: key.line, element: key.id };
651
+ if (key.id === null || key.id.length === 0) {
652
+ this.report.error("validation-error", GRAPHML_ISSUE.KEY_MISSING_ID, "<key> without an id", where);
653
+ return;
654
+ }
655
+ if (this.keys.has(key.id)) {
656
+ this.report.error("validation-error", GRAPHML_ISSUE.DUPLICATE_KEY, `key "${key.id}" is declared twice`, where);
657
+ return;
658
+ }
659
+ const yfiles = key.yfilesType !== null;
660
+ const name = key.attrName !== null && key.attrName.length > 0 ? key.attrName : key.id;
661
+ const skipped = yfiles && this.yfilesMode === "skip";
662
+ if (skipped) {
663
+ this.report.warning("unsupported", GRAPHML_ISSUE.YFILES_SKIPPED, `yFiles key "${key.id}" (${key.yfilesType ?? ""}) skipped per the yfiles option`, where);
664
+ }
665
+ let weight = false;
666
+ let originalId = false;
667
+ let node = null;
668
+ let edge = null;
669
+ let graph = null;
670
+ for (const domain of key.domains) {
671
+ if (skipped) {
672
+ continue;
673
+ }
674
+ if (domain === "edge" && !yfiles && isWeightField(name, this.options.weightFrom)) {
675
+ weight = true;
676
+ this.weightOrigin ?? (this.weightOrigin = { id: key.id, title: key.attrName, type: key.attrType });
677
+ continue;
678
+ }
679
+ if (domain === "node" && !yfiles && name === ORIGINAL_ID_ATTRIBUTE) {
680
+ if (this.options.restoreMangledIds) {
681
+ originalId = true;
682
+ continue;
683
+ }
684
+ node = this.declareOriginalIdColumn(key);
685
+ continue;
686
+ }
687
+ try {
688
+ const target = yfiles ? this.declareYfiles(key, name, domain) : this.declareTyped(key, name, domain);
689
+ if (domain === "node") {
690
+ node = target;
691
+ }
692
+ else if (domain === "edge") {
693
+ edge = target;
694
+ }
695
+ else {
696
+ graph = target;
697
+ }
698
+ }
699
+ catch (err) {
700
+ this.report.recordError(err, where);
701
+ }
702
+ }
703
+ this.keys.set(key.id, {
704
+ id: key.id,
705
+ name,
706
+ node,
707
+ edge,
708
+ graph,
709
+ weight,
710
+ originalId,
711
+ yfiles,
712
+ skipped,
713
+ domains: key.domains,
714
+ });
715
+ }
716
+ /**
717
+ * Declare the column of a typed (or untyped, then string) key in one domain.
718
+ * @param key - the key
719
+ * @param name - the resolved attribute name
720
+ * @param domain - the domain
721
+ * @returns the column target
722
+ */
723
+ declareTyped(key, name, domain) {
724
+ const id = key.id ?? "";
725
+ const declared = declareAttribute({
726
+ format: FORMAT,
727
+ id,
728
+ title: key.attrName !== null && key.attrName.length > 0 ? key.attrName : null,
729
+ type: key.attrType,
730
+ defaultText: key.defaultTree === undefined ? key.defaultText : null,
731
+ listSyntax: "comma",
732
+ role: domain !== "graph" && name === LABEL_COLUMN ? "label" : null,
733
+ long: this.options.long,
734
+ taken: this.takenIn(domain),
735
+ });
736
+ for (const issue of declared.issues) {
737
+ this.report.warning(issue.category, issue.code, issue.message, { line: key.line, element: id });
738
+ }
739
+ if (key.defaultTree !== undefined) {
740
+ this.report.warning("validation-error", GRAPHML_ISSUE.DATA_NESTED, `the <default> of key "${id}" holds elements; a ${key.attrType ?? "string"} default must be text`, { line: key.line, element: id });
741
+ }
742
+ const { decl } = declared;
743
+ if (key.desc.length > 0) {
744
+ decl.extra = { desc: key.desc };
745
+ }
746
+ return this.declareTarget(domain, decl, declared.spec, declared.listSyntax);
747
+ }
748
+ /**
749
+ * Declare the json column of a yFiles key in one domain.
750
+ * @param key - the key
751
+ * @param name - the resolved attribute name
752
+ * @param domain - the domain
753
+ * @returns the column target
754
+ */
755
+ declareYfiles(key, name, domain) {
756
+ const id = key.id ?? "";
757
+ const columnName = uniqueColumnName(name, id, this.takenIn(domain));
758
+ const decl = {
759
+ name: columnName,
760
+ dtype: "json",
761
+ nullable: true,
762
+ origin: {
763
+ format: FORMAT,
764
+ id,
765
+ title: key.attrName !== null && key.attrName !== columnName ? key.attrName : null,
766
+ type: key.yfilesType,
767
+ namespace: "yfiles",
768
+ },
769
+ };
770
+ if (key.defaultTree !== undefined) {
771
+ decl.default = key.defaultTree;
772
+ }
773
+ else if (key.defaultText !== null) {
774
+ decl.default = key.defaultText;
775
+ }
776
+ if (key.desc.length > 0) {
777
+ decl.extra = { desc: key.desc };
778
+ }
779
+ return this.declareTarget(domain, decl, null, "comma");
780
+ }
781
+ /**
782
+ * Declare the `graphty.originalId` column that keeps the exporter's original ids when
783
+ * restoreMangledIds is off.
784
+ * @param key - the key
785
+ * @returns the column target
786
+ */
787
+ declareOriginalIdColumn(key) {
788
+ const decl = {
789
+ name: uniqueColumnName(ORIGINAL_ID_COLUMN, key.id, this.takenIn("node")),
790
+ dtype: "string",
791
+ nullable: true,
792
+ role: "originalId",
793
+ origin: { format: FORMAT, id: key.id, title: key.attrName, type: key.attrType, namespace: null },
794
+ };
795
+ return this.declareTarget("node", decl, null, "comma");
796
+ }
797
+ /**
798
+ * Push a declaration into the sink for one domain through the shared design section 5.6 rule:
799
+ * a role another column already holds is dropped (reported), a name the sink holds with
800
+ * another shape is renamed `<name>#<id>` (reported).
801
+ * @param domain - the domain
802
+ * @param decl - the declaration
803
+ * @param spec - the value spec, or null for json
804
+ * @param listSyntax - the list syntax
805
+ * @returns the target
806
+ */
807
+ declareTarget(domain, decl, spec, listSyntax) {
808
+ if (domain === "graph") {
809
+ this.sink.setGraphValue(decl.name, undefined, decl);
810
+ this.graphColumnNames.add(decl.name);
811
+ return { name: decl.name, handle: INVALID_INDEX, decl, spec, listSyntax };
812
+ }
813
+ const resolved = declareResolved(this.sink, domain, decl, this.report, { element: decl.name });
814
+ return { name: resolved.decl.name, handle: resolved.handle, decl: resolved.decl, spec, listSyntax };
815
+ }
816
+ /**
817
+ * The `taken` predicate of a domain: the reserved XML-derived names plus the sink's columns.
818
+ * @param domain - the domain
819
+ * @returns the predicate
820
+ */
821
+ takenIn(domain) {
822
+ if (domain === "graph") {
823
+ return (name) => this.graphColumnNames.has(name);
824
+ }
825
+ const reserved = domain === "node" ? RESERVED_NODE_NAMES : RESERVED_EDGE_NAMES;
826
+ const inSink = takenIn(this.sink, domain);
827
+ return (name) => reserved.has(name) || inSink(name);
828
+ }
829
+ // ------------------------------------------------------------------ data and defaults
830
+ /**
831
+ * Open a `<data>`.
832
+ * @param attrs - its attributes
833
+ * @param line - the line
834
+ * @param domain - the domain of the element holding it
835
+ */
836
+ beginData(attrs, line, domain) {
837
+ const keyId = attrs.get("key");
838
+ const { content } = this;
839
+ content.key = null;
840
+ content.pendingKey = null;
841
+ content.domain = domain;
842
+ content.text = "";
843
+ content.tree = null;
844
+ content.line = line;
845
+ if (keyId === undefined) {
846
+ this.report.error("validation-error", GRAPHML_ISSUE.DATA_MISSING_KEY, "<data> without a key attribute", {
847
+ line,
848
+ });
849
+ }
850
+ else {
851
+ const key = this.keys.get(keyId);
852
+ if (key === undefined) {
853
+ this.report.error("validation-error", GRAPHML_ISSUE.UNKNOWN_KEY, `<data> references undeclared key "${keyId}"`, { line, element: keyId });
854
+ }
855
+ else if (!key.skipped && !key.domains.includes(domain)) {
856
+ this.report.warnOnce("validation-error", GRAPHML_ISSUE.KEY_DOMAIN, `key "${keyId}" is declared for ${key.domains.join(" / ")} elements but used on a ${domain}; ignored`, { line, element: keyId });
857
+ }
858
+ else {
859
+ content.key = key;
860
+ }
861
+ }
862
+ this.ctx.push(Ctx.Data);
863
+ }
864
+ /**
865
+ * A child element inside `<data>` / `<default>`: the content becomes a tree.
866
+ * @param name - the element name
867
+ * @param attrs - its attributes
868
+ */
869
+ captureStart(name, attrs) {
870
+ const { content } = this;
871
+ if (content.tree === null) {
872
+ content.tree = new XmlTreeBuilder();
873
+ if (content.text.length > 0) {
874
+ content.tree.text(content.text);
875
+ content.text = "";
876
+ }
877
+ }
878
+ content.tree.start(name, attrs);
879
+ this.ctx.push(Ctx.Capture);
880
+ }
881
+ /** A captured element ends. */
882
+ captureEnd() {
883
+ this.content.tree?.end();
884
+ }
885
+ /** Close a `<default>`: keep its text or tree for the key. */
886
+ finishDefault() {
887
+ const { content } = this;
888
+ const key = content.pendingKey;
889
+ if (key === null) {
890
+ return;
891
+ }
892
+ if (content.tree !== null) {
893
+ key.defaultTree = content.tree.finish();
894
+ }
895
+ else {
896
+ key.defaultText = content.text;
897
+ }
898
+ content.tree = null;
899
+ content.text = "";
900
+ content.pendingKey = null;
901
+ }
902
+ /** Close a `<data>`: parse the value and route it to its element. */
903
+ finishData() {
904
+ const { content } = this;
905
+ const { key } = content;
906
+ const value = content.tree === null ? content.text : content.tree.finish();
907
+ content.tree = null;
908
+ content.text = "";
909
+ content.key = null;
910
+ if (key === null || key.skipped) {
911
+ return;
912
+ }
913
+ const where = { line: content.line, element: key.id };
914
+ switch (content.domain) {
915
+ case "node": {
916
+ const node = this.nodes[this.nodes.length - 1];
917
+ if (key.originalId) {
918
+ if (typeof value === "string") {
919
+ node.restoredId = value;
920
+ }
921
+ return;
922
+ }
923
+ if (key.node === null) {
924
+ return;
925
+ }
926
+ const parsed = this.parseValue(key.node, value, where);
927
+ if (parsed === undefined) {
928
+ return;
929
+ }
930
+ if (node.index === INVALID_INDEX) {
931
+ node.pending.push(key.node.handle, parsed);
932
+ }
933
+ else if (!node.failed) {
934
+ this.sink.setNodeValue(key.node.handle, node.index, parsed);
935
+ }
936
+ return;
937
+ }
938
+ case "edge": {
939
+ const { edge } = this;
940
+ if (edge === null) {
941
+ return;
942
+ }
943
+ if (key.weight) {
944
+ if (typeof value !== "string") {
945
+ this.report.error("validation-error", GRAPHML_ISSUE.DATA_NESTED, "a weight value must be text", where);
946
+ return;
947
+ }
948
+ try {
949
+ edge.weight = parseWeightText(value);
950
+ }
951
+ catch (err) {
952
+ this.skipEdge(edge);
953
+ this.report.recordError(err, where);
954
+ }
955
+ return;
956
+ }
957
+ if (key.edge === null) {
958
+ return;
959
+ }
960
+ const parsed = this.parseValue(key.edge, value, where);
961
+ if (parsed !== undefined) {
962
+ edge.pending.push(key.edge.handle, parsed);
963
+ }
964
+ return;
965
+ }
966
+ case "graph": {
967
+ if (key.graph === null) {
968
+ return;
969
+ }
970
+ const parsed = this.parseValue(key.graph, value, where);
971
+ if (parsed !== undefined) {
972
+ try {
973
+ this.sink.setGraphValue(key.graph.name, parsed, key.graph.decl);
974
+ }
975
+ catch (err) {
976
+ this.report.recordError(err, where);
977
+ }
978
+ }
979
+ return;
980
+ }
981
+ default: {
982
+ const { domain } = content;
983
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown data domain ${String(domain)}`, {});
984
+ }
985
+ }
986
+ }
987
+ /**
988
+ * Parse the content of a `<data>` by its column's declaration.
989
+ * @param target - the column
990
+ * @param value - the text, or the tree of a nested subtree
991
+ * @param where - the location for issues
992
+ * @returns the value to write, or undefined when there is none (blank, or an error was recorded)
993
+ */
994
+ parseValue(target, value, where) {
995
+ if (target.spec === null) {
996
+ if (!this.yfilesLossNoted) {
997
+ this.yfilesLossNoted = true;
998
+ this.report.loss(GRAPHML_LOSS.YFILES_JSON, "yFiles nested XML is kept as a JSON tree: structure preserved, not byte-exact", target.name);
999
+ }
1000
+ return value;
1001
+ }
1002
+ if (typeof value !== "string") {
1003
+ this.report.error("validation-error", GRAPHML_ISSUE.DATA_NESTED, `<data> of the ${target.spec.declared} attribute "${target.name}" holds elements, not text`, where);
1004
+ return undefined;
1005
+ }
1006
+ const { spec } = target;
1007
+ if (spec.kind !== "string" && isWhitespace(value)) {
1008
+ return undefined;
1009
+ }
1010
+ try {
1011
+ const parsed = parseDeclaredValue(value, spec, target.listSyntax);
1012
+ if (spec.precision && losesPrecision(spec, value)) {
1013
+ this.report.warnOnce("precision", PRECISION_CODE, `long values of "${target.name}" beyond 2^53 are stored as the nearest double`, { line: where.line, element: target.name });
1014
+ }
1015
+ return parsed;
1016
+ }
1017
+ catch (err) {
1018
+ this.report.recordError(err, where);
1019
+ return undefined;
1020
+ }
1021
+ }
1022
+ // ------------------------------------------------------------------ nodes
1023
+ /**
1024
+ * Open a `<node>`; it is added to the sink when its data is complete (or a nested graph
1025
+ * starts), so a `graphty:originalId` value can replace its id.
1026
+ * @param attrs - the node attributes
1027
+ * @param line - the line
1028
+ */
1029
+ beginNode(attrs, line) {
1030
+ const graph = this.graphs[this.graphs.length - 1];
1031
+ const state = {
1032
+ id: null,
1033
+ idText: null,
1034
+ index: INVALID_INDEX,
1035
+ parent: graph.parent,
1036
+ pending: [],
1037
+ restoredId: null,
1038
+ failed: false,
1039
+ line,
1040
+ };
1041
+ const idText = attrs.get("id");
1042
+ if (idText === undefined || idText.length === 0) {
1043
+ this.skipNode(state);
1044
+ this.report.error("missing-value", GRAPHML_ISSUE.MISSING_ID, "<node> without an id", { line });
1045
+ }
1046
+ else {
1047
+ try {
1048
+ state.id = this.coercer.text(idText);
1049
+ state.idText = idText;
1050
+ this.reportMerge(idText, line);
1051
+ }
1052
+ catch (err) {
1053
+ this.skipNode(state);
1054
+ this.report.recordError(err, { line, element: idText });
1055
+ }
1056
+ }
1057
+ this.nodes.push(state);
1058
+ this.ctx.push(Ctx.Node);
1059
+ }
1060
+ /**
1061
+ * Add a node to the sink and flush its pending values.
1062
+ * @param node - the node state
1063
+ */
1064
+ materializeNode(node) {
1065
+ if (node.index !== INVALID_INDEX || node.failed) {
1066
+ return;
1067
+ }
1068
+ const id = node.restoredId ?? node.id;
1069
+ if (id === null) {
1070
+ node.failed = true;
1071
+ return;
1072
+ }
1073
+ if (node.restoredId !== null && node.idText !== null) {
1074
+ this.restored.set(node.idText, node.restoredId);
1075
+ }
1076
+ try {
1077
+ const existing = this.sink.indexOf(id);
1078
+ const index = this.sink.addNode(id);
1079
+ if (existing !== INVALID_INDEX && this.declared.has(index)) {
1080
+ this.report.warning("validation-error", GRAPHML_ISSUE.DUPLICATE_NODE, `node "${String(id)}" is declared twice; the declarations are merged`, { line: node.line, element: String(id) });
1081
+ }
1082
+ else {
1083
+ this.report.counts.nodes++;
1084
+ }
1085
+ this.declared.add(index);
1086
+ node.index = index;
1087
+ this.checkAbort();
1088
+ if (node.parent !== INVALID_INDEX) {
1089
+ this.sink.setNodeValue(this.parentColumn(), index, node.parent);
1090
+ }
1091
+ const { pending } = node;
1092
+ for (let i = 0; i < pending.length; i += 2) {
1093
+ this.sink.setNodeValue(pending[i], index, pending[i + 1]);
1094
+ }
1095
+ pending.length = 0;
1096
+ }
1097
+ catch (err) {
1098
+ this.skipNode(node);
1099
+ this.report.recordError(err, { line: node.line, element: String(id) });
1100
+ }
1101
+ }
1102
+ /**
1103
+ * Mark a node element skipped (once) before its error is recorded, so the report's counts
1104
+ * include it even when that error is the one that exceeds the limit.
1105
+ * @param node - the node state
1106
+ */
1107
+ skipNode(node) {
1108
+ if (!node.failed) {
1109
+ node.failed = true;
1110
+ this.report.counts.skippedNodes++;
1111
+ }
1112
+ }
1113
+ /** Close a `<node>`. */
1114
+ finishNode() {
1115
+ const node = this.nodes.pop();
1116
+ if (node !== undefined) {
1117
+ this.materializeNode(node);
1118
+ }
1119
+ }
1120
+ /**
1121
+ * The `parent` column, declared on first use.
1122
+ * @returns the handle
1123
+ */
1124
+ parentColumn() {
1125
+ if (this.parentHandle === INVALID_INDEX) {
1126
+ this.parentHandle = this.sink.declareNodeColumn({
1127
+ name: PARENT_COLUMN,
1128
+ dtype: "u32",
1129
+ role: "parent",
1130
+ refersTo: "node",
1131
+ nullable: true,
1132
+ origin: { format: FORMAT, id: null, title: null, type: "graph", namespace: null },
1133
+ });
1134
+ }
1135
+ return this.parentHandle;
1136
+ }
1137
+ // ------------------------------------------------------------------ edges
1138
+ /**
1139
+ * Open an `<edge>`; it is pushed at `</edge>` once its weight and data are known.
1140
+ * @param attrs - the edge attributes
1141
+ * @param line - the line
1142
+ */
1143
+ beginEdge(attrs, line) {
1144
+ const graph = this.graphs[this.graphs.length - 1];
1145
+ const edge = {
1146
+ source: null,
1147
+ target: null,
1148
+ id: attrs.get("id") ?? null,
1149
+ kind: graph.directed ? "directed" : "undirected",
1150
+ sourcePort: attrs.get("sourceport") ?? null,
1151
+ targetPort: attrs.get("targetport") ?? null,
1152
+ weight: undefined,
1153
+ pending: [],
1154
+ failed: false,
1155
+ line,
1156
+ };
1157
+ const where = { line, element: edge.id };
1158
+ const sourceText = attrs.get("source");
1159
+ const targetText = attrs.get("target");
1160
+ if (sourceText === undefined || sourceText.length === 0) {
1161
+ this.skipEdge(edge);
1162
+ this.report.error("missing-value", GRAPHML_ISSUE.MISSING_ENDPOINT, "<edge> without a source", where);
1163
+ }
1164
+ if (targetText === undefined || targetText.length === 0) {
1165
+ this.skipEdge(edge);
1166
+ this.report.error("missing-value", GRAPHML_ISSUE.MISSING_ENDPOINT, "<edge> without a target", where);
1167
+ }
1168
+ if (!edge.failed && sourceText !== undefined && targetText !== undefined) {
1169
+ try {
1170
+ edge.source = this.endpointId(sourceText, line);
1171
+ edge.target = this.endpointId(targetText, line);
1172
+ }
1173
+ catch (err) {
1174
+ this.skipEdge(edge);
1175
+ this.report.recordError(err, where);
1176
+ }
1177
+ }
1178
+ const directedText = attrs.get("directed");
1179
+ if (directedText !== undefined) {
1180
+ if (directedText === "true") {
1181
+ edge.kind = "directed";
1182
+ }
1183
+ else if (directedText === "false") {
1184
+ edge.kind = "undirected";
1185
+ }
1186
+ else {
1187
+ this.skipEdge(edge);
1188
+ this.report.error("validation-error", GRAPHML_ISSUE.INVALID_DIRECTED, `directed="${directedText}" is neither true nor false`, where);
1189
+ }
1190
+ }
1191
+ this.edge = edge;
1192
+ this.ctx.push(Ctx.Edge);
1193
+ }
1194
+ /**
1195
+ * Close an `<edge>`: push it through the direction resolver and write its values.
1196
+ */
1197
+ finishEdge() {
1198
+ const { edge } = this;
1199
+ this.edge = null;
1200
+ if (edge === null) {
1201
+ return;
1202
+ }
1203
+ if (edge.failed || edge.source === null || edge.target === null) {
1204
+ this.skipEdge(edge);
1205
+ return;
1206
+ }
1207
+ const element = edge.id ?? `${String(edge.source)} -> ${String(edge.target)}`;
1208
+ const where = { line: edge.line, element };
1209
+ try {
1210
+ if (!this.options.addMissingNodes) {
1211
+ this.requireNode(edge.source);
1212
+ this.requireNode(edge.target);
1213
+ }
1214
+ if (edge.id !== null && this.edgeIds.has(edge.id)) {
1215
+ throw new GraphFormatError("E_DUPLICATE_EDGE_ID", `edge id "${edge.id}" is used twice`, {
1216
+ id: edge.id,
1217
+ });
1218
+ }
1219
+ const before = this.sink.edgeCount;
1220
+ this.countEndpoints(edge.source, edge.target);
1221
+ const e = this.direction.addEdge(edge.source, edge.target, edge.kind, edge.weight, where);
1222
+ this.report.counts.edges += this.sink.edgeCount - before;
1223
+ this.checkAbort();
1224
+ if (edge.id !== null) {
1225
+ this.edgeIds.add(edge.id);
1226
+ this.sink.setEdgeValue(this.edgeIdColumn(), e, edge.id);
1227
+ }
1228
+ if (edge.sourcePort !== null) {
1229
+ this.sink.setEdgeValue(this.portColumn("source"), e, edge.sourcePort);
1230
+ }
1231
+ if (edge.targetPort !== null) {
1232
+ this.sink.setEdgeValue(this.portColumn("target"), e, edge.targetPort);
1233
+ }
1234
+ const { pending } = edge;
1235
+ for (let i = 0; i < pending.length; i += 2) {
1236
+ this.sink.setEdgeValue(pending[i], e, pending[i + 1]);
1237
+ }
1238
+ }
1239
+ catch (err) {
1240
+ this.skipEdge(edge);
1241
+ this.report.recordError(err, where);
1242
+ }
1243
+ }
1244
+ /**
1245
+ * Mark an edge (or hyperedge) element skipped once, before its error is recorded.
1246
+ * @param edge - the edge state
1247
+ */
1248
+ skipEdge(edge) {
1249
+ if (!edge.failed) {
1250
+ edge.failed = true;
1251
+ this.report.counts.skippedEdges++;
1252
+ }
1253
+ }
1254
+ /**
1255
+ * E_UNKNOWN_NODE when an endpoint was never declared (addMissingNodes false enforced by the
1256
+ * importer, whatever the sink allows).
1257
+ * @param id - the endpoint id
1258
+ */
1259
+ requireNode(id) {
1260
+ if (this.sink.indexOf(id) === INVALID_INDEX) {
1261
+ throw new GraphFormatError("E_UNKNOWN_NODE", `edge endpoint "${String(id)}" is not a declared node`, {
1262
+ id,
1263
+ });
1264
+ }
1265
+ }
1266
+ /**
1267
+ * The edge `id` column (role id, unique), declared on first use.
1268
+ * @returns the handle
1269
+ */
1270
+ edgeIdColumn() {
1271
+ if (this.edgeIdHandle === INVALID_INDEX) {
1272
+ this.edgeIdHandle = this.sink.declareEdgeColumn({
1273
+ name: EDGE_ID_COLUMN,
1274
+ dtype: "string",
1275
+ role: "id",
1276
+ unique: true,
1277
+ nullable: true,
1278
+ origin: { format: FORMAT, id: null, title: null, type: "id", namespace: null },
1279
+ });
1280
+ }
1281
+ return this.edgeIdHandle;
1282
+ }
1283
+ /**
1284
+ * The sourceport / targetport column, declared on first use.
1285
+ * @param side - which end
1286
+ * @returns the handle
1287
+ */
1288
+ portColumn(side) {
1289
+ if (side === "source") {
1290
+ if (this.sourcePortHandle === INVALID_INDEX) {
1291
+ this.sourcePortHandle = this.sink.declareEdgeColumn({
1292
+ name: SOURCE_PORT_COLUMN,
1293
+ dtype: "string",
1294
+ role: "sourcePort",
1295
+ nullable: true,
1296
+ origin: { format: FORMAT, id: null, title: null, type: "sourceport", namespace: null },
1297
+ });
1298
+ }
1299
+ return this.sourcePortHandle;
1300
+ }
1301
+ if (this.targetPortHandle === INVALID_INDEX) {
1302
+ this.targetPortHandle = this.sink.declareEdgeColumn({
1303
+ name: TARGET_PORT_COLUMN,
1304
+ dtype: "string",
1305
+ role: "targetPort",
1306
+ nullable: true,
1307
+ origin: { format: FORMAT, id: null, title: null, type: "targetport", namespace: null },
1308
+ });
1309
+ }
1310
+ return this.targetPortHandle;
1311
+ }
1312
+ // ------------------------------------------------------------------ hyperedges
1313
+ /**
1314
+ * Open a `<hyperedge>` per the hyperedges option.
1315
+ * @param attrs - its attributes
1316
+ * @param line - the line
1317
+ */
1318
+ beginHyperedge(attrs, line) {
1319
+ const id = attrs.get("id") ?? null;
1320
+ const where = { line, element: id };
1321
+ switch (this.options.hyperedges) {
1322
+ case "error":
1323
+ this.report.error("unsupported", GRAPHML_ISSUE.HYPEREDGE, 'hyperedge refused (hyperedges: "error")', where);
1324
+ throw this.report.abort("hyperedge refused", { code: GRAPHML_ISSUE.HYPEREDGE });
1325
+ case "skip":
1326
+ this.report.warnOnce("unsupported", GRAPHML_ISSUE.HYPEREDGE_SKIPPED, 'hyperedges are skipped (hyperedges: "skip"); use "star" or "clique" to expand them', where);
1327
+ this.report.counts.skippedEdges++;
1328
+ this.ctx.push(Ctx.Skip);
1329
+ return;
1330
+ case "star":
1331
+ case "clique":
1332
+ this.hyperedge = { id, endpoints: [], failed: false, line };
1333
+ this.ctx.push(Ctx.Hyperedge);
1334
+ return;
1335
+ default: {
1336
+ const policy = this.options.hyperedges;
1337
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown hyperedges policy ${String(policy)}`, {
1338
+ option: "hyperedges",
1339
+ });
1340
+ }
1341
+ }
1342
+ }
1343
+ /**
1344
+ * An `<endpoint>` of the open hyperedge.
1345
+ * @param attrs - its attributes
1346
+ * @param line - the line
1347
+ */
1348
+ readEndpoint(attrs, line) {
1349
+ const { hyperedge } = this;
1350
+ if (hyperedge === null) {
1351
+ return;
1352
+ }
1353
+ const where = { line, element: hyperedge.id };
1354
+ const nodeText = attrs.get("node");
1355
+ const typeText = attrs.get("type") ?? "undir";
1356
+ if (nodeText === undefined || nodeText.length === 0) {
1357
+ this.skipEdge(hyperedge);
1358
+ this.report.error("missing-value", GRAPHML_ISSUE.HYPEREDGE_ENDPOINT, "<endpoint> without a node", where);
1359
+ return;
1360
+ }
1361
+ if (typeText !== "in" && typeText !== "out" && typeText !== "undir") {
1362
+ this.skipEdge(hyperedge);
1363
+ this.report.error("validation-error", GRAPHML_ISSUE.HYPEREDGE_ENDPOINT, `endpoint type "${typeText}" is not in, out or undir`, where);
1364
+ return;
1365
+ }
1366
+ try {
1367
+ const node = this.endpointId(nodeText, line);
1368
+ hyperedge.endpoints.push({ node, type: typeText });
1369
+ }
1370
+ catch (err) {
1371
+ this.skipEdge(hyperedge);
1372
+ this.report.recordError(err, where);
1373
+ }
1374
+ }
1375
+ /** Close a `<hyperedge>`: expand it to a star or a clique. */
1376
+ finishHyperedge() {
1377
+ const { hyperedge } = this;
1378
+ this.hyperedge = null;
1379
+ if (hyperedge === null) {
1380
+ return;
1381
+ }
1382
+ if (hyperedge.failed) {
1383
+ return;
1384
+ }
1385
+ const where = { line: hyperedge.line, element: hyperedge.id };
1386
+ try {
1387
+ if (this.options.hyperedges === "star") {
1388
+ this.expandStar(hyperedge, where);
1389
+ }
1390
+ else {
1391
+ this.expandClique(hyperedge, where);
1392
+ }
1393
+ }
1394
+ catch (err) {
1395
+ this.skipEdge(hyperedge);
1396
+ this.report.recordError(err, where);
1397
+ }
1398
+ }
1399
+ /**
1400
+ * Star expansion: a synthetic hub node (marked in the `graphty.hyperedge` column) joined to
1401
+ * every endpoint; `in` endpoints point at the hub, `out` endpoints are pointed at by it,
1402
+ * `undir` endpoints are undirected.
1403
+ * @param hyperedge - the hyperedge
1404
+ * @param where - the location for issues
1405
+ */
1406
+ expandStar(hyperedge, where) {
1407
+ this.hyperedgeCount++;
1408
+ let hubId = hyperedge.id ?? `hyperedge${this.hyperedgeCount}`;
1409
+ for (let n = 2; this.sink.indexOf(hubId) !== INVALID_INDEX; n++) {
1410
+ hubId = `${hyperedge.id ?? `hyperedge${this.hyperedgeCount}`}#${n}`;
1411
+ }
1412
+ const hub = this.sink.addNode(hubId);
1413
+ this.declared.add(hub);
1414
+ this.report.counts.nodes++;
1415
+ if (this.hubHandle === INVALID_INDEX) {
1416
+ this.hubHandle = this.sink.declareNodeColumn({
1417
+ name: HYPEREDGE_HUB_COLUMN,
1418
+ dtype: "bool",
1419
+ nullable: true,
1420
+ origin: { format: FORMAT, id: null, title: null, type: "hyperedge", namespace: null },
1421
+ });
1422
+ }
1423
+ this.sink.setNodeValue(this.hubHandle, hub, true);
1424
+ for (const endpoint of hyperedge.endpoints) {
1425
+ const before = this.sink.edgeCount;
1426
+ switch (endpoint.type) {
1427
+ case "in":
1428
+ this.direction.addEdge(endpoint.node, hubId, "directed", undefined, where);
1429
+ break;
1430
+ case "out":
1431
+ this.direction.addEdge(hubId, endpoint.node, "directed", undefined, where);
1432
+ break;
1433
+ case "undir":
1434
+ this.direction.addEdge(endpoint.node, hubId, "undirected", undefined, where);
1435
+ break;
1436
+ default: {
1437
+ const { type } = endpoint;
1438
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown endpoint type ${String(type)}`, {});
1439
+ }
1440
+ }
1441
+ this.report.counts.edges += this.sink.edgeCount - before;
1442
+ }
1443
+ }
1444
+ /**
1445
+ * Clique expansion: every `in` endpoint points at every `out` endpoint; `undir` endpoints are
1446
+ * joined undirected to every other endpoint; two endpoints of the same direction are not joined.
1447
+ * @param hyperedge - the hyperedge
1448
+ * @param where - the location for issues
1449
+ */
1450
+ expandClique(hyperedge, where) {
1451
+ const { endpoints } = hyperedge;
1452
+ for (let i = 0; i < endpoints.length; i++) {
1453
+ for (let j = i + 1; j < endpoints.length; j++) {
1454
+ const a = endpoints[i];
1455
+ const b = endpoints[j];
1456
+ const before = this.sink.edgeCount;
1457
+ if (a.type === "undir" || b.type === "undir") {
1458
+ this.direction.addEdge(a.node, b.node, "undirected", undefined, where);
1459
+ }
1460
+ else if (a.type === "in" && b.type === "out") {
1461
+ this.direction.addEdge(a.node, b.node, "directed", undefined, where);
1462
+ }
1463
+ else if (a.type === "out" && b.type === "in") {
1464
+ this.direction.addEdge(b.node, a.node, "directed", undefined, where);
1465
+ }
1466
+ else {
1467
+ continue;
1468
+ }
1469
+ this.report.counts.edges += this.sink.edgeCount - before;
1470
+ }
1471
+ }
1472
+ }
1473
+ // ------------------------------------------------------------------ options and ids
1474
+ /**
1475
+ * Count the endpoints of an edge the file never declared as nodes of the sink (created under
1476
+ * addMissingNodes), so the report describes what the sink holds (design section 8.6).
1477
+ * @param source - the source id
1478
+ * @param target - the target id
1479
+ */
1480
+ countEndpoints(source, target) {
1481
+ if (!this.options.addMissingNodes) {
1482
+ return;
1483
+ }
1484
+ let created = 0;
1485
+ if (this.sink.indexOf(source) === INVALID_INDEX) {
1486
+ created++;
1487
+ }
1488
+ if (source !== target && this.sink.indexOf(target) === INVALID_INDEX) {
1489
+ created++;
1490
+ }
1491
+ this.report.counts.nodes += created;
1492
+ }
1493
+ /** Check the cancellation signal every ABORT_CHECK_INTERVAL elements. */
1494
+ checkAbort() {
1495
+ if (++this.elementsSinceCheck >= ABORT_CHECK_INTERVAL) {
1496
+ this.elementsSinceCheck = 0;
1497
+ throwIfAborted(this.options.signal);
1498
+ }
1499
+ }
1500
+ /**
1501
+ * The id an edge endpoint refers to: the original id when the text is a mangled id restored
1502
+ * from `graphty:originalId` (the exporter writes endpoints with the mangled id), else the
1503
+ * coerced text.
1504
+ * @param text - the source / target / endpoint node text
1505
+ * @param line - the line
1506
+ * @returns the node id
1507
+ */
1508
+ endpointId(text, line) {
1509
+ const restored = this.restored.get(text);
1510
+ if (restored !== undefined) {
1511
+ return restored;
1512
+ }
1513
+ const id = this.coercer.text(text);
1514
+ this.reportMerge(text, line);
1515
+ return id;
1516
+ }
1517
+ /**
1518
+ * Report a merge the coercer detected under ids: "number".
1519
+ * @param text - the id text just coerced
1520
+ * @param line - the line
1521
+ */
1522
+ reportMerge(text, line) {
1523
+ const merge = this.coercer.lastMerge;
1524
+ if (merge !== null) {
1525
+ this.report.warning("coercion", GRAPHML_ISSUE.ID_MERGED, `id "${merge.text}" merged with "${merge.previousText}" as ${merge.id} under ids: "number"`, { line, element: text });
1526
+ }
1527
+ }
1528
+ }
1529
+ /**
1530
+ * A `parse.nodes` / `parse.edges` hint as a count.
1531
+ * @param text - the attribute text
1532
+ * @returns the count, or null when absent or not a non-negative integer
1533
+ */
1534
+ function hint(text) {
1535
+ if (text === undefined || !/^[0-9]+$/.test(text)) {
1536
+ return null;
1537
+ }
1538
+ const n = Number(text);
1539
+ return Number.isSafeInteger(n) ? n : null;
1540
+ }
1541
+ /**
1542
+ * Resolve the format-specific options.
1543
+ * @param options - the caller's options
1544
+ * @returns the yfiles mode
1545
+ */
1546
+ function resolveYfiles(options) {
1547
+ const value = options?.yfiles;
1548
+ if (value === undefined) {
1549
+ return "json";
1550
+ }
1551
+ if (typeof value !== "string" || !YFILES_MODES.has(value)) {
1552
+ throw new GraphFormatError("E_UNSUPPORTED", `option yfiles: ${JSON.stringify(value)} is not "json" or "skip"`, {
1553
+ option: "yfiles",
1554
+ found: value,
1555
+ });
1556
+ }
1557
+ return value;
1558
+ }
1559
+ /**
1560
+ * Confidence that a head of bytes is GraphML.
1561
+ * @param head - the first bytes
1562
+ * @returns 1 for a `<graphml` root in the GraphML namespace, 0.9 for a `<graphml` root, 0.05 for other XML, 0 otherwise
1563
+ */
1564
+ function sniffGraphml(head) {
1565
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(head.subarray(0, SNIFF_BYTES));
1566
+ const root = text.indexOf("<graphml");
1567
+ if (root >= 0) {
1568
+ return text.includes(GRAPHML_NAMESPACE, root) ? 1 : 0.9;
1569
+ }
1570
+ return /^\s*<(\?xml|!--|!DOCTYPE)/.test(text) ? 0.05 : 0;
1571
+ }
1572
+ /**
1573
+ * Import GraphML into a sink.
1574
+ * @param input - the document
1575
+ * @param sink - the sink
1576
+ * @param options - format-specific and common options
1577
+ * @returns the report; ImportError when the document cannot be read or the error limit is exceeded
1578
+ */
1579
+ async function importGraphml(input, sink, options) {
1580
+ const common = resolveImportOptions(options, FORMAT_DEFAULTS);
1581
+ const yfilesMode = resolveYfiles(options);
1582
+ const report = new ImportReportBuilder(FORMAT, common.errorLimit);
1583
+ reportSinkOptions(sink, options, report, true);
1584
+ reportUnusedOptions(options, report, USED_OPTIONS);
1585
+ const reader = new GraphmlReader(sink, report, common, yfilesMode);
1586
+ try {
1587
+ await tokenizeXml(textChunks(input, report, common), reader);
1588
+ }
1589
+ catch (err) {
1590
+ if (err instanceof XmlSyntaxError) {
1591
+ report.fail(GRAPHML_ISSUE.XML_SYNTAX, err.message, { line: err.line });
1592
+ }
1593
+ throw err;
1594
+ }
1595
+ reader.finish();
1596
+ throwIfAborted(common.signal);
1597
+ return report.finish();
1598
+ }
1599
+ /** The GraphML importer (design section 8.4). */
1600
+ export const graphmlImporter = Object.freeze({
1601
+ format: FORMAT,
1602
+ extensions: EXTENSIONS,
1603
+ mimeTypes: MIME_TYPES,
1604
+ sniff: sniffGraphml,
1605
+ import: importGraphml,
1606
+ });
1607
+ //# sourceMappingURL=importer.js.map