@graphty/graph-io 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -28
  3. package/dist/chunks/children-CL3Cy0ez.js +238 -0
  4. package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
  5. package/dist/chunks/escape-DyI8JofU.js +938 -0
  6. package/dist/chunks/escape-DyI8JofU.js.map +1 -0
  7. package/dist/chunks/importer-CQnJuWJw.js +2987 -0
  8. package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
  9. package/dist/chunks/importer-CpCpfbxr.js +2015 -0
  10. package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
  11. package/dist/chunks/importer-DbnGYr3_.js +2342 -0
  12. package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
  13. package/dist/chunks/importer-GozH8DkN.js +3050 -0
  14. package/dist/chunks/importer-GozH8DkN.js.map +1 -0
  15. package/dist/chunks/records-CGpxszm1.js +605 -0
  16. package/dist/chunks/records-CGpxszm1.js.map +1 -0
  17. package/dist/chunks/text-CajMdVFy.js +189 -0
  18. package/dist/chunks/text-CajMdVFy.js.map +1 -0
  19. package/dist/chunks/writer-DxSKC7TL.js +2842 -0
  20. package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
  21. package/dist/csv.d.ts +1 -0
  22. package/dist/csv.js +1702 -0
  23. package/dist/csv.js.map +1 -0
  24. package/dist/dot.d.ts +1 -0
  25. package/dist/dot.js +8 -0
  26. package/dist/dot.js.map +1 -0
  27. package/dist/gexf.d.ts +1 -0
  28. package/dist/gexf.js +3466 -0
  29. package/dist/gexf.js.map +1 -0
  30. package/dist/gml.d.ts +1 -0
  31. package/dist/gml.js +2647 -0
  32. package/dist/gml.js.map +1 -0
  33. package/dist/graph-io.d.ts +1 -0
  34. package/dist/graph-io.js +790 -0
  35. package/dist/graph-io.js.map +1 -0
  36. package/dist/graphml.d.ts +1 -0
  37. package/dist/graphml.js +8 -0
  38. package/dist/graphml.js.map +1 -0
  39. package/dist/json.d.ts +1 -0
  40. package/dist/json.js +11 -0
  41. package/dist/json.js.map +1 -0
  42. package/dist/neo4j.d.ts +1 -0
  43. package/dist/neo4j.js +2046 -0
  44. package/dist/neo4j.js.map +1 -0
  45. package/dist/pajek.d.ts +1 -0
  46. package/dist/pajek.js +8 -0
  47. package/dist/pajek.js.map +1 -0
  48. package/dist/src/children.d.ts +134 -0
  49. package/dist/src/children.d.ts.map +1 -0
  50. package/dist/src/children.js +274 -0
  51. package/dist/src/children.js.map +1 -0
  52. package/dist/src/common/attributes.d.ts +229 -0
  53. package/dist/src/common/attributes.d.ts.map +1 -0
  54. package/dist/src/common/attributes.js +368 -0
  55. package/dist/src/common/attributes.js.map +1 -0
  56. package/dist/src/common/codes.d.ts +105 -0
  57. package/dist/src/common/codes.d.ts.map +1 -0
  58. package/dist/src/common/codes.js +107 -0
  59. package/dist/src/common/codes.js.map +1 -0
  60. package/dist/src/common/declared-types.d.ts +84 -0
  61. package/dist/src/common/declared-types.d.ts.map +1 -0
  62. package/dist/src/common/declared-types.js +326 -0
  63. package/dist/src/common/declared-types.js.map +1 -0
  64. package/dist/src/common/direction.d.ts +206 -0
  65. package/dist/src/common/direction.d.ts.map +1 -0
  66. package/dist/src/common/direction.js +370 -0
  67. package/dist/src/common/direction.js.map +1 -0
  68. package/dist/src/common/escape.d.ts +92 -0
  69. package/dist/src/common/escape.d.ts.map +1 -0
  70. package/dist/src/common/escape.js +212 -0
  71. package/dist/src/common/escape.js.map +1 -0
  72. package/dist/src/common/export.d.ts +249 -0
  73. package/dist/src/common/export.d.ts.map +1 -0
  74. package/dist/src/common/export.js +594 -0
  75. package/dist/src/common/export.js.map +1 -0
  76. package/dist/src/common/format.d.ts +59 -0
  77. package/dist/src/common/format.d.ts.map +1 -0
  78. package/dist/src/common/format.js +106 -0
  79. package/dist/src/common/format.js.map +1 -0
  80. package/dist/src/common/ids.d.ts +83 -0
  81. package/dist/src/common/ids.d.ts.map +1 -0
  82. package/dist/src/common/ids.js +158 -0
  83. package/dist/src/common/ids.js.map +1 -0
  84. package/dist/src/common/input.d.ts +100 -0
  85. package/dist/src/common/input.d.ts.map +1 -0
  86. package/dist/src/common/input.js +335 -0
  87. package/dist/src/common/input.js.map +1 -0
  88. package/dist/src/common/lists.d.ts +34 -0
  89. package/dist/src/common/lists.d.ts.map +1 -0
  90. package/dist/src/common/lists.js +185 -0
  91. package/dist/src/common/lists.js.map +1 -0
  92. package/dist/src/common/options.d.ts +108 -0
  93. package/dist/src/common/options.d.ts.map +1 -0
  94. package/dist/src/common/options.js +265 -0
  95. package/dist/src/common/options.js.map +1 -0
  96. package/dist/src/common/report.d.ts +187 -0
  97. package/dist/src/common/report.d.ts.map +1 -0
  98. package/dist/src/common/report.js +274 -0
  99. package/dist/src/common/report.js.map +1 -0
  100. package/dist/src/common/temporal.d.ts +71 -0
  101. package/dist/src/common/temporal.d.ts.map +1 -0
  102. package/dist/src/common/temporal.js +266 -0
  103. package/dist/src/common/temporal.js.map +1 -0
  104. package/dist/src/common/text.d.ts +104 -0
  105. package/dist/src/common/text.d.ts.map +1 -0
  106. package/dist/src/common/text.js +255 -0
  107. package/dist/src/common/text.js.map +1 -0
  108. package/dist/src/common/weights.d.ts +77 -0
  109. package/dist/src/common/weights.d.ts.map +1 -0
  110. package/dist/src/common/weights.js +156 -0
  111. package/dist/src/common/weights.js.map +1 -0
  112. package/dist/src/common/writer.d.ts +51 -0
  113. package/dist/src/common/writer.d.ts.map +1 -0
  114. package/dist/src/common/writer.js +108 -0
  115. package/dist/src/common/writer.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +245 -0
  117. package/dist/src/common/xml.d.ts.map +1 -0
  118. package/dist/src/common/xml.js +942 -0
  119. package/dist/src/common/xml.js.map +1 -0
  120. package/dist/src/formats/csv/exporter.d.ts +70 -0
  121. package/dist/src/formats/csv/exporter.d.ts.map +1 -0
  122. package/dist/src/formats/csv/exporter.js +682 -0
  123. package/dist/src/formats/csv/exporter.js.map +1 -0
  124. package/dist/src/formats/csv/header.d.ts +66 -0
  125. package/dist/src/formats/csv/header.d.ts.map +1 -0
  126. package/dist/src/formats/csv/header.js +152 -0
  127. package/dist/src/formats/csv/header.js.map +1 -0
  128. package/dist/src/formats/csv/importer.d.ts +82 -0
  129. package/dist/src/formats/csv/importer.d.ts.map +1 -0
  130. package/dist/src/formats/csv/importer.js +849 -0
  131. package/dist/src/formats/csv/importer.js.map +1 -0
  132. package/dist/src/formats/csv/index.d.ts +60 -0
  133. package/dist/src/formats/csv/index.d.ts.map +1 -0
  134. package/dist/src/formats/csv/index.js +63 -0
  135. package/dist/src/formats/csv/index.js.map +1 -0
  136. package/dist/src/formats/csv/records.d.ts +188 -0
  137. package/dist/src/formats/csv/records.d.ts.map +1 -0
  138. package/dist/src/formats/csv/records.js +702 -0
  139. package/dist/src/formats/csv/records.js.map +1 -0
  140. package/dist/src/formats/csv/values.d.ts +105 -0
  141. package/dist/src/formats/csv/values.d.ts.map +1 -0
  142. package/dist/src/formats/csv/values.js +192 -0
  143. package/dist/src/formats/csv/values.js.map +1 -0
  144. package/dist/src/formats/dot/exporter.d.ts +52 -0
  145. package/dist/src/formats/dot/exporter.d.ts.map +1 -0
  146. package/dist/src/formats/dot/exporter.js +836 -0
  147. package/dist/src/formats/dot/exporter.js.map +1 -0
  148. package/dist/src/formats/dot/importer.d.ts +102 -0
  149. package/dist/src/formats/dot/importer.d.ts.map +1 -0
  150. package/dist/src/formats/dot/importer.js +1291 -0
  151. package/dist/src/formats/dot/importer.js.map +1 -0
  152. package/dist/src/formats/dot/index.d.ts +7 -0
  153. package/dist/src/formats/dot/index.d.ts.map +1 -0
  154. package/dist/src/formats/dot/index.js +7 -0
  155. package/dist/src/formats/dot/index.js.map +1 -0
  156. package/dist/src/formats/dot/names.d.ts +29 -0
  157. package/dist/src/formats/dot/names.d.ts.map +1 -0
  158. package/dist/src/formats/dot/names.js +28 -0
  159. package/dist/src/formats/dot/names.js.map +1 -0
  160. package/dist/src/formats/dot/tokenizer.d.ts +114 -0
  161. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
  162. package/dist/src/formats/dot/tokenizer.js +341 -0
  163. package/dist/src/formats/dot/tokenizer.js.map +1 -0
  164. package/dist/src/formats/gexf/exporter.d.ts +56 -0
  165. package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
  166. package/dist/src/formats/gexf/exporter.js +1395 -0
  167. package/dist/src/formats/gexf/exporter.js.map +1 -0
  168. package/dist/src/formats/gexf/importer.d.ts +73 -0
  169. package/dist/src/formats/gexf/importer.d.ts.map +1 -0
  170. package/dist/src/formats/gexf/importer.js +1880 -0
  171. package/dist/src/formats/gexf/importer.js.map +1 -0
  172. package/dist/src/formats/gexf/index.d.ts +96 -0
  173. package/dist/src/formats/gexf/index.d.ts.map +1 -0
  174. package/dist/src/formats/gexf/index.js +97 -0
  175. package/dist/src/formats/gexf/index.js.map +1 -0
  176. package/dist/src/formats/gexf/schema.d.ts +135 -0
  177. package/dist/src/formats/gexf/schema.d.ts.map +1 -0
  178. package/dist/src/formats/gexf/schema.js +323 -0
  179. package/dist/src/formats/gexf/schema.js.map +1 -0
  180. package/dist/src/formats/gml/exporter.d.ts +69 -0
  181. package/dist/src/formats/gml/exporter.d.ts.map +1 -0
  182. package/dist/src/formats/gml/exporter.js +1093 -0
  183. package/dist/src/formats/gml/exporter.js.map +1 -0
  184. package/dist/src/formats/gml/importer.d.ts +66 -0
  185. package/dist/src/formats/gml/importer.d.ts.map +1 -0
  186. package/dist/src/formats/gml/importer.js +1331 -0
  187. package/dist/src/formats/gml/importer.js.map +1 -0
  188. package/dist/src/formats/gml/index.d.ts +85 -0
  189. package/dist/src/formats/gml/index.d.ts.map +1 -0
  190. package/dist/src/formats/gml/index.js +88 -0
  191. package/dist/src/formats/gml/index.js.map +1 -0
  192. package/dist/src/formats/gml/syntax.d.ts +186 -0
  193. package/dist/src/formats/gml/syntax.d.ts.map +1 -0
  194. package/dist/src/formats/gml/syntax.js +467 -0
  195. package/dist/src/formats/gml/syntax.js.map +1 -0
  196. package/dist/src/formats/graphml/constants.d.ts +169 -0
  197. package/dist/src/formats/graphml/constants.d.ts.map +1 -0
  198. package/dist/src/formats/graphml/constants.js +165 -0
  199. package/dist/src/formats/graphml/constants.js.map +1 -0
  200. package/dist/src/formats/graphml/exporter.d.ts +34 -0
  201. package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
  202. package/dist/src/formats/graphml/exporter.js +1176 -0
  203. package/dist/src/formats/graphml/exporter.js.map +1 -0
  204. package/dist/src/formats/graphml/importer.d.ts +31 -0
  205. package/dist/src/formats/graphml/importer.d.ts.map +1 -0
  206. package/dist/src/formats/graphml/importer.js +1607 -0
  207. package/dist/src/formats/graphml/importer.js.map +1 -0
  208. package/dist/src/formats/graphml/index.d.ts +8 -0
  209. package/dist/src/formats/graphml/index.d.ts.map +1 -0
  210. package/dist/src/formats/graphml/index.js +8 -0
  211. package/dist/src/formats/graphml/index.js.map +1 -0
  212. package/dist/src/formats/graphml/tree.d.ts +72 -0
  213. package/dist/src/formats/graphml/tree.d.ts.map +1 -0
  214. package/dist/src/formats/graphml/tree.js +290 -0
  215. package/dist/src/formats/graphml/tree.js.map +1 -0
  216. package/dist/src/formats/json/dialect.d.ts +125 -0
  217. package/dist/src/formats/json/dialect.d.ts.map +1 -0
  218. package/dist/src/formats/json/dialect.js +262 -0
  219. package/dist/src/formats/json/dialect.js.map +1 -0
  220. package/dist/src/formats/json/exporter.d.ts +89 -0
  221. package/dist/src/formats/json/exporter.d.ts.map +1 -0
  222. package/dist/src/formats/json/exporter.js +1358 -0
  223. package/dist/src/formats/json/exporter.js.map +1 -0
  224. package/dist/src/formats/json/importer.d.ts +108 -0
  225. package/dist/src/formats/json/importer.d.ts.map +1 -0
  226. package/dist/src/formats/json/importer.js +1838 -0
  227. package/dist/src/formats/json/importer.js.map +1 -0
  228. package/dist/src/formats/json/index.d.ts +8 -0
  229. package/dist/src/formats/json/index.d.ts.map +1 -0
  230. package/dist/src/formats/json/index.js +8 -0
  231. package/dist/src/formats/json/index.js.map +1 -0
  232. package/dist/src/formats/neo4j/exporter.d.ts +68 -0
  233. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
  234. package/dist/src/formats/neo4j/exporter.js +1055 -0
  235. package/dist/src/formats/neo4j/exporter.js.map +1 -0
  236. package/dist/src/formats/neo4j/header.d.ts +52 -0
  237. package/dist/src/formats/neo4j/header.d.ts.map +1 -0
  238. package/dist/src/formats/neo4j/header.js +131 -0
  239. package/dist/src/formats/neo4j/header.js.map +1 -0
  240. package/dist/src/formats/neo4j/importer.d.ts +73 -0
  241. package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
  242. package/dist/src/formats/neo4j/importer.js +932 -0
  243. package/dist/src/formats/neo4j/importer.js.map +1 -0
  244. package/dist/src/formats/neo4j/index.d.ts +79 -0
  245. package/dist/src/formats/neo4j/index.d.ts.map +1 -0
  246. package/dist/src/formats/neo4j/index.js +83 -0
  247. package/dist/src/formats/neo4j/index.js.map +1 -0
  248. package/dist/src/formats/pajek/exporter.d.ts +58 -0
  249. package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
  250. package/dist/src/formats/pajek/exporter.js +825 -0
  251. package/dist/src/formats/pajek/exporter.js.map +1 -0
  252. package/dist/src/formats/pajek/importer.d.ts +88 -0
  253. package/dist/src/formats/pajek/importer.d.ts.map +1 -0
  254. package/dist/src/formats/pajek/importer.js +1047 -0
  255. package/dist/src/formats/pajek/importer.js.map +1 -0
  256. package/dist/src/formats/pajek/index.d.ts +7 -0
  257. package/dist/src/formats/pajek/index.d.ts.map +1 -0
  258. package/dist/src/formats/pajek/index.js +7 -0
  259. package/dist/src/formats/pajek/index.js.map +1 -0
  260. package/dist/src/formats/pajek/syntax.d.ts +112 -0
  261. package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
  262. package/dist/src/formats/pajek/syntax.js +269 -0
  263. package/dist/src/formats/pajek/syntax.js.map +1 -0
  264. package/dist/src/index.d.ts +35 -0
  265. package/dist/src/index.d.ts.map +1 -0
  266. package/dist/src/index.js +39 -0
  267. package/dist/src/index.js.map +1 -0
  268. package/dist/src/registry.d.ts +207 -0
  269. package/dist/src/registry.d.ts.map +1 -0
  270. package/dist/src/registry.js +481 -0
  271. package/dist/src/registry.js.map +1 -0
  272. package/dist/src/sniff.d.ts +104 -0
  273. package/dist/src/sniff.d.ts.map +1 -0
  274. package/dist/src/sniff.js +357 -0
  275. package/dist/src/sniff.js.map +1 -0
  276. package/dist/src/types.d.ts +238 -0
  277. package/dist/src/types.d.ts.map +1 -0
  278. package/dist/src/types.js +29 -0
  279. package/dist/src/types.js.map +1 -0
  280. package/dist/tsconfig.build.tsbuildinfo +1 -0
  281. package/package.json +122 -7
  282. package/src/children.ts +335 -0
  283. package/src/common/attributes.ts +520 -0
  284. package/src/common/codes.ts +153 -0
  285. package/src/common/declared-types.ts +374 -0
  286. package/src/common/direction.ts +518 -0
  287. package/src/common/escape.ts +231 -0
  288. package/src/common/export.ts +817 -0
  289. package/src/common/format.ts +111 -0
  290. package/src/common/ids.ts +176 -0
  291. package/src/common/input.ts +378 -0
  292. package/src/common/lists.ts +196 -0
  293. package/src/common/options.ts +377 -0
  294. package/src/common/report.ts +352 -0
  295. package/src/common/temporal.ts +302 -0
  296. package/src/common/text.ts +294 -0
  297. package/src/common/weights.ts +202 -0
  298. package/src/common/writer.ts +123 -0
  299. package/src/common/xml.ts +1053 -0
  300. package/src/formats/csv/exporter.ts +894 -0
  301. package/src/formats/csv/header.ts +172 -0
  302. package/src/formats/csv/importer.ts +1104 -0
  303. package/src/formats/csv/index.ts +88 -0
  304. package/src/formats/csv/records.ts +813 -0
  305. package/src/formats/csv/values.ts +224 -0
  306. package/src/formats/dot/exporter.ts +1014 -0
  307. package/src/formats/dot/importer.ts +1549 -0
  308. package/src/formats/dot/index.ts +7 -0
  309. package/src/formats/dot/names.ts +40 -0
  310. package/src/formats/dot/tokenizer.ts +384 -0
  311. package/src/formats/gexf/exporter.ts +1696 -0
  312. package/src/formats/gexf/importer.ts +2333 -0
  313. package/src/formats/gexf/index.ts +142 -0
  314. package/src/formats/gexf/schema.ts +361 -0
  315. package/src/formats/gml/exporter.ts +1404 -0
  316. package/src/formats/gml/importer.ts +1591 -0
  317. package/src/formats/gml/index.ts +128 -0
  318. package/src/formats/gml/syntax.ts +545 -0
  319. package/src/formats/graphml/constants.ts +225 -0
  320. package/src/formats/graphml/exporter.ts +1458 -0
  321. package/src/formats/graphml/importer.ts +2027 -0
  322. package/src/formats/graphml/index.ts +8 -0
  323. package/src/formats/graphml/tree.ts +318 -0
  324. package/src/formats/json/dialect.ts +317 -0
  325. package/src/formats/json/exporter.ts +1616 -0
  326. package/src/formats/json/importer.ts +2271 -0
  327. package/src/formats/json/index.ts +8 -0
  328. package/src/formats/neo4j/exporter.ts +1287 -0
  329. package/src/formats/neo4j/header.ts +156 -0
  330. package/src/formats/neo4j/importer.ts +1220 -0
  331. package/src/formats/neo4j/index.ts +116 -0
  332. package/src/formats/pajek/exporter.ts +1000 -0
  333. package/src/formats/pajek/importer.ts +1311 -0
  334. package/src/formats/pajek/index.ts +7 -0
  335. package/src/formats/pajek/syntax.ts +307 -0
  336. package/src/index.ts +244 -0
  337. package/src/registry.ts +617 -0
  338. package/src/sniff.ts +397 -0
  339. package/src/types.ts +262 -0
@@ -0,0 +1,2015 @@
1
+ import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
2
+ import { L as LOSS, au as joinText, ah as encodeChunks, aB as resolveExportOptions, aD as sanitizeIds, aa as checkCapabilities, aw as pairFolding, a9 as capabilities, ai as explicitWeights, al as formatF64, aj as formatDecimal, ak as formatF32, an as formatInteger, a8 as canonicalId, W as SINK_OPTION_CODE, J as OPTION_IGNORED_CODE, V as ROLE_TAKEN_CODE, S as RENAMED_CODE, n as ID_MERGED_CODE, k as DUPLICATE_NODE_CODE, Z as SYNTAX_CODE, r as INVALID_UTF8_CODE, aC as resolveImportOptions, I as ImportReportBuilder, az as reportSinkOptions, aA as reportUnusedOptions, v as LineReader, a as throwIfAborted, l as DirectionResolver, s as IdCoercer, ac as coerceIdText, af as declareResolved, at as isWeightField, ax as parseWeightText } from "./writer-DxSKC7TL.js";
3
+ import { i as isPajekLabel, q as quotePajekLabel } from "./escape-DyI8JofU.js";
4
+ import { W as WIDENING_UNSUPPORTED_CODE, a as isNumericText, T as TextCellWriter } from "./text-CajMdVFy.js";
5
+ const LABEL_COLUMN = "label";
6
+ const POSITION_COLUMN = "position";
7
+ const SHAPE_COLUMN = "shape";
8
+ const RELATION_COLUMN = "relation";
9
+ const SPELLS_COLUMN = "spells";
10
+ const VALUE_COLUMN = "value";
11
+ const VALUE_FIELD = "value";
12
+ const SHAPES = /* @__PURE__ */ new Set(["ellipse", "box", "diamond", "triangle", "cross", "empty"]);
13
+ const ORIGINAL_ID_KEY = "graphty_originalId";
14
+ const SECTION_KINDS = /* @__PURE__ */ new Map([
15
+ ["network", "network"],
16
+ ["vertices", "vertices"],
17
+ ["arcs", "arcs"],
18
+ ["edges", "edges"],
19
+ ["arcslist", "arcslist"],
20
+ ["edgeslist", "edgeslist"],
21
+ ["matrix", "matrix"]
22
+ ]);
23
+ const INTEGER_TEXT = /^[+-]?[0-9]+$/;
24
+ const TIME_POINT = /^(\*|[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)(?:-(\*|[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?))?$/;
25
+ function tokenize(line) {
26
+ const tokens = [];
27
+ let current = "";
28
+ let started = false;
29
+ let quoted = false;
30
+ for (let i = 0; i < line.length; i++) {
31
+ const c = line.charCodeAt(i);
32
+ if (c === 34) {
33
+ quoted = !quoted;
34
+ started = true;
35
+ continue;
36
+ }
37
+ if (!quoted && (c === 32 || c === 9 || c === 13 || c === 12 || c === 11)) {
38
+ if (started) {
39
+ tokens.push(current);
40
+ current = "";
41
+ started = false;
42
+ }
43
+ continue;
44
+ }
45
+ current += line[i];
46
+ started = true;
47
+ }
48
+ if (quoted) {
49
+ return null;
50
+ }
51
+ if (started) {
52
+ tokens.push(current);
53
+ }
54
+ return tokens;
55
+ }
56
+ function isCommentOrBlank(line) {
57
+ for (let i = 0; i < line.length; i++) {
58
+ const c = line.charCodeAt(i);
59
+ if (c === 32 || c === 9 || c === 13 || c === 12 || c === 11) {
60
+ continue;
61
+ }
62
+ return c === 37;
63
+ }
64
+ return true;
65
+ }
66
+ function isSectionLine(line) {
67
+ for (let i = 0; i < line.length; i++) {
68
+ const c = line.charCodeAt(i);
69
+ if (c === 32 || c === 9 || c === 13 || c === 12 || c === 11) {
70
+ continue;
71
+ }
72
+ return c === 42;
73
+ }
74
+ return false;
75
+ }
76
+ function parseSectionHeader(line) {
77
+ const tokens = tokenize(line.trimStart().slice(1));
78
+ if (tokens === null || tokens.length === 0 || tokens[0].length === 0) {
79
+ return null;
80
+ }
81
+ const keyword = tokens[0];
82
+ const kind = SECTION_KINDS.get(keyword.toLowerCase()) ?? "unsupported";
83
+ let count = null;
84
+ let secondCount = null;
85
+ let relation = null;
86
+ let name = null;
87
+ const extra = [];
88
+ let i = 1;
89
+ switch (kind) {
90
+ case "vertices":
91
+ if (i < tokens.length && INTEGER_TEXT.test(tokens[i])) {
92
+ count = Number(tokens[i]);
93
+ i++;
94
+ if (i < tokens.length && INTEGER_TEXT.test(tokens[i])) {
95
+ secondCount = Number(tokens[i]);
96
+ i++;
97
+ }
98
+ }
99
+ break;
100
+ case "arcs":
101
+ case "edges":
102
+ case "arcslist":
103
+ case "edgeslist":
104
+ case "matrix":
105
+ if (i < tokens.length && tokens[i].startsWith(":") && INTEGER_TEXT.test(tokens[i].slice(1))) {
106
+ relation = Number(tokens[i].slice(1));
107
+ i++;
108
+ if (i < tokens.length) {
109
+ name = tokens[i];
110
+ i++;
111
+ }
112
+ }
113
+ break;
114
+ case "network":
115
+ if (i < tokens.length) {
116
+ name = tokens.slice(i).join(" ");
117
+ i = tokens.length;
118
+ }
119
+ break;
120
+ case "unsupported":
121
+ i = tokens.length;
122
+ break;
123
+ default: {
124
+ const unknown = kind;
125
+ throw new Error(`unknown section kind ${unknown}`);
126
+ }
127
+ }
128
+ for (; i < tokens.length; i++) {
129
+ extra.push(tokens[i]);
130
+ }
131
+ return { kind, keyword, count, secondCount, relation, name, extra };
132
+ }
133
+ function isVertexNumber(token) {
134
+ if (token.length === 0) {
135
+ return false;
136
+ }
137
+ for (let i = 0; i < token.length; i++) {
138
+ const c = token.charCodeAt(i);
139
+ if (c < 48 || c > 57) {
140
+ return false;
141
+ }
142
+ }
143
+ return true;
144
+ }
145
+ function isIntervalToken(token) {
146
+ return token.length >= 2 && token.startsWith("[") && token.endsWith("]");
147
+ }
148
+ function parseIntervals(token) {
149
+ const body = token.slice(1, -1);
150
+ if (body.trim().length === 0) {
151
+ throw new Error(`empty time interval ${token}`);
152
+ }
153
+ const spells = [];
154
+ for (const part of body.split(",")) {
155
+ const match = TIME_POINT.exec(part.trim());
156
+ if (match === null) {
157
+ throw new Error(`malformed time interval ${token}: "${part}" is not a-b, a-* or a`);
158
+ }
159
+ const start = match[1] === "*" ? -Infinity : Number(match[1]);
160
+ const endText = match[2];
161
+ let end;
162
+ if (endText === void 0) {
163
+ end = start;
164
+ } else if (endText === "*") {
165
+ end = Infinity;
166
+ } else {
167
+ end = Number(endText);
168
+ }
169
+ if (start > end) {
170
+ throw new Error(`malformed time interval ${token}: ${match[1]} is after ${endText ?? ""}`);
171
+ }
172
+ spells.push([start, end]);
173
+ }
174
+ return spells;
175
+ }
176
+ function formatIntervals(spells) {
177
+ const parts = [];
178
+ for (const [start, end] of spells) {
179
+ const s = start === -Infinity ? "*" : String(start);
180
+ if (start === end) {
181
+ parts.push(s);
182
+ } else {
183
+ parts.push(`${s}-${end === Infinity ? "*" : String(end)}`);
184
+ }
185
+ }
186
+ return `[${parts.join(",")}]`;
187
+ }
188
+ function isParameterKey(text) {
189
+ if (text.length === 0 || /[\s"]/.test(text) || text.startsWith("[") || text.startsWith("*")) {
190
+ return false;
191
+ }
192
+ if (SHAPES.has(text)) {
193
+ return false;
194
+ }
195
+ return !/^[+-]?(\.[0-9]|[0-9])/.test(text);
196
+ }
197
+ const PAJEK_LOSS = Object.freeze({
198
+ /** A label or text value holds a double quote or a line break; export() will throw E_UNSUPPORTED. */
199
+ TEXT: "E_PAJEK_TEXT",
200
+ /** A column whose name cannot be a parameter key (whitespace, a quote, numeric, a shape keyword); skipped. */
201
+ KEY_DROPPED: "W_PAJEK_KEY_DROPPED",
202
+ /** A label role column that is not text; its values are written as text and re-import as string. */
203
+ LABEL_AS_TEXT: "W_PAJEK_LABEL_AS_TEXT",
204
+ /** A non-finite f64 value; written as Infinity / NaN text, which re-imports as string. */
205
+ NONFINITE_AS_TEXT: "W_PAJEK_NONFINITE_AS_TEXT",
206
+ /** A mutual pair; written as one undirected edge, the mark lost. */
207
+ MUTUAL_AS_UNDIRECTED: LOSS.MUTUAL_AS_UNDIRECTED,
208
+ /** A start / end / timestamp role column; Pajek intervals are written from the spells role only. */
209
+ TEMPORAL_DROPPED: LOSS.TEMPORAL,
210
+ /** A position column with a stride other than 2 or 3. */
211
+ POSITION_STRIDE: "W_PAJEK_POSITION_STRIDE",
212
+ /** meta.extra.pajek.firstMode is not a count within 0..N; the two-mode header is not written. */
213
+ FIRST_MODE_DROPPED: "W_PAJEK_FIRST_MODE_DROPPED",
214
+ /** A role column Pajek has no slot for (kind, ...) written as a plain parameter; the role is lost. */
215
+ ROLE_DROPPED: LOSS.ROLE,
216
+ /** A `shape` column with a value outside the shape keywords is written as a parameter (a string on re-import). */
217
+ SHAPE_AS_PARAMETER: "W_PAJEK_SHAPE_AS_PARAMETER",
218
+ /** A vertex line with coordinates, a shape or parameters needs a label: the id text is written and reads back as a label. */
219
+ LABEL_GAINED: "W_PAJEK_LABEL_GAINED",
220
+ /** A role-less node column named `label` reads back with the label role (its values become the vertex labels). */
221
+ ROLE_ASSUMED: LOSS.ROLE_ASSUMED,
222
+ /** Under sanitizeIds "mangle": an original id whose text reads back as the other type under ids "canonical". */
223
+ ID_TEXT_TYPE: LOSS.ID_TEXT_TYPE
224
+ });
225
+ const SLOT_ROLES = /* @__PURE__ */ new Set(["label"]);
226
+ const ROLE_NAMES = Object.freeze({ label: "label" });
227
+ const STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
228
+ "directed",
229
+ "pair",
230
+ "mutual",
231
+ "weight",
232
+ "timeText",
233
+ "originalId"
234
+ ]);
235
+ const CHECKED_ROLES = /* @__PURE__ */ new Set([
236
+ "color",
237
+ "size",
238
+ "shape",
239
+ "thickness",
240
+ "parent",
241
+ "parents",
242
+ "open"
243
+ ]);
244
+ const DROPPED_TEMPORAL_ROLES = /* @__PURE__ */ new Set(["start", "end", "timestamp", "timestamps"]);
245
+ const CAPABILITIES = capabilities({
246
+ mixedDirection: true,
247
+ multiEdges: true,
248
+ selfLoops: true,
249
+ edgeIds: "none",
250
+ idCharset: "dense-1-based",
251
+ dtypes: ["f64", "i32", "bool", "string"],
252
+ temporal: "spells",
253
+ positions: true
254
+ });
255
+ function plan(table, domain, notes) {
256
+ const planned = [];
257
+ const rows = (column) => column.length - column.nullCount;
258
+ for (const column of table) {
259
+ const { meta } = column;
260
+ const { role, name, dtype } = meta;
261
+ if (role === "label" && domain === "node") {
262
+ if (dtype !== "string" && dtype !== "dict" && notes !== null) {
263
+ notes.push(
264
+ note(
265
+ PAJEK_LOSS.LABEL_AS_TEXT,
266
+ `node column "${name}" (label) is ${dtype}; labels are text and re-import as string`,
267
+ name,
268
+ rows(column)
269
+ )
270
+ );
271
+ }
272
+ planned.push({ column, slot: "label" });
273
+ continue;
274
+ }
275
+ if (role === "position") {
276
+ if (domain === "node" && (meta.components === 2 || meta.components === 3)) {
277
+ planned.push({ column, slot: "position" });
278
+ } else if (notes !== null && domain === "node") {
279
+ notes.push(
280
+ note(
281
+ PAJEK_LOSS.POSITION_STRIDE,
282
+ `node column "${name}" (position) has ${meta.components} components; Pajek coordinates are x y [z]`,
283
+ name,
284
+ rows(column)
285
+ )
286
+ );
287
+ } else if (notes !== null) {
288
+ notes.push(
289
+ note(LOSS.POSITIONS, `edge column "${name}" (position) cannot be written`, name, rows(column))
290
+ );
291
+ }
292
+ continue;
293
+ }
294
+ if (role === "spells") {
295
+ if (dtype === "list" && (meta.itemDtype === "f64" || meta.itemDtype === "i32" || meta.itemDtype === "f32")) {
296
+ planned.push({ column, slot: "spells" });
297
+ continue;
298
+ }
299
+ if (notes !== null) {
300
+ notes.push(
301
+ note(
302
+ LOSS.SPELLS,
303
+ `${domain} column "${name}" (spells) is not a list of number pairs; it cannot be written`,
304
+ name,
305
+ rows(column)
306
+ )
307
+ );
308
+ }
309
+ continue;
310
+ }
311
+ if (role !== null && DROPPED_TEMPORAL_ROLES.has(role)) {
312
+ if (notes !== null) {
313
+ notes.push(
314
+ note(
315
+ PAJEK_LOSS.TEMPORAL_DROPPED,
316
+ `${domain} column "${name}" (${role}) cannot be written; Pajek intervals come from the spells role`,
317
+ name,
318
+ rows(column)
319
+ )
320
+ );
321
+ }
322
+ continue;
323
+ }
324
+ if (role !== null && (STRUCTURAL_ROLES.has(role) || CHECKED_ROLES.has(role))) {
325
+ continue;
326
+ }
327
+ if (domain === "edge" && role === "id") {
328
+ continue;
329
+ }
330
+ if (dtype === "list" || dtype === "json") {
331
+ continue;
332
+ }
333
+ if (domain === "node" && name === SHAPE_COLUMN && (dtype === "dict" || dtype === "string")) {
334
+ if (allShapeKeywords(column)) {
335
+ planned.push({ column, slot: "shape" });
336
+ continue;
337
+ }
338
+ if (notes !== null && dtype === "dict") {
339
+ notes.push(
340
+ note(
341
+ PAJEK_LOSS.SHAPE_AS_PARAMETER,
342
+ `node column "${name}" holds values outside the Pajek shape keywords; it is written as a parameter and reads back as string`,
343
+ name,
344
+ rows(column)
345
+ )
346
+ );
347
+ }
348
+ planned.push({ column, slot: "param" });
349
+ continue;
350
+ }
351
+ if (domain === "edge" && name === RELATION_COLUMN && (dtype === "dict" || dtype === "string")) {
352
+ planned.push({ column, slot: "relation" });
353
+ continue;
354
+ }
355
+ if (!isParameterKey(name)) {
356
+ if (notes !== null) {
357
+ notes.push(
358
+ note(
359
+ PAJEK_LOSS.KEY_DROPPED,
360
+ `${domain} column "${name}" cannot be a Pajek parameter key; it is not written`,
361
+ name,
362
+ rows(column)
363
+ )
364
+ );
365
+ }
366
+ continue;
367
+ }
368
+ planned.push({ column, slot: "param" });
369
+ }
370
+ return planned;
371
+ }
372
+ function labelNotes(snapshot, nodePlan, notes) {
373
+ let labels = null;
374
+ let carried = 0;
375
+ for (const { column, slot } of nodePlan) {
376
+ if (slot === "label") {
377
+ labels = column;
378
+ } else if (slot !== "skip" && slot !== "relation") {
379
+ carried++;
380
+ }
381
+ }
382
+ const plain = [...snapshot.nodes].find((c) => c.meta.role === null && c.meta.name === LABEL_COLUMN);
383
+ if (plain !== void 0) {
384
+ notes.push(
385
+ note(
386
+ PAJEK_LOSS.ROLE_ASSUMED,
387
+ `node column "${LABEL_COLUMN}" has no role; written as a parameter, it reads back as the vertex label (role label)`,
388
+ LABEL_COLUMN,
389
+ plain.length - plain.nullCount
390
+ )
391
+ );
392
+ }
393
+ if (carried === 0) {
394
+ return;
395
+ }
396
+ let gained = 0;
397
+ for (let i = 0; i < snapshot.nodeCount; i++) {
398
+ if (labels === null || !labels.isSet(i)) {
399
+ gained++;
400
+ }
401
+ }
402
+ if (gained > 0) {
403
+ notes.push(
404
+ note(
405
+ PAJEK_LOSS.LABEL_GAINED,
406
+ `${gained} vertex line(s) carry coordinates, a shape or parameters and need a label; the id text is written there and reads back as a label`,
407
+ labels === null ? LABEL_COLUMN : labels.meta.name,
408
+ gained
409
+ )
410
+ );
411
+ }
412
+ }
413
+ function allShapeKeywords(column) {
414
+ for (let r = 0; r < column.length; r++) {
415
+ if (column.isSet(r) && !SHAPES.has(column.value(r))) {
416
+ return false;
417
+ }
418
+ }
419
+ return true;
420
+ }
421
+ function note(code, message, column = null, count = null) {
422
+ return Object.freeze({ code, message, column, count });
423
+ }
424
+ function numberText(value, dtype) {
425
+ switch (dtype) {
426
+ case "i32":
427
+ case "u32":
428
+ case "u8":
429
+ return formatInteger(value);
430
+ case "f32":
431
+ return formatF32(value);
432
+ case "f64":
433
+ return formatDecimal(value, "f64");
434
+ default: {
435
+ const name = dtype;
436
+ throw new Error(`unknown numeric dtype ${name}`);
437
+ }
438
+ }
439
+ }
440
+ function cellText(column, row) {
441
+ switch (column.dtype) {
442
+ case "f32":
443
+ case "f64":
444
+ case "i32":
445
+ case "u32":
446
+ case "u8": {
447
+ const value = column.value(row);
448
+ if (typeof value === "number") {
449
+ return numberText(value, column.dtype);
450
+ }
451
+ const parts = [];
452
+ const vector = value;
453
+ for (let k = 0; k < vector.length; k++) {
454
+ parts.push(numberText(vector[k], column.dtype));
455
+ }
456
+ return parts.join(" ");
457
+ }
458
+ case "bool":
459
+ return column.value(row) === true ? "true" : "false";
460
+ case "dict":
461
+ case "string":
462
+ return column.value(row);
463
+ case "list":
464
+ case "json":
465
+ return JSON.stringify(column.value(row));
466
+ default: {
467
+ const name = column.dtype;
468
+ throw new Error(`unknown dtype ${name}`);
469
+ }
470
+ }
471
+ }
472
+ function checkCells(planned, domain, notes) {
473
+ for (const { column, slot } of planned) {
474
+ if (slot === "position" || slot === "spells") {
475
+ continue;
476
+ }
477
+ const { name } = column.meta;
478
+ let badText = 0;
479
+ let nonFinite = 0;
480
+ for (let r = 0; r < column.length; r++) {
481
+ if (!column.isSet(r)) {
482
+ continue;
483
+ }
484
+ if (column.dtype === "f64" || column.dtype === "f32") {
485
+ const value = column.value(r);
486
+ if (typeof value === "number") {
487
+ if (!Number.isFinite(value)) {
488
+ nonFinite++;
489
+ }
490
+ } else if (value !== void 0 && !Array.from(value).every((v) => Number.isFinite(v))) {
491
+ nonFinite++;
492
+ }
493
+ } else if (column.dtype === "string" || column.dtype === "dict") {
494
+ if (!isPajekLabel(column.value(r))) {
495
+ badText++;
496
+ }
497
+ }
498
+ }
499
+ if (badText > 0) {
500
+ notes.push(
501
+ note(
502
+ PAJEK_LOSS.TEXT,
503
+ `${badText} value(s) of ${domain} column "${name}" hold a double quote or a line break; Pajek cannot write them`,
504
+ name,
505
+ badText
506
+ )
507
+ );
508
+ }
509
+ if (nonFinite > 0) {
510
+ notes.push(
511
+ note(
512
+ PAJEK_LOSS.NONFINITE_AS_TEXT,
513
+ `${nonFinite} non-finite value(s) of ${domain} column "${name}" are written as text`,
514
+ name,
515
+ nonFinite
516
+ )
517
+ );
518
+ }
519
+ }
520
+ }
521
+ function firstModeOf(snapshot) {
522
+ const { pajek } = snapshot.meta.extra;
523
+ if (typeof pajek !== "object" || pajek === null) {
524
+ return null;
525
+ }
526
+ const value = pajek.firstMode;
527
+ if (value === void 0) {
528
+ return null;
529
+ }
530
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= snapshot.nodeCount) {
531
+ return value;
532
+ }
533
+ return NaN;
534
+ }
535
+ function labelOf(labels, ids, i, needed) {
536
+ if (labels !== null && labels.isSet(i)) {
537
+ return cellText(labels, i);
538
+ }
539
+ if (needed || ids.isChanged(i)) {
540
+ return idText(ids.originalAt(i));
541
+ }
542
+ return null;
543
+ }
544
+ function idText(id) {
545
+ return typeof id === "number" ? formatF64(id) : id;
546
+ }
547
+ function spellsText(column, row) {
548
+ if (column.dtype !== "list" || !column.isSet(row)) {
549
+ return null;
550
+ }
551
+ const items = column.sliceOf(row);
552
+ if (items.length === 0) {
553
+ return null;
554
+ }
555
+ const pairs = [];
556
+ for (const item of items) {
557
+ const pair = item;
558
+ pairs.push([pair[0], pair[1]]);
559
+ }
560
+ return formatIntervals(pairs);
561
+ }
562
+ function coordinatesText(column, i, writeZ) {
563
+ if (!column.isSet(i)) {
564
+ return null;
565
+ }
566
+ const dtype = column.dtype;
567
+ const vector = column.value(i);
568
+ const x = numberText(vector[0], dtype);
569
+ const y = numberText(vector[1], dtype);
570
+ if (column.meta.components === 3 && writeZ) {
571
+ return `${x} ${y} ${numberText(vector[2], dtype)}`;
572
+ }
573
+ return `${x} ${y}`;
574
+ }
575
+ function needsZ(column) {
576
+ if (column.meta.components !== 3) {
577
+ return false;
578
+ }
579
+ if (column.meta.extra.sourceDims !== 2) {
580
+ return true;
581
+ }
582
+ for (let i = 0; i < column.length; i++) {
583
+ if (column.isSet(i)) {
584
+ const vector = column.value(i);
585
+ if (vector[2] !== 0) {
586
+ return true;
587
+ }
588
+ }
589
+ }
590
+ return false;
591
+ }
592
+ function* writeParts(snapshot, options) {
593
+ const ids = sanitizeIds(snapshot, CAPABILITIES.idCharset, options.mangle ? "mangle" : "error");
594
+ const nodePlan = withoutReservedKey(plan(snapshot.nodes, "node", null), options.mangle);
595
+ const edgePlan = plan(snapshot.edges, "edge", null);
596
+ const preflight = [];
597
+ checkCells(nodePlan, "node", preflight);
598
+ checkCells(edgePlan, "edge", preflight);
599
+ checkIdTexts(snapshot.nodeCount, ids, options.mangle, preflight);
600
+ for (const n of preflight) {
601
+ if (n.code === PAJEK_LOSS.TEXT) {
602
+ throw new GraphFormatError("E_UNSUPPORTED", n.message, {
603
+ reason: "pajek label",
604
+ column: n.column,
605
+ count: n.count
606
+ });
607
+ }
608
+ }
609
+ if (options.networkHeader && snapshot.meta.name !== null) {
610
+ yield `*Network ${snapshot.meta.name}
611
+ `;
612
+ }
613
+ const firstMode = firstModeOf(snapshot);
614
+ yield firstMode !== null && !Number.isNaN(firstMode) ? `*Vertices ${snapshot.nodeCount} ${firstMode}
615
+ ` : `*Vertices ${snapshot.nodeCount}
616
+ `;
617
+ yield* writeVertices(snapshot, ids, nodePlan, options.mangle);
618
+ yield* writeLines(snapshot, edgePlan);
619
+ }
620
+ function withoutReservedKey(nodePlan, mangle) {
621
+ if (!mangle) {
622
+ return nodePlan;
623
+ }
624
+ return nodePlan.filter((p) => !(p.slot === "param" && p.column.meta.name === ORIGINAL_ID_KEY));
625
+ }
626
+ function checkIdTexts(nodeCount, ids, mangle, notes) {
627
+ let badText = 0;
628
+ let typeChanged = 0;
629
+ for (let i = 0; i < nodeCount; i++) {
630
+ if (!ids.isChanged(i)) {
631
+ continue;
632
+ }
633
+ const original = ids.originalAt(i);
634
+ const text = idText(original);
635
+ if (!isPajekLabel(text)) {
636
+ badText++;
637
+ } else if (mangle && typeof canonicalId(text) !== typeof original) {
638
+ typeChanged++;
639
+ }
640
+ }
641
+ if (badText > 0) {
642
+ notes.push(
643
+ note(
644
+ PAJEK_LOSS.TEXT,
645
+ `${badText} node id(s) hold a double quote or a line break; Pajek cannot write them as labels or parameters`,
646
+ null,
647
+ badText
648
+ )
649
+ );
650
+ }
651
+ if (typeChanged > 0) {
652
+ notes.push(
653
+ note(
654
+ PAJEK_LOSS.ID_TEXT_TYPE,
655
+ `${typeChanged} original id(s) read back from ${ORIGINAL_ID_KEY} as the other type under ids: "canonical" (a string "1" becomes 1, a number 1.5 becomes "1.5")`,
656
+ null,
657
+ typeChanged
658
+ )
659
+ );
660
+ }
661
+ }
662
+ function* writeVertices(snapshot, ids, nodePlan, mangle) {
663
+ let labels = null;
664
+ let position = null;
665
+ let shape = null;
666
+ let nodeSpells = null;
667
+ const nodeParams = [];
668
+ for (const { column, slot } of nodePlan) {
669
+ switch (slot) {
670
+ case "label":
671
+ labels = column;
672
+ break;
673
+ case "position":
674
+ position = column;
675
+ break;
676
+ case "shape":
677
+ shape = column;
678
+ break;
679
+ case "spells":
680
+ nodeSpells = column;
681
+ break;
682
+ case "param":
683
+ nodeParams.push(column);
684
+ break;
685
+ case "relation":
686
+ case "skip":
687
+ break;
688
+ default: {
689
+ const name = slot;
690
+ throw new Error(`unknown slot ${name}`);
691
+ }
692
+ }
693
+ }
694
+ const writeZ = position !== null && needsZ(position);
695
+ for (let i = 0; i < snapshot.nodeCount; i++) {
696
+ const parts = [];
697
+ if (position !== null) {
698
+ const coordinates = coordinatesText(position, i, writeZ);
699
+ if (coordinates !== null) {
700
+ parts.push(coordinates);
701
+ }
702
+ }
703
+ if (shape !== null && shape.isSet(i)) {
704
+ const keyword = cellText(shape, i);
705
+ if (SHAPES.has(keyword)) {
706
+ parts.push(keyword);
707
+ } else {
708
+ parts.push(`${SHAPE_COLUMN} ${quotePajekLabel(keyword)}`);
709
+ }
710
+ }
711
+ for (const column of nodeParams) {
712
+ if (column.isSet(i)) {
713
+ parts.push(`${column.meta.name} ${quotePajekLabel(cellText(column, i))}`);
714
+ }
715
+ }
716
+ if (mangle && ids.isChanged(i)) {
717
+ parts.push(`${ORIGINAL_ID_KEY} ${quotePajekLabel(idText(ids.originalAt(i)))}`);
718
+ }
719
+ if (nodeSpells !== null) {
720
+ const spells = spellsText(nodeSpells, i);
721
+ if (spells !== null) {
722
+ parts.push(spells);
723
+ }
724
+ }
725
+ const label = labelOf(labels, ids, i, parts.length > 0);
726
+ let line = String(i + 1);
727
+ if (label !== null) {
728
+ line += ` ${quotePajekLabel(label)}`;
729
+ }
730
+ if (parts.length > 0) {
731
+ line += ` ${parts.join(" ")}`;
732
+ }
733
+ yield `${line}
734
+ `;
735
+ }
736
+ }
737
+ function* writeLines(snapshot, edgePlan) {
738
+ const folding = pairFolding(snapshot, { foldMutual: true });
739
+ const weights = explicitWeights(snapshot);
740
+ const { src, dst } = snapshot.edgeList();
741
+ let relation = null;
742
+ let edgeSpells = null;
743
+ const edgeParams = [];
744
+ for (const { column, slot } of edgePlan) {
745
+ switch (slot) {
746
+ case "relation":
747
+ relation = column;
748
+ break;
749
+ case "spells":
750
+ edgeSpells = column;
751
+ break;
752
+ case "param":
753
+ edgeParams.push(column);
754
+ break;
755
+ case "label":
756
+ case "position":
757
+ case "shape":
758
+ case "skip":
759
+ break;
760
+ default: {
761
+ const name = slot;
762
+ throw new Error(`unknown slot ${name}`);
763
+ }
764
+ }
765
+ }
766
+ const relationNumbers = /* @__PURE__ */ new Map();
767
+ let currentKind = null;
768
+ let currentRelation = null;
769
+ let sectionOpen = false;
770
+ for (let e = 0; e < snapshot.edgeCount; e++) {
771
+ if (folding.folded(e)) {
772
+ continue;
773
+ }
774
+ const directed = snapshot.directed && folding.sourceDirected(e) && !folding.isMutual(e);
775
+ const kind = directed ? "arcs" : "edges";
776
+ const relationName = relation !== null && relation.isSet(e) ? cellText(relation, e) : null;
777
+ if (!sectionOpen || kind !== currentKind || relationName !== currentRelation) {
778
+ currentKind = kind;
779
+ currentRelation = relationName;
780
+ sectionOpen = true;
781
+ const keyword = kind === "arcs" ? "*Arcs" : "*Edges";
782
+ if (relationName === null) {
783
+ yield `${keyword}
784
+ `;
785
+ } else {
786
+ let number = relationNumbers.get(relationName);
787
+ if (number === void 0) {
788
+ number = relationNumbers.size + 1;
789
+ relationNumbers.set(relationName, number);
790
+ }
791
+ yield `${keyword} :${number} ${quotePajekLabel(relationName)}
792
+ `;
793
+ }
794
+ }
795
+ let line = `${src[e] + 1} ${dst[e] + 1}`;
796
+ const weight = weights.text(e);
797
+ if (weight !== null) {
798
+ line += ` ${weight}`;
799
+ }
800
+ for (const column of edgeParams) {
801
+ if (column.isSet(e)) {
802
+ line += ` ${column.meta.name} ${quotePajekLabel(cellText(column, e))}`;
803
+ }
804
+ }
805
+ if (edgeSpells !== null) {
806
+ const spells = spellsText(edgeSpells, e);
807
+ if (spells !== null) {
808
+ line += ` ${spells}`;
809
+ }
810
+ }
811
+ yield `${line}
812
+ `;
813
+ }
814
+ if (!sectionOpen) {
815
+ yield snapshot.directed ? "*Arcs\n" : "*Edges\n";
816
+ }
817
+ }
818
+ function resolvePajekOptions(options) {
819
+ const common = resolveExportOptions(options);
820
+ const value = options?.networkHeader;
821
+ if (value !== void 0 && typeof value !== "boolean") {
822
+ throw new GraphFormatError("E_UNSUPPORTED", "option networkHeader: not a boolean", {
823
+ option: "networkHeader",
824
+ found: typeof value
825
+ });
826
+ }
827
+ return { networkHeader: value ?? false, mangle: common.sanitizeIds === "mangle" };
828
+ }
829
+ const pajekExporter = Object.freeze({
830
+ format: "pajek",
831
+ capabilities: CAPABILITIES,
832
+ /**
833
+ * Pre-flight: the generic capability gaps plus the Pajek-specific ones.
834
+ * @param snapshot - the snapshot
835
+ * @param options - export options
836
+ * @returns the loss notes, empty when the export is exact
837
+ */
838
+ check(snapshot, options) {
839
+ const resolved = resolveExportOptions(options);
840
+ const { mangle } = resolvePajekOptions(options);
841
+ const notes = [];
842
+ const planned = plan(snapshot.nodes, "node", notes);
843
+ const nodePlan = withoutReservedKey(planned, mangle);
844
+ const edgePlan = plan(snapshot.edges, "edge", notes);
845
+ const ids = sanitizeIds(snapshot, CAPABILITIES.idCharset, mangle ? "mangle" : "error");
846
+ checkIdTexts(snapshot.nodeCount, ids, mangle, notes);
847
+ if (nodePlan.length !== planned.length) {
848
+ const reserved = planned.find((p) => p.slot === "param" && p.column.meta.name === ORIGINAL_ID_KEY);
849
+ if (reserved !== void 0) {
850
+ notes.push(
851
+ note(
852
+ PAJEK_LOSS.KEY_DROPPED,
853
+ `node column "${ORIGINAL_ID_KEY}" is the parameter the exporter writes the original ids under (sanitizeIds "mangle"); it is not written`,
854
+ ORIGINAL_ID_KEY,
855
+ reserved.column.length - reserved.column.nullCount
856
+ )
857
+ );
858
+ }
859
+ }
860
+ const owned = /* @__PURE__ */ new Set();
861
+ for (const { column, slot } of [...nodePlan, ...edgePlan]) {
862
+ if (slot === "shape" || slot === "relation") {
863
+ owned.add(`${column.meta.domain}:${column.meta.name}`);
864
+ } else if (slot === "param" && column.meta.domain === "node" && column.meta.name === SHAPE_COLUMN) {
865
+ owned.add(`${column.meta.domain}:${column.meta.name}`);
866
+ }
867
+ }
868
+ for (const table of [snapshot.nodes, snapshot.edges]) {
869
+ for (const column of table) {
870
+ if (column.meta.role === "spells") {
871
+ owned.add(`${column.meta.domain}:${column.meta.name}`);
872
+ }
873
+ }
874
+ }
875
+ for (const n of checkCapabilities(snapshot, CAPABILITIES, resolved, {
876
+ roles: SLOT_ROLES,
877
+ roleNames: ROLE_NAMES
878
+ })) {
879
+ if (n.column !== null && (n.code === LOSS.LIST || n.code === LOSS.DTYPE)) {
880
+ const domain = n.message.startsWith("node column") ? "node" : "edge";
881
+ if (owned.has(`${domain}:${n.column}`)) {
882
+ continue;
883
+ }
884
+ }
885
+ if (n.code === LOSS.COLUMN_NAME_CHANGED && n.message.startsWith("edge column")) {
886
+ continue;
887
+ }
888
+ notes.push(n);
889
+ }
890
+ labelNotes(snapshot, nodePlan, notes);
891
+ for (const column of snapshot.edges) {
892
+ if (column.meta.role === "label") {
893
+ notes.push(
894
+ note(
895
+ LOSS.ROLE,
896
+ `edge column "${column.meta.name}" (label) is written as a plain parameter; Pajek labels vertices only and the role is lost`,
897
+ column.meta.name,
898
+ column.length - column.nullCount
899
+ )
900
+ );
901
+ }
902
+ }
903
+ checkCells(nodePlan, "node", notes);
904
+ checkCells(edgePlan, "edge", notes);
905
+ const folding = pairFolding(snapshot, { foldMutual: true });
906
+ if (folding.mutualCount > 0) {
907
+ notes.push(
908
+ note(
909
+ PAJEK_LOSS.MUTUAL_AS_UNDIRECTED,
910
+ `${folding.mutualCount} mutual pair(s) are written as undirected edges; the mutual mark is lost`,
911
+ snapshot.edges.byRole("mutual")?.meta.name ?? null,
912
+ folding.mutualCount
913
+ )
914
+ );
915
+ }
916
+ if (Number.isNaN(firstModeOf(snapshot))) {
917
+ notes.push(
918
+ note(
919
+ PAJEK_LOSS.FIRST_MODE_DROPPED,
920
+ "meta.extra.pajek.firstMode is not a count within 0..nodeCount; the two-mode header is not written"
921
+ )
922
+ );
923
+ }
924
+ return notes;
925
+ },
926
+ /**
927
+ * Write the snapshot as UTF-8 chunks.
928
+ * @param snapshot - the snapshot
929
+ * @param options - export options
930
+ * @returns the chunks
931
+ */
932
+ export(snapshot, options) {
933
+ const resolved = resolvePajekOptions(options);
934
+ return encodeChunks(writeParts(snapshot, resolved));
935
+ },
936
+ /**
937
+ * Write the snapshot as one string.
938
+ * @param snapshot - the snapshot
939
+ * @param options - export options
940
+ * @returns the document
941
+ */
942
+ exportToString(snapshot, options) {
943
+ const resolved = resolvePajekOptions(options);
944
+ return joinText(writeParts(snapshot, resolved));
945
+ }
946
+ });
947
+ const PAJEK_ISSUE = Object.freeze({
948
+ /** The input holds invalid UTF-8 (fatal). */
949
+ INVALID_UTF8: INVALID_UTF8_CODE,
950
+ /** Fatal: no `*Vertices` section (an empty file, or not a Pajek network). */
951
+ NO_VERTICES: "E_PAJEK_NO_VERTICES",
952
+ /** Fatal: `*Vertices` without a vertex count, or one the sink cannot hold. */
953
+ VERTICES_COUNT: "E_PAJEK_VERTICES_COUNT",
954
+ /** Aborts: a second `*Vertices` or `*Network` section; only one network per file is read. */
955
+ MULTIPLE_NETWORKS: "E_PAJEK_MULTIPLE_NETWORKS",
956
+ /** A data line before the first section header. */
957
+ OUTSIDE_SECTION: "E_PAJEK_OUTSIDE_SECTION",
958
+ /** A section header the importer cannot parse. */
959
+ SYNTAX: SYNTAX_CODE,
960
+ /** A double quote not closed before the end of the line. */
961
+ UNTERMINATED_QUOTE: "E_PAJEK_UNTERMINATED_QUOTE",
962
+ /** A vertex line the grammar does not accept. */
963
+ VERTEX_LINE: "E_PAJEK_VERTEX_LINE",
964
+ /** A vertex number outside the declared range. */
965
+ VERTEX_RANGE: "E_PAJEK_VERTEX_RANGE",
966
+ /** Fewer vertex lines than `*Vertices` declares (vertices without a line have no label). */
967
+ VERTEX_COUNT: "E_PAJEK_VERTEX_COUNT",
968
+ /** A second line for the same vertex; the later values overwrite. */
969
+ DUPLICATE_NODE: DUPLICATE_NODE_CODE,
970
+ /** A line (arc, edge, list or matrix row) the grammar does not accept. */
971
+ LINE: "E_PAJEK_LINE",
972
+ /** A line endpoint outside the declared vertex range (the core's code, forwarded). */
973
+ UNKNOWN_NODE: "E_UNKNOWN_NODE",
974
+ /** A malformed time interval token. */
975
+ INTERVAL: "E_PAJEK_INTERVAL",
976
+ /** A `*Matrix` section with the wrong number of rows. */
977
+ MATRIX_ROWS: "E_PAJEK_MATRIX_ROWS",
978
+ /** A project-file section (`*Partition`, `*Vector`, ...) the importer does not read. */
979
+ UNSUPPORTED_SECTION: "E_PAJEK_UNSUPPORTED_SECTION",
980
+ /** Tokens after a section header the grammar does not account for. */
981
+ HEADER_EXTRA: "W_PAJEK_HEADER_EXTRA",
982
+ /** The file declares vertices but no line section. */
983
+ NO_LINES: "W_PAJEK_NO_LINES",
984
+ /** Vertex numbering starts at 0 rather than 1. */
985
+ ZERO_BASED: "W_PAJEK_ZERO_BASED",
986
+ /** Vertex lines mix two and three coordinates. */
987
+ COORD_DIMS: "W_PAJEK_COORD_DIMS",
988
+ /** Two vertices share a label under nodeIdFrom "label" and became one node. */
989
+ LABEL_MERGED: "W_PAJEK_LABEL_MERGED",
990
+ /** Two distinct label texts became one numeric id under ids "number". */
991
+ ID_MERGED: ID_MERGED_CODE,
992
+ /** A parameter column of `2.0`-style text kept at the value-inferred dtype (the sink cannot widen). */
993
+ WIDENING_UNSUPPORTED: WIDENING_UNSUPPORTED_CODE,
994
+ /** A structural column (label, position, shape, spells, relation) renamed `<name>#<id>` because the name was taken (design section 5.6). */
995
+ COLUMN_RENAMED: RENAMED_CODE,
996
+ /** A structural column declared without its role because the sink already holds it. */
997
+ ROLE_TAKEN: ROLE_TAKEN_CODE,
998
+ /** Two vertices carry the same `graphty_originalId` under restoreMangledIds and became one node. */
999
+ ORIGINAL_ID_MERGED: "W_PAJEK_ORIGINAL_ID_MERGED",
1000
+ /** A vertex line's `graphty_originalId` came after a later vertex's line had created it under its number. */
1001
+ ORIGINAL_ID_UNRESTORED: "W_PAJEK_ORIGINAL_ID_UNRESTORED",
1002
+ /** A common option the importer has no use for (weightFrom naming a parameter and restoreMangledIds are honoured; long, hyperedges are not). */
1003
+ OPTION_IGNORED: OPTION_IGNORED_CODE,
1004
+ /** A builder-policy option the caller passed that the caller's sink does not use (the shared W_SINK_OPTION). */
1005
+ SINK_OPTION: SINK_OPTION_CODE
1006
+ });
1007
+ const USED_OPTIONS = /* @__PURE__ */ new Set([
1008
+ "ids",
1009
+ "nodeIdFrom",
1010
+ "restoreMangledIds",
1011
+ "addMissingNodes",
1012
+ "duplicateEdges",
1013
+ "selfLoops",
1014
+ "onMixedDirection",
1015
+ "defaultDirected",
1016
+ "weightFrom",
1017
+ "weightDtype",
1018
+ "errorLimit",
1019
+ "signal",
1020
+ "onProgress"
1021
+ ]);
1022
+ const DEFAULTS = { ids: "canonical", defaultDirected: true, weightFrom: VALUE_FIELD };
1023
+ const ABORT_CHECK_INTERVAL = 64;
1024
+ const FIRST_VERTEX_VALUES = /* @__PURE__ */ new Set([0, 1, "auto"]);
1025
+ const LABEL_DECL = {
1026
+ name: LABEL_COLUMN,
1027
+ dtype: "string",
1028
+ nullable: true,
1029
+ role: "label",
1030
+ origin: { format: "pajek", id: "label" }
1031
+ };
1032
+ const SHAPE_DECL = {
1033
+ name: SHAPE_COLUMN,
1034
+ dtype: "dict",
1035
+ nullable: true,
1036
+ origin: { format: "pajek", id: "shape" }
1037
+ };
1038
+ const RELATION_DECL = {
1039
+ name: RELATION_COLUMN,
1040
+ dtype: "dict",
1041
+ nullable: true,
1042
+ origin: { format: "pajek", id: "relation" }
1043
+ };
1044
+ function spellsDecl() {
1045
+ return {
1046
+ name: SPELLS_COLUMN,
1047
+ dtype: "list",
1048
+ itemDtype: "f64",
1049
+ itemComponents: 2,
1050
+ nullable: true,
1051
+ role: "spells",
1052
+ origin: { format: "pajek", id: "interval" }
1053
+ };
1054
+ }
1055
+ function positionDecl(sourceDims) {
1056
+ return {
1057
+ name: POSITION_COLUMN,
1058
+ dtype: "f32",
1059
+ components: 3,
1060
+ nullable: true,
1061
+ role: "position",
1062
+ origin: { format: "pajek", id: "coordinates" },
1063
+ extra: { sourceDims, units: "file" }
1064
+ };
1065
+ }
1066
+ class LineError extends Error {
1067
+ /**
1068
+ * Create a line error.
1069
+ * @param category - the issue category
1070
+ * @param code - the issue code
1071
+ * @param message - a plain-ASCII message
1072
+ */
1073
+ constructor(category, code, message) {
1074
+ super(message);
1075
+ this.name = "PajekLineError";
1076
+ this.category = category;
1077
+ this.code = code;
1078
+ }
1079
+ }
1080
+ class PajekParser {
1081
+ /**
1082
+ * Create the parser for one import call.
1083
+ * @param sink - the sink
1084
+ * @param report - the report
1085
+ * @param options - the resolved common options
1086
+ * @param firstVertex - the firstVertex format option
1087
+ */
1088
+ constructor(sink, report, options, firstVertex) {
1089
+ this.section = "none";
1090
+ this.kind = "directed";
1091
+ this.relation = null;
1092
+ this.vertexCount = -1;
1093
+ this.firstMode = null;
1094
+ this.networkName = null;
1095
+ this.base = 1;
1096
+ this.baseKnown = false;
1097
+ this.nodesCreated = false;
1098
+ this.verticesDone = false;
1099
+ this.sawLineSection = false;
1100
+ this.indexOfPos = new Uint32Array(0);
1101
+ this.idOfPos = null;
1102
+ this.seen = new Uint8Array(0);
1103
+ this.seenCount = 0;
1104
+ this.filledBelow = 0;
1105
+ this.matrixRows = 0;
1106
+ this.coordDims = 0;
1107
+ this.labelHandle = INVALID_INDEX;
1108
+ this.positionHandle = INVALID_INDEX;
1109
+ this.shapeHandle = INVALID_INDEX;
1110
+ this.nodeSpellsHandle = INVALID_INDEX;
1111
+ this.relationHandle = INVALID_INDEX;
1112
+ this.edgeSpellsHandle = INVALID_INDEX;
1113
+ this.nodeParams = /* @__PURE__ */ new Map();
1114
+ this.edgeParams = /* @__PURE__ */ new Map();
1115
+ this.sink = sink;
1116
+ this.report = report;
1117
+ this.options = options;
1118
+ this.firstVertex = firstVertex;
1119
+ this.resolver = new DirectionResolver(sink, report, options.onMixedDirection);
1120
+ this.coercer = new IdCoercer(options.ids);
1121
+ }
1122
+ /**
1123
+ * Handle one line; every problem of the line is recorded as an issue and the line skipped.
1124
+ * @param text - the line without its terminator
1125
+ * @param line - the 1-based line number
1126
+ */
1127
+ line(text, line) {
1128
+ if (isCommentOrBlank(text)) {
1129
+ return;
1130
+ }
1131
+ try {
1132
+ if (isSectionLine(text)) {
1133
+ this.header(text, line);
1134
+ return;
1135
+ }
1136
+ switch (this.section) {
1137
+ case "none":
1138
+ throw new LineError(
1139
+ "parse-error",
1140
+ PAJEK_ISSUE.OUTSIDE_SECTION,
1141
+ "data before the first section header; a Pajek network starts with *Vertices"
1142
+ );
1143
+ case "vertices":
1144
+ this.vertexLine(text, line);
1145
+ return;
1146
+ case "lines":
1147
+ this.edgeLine(text, line);
1148
+ return;
1149
+ case "list":
1150
+ this.listLine(text, line);
1151
+ return;
1152
+ case "matrix":
1153
+ this.matrixLine(text, line);
1154
+ return;
1155
+ case "skip":
1156
+ return;
1157
+ default: {
1158
+ const name = this.section;
1159
+ throw new Error(`unknown section state ${name}`);
1160
+ }
1161
+ }
1162
+ } catch (err) {
1163
+ if (err instanceof LineError) {
1164
+ this.report.error(err.category, err.code, err.message, { line });
1165
+ return;
1166
+ }
1167
+ this.report.recordError(err, { line });
1168
+ }
1169
+ }
1170
+ /**
1171
+ * End of input: the vertex set is completed, a network without line sections gets the default
1172
+ * direction, and the metadata is written.
1173
+ */
1174
+ finish() {
1175
+ if (this.vertexCount < 0) {
1176
+ this.report.fail(PAJEK_ISSUE.NO_VERTICES, "not a Pajek network: no *Vertices section found");
1177
+ }
1178
+ this.endSection();
1179
+ this.finishVertices();
1180
+ if (!this.sawLineSection) {
1181
+ this.report.warning(
1182
+ "validation-error",
1183
+ PAJEK_ISSUE.NO_LINES,
1184
+ "the file declares vertices but no *Arcs, *Edges, *Arcslist, *Edgeslist or *Matrix section"
1185
+ );
1186
+ this.resolver.setHeader(this.options.defaultDirected);
1187
+ }
1188
+ if (this.coercer.mergeCount > 0) {
1189
+ this.report.warning(
1190
+ "coercion",
1191
+ PAJEK_ISSUE.ID_MERGED,
1192
+ `${this.coercer.mergeCount} label(s) merged into an id another label already produced under ids "number"`
1193
+ );
1194
+ }
1195
+ const meta = {
1196
+ sourceFormat: "pajek",
1197
+ ...this.networkName === null ? {} : { name: this.networkName },
1198
+ ...this.firstMode === null ? {} : { extra: { pajek: { firstMode: this.firstMode } } }
1199
+ };
1200
+ this.sink.setMeta(meta);
1201
+ }
1202
+ // ============================================================ headers
1203
+ /**
1204
+ * A section header line.
1205
+ * @param text - the line
1206
+ * @param line - the line number
1207
+ */
1208
+ header(text, line) {
1209
+ const skipping = this.section === "skip";
1210
+ this.endSection();
1211
+ const h = parseSectionHeader(text);
1212
+ if (h === null) {
1213
+ this.section = "skip";
1214
+ throw new LineError("parse-error", PAJEK_ISSUE.SYNTAX, `cannot read the section header "${text}"`);
1215
+ }
1216
+ if (h.extra.length > 0) {
1217
+ this.report.warning(
1218
+ "validation-error",
1219
+ PAJEK_ISSUE.HEADER_EXTRA,
1220
+ `ignored ${h.extra.length} unexpected token(s) after *${h.keyword}: ${h.extra.join(" ")}`,
1221
+ { line }
1222
+ );
1223
+ }
1224
+ switch (h.kind) {
1225
+ case "network":
1226
+ if (this.networkName !== null || this.vertexCount >= 0) {
1227
+ this.refuseSecondNetwork(h.keyword, line);
1228
+ }
1229
+ this.networkName = h.name ?? "";
1230
+ this.section = "none";
1231
+ return;
1232
+ case "vertices":
1233
+ if (skipping && this.vertexCount >= 0) {
1234
+ this.section = "skip";
1235
+ return;
1236
+ }
1237
+ this.verticesHeader(h, line);
1238
+ return;
1239
+ case "arcs":
1240
+ case "edges":
1241
+ this.lineSection(h, line, "lines", h.kind === "arcs" ? "directed" : "undirected");
1242
+ return;
1243
+ case "arcslist":
1244
+ case "edgeslist":
1245
+ this.lineSection(h, line, "list", h.kind === "arcslist" ? "directed" : "undirected");
1246
+ return;
1247
+ case "matrix":
1248
+ this.lineSection(h, line, "matrix", "directed");
1249
+ this.matrixRows = 0;
1250
+ return;
1251
+ case "unsupported":
1252
+ this.section = "skip";
1253
+ this.report.error(
1254
+ "unsupported",
1255
+ PAJEK_ISSUE.UNSUPPORTED_SECTION,
1256
+ `the *${h.keyword} section is not supported; its lines are skipped`,
1257
+ { line }
1258
+ );
1259
+ return;
1260
+ default: {
1261
+ const name = h.kind;
1262
+ throw new Error(`unknown section kind ${name}`);
1263
+ }
1264
+ }
1265
+ }
1266
+ /**
1267
+ * `*Vertices N [N1]`.
1268
+ * @param h - the header
1269
+ * @param line - the line number
1270
+ */
1271
+ verticesHeader(h, line) {
1272
+ if (this.vertexCount >= 0) {
1273
+ this.refuseSecondNetwork(h.keyword, line);
1274
+ }
1275
+ if (h.count === null) {
1276
+ this.report.fail(PAJEK_ISSUE.VERTICES_COUNT, "*Vertices needs a vertex count", { line });
1277
+ }
1278
+ if (!Number.isSafeInteger(h.count)) {
1279
+ this.report.fail(PAJEK_ISSUE.VERTICES_COUNT, `*Vertices ${h.count}: not a count`, { line });
1280
+ }
1281
+ try {
1282
+ this.sink.reserve(h.count);
1283
+ } catch (err) {
1284
+ const reason = err instanceof Error ? err.message : String(err);
1285
+ this.report.fail(
1286
+ PAJEK_ISSUE.VERTICES_COUNT,
1287
+ `*Vertices ${h.count}: the sink cannot hold that many (${reason})`,
1288
+ {
1289
+ line
1290
+ }
1291
+ );
1292
+ }
1293
+ this.vertexCount = h.count;
1294
+ if (h.secondCount !== null) {
1295
+ this.firstMode = h.secondCount;
1296
+ }
1297
+ this.section = "vertices";
1298
+ this.indexOfPos = new Uint32Array(h.count);
1299
+ this.seen = new Uint8Array(h.count);
1300
+ this.filledBelow = 0;
1301
+ if (this.options.nodeIdFrom === "label" || this.restoresIds()) {
1302
+ this.idOfPos = new Array(h.count);
1303
+ }
1304
+ if (this.firstVertex !== "auto") {
1305
+ this.setBase(this.firstVertex, line);
1306
+ }
1307
+ }
1308
+ /**
1309
+ * A line section header: the vertex set is completed, the first one sets the sink's direction
1310
+ * (rule 1 of design section 8.4), later ones only switch the kind.
1311
+ * @param h - the header
1312
+ * @param line - the line number
1313
+ * @param section - the parser state for the section
1314
+ * @param kind - the direction of its lines
1315
+ */
1316
+ lineSection(h, line, section, kind) {
1317
+ if (this.vertexCount < 0) {
1318
+ this.report.fail(
1319
+ PAJEK_ISSUE.NO_VERTICES,
1320
+ `*${h.keyword} before *Vertices; a Pajek network starts with *Vertices`,
1321
+ { line }
1322
+ );
1323
+ }
1324
+ this.finishVertices();
1325
+ if (!this.sawLineSection) {
1326
+ this.sawLineSection = true;
1327
+ this.resolver.setHeader(kind === "directed", { line });
1328
+ }
1329
+ this.section = section;
1330
+ this.kind = kind;
1331
+ this.relation = h.relation === null ? null : h.name ?? String(h.relation);
1332
+ }
1333
+ /**
1334
+ * Refuse a second network: an issue, then ImportError with the report so far.
1335
+ * @param keyword - the header keyword
1336
+ * @param line - the line number
1337
+ */
1338
+ refuseSecondNetwork(keyword, line) {
1339
+ const message = `*${keyword} starts a second network; the importer reads one network per file`;
1340
+ this.report.error("unsupported", PAJEK_ISSUE.MULTIPLE_NETWORKS, message, { line });
1341
+ throw this.report.abort(message, { code: PAJEK_ISSUE.MULTIPLE_NETWORKS, line });
1342
+ }
1343
+ /**
1344
+ * Checks that run when a section ends.
1345
+ */
1346
+ endSection() {
1347
+ if (this.section === "matrix" && this.matrixRows !== this.vertexCount) {
1348
+ this.report.error(
1349
+ "validation-error",
1350
+ PAJEK_ISSUE.MATRIX_ROWS,
1351
+ `*Matrix has ${this.matrixRows} row(s); *Vertices declares ${this.vertexCount}`
1352
+ );
1353
+ }
1354
+ this.section = "none";
1355
+ }
1356
+ // ============================================================ vertices
1357
+ /**
1358
+ * Fix the vertex numbering base and, under nodeIdFrom "id" / "index", create the declared
1359
+ * vertices in number order so the id map is identity.
1360
+ * @param base - 0 or 1
1361
+ * @param line - the line number
1362
+ */
1363
+ setBase(base, line) {
1364
+ this.base = base;
1365
+ this.baseKnown = true;
1366
+ if (base === 0) {
1367
+ this.report.warning(
1368
+ "coercion",
1369
+ PAJEK_ISSUE.ZERO_BASED,
1370
+ "vertex numbering starts at 0; Pajek numbers vertices from 1",
1371
+ { line }
1372
+ );
1373
+ }
1374
+ }
1375
+ /**
1376
+ * Push vertices base..base+N-1 in order (nodeIdFrom "id" / "index"), so the id map is identity;
1377
+ * called on the first vertex line (once it is known that the lines carry no `graphty_originalId`
1378
+ * parameter to restore) or when the vertex section ends without one.
1379
+ */
1380
+ createNodes() {
1381
+ if (this.nodesCreated || this.idOfPos !== null) {
1382
+ return;
1383
+ }
1384
+ this.nodesCreated = true;
1385
+ const n = this.vertexCount;
1386
+ const { sink } = this;
1387
+ for (let pos = 0; pos < n; pos++) {
1388
+ this.indexOfPos[pos] = sink.addNode(this.idOfNumber(pos + this.base));
1389
+ }
1390
+ this.report.counts.nodes += n;
1391
+ }
1392
+ /**
1393
+ * The id of a vertex number under nodeIdFrom "id" (the number coerced by the ids rule) or
1394
+ * "index" (the position).
1395
+ * @param k - the vertex number
1396
+ * @returns the id
1397
+ */
1398
+ idOfNumber(k) {
1399
+ if (this.options.nodeIdFrom === "index") {
1400
+ return k - this.base;
1401
+ }
1402
+ switch (this.options.ids) {
1403
+ case "canonical":
1404
+ case "number":
1405
+ return k;
1406
+ case "keep":
1407
+ case "string":
1408
+ return String(k);
1409
+ default:
1410
+ return coerceIdText(String(k), this.options.ids);
1411
+ }
1412
+ }
1413
+ /**
1414
+ * The id of a vertex position, for addEdge.
1415
+ * @param pos - the vertex number minus the base
1416
+ * @returns the id
1417
+ */
1418
+ idAt(pos) {
1419
+ if (this.idOfPos !== null) {
1420
+ return this.idOfPos[pos];
1421
+ }
1422
+ return this.idOfNumber(pos + this.base);
1423
+ }
1424
+ /**
1425
+ * Complete the vertex set once the vertex section is over: vertices without a line are
1426
+ * created (under "label" or a restored `graphty_originalId` with their number as the id), and
1427
+ * a partial list is reported.
1428
+ */
1429
+ finishVertices() {
1430
+ if (this.verticesDone) {
1431
+ return;
1432
+ }
1433
+ this.verticesDone = true;
1434
+ if (!this.baseKnown) {
1435
+ this.setBase(1, 0);
1436
+ }
1437
+ const n = this.vertexCount;
1438
+ this.createNodes();
1439
+ if (this.idOfPos !== null) {
1440
+ for (let pos = 0; pos < n; pos++) {
1441
+ if (this.idOfPos[pos] === void 0) {
1442
+ this.numberedNode(this.idOfPos, pos);
1443
+ }
1444
+ }
1445
+ }
1446
+ const lines = this.seenCount + this.report.counts.skippedNodes;
1447
+ if (this.seenCount > 0 && lines < n) {
1448
+ this.report.error(
1449
+ "validation-error",
1450
+ PAJEK_ISSUE.VERTEX_COUNT,
1451
+ `*Vertices declares ${n} vertices but ${lines} vertex line(s) were read; the others have no label`
1452
+ );
1453
+ }
1454
+ }
1455
+ /**
1456
+ * A vertex line: `number [label] [x y [z]] [shape] [key value ...] [interval]`.
1457
+ * @param text - the line
1458
+ * @param line - the line number
1459
+ */
1460
+ vertexLine(text, line) {
1461
+ const tokens = this.tokens(text, "node");
1462
+ if (!isVertexNumber(tokens[0])) {
1463
+ this.report.counts.skippedNodes++;
1464
+ throw new LineError(
1465
+ "parse-error",
1466
+ PAJEK_ISSUE.VERTEX_LINE,
1467
+ `a vertex line starts with a vertex number, not "${tokens[0]}"`
1468
+ );
1469
+ }
1470
+ const k = Number(tokens[0]);
1471
+ if (!this.baseKnown) {
1472
+ this.setBase(k === 0 ? 0 : 1, line);
1473
+ }
1474
+ const pos = k - this.base;
1475
+ if (pos < 0 || pos >= this.vertexCount) {
1476
+ this.report.counts.skippedNodes++;
1477
+ throw new LineError(
1478
+ "validation-error",
1479
+ PAJEK_ISSUE.VERTEX_RANGE,
1480
+ `vertex ${k} is outside ${this.base}..${this.base + this.vertexCount - 1}`
1481
+ );
1482
+ }
1483
+ let i = 1;
1484
+ const label = tokens.length > 1 ? tokens[1] : null;
1485
+ if (label !== null) {
1486
+ i = 2;
1487
+ }
1488
+ let x = 0;
1489
+ let y = 0;
1490
+ let z = 0;
1491
+ let dims = 0;
1492
+ if (i + 1 < tokens.length && isNumericText(tokens[i]) && isNumericText(tokens[i + 1])) {
1493
+ x = Number(tokens[i]);
1494
+ y = Number(tokens[i + 1]);
1495
+ i += 2;
1496
+ dims = 2;
1497
+ if (i < tokens.length && isNumericText(tokens[i])) {
1498
+ z = Number(tokens[i]);
1499
+ i++;
1500
+ dims = 3;
1501
+ }
1502
+ } else if (i < tokens.length && isNumericText(tokens[i])) {
1503
+ this.report.counts.skippedNodes++;
1504
+ throw new LineError(
1505
+ "parse-error",
1506
+ PAJEK_ISSUE.VERTEX_LINE,
1507
+ `vertex ${k}: a single coordinate "${tokens[i]}"; coordinates are x y [z]`
1508
+ );
1509
+ }
1510
+ let shape = null;
1511
+ if (i < tokens.length && SHAPES.has(tokens[i])) {
1512
+ shape = tokens[i];
1513
+ i++;
1514
+ }
1515
+ const extras = this.extras(tokens, i, "node");
1516
+ const restored = this.restoredId(extras);
1517
+ this.createNodes();
1518
+ const repeated = this.seen[pos] !== 0;
1519
+ if (repeated) {
1520
+ this.report.warning(
1521
+ "merged",
1522
+ PAJEK_ISSUE.DUPLICATE_NODE,
1523
+ `vertex ${k} has more than one line; the later values overwrite`,
1524
+ { line, element: String(k) }
1525
+ );
1526
+ } else {
1527
+ this.seen[pos] = 1;
1528
+ this.seenCount++;
1529
+ }
1530
+ let index;
1531
+ if (this.idOfPos !== null && !repeated && this.options.nodeIdFrom === "label") {
1532
+ index = this.deferredNode(this.idOfPos, pos, k, label, line, "label");
1533
+ } else if (this.idOfPos !== null && this.idOfPos[pos] === void 0) {
1534
+ for (let p = this.filledBelow; p < pos; p++) {
1535
+ if (this.idOfPos[p] === void 0) {
1536
+ this.numberedNode(this.idOfPos, p);
1537
+ }
1538
+ }
1539
+ if (pos > this.filledBelow) {
1540
+ this.filledBelow = pos;
1541
+ }
1542
+ index = this.deferredNode(this.idOfPos, pos, k, restored, line, "originalId");
1543
+ } else {
1544
+ index = this.indexOfPos[pos];
1545
+ if (restored !== null && !repeated) {
1546
+ this.report.warning(
1547
+ "coercion",
1548
+ PAJEK_ISSUE.ORIGINAL_ID_UNRESTORED,
1549
+ `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`,
1550
+ { line, element: String(k) }
1551
+ );
1552
+ }
1553
+ }
1554
+ const { sink } = this;
1555
+ if (label !== null) {
1556
+ if (this.labelHandle === INVALID_INDEX) {
1557
+ this.labelHandle = declareResolved(sink, "node", LABEL_DECL, this.report, { line }).handle;
1558
+ }
1559
+ sink.setNodeValue(this.labelHandle, index, label);
1560
+ }
1561
+ if (dims > 0) {
1562
+ if (this.positionHandle === INVALID_INDEX) {
1563
+ this.coordDims = dims;
1564
+ this.positionHandle = declareResolved(sink, "node", positionDecl(dims), this.report, { line }).handle;
1565
+ } else if (dims !== this.coordDims) {
1566
+ this.report.warnOnce(
1567
+ "validation-error",
1568
+ PAJEK_ISSUE.COORD_DIMS,
1569
+ `vertex lines mix ${this.coordDims} and ${dims} coordinates; the position column records sourceDims ${this.coordDims}`,
1570
+ { line, element: String(k) }
1571
+ );
1572
+ }
1573
+ sink.setNodeValue(this.positionHandle, index, [x, y, z]);
1574
+ }
1575
+ if (shape !== null) {
1576
+ if (this.shapeHandle === INVALID_INDEX && this.nodeParams.has(SHAPE_COLUMN)) {
1577
+ this.writeParameter("node", SHAPE_COLUMN, index, shape);
1578
+ } else {
1579
+ if (this.shapeHandle === INVALID_INDEX) {
1580
+ this.shapeHandle = declareResolved(sink, "node", SHAPE_DECL, this.report, { line }).handle;
1581
+ }
1582
+ sink.setNodeValue(this.shapeHandle, index, shape);
1583
+ }
1584
+ }
1585
+ for (let p = 0; p < extras.keys.length; p++) {
1586
+ this.writeParameter("node", extras.keys[p], index, extras.values[p]);
1587
+ }
1588
+ if (extras.spells !== null) {
1589
+ if (this.nodeSpellsHandle === INVALID_INDEX) {
1590
+ this.nodeSpellsHandle = declareResolved(sink, "node", spellsDecl(), this.report, { line }).handle;
1591
+ }
1592
+ sink.setNodeValue(this.nodeSpellsHandle, index, extras.spells);
1593
+ }
1594
+ }
1595
+ /**
1596
+ * Whether a `graphty_originalId` parameter is restored as the vertex id: restoreMangledIds
1597
+ * under nodeIdFrom "id" (under "label" / "index" the parameter stays an ordinary column).
1598
+ * @returns true when the vertex lines may carry restorable ids
1599
+ */
1600
+ restoresIds() {
1601
+ return this.options.restoreMangledIds && this.options.nodeIdFrom === "id";
1602
+ }
1603
+ /**
1604
+ * Push a vertex whose id is its number (restore mode, a position whose line has not come or
1605
+ * carries no original id; or the end of the vertex section under "label").
1606
+ * @param ids - the id per vertex position
1607
+ * @param pos - the vertex position
1608
+ */
1609
+ numberedNode(ids, pos) {
1610
+ const id = this.idOfNumber(pos + this.base);
1611
+ ids[pos] = id;
1612
+ this.indexOfPos[pos] = this.sink.addNode(id);
1613
+ this.report.counts.nodes++;
1614
+ }
1615
+ /**
1616
+ * The `graphty_originalId` parameter of a vertex line, taken out of the extras when it is to be
1617
+ * restored as the id (restoreMangledIds, under nodeIdFrom "id"); otherwise null and the
1618
+ * parameter stays an ordinary column.
1619
+ * @param extras - the parsed extras
1620
+ * @returns the original id text, or null
1621
+ */
1622
+ restoredId(extras) {
1623
+ if (!this.restoresIds()) {
1624
+ return null;
1625
+ }
1626
+ const at = extras.keys.indexOf(ORIGINAL_ID_KEY);
1627
+ if (at < 0) {
1628
+ return null;
1629
+ }
1630
+ const [text] = extras.values.splice(at, 1);
1631
+ extras.keys.splice(at, 1);
1632
+ return text;
1633
+ }
1634
+ /**
1635
+ * Push the node of a vertex line whose id comes from the line itself: its label under
1636
+ * nodeIdFrom "label", or its restored `graphty_originalId` (the number when the line carries
1637
+ * none), reporting a merge when the id was seen before.
1638
+ * @param ids - the id per vertex position
1639
+ * @param pos - the vertex position
1640
+ * @param k - the vertex number
1641
+ * @param text - the label or original id text, or null
1642
+ * @param line - the line number
1643
+ * @param from - which text the id comes from
1644
+ * @returns the node index
1645
+ */
1646
+ deferredNode(ids, pos, k, text, line, from) {
1647
+ const id = text === null ? this.idOfNumber(k) : this.coercer.text(text);
1648
+ const before = this.sink.indexOf(id);
1649
+ const index = this.sink.addNode(id);
1650
+ ids[pos] = id;
1651
+ this.indexOfPos[pos] = index;
1652
+ this.report.counts.nodes++;
1653
+ if (before !== INVALID_INDEX) {
1654
+ this.report.warning(
1655
+ "merged",
1656
+ from === "label" ? PAJEK_ISSUE.LABEL_MERGED : PAJEK_ISSUE.ORIGINAL_ID_MERGED,
1657
+ from === "label" ? `vertex ${k} has the label ${JSON.stringify(String(id))} of an earlier vertex; both are one node` : `vertex ${k} has the ${ORIGINAL_ID_KEY} ${JSON.stringify(String(id))} of an earlier vertex; both are one node`,
1658
+ { line, element: String(k) }
1659
+ );
1660
+ }
1661
+ return index;
1662
+ }
1663
+ // ============================================================ lines
1664
+ /**
1665
+ * An arc or edge line: `u v [value] [key value ...] [interval]`.
1666
+ * @param text - the line
1667
+ * @param line - the line number
1668
+ */
1669
+ edgeLine(text, line) {
1670
+ const tokens = this.tokens(text, "edge");
1671
+ if (tokens.length < 2 || !isVertexNumber(tokens[0]) || !isVertexNumber(tokens[1])) {
1672
+ this.report.counts.skippedEdges++;
1673
+ throw new LineError(
1674
+ "parse-error",
1675
+ PAJEK_ISSUE.LINE,
1676
+ `a line starts with two vertex numbers, not "${tokens.slice(0, 2).join(" ")}"`
1677
+ );
1678
+ }
1679
+ const u = this.endpoint(Number(tokens[0]));
1680
+ const v = this.endpoint(Number(tokens[1]));
1681
+ let i = 2;
1682
+ let valueText = null;
1683
+ if (i < tokens.length && isNumericText(tokens[i])) {
1684
+ valueText = tokens[i];
1685
+ i++;
1686
+ }
1687
+ const extras = this.extras(tokens, i, "edge");
1688
+ const { weightFrom } = this.options;
1689
+ let weight;
1690
+ let valueIsWeight = false;
1691
+ if (isWeightField(VALUE_FIELD, weightFrom)) {
1692
+ valueIsWeight = true;
1693
+ if (valueText !== null) {
1694
+ weight = this.weightOf(valueText);
1695
+ }
1696
+ } else if (weightFrom !== null) {
1697
+ const at = extras.keys.indexOf(weightFrom);
1698
+ if (at >= 0) {
1699
+ weight = this.weightOf(extras.values[at]);
1700
+ extras.keys.splice(at, 1);
1701
+ extras.values.splice(at, 1);
1702
+ }
1703
+ }
1704
+ const e = this.pushEdge(u, v, weight, line);
1705
+ if (valueText !== null && !valueIsWeight) {
1706
+ this.writeParameter("edge", VALUE_COLUMN, e, valueText);
1707
+ }
1708
+ this.writeEdgeExtras(e, extras);
1709
+ }
1710
+ /**
1711
+ * An adjacency list line of `*Arcslist` / `*Edgeslist`: `v n1 n2 ...`.
1712
+ * @param text - the line
1713
+ * @param line - the line number
1714
+ */
1715
+ listLine(text, line) {
1716
+ const tokens = this.tokens(text, "edge");
1717
+ for (const token of tokens) {
1718
+ if (!isVertexNumber(token)) {
1719
+ this.report.counts.skippedEdges++;
1720
+ throw new LineError(
1721
+ "parse-error",
1722
+ PAJEK_ISSUE.LINE,
1723
+ `an adjacency list holds vertex numbers only, not "${token}"`
1724
+ );
1725
+ }
1726
+ }
1727
+ const u = this.endpoint(Number(tokens[0]));
1728
+ for (let i = 1; i < tokens.length; i++) {
1729
+ const v = this.endpoint(Number(tokens[i]));
1730
+ const e = this.pushEdge(u, v, void 0, line);
1731
+ this.writeRelation(e);
1732
+ }
1733
+ }
1734
+ /**
1735
+ * A `*Matrix` row: N values, every non-zero entry an arc from the row's vertex with that value.
1736
+ * @param text - the line
1737
+ * @param line - the line number
1738
+ */
1739
+ matrixLine(text, line) {
1740
+ const tokens = this.tokens(text, "edge");
1741
+ const row = this.matrixRows;
1742
+ this.matrixRows++;
1743
+ if (row >= this.vertexCount) {
1744
+ throw new LineError(
1745
+ "validation-error",
1746
+ PAJEK_ISSUE.MATRIX_ROWS,
1747
+ `*Matrix row ${row + 1} is beyond the ${this.vertexCount} declared vertices`
1748
+ );
1749
+ }
1750
+ if (tokens.length !== this.vertexCount) {
1751
+ throw new LineError(
1752
+ "parse-error",
1753
+ PAJEK_ISSUE.LINE,
1754
+ `*Matrix row ${row + 1} has ${tokens.length} value(s); *Vertices declares ${this.vertexCount}`
1755
+ );
1756
+ }
1757
+ for (const token of tokens) {
1758
+ if (!isNumericText(token)) {
1759
+ throw new LineError(
1760
+ "parse-error",
1761
+ PAJEK_ISSUE.LINE,
1762
+ `*Matrix row ${row + 1}: "${token}" is not a number`
1763
+ );
1764
+ }
1765
+ }
1766
+ for (let j = 0; j < tokens.length; j++) {
1767
+ const value = Number(tokens[j]);
1768
+ if (value === 0) {
1769
+ continue;
1770
+ }
1771
+ const e = this.pushEdge(row, j, value, line);
1772
+ this.writeRelation(e);
1773
+ }
1774
+ }
1775
+ /**
1776
+ * Push one line through the direction resolver, counting the logical edges it produced.
1777
+ * @param u - the source position
1778
+ * @param v - the target position
1779
+ * @param weight - the weight, or undefined for none
1780
+ * @param line - the line number
1781
+ * @returns the primary logical edge index
1782
+ */
1783
+ pushEdge(u, v, weight, line) {
1784
+ const { sink } = this;
1785
+ const before = sink.edgeCount;
1786
+ const e = this.resolver.addEdge(this.idAt(u), this.idAt(v), this.kind, weight, { line });
1787
+ this.report.counts.edges += sink.edgeCount - before;
1788
+ return e;
1789
+ }
1790
+ /**
1791
+ * Write the relation, parameters and spells of a line on its primary half.
1792
+ * @param e - the edge index
1793
+ * @param extras - the parsed extras
1794
+ */
1795
+ writeEdgeExtras(e, extras) {
1796
+ const { sink } = this;
1797
+ this.writeRelation(e);
1798
+ for (let p = 0; p < extras.keys.length; p++) {
1799
+ this.writeParameter("edge", extras.keys[p], e, extras.values[p]);
1800
+ }
1801
+ if (extras.spells !== null) {
1802
+ if (this.edgeSpellsHandle === INVALID_INDEX) {
1803
+ this.edgeSpellsHandle = declareResolved(sink, "edge", spellsDecl(), this.report).handle;
1804
+ }
1805
+ sink.setEdgeValue(this.edgeSpellsHandle, e, extras.spells);
1806
+ }
1807
+ }
1808
+ /**
1809
+ * Write the current section's relation name on an edge, when the section has one.
1810
+ * @param e - the edge index
1811
+ */
1812
+ writeRelation(e) {
1813
+ if (this.relation === null) {
1814
+ return;
1815
+ }
1816
+ if (this.relationHandle === INVALID_INDEX) {
1817
+ this.relationHandle = declareResolved(this.sink, "edge", RELATION_DECL, this.report).handle;
1818
+ }
1819
+ this.sink.setEdgeValue(this.relationHandle, e, this.relation);
1820
+ }
1821
+ /**
1822
+ * A line endpoint: the vertex number checked against the declared range; a reference outside
1823
+ * it is a missing-value error and the line is skipped (Pajek's `*Vertices N` bounds the id space,
1824
+ * so addMissingNodes does not apply).
1825
+ * @param k - the vertex number
1826
+ * @returns the vertex position
1827
+ */
1828
+ endpoint(k) {
1829
+ const pos = k - this.base;
1830
+ if (pos < 0 || pos >= this.vertexCount) {
1831
+ this.report.counts.skippedEdges++;
1832
+ throw new LineError(
1833
+ "missing-value",
1834
+ PAJEK_ISSUE.UNKNOWN_NODE,
1835
+ `vertex ${k} is outside ${this.base}..${this.base + this.vertexCount - 1}`
1836
+ );
1837
+ }
1838
+ return pos;
1839
+ }
1840
+ /**
1841
+ * The weight of a line value text.
1842
+ * @param text - the text
1843
+ * @returns the weight; E_INVALID_WEIGHT is recorded by the caller's catch
1844
+ */
1845
+ weightOf(text) {
1846
+ try {
1847
+ return parseWeightText(text);
1848
+ } catch (err) {
1849
+ this.report.counts.skippedEdges++;
1850
+ throw err;
1851
+ }
1852
+ }
1853
+ // ============================================================ shared
1854
+ /**
1855
+ * Tokenize a data line.
1856
+ * @param text - the line
1857
+ * @param domain - what is skipped on an unterminated quote
1858
+ * @returns the tokens (at least one: blank lines never reach here)
1859
+ */
1860
+ tokens(text, domain) {
1861
+ const tokens = tokenize(text);
1862
+ if (tokens === null) {
1863
+ if (domain === "node") {
1864
+ this.report.counts.skippedNodes++;
1865
+ } else {
1866
+ this.report.counts.skippedEdges++;
1867
+ }
1868
+ throw new LineError("parse-error", PAJEK_ISSUE.UNTERMINATED_QUOTE, "a double quote is not closed");
1869
+ }
1870
+ return tokens;
1871
+ }
1872
+ /**
1873
+ * The `key value` parameters and the interval token at the end of a row, validated before
1874
+ * anything is written.
1875
+ * @param tokens - the row's tokens
1876
+ * @param start - the first token to read
1877
+ * @param domain - what is skipped when the row is malformed
1878
+ * @returns the parsed extras
1879
+ */
1880
+ extras(tokens, start, domain) {
1881
+ const extras = { keys: [], values: [], spells: null };
1882
+ for (let i = start; i < tokens.length; ) {
1883
+ const token = tokens[i];
1884
+ if (isIntervalToken(token)) {
1885
+ try {
1886
+ extras.spells = parseIntervals(token);
1887
+ } catch (err) {
1888
+ this.skip(domain);
1889
+ throw new LineError(
1890
+ "validation-error",
1891
+ PAJEK_ISSUE.INTERVAL,
1892
+ err instanceof Error ? err.message : String(err)
1893
+ );
1894
+ }
1895
+ i++;
1896
+ continue;
1897
+ }
1898
+ if (i + 1 >= tokens.length) {
1899
+ this.skip(domain);
1900
+ throw new LineError(
1901
+ "parse-error",
1902
+ domain === "node" ? PAJEK_ISSUE.VERTEX_LINE : PAJEK_ISSUE.LINE,
1903
+ `parameter "${token}" has no value`
1904
+ );
1905
+ }
1906
+ extras.keys.push(token);
1907
+ extras.values.push(tokens[i + 1]);
1908
+ i += 2;
1909
+ }
1910
+ return extras;
1911
+ }
1912
+ /**
1913
+ * Count a skipped element.
1914
+ * @param domain - node or edge
1915
+ */
1916
+ skip(domain) {
1917
+ if (domain === "node") {
1918
+ this.report.counts.skippedNodes++;
1919
+ } else {
1920
+ this.report.counts.skippedEdges++;
1921
+ }
1922
+ }
1923
+ /**
1924
+ * Write one `key value` parameter cell through the column's text writer (the 5.1 grammar per
1925
+ * column, the lexical form kept). A `shape` parameter next to the shape keyword column goes
1926
+ * into that column (a dict takes any text), and a shape keyword next to a `shape` parameter
1927
+ * column into that one, so a file mixing the two spellings reads as one column.
1928
+ * @param domain - node or edge
1929
+ * @param key - the parameter key (the column name)
1930
+ * @param row - the node or edge index
1931
+ * @param text - the value text
1932
+ */
1933
+ writeParameter(domain, key, row, text) {
1934
+ if (domain === "node" && key === SHAPE_COLUMN && this.shapeHandle !== INVALID_INDEX) {
1935
+ this.sink.setNodeValue(this.shapeHandle, row, text);
1936
+ return;
1937
+ }
1938
+ const writers = domain === "node" ? this.nodeParams : this.edgeParams;
1939
+ let writer = writers.get(key);
1940
+ if (writer === void 0) {
1941
+ writer = new TextCellWriter(key, domain, this.sink, this.report);
1942
+ writers.set(key, writer);
1943
+ }
1944
+ writer.write(row, text);
1945
+ }
1946
+ }
1947
+ function firstVertexOption(value) {
1948
+ if (value === void 0) {
1949
+ return "auto";
1950
+ }
1951
+ if (FIRST_VERTEX_VALUES.has(value)) {
1952
+ return value;
1953
+ }
1954
+ throw new GraphFormatError(
1955
+ "E_UNSUPPORTED",
1956
+ `option firstVertex: ${JSON.stringify(value) ?? typeof value} is not 0, 1 or "auto"`,
1957
+ {
1958
+ option: "firstVertex",
1959
+ found: value
1960
+ }
1961
+ );
1962
+ }
1963
+ const HEAD_PATTERN = /^\s*\*(vertices|network)\b/i;
1964
+ const pajekImporter = Object.freeze({
1965
+ format: "pajek",
1966
+ extensions: Object.freeze([".net", ".paj"]),
1967
+ mimeTypes: Object.freeze(["text/x-pajek", "text/plain"]),
1968
+ /**
1969
+ * Confidence that the input is a Pajek network: it starts with `*Vertices` (or `*Network`).
1970
+ * @param head - the first bytes
1971
+ * @returns 0.9 for a `*Vertices` start, 0.8 for `*Network`, 0 otherwise
1972
+ */
1973
+ sniff(head) {
1974
+ const text = new TextDecoder("utf-8").decode(head.subarray(0, Math.min(head.byteLength, 512)));
1975
+ const match = HEAD_PATTERN.exec(text.startsWith(String.fromCharCode(65279)) ? text.slice(1) : text);
1976
+ if (match === null) {
1977
+ return 0;
1978
+ }
1979
+ return match[1].toLowerCase() === "vertices" ? 0.9 : 0.8;
1980
+ },
1981
+ /**
1982
+ * Read a Pajek network into the sink.
1983
+ * @param input - the text, bytes or stream
1984
+ * @param sink - the sink
1985
+ * @param options - common and Pajek options
1986
+ * @returns the report
1987
+ */
1988
+ async import(input, sink, options) {
1989
+ const resolved = resolveImportOptions(options, DEFAULTS);
1990
+ const firstVertex = firstVertexOption(options?.firstVertex);
1991
+ const report = new ImportReportBuilder("pajek", resolved.errorLimit);
1992
+ reportSinkOptions(sink, options, report);
1993
+ reportUnusedOptions(options, report, USED_OPTIONS);
1994
+ const parser = new PajekParser(sink, report, resolved, firstVertex);
1995
+ const reader = new LineReader(input, report, resolved);
1996
+ let sinceCheck = 0;
1997
+ for await (const text of reader) {
1998
+ parser.line(text, reader.line);
1999
+ if (++sinceCheck >= ABORT_CHECK_INTERVAL) {
2000
+ sinceCheck = 0;
2001
+ throwIfAborted(resolved.signal);
2002
+ }
2003
+ }
2004
+ parser.finish();
2005
+ throwIfAborted(resolved.signal);
2006
+ return report.finish();
2007
+ }
2008
+ });
2009
+ export {
2010
+ PAJEK_ISSUE as P,
2011
+ PAJEK_LOSS as a,
2012
+ pajekImporter as b,
2013
+ pajekExporter as p
2014
+ };
2015
+ //# sourceMappingURL=importer-CpCpfbxr.js.map