@graphty/graph-io 0.3.17 → 0.3.19

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 (154) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +64 -3
  3. package/dist/chunks/{escape-scjHxjpr.js → escape-CWExcecC.js} +3 -2
  4. package/dist/chunks/{escape-scjHxjpr.js.map → escape-CWExcecC.js.map} +1 -1
  5. package/dist/chunks/exporter-CbMZyVt-.js +735 -0
  6. package/dist/chunks/exporter-CbMZyVt-.js.map +1 -0
  7. package/dist/chunks/importer-BAP4PrxR.js +2194 -0
  8. package/dist/chunks/importer-BAP4PrxR.js.map +1 -0
  9. package/dist/chunks/{importer-Xg07gk7p.js → importer-BNFuV1-K.js} +5 -3
  10. package/dist/chunks/{importer-Xg07gk7p.js.map → importer-BNFuV1-K.js.map} +1 -1
  11. package/dist/chunks/{importer-BmOl9gLW.js → importer-BVGtU1NA.js} +5 -3
  12. package/dist/chunks/{importer-BmOl9gLW.js.map → importer-BVGtU1NA.js.map} +1 -1
  13. package/dist/chunks/{importer-ruMWWvVs.js → importer-BW-Ft2ps.js} +5 -3
  14. package/dist/chunks/{importer-ruMWWvVs.js.map → importer-BW-Ft2ps.js.map} +1 -1
  15. package/dist/chunks/{importer-B6rKRxzv.js → importer-Bm1_5vPS.js} +781 -104
  16. package/dist/chunks/importer-Bm1_5vPS.js.map +1 -0
  17. package/dist/chunks/importer-ByPGO-09.js +1614 -0
  18. package/dist/chunks/importer-ByPGO-09.js.map +1 -0
  19. package/dist/chunks/importer-DOepkgnG.js +1713 -0
  20. package/dist/chunks/importer-DOepkgnG.js.map +1 -0
  21. package/dist/chunks/json-elements-CZY1wiZh.js +779 -0
  22. package/dist/chunks/json-elements-CZY1wiZh.js.map +1 -0
  23. package/dist/chunks/ontology-BnrJ4I98.js +113 -0
  24. package/dist/chunks/ontology-BnrJ4I98.js.map +1 -0
  25. package/dist/chunks/{records-DSpbTE5s.js → records-BzNicMsf.js} +2 -2
  26. package/dist/chunks/{records-DSpbTE5s.js.map → records-BzNicMsf.js.map} +1 -1
  27. package/dist/chunks/{writer-DHHfHn11.js → report-BcWboivV.js} +106 -928
  28. package/dist/chunks/report-BcWboivV.js.map +1 -0
  29. package/dist/chunks/weights-CwISIpCP.js +176 -0
  30. package/dist/chunks/weights-CwISIpCP.js.map +1 -0
  31. package/dist/chunks/writer-DQiKgQJc.js +656 -0
  32. package/dist/chunks/writer-DQiKgQJc.js.map +1 -0
  33. package/dist/csv.js +5 -3
  34. package/dist/csv.js.map +1 -1
  35. package/dist/cx.d.ts +1 -0
  36. package/dist/cx.js +6 -0
  37. package/dist/cx.js.map +1 -0
  38. package/dist/cx2.d.ts +1 -0
  39. package/dist/cx2.js +10 -0
  40. package/dist/cx2.js.map +1 -0
  41. package/dist/dot.js +1 -1
  42. package/dist/gexf.js +15 -3
  43. package/dist/gexf.js.map +1 -1
  44. package/dist/gml.js +5 -3
  45. package/dist/gml.js.map +1 -1
  46. package/dist/graph-io.js +158 -383
  47. package/dist/graph-io.js.map +1 -1
  48. package/dist/graphml.js +1 -1
  49. package/dist/json.js +1 -1
  50. package/dist/neo4j.js +5 -3
  51. package/dist/neo4j.js.map +1 -1
  52. package/dist/obo.d.ts +1 -0
  53. package/dist/obo.js +6 -0
  54. package/dist/obo.js.map +1 -0
  55. package/dist/pajek.js +1 -1
  56. package/dist/src/common/json-elements.d.ts +286 -0
  57. package/dist/src/common/json-elements.d.ts.map +1 -0
  58. package/dist/src/common/json-elements.js +926 -0
  59. package/dist/src/common/json-elements.js.map +1 -0
  60. package/dist/src/common/ontology.d.ts +59 -0
  61. package/dist/src/common/ontology.d.ts.map +1 -0
  62. package/dist/src/common/ontology.js +147 -0
  63. package/dist/src/common/ontology.js.map +1 -0
  64. package/dist/src/formats/cx/importer.d.ts +133 -0
  65. package/dist/src/formats/cx/importer.d.ts.map +1 -0
  66. package/dist/src/formats/cx/importer.js +2220 -0
  67. package/dist/src/formats/cx/importer.js.map +1 -0
  68. package/dist/src/formats/cx/index.d.ts +7 -0
  69. package/dist/src/formats/cx/index.d.ts.map +1 -0
  70. package/dist/src/formats/cx/index.js +7 -0
  71. package/dist/src/formats/cx/index.js.map +1 -0
  72. package/dist/src/formats/cx2/exporter.d.ts +59 -0
  73. package/dist/src/formats/cx2/exporter.d.ts.map +1 -0
  74. package/dist/src/formats/cx2/exporter.js +864 -0
  75. package/dist/src/formats/cx2/exporter.js.map +1 -0
  76. package/dist/src/formats/cx2/importer.d.ts +169 -0
  77. package/dist/src/formats/cx2/importer.d.ts.map +1 -0
  78. package/dist/src/formats/cx2/importer.js +1652 -0
  79. package/dist/src/formats/cx2/importer.js.map +1 -0
  80. package/dist/src/formats/cx2/index.d.ts +7 -0
  81. package/dist/src/formats/cx2/index.d.ts.map +1 -0
  82. package/dist/src/formats/cx2/index.js +7 -0
  83. package/dist/src/formats/cx2/index.js.map +1 -0
  84. package/dist/src/formats/gexf/exporter.d.ts +2 -0
  85. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  86. package/dist/src/formats/gexf/exporter.js +6 -0
  87. package/dist/src/formats/gexf/exporter.js.map +1 -1
  88. package/dist/src/formats/gexf/importer.d.ts +2 -4
  89. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  90. package/dist/src/formats/gexf/importer.js +2 -4
  91. package/dist/src/formats/gexf/importer.js.map +1 -1
  92. package/dist/src/formats/gexf/index.d.ts +1 -1
  93. package/dist/src/formats/gexf/index.js +2 -2
  94. package/dist/src/formats/gexf/index.js.map +1 -1
  95. package/dist/src/formats/gml/exporter.js +1 -1
  96. package/dist/src/formats/gml/exporter.js.map +1 -1
  97. package/dist/src/formats/json/dialect.d.ts +8 -6
  98. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  99. package/dist/src/formats/json/dialect.js +26 -3
  100. package/dist/src/formats/json/dialect.js.map +1 -1
  101. package/dist/src/formats/json/importer.d.ts +324 -7
  102. package/dist/src/formats/json/importer.d.ts.map +1 -1
  103. package/dist/src/formats/json/importer.js +159 -126
  104. package/dist/src/formats/json/importer.js.map +1 -1
  105. package/dist/src/formats/json/obographs.d.ts +21 -0
  106. package/dist/src/formats/json/obographs.d.ts.map +1 -0
  107. package/dist/src/formats/json/obographs.js +476 -0
  108. package/dist/src/formats/json/obographs.js.map +1 -0
  109. package/dist/src/formats/obo/importer.d.ts +90 -0
  110. package/dist/src/formats/obo/importer.d.ts.map +1 -0
  111. package/dist/src/formats/obo/importer.js +1248 -0
  112. package/dist/src/formats/obo/importer.js.map +1 -0
  113. package/dist/src/formats/obo/index.d.ts +7 -0
  114. package/dist/src/formats/obo/index.d.ts.map +1 -0
  115. package/dist/src/formats/obo/index.js +7 -0
  116. package/dist/src/formats/obo/index.js.map +1 -0
  117. package/dist/src/formats/obo/syntax.d.ts +121 -0
  118. package/dist/src/formats/obo/syntax.d.ts.map +1 -0
  119. package/dist/src/formats/obo/syntax.js +424 -0
  120. package/dist/src/formats/obo/syntax.js.map +1 -0
  121. package/dist/src/index.d.ts +3 -0
  122. package/dist/src/index.d.ts.map +1 -1
  123. package/dist/src/index.js +3 -0
  124. package/dist/src/index.js.map +1 -1
  125. package/dist/src/registry.d.ts.map +1 -1
  126. package/dist/src/registry.js +8 -1
  127. package/dist/src/registry.js.map +1 -1
  128. package/dist/src/sniff.d.ts +1 -1
  129. package/dist/src/sniff.d.ts.map +1 -1
  130. package/dist/src/sniff.js +25 -3
  131. package/dist/src/sniff.js.map +1 -1
  132. package/package.json +16 -1
  133. package/src/common/json-elements.ts +1147 -0
  134. package/src/common/ontology.ts +169 -0
  135. package/src/formats/cx/importer.ts +2733 -0
  136. package/src/formats/cx/index.ts +7 -0
  137. package/src/formats/cx2/exporter.ts +1036 -0
  138. package/src/formats/cx2/importer.ts +2187 -0
  139. package/src/formats/cx2/index.ts +7 -0
  140. package/src/formats/gexf/exporter.ts +11 -0
  141. package/src/formats/gexf/importer.ts +2 -2
  142. package/src/formats/gexf/index.ts +1 -1
  143. package/src/formats/gml/exporter.ts +1 -1
  144. package/src/formats/json/dialect.ts +37 -7
  145. package/src/formats/json/importer.ts +210 -132
  146. package/src/formats/json/obographs.ts +563 -0
  147. package/src/formats/obo/importer.ts +1695 -0
  148. package/src/formats/obo/index.ts +7 -0
  149. package/src/formats/obo/syntax.ts +466 -0
  150. package/src/index.ts +11 -0
  151. package/src/registry.ts +8 -1
  152. package/src/sniff.ts +48 -5
  153. package/dist/chunks/importer-B6rKRxzv.js.map +0 -1
  154. package/dist/chunks/writer-DHHfHn11.js.map +0 -1
@@ -0,0 +1,2194 @@
1
+ import { INVALID_INDEX } from "@graphty/graph-format";
2
+ import { ac as TOO_LARGE_CODE, a2 as SINK_OPTION_CODE, U as OPTION_IGNORED_CODE, j as DIRECTION_FORCED_CODE, k as DIRECTION_REFUSED_CODE, a1 as ROLE_TAKEN_CODE, C as COLUMN_RENAMED_CODE, S as NO_GRAPH_CODE, A as AMBIGUOUS_GRAPH_NAME_CODE, G as GRAPH_NOT_FOUND_CODE, O as MULTIPLE_GRAPHS_CODE, u as ID_MERGED_CODE, Z as PRECISION_CODE, w as ID_TEXT_TYPE_CODE, m as DUPLICATE_EDGE_ID_CODE, o as DUPLICATE_NODE_CODE, M as MISSING_ENDPOINT_CODE, K as MISSING_ID_CODE, a7 as STYLES_NOT_IMPORTED_CODE, X as PARENT_CYCLE_CODE, ag as UNKNOWN_PARENT_CODE, l as DUPLICATE_ATTRIBUTE_CODE, D as DANGLING_REFERENCE_CODE, ad as UNKNOWN_ATTR_TYPE_CODE, aj as WIDENED_CODE, d as BAD_VALUE_CODE, g as COUNT_MISMATCH_CODE, a as ASPECT_ORDER_CODE, B as BAD_ASPECT_BLOCK_CODE, a4 as STATUS_WARNING_CODE, a3 as STATUS_FAILED_CODE, af as UNKNOWN_ENCODING_CODE, r as ENCODING_FALLBACK_CODE, y as INVALID_ENCODING_CODE, z as INVALID_UTF8_CODE, a8 as SYNTAX_CODE, q as EMPTY_INPUT_CODE, aC as resolveImportOptions, I as ImportReportBuilder, aD as textChunks, t as throwIfAborted, aA as reportUnusedOptions, az as reportSinkOptions, an as chooseGraph, p as DirectionResolver, F as IdCoercer, ar as declareResolved, aE as uniqueColumnName } from "./report-BcWboivV.js";
3
+ import { s as scanAspects, r as reportTooDeep, C as CxStructure, i as isRecord, J as JsonScanError, p as plainJson, E as ExactInteger, c as cxId, f as flipY, z as zDecl, a as positionDecl, d as declareFresh, b as inexactLiteral } from "./json-elements-CZY1wiZh.js";
4
+ import { w as weightFromValue } from "./weights-CwISIpCP.js";
5
+ import { z as zAsOption } from "./importer-DOepkgnG.js";
6
+ const CX_FORMAT = "cx";
7
+ const CX_BYPASS_NAMESPACE = "cx.bypass";
8
+ const CX_ISSUE = Object.freeze({
9
+ /** The input is empty (fatal). */
10
+ EMPTY_INPUT: EMPTY_INPUT_CODE,
11
+ /** The text is not JSON (fatal). */
12
+ SYNTAX: SYNTAX_CODE,
13
+ /** Invalid UTF-8 (fatal). */
14
+ INVALID_UTF8: INVALID_UTF8_CODE,
15
+ /** Invalid bytes in the encoding a BOM or the encoding option chose (fatal). */
16
+ INVALID_ENCODING: INVALID_ENCODING_CODE,
17
+ /** Bytes that are not UTF-8 were read as windows-1252. */
18
+ ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
19
+ /** An encoding the platform cannot decode was ignored. */
20
+ UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
21
+ /** The document is not a CX array, or it is CX2 (fatal; the message names CX2). */
22
+ NOT_CX: "E_CX_NOT_CX",
23
+ /** numberVerification holds another value than 2^48 - 1, or comes twice. */
24
+ NUMBER_VERIFICATION: "W_CX_NUMBER_VERIFICATION",
25
+ /** An old Cytoscape aspect name (visualProperties, subNetworks, ...) read under its cy name. */
26
+ OLD_ASPECT_NAME: "W_CX_OLD_ASPECT_NAME",
27
+ /** A cyGroups group whose id is not a node: the group node is added. */
28
+ GROUP_NODE_ADDED: "W_CX_GROUP_NODE_ADDED",
29
+ /** Nodes or edges of the root network that no subnetwork holds: Cytoscape shows them in no network; not read. */
30
+ ROOT_ONLY: "W_CX_ROOT_ONLY",
31
+ /** The producer marked the document as failed (fatal). */
32
+ STATUS_FAILED: STATUS_FAILED_CODE,
33
+ /** The producer marked the document as successful with an error text. */
34
+ STATUS_WARNING: STATUS_WARNING_CODE,
35
+ /** A member of the array that is not a one-key aspect block, or an element that is not an object. */
36
+ BAD_ASPECT_BLOCK: BAD_ASPECT_BLOCK_CODE,
37
+ /** An aspect after the post-metadata or after the status, a third metaData. */
38
+ ASPECT_ORDER: ASPECT_ORDER_CODE,
39
+ /** A metaData element count disagrees with what was read. */
40
+ COUNT_MISMATCH: COUNT_MISMATCH_CODE,
41
+ /** A value that does not parse as its data type; the cell is unset. */
42
+ BAD_VALUE: BAD_VALUE_CODE,
43
+ /** One attribute name with several data types: the column takes the wider one. */
44
+ WIDENED: WIDENED_CODE,
45
+ /** A data type CX does not define; the value is kept as text. */
46
+ UNKNOWN_ATTR_TYPE: UNKNOWN_ATTR_TYPE_CODE,
47
+ /** An attribute, layout, bypass, subnetwork member, view or provenance entry naming nothing. */
48
+ DANGLING_REFERENCE: DANGLING_REFERENCE_CODE,
49
+ /** The same attribute twice on one element (or n and a different name attribute); the later wins. */
50
+ DUPLICATE_ATTRIBUTE: DUPLICATE_ATTRIBUTE_CODE,
51
+ /** A group member naming no node. */
52
+ UNKNOWN_PARENT: UNKNOWN_PARENT_CODE,
53
+ /** A group membership that would close a parent cycle; dropped. */
54
+ PARENT_CYCLE: PARENT_CYCLE_CODE,
55
+ /** The style rules of cyVisualProperties are not applied (issue #706). */
56
+ STYLES_NOT_IMPORTED: STYLES_NOT_IMPORTED_CODE,
57
+ /** A node without an @id. */
58
+ MISSING_ID: MISSING_ID_CODE,
59
+ /** An edge without s or t. */
60
+ MISSING_ENDPOINT: MISSING_ENDPOINT_CODE,
61
+ /** A node id declared twice; the second merges into the first. */
62
+ DUPLICATE_NODE: DUPLICATE_NODE_CODE,
63
+ /** An edge id declared twice; the second edge is skipped. */
64
+ DUPLICATE_EDGE_ID: DUPLICATE_EDGE_ID_CODE,
65
+ /** An id that is not an integer. */
66
+ INVALID_ID: "E_INVALID_ID",
67
+ /** An edge endpoint naming no node of the graph (addMissingNodes false, the default). */
68
+ UNKNOWN_NODE: "E_UNKNOWN_NODE",
69
+ /** A weight that is not a number. */
70
+ INVALID_WEIGHT: "E_INVALID_WEIGHT",
71
+ /** An id spelled as a string or a non-integer literal; read as the integer. */
72
+ ID_TEXT_TYPE: ID_TEXT_TYPE_CODE,
73
+ /** An integer beyond 2^53: an id kept as its digits, a value stored as the nearest f64. */
74
+ PRECISION: PRECISION_CODE,
75
+ /** Two id texts merged under `ids: "number"`. */
76
+ ID_MERGED: ID_MERGED_CODE,
77
+ /** A collection read by import(): the other subnetworks are skipped. */
78
+ MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
79
+ /** graphIndex or graphName names no subnetwork (fatal). */
80
+ GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
81
+ /** graphName names several subnetworks (fatal). */
82
+ AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE,
83
+ /** The input holds no graph (fatal). */
84
+ NO_GRAPH: NO_GRAPH_CODE,
85
+ /** A column renamed because its name was taken. */
86
+ COLUMN_RENAMED: COLUMN_RENAMED_CODE,
87
+ /** A column that lost its role because another column holds it. */
88
+ ROLE_TAKEN: ROLE_TAKEN_CODE,
89
+ /** The sink refused the direction. */
90
+ DIRECTION_REFUSED: DIRECTION_REFUSED_CODE,
91
+ /** Edges forced to the policy's direction. */
92
+ DIRECTION_FORCED: DIRECTION_FORCED_CODE,
93
+ /** A common option CX has no use for. */
94
+ OPTION_IGNORED: OPTION_IGNORED_CODE,
95
+ /** A builder option the caller's sink does not honour. */
96
+ SINK_OPTION: SINK_OPTION_CODE,
97
+ /** The input is beyond a size limit (fatal). */
98
+ TOO_LARGE: TOO_LARGE_CODE
99
+ });
100
+ const USED_OPTIONS = /* @__PURE__ */ new Set([
101
+ "ids",
102
+ "addMissingNodes",
103
+ "duplicateEdges",
104
+ "selfLoops",
105
+ "onMixedDirection",
106
+ "weightFrom",
107
+ "weightDtype",
108
+ "long",
109
+ "errorLimit",
110
+ "signal",
111
+ "onProgress"
112
+ ]);
113
+ const FORMAT_DEFAULTS = {
114
+ ids: "keep",
115
+ defaultDirected: true,
116
+ weightFrom: "weight",
117
+ addMissingNodes: false
118
+ };
119
+ const ABORT_CHECK_INTERVAL = 64;
120
+ const NUMBER_VERIFICATION_VALUES = /* @__PURE__ */ new Set(["281474976710655", "9223372036854775807"]);
121
+ const OLD_ASPECT_NAMES = {
122
+ visualProperties: "cyVisualProperties",
123
+ subNetworks: "cySubNetworks",
124
+ networkRelations: "cyNetworkRelations",
125
+ hiddenAttributes: "cyHiddenAttributes"
126
+ };
127
+ const READ_ASPECTS = /* @__PURE__ */ new Set([
128
+ "nodes",
129
+ "edges",
130
+ "nodeAttributes",
131
+ "edgeAttributes",
132
+ "networkAttributes",
133
+ "cartesianLayout",
134
+ "cySubNetworks",
135
+ "cyNetworkRelations",
136
+ "cyViews",
137
+ "cyGroups",
138
+ "cyVisualProperties",
139
+ "cyTableColumn",
140
+ "citations",
141
+ "supports",
142
+ "nodeCitations",
143
+ "edgeCitations",
144
+ "nodeSupports",
145
+ "edgeSupports",
146
+ "functionTerms",
147
+ "reifiedEdges",
148
+ "numberVerification",
149
+ "metaData",
150
+ "status"
151
+ ]);
152
+ const ALSO_KEPT = /* @__PURE__ */ new Set([
153
+ "cySubNetworks",
154
+ "cyNetworkRelations",
155
+ "cyViews",
156
+ "cyVisualProperties",
157
+ "cyTableColumn",
158
+ "numberVerification",
159
+ "metaData"
160
+ ]);
161
+ const TABLE_STYLE_ASPECTS = ["tableVisualProperties", "cyTableVisualProperties"];
162
+ const CX_FIRST_KEYS = ["numberVerification", "metaData"];
163
+ const CX_ASPECT_KEYS = [
164
+ "nodes",
165
+ "edges",
166
+ "nodeAttributes",
167
+ "edgeAttributes",
168
+ "networkAttributes",
169
+ "cartesianLayout",
170
+ "@context",
171
+ "cySubNetworks",
172
+ "subNetworks",
173
+ "cyNetworkRelations",
174
+ "networkRelations",
175
+ "cyVisualProperties",
176
+ "visualProperties",
177
+ "cyHiddenAttributes",
178
+ "hiddenAttributes",
179
+ "cyTableColumn",
180
+ "cyGroups",
181
+ "cyViews",
182
+ "ndexStatus",
183
+ "provenanceHistory",
184
+ "citations",
185
+ "supports",
186
+ "functionTerms"
187
+ ];
188
+ const SCALARS = {
189
+ string: "string",
190
+ boolean: "boolean",
191
+ integer: "integer",
192
+ long: "long",
193
+ double: "double",
194
+ float: "double"
195
+ };
196
+ function cxType(d) {
197
+ if (d === void 0 || d === null) {
198
+ return { scalar: "string", list: false };
199
+ }
200
+ if (typeof d !== "string") {
201
+ return null;
202
+ }
203
+ const list = d.startsWith("list_of_");
204
+ const scalar = SCALARS[list ? d.slice("list_of_".length) : d];
205
+ return scalar === void 0 ? null : { scalar, list };
206
+ }
207
+ function typeText(type) {
208
+ return type.list ? `list_of_${type.scalar}` : type.scalar;
209
+ }
210
+ const RANK = { boolean: 0, integer: 1, long: 2, double: 3, string: 4 };
211
+ function widenType(a, b) {
212
+ if (a.list !== b.list) {
213
+ return null;
214
+ }
215
+ if (a.scalar === b.scalar) {
216
+ return a;
217
+ }
218
+ const numeric = (s) => s === "integer" || s === "long" || s === "double";
219
+ if (numeric(a.scalar) && numeric(b.scalar)) {
220
+ return RANK[a.scalar] > RANK[b.scalar] ? a : b;
221
+ }
222
+ return { scalar: "string", list: a.list };
223
+ }
224
+ const BAD = /* @__PURE__ */ Symbol("bad");
225
+ const UNSET = /* @__PURE__ */ Symbol("unset");
226
+ const I32_MIN = -2147483648;
227
+ const I32_MAX = 2147483647;
228
+ const INTEGER_TEXT = /^-?[0-9]+$/;
229
+ const DECIMAL_TEXT = /^-?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$/;
230
+ function isNullText(text) {
231
+ return text === "" || text.toLowerCase() === "null";
232
+ }
233
+ function parseScalar(value, scalar, long, onPrecision) {
234
+ if (value instanceof ExactInteger) {
235
+ if (scalar === "string" || scalar === "long" && long === "string") {
236
+ return value.digits;
237
+ }
238
+ if (scalar === "long" || scalar === "double") {
239
+ if (scalar === "long") {
240
+ onPrecision(value.digits);
241
+ }
242
+ return Number(value.digits);
243
+ }
244
+ return BAD;
245
+ }
246
+ if (typeof value === "number" || typeof value === "boolean") {
247
+ return parseNative(value, scalar, long);
248
+ }
249
+ if (typeof value !== "string") {
250
+ return BAD;
251
+ }
252
+ if (scalar === "string") {
253
+ return value;
254
+ }
255
+ if (isNullText(value)) {
256
+ return UNSET;
257
+ }
258
+ switch (scalar) {
259
+ case "boolean": {
260
+ const lower = value.toLowerCase();
261
+ if (lower === "true" || lower === "false") {
262
+ return lower === "true";
263
+ }
264
+ return BAD;
265
+ }
266
+ case "integer":
267
+ case "long": {
268
+ if (value === "NaN") {
269
+ return UNSET;
270
+ }
271
+ if (!INTEGER_TEXT.test(value)) {
272
+ return BAD;
273
+ }
274
+ const n = Number(value);
275
+ if (scalar === "integer") {
276
+ return n >= I32_MIN && n <= I32_MAX ? n : BAD;
277
+ }
278
+ if (long === "string") {
279
+ return value;
280
+ }
281
+ if (!Number.isSafeInteger(n)) {
282
+ onPrecision(value);
283
+ }
284
+ return n;
285
+ }
286
+ default: {
287
+ if (value === "NaN" || value === "nan") {
288
+ return NaN;
289
+ }
290
+ if (value === "Infinity" || value === "-Infinity") {
291
+ return Number(value);
292
+ }
293
+ return DECIMAL_TEXT.test(value) ? Number(value) : BAD;
294
+ }
295
+ }
296
+ }
297
+ function parseNative(value, scalar, long) {
298
+ switch (scalar) {
299
+ case "string":
300
+ return String(value);
301
+ case "boolean":
302
+ return typeof value === "boolean" ? value : BAD;
303
+ case "integer":
304
+ return typeof value === "number" && Number.isInteger(value) && value >= I32_MIN && value <= I32_MAX ? value : BAD;
305
+ case "long":
306
+ if (typeof value !== "number" || !Number.isInteger(value)) {
307
+ return BAD;
308
+ }
309
+ return long === "string" ? String(value) : value;
310
+ default:
311
+ return typeof value === "number" ? value : BAD;
312
+ }
313
+ }
314
+ function parseValue(value, type, long, onPrecision) {
315
+ if (Array.isArray(value) !== type.list) {
316
+ return BAD;
317
+ }
318
+ if (!type.list) {
319
+ return parseScalar(value, type.scalar, long, onPrecision);
320
+ }
321
+ const out = [];
322
+ for (const item of value) {
323
+ let parsed = item === null ? UNSET : parseScalar(item, type.scalar, long, onPrecision);
324
+ if (parsed === UNSET) {
325
+ parsed = type.scalar === "double" ? NaN : BAD;
326
+ }
327
+ if (parsed === BAD) {
328
+ return BAD;
329
+ }
330
+ out.push(parsed);
331
+ }
332
+ return out;
333
+ }
334
+ function scalarDtype(scalar, long) {
335
+ switch (scalar) {
336
+ case "boolean":
337
+ return "bool";
338
+ case "integer":
339
+ return "i32";
340
+ case "long":
341
+ return long === "string" ? "string" : "f64";
342
+ case "double":
343
+ return "f64";
344
+ default:
345
+ return "string";
346
+ }
347
+ }
348
+ function toColumn(value, type) {
349
+ if (type.scalar !== "string") {
350
+ return value;
351
+ }
352
+ if (type.list) {
353
+ return value.map((item) => typeof item === "string" ? item : String(item));
354
+ }
355
+ return typeof value === "string" ? value : String(value);
356
+ }
357
+ function shown(value) {
358
+ if (value instanceof ExactInteger) {
359
+ return value.digits;
360
+ }
361
+ let text;
362
+ try {
363
+ text = JSON.stringify(value) ?? String(value);
364
+ } catch {
365
+ text = String(value);
366
+ }
367
+ return text.length > 60 ? `${text.slice(0, 57)}...` : text;
368
+ }
369
+ function aspect(doc, name) {
370
+ return doc.aspects.get(name) ?? [];
371
+ }
372
+ function inexactBits(text, keys) {
373
+ if (!/[0-9][.eE]/.test(text)) {
374
+ return 0;
375
+ }
376
+ let bits = 0;
377
+ keys.forEach((key, i) => {
378
+ if (inexactLiteral(text, key)) {
379
+ bits |= 1 << i;
380
+ }
381
+ });
382
+ return bits;
383
+ }
384
+ function attributeTable(doc, name) {
385
+ switch (name) {
386
+ case "nodeAttributes":
387
+ return doc.attributes.node;
388
+ case "edgeAttributes":
389
+ return doc.attributes.edge;
390
+ case "networkAttributes":
391
+ return doc.attributes.network;
392
+ default:
393
+ return null;
394
+ }
395
+ }
396
+ function checkNumberVerification(value, count, report, line) {
397
+ const raw = isRecord(value) ? value.longNumber : void 0;
398
+ const digits = raw instanceof ExactInteger ? raw.digits : String(raw);
399
+ if (count > 1 || !NUMBER_VERIFICATION_VALUES.has(digits)) {
400
+ report.warning(
401
+ "validation-error",
402
+ CX_ISSUE.NUMBER_VERIFICATION,
403
+ count > 1 ? "a second numberVerification element; ignored" : `numberVerification holds ${shown(raw === void 0 ? null : digits)}, not 281474976710655`,
404
+ { line, element: "numberVerification" }
405
+ );
406
+ }
407
+ }
408
+ async function readDocument(input, report, options) {
409
+ const structure = new CxStructure(report);
410
+ const doc = {
411
+ aspects: /* @__PURE__ */ new Map(),
412
+ attributes: { node: /* @__PURE__ */ new Map(), edge: /* @__PURE__ */ new Map(), network: /* @__PURE__ */ new Map() },
413
+ kept: /* @__PURE__ */ new Map(),
414
+ structure
415
+ };
416
+ let sinceCheck = 0;
417
+ let verifications = 0;
418
+ const canonical = (name, line) => {
419
+ const renamed = OLD_ASPECT_NAMES[name];
420
+ if (renamed === void 0) {
421
+ return name;
422
+ }
423
+ report.warnOnce(
424
+ "coercion",
425
+ CX_ISSUE.OLD_ASPECT_NAME,
426
+ `the old aspect name "${name}" is read as "${renamed}"`,
427
+ { line, element: name },
428
+ `${CX_ISSUE.OLD_ASPECT_NAME}:${name}`
429
+ );
430
+ return renamed;
431
+ };
432
+ const collect = (name, value, text, line, exact) => {
433
+ structure.element(name, value, line);
434
+ if (ALSO_KEPT.has(name) || !READ_ASPECTS.has(name)) {
435
+ if (!doc.kept.has(name)) {
436
+ doc.kept.set(name, []);
437
+ }
438
+ doc.kept.get(name)?.push(exact ? plainJson(value) : value);
439
+ }
440
+ if (!READ_ASPECTS.has(name) || name === "metaData" || name === "status") {
441
+ return;
442
+ }
443
+ if (name === "numberVerification") {
444
+ checkNumberVerification(value, ++verifications, report, line);
445
+ return;
446
+ }
447
+ let inexact = 0;
448
+ if (name === "nodes") {
449
+ inexact = inexactBits(text, ["@id"]);
450
+ } else if (name === "edges") {
451
+ inexact = inexactBits(text, ["@id", "s", "t"]);
452
+ }
453
+ const held = { value, line, inexact };
454
+ const table = attributeTable(doc, name);
455
+ if (table !== null) {
456
+ if (!isRecord(value) || typeof value.n !== "string") {
457
+ report.error("missing-value", MISSING_ID_CODE, `a ${name} element has no attribute name n; skipped`, {
458
+ line,
459
+ element: name
460
+ });
461
+ return;
462
+ }
463
+ const key = value.n;
464
+ if (!table.has(key)) {
465
+ table.set(key, []);
466
+ }
467
+ table.get(key)?.push(held);
468
+ return;
469
+ }
470
+ if (!doc.aspects.has(name)) {
471
+ doc.aspects.set(name, []);
472
+ }
473
+ doc.aspects.get(name)?.push(held);
474
+ };
475
+ try {
476
+ for await (const event of scanAspects(textChunks(input, report, options))) {
477
+ if (++sinceCheck >= ABORT_CHECK_INTERVAL) {
478
+ sinceCheck = 0;
479
+ throwIfAborted(options.signal);
480
+ }
481
+ switch (event.kind) {
482
+ case "root":
483
+ report.fail(
484
+ CX_ISSUE.NOT_CX,
485
+ `a CX document is a JSON array of aspects; found ${isRecord(event.value) ? "an object" : shown(event.value)}`,
486
+ { line: event.line }
487
+ );
488
+ break;
489
+ case "member": {
490
+ const { value } = event;
491
+ if (isRecord(value) && "CXVersion" in value) {
492
+ report.fail(
493
+ CX_ISSUE.NOT_CX,
494
+ `the document is CX2 (CXVersion ${shown(value.CXVersion)}), not CX version 1; read it with the cx2 importer`,
495
+ { line: event.line }
496
+ );
497
+ }
498
+ const keys = isRecord(value) ? Object.keys(value) : [];
499
+ if (isRecord(value) && keys.length === 1 && isRecord(value[keys[0]])) {
500
+ const name = canonical(keys[0], event.line);
501
+ structure.block(name, event.line);
502
+ collect(name, value[keys[0]], "", event.line, event.exact);
503
+ break;
504
+ }
505
+ report.error(
506
+ "parse-error",
507
+ BAD_ASPECT_BLOCK_CODE,
508
+ `a member of the document is ${keys.length > 1 ? `an object with ${keys.length} keys (${keys.join(", ")})` : shown(value)}, not a one-key aspect fragment; skipped`,
509
+ { line: event.line, element: keys[0] ?? null }
510
+ );
511
+ break;
512
+ }
513
+ case "block":
514
+ structure.block(canonical(event.aspect, event.line), event.line);
515
+ break;
516
+ case "element": {
517
+ const name = OLD_ASPECT_NAMES[event.aspect] ?? event.aspect;
518
+ collect(name, event.value, event.text, event.line, event.exact);
519
+ break;
520
+ }
521
+ case "deep":
522
+ reportTooDeep(report, event);
523
+ break;
524
+ case "extraKeys":
525
+ report.error(
526
+ "parse-error",
527
+ BAD_ASPECT_BLOCK_CODE,
528
+ `the "${event.aspect}" fragment holds more keys (${event.keys.join(", ")}); a fragment has one key, the others are skipped`,
529
+ { line: event.line, element: event.aspect }
530
+ );
531
+ break;
532
+ default:
533
+ break;
534
+ }
535
+ }
536
+ } catch (err) {
537
+ if (err instanceof JsonScanError) {
538
+ report.fail(err.empty ? EMPTY_INPUT_CODE : SYNTAX_CODE, err.message, { line: err.line });
539
+ }
540
+ throw err;
541
+ }
542
+ structure.checkCounts();
543
+ return doc;
544
+ }
545
+ function refId(raw) {
546
+ try {
547
+ return cxId(raw).id;
548
+ } catch {
549
+ return null;
550
+ }
551
+ }
552
+ function memberSet(raw) {
553
+ if (raw === "all") {
554
+ return null;
555
+ }
556
+ const out = /* @__PURE__ */ new Set();
557
+ if (Array.isArray(raw)) {
558
+ for (const item of raw) {
559
+ const id = refId(item);
560
+ if (id !== null) {
561
+ out.add(id);
562
+ }
563
+ }
564
+ }
565
+ return out;
566
+ }
567
+ function graphPlans(doc) {
568
+ const subs = /* @__PURE__ */ new Map();
569
+ for (const { value } of aspect(doc, "cySubNetworks")) {
570
+ const id = isRecord(value) ? refId(value["@id"]) : null;
571
+ if (id !== null && isRecord(value) && !subs.has(id)) {
572
+ subs.set(id, { nodes: memberSet(value.nodes), edges: memberSet(value.edges) });
573
+ }
574
+ }
575
+ const relationNames = /* @__PURE__ */ new Map();
576
+ const order = [];
577
+ const views = /* @__PURE__ */ new Map();
578
+ const addView = (sub, view) => {
579
+ const list = views.get(sub) ?? [];
580
+ if (!list.includes(view)) {
581
+ list.push(view);
582
+ }
583
+ views.set(sub, list);
584
+ };
585
+ for (const { value } of aspect(doc, "cyNetworkRelations")) {
586
+ if (!isRecord(value)) {
587
+ continue;
588
+ }
589
+ const child = refId(value.c);
590
+ if (child === null) {
591
+ continue;
592
+ }
593
+ if (value.r === "view") {
594
+ const parent = refId(value.p);
595
+ if (parent !== null) {
596
+ addView(parent, child);
597
+ }
598
+ continue;
599
+ }
600
+ if (typeof value.name === "string") {
601
+ relationNames.set(child, value.name);
602
+ }
603
+ if (subs.has(child) && !order.includes(child)) {
604
+ order.push(child);
605
+ }
606
+ }
607
+ for (const { value } of aspect(doc, "cyViews")) {
608
+ const view = isRecord(value) ? refId(value["@id"]) : null;
609
+ const sub = isRecord(value) ? refId(value.s) : null;
610
+ if (view !== null && sub !== null) {
611
+ addView(sub, view);
612
+ }
613
+ }
614
+ for (const id of subs.keys()) {
615
+ if (!order.includes(id)) {
616
+ order.push(id);
617
+ }
618
+ }
619
+ const networkName = (sub) => {
620
+ let found = null;
621
+ for (const { value } of doc.attributes.network.get("name") ?? []) {
622
+ if (!isRecord(value) || typeof value.v !== "string") {
623
+ continue;
624
+ }
625
+ const scope = value.s === void 0 ? null : refId(value.s);
626
+ if (scope === sub) {
627
+ return value.v;
628
+ }
629
+ if (scope === null) {
630
+ found ??= value.v;
631
+ }
632
+ }
633
+ return found;
634
+ };
635
+ if (order.length <= 1) {
636
+ const sub = order.length === 1 ? order[0] : null;
637
+ const members = sub === null ? void 0 : subs.get(sub);
638
+ let graphViews = sub === null ? [] : views.get(sub) ?? [];
639
+ if (graphViews.length === 0) {
640
+ graphViews = [...new Set([...views.values()].flat())];
641
+ }
642
+ if (graphViews.length === 0) {
643
+ graphViews = layoutViews(doc);
644
+ }
645
+ return [
646
+ {
647
+ index: 0,
648
+ name: (sub === null ? null : relationNames.get(sub) ?? null) ?? networkName(sub),
649
+ subnetwork: sub,
650
+ nodes: members?.nodes ?? null,
651
+ edges: members?.edges ?? null,
652
+ views: graphViews
653
+ }
654
+ ];
655
+ }
656
+ return order.map((sub, index) => ({
657
+ index,
658
+ name: relationNames.get(sub) ?? networkName(sub),
659
+ subnetwork: sub,
660
+ nodes: subs.get(sub)?.nodes ?? null,
661
+ edges: subs.get(sub)?.edges ?? null,
662
+ views: views.get(sub) ?? []
663
+ }));
664
+ }
665
+ function layoutViews(doc) {
666
+ const out = [];
667
+ for (const { value } of aspect(doc, "cartesianLayout")) {
668
+ const view = isRecord(value) && value.view !== void 0 ? refId(value.view) : null;
669
+ if (view !== null && !out.includes(view)) {
670
+ out.push(view);
671
+ }
672
+ }
673
+ return out;
674
+ }
675
+ function memberCount(members, total) {
676
+ return members === null ? total : members.size;
677
+ }
678
+ class CxReader {
679
+ /**
680
+ * Create the reader.
681
+ * @param sink - the sink
682
+ * @param report - the report
683
+ * @param options - the resolved options
684
+ * @param zAs - where z goes
685
+ * @param doc - the collected document
686
+ * @param plan - the graph to read
687
+ */
688
+ constructor(sink, report, options, zAs, doc, plan) {
689
+ this.rootNodes = /* @__PURE__ */ new Set();
690
+ this.rootEdges = /* @__PURE__ */ new Set();
691
+ this.nodeRows = /* @__PURE__ */ new Map();
692
+ this.edgeRows = /* @__PURE__ */ new Map();
693
+ this.subnetworks = /* @__PURE__ */ new Set();
694
+ this.dangling = /* @__PURE__ */ new Map();
695
+ this.nameHandle = INVALID_INDEX;
696
+ this.representsHandle = INVALID_INDEX;
697
+ this.interactionHandle = INVALID_INDEX;
698
+ this.edgeIdHandle = INVALID_INDEX;
699
+ this.sinceCheck = 0;
700
+ this.idType = "integer";
701
+ this.weighted = false;
702
+ this.coreValues = /* @__PURE__ */ new Map();
703
+ this.weights = null;
704
+ this.sink = sink;
705
+ this.report = report;
706
+ this.options = options;
707
+ this.zAs = zAs;
708
+ this.doc = doc;
709
+ this.plan = plan;
710
+ this.direction = new DirectionResolver(sink, report, options.onMixedDirection);
711
+ this.coercer = new IdCoercer(options.ids);
712
+ for (const { value } of aspect(doc, "nodes")) {
713
+ const id = isRecord(value) ? refId(value["@id"]) : null;
714
+ if (id !== null) {
715
+ this.rootNodes.add(id);
716
+ }
717
+ }
718
+ for (const { value } of aspect(doc, "edges")) {
719
+ const id = isRecord(value) ? refId(value["@id"]) : null;
720
+ if (id !== null) {
721
+ this.rootEdges.add(id);
722
+ }
723
+ }
724
+ for (const { value } of aspect(doc, "cySubNetworks")) {
725
+ const id = isRecord(value) ? refId(value["@id"]) : null;
726
+ if (id !== null) {
727
+ this.subnetworks.add(id);
728
+ }
729
+ }
730
+ }
731
+ /** Build the graph. */
732
+ build() {
733
+ const { report } = this;
734
+ this.direction.setHeader(true);
735
+ this.checkMembers();
736
+ this.checkRootOnly();
737
+ const nodeColumns = this.declareAttributes("node");
738
+ this.readNodes();
739
+ this.readGroups();
740
+ this.writeAttributes("node", nodeColumns);
741
+ this.readLayout();
742
+ const edgeColumns = this.declareAttributes("edge");
743
+ this.readEdges();
744
+ this.writeAttributes("edge", edgeColumns);
745
+ this.readNetworkAttributes();
746
+ this.readVisualProperties();
747
+ this.readProvenance();
748
+ for (const [kind, count] of this.dangling) {
749
+ report.warning("validation-error", DANGLING_REFERENCE_CODE, `${count} ${kind}(s) name nothing; ignored`, {
750
+ element: kind
751
+ });
752
+ }
753
+ this.setMeta();
754
+ }
755
+ // ------------------------------------------------------------ helpers
756
+ /**
757
+ * Count a reference that names nothing.
758
+ * @param kind - what referred
759
+ */
760
+ dangle(kind) {
761
+ this.dangling.set(kind, (this.dangling.get(kind) ?? 0) + 1);
762
+ }
763
+ /** Check the cancellation signal every ABORT_CHECK_INTERVAL elements. */
764
+ checkAbort() {
765
+ if (++this.sinceCheck >= ABORT_CHECK_INTERVAL) {
766
+ this.sinceCheck = 0;
767
+ throwIfAborted(this.options.signal);
768
+ }
769
+ }
770
+ /**
771
+ * Whether a node id belongs to this graph.
772
+ * @param id - the CX id
773
+ * @returns true for a member
774
+ */
775
+ inGraph(id) {
776
+ return this.plan.nodes === null || this.plan.nodes.has(id);
777
+ }
778
+ /**
779
+ * Whether a scoped value applies to this graph: unscoped, or scoped to its subnetwork. A scope
780
+ * naming no subnetwork is counted as dangling.
781
+ * @param scope - the element's s
782
+ * @returns 1 for an unscoped value, 2 for this graph's own, 0 for another graph's
783
+ */
784
+ scopeOf(scope) {
785
+ if (scope === void 0 || scope === null) {
786
+ return 1;
787
+ }
788
+ const id = refId(scope);
789
+ if (id !== null && id === this.plan.subnetwork) {
790
+ return 2;
791
+ }
792
+ if (id === null || !this.subnetworks.has(id)) {
793
+ this.dangle("attribute subnetwork reference");
794
+ }
795
+ return 0;
796
+ }
797
+ /**
798
+ * The id of an element, by the CX id rule and the `ids` option.
799
+ * @param raw - the parsed id
800
+ * @param inexact - whether the literal was not a plain integer
801
+ * @param element - the element name for issues
802
+ * @param edgeId - whether this is an edge's own id (stored in the f64 id column, not kept as digits)
803
+ * @returns the id, or null when it was reported
804
+ */
805
+ idOf(raw, inexact, element, edgeId = false) {
806
+ try {
807
+ const parsed = cxId(raw, inexact);
808
+ if (parsed.note === "text") {
809
+ this.report.warnOnce(
810
+ "coercion",
811
+ ID_TEXT_TYPE_CODE,
812
+ `${element}: the id ${shown(raw)} is not written as an integer; read as ${String(parsed.id)}`,
813
+ { element }
814
+ );
815
+ } else if (parsed.note === "precision" && edgeId) {
816
+ this.report.warnOnce(
817
+ "precision",
818
+ PRECISION_CODE,
819
+ `${element}: the edge id ${String(parsed.id)} is beyond 2^53; the id column holds the nearest double`,
820
+ { element },
821
+ `${PRECISION_CODE}:edge`
822
+ );
823
+ } else if (parsed.note === "precision") {
824
+ this.idType = "mixed";
825
+ this.report.warnOnce(
826
+ "precision",
827
+ PRECISION_CODE,
828
+ `${element}: the id ${String(parsed.id)} is beyond 2^53; kept as its digits (a string id)`,
829
+ { element }
830
+ );
831
+ }
832
+ return this.options.ids === "keep" ? parsed.id : this.coercer.value(parsed.id);
833
+ } catch (err) {
834
+ this.report.recordError(err, { element });
835
+ return null;
836
+ }
837
+ }
838
+ /**
839
+ * Record a long value beyond 2^53 (once).
840
+ * @param name - the attribute
841
+ * @param digits - the value
842
+ */
843
+ precision(name, digits) {
844
+ this.report.warnOnce(
845
+ "precision",
846
+ PRECISION_CODE,
847
+ `"${name}": ${digits} is beyond 2^53; stored as the nearest double`,
848
+ { element: name },
849
+ `${PRECISION_CODE}:value`
850
+ );
851
+ }
852
+ /** Count the subnetwork members that name no node or edge. */
853
+ checkMembers() {
854
+ for (const id of this.plan.nodes ?? []) {
855
+ if (!this.rootNodes.has(id)) {
856
+ this.dangle("subnetwork node member");
857
+ }
858
+ }
859
+ for (const id of this.plan.edges ?? []) {
860
+ if (!this.rootEdges.has(id)) {
861
+ this.dangle("subnetwork edge member");
862
+ }
863
+ }
864
+ }
865
+ /**
866
+ * Report the root network's nodes and edges that no subnetwork holds (W_CX_ROOT_ONLY): they
867
+ * belong to no graph Cytoscape opens, so no graph of this file reads them.
868
+ */
869
+ checkRootOnly() {
870
+ const subs = aspect(this.doc, "cySubNetworks");
871
+ if (subs.length === 0) {
872
+ return;
873
+ }
874
+ const held = { nodes: /* @__PURE__ */ new Set(), edges: /* @__PURE__ */ new Set() };
875
+ const all = { nodes: false, edges: false };
876
+ for (const { value } of subs) {
877
+ if (!isRecord(value)) {
878
+ continue;
879
+ }
880
+ for (const key of ["nodes", "edges"]) {
881
+ const members = memberSet(value[key]);
882
+ if (members === null) {
883
+ all[key] = true;
884
+ } else {
885
+ members.forEach((id) => held[key].add(id));
886
+ }
887
+ }
888
+ }
889
+ const lost = (key, root) => all[key] ? 0 : [...root].filter((id) => !held[key].has(id)).length;
890
+ const nodes = lost("nodes", this.rootNodes);
891
+ const edges = lost("edges", this.rootEdges);
892
+ if (nodes + edges > 0) {
893
+ this.report.warning(
894
+ "unsupported",
895
+ CX_ISSUE.ROOT_ONLY,
896
+ `${nodes} node(s) and ${edges} edge(s) of the root network belong to no subnetwork (cySubNetworks); they are not read`,
897
+ { element: "cySubNetworks" }
898
+ );
899
+ }
900
+ }
901
+ /**
902
+ * The handle of a structural column, declared on first use.
903
+ * @param domain - node or edge
904
+ * @param current - the handle so far
905
+ * @param decl - the declaration
906
+ * @returns the handle
907
+ */
908
+ structural(domain, current, decl) {
909
+ return current === INVALID_INDEX ? declareResolved(this.sink, domain, decl, this.report).handle : current;
910
+ }
911
+ // ------------------------------------------------------------ attribute columns
912
+ /**
913
+ * Type and declare the attribute columns of a table for this graph: each name's type is the
914
+ * wider of the types its applicable elements and its cyTableColumn entries give (W_WIDENED when
915
+ * they differ); a name with a type CX does not define keeps its values as text.
916
+ * @param domain - node or edge
917
+ * @returns the columns by attribute name
918
+ */
919
+ declareAttributes(domain) {
920
+ const columns = /* @__PURE__ */ new Map();
921
+ const declared = this.tableColumns(domain === "node" ? "node_table" : "edge_table");
922
+ const names = /* @__PURE__ */ new Set([...declared.keys(), ...this.doc.attributes[domain].keys()]);
923
+ for (const name of names) {
924
+ if (name === "" || domain === "edge" && name === this.options.weightFrom) {
925
+ continue;
926
+ }
927
+ const types = [];
928
+ let unknown = false;
929
+ const tableType = declared.get(name);
930
+ if (tableType !== void 0) {
931
+ types.push(tableType);
932
+ }
933
+ for (const { value } of this.doc.attributes[domain].get(name) ?? []) {
934
+ if (!isRecord(value) || this.scopePeek(value.s) === 0) {
935
+ continue;
936
+ }
937
+ const type2 = cxType(value.d);
938
+ if (type2 === null) {
939
+ unknown = true;
940
+ continue;
941
+ }
942
+ if (!types.some((t) => t.scalar === type2.scalar && t.list === type2.list)) {
943
+ types.push(type2);
944
+ }
945
+ }
946
+ if (unknown) {
947
+ this.report.warnOnce(
948
+ "unsupported",
949
+ UNKNOWN_ATTR_TYPE_CODE,
950
+ `a ${domain} attribute "${name}" declares a data type CX does not define; its values are kept as text`,
951
+ { element: name },
952
+ `${UNKNOWN_ATTR_TYPE_CODE}:${domain}:${name}`
953
+ );
954
+ types.push({ scalar: "string", list: false });
955
+ }
956
+ let type = types[0] ?? { scalar: "string", list: false };
957
+ for (const other of types.slice(1)) {
958
+ type = type === null ? null : widenType(type, other);
959
+ }
960
+ if (types.length > 1) {
961
+ this.report.warnOnce(
962
+ "coercion",
963
+ WIDENED_CODE,
964
+ `the ${domain} attribute "${name}" has the data types ${types.map(typeText).join(", ")}; the column holds ${type === null ? "their values as JSON" : typeText(type)}`,
965
+ { element: name },
966
+ `${WIDENED_CODE}:${domain}:${name}`
967
+ );
968
+ }
969
+ if (this.isStructural(domain, name)) {
970
+ columns.set(name, {
971
+ name,
972
+ type: { scalar: "string", list: false },
973
+ handle: INVALID_INDEX
974
+ });
975
+ continue;
976
+ }
977
+ columns.set(name, { name, type, handle: this.declareColumn(domain, name, type) });
978
+ }
979
+ return columns;
980
+ }
981
+ /**
982
+ * Whether an attribute name is the column of a core field (n / r on nodes, i on edges).
983
+ * @param domain - node or edge
984
+ * @param name - the attribute name
985
+ * @returns true for name, represents and interaction
986
+ */
987
+ isStructural(domain, name) {
988
+ return domain === "node" ? name === "name" || name === "represents" : name === "interaction";
989
+ }
990
+ /**
991
+ * The scope of a value without counting a dangling one (for typing, which runs before the values).
992
+ * @param scope - the element's s
993
+ * @returns as scopeOf()
994
+ */
995
+ scopePeek(scope) {
996
+ if (scope === void 0 || scope === null) {
997
+ return 1;
998
+ }
999
+ const id = refId(scope);
1000
+ return id !== null && id === this.plan.subnetwork ? 2 : 0;
1001
+ }
1002
+ /**
1003
+ * The column types cyTableColumn declares for one table and this graph.
1004
+ * @param table - node_table, edge_table or network_table
1005
+ * @returns name -> type
1006
+ */
1007
+ tableColumns(table) {
1008
+ const out = /* @__PURE__ */ new Map();
1009
+ for (const { value } of aspect(this.doc, "cyTableColumn")) {
1010
+ if (!isRecord(value) || value.applies_to !== table || typeof value.n !== "string") {
1011
+ continue;
1012
+ }
1013
+ if (this.scopePeek(value.s) === 0) {
1014
+ continue;
1015
+ }
1016
+ const type = cxType(value.d);
1017
+ if (type !== null) {
1018
+ out.set(value.n, type);
1019
+ }
1020
+ }
1021
+ return out;
1022
+ }
1023
+ /**
1024
+ * Declare one attribute column.
1025
+ * @param domain - node or edge
1026
+ * @param name - the attribute name
1027
+ * @param type - the type, or null for a json column
1028
+ * @returns the handle
1029
+ */
1030
+ declareColumn(domain, name, type) {
1031
+ const { long } = this.options;
1032
+ const decl = type === null ? { name, dtype: "json", nullable: true, origin: { format: CX_FORMAT, id: null } } : {
1033
+ name,
1034
+ dtype: type.list ? "list" : scalarDtype(type.scalar, long),
1035
+ ...type.list ? { itemDtype: scalarDtype(type.scalar, long) } : {},
1036
+ nullable: true,
1037
+ origin: { format: CX_FORMAT, id: null, type: typeText(type) }
1038
+ };
1039
+ return declareResolved(this.sink, domain, decl, this.report).handle;
1040
+ }
1041
+ /**
1042
+ * Write the attribute values of a table: per element, its own subnetwork's value beats the
1043
+ * unscoped one; a repeated value of the same scope is the later one (W_DUPLICATE_ATTRIBUTE
1044
+ * when they differ).
1045
+ * @param domain - node or edge
1046
+ * @param columns - the columns from declareAttributes()
1047
+ */
1048
+ writeAttributes(domain, columns) {
1049
+ const rows = domain === "node" ? this.nodeRows : this.edgeRows;
1050
+ const root = domain === "node" ? this.rootNodes : this.rootEdges;
1051
+ for (const column of columns.values()) {
1052
+ const chosen = /* @__PURE__ */ new Map();
1053
+ for (const held of this.doc.attributes[domain].get(column.name) ?? []) {
1054
+ this.checkAbort();
1055
+ const { value, line } = held;
1056
+ if (!isRecord(value)) {
1057
+ this.report.error(
1058
+ "parse-error",
1059
+ BAD_ASPECT_BLOCK_CODE,
1060
+ `a ${domain}Attributes element is not an object`,
1061
+ {
1062
+ line,
1063
+ element: `${domain}Attributes`
1064
+ }
1065
+ );
1066
+ continue;
1067
+ }
1068
+ const scope = this.scopeOf(value.s);
1069
+ if (scope === 0) {
1070
+ continue;
1071
+ }
1072
+ const target = refId(value.po);
1073
+ const row = target === null ? void 0 : rows.get(this.options.ids === "keep" ? target : this.coercer.value(target));
1074
+ if (row === void 0) {
1075
+ if (target === null || !root.has(target)) {
1076
+ this.dangle(`${domain} attribute target`);
1077
+ }
1078
+ continue;
1079
+ }
1080
+ if (value.v === void 0) {
1081
+ this.report.error(
1082
+ "missing-value",
1083
+ BAD_VALUE_CODE,
1084
+ `a ${domain}Attributes element for "${column.name}" has no v; skipped`,
1085
+ {
1086
+ line,
1087
+ element: column.name
1088
+ }
1089
+ );
1090
+ continue;
1091
+ }
1092
+ if (value.v === null) {
1093
+ continue;
1094
+ }
1095
+ const previous = chosen.get(row);
1096
+ if (previous !== void 0 && previous.scope > scope) {
1097
+ continue;
1098
+ }
1099
+ if (previous?.scope === scope && JSON.stringify(previous.value) !== JSON.stringify(plainJson(value.v))) {
1100
+ this.report.warnOnce(
1101
+ "validation-error",
1102
+ DUPLICATE_ATTRIBUTE_CODE,
1103
+ `${domain} ${shown(value.po)} has the attribute "${column.name}" twice; the later value wins`,
1104
+ { line, element: column.name },
1105
+ `${DUPLICATE_ATTRIBUTE_CODE}:${domain}:${column.name}`
1106
+ );
1107
+ }
1108
+ chosen.set(row, { scope, value: value.v, d: value.d, line });
1109
+ }
1110
+ for (const [row, { value, d, line }] of chosen) {
1111
+ this.writeCell(domain, column, row, value, d, line);
1112
+ }
1113
+ }
1114
+ }
1115
+ /**
1116
+ * Parse and write one attribute cell. The core fields' attributes (name, represents,
1117
+ * interaction) go into their columns; a name attribute that differs from n wins with
1118
+ * W_DUPLICATE_ATTRIBUTE.
1119
+ * @param domain - node or edge
1120
+ * @param column - the column
1121
+ * @param row - the row
1122
+ * @param raw - the value
1123
+ * @param d - the data type the value was written with
1124
+ * @param line - its line
1125
+ */
1126
+ writeCell(domain, column, row, raw, d, line) {
1127
+ const element = `${domain} attribute "${column.name}"`;
1128
+ let value;
1129
+ if (column.type === null) {
1130
+ value = plainJson(raw, (digits) => {
1131
+ this.precision(column.name, digits);
1132
+ });
1133
+ } else {
1134
+ const own = cxType(d) ?? { scalar: "string", list: false };
1135
+ const parsed = parseValue(raw, own, this.options.long, (digits) => {
1136
+ this.precision(column.name, digits);
1137
+ });
1138
+ if (parsed === UNSET) {
1139
+ return;
1140
+ }
1141
+ if (parsed === BAD) {
1142
+ this.report.error(
1143
+ "validation-error",
1144
+ BAD_VALUE_CODE,
1145
+ `${element}: ${shown(raw)} is not a ${typeText(own)}; the cell is unset`,
1146
+ { line, element: column.name }
1147
+ );
1148
+ return;
1149
+ }
1150
+ value = toColumn(parsed, column.type);
1151
+ }
1152
+ let { handle } = column;
1153
+ if (this.isStructural(domain, column.name)) {
1154
+ handle = this.coreColumn(domain, column.name);
1155
+ const prior = this.coreValue(domain, row, column.name);
1156
+ if (prior !== void 0 && prior !== value) {
1157
+ this.report.warnOnce(
1158
+ "validation-error",
1159
+ DUPLICATE_ATTRIBUTE_CODE,
1160
+ `a ${domain} has "${column.name}" both as its core field and as an attribute with another value; the attribute wins`,
1161
+ { line, element: column.name },
1162
+ `${DUPLICATE_ATTRIBUTE_CODE}:core:${column.name}`
1163
+ );
1164
+ }
1165
+ }
1166
+ try {
1167
+ if (domain === "node") {
1168
+ this.sink.setNodeValue(handle, row, value);
1169
+ } else {
1170
+ this.sink.setEdgeValue(handle, row, value);
1171
+ }
1172
+ } catch (err) {
1173
+ this.report.recordError(err, { line, element: column.name });
1174
+ }
1175
+ }
1176
+ /**
1177
+ * The core field value of a row.
1178
+ * @param domain - node or edge
1179
+ * @param row - the row
1180
+ * @param name - name, represents or interaction
1181
+ * @returns the value, or undefined
1182
+ */
1183
+ coreValue(domain, row, name) {
1184
+ return this.coreValues.get(`${domain}:${name}`)?.get(row);
1185
+ }
1186
+ /**
1187
+ * The column of a core field.
1188
+ * @param domain - node or edge
1189
+ * @param name - name, represents or interaction
1190
+ * @returns the handle
1191
+ */
1192
+ coreColumn(domain, name) {
1193
+ if (domain === "edge") {
1194
+ this.interactionHandle = this.structural("edge", this.interactionHandle, {
1195
+ name: "interaction",
1196
+ dtype: "string",
1197
+ nullable: true,
1198
+ origin: { format: CX_FORMAT, id: "i", type: "string" }
1199
+ });
1200
+ return this.interactionHandle;
1201
+ }
1202
+ if (name === "name") {
1203
+ this.nameHandle = this.structural("node", this.nameHandle, {
1204
+ name: "name",
1205
+ dtype: "string",
1206
+ role: "label",
1207
+ nullable: true,
1208
+ origin: { format: CX_FORMAT, id: "n", type: "string" }
1209
+ });
1210
+ return this.nameHandle;
1211
+ }
1212
+ this.representsHandle = this.structural("node", this.representsHandle, {
1213
+ name: "represents",
1214
+ dtype: "string",
1215
+ nullable: true,
1216
+ origin: { format: CX_FORMAT, id: "r", type: "string" }
1217
+ });
1218
+ return this.representsHandle;
1219
+ }
1220
+ // ------------------------------------------------------------ nodes, groups, layout
1221
+ /** Read the nodes of this graph. */
1222
+ readNodes() {
1223
+ const { report, sink } = this;
1224
+ for (const held of aspect(this.doc, "nodes")) {
1225
+ this.checkAbort();
1226
+ const { value, line } = held;
1227
+ if (!isRecord(value)) {
1228
+ report.error(
1229
+ "parse-error",
1230
+ BAD_ASPECT_BLOCK_CODE,
1231
+ `a nodes element is ${shown(value)}, not an object`,
1232
+ {
1233
+ line,
1234
+ element: "nodes"
1235
+ }
1236
+ );
1237
+ report.counts.skippedNodes++;
1238
+ continue;
1239
+ }
1240
+ if (value["@id"] === void 0 || value["@id"] === null) {
1241
+ report.error("missing-value", MISSING_ID_CODE, "a node has no @id", { line, element: "nodes" });
1242
+ report.counts.skippedNodes++;
1243
+ continue;
1244
+ }
1245
+ const element = `node ${shown(value["@id"])}`;
1246
+ const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element);
1247
+ if (id === null) {
1248
+ report.counts.skippedNodes++;
1249
+ continue;
1250
+ }
1251
+ if (!this.inGraph(id)) {
1252
+ continue;
1253
+ }
1254
+ let row = this.nodeRows.get(id);
1255
+ if (row !== void 0) {
1256
+ report.warning(
1257
+ "merged",
1258
+ DUPLICATE_NODE_CODE,
1259
+ `${element} is declared more than once; its fields are merged (the later values win)`,
1260
+ { line, element }
1261
+ );
1262
+ } else {
1263
+ try {
1264
+ row = sink.addNode(id);
1265
+ } catch (err) {
1266
+ report.recordError(err, { line, element });
1267
+ report.counts.skippedNodes++;
1268
+ continue;
1269
+ }
1270
+ this.nodeRows.set(id, row);
1271
+ report.counts.nodes++;
1272
+ }
1273
+ this.writeCore("node", row, "name", value.n, element, line);
1274
+ this.writeCore("node", row, "represents", value.r, element, line);
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Write a core string field (n, r, i).
1279
+ * @param domain - node or edge
1280
+ * @param row - the row
1281
+ * @param name - the column
1282
+ * @param raw - the value
1283
+ * @param element - the element name
1284
+ * @param line - its line
1285
+ */
1286
+ writeCore(domain, row, name, raw, element, line) {
1287
+ if (raw === void 0 || raw === null) {
1288
+ return;
1289
+ }
1290
+ if (typeof raw !== "string") {
1291
+ this.report.error(
1292
+ "validation-error",
1293
+ BAD_VALUE_CODE,
1294
+ `${element}: ${name} is ${shown(raw)}, not a string`,
1295
+ {
1296
+ line,
1297
+ element
1298
+ }
1299
+ );
1300
+ return;
1301
+ }
1302
+ const handle = this.coreColumn(domain, name);
1303
+ if (domain === "node") {
1304
+ this.sink.setNodeValue(handle, row, raw);
1305
+ } else {
1306
+ this.sink.setEdgeValue(handle, row, raw);
1307
+ }
1308
+ const key = `${domain}:${name}`;
1309
+ let values = this.coreValues.get(key);
1310
+ if (values === void 0) {
1311
+ values = /* @__PURE__ */ new Map();
1312
+ this.coreValues.set(key, values);
1313
+ }
1314
+ values.set(row, raw);
1315
+ }
1316
+ /**
1317
+ * Read cyGroups: a group's members get the group node as their parent (parents when a node is in
1318
+ * several groups); a group whose id is not a node gets one (W_CX_GROUP_NODE_ADDED); collapsed is
1319
+ * a bool column on the group node.
1320
+ */
1321
+ readGroups() {
1322
+ const groups = aspect(this.doc, "cyGroups");
1323
+ if (groups.length === 0) {
1324
+ return;
1325
+ }
1326
+ const parents = /* @__PURE__ */ new Map();
1327
+ const collapsed = [];
1328
+ for (const { value, line } of groups) {
1329
+ if (!isRecord(value)) {
1330
+ continue;
1331
+ }
1332
+ const id = refId(value["@id"]);
1333
+ if (id === null) {
1334
+ this.report.error("missing-value", MISSING_ID_CODE, "a cyGroups element has no @id", {
1335
+ line,
1336
+ element: "cyGroups"
1337
+ });
1338
+ continue;
1339
+ }
1340
+ let groupRow = this.nodeRows.get(id);
1341
+ if (groupRow === void 0) {
1342
+ if (this.rootNodes.has(id) || !this.hasMemberIn(value.nodes)) {
1343
+ continue;
1344
+ }
1345
+ try {
1346
+ groupRow = this.sink.addNode(id);
1347
+ } catch (err) {
1348
+ this.report.recordError(err, { line, element: String(id) });
1349
+ continue;
1350
+ }
1351
+ this.nodeRows.set(id, groupRow);
1352
+ this.report.counts.nodes++;
1353
+ this.report.warning(
1354
+ "coercion",
1355
+ CX_ISSUE.GROUP_NODE_ADDED,
1356
+ `group ${String(id)} is not a node; its group node is added`,
1357
+ { line, element: String(id) }
1358
+ );
1359
+ }
1360
+ if (typeof value.n === "string" && this.coreValue("node", groupRow, "name") === void 0) {
1361
+ this.writeCore("node", groupRow, "name", value.n, `group ${String(id)}`, line);
1362
+ }
1363
+ if (typeof value.collapsed === "boolean") {
1364
+ collapsed.push([groupRow, value.collapsed]);
1365
+ }
1366
+ for (const raw of Array.isArray(value.nodes) ? value.nodes : []) {
1367
+ const member = refId(raw);
1368
+ const row = member === null ? void 0 : this.nodeRows.get(member);
1369
+ if (row === void 0) {
1370
+ if (member === null || !this.rootNodes.has(member)) {
1371
+ this.report.error(
1372
+ "missing-value",
1373
+ UNKNOWN_PARENT_CODE,
1374
+ `group ${String(id)}: the member ${shown(raw)} is not a node`,
1375
+ { line, element: String(id) }
1376
+ );
1377
+ }
1378
+ continue;
1379
+ }
1380
+ if (this.closesCycle(parents, row, groupRow)) {
1381
+ this.report.error(
1382
+ "validation-error",
1383
+ PARENT_CYCLE_CODE,
1384
+ `group ${String(id)}: making ${shown(raw)} a member would close a parent cycle; that membership is dropped`,
1385
+ { line, element: String(id) }
1386
+ );
1387
+ continue;
1388
+ }
1389
+ const list = parents.get(row) ?? [];
1390
+ if (!list.includes(groupRow)) {
1391
+ list.push(groupRow);
1392
+ }
1393
+ parents.set(row, list);
1394
+ }
1395
+ }
1396
+ this.writeParents(parents);
1397
+ if (collapsed.length > 0) {
1398
+ const { handle } = declareResolved(
1399
+ this.sink,
1400
+ "node",
1401
+ {
1402
+ name: "collapsed",
1403
+ dtype: "bool",
1404
+ nullable: true,
1405
+ origin: { format: CX_FORMAT, namespace: "cyGroups" }
1406
+ },
1407
+ this.report
1408
+ );
1409
+ for (const [row, flag] of collapsed) {
1410
+ this.sink.setNodeValue(handle, row, flag);
1411
+ }
1412
+ }
1413
+ }
1414
+ /**
1415
+ * Whether a member list names a node of this graph.
1416
+ * @param raw - the list
1417
+ * @returns true when one member is in the graph
1418
+ */
1419
+ hasMemberIn(raw) {
1420
+ return Array.isArray(raw) && raw.some((item) => {
1421
+ const id = refId(item);
1422
+ return id !== null && this.nodeRows.has(id);
1423
+ });
1424
+ }
1425
+ /**
1426
+ * Whether making `child` a member of `group` closes a cycle: the group already is, through its
1427
+ * own parents, below the child.
1428
+ * @param parents - the memberships so far
1429
+ * @param child - the member row
1430
+ * @param group - the group row
1431
+ * @returns true for a cycle
1432
+ */
1433
+ closesCycle(parents, child, group) {
1434
+ const stack = [group];
1435
+ const seen = /* @__PURE__ */ new Set();
1436
+ while (stack.length > 0) {
1437
+ const row = stack.pop();
1438
+ if (row === child) {
1439
+ return true;
1440
+ }
1441
+ if (seen.has(row)) {
1442
+ continue;
1443
+ }
1444
+ seen.add(row);
1445
+ stack.push(...parents.get(row) ?? []);
1446
+ }
1447
+ return false;
1448
+ }
1449
+ /**
1450
+ * Write the group memberships: a parent column, or a parents list column when a node is in
1451
+ * several groups.
1452
+ * @param parents - member row -> group rows
1453
+ */
1454
+ writeParents(parents) {
1455
+ if (parents.size === 0) {
1456
+ return;
1457
+ }
1458
+ const several = [...parents.values()].some((list) => list.length > 1);
1459
+ const decl = several ? { name: "parents", dtype: "list", itemDtype: "u32", role: "parents", refersTo: "node", nullable: true } : { name: "parent", dtype: "u32", role: "parent", refersTo: "node", nullable: true };
1460
+ const { handle } = declareResolved(this.sink, "node", decl, this.report);
1461
+ for (const [row, list] of parents) {
1462
+ this.sink.setNodeValue(handle, row, several ? list : list[0]);
1463
+ }
1464
+ }
1465
+ /**
1466
+ * Read cartesianLayout for this graph's views: the first view is the position (y flipped), z the
1467
+ * z column, every other view a position@n column.
1468
+ */
1469
+ readLayout() {
1470
+ const entries = aspect(this.doc, "cartesianLayout");
1471
+ if (entries.length === 0) {
1472
+ return;
1473
+ }
1474
+ const { views } = this.plan;
1475
+ const knownViews = this.allViews();
1476
+ const columns = /* @__PURE__ */ new Map();
1477
+ const zHandle = { current: INVALID_INDEX };
1478
+ const seen = /* @__PURE__ */ new Set();
1479
+ const point = [0, 0, 0];
1480
+ for (const { value, line } of entries) {
1481
+ this.checkAbort();
1482
+ if (!isRecord(value)) {
1483
+ continue;
1484
+ }
1485
+ const view = value.view === void 0 || value.view === null ? null : refId(value.view);
1486
+ if (view !== null && knownViews.size > 0 && !knownViews.has(view)) {
1487
+ this.dangle("layout view reference");
1488
+ continue;
1489
+ }
1490
+ const slot = view === null ? 0 : views.indexOf(view);
1491
+ if (slot < 0) {
1492
+ continue;
1493
+ }
1494
+ if (typeof value.x !== "number" || typeof value.y !== "number") {
1495
+ this.report.error(
1496
+ "validation-error",
1497
+ BAD_VALUE_CODE,
1498
+ `a cartesianLayout element for node ${shown(value.node)} has no numeric x and y; skipped`,
1499
+ { line, element: "cartesianLayout" }
1500
+ );
1501
+ continue;
1502
+ }
1503
+ const node = refId(value.node);
1504
+ const row = node === null ? void 0 : this.nodeRows.get(node);
1505
+ if (row === void 0) {
1506
+ if (node === null || !this.rootNodes.has(node)) {
1507
+ this.dangle("layout entry");
1508
+ }
1509
+ continue;
1510
+ }
1511
+ const key = `${slot}:${row}`;
1512
+ if (seen.has(key)) {
1513
+ this.report.warnOnce(
1514
+ "validation-error",
1515
+ DUPLICATE_ATTRIBUTE_CODE,
1516
+ `node ${shown(value.node)} has two layout entries for one view; the later wins`,
1517
+ { line, element: "cartesianLayout" },
1518
+ `${DUPLICATE_ATTRIBUTE_CODE}:layout`
1519
+ );
1520
+ }
1521
+ seen.add(key);
1522
+ const z = typeof value.z === "number" ? value.z : null;
1523
+ point[0] = value.x;
1524
+ point[1] = flipY(value.y);
1525
+ point[2] = slot === 0 && this.zAs === "position" && z !== null ? z : 0;
1526
+ this.sink.setNodeValue(this.layoutColumn(columns, slot, views), row, point);
1527
+ if (slot === 0 && z !== null && this.zAs === "column") {
1528
+ zHandle.current = this.structural("node", zHandle.current, zDecl(CX_FORMAT));
1529
+ this.sink.setNodeValue(zHandle.current, row, z);
1530
+ }
1531
+ }
1532
+ }
1533
+ /**
1534
+ * Every view id the document names.
1535
+ * @returns the set (empty when the document names none)
1536
+ */
1537
+ allViews() {
1538
+ const out = /* @__PURE__ */ new Set();
1539
+ for (const { value } of aspect(this.doc, "cyNetworkRelations")) {
1540
+ if (isRecord(value) && value.r === "view") {
1541
+ const id = refId(value.c);
1542
+ if (id !== null) {
1543
+ out.add(id);
1544
+ }
1545
+ }
1546
+ }
1547
+ for (const { value } of aspect(this.doc, "cyViews")) {
1548
+ const id = isRecord(value) ? refId(value["@id"]) : null;
1549
+ if (id !== null) {
1550
+ out.add(id);
1551
+ }
1552
+ }
1553
+ return out;
1554
+ }
1555
+ /**
1556
+ * The position column of a view slot, declared on first use.
1557
+ * @param columns - slot -> handle
1558
+ * @param slot - 0 for the position, n - 1 for position@n
1559
+ * @param views - the graph's views
1560
+ * @returns the handle
1561
+ */
1562
+ layoutColumn(columns, slot, views) {
1563
+ let handle = columns.get(slot);
1564
+ if (handle === void 0) {
1565
+ const base = positionDecl(CX_FORMAT, this.zAs === "position" && slot === 0 ? 3 : 2);
1566
+ const decl = slot === 0 ? base : {
1567
+ ...base,
1568
+ name: `position@${slot + 1}`,
1569
+ role: void 0,
1570
+ origin: { format: CX_FORMAT, namespace: "cytoscape", id: String(views[slot]) }
1571
+ };
1572
+ ({ handle } = declareResolved(this.sink, "node", decl, this.report));
1573
+ columns.set(slot, handle);
1574
+ }
1575
+ return handle;
1576
+ }
1577
+ // ------------------------------------------------------------ edges
1578
+ /** Read the edges of this graph. */
1579
+ readEdges() {
1580
+ const { report, sink } = this;
1581
+ const members = this.plan.edges;
1582
+ for (const held of aspect(this.doc, "edges")) {
1583
+ this.checkAbort();
1584
+ const { value, line } = held;
1585
+ if (!isRecord(value)) {
1586
+ report.error(
1587
+ "parse-error",
1588
+ BAD_ASPECT_BLOCK_CODE,
1589
+ `an edges element is ${shown(value)}, not an object`,
1590
+ {
1591
+ line,
1592
+ element: "edges"
1593
+ }
1594
+ );
1595
+ report.counts.skippedEdges++;
1596
+ continue;
1597
+ }
1598
+ if (value["@id"] === void 0 || value["@id"] === null) {
1599
+ report.error("missing-value", MISSING_ID_CODE, "an edge has no @id", { line, element: "edges" });
1600
+ report.counts.skippedEdges++;
1601
+ continue;
1602
+ }
1603
+ const element = `edge ${shown(value["@id"])}`;
1604
+ const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element, true);
1605
+ if (id === null) {
1606
+ report.counts.skippedEdges++;
1607
+ continue;
1608
+ }
1609
+ if (members !== null && !members.has(id)) {
1610
+ continue;
1611
+ }
1612
+ if (value.s === void 0 || value.s === null || value.t === void 0 || value.t === null) {
1613
+ report.error(
1614
+ "missing-value",
1615
+ MISSING_ENDPOINT_CODE,
1616
+ `${element} has no ${value.s === void 0 || value.s === null ? "s" : "t"}`,
1617
+ {
1618
+ line,
1619
+ element
1620
+ }
1621
+ );
1622
+ report.counts.skippedEdges++;
1623
+ continue;
1624
+ }
1625
+ const s = this.idOf(value.s, (held.inexact & 2) !== 0, element);
1626
+ const t = s === null ? null : this.idOf(value.t, (held.inexact & 4) !== 0, element);
1627
+ if (s === null || t === null) {
1628
+ report.counts.skippedEdges++;
1629
+ continue;
1630
+ }
1631
+ if (this.edgeRows.has(id)) {
1632
+ report.error(
1633
+ "validation-error",
1634
+ DUPLICATE_EDGE_ID_CODE,
1635
+ `${element} is declared more than once; the later edge is skipped`,
1636
+ { line, element }
1637
+ );
1638
+ report.counts.skippedEdges++;
1639
+ continue;
1640
+ }
1641
+ if (!this.endpoint(s, element, line) || !this.endpoint(t, element, line)) {
1642
+ report.counts.skippedEdges++;
1643
+ continue;
1644
+ }
1645
+ let weight;
1646
+ try {
1647
+ weight = this.weightOf(id);
1648
+ } catch (err) {
1649
+ report.recordError(err, { line, element });
1650
+ report.counts.skippedEdges++;
1651
+ continue;
1652
+ }
1653
+ const before = sink.edgeCount;
1654
+ let edge;
1655
+ try {
1656
+ edge = this.direction.addEdge(s, t, "directed", weight, { line, element });
1657
+ } catch (err) {
1658
+ report.recordError(err, { line, element });
1659
+ report.counts.skippedEdges++;
1660
+ continue;
1661
+ }
1662
+ if (weight !== void 0) {
1663
+ this.weighted = true;
1664
+ }
1665
+ report.counts.edges += sink.edgeCount - before;
1666
+ this.edgeRows.set(id, edge);
1667
+ this.edgeIdHandle = this.structural("edge", this.edgeIdHandle, {
1668
+ name: uniqueColumnName("id", "cx", (n) => sink.edgeColumn(n) !== INVALID_INDEX),
1669
+ dtype: "f64",
1670
+ role: "id",
1671
+ nullable: true
1672
+ });
1673
+ sink.setEdgeValue(this.edgeIdHandle, edge, typeof id === "number" ? id : Number(id));
1674
+ this.writeCore("edge", edge, "interaction", value.i, element, line);
1675
+ }
1676
+ }
1677
+ /**
1678
+ * Whether an endpoint is a node of this graph; under addMissingNodes it is created.
1679
+ * @param id - the endpoint
1680
+ * @param element - the edge name
1681
+ * @param line - its line
1682
+ * @returns false when the edge is to be skipped (reported)
1683
+ */
1684
+ endpoint(id, element, line) {
1685
+ if (this.nodeRows.has(id)) {
1686
+ return true;
1687
+ }
1688
+ if (!this.options.addMissingNodes) {
1689
+ this.report.error(
1690
+ "missing-value",
1691
+ CX_ISSUE.UNKNOWN_NODE,
1692
+ `${element}: the endpoint ${String(id)} is not a node of the graph; the edge is skipped`,
1693
+ { line, element }
1694
+ );
1695
+ return false;
1696
+ }
1697
+ try {
1698
+ this.nodeRows.set(id, this.sink.addNode(id));
1699
+ } catch (err) {
1700
+ this.report.recordError(err, { line, element });
1701
+ return false;
1702
+ }
1703
+ this.report.counts.nodes++;
1704
+ return true;
1705
+ }
1706
+ /**
1707
+ * The weight of an edge: its weightFrom attribute, parsed as a number.
1708
+ * @param id - the edge id
1709
+ * @returns the weight, or undefined; E_INVALID_WEIGHT for a non-number
1710
+ */
1711
+ weightOf(id) {
1712
+ const { weightFrom } = this.options;
1713
+ if (weightFrom === null) {
1714
+ return void 0;
1715
+ }
1716
+ if (this.weights === null) {
1717
+ this.weights = /* @__PURE__ */ new Map();
1718
+ for (const { value } of this.doc.attributes.edge.get(weightFrom) ?? []) {
1719
+ if (isRecord(value) && this.scopePeek(value.s) !== 0) {
1720
+ const po = refId(value.po);
1721
+ if (po !== null && value.v !== void 0 && value.v !== null) {
1722
+ this.weights.set(po, plainJson(value.v));
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ const raw = this.weights.get(id);
1728
+ if (typeof raw === "string" && isNullText(raw)) {
1729
+ return void 0;
1730
+ }
1731
+ return weightFromValue(raw);
1732
+ }
1733
+ // ------------------------------------------------------------ network, visual properties, provenance
1734
+ /** Write the network attributes (unscoped and this subnetwork's, its own winning) as graph columns. */
1735
+ readNetworkAttributes() {
1736
+ for (const [name, elements] of this.doc.attributes.network) {
1737
+ let chosen = null;
1738
+ let chosenScope = 0;
1739
+ for (const { value } of elements) {
1740
+ if (!isRecord(value)) {
1741
+ continue;
1742
+ }
1743
+ const scope = this.scopeOf(value.s);
1744
+ if (scope !== 0 && scope >= chosenScope) {
1745
+ chosen = value;
1746
+ chosenScope = scope;
1747
+ }
1748
+ }
1749
+ if (chosen === null || chosen.v === void 0 || chosen.v === null || name === "") {
1750
+ continue;
1751
+ }
1752
+ const type = cxType(chosen.d);
1753
+ if (type === null) {
1754
+ this.report.warnOnce(
1755
+ "unsupported",
1756
+ UNKNOWN_ATTR_TYPE_CODE,
1757
+ `the network attribute "${name}" declares a data type CX does not define; kept as text`,
1758
+ { element: name },
1759
+ `${UNKNOWN_ATTR_TYPE_CODE}:network:${name}`
1760
+ );
1761
+ }
1762
+ const effective = type ?? { scalar: "string", list: false };
1763
+ const parsed = parseValue(chosen.v, effective, this.options.long, (digits) => {
1764
+ this.precision(name, digits);
1765
+ });
1766
+ if (parsed === UNSET) {
1767
+ continue;
1768
+ }
1769
+ if (parsed === BAD) {
1770
+ this.report.error(
1771
+ "validation-error",
1772
+ BAD_VALUE_CODE,
1773
+ `the network attribute "${name}" is ${shown(chosen.v)}, not a ${typeText(effective)}; unset`,
1774
+ { element: name }
1775
+ );
1776
+ continue;
1777
+ }
1778
+ if ((name === "name" || name === "description") && typeof parsed === "string") {
1779
+ continue;
1780
+ }
1781
+ const scalar = scalarDtype(effective.scalar, this.options.long);
1782
+ try {
1783
+ this.sink.setGraphValue(name, parsed, {
1784
+ dtype: effective.list ? "list" : scalar,
1785
+ ...effective.list ? { itemDtype: scalar } : {},
1786
+ origin: { format: CX_FORMAT, id: null, type: typeText(effective) }
1787
+ });
1788
+ } catch (err) {
1789
+ this.report.recordError(err, { element: name });
1790
+ }
1791
+ }
1792
+ }
1793
+ /**
1794
+ * Read cyVisualProperties for this graph's views: per-element and network values become columns
1795
+ * (origin namespace cx.bypass); defaults, mappings and dependencies are style rules, kept and not
1796
+ * applied.
1797
+ */
1798
+ readVisualProperties() {
1799
+ const entries = aspect(this.doc, "cyVisualProperties");
1800
+ const tableStyles = TABLE_STYLE_ASPECTS.reduce((n, name) => n + (this.doc.kept.get(name)?.length ?? 0), 0);
1801
+ const views = new Set(this.plan.views.slice(0, 1));
1802
+ const columns = { node: /* @__PURE__ */ new Map(), edge: /* @__PURE__ */ new Map() };
1803
+ const network = /* @__PURE__ */ new Map();
1804
+ let defaults = 0;
1805
+ let mappings = 0;
1806
+ let dependencies = 0;
1807
+ for (const { value } of entries) {
1808
+ if (!isRecord(value)) {
1809
+ continue;
1810
+ }
1811
+ const view = value.view === void 0 || value.view === null ? null : refId(value.view);
1812
+ if (view !== null && views.size > 0 && !views.has(view)) {
1813
+ continue;
1814
+ }
1815
+ const properties = isRecord(value.properties) ? value.properties : {};
1816
+ mappings += isRecord(value.mappings) ? Object.keys(value.mappings).length : 0;
1817
+ dependencies += isRecord(value.dependencies) ? Object.keys(value.dependencies).length : 0;
1818
+ switch (value.properties_of) {
1819
+ case "nodes":
1820
+ case "edges": {
1821
+ const domain = value.properties_of === "nodes" ? "node" : "edge";
1822
+ const target = refId(value.applies_to);
1823
+ const row = target === null ? void 0 : (domain === "node" ? this.nodeRows : this.edgeRows).get(target);
1824
+ if (row === void 0) {
1825
+ if (target === null || !(domain === "node" ? this.rootNodes : this.rootEdges).has(target)) {
1826
+ this.dangle(`${domain} visual property entry`);
1827
+ }
1828
+ continue;
1829
+ }
1830
+ for (const [property, raw] of Object.entries(properties)) {
1831
+ const list = columns[domain].get(property) ?? [];
1832
+ list.push([row, typeof raw === "string" ? raw : JSON.stringify(raw)]);
1833
+ columns[domain].set(property, list);
1834
+ }
1835
+ break;
1836
+ }
1837
+ case "network":
1838
+ for (const [property, raw] of Object.entries(properties)) {
1839
+ network.set(property, typeof raw === "string" ? raw : JSON.stringify(raw));
1840
+ }
1841
+ break;
1842
+ default:
1843
+ defaults += Object.keys(properties).length;
1844
+ break;
1845
+ }
1846
+ }
1847
+ for (const domain of ["node", "edge"]) {
1848
+ for (const [property, cells] of columns[domain]) {
1849
+ const handle = declareFresh(
1850
+ this.sink,
1851
+ domain,
1852
+ {
1853
+ name: property,
1854
+ dtype: "string",
1855
+ nullable: true,
1856
+ origin: { format: CX_FORMAT, id: null, namespace: CX_BYPASS_NAMESPACE }
1857
+ },
1858
+ this.report
1859
+ );
1860
+ for (const [row, text] of cells) {
1861
+ if (domain === "node") {
1862
+ this.sink.setNodeValue(handle, row, text);
1863
+ } else {
1864
+ this.sink.setEdgeValue(handle, row, text);
1865
+ }
1866
+ }
1867
+ }
1868
+ }
1869
+ for (const [property, text] of network) {
1870
+ try {
1871
+ this.sink.setGraphValue(property, text, {
1872
+ dtype: "string",
1873
+ origin: { format: CX_FORMAT, id: null, namespace: CX_BYPASS_NAMESPACE }
1874
+ });
1875
+ } catch (err) {
1876
+ this.report.recordError(err, { element: property });
1877
+ }
1878
+ }
1879
+ if (defaults + mappings + dependencies + tableStyles > 0) {
1880
+ const parts = [
1881
+ `cyVisualProperties: ${defaults} default(s), ${mappings} mapping(s), ${dependencies} dependenc(ies)`
1882
+ ];
1883
+ if (tableStyles > 0) {
1884
+ parts.push(`${tableStyles} table style element(s)`);
1885
+ }
1886
+ this.report.warning(
1887
+ "unsupported",
1888
+ STYLES_NOT_IMPORTED_CODE,
1889
+ `the file's style rules are not applied (${parts.join("; ")}); they are kept in meta.extra.cx (style import is issue #706)`,
1890
+ { element: "cyVisualProperties" }
1891
+ );
1892
+ }
1893
+ }
1894
+ /**
1895
+ * Read the provenance aspects: citations and supports as extension tables cx:citations and
1896
+ * cx:supports, the node / edge links to them as list columns, functionTerms as a json column,
1897
+ * reifiedEdges as a u32 column referring to edges.
1898
+ */
1899
+ readProvenance() {
1900
+ for (const name of ["citations", "supports"]) {
1901
+ this.extensionTable(`cx:${name}`, aspect(this.doc, name));
1902
+ }
1903
+ for (const [aspectName, domain, column] of [
1904
+ ["nodeCitations", "node", "citations"],
1905
+ ["edgeCitations", "edge", "citations"],
1906
+ ["nodeSupports", "node", "supports"],
1907
+ ["edgeSupports", "edge", "supports"]
1908
+ ]) {
1909
+ this.linkColumn(aspectName, domain, column);
1910
+ }
1911
+ const terms = [];
1912
+ for (const { value } of aspect(this.doc, "functionTerms")) {
1913
+ if (!isRecord(value)) {
1914
+ continue;
1915
+ }
1916
+ const po = refId(value.po);
1917
+ const row = po === null ? void 0 : this.nodeRows.get(po);
1918
+ if (row === void 0) {
1919
+ if (po === null || !this.rootNodes.has(po)) {
1920
+ this.dangle("functionTerms entry");
1921
+ }
1922
+ continue;
1923
+ }
1924
+ const { po: _po, ...term } = value;
1925
+ terms.push([row, plainJson(term)]);
1926
+ }
1927
+ this.writeColumn("node", "functionTerm", "json", null, terms);
1928
+ const reified = [];
1929
+ for (const { value } of aspect(this.doc, "reifiedEdges")) {
1930
+ if (!isRecord(value)) {
1931
+ continue;
1932
+ }
1933
+ const node = refId(value.node);
1934
+ const edge = refId(value.edge);
1935
+ const row = node === null ? void 0 : this.nodeRows.get(node);
1936
+ const edgeRow = edge === null ? void 0 : this.edgeRows.get(edge);
1937
+ if (row === void 0 || edgeRow === void 0) {
1938
+ if (node === null || !this.rootNodes.has(node) || edge === null || !this.rootEdges.has(edge)) {
1939
+ this.dangle("reifiedEdges entry");
1940
+ }
1941
+ continue;
1942
+ }
1943
+ reified.push([row, edgeRow]);
1944
+ }
1945
+ if (reified.length > 0) {
1946
+ const { handle } = declareResolved(
1947
+ this.sink,
1948
+ "node",
1949
+ {
1950
+ name: "reifiedEdge",
1951
+ dtype: "u32",
1952
+ refersTo: "edge",
1953
+ nullable: true,
1954
+ origin: { format: CX_FORMAT, namespace: "reifiedEdges" }
1955
+ },
1956
+ this.report
1957
+ );
1958
+ for (const [row, edge] of reified) {
1959
+ this.sink.setNodeValue(handle, row, edge);
1960
+ }
1961
+ }
1962
+ }
1963
+ /**
1964
+ * An extension table of a provenance aspect: an f64 `id` column and one json column per field.
1965
+ * @param name - the table name
1966
+ * @param elements - the elements
1967
+ */
1968
+ extensionTable(name, elements) {
1969
+ const records = elements.map((h) => h.value).filter(isRecord);
1970
+ if (records.length === 0) {
1971
+ return;
1972
+ }
1973
+ const fields = [...new Set(records.flatMap((r) => Object.keys(r)).filter((k) => k !== "@id"))];
1974
+ const decls = [
1975
+ { name: "id", dtype: "f64", nullable: true },
1976
+ ...fields.map((field) => ({ name: field, dtype: "json", nullable: true }))
1977
+ ];
1978
+ const table = this.sink.addExtensionTable(name, decls);
1979
+ for (const record of records) {
1980
+ const id = refId(record["@id"]);
1981
+ this.sink.addExtensionRow(table, [
1982
+ id === null ? null : Number(id),
1983
+ ...fields.map((field) => record[field] === void 0 ? null : plainJson(record[field]))
1984
+ ]);
1985
+ }
1986
+ }
1987
+ /**
1988
+ * A list column of the citation or support ids a node or edge links to.
1989
+ * @param aspectName - nodeCitations, edgeCitations, nodeSupports or edgeSupports
1990
+ * @param domain - node or edge
1991
+ * @param column - citations or supports
1992
+ */
1993
+ linkColumn(aspectName, domain, column) {
1994
+ const rows = domain === "node" ? this.nodeRows : this.edgeRows;
1995
+ const root = domain === "node" ? this.rootNodes : this.rootEdges;
1996
+ const links = /* @__PURE__ */ new Map();
1997
+ for (const { value } of aspect(this.doc, aspectName)) {
1998
+ if (!isRecord(value)) {
1999
+ continue;
2000
+ }
2001
+ const targets = Array.isArray(value.po) ? value.po : [value.po];
2002
+ const ids = (Array.isArray(value[column]) ? value[column] : []).map(refId).filter((id) => id !== null).map(Number);
2003
+ for (const raw of targets) {
2004
+ const target = refId(raw);
2005
+ const row = target === null ? void 0 : rows.get(target);
2006
+ if (row === void 0) {
2007
+ if (target === null || !root.has(target)) {
2008
+ this.dangle(`${aspectName} entry`);
2009
+ }
2010
+ continue;
2011
+ }
2012
+ links.set(row, [...links.get(row) ?? [], ...ids]);
2013
+ }
2014
+ }
2015
+ this.writeColumn(domain, column, "list", "f64", [...links]);
2016
+ }
2017
+ /**
2018
+ * Declare a column and write its cells (nothing when there are none).
2019
+ * @param domain - node or edge
2020
+ * @param name - the column name
2021
+ * @param dtype - the dtype
2022
+ * @param itemDtype - the list item dtype, or null
2023
+ * @param cells - row and value pairs
2024
+ */
2025
+ writeColumn(domain, name, dtype, itemDtype, cells) {
2026
+ if (cells.length === 0) {
2027
+ return;
2028
+ }
2029
+ const decl = {
2030
+ name,
2031
+ dtype,
2032
+ ...itemDtype === null ? {} : { itemDtype },
2033
+ nullable: true,
2034
+ origin: { format: CX_FORMAT, namespace: "provenance" }
2035
+ };
2036
+ const { handle } = declareResolved(this.sink, domain, decl, this.report);
2037
+ for (const [row, value] of cells) {
2038
+ if (domain === "node") {
2039
+ this.sink.setNodeValue(handle, row, value);
2040
+ } else {
2041
+ this.sink.setEdgeValue(handle, row, value);
2042
+ }
2043
+ }
2044
+ }
2045
+ /** Record the metadata: source format, name, description, id type, the kept aspects. */
2046
+ setMeta() {
2047
+ const describe = (key) => {
2048
+ let found = null;
2049
+ for (const { value } of this.doc.attributes.network.get(key) ?? []) {
2050
+ if (isRecord(value) && typeof value.v === "string") {
2051
+ const scope = this.scopePeek(value.s);
2052
+ if (scope === 2) {
2053
+ return value.v;
2054
+ }
2055
+ if (scope === 1) {
2056
+ found ??= value.v;
2057
+ }
2058
+ }
2059
+ }
2060
+ return found;
2061
+ };
2062
+ const extra = Object.fromEntries(this.doc.kept);
2063
+ const groups = aspect(this.doc, "cyGroups").map((h) => plainJson(h.value));
2064
+ if (groups.length > 0) {
2065
+ extra.groups = groups;
2066
+ }
2067
+ if (this.plan.subnetwork !== null) {
2068
+ extra.subnetwork = this.plan.subnetwork;
2069
+ }
2070
+ const { weightFrom } = this.options;
2071
+ const name = this.plan.name ?? describe("name");
2072
+ const description = describe("description");
2073
+ const patch = {
2074
+ sourceFormat: CX_FORMAT,
2075
+ sourceVersion: "1",
2076
+ idType: this.idType,
2077
+ ...name === null ? {} : { name },
2078
+ ...description === null ? {} : { description },
2079
+ ...this.weighted && weightFrom !== null ? { weightOrigin: { format: CX_FORMAT, id: null, title: weightFrom, type: "double", namespace: null } } : {},
2080
+ extra: { cx: extra }
2081
+ };
2082
+ this.sink.setMeta(patch);
2083
+ }
2084
+ }
2085
+ async function run(input, options, sinkFor, pick) {
2086
+ const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
2087
+ const zAs = zAsOption(options?.zAs);
2088
+ const first = new ImportReportBuilder(CX_FORMAT, resolved.errorLimit);
2089
+ reportUnusedOptions(options, first, USED_OPTIONS);
2090
+ const doc = await readDocument(input, first, resolved);
2091
+ const plans = graphPlans(doc);
2092
+ const chosen = pick(plans, first);
2093
+ const reports = [];
2094
+ chosen.forEach((index, k) => {
2095
+ const report = k === 0 ? first : new ImportReportBuilder(CX_FORMAT, resolved.errorLimit);
2096
+ const sink = sinkFor(index);
2097
+ reportSinkOptions(sink, options, report, true);
2098
+ new CxReader(sink, report, resolved, zAs, doc, plans[index]).build();
2099
+ throwIfAborted(resolved.signal);
2100
+ reports.push(report.finish());
2101
+ });
2102
+ return reports;
2103
+ }
2104
+ const cxImporter = Object.freeze({
2105
+ format: CX_FORMAT,
2106
+ extensions: Object.freeze([".cx"]),
2107
+ mimeTypes: Object.freeze(["application/json"]),
2108
+ /**
2109
+ * Confidence that the head is CX version 1: 0.95 when a top-level array starts with an object
2110
+ * whose first key is numberVerification or metaData (Cytoscape's file filter), 0.7 for another
2111
+ * CX aspect name.
2112
+ * @param head - the first bytes
2113
+ * @returns the confidence
2114
+ */
2115
+ sniff(head) {
2116
+ const text = new TextDecoder("utf-8").decode(head.subarray(0, 1024)).replace(/^\uFEFF/, "");
2117
+ const match = /^\s*\[\s*\{\s*"((?:[^"\\]|\\.)*)"\s*:/.exec(text);
2118
+ if (match === null) {
2119
+ return 0;
2120
+ }
2121
+ if (CX_FIRST_KEYS.includes(match[1])) {
2122
+ return 0.95;
2123
+ }
2124
+ return CX_ASPECT_KEYS.includes(match[1]) ? 0.7 : 0;
2125
+ },
2126
+ /**
2127
+ * Read one graph of a CX document into the sink: the root network, or the subnetwork
2128
+ * graphIndex / graphName picks (the first by default; W_MULTIPLE_GRAPHS names the others).
2129
+ * @param input - the text, bytes or stream
2130
+ * @param sink - the sink
2131
+ * @param options - format-specific and common options
2132
+ * @returns the import report; ImportError on a fatal error or beyond the error limit
2133
+ */
2134
+ async import(input, sink, options) {
2135
+ const [report] = await run(
2136
+ input,
2137
+ options,
2138
+ () => sink,
2139
+ (plans, first) => {
2140
+ const index = chooseGraph(
2141
+ plans.map((p) => p.name),
2142
+ options,
2143
+ first
2144
+ );
2145
+ if (plans.length > 1) {
2146
+ first.warning(
2147
+ "unsupported",
2148
+ MULTIPLE_GRAPHS_CODE,
2149
+ `the collection holds ${plans.length} subnetworks; graph ${index} is read and ${plans.length - 1} skipped (importAll() reads every one)`
2150
+ );
2151
+ }
2152
+ return [index];
2153
+ }
2154
+ );
2155
+ return report;
2156
+ },
2157
+ /**
2158
+ * Read every graph of a CX document: one per subnetwork of a collection, in the order of
2159
+ * cyNetworkRelations; the root network for any other document.
2160
+ * @param input - the text, bytes or stream
2161
+ * @param sinkFor - the sink of the graph with this index, called before its first push
2162
+ * @param options - format-specific and common options
2163
+ * @returns one report per graph
2164
+ */
2165
+ importAll(input, sinkFor, options) {
2166
+ return run(input, options, sinkFor, (plans) => plans.map((p) => p.index));
2167
+ },
2168
+ /**
2169
+ * List the graphs of a CX document with their names and member counts.
2170
+ * @param input - the text, bytes or stream
2171
+ * @param options - format-specific and common options
2172
+ * @returns one listing per graph
2173
+ */
2174
+ async listGraphs(input, options) {
2175
+ const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
2176
+ const report = new ImportReportBuilder(CX_FORMAT, resolved.errorLimit);
2177
+ const doc = await readDocument(input, report, resolved);
2178
+ const nodes = aspect(doc, "nodes").length;
2179
+ const edges = aspect(doc, "edges").length;
2180
+ return graphPlans(doc).map(
2181
+ (plan) => Object.freeze({
2182
+ index: plan.index,
2183
+ name: plan.name,
2184
+ nodes: memberCount(plan.nodes, nodes),
2185
+ edges: memberCount(plan.edges, edges)
2186
+ })
2187
+ );
2188
+ }
2189
+ });
2190
+ export {
2191
+ CX_ISSUE as C,
2192
+ cxImporter as c
2193
+ };
2194
+ //# sourceMappingURL=importer-BAP4PrxR.js.map