@graphty/graph-io 0.3.25 → 0.3.27

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 (581) hide show
  1. package/README.md +49 -414
  2. package/dist/chunks/children-CL3Cy0ez.js.map +1 -1
  3. package/dist/chunks/choose-DrXtu_96.js +56 -0
  4. package/dist/chunks/choose-DrXtu_96.js.map +1 -0
  5. package/dist/chunks/constants-ikG85dT8.js +231 -0
  6. package/dist/chunks/constants-ikG85dT8.js.map +1 -0
  7. package/dist/chunks/{escape-B-tkk_Cl.js → escape-D1HPTUrP.js} +390 -99
  8. package/dist/chunks/escape-D1HPTUrP.js.map +1 -0
  9. package/dist/chunks/export-CHd4tQE2.js +4333 -0
  10. package/dist/chunks/export-CHd4tQE2.js.map +1 -0
  11. package/dist/chunks/{importer-eUREhBGj.js → exporter-D23CKNqF.js} +4969 -3201
  12. package/dist/chunks/exporter-D23CKNqF.js.map +1 -0
  13. package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-DfWTgrti.js} +23 -21
  14. package/dist/chunks/exporter-DfWTgrti.js.map +1 -0
  15. package/dist/chunks/exporter-DhtW6FxK.js +3465 -0
  16. package/dist/chunks/exporter-DhtW6FxK.js.map +1 -0
  17. package/dist/chunks/{exporter-BGrsWamJ.js → exporter-IHv4nkCm.js} +38 -229
  18. package/dist/chunks/exporter-IHv4nkCm.js.map +1 -0
  19. package/dist/chunks/{importer-BWY2FFc5.js → importer-B1wmPQjr.js} +255 -77
  20. package/dist/chunks/importer-B1wmPQjr.js.map +1 -0
  21. package/dist/chunks/{importer-D_e7e7LX.js → importer-B2W_58pz.js} +399 -146
  22. package/dist/chunks/importer-B2W_58pz.js.map +1 -0
  23. package/dist/chunks/importer-BSALvkui.js +2811 -0
  24. package/dist/chunks/importer-BSALvkui.js.map +1 -0
  25. package/dist/chunks/{importer-vi3bSdtw.js → importer-BVRE--Un.js} +510 -77
  26. package/dist/chunks/importer-BVRE--Un.js.map +1 -0
  27. package/dist/chunks/{importer-DET1tg6s.js → importer-CIAzt-wo.js} +1689 -312
  28. package/dist/chunks/importer-CIAzt-wo.js.map +1 -0
  29. package/dist/chunks/{importer-DIrbbnAf.js → importer-DpEW5zeG.js} +585 -226
  30. package/dist/chunks/importer-DpEW5zeG.js.map +1 -0
  31. package/dist/chunks/{importer-zGLo8Dg_.js → importer-z_FL4VMb.js} +488 -161
  32. package/dist/chunks/importer-z_FL4VMb.js.map +1 -0
  33. package/dist/chunks/json-elements-BAmNBZok.js +1225 -0
  34. package/dist/chunks/json-elements-BAmNBZok.js.map +1 -0
  35. package/dist/chunks/ontology-export-Bab4eUA8.js +357 -0
  36. package/dist/chunks/ontology-export-Bab4eUA8.js.map +1 -0
  37. package/dist/chunks/{records-dbRkxwaq.js → records-CdlvL4RO.js} +298 -114
  38. package/dist/chunks/records-CdlvL4RO.js.map +1 -0
  39. package/dist/chunks/{text-Dr0Ifpag.js → text-DzXjAiTl.js} +29 -11
  40. package/dist/chunks/text-DzXjAiTl.js.map +1 -0
  41. package/dist/chunks/{writer-C7flM-Ih.js → writer-DxjgHwSe.js} +19 -1
  42. package/dist/chunks/writer-DxjgHwSe.js.map +1 -0
  43. package/dist/csv.js +952 -262
  44. package/dist/csv.js.map +1 -1
  45. package/dist/cx.js +6 -3
  46. package/dist/cx2.js +2 -2
  47. package/dist/cys.js +6 -3
  48. package/dist/dot.js +1 -1
  49. package/dist/gexf.js +554 -122
  50. package/dist/gexf.js.map +1 -1
  51. package/dist/gml.js +344 -87
  52. package/dist/gml.js.map +1 -1
  53. package/dist/graph-io.js +1105 -213
  54. package/dist/graph-io.js.map +1 -1
  55. package/dist/graphml.js +3 -2
  56. package/dist/graphml.js.map +1 -1
  57. package/dist/json.js +3 -2
  58. package/dist/neo4j.js +565 -158
  59. package/dist/neo4j.js.map +1 -1
  60. package/dist/obo.js +6 -3
  61. package/dist/pajek.js +1 -1
  62. package/dist/src/children.d.ts +18 -6
  63. package/dist/src/children.d.ts.map +1 -1
  64. package/dist/src/children.js +6 -3
  65. package/dist/src/children.js.map +1 -1
  66. package/dist/src/common/attributes.d.ts +29 -11
  67. package/dist/src/common/attributes.d.ts.map +1 -1
  68. package/dist/src/common/attributes.js +25 -7
  69. package/dist/src/common/attributes.js.map +1 -1
  70. package/dist/src/common/cell-budget.d.ts +9 -2
  71. package/dist/src/common/cell-budget.d.ts.map +1 -1
  72. package/dist/src/common/cell-budget.js +11 -3
  73. package/dist/src/common/cell-budget.js.map +1 -1
  74. package/dist/src/common/choose.d.ts +19 -0
  75. package/dist/src/common/choose.d.ts.map +1 -0
  76. package/dist/src/common/choose.js +72 -0
  77. package/dist/src/common/choose.js.map +1 -0
  78. package/dist/src/common/codes.d.ts +456 -66
  79. package/dist/src/common/codes.d.ts.map +1 -1
  80. package/dist/src/common/codes.js +458 -66
  81. package/dist/src/common/codes.js.map +1 -1
  82. package/dist/src/common/compare.d.ts +81 -0
  83. package/dist/src/common/compare.d.ts.map +1 -0
  84. package/dist/src/common/compare.js +394 -0
  85. package/dist/src/common/compare.js.map +1 -0
  86. package/dist/src/common/cx-export.d.ts +123 -0
  87. package/dist/src/common/cx-export.d.ts.map +1 -0
  88. package/dist/src/common/cx-export.js +334 -0
  89. package/dist/src/common/cx-export.js.map +1 -0
  90. package/dist/src/common/declared-types.d.ts +22 -2
  91. package/dist/src/common/declared-types.d.ts.map +1 -1
  92. package/dist/src/common/declared-types.js +21 -3
  93. package/dist/src/common/declared-types.js.map +1 -1
  94. package/dist/src/common/direction.d.ts +74 -50
  95. package/dist/src/common/direction.d.ts.map +1 -1
  96. package/dist/src/common/direction.js +47 -27
  97. package/dist/src/common/direction.js.map +1 -1
  98. package/dist/src/common/escape.d.ts +21 -9
  99. package/dist/src/common/escape.d.ts.map +1 -1
  100. package/dist/src/common/escape.js +39 -22
  101. package/dist/src/common/escape.js.map +1 -1
  102. package/dist/src/common/export.d.ts +144 -32
  103. package/dist/src/common/export.d.ts.map +1 -1
  104. package/dist/src/common/export.js +216 -42
  105. package/dist/src/common/export.js.map +1 -1
  106. package/dist/src/common/format.d.ts +23 -1
  107. package/dist/src/common/format.d.ts.map +1 -1
  108. package/dist/src/common/format.js +26 -1
  109. package/dist/src/common/format.js.map +1 -1
  110. package/dist/src/common/ids.d.ts +9 -2
  111. package/dist/src/common/ids.d.ts.map +1 -1
  112. package/dist/src/common/ids.js +6 -2
  113. package/dist/src/common/ids.js.map +1 -1
  114. package/dist/src/common/input.d.ts +122 -28
  115. package/dist/src/common/input.d.ts.map +1 -1
  116. package/dist/src/common/input.js +890 -124
  117. package/dist/src/common/input.js.map +1 -1
  118. package/dist/src/common/json-elements.d.ts +144 -20
  119. package/dist/src/common/json-elements.d.ts.map +1 -1
  120. package/dist/src/common/json-elements.js +869 -189
  121. package/dist/src/common/json-elements.js.map +1 -1
  122. package/dist/src/common/line-format.d.ts +90 -0
  123. package/dist/src/common/line-format.d.ts.map +1 -0
  124. package/dist/src/common/line-format.js +185 -0
  125. package/dist/src/common/line-format.js.map +1 -0
  126. package/dist/src/common/lists.d.ts +6 -1
  127. package/dist/src/common/lists.d.ts.map +1 -1
  128. package/dist/src/common/lists.js +3 -1
  129. package/dist/src/common/lists.js.map +1 -1
  130. package/dist/src/common/ontology-export.d.ts +167 -0
  131. package/dist/src/common/ontology-export.d.ts.map +1 -0
  132. package/dist/src/common/ontology-export.js +359 -0
  133. package/dist/src/common/ontology-export.js.map +1 -0
  134. package/dist/src/common/ontology.d.ts +25 -3
  135. package/dist/src/common/ontology.d.ts.map +1 -1
  136. package/dist/src/common/ontology.js +30 -9
  137. package/dist/src/common/ontology.js.map +1 -1
  138. package/dist/src/common/options.d.ts +62 -12
  139. package/dist/src/common/options.d.ts.map +1 -1
  140. package/dist/src/common/options.js +89 -11
  141. package/dist/src/common/options.js.map +1 -1
  142. package/dist/src/common/plural.d.ts +31 -0
  143. package/dist/src/common/plural.d.ts.map +1 -0
  144. package/dist/src/common/plural.js +46 -0
  145. package/dist/src/common/plural.js.map +1 -0
  146. package/dist/src/common/report.d.ts +72 -14
  147. package/dist/src/common/report.d.ts.map +1 -1
  148. package/dist/src/common/report.js +146 -22
  149. package/dist/src/common/report.js.map +1 -1
  150. package/dist/src/common/temporal.d.ts +22 -5
  151. package/dist/src/common/temporal.d.ts.map +1 -1
  152. package/dist/src/common/temporal.js +13 -2
  153. package/dist/src/common/temporal.js.map +1 -1
  154. package/dist/src/common/text.d.ts +39 -23
  155. package/dist/src/common/text.d.ts.map +1 -1
  156. package/dist/src/common/text.js +51 -21
  157. package/dist/src/common/text.js.map +1 -1
  158. package/dist/src/common/weights.d.ts +37 -4
  159. package/dist/src/common/weights.d.ts.map +1 -1
  160. package/dist/src/common/weights.js +61 -2
  161. package/dist/src/common/weights.js.map +1 -1
  162. package/dist/src/common/writer.d.ts +19 -3
  163. package/dist/src/common/writer.d.ts.map +1 -1
  164. package/dist/src/common/writer.js +30 -2
  165. package/dist/src/common/writer.js.map +1 -1
  166. package/dist/src/common/xml.d.ts +125 -9
  167. package/dist/src/common/xml.d.ts.map +1 -1
  168. package/dist/src/common/xml.js +402 -85
  169. package/dist/src/common/xml.js.map +1 -1
  170. package/dist/src/common/zip.d.ts +44 -4
  171. package/dist/src/common/zip.d.ts.map +1 -1
  172. package/dist/src/common/zip.js +134 -5
  173. package/dist/src/common/zip.js.map +1 -1
  174. package/dist/src/formats/csv/exporter.d.ts +80 -24
  175. package/dist/src/formats/csv/exporter.d.ts.map +1 -1
  176. package/dist/src/formats/csv/exporter.js +146 -33
  177. package/dist/src/formats/csv/exporter.js.map +1 -1
  178. package/dist/src/formats/csv/header.d.ts +38 -9
  179. package/dist/src/formats/csv/header.d.ts.map +1 -1
  180. package/dist/src/formats/csv/header.js +46 -10
  181. package/dist/src/formats/csv/header.js.map +1 -1
  182. package/dist/src/formats/csv/importer.d.ts +187 -37
  183. package/dist/src/formats/csv/importer.d.ts.map +1 -1
  184. package/dist/src/formats/csv/importer.js +835 -204
  185. package/dist/src/formats/csv/importer.js.map +1 -1
  186. package/dist/src/formats/csv/index.d.ts +100 -19
  187. package/dist/src/formats/csv/index.d.ts.map +1 -1
  188. package/dist/src/formats/csv/index.js +94 -21
  189. package/dist/src/formats/csv/index.js.map +1 -1
  190. package/dist/src/formats/csv/records.d.ts +95 -10
  191. package/dist/src/formats/csv/records.d.ts.map +1 -1
  192. package/dist/src/formats/csv/records.js +333 -122
  193. package/dist/src/formats/csv/records.js.map +1 -1
  194. package/dist/src/formats/csv/values.d.ts +1 -0
  195. package/dist/src/formats/csv/values.d.ts.map +1 -1
  196. package/dist/src/formats/csv/values.js +1 -0
  197. package/dist/src/formats/csv/values.js.map +1 -1
  198. package/dist/src/formats/cx/exporter.d.ts +102 -0
  199. package/dist/src/formats/cx/exporter.d.ts.map +1 -0
  200. package/dist/src/formats/cx/exporter.js +1217 -0
  201. package/dist/src/formats/cx/exporter.js.map +1 -0
  202. package/dist/src/formats/cx/importer.d.ts +79 -32
  203. package/dist/src/formats/cx/importer.d.ts.map +1 -1
  204. package/dist/src/formats/cx/importer.js +682 -287
  205. package/dist/src/formats/cx/importer.js.map +1 -1
  206. package/dist/src/formats/cx/index.d.ts +4 -3
  207. package/dist/src/formats/cx/index.d.ts.map +1 -1
  208. package/dist/src/formats/cx/index.js +4 -3
  209. package/dist/src/formats/cx/index.js.map +1 -1
  210. package/dist/src/formats/cx2/exporter.d.ts +29 -11
  211. package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
  212. package/dist/src/formats/cx2/exporter.js +39 -268
  213. package/dist/src/formats/cx2/exporter.js.map +1 -1
  214. package/dist/src/formats/cx2/importer.d.ts +78 -28
  215. package/dist/src/formats/cx2/importer.d.ts.map +1 -1
  216. package/dist/src/formats/cx2/importer.js +227 -80
  217. package/dist/src/formats/cx2/importer.js.map +1 -1
  218. package/dist/src/formats/cx2/index.d.ts +2 -1
  219. package/dist/src/formats/cx2/index.d.ts.map +1 -1
  220. package/dist/src/formats/cx2/index.js +2 -1
  221. package/dist/src/formats/cx2/index.js.map +1 -1
  222. package/dist/src/formats/cys/constants.d.ts +66 -17
  223. package/dist/src/formats/cys/constants.d.ts.map +1 -1
  224. package/dist/src/formats/cys/constants.js +59 -18
  225. package/dist/src/formats/cys/constants.js.map +1 -1
  226. package/dist/src/formats/cys/exporter.d.ts +118 -0
  227. package/dist/src/formats/cys/exporter.d.ts.map +1 -0
  228. package/dist/src/formats/cys/exporter.js +1072 -0
  229. package/dist/src/formats/cys/exporter.js.map +1 -0
  230. package/dist/src/formats/cys/importer.d.ts +22 -8
  231. package/dist/src/formats/cys/importer.d.ts.map +1 -1
  232. package/dist/src/formats/cys/importer.js +510 -149
  233. package/dist/src/formats/cys/importer.js.map +1 -1
  234. package/dist/src/formats/cys/index.d.ts +4 -2
  235. package/dist/src/formats/cys/index.d.ts.map +1 -1
  236. package/dist/src/formats/cys/index.js +4 -2
  237. package/dist/src/formats/cys/index.js.map +1 -1
  238. package/dist/src/formats/cys/session.d.ts +46 -7
  239. package/dist/src/formats/cys/session.d.ts.map +1 -1
  240. package/dist/src/formats/cys/session.js +101 -27
  241. package/dist/src/formats/cys/session.js.map +1 -1
  242. package/dist/src/formats/cys/tables.d.ts +9 -2
  243. package/dist/src/formats/cys/tables.d.ts.map +1 -1
  244. package/dist/src/formats/cys/tables.js +29 -7
  245. package/dist/src/formats/cys/tables.js.map +1 -1
  246. package/dist/src/formats/dot/exporter.d.ts +42 -14
  247. package/dist/src/formats/dot/exporter.d.ts.map +1 -1
  248. package/dist/src/formats/dot/exporter.js +56 -29
  249. package/dist/src/formats/dot/exporter.js.map +1 -1
  250. package/dist/src/formats/dot/importer.d.ts +91 -29
  251. package/dist/src/formats/dot/importer.d.ts.map +1 -1
  252. package/dist/src/formats/dot/importer.js +190 -39
  253. package/dist/src/formats/dot/importer.js.map +1 -1
  254. package/dist/src/formats/dot/index.d.ts +2 -1
  255. package/dist/src/formats/dot/index.d.ts.map +1 -1
  256. package/dist/src/formats/dot/index.js +2 -1
  257. package/dist/src/formats/dot/index.js.map +1 -1
  258. package/dist/src/formats/dot/names.d.ts +44 -11
  259. package/dist/src/formats/dot/names.d.ts.map +1 -1
  260. package/dist/src/formats/dot/names.js +44 -11
  261. package/dist/src/formats/dot/names.js.map +1 -1
  262. package/dist/src/formats/dot/tokenizer.d.ts +9 -2
  263. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
  264. package/dist/src/formats/dot/tokenizer.js +20 -5
  265. package/dist/src/formats/dot/tokenizer.js.map +1 -1
  266. package/dist/src/formats/gexf/exporter.d.ts +46 -15
  267. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  268. package/dist/src/formats/gexf/exporter.js +45 -15
  269. package/dist/src/formats/gexf/exporter.js.map +1 -1
  270. package/dist/src/formats/gexf/importer.d.ts +87 -23
  271. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  272. package/dist/src/formats/gexf/importer.js +471 -90
  273. package/dist/src/formats/gexf/importer.js.map +1 -1
  274. package/dist/src/formats/gexf/index.d.ts +84 -21
  275. package/dist/src/formats/gexf/index.d.ts.map +1 -1
  276. package/dist/src/formats/gexf/index.js +80 -23
  277. package/dist/src/formats/gexf/index.js.map +1 -1
  278. package/dist/src/formats/gexf/schema.d.ts +67 -15
  279. package/dist/src/formats/gexf/schema.d.ts.map +1 -1
  280. package/dist/src/formats/gexf/schema.js +59 -13
  281. package/dist/src/formats/gexf/schema.js.map +1 -1
  282. package/dist/src/formats/gml/exporter.d.ts +69 -21
  283. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  284. package/dist/src/formats/gml/exporter.js +74 -29
  285. package/dist/src/formats/gml/exporter.js.map +1 -1
  286. package/dist/src/formats/gml/importer.d.ts +116 -25
  287. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  288. package/dist/src/formats/gml/importer.js +290 -61
  289. package/dist/src/formats/gml/importer.js.map +1 -1
  290. package/dist/src/formats/gml/index.d.ts +87 -24
  291. package/dist/src/formats/gml/index.d.ts.map +1 -1
  292. package/dist/src/formats/gml/index.js +79 -26
  293. package/dist/src/formats/gml/index.js.map +1 -1
  294. package/dist/src/formats/gml/syntax.d.ts +67 -18
  295. package/dist/src/formats/gml/syntax.d.ts.map +1 -1
  296. package/dist/src/formats/gml/syntax.js +110 -28
  297. package/dist/src/formats/gml/syntax.js.map +1 -1
  298. package/dist/src/formats/graphml/constants.d.ts +174 -44
  299. package/dist/src/formats/graphml/constants.d.ts.map +1 -1
  300. package/dist/src/formats/graphml/constants.js +165 -44
  301. package/dist/src/formats/graphml/constants.js.map +1 -1
  302. package/dist/src/formats/graphml/exporter.d.ts +17 -9
  303. package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
  304. package/dist/src/formats/graphml/exporter.js +33 -12
  305. package/dist/src/formats/graphml/exporter.js.map +1 -1
  306. package/dist/src/formats/graphml/importer.d.ts +19 -7
  307. package/dist/src/formats/graphml/importer.d.ts.map +1 -1
  308. package/dist/src/formats/graphml/importer.js +466 -39
  309. package/dist/src/formats/graphml/importer.js.map +1 -1
  310. package/dist/src/formats/graphml/index.d.ts +2 -1
  311. package/dist/src/formats/graphml/index.d.ts.map +1 -1
  312. package/dist/src/formats/graphml/index.js +2 -1
  313. package/dist/src/formats/graphml/index.js.map +1 -1
  314. package/dist/src/formats/graphml/tree.d.ts +6 -2
  315. package/dist/src/formats/graphml/tree.d.ts.map +1 -1
  316. package/dist/src/formats/graphml/tree.js +6 -2
  317. package/dist/src/formats/graphml/tree.js.map +1 -1
  318. package/dist/src/formats/graphml/yfiles.d.ts +8 -3
  319. package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
  320. package/dist/src/formats/graphml/yfiles.js +36 -15
  321. package/dist/src/formats/graphml/yfiles.js.map +1 -1
  322. package/dist/src/formats/json/dialect.d.ts +88 -24
  323. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  324. package/dist/src/formats/json/dialect.js +109 -19
  325. package/dist/src/formats/json/dialect.js.map +1 -1
  326. package/dist/src/formats/json/exporter.d.ts +134 -22
  327. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  328. package/dist/src/formats/json/exporter.js +135 -54
  329. package/dist/src/formats/json/exporter.js.map +1 -1
  330. package/dist/src/formats/json/importer.d.ts +173 -52
  331. package/dist/src/formats/json/importer.d.ts.map +1 -1
  332. package/dist/src/formats/json/importer.js +899 -220
  333. package/dist/src/formats/json/importer.js.map +1 -1
  334. package/dist/src/formats/json/index.d.ts +3 -2
  335. package/dist/src/formats/json/index.d.ts.map +1 -1
  336. package/dist/src/formats/json/index.js +3 -2
  337. package/dist/src/formats/json/index.js.map +1 -1
  338. package/dist/src/formats/json/obographs-export.d.ts +68 -0
  339. package/dist/src/formats/json/obographs-export.d.ts.map +1 -0
  340. package/dist/src/formats/json/obographs-export.js +941 -0
  341. package/dist/src/formats/json/obographs-export.js.map +1 -0
  342. package/dist/src/formats/json/obographs.d.ts +26 -0
  343. package/dist/src/formats/json/obographs.d.ts.map +1 -1
  344. package/dist/src/formats/json/obographs.js +297 -80
  345. package/dist/src/formats/json/obographs.js.map +1 -1
  346. package/dist/src/formats/neo4j/exporter.d.ts +109 -21
  347. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
  348. package/dist/src/formats/neo4j/exporter.js +239 -86
  349. package/dist/src/formats/neo4j/exporter.js.map +1 -1
  350. package/dist/src/formats/neo4j/header.d.ts +17 -4
  351. package/dist/src/formats/neo4j/header.d.ts.map +1 -1
  352. package/dist/src/formats/neo4j/header.js +36 -4
  353. package/dist/src/formats/neo4j/header.js.map +1 -1
  354. package/dist/src/formats/neo4j/importer.d.ts +107 -27
  355. package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
  356. package/dist/src/formats/neo4j/importer.js +415 -63
  357. package/dist/src/formats/neo4j/importer.js.map +1 -1
  358. package/dist/src/formats/neo4j/index.d.ts +73 -23
  359. package/dist/src/formats/neo4j/index.d.ts.map +1 -1
  360. package/dist/src/formats/neo4j/index.js +65 -25
  361. package/dist/src/formats/neo4j/index.js.map +1 -1
  362. package/dist/src/formats/obo/exporter.d.ts +150 -0
  363. package/dist/src/formats/obo/exporter.d.ts.map +1 -0
  364. package/dist/src/formats/obo/exporter.js +1736 -0
  365. package/dist/src/formats/obo/exporter.js.map +1 -0
  366. package/dist/src/formats/obo/importer.d.ts +95 -19
  367. package/dist/src/formats/obo/importer.d.ts.map +1 -1
  368. package/dist/src/formats/obo/importer.js +475 -104
  369. package/dist/src/formats/obo/importer.js.map +1 -1
  370. package/dist/src/formats/obo/index.d.ts +6 -3
  371. package/dist/src/formats/obo/index.d.ts.map +1 -1
  372. package/dist/src/formats/obo/index.js +6 -3
  373. package/dist/src/formats/obo/index.js.map +1 -1
  374. package/dist/src/formats/obo/syntax.d.ts +108 -4
  375. package/dist/src/formats/obo/syntax.d.ts.map +1 -1
  376. package/dist/src/formats/obo/syntax.js +211 -34
  377. package/dist/src/formats/obo/syntax.js.map +1 -1
  378. package/dist/src/formats/pajek/exporter.d.ts +54 -16
  379. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  380. package/dist/src/formats/pajek/exporter.js +67 -35
  381. package/dist/src/formats/pajek/exporter.js.map +1 -1
  382. package/dist/src/formats/pajek/importer.d.ts +84 -22
  383. package/dist/src/formats/pajek/importer.d.ts.map +1 -1
  384. package/dist/src/formats/pajek/importer.js +303 -83
  385. package/dist/src/formats/pajek/importer.js.map +1 -1
  386. package/dist/src/formats/pajek/index.d.ts +2 -1
  387. package/dist/src/formats/pajek/index.d.ts.map +1 -1
  388. package/dist/src/formats/pajek/index.js +2 -1
  389. package/dist/src/formats/pajek/index.js.map +1 -1
  390. package/dist/src/formats/pajek/syntax.d.ts +77 -14
  391. package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
  392. package/dist/src/formats/pajek/syntax.js +186 -54
  393. package/dist/src/formats/pajek/syntax.js.map +1 -1
  394. package/dist/src/formats/xgmml/columns.d.ts +1 -0
  395. package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
  396. package/dist/src/formats/xgmml/columns.js +4 -0
  397. package/dist/src/formats/xgmml/columns.js.map +1 -1
  398. package/dist/src/formats/xgmml/constants.d.ts +186 -56
  399. package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
  400. package/dist/src/formats/xgmml/constants.js +181 -57
  401. package/dist/src/formats/xgmml/constants.js.map +1 -1
  402. package/dist/src/formats/xgmml/document.d.ts +65 -11
  403. package/dist/src/formats/xgmml/document.d.ts.map +1 -1
  404. package/dist/src/formats/xgmml/document.js +157 -28
  405. package/dist/src/formats/xgmml/document.js.map +1 -1
  406. package/dist/src/formats/xgmml/emit.d.ts +61 -3
  407. package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
  408. package/dist/src/formats/xgmml/emit.js +129 -26
  409. package/dist/src/formats/xgmml/emit.js.map +1 -1
  410. package/dist/src/formats/xgmml/exporter.d.ts +13 -6
  411. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
  412. package/dist/src/formats/xgmml/exporter.js +21 -22
  413. package/dist/src/formats/xgmml/exporter.js.map +1 -1
  414. package/dist/src/formats/xgmml/importer.d.ts +33 -12
  415. package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
  416. package/dist/src/formats/xgmml/importer.js +35 -72
  417. package/dist/src/formats/xgmml/importer.js.map +1 -1
  418. package/dist/src/formats/xgmml/index.d.ts +1 -0
  419. package/dist/src/formats/xgmml/index.d.ts.map +1 -1
  420. package/dist/src/formats/xgmml/index.js +1 -0
  421. package/dist/src/formats/xgmml/index.js.map +1 -1
  422. package/dist/src/formats/xgmml/values.d.ts +8 -1
  423. package/dist/src/formats/xgmml/values.d.ts.map +1 -1
  424. package/dist/src/formats/xgmml/values.js +8 -3
  425. package/dist/src/formats/xgmml/values.js.map +1 -1
  426. package/dist/src/index.d.ts +23 -18
  427. package/dist/src/index.d.ts.map +1 -1
  428. package/dist/src/index.js +28 -16
  429. package/dist/src/index.js.map +1 -1
  430. package/dist/src/registry.d.ts +478 -92
  431. package/dist/src/registry.d.ts.map +1 -1
  432. package/dist/src/registry.js +807 -88
  433. package/dist/src/registry.js.map +1 -1
  434. package/dist/src/sniff.d.ts +61 -19
  435. package/dist/src/sniff.d.ts.map +1 -1
  436. package/dist/src/sniff.js +121 -29
  437. package/dist/src/sniff.js.map +1 -1
  438. package/dist/src/types.d.ts +362 -112
  439. package/dist/src/types.d.ts.map +1 -1
  440. package/dist/src/types.js +28 -7
  441. package/dist/src/types.js.map +1 -1
  442. package/dist/xgmml.js +2 -2
  443. package/docs/samples/README.md +25 -0
  444. package/docs/samples/got-edges.csv +353 -0
  445. package/docs/samples/got-network.graphml +1384 -0
  446. package/docs/samples/got-nodes.csv +108 -0
  447. package/docs/samples/got.cx +930 -0
  448. package/docs/samples/got.cx2 +465 -0
  449. package/docs/samples/got.gexf +471 -0
  450. package/docs/samples/got.gml +2650 -0
  451. package/docs/samples/got.json +2546 -0
  452. package/docs/samples/got.net +461 -0
  453. package/docs/samples/movies-nodes.csv +9 -0
  454. package/docs/samples/movies-rels.csv +8 -0
  455. package/docs/samples/networks.cys +0 -0
  456. package/docs/samples/proteins.xgmml +9 -0
  457. package/docs/samples/teams.gv +24 -0
  458. package/docs/samples/vehicles.obo +25 -0
  459. package/package.json +9 -4
  460. package/src/children.ts +18 -6
  461. package/src/common/attributes.ts +29 -11
  462. package/src/common/cell-budget.ts +11 -3
  463. package/src/common/choose.ts +89 -0
  464. package/src/common/codes.ts +490 -66
  465. package/src/common/compare.ts +472 -0
  466. package/src/common/cx-export.ts +432 -0
  467. package/src/common/declared-types.ts +40 -6
  468. package/src/common/direction.ts +78 -51
  469. package/src/common/escape.ts +39 -22
  470. package/src/common/export.ts +284 -47
  471. package/src/common/format.ts +30 -1
  472. package/src/common/ids.ts +14 -3
  473. package/src/common/input.ts +1057 -134
  474. package/src/common/json-elements.ts +1035 -199
  475. package/src/common/line-format.ts +249 -0
  476. package/src/common/lists.ts +7 -2
  477. package/src/common/ontology-export.ts +461 -0
  478. package/src/common/ontology.ts +31 -9
  479. package/src/common/options.ts +110 -15
  480. package/src/common/plural.ts +49 -0
  481. package/src/common/report.ts +168 -25
  482. package/src/common/temporal.ts +22 -5
  483. package/src/common/text.ts +62 -23
  484. package/src/common/weights.ts +74 -4
  485. package/src/common/writer.ts +37 -3
  486. package/src/common/xml.ts +479 -90
  487. package/src/common/zip.ts +169 -7
  488. package/src/formats/csv/exporter.ts +202 -47
  489. package/src/formats/csv/header.ts +50 -11
  490. package/src/formats/csv/importer.ts +1133 -245
  491. package/src/formats/csv/index.ts +106 -19
  492. package/src/formats/csv/records.ts +396 -122
  493. package/src/formats/csv/values.ts +1 -0
  494. package/src/formats/cx/exporter.ts +1483 -0
  495. package/src/formats/cx/importer.ts +890 -302
  496. package/src/formats/cx/index.ts +4 -3
  497. package/src/formats/cx2/exporter.ts +62 -339
  498. package/src/formats/cx2/importer.ts +318 -87
  499. package/src/formats/cx2/index.ts +2 -1
  500. package/src/formats/cys/constants.ts +62 -17
  501. package/src/formats/cys/exporter.ts +1368 -0
  502. package/src/formats/cys/importer.ts +659 -157
  503. package/src/formats/cys/index.ts +4 -2
  504. package/src/formats/cys/session.ts +142 -34
  505. package/src/formats/cys/tables.ts +47 -9
  506. package/src/formats/dot/exporter.ts +83 -37
  507. package/src/formats/dot/importer.ts +248 -54
  508. package/src/formats/dot/index.ts +2 -1
  509. package/src/formats/dot/names.ts +44 -11
  510. package/src/formats/dot/tokenizer.ts +25 -6
  511. package/src/formats/gexf/exporter.ts +57 -18
  512. package/src/formats/gexf/importer.ts +636 -105
  513. package/src/formats/gexf/index.ts +90 -21
  514. package/src/formats/gexf/schema.ts +67 -15
  515. package/src/formats/gml/exporter.ts +90 -36
  516. package/src/formats/gml/importer.ts +388 -67
  517. package/src/formats/gml/index.ts +85 -24
  518. package/src/formats/gml/syntax.ts +113 -28
  519. package/src/formats/graphml/constants.ts +178 -44
  520. package/src/formats/graphml/exporter.ts +52 -18
  521. package/src/formats/graphml/importer.ts +640 -60
  522. package/src/formats/graphml/index.ts +2 -1
  523. package/src/formats/graphml/tree.ts +6 -2
  524. package/src/formats/graphml/yfiles.ts +37 -15
  525. package/src/formats/json/dialect.ts +128 -29
  526. package/src/formats/json/exporter.ts +205 -68
  527. package/src/formats/json/importer.ts +1163 -253
  528. package/src/formats/json/index.ts +3 -1
  529. package/src/formats/json/obographs-export.ts +1197 -0
  530. package/src/formats/json/obographs.ts +353 -92
  531. package/src/formats/neo4j/exporter.ts +321 -102
  532. package/src/formats/neo4j/header.ts +44 -6
  533. package/src/formats/neo4j/importer.ts +512 -73
  534. package/src/formats/neo4j/index.ts +75 -23
  535. package/src/formats/obo/exporter.ts +2210 -0
  536. package/src/formats/obo/importer.ts +622 -109
  537. package/src/formats/obo/index.ts +6 -3
  538. package/src/formats/obo/syntax.ts +232 -37
  539. package/src/formats/pajek/exporter.ts +90 -42
  540. package/src/formats/pajek/importer.ts +407 -93
  541. package/src/formats/pajek/index.ts +2 -1
  542. package/src/formats/pajek/syntax.ts +216 -55
  543. package/src/formats/xgmml/columns.ts +10 -0
  544. package/src/formats/xgmml/constants.ts +183 -56
  545. package/src/formats/xgmml/document.ts +212 -34
  546. package/src/formats/xgmml/emit.ts +175 -34
  547. package/src/formats/xgmml/exporter.ts +36 -28
  548. package/src/formats/xgmml/importer.ts +64 -86
  549. package/src/formats/xgmml/index.ts +1 -0
  550. package/src/formats/xgmml/values.ts +12 -4
  551. package/src/index.ts +81 -11
  552. package/src/registry.ts +1121 -135
  553. package/src/sniff.ts +163 -32
  554. package/src/types.ts +385 -114
  555. package/dist/chunks/escape-B-tkk_Cl.js.map +0 -1
  556. package/dist/chunks/export-Bh60Dv-n.js +0 -582
  557. package/dist/chunks/export-Bh60Dv-n.js.map +0 -1
  558. package/dist/chunks/exporter-BGrsWamJ.js.map +0 -1
  559. package/dist/chunks/exporter-DIeGJXAZ.js.map +0 -1
  560. package/dist/chunks/importer-BWY2FFc5.js.map +0 -1
  561. package/dist/chunks/importer-DD-xv9_Y.js +0 -1471
  562. package/dist/chunks/importer-DD-xv9_Y.js.map +0 -1
  563. package/dist/chunks/importer-DET1tg6s.js.map +0 -1
  564. package/dist/chunks/importer-DEcKhsmQ.js +0 -1614
  565. package/dist/chunks/importer-DEcKhsmQ.js.map +0 -1
  566. package/dist/chunks/importer-DIrbbnAf.js.map +0 -1
  567. package/dist/chunks/importer-D_e7e7LX.js.map +0 -1
  568. package/dist/chunks/importer-eUREhBGj.js.map +0 -1
  569. package/dist/chunks/importer-vi3bSdtw.js.map +0 -1
  570. package/dist/chunks/importer-zGLo8Dg_.js.map +0 -1
  571. package/dist/chunks/json-elements-DDS8N2Dd.js +0 -779
  572. package/dist/chunks/json-elements-DDS8N2Dd.js.map +0 -1
  573. package/dist/chunks/ontology-BnrJ4I98.js +0 -113
  574. package/dist/chunks/ontology-BnrJ4I98.js.map +0 -1
  575. package/dist/chunks/records-dbRkxwaq.js.map +0 -1
  576. package/dist/chunks/report-B1z4WT9e.js +0 -2372
  577. package/dist/chunks/report-B1z4WT9e.js.map +0 -1
  578. package/dist/chunks/text-Dr0Ifpag.js.map +0 -1
  579. package/dist/chunks/weights-Dzba96G3.js +0 -176
  580. package/dist/chunks/weights-Dzba96G3.js.map +0 -1
  581. package/dist/chunks/writer-C7flM-Ih.js.map +0 -1
@@ -28,20 +28,29 @@ import {
28
28
  } from "@graphty/graph-format";
29
29
 
30
30
  import { declareResolved, RENAMED_CODE, ROLE_TAKEN_CODE, uniqueColumnName } from "../../common/attributes.js";
31
+ import { importChosenGraph } from "../../common/choose.js";
31
32
  import {
33
+ AMBIGUOUS_GRAPH_NAME_CODE,
34
+ DUPLICATE_ATTRIBUTE_CODE,
32
35
  DUPLICATE_NODE_CODE,
36
+ ELEMENT_ISSUE,
33
37
  ENCODING_FALLBACK_CODE,
38
+ GRAPH_NOT_FOUND_CODE,
39
+ INPUT_ISSUE,
34
40
  INVALID_ENCODING_CODE,
35
41
  INVALID_UTF8_CODE,
36
42
  MULTIPLE_GRAPHS_CODE,
37
43
  OPTION_IGNORED_CODE,
44
+ PRECISION_CODE,
38
45
  SYNTAX_CODE,
39
46
  UNKNOWN_ENCODING_CODE,
40
47
  } from "../../common/codes.js";
41
48
  import { DirectionResolver, type EdgeKind } from "../../common/direction.js";
49
+ import { survivesF32 } from "../../common/format.js";
42
50
  import { coerceIdText, ID_MERGED_CODE, IdCoercer } from "../../common/ids.js";
43
51
  import { LineReader, throwIfAborted } from "../../common/input.js";
44
52
  import {
53
+ graphChosen,
45
54
  type ImportFormatDefaults,
46
55
  reportSinkOptions,
47
56
  reportUnusedOptions,
@@ -49,14 +58,22 @@ import {
49
58
  resolveImportOptions,
50
59
  SINK_OPTION_CODE,
51
60
  } from "../../common/options.js";
61
+ import { agree, plural } from "../../common/plural.js";
52
62
  import { ImportReportBuilder } from "../../common/report.js";
53
63
  import { isNumericText, TextCellWriter, WIDENING_UNSUPPORTED_CODE } from "../../common/text.js";
54
64
  import { isWeightField, parseWeightText } from "../../common/weights.js";
55
- import { type CommonImportOptions, type GraphImporter, type ImportInput, type ImportReport } from "../../types.js";
65
+ import {
66
+ type CommonImportOptions,
67
+ type GraphChoiceOptions,
68
+ type GraphImporter,
69
+ type ImportInput,
70
+ type ImportReport,
71
+ } from "../../types.js";
56
72
  import {
57
73
  decodeCharacterReferences,
58
74
  isCommentOrBlank,
59
75
  isIntervalToken,
76
+ isProjectObject,
60
77
  isSectionLine,
61
78
  isShapeKeyword,
62
79
  isVertexNumber,
@@ -71,43 +88,66 @@ import {
71
88
  SHAPE_COLUMN,
72
89
  SPELLS_COLUMN,
73
90
  tokenize,
91
+ type TokenNotes,
74
92
  VALUE_COLUMN,
75
93
  VALUE_FIELD,
76
94
  VECTOR_COLUMN,
77
95
  } from "./syntax.js";
78
96
 
79
- /** The format-specific options of the Pajek importer. */
80
- export interface PajekImportOptions {
97
+ /**
98
+ * The format-specific options of the Pajek importer.
99
+ * @category Built-in formats
100
+ */
101
+ export interface PajekImportOptions extends GraphChoiceOptions, CommonImportOptions {
81
102
  /**
82
103
  * The number of the first vertex: 1 (Pajek's rule), 0 (files written by zero-based scripts), or
83
- * "auto" (default): 0 when the first vertex line is numbered 0, 1 otherwise.
104
+ * "auto": 0 when the first vertex line is numbered 0, 1 otherwise.
105
+ * @defaultValue "auto"
84
106
  */
85
107
  firstVertex?: 0 | 1 | "auto" | undefined;
86
108
  }
87
109
 
88
- /** Issue codes of the Pajek importer. */
110
+ /**
111
+ * Issue codes of the Pajek importer.
112
+ * @category Built-in formats
113
+ */
89
114
  export const PAJEK_ISSUE = Object.freeze({
90
- /** The input holds invalid UTF-8 (fatal). */
115
+ ...INPUT_ISSUE,
116
+ ...ELEMENT_ISSUE,
117
+ /** The input is not valid UTF-8. The import stops. */
91
118
  INVALID_UTF8: INVALID_UTF8_CODE,
92
- /** Invalid bytes in the encoding a BOM, a declaration or the encoding option chose (fatal). */
119
+ /**
120
+ * Some bytes are not valid in the encoding that was chosen (by a byte order mark, the file's declaration or the
121
+ * `encoding` option). The import stops.
122
+ */
93
123
  INVALID_ENCODING: INVALID_ENCODING_CODE,
94
124
  /** Bytes that are not UTF-8 and declare no encoding were read as windows-1252. */
95
125
  ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
96
126
  /** A declared encoding the platform cannot decode was ignored. */
97
127
  UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
98
- /** Fatal: no `*Vertices` section (an empty file, or not a Pajek network). */
128
+ /** There is no `*Vertices` section: the file is empty or not a Pajek network. The import stops. */
99
129
  NO_VERTICES: "E_PAJEK_NO_VERTICES",
100
- /** Fatal: `*Vertices` without a vertex count, one the sink cannot hold, or a first-mode count outside 0..N. */
130
+ /**
131
+ * `*Vertices` without a vertex count, with a count too large to hold, or with a first-mode count outside 0 to N;
132
+ * the import stops.
133
+ */
101
134
  VERTICES_COUNT: "E_PAJEK_VERTICES_COUNT",
102
- /** A project file holds several networks; import() reads the first, importAll() reads every one. */
135
+ /**
136
+ * The file holds several graphs and only the first was read. It is not added when `graphIndex` or `graphName` chose the graph.
137
+ * `importAllGraphs()` reads every one.
138
+ */
103
139
  MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
140
+ /** `graphIndex` or `graphName` names no graph of the file; the message lists the graphs it holds. The import stops. */
141
+ GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
142
+ /** `graphName` matches more than one graph; pass `graphIndex`. The import stops. */
143
+ AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE,
104
144
  /** A data line before the first section header. */
105
145
  OUTSIDE_SECTION: "E_PAJEK_OUTSIDE_SECTION",
106
146
  /** A section header the importer cannot parse. */
107
147
  SYNTAX: SYNTAX_CODE,
108
148
  /** A double quote not closed before the end of the line. */
109
149
  UNTERMINATED_QUOTE: "E_PAJEK_UNTERMINATED_QUOTE",
110
- /** A vertex line the grammar does not accept. */
150
+ /** A vertex line that is not in Pajek's vertex syntax; the vertex is skipped. */
111
151
  VERTEX_LINE: "E_PAJEK_VERTEX_LINE",
112
152
  /** A vertex number outside the declared range. */
113
153
  VERTEX_RANGE: "E_PAJEK_VERTEX_RANGE",
@@ -115,9 +155,12 @@ export const PAJEK_ISSUE = Object.freeze({
115
155
  VERTEX_COUNT: "W_PAJEK_VERTEX_COUNT",
116
156
  /** A second line for the same vertex; the later values overwrite. */
117
157
  DUPLICATE_NODE: DUPLICATE_NODE_CODE,
118
- /** A line (arc, edge, list, matrix row, partition or vector value) the grammar does not accept. */
158
+ /**
159
+ * A line that is not in Pajek's syntax for its section (an arc, edge, list, matrix row, partition or vector
160
+ * value); it is skipped.
161
+ */
119
162
  LINE: "E_PAJEK_LINE",
120
- /** A line endpoint outside the declared vertex range (the core's code, forwarded). */
163
+ /** An edge names a vertex number outside the range `*Vertices` declared; the edge is skipped. */
121
164
  UNKNOWN_NODE: "E_UNKNOWN_NODE",
122
165
  /** A malformed time interval token. */
123
166
  INTERVAL: "E_PAJEK_INTERVAL",
@@ -129,32 +172,57 @@ export const PAJEK_ISSUE = Object.freeze({
129
172
  OBJECT_COUNT: "E_PAJEK_OBJECT_COUNT",
130
173
  /** A project-file section (`*Events`, `*Permutation`, ...) the importer does not read; its lines are skipped. */
131
174
  UNSUPPORTED_SECTION: "W_PAJEK_UNSUPPORTED_SECTION",
132
- /** Tokens after a section header the grammar does not account for. */
175
+ /** A section header has extra words Pajek does not define; they are ignored. */
133
176
  HEADER_EXTRA: "W_PAJEK_HEADER_EXTRA",
134
177
  /** The file declares vertices but no line section. */
135
178
  NO_LINES: "W_PAJEK_NO_LINES",
136
- /** Vertex numbering starts at 0 rather than 1. */
179
+ /** Vertex numbering starts at 0 rather than 1, found by `firstVertex: "auto"` (passing `firstVertex: 0` says so and gives no warning). */
137
180
  ZERO_BASED: "W_PAJEK_ZERO_BASED",
138
181
  /** Vertex lines mix two and three coordinates. */
139
182
  COORD_DIMS: "W_PAJEK_COORD_DIMS",
140
183
  /** Two vertices share a label under nodeIdFrom "label" and became one node. */
141
184
  LABEL_MERGED: "W_PAJEK_LABEL_MERGED",
142
- /** Two distinct label texts became one numeric id under ids "number". */
185
+ /** Two different labels became the same number because `ids` is "number", so their vertices were merged. */
143
186
  ID_MERGED: ID_MERGED_CODE,
144
- /** A parameter column of `2.0`-style text kept at the value-inferred dtype (the sink cannot widen). */
187
+ /**
188
+ * Your graph builder cannot change an attribute's type after its first value, so a text column keeps the type of
189
+ * its first values.
190
+ */
145
191
  WIDENING_UNSUPPORTED: WIDENING_UNSUPPORTED_CODE,
146
- /** A structural column (label, position, shape, spells, relation) renamed `<name>#<id>` because the name was taken (design section 5.6). */
192
+ /** A structural column (label, position, shape, spells, relation) renamed `<name>#<id>` because the name was taken. */
147
193
  COLUMN_RENAMED: RENAMED_CODE,
148
- /** A structural column declared without its role because the sink already holds it. */
194
+ /**
195
+ * You read into a graph builder that already has an id, label or position attribute, so this file's one is kept as
196
+ * a plain attribute.
197
+ */
149
198
  ROLE_TAKEN: ROLE_TAKEN_CODE,
150
199
  /** Two vertices carry the same `graphty_originalId` under restoreMangledIds and became one node. */
151
200
  ORIGINAL_ID_MERGED: "W_PAJEK_ORIGINAL_ID_MERGED",
152
201
  /** A vertex line's `graphty_originalId` came after a later vertex's line had created it under its number. */
153
202
  ORIGINAL_ID_UNRESTORED: "W_PAJEK_ORIGINAL_ID_UNRESTORED",
154
- /** A common option the importer has no use for (weightFrom naming a parameter and restoreMangledIds are honoured; long, hyperedges are not). */
203
+ /** You set an option this format does not use; it had no effect. The message names the option. */
155
204
  OPTION_IGNORED: OPTION_IGNORED_CODE,
156
- /** A builder-policy option the caller passed that the caller's sink does not use (the shared W_SINK_OPTION). */
205
+ /**
206
+ * You read into your own graph builder, which was created with a different `addMissingNodes`, `duplicateEdges`,
207
+ * `selfLoops` or `weightDtype` than the option you passed; the builder's setting applies.
208
+ */
157
209
  SINK_OPTION: SINK_OPTION_CODE,
210
+ /** A double quote inside a token (a CSV-style doubled quote, a quote mid-word): removed and the parts joined. */
211
+ QUOTE_IN_TOKEN: "W_PAJEK_QUOTE_IN_TOKEN",
212
+ /** The same parameter twice on one line; the later value stands. */
213
+ DUPLICATE_ATTRIBUTE: DUPLICATE_ATTRIBUTE_CODE,
214
+ /** A character reference beyond U+10FFFF in a label; kept as written. */
215
+ REFERENCE_RANGE: "W_PAJEK_REFERENCE_RANGE",
216
+ /** A line of a two-mode network whose endpoints are both in one mode. */
217
+ TWO_MODE_LINE: "W_PAJEK_TWO_MODE_LINE",
218
+ /** A bare non-integer number read as a vertex label before two coordinates (`1 0.1 0.2 0.3`); it may be an x y z line without a label. */
219
+ NUMERIC_LABEL: "W_PAJEK_NUMERIC_LABEL",
220
+ /** A relation number given a second name by a later `*Arcs :k "name"` header. */
221
+ RELATION_RENAMED: "W_PAJEK_RELATION_RENAMED",
222
+ /** A negative vertex number in an adjacency list, read as its absolute value. */
223
+ NEGATIVE_LIST_ENTRY: "W_PAJEK_NEGATIVE_LIST_ENTRY",
224
+ /** An integer beyond 2^53 was stored as the nearest 64-bit float; pass `long: "string"` to keep every digit. */
225
+ PRECISION: PRECISION_CODE,
158
226
  });
159
227
 
160
228
  /** The common options the Pajek importer reads (the rest is reported by reportUnusedOptions). */
@@ -181,6 +249,10 @@ const ABORT_CHECK_INTERVAL = 64;
181
249
 
182
250
  const FIRST_VERTEX_VALUES: ReadonlySet<unknown> = new Set([0, 1, "auto"]);
183
251
 
252
+ /** A numeral that is not a finite number (`1e999`, `NaN`, `Infinity`): never a parameter key. */
253
+ const NUMERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
254
+ const NON_FINITE_WORD = /^[+-]?(?:nan|inf|infinity)$/i;
255
+
184
256
  const LABEL_DECL: ColumnDecl = {
185
257
  name: LABEL_COLUMN,
186
258
  dtype: "string",
@@ -327,6 +399,8 @@ class PajekParser {
327
399
 
328
400
  /** `*Vertices N`; -1 before the header. */
329
401
  private vertexCount = -1;
402
+ /** The line of the `*Vertices` header. */
403
+ private verticesLine = 0;
330
404
 
331
405
  /** The first-mode count of a two-mode network, or null. */
332
406
  private firstMode: number | null = null;
@@ -358,6 +432,12 @@ class PajekParser {
358
432
  /** Set when the previous line was a project-object header, whose `*Vertices N` line comes next. */
359
433
  private objectHeader = false;
360
434
 
435
+ /** The first unsupported section keyword, for the message of a file without `*Vertices`. */
436
+ private firstUnsupported: string | null = null;
437
+
438
+ /** Vertex lines skipped because their number is outside the declared range (not lines of declared vertices). */
439
+ private outOfRange = 0;
440
+
361
441
  /** Node index per vertex position (vertex number minus base). */
362
442
  private indexOfPos: U32 = new Uint32Array(0);
363
443
 
@@ -462,7 +542,7 @@ class PajekParser {
462
542
  this.matrixLine(text, line);
463
543
  return false;
464
544
  case "object":
465
- this.objectLine(text);
545
+ this.objectLine(text, line);
466
546
  return false;
467
547
  case "skip":
468
548
  return false;
@@ -487,7 +567,11 @@ class PajekParser {
487
567
  */
488
568
  finish(): void {
489
569
  if (this.vertexCount < 0) {
490
- this.report.fail(PAJEK_ISSUE.NO_VERTICES, "not a Pajek network: no *Vertices section found");
570
+ const seen =
571
+ this.firstUnsupported === null
572
+ ? ""
573
+ : ` (the file has the unrecognised section *${this.firstUnsupported})`;
574
+ this.report.fail(PAJEK_ISSUE.NO_VERTICES, `not a Pajek network: no *Vertices section found${seen}`);
491
575
  }
492
576
  this.endSection();
493
577
  this.finishVertices();
@@ -507,7 +591,7 @@ class PajekParser {
507
591
  this.report.warning(
508
592
  "coercion",
509
593
  PAJEK_ISSUE.ID_MERGED,
510
- `${this.coercer.mergeCount} label(s) merged into an id another label already produced under ids "number"`,
594
+ `${this.coercer.mergeCount} label${plural(this.coercer.mergeCount)} merged into an id another label already produced under ids "number"`,
511
595
  );
512
596
  }
513
597
  const pajek = {
@@ -560,7 +644,7 @@ class PajekParser {
560
644
  this.report.error(
561
645
  "validation-error",
562
646
  PAJEK_ISSUE.OBJECT_COUNT,
563
- `*${partition ? "Partition" : "Vector"} "${object.name}" declares ${declared} vertices and holds ${object.values.length} value(s); the network has ${this.vertexCount}`,
647
+ `*${partition ? "Partition" : "Vector"} "${object.name}" declares ${declared} vertices and holds ${object.values.length} value${plural(object.values.length)}; the network has ${this.vertexCount}`,
564
648
  where,
565
649
  );
566
650
  }
@@ -589,7 +673,7 @@ class PajekParser {
589
673
  this.report.warning(
590
674
  "validation-error",
591
675
  PAJEK_ISSUE.HEADER_EXTRA,
592
- `ignored ${h.extra.length} unexpected token(s) after *${h.keyword}: ${h.extra.join(" ")}`,
676
+ `ignored ${h.extra.length} unexpected token${plural(h.extra.length)} after *${h.keyword}: ${h.extra.join(" ")}`,
593
677
  { line },
594
678
  );
595
679
  }
@@ -639,7 +723,9 @@ class PajekParser {
639
723
  return;
640
724
  case "unsupported":
641
725
  this.section = "skip";
642
- this.objectHeader = true;
726
+ // only a project object's next line is its own `*Vertices N`
727
+ this.objectHeader = isProjectObject(h.keyword);
728
+ this.firstUnsupported ??= h.keyword;
643
729
  this.report.warning(
644
730
  "unsupported",
645
731
  PAJEK_ISSUE.UNSUPPORTED_SECTION,
@@ -674,8 +760,9 @@ class PajekParser {
674
760
  );
675
761
  }
676
762
  try {
677
- // the declared count must be reservable (the sink is the authority on what it can hold,
678
- // E_TOO_LARGE beyond MAX_COUNT) before any vertex is materialised
763
+ // the declared count must be reservable (the sink is the authority on what it can hold;
764
+ // the builder refuses more than its id map holds with E_TOO_LARGE) before any vertex
765
+ // or per-vertex table is allocated
679
766
  this.sink.reserve(h.count);
680
767
  } catch (err) {
681
768
  const reason = err instanceof Error ? err.message : String(err);
@@ -688,6 +775,7 @@ class PajekParser {
688
775
  );
689
776
  }
690
777
  this.vertexCount = h.count;
778
+ this.verticesLine = line;
691
779
  if (h.secondCount !== null) {
692
780
  this.firstMode = h.secondCount;
693
781
  }
@@ -729,7 +817,17 @@ class PajekParser {
729
817
  this.kind = kind;
730
818
  this.relation = h.relation === null ? null : (h.name ?? String(h.relation));
731
819
  if (h.relation !== null) {
732
- this.relationNames.set(h.relation, this.relation ?? String(h.relation));
820
+ const name = this.relation ?? String(h.relation);
821
+ const before = this.relationNames.get(h.relation);
822
+ if (before !== undefined && before !== name) {
823
+ this.report.warning(
824
+ "validation-error",
825
+ PAJEK_ISSUE.RELATION_RENAMED,
826
+ `relation ${h.relation} was named ${JSON.stringify(before)} and is now named ${JSON.stringify(name)}; its lines carry the name of their own section`,
827
+ { line, element: String(h.relation) },
828
+ );
829
+ }
830
+ this.relationNames.set(h.relation, name);
733
831
  }
734
832
  }
735
833
 
@@ -742,7 +840,7 @@ class PajekParser {
742
840
  this.report.error(
743
841
  "validation-error",
744
842
  PAJEK_ISSUE.MATRIX_ROWS,
745
- `*Matrix has ${this.matrixRows} row(s); *Vertices declares ${rows}`,
843
+ `*Matrix has ${this.matrixRows} row${plural(this.matrixRows)}; *Vertices declares ${rows}`,
746
844
  );
747
845
  }
748
846
  this.section = "none";
@@ -759,7 +857,8 @@ class PajekParser {
759
857
  private setBase(base: 0 | 1, line: number): void {
760
858
  this.base = base;
761
859
  this.baseKnown = true;
762
- if (base === 0) {
860
+ // the caller who passed firstVertex: 0 knows the numbering; the warning is for a guess
861
+ if (base === 0 && this.firstVertex === "auto") {
763
862
  this.report.warning(
764
863
  "coercion",
765
864
  PAJEK_ISSUE.ZERO_BASED,
@@ -780,13 +879,34 @@ class PajekParser {
780
879
  }
781
880
  this.nodesCreated = true;
782
881
  const n = this.vertexCount;
783
- const { sink } = this;
784
882
  for (let pos = 0; pos < n; pos++) {
785
- this.indexOfPos[pos] = sink.addNode(this.idOfNumber(pos + this.base));
883
+ this.indexOfPos[pos] = this.addNode(this.idOfNumber(pos + this.base));
786
884
  }
787
885
  this.report.counts.nodes += n;
788
886
  }
789
887
 
888
+ /**
889
+ * Add a vertex to the sink. A sink that cannot hold the declared vertex set (the builder's
890
+ * E_TOO_LARGE id map limit) makes the file unreadable, so its refusal is fatal; any other
891
+ * error passes to the caller.
892
+ * @param id - the vertex id
893
+ * @returns the node index
894
+ */
895
+ private addNode(id: NodeId): number {
896
+ try {
897
+ return this.sink.addNode(id);
898
+ } catch (err) {
899
+ if (!(err instanceof GraphFormatError) || err.code !== "E_TOO_LARGE") {
900
+ throw err;
901
+ }
902
+ return this.report.fail(
903
+ PAJEK_ISSUE.VERTICES_COUNT,
904
+ `*Vertices ${this.vertexCount}: the sink cannot hold that many (${err.message})`,
905
+ { line: this.verticesLine },
906
+ );
907
+ }
908
+ }
909
+
790
910
  /**
791
911
  * The id of a vertex number under nodeIdFrom "id" (the number coerced by the ids rule) or
792
912
  * "index" (the position).
@@ -844,12 +964,12 @@ class PajekParser {
844
964
  }
845
965
  }
846
966
  // a partial list: fewer lines than declared, counting the lines skipped after an error
847
- const lines = this.seenCount + this.report.counts.skippedNodes;
967
+ const lines = this.seenCount + this.report.counts.skippedNodes - this.outOfRange;
848
968
  if (this.seenCount > 0 && lines < n) {
849
969
  this.report.warning(
850
970
  "validation-error",
851
971
  PAJEK_ISSUE.VERTEX_COUNT,
852
- `*Vertices declares ${n} vertices but ${lines} vertex line(s) were read; the others have no label`,
972
+ `*Vertices declares ${n} vertices but ${lines} vertex line${plural(lines)} ${agree(lines, "was", "were")} read; the others have no label`,
853
973
  );
854
974
  }
855
975
  }
@@ -860,7 +980,7 @@ class PajekParser {
860
980
  * @param line - the line number
861
981
  */
862
982
  private vertexLine(text: string, line: number): void {
863
- const tokens = this.tokens(text, "node");
983
+ const tokens = this.tokens(text, "node", line);
864
984
  if (!isVertexNumber(tokens[0])) {
865
985
  this.report.counts.skippedNodes++;
866
986
  throw new LineError(
@@ -876,6 +996,7 @@ class PajekParser {
876
996
  const pos = k - this.base;
877
997
  if (pos < 0 || pos >= this.vertexCount) {
878
998
  this.report.counts.skippedNodes++;
999
+ this.outOfRange++;
879
1000
  throw new LineError(
880
1001
  "validation-error",
881
1002
  PAJEK_ISSUE.VERTEX_RANGE,
@@ -886,7 +1007,7 @@ class PajekParser {
886
1007
  let i = 1;
887
1008
  let label: string | null = null;
888
1009
  if (tokens.length > 1 && !startsWithCoordinates(text, tokens)) {
889
- label = decodeCharacterReferences(tokens[1]);
1010
+ label = this.labelOf(tokens[1], k, line);
890
1011
  i = 2;
891
1012
  }
892
1013
  let x = 0;
@@ -911,12 +1032,13 @@ class PajekParser {
911
1032
  `vertex ${k}: a single coordinate "${tokens[i]}"; coordinates are x y [z]`,
912
1033
  );
913
1034
  }
1035
+ this.checkCoordinates(text, tokens, i, dims === 2 && label !== null, k, line);
914
1036
  let shape: string | null = null;
915
1037
  if (i < tokens.length && isShapeKeyword(tokens[i])) {
916
1038
  shape = tokens[i].toLowerCase();
917
1039
  i++;
918
1040
  }
919
- const extras = this.extras(tokens, i, "node");
1041
+ const extras = this.extras(tokens, i, "node", line);
920
1042
  const restored = this.restoredId(extras);
921
1043
  this.createNodes();
922
1044
  // write
@@ -970,18 +1092,7 @@ class PajekParser {
970
1092
  sink.setNodeValue(this.labelHandle, index, label);
971
1093
  }
972
1094
  if (dims > 0) {
973
- if (this.positionHandle === INVALID_INDEX) {
974
- this.coordDims = dims;
975
- this.positionHandle = declareResolved(sink, "node", positionDecl(dims), this.report, { line }).handle;
976
- } else if (dims !== this.coordDims) {
977
- this.report.warnOnce(
978
- "validation-error",
979
- PAJEK_ISSUE.COORD_DIMS,
980
- `vertex lines mix ${this.coordDims} and ${dims} coordinates; the position column records sourceDims ${this.coordDims}`,
981
- { line, element: String(k) },
982
- );
983
- }
984
- sink.setNodeValue(this.positionHandle, index, [x, y, z]);
1095
+ this.writePosition(index, dims, [x, y, z], k, line);
985
1096
  }
986
1097
  if (shape !== null) {
987
1098
  if (this.shapeHandle === INVALID_INDEX && this.nodeParams.has(SHAPE_COLUMN)) {
@@ -1005,6 +1116,104 @@ class PajekParser {
1005
1116
  }
1006
1117
  }
1007
1118
 
1119
+ /**
1120
+ * A vertex label as written, its character references decoded.
1121
+ * @param token - the label token
1122
+ * @param k - the vertex number
1123
+ * @param line - the line number
1124
+ * @returns the label
1125
+ */
1126
+ private labelOf(token: string, k: number, line: number): string {
1127
+ return decodeCharacterReferences(token, (reference) => {
1128
+ this.report.warnOnce(
1129
+ "validation-error",
1130
+ PAJEK_ISSUE.REFERENCE_RANGE,
1131
+ `character reference ${reference} is beyond U+10FFFF; it is kept as written`,
1132
+ { line, element: String(k) },
1133
+ `${PAJEK_ISSUE.REFERENCE_RANGE}:${reference}`,
1134
+ );
1135
+ });
1136
+ }
1137
+
1138
+ /**
1139
+ * Check the token after a vertex line's coordinates (or where they would stand): a numeral the
1140
+ * number range cannot hold is a broken coordinate, never a parameter named "1e999" or "NaN";
1141
+ * and a bare non-integer label before two coordinates may be the x of an x y z line.
1142
+ * @param text - the line
1143
+ * @param tokens - its tokens
1144
+ * @param i - the index after the coordinates
1145
+ * @param labelAndXy - whether the line was read as a label and two coordinates
1146
+ * @param k - the vertex number
1147
+ * @param line - the line number
1148
+ */
1149
+ private checkCoordinates(
1150
+ text: string,
1151
+ tokens: readonly string[],
1152
+ i: number,
1153
+ labelAndXy: boolean,
1154
+ k: number,
1155
+ line: number,
1156
+ ): void {
1157
+ if (
1158
+ i < tokens.length &&
1159
+ (NUMERAL.test(tokens[i]) || NON_FINITE_WORD.test(tokens[i])) &&
1160
+ !isNumericText(tokens[i])
1161
+ ) {
1162
+ this.report.counts.skippedNodes++;
1163
+ throw new LineError(
1164
+ "parse-error",
1165
+ PAJEK_ISSUE.VERTEX_LINE,
1166
+ `vertex ${k}: coordinate "${tokens[i]}" is not a finite number`,
1167
+ );
1168
+ }
1169
+ const label = tokens[1];
1170
+ if (
1171
+ labelAndXy &&
1172
+ isNumericText(label) &&
1173
+ !Number.isInteger(Number(label)) &&
1174
+ !quotedAfterNumber(text, tokens)
1175
+ ) {
1176
+ this.report.warnOnce(
1177
+ "validation-error",
1178
+ PAJEK_ISSUE.NUMERIC_LABEL,
1179
+ `vertex ${k}: "${label} ${tokens[2]} ${tokens[3]}" is read as the label ${label} and x y; quote the label, or it may be meant as x y z`,
1180
+ { line, element: String(k) },
1181
+ );
1182
+ }
1183
+ }
1184
+
1185
+ /**
1186
+ * Write a vertex's coordinates into the position column (f32 x 3), declared on first use.
1187
+ * @param index - the node index
1188
+ * @param dims - 2 or 3, the coordinates the line gave
1189
+ * @param point - x, y, z (0 when absent)
1190
+ * @param k - the vertex number
1191
+ * @param line - the line number
1192
+ */
1193
+ private writePosition(index: number, dims: number, point: readonly number[], k: number, line: number): void {
1194
+ if (this.positionHandle === INVALID_INDEX) {
1195
+ this.coordDims = dims;
1196
+ this.positionHandle = declareResolved(this.sink, "node", positionDecl(dims), this.report, { line }).handle;
1197
+ } else if (dims !== this.coordDims) {
1198
+ this.report.warnOnce(
1199
+ "validation-error",
1200
+ PAJEK_ISSUE.COORD_DIMS,
1201
+ `vertex lines mix ${this.coordDims} and ${dims} coordinates; the position column records sourceDims ${this.coordDims}`,
1202
+ { line, element: String(k) },
1203
+ );
1204
+ }
1205
+ if (!point.every(survivesF32)) {
1206
+ this.report.warnOnce(
1207
+ "precision",
1208
+ PAJEK_ISSUE.PRECISION,
1209
+ `vertex ${k} has a coordinate the f32 position column rounds (warned once)`,
1210
+ { line, element: String(k) },
1211
+ `${PAJEK_ISSUE.PRECISION}:position`,
1212
+ );
1213
+ }
1214
+ this.sink.setNodeValue(this.positionHandle, index, point);
1215
+ }
1216
+
1008
1217
  /**
1009
1218
  * Whether a `graphty_originalId` parameter is restored as the vertex id: restoreMangledIds
1010
1219
  * under nodeIdFrom "id" (under "label" / "index" the parameter stays an ordinary column).
@@ -1023,7 +1232,7 @@ class PajekParser {
1023
1232
  private numberedNode(ids: NodeId[], pos: number): void {
1024
1233
  const id = this.idOfNumber(pos + this.base);
1025
1234
  ids[pos] = id;
1026
- this.indexOfPos[pos] = this.sink.addNode(id);
1235
+ this.indexOfPos[pos] = this.addNode(id);
1027
1236
  this.report.counts.nodes++;
1028
1237
  }
1029
1238
 
@@ -1069,7 +1278,7 @@ class PajekParser {
1069
1278
  ): number {
1070
1279
  const id = text === null ? this.idOfNumber(k) : this.coercer.text(text);
1071
1280
  const before = this.sink.indexOf(id);
1072
- const index = this.sink.addNode(id);
1281
+ const index = this.addNode(id);
1073
1282
  ids[pos] = id;
1074
1283
  this.indexOfPos[pos] = index;
1075
1284
  this.report.counts.nodes++;
@@ -1094,7 +1303,7 @@ class PajekParser {
1094
1303
  * @param line - the line number
1095
1304
  */
1096
1305
  private edgeLine(text: string, line: number): void {
1097
- let tokens = this.tokens(text, "edge");
1306
+ let tokens = this.tokens(text, "edge", line);
1098
1307
  // a `k:` prefix puts this one line in relation k
1099
1308
  let { relation } = this;
1100
1309
  const prefix = RELATION_PREFIX.exec(tokens[0]);
@@ -1119,7 +1328,7 @@ class PajekParser {
1119
1328
  valueText = tokens[i];
1120
1329
  i++;
1121
1330
  }
1122
- const extras = this.extras(tokens, i, "edge");
1331
+ const extras = this.extras(tokens, i, "edge", line);
1123
1332
  const { weightFrom } = this.options;
1124
1333
  let weight: number | undefined;
1125
1334
  let valueIsWeight = false;
@@ -1151,7 +1360,7 @@ class PajekParser {
1151
1360
  * @param line - the line number
1152
1361
  */
1153
1362
  private listLine(text: string, line: number): void {
1154
- const tokens = this.tokens(text, "edge");
1363
+ const tokens = this.tokens(text, "edge", line);
1155
1364
  for (const token of tokens) {
1156
1365
  if (!LIST_VERTEX.test(token)) {
1157
1366
  this.report.counts.skippedEdges++;
@@ -1162,6 +1371,14 @@ class PajekParser {
1162
1371
  );
1163
1372
  }
1164
1373
  }
1374
+ if (tokens.some((token) => token.startsWith("-"))) {
1375
+ this.report.warnOnce(
1376
+ "coercion",
1377
+ PAJEK_ISSUE.NEGATIVE_LIST_ENTRY,
1378
+ "an adjacency list holds a negative vertex number; it is read as its absolute value and the sign is dropped",
1379
+ { line },
1380
+ );
1381
+ }
1165
1382
  const u = this.endpoint(Math.abs(Number(tokens[0])));
1166
1383
  for (let i = 1; i < tokens.length; i++) {
1167
1384
  const v = this.endpoint(Math.abs(Number(tokens[i])));
@@ -1178,7 +1395,7 @@ class PajekParser {
1178
1395
  * @param line - the line number
1179
1396
  */
1180
1397
  private matrixLine(text: string, line: number): void {
1181
- const tokens = this.tokens(text, "edge");
1398
+ const tokens = this.tokens(text, "edge", line);
1182
1399
  const row = this.matrixRows;
1183
1400
  this.matrixRows++;
1184
1401
  const offset = this.firstMode ?? 0;
@@ -1188,14 +1405,14 @@ class PajekParser {
1188
1405
  throw new LineError(
1189
1406
  "validation-error",
1190
1407
  PAJEK_ISSUE.MATRIX_ROWS,
1191
- `*Matrix row ${row + 1} is beyond the ${rows} declared row(s)`,
1408
+ `*Matrix row ${row + 1} is beyond the ${rows} declared row${plural(rows)}`,
1192
1409
  );
1193
1410
  }
1194
1411
  if (tokens.length < columns) {
1195
1412
  throw new LineError(
1196
1413
  "parse-error",
1197
1414
  PAJEK_ISSUE.LINE,
1198
- `*Matrix row ${row + 1} has ${tokens.length} value(s); the matrix has ${columns} column(s)`,
1415
+ `*Matrix row ${row + 1} has ${tokens.length} value${plural(tokens.length)}; the matrix has ${columns} column${plural(columns)}`,
1199
1416
  );
1200
1417
  }
1201
1418
  for (let j = 0; j < columns; j++) {
@@ -1211,7 +1428,7 @@ class PajekParser {
1211
1428
  this.report.warnOnce(
1212
1429
  "validation-error",
1213
1430
  PAJEK_ISSUE.MATRIX_EXTRA,
1214
- `*Matrix row ${row + 1} has ${tokens.length} value(s) for ${columns} column(s); the extra values are ignored`,
1431
+ `*Matrix row ${row + 1} has ${tokens.length} value${plural(tokens.length)} for ${columns} column${plural(columns)}; the extra values are ignored`,
1215
1432
  { line },
1216
1433
  );
1217
1434
  }
@@ -1229,10 +1446,11 @@ class PajekParser {
1229
1446
  * A value line of a `*Partition` (integers) or `*Vector` (numbers): the values of the next
1230
1447
  * vertices, usually one per line.
1231
1448
  * @param text - the line
1449
+ * @param line - the line number
1232
1450
  */
1233
- private objectLine(text: string): void {
1451
+ private objectLine(text: string, line: number): void {
1234
1452
  const object = this.objects[this.objects.length - 1];
1235
- const tokens = this.tokens(text, "node");
1453
+ const tokens = this.tokens(text, "node", line);
1236
1454
  for (const token of tokens) {
1237
1455
  if (!isNumericText(token) || (object.kind === "partition" && !isI32(Number(token)))) {
1238
1456
  // keep the later values on their vertices
@@ -1263,6 +1481,14 @@ class PajekParser {
1263
1481
  this.directionSet = true;
1264
1482
  this.resolver.setHeader(this.kind === "directed", { line });
1265
1483
  }
1484
+ if (this.firstMode !== null && u < this.firstMode === v < this.firstMode) {
1485
+ this.report.warnOnce(
1486
+ "validation-error",
1487
+ PAJEK_ISSUE.TWO_MODE_LINE,
1488
+ `a line of the two-mode network joins two vertices of the ${u < this.firstMode ? "first" : "second"} mode`,
1489
+ { line },
1490
+ );
1491
+ }
1266
1492
  const before = sink.edgeCount;
1267
1493
  const e = this.resolver.addEdge(this.idAt(u), this.idAt(v), this.kind, weight, { line });
1268
1494
  this.report.counts.edges += sink.edgeCount - before;
@@ -1331,7 +1557,7 @@ class PajekParser {
1331
1557
  */
1332
1558
  private weightOf(text: string): number | undefined {
1333
1559
  try {
1334
- return parseWeightText(text);
1560
+ return parseWeightText(text, this.report);
1335
1561
  } catch (err) {
1336
1562
  this.report.counts.skippedEdges++;
1337
1563
  throw err;
@@ -1344,10 +1570,20 @@ class PajekParser {
1344
1570
  * Tokenize a data line.
1345
1571
  * @param text - the line
1346
1572
  * @param domain - what is skipped on an unterminated quote
1573
+ * @param line - the line number
1347
1574
  * @returns the tokens (at least one: blank lines never reach here)
1348
1575
  */
1349
- private tokens(text: string, domain: "node" | "edge"): string[] {
1350
- const tokens = tokenize(text);
1576
+ private tokens(text: string, domain: "node" | "edge", line: number): string[] {
1577
+ const notes: TokenNotes = { oddQuote: false };
1578
+ const tokens = tokenize(text, notes);
1579
+ if (notes.oddQuote) {
1580
+ this.report.warnOnce(
1581
+ "coercion",
1582
+ PAJEK_ISSUE.QUOTE_IN_TOKEN,
1583
+ "a double quote inside a token is removed and the parts joined (Pajek has no escape for a quote)",
1584
+ { line },
1585
+ );
1586
+ }
1351
1587
  if (tokens === null) {
1352
1588
  if (domain === "node") {
1353
1589
  this.report.counts.skippedNodes++;
@@ -1365,25 +1601,16 @@ class PajekParser {
1365
1601
  * @param tokens - the row's tokens
1366
1602
  * @param start - the first token to read
1367
1603
  * @param domain - what is skipped when the row is malformed
1604
+ * @param line - the line number
1368
1605
  * @returns the parsed extras
1369
1606
  */
1370
- private extras(tokens: readonly string[], start: number, domain: "node" | "edge"): RowExtras {
1607
+ private extras(tokens: readonly string[], start: number, domain: "node" | "edge", line: number): RowExtras {
1371
1608
  const extras: RowExtras = { keys: [], values: [], spells: null };
1372
- for (let i = start; i < tokens.length;) {
1609
+ const seen = new Set<string>();
1610
+ for (let i = start; i < tokens.length; ) {
1373
1611
  const token = tokens[i];
1374
- if (isIntervalToken(token)) {
1375
- try {
1376
- const spells = parseIntervals(token);
1377
- // an empty `[]` is no spell at all
1378
- extras.spells = spells.length === 0 ? null : spells;
1379
- } catch (err) {
1380
- this.skip(domain);
1381
- throw new LineError(
1382
- "validation-error",
1383
- PAJEK_ISSUE.INTERVAL,
1384
- err instanceof Error ? err.message : String(err),
1385
- );
1386
- }
1612
+ if (token.startsWith("[")) {
1613
+ extras.spells = this.intervals(token, domain);
1387
1614
  i++;
1388
1615
  continue;
1389
1616
  }
@@ -1395,6 +1622,15 @@ class PajekParser {
1395
1622
  `parameter "${token}" has no value`,
1396
1623
  );
1397
1624
  }
1625
+ if (seen.has(token)) {
1626
+ this.report.warning(
1627
+ "merged",
1628
+ PAJEK_ISSUE.DUPLICATE_ATTRIBUTE,
1629
+ `parameter ${JSON.stringify(token)} is given twice on one line; the later value ${JSON.stringify(tokens[i + 1])} stands`,
1630
+ { line, element: token },
1631
+ );
1632
+ }
1633
+ seen.add(token);
1398
1634
  extras.keys.push(token);
1399
1635
  extras.values.push(tokens[i + 1]);
1400
1636
  i += 2;
@@ -1402,6 +1638,34 @@ class PajekParser {
1402
1638
  return extras;
1403
1639
  }
1404
1640
 
1641
+ /**
1642
+ * The spells of a row's interval token; a malformed one skips the row.
1643
+ * @param token - the token, starting with "["
1644
+ * @param domain - what is skipped when the token is malformed
1645
+ * @returns the spells, or null for an empty `[]`, which is no spell at all
1646
+ */
1647
+ private intervals(token: string, domain: "node" | "edge"): [number, number][] | null {
1648
+ if (!isIntervalToken(token)) {
1649
+ this.skip(domain);
1650
+ throw new LineError(
1651
+ "validation-error",
1652
+ PAJEK_ISSUE.INTERVAL,
1653
+ `time interval ${token} is not closed by "]"`,
1654
+ );
1655
+ }
1656
+ try {
1657
+ const spells = parseIntervals(token);
1658
+ return spells.length === 0 ? null : spells;
1659
+ } catch (err) {
1660
+ this.skip(domain);
1661
+ throw new LineError(
1662
+ "validation-error",
1663
+ PAJEK_ISSUE.INTERVAL,
1664
+ err instanceof Error ? err.message : String(err),
1665
+ );
1666
+ }
1667
+ }
1668
+
1405
1669
  /**
1406
1670
  * Count a skipped element.
1407
1671
  * @param domain - node or edge
@@ -1449,7 +1713,7 @@ class PajekParser {
1449
1713
  * @returns true when tokens[1] is the first coordinate
1450
1714
  */
1451
1715
  function startsWithCoordinates(text: string, tokens: readonly string[]): boolean {
1452
- if (text.trimStart().slice(tokens[0].length).trimStart().startsWith('"')) {
1716
+ if (quotedAfterNumber(text, tokens)) {
1453
1717
  return false;
1454
1718
  }
1455
1719
  let run = 0;
@@ -1459,6 +1723,16 @@ function startsWithCoordinates(text: string, tokens: readonly string[]): boolean
1459
1723
  return run === 2;
1460
1724
  }
1461
1725
 
1726
+ /**
1727
+ * Whether the token after a row's vertex number is written in quotes.
1728
+ * @param text - the line
1729
+ * @param tokens - its tokens
1730
+ * @returns true when the second token was quoted
1731
+ */
1732
+ function quotedAfterNumber(text: string, tokens: readonly string[]): boolean {
1733
+ return text.trimStart().slice(tokens[0].length).trimStart().startsWith('"');
1734
+ }
1735
+
1462
1736
  /**
1463
1737
  * Resolve the firstVertex option.
1464
1738
  * @param value - the caller's value
@@ -1481,26 +1755,43 @@ function firstVertexOption(value: unknown): 0 | 1 | "auto" {
1481
1755
  );
1482
1756
  }
1483
1757
 
1484
- const HEAD_PATTERN = /^\s*\*(vertices|network)\b/i;
1758
+ /**
1759
+ * The section a Pajek network starts with, after blank lines and `%` comment lines.
1760
+ * @param text - the head of the input
1761
+ * @returns "vertices" or "network" (lower case), or null when the text starts otherwise
1762
+ */
1763
+ function headSection(text: string): string | null {
1764
+ let rest = text.trimStart();
1765
+ while (rest.startsWith("%")) {
1766
+ const end = rest.search(/[\r\n]/);
1767
+ rest = end < 0 ? "" : rest.slice(end).trimStart();
1768
+ }
1769
+ return /^\*(vertices|network)\b/i.exec(rest)?.[1].toLowerCase() ?? null;
1770
+ }
1485
1771
 
1486
- /** The Pajek NET importer. */
1772
+ /**
1773
+ * The Pajek NET importer.
1774
+ * @category Built-in formats
1775
+ */
1487
1776
  export const pajekImporter: GraphImporter<PajekImportOptions> = Object.freeze({
1488
1777
  format: "pajek",
1778
+ options: Object.freeze(["firstVertex"]),
1489
1779
  extensions: Object.freeze([".net", ".paj"]),
1490
1780
  mimeTypes: Object.freeze(["text/x-pajek", "text/plain"]),
1491
1781
 
1492
1782
  /**
1493
- * Confidence that the input is a Pajek network: it starts with `*Vertices` (or `*Network`).
1783
+ * Confidence that the input is a Pajek network: it starts with `*Vertices` (or `*Network`),
1784
+ * after any `%` comment lines.
1494
1785
  * @param head - the first bytes
1495
1786
  * @returns 0.9 for a `*Vertices` start, 0.8 for `*Network`, 0 otherwise
1496
1787
  */
1497
1788
  sniff(head: Uint8Array): number {
1498
- const text = new TextDecoder("utf-8").decode(head.subarray(0, Math.min(head.byteLength, 512)));
1499
- const match = HEAD_PATTERN.exec(text.startsWith(String.fromCharCode(0xfeff)) ? text.slice(1) : text);
1500
- if (match === null) {
1789
+ const text = new TextDecoder("utf-8").decode(head);
1790
+ const section = headSection(text.startsWith("\ufeff") ? text.slice(1) : text);
1791
+ if (section === null) {
1501
1792
  return 0;
1502
1793
  }
1503
- return match[1].toLowerCase() === "vertices" ? 0.9 : 0.8;
1794
+ return section === "vertices" ? 0.9 : 0.8;
1504
1795
  },
1505
1796
 
1506
1797
  /**
@@ -1515,6 +1806,10 @@ export const pajekImporter: GraphImporter<PajekImportOptions> = Object.freeze({
1515
1806
  sink: GraphSink,
1516
1807
  options?: PajekImportOptions & CommonImportOptions,
1517
1808
  ): Promise<ImportReport> {
1809
+ if (graphChosen(options)) {
1810
+ // the choice is honored here too, not only by the registry
1811
+ return importChosenGraph(pajekImporter, input, sink, options);
1812
+ }
1518
1813
  const resolved = resolveImportOptions(options, DEFAULTS);
1519
1814
  const firstVertex = firstVertexOption(options?.firstVertex);
1520
1815
  const report = new ImportReportBuilder("pajek", resolved.errorLimit);
@@ -1526,7 +1821,8 @@ export const pajekImporter: GraphImporter<PajekImportOptions> = Object.freeze({
1526
1821
  let rest: NetworkCounter | null = null;
1527
1822
  let restLine = 0;
1528
1823
  let sinceCheck = 0;
1529
- for await (const text of reader) {
1824
+ for await (const raw of reader) {
1825
+ const text = withoutStrayBom(raw);
1530
1826
  if (rest !== null) {
1531
1827
  rest.line(text);
1532
1828
  } else if (parser.line(text, reader.line)) {
@@ -1541,10 +1837,12 @@ export const pajekImporter: GraphImporter<PajekImportOptions> = Object.freeze({
1541
1837
  }
1542
1838
  parser.finish();
1543
1839
  if (rest !== null) {
1840
+ const objects =
1841
+ rest.objects > 0 ? `; ${rest.objects} *Partition / *Vector object(s) after them are not read` : "";
1544
1842
  report.warning(
1545
1843
  "unsupported",
1546
1844
  PAJEK_ISSUE.MULTIPLE_GRAPHS,
1547
- `the project file holds ${rest.count} more network(s) after the first; import() reads the first, importAll() reads every one`,
1845
+ `the project file holds ${rest.count} more network${plural(rest.count)} after the first; the first is read (graphIndex or graphName chooses another; importAllGraphs() reads every one)${objects}`,
1548
1846
  { line: restLine },
1549
1847
  );
1550
1848
  }
@@ -1580,7 +1878,8 @@ export const pajekImporter: GraphImporter<PajekImportOptions> = Object.freeze({
1580
1878
  // decoding issues belong to the file, so they go to the first network's report
1581
1879
  const reader = new LineReader(input, reports[0], resolved);
1582
1880
  let sinceCheck = 0;
1583
- for await (const text of reader) {
1881
+ for await (const raw of reader) {
1882
+ const text = withoutStrayBom(raw);
1584
1883
  if (parser.line(text, reader.line)) {
1585
1884
  parser.finish();
1586
1885
  reports.push(new ImportReportBuilder("pajek", resolved.errorLimit));
@@ -1598,6 +1897,16 @@ export const pajekImporter: GraphImporter<PajekImportOptions> = Object.freeze({
1598
1897
  },
1599
1898
  });
1600
1899
 
1900
+ /**
1901
+ * A line without the U+FEFF that starts it: a BOM left at the start of a later line by
1902
+ * concatenated files (the decoder removes only the first; the shared text check reports it).
1903
+ * @param text - the line
1904
+ * @returns the line without a leading U+FEFF
1905
+ */
1906
+ function withoutStrayBom(text: string): string {
1907
+ return text.codePointAt(0) === 0xfeff ? text.slice(1) : text;
1908
+ }
1909
+
1601
1910
  /**
1602
1911
  * Counts the networks of the rest of a project file by their headers alone, with the parser's
1603
1912
  * rule: a network starts at `*Network` after a named or populated one, or at a `*Vertices` after a
@@ -1608,6 +1917,9 @@ class NetworkCounter {
1608
1917
  /** Networks started so far (the first header fed opens the first). */
1609
1918
  count = 0;
1610
1919
 
1920
+ /** `*Partition` / `*Vector` objects seen; import() does not read them. */
1921
+ objects = 0;
1922
+
1611
1923
  private named = false;
1612
1924
 
1613
1925
  private populated = false;
@@ -1632,8 +1944,11 @@ class NetworkCounter {
1632
1944
  return;
1633
1945
  }
1634
1946
  const { kind } = h;
1635
- if (kind === "partition" || kind === "vector" || kind === "unsupported") {
1947
+ if (kind === "partition" || kind === "vector") {
1636
1948
  this.afterObjectHeader = true;
1949
+ this.objects++;
1950
+ } else if (kind === "unsupported") {
1951
+ this.afterObjectHeader = isProjectObject(h.keyword);
1637
1952
  } else if (kind === "network") {
1638
1953
  if (this.count === 0 || this.named || this.populated) {
1639
1954
  this.count++;
@@ -1649,4 +1964,3 @@ class NetworkCounter {
1649
1964
  }
1650
1965
  }
1651
1966
  }
1652
-