@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,1047 @@
1
+ /**
2
+ * The Pajek NET importer (design sections 8.4 and 8.6; research note 07 section 2.5): a
3
+ * line-oriented streaming reader over the common LineReader that pushes scalars into the sink as
4
+ * each line is read. `*Vertices N` declares vertices 1..N (0..N-1 for the zero-based files some
5
+ * scripts write, detected from the first vertex line), so the id map of a well-formed file is
6
+ * `identity` with offset 1; vertex lines add the label, the coordinates (the position role,
7
+ * units "file"), the shape keyword and the `key value` parameters (a `graphty_originalId`
8
+ * parameter, written by the exporter under `sanitizeIds: "mangle"`, is restored as the vertex's
9
+ * id under `restoreMangledIds`); `*Arcs` sections are directed
10
+ * and `*Edges` sections undirected, resolved through the common DirectionResolver under the
11
+ * mixed-direction policy; the third column of a line is the weight unless `weightFrom` says
12
+ * otherwise; `*Arcslist` / `*Edgeslist` adjacency lists and `*Matrix` rows are read too; time
13
+ * interval tokens `[1-5,7-*]` become the spells role. A second network in one file is refused
14
+ * with an issue; project-file sections (`*Partition`, `*Vector`, ...) are reported as unsupported.
15
+ */
16
+ import { GraphFormatError, INVALID_INDEX, } from "@graphty/graph-format";
17
+ import { declareResolved, RENAMED_CODE, ROLE_TAKEN_CODE } from "../../common/attributes.js";
18
+ import { DUPLICATE_NODE_CODE, INVALID_UTF8_CODE, OPTION_IGNORED_CODE, SYNTAX_CODE } from "../../common/codes.js";
19
+ import { DirectionResolver } from "../../common/direction.js";
20
+ import { coerceIdText, ID_MERGED_CODE, IdCoercer } from "../../common/ids.js";
21
+ import { LineReader, throwIfAborted } from "../../common/input.js";
22
+ import { reportSinkOptions, reportUnusedOptions, resolveImportOptions, SINK_OPTION_CODE, } from "../../common/options.js";
23
+ import { ImportReportBuilder } from "../../common/report.js";
24
+ import { isNumericText, TextCellWriter, WIDENING_UNSUPPORTED_CODE } from "../../common/text.js";
25
+ import { isWeightField, parseWeightText } from "../../common/weights.js";
26
+ import { isCommentOrBlank, isIntervalToken, isSectionLine, isVertexNumber, LABEL_COLUMN, ORIGINAL_ID_KEY, parseIntervals, parseSectionHeader, POSITION_COLUMN, RELATION_COLUMN, SHAPE_COLUMN, SHAPES, SPELLS_COLUMN, tokenize, VALUE_COLUMN, VALUE_FIELD, } from "./syntax.js";
27
+ /** Issue codes of the Pajek importer. */
28
+ export const PAJEK_ISSUE = Object.freeze({
29
+ /** The input holds invalid UTF-8 (fatal). */
30
+ INVALID_UTF8: INVALID_UTF8_CODE,
31
+ /** Fatal: no `*Vertices` section (an empty file, or not a Pajek network). */
32
+ NO_VERTICES: "E_PAJEK_NO_VERTICES",
33
+ /** Fatal: `*Vertices` without a vertex count, or one the sink cannot hold. */
34
+ VERTICES_COUNT: "E_PAJEK_VERTICES_COUNT",
35
+ /** Aborts: a second `*Vertices` or `*Network` section; only one network per file is read. */
36
+ MULTIPLE_NETWORKS: "E_PAJEK_MULTIPLE_NETWORKS",
37
+ /** A data line before the first section header. */
38
+ OUTSIDE_SECTION: "E_PAJEK_OUTSIDE_SECTION",
39
+ /** A section header the importer cannot parse. */
40
+ SYNTAX: SYNTAX_CODE,
41
+ /** A double quote not closed before the end of the line. */
42
+ UNTERMINATED_QUOTE: "E_PAJEK_UNTERMINATED_QUOTE",
43
+ /** A vertex line the grammar does not accept. */
44
+ VERTEX_LINE: "E_PAJEK_VERTEX_LINE",
45
+ /** A vertex number outside the declared range. */
46
+ VERTEX_RANGE: "E_PAJEK_VERTEX_RANGE",
47
+ /** Fewer vertex lines than `*Vertices` declares (vertices without a line have no label). */
48
+ VERTEX_COUNT: "E_PAJEK_VERTEX_COUNT",
49
+ /** A second line for the same vertex; the later values overwrite. */
50
+ DUPLICATE_NODE: DUPLICATE_NODE_CODE,
51
+ /** A line (arc, edge, list or matrix row) the grammar does not accept. */
52
+ LINE: "E_PAJEK_LINE",
53
+ /** A line endpoint outside the declared vertex range (the core's code, forwarded). */
54
+ UNKNOWN_NODE: "E_UNKNOWN_NODE",
55
+ /** A malformed time interval token. */
56
+ INTERVAL: "E_PAJEK_INTERVAL",
57
+ /** A `*Matrix` section with the wrong number of rows. */
58
+ MATRIX_ROWS: "E_PAJEK_MATRIX_ROWS",
59
+ /** A project-file section (`*Partition`, `*Vector`, ...) the importer does not read. */
60
+ UNSUPPORTED_SECTION: "E_PAJEK_UNSUPPORTED_SECTION",
61
+ /** Tokens after a section header the grammar does not account for. */
62
+ HEADER_EXTRA: "W_PAJEK_HEADER_EXTRA",
63
+ /** The file declares vertices but no line section. */
64
+ NO_LINES: "W_PAJEK_NO_LINES",
65
+ /** Vertex numbering starts at 0 rather than 1. */
66
+ ZERO_BASED: "W_PAJEK_ZERO_BASED",
67
+ /** Vertex lines mix two and three coordinates. */
68
+ COORD_DIMS: "W_PAJEK_COORD_DIMS",
69
+ /** Two vertices share a label under nodeIdFrom "label" and became one node. */
70
+ LABEL_MERGED: "W_PAJEK_LABEL_MERGED",
71
+ /** Two distinct label texts became one numeric id under ids "number". */
72
+ ID_MERGED: ID_MERGED_CODE,
73
+ /** A parameter column of `2.0`-style text kept at the value-inferred dtype (the sink cannot widen). */
74
+ WIDENING_UNSUPPORTED: WIDENING_UNSUPPORTED_CODE,
75
+ /** A structural column (label, position, shape, spells, relation) renamed `<name>#<id>` because the name was taken (design section 5.6). */
76
+ COLUMN_RENAMED: RENAMED_CODE,
77
+ /** A structural column declared without its role because the sink already holds it. */
78
+ ROLE_TAKEN: ROLE_TAKEN_CODE,
79
+ /** Two vertices carry the same `graphty_originalId` under restoreMangledIds and became one node. */
80
+ ORIGINAL_ID_MERGED: "W_PAJEK_ORIGINAL_ID_MERGED",
81
+ /** A vertex line's `graphty_originalId` came after a later vertex's line had created it under its number. */
82
+ ORIGINAL_ID_UNRESTORED: "W_PAJEK_ORIGINAL_ID_UNRESTORED",
83
+ /** A common option the importer has no use for (weightFrom naming a parameter and restoreMangledIds are honoured; long, hyperedges are not). */
84
+ OPTION_IGNORED: OPTION_IGNORED_CODE,
85
+ /** A builder-policy option the caller passed that the caller's sink does not use (the shared W_SINK_OPTION). */
86
+ SINK_OPTION: SINK_OPTION_CODE,
87
+ });
88
+ /** The common options the Pajek importer reads (the rest is reported by reportUnusedOptions). */
89
+ const USED_OPTIONS = new Set([
90
+ "ids",
91
+ "nodeIdFrom",
92
+ "restoreMangledIds",
93
+ "addMissingNodes",
94
+ "duplicateEdges",
95
+ "selfLoops",
96
+ "onMixedDirection",
97
+ "defaultDirected",
98
+ "weightFrom",
99
+ "weightDtype",
100
+ "errorLimit",
101
+ "signal",
102
+ "onProgress",
103
+ ]);
104
+ const DEFAULTS = { ids: "canonical", defaultDirected: true, weightFrom: VALUE_FIELD };
105
+ /** Lines between two checks of the cancellation signal (a whole string input is one chunk). */
106
+ const ABORT_CHECK_INTERVAL = 64;
107
+ const FIRST_VERTEX_VALUES = new Set([0, 1, "auto"]);
108
+ const LABEL_DECL = {
109
+ name: LABEL_COLUMN,
110
+ dtype: "string",
111
+ nullable: true,
112
+ role: "label",
113
+ origin: { format: "pajek", id: "label" },
114
+ };
115
+ const SHAPE_DECL = {
116
+ name: SHAPE_COLUMN,
117
+ dtype: "dict",
118
+ nullable: true,
119
+ origin: { format: "pajek", id: "shape" },
120
+ };
121
+ const RELATION_DECL = {
122
+ name: RELATION_COLUMN,
123
+ dtype: "dict",
124
+ nullable: true,
125
+ origin: { format: "pajek", id: "relation" },
126
+ };
127
+ /**
128
+ * The spells column declaration of a domain (design section 5.1: a list of f64 pairs).
129
+ * @returns a fresh declaration
130
+ */
131
+ function spellsDecl() {
132
+ return {
133
+ name: SPELLS_COLUMN,
134
+ dtype: "list",
135
+ itemDtype: "f64",
136
+ itemComponents: 2,
137
+ nullable: true,
138
+ role: "spells",
139
+ origin: { format: "pajek", id: "interval" },
140
+ };
141
+ }
142
+ /**
143
+ * The position column declaration (design section 5.2: f32 x 3, role position, units "file").
144
+ * @param sourceDims - 2 or 3, the coordinate count of the first vertex line with coordinates
145
+ * @returns a fresh declaration
146
+ */
147
+ function positionDecl(sourceDims) {
148
+ return {
149
+ name: POSITION_COLUMN,
150
+ dtype: "f32",
151
+ components: 3,
152
+ nullable: true,
153
+ role: "position",
154
+ origin: { format: "pajek", id: "coordinates" },
155
+ extra: { sourceDims, units: "file" },
156
+ };
157
+ }
158
+ /** A problem with one line, recorded as an issue by the per-line catch. */
159
+ class LineError extends Error {
160
+ /**
161
+ * Create a line error.
162
+ * @param category - the issue category
163
+ * @param code - the issue code
164
+ * @param message - a plain-ASCII message
165
+ */
166
+ constructor(category, code, message) {
167
+ super(message);
168
+ this.name = "PajekLineError";
169
+ this.category = category;
170
+ this.code = code;
171
+ }
172
+ }
173
+ /**
174
+ * One import call's state machine: the sections, the vertex range, the columns declared so far
175
+ * and the direction resolver.
176
+ */
177
+ class PajekParser {
178
+ /**
179
+ * Create the parser for one import call.
180
+ * @param sink - the sink
181
+ * @param report - the report
182
+ * @param options - the resolved common options
183
+ * @param firstVertex - the firstVertex format option
184
+ */
185
+ constructor(sink, report, options, firstVertex) {
186
+ this.section = "none";
187
+ /** The direction of the current line section. */
188
+ this.kind = "directed";
189
+ /** The relation name of the current line section, or null. */
190
+ this.relation = null;
191
+ /** `*Vertices N`; -1 before the header. */
192
+ this.vertexCount = -1;
193
+ /** The first-mode count of a two-mode network, or null. */
194
+ this.firstMode = null;
195
+ this.networkName = null;
196
+ this.base = 1;
197
+ this.baseKnown = false;
198
+ this.nodesCreated = false;
199
+ this.verticesDone = false;
200
+ this.sawLineSection = false;
201
+ /** Node index per vertex position (vertex number minus base). */
202
+ this.indexOfPos = new Uint32Array(0);
203
+ /**
204
+ * The node id per vertex position when the vertex lines decide the ids (nodeIdFrom "label",
205
+ * or a restorable `graphty_originalId` under restoreMangledIds); null when the ids are the
206
+ * vertex numbers and every vertex is created up front.
207
+ */
208
+ this.idOfPos = null;
209
+ /** Which positions had a vertex line. */
210
+ this.seen = new Uint8Array(0);
211
+ this.seenCount = 0;
212
+ /**
213
+ * Every position below this one already has an id in `idOfPos`: the high-water mark of the
214
+ * gap-fill in `vertexLine`. Without it the gap-fill restarts at zero on every vertex line, which
215
+ * is quadratic in the vertex count for a file whose lines are in ascending order -- which is the
216
+ * order Pajek itself writes.
217
+ */
218
+ this.filledBelow = 0;
219
+ this.matrixRows = 0;
220
+ this.coordDims = 0;
221
+ this.labelHandle = INVALID_INDEX;
222
+ this.positionHandle = INVALID_INDEX;
223
+ this.shapeHandle = INVALID_INDEX;
224
+ this.nodeSpellsHandle = INVALID_INDEX;
225
+ this.relationHandle = INVALID_INDEX;
226
+ this.edgeSpellsHandle = INVALID_INDEX;
227
+ this.nodeParams = new Map();
228
+ this.edgeParams = new Map();
229
+ this.sink = sink;
230
+ this.report = report;
231
+ this.options = options;
232
+ this.firstVertex = firstVertex;
233
+ this.resolver = new DirectionResolver(sink, report, options.onMixedDirection);
234
+ this.coercer = new IdCoercer(options.ids);
235
+ }
236
+ /**
237
+ * Handle one line; every problem of the line is recorded as an issue and the line skipped.
238
+ * @param text - the line without its terminator
239
+ * @param line - the 1-based line number
240
+ */
241
+ line(text, line) {
242
+ if (isCommentOrBlank(text)) {
243
+ return;
244
+ }
245
+ try {
246
+ if (isSectionLine(text)) {
247
+ this.header(text, line);
248
+ return;
249
+ }
250
+ switch (this.section) {
251
+ case "none":
252
+ throw new LineError("parse-error", PAJEK_ISSUE.OUTSIDE_SECTION, "data before the first section header; a Pajek network starts with *Vertices");
253
+ case "vertices":
254
+ this.vertexLine(text, line);
255
+ return;
256
+ case "lines":
257
+ this.edgeLine(text, line);
258
+ return;
259
+ case "list":
260
+ this.listLine(text, line);
261
+ return;
262
+ case "matrix":
263
+ this.matrixLine(text, line);
264
+ return;
265
+ case "skip":
266
+ return;
267
+ default: {
268
+ const name = this.section;
269
+ throw new Error(`unknown section state ${name}`);
270
+ }
271
+ }
272
+ }
273
+ catch (err) {
274
+ if (err instanceof LineError) {
275
+ this.report.error(err.category, err.code, err.message, { line });
276
+ return;
277
+ }
278
+ this.report.recordError(err, { line });
279
+ }
280
+ }
281
+ /**
282
+ * End of input: the vertex set is completed, a network without line sections gets the default
283
+ * direction, and the metadata is written.
284
+ */
285
+ finish() {
286
+ if (this.vertexCount < 0) {
287
+ this.report.fail(PAJEK_ISSUE.NO_VERTICES, "not a Pajek network: no *Vertices section found");
288
+ }
289
+ this.endSection();
290
+ this.finishVertices();
291
+ if (!this.sawLineSection) {
292
+ this.report.warning("validation-error", PAJEK_ISSUE.NO_LINES, "the file declares vertices but no *Arcs, *Edges, *Arcslist, *Edgeslist or *Matrix section");
293
+ this.resolver.setHeader(this.options.defaultDirected);
294
+ }
295
+ if (this.coercer.mergeCount > 0) {
296
+ this.report.warning("coercion", PAJEK_ISSUE.ID_MERGED, `${this.coercer.mergeCount} label(s) merged into an id another label already produced under ids "number"`);
297
+ }
298
+ const meta = {
299
+ sourceFormat: "pajek",
300
+ ...(this.networkName === null ? {} : { name: this.networkName }),
301
+ ...(this.firstMode === null ? {} : { extra: { pajek: { firstMode: this.firstMode } } }),
302
+ };
303
+ this.sink.setMeta(meta);
304
+ }
305
+ // ============================================================ headers
306
+ /**
307
+ * A section header line.
308
+ * @param text - the line
309
+ * @param line - the line number
310
+ */
311
+ header(text, line) {
312
+ const skipping = this.section === "skip";
313
+ this.endSection();
314
+ const h = parseSectionHeader(text);
315
+ if (h === null) {
316
+ this.section = "skip";
317
+ throw new LineError("parse-error", PAJEK_ISSUE.SYNTAX, `cannot read the section header "${text}"`);
318
+ }
319
+ if (h.extra.length > 0) {
320
+ this.report.warning("validation-error", PAJEK_ISSUE.HEADER_EXTRA, `ignored ${h.extra.length} unexpected token(s) after *${h.keyword}: ${h.extra.join(" ")}`, { line });
321
+ }
322
+ switch (h.kind) {
323
+ case "network":
324
+ if (this.networkName !== null || this.vertexCount >= 0) {
325
+ this.refuseSecondNetwork(h.keyword, line);
326
+ }
327
+ this.networkName = h.name ?? "";
328
+ this.section = "none";
329
+ return;
330
+ case "vertices":
331
+ if (skipping && this.vertexCount >= 0) {
332
+ // the `*Vertices N` line of a skipped project-file section (*Partition, *Vector)
333
+ this.section = "skip";
334
+ return;
335
+ }
336
+ this.verticesHeader(h, line);
337
+ return;
338
+ case "arcs":
339
+ case "edges":
340
+ this.lineSection(h, line, "lines", h.kind === "arcs" ? "directed" : "undirected");
341
+ return;
342
+ case "arcslist":
343
+ case "edgeslist":
344
+ this.lineSection(h, line, "list", h.kind === "arcslist" ? "directed" : "undirected");
345
+ return;
346
+ case "matrix":
347
+ this.lineSection(h, line, "matrix", "directed");
348
+ this.matrixRows = 0;
349
+ return;
350
+ case "unsupported":
351
+ this.section = "skip";
352
+ this.report.error("unsupported", PAJEK_ISSUE.UNSUPPORTED_SECTION, `the *${h.keyword} section is not supported; its lines are skipped`, { line });
353
+ return;
354
+ default: {
355
+ const name = h.kind;
356
+ throw new Error(`unknown section kind ${name}`);
357
+ }
358
+ }
359
+ }
360
+ /**
361
+ * `*Vertices N [N1]`.
362
+ * @param h - the header
363
+ * @param line - the line number
364
+ */
365
+ verticesHeader(h, line) {
366
+ if (this.vertexCount >= 0) {
367
+ this.refuseSecondNetwork(h.keyword, line);
368
+ }
369
+ if (h.count === null) {
370
+ this.report.fail(PAJEK_ISSUE.VERTICES_COUNT, "*Vertices needs a vertex count", { line });
371
+ }
372
+ if (!Number.isSafeInteger(h.count)) {
373
+ this.report.fail(PAJEK_ISSUE.VERTICES_COUNT, `*Vertices ${h.count}: not a count`, { line });
374
+ }
375
+ try {
376
+ // the declared count must be reservable (the sink is the authority on what it can hold,
377
+ // E_TOO_LARGE beyond MAX_COUNT) before any vertex is materialised
378
+ this.sink.reserve(h.count);
379
+ }
380
+ catch (err) {
381
+ const reason = err instanceof Error ? err.message : String(err);
382
+ this.report.fail(PAJEK_ISSUE.VERTICES_COUNT, `*Vertices ${h.count}: the sink cannot hold that many (${reason})`, {
383
+ line,
384
+ });
385
+ }
386
+ this.vertexCount = h.count;
387
+ if (h.secondCount !== null) {
388
+ this.firstMode = h.secondCount;
389
+ }
390
+ this.section = "vertices";
391
+ this.indexOfPos = new Uint32Array(h.count);
392
+ this.seen = new Uint8Array(h.count);
393
+ this.filledBelow = 0;
394
+ if (this.options.nodeIdFrom === "label" || this.restoresIds()) {
395
+ this.idOfPos = new Array(h.count);
396
+ }
397
+ if (this.firstVertex !== "auto") {
398
+ this.setBase(this.firstVertex, line);
399
+ }
400
+ }
401
+ /**
402
+ * A line section header: the vertex set is completed, the first one sets the sink's direction
403
+ * (rule 1 of design section 8.4), later ones only switch the kind.
404
+ * @param h - the header
405
+ * @param line - the line number
406
+ * @param section - the parser state for the section
407
+ * @param kind - the direction of its lines
408
+ */
409
+ lineSection(h, line, section, kind) {
410
+ if (this.vertexCount < 0) {
411
+ this.report.fail(PAJEK_ISSUE.NO_VERTICES, `*${h.keyword} before *Vertices; a Pajek network starts with *Vertices`, { line });
412
+ }
413
+ this.finishVertices();
414
+ if (!this.sawLineSection) {
415
+ this.sawLineSection = true;
416
+ this.resolver.setHeader(kind === "directed", { line });
417
+ }
418
+ this.section = section;
419
+ this.kind = kind;
420
+ this.relation = h.relation === null ? null : (h.name ?? String(h.relation));
421
+ }
422
+ /**
423
+ * Refuse a second network: an issue, then ImportError with the report so far.
424
+ * @param keyword - the header keyword
425
+ * @param line - the line number
426
+ */
427
+ refuseSecondNetwork(keyword, line) {
428
+ const message = `*${keyword} starts a second network; the importer reads one network per file`;
429
+ this.report.error("unsupported", PAJEK_ISSUE.MULTIPLE_NETWORKS, message, { line });
430
+ throw this.report.abort(message, { code: PAJEK_ISSUE.MULTIPLE_NETWORKS, line });
431
+ }
432
+ /**
433
+ * Checks that run when a section ends.
434
+ */
435
+ endSection() {
436
+ if (this.section === "matrix" && this.matrixRows !== this.vertexCount) {
437
+ this.report.error("validation-error", PAJEK_ISSUE.MATRIX_ROWS, `*Matrix has ${this.matrixRows} row(s); *Vertices declares ${this.vertexCount}`);
438
+ }
439
+ this.section = "none";
440
+ }
441
+ // ============================================================ vertices
442
+ /**
443
+ * Fix the vertex numbering base and, under nodeIdFrom "id" / "index", create the declared
444
+ * vertices in number order so the id map is identity.
445
+ * @param base - 0 or 1
446
+ * @param line - the line number
447
+ */
448
+ setBase(base, line) {
449
+ this.base = base;
450
+ this.baseKnown = true;
451
+ if (base === 0) {
452
+ this.report.warning("coercion", PAJEK_ISSUE.ZERO_BASED, "vertex numbering starts at 0; Pajek numbers vertices from 1", { line });
453
+ }
454
+ }
455
+ /**
456
+ * Push vertices base..base+N-1 in order (nodeIdFrom "id" / "index"), so the id map is identity;
457
+ * called on the first vertex line (once it is known that the lines carry no `graphty_originalId`
458
+ * parameter to restore) or when the vertex section ends without one.
459
+ */
460
+ createNodes() {
461
+ if (this.nodesCreated || this.idOfPos !== null) {
462
+ return;
463
+ }
464
+ this.nodesCreated = true;
465
+ const n = this.vertexCount;
466
+ const { sink } = this;
467
+ for (let pos = 0; pos < n; pos++) {
468
+ this.indexOfPos[pos] = sink.addNode(this.idOfNumber(pos + this.base));
469
+ }
470
+ this.report.counts.nodes += n;
471
+ }
472
+ /**
473
+ * The id of a vertex number under nodeIdFrom "id" (the number coerced by the ids rule) or
474
+ * "index" (the position).
475
+ * @param k - the vertex number
476
+ * @returns the id
477
+ */
478
+ idOfNumber(k) {
479
+ if (this.options.nodeIdFrom === "index") {
480
+ return k - this.base;
481
+ }
482
+ switch (this.options.ids) {
483
+ case "canonical":
484
+ case "number":
485
+ return k;
486
+ case "keep":
487
+ case "string":
488
+ return String(k);
489
+ default:
490
+ return coerceIdText(String(k), this.options.ids);
491
+ }
492
+ }
493
+ /**
494
+ * The id of a vertex position, for addEdge.
495
+ * @param pos - the vertex number minus the base
496
+ * @returns the id
497
+ */
498
+ idAt(pos) {
499
+ if (this.idOfPos !== null) {
500
+ return this.idOfPos[pos];
501
+ }
502
+ return this.idOfNumber(pos + this.base);
503
+ }
504
+ /**
505
+ * Complete the vertex set once the vertex section is over: vertices without a line are
506
+ * created (under "label" or a restored `graphty_originalId` with their number as the id), and
507
+ * a partial list is reported.
508
+ */
509
+ finishVertices() {
510
+ if (this.verticesDone) {
511
+ return;
512
+ }
513
+ this.verticesDone = true;
514
+ if (!this.baseKnown) {
515
+ this.setBase(1, 0);
516
+ }
517
+ const n = this.vertexCount;
518
+ this.createNodes();
519
+ if (this.idOfPos !== null) {
520
+ for (let pos = 0; pos < n; pos++) {
521
+ if (this.idOfPos[pos] === undefined) {
522
+ this.numberedNode(this.idOfPos, pos);
523
+ }
524
+ }
525
+ }
526
+ // a partial list: fewer lines than declared, counting the lines skipped after an error
527
+ const lines = this.seenCount + this.report.counts.skippedNodes;
528
+ if (this.seenCount > 0 && lines < n) {
529
+ this.report.error("validation-error", PAJEK_ISSUE.VERTEX_COUNT, `*Vertices declares ${n} vertices but ${lines} vertex line(s) were read; the others have no label`);
530
+ }
531
+ }
532
+ /**
533
+ * A vertex line: `number [label] [x y [z]] [shape] [key value ...] [interval]`.
534
+ * @param text - the line
535
+ * @param line - the line number
536
+ */
537
+ vertexLine(text, line) {
538
+ const tokens = this.tokens(text, "node");
539
+ if (!isVertexNumber(tokens[0])) {
540
+ this.report.counts.skippedNodes++;
541
+ throw new LineError("parse-error", PAJEK_ISSUE.VERTEX_LINE, `a vertex line starts with a vertex number, not "${tokens[0]}"`);
542
+ }
543
+ const k = Number(tokens[0]);
544
+ if (!this.baseKnown) {
545
+ this.setBase(k === 0 ? 0 : 1, line);
546
+ }
547
+ const pos = k - this.base;
548
+ if (pos < 0 || pos >= this.vertexCount) {
549
+ this.report.counts.skippedNodes++;
550
+ throw new LineError("validation-error", PAJEK_ISSUE.VERTEX_RANGE, `vertex ${k} is outside ${this.base}..${this.base + this.vertexCount - 1}`);
551
+ }
552
+ // parse everything before writing anything
553
+ let i = 1;
554
+ const label = tokens.length > 1 ? tokens[1] : null;
555
+ if (label !== null) {
556
+ i = 2;
557
+ }
558
+ let x = 0;
559
+ let y = 0;
560
+ let z = 0;
561
+ let dims = 0;
562
+ if (i + 1 < tokens.length && isNumericText(tokens[i]) && isNumericText(tokens[i + 1])) {
563
+ x = Number(tokens[i]);
564
+ y = Number(tokens[i + 1]);
565
+ i += 2;
566
+ dims = 2;
567
+ if (i < tokens.length && isNumericText(tokens[i])) {
568
+ z = Number(tokens[i]);
569
+ i++;
570
+ dims = 3;
571
+ }
572
+ }
573
+ else if (i < tokens.length && isNumericText(tokens[i])) {
574
+ this.report.counts.skippedNodes++;
575
+ throw new LineError("parse-error", PAJEK_ISSUE.VERTEX_LINE, `vertex ${k}: a single coordinate "${tokens[i]}"; coordinates are x y [z]`);
576
+ }
577
+ let shape = null;
578
+ if (i < tokens.length && SHAPES.has(tokens[i])) {
579
+ shape = tokens[i];
580
+ i++;
581
+ }
582
+ const extras = this.extras(tokens, i, "node");
583
+ const restored = this.restoredId(extras);
584
+ this.createNodes();
585
+ // write
586
+ const repeated = this.seen[pos] !== 0;
587
+ if (repeated) {
588
+ this.report.warning("merged", PAJEK_ISSUE.DUPLICATE_NODE, `vertex ${k} has more than one line; the later values overwrite`, { line, element: String(k) });
589
+ }
590
+ else {
591
+ this.seen[pos] = 1;
592
+ this.seenCount++;
593
+ }
594
+ let index;
595
+ if (this.idOfPos !== null && !repeated && this.options.nodeIdFrom === "label") {
596
+ index = this.deferredNode(this.idOfPos, pos, k, label, line, "label");
597
+ }
598
+ else if (this.idOfPos !== null && this.idOfPos[pos] === undefined) {
599
+ // restore mode: the earlier positions without a line yet get their numbers first, so the
600
+ // index order is the vertex number order whatever the line order (the identity id map
601
+ // of an ordered file); then this vertex with its original id, or its number
602
+ for (let p = this.filledBelow; p < pos; p++) {
603
+ if (this.idOfPos[p] === undefined) {
604
+ this.numberedNode(this.idOfPos, p);
605
+ }
606
+ }
607
+ // after the loop on purpose: a throw part-way leaves the mark at its smaller, still
608
+ // correct value and the next line re-scans the range
609
+ if (pos > this.filledBelow) {
610
+ this.filledBelow = pos;
611
+ }
612
+ index = this.deferredNode(this.idOfPos, pos, k, restored, line, "originalId");
613
+ }
614
+ else {
615
+ index = this.indexOfPos[pos];
616
+ if (restored !== null && !repeated) {
617
+ // an out-of-order line: the vertex already exists under its number
618
+ this.report.warning("coercion", PAJEK_ISSUE.ORIGINAL_ID_UNRESTORED, `vertex ${k} carries ${ORIGINAL_ID_KEY} ${JSON.stringify(restored)} but a later vertex's line came first and created it under its number; the original id is not restored`, { line, element: String(k) });
619
+ }
620
+ }
621
+ const { sink } = this;
622
+ if (label !== null) {
623
+ if (this.labelHandle === INVALID_INDEX) {
624
+ this.labelHandle = declareResolved(sink, "node", LABEL_DECL, this.report, { line }).handle;
625
+ }
626
+ sink.setNodeValue(this.labelHandle, index, label);
627
+ }
628
+ if (dims > 0) {
629
+ if (this.positionHandle === INVALID_INDEX) {
630
+ this.coordDims = dims;
631
+ this.positionHandle = declareResolved(sink, "node", positionDecl(dims), this.report, { line }).handle;
632
+ }
633
+ else if (dims !== this.coordDims) {
634
+ this.report.warnOnce("validation-error", PAJEK_ISSUE.COORD_DIMS, `vertex lines mix ${this.coordDims} and ${dims} coordinates; the position column records sourceDims ${this.coordDims}`, { line, element: String(k) });
635
+ }
636
+ sink.setNodeValue(this.positionHandle, index, [x, y, z]);
637
+ }
638
+ if (shape !== null) {
639
+ if (this.shapeHandle === INVALID_INDEX && this.nodeParams.has(SHAPE_COLUMN)) {
640
+ // an earlier `shape "<text>"` parameter owns the column: the keyword is written there
641
+ this.writeParameter("node", SHAPE_COLUMN, index, shape);
642
+ }
643
+ else {
644
+ if (this.shapeHandle === INVALID_INDEX) {
645
+ this.shapeHandle = declareResolved(sink, "node", SHAPE_DECL, this.report, { line }).handle;
646
+ }
647
+ sink.setNodeValue(this.shapeHandle, index, shape);
648
+ }
649
+ }
650
+ for (let p = 0; p < extras.keys.length; p++) {
651
+ this.writeParameter("node", extras.keys[p], index, extras.values[p]);
652
+ }
653
+ if (extras.spells !== null) {
654
+ if (this.nodeSpellsHandle === INVALID_INDEX) {
655
+ this.nodeSpellsHandle = declareResolved(sink, "node", spellsDecl(), this.report, { line }).handle;
656
+ }
657
+ sink.setNodeValue(this.nodeSpellsHandle, index, extras.spells);
658
+ }
659
+ }
660
+ /**
661
+ * Whether a `graphty_originalId` parameter is restored as the vertex id: restoreMangledIds
662
+ * under nodeIdFrom "id" (under "label" / "index" the parameter stays an ordinary column).
663
+ * @returns true when the vertex lines may carry restorable ids
664
+ */
665
+ restoresIds() {
666
+ return this.options.restoreMangledIds && this.options.nodeIdFrom === "id";
667
+ }
668
+ /**
669
+ * Push a vertex whose id is its number (restore mode, a position whose line has not come or
670
+ * carries no original id; or the end of the vertex section under "label").
671
+ * @param ids - the id per vertex position
672
+ * @param pos - the vertex position
673
+ */
674
+ numberedNode(ids, pos) {
675
+ const id = this.idOfNumber(pos + this.base);
676
+ ids[pos] = id;
677
+ this.indexOfPos[pos] = this.sink.addNode(id);
678
+ this.report.counts.nodes++;
679
+ }
680
+ /**
681
+ * The `graphty_originalId` parameter of a vertex line, taken out of the extras when it is to be
682
+ * restored as the id (restoreMangledIds, under nodeIdFrom "id"); otherwise null and the
683
+ * parameter stays an ordinary column.
684
+ * @param extras - the parsed extras
685
+ * @returns the original id text, or null
686
+ */
687
+ restoredId(extras) {
688
+ if (!this.restoresIds()) {
689
+ return null;
690
+ }
691
+ const at = extras.keys.indexOf(ORIGINAL_ID_KEY);
692
+ if (at < 0) {
693
+ return null;
694
+ }
695
+ const [text] = extras.values.splice(at, 1);
696
+ extras.keys.splice(at, 1);
697
+ return text;
698
+ }
699
+ /**
700
+ * Push the node of a vertex line whose id comes from the line itself: its label under
701
+ * nodeIdFrom "label", or its restored `graphty_originalId` (the number when the line carries
702
+ * none), reporting a merge when the id was seen before.
703
+ * @param ids - the id per vertex position
704
+ * @param pos - the vertex position
705
+ * @param k - the vertex number
706
+ * @param text - the label or original id text, or null
707
+ * @param line - the line number
708
+ * @param from - which text the id comes from
709
+ * @returns the node index
710
+ */
711
+ deferredNode(ids, pos, k, text, line, from) {
712
+ const id = text === null ? this.idOfNumber(k) : this.coercer.text(text);
713
+ const before = this.sink.indexOf(id);
714
+ const index = this.sink.addNode(id);
715
+ ids[pos] = id;
716
+ this.indexOfPos[pos] = index;
717
+ this.report.counts.nodes++;
718
+ if (before !== INVALID_INDEX) {
719
+ this.report.warning("merged", from === "label" ? PAJEK_ISSUE.LABEL_MERGED : PAJEK_ISSUE.ORIGINAL_ID_MERGED, from === "label"
720
+ ? `vertex ${k} has the label ${JSON.stringify(String(id))} of an earlier vertex; both are one node`
721
+ : `vertex ${k} has the ${ORIGINAL_ID_KEY} ${JSON.stringify(String(id))} of an earlier vertex; both are one node`, { line, element: String(k) });
722
+ }
723
+ return index;
724
+ }
725
+ // ============================================================ lines
726
+ /**
727
+ * An arc or edge line: `u v [value] [key value ...] [interval]`.
728
+ * @param text - the line
729
+ * @param line - the line number
730
+ */
731
+ edgeLine(text, line) {
732
+ const tokens = this.tokens(text, "edge");
733
+ if (tokens.length < 2 || !isVertexNumber(tokens[0]) || !isVertexNumber(tokens[1])) {
734
+ this.report.counts.skippedEdges++;
735
+ throw new LineError("parse-error", PAJEK_ISSUE.LINE, `a line starts with two vertex numbers, not "${tokens.slice(0, 2).join(" ")}"`);
736
+ }
737
+ const u = this.endpoint(Number(tokens[0]));
738
+ const v = this.endpoint(Number(tokens[1]));
739
+ let i = 2;
740
+ let valueText = null;
741
+ if (i < tokens.length && isNumericText(tokens[i])) {
742
+ valueText = tokens[i];
743
+ i++;
744
+ }
745
+ const extras = this.extras(tokens, i, "edge");
746
+ const { weightFrom } = this.options;
747
+ let weight;
748
+ let valueIsWeight = false;
749
+ if (isWeightField(VALUE_FIELD, weightFrom)) {
750
+ valueIsWeight = true;
751
+ if (valueText !== null) {
752
+ weight = this.weightOf(valueText);
753
+ }
754
+ }
755
+ else if (weightFrom !== null) {
756
+ const at = extras.keys.indexOf(weightFrom);
757
+ if (at >= 0) {
758
+ weight = this.weightOf(extras.values[at]);
759
+ extras.keys.splice(at, 1);
760
+ extras.values.splice(at, 1);
761
+ }
762
+ }
763
+ const e = this.pushEdge(u, v, weight, line);
764
+ if (valueText !== null && !valueIsWeight) {
765
+ // inferred like every parameter, so a re-import of the exporter's `value` parameter agrees
766
+ this.writeParameter("edge", VALUE_COLUMN, e, valueText);
767
+ }
768
+ this.writeEdgeExtras(e, extras);
769
+ }
770
+ /**
771
+ * An adjacency list line of `*Arcslist` / `*Edgeslist`: `v n1 n2 ...`.
772
+ * @param text - the line
773
+ * @param line - the line number
774
+ */
775
+ listLine(text, line) {
776
+ const tokens = this.tokens(text, "edge");
777
+ for (const token of tokens) {
778
+ if (!isVertexNumber(token)) {
779
+ this.report.counts.skippedEdges++;
780
+ throw new LineError("parse-error", PAJEK_ISSUE.LINE, `an adjacency list holds vertex numbers only, not "${token}"`);
781
+ }
782
+ }
783
+ const u = this.endpoint(Number(tokens[0]));
784
+ for (let i = 1; i < tokens.length; i++) {
785
+ const v = this.endpoint(Number(tokens[i]));
786
+ const e = this.pushEdge(u, v, undefined, line);
787
+ this.writeRelation(e);
788
+ }
789
+ }
790
+ /**
791
+ * A `*Matrix` row: N values, every non-zero entry an arc from the row's vertex with that value.
792
+ * @param text - the line
793
+ * @param line - the line number
794
+ */
795
+ matrixLine(text, line) {
796
+ const tokens = this.tokens(text, "edge");
797
+ const row = this.matrixRows;
798
+ this.matrixRows++;
799
+ if (row >= this.vertexCount) {
800
+ throw new LineError("validation-error", PAJEK_ISSUE.MATRIX_ROWS, `*Matrix row ${row + 1} is beyond the ${this.vertexCount} declared vertices`);
801
+ }
802
+ if (tokens.length !== this.vertexCount) {
803
+ throw new LineError("parse-error", PAJEK_ISSUE.LINE, `*Matrix row ${row + 1} has ${tokens.length} value(s); *Vertices declares ${this.vertexCount}`);
804
+ }
805
+ for (const token of tokens) {
806
+ if (!isNumericText(token)) {
807
+ throw new LineError("parse-error", PAJEK_ISSUE.LINE, `*Matrix row ${row + 1}: "${token}" is not a number`);
808
+ }
809
+ }
810
+ for (let j = 0; j < tokens.length; j++) {
811
+ const value = Number(tokens[j]);
812
+ if (value === 0) {
813
+ continue;
814
+ }
815
+ const e = this.pushEdge(row, j, value, line);
816
+ this.writeRelation(e);
817
+ }
818
+ }
819
+ /**
820
+ * Push one line through the direction resolver, counting the logical edges it produced.
821
+ * @param u - the source position
822
+ * @param v - the target position
823
+ * @param weight - the weight, or undefined for none
824
+ * @param line - the line number
825
+ * @returns the primary logical edge index
826
+ */
827
+ pushEdge(u, v, weight, line) {
828
+ const { sink } = this;
829
+ const before = sink.edgeCount;
830
+ const e = this.resolver.addEdge(this.idAt(u), this.idAt(v), this.kind, weight, { line });
831
+ this.report.counts.edges += sink.edgeCount - before;
832
+ return e;
833
+ }
834
+ /**
835
+ * Write the relation, parameters and spells of a line on its primary half.
836
+ * @param e - the edge index
837
+ * @param extras - the parsed extras
838
+ */
839
+ writeEdgeExtras(e, extras) {
840
+ const { sink } = this;
841
+ this.writeRelation(e);
842
+ for (let p = 0; p < extras.keys.length; p++) {
843
+ this.writeParameter("edge", extras.keys[p], e, extras.values[p]);
844
+ }
845
+ if (extras.spells !== null) {
846
+ if (this.edgeSpellsHandle === INVALID_INDEX) {
847
+ this.edgeSpellsHandle = declareResolved(sink, "edge", spellsDecl(), this.report).handle;
848
+ }
849
+ sink.setEdgeValue(this.edgeSpellsHandle, e, extras.spells);
850
+ }
851
+ }
852
+ /**
853
+ * Write the current section's relation name on an edge, when the section has one.
854
+ * @param e - the edge index
855
+ */
856
+ writeRelation(e) {
857
+ if (this.relation === null) {
858
+ return;
859
+ }
860
+ if (this.relationHandle === INVALID_INDEX) {
861
+ this.relationHandle = declareResolved(this.sink, "edge", RELATION_DECL, this.report).handle;
862
+ }
863
+ this.sink.setEdgeValue(this.relationHandle, e, this.relation);
864
+ }
865
+ /**
866
+ * A line endpoint: the vertex number checked against the declared range; a reference outside
867
+ * it is a missing-value error and the line is skipped (Pajek's `*Vertices N` bounds the id space,
868
+ * so addMissingNodes does not apply).
869
+ * @param k - the vertex number
870
+ * @returns the vertex position
871
+ */
872
+ endpoint(k) {
873
+ const pos = k - this.base;
874
+ if (pos < 0 || pos >= this.vertexCount) {
875
+ this.report.counts.skippedEdges++;
876
+ throw new LineError("missing-value", PAJEK_ISSUE.UNKNOWN_NODE, `vertex ${k} is outside ${this.base}..${this.base + this.vertexCount - 1}`);
877
+ }
878
+ return pos;
879
+ }
880
+ /**
881
+ * The weight of a line value text.
882
+ * @param text - the text
883
+ * @returns the weight; E_INVALID_WEIGHT is recorded by the caller's catch
884
+ */
885
+ weightOf(text) {
886
+ try {
887
+ return parseWeightText(text);
888
+ }
889
+ catch (err) {
890
+ this.report.counts.skippedEdges++;
891
+ throw err;
892
+ }
893
+ }
894
+ // ============================================================ shared
895
+ /**
896
+ * Tokenize a data line.
897
+ * @param text - the line
898
+ * @param domain - what is skipped on an unterminated quote
899
+ * @returns the tokens (at least one: blank lines never reach here)
900
+ */
901
+ tokens(text, domain) {
902
+ const tokens = tokenize(text);
903
+ if (tokens === null) {
904
+ if (domain === "node") {
905
+ this.report.counts.skippedNodes++;
906
+ }
907
+ else {
908
+ this.report.counts.skippedEdges++;
909
+ }
910
+ throw new LineError("parse-error", PAJEK_ISSUE.UNTERMINATED_QUOTE, "a double quote is not closed");
911
+ }
912
+ return tokens;
913
+ }
914
+ /**
915
+ * The `key value` parameters and the interval token at the end of a row, validated before
916
+ * anything is written.
917
+ * @param tokens - the row's tokens
918
+ * @param start - the first token to read
919
+ * @param domain - what is skipped when the row is malformed
920
+ * @returns the parsed extras
921
+ */
922
+ extras(tokens, start, domain) {
923
+ const extras = { keys: [], values: [], spells: null };
924
+ for (let i = start; i < tokens.length;) {
925
+ const token = tokens[i];
926
+ if (isIntervalToken(token)) {
927
+ try {
928
+ extras.spells = parseIntervals(token);
929
+ }
930
+ catch (err) {
931
+ this.skip(domain);
932
+ throw new LineError("validation-error", PAJEK_ISSUE.INTERVAL, err instanceof Error ? err.message : String(err));
933
+ }
934
+ i++;
935
+ continue;
936
+ }
937
+ if (i + 1 >= tokens.length) {
938
+ this.skip(domain);
939
+ throw new LineError("parse-error", domain === "node" ? PAJEK_ISSUE.VERTEX_LINE : PAJEK_ISSUE.LINE, `parameter "${token}" has no value`);
940
+ }
941
+ extras.keys.push(token);
942
+ extras.values.push(tokens[i + 1]);
943
+ i += 2;
944
+ }
945
+ return extras;
946
+ }
947
+ /**
948
+ * Count a skipped element.
949
+ * @param domain - node or edge
950
+ */
951
+ skip(domain) {
952
+ if (domain === "node") {
953
+ this.report.counts.skippedNodes++;
954
+ }
955
+ else {
956
+ this.report.counts.skippedEdges++;
957
+ }
958
+ }
959
+ /**
960
+ * Write one `key value` parameter cell through the column's text writer (the 5.1 grammar per
961
+ * column, the lexical form kept). A `shape` parameter next to the shape keyword column goes
962
+ * into that column (a dict takes any text), and a shape keyword next to a `shape` parameter
963
+ * column into that one, so a file mixing the two spellings reads as one column.
964
+ * @param domain - node or edge
965
+ * @param key - the parameter key (the column name)
966
+ * @param row - the node or edge index
967
+ * @param text - the value text
968
+ */
969
+ writeParameter(domain, key, row, text) {
970
+ if (domain === "node" && key === SHAPE_COLUMN && this.shapeHandle !== INVALID_INDEX) {
971
+ this.sink.setNodeValue(this.shapeHandle, row, text);
972
+ return;
973
+ }
974
+ const writers = domain === "node" ? this.nodeParams : this.edgeParams;
975
+ let writer = writers.get(key);
976
+ if (writer === undefined) {
977
+ writer = new TextCellWriter(key, domain, this.sink, this.report);
978
+ writers.set(key, writer);
979
+ }
980
+ writer.write(row, text);
981
+ }
982
+ }
983
+ /**
984
+ * Resolve the firstVertex option.
985
+ * @param value - the caller's value
986
+ * @returns 0, 1 or "auto"; E_UNSUPPORTED for anything else
987
+ */
988
+ function firstVertexOption(value) {
989
+ if (value === undefined) {
990
+ return "auto";
991
+ }
992
+ if (FIRST_VERTEX_VALUES.has(value)) {
993
+ return value;
994
+ }
995
+ throw new GraphFormatError("E_UNSUPPORTED", `option firstVertex: ${JSON.stringify(value) ?? typeof value} is not 0, 1 or "auto"`, {
996
+ option: "firstVertex",
997
+ found: value,
998
+ });
999
+ }
1000
+ const HEAD_PATTERN = /^\s*\*(vertices|network)\b/i;
1001
+ /** The Pajek NET importer. */
1002
+ export const pajekImporter = Object.freeze({
1003
+ format: "pajek",
1004
+ extensions: Object.freeze([".net", ".paj"]),
1005
+ mimeTypes: Object.freeze(["text/x-pajek", "text/plain"]),
1006
+ /**
1007
+ * Confidence that the input is a Pajek network: it starts with `*Vertices` (or `*Network`).
1008
+ * @param head - the first bytes
1009
+ * @returns 0.9 for a `*Vertices` start, 0.8 for `*Network`, 0 otherwise
1010
+ */
1011
+ sniff(head) {
1012
+ const text = new TextDecoder("utf-8").decode(head.subarray(0, Math.min(head.byteLength, 512)));
1013
+ const match = HEAD_PATTERN.exec(text.startsWith(String.fromCharCode(0xfeff)) ? text.slice(1) : text);
1014
+ if (match === null) {
1015
+ return 0;
1016
+ }
1017
+ return match[1].toLowerCase() === "vertices" ? 0.9 : 0.8;
1018
+ },
1019
+ /**
1020
+ * Read a Pajek network into the sink.
1021
+ * @param input - the text, bytes or stream
1022
+ * @param sink - the sink
1023
+ * @param options - common and Pajek options
1024
+ * @returns the report
1025
+ */
1026
+ async import(input, sink, options) {
1027
+ const resolved = resolveImportOptions(options, DEFAULTS);
1028
+ const firstVertex = firstVertexOption(options?.firstVertex);
1029
+ const report = new ImportReportBuilder("pajek", resolved.errorLimit);
1030
+ reportSinkOptions(sink, options, report);
1031
+ reportUnusedOptions(options, report, USED_OPTIONS);
1032
+ const parser = new PajekParser(sink, report, resolved, firstVertex);
1033
+ const reader = new LineReader(input, report, resolved);
1034
+ let sinceCheck = 0;
1035
+ for await (const text of reader) {
1036
+ parser.line(text, reader.line);
1037
+ if (++sinceCheck >= ABORT_CHECK_INTERVAL) {
1038
+ sinceCheck = 0;
1039
+ throwIfAborted(resolved.signal);
1040
+ }
1041
+ }
1042
+ parser.finish();
1043
+ throwIfAborted(resolved.signal);
1044
+ return report.finish();
1045
+ },
1046
+ });
1047
+ //# sourceMappingURL=importer.js.map