@graphty/graph-io 0.0.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -28
  3. package/dist/chunks/children-CL3Cy0ez.js +238 -0
  4. package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
  5. package/dist/chunks/escape-DyI8JofU.js +938 -0
  6. package/dist/chunks/escape-DyI8JofU.js.map +1 -0
  7. package/dist/chunks/importer-CQnJuWJw.js +2987 -0
  8. package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
  9. package/dist/chunks/importer-CpCpfbxr.js +2015 -0
  10. package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
  11. package/dist/chunks/importer-DbnGYr3_.js +2342 -0
  12. package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
  13. package/dist/chunks/importer-GozH8DkN.js +3050 -0
  14. package/dist/chunks/importer-GozH8DkN.js.map +1 -0
  15. package/dist/chunks/records-CGpxszm1.js +605 -0
  16. package/dist/chunks/records-CGpxszm1.js.map +1 -0
  17. package/dist/chunks/text-CajMdVFy.js +189 -0
  18. package/dist/chunks/text-CajMdVFy.js.map +1 -0
  19. package/dist/chunks/writer-DxSKC7TL.js +2842 -0
  20. package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
  21. package/dist/csv.d.ts +1 -0
  22. package/dist/csv.js +1702 -0
  23. package/dist/csv.js.map +1 -0
  24. package/dist/dot.d.ts +1 -0
  25. package/dist/dot.js +8 -0
  26. package/dist/dot.js.map +1 -0
  27. package/dist/gexf.d.ts +1 -0
  28. package/dist/gexf.js +3466 -0
  29. package/dist/gexf.js.map +1 -0
  30. package/dist/gml.d.ts +1 -0
  31. package/dist/gml.js +2647 -0
  32. package/dist/gml.js.map +1 -0
  33. package/dist/graph-io.d.ts +1 -0
  34. package/dist/graph-io.js +790 -0
  35. package/dist/graph-io.js.map +1 -0
  36. package/dist/graphml.d.ts +1 -0
  37. package/dist/graphml.js +8 -0
  38. package/dist/graphml.js.map +1 -0
  39. package/dist/json.d.ts +1 -0
  40. package/dist/json.js +11 -0
  41. package/dist/json.js.map +1 -0
  42. package/dist/neo4j.d.ts +1 -0
  43. package/dist/neo4j.js +2046 -0
  44. package/dist/neo4j.js.map +1 -0
  45. package/dist/pajek.d.ts +1 -0
  46. package/dist/pajek.js +8 -0
  47. package/dist/pajek.js.map +1 -0
  48. package/dist/src/children.d.ts +134 -0
  49. package/dist/src/children.d.ts.map +1 -0
  50. package/dist/src/children.js +274 -0
  51. package/dist/src/children.js.map +1 -0
  52. package/dist/src/common/attributes.d.ts +229 -0
  53. package/dist/src/common/attributes.d.ts.map +1 -0
  54. package/dist/src/common/attributes.js +368 -0
  55. package/dist/src/common/attributes.js.map +1 -0
  56. package/dist/src/common/codes.d.ts +105 -0
  57. package/dist/src/common/codes.d.ts.map +1 -0
  58. package/dist/src/common/codes.js +107 -0
  59. package/dist/src/common/codes.js.map +1 -0
  60. package/dist/src/common/declared-types.d.ts +84 -0
  61. package/dist/src/common/declared-types.d.ts.map +1 -0
  62. package/dist/src/common/declared-types.js +326 -0
  63. package/dist/src/common/declared-types.js.map +1 -0
  64. package/dist/src/common/direction.d.ts +206 -0
  65. package/dist/src/common/direction.d.ts.map +1 -0
  66. package/dist/src/common/direction.js +370 -0
  67. package/dist/src/common/direction.js.map +1 -0
  68. package/dist/src/common/escape.d.ts +92 -0
  69. package/dist/src/common/escape.d.ts.map +1 -0
  70. package/dist/src/common/escape.js +212 -0
  71. package/dist/src/common/escape.js.map +1 -0
  72. package/dist/src/common/export.d.ts +249 -0
  73. package/dist/src/common/export.d.ts.map +1 -0
  74. package/dist/src/common/export.js +594 -0
  75. package/dist/src/common/export.js.map +1 -0
  76. package/dist/src/common/format.d.ts +59 -0
  77. package/dist/src/common/format.d.ts.map +1 -0
  78. package/dist/src/common/format.js +106 -0
  79. package/dist/src/common/format.js.map +1 -0
  80. package/dist/src/common/ids.d.ts +83 -0
  81. package/dist/src/common/ids.d.ts.map +1 -0
  82. package/dist/src/common/ids.js +158 -0
  83. package/dist/src/common/ids.js.map +1 -0
  84. package/dist/src/common/input.d.ts +100 -0
  85. package/dist/src/common/input.d.ts.map +1 -0
  86. package/dist/src/common/input.js +335 -0
  87. package/dist/src/common/input.js.map +1 -0
  88. package/dist/src/common/lists.d.ts +34 -0
  89. package/dist/src/common/lists.d.ts.map +1 -0
  90. package/dist/src/common/lists.js +185 -0
  91. package/dist/src/common/lists.js.map +1 -0
  92. package/dist/src/common/options.d.ts +108 -0
  93. package/dist/src/common/options.d.ts.map +1 -0
  94. package/dist/src/common/options.js +265 -0
  95. package/dist/src/common/options.js.map +1 -0
  96. package/dist/src/common/report.d.ts +187 -0
  97. package/dist/src/common/report.d.ts.map +1 -0
  98. package/dist/src/common/report.js +274 -0
  99. package/dist/src/common/report.js.map +1 -0
  100. package/dist/src/common/temporal.d.ts +71 -0
  101. package/dist/src/common/temporal.d.ts.map +1 -0
  102. package/dist/src/common/temporal.js +266 -0
  103. package/dist/src/common/temporal.js.map +1 -0
  104. package/dist/src/common/text.d.ts +104 -0
  105. package/dist/src/common/text.d.ts.map +1 -0
  106. package/dist/src/common/text.js +255 -0
  107. package/dist/src/common/text.js.map +1 -0
  108. package/dist/src/common/weights.d.ts +77 -0
  109. package/dist/src/common/weights.d.ts.map +1 -0
  110. package/dist/src/common/weights.js +156 -0
  111. package/dist/src/common/weights.js.map +1 -0
  112. package/dist/src/common/writer.d.ts +51 -0
  113. package/dist/src/common/writer.d.ts.map +1 -0
  114. package/dist/src/common/writer.js +108 -0
  115. package/dist/src/common/writer.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +245 -0
  117. package/dist/src/common/xml.d.ts.map +1 -0
  118. package/dist/src/common/xml.js +942 -0
  119. package/dist/src/common/xml.js.map +1 -0
  120. package/dist/src/formats/csv/exporter.d.ts +70 -0
  121. package/dist/src/formats/csv/exporter.d.ts.map +1 -0
  122. package/dist/src/formats/csv/exporter.js +682 -0
  123. package/dist/src/formats/csv/exporter.js.map +1 -0
  124. package/dist/src/formats/csv/header.d.ts +66 -0
  125. package/dist/src/formats/csv/header.d.ts.map +1 -0
  126. package/dist/src/formats/csv/header.js +152 -0
  127. package/dist/src/formats/csv/header.js.map +1 -0
  128. package/dist/src/formats/csv/importer.d.ts +82 -0
  129. package/dist/src/formats/csv/importer.d.ts.map +1 -0
  130. package/dist/src/formats/csv/importer.js +849 -0
  131. package/dist/src/formats/csv/importer.js.map +1 -0
  132. package/dist/src/formats/csv/index.d.ts +60 -0
  133. package/dist/src/formats/csv/index.d.ts.map +1 -0
  134. package/dist/src/formats/csv/index.js +63 -0
  135. package/dist/src/formats/csv/index.js.map +1 -0
  136. package/dist/src/formats/csv/records.d.ts +188 -0
  137. package/dist/src/formats/csv/records.d.ts.map +1 -0
  138. package/dist/src/formats/csv/records.js +702 -0
  139. package/dist/src/formats/csv/records.js.map +1 -0
  140. package/dist/src/formats/csv/values.d.ts +105 -0
  141. package/dist/src/formats/csv/values.d.ts.map +1 -0
  142. package/dist/src/formats/csv/values.js +192 -0
  143. package/dist/src/formats/csv/values.js.map +1 -0
  144. package/dist/src/formats/dot/exporter.d.ts +52 -0
  145. package/dist/src/formats/dot/exporter.d.ts.map +1 -0
  146. package/dist/src/formats/dot/exporter.js +836 -0
  147. package/dist/src/formats/dot/exporter.js.map +1 -0
  148. package/dist/src/formats/dot/importer.d.ts +102 -0
  149. package/dist/src/formats/dot/importer.d.ts.map +1 -0
  150. package/dist/src/formats/dot/importer.js +1291 -0
  151. package/dist/src/formats/dot/importer.js.map +1 -0
  152. package/dist/src/formats/dot/index.d.ts +7 -0
  153. package/dist/src/formats/dot/index.d.ts.map +1 -0
  154. package/dist/src/formats/dot/index.js +7 -0
  155. package/dist/src/formats/dot/index.js.map +1 -0
  156. package/dist/src/formats/dot/names.d.ts +29 -0
  157. package/dist/src/formats/dot/names.d.ts.map +1 -0
  158. package/dist/src/formats/dot/names.js +28 -0
  159. package/dist/src/formats/dot/names.js.map +1 -0
  160. package/dist/src/formats/dot/tokenizer.d.ts +114 -0
  161. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
  162. package/dist/src/formats/dot/tokenizer.js +341 -0
  163. package/dist/src/formats/dot/tokenizer.js.map +1 -0
  164. package/dist/src/formats/gexf/exporter.d.ts +56 -0
  165. package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
  166. package/dist/src/formats/gexf/exporter.js +1395 -0
  167. package/dist/src/formats/gexf/exporter.js.map +1 -0
  168. package/dist/src/formats/gexf/importer.d.ts +73 -0
  169. package/dist/src/formats/gexf/importer.d.ts.map +1 -0
  170. package/dist/src/formats/gexf/importer.js +1880 -0
  171. package/dist/src/formats/gexf/importer.js.map +1 -0
  172. package/dist/src/formats/gexf/index.d.ts +96 -0
  173. package/dist/src/formats/gexf/index.d.ts.map +1 -0
  174. package/dist/src/formats/gexf/index.js +97 -0
  175. package/dist/src/formats/gexf/index.js.map +1 -0
  176. package/dist/src/formats/gexf/schema.d.ts +135 -0
  177. package/dist/src/formats/gexf/schema.d.ts.map +1 -0
  178. package/dist/src/formats/gexf/schema.js +323 -0
  179. package/dist/src/formats/gexf/schema.js.map +1 -0
  180. package/dist/src/formats/gml/exporter.d.ts +69 -0
  181. package/dist/src/formats/gml/exporter.d.ts.map +1 -0
  182. package/dist/src/formats/gml/exporter.js +1093 -0
  183. package/dist/src/formats/gml/exporter.js.map +1 -0
  184. package/dist/src/formats/gml/importer.d.ts +66 -0
  185. package/dist/src/formats/gml/importer.d.ts.map +1 -0
  186. package/dist/src/formats/gml/importer.js +1331 -0
  187. package/dist/src/formats/gml/importer.js.map +1 -0
  188. package/dist/src/formats/gml/index.d.ts +85 -0
  189. package/dist/src/formats/gml/index.d.ts.map +1 -0
  190. package/dist/src/formats/gml/index.js +88 -0
  191. package/dist/src/formats/gml/index.js.map +1 -0
  192. package/dist/src/formats/gml/syntax.d.ts +186 -0
  193. package/dist/src/formats/gml/syntax.d.ts.map +1 -0
  194. package/dist/src/formats/gml/syntax.js +467 -0
  195. package/dist/src/formats/gml/syntax.js.map +1 -0
  196. package/dist/src/formats/graphml/constants.d.ts +169 -0
  197. package/dist/src/formats/graphml/constants.d.ts.map +1 -0
  198. package/dist/src/formats/graphml/constants.js +165 -0
  199. package/dist/src/formats/graphml/constants.js.map +1 -0
  200. package/dist/src/formats/graphml/exporter.d.ts +34 -0
  201. package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
  202. package/dist/src/formats/graphml/exporter.js +1176 -0
  203. package/dist/src/formats/graphml/exporter.js.map +1 -0
  204. package/dist/src/formats/graphml/importer.d.ts +31 -0
  205. package/dist/src/formats/graphml/importer.d.ts.map +1 -0
  206. package/dist/src/formats/graphml/importer.js +1607 -0
  207. package/dist/src/formats/graphml/importer.js.map +1 -0
  208. package/dist/src/formats/graphml/index.d.ts +8 -0
  209. package/dist/src/formats/graphml/index.d.ts.map +1 -0
  210. package/dist/src/formats/graphml/index.js +8 -0
  211. package/dist/src/formats/graphml/index.js.map +1 -0
  212. package/dist/src/formats/graphml/tree.d.ts +72 -0
  213. package/dist/src/formats/graphml/tree.d.ts.map +1 -0
  214. package/dist/src/formats/graphml/tree.js +290 -0
  215. package/dist/src/formats/graphml/tree.js.map +1 -0
  216. package/dist/src/formats/json/dialect.d.ts +125 -0
  217. package/dist/src/formats/json/dialect.d.ts.map +1 -0
  218. package/dist/src/formats/json/dialect.js +262 -0
  219. package/dist/src/formats/json/dialect.js.map +1 -0
  220. package/dist/src/formats/json/exporter.d.ts +89 -0
  221. package/dist/src/formats/json/exporter.d.ts.map +1 -0
  222. package/dist/src/formats/json/exporter.js +1358 -0
  223. package/dist/src/formats/json/exporter.js.map +1 -0
  224. package/dist/src/formats/json/importer.d.ts +108 -0
  225. package/dist/src/formats/json/importer.d.ts.map +1 -0
  226. package/dist/src/formats/json/importer.js +1838 -0
  227. package/dist/src/formats/json/importer.js.map +1 -0
  228. package/dist/src/formats/json/index.d.ts +8 -0
  229. package/dist/src/formats/json/index.d.ts.map +1 -0
  230. package/dist/src/formats/json/index.js +8 -0
  231. package/dist/src/formats/json/index.js.map +1 -0
  232. package/dist/src/formats/neo4j/exporter.d.ts +68 -0
  233. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
  234. package/dist/src/formats/neo4j/exporter.js +1055 -0
  235. package/dist/src/formats/neo4j/exporter.js.map +1 -0
  236. package/dist/src/formats/neo4j/header.d.ts +52 -0
  237. package/dist/src/formats/neo4j/header.d.ts.map +1 -0
  238. package/dist/src/formats/neo4j/header.js +131 -0
  239. package/dist/src/formats/neo4j/header.js.map +1 -0
  240. package/dist/src/formats/neo4j/importer.d.ts +73 -0
  241. package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
  242. package/dist/src/formats/neo4j/importer.js +932 -0
  243. package/dist/src/formats/neo4j/importer.js.map +1 -0
  244. package/dist/src/formats/neo4j/index.d.ts +79 -0
  245. package/dist/src/formats/neo4j/index.d.ts.map +1 -0
  246. package/dist/src/formats/neo4j/index.js +83 -0
  247. package/dist/src/formats/neo4j/index.js.map +1 -0
  248. package/dist/src/formats/pajek/exporter.d.ts +58 -0
  249. package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
  250. package/dist/src/formats/pajek/exporter.js +825 -0
  251. package/dist/src/formats/pajek/exporter.js.map +1 -0
  252. package/dist/src/formats/pajek/importer.d.ts +88 -0
  253. package/dist/src/formats/pajek/importer.d.ts.map +1 -0
  254. package/dist/src/formats/pajek/importer.js +1047 -0
  255. package/dist/src/formats/pajek/importer.js.map +1 -0
  256. package/dist/src/formats/pajek/index.d.ts +7 -0
  257. package/dist/src/formats/pajek/index.d.ts.map +1 -0
  258. package/dist/src/formats/pajek/index.js +7 -0
  259. package/dist/src/formats/pajek/index.js.map +1 -0
  260. package/dist/src/formats/pajek/syntax.d.ts +112 -0
  261. package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
  262. package/dist/src/formats/pajek/syntax.js +269 -0
  263. package/dist/src/formats/pajek/syntax.js.map +1 -0
  264. package/dist/src/index.d.ts +35 -0
  265. package/dist/src/index.d.ts.map +1 -0
  266. package/dist/src/index.js +39 -0
  267. package/dist/src/index.js.map +1 -0
  268. package/dist/src/registry.d.ts +207 -0
  269. package/dist/src/registry.d.ts.map +1 -0
  270. package/dist/src/registry.js +481 -0
  271. package/dist/src/registry.js.map +1 -0
  272. package/dist/src/sniff.d.ts +104 -0
  273. package/dist/src/sniff.d.ts.map +1 -0
  274. package/dist/src/sniff.js +357 -0
  275. package/dist/src/sniff.js.map +1 -0
  276. package/dist/src/types.d.ts +238 -0
  277. package/dist/src/types.d.ts.map +1 -0
  278. package/dist/src/types.js +29 -0
  279. package/dist/src/types.js.map +1 -0
  280. package/dist/tsconfig.build.tsbuildinfo +1 -0
  281. package/package.json +122 -7
  282. package/src/children.ts +335 -0
  283. package/src/common/attributes.ts +520 -0
  284. package/src/common/codes.ts +153 -0
  285. package/src/common/declared-types.ts +374 -0
  286. package/src/common/direction.ts +518 -0
  287. package/src/common/escape.ts +231 -0
  288. package/src/common/export.ts +817 -0
  289. package/src/common/format.ts +111 -0
  290. package/src/common/ids.ts +176 -0
  291. package/src/common/input.ts +378 -0
  292. package/src/common/lists.ts +196 -0
  293. package/src/common/options.ts +377 -0
  294. package/src/common/report.ts +352 -0
  295. package/src/common/temporal.ts +302 -0
  296. package/src/common/text.ts +294 -0
  297. package/src/common/weights.ts +202 -0
  298. package/src/common/writer.ts +123 -0
  299. package/src/common/xml.ts +1053 -0
  300. package/src/formats/csv/exporter.ts +894 -0
  301. package/src/formats/csv/header.ts +172 -0
  302. package/src/formats/csv/importer.ts +1104 -0
  303. package/src/formats/csv/index.ts +88 -0
  304. package/src/formats/csv/records.ts +813 -0
  305. package/src/formats/csv/values.ts +224 -0
  306. package/src/formats/dot/exporter.ts +1014 -0
  307. package/src/formats/dot/importer.ts +1549 -0
  308. package/src/formats/dot/index.ts +7 -0
  309. package/src/formats/dot/names.ts +40 -0
  310. package/src/formats/dot/tokenizer.ts +384 -0
  311. package/src/formats/gexf/exporter.ts +1696 -0
  312. package/src/formats/gexf/importer.ts +2333 -0
  313. package/src/formats/gexf/index.ts +142 -0
  314. package/src/formats/gexf/schema.ts +361 -0
  315. package/src/formats/gml/exporter.ts +1404 -0
  316. package/src/formats/gml/importer.ts +1591 -0
  317. package/src/formats/gml/index.ts +128 -0
  318. package/src/formats/gml/syntax.ts +545 -0
  319. package/src/formats/graphml/constants.ts +225 -0
  320. package/src/formats/graphml/exporter.ts +1458 -0
  321. package/src/formats/graphml/importer.ts +2027 -0
  322. package/src/formats/graphml/index.ts +8 -0
  323. package/src/formats/graphml/tree.ts +318 -0
  324. package/src/formats/json/dialect.ts +317 -0
  325. package/src/formats/json/exporter.ts +1616 -0
  326. package/src/formats/json/importer.ts +2271 -0
  327. package/src/formats/json/index.ts +8 -0
  328. package/src/formats/neo4j/exporter.ts +1287 -0
  329. package/src/formats/neo4j/header.ts +156 -0
  330. package/src/formats/neo4j/importer.ts +1220 -0
  331. package/src/formats/neo4j/index.ts +116 -0
  332. package/src/formats/pajek/exporter.ts +1000 -0
  333. package/src/formats/pajek/importer.ts +1311 -0
  334. package/src/formats/pajek/index.ts +7 -0
  335. package/src/formats/pajek/syntax.ts +307 -0
  336. package/src/index.ts +244 -0
  337. package/src/registry.ts +617 -0
  338. package/src/sniff.ts +397 -0
  339. package/src/types.ts +262 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * List value syntaxes of the text formats (design section 5.1, research note 07 section 2.1):
3
+ * GEXF 1.3 bracket lists `[1, 2, 3]` / `[foo, 'bar baz']`, GEXF 1.2 `liststring` values separated
4
+ * by `|`, `,` or `;` ("an unsafe type"), Gephi and Neo4j `;`-separated arrays. The importer splits
5
+ * the text into item texts with splitListText() and parses each item by the declared item type.
6
+ */
7
+
8
+ import { GraphFormatError } from "@graphty/graph-format";
9
+
10
+ /** The list syntaxes an importer or exporter names. */
11
+ export type ListSyntax = "gexf" | "brackets" | "pipe" | "comma" | "semicolon";
12
+
13
+ /**
14
+ * Split a list value into its item texts.
15
+ *
16
+ * - "gexf": a bracketed text `[a, b]` is parsed with single or double quotes around items that
17
+ * contain commas; otherwise the 1.2 rule applies: the text is split on `|` when it contains one,
18
+ * else on `,` when it contains one, else on `;` when it contains one, else it is one item.
19
+ * - "brackets": bracketed only; an unbracketed text is one item.
20
+ * - "pipe" / "comma" / "semicolon": split on that separator.
21
+ *
22
+ * Items are trimmed; an empty text (or `[]`) is an empty list; an empty item between separators
23
+ * is kept as an empty string.
24
+ * @param text - the value text
25
+ * @param syntax - the syntax
26
+ * @returns the item texts
27
+ */
28
+ export function splitListText(text: string, syntax: ListSyntax): string[] {
29
+ const trimmed = text.trim();
30
+ if (trimmed.length === 0) {
31
+ return [];
32
+ }
33
+ switch (syntax) {
34
+ case "gexf": {
35
+ if (isBracketed(trimmed)) {
36
+ return splitBracketed(trimmed);
37
+ }
38
+ const separator = firstSeparator(trimmed, ["|", ",", ";"]);
39
+ return separator === null ? [trimmed] : splitOn(trimmed, separator);
40
+ }
41
+ case "brackets":
42
+ return isBracketed(trimmed) ? splitBracketed(trimmed) : [trimmed];
43
+ case "pipe":
44
+ return splitOn(trimmed, "|");
45
+ case "comma":
46
+ return splitOn(trimmed, ",");
47
+ case "semicolon":
48
+ return splitOn(trimmed, ";");
49
+ default: {
50
+ const name: string = syntax;
51
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
52
+ option: "listSyntax",
53
+ found: name,
54
+ });
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Join item texts into a list value of one syntax, the inverse of splitListText() for the exporters.
61
+ * Bracket syntaxes quote an item that contains a comma, a bracket or a quote (with double quotes,
62
+ * a double quote inside doubled); separator syntaxes cannot escape and leave items as they are.
63
+ * @param items - the item texts
64
+ * @param syntax - the syntax; "gexf" writes the 1.3 bracket form
65
+ * @returns the list text
66
+ */
67
+ export function joinListText(items: readonly string[], syntax: ListSyntax): string {
68
+ switch (syntax) {
69
+ case "gexf":
70
+ case "brackets":
71
+ return `[${items.map(quoteBracketItem).join(", ")}]`;
72
+ case "pipe":
73
+ return items.join("|");
74
+ case "comma":
75
+ return items.join(",");
76
+ case "semicolon":
77
+ return items.join(";");
78
+ default: {
79
+ const name: string = syntax;
80
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
81
+ option: "listSyntax",
82
+ found: name,
83
+ });
84
+ }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Whether a trimmed text is `[...]`.
90
+ * @param text - the trimmed text
91
+ * @returns true when bracketed
92
+ */
93
+ function isBracketed(text: string): boolean {
94
+ return text.length >= 2 && text.startsWith("[") && text.endsWith("]");
95
+ }
96
+
97
+ /**
98
+ * The first of several separators that occurs in a text.
99
+ * @param text - the text
100
+ * @param candidates - separators in priority order
101
+ * @returns the first candidate found, or null
102
+ */
103
+ function firstSeparator(text: string, candidates: readonly string[]): string | null {
104
+ for (const candidate of candidates) {
105
+ if (text.includes(candidate)) {
106
+ return candidate;
107
+ }
108
+ }
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Split on a separator and trim every item.
114
+ * @param text - the text
115
+ * @param separator - the separator
116
+ * @returns the trimmed items
117
+ */
118
+ function splitOn(text: string, separator: string): string[] {
119
+ return text.split(separator).map((item) => item.trim());
120
+ }
121
+
122
+ /**
123
+ * Parse the inside of a bracketed list: comma-separated items, each optionally wrapped in single
124
+ * or double quotes (a doubled quote inside a quoted item is one quote).
125
+ * @param text - the bracketed text
126
+ * @returns the items
127
+ */
128
+ function splitBracketed(text: string): string[] {
129
+ const inner = text.slice(1, -1);
130
+ const items: string[] = [];
131
+ const n = inner.length;
132
+ if (inner.trim().length === 0) {
133
+ return items;
134
+ }
135
+ let i = 0;
136
+ for (;;) {
137
+ while (i < n && isSpace(inner.charCodeAt(i))) {
138
+ i++;
139
+ }
140
+ let item = "";
141
+ if (i < n && (inner[i] === '"' || inner[i] === "'")) {
142
+ const quote = inner[i];
143
+ i++;
144
+ while (i < n) {
145
+ if (inner[i] === quote) {
146
+ if (inner[i + 1] === quote) {
147
+ item += quote;
148
+ i += 2;
149
+ continue;
150
+ }
151
+ i++;
152
+ break;
153
+ }
154
+ item += inner[i];
155
+ i++;
156
+ }
157
+ }
158
+ // unquoted text, or text after a closing quote that is not a separator, runs to the next comma
159
+ let end = inner.indexOf(",", i);
160
+ if (end < 0) {
161
+ end = n;
162
+ }
163
+ item += inner.slice(i, end).trim();
164
+ items.push(item);
165
+ i = end;
166
+ if (i >= n) {
167
+ return items;
168
+ }
169
+ i++;
170
+ if (inner.slice(i).trim().length === 0) {
171
+ items.push("");
172
+ return items;
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Whether a char code is ASCII whitespace.
179
+ * @param c - the char code
180
+ * @returns true for space, tab, CR, LF
181
+ */
182
+ function isSpace(c: number): boolean {
183
+ return c === 32 || c === 9 || c === 13 || c === 10;
184
+ }
185
+
186
+ /**
187
+ * Quote one item for a bracket list when it needs it.
188
+ * @param item - the item text
189
+ * @returns the item, quoted when it contains a comma, a bracket, a quote or surrounding whitespace
190
+ */
191
+ function quoteBracketItem(item: string): string {
192
+ if (item.length === 0 || /[,[\]"']/.test(item) || item !== item.trim()) {
193
+ return `"${item.replace(/"/g, '""')}"`;
194
+ }
195
+ return item;
196
+ }
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Option normalisation for importers and exporters (design sections 8.4 and 8.5): every common
3
+ * option resolved to its documented default, enum values checked (E_UNSUPPORTED, the core's
4
+ * convention for an option outside its set), and the per-format defaults (`ids`, `defaultDirected`,
5
+ * `weightFrom`, `addMissingNodes`) supplied by the importer that calls resolveImportOptions().
6
+ */
7
+
8
+ import { type DuplicatePolicy, GraphFormatError, type GraphSink, type IdCoercion } from "@graphty/graph-format";
9
+
10
+ import { type CommonExportOptions, type CommonImportOptions } from "../types.js";
11
+ import { OPTION_IGNORED_CODE, SINK_OPTION_CODE } from "./codes.js";
12
+ import { type ImportReportBuilder } from "./report.js";
13
+
14
+ /** The defaults an importer supplies for the options whose default is per format (design section 8.4). */
15
+ export interface ImportFormatDefaults {
16
+ /** "canonical" for text-cell formats, "keep" for JSON. */
17
+ readonly ids: IdCoercion;
18
+ /** The direction assumed when the file declares none. */
19
+ readonly defaultDirected: boolean;
20
+ /** The attribute that becomes the weight ("weight", GML "value"), or null for an unweighted format. */
21
+ readonly weightFrom: string | null;
22
+ /** Whether edges may reference undeclared nodes; true unless the format says otherwise (GEXF: false). */
23
+ readonly addMissingNodes?: boolean | undefined;
24
+ }
25
+
26
+ /**
27
+ * CommonImportOptions with every field present (design section 8.4 defaults applied).
28
+ * Consumed by the per-format importers and exporters under src/formats.
29
+ * @public
30
+ */
31
+ export interface ResolvedImportOptions {
32
+ /** The id coercion rule. */
33
+ readonly ids: IdCoercion;
34
+ /** Which field becomes the node id. */
35
+ readonly nodeIdFrom: "id" | "label" | "index";
36
+ /** Whether an edge may reference an undeclared node. */
37
+ readonly addMissingNodes: boolean;
38
+ /** The builder's duplicate-edge policy seed. */
39
+ readonly duplicateEdges: DuplicatePolicy;
40
+ /** The builder's self-loop policy seed. */
41
+ readonly selfLoops: "keep" | "drop" | "error";
42
+ /** The mixed-direction policy. */
43
+ readonly onMixedDirection: "expand" | "directed" | "undirected" | "error";
44
+ /** The direction assumed when the file declares none. */
45
+ readonly defaultDirected: boolean;
46
+ /** The weight attribute, or null for unweighted. */
47
+ readonly weightFrom: string | null;
48
+ /** The weight staging precision. */
49
+ readonly weightDtype: "f32" | "f64";
50
+ /** How declared long columns are stored. */
51
+ readonly long: "f64" | "string";
52
+ /** Whether mangled ids are restored. */
53
+ readonly restoreMangledIds: boolean;
54
+ /** The hyperedge policy. */
55
+ readonly hyperedges: "error" | "skip" | "star" | "clique";
56
+ /** Errors tolerated before aborting. */
57
+ readonly errorLimit: number;
58
+ /** The cancellation signal, or null. */
59
+ readonly signal: AbortSignal | null;
60
+ /** The progress callback, or null. */
61
+ readonly onProgress: ((bytesDone: number, bytesTotal?: number) => void) | null;
62
+ }
63
+
64
+ /** CommonExportOptions with every field present (design section 8.5 defaults applied). */
65
+ export interface ResolvedExportOptions {
66
+ /** "error" never renames a node; "mangle" rewrites and keeps the original. */
67
+ readonly sanitizeIds: "error" | "mangle";
68
+ /** What a format without mixed-direction support does with a mixed snapshot. */
69
+ readonly onMixedDirection: "error" | "directed" | "undirected";
70
+ }
71
+
72
+ const ID_COERCIONS: ReadonlySet<string> = new Set(["keep", "canonical", "string", "number"]);
73
+ const NODE_ID_SOURCES: ReadonlySet<string> = new Set(["id", "label", "index"]);
74
+ const DUPLICATE_POLICIES: ReadonlySet<string> = new Set(["keep", "error", "first", "last", "sum", "min", "max"]);
75
+ const SELF_LOOP_POLICIES: ReadonlySet<string> = new Set(["keep", "drop", "error"]);
76
+ const IMPORT_MIXED_POLICIES: ReadonlySet<string> = new Set(["expand", "directed", "undirected", "error"]);
77
+ const EXPORT_MIXED_POLICIES: ReadonlySet<string> = new Set(["error", "directed", "undirected"]);
78
+ const WEIGHT_DTYPES: ReadonlySet<string> = new Set(["f32", "f64"]);
79
+ const LONG_MODES: ReadonlySet<string> = new Set(["f64", "string"]);
80
+ const HYPEREDGE_POLICIES: ReadonlySet<string> = new Set(["error", "skip", "star", "clique"]);
81
+ const SANITIZE_MODES: ReadonlySet<string> = new Set(["error", "mangle"]);
82
+
83
+ /** The default error limit of design section 8.4. */
84
+ export const DEFAULT_ERROR_LIMIT = 100;
85
+
86
+ export { SINK_OPTION_CODE };
87
+
88
+ /** The builder-policy options a sink exposes read-only as `sink.options` (design section 8.4). */
89
+ const SINK_OPTION_NAMES = ["addMissingNodes", "duplicateEdges", "selfLoops", "weightDtype"] as const;
90
+
91
+ /**
92
+ * Report every builder-policy option the caller explicitly requested that the sink does not use
93
+ * (design section 8.4 precedence), one `W_SINK_OPTION` warning per option with the option name as
94
+ * the element. Options left undefined are never reported: they are defaults, not requests. On the
95
+ * registry's builder, which is seeded from the same options, nothing is ever reported.
96
+ * @param sink - the sink the importer pushes into
97
+ * @param options - the caller's raw options, possibly undefined
98
+ * @param report - the report to record into
99
+ * @param enforcesMissingNodes - true when the importer applies `addMissingNodes: false` itself (it
100
+ * refuses unknown endpoints before the sink sees them), so that request is honoured on any sink and
101
+ * only `addMissingNodes: true` against a refusing sink is reported
102
+ * @returns the number of warnings recorded
103
+ */
104
+ export function reportSinkOptions(
105
+ sink: GraphSink,
106
+ options: CommonImportOptions | undefined,
107
+ report: ImportReportBuilder,
108
+ enforcesMissingNodes = false,
109
+ ): number {
110
+ if (options === undefined) {
111
+ return 0;
112
+ }
113
+ let recorded = 0;
114
+ for (const name of SINK_OPTION_NAMES) {
115
+ const wanted: unknown = options[name];
116
+ const actual: unknown = sink.options[name];
117
+ if (wanted === undefined || wanted === actual) {
118
+ continue;
119
+ }
120
+ if (name === "addMissingNodes" && enforcesMissingNodes && wanted === false) {
121
+ continue;
122
+ }
123
+ report.warning(
124
+ "coercion",
125
+ SINK_OPTION_CODE,
126
+ `option ${name}: ${JSON.stringify(wanted)} requested but the sink uses ${JSON.stringify(actual)}; the sink's setting applies`,
127
+ { element: name },
128
+ );
129
+ recorded++;
130
+ }
131
+ return recorded;
132
+ }
133
+
134
+ /** The common options a format may leave unused; `signal`, `onProgress` and `errorLimit` apply everywhere. */
135
+ const IGNORABLE_OPTION_NAMES = [
136
+ "ids",
137
+ "nodeIdFrom",
138
+ "addMissingNodes",
139
+ "duplicateEdges",
140
+ "selfLoops",
141
+ "onMixedDirection",
142
+ "defaultDirected",
143
+ "weightFrom",
144
+ "weightDtype",
145
+ "long",
146
+ "restoreMangledIds",
147
+ "hyperedges",
148
+ ] as const;
149
+
150
+ /**
151
+ * Report every common option the caller set to a non-default value that the format has no use
152
+ * for (design section 8.4: "the importer reports every option it could not honour"): one
153
+ * `W_OPTION_IGNORED` warning (category `unsupported`) per option, the option name as the element.
154
+ * The builder-policy options are reportSinkOptions()'s and are skipped here.
155
+ * @param options - the caller's raw options, possibly undefined
156
+ * @param report - the report to record into
157
+ * @param used - the common option names the importer reads
158
+ * @returns the number of warnings recorded
159
+ */
160
+ export function reportUnusedOptions(
161
+ options: CommonImportOptions | undefined,
162
+ report: ImportReportBuilder,
163
+ used: ReadonlySet<keyof CommonImportOptions>,
164
+ ): number {
165
+ if (options === undefined) {
166
+ return 0;
167
+ }
168
+ let recorded = 0;
169
+ for (const name of IGNORABLE_OPTION_NAMES) {
170
+ if (used.has(name) || (SINK_OPTION_NAMES as readonly string[]).includes(name)) {
171
+ continue;
172
+ }
173
+ const value: unknown = options[name];
174
+ if (value === undefined) {
175
+ continue;
176
+ }
177
+ report.warning(
178
+ "unsupported",
179
+ OPTION_IGNORED_CODE,
180
+ `option ${name}: ${JSON.stringify(value)} has no effect on the ${report.format} importer`,
181
+ { element: name },
182
+ );
183
+ recorded++;
184
+ }
185
+ return recorded;
186
+ }
187
+
188
+ /**
189
+ * Apply the design section 8.4 defaults to an importer's common options and check every enum
190
+ * value. Format-specific options in the same object are ignored here.
191
+ * @param options - the caller's options, possibly undefined
192
+ * @param defaults - the importer's per-format defaults
193
+ * @returns the resolved options; E_UNSUPPORTED for a value outside its set
194
+ */
195
+ export function resolveImportOptions(
196
+ options: CommonImportOptions | undefined,
197
+ defaults: ImportFormatDefaults,
198
+ ): ResolvedImportOptions {
199
+ const o: CommonImportOptions = options ?? {};
200
+ return Object.freeze({
201
+ ids: enumOption("ids", o.ids, defaults.ids, ID_COERCIONS),
202
+ nodeIdFrom: enumOption("nodeIdFrom", o.nodeIdFrom, "id", NODE_ID_SOURCES),
203
+ addMissingNodes: booleanOption("addMissingNodes", o.addMissingNodes, defaults.addMissingNodes ?? true),
204
+ duplicateEdges: enumOption("duplicateEdges", o.duplicateEdges, "keep", DUPLICATE_POLICIES),
205
+ selfLoops: enumOption("selfLoops", o.selfLoops, "keep", SELF_LOOP_POLICIES),
206
+ onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "expand", IMPORT_MIXED_POLICIES),
207
+ defaultDirected: booleanOption("defaultDirected", o.defaultDirected, defaults.defaultDirected),
208
+ weightFrom: weightFromOption(o.weightFrom, defaults.weightFrom),
209
+ weightDtype: enumOption("weightDtype", o.weightDtype, "f64", WEIGHT_DTYPES),
210
+ long: enumOption("long", o.long, "f64", LONG_MODES),
211
+ restoreMangledIds: booleanOption("restoreMangledIds", o.restoreMangledIds, true),
212
+ hyperedges: enumOption("hyperedges", o.hyperedges, "skip", HYPEREDGE_POLICIES),
213
+ errorLimit: errorLimitOption(o.errorLimit),
214
+ signal: signalOption(o.signal),
215
+ onProgress: progressOption(o.onProgress),
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Apply the design section 8.5 defaults to an exporter's common options and check the enum values.
221
+ * @param options - the caller's options, possibly undefined
222
+ * @returns the resolved options; E_UNSUPPORTED for a value outside its set
223
+ */
224
+ export function resolveExportOptions(options: CommonExportOptions | undefined): ResolvedExportOptions {
225
+ const o: CommonExportOptions = options ?? {};
226
+ return Object.freeze({
227
+ sanitizeIds: enumOption("sanitizeIds", o.sanitizeIds, "error", SANITIZE_MODES),
228
+ onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "error", EXPORT_MIXED_POLICIES),
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Resolve one enum-valued option.
234
+ * @param name - the option name, for the error
235
+ * @param value - the caller's value
236
+ * @param fallback - the default
237
+ * @param allowed - the accepted values
238
+ * @returns the value or the default; E_UNSUPPORTED when outside the set
239
+ */
240
+ function enumOption<T extends string>(name: string, value: unknown, fallback: T, allowed: ReadonlySet<string>): T {
241
+ if (value === undefined) {
242
+ return fallback;
243
+ }
244
+ if (typeof value !== "string" || !allowed.has(value)) {
245
+ throw new GraphFormatError(
246
+ "E_UNSUPPORTED",
247
+ `option ${name}: ${describe(value)} is not one of ${list(allowed)}`,
248
+ {
249
+ option: name,
250
+ found: value,
251
+ supported: [...allowed],
252
+ },
253
+ );
254
+ }
255
+ return value as T;
256
+ }
257
+
258
+ /**
259
+ * Resolve one boolean option.
260
+ * @param name - the option name, for the error
261
+ * @param value - the caller's value
262
+ * @param fallback - the default
263
+ * @returns the value or the default; E_UNSUPPORTED when not a boolean
264
+ */
265
+ function booleanOption(name: string, value: unknown, fallback: boolean): boolean {
266
+ if (value === undefined) {
267
+ return fallback;
268
+ }
269
+ if (typeof value !== "boolean") {
270
+ throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: ${describe(value)} is not a boolean`, {
271
+ option: name,
272
+ found: value,
273
+ });
274
+ }
275
+ return value;
276
+ }
277
+
278
+ /**
279
+ * Resolve the weightFrom option: a non-empty attribute name, null for unweighted, or the format default.
280
+ * @param value - the caller's value
281
+ * @param fallback - the format default
282
+ * @returns the resolved value; E_UNSUPPORTED for anything else
283
+ */
284
+ function weightFromOption(value: unknown, fallback: string | null): string | null {
285
+ if (value === undefined) {
286
+ return fallback;
287
+ }
288
+ if (value === null || (typeof value === "string" && value.length > 0)) {
289
+ return value;
290
+ }
291
+ throw new GraphFormatError(
292
+ "E_UNSUPPORTED",
293
+ `option weightFrom: ${describe(value)} is not an attribute name or null`,
294
+ { option: "weightFrom", found: value },
295
+ );
296
+ }
297
+
298
+ /**
299
+ * Resolve the error limit: a non-negative integer or Infinity.
300
+ * @param value - the caller's value
301
+ * @returns the limit or the default
302
+ */
303
+ function errorLimitOption(value: unknown): number {
304
+ if (value === undefined) {
305
+ return DEFAULT_ERROR_LIMIT;
306
+ }
307
+ if (typeof value === "number" && value >= 0 && (Number.isInteger(value) || value === Infinity)) {
308
+ return value;
309
+ }
310
+ throw new GraphFormatError(
311
+ "E_UNSUPPORTED",
312
+ `option errorLimit: ${describe(value)} is not a non-negative integer or Infinity`,
313
+ { option: "errorLimit", found: value },
314
+ );
315
+ }
316
+
317
+ /**
318
+ * Resolve the signal option by duck type, so a signal from another realm is accepted.
319
+ * @param value - the caller's value
320
+ * @returns the signal or null
321
+ */
322
+ function signalOption(value: unknown): AbortSignal | null {
323
+ if (value === undefined || value === null) {
324
+ return null;
325
+ }
326
+ if (typeof value === "object" && typeof (value as { aborted?: unknown }).aborted === "boolean") {
327
+ return value as AbortSignal;
328
+ }
329
+ throw new GraphFormatError("E_UNSUPPORTED", `option signal: ${describe(value)} is not an AbortSignal`, {
330
+ option: "signal",
331
+ found: typeof value,
332
+ });
333
+ }
334
+
335
+ /**
336
+ * Resolve the progress callback option.
337
+ * @param value - the caller's value
338
+ * @returns the callback or null
339
+ */
340
+ function progressOption(value: unknown): ((bytesDone: number, bytesTotal?: number) => void) | null {
341
+ if (value === undefined || value === null) {
342
+ return null;
343
+ }
344
+ if (typeof value === "function") {
345
+ return value as (bytesDone: number, bytesTotal?: number) => void;
346
+ }
347
+ throw new GraphFormatError("E_UNSUPPORTED", `option onProgress: ${describe(value)} is not a function`, {
348
+ option: "onProgress",
349
+ found: typeof value,
350
+ });
351
+ }
352
+
353
+ /**
354
+ * A short description of an option value for an error message.
355
+ * @param value - the value
356
+ * @returns the JSON text of a primitive, or the type name otherwise
357
+ */
358
+ function describe(value: unknown): string {
359
+ switch (typeof value) {
360
+ case "string":
361
+ return JSON.stringify(value);
362
+ case "number":
363
+ case "boolean":
364
+ return String(value);
365
+ default:
366
+ return value === null ? "null" : typeof value;
367
+ }
368
+ }
369
+
370
+ /**
371
+ * The accepted values of an enum option, for an error message.
372
+ * @param allowed - the set
373
+ * @returns the quoted values joined by commas
374
+ */
375
+ function list(allowed: ReadonlySet<string>): string {
376
+ return [...allowed].map((v) => JSON.stringify(v)).join(", ");
377
+ }