@graphty/graph-io 0.0.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -28
  3. package/dist/chunks/children-CL3Cy0ez.js +238 -0
  4. package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
  5. package/dist/chunks/escape-DyI8JofU.js +938 -0
  6. package/dist/chunks/escape-DyI8JofU.js.map +1 -0
  7. package/dist/chunks/importer-CQnJuWJw.js +2987 -0
  8. package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
  9. package/dist/chunks/importer-CpCpfbxr.js +2015 -0
  10. package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
  11. package/dist/chunks/importer-DbnGYr3_.js +2342 -0
  12. package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
  13. package/dist/chunks/importer-GozH8DkN.js +3050 -0
  14. package/dist/chunks/importer-GozH8DkN.js.map +1 -0
  15. package/dist/chunks/records-CGpxszm1.js +605 -0
  16. package/dist/chunks/records-CGpxszm1.js.map +1 -0
  17. package/dist/chunks/text-CajMdVFy.js +189 -0
  18. package/dist/chunks/text-CajMdVFy.js.map +1 -0
  19. package/dist/chunks/writer-DxSKC7TL.js +2842 -0
  20. package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
  21. package/dist/csv.d.ts +1 -0
  22. package/dist/csv.js +1702 -0
  23. package/dist/csv.js.map +1 -0
  24. package/dist/dot.d.ts +1 -0
  25. package/dist/dot.js +8 -0
  26. package/dist/dot.js.map +1 -0
  27. package/dist/gexf.d.ts +1 -0
  28. package/dist/gexf.js +3466 -0
  29. package/dist/gexf.js.map +1 -0
  30. package/dist/gml.d.ts +1 -0
  31. package/dist/gml.js +2647 -0
  32. package/dist/gml.js.map +1 -0
  33. package/dist/graph-io.d.ts +1 -0
  34. package/dist/graph-io.js +790 -0
  35. package/dist/graph-io.js.map +1 -0
  36. package/dist/graphml.d.ts +1 -0
  37. package/dist/graphml.js +8 -0
  38. package/dist/graphml.js.map +1 -0
  39. package/dist/json.d.ts +1 -0
  40. package/dist/json.js +11 -0
  41. package/dist/json.js.map +1 -0
  42. package/dist/neo4j.d.ts +1 -0
  43. package/dist/neo4j.js +2046 -0
  44. package/dist/neo4j.js.map +1 -0
  45. package/dist/pajek.d.ts +1 -0
  46. package/dist/pajek.js +8 -0
  47. package/dist/pajek.js.map +1 -0
  48. package/dist/src/children.d.ts +134 -0
  49. package/dist/src/children.d.ts.map +1 -0
  50. package/dist/src/children.js +274 -0
  51. package/dist/src/children.js.map +1 -0
  52. package/dist/src/common/attributes.d.ts +229 -0
  53. package/dist/src/common/attributes.d.ts.map +1 -0
  54. package/dist/src/common/attributes.js +368 -0
  55. package/dist/src/common/attributes.js.map +1 -0
  56. package/dist/src/common/codes.d.ts +105 -0
  57. package/dist/src/common/codes.d.ts.map +1 -0
  58. package/dist/src/common/codes.js +107 -0
  59. package/dist/src/common/codes.js.map +1 -0
  60. package/dist/src/common/declared-types.d.ts +84 -0
  61. package/dist/src/common/declared-types.d.ts.map +1 -0
  62. package/dist/src/common/declared-types.js +326 -0
  63. package/dist/src/common/declared-types.js.map +1 -0
  64. package/dist/src/common/direction.d.ts +206 -0
  65. package/dist/src/common/direction.d.ts.map +1 -0
  66. package/dist/src/common/direction.js +370 -0
  67. package/dist/src/common/direction.js.map +1 -0
  68. package/dist/src/common/escape.d.ts +92 -0
  69. package/dist/src/common/escape.d.ts.map +1 -0
  70. package/dist/src/common/escape.js +212 -0
  71. package/dist/src/common/escape.js.map +1 -0
  72. package/dist/src/common/export.d.ts +249 -0
  73. package/dist/src/common/export.d.ts.map +1 -0
  74. package/dist/src/common/export.js +594 -0
  75. package/dist/src/common/export.js.map +1 -0
  76. package/dist/src/common/format.d.ts +59 -0
  77. package/dist/src/common/format.d.ts.map +1 -0
  78. package/dist/src/common/format.js +106 -0
  79. package/dist/src/common/format.js.map +1 -0
  80. package/dist/src/common/ids.d.ts +83 -0
  81. package/dist/src/common/ids.d.ts.map +1 -0
  82. package/dist/src/common/ids.js +158 -0
  83. package/dist/src/common/ids.js.map +1 -0
  84. package/dist/src/common/input.d.ts +100 -0
  85. package/dist/src/common/input.d.ts.map +1 -0
  86. package/dist/src/common/input.js +335 -0
  87. package/dist/src/common/input.js.map +1 -0
  88. package/dist/src/common/lists.d.ts +34 -0
  89. package/dist/src/common/lists.d.ts.map +1 -0
  90. package/dist/src/common/lists.js +185 -0
  91. package/dist/src/common/lists.js.map +1 -0
  92. package/dist/src/common/options.d.ts +108 -0
  93. package/dist/src/common/options.d.ts.map +1 -0
  94. package/dist/src/common/options.js +265 -0
  95. package/dist/src/common/options.js.map +1 -0
  96. package/dist/src/common/report.d.ts +187 -0
  97. package/dist/src/common/report.d.ts.map +1 -0
  98. package/dist/src/common/report.js +274 -0
  99. package/dist/src/common/report.js.map +1 -0
  100. package/dist/src/common/temporal.d.ts +71 -0
  101. package/dist/src/common/temporal.d.ts.map +1 -0
  102. package/dist/src/common/temporal.js +266 -0
  103. package/dist/src/common/temporal.js.map +1 -0
  104. package/dist/src/common/text.d.ts +104 -0
  105. package/dist/src/common/text.d.ts.map +1 -0
  106. package/dist/src/common/text.js +255 -0
  107. package/dist/src/common/text.js.map +1 -0
  108. package/dist/src/common/weights.d.ts +77 -0
  109. package/dist/src/common/weights.d.ts.map +1 -0
  110. package/dist/src/common/weights.js +156 -0
  111. package/dist/src/common/weights.js.map +1 -0
  112. package/dist/src/common/writer.d.ts +51 -0
  113. package/dist/src/common/writer.d.ts.map +1 -0
  114. package/dist/src/common/writer.js +108 -0
  115. package/dist/src/common/writer.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +245 -0
  117. package/dist/src/common/xml.d.ts.map +1 -0
  118. package/dist/src/common/xml.js +942 -0
  119. package/dist/src/common/xml.js.map +1 -0
  120. package/dist/src/formats/csv/exporter.d.ts +70 -0
  121. package/dist/src/formats/csv/exporter.d.ts.map +1 -0
  122. package/dist/src/formats/csv/exporter.js +682 -0
  123. package/dist/src/formats/csv/exporter.js.map +1 -0
  124. package/dist/src/formats/csv/header.d.ts +66 -0
  125. package/dist/src/formats/csv/header.d.ts.map +1 -0
  126. package/dist/src/formats/csv/header.js +152 -0
  127. package/dist/src/formats/csv/header.js.map +1 -0
  128. package/dist/src/formats/csv/importer.d.ts +82 -0
  129. package/dist/src/formats/csv/importer.d.ts.map +1 -0
  130. package/dist/src/formats/csv/importer.js +849 -0
  131. package/dist/src/formats/csv/importer.js.map +1 -0
  132. package/dist/src/formats/csv/index.d.ts +60 -0
  133. package/dist/src/formats/csv/index.d.ts.map +1 -0
  134. package/dist/src/formats/csv/index.js +63 -0
  135. package/dist/src/formats/csv/index.js.map +1 -0
  136. package/dist/src/formats/csv/records.d.ts +188 -0
  137. package/dist/src/formats/csv/records.d.ts.map +1 -0
  138. package/dist/src/formats/csv/records.js +702 -0
  139. package/dist/src/formats/csv/records.js.map +1 -0
  140. package/dist/src/formats/csv/values.d.ts +105 -0
  141. package/dist/src/formats/csv/values.d.ts.map +1 -0
  142. package/dist/src/formats/csv/values.js +192 -0
  143. package/dist/src/formats/csv/values.js.map +1 -0
  144. package/dist/src/formats/dot/exporter.d.ts +52 -0
  145. package/dist/src/formats/dot/exporter.d.ts.map +1 -0
  146. package/dist/src/formats/dot/exporter.js +836 -0
  147. package/dist/src/formats/dot/exporter.js.map +1 -0
  148. package/dist/src/formats/dot/importer.d.ts +102 -0
  149. package/dist/src/formats/dot/importer.d.ts.map +1 -0
  150. package/dist/src/formats/dot/importer.js +1291 -0
  151. package/dist/src/formats/dot/importer.js.map +1 -0
  152. package/dist/src/formats/dot/index.d.ts +7 -0
  153. package/dist/src/formats/dot/index.d.ts.map +1 -0
  154. package/dist/src/formats/dot/index.js +7 -0
  155. package/dist/src/formats/dot/index.js.map +1 -0
  156. package/dist/src/formats/dot/names.d.ts +29 -0
  157. package/dist/src/formats/dot/names.d.ts.map +1 -0
  158. package/dist/src/formats/dot/names.js +28 -0
  159. package/dist/src/formats/dot/names.js.map +1 -0
  160. package/dist/src/formats/dot/tokenizer.d.ts +114 -0
  161. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
  162. package/dist/src/formats/dot/tokenizer.js +341 -0
  163. package/dist/src/formats/dot/tokenizer.js.map +1 -0
  164. package/dist/src/formats/gexf/exporter.d.ts +56 -0
  165. package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
  166. package/dist/src/formats/gexf/exporter.js +1395 -0
  167. package/dist/src/formats/gexf/exporter.js.map +1 -0
  168. package/dist/src/formats/gexf/importer.d.ts +73 -0
  169. package/dist/src/formats/gexf/importer.d.ts.map +1 -0
  170. package/dist/src/formats/gexf/importer.js +1880 -0
  171. package/dist/src/formats/gexf/importer.js.map +1 -0
  172. package/dist/src/formats/gexf/index.d.ts +96 -0
  173. package/dist/src/formats/gexf/index.d.ts.map +1 -0
  174. package/dist/src/formats/gexf/index.js +97 -0
  175. package/dist/src/formats/gexf/index.js.map +1 -0
  176. package/dist/src/formats/gexf/schema.d.ts +135 -0
  177. package/dist/src/formats/gexf/schema.d.ts.map +1 -0
  178. package/dist/src/formats/gexf/schema.js +323 -0
  179. package/dist/src/formats/gexf/schema.js.map +1 -0
  180. package/dist/src/formats/gml/exporter.d.ts +69 -0
  181. package/dist/src/formats/gml/exporter.d.ts.map +1 -0
  182. package/dist/src/formats/gml/exporter.js +1093 -0
  183. package/dist/src/formats/gml/exporter.js.map +1 -0
  184. package/dist/src/formats/gml/importer.d.ts +66 -0
  185. package/dist/src/formats/gml/importer.d.ts.map +1 -0
  186. package/dist/src/formats/gml/importer.js +1331 -0
  187. package/dist/src/formats/gml/importer.js.map +1 -0
  188. package/dist/src/formats/gml/index.d.ts +85 -0
  189. package/dist/src/formats/gml/index.d.ts.map +1 -0
  190. package/dist/src/formats/gml/index.js +88 -0
  191. package/dist/src/formats/gml/index.js.map +1 -0
  192. package/dist/src/formats/gml/syntax.d.ts +186 -0
  193. package/dist/src/formats/gml/syntax.d.ts.map +1 -0
  194. package/dist/src/formats/gml/syntax.js +467 -0
  195. package/dist/src/formats/gml/syntax.js.map +1 -0
  196. package/dist/src/formats/graphml/constants.d.ts +169 -0
  197. package/dist/src/formats/graphml/constants.d.ts.map +1 -0
  198. package/dist/src/formats/graphml/constants.js +165 -0
  199. package/dist/src/formats/graphml/constants.js.map +1 -0
  200. package/dist/src/formats/graphml/exporter.d.ts +34 -0
  201. package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
  202. package/dist/src/formats/graphml/exporter.js +1176 -0
  203. package/dist/src/formats/graphml/exporter.js.map +1 -0
  204. package/dist/src/formats/graphml/importer.d.ts +31 -0
  205. package/dist/src/formats/graphml/importer.d.ts.map +1 -0
  206. package/dist/src/formats/graphml/importer.js +1607 -0
  207. package/dist/src/formats/graphml/importer.js.map +1 -0
  208. package/dist/src/formats/graphml/index.d.ts +8 -0
  209. package/dist/src/formats/graphml/index.d.ts.map +1 -0
  210. package/dist/src/formats/graphml/index.js +8 -0
  211. package/dist/src/formats/graphml/index.js.map +1 -0
  212. package/dist/src/formats/graphml/tree.d.ts +72 -0
  213. package/dist/src/formats/graphml/tree.d.ts.map +1 -0
  214. package/dist/src/formats/graphml/tree.js +290 -0
  215. package/dist/src/formats/graphml/tree.js.map +1 -0
  216. package/dist/src/formats/json/dialect.d.ts +125 -0
  217. package/dist/src/formats/json/dialect.d.ts.map +1 -0
  218. package/dist/src/formats/json/dialect.js +262 -0
  219. package/dist/src/formats/json/dialect.js.map +1 -0
  220. package/dist/src/formats/json/exporter.d.ts +89 -0
  221. package/dist/src/formats/json/exporter.d.ts.map +1 -0
  222. package/dist/src/formats/json/exporter.js +1358 -0
  223. package/dist/src/formats/json/exporter.js.map +1 -0
  224. package/dist/src/formats/json/importer.d.ts +108 -0
  225. package/dist/src/formats/json/importer.d.ts.map +1 -0
  226. package/dist/src/formats/json/importer.js +1838 -0
  227. package/dist/src/formats/json/importer.js.map +1 -0
  228. package/dist/src/formats/json/index.d.ts +8 -0
  229. package/dist/src/formats/json/index.d.ts.map +1 -0
  230. package/dist/src/formats/json/index.js +8 -0
  231. package/dist/src/formats/json/index.js.map +1 -0
  232. package/dist/src/formats/neo4j/exporter.d.ts +68 -0
  233. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
  234. package/dist/src/formats/neo4j/exporter.js +1055 -0
  235. package/dist/src/formats/neo4j/exporter.js.map +1 -0
  236. package/dist/src/formats/neo4j/header.d.ts +52 -0
  237. package/dist/src/formats/neo4j/header.d.ts.map +1 -0
  238. package/dist/src/formats/neo4j/header.js +131 -0
  239. package/dist/src/formats/neo4j/header.js.map +1 -0
  240. package/dist/src/formats/neo4j/importer.d.ts +73 -0
  241. package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
  242. package/dist/src/formats/neo4j/importer.js +932 -0
  243. package/dist/src/formats/neo4j/importer.js.map +1 -0
  244. package/dist/src/formats/neo4j/index.d.ts +79 -0
  245. package/dist/src/formats/neo4j/index.d.ts.map +1 -0
  246. package/dist/src/formats/neo4j/index.js +83 -0
  247. package/dist/src/formats/neo4j/index.js.map +1 -0
  248. package/dist/src/formats/pajek/exporter.d.ts +58 -0
  249. package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
  250. package/dist/src/formats/pajek/exporter.js +825 -0
  251. package/dist/src/formats/pajek/exporter.js.map +1 -0
  252. package/dist/src/formats/pajek/importer.d.ts +88 -0
  253. package/dist/src/formats/pajek/importer.d.ts.map +1 -0
  254. package/dist/src/formats/pajek/importer.js +1047 -0
  255. package/dist/src/formats/pajek/importer.js.map +1 -0
  256. package/dist/src/formats/pajek/index.d.ts +7 -0
  257. package/dist/src/formats/pajek/index.d.ts.map +1 -0
  258. package/dist/src/formats/pajek/index.js +7 -0
  259. package/dist/src/formats/pajek/index.js.map +1 -0
  260. package/dist/src/formats/pajek/syntax.d.ts +112 -0
  261. package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
  262. package/dist/src/formats/pajek/syntax.js +269 -0
  263. package/dist/src/formats/pajek/syntax.js.map +1 -0
  264. package/dist/src/index.d.ts +35 -0
  265. package/dist/src/index.d.ts.map +1 -0
  266. package/dist/src/index.js +39 -0
  267. package/dist/src/index.js.map +1 -0
  268. package/dist/src/registry.d.ts +207 -0
  269. package/dist/src/registry.d.ts.map +1 -0
  270. package/dist/src/registry.js +481 -0
  271. package/dist/src/registry.js.map +1 -0
  272. package/dist/src/sniff.d.ts +104 -0
  273. package/dist/src/sniff.d.ts.map +1 -0
  274. package/dist/src/sniff.js +357 -0
  275. package/dist/src/sniff.js.map +1 -0
  276. package/dist/src/types.d.ts +238 -0
  277. package/dist/src/types.d.ts.map +1 -0
  278. package/dist/src/types.js +29 -0
  279. package/dist/src/types.js.map +1 -0
  280. package/dist/tsconfig.build.tsbuildinfo +1 -0
  281. package/package.json +122 -7
  282. package/src/children.ts +335 -0
  283. package/src/common/attributes.ts +520 -0
  284. package/src/common/codes.ts +153 -0
  285. package/src/common/declared-types.ts +374 -0
  286. package/src/common/direction.ts +518 -0
  287. package/src/common/escape.ts +231 -0
  288. package/src/common/export.ts +817 -0
  289. package/src/common/format.ts +111 -0
  290. package/src/common/ids.ts +176 -0
  291. package/src/common/input.ts +378 -0
  292. package/src/common/lists.ts +196 -0
  293. package/src/common/options.ts +377 -0
  294. package/src/common/report.ts +352 -0
  295. package/src/common/temporal.ts +302 -0
  296. package/src/common/text.ts +294 -0
  297. package/src/common/weights.ts +202 -0
  298. package/src/common/writer.ts +123 -0
  299. package/src/common/xml.ts +1053 -0
  300. package/src/formats/csv/exporter.ts +894 -0
  301. package/src/formats/csv/header.ts +172 -0
  302. package/src/formats/csv/importer.ts +1104 -0
  303. package/src/formats/csv/index.ts +88 -0
  304. package/src/formats/csv/records.ts +813 -0
  305. package/src/formats/csv/values.ts +224 -0
  306. package/src/formats/dot/exporter.ts +1014 -0
  307. package/src/formats/dot/importer.ts +1549 -0
  308. package/src/formats/dot/index.ts +7 -0
  309. package/src/formats/dot/names.ts +40 -0
  310. package/src/formats/dot/tokenizer.ts +384 -0
  311. package/src/formats/gexf/exporter.ts +1696 -0
  312. package/src/formats/gexf/importer.ts +2333 -0
  313. package/src/formats/gexf/index.ts +142 -0
  314. package/src/formats/gexf/schema.ts +361 -0
  315. package/src/formats/gml/exporter.ts +1404 -0
  316. package/src/formats/gml/importer.ts +1591 -0
  317. package/src/formats/gml/index.ts +128 -0
  318. package/src/formats/gml/syntax.ts +545 -0
  319. package/src/formats/graphml/constants.ts +225 -0
  320. package/src/formats/graphml/exporter.ts +1458 -0
  321. package/src/formats/graphml/importer.ts +2027 -0
  322. package/src/formats/graphml/index.ts +8 -0
  323. package/src/formats/graphml/tree.ts +318 -0
  324. package/src/formats/json/dialect.ts +317 -0
  325. package/src/formats/json/exporter.ts +1616 -0
  326. package/src/formats/json/importer.ts +2271 -0
  327. package/src/formats/json/index.ts +8 -0
  328. package/src/formats/neo4j/exporter.ts +1287 -0
  329. package/src/formats/neo4j/header.ts +156 -0
  330. package/src/formats/neo4j/importer.ts +1220 -0
  331. package/src/formats/neo4j/index.ts +116 -0
  332. package/src/formats/pajek/exporter.ts +1000 -0
  333. package/src/formats/pajek/importer.ts +1311 -0
  334. package/src/formats/pajek/index.ts +7 -0
  335. package/src/formats/pajek/syntax.ts +307 -0
  336. package/src/index.ts +244 -0
  337. package/src/registry.ts +617 -0
  338. package/src/sniff.ts +397 -0
  339. package/src/types.ts +262 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The `@graphty/graph-io/gml` subpath (design section 8.2): the GML importer and exporter, their
3
+ * option types, and their issue and loss-note codes grouped in two tables.
4
+ */
5
+
6
+ import {
7
+ COLUMN_RENAMED_CODE,
8
+ DIRECTION_FORCED_CODE,
9
+ DIRECTION_REFUSED_CODE,
10
+ ID_MERGED_CODE,
11
+ INVALID_UTF8_CODE,
12
+ MIXED_DIRECTION_CODE,
13
+ OPTION_IGNORED_CODE,
14
+ SINK_OPTION_CODE,
15
+ SYNTAX_CODE,
16
+ } from "../../common/codes.js";
17
+ import {
18
+ GRAPHICS_CONFLICT_CODE,
19
+ GRAPHICS_OVERRIDDEN_CODE,
20
+ INVALID_KEY_CODE,
21
+ JSON_ARRAY_CODE,
22
+ KEY_MANGLED_CODE,
23
+ NESTED_ARRAY_CODE,
24
+ POSITION_COMPONENTS_CODE,
25
+ RECORD_BOOLEAN_CODE,
26
+ RECORD_NULL_CODE,
27
+ RECORD_NUMBER_TYPE_CODE,
28
+ RESERVED_KEY_CODE,
29
+ } from "./exporter.js";
30
+ import {
31
+ DUPLICATE_NODE_CODE,
32
+ ELEMENT_TYPE_CODE,
33
+ FLAG_TYPE_CODE,
34
+ FLAG_VALUE_CODE,
35
+ ID_DROPPED_CODE,
36
+ ID_TYPE_CODE,
37
+ MISSING_ENDPOINT_CODE,
38
+ MISSING_ID_CODE,
39
+ MISSING_LABEL_CODE,
40
+ NO_GRAPH_CODE,
41
+ PRECISION_CODE,
42
+ REPEATED_KEY_CODE,
43
+ ROLE_TAKEN_CODE,
44
+ SECOND_GRAPH_CODE,
45
+ } from "./importer.js";
46
+
47
+ export { gmlExporter, type GmlExportOptions } from "./exporter.js";
48
+ export { gmlImporter, type GmlImportOptions } from "./importer.js";
49
+
50
+ /**
51
+ * The issue codes the GML importer records (design section 8.6), by name: the codes shared with
52
+ * the other importers (src/common/codes.ts) and the GML-specific ones. A key is the code without
53
+ * its severity and format prefixes.
54
+ */
55
+ export const GML_ISSUE = Object.freeze({
56
+ /** A grammar violation: an untokenisable bare token, an unclosed string or `[`, a stray `]`, a key without a value (fatal). */
57
+ SYNTAX: SYNTAX_CODE,
58
+ /** The input holds invalid UTF-8 (fatal). */
59
+ INVALID_UTF8: INVALID_UTF8_CODE,
60
+ /** No `graph [` block (fatal). */
61
+ NO_GRAPH: NO_GRAPH_CODE,
62
+ /** More than one `graph` block (fatal). */
63
+ SECOND_GRAPH: SECOND_GRAPH_CODE,
64
+ /** A node without an `id`. */
65
+ MISSING_ID: MISSING_ID_CODE,
66
+ /** A node without a `label` under nodeIdFrom "label". */
67
+ MISSING_LABEL: MISSING_LABEL_CODE,
68
+ /** An edge without `source` or `target`. */
69
+ MISSING_ENDPOINT: MISSING_ENDPOINT_CODE,
70
+ /** A node id, source or target that is not an integer. */
71
+ ID_TYPE: ID_TYPE_CODE,
72
+ /** A node id declared twice (later keys overwrite). */
73
+ DUPLICATE_NODE: DUPLICATE_NODE_CODE,
74
+ /** A structural key repeated in one element. */
75
+ REPEATED_KEY: REPEATED_KEY_CODE,
76
+ /** A `node` / `edge` key whose value is not a record. */
77
+ ELEMENT_TYPE: ELEMENT_TYPE_CODE,
78
+ /** A `directed` / `multigraph` flag that is not an integer. */
79
+ FLAG_TYPE: FLAG_TYPE_CODE,
80
+ /** A `directed` / `multigraph` flag outside 0 / 1, or repeated. */
81
+ FLAG_VALUE: FLAG_VALUE_CODE,
82
+ /** An integer beyond 2^53 rounded to f64. */
83
+ PRECISION: PRECISION_CODE,
84
+ /** A column renamed `<name>#<key>` because the sink held the name with another shape. */
85
+ COLUMN_RENAMED: COLUMN_RENAMED_CODE,
86
+ /** A column declared without its role because the sink already holds it. */
87
+ ROLE_TAKEN: ROLE_TAKEN_CODE,
88
+ /** Two id texts merged into one number under ids "number". */
89
+ ID_MERGED: ID_MERGED_CODE,
90
+ /** A common option the importer has no use for was given. */
91
+ OPTION_IGNORED: OPTION_IGNORED_CODE,
92
+ /** A builder-policy option the sink does not honour. */
93
+ SINK_OPTION: SINK_OPTION_CODE,
94
+ /** The sink refused the file's direction. */
95
+ DIRECTION_REFUSED: DIRECTION_REFUSED_CODE,
96
+ /** Edges forced to the policy's direction. */
97
+ DIRECTION_FORCED: DIRECTION_FORCED_CODE,
98
+ /** A mixed file under onMixedDirection "error" (fatal). */
99
+ MIXED_DIRECTION: MIXED_DIRECTION_CODE,
100
+ /** Under nodeIdFrom "label" / "index" the integer ids are not kept (a loss note). */
101
+ ID_DROPPED: ID_DROPPED_CODE,
102
+ });
103
+
104
+ /** The loss-note codes the GML exporter's check() reports beyond the shared LOSS table, by name. */
105
+ export const GML_LOSS = Object.freeze({
106
+ /** A json column holds numbers; GML records cannot keep int versus real (design section 8.5). */
107
+ RECORD_NUMBER_TYPE: RECORD_NUMBER_TYPE_CODE,
108
+ /** A json column holds booleans, written 1 / 0. */
109
+ RECORD_BOOLEAN: RECORD_BOOLEAN_CODE,
110
+ /** A json column holds nulls, omitted. */
111
+ RECORD_NULL: RECORD_NULL_CODE,
112
+ /** An array inside an array; export() throws. */
113
+ NESTED_ARRAY: NESTED_ARRAY_CODE,
114
+ /** A json row that is an array, written as repeated keys. */
115
+ JSON_ARRAY: JSON_ARRAY_CODE,
116
+ /** A column name or record key outside the GML key grammar. */
117
+ INVALID_KEY: INVALID_KEY_CODE,
118
+ /** A column named like a structural key. */
119
+ RESERVED_KEY: RESERVED_KEY_CODE,
120
+ /** A key rewritten under sanitizeKeys "mangle". */
121
+ KEY_MANGLED: KEY_MANGLED_CODE,
122
+ /** A position column with more than three components. */
123
+ POSITION_COMPONENTS: POSITION_COMPONENTS_CODE,
124
+ /** A graphics record whose x / y / z the position column overrides. */
125
+ GRAPHICS_OVERRIDDEN: GRAPHICS_OVERRIDDEN_CODE,
126
+ /** A graphics record that cannot hold the position. */
127
+ GRAPHICS_CONFLICT: GRAPHICS_CONFLICT_CODE,
128
+ });
@@ -0,0 +1,545 @@
1
+ /**
2
+ * The GML lexical layer shared by the importer and the exporter (research note 07 section 2.2,
3
+ * NetworkX readwrite/gml.py as the reference reader): the token list a whole GML text lexes into,
4
+ * the structural validation that guarantees every later walk sees balanced `key value` pairs, the
5
+ * value grammar (int, real, quoted string with `&#NN;` character references, `[ ... ]` record),
6
+ * the NetworkX list conventions (`_networkx_list_start`, `"[]"`) and the key charset.
7
+ *
8
+ * Tokens are kept in typed arrays (kind, start, end, line, matching bracket) rather than objects:
9
+ * the token list is the one intermediate structure of the GML importer, and it costs 17 bytes per
10
+ * token instead of an object per node.
11
+ */
12
+
13
+ import { SYNTAX_CODE } from "../../common/codes.js";
14
+ import { decodeGmlString } from "../../common/escape.js";
15
+
16
+ /** A bare word: a key, or `INF` / `NAN` at a value position. */
17
+ export const TOKEN_WORD = 0;
18
+ /** An integer literal. */
19
+ export const TOKEN_INT = 1;
20
+ /** A real literal, including the non-finite spellings. */
21
+ export const TOKEN_REAL = 2;
22
+ /** A double-quoted string; start / end delimit the body between the quotes. */
23
+ export const TOKEN_STRING = 3;
24
+ /** `[`. */
25
+ export const TOKEN_OPEN = 4;
26
+ /** `]`. */
27
+ export const TOKEN_CLOSE = 5;
28
+
29
+ /** The kind of one token. */
30
+ type TokenKind = 0 | 1 | 2 | 3 | 4 | 5;
31
+
32
+ /** The NetworkX marker that starts a one-element list written as repeated keys. */
33
+ export const LIST_START_MARKER = "_networkx_list_start";
34
+
35
+ /** The string NetworkX writes for an empty list. */
36
+ export const EMPTY_LIST_TEXT = "[]";
37
+
38
+ /** The string NetworkX writes for an empty tuple, read as an empty list too. */
39
+ export const EMPTY_TUPLE_TEXT = "()";
40
+
41
+ /** The node key the exporter writes a mangled node's original id into (design section 8.5). */
42
+ export const ORIGINAL_ID_KEY = "graphty_originalId";
43
+
44
+ /**
45
+ * Issue code of every grammar violation (shared with the other text formats; the message carries
46
+ * the detail): a bare token that is neither a key nor a number, an unclosed string, an unclosed
47
+ * `[`, a `]` with no open `[`, a value without a key or a key without a value.
48
+ */
49
+ export const SYNTAX_STRUCTURE_CODE = SYNTAX_CODE;
50
+ /** The code of an untokenisable bare token (E_SYNTAX). */
51
+ export const SYNTAX_TOKEN_CODE = SYNTAX_CODE;
52
+ /** The code of an unclosed string (E_SYNTAX). */
53
+ export const SYNTAX_STRING_CODE = SYNTAX_CODE;
54
+ /** The code of an unclosed `[` (E_SYNTAX). */
55
+ export const SYNTAX_BRACKET_CODE = SYNTAX_CODE;
56
+
57
+ const KEY_TEXT = /^[A-Za-z_][0-9A-Za-z_]*$/;
58
+ const STRICT_KEY_TEXT = /^[A-Za-z][0-9A-Za-z_]*$/;
59
+ const INT_TEXT = /^[+-]?[0-9]+$/;
60
+ const REAL_TEXT = /^[+-]?(?:(?:[0-9]+\.[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)$/;
61
+ const NON_FINITE_TEXT = /^[+-]?(?:inf|infinity|nan)$/i;
62
+ const NON_FINITE_WORD = /^(?:inf|infinity|nan)$/i;
63
+
64
+ /**
65
+ * A lexical or structural error of a GML text, with the 1-based line it was found on. The importer
66
+ * turns it into a fatal parse-error issue.
67
+ */
68
+ export class GmlSyntaxError extends Error {
69
+ /** The issue code. */
70
+ readonly code: string;
71
+
72
+ /** The 1-based line. */
73
+ readonly line: number;
74
+
75
+ /**
76
+ * Create a syntax error.
77
+ * @param code - the issue code
78
+ * @param message - a plain-ASCII message
79
+ * @param line - the 1-based line
80
+ */
81
+ constructor(code: string, message: string, line: number) {
82
+ super(message);
83
+ this.name = "GmlSyntaxError";
84
+ this.code = code;
85
+ this.line = line;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * The tokens of one GML text, structurally validated: every `[` has its matching `]` recorded and
91
+ * every key is followed by a value, so walkers need no error paths.
92
+ */
93
+ export class GmlTokens {
94
+ /** The source text the offsets index into. */
95
+ readonly text: string;
96
+
97
+ /** The kind of every token. */
98
+ kind: Uint8Array;
99
+
100
+ /** The start offset of every token (a string's body start). */
101
+ start: Uint32Array;
102
+
103
+ /** The end offset (exclusive) of every token (a string's body end). */
104
+ end: Uint32Array;
105
+
106
+ /** The 1-based line of every token. */
107
+ line: Uint32Array;
108
+
109
+ /** For an open bracket, the index of its matching close bracket; 0 elsewhere. */
110
+ match: Uint32Array;
111
+
112
+ /** The number of tokens. */
113
+ count = 0;
114
+
115
+ /**
116
+ * Create an empty token list over a text.
117
+ * @param text - the source text
118
+ * @param capacity - the initial capacity
119
+ */
120
+ constructor(text: string, capacity = 1024) {
121
+ this.text = text;
122
+ this.kind = new Uint8Array(capacity);
123
+ this.start = new Uint32Array(capacity);
124
+ this.end = new Uint32Array(capacity);
125
+ this.line = new Uint32Array(capacity);
126
+ this.match = new Uint32Array(0);
127
+ }
128
+
129
+ /**
130
+ * Append a token.
131
+ * @param kind - the kind
132
+ * @param start - the start offset
133
+ * @param end - the end offset
134
+ * @param line - the 1-based line
135
+ */
136
+ push(kind: TokenKind, start: number, end: number, line: number): void {
137
+ if (this.count === this.kind.length) {
138
+ this.grow();
139
+ }
140
+ const i = this.count++;
141
+ this.kind[i] = kind;
142
+ this.start[i] = start;
143
+ this.end[i] = end;
144
+ this.line[i] = line;
145
+ }
146
+
147
+ /**
148
+ * The raw text of a token (a string's body without the quotes, entities undecoded).
149
+ * @param i - the token index
150
+ * @returns the text
151
+ */
152
+ textOf(i: number): string {
153
+ return this.text.slice(this.start[i], this.end[i]);
154
+ }
155
+
156
+ /**
157
+ * The decoded value of a string token.
158
+ * @param i - the token index
159
+ * @returns the body with character references decoded
160
+ */
161
+ stringOf(i: number): string {
162
+ return decodeGmlString(this.textOf(i));
163
+ }
164
+
165
+ /**
166
+ * Whether a token is a `[` or `]`.
167
+ * @param i - the token index
168
+ * @returns true for a bracket
169
+ */
170
+ isBracket(i: number): boolean {
171
+ return this.kind[i] >= TOKEN_OPEN;
172
+ }
173
+
174
+ /**
175
+ * The index of the pair following the pair whose key is at `key`.
176
+ * @param key - the index of a key token
177
+ * @returns the index after the pair's value (after the matching `]` for a record)
178
+ */
179
+ nextPair(key: number): number {
180
+ const value = key + 1;
181
+ return this.kind[value] === TOKEN_OPEN ? this.match[value] + 1 : value + 1;
182
+ }
183
+
184
+ /** Double every array. */
185
+ private grow(): void {
186
+ const capacity = this.kind.length * 2;
187
+ const kind = new Uint8Array(capacity);
188
+ kind.set(this.kind);
189
+ this.kind = kind;
190
+ const start = new Uint32Array(capacity);
191
+ start.set(this.start);
192
+ this.start = start;
193
+ const end = new Uint32Array(capacity);
194
+ end.set(this.end);
195
+ this.end = end;
196
+ const line = new Uint32Array(capacity);
197
+ line.set(this.line);
198
+ this.line = line;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Lex and structurally validate a whole GML text.
204
+ *
205
+ * Lexical rules (NetworkX): whitespace separates tokens; `#` outside a string starts a comment
206
+ * that runs to the end of the line; a string is delimited by double quotes and must close on its
207
+ * line (there is no escape mechanism; `&#NN;` references are decoded later); `[` and `]` are
208
+ * single-character tokens; every other run of characters is a key (`[A-Za-z_][0-9A-Za-z_]*`),
209
+ * an integer (`[+-]?[0-9]+`), a real (with a decimal point or an exponent, or `INF` / `NAN` in
210
+ * any case with an optional sign) or an error.
211
+ *
212
+ * Structural rules: the text is a sequence of `key value` pairs where a value is a number, a
213
+ * string, `INF` / `NAN`, or a bracketed sequence of pairs; every `[` has a `]`.
214
+ * @param text - the GML text
215
+ * @returns the validated tokens; GmlSyntaxError with the line of the first problem
216
+ */
217
+ export function tokenizeGml(text: string): GmlTokens {
218
+ const tokens = new GmlTokens(text, Math.max(1024, text.length >>> 3));
219
+ const n = text.length;
220
+ let i = 0;
221
+ let line = 1;
222
+ while (i < n) {
223
+ const c = text.charCodeAt(i);
224
+ if (c === 10) {
225
+ line++;
226
+ i++;
227
+ } else if (c === 13) {
228
+ line++;
229
+ i++;
230
+ if (i < n && text.charCodeAt(i) === 10) {
231
+ i++;
232
+ }
233
+ } else if (c === 32 || c === 9 || c === 11 || c === 12) {
234
+ i++;
235
+ } else if (c === 35) {
236
+ i = endOfLine(text, i);
237
+ } else if (c === 91) {
238
+ tokens.push(TOKEN_OPEN, i, i + 1, line);
239
+ i++;
240
+ } else if (c === 93) {
241
+ tokens.push(TOKEN_CLOSE, i, i + 1, line);
242
+ i++;
243
+ } else if (c === 34) {
244
+ const close = text.indexOf('"', i + 1);
245
+ const eol = endOfLine(text, i + 1);
246
+ if (close < 0 || close > eol) {
247
+ throw new GmlSyntaxError(SYNTAX_STRING_CODE, `unclosed string at line ${line}`, line);
248
+ }
249
+ tokens.push(TOKEN_STRING, i + 1, close, line);
250
+ i = close + 1;
251
+ } else {
252
+ let j = i + 1;
253
+ while (j < n && !isDelimiter(text.charCodeAt(j))) {
254
+ j++;
255
+ }
256
+ tokens.push(classifyBare(text, i, j, line), i, j, line);
257
+ i = j;
258
+ }
259
+ }
260
+ validateStructure(tokens);
261
+ return tokens;
262
+ }
263
+
264
+ /**
265
+ * The offset of the line terminator at or after `from`, or the text length.
266
+ * @param text - the text
267
+ * @param from - the offset to search from
268
+ * @returns the offset of the next `\n` or `\r`
269
+ */
270
+ function endOfLine(text: string, from: number): number {
271
+ const n = text.length;
272
+ for (let i = from; i < n; i++) {
273
+ const c = text.charCodeAt(i);
274
+ if (c === 10 || c === 13) {
275
+ return i;
276
+ }
277
+ }
278
+ return n;
279
+ }
280
+
281
+ /**
282
+ * Whether a character ends a bare token.
283
+ * @param c - the char code
284
+ * @returns true for whitespace, brackets, a quote or `#`
285
+ */
286
+ function isDelimiter(c: number): boolean {
287
+ return (
288
+ c === 32 ||
289
+ c === 9 ||
290
+ c === 10 ||
291
+ c === 13 ||
292
+ c === 11 ||
293
+ c === 12 ||
294
+ c === 91 ||
295
+ c === 93 ||
296
+ c === 34 ||
297
+ c === 35
298
+ );
299
+ }
300
+
301
+ /**
302
+ * Classify a bare token.
303
+ * @param text - the text
304
+ * @param start - the token start
305
+ * @param end - the token end
306
+ * @param line - the token line, for the error
307
+ * @returns WORD, INT or REAL; GmlSyntaxError for anything else
308
+ */
309
+ function classifyBare(text: string, start: number, end: number, line: number): TokenKind {
310
+ const token = text.slice(start, end);
311
+ if (KEY_TEXT.test(token)) {
312
+ return TOKEN_WORD;
313
+ }
314
+ if (INT_TEXT.test(token)) {
315
+ return TOKEN_INT;
316
+ }
317
+ if (REAL_TEXT.test(token) || NON_FINITE_TEXT.test(token)) {
318
+ return TOKEN_REAL;
319
+ }
320
+ const shown = token.length > 20 ? `${token.slice(0, 20)}...` : token;
321
+ throw new GmlSyntaxError(SYNTAX_TOKEN_CODE, `cannot tokenize "${shown}" at line ${line}`, line);
322
+ }
323
+
324
+ /**
325
+ * Check the `key value` structure and record the matching bracket of every `[`.
326
+ * @param tokens - the lexed tokens
327
+ */
328
+ function validateStructure(tokens: GmlTokens): void {
329
+ const { count, kind } = tokens;
330
+ const match = new Uint32Array(count);
331
+ const stack: number[] = [];
332
+ let i = 0;
333
+ while (i < count) {
334
+ const k = kind[i];
335
+ if (k === TOKEN_CLOSE) {
336
+ const open = stack.pop();
337
+ if (open === undefined) {
338
+ throw new GmlSyntaxError(
339
+ SYNTAX_STRUCTURE_CODE,
340
+ `unexpected "]" at line ${tokens.line[i]}: no open "["`,
341
+ tokens.line[i],
342
+ );
343
+ }
344
+ match[open] = i;
345
+ i++;
346
+ continue;
347
+ }
348
+ if (k !== TOKEN_WORD) {
349
+ throw new GmlSyntaxError(
350
+ SYNTAX_STRUCTURE_CODE,
351
+ `expected a key at line ${tokens.line[i]}, found ${describeToken(tokens, i)}`,
352
+ tokens.line[i],
353
+ );
354
+ }
355
+ const value = i + 1;
356
+ if (value >= count) {
357
+ throw new GmlSyntaxError(
358
+ SYNTAX_STRUCTURE_CODE,
359
+ `key "${tokens.textOf(i)}" at line ${tokens.line[i]} has no value`,
360
+ tokens.line[i],
361
+ );
362
+ }
363
+ const vk = kind[value];
364
+ if (vk === TOKEN_OPEN) {
365
+ stack.push(value);
366
+ } else if (vk === TOKEN_CLOSE || (vk === TOKEN_WORD && !NON_FINITE_WORD.test(tokens.textOf(value)))) {
367
+ throw new GmlSyntaxError(
368
+ SYNTAX_STRUCTURE_CODE,
369
+ `expected a value for key "${tokens.textOf(i)}" at line ${tokens.line[i]}, found ${describeToken(tokens, value)}`,
370
+ tokens.line[value],
371
+ );
372
+ }
373
+ i = value + 1;
374
+ }
375
+ if (stack.length > 0) {
376
+ const open = stack[stack.length - 1];
377
+ throw new GmlSyntaxError(
378
+ SYNTAX_BRACKET_CODE,
379
+ `unexpected end of input: ${stack.length} bracket(s) still open, the last opened at line ${tokens.line[open]}`,
380
+ tokens.line[open],
381
+ );
382
+ }
383
+ tokens.match = match;
384
+ }
385
+
386
+ /**
387
+ * A short description of a token for messages.
388
+ * @param tokens - the tokens
389
+ * @param i - the token index
390
+ * @returns the quoted text or the bracket
391
+ */
392
+ function describeToken(tokens: GmlTokens, i: number): string {
393
+ switch (tokens.kind[i]) {
394
+ case TOKEN_OPEN:
395
+ return '"["';
396
+ case TOKEN_CLOSE:
397
+ return '"]"';
398
+ case TOKEN_STRING:
399
+ return `the string "${tokens.textOf(i)}"`;
400
+ default:
401
+ return `"${tokens.textOf(i)}"`;
402
+ }
403
+ }
404
+
405
+ /**
406
+ * The numeric value of an INT or REAL token (or a WORD that is `INF` / `NAN` at a value position).
407
+ * @param text - the token text
408
+ * @returns the number; Infinity / -Infinity / NaN for the non-finite spellings
409
+ */
410
+ export function numberOfText(text: string): number {
411
+ if (NON_FINITE_TEXT.test(text)) {
412
+ const lower = text.toLowerCase();
413
+ if (lower.endsWith("nan")) {
414
+ return NaN;
415
+ }
416
+ return lower.startsWith("-") ? -Infinity : Infinity;
417
+ }
418
+ return Number(text);
419
+ }
420
+
421
+ /**
422
+ * Whether a value token is one of the non-finite bare words (`INF`, `NAN`), which lex as words.
423
+ * @param tokens - the tokens
424
+ * @param i - the value token index
425
+ * @returns true when the word is a real
426
+ */
427
+ export function isNonFiniteWord(tokens: GmlTokens, i: number): boolean {
428
+ return tokens.kind[i] === TOKEN_WORD && NON_FINITE_WORD.test(tokens.textOf(i));
429
+ }
430
+
431
+ /**
432
+ * Whether a text is a GML key the strict grammar accepts (`[A-Za-z][0-9A-Za-z_]*`, what NetworkX
433
+ * writes and reads).
434
+ * @param text - the candidate key
435
+ * @returns true when it can be written as a key
436
+ */
437
+ export function isGmlKey(text: string): boolean {
438
+ return STRICT_KEY_TEXT.test(text);
439
+ }
440
+
441
+ /**
442
+ * Rewrite a text as a GML key: every character outside `[0-9A-Za-z_]` becomes `_` and a leading
443
+ * non-letter is prefixed with `x` (the exporter's `sanitizeKeys: "mangle"`).
444
+ * @param text - the text
445
+ * @returns a valid key
446
+ */
447
+ export function mangleGmlKey(text: string): string {
448
+ const body = text.replace(/[^0-9A-Za-z_]/g, "_");
449
+ return /^[A-Za-z]/.test(body) ? body : `x${body}`;
450
+ }
451
+
452
+ /** One open record while parseRecord() walks the tokens without recursion. */
453
+ interface RecordFrame {
454
+ readonly record: Record<string, unknown>;
455
+ listKeys: Set<string> | null;
456
+ /** The index of the record's `]`. */
457
+ readonly close: number;
458
+ /** The next key token to read. */
459
+ next: number;
460
+ /** The key the record is stored under in its parent, or null for the root. */
461
+ readonly key: string | null;
462
+ }
463
+
464
+ /**
465
+ * Parse a bracketed record into a JSON object (design section 8.5: nested GML records map to
466
+ * `json`). Values follow the value grammar; repeated keys become arrays with the NetworkX
467
+ * conventions: the `_networkx_list_start` marker starts a list so a one-element list survives,
468
+ * `"[]"` and `"()"` are empty lists, and a key seen twice without the marker becomes a
469
+ * two-element array. Nested records are walked with an explicit stack, so the nesting depth is
470
+ * bounded by memory, not by the call stack.
471
+ * @param tokens - the validated tokens
472
+ * @param open - the index of the record's `[`
473
+ * @returns a null-prototype object
474
+ */
475
+ export function parseRecord(tokens: GmlTokens, open: number): Record<string, unknown> {
476
+ const root = Object.create(null) as Record<string, unknown>;
477
+ const stack: RecordFrame[] = [
478
+ { record: root, listKeys: null, close: tokens.match[open], next: open + 1, key: null },
479
+ ];
480
+ while (stack.length > 0) {
481
+ const frame = stack[stack.length - 1];
482
+ if (frame.next >= frame.close) {
483
+ stack.pop();
484
+ if (frame.key !== null) {
485
+ storeValue(stack[stack.length - 1], frame.key, frame.record);
486
+ }
487
+ continue;
488
+ }
489
+ const p = frame.next;
490
+ frame.next = tokens.nextPair(p);
491
+ const key = tokens.textOf(p);
492
+ const v = p + 1;
493
+ if (tokens.kind[v] === TOKEN_STRING) {
494
+ const text = tokens.stringOf(v);
495
+ if (text === LIST_START_MARKER) {
496
+ frame.record[key] = [];
497
+ frame.listKeys ??= new Set();
498
+ frame.listKeys.add(key);
499
+ continue;
500
+ }
501
+ storeValue(frame, key, text === EMPTY_LIST_TEXT || text === EMPTY_TUPLE_TEXT ? [] : text);
502
+ } else if (tokens.kind[v] === TOKEN_OPEN) {
503
+ stack.push({
504
+ record: Object.create(null) as Record<string, unknown>,
505
+ listKeys: null,
506
+ close: tokens.match[v],
507
+ next: v + 1,
508
+ key,
509
+ });
510
+ } else {
511
+ storeValue(frame, key, numberOfText(tokens.textOf(v)));
512
+ }
513
+ }
514
+ return root;
515
+ }
516
+
517
+ /**
518
+ * Store one value under a key: appended to the key's list when the key is a list, paired with the
519
+ * earlier value when the key repeats, set otherwise.
520
+ * @param frame - the open record
521
+ * @param key - the key
522
+ * @param value - the value
523
+ */
524
+ function storeValue(frame: RecordFrame, key: string, value: unknown): void {
525
+ const { record } = frame;
526
+ if (frame.listKeys !== null && frame.listKeys.has(key)) {
527
+ (record[key] as unknown[]).push(value);
528
+ } else if (key in record) {
529
+ record[key] = [record[key], value];
530
+ frame.listKeys ??= new Set();
531
+ frame.listKeys.add(key);
532
+ } else {
533
+ record[key] = value;
534
+ }
535
+ }
536
+
537
+ /**
538
+ * The JSON value of a non-string value token: a number, or a record.
539
+ * @param tokens - the tokens
540
+ * @param v - the value token index (INT, REAL, non-finite WORD or OPEN)
541
+ * @returns the value
542
+ */
543
+ export function scalarOrRecord(tokens: GmlTokens, v: number): unknown {
544
+ return tokens.kind[v] === TOKEN_OPEN ? parseRecord(tokens, v) : numberOfText(tokens.textOf(v));
545
+ }