@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.
- package/LICENSE +21 -0
- package/README.md +250 -28
- package/dist/chunks/children-CL3Cy0ez.js +238 -0
- package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
- package/dist/chunks/escape-DyI8JofU.js +938 -0
- package/dist/chunks/escape-DyI8JofU.js.map +1 -0
- package/dist/chunks/importer-CQnJuWJw.js +2987 -0
- package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
- package/dist/chunks/importer-CpCpfbxr.js +2015 -0
- package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
- package/dist/chunks/importer-DbnGYr3_.js +2342 -0
- package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
- package/dist/chunks/importer-GozH8DkN.js +3050 -0
- package/dist/chunks/importer-GozH8DkN.js.map +1 -0
- package/dist/chunks/records-CGpxszm1.js +605 -0
- package/dist/chunks/records-CGpxszm1.js.map +1 -0
- package/dist/chunks/text-CajMdVFy.js +189 -0
- package/dist/chunks/text-CajMdVFy.js.map +1 -0
- package/dist/chunks/writer-DxSKC7TL.js +2842 -0
- package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
- package/dist/csv.d.ts +1 -0
- package/dist/csv.js +1702 -0
- package/dist/csv.js.map +1 -0
- package/dist/dot.d.ts +1 -0
- package/dist/dot.js +8 -0
- package/dist/dot.js.map +1 -0
- package/dist/gexf.d.ts +1 -0
- package/dist/gexf.js +3466 -0
- package/dist/gexf.js.map +1 -0
- package/dist/gml.d.ts +1 -0
- package/dist/gml.js +2647 -0
- package/dist/gml.js.map +1 -0
- package/dist/graph-io.d.ts +1 -0
- package/dist/graph-io.js +790 -0
- package/dist/graph-io.js.map +1 -0
- package/dist/graphml.d.ts +1 -0
- package/dist/graphml.js +8 -0
- package/dist/graphml.js.map +1 -0
- package/dist/json.d.ts +1 -0
- package/dist/json.js +11 -0
- package/dist/json.js.map +1 -0
- package/dist/neo4j.d.ts +1 -0
- package/dist/neo4j.js +2046 -0
- package/dist/neo4j.js.map +1 -0
- package/dist/pajek.d.ts +1 -0
- package/dist/pajek.js +8 -0
- package/dist/pajek.js.map +1 -0
- package/dist/src/children.d.ts +134 -0
- package/dist/src/children.d.ts.map +1 -0
- package/dist/src/children.js +274 -0
- package/dist/src/children.js.map +1 -0
- package/dist/src/common/attributes.d.ts +229 -0
- package/dist/src/common/attributes.d.ts.map +1 -0
- package/dist/src/common/attributes.js +368 -0
- package/dist/src/common/attributes.js.map +1 -0
- package/dist/src/common/codes.d.ts +105 -0
- package/dist/src/common/codes.d.ts.map +1 -0
- package/dist/src/common/codes.js +107 -0
- package/dist/src/common/codes.js.map +1 -0
- package/dist/src/common/declared-types.d.ts +84 -0
- package/dist/src/common/declared-types.d.ts.map +1 -0
- package/dist/src/common/declared-types.js +326 -0
- package/dist/src/common/declared-types.js.map +1 -0
- package/dist/src/common/direction.d.ts +206 -0
- package/dist/src/common/direction.d.ts.map +1 -0
- package/dist/src/common/direction.js +370 -0
- package/dist/src/common/direction.js.map +1 -0
- package/dist/src/common/escape.d.ts +92 -0
- package/dist/src/common/escape.d.ts.map +1 -0
- package/dist/src/common/escape.js +212 -0
- package/dist/src/common/escape.js.map +1 -0
- package/dist/src/common/export.d.ts +249 -0
- package/dist/src/common/export.d.ts.map +1 -0
- package/dist/src/common/export.js +594 -0
- package/dist/src/common/export.js.map +1 -0
- package/dist/src/common/format.d.ts +59 -0
- package/dist/src/common/format.d.ts.map +1 -0
- package/dist/src/common/format.js +106 -0
- package/dist/src/common/format.js.map +1 -0
- package/dist/src/common/ids.d.ts +83 -0
- package/dist/src/common/ids.d.ts.map +1 -0
- package/dist/src/common/ids.js +158 -0
- package/dist/src/common/ids.js.map +1 -0
- package/dist/src/common/input.d.ts +100 -0
- package/dist/src/common/input.d.ts.map +1 -0
- package/dist/src/common/input.js +335 -0
- package/dist/src/common/input.js.map +1 -0
- package/dist/src/common/lists.d.ts +34 -0
- package/dist/src/common/lists.d.ts.map +1 -0
- package/dist/src/common/lists.js +185 -0
- package/dist/src/common/lists.js.map +1 -0
- package/dist/src/common/options.d.ts +108 -0
- package/dist/src/common/options.d.ts.map +1 -0
- package/dist/src/common/options.js +265 -0
- package/dist/src/common/options.js.map +1 -0
- package/dist/src/common/report.d.ts +187 -0
- package/dist/src/common/report.d.ts.map +1 -0
- package/dist/src/common/report.js +274 -0
- package/dist/src/common/report.js.map +1 -0
- package/dist/src/common/temporal.d.ts +71 -0
- package/dist/src/common/temporal.d.ts.map +1 -0
- package/dist/src/common/temporal.js +266 -0
- package/dist/src/common/temporal.js.map +1 -0
- package/dist/src/common/text.d.ts +104 -0
- package/dist/src/common/text.d.ts.map +1 -0
- package/dist/src/common/text.js +255 -0
- package/dist/src/common/text.js.map +1 -0
- package/dist/src/common/weights.d.ts +77 -0
- package/dist/src/common/weights.d.ts.map +1 -0
- package/dist/src/common/weights.js +156 -0
- package/dist/src/common/weights.js.map +1 -0
- package/dist/src/common/writer.d.ts +51 -0
- package/dist/src/common/writer.d.ts.map +1 -0
- package/dist/src/common/writer.js +108 -0
- package/dist/src/common/writer.js.map +1 -0
- package/dist/src/common/xml.d.ts +245 -0
- package/dist/src/common/xml.d.ts.map +1 -0
- package/dist/src/common/xml.js +942 -0
- package/dist/src/common/xml.js.map +1 -0
- package/dist/src/formats/csv/exporter.d.ts +70 -0
- package/dist/src/formats/csv/exporter.d.ts.map +1 -0
- package/dist/src/formats/csv/exporter.js +682 -0
- package/dist/src/formats/csv/exporter.js.map +1 -0
- package/dist/src/formats/csv/header.d.ts +66 -0
- package/dist/src/formats/csv/header.d.ts.map +1 -0
- package/dist/src/formats/csv/header.js +152 -0
- package/dist/src/formats/csv/header.js.map +1 -0
- package/dist/src/formats/csv/importer.d.ts +82 -0
- package/dist/src/formats/csv/importer.d.ts.map +1 -0
- package/dist/src/formats/csv/importer.js +849 -0
- package/dist/src/formats/csv/importer.js.map +1 -0
- package/dist/src/formats/csv/index.d.ts +60 -0
- package/dist/src/formats/csv/index.d.ts.map +1 -0
- package/dist/src/formats/csv/index.js +63 -0
- package/dist/src/formats/csv/index.js.map +1 -0
- package/dist/src/formats/csv/records.d.ts +188 -0
- package/dist/src/formats/csv/records.d.ts.map +1 -0
- package/dist/src/formats/csv/records.js +702 -0
- package/dist/src/formats/csv/records.js.map +1 -0
- package/dist/src/formats/csv/values.d.ts +105 -0
- package/dist/src/formats/csv/values.d.ts.map +1 -0
- package/dist/src/formats/csv/values.js +192 -0
- package/dist/src/formats/csv/values.js.map +1 -0
- package/dist/src/formats/dot/exporter.d.ts +52 -0
- package/dist/src/formats/dot/exporter.d.ts.map +1 -0
- package/dist/src/formats/dot/exporter.js +836 -0
- package/dist/src/formats/dot/exporter.js.map +1 -0
- package/dist/src/formats/dot/importer.d.ts +102 -0
- package/dist/src/formats/dot/importer.d.ts.map +1 -0
- package/dist/src/formats/dot/importer.js +1291 -0
- package/dist/src/formats/dot/importer.js.map +1 -0
- package/dist/src/formats/dot/index.d.ts +7 -0
- package/dist/src/formats/dot/index.d.ts.map +1 -0
- package/dist/src/formats/dot/index.js +7 -0
- package/dist/src/formats/dot/index.js.map +1 -0
- package/dist/src/formats/dot/names.d.ts +29 -0
- package/dist/src/formats/dot/names.d.ts.map +1 -0
- package/dist/src/formats/dot/names.js +28 -0
- package/dist/src/formats/dot/names.js.map +1 -0
- package/dist/src/formats/dot/tokenizer.d.ts +114 -0
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
- package/dist/src/formats/dot/tokenizer.js +341 -0
- package/dist/src/formats/dot/tokenizer.js.map +1 -0
- package/dist/src/formats/gexf/exporter.d.ts +56 -0
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
- package/dist/src/formats/gexf/exporter.js +1395 -0
- package/dist/src/formats/gexf/exporter.js.map +1 -0
- package/dist/src/formats/gexf/importer.d.ts +73 -0
- package/dist/src/formats/gexf/importer.d.ts.map +1 -0
- package/dist/src/formats/gexf/importer.js +1880 -0
- package/dist/src/formats/gexf/importer.js.map +1 -0
- package/dist/src/formats/gexf/index.d.ts +96 -0
- package/dist/src/formats/gexf/index.d.ts.map +1 -0
- package/dist/src/formats/gexf/index.js +97 -0
- package/dist/src/formats/gexf/index.js.map +1 -0
- package/dist/src/formats/gexf/schema.d.ts +135 -0
- package/dist/src/formats/gexf/schema.d.ts.map +1 -0
- package/dist/src/formats/gexf/schema.js +323 -0
- package/dist/src/formats/gexf/schema.js.map +1 -0
- package/dist/src/formats/gml/exporter.d.ts +69 -0
- package/dist/src/formats/gml/exporter.d.ts.map +1 -0
- package/dist/src/formats/gml/exporter.js +1093 -0
- package/dist/src/formats/gml/exporter.js.map +1 -0
- package/dist/src/formats/gml/importer.d.ts +66 -0
- package/dist/src/formats/gml/importer.d.ts.map +1 -0
- package/dist/src/formats/gml/importer.js +1331 -0
- package/dist/src/formats/gml/importer.js.map +1 -0
- package/dist/src/formats/gml/index.d.ts +85 -0
- package/dist/src/formats/gml/index.d.ts.map +1 -0
- package/dist/src/formats/gml/index.js +88 -0
- package/dist/src/formats/gml/index.js.map +1 -0
- package/dist/src/formats/gml/syntax.d.ts +186 -0
- package/dist/src/formats/gml/syntax.d.ts.map +1 -0
- package/dist/src/formats/gml/syntax.js +467 -0
- package/dist/src/formats/gml/syntax.js.map +1 -0
- package/dist/src/formats/graphml/constants.d.ts +169 -0
- package/dist/src/formats/graphml/constants.d.ts.map +1 -0
- package/dist/src/formats/graphml/constants.js +165 -0
- package/dist/src/formats/graphml/constants.js.map +1 -0
- package/dist/src/formats/graphml/exporter.d.ts +34 -0
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
- package/dist/src/formats/graphml/exporter.js +1176 -0
- package/dist/src/formats/graphml/exporter.js.map +1 -0
- package/dist/src/formats/graphml/importer.d.ts +31 -0
- package/dist/src/formats/graphml/importer.d.ts.map +1 -0
- package/dist/src/formats/graphml/importer.js +1607 -0
- package/dist/src/formats/graphml/importer.js.map +1 -0
- package/dist/src/formats/graphml/index.d.ts +8 -0
- package/dist/src/formats/graphml/index.d.ts.map +1 -0
- package/dist/src/formats/graphml/index.js +8 -0
- package/dist/src/formats/graphml/index.js.map +1 -0
- package/dist/src/formats/graphml/tree.d.ts +72 -0
- package/dist/src/formats/graphml/tree.d.ts.map +1 -0
- package/dist/src/formats/graphml/tree.js +290 -0
- package/dist/src/formats/graphml/tree.js.map +1 -0
- package/dist/src/formats/json/dialect.d.ts +125 -0
- package/dist/src/formats/json/dialect.d.ts.map +1 -0
- package/dist/src/formats/json/dialect.js +262 -0
- package/dist/src/formats/json/dialect.js.map +1 -0
- package/dist/src/formats/json/exporter.d.ts +89 -0
- package/dist/src/formats/json/exporter.d.ts.map +1 -0
- package/dist/src/formats/json/exporter.js +1358 -0
- package/dist/src/formats/json/exporter.js.map +1 -0
- package/dist/src/formats/json/importer.d.ts +108 -0
- package/dist/src/formats/json/importer.d.ts.map +1 -0
- package/dist/src/formats/json/importer.js +1838 -0
- package/dist/src/formats/json/importer.js.map +1 -0
- package/dist/src/formats/json/index.d.ts +8 -0
- package/dist/src/formats/json/index.d.ts.map +1 -0
- package/dist/src/formats/json/index.js +8 -0
- package/dist/src/formats/json/index.js.map +1 -0
- package/dist/src/formats/neo4j/exporter.d.ts +68 -0
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
- package/dist/src/formats/neo4j/exporter.js +1055 -0
- package/dist/src/formats/neo4j/exporter.js.map +1 -0
- package/dist/src/formats/neo4j/header.d.ts +52 -0
- package/dist/src/formats/neo4j/header.d.ts.map +1 -0
- package/dist/src/formats/neo4j/header.js +131 -0
- package/dist/src/formats/neo4j/header.js.map +1 -0
- package/dist/src/formats/neo4j/importer.d.ts +73 -0
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
- package/dist/src/formats/neo4j/importer.js +932 -0
- package/dist/src/formats/neo4j/importer.js.map +1 -0
- package/dist/src/formats/neo4j/index.d.ts +79 -0
- package/dist/src/formats/neo4j/index.d.ts.map +1 -0
- package/dist/src/formats/neo4j/index.js +83 -0
- package/dist/src/formats/neo4j/index.js.map +1 -0
- package/dist/src/formats/pajek/exporter.d.ts +58 -0
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
- package/dist/src/formats/pajek/exporter.js +825 -0
- package/dist/src/formats/pajek/exporter.js.map +1 -0
- package/dist/src/formats/pajek/importer.d.ts +88 -0
- package/dist/src/formats/pajek/importer.d.ts.map +1 -0
- package/dist/src/formats/pajek/importer.js +1047 -0
- package/dist/src/formats/pajek/importer.js.map +1 -0
- package/dist/src/formats/pajek/index.d.ts +7 -0
- package/dist/src/formats/pajek/index.d.ts.map +1 -0
- package/dist/src/formats/pajek/index.js +7 -0
- package/dist/src/formats/pajek/index.js.map +1 -0
- package/dist/src/formats/pajek/syntax.d.ts +112 -0
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
- package/dist/src/formats/pajek/syntax.js +269 -0
- package/dist/src/formats/pajek/syntax.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/registry.d.ts +207 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +481 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/sniff.d.ts +104 -0
- package/dist/src/sniff.d.ts.map +1 -0
- package/dist/src/sniff.js +357 -0
- package/dist/src/sniff.js.map +1 -0
- package/dist/src/types.d.ts +238 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +29 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +122 -7
- package/src/children.ts +335 -0
- package/src/common/attributes.ts +520 -0
- package/src/common/codes.ts +153 -0
- package/src/common/declared-types.ts +374 -0
- package/src/common/direction.ts +518 -0
- package/src/common/escape.ts +231 -0
- package/src/common/export.ts +817 -0
- package/src/common/format.ts +111 -0
- package/src/common/ids.ts +176 -0
- package/src/common/input.ts +378 -0
- package/src/common/lists.ts +196 -0
- package/src/common/options.ts +377 -0
- package/src/common/report.ts +352 -0
- package/src/common/temporal.ts +302 -0
- package/src/common/text.ts +294 -0
- package/src/common/weights.ts +202 -0
- package/src/common/writer.ts +123 -0
- package/src/common/xml.ts +1053 -0
- package/src/formats/csv/exporter.ts +894 -0
- package/src/formats/csv/header.ts +172 -0
- package/src/formats/csv/importer.ts +1104 -0
- package/src/formats/csv/index.ts +88 -0
- package/src/formats/csv/records.ts +813 -0
- package/src/formats/csv/values.ts +224 -0
- package/src/formats/dot/exporter.ts +1014 -0
- package/src/formats/dot/importer.ts +1549 -0
- package/src/formats/dot/index.ts +7 -0
- package/src/formats/dot/names.ts +40 -0
- package/src/formats/dot/tokenizer.ts +384 -0
- package/src/formats/gexf/exporter.ts +1696 -0
- package/src/formats/gexf/importer.ts +2333 -0
- package/src/formats/gexf/index.ts +142 -0
- package/src/formats/gexf/schema.ts +361 -0
- package/src/formats/gml/exporter.ts +1404 -0
- package/src/formats/gml/importer.ts +1591 -0
- package/src/formats/gml/index.ts +128 -0
- package/src/formats/gml/syntax.ts +545 -0
- package/src/formats/graphml/constants.ts +225 -0
- package/src/formats/graphml/exporter.ts +1458 -0
- package/src/formats/graphml/importer.ts +2027 -0
- package/src/formats/graphml/index.ts +8 -0
- package/src/formats/graphml/tree.ts +318 -0
- package/src/formats/json/dialect.ts +317 -0
- package/src/formats/json/exporter.ts +1616 -0
- package/src/formats/json/importer.ts +2271 -0
- package/src/formats/json/index.ts +8 -0
- package/src/formats/neo4j/exporter.ts +1287 -0
- package/src/formats/neo4j/header.ts +156 -0
- package/src/formats/neo4j/importer.ts +1220 -0
- package/src/formats/neo4j/index.ts +116 -0
- package/src/formats/pajek/exporter.ts +1000 -0
- package/src/formats/pajek/importer.ts +1311 -0
- package/src/formats/pajek/index.ts +7 -0
- package/src/formats/pajek/syntax.ts +307 -0
- package/src/index.ts +244 -0
- package/src/registry.ts +617 -0
- package/src/sniff.ts +397 -0
- package/src/types.ts +262 -0
|
@@ -0,0 +1,2842 @@
|
|
|
1
|
+
import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
|
|
2
|
+
const MISSING_ID_CODE = "E_MISSING_ID";
|
|
3
|
+
const MISSING_ENDPOINT_CODE = "E_MISSING_ENDPOINT";
|
|
4
|
+
const DUPLICATE_NODE_CODE = "W_DUPLICATE_NODE";
|
|
5
|
+
const NO_GRAPH_CODE = "E_NO_GRAPH";
|
|
6
|
+
const MULTIPLE_GRAPHS_CODE = "W_MULTIPLE_GRAPHS";
|
|
7
|
+
const EMPTY_INPUT_CODE = "E_EMPTY_INPUT";
|
|
8
|
+
const SYNTAX_CODE = "E_SYNTAX";
|
|
9
|
+
const XML_SYNTAX_CODE = "E_XML_SYNTAX";
|
|
10
|
+
const UNKNOWN_ELEMENT_CODE = "W_UNKNOWN_ELEMENT";
|
|
11
|
+
const STRAY_TEXT_CODE = "W_STRAY_TEXT";
|
|
12
|
+
const UNKNOWN_PARENT_CODE = "E_UNKNOWN_PARENT";
|
|
13
|
+
const HYPEREDGE_CODE = "E_HYPEREDGE";
|
|
14
|
+
const DUPLICATE_KEY_CODE = "E_DUPLICATE_KEY";
|
|
15
|
+
const COLUMN_RENAMED_CODE = "W_COLUMN_RENAMED";
|
|
16
|
+
const ROLE_TAKEN_CODE = "W_ROLE_TAKEN";
|
|
17
|
+
const PRECISION_CODE = "W_PRECISION";
|
|
18
|
+
const ID_MERGED_CODE = "W_ID_MERGED";
|
|
19
|
+
const SINK_OPTION_CODE = "W_SINK_OPTION";
|
|
20
|
+
const OPTION_IGNORED_CODE = "W_OPTION_IGNORED";
|
|
21
|
+
const INVALID_UTF8_CODE = "E_INVALID_UTF8";
|
|
22
|
+
const DIRECTION_REFUSED_CODE = "W_DIRECTION_REFUSED";
|
|
23
|
+
const DIRECTION_FORCED_CODE = "W_DIRECTION_FORCED";
|
|
24
|
+
const MIXED_DIRECTION_CODE = "E_MIXED_DIRECTION";
|
|
25
|
+
const COUNT_HINT_CODE = "W_COUNT_HINT";
|
|
26
|
+
const UNKNOWN_ATTR_TYPE_CODE = "W_UNKNOWN_ATTR_TYPE";
|
|
27
|
+
const BAD_DEFAULT_CODE = "W_BAD_DEFAULT";
|
|
28
|
+
const BAD_OPTIONS_CODE = "W_BAD_OPTIONS";
|
|
29
|
+
const DUPLICATE_EDGE_ID_CODE = "E_DUPLICATE_EDGE_ID";
|
|
30
|
+
const ROLE_DROPPED_CODE = "W_ROLE_DROPPED";
|
|
31
|
+
const PARENTS_DROPPED_CODE = "W_PARENTS_DROPPED";
|
|
32
|
+
const TEMPORAL_DROPPED_CODE = "W_TEMPORAL_DROPPED";
|
|
33
|
+
const MUTUAL_EXPANDED_CODE = "W_MUTUAL_EXPANDED";
|
|
34
|
+
const MUTUAL_AS_UNDIRECTED_CODE = "W_MUTUAL_AS_UNDIRECTED";
|
|
35
|
+
const ID_TEXT_TYPE_CODE = "W_ID_TEXT_TYPE";
|
|
36
|
+
const ID_TEXT_COLLISION_CODE = "E_ID_TEXT_COLLISION";
|
|
37
|
+
const TEMPORAL_TEXT_DROPPED_CODE = "W_TEMPORAL_TEXT_DROPPED";
|
|
38
|
+
const WEIGHT_KEY_CLASH_CODE = "W_WEIGHT_KEY_CLASH";
|
|
39
|
+
const ROLE_ASSUMED_CODE = "W_ROLE_ASSUMED";
|
|
40
|
+
const COLUMN_RENAMED_LOSS_CODE = "W_COLUMN_NAME_CHANGED";
|
|
41
|
+
const EMPTY_COLUMN_DROPPED_CODE = "W_EMPTY_COLUMN_DROPPED";
|
|
42
|
+
const STORAGE_CLASS_CODE = "W_STORAGE_CLASS_CHANGED";
|
|
43
|
+
const INTEGRAL_F64_CODE = "W_INTEGRAL_F64_AS_I32";
|
|
44
|
+
const TEXT_INFERRED_CODE = "W_TEXT_INFERRED";
|
|
45
|
+
const XML_ILLEGAL_CHAR_CODE = "E_XML_ILLEGAL_CHAR";
|
|
46
|
+
const OPTIONS_GAINED_CODE = "W_OPTIONS_GAINED";
|
|
47
|
+
const TIME_TEXT_ROLE = "timeText";
|
|
48
|
+
const TIME_TEXT_SUFFIX = ".text";
|
|
49
|
+
const DATE_TIME$1 = /^(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})(?:[T ]([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]{1,9}))?)?)?(Z|z|[+-][0-9]{2}(?::?[0-9]{2})?)?$/;
|
|
50
|
+
const TIME$1 = /^([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]{1,9}))?)?(Z|z|[+-][0-9]{2}(?::?[0-9]{2})?)?$/;
|
|
51
|
+
const NUMERIC = /^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$/;
|
|
52
|
+
const MS_PER_MINUTE = 6e4;
|
|
53
|
+
const MS_PER_DAY = 864e5;
|
|
54
|
+
function parseTemporal(text, kind) {
|
|
55
|
+
let value;
|
|
56
|
+
switch (kind) {
|
|
57
|
+
case "date":
|
|
58
|
+
case "dateTime":
|
|
59
|
+
case "localDateTime":
|
|
60
|
+
value = parseDateTime(text, kind);
|
|
61
|
+
break;
|
|
62
|
+
case "time":
|
|
63
|
+
case "localTime":
|
|
64
|
+
value = parseTime(text, kind);
|
|
65
|
+
break;
|
|
66
|
+
default: {
|
|
67
|
+
const name = kind;
|
|
68
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `unknown temporal kind ${name}`, { kind: name });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const canonical = formatTemporal(value, kind);
|
|
72
|
+
return { value, text: canonical === text ? null : text };
|
|
73
|
+
}
|
|
74
|
+
function parseTimeText(text, timeFormat) {
|
|
75
|
+
const trimmed = text.trim();
|
|
76
|
+
switch (timeFormat) {
|
|
77
|
+
case "integer":
|
|
78
|
+
case "double":
|
|
79
|
+
return parseNumericTime(trimmed);
|
|
80
|
+
case "date":
|
|
81
|
+
return parseTemporal(trimmed, "date");
|
|
82
|
+
case "dateTime":
|
|
83
|
+
return parseTemporal(trimmed, "dateTime");
|
|
84
|
+
case null:
|
|
85
|
+
return NUMERIC.test(trimmed) ? parseNumericTime(trimmed) : parseTemporal(trimmed, "dateTime");
|
|
86
|
+
default: {
|
|
87
|
+
const name = timeFormat;
|
|
88
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `unknown timeformat ${name}`, { timeFormat: name });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function formatTemporal(value, kind) {
|
|
93
|
+
switch (kind) {
|
|
94
|
+
case "date": {
|
|
95
|
+
const d = new Date(value);
|
|
96
|
+
return `${year(d)}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
|
|
97
|
+
}
|
|
98
|
+
case "dateTime":
|
|
99
|
+
case "localDateTime": {
|
|
100
|
+
const d = new Date(value);
|
|
101
|
+
const date = `${year(d)}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
|
|
102
|
+
const time = clock(d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());
|
|
103
|
+
return kind === "dateTime" ? `${date}T${time}Z` : `${date}T${time}`;
|
|
104
|
+
}
|
|
105
|
+
case "time":
|
|
106
|
+
case "localTime": {
|
|
107
|
+
const ms = (value % MS_PER_DAY + MS_PER_DAY) % MS_PER_DAY;
|
|
108
|
+
const h = Math.floor(ms / 36e5);
|
|
109
|
+
const m = Math.floor(ms % 36e5 / MS_PER_MINUTE);
|
|
110
|
+
const s = Math.floor(ms % MS_PER_MINUTE / 1e3);
|
|
111
|
+
const text = clock(h, m, s, ms % 1e3);
|
|
112
|
+
return kind === "time" ? `${text}Z` : text;
|
|
113
|
+
}
|
|
114
|
+
default: {
|
|
115
|
+
const name = kind;
|
|
116
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `unknown temporal kind ${name}`, { kind: name });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function formatTimeValue(value, timeFormat) {
|
|
121
|
+
switch (timeFormat) {
|
|
122
|
+
case "date":
|
|
123
|
+
return formatTemporal(value, "date");
|
|
124
|
+
case "dateTime":
|
|
125
|
+
return formatTemporal(value, "dateTime");
|
|
126
|
+
case "integer":
|
|
127
|
+
case "double":
|
|
128
|
+
case null:
|
|
129
|
+
return String(value);
|
|
130
|
+
default: {
|
|
131
|
+
const name = timeFormat;
|
|
132
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `unknown timeformat ${name}`, { timeFormat: name });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function timeTextCompanion(column) {
|
|
137
|
+
return {
|
|
138
|
+
name: column + TIME_TEXT_SUFFIX,
|
|
139
|
+
dtype: "string",
|
|
140
|
+
role: TIME_TEXT_ROLE,
|
|
141
|
+
nullable: true,
|
|
142
|
+
extra: { for: column }
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function parseNumericTime(text) {
|
|
146
|
+
if (!NUMERIC.test(text)) {
|
|
147
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not a numeric time`, { value: text });
|
|
148
|
+
}
|
|
149
|
+
const value = Number(text);
|
|
150
|
+
return { value, text: String(value) === text ? null : text };
|
|
151
|
+
}
|
|
152
|
+
function parseDateTime(text, kind) {
|
|
153
|
+
const m = DATE_TIME$1.exec(text);
|
|
154
|
+
if (m === null) {
|
|
155
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not an ISO-8601 ${kind}`, { value: text, kind });
|
|
156
|
+
}
|
|
157
|
+
const y = Number(m[1]);
|
|
158
|
+
const month = Number(m[2]);
|
|
159
|
+
const day = Number(m[3]);
|
|
160
|
+
const hour = m[4] === void 0 ? 0 : Number(m[4]);
|
|
161
|
+
const minute = m[5] === void 0 ? 0 : Number(m[5]);
|
|
162
|
+
const second = m[6] === void 0 ? 0 : Number(m[6]);
|
|
163
|
+
const ms = m[7] === void 0 ? 0 : fractionMs(m[7]);
|
|
164
|
+
if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 24 || minute > 59 || second > 60) {
|
|
165
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is out of range for a ${kind}`, { value: text, kind });
|
|
166
|
+
}
|
|
167
|
+
const d = /* @__PURE__ */ new Date(0);
|
|
168
|
+
d.setUTCFullYear(y, month - 1, day);
|
|
169
|
+
d.setUTCHours(hour, minute, second, ms);
|
|
170
|
+
if (d.getUTCDate() !== day && hour !== 24) {
|
|
171
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not a calendar date`, { value: text, kind });
|
|
172
|
+
}
|
|
173
|
+
const offset = m[8] === void 0 ? 0 : offsetMinutes(m[8]);
|
|
174
|
+
return d.getTime() - offset * MS_PER_MINUTE;
|
|
175
|
+
}
|
|
176
|
+
function parseTime(text, kind) {
|
|
177
|
+
const m = TIME$1.exec(text);
|
|
178
|
+
if (m === null) {
|
|
179
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not an ISO-8601 ${kind}`, { value: text, kind });
|
|
180
|
+
}
|
|
181
|
+
const hour = Number(m[1]);
|
|
182
|
+
const minute = Number(m[2]);
|
|
183
|
+
const second = m[3] === void 0 ? 0 : Number(m[3]);
|
|
184
|
+
const ms = m[4] === void 0 ? 0 : fractionMs(m[4]);
|
|
185
|
+
if (hour > 24 || minute > 59 || second > 60) {
|
|
186
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is out of range for a ${kind}`, { value: text, kind });
|
|
187
|
+
}
|
|
188
|
+
const local = ((hour * 60 + minute) * 60 + second) * 1e3 + ms;
|
|
189
|
+
const offset = m[5] === void 0 ? 0 : offsetMinutes(m[5]);
|
|
190
|
+
const utc = local - offset * MS_PER_MINUTE;
|
|
191
|
+
return (utc % MS_PER_DAY + MS_PER_DAY) % MS_PER_DAY;
|
|
192
|
+
}
|
|
193
|
+
function fractionMs(digits) {
|
|
194
|
+
return Math.floor(Number(`0.${digits}`) * 1e3);
|
|
195
|
+
}
|
|
196
|
+
function offsetMinutes(zone) {
|
|
197
|
+
if (zone === "Z" || zone === "z") {
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
const sign = zone.startsWith("-") ? -1 : 1;
|
|
201
|
+
const digits = zone.slice(1).replace(":", "");
|
|
202
|
+
const hours = Number(digits.slice(0, 2));
|
|
203
|
+
const minutes = digits.length > 2 ? Number(digits.slice(2, 4)) : 0;
|
|
204
|
+
return sign * (hours * 60 + minutes);
|
|
205
|
+
}
|
|
206
|
+
function pad2(n) {
|
|
207
|
+
return n < 10 ? `0${n}` : String(n);
|
|
208
|
+
}
|
|
209
|
+
function year(d) {
|
|
210
|
+
const y = d.getUTCFullYear();
|
|
211
|
+
const abs = String(Math.abs(y)).padStart(4, "0");
|
|
212
|
+
return y < 0 ? `-${abs}` : abs;
|
|
213
|
+
}
|
|
214
|
+
function clock(h, m, s, ms) {
|
|
215
|
+
const base = `${pad2(h)}:${pad2(m)}:${pad2(s)}`;
|
|
216
|
+
return ms === 0 ? base : `${base}.${String(ms).padStart(3, "0")}`;
|
|
217
|
+
}
|
|
218
|
+
const BOOLEAN = { dtype: "bool", kind: "boolean" };
|
|
219
|
+
const INTEGER = { dtype: "i32", kind: "integer" };
|
|
220
|
+
const LONG = { dtype: "f64", kind: "long", precision: true };
|
|
221
|
+
const FLOAT = { dtype: "f32", kind: "float" };
|
|
222
|
+
const DOUBLE = { dtype: "f64", kind: "double" };
|
|
223
|
+
const STRING = { dtype: "string", kind: "string" };
|
|
224
|
+
const DATE = { dtype: "f64", kind: "temporal", temporal: "date" };
|
|
225
|
+
const DATE_TIME = { dtype: "f64", kind: "temporal", temporal: "dateTime" };
|
|
226
|
+
const LOCAL_DATE_TIME = { dtype: "f64", kind: "temporal", temporal: "localDateTime" };
|
|
227
|
+
const TIME = { dtype: "f64", kind: "temporal", temporal: "time" };
|
|
228
|
+
const LOCAL_TIME = { dtype: "f64", kind: "temporal", temporal: "localTime" };
|
|
229
|
+
const DURATION = { dtype: "string", kind: "duration" };
|
|
230
|
+
const POINT = { dtype: "json", kind: "point" };
|
|
231
|
+
const RECORD = { dtype: "json", kind: "json" };
|
|
232
|
+
const GEXF_SCALARS = {
|
|
233
|
+
integer: INTEGER,
|
|
234
|
+
long: LONG,
|
|
235
|
+
double: DOUBLE,
|
|
236
|
+
float: FLOAT,
|
|
237
|
+
boolean: BOOLEAN,
|
|
238
|
+
string: STRING,
|
|
239
|
+
anyuri: STRING,
|
|
240
|
+
char: STRING,
|
|
241
|
+
byte: INTEGER,
|
|
242
|
+
short: INTEGER,
|
|
243
|
+
bigdecimal: STRING,
|
|
244
|
+
biginteger: STRING,
|
|
245
|
+
date: DATE,
|
|
246
|
+
datetime: DATE_TIME
|
|
247
|
+
};
|
|
248
|
+
const GRAPHML_SCALARS = {
|
|
249
|
+
boolean: BOOLEAN,
|
|
250
|
+
int: INTEGER,
|
|
251
|
+
long: LONG,
|
|
252
|
+
float: FLOAT,
|
|
253
|
+
double: DOUBLE,
|
|
254
|
+
string: STRING
|
|
255
|
+
};
|
|
256
|
+
const GML_SCALARS = {
|
|
257
|
+
int: INTEGER,
|
|
258
|
+
real: DOUBLE,
|
|
259
|
+
string: STRING,
|
|
260
|
+
record: RECORD
|
|
261
|
+
};
|
|
262
|
+
const NEO4J_SCALARS = {
|
|
263
|
+
int: INTEGER,
|
|
264
|
+
integer: LONG,
|
|
265
|
+
long: LONG,
|
|
266
|
+
float: FLOAT,
|
|
267
|
+
double: DOUBLE,
|
|
268
|
+
boolean: BOOLEAN,
|
|
269
|
+
byte: INTEGER,
|
|
270
|
+
short: INTEGER,
|
|
271
|
+
char: STRING,
|
|
272
|
+
string: STRING,
|
|
273
|
+
point: POINT,
|
|
274
|
+
date: DATE,
|
|
275
|
+
localtime: LOCAL_TIME,
|
|
276
|
+
time: TIME,
|
|
277
|
+
localdatetime: LOCAL_DATE_TIME,
|
|
278
|
+
datetime: DATE_TIME,
|
|
279
|
+
duration: DURATION
|
|
280
|
+
};
|
|
281
|
+
const TABLES = {
|
|
282
|
+
gexf: GEXF_SCALARS,
|
|
283
|
+
graphml: GRAPHML_SCALARS,
|
|
284
|
+
gml: GML_SCALARS,
|
|
285
|
+
neo4j: NEO4J_SCALARS
|
|
286
|
+
};
|
|
287
|
+
const INTEGER_TEXT$1 = /^[+-]?[0-9]+$/;
|
|
288
|
+
const DECIMAL_TEXT = /^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$/;
|
|
289
|
+
const I32_MIN = -2147483648;
|
|
290
|
+
const I32_MAX = 2147483647;
|
|
291
|
+
function mapDeclaredType(format, type, long) {
|
|
292
|
+
const table = TABLES[format];
|
|
293
|
+
let key = type.trim().toLowerCase();
|
|
294
|
+
let list2 = false;
|
|
295
|
+
if (format === "gexf" && key.startsWith("list") && key.length > 4) {
|
|
296
|
+
list2 = true;
|
|
297
|
+
key = key.slice(4);
|
|
298
|
+
} else if (format === "neo4j" && key.endsWith("[]")) {
|
|
299
|
+
list2 = true;
|
|
300
|
+
key = key.slice(0, -2);
|
|
301
|
+
} else if (format === "gml" && key === "list") {
|
|
302
|
+
return {
|
|
303
|
+
declared: type,
|
|
304
|
+
dtype: "list",
|
|
305
|
+
itemDtype: "json",
|
|
306
|
+
kind: "json",
|
|
307
|
+
list: true,
|
|
308
|
+
temporal: null,
|
|
309
|
+
precision: false
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
let entry = table[key];
|
|
313
|
+
if (entry === void 0) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
if (entry.kind === "long" && long === "string") {
|
|
317
|
+
entry = STRING;
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
declared: type,
|
|
321
|
+
dtype: list2 ? "list" : entry.dtype,
|
|
322
|
+
itemDtype: list2 ? entry.dtype : null,
|
|
323
|
+
kind: entry.kind,
|
|
324
|
+
list: list2,
|
|
325
|
+
temporal: entry.temporal ?? null,
|
|
326
|
+
precision: entry.precision ?? false
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function stringSpec(declared) {
|
|
330
|
+
return {
|
|
331
|
+
declared: declared ?? "",
|
|
332
|
+
dtype: "string",
|
|
333
|
+
itemDtype: null,
|
|
334
|
+
kind: "string",
|
|
335
|
+
list: false,
|
|
336
|
+
temporal: null,
|
|
337
|
+
precision: false
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function parseScalarText(text, kind, temporal = null) {
|
|
341
|
+
switch (kind) {
|
|
342
|
+
case "boolean": {
|
|
343
|
+
const value = parseBooleanText(text);
|
|
344
|
+
if (value === null) {
|
|
345
|
+
throw typeError(text, kind);
|
|
346
|
+
}
|
|
347
|
+
return value;
|
|
348
|
+
}
|
|
349
|
+
case "integer": {
|
|
350
|
+
const trimmed = text.trim();
|
|
351
|
+
if (!INTEGER_TEXT$1.test(trimmed)) {
|
|
352
|
+
throw typeError(text, kind);
|
|
353
|
+
}
|
|
354
|
+
const value = Number(trimmed);
|
|
355
|
+
if (value < I32_MIN || value > I32_MAX) {
|
|
356
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${trimmed}" is outside the 32-bit integer range`, {
|
|
357
|
+
value: trimmed,
|
|
358
|
+
kind
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
case "long": {
|
|
364
|
+
const trimmed = text.trim();
|
|
365
|
+
if (!INTEGER_TEXT$1.test(trimmed)) {
|
|
366
|
+
throw typeError(text, kind);
|
|
367
|
+
}
|
|
368
|
+
return Number(trimmed);
|
|
369
|
+
}
|
|
370
|
+
case "float":
|
|
371
|
+
case "double":
|
|
372
|
+
return parseDecimalText(text, kind);
|
|
373
|
+
case "string":
|
|
374
|
+
case "duration":
|
|
375
|
+
return text;
|
|
376
|
+
case "temporal": {
|
|
377
|
+
if (temporal === null) {
|
|
378
|
+
throw new GraphFormatError("E_COLUMN_TYPE", "a temporal value needs its temporal kind", { kind });
|
|
379
|
+
}
|
|
380
|
+
return parseTemporal(text.trim(), temporal).value;
|
|
381
|
+
}
|
|
382
|
+
case "point":
|
|
383
|
+
return parsePointText(text);
|
|
384
|
+
case "json":
|
|
385
|
+
return parseJsonText(text);
|
|
386
|
+
default: {
|
|
387
|
+
const name = kind;
|
|
388
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `unknown value kind ${name}`, { kind: name });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function parseBooleanText(text) {
|
|
393
|
+
switch (text.trim().toLowerCase()) {
|
|
394
|
+
case "true":
|
|
395
|
+
case "1":
|
|
396
|
+
return true;
|
|
397
|
+
case "false":
|
|
398
|
+
case "0":
|
|
399
|
+
return false;
|
|
400
|
+
default:
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function parseDecimalText(text, kind = "double") {
|
|
405
|
+
const trimmed = text.trim();
|
|
406
|
+
if (DECIMAL_TEXT.test(trimmed)) {
|
|
407
|
+
return Number(trimmed);
|
|
408
|
+
}
|
|
409
|
+
switch (trimmed) {
|
|
410
|
+
case "INF":
|
|
411
|
+
case "+INF":
|
|
412
|
+
case "Infinity":
|
|
413
|
+
case "+Infinity":
|
|
414
|
+
return Infinity;
|
|
415
|
+
case "-INF":
|
|
416
|
+
case "-Infinity":
|
|
417
|
+
return -Infinity;
|
|
418
|
+
case "NaN":
|
|
419
|
+
return NaN;
|
|
420
|
+
default:
|
|
421
|
+
throw typeError(text, kind);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function parsePointText(text) {
|
|
425
|
+
const trimmed = text.trim();
|
|
426
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
427
|
+
throw typeError(text, "point");
|
|
428
|
+
}
|
|
429
|
+
const out = {};
|
|
430
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
431
|
+
if (inner.length === 0) {
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
for (const part of inner.split(",")) {
|
|
435
|
+
const colon = part.indexOf(":");
|
|
436
|
+
if (colon < 0) {
|
|
437
|
+
throw typeError(text, "point");
|
|
438
|
+
}
|
|
439
|
+
const key = part.slice(0, colon).trim().replace(/^["']|["']$/g, "");
|
|
440
|
+
const raw = part.slice(colon + 1).trim();
|
|
441
|
+
if (key.length === 0) {
|
|
442
|
+
throw typeError(text, "point");
|
|
443
|
+
}
|
|
444
|
+
if (DECIMAL_TEXT.test(raw)) {
|
|
445
|
+
out[key] = Number(raw);
|
|
446
|
+
} else if (raw.startsWith("'") && raw.endsWith("'") || raw.startsWith('"') && raw.endsWith('"')) {
|
|
447
|
+
out[key] = raw.slice(1, -1);
|
|
448
|
+
} else {
|
|
449
|
+
throw typeError(text, "point");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
454
|
+
function parseJsonText(text) {
|
|
455
|
+
try {
|
|
456
|
+
return JSON.parse(text);
|
|
457
|
+
} catch {
|
|
458
|
+
throw typeError(text, "json");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function typeError(text, kind) {
|
|
462
|
+
const shown = text.length > 40 ? `${text.slice(0, 40)}...` : text;
|
|
463
|
+
return new GraphFormatError("E_COLUMN_TYPE", `"${shown}" is not a ${kind}`, { value: text, kind });
|
|
464
|
+
}
|
|
465
|
+
function splitListText(text, syntax) {
|
|
466
|
+
const trimmed = text.trim();
|
|
467
|
+
if (trimmed.length === 0) {
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
switch (syntax) {
|
|
471
|
+
case "gexf": {
|
|
472
|
+
if (isBracketed(trimmed)) {
|
|
473
|
+
return splitBracketed(trimmed);
|
|
474
|
+
}
|
|
475
|
+
const separator = firstSeparator(trimmed, ["|", ",", ";"]);
|
|
476
|
+
return separator === null ? [trimmed] : splitOn(trimmed, separator);
|
|
477
|
+
}
|
|
478
|
+
case "brackets":
|
|
479
|
+
return isBracketed(trimmed) ? splitBracketed(trimmed) : [trimmed];
|
|
480
|
+
case "pipe":
|
|
481
|
+
return splitOn(trimmed, "|");
|
|
482
|
+
case "comma":
|
|
483
|
+
return splitOn(trimmed, ",");
|
|
484
|
+
case "semicolon":
|
|
485
|
+
return splitOn(trimmed, ";");
|
|
486
|
+
default: {
|
|
487
|
+
const name = syntax;
|
|
488
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
|
|
489
|
+
option: "listSyntax",
|
|
490
|
+
found: name
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function joinListText(items, syntax) {
|
|
496
|
+
switch (syntax) {
|
|
497
|
+
case "gexf":
|
|
498
|
+
case "brackets":
|
|
499
|
+
return `[${items.map(quoteBracketItem).join(", ")}]`;
|
|
500
|
+
case "pipe":
|
|
501
|
+
return items.join("|");
|
|
502
|
+
case "comma":
|
|
503
|
+
return items.join(",");
|
|
504
|
+
case "semicolon":
|
|
505
|
+
return items.join(";");
|
|
506
|
+
default: {
|
|
507
|
+
const name = syntax;
|
|
508
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
|
|
509
|
+
option: "listSyntax",
|
|
510
|
+
found: name
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function isBracketed(text) {
|
|
516
|
+
return text.length >= 2 && text.startsWith("[") && text.endsWith("]");
|
|
517
|
+
}
|
|
518
|
+
function firstSeparator(text, candidates) {
|
|
519
|
+
for (const candidate of candidates) {
|
|
520
|
+
if (text.includes(candidate)) {
|
|
521
|
+
return candidate;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
function splitOn(text, separator) {
|
|
527
|
+
return text.split(separator).map((item) => item.trim());
|
|
528
|
+
}
|
|
529
|
+
function splitBracketed(text) {
|
|
530
|
+
const inner = text.slice(1, -1);
|
|
531
|
+
const items = [];
|
|
532
|
+
const n = inner.length;
|
|
533
|
+
if (inner.trim().length === 0) {
|
|
534
|
+
return items;
|
|
535
|
+
}
|
|
536
|
+
let i = 0;
|
|
537
|
+
for (; ; ) {
|
|
538
|
+
while (i < n && isSpace(inner.charCodeAt(i))) {
|
|
539
|
+
i++;
|
|
540
|
+
}
|
|
541
|
+
let item = "";
|
|
542
|
+
if (i < n && (inner[i] === '"' || inner[i] === "'")) {
|
|
543
|
+
const quote = inner[i];
|
|
544
|
+
i++;
|
|
545
|
+
while (i < n) {
|
|
546
|
+
if (inner[i] === quote) {
|
|
547
|
+
if (inner[i + 1] === quote) {
|
|
548
|
+
item += quote;
|
|
549
|
+
i += 2;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
i++;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
item += inner[i];
|
|
556
|
+
i++;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
let end = inner.indexOf(",", i);
|
|
560
|
+
if (end < 0) {
|
|
561
|
+
end = n;
|
|
562
|
+
}
|
|
563
|
+
item += inner.slice(i, end).trim();
|
|
564
|
+
items.push(item);
|
|
565
|
+
i = end;
|
|
566
|
+
if (i >= n) {
|
|
567
|
+
return items;
|
|
568
|
+
}
|
|
569
|
+
i++;
|
|
570
|
+
if (inner.slice(i).trim().length === 0) {
|
|
571
|
+
items.push("");
|
|
572
|
+
return items;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function isSpace(c) {
|
|
577
|
+
return c === 32 || c === 9 || c === 13 || c === 10;
|
|
578
|
+
}
|
|
579
|
+
function quoteBracketItem(item) {
|
|
580
|
+
if (item.length === 0 || /[,[\]"']/.test(item) || item !== item.trim()) {
|
|
581
|
+
return `"${item.replace(/"/g, '""')}"`;
|
|
582
|
+
}
|
|
583
|
+
return item;
|
|
584
|
+
}
|
|
585
|
+
const UNKNOWN_TYPE_CODE = UNKNOWN_ATTR_TYPE_CODE;
|
|
586
|
+
const RENAMED_CODE = COLUMN_RENAMED_CODE;
|
|
587
|
+
const DICT_SAMPLE_ROWS = 1024;
|
|
588
|
+
function declareAttribute(input) {
|
|
589
|
+
const issues = [];
|
|
590
|
+
const listSyntax = input.listSyntax ?? "gexf";
|
|
591
|
+
let spec;
|
|
592
|
+
if (input.type === null) {
|
|
593
|
+
spec = stringSpec(null);
|
|
594
|
+
} else {
|
|
595
|
+
const mapped = mapDeclaredType(input.format, input.type, input.long);
|
|
596
|
+
if (mapped === null) {
|
|
597
|
+
spec = stringSpec(input.type);
|
|
598
|
+
issues.push({
|
|
599
|
+
category: "unsupported",
|
|
600
|
+
code: UNKNOWN_TYPE_CODE,
|
|
601
|
+
message: `${input.format} type "${input.type}" is not supported; values kept as text`
|
|
602
|
+
});
|
|
603
|
+
} else {
|
|
604
|
+
spec = mapped;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const { name, renamed } = resolveName(input);
|
|
608
|
+
if (renamed) {
|
|
609
|
+
issues.push({
|
|
610
|
+
category: "coercion",
|
|
611
|
+
code: RENAMED_CODE,
|
|
612
|
+
message: `attribute "${input.title ?? input.id ?? ""}" renamed to "${name}": the name was taken`
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
const decl = {
|
|
616
|
+
name,
|
|
617
|
+
dtype: spec.dtype,
|
|
618
|
+
nullable: true,
|
|
619
|
+
origin: {
|
|
620
|
+
format: input.format,
|
|
621
|
+
id: input.id,
|
|
622
|
+
title: input.title !== null && input.title !== name ? input.title : null,
|
|
623
|
+
type: input.type,
|
|
624
|
+
namespace: input.namespace ?? null
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
if (spec.list) {
|
|
628
|
+
decl.itemDtype = spec.itemDtype ?? "string";
|
|
629
|
+
}
|
|
630
|
+
if (input.role !== void 0 && input.role !== null) {
|
|
631
|
+
decl.role = input.role;
|
|
632
|
+
}
|
|
633
|
+
if (input.dynamic === true) {
|
|
634
|
+
decl.dynamic = true;
|
|
635
|
+
}
|
|
636
|
+
const optionsText = input.optionsText ?? null;
|
|
637
|
+
if (optionsText !== null) {
|
|
638
|
+
try {
|
|
639
|
+
const options = splitListText(optionsText, listSyntax).map((item) => parseItem(item, spec));
|
|
640
|
+
decl.options = options;
|
|
641
|
+
if (spec.dtype === "string" && !spec.list && options.every((o) => typeof o === "string")) {
|
|
642
|
+
decl.dtype = "dict";
|
|
643
|
+
spec = { ...spec, dtype: "dict" };
|
|
644
|
+
}
|
|
645
|
+
} catch (err) {
|
|
646
|
+
issues.push({
|
|
647
|
+
category: "validation-error",
|
|
648
|
+
code: BAD_OPTIONS_CODE,
|
|
649
|
+
message: `options "${optionsText}" of attribute "${name}" do not parse as ${spec.declared}: ${messageOf(err)}`
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const defaultText = input.defaultText ?? null;
|
|
654
|
+
if (defaultText !== null) {
|
|
655
|
+
try {
|
|
656
|
+
decl.default = parseDeclaredValue(defaultText, spec, listSyntax);
|
|
657
|
+
} catch (err) {
|
|
658
|
+
issues.push({
|
|
659
|
+
category: "validation-error",
|
|
660
|
+
code: BAD_DEFAULT_CODE,
|
|
661
|
+
message: `default "${defaultText}" of attribute "${name}" does not parse as ${spec.declared}: ${messageOf(err)}`
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const companion = spec.temporal !== null && !spec.list ? timeTextCompanion(name) : null;
|
|
666
|
+
return { decl, spec, listSyntax, companion, renamed, issues };
|
|
667
|
+
}
|
|
668
|
+
function parseDeclaredValue(text, spec, listSyntax) {
|
|
669
|
+
if (spec.list) {
|
|
670
|
+
return splitListText(text, listSyntax).map((item) => parseItem(item, spec));
|
|
671
|
+
}
|
|
672
|
+
return parseItem(text, spec);
|
|
673
|
+
}
|
|
674
|
+
function parseDeclaredTemporal(text, spec) {
|
|
675
|
+
if (spec.temporal === null || spec.list) {
|
|
676
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `"${spec.declared}" is not a scalar temporal type`, {
|
|
677
|
+
declared: spec.declared
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
return parseTemporal(text.trim(), spec.temporal);
|
|
681
|
+
}
|
|
682
|
+
const INTEGER_TEXT = /^[+-]?[0-9]+$/;
|
|
683
|
+
function losesPrecision(spec, text) {
|
|
684
|
+
if (!spec.precision) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
const trimmed = text.trim();
|
|
688
|
+
if (!INTEGER_TEXT.test(trimmed)) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
const n = Number(trimmed);
|
|
692
|
+
return !Number.isFinite(n) || BigInt(trimmed) !== BigInt(n);
|
|
693
|
+
}
|
|
694
|
+
function declareOn(sink, domain, decl) {
|
|
695
|
+
return domain === "node" ? sink.declareNodeColumn(decl) : sink.declareEdgeColumn(decl);
|
|
696
|
+
}
|
|
697
|
+
function declareResolved(sink, domain, decl, report, where) {
|
|
698
|
+
let current = decl;
|
|
699
|
+
let renamed = false;
|
|
700
|
+
let roleDropped = false;
|
|
701
|
+
for (; ; ) {
|
|
702
|
+
try {
|
|
703
|
+
return { handle: declareOn(sink, domain, current), decl: current, renamed, roleDropped };
|
|
704
|
+
} catch (err) {
|
|
705
|
+
if (!(err instanceof GraphFormatError)) {
|
|
706
|
+
throw err;
|
|
707
|
+
}
|
|
708
|
+
if (err.code === "E_COLUMN_EXISTS" && !renamed) {
|
|
709
|
+
const taken = takenIn(sink, domain);
|
|
710
|
+
const id = current.origin?.id ?? null;
|
|
711
|
+
const name = uniqueColumnName(current.name, id, taken);
|
|
712
|
+
report.warning(
|
|
713
|
+
"coercion",
|
|
714
|
+
COLUMN_RENAMED_CODE,
|
|
715
|
+
`${domain} column "${current.name}" renamed to "${name}": the sink already holds that name with another shape`,
|
|
716
|
+
{ ...where, element: where?.element ?? current.name }
|
|
717
|
+
);
|
|
718
|
+
current = { ...current, name };
|
|
719
|
+
renamed = true;
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (err.code === "E_DUPLICATE_ROLE" && current.role !== void 0 && !roleDropped) {
|
|
723
|
+
const holder = typeof err.details.holder === "string" ? err.details.holder : "another column";
|
|
724
|
+
report.warning(
|
|
725
|
+
"coercion",
|
|
726
|
+
ROLE_TAKEN_CODE,
|
|
727
|
+
`${domain} column "${current.name}" loses the ${current.role} role: "${holder}" already holds it`,
|
|
728
|
+
{ ...where, element: where?.element ?? current.name }
|
|
729
|
+
);
|
|
730
|
+
const { role: _role, ...withoutRole } = current;
|
|
731
|
+
current = withoutRole;
|
|
732
|
+
roleDropped = true;
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
throw err;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function declareCompanion(sink, domain, companion, report) {
|
|
740
|
+
try {
|
|
741
|
+
return declareOn(sink, domain, companion);
|
|
742
|
+
} catch (err) {
|
|
743
|
+
if (err instanceof GraphFormatError && err.code === "E_DUPLICATE_ROLE") {
|
|
744
|
+
report?.warnOnce(
|
|
745
|
+
"coercion",
|
|
746
|
+
ROLE_TAKEN_CODE,
|
|
747
|
+
`${domain} column "${companion.name}" is declared without the timeText role: another companion holds it (exporters use extra.for)`,
|
|
748
|
+
{ element: companion.name }
|
|
749
|
+
);
|
|
750
|
+
const { role: _role, ...withoutRole } = companion;
|
|
751
|
+
return declareOn(sink, domain, withoutRole);
|
|
752
|
+
}
|
|
753
|
+
throw err;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function takenIn(sink, domain) {
|
|
757
|
+
return (name) => (domain === "node" ? sink.nodeColumn(name) : sink.edgeColumn(name)) !== INVALID_INDEX;
|
|
758
|
+
}
|
|
759
|
+
function uniqueColumnName(name, id, taken) {
|
|
760
|
+
if (!taken(name)) {
|
|
761
|
+
return name;
|
|
762
|
+
}
|
|
763
|
+
const base = id === null ? name : `${name}#${id}`;
|
|
764
|
+
if (id !== null && !taken(base)) {
|
|
765
|
+
return base;
|
|
766
|
+
}
|
|
767
|
+
for (let n = 2; ; n++) {
|
|
768
|
+
const candidate = `${base}#${n}`;
|
|
769
|
+
if (!taken(candidate)) {
|
|
770
|
+
return candidate;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
class DictHeuristic {
|
|
775
|
+
/**
|
|
776
|
+
* Create a heuristic.
|
|
777
|
+
* @param sampleRows - rows observed before deciding; DICT_SAMPLE_ROWS by default
|
|
778
|
+
*/
|
|
779
|
+
constructor(sampleRows = DICT_SAMPLE_ROWS) {
|
|
780
|
+
this.distinct = /* @__PURE__ */ new Set();
|
|
781
|
+
this.rowsSeen = 0;
|
|
782
|
+
this.sampleRows = sampleRows;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Rows observed so far.
|
|
786
|
+
* @returns the count
|
|
787
|
+
*/
|
|
788
|
+
get rows() {
|
|
789
|
+
return this.rowsSeen;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Distinct values observed so far.
|
|
793
|
+
* @returns the count
|
|
794
|
+
*/
|
|
795
|
+
get distinctCount() {
|
|
796
|
+
return this.distinct.size;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Whether the sample is full.
|
|
800
|
+
* @returns true once sampleRows values were observed
|
|
801
|
+
*/
|
|
802
|
+
get decided() {
|
|
803
|
+
return this.rowsSeen >= this.sampleRows;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Observe one value.
|
|
807
|
+
* @param value - the value text
|
|
808
|
+
* @returns true when this observation completed the sample
|
|
809
|
+
*/
|
|
810
|
+
observe(value) {
|
|
811
|
+
this.rowsSeen++;
|
|
812
|
+
if (this.distinct.size < this.rowsSeen) {
|
|
813
|
+
this.distinct.add(value);
|
|
814
|
+
}
|
|
815
|
+
return this.rowsSeen === this.sampleRows;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* The dtype the sample suggests: dict when the distinct count is below rows / 2 (and at least
|
|
819
|
+
* one row was seen), string otherwise. Valid before the sample is full (end of input).
|
|
820
|
+
* @returns "dict" or "string"
|
|
821
|
+
*/
|
|
822
|
+
decide() {
|
|
823
|
+
return this.rowsSeen > 0 && this.distinct.size * 2 < this.rowsSeen ? "dict" : "string";
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function resolveName(input) {
|
|
827
|
+
const preferred = input.title !== null && input.title.length > 0 ? input.title : input.id;
|
|
828
|
+
if (preferred === null || preferred.length === 0) {
|
|
829
|
+
throw new GraphFormatError("E_COLUMN_TYPE", "an attribute declaration needs a title or an id", {
|
|
830
|
+
field: "name",
|
|
831
|
+
format: input.format
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (input.taken === void 0) {
|
|
835
|
+
return { name: preferred, renamed: false };
|
|
836
|
+
}
|
|
837
|
+
const name = uniqueColumnName(preferred, input.id, input.taken);
|
|
838
|
+
return { name, renamed: name !== preferred };
|
|
839
|
+
}
|
|
840
|
+
function parseItem(text, spec) {
|
|
841
|
+
return parseScalarText(text, spec.kind, spec.temporal);
|
|
842
|
+
}
|
|
843
|
+
function messageOf(err) {
|
|
844
|
+
return err instanceof Error ? err.message : String(err);
|
|
845
|
+
}
|
|
846
|
+
const DIRECTED_COLUMN = "graphty.directed";
|
|
847
|
+
const PAIR_COLUMN = "graphty.pair";
|
|
848
|
+
const MUTUAL_COLUMN = "graphty.mutual";
|
|
849
|
+
const DIRECTED_DECL = { name: DIRECTED_COLUMN, dtype: "bool", role: "directed" };
|
|
850
|
+
const PAIR_DECL = { name: PAIR_COLUMN, dtype: "u32", role: "pair", refersTo: "edge" };
|
|
851
|
+
const MUTUAL_DECL = { name: MUTUAL_COLUMN, dtype: "bool", role: "mutual" };
|
|
852
|
+
class DirectionResolver {
|
|
853
|
+
/**
|
|
854
|
+
* Create a resolver.
|
|
855
|
+
* @param sink - the sink edges are pushed into
|
|
856
|
+
* @param report - the report issues are recorded in
|
|
857
|
+
* @param policy - the resolved onMixedDirection option
|
|
858
|
+
*/
|
|
859
|
+
constructor(sink, report, policy) {
|
|
860
|
+
this.directedHandle = INVALID_INDEX;
|
|
861
|
+
this.pairHandle = INVALID_INDEX;
|
|
862
|
+
this.mutualHandle = INVALID_INDEX;
|
|
863
|
+
this.firstEdge = -1;
|
|
864
|
+
this.nextEdge = -1;
|
|
865
|
+
this.forcedCount = 0;
|
|
866
|
+
this.headerSet = false;
|
|
867
|
+
this.mirrorOfLast = INVALID_INDEX;
|
|
868
|
+
this.sink = sink;
|
|
869
|
+
this.report = report;
|
|
870
|
+
this.policy = policy;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* The sink's current direction.
|
|
874
|
+
* @returns true when directed
|
|
875
|
+
*/
|
|
876
|
+
get directed() {
|
|
877
|
+
return this.sink.directed;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Whether the reserved direction columns exist (the sink holds expanded edges).
|
|
881
|
+
* @returns true once graphty.pair was declared or adopted
|
|
882
|
+
*/
|
|
883
|
+
get expanded() {
|
|
884
|
+
return this.pairHandle !== INVALID_INDEX;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Rule 1 of design section 8.4: set the sink's direction from the file's header (or the
|
|
888
|
+
* `defaultDirected` option for a file that declares none) before the first edge. Under the
|
|
889
|
+
* "directed" / "undirected" policies the policy's direction is used instead and the difference
|
|
890
|
+
* is reported. A refusal (E_DIRECTED: locked, or a non-empty directed sink for an undirected
|
|
891
|
+
* header) is recorded as a coercion issue and the sink's direction stands; a non-empty unlocked
|
|
892
|
+
* undirected sink with a directed header is expanded in place.
|
|
893
|
+
* @param headerDirected - the direction the file declares (or the default)
|
|
894
|
+
* @param where - the line of the header, when known
|
|
895
|
+
*/
|
|
896
|
+
setHeader(headerDirected, where) {
|
|
897
|
+
this.headerSet = true;
|
|
898
|
+
let wanted = headerDirected;
|
|
899
|
+
if (this.policy === "directed" || this.policy === "undirected") {
|
|
900
|
+
wanted = this.policy === "directed";
|
|
901
|
+
if (wanted !== headerDirected) {
|
|
902
|
+
this.report.warning(
|
|
903
|
+
"coercion",
|
|
904
|
+
DIRECTION_FORCED_CODE,
|
|
905
|
+
`the file declares ${direction(headerDirected)} edges; read as ${direction(wanted)} per onMixedDirection`,
|
|
906
|
+
{ line: where?.line ?? null }
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const { sink } = this;
|
|
911
|
+
if (sink.directed === wanted) {
|
|
912
|
+
this.adoptExisting();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
try {
|
|
916
|
+
if (wanted && sink.edgeCount > 0 && !sink.directedLocked) {
|
|
917
|
+
const before = sink.edgeCount;
|
|
918
|
+
sink.setDirected(true, { expand: true });
|
|
919
|
+
this.adoptExpansionColumns();
|
|
920
|
+
this.report.counts.expandedMixed += before;
|
|
921
|
+
} else {
|
|
922
|
+
sink.setDirected(wanted);
|
|
923
|
+
}
|
|
924
|
+
} catch (err) {
|
|
925
|
+
if (!(err instanceof GraphFormatError) || err.code !== "E_DIRECTED") {
|
|
926
|
+
throw err;
|
|
927
|
+
}
|
|
928
|
+
this.report.warning(
|
|
929
|
+
"coercion",
|
|
930
|
+
DIRECTION_REFUSED_CODE,
|
|
931
|
+
`the file is ${direction(wanted)} but the sink is ${direction(sink.directed)} (${reasonOf(err)}); read as ${direction(sink.directed)}`,
|
|
932
|
+
{ line: where?.line ?? null }
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
this.adoptExisting();
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Pick up reserved columns a caller's sink already holds (an earlier expansion), so every edge
|
|
939
|
+
* pushed from now on gets its graphty.directed value.
|
|
940
|
+
*/
|
|
941
|
+
adoptExisting() {
|
|
942
|
+
if (this.pairHandle === INVALID_INDEX && this.sink.edgeColumn(PAIR_COLUMN) !== INVALID_INDEX) {
|
|
943
|
+
this.adoptExpansionColumns();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* The mirror half of the edge most recently pushed by addEdge(), when it was expanded into a
|
|
948
|
+
* pair; INVALID_INDEX otherwise. An importer that learns an edge's weight after pushing it
|
|
949
|
+
* (a GEXF attvalue) sets it on both halves.
|
|
950
|
+
* @returns the mirror's logical index, or INVALID_INDEX
|
|
951
|
+
*/
|
|
952
|
+
get lastMirror() {
|
|
953
|
+
return this.mirrorOfLast;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Rule 2 of design section 8.4: push one source edge. An edge whose direction equals the
|
|
957
|
+
* sink's is one addEdge; one that differs is expanded, forced or refused per the policy.
|
|
958
|
+
* @param source - the source id
|
|
959
|
+
* @param target - the target id
|
|
960
|
+
* @param kind - the edge's direction in the file
|
|
961
|
+
* @param weight - the weight, or undefined when the file gives none
|
|
962
|
+
* @param where - the line and element, for issues
|
|
963
|
+
* @returns the logical index of the (primary) edge
|
|
964
|
+
*/
|
|
965
|
+
addEdge(source, target, kind, weight, where) {
|
|
966
|
+
if (!this.headerSet) {
|
|
967
|
+
throw new GraphFormatError("E_DIRECTED", "DirectionResolver.setHeader() must run before the first edge", {
|
|
968
|
+
reason: "header not set"
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
const { sink } = this;
|
|
972
|
+
this.mirrorOfLast = INVALID_INDEX;
|
|
973
|
+
const edgeDirected = kind !== "undirected";
|
|
974
|
+
if (edgeDirected === sink.directed && kind !== "mutual") {
|
|
975
|
+
const e = sink.addEdge(source, target, weight);
|
|
976
|
+
this.track(e);
|
|
977
|
+
if (this.directedHandle !== INVALID_INDEX) {
|
|
978
|
+
sink.setEdgeValue(this.directedHandle, e, edgeDirected);
|
|
979
|
+
}
|
|
980
|
+
return e;
|
|
981
|
+
}
|
|
982
|
+
switch (this.policy) {
|
|
983
|
+
case "directed":
|
|
984
|
+
case "undirected": {
|
|
985
|
+
this.forcedCount++;
|
|
986
|
+
this.report.warnOnce(
|
|
987
|
+
"coercion",
|
|
988
|
+
DIRECTION_FORCED_CODE,
|
|
989
|
+
`${kind} edges read as ${direction(sink.directed)} per onMixedDirection`,
|
|
990
|
+
where
|
|
991
|
+
);
|
|
992
|
+
const e = sink.addEdge(source, target, weight);
|
|
993
|
+
this.track(e);
|
|
994
|
+
if (this.directedHandle !== INVALID_INDEX) {
|
|
995
|
+
sink.setEdgeValue(this.directedHandle, e, sink.directed);
|
|
996
|
+
}
|
|
997
|
+
return e;
|
|
998
|
+
}
|
|
999
|
+
case "error":
|
|
1000
|
+
this.report.error(
|
|
1001
|
+
"validation-error",
|
|
1002
|
+
MIXED_DIRECTION_CODE,
|
|
1003
|
+
`${kind} edge in a ${direction(sink.directed)} graph (onMixedDirection: "error")`,
|
|
1004
|
+
where
|
|
1005
|
+
);
|
|
1006
|
+
throw this.report.abort("mixed direction refused", { code: MIXED_DIRECTION_CODE });
|
|
1007
|
+
case "expand":
|
|
1008
|
+
return this.expand(source, target, kind, weight, where);
|
|
1009
|
+
default: {
|
|
1010
|
+
const name = this.policy;
|
|
1011
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown onMixedDirection ${name}`, {
|
|
1012
|
+
option: "onMixedDirection",
|
|
1013
|
+
found: name
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* How many edges were pushed with a direction other than their own under the forcing policies.
|
|
1020
|
+
* @returns the count
|
|
1021
|
+
*/
|
|
1022
|
+
get forced() {
|
|
1023
|
+
return this.forcedCount;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Expand one edge that differs from the sink's direction.
|
|
1027
|
+
* @param source - the source id
|
|
1028
|
+
* @param target - the target id
|
|
1029
|
+
* @param kind - the edge's direction in the file
|
|
1030
|
+
* @param weight - the weight, or undefined
|
|
1031
|
+
* @param where - the line and element, for issues
|
|
1032
|
+
* @returns the primary edge index
|
|
1033
|
+
*/
|
|
1034
|
+
expand(source, target, kind, weight, where) {
|
|
1035
|
+
const { sink } = this;
|
|
1036
|
+
if (!sink.directed) {
|
|
1037
|
+
try {
|
|
1038
|
+
const before = sink.edgeCount;
|
|
1039
|
+
sink.setDirected(true, { expand: true });
|
|
1040
|
+
this.adoptExpansionColumns();
|
|
1041
|
+
this.report.counts.expandedMixed += before;
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
if (!(err instanceof GraphFormatError) || err.code !== "E_DIRECTED") {
|
|
1044
|
+
throw err;
|
|
1045
|
+
}
|
|
1046
|
+
this.report.error(
|
|
1047
|
+
"coercion",
|
|
1048
|
+
DIRECTION_REFUSED_CODE,
|
|
1049
|
+
`${direction(true)} edge in a locked ${direction(false)} sink cannot be expanded`,
|
|
1050
|
+
where
|
|
1051
|
+
);
|
|
1052
|
+
throw this.report.abort("the sink refused the expansion to directed", {
|
|
1053
|
+
code: DIRECTION_REFUSED_CODE,
|
|
1054
|
+
reason: err.details.reason
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
if (kind === "directed") {
|
|
1058
|
+
const e = sink.addEdge(source, target, weight);
|
|
1059
|
+
this.track(e);
|
|
1060
|
+
sink.setEdgeValue(this.directedHandle, e, true);
|
|
1061
|
+
return e;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
this.ensureExpansionColumns();
|
|
1065
|
+
const first = sink.addEdge(source, target, weight);
|
|
1066
|
+
this.track(first);
|
|
1067
|
+
const mutual = kind === "mutual";
|
|
1068
|
+
sink.setEdgeValue(this.directedHandle, first, mutual);
|
|
1069
|
+
this.report.counts.expandedMixed++;
|
|
1070
|
+
if (mutual) {
|
|
1071
|
+
this.ensureMutualColumn();
|
|
1072
|
+
sink.setEdgeValue(this.mutualHandle, first, true);
|
|
1073
|
+
}
|
|
1074
|
+
const u = sink.indexOf(source);
|
|
1075
|
+
const v = sink.indexOf(target);
|
|
1076
|
+
if (u === v) {
|
|
1077
|
+
return first;
|
|
1078
|
+
}
|
|
1079
|
+
const mirror = sink.addEdge(target, source, weight);
|
|
1080
|
+
this.track(mirror);
|
|
1081
|
+
this.mirrorOfLast = mirror;
|
|
1082
|
+
sink.setEdgeValue(this.directedHandle, mirror, mutual);
|
|
1083
|
+
sink.setEdgeValue(this.pairHandle, first, mirror);
|
|
1084
|
+
sink.setEdgeValue(this.pairHandle, mirror, first);
|
|
1085
|
+
return first;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Note an edge index this resolver pushed, for the backfill of graphty.directed.
|
|
1089
|
+
* @param e - the edge index
|
|
1090
|
+
*/
|
|
1091
|
+
track(e) {
|
|
1092
|
+
if (this.firstEdge < 0) {
|
|
1093
|
+
this.firstEdge = e;
|
|
1094
|
+
}
|
|
1095
|
+
this.nextEdge = e + 1;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Pick up the reserved columns the builder wrote during its own expansion.
|
|
1099
|
+
*/
|
|
1100
|
+
adoptExpansionColumns() {
|
|
1101
|
+
this.directedHandle = this.sink.declareEdgeColumn(DIRECTED_DECL);
|
|
1102
|
+
this.pairHandle = this.sink.declareEdgeColumn(PAIR_DECL);
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Declare the reserved columns on first use in a directed sink and backfill graphty.directed
|
|
1106
|
+
* = true for every edge this resolver pushed so far (they were directed in the source).
|
|
1107
|
+
*/
|
|
1108
|
+
ensureExpansionColumns() {
|
|
1109
|
+
if (this.pairHandle !== INVALID_INDEX) {
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
this.directedHandle = this.sink.declareEdgeColumn(DIRECTED_DECL);
|
|
1113
|
+
this.pairHandle = this.sink.declareEdgeColumn(PAIR_DECL);
|
|
1114
|
+
if (this.firstEdge >= 0) {
|
|
1115
|
+
for (let e = this.firstEdge; e < this.nextEdge; e++) {
|
|
1116
|
+
this.sink.setEdgeValue(this.directedHandle, e, true);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
/** Declare graphty.mutual on first use. */
|
|
1121
|
+
ensureMutualColumn() {
|
|
1122
|
+
if (this.mutualHandle === INVALID_INDEX) {
|
|
1123
|
+
this.mutualHandle = this.sink.declareEdgeColumn(MUTUAL_DECL);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
function reasonOf(err) {
|
|
1128
|
+
const { reason } = err.details;
|
|
1129
|
+
return typeof reason === "string" ? reason : "refused";
|
|
1130
|
+
}
|
|
1131
|
+
function direction(directed) {
|
|
1132
|
+
return directed ? "directed" : "undirected";
|
|
1133
|
+
}
|
|
1134
|
+
function pairFolding(snapshot, options = {}) {
|
|
1135
|
+
const foldMutual = options.foldMutual ?? false;
|
|
1136
|
+
const directedColumn = roleColumn(snapshot.edges.byRole("directed"), "bool");
|
|
1137
|
+
const pairColumn = roleColumn(snapshot.edges.byRole("pair"), "u32");
|
|
1138
|
+
const mutualColumn = roleColumn(snapshot.edges.byRole("mutual"), "bool");
|
|
1139
|
+
const { edgeCount } = snapshot;
|
|
1140
|
+
const mateOf = (e) => {
|
|
1141
|
+
if (pairColumn === null || !pairColumn.isSet(e)) {
|
|
1142
|
+
return INVALID_INDEX;
|
|
1143
|
+
}
|
|
1144
|
+
const mate = pairColumn.value(e);
|
|
1145
|
+
return typeof mate === "number" && mate !== INVALID_INDEX ? mate : INVALID_INDEX;
|
|
1146
|
+
};
|
|
1147
|
+
const sourceDirected = (e) => directedColumn === null || !directedColumn.isSet(e) || directedColumn.value(e) === true;
|
|
1148
|
+
const isMutual = (e) => {
|
|
1149
|
+
if (mutualColumn === null) {
|
|
1150
|
+
return false;
|
|
1151
|
+
}
|
|
1152
|
+
if (mutualColumn.isSet(e) && mutualColumn.value(e) === true) {
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
const mate = mateOf(e);
|
|
1156
|
+
return mate !== INVALID_INDEX && mutualColumn.isSet(mate) && mutualColumn.value(mate) === true;
|
|
1157
|
+
};
|
|
1158
|
+
const folded = (e) => {
|
|
1159
|
+
const mate = mateOf(e);
|
|
1160
|
+
if (mate === INVALID_INDEX || mate >= e) {
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
return !sourceDirected(e) || foldMutual && isMutual(e);
|
|
1164
|
+
};
|
|
1165
|
+
let mutualCount = 0;
|
|
1166
|
+
if (mutualColumn !== null) {
|
|
1167
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
1168
|
+
if (mutualColumn.isSet(e) && mutualColumn.value(e) === true && mateOf(e) > e) {
|
|
1169
|
+
mutualCount++;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return { expanded: pairColumn !== null, mutualCount, folded, mateOf, sourceDirected, isMutual };
|
|
1174
|
+
}
|
|
1175
|
+
function roleColumn(column, dtype) {
|
|
1176
|
+
return column !== null && column.dtype === dtype ? column : null;
|
|
1177
|
+
}
|
|
1178
|
+
const CANONICAL_INTEGER = /^-?(0|[1-9][0-9]*)$/;
|
|
1179
|
+
function canonicalId(text) {
|
|
1180
|
+
if (text !== "-0" && CANONICAL_INTEGER.test(text)) {
|
|
1181
|
+
const n = Number(text);
|
|
1182
|
+
if (Number.isSafeInteger(n)) {
|
|
1183
|
+
return n;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return text;
|
|
1187
|
+
}
|
|
1188
|
+
function isCanonicalIntegerText(text) {
|
|
1189
|
+
return typeof canonicalId(text) === "number";
|
|
1190
|
+
}
|
|
1191
|
+
function coerceIdText(text, mode) {
|
|
1192
|
+
switch (mode) {
|
|
1193
|
+
case "keep":
|
|
1194
|
+
case "string":
|
|
1195
|
+
return text;
|
|
1196
|
+
case "canonical":
|
|
1197
|
+
return canonicalId(text);
|
|
1198
|
+
case "number": {
|
|
1199
|
+
if (text.trim().length === 0) {
|
|
1200
|
+
throw invalidId(text, "empty text is not a number");
|
|
1201
|
+
}
|
|
1202
|
+
const n = Number(text);
|
|
1203
|
+
if (!Number.isFinite(n)) {
|
|
1204
|
+
throw invalidId(text, "not a finite number");
|
|
1205
|
+
}
|
|
1206
|
+
return n;
|
|
1207
|
+
}
|
|
1208
|
+
default: {
|
|
1209
|
+
const name = mode;
|
|
1210
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown id coercion ${name}`, { option: "ids", found: name });
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function coerceId(value, mode) {
|
|
1215
|
+
if (typeof value === "string") {
|
|
1216
|
+
return coerceIdText(value, mode);
|
|
1217
|
+
}
|
|
1218
|
+
if (typeof value === "number") {
|
|
1219
|
+
if (!Number.isFinite(value)) {
|
|
1220
|
+
throw invalidId(value, "not a finite number");
|
|
1221
|
+
}
|
|
1222
|
+
return mode === "string" ? String(value) : value;
|
|
1223
|
+
}
|
|
1224
|
+
if (mode === "string" && (typeof value === "boolean" || typeof value === "bigint" || value === null)) {
|
|
1225
|
+
return String(value);
|
|
1226
|
+
}
|
|
1227
|
+
throw invalidId(value, `a ${value === null ? "null" : typeof value} is not an id under ids: "${mode}"`);
|
|
1228
|
+
}
|
|
1229
|
+
function invalidId(value, reason) {
|
|
1230
|
+
const shown = typeof value === "string" || typeof value === "number" ? value : typeof value;
|
|
1231
|
+
return new GraphFormatError("E_INVALID_ID", `invalid node id ${JSON.stringify(shown)}: ${reason}`, {
|
|
1232
|
+
reason,
|
|
1233
|
+
value: typeof value === "bigint" ? value.toString() : value
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
class IdCoercer {
|
|
1237
|
+
/**
|
|
1238
|
+
* Create a coercer.
|
|
1239
|
+
* @param mode - the rule
|
|
1240
|
+
*/
|
|
1241
|
+
constructor(mode) {
|
|
1242
|
+
this.mergeCount = 0;
|
|
1243
|
+
this.lastMerge = null;
|
|
1244
|
+
this.mode = mode;
|
|
1245
|
+
this.firstText = mode === "number" ? /* @__PURE__ */ new Map() : null;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Coerce a text cell, recording a merge under "number" when a different text already produced
|
|
1249
|
+
* the same number.
|
|
1250
|
+
* @param text - the cell text
|
|
1251
|
+
* @returns the id
|
|
1252
|
+
*/
|
|
1253
|
+
text(text) {
|
|
1254
|
+
const id = coerceIdText(text, this.mode);
|
|
1255
|
+
this.lastMerge = null;
|
|
1256
|
+
if (this.firstText !== null && typeof id === "number") {
|
|
1257
|
+
const previous = this.firstText.get(id);
|
|
1258
|
+
if (previous === void 0) {
|
|
1259
|
+
this.firstText.set(id, text);
|
|
1260
|
+
} else if (previous !== text) {
|
|
1261
|
+
this.mergeCount++;
|
|
1262
|
+
this.lastMerge = { id, text, previousText: previous };
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return id;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Coerce a typed value.
|
|
1269
|
+
* @param value - the value
|
|
1270
|
+
* @returns the id
|
|
1271
|
+
*/
|
|
1272
|
+
value(value) {
|
|
1273
|
+
if (typeof value === "string") {
|
|
1274
|
+
return this.text(value);
|
|
1275
|
+
}
|
|
1276
|
+
this.lastMerge = null;
|
|
1277
|
+
return coerceId(value, this.mode);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const DECODE_SLICE = 256 * 1024;
|
|
1281
|
+
const BOM = String.fromCharCode(65279);
|
|
1282
|
+
function isImportInput(input) {
|
|
1283
|
+
if (typeof input === "string" || input instanceof Uint8Array) {
|
|
1284
|
+
return true;
|
|
1285
|
+
}
|
|
1286
|
+
if (typeof input !== "object" || input === null) {
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
return typeof input.getReader === "function" || Symbol.asyncIterator in input;
|
|
1290
|
+
}
|
|
1291
|
+
function inputLength(input) {
|
|
1292
|
+
if (typeof input === "string") {
|
|
1293
|
+
return input.length;
|
|
1294
|
+
}
|
|
1295
|
+
if (input instanceof Uint8Array) {
|
|
1296
|
+
return input.byteLength;
|
|
1297
|
+
}
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1300
|
+
function throwIfAborted(signal) {
|
|
1301
|
+
if (signal === null || signal === void 0 || !signal.aborted) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const { reason } = signal;
|
|
1305
|
+
if (reason === void 0) {
|
|
1306
|
+
throw abortError();
|
|
1307
|
+
}
|
|
1308
|
+
throw reason;
|
|
1309
|
+
}
|
|
1310
|
+
function abortError() {
|
|
1311
|
+
const message = "The import was aborted";
|
|
1312
|
+
if (typeof DOMException === "function") {
|
|
1313
|
+
return new DOMException(message, "AbortError");
|
|
1314
|
+
}
|
|
1315
|
+
const err = new Error(message);
|
|
1316
|
+
err.name = "AbortError";
|
|
1317
|
+
return err;
|
|
1318
|
+
}
|
|
1319
|
+
async function* textChunks(input, report, options = {}) {
|
|
1320
|
+
const signal = options.signal ?? null;
|
|
1321
|
+
const onProgress = options.onProgress ?? null;
|
|
1322
|
+
const total = inputLength(input);
|
|
1323
|
+
let done = 0;
|
|
1324
|
+
let first = true;
|
|
1325
|
+
const emit = (text) => {
|
|
1326
|
+
if (first && text.length > 0) {
|
|
1327
|
+
first = false;
|
|
1328
|
+
return text.startsWith(BOM) ? text.slice(1) : text;
|
|
1329
|
+
}
|
|
1330
|
+
return text;
|
|
1331
|
+
};
|
|
1332
|
+
throwIfAborted(signal);
|
|
1333
|
+
if (typeof input === "string") {
|
|
1334
|
+
const text = emit(input);
|
|
1335
|
+
done = input.length;
|
|
1336
|
+
if (text.length > 0) {
|
|
1337
|
+
yield text;
|
|
1338
|
+
}
|
|
1339
|
+
onProgress?.(done, total ?? void 0);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
1343
|
+
const decode = (bytes, stream) => {
|
|
1344
|
+
try {
|
|
1345
|
+
return decoder.decode(bytes, { stream });
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
if (err instanceof TypeError) {
|
|
1348
|
+
report.fail(INVALID_UTF8_CODE, `invalid UTF-8 near byte ${done}`, void 0, { byteOffset: done });
|
|
1349
|
+
}
|
|
1350
|
+
throw err;
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
if (input instanceof Uint8Array) {
|
|
1354
|
+
for (let offset = 0; offset < input.byteLength; offset += DECODE_SLICE) {
|
|
1355
|
+
throwIfAborted(signal);
|
|
1356
|
+
const slice = input.subarray(offset, Math.min(offset + DECODE_SLICE, input.byteLength));
|
|
1357
|
+
const text = emit(decode(slice, offset + DECODE_SLICE < input.byteLength));
|
|
1358
|
+
done = Math.min(offset + DECODE_SLICE, input.byteLength);
|
|
1359
|
+
if (text.length > 0) {
|
|
1360
|
+
yield text;
|
|
1361
|
+
}
|
|
1362
|
+
onProgress?.(done, total ?? void 0);
|
|
1363
|
+
}
|
|
1364
|
+
if (input.byteLength === 0) {
|
|
1365
|
+
onProgress?.(0, 0);
|
|
1366
|
+
}
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
const chunks = isReadableStream(input) ? streamChunks(input, signal) : input;
|
|
1370
|
+
for await (const chunk of chunks) {
|
|
1371
|
+
throwIfAborted(signal);
|
|
1372
|
+
let text;
|
|
1373
|
+
if (typeof chunk === "string") {
|
|
1374
|
+
const pending = decode(new Uint8Array(0), false);
|
|
1375
|
+
text = emit(pending + chunk);
|
|
1376
|
+
done += chunk.length;
|
|
1377
|
+
} else if (chunk instanceof Uint8Array) {
|
|
1378
|
+
text = emit(decode(chunk, true));
|
|
1379
|
+
done += chunk.byteLength;
|
|
1380
|
+
} else {
|
|
1381
|
+
throw new GraphFormatError("E_UNSUPPORTED", "an input chunk must be a string or a Uint8Array", {
|
|
1382
|
+
reason: "chunk type",
|
|
1383
|
+
found: typeof chunk
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
if (text.length > 0) {
|
|
1387
|
+
yield text;
|
|
1388
|
+
}
|
|
1389
|
+
onProgress?.(done);
|
|
1390
|
+
}
|
|
1391
|
+
const tail = emit(decode(new Uint8Array(0), false));
|
|
1392
|
+
if (tail.length > 0) {
|
|
1393
|
+
yield tail;
|
|
1394
|
+
}
|
|
1395
|
+
onProgress?.(done, done);
|
|
1396
|
+
}
|
|
1397
|
+
function isReadableStream(input) {
|
|
1398
|
+
return typeof input.getReader === "function";
|
|
1399
|
+
}
|
|
1400
|
+
async function* streamChunks(stream, signal) {
|
|
1401
|
+
const reader = stream.getReader();
|
|
1402
|
+
let finished = false;
|
|
1403
|
+
try {
|
|
1404
|
+
for (; ; ) {
|
|
1405
|
+
throwIfAborted(signal);
|
|
1406
|
+
const { done, value } = await reader.read();
|
|
1407
|
+
if (done) {
|
|
1408
|
+
finished = true;
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
yield value;
|
|
1412
|
+
}
|
|
1413
|
+
} finally {
|
|
1414
|
+
if (!finished) {
|
|
1415
|
+
await reader.cancel(signal?.reason).catch(() => void 0);
|
|
1416
|
+
}
|
|
1417
|
+
reader.releaseLock();
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
async function readText(input, report, options = {}) {
|
|
1421
|
+
if (typeof input === "string") {
|
|
1422
|
+
throwIfAborted(options.signal);
|
|
1423
|
+
options.onProgress?.(input.length, input.length);
|
|
1424
|
+
return input.startsWith(BOM) ? input.slice(1) : input;
|
|
1425
|
+
}
|
|
1426
|
+
const parts = [];
|
|
1427
|
+
for await (const chunk of textChunks(input, report, options)) {
|
|
1428
|
+
parts.push(chunk);
|
|
1429
|
+
}
|
|
1430
|
+
return parts.length === 1 ? parts[0] : parts.join("");
|
|
1431
|
+
}
|
|
1432
|
+
class LineReader {
|
|
1433
|
+
/**
|
|
1434
|
+
* Create a reader over an input; nothing is read until iteration starts.
|
|
1435
|
+
* @param input - the input
|
|
1436
|
+
* @param report - the report decode errors are recorded in
|
|
1437
|
+
* @param options - cancellation and progress
|
|
1438
|
+
*/
|
|
1439
|
+
constructor(input, report, options = {}) {
|
|
1440
|
+
this.lineNumber = 0;
|
|
1441
|
+
this.input = input;
|
|
1442
|
+
this.report = report;
|
|
1443
|
+
this.options = options;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* The 1-based number of the line most recently yielded.
|
|
1447
|
+
* @returns the line number; 0 before the first line
|
|
1448
|
+
*/
|
|
1449
|
+
get line() {
|
|
1450
|
+
return this.lineNumber;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Iterate the lines.
|
|
1454
|
+
* @yields one line at a time, terminator removed
|
|
1455
|
+
* @returns nothing
|
|
1456
|
+
*/
|
|
1457
|
+
async *[Symbol.asyncIterator]() {
|
|
1458
|
+
const pending = [];
|
|
1459
|
+
let trailingCr = false;
|
|
1460
|
+
for await (const chunk of textChunks(this.input, this.report, this.options)) {
|
|
1461
|
+
let start = 0;
|
|
1462
|
+
const end = chunk.length;
|
|
1463
|
+
if (trailingCr) {
|
|
1464
|
+
trailingCr = false;
|
|
1465
|
+
this.lineNumber++;
|
|
1466
|
+
const joined = pending.join("");
|
|
1467
|
+
pending.length = 0;
|
|
1468
|
+
yield joined.slice(0, -1);
|
|
1469
|
+
if (chunk.charCodeAt(0) === 10) {
|
|
1470
|
+
start = 1;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
let nl = chunk.indexOf("\n", start);
|
|
1474
|
+
let cr = chunk.indexOf("\r", start);
|
|
1475
|
+
while (nl >= 0 || cr >= 0) {
|
|
1476
|
+
let cut;
|
|
1477
|
+
let next;
|
|
1478
|
+
if (cr >= 0 && (nl < 0 || cr < nl)) {
|
|
1479
|
+
if (cr === end - 1) {
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
cut = cr;
|
|
1483
|
+
next = chunk.charCodeAt(cr + 1) === 10 ? cr + 2 : cr + 1;
|
|
1484
|
+
} else {
|
|
1485
|
+
cut = nl;
|
|
1486
|
+
next = nl + 1;
|
|
1487
|
+
}
|
|
1488
|
+
this.lineNumber++;
|
|
1489
|
+
const piece = chunk.slice(start, cut);
|
|
1490
|
+
if (pending.length === 0) {
|
|
1491
|
+
yield piece;
|
|
1492
|
+
} else {
|
|
1493
|
+
pending.push(piece);
|
|
1494
|
+
const joined = pending.join("");
|
|
1495
|
+
pending.length = 0;
|
|
1496
|
+
yield joined;
|
|
1497
|
+
}
|
|
1498
|
+
start = next;
|
|
1499
|
+
if (nl >= 0 && nl < next) {
|
|
1500
|
+
nl = chunk.indexOf("\n", next);
|
|
1501
|
+
}
|
|
1502
|
+
if (cr >= 0 && cr < next) {
|
|
1503
|
+
cr = chunk.indexOf("\r", next);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
if (start < end) {
|
|
1507
|
+
pending.push(start === 0 ? chunk : chunk.slice(start));
|
|
1508
|
+
trailingCr = chunk.charCodeAt(end - 1) === 13;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (pending.length > 0) {
|
|
1512
|
+
this.lineNumber++;
|
|
1513
|
+
const joined = pending.join("");
|
|
1514
|
+
yield joined.endsWith("\r") ? joined.slice(0, -1) : joined;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
const ID_COERCIONS = /* @__PURE__ */ new Set(["keep", "canonical", "string", "number"]);
|
|
1519
|
+
const NODE_ID_SOURCES = /* @__PURE__ */ new Set(["id", "label", "index"]);
|
|
1520
|
+
const DUPLICATE_POLICIES = /* @__PURE__ */ new Set(["keep", "error", "first", "last", "sum", "min", "max"]);
|
|
1521
|
+
const SELF_LOOP_POLICIES = /* @__PURE__ */ new Set(["keep", "drop", "error"]);
|
|
1522
|
+
const IMPORT_MIXED_POLICIES = /* @__PURE__ */ new Set(["expand", "directed", "undirected", "error"]);
|
|
1523
|
+
const EXPORT_MIXED_POLICIES = /* @__PURE__ */ new Set(["error", "directed", "undirected"]);
|
|
1524
|
+
const WEIGHT_DTYPES = /* @__PURE__ */ new Set(["f32", "f64"]);
|
|
1525
|
+
const LONG_MODES = /* @__PURE__ */ new Set(["f64", "string"]);
|
|
1526
|
+
const HYPEREDGE_POLICIES = /* @__PURE__ */ new Set(["error", "skip", "star", "clique"]);
|
|
1527
|
+
const SANITIZE_MODES = /* @__PURE__ */ new Set(["error", "mangle"]);
|
|
1528
|
+
const DEFAULT_ERROR_LIMIT = 100;
|
|
1529
|
+
const SINK_OPTION_NAMES = ["addMissingNodes", "duplicateEdges", "selfLoops", "weightDtype"];
|
|
1530
|
+
function reportSinkOptions(sink, options, report, enforcesMissingNodes = false) {
|
|
1531
|
+
if (options === void 0) {
|
|
1532
|
+
return 0;
|
|
1533
|
+
}
|
|
1534
|
+
let recorded = 0;
|
|
1535
|
+
for (const name of SINK_OPTION_NAMES) {
|
|
1536
|
+
const wanted = options[name];
|
|
1537
|
+
const actual = sink.options[name];
|
|
1538
|
+
if (wanted === void 0 || wanted === actual) {
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
if (name === "addMissingNodes" && enforcesMissingNodes && wanted === false) {
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
report.warning(
|
|
1545
|
+
"coercion",
|
|
1546
|
+
SINK_OPTION_CODE,
|
|
1547
|
+
`option ${name}: ${JSON.stringify(wanted)} requested but the sink uses ${JSON.stringify(actual)}; the sink's setting applies`,
|
|
1548
|
+
{ element: name }
|
|
1549
|
+
);
|
|
1550
|
+
recorded++;
|
|
1551
|
+
}
|
|
1552
|
+
return recorded;
|
|
1553
|
+
}
|
|
1554
|
+
const IGNORABLE_OPTION_NAMES = [
|
|
1555
|
+
"ids",
|
|
1556
|
+
"nodeIdFrom",
|
|
1557
|
+
"addMissingNodes",
|
|
1558
|
+
"duplicateEdges",
|
|
1559
|
+
"selfLoops",
|
|
1560
|
+
"onMixedDirection",
|
|
1561
|
+
"defaultDirected",
|
|
1562
|
+
"weightFrom",
|
|
1563
|
+
"weightDtype",
|
|
1564
|
+
"long",
|
|
1565
|
+
"restoreMangledIds",
|
|
1566
|
+
"hyperedges"
|
|
1567
|
+
];
|
|
1568
|
+
function reportUnusedOptions(options, report, used) {
|
|
1569
|
+
if (options === void 0) {
|
|
1570
|
+
return 0;
|
|
1571
|
+
}
|
|
1572
|
+
let recorded = 0;
|
|
1573
|
+
for (const name of IGNORABLE_OPTION_NAMES) {
|
|
1574
|
+
if (used.has(name) || SINK_OPTION_NAMES.includes(name)) {
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
const value = options[name];
|
|
1578
|
+
if (value === void 0) {
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
report.warning(
|
|
1582
|
+
"unsupported",
|
|
1583
|
+
OPTION_IGNORED_CODE,
|
|
1584
|
+
`option ${name}: ${JSON.stringify(value)} has no effect on the ${report.format} importer`,
|
|
1585
|
+
{ element: name }
|
|
1586
|
+
);
|
|
1587
|
+
recorded++;
|
|
1588
|
+
}
|
|
1589
|
+
return recorded;
|
|
1590
|
+
}
|
|
1591
|
+
function resolveImportOptions(options, defaults) {
|
|
1592
|
+
const o = options ?? {};
|
|
1593
|
+
return Object.freeze({
|
|
1594
|
+
ids: enumOption("ids", o.ids, defaults.ids, ID_COERCIONS),
|
|
1595
|
+
nodeIdFrom: enumOption("nodeIdFrom", o.nodeIdFrom, "id", NODE_ID_SOURCES),
|
|
1596
|
+
addMissingNodes: booleanOption("addMissingNodes", o.addMissingNodes, defaults.addMissingNodes ?? true),
|
|
1597
|
+
duplicateEdges: enumOption("duplicateEdges", o.duplicateEdges, "keep", DUPLICATE_POLICIES),
|
|
1598
|
+
selfLoops: enumOption("selfLoops", o.selfLoops, "keep", SELF_LOOP_POLICIES),
|
|
1599
|
+
onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "expand", IMPORT_MIXED_POLICIES),
|
|
1600
|
+
defaultDirected: booleanOption("defaultDirected", o.defaultDirected, defaults.defaultDirected),
|
|
1601
|
+
weightFrom: weightFromOption(o.weightFrom, defaults.weightFrom),
|
|
1602
|
+
weightDtype: enumOption("weightDtype", o.weightDtype, "f64", WEIGHT_DTYPES),
|
|
1603
|
+
long: enumOption("long", o.long, "f64", LONG_MODES),
|
|
1604
|
+
restoreMangledIds: booleanOption("restoreMangledIds", o.restoreMangledIds, true),
|
|
1605
|
+
hyperedges: enumOption("hyperedges", o.hyperedges, "skip", HYPEREDGE_POLICIES),
|
|
1606
|
+
errorLimit: errorLimitOption(o.errorLimit),
|
|
1607
|
+
signal: signalOption(o.signal),
|
|
1608
|
+
onProgress: progressOption(o.onProgress)
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
function resolveExportOptions(options) {
|
|
1612
|
+
const o = options ?? {};
|
|
1613
|
+
return Object.freeze({
|
|
1614
|
+
sanitizeIds: enumOption("sanitizeIds", o.sanitizeIds, "error", SANITIZE_MODES),
|
|
1615
|
+
onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "error", EXPORT_MIXED_POLICIES)
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
function enumOption(name, value, fallback, allowed) {
|
|
1619
|
+
if (value === void 0) {
|
|
1620
|
+
return fallback;
|
|
1621
|
+
}
|
|
1622
|
+
if (typeof value !== "string" || !allowed.has(value)) {
|
|
1623
|
+
throw new GraphFormatError(
|
|
1624
|
+
"E_UNSUPPORTED",
|
|
1625
|
+
`option ${name}: ${describe(value)} is not one of ${list(allowed)}`,
|
|
1626
|
+
{
|
|
1627
|
+
option: name,
|
|
1628
|
+
found: value,
|
|
1629
|
+
supported: [...allowed]
|
|
1630
|
+
}
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
return value;
|
|
1634
|
+
}
|
|
1635
|
+
function booleanOption(name, value, fallback) {
|
|
1636
|
+
if (value === void 0) {
|
|
1637
|
+
return fallback;
|
|
1638
|
+
}
|
|
1639
|
+
if (typeof value !== "boolean") {
|
|
1640
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: ${describe(value)} is not a boolean`, {
|
|
1641
|
+
option: name,
|
|
1642
|
+
found: value
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
return value;
|
|
1646
|
+
}
|
|
1647
|
+
function weightFromOption(value, fallback) {
|
|
1648
|
+
if (value === void 0) {
|
|
1649
|
+
return fallback;
|
|
1650
|
+
}
|
|
1651
|
+
if (value === null || typeof value === "string" && value.length > 0) {
|
|
1652
|
+
return value;
|
|
1653
|
+
}
|
|
1654
|
+
throw new GraphFormatError(
|
|
1655
|
+
"E_UNSUPPORTED",
|
|
1656
|
+
`option weightFrom: ${describe(value)} is not an attribute name or null`,
|
|
1657
|
+
{ option: "weightFrom", found: value }
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
function errorLimitOption(value) {
|
|
1661
|
+
if (value === void 0) {
|
|
1662
|
+
return DEFAULT_ERROR_LIMIT;
|
|
1663
|
+
}
|
|
1664
|
+
if (typeof value === "number" && value >= 0 && (Number.isInteger(value) || value === Infinity)) {
|
|
1665
|
+
return value;
|
|
1666
|
+
}
|
|
1667
|
+
throw new GraphFormatError(
|
|
1668
|
+
"E_UNSUPPORTED",
|
|
1669
|
+
`option errorLimit: ${describe(value)} is not a non-negative integer or Infinity`,
|
|
1670
|
+
{ option: "errorLimit", found: value }
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
function signalOption(value) {
|
|
1674
|
+
if (value === void 0 || value === null) {
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
if (typeof value === "object" && typeof value.aborted === "boolean") {
|
|
1678
|
+
return value;
|
|
1679
|
+
}
|
|
1680
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option signal: ${describe(value)} is not an AbortSignal`, {
|
|
1681
|
+
option: "signal",
|
|
1682
|
+
found: typeof value
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
function progressOption(value) {
|
|
1686
|
+
if (value === void 0 || value === null) {
|
|
1687
|
+
return null;
|
|
1688
|
+
}
|
|
1689
|
+
if (typeof value === "function") {
|
|
1690
|
+
return value;
|
|
1691
|
+
}
|
|
1692
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option onProgress: ${describe(value)} is not a function`, {
|
|
1693
|
+
option: "onProgress",
|
|
1694
|
+
found: typeof value
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
function describe(value) {
|
|
1698
|
+
switch (typeof value) {
|
|
1699
|
+
case "string":
|
|
1700
|
+
return JSON.stringify(value);
|
|
1701
|
+
case "number":
|
|
1702
|
+
case "boolean":
|
|
1703
|
+
return String(value);
|
|
1704
|
+
default:
|
|
1705
|
+
return value === null ? "null" : typeof value;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
function list(allowed) {
|
|
1709
|
+
return [...allowed].map((v) => JSON.stringify(v)).join(", ");
|
|
1710
|
+
}
|
|
1711
|
+
class ImportError extends GraphFormatError {
|
|
1712
|
+
/**
|
|
1713
|
+
* Create an ImportError.
|
|
1714
|
+
* @param message - a plain-ASCII human-readable message
|
|
1715
|
+
* @param report - the partial report
|
|
1716
|
+
* @param details - optional machine-readable context
|
|
1717
|
+
*/
|
|
1718
|
+
constructor(message, report, details) {
|
|
1719
|
+
super("E_IMPORT", message, details);
|
|
1720
|
+
this.name = "ImportError";
|
|
1721
|
+
this.report = report;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
const CATEGORY_BY_CODE = {
|
|
1725
|
+
E_UNKNOWN_NODE: "missing-value",
|
|
1726
|
+
E_UNKNOWN_COLUMN: "missing-value",
|
|
1727
|
+
E_DIRECTED: "coercion",
|
|
1728
|
+
E_TOO_LARGE: "unsupported",
|
|
1729
|
+
E_UNSUPPORTED: "unsupported",
|
|
1730
|
+
E_GPU_INELIGIBLE: "unsupported"
|
|
1731
|
+
};
|
|
1732
|
+
const PARSE_ERROR_CODE = "E_PARSE";
|
|
1733
|
+
class ImportReportBuilder {
|
|
1734
|
+
/**
|
|
1735
|
+
* Create a builder for one import call.
|
|
1736
|
+
* @param format - the importer's format name
|
|
1737
|
+
* @param errorLimit - errors tolerated before the import aborts; Infinity for no limit
|
|
1738
|
+
*/
|
|
1739
|
+
constructor(format, errorLimit) {
|
|
1740
|
+
this.counts = { nodes: 0, edges: 0, skippedNodes: 0, skippedEdges: 0, expandedMixed: 0 };
|
|
1741
|
+
this.issueList = [];
|
|
1742
|
+
this.lossList = [];
|
|
1743
|
+
this.onceCodes = /* @__PURE__ */ new Set();
|
|
1744
|
+
this.errors = 0;
|
|
1745
|
+
this.warnings = 0;
|
|
1746
|
+
this.truncatedFlag = false;
|
|
1747
|
+
this.format = format;
|
|
1748
|
+
this.errorLimit = errorLimit;
|
|
1749
|
+
this.startedAt = now();
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Issues recorded so far, in order.
|
|
1753
|
+
* @returns the live list (not copied; finish() copies)
|
|
1754
|
+
*/
|
|
1755
|
+
get issues() {
|
|
1756
|
+
return this.issueList;
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Issues with severity "error".
|
|
1760
|
+
* @returns the count
|
|
1761
|
+
*/
|
|
1762
|
+
get errorCount() {
|
|
1763
|
+
return this.errors;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Issues with severity "warning".
|
|
1767
|
+
* @returns the count
|
|
1768
|
+
*/
|
|
1769
|
+
get warningCount() {
|
|
1770
|
+
return this.warnings;
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Whether the error limit was exceeded.
|
|
1774
|
+
* @returns true once an error beyond the limit was recorded
|
|
1775
|
+
*/
|
|
1776
|
+
get truncated() {
|
|
1777
|
+
return this.truncatedFlag;
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Loss notes recorded so far.
|
|
1781
|
+
* @returns the live list
|
|
1782
|
+
*/
|
|
1783
|
+
get lossy() {
|
|
1784
|
+
return this.lossList;
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Record an error. When the count goes beyond the error limit the report is marked truncated
|
|
1788
|
+
* and an ImportError carrying it is thrown; the importer does not catch that.
|
|
1789
|
+
* @param category - the issue category
|
|
1790
|
+
* @param code - a stable code such as "E_UNKNOWN_NODE"
|
|
1791
|
+
* @param message - a plain-ASCII message
|
|
1792
|
+
* @param where - the line and element, when known
|
|
1793
|
+
* @returns the recorded issue
|
|
1794
|
+
*/
|
|
1795
|
+
error(category, code, message, where) {
|
|
1796
|
+
const issue = makeIssue(category, "error", code, message, where);
|
|
1797
|
+
this.issueList.push(issue);
|
|
1798
|
+
this.errors++;
|
|
1799
|
+
if (this.errors > this.errorLimit) {
|
|
1800
|
+
this.truncatedFlag = true;
|
|
1801
|
+
throw this.abort(`error limit of ${this.errorLimit} exceeded: ${message}`, {
|
|
1802
|
+
code,
|
|
1803
|
+
limit: this.errorLimit
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
return issue;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Record a warning; warnings never count toward the limit.
|
|
1810
|
+
* @param category - the issue category
|
|
1811
|
+
* @param code - a stable code such as "W_WIDENED"
|
|
1812
|
+
* @param message - a plain-ASCII message
|
|
1813
|
+
* @param where - the line and element, when known
|
|
1814
|
+
* @returns the recorded issue
|
|
1815
|
+
*/
|
|
1816
|
+
warning(category, code, message, where) {
|
|
1817
|
+
const issue = makeIssue(category, "warning", code, message, where);
|
|
1818
|
+
this.issueList.push(issue);
|
|
1819
|
+
this.warnings++;
|
|
1820
|
+
return issue;
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Record a warning the first time its key is seen and ignore later repeats, for conditions
|
|
1824
|
+
* that would otherwise produce one warning per row.
|
|
1825
|
+
* @param category - the issue category
|
|
1826
|
+
* @param code - the stable code
|
|
1827
|
+
* @param message - a plain-ASCII message
|
|
1828
|
+
* @param where - the line and element, when known
|
|
1829
|
+
* @param key - what repeats are deduplicated on; the code itself by default (pass `code:column`
|
|
1830
|
+
* to warn once per column)
|
|
1831
|
+
* @returns the recorded issue, or null when the key was already recorded
|
|
1832
|
+
*/
|
|
1833
|
+
warnOnce(category, code, message, where, key = code) {
|
|
1834
|
+
if (this.onceCodes.has(key)) {
|
|
1835
|
+
return null;
|
|
1836
|
+
}
|
|
1837
|
+
this.onceCodes.add(key);
|
|
1838
|
+
return this.warning(category, code, message, where);
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Record something the importer could not represent.
|
|
1842
|
+
* @param code - a stable code
|
|
1843
|
+
* @param message - a plain-ASCII message
|
|
1844
|
+
* @param column - the affected column, or null
|
|
1845
|
+
* @param count - the affected count, or null
|
|
1846
|
+
* @returns the recorded note
|
|
1847
|
+
*/
|
|
1848
|
+
loss(code, message, column = null, count = null) {
|
|
1849
|
+
const note = Object.freeze({ code, message, column, count });
|
|
1850
|
+
this.lossList.push(note);
|
|
1851
|
+
return note;
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* The per-element catch of design section 8.6: record a thrown GraphFormatError as an error
|
|
1855
|
+
* issue with its code and the category the code implies. Anything else is re-thrown unchanged:
|
|
1856
|
+
* an ImportError (the import already aborted), an abort reason, and any other thrown value (a
|
|
1857
|
+
* TypeError or RangeError is a programming error in the importer or the sink, never a defect of
|
|
1858
|
+
* the input, so it must surface as an exception rather than as an issue blamed on the file).
|
|
1859
|
+
* @param err - the thrown value
|
|
1860
|
+
* @param where - the line and element, when known
|
|
1861
|
+
* @returns the recorded issue
|
|
1862
|
+
*/
|
|
1863
|
+
recordError(err, where) {
|
|
1864
|
+
if (err instanceof GraphFormatError && !(err instanceof ImportError)) {
|
|
1865
|
+
const category = CATEGORY_BY_CODE[err.code] ?? "validation-error";
|
|
1866
|
+
return this.error(category, err.code, err.message, where);
|
|
1867
|
+
}
|
|
1868
|
+
throw err;
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Build the ImportError that aborts the import, carrying the report so far. The caller throws
|
|
1872
|
+
* it; this method only constructs it so it can be used in expression position.
|
|
1873
|
+
* @param message - a plain-ASCII message
|
|
1874
|
+
* @param details - optional machine-readable context
|
|
1875
|
+
* @returns the error to throw
|
|
1876
|
+
*/
|
|
1877
|
+
abort(message, details) {
|
|
1878
|
+
return new ImportError(message, this.finish(), details);
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Record a fatal parse error and abort: the issue is recorded (even beyond the error limit)
|
|
1882
|
+
* and an ImportError carrying the report is thrown.
|
|
1883
|
+
* @param code - a stable code such as "E_INVALID_UTF8"
|
|
1884
|
+
* @param message - a plain-ASCII message
|
|
1885
|
+
* @param where - the line and element, when known
|
|
1886
|
+
* @param details - optional machine-readable context for the ImportError
|
|
1887
|
+
*/
|
|
1888
|
+
fail(code, message, where, details) {
|
|
1889
|
+
const issue = makeIssue("parse-error", "error", code, message, where);
|
|
1890
|
+
this.issueList.push(issue);
|
|
1891
|
+
this.errors++;
|
|
1892
|
+
if (this.errors > this.errorLimit) {
|
|
1893
|
+
this.truncatedFlag = true;
|
|
1894
|
+
}
|
|
1895
|
+
throw this.abort(message, { code, ...details });
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* The report as it stands: a frozen snapshot with the parse duration so far. Can be called more
|
|
1899
|
+
* than once; each call reflects everything recorded up to that point.
|
|
1900
|
+
* @returns the report
|
|
1901
|
+
*/
|
|
1902
|
+
finish() {
|
|
1903
|
+
const { nodes, edges, skippedNodes, skippedEdges, expandedMixed } = this.counts;
|
|
1904
|
+
return Object.freeze({
|
|
1905
|
+
format: this.format,
|
|
1906
|
+
counts: Object.freeze({ nodes, edges, skippedNodes, skippedEdges, expandedMixed }),
|
|
1907
|
+
issues: Object.freeze([...this.issueList]),
|
|
1908
|
+
errorCount: this.errors,
|
|
1909
|
+
warningCount: this.warnings,
|
|
1910
|
+
truncated: this.truncatedFlag,
|
|
1911
|
+
lossy: Object.freeze([...this.lossList]),
|
|
1912
|
+
durationMs: now() - this.startedAt
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
function makeIssue(category, severity, code, message, where) {
|
|
1917
|
+
return Object.freeze({
|
|
1918
|
+
category,
|
|
1919
|
+
severity,
|
|
1920
|
+
code,
|
|
1921
|
+
message,
|
|
1922
|
+
line: where?.line ?? null,
|
|
1923
|
+
element: where?.element ?? null
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
function isAbortError(err) {
|
|
1927
|
+
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
1928
|
+
}
|
|
1929
|
+
function now() {
|
|
1930
|
+
return typeof performance === "object" ? performance.now() : Date.now();
|
|
1931
|
+
}
|
|
1932
|
+
function formatF32(value) {
|
|
1933
|
+
if (!Number.isFinite(value)) {
|
|
1934
|
+
return String(value);
|
|
1935
|
+
}
|
|
1936
|
+
if (value === 0) {
|
|
1937
|
+
return "0";
|
|
1938
|
+
}
|
|
1939
|
+
for (let digits = 1; digits <= 9; digits++) {
|
|
1940
|
+
const text = value.toPrecision(digits);
|
|
1941
|
+
if (Math.fround(Number(text)) === value) {
|
|
1942
|
+
return String(Number(text));
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
return String(value);
|
|
1946
|
+
}
|
|
1947
|
+
function formatF64(value) {
|
|
1948
|
+
return String(value);
|
|
1949
|
+
}
|
|
1950
|
+
function formatDecimal(value, dtype = "f64") {
|
|
1951
|
+
if (!Number.isFinite(value)) {
|
|
1952
|
+
return String(value);
|
|
1953
|
+
}
|
|
1954
|
+
if (Object.is(value, -0)) {
|
|
1955
|
+
return "-0.0";
|
|
1956
|
+
}
|
|
1957
|
+
const text = formatNumber(value, dtype);
|
|
1958
|
+
return text.includes(".") || text.includes("e") ? text : `${text}.0`;
|
|
1959
|
+
}
|
|
1960
|
+
function formatGmlReal(value, dtype = "f64") {
|
|
1961
|
+
if (Number.isNaN(value)) {
|
|
1962
|
+
return "NAN";
|
|
1963
|
+
}
|
|
1964
|
+
if (!Number.isFinite(value)) {
|
|
1965
|
+
return value > 0 ? "+INF" : "-INF";
|
|
1966
|
+
}
|
|
1967
|
+
const text = formatDecimal(value, dtype);
|
|
1968
|
+
if (text.includes(".")) {
|
|
1969
|
+
return text;
|
|
1970
|
+
}
|
|
1971
|
+
const e = text.indexOf("e");
|
|
1972
|
+
return `${text.slice(0, e)}.0${text.slice(e)}`;
|
|
1973
|
+
}
|
|
1974
|
+
function formatInteger(value) {
|
|
1975
|
+
if (Number.isSafeInteger(value) || !Number.isFinite(value)) {
|
|
1976
|
+
return String(value);
|
|
1977
|
+
}
|
|
1978
|
+
return BigInt(value).toString();
|
|
1979
|
+
}
|
|
1980
|
+
function formatNumber(value, dtype) {
|
|
1981
|
+
return dtype === "f32" ? formatF32(value) : formatF64(value);
|
|
1982
|
+
}
|
|
1983
|
+
const DEFAULT_WEIGHT = 1;
|
|
1984
|
+
function isWeightField(name, weightFrom) {
|
|
1985
|
+
return weightFrom !== null && name === weightFrom;
|
|
1986
|
+
}
|
|
1987
|
+
function parseWeightText(text) {
|
|
1988
|
+
const trimmed = text.trim();
|
|
1989
|
+
if (trimmed.length === 0) {
|
|
1990
|
+
return void 0;
|
|
1991
|
+
}
|
|
1992
|
+
let value;
|
|
1993
|
+
try {
|
|
1994
|
+
value = parseDecimalText(trimmed);
|
|
1995
|
+
} catch (err) {
|
|
1996
|
+
throw invalidWeight(text, err);
|
|
1997
|
+
}
|
|
1998
|
+
if (Number.isNaN(value)) {
|
|
1999
|
+
throw invalidWeight(text, null);
|
|
2000
|
+
}
|
|
2001
|
+
return value;
|
|
2002
|
+
}
|
|
2003
|
+
function weightFromValue(value) {
|
|
2004
|
+
if (value === void 0 || value === null) {
|
|
2005
|
+
return void 0;
|
|
2006
|
+
}
|
|
2007
|
+
if (typeof value === "number") {
|
|
2008
|
+
if (Number.isNaN(value)) {
|
|
2009
|
+
throw invalidWeight(value, null);
|
|
2010
|
+
}
|
|
2011
|
+
return value;
|
|
2012
|
+
}
|
|
2013
|
+
if (typeof value === "string") {
|
|
2014
|
+
return parseWeightText(value);
|
|
2015
|
+
}
|
|
2016
|
+
throw invalidWeight(value, null);
|
|
2017
|
+
}
|
|
2018
|
+
function invalidWeight(value, cause) {
|
|
2019
|
+
let shown;
|
|
2020
|
+
switch (typeof value) {
|
|
2021
|
+
case "string":
|
|
2022
|
+
shown = JSON.stringify(value);
|
|
2023
|
+
break;
|
|
2024
|
+
case "number":
|
|
2025
|
+
shown = String(value);
|
|
2026
|
+
break;
|
|
2027
|
+
default:
|
|
2028
|
+
shown = typeof value;
|
|
2029
|
+
}
|
|
2030
|
+
return new GraphFormatError("E_INVALID_WEIGHT", `invalid edge weight ${shown}`, {
|
|
2031
|
+
value: typeof value === "object" ? typeof value : value,
|
|
2032
|
+
cause: cause instanceof Error ? cause.message : null
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
const NUMERIC_DTYPES = /* @__PURE__ */ new Set(["f32", "f64", "i32", "u32", "u8"]);
|
|
2036
|
+
function weightFormatter(dtype) {
|
|
2037
|
+
switch (dtype) {
|
|
2038
|
+
case "f32":
|
|
2039
|
+
return formatF32;
|
|
2040
|
+
case "f64":
|
|
2041
|
+
return formatF64;
|
|
2042
|
+
default:
|
|
2043
|
+
return formatInteger;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
function explicitWeights(snapshot) {
|
|
2047
|
+
const shadowColumn = snapshot.edges.byRole("weight");
|
|
2048
|
+
const shadow = shadowColumn !== null && NUMERIC_DTYPES.has(shadowColumn.dtype) ? shadowColumn : null;
|
|
2049
|
+
if (shadow !== null) {
|
|
2050
|
+
const dtype = shadow.dtype === "f32" ? "f32" : "f64";
|
|
2051
|
+
const format = weightFormatter(shadow.dtype);
|
|
2052
|
+
return {
|
|
2053
|
+
weighted: true,
|
|
2054
|
+
dtype,
|
|
2055
|
+
isExplicit: (e) => shadow.isSet(e),
|
|
2056
|
+
value: (e) => shadow.value(e),
|
|
2057
|
+
text: (e, integral = false) => {
|
|
2058
|
+
if (!shadow.isSet(e)) {
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
const value = shadow.value(e);
|
|
2062
|
+
return integral && Number.isInteger(value) ? formatInteger(value) : format(value);
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
const { weights } = snapshot.edgeList();
|
|
2067
|
+
if (weights === null || !snapshot.flags.weighted) {
|
|
2068
|
+
return {
|
|
2069
|
+
weighted: false,
|
|
2070
|
+
dtype: "f32",
|
|
2071
|
+
isExplicit: () => false,
|
|
2072
|
+
value: () => DEFAULT_WEIGHT,
|
|
2073
|
+
text: () => null
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
return {
|
|
2077
|
+
weighted: true,
|
|
2078
|
+
dtype: "f32",
|
|
2079
|
+
isExplicit: () => true,
|
|
2080
|
+
value: (e) => weights[e],
|
|
2081
|
+
text: (e, integral = false) => {
|
|
2082
|
+
const value = weights[e];
|
|
2083
|
+
return integral && Number.isInteger(value) ? formatInteger(value) : formatF32(value);
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
const LOSS = Object.freeze({
|
|
2088
|
+
/** The format has no mixed direction; expanded pairs are written per onMixedDirection. */
|
|
2089
|
+
MIXED_DIRECTION: "W_MIXED_DIRECTION",
|
|
2090
|
+
/** The format has no mixed direction and onMixedDirection is "error": export() will throw E_DIRECTED. */
|
|
2091
|
+
MIXED_DIRECTION_ERROR: MIXED_DIRECTION_CODE,
|
|
2092
|
+
/** Parallel edges in a format without them. */
|
|
2093
|
+
MULTI_EDGES: "W_MULTI_EDGES",
|
|
2094
|
+
/** Self-loops in a format without them. */
|
|
2095
|
+
SELF_LOOPS: "W_SELF_LOOPS",
|
|
2096
|
+
/** The format requires edge ids and the snapshot has none; canonical e0..e{E-1} are generated. */
|
|
2097
|
+
EDGE_IDS_GENERATED: "W_EDGE_IDS_GENERATED",
|
|
2098
|
+
/** The snapshot has an edge id column and the format cannot write one. */
|
|
2099
|
+
EDGE_IDS_DROPPED: "W_EDGE_IDS_DROPPED",
|
|
2100
|
+
/** Ids outside the format's charset are rewritten (sanitizeIds "mangle"). */
|
|
2101
|
+
ID_MANGLED: "W_ID_MANGLED",
|
|
2102
|
+
/** Ids outside the format's charset and sanitizeIds "error": export() will throw E_INVALID_ID. */
|
|
2103
|
+
ID_CHARSET: "E_ID_CHARSET",
|
|
2104
|
+
/** The format numbers nodes 1..N; ids that are not their 1-based index are kept as labels only. */
|
|
2105
|
+
ID_RENUMBERED: "W_ID_RENUMBERED",
|
|
2106
|
+
/** A column dtype the format does not keep as declared. */
|
|
2107
|
+
DTYPE: "W_DTYPE_UNSUPPORTED",
|
|
2108
|
+
/** A multi-component column in a format without strides. */
|
|
2109
|
+
COMPONENTS: "W_COMPONENTS_FLATTENED",
|
|
2110
|
+
/** A list column in a format without lists. */
|
|
2111
|
+
LIST: "W_LIST_UNSUPPORTED",
|
|
2112
|
+
/** A json column in a format without nested values. */
|
|
2113
|
+
JSON: "W_JSON_UNSUPPORTED",
|
|
2114
|
+
/** A declared default in a format without defaults. */
|
|
2115
|
+
DEFAULT: "W_DEFAULT_DROPPED",
|
|
2116
|
+
/** Declared options in a format without enumerations. */
|
|
2117
|
+
OPTIONS: "W_OPTIONS_DROPPED",
|
|
2118
|
+
/** A parent / parents column in a format without containment. */
|
|
2119
|
+
HIERARCHY: "W_HIERARCHY_DROPPED",
|
|
2120
|
+
/** A start / end / timestamp column in a format without temporal support. */
|
|
2121
|
+
TEMPORAL: TEMPORAL_DROPPED_CODE,
|
|
2122
|
+
/** A `<column>.text` companion (design section 5.1) the format cannot carry. */
|
|
2123
|
+
TEMPORAL_TEXT: TEMPORAL_TEXT_DROPPED_CODE,
|
|
2124
|
+
/** A role column the format has no slot for; written as a plain attribute, the role lost. */
|
|
2125
|
+
ROLE: ROLE_DROPPED_CODE,
|
|
2126
|
+
/** A parents (multi-parent) column in a format with single containment only. */
|
|
2127
|
+
PARENTS: PARENTS_DROPPED_CODE,
|
|
2128
|
+
/** A mutual pair written as two directed edges without the mutual mark. */
|
|
2129
|
+
MUTUAL_EXPANDED: MUTUAL_EXPANDED_CODE,
|
|
2130
|
+
/** A mutual pair written as one undirected edge; the mark is lost and the pair reads back undirected. */
|
|
2131
|
+
MUTUAL_AS_UNDIRECTED: MUTUAL_AS_UNDIRECTED_CODE,
|
|
2132
|
+
/** Node ids whose written text reads back as the other type under the importer's id rule. */
|
|
2133
|
+
ID_TEXT_TYPE: ID_TEXT_TYPE_CODE,
|
|
2134
|
+
/** Two node ids share one written text; export() throws E_INVALID_ID. */
|
|
2135
|
+
ID_TEXT_COLLISION: ID_TEXT_COLLISION_CODE,
|
|
2136
|
+
/** A plain column named like the importer's weight key reads back as THE weight. */
|
|
2137
|
+
WEIGHT_KEY_CLASH: WEIGHT_KEY_CLASH_CODE,
|
|
2138
|
+
/** A role-less column whose written name the importer maps to a role. */
|
|
2139
|
+
ROLE_ASSUMED: ROLE_ASSUMED_CODE,
|
|
2140
|
+
/** A role column written into the format's slot reads back under the importer's fixed name. */
|
|
2141
|
+
COLUMN_NAME_CHANGED: COLUMN_RENAMED_LOSS_CODE,
|
|
2142
|
+
/** A declared column whose every row is unset vanishes through a format without declarations. */
|
|
2143
|
+
EMPTY_COLUMN: EMPTY_COLUMN_DROPPED_CODE,
|
|
2144
|
+
/** A string / dict column that reads back as the other storage class (the dictionary heuristic). */
|
|
2145
|
+
STORAGE_CLASS: STORAGE_CLASS_CODE,
|
|
2146
|
+
/** An f64 column whose set values are all integral reads back as i32 through an untyped format. */
|
|
2147
|
+
INTEGRAL_F64: INTEGRAL_F64_CODE,
|
|
2148
|
+
/** Text cells that read back as numbers or booleans under the 5.1 grammar. */
|
|
2149
|
+
TEXT_INFERRED: TEXT_INFERRED_CODE,
|
|
2150
|
+
/** A string cell holding a character XML 1.0 forbids; export() throws E_COLUMN_TYPE. */
|
|
2151
|
+
XML_ILLEGAL_CHAR: XML_ILLEGAL_CHAR_CODE,
|
|
2152
|
+
/** A dict column without declared options gains one on re-import. */
|
|
2153
|
+
OPTIONS_GAINED: OPTIONS_GAINED_CODE,
|
|
2154
|
+
/** A spells column in a format without spells. */
|
|
2155
|
+
SPELLS: "W_SPELLS_DROPPED",
|
|
2156
|
+
/** Dynamic attribute values (extension tables) in a format without them. */
|
|
2157
|
+
DYNAMIC_VALUES: "W_DYNAMIC_VALUES_DROPPED",
|
|
2158
|
+
/** An open-interval column in a format without open intervals (design section 5.1). */
|
|
2159
|
+
OPEN_INTERVAL: "W_OPEN_INTERVAL",
|
|
2160
|
+
/** Graph-level attributes in a format without them. */
|
|
2161
|
+
GRAPH_ATTRIBUTES: "W_GRAPH_ATTRIBUTES_DROPPED",
|
|
2162
|
+
/** A position column in a format without positions. */
|
|
2163
|
+
POSITIONS: "W_POSITIONS_DROPPED",
|
|
2164
|
+
/** Visual columns in a format without them. */
|
|
2165
|
+
VIZ: "W_VIZ_DROPPED",
|
|
2166
|
+
/** An extension table the format cannot carry. */
|
|
2167
|
+
EXTENSION_TABLE: "W_EXTENSION_TABLE_DROPPED"
|
|
2168
|
+
});
|
|
2169
|
+
const NO_CAPABILITIES = Object.freeze({
|
|
2170
|
+
mixedDirection: false,
|
|
2171
|
+
multiEdges: false,
|
|
2172
|
+
selfLoops: false,
|
|
2173
|
+
edgeIds: "none",
|
|
2174
|
+
idCharset: "any",
|
|
2175
|
+
dtypes: Object.freeze([]),
|
|
2176
|
+
components: false,
|
|
2177
|
+
lists: false,
|
|
2178
|
+
json: false,
|
|
2179
|
+
defaults: false,
|
|
2180
|
+
options: false,
|
|
2181
|
+
hierarchy: false,
|
|
2182
|
+
temporal: "none",
|
|
2183
|
+
graphAttributes: false,
|
|
2184
|
+
positions: false,
|
|
2185
|
+
viz: false
|
|
2186
|
+
});
|
|
2187
|
+
function capabilities(supported) {
|
|
2188
|
+
return Object.freeze({ ...NO_CAPABILITIES, ...supported, dtypes: Object.freeze([...supported.dtypes ?? []]) });
|
|
2189
|
+
}
|
|
2190
|
+
const STRUCTURAL_ROLES = /* @__PURE__ */ new Set(["directed", "pair", "mutual", "weight", "originalId"]);
|
|
2191
|
+
const VIZ_ROLES = /* @__PURE__ */ new Set(["color", "size", "shape", "thickness"]);
|
|
2192
|
+
const TEMPORAL_ROLES = /* @__PURE__ */ new Set(["start", "end", "timestamp", "timestamps"]);
|
|
2193
|
+
const HIERARCHY_ROLES = /* @__PURE__ */ new Set(["parent", "parents"]);
|
|
2194
|
+
function checkCapabilities(snapshot, caps, options, extras = {}) {
|
|
2195
|
+
const notes = [];
|
|
2196
|
+
const note = (code, message, column = null, count = null) => {
|
|
2197
|
+
notes.push(Object.freeze({ code, message, column, count }));
|
|
2198
|
+
};
|
|
2199
|
+
const mixed = countMixedEdges(snapshot);
|
|
2200
|
+
if (mixed > 0 && !caps.mixedDirection) {
|
|
2201
|
+
if (options.onMixedDirection === "error") {
|
|
2202
|
+
note(
|
|
2203
|
+
LOSS.MIXED_DIRECTION_ERROR,
|
|
2204
|
+
`${mixed} undirected edge(s) in a directed graph; the format has no mixed direction and onMixedDirection is "error"`,
|
|
2205
|
+
null,
|
|
2206
|
+
mixed
|
|
2207
|
+
);
|
|
2208
|
+
} else {
|
|
2209
|
+
note(
|
|
2210
|
+
LOSS.MIXED_DIRECTION,
|
|
2211
|
+
`${mixed} undirected edge(s) in a directed graph written as ${options.onMixedDirection}`,
|
|
2212
|
+
null,
|
|
2213
|
+
mixed
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
if (snapshot.flags.multigraph && !caps.multiEdges) {
|
|
2218
|
+
const parallel = countParallelArcs(snapshot);
|
|
2219
|
+
note(LOSS.MULTI_EDGES, `${parallel} parallel edge(s); the format has no parallel edges`, null, parallel);
|
|
2220
|
+
}
|
|
2221
|
+
if (snapshot.selfLoopCount > 0 && !caps.selfLoops) {
|
|
2222
|
+
note(
|
|
2223
|
+
LOSS.SELF_LOOPS,
|
|
2224
|
+
`${snapshot.selfLoopCount} self-loop(s); the format has no self-loops`,
|
|
2225
|
+
null,
|
|
2226
|
+
snapshot.selfLoopCount
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
const edgeIds = snapshot.edges.byRole("id");
|
|
2230
|
+
if (caps.edgeIds === "required" && edgeIds === null && snapshot.edgeCount > 0) {
|
|
2231
|
+
note(
|
|
2232
|
+
LOSS.EDGE_IDS_GENERATED,
|
|
2233
|
+
"the format requires edge ids; canonical e0..e{E-1} are generated and not written back",
|
|
2234
|
+
null,
|
|
2235
|
+
snapshot.edgeCount
|
|
2236
|
+
);
|
|
2237
|
+
} else if (caps.edgeIds === "none" && edgeIds !== null) {
|
|
2238
|
+
note(
|
|
2239
|
+
LOSS.EDGE_IDS_DROPPED,
|
|
2240
|
+
`edge id column "${edgeIds.meta.name}" cannot be written`,
|
|
2241
|
+
edgeIds.meta.name,
|
|
2242
|
+
snapshot.edgeCount
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
const unrepresentable = countUnrepresentableIds(snapshot, caps.idCharset);
|
|
2246
|
+
if (unrepresentable > 0) {
|
|
2247
|
+
if (caps.idCharset === "dense-1-based") {
|
|
2248
|
+
note(
|
|
2249
|
+
LOSS.ID_RENUMBERED,
|
|
2250
|
+
options.sanitizeIds === "mangle" ? `${unrepresentable} node id(s) are not their 1-based index; nodes are numbered 1..N, the originals kept in the exporter's originalId attribute (restored by restoreMangledIds) and as labels of the nodes without a label value` : `${unrepresentable} node id(s) are not their 1-based index; nodes are numbered 1..N and ids kept as labels of the nodes without a label value (a node with one loses its id)`,
|
|
2251
|
+
null,
|
|
2252
|
+
unrepresentable
|
|
2253
|
+
);
|
|
2254
|
+
} else if (options.sanitizeIds === "mangle") {
|
|
2255
|
+
note(
|
|
2256
|
+
LOSS.ID_MANGLED,
|
|
2257
|
+
`${unrepresentable} node id(s) outside the ${caps.idCharset} charset are rewritten; originals kept in the originalId attribute`,
|
|
2258
|
+
null,
|
|
2259
|
+
unrepresentable
|
|
2260
|
+
);
|
|
2261
|
+
} else {
|
|
2262
|
+
note(
|
|
2263
|
+
LOSS.ID_CHARSET,
|
|
2264
|
+
`${unrepresentable} node id(s) outside the ${caps.idCharset} charset; export() will throw unless sanitizeIds is "mangle"`,
|
|
2265
|
+
null,
|
|
2266
|
+
unrepresentable
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
checkColumns(snapshot.nodes, "node", caps, extras, note);
|
|
2271
|
+
checkColumns(snapshot.edges, "edge", caps, extras, note);
|
|
2272
|
+
const graphColumns = snapshot.graph.names();
|
|
2273
|
+
if (graphColumns.length > 0) {
|
|
2274
|
+
if (caps.graphAttributes) {
|
|
2275
|
+
checkColumns(snapshot.graph, "graph", caps, extras, note);
|
|
2276
|
+
} else {
|
|
2277
|
+
note(
|
|
2278
|
+
LOSS.GRAPH_ATTRIBUTES,
|
|
2279
|
+
`${graphColumns.length} graph attribute(s) cannot be written`,
|
|
2280
|
+
null,
|
|
2281
|
+
graphColumns.length
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
for (const [name, table] of snapshot.extensions) {
|
|
2286
|
+
if (name.startsWith("temporal:")) {
|
|
2287
|
+
if (caps.temporal !== "dynamic-values") {
|
|
2288
|
+
note(
|
|
2289
|
+
LOSS.DYNAMIC_VALUES,
|
|
2290
|
+
`dynamic values of "${name}" (${table.rowCount} row(s)) cannot be written`,
|
|
2291
|
+
name,
|
|
2292
|
+
table.rowCount
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
} else {
|
|
2296
|
+
note(
|
|
2297
|
+
LOSS.EXTENSION_TABLE,
|
|
2298
|
+
`extension table "${name}" (${table.rowCount} row(s)) cannot be written`,
|
|
2299
|
+
name,
|
|
2300
|
+
table.rowCount
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
return notes;
|
|
2305
|
+
}
|
|
2306
|
+
function checkColumns(table, domain, caps, extras, note) {
|
|
2307
|
+
for (const column of table) {
|
|
2308
|
+
const { meta } = column;
|
|
2309
|
+
const { name, role } = meta;
|
|
2310
|
+
if (role !== null && STRUCTURAL_ROLES.has(role)) {
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
const label = `${domain} column "${name}"`;
|
|
2314
|
+
if (role !== null && extras.roleNames !== void 0) {
|
|
2315
|
+
const fixed = extras.roleNames[role];
|
|
2316
|
+
if (fixed !== void 0 && fixed !== name) {
|
|
2317
|
+
note(
|
|
2318
|
+
LOSS.COLUMN_NAME_CHANGED,
|
|
2319
|
+
`${label} (${role}) is written into the format's ${role} slot and reads back as "${fixed}"`,
|
|
2320
|
+
name,
|
|
2321
|
+
column.length - column.nullCount
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (domain === "edge" && role === "id") {
|
|
2326
|
+
continue;
|
|
2327
|
+
}
|
|
2328
|
+
if (role === "timeText") {
|
|
2329
|
+
if (extras.temporalText !== true) {
|
|
2330
|
+
note(
|
|
2331
|
+
LOSS.TEMPORAL_TEXT,
|
|
2332
|
+
`${label} (the lexical form of a temporal column, design section 5.1) cannot be written`,
|
|
2333
|
+
name,
|
|
2334
|
+
column.length - column.nullCount
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
if (role === "position") {
|
|
2340
|
+
if (!caps.positions) {
|
|
2341
|
+
note(LOSS.POSITIONS, `${label} (positions) cannot be written`, name, column.length - column.nullCount);
|
|
2342
|
+
} else if (extras.positionDtype !== void 0 && meta.dtype !== extras.positionDtype) {
|
|
2343
|
+
note(
|
|
2344
|
+
LOSS.DTYPE,
|
|
2345
|
+
`${label} (positions) is ${meta.dtype}; the format reads positions back as ${extras.positionDtype}`,
|
|
2346
|
+
name,
|
|
2347
|
+
column.length - column.nullCount
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
continue;
|
|
2351
|
+
}
|
|
2352
|
+
if (role !== null && VIZ_ROLES.has(role)) {
|
|
2353
|
+
if (!caps.viz) {
|
|
2354
|
+
note(LOSS.VIZ, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
|
|
2355
|
+
}
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
if (role !== null && HIERARCHY_ROLES.has(role)) {
|
|
2359
|
+
if (!caps.hierarchy) {
|
|
2360
|
+
note(LOSS.HIERARCHY, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
|
|
2361
|
+
}
|
|
2362
|
+
continue;
|
|
2363
|
+
}
|
|
2364
|
+
if (role !== null && TEMPORAL_ROLES.has(role)) {
|
|
2365
|
+
if (caps.temporal === "none") {
|
|
2366
|
+
note(LOSS.TEMPORAL, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
|
|
2367
|
+
}
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2370
|
+
if (role === "spells") {
|
|
2371
|
+
if (caps.temporal !== "spells" && caps.temporal !== "dynamic-values") {
|
|
2372
|
+
note(LOSS.SPELLS, `${label} (spells) cannot be written`, name, column.length - column.nullCount);
|
|
2373
|
+
}
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
if (role === "open") {
|
|
2377
|
+
if (extras.openIntervals !== true) {
|
|
2378
|
+
note(
|
|
2379
|
+
LOSS.OPEN_INTERVAL,
|
|
2380
|
+
`${label} (open intervals) cannot be written`,
|
|
2381
|
+
name,
|
|
2382
|
+
column.length - column.nullCount
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
if (role !== null && !(extras.roles?.has(role) ?? false)) {
|
|
2388
|
+
note(
|
|
2389
|
+
LOSS.ROLE,
|
|
2390
|
+
`${label} (${role}) is written as a plain attribute; the format has no ${role} slot and the role is lost`,
|
|
2391
|
+
name,
|
|
2392
|
+
column.length - column.nullCount
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
if (meta.dynamic && caps.temporal !== "dynamic-values") {
|
|
2396
|
+
note(LOSS.DYNAMIC_VALUES, `${label} is dynamic; only its static value can be written`, name, null);
|
|
2397
|
+
}
|
|
2398
|
+
checkDtype(column, label, caps, note);
|
|
2399
|
+
if (meta.default !== void 0 && !caps.defaults) {
|
|
2400
|
+
note(LOSS.DEFAULT, `${label} declares a default; the format has no defaults`, name, null);
|
|
2401
|
+
}
|
|
2402
|
+
if (meta.options !== null && !caps.options) {
|
|
2403
|
+
note(LOSS.OPTIONS, `${label} declares options; the format has no enumerations`, name, null);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function checkDtype(column, label, caps, note) {
|
|
2408
|
+
const { meta } = column;
|
|
2409
|
+
const { name } = meta;
|
|
2410
|
+
const set = column.length - column.nullCount;
|
|
2411
|
+
switch (meta.dtype) {
|
|
2412
|
+
case "list":
|
|
2413
|
+
if (!caps.lists) {
|
|
2414
|
+
note(LOSS.LIST, `${label} is a list; the format has no lists`, name, set);
|
|
2415
|
+
} else if (meta.itemDtype !== null && !caps.dtypes.includes(meta.itemDtype)) {
|
|
2416
|
+
note(
|
|
2417
|
+
LOSS.DTYPE,
|
|
2418
|
+
`${label} holds ${meta.itemDtype} items; the format cannot keep that dtype`,
|
|
2419
|
+
name,
|
|
2420
|
+
set
|
|
2421
|
+
);
|
|
2422
|
+
} else if (meta.itemDtype === "dict") {
|
|
2423
|
+
note(LOSS.DTYPE, `${label} holds dict items; list items read back as string`, name, set);
|
|
2424
|
+
}
|
|
2425
|
+
break;
|
|
2426
|
+
case "json":
|
|
2427
|
+
if (!caps.json) {
|
|
2428
|
+
note(LOSS.JSON, `${label} holds nested values; the format has no nested values`, name, set);
|
|
2429
|
+
}
|
|
2430
|
+
break;
|
|
2431
|
+
default:
|
|
2432
|
+
if (!caps.dtypes.includes(meta.dtype)) {
|
|
2433
|
+
note(LOSS.DTYPE, `${label} is ${meta.dtype}; the format cannot keep that dtype`, name, set);
|
|
2434
|
+
}
|
|
2435
|
+
if (meta.components > 1 && !caps.components) {
|
|
2436
|
+
note(
|
|
2437
|
+
LOSS.COMPONENTS,
|
|
2438
|
+
`${label} has ${meta.components} components; the format has no strides`,
|
|
2439
|
+
name,
|
|
2440
|
+
set
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
break;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
function countMixedEdges(snapshot) {
|
|
2447
|
+
if (!snapshot.directed) {
|
|
2448
|
+
return 0;
|
|
2449
|
+
}
|
|
2450
|
+
const directed = snapshot.edges.byRole("directed");
|
|
2451
|
+
if (directed === null || directed.dtype !== "bool") {
|
|
2452
|
+
return 0;
|
|
2453
|
+
}
|
|
2454
|
+
const pair = snapshot.edges.byRole("pair");
|
|
2455
|
+
let undirectedRows = 0;
|
|
2456
|
+
let paired = 0;
|
|
2457
|
+
for (let e = 0; e < snapshot.edgeCount; e++) {
|
|
2458
|
+
if (directed.isSet(e) && directed.value(e) === false) {
|
|
2459
|
+
undirectedRows++;
|
|
2460
|
+
if (pair !== null && pair.dtype === "u32" && pair.isSet(e) && pair.data[e] !== INVALID_INDEX) {
|
|
2461
|
+
paired++;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
return paired / 2 + (undirectedRows - paired);
|
|
2466
|
+
}
|
|
2467
|
+
function countParallelArcs(snapshot) {
|
|
2468
|
+
const { rowPtr, colIdx, nodeCount } = snapshot;
|
|
2469
|
+
let count = 0;
|
|
2470
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
2471
|
+
for (let a = rowPtr[u] + 1; a < rowPtr[u + 1]; a++) {
|
|
2472
|
+
if (colIdx[a] === colIdx[a - 1]) {
|
|
2473
|
+
count++;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
return snapshot.directed ? count : Math.ceil(count / 2);
|
|
2478
|
+
}
|
|
2479
|
+
const NAME_CHAR_RANGES = [
|
|
2480
|
+
[45, 46],
|
|
2481
|
+
[48, 58],
|
|
2482
|
+
[65, 90],
|
|
2483
|
+
[95, 95],
|
|
2484
|
+
[97, 122],
|
|
2485
|
+
[183, 183],
|
|
2486
|
+
[192, 214],
|
|
2487
|
+
[216, 246],
|
|
2488
|
+
[248, 893],
|
|
2489
|
+
[895, 8191],
|
|
2490
|
+
[8204, 8205],
|
|
2491
|
+
[8255, 8256],
|
|
2492
|
+
[8304, 8591],
|
|
2493
|
+
[11264, 12271],
|
|
2494
|
+
[12289, 55295],
|
|
2495
|
+
[63744, 64975],
|
|
2496
|
+
[65008, 65533],
|
|
2497
|
+
[65536, 983039]
|
|
2498
|
+
];
|
|
2499
|
+
function isNameChar(cp) {
|
|
2500
|
+
for (const [lo, hi] of NAME_CHAR_RANGES) {
|
|
2501
|
+
if (cp < lo) {
|
|
2502
|
+
return false;
|
|
2503
|
+
}
|
|
2504
|
+
if (cp <= hi) {
|
|
2505
|
+
return true;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
return false;
|
|
2509
|
+
}
|
|
2510
|
+
function isNmtoken(text) {
|
|
2511
|
+
if (text.length === 0) {
|
|
2512
|
+
return false;
|
|
2513
|
+
}
|
|
2514
|
+
for (const ch of text) {
|
|
2515
|
+
const cp = ch.codePointAt(0);
|
|
2516
|
+
if (cp === void 0 || !isNameChar(cp)) {
|
|
2517
|
+
return false;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
return true;
|
|
2521
|
+
}
|
|
2522
|
+
function isRepresentableId(id, charset) {
|
|
2523
|
+
switch (charset) {
|
|
2524
|
+
case "any":
|
|
2525
|
+
return true;
|
|
2526
|
+
case "nmtoken":
|
|
2527
|
+
return isNmtoken(String(id));
|
|
2528
|
+
case "integer":
|
|
2529
|
+
return typeof id === "number" && Number.isSafeInteger(id);
|
|
2530
|
+
case "dense-1-based":
|
|
2531
|
+
return false;
|
|
2532
|
+
default: {
|
|
2533
|
+
const name = charset;
|
|
2534
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown id charset ${name}`, { charset: name });
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
function countUnrepresentableIds(snapshot, charset) {
|
|
2539
|
+
const { ids } = snapshot;
|
|
2540
|
+
if (charset === "any") {
|
|
2541
|
+
return 0;
|
|
2542
|
+
}
|
|
2543
|
+
if (charset === "dense-1-based") {
|
|
2544
|
+
if (ids.kind === "identity" && ids.offset === 1) {
|
|
2545
|
+
return 0;
|
|
2546
|
+
}
|
|
2547
|
+
let changed = 0;
|
|
2548
|
+
for (let i = 0; i < ids.size; i++) {
|
|
2549
|
+
if (ids.idOf(i) !== i + 1) {
|
|
2550
|
+
changed++;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
return changed;
|
|
2554
|
+
}
|
|
2555
|
+
let count = 0;
|
|
2556
|
+
for (let i = 0; i < ids.size; i++) {
|
|
2557
|
+
if (!isRepresentableId(ids.idOf(i), charset)) {
|
|
2558
|
+
count++;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
return count;
|
|
2562
|
+
}
|
|
2563
|
+
function sanitizeIds(snapshot, charset, mode) {
|
|
2564
|
+
const { ids } = snapshot;
|
|
2565
|
+
const n = ids.size;
|
|
2566
|
+
if (charset === "dense-1-based") {
|
|
2567
|
+
const changed = countUnrepresentableIds(snapshot, charset);
|
|
2568
|
+
return {
|
|
2569
|
+
charset,
|
|
2570
|
+
changed,
|
|
2571
|
+
idAt: (i) => i + 1,
|
|
2572
|
+
isChanged: (i) => ids.idOf(i) !== i + 1,
|
|
2573
|
+
originalAt: (i) => ids.idOf(i)
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
const unchanged = {
|
|
2577
|
+
charset,
|
|
2578
|
+
changed: 0,
|
|
2579
|
+
idAt: (i) => ids.idOf(i),
|
|
2580
|
+
isChanged: () => false,
|
|
2581
|
+
originalAt: (i) => ids.idOf(i)
|
|
2582
|
+
};
|
|
2583
|
+
if (charset === "any") {
|
|
2584
|
+
return unchanged;
|
|
2585
|
+
}
|
|
2586
|
+
const bad = [];
|
|
2587
|
+
for (let i = 0; i < n; i++) {
|
|
2588
|
+
if (!isRepresentableId(ids.idOf(i), charset)) {
|
|
2589
|
+
bad.push(i);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
if (bad.length === 0) {
|
|
2593
|
+
return unchanged;
|
|
2594
|
+
}
|
|
2595
|
+
if (mode === "error") {
|
|
2596
|
+
const first = ids.idOf(bad[0]);
|
|
2597
|
+
throw new GraphFormatError(
|
|
2598
|
+
"E_INVALID_ID",
|
|
2599
|
+
`${bad.length} node id(s) cannot be written as ${charset} (first: ${JSON.stringify(first)} at index ${bad[0]}); pass sanitizeIds: "mangle" to rewrite them`,
|
|
2600
|
+
{ reason: "charset", charset, count: bad.length, id: first, index: bad[0] }
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
const written = /* @__PURE__ */ new Map();
|
|
2604
|
+
if (charset === "nmtoken") {
|
|
2605
|
+
const used = /* @__PURE__ */ new Set();
|
|
2606
|
+
for (let i = 0; i < n; i++) {
|
|
2607
|
+
const id = ids.idOf(i);
|
|
2608
|
+
if (isRepresentableId(id, charset)) {
|
|
2609
|
+
used.add(String(id));
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
for (const i of bad) {
|
|
2613
|
+
const base = mangleNmtoken(String(ids.idOf(i)));
|
|
2614
|
+
let candidate = base;
|
|
2615
|
+
for (let k = 2; used.has(candidate); k++) {
|
|
2616
|
+
candidate = `${base}_${k}`;
|
|
2617
|
+
}
|
|
2618
|
+
used.add(candidate);
|
|
2619
|
+
written.set(i, candidate);
|
|
2620
|
+
}
|
|
2621
|
+
} else {
|
|
2622
|
+
const used = /* @__PURE__ */ new Set();
|
|
2623
|
+
for (let i = 0; i < n; i++) {
|
|
2624
|
+
const id = ids.idOf(i);
|
|
2625
|
+
if (typeof id === "number" && Number.isSafeInteger(id)) {
|
|
2626
|
+
used.add(id);
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
let next = 0;
|
|
2630
|
+
for (const i of bad) {
|
|
2631
|
+
while (used.has(next)) {
|
|
2632
|
+
next++;
|
|
2633
|
+
}
|
|
2634
|
+
used.add(next);
|
|
2635
|
+
written.set(i, next);
|
|
2636
|
+
next++;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
return {
|
|
2640
|
+
charset,
|
|
2641
|
+
changed: bad.length,
|
|
2642
|
+
idAt: (i) => written.get(i) ?? ids.idOf(i),
|
|
2643
|
+
isChanged: (i) => written.has(i),
|
|
2644
|
+
originalAt: (i) => ids.idOf(i)
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
function mangleNmtoken(text) {
|
|
2648
|
+
let out = "";
|
|
2649
|
+
for (const ch of text) {
|
|
2650
|
+
const cp = ch.codePointAt(0);
|
|
2651
|
+
out += cp !== void 0 && isNameChar(cp) ? ch : "_";
|
|
2652
|
+
}
|
|
2653
|
+
return out.length === 0 ? "_" : out;
|
|
2654
|
+
}
|
|
2655
|
+
const DEFAULT_CHUNK_BYTES = 64 * 1024;
|
|
2656
|
+
async function* encodeChunks(parts, chunkBytes = DEFAULT_CHUNK_BYTES) {
|
|
2657
|
+
const encoder = new TextEncoder();
|
|
2658
|
+
let pending = [];
|
|
2659
|
+
let pendingUnits = 0;
|
|
2660
|
+
for await (const part of parts) {
|
|
2661
|
+
if (part.length === 0) {
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
pending.push(part);
|
|
2665
|
+
pendingUnits += part.length;
|
|
2666
|
+
if (pendingUnits >= chunkBytes) {
|
|
2667
|
+
yield encoder.encode(pending.length === 1 ? pending[0] : pending.join(""));
|
|
2668
|
+
pending = [];
|
|
2669
|
+
pendingUnits = 0;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
if (pending.length > 0) {
|
|
2673
|
+
yield encoder.encode(pending.length === 1 ? pending[0] : pending.join(""));
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
async function joinText(parts) {
|
|
2677
|
+
const collected = [];
|
|
2678
|
+
for await (const part of parts) {
|
|
2679
|
+
collected.push(part);
|
|
2680
|
+
}
|
|
2681
|
+
return collected.join("");
|
|
2682
|
+
}
|
|
2683
|
+
async function decodeChunks(chunks) {
|
|
2684
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
2685
|
+
const parts = [];
|
|
2686
|
+
for await (const chunk of chunks) {
|
|
2687
|
+
parts.push(decoder.decode(chunk, { stream: true }));
|
|
2688
|
+
}
|
|
2689
|
+
parts.push(decoder.decode());
|
|
2690
|
+
return parts.join("");
|
|
2691
|
+
}
|
|
2692
|
+
async function collectBytes(chunks) {
|
|
2693
|
+
const parts = [];
|
|
2694
|
+
let total = 0;
|
|
2695
|
+
for await (const chunk of chunks) {
|
|
2696
|
+
parts.push(chunk);
|
|
2697
|
+
total += chunk.byteLength;
|
|
2698
|
+
}
|
|
2699
|
+
const out = new Uint8Array(total);
|
|
2700
|
+
let offset = 0;
|
|
2701
|
+
for (const part of parts) {
|
|
2702
|
+
out.set(part, offset);
|
|
2703
|
+
offset += part.byteLength;
|
|
2704
|
+
}
|
|
2705
|
+
return out;
|
|
2706
|
+
}
|
|
2707
|
+
function toReadableStream(chunks) {
|
|
2708
|
+
const iterator = chunks[Symbol.asyncIterator]();
|
|
2709
|
+
return new ReadableStream({
|
|
2710
|
+
async pull(controller) {
|
|
2711
|
+
const { done, value } = await iterator.next();
|
|
2712
|
+
if (done) {
|
|
2713
|
+
controller.close();
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
controller.enqueue(value);
|
|
2717
|
+
},
|
|
2718
|
+
async cancel() {
|
|
2719
|
+
await iterator.return?.(void 0);
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
export {
|
|
2724
|
+
TEMPORAL_TEXT_DROPPED_CODE as $,
|
|
2725
|
+
MUTUAL_COLUMN as A,
|
|
2726
|
+
BAD_DEFAULT_CODE as B,
|
|
2727
|
+
COLUMN_RENAMED_CODE as C,
|
|
2728
|
+
DEFAULT_CHUNK_BYTES as D,
|
|
2729
|
+
EMPTY_COLUMN_DROPPED_CODE as E,
|
|
2730
|
+
MUTUAL_EXPANDED_CODE as F,
|
|
2731
|
+
NO_GRAPH_CODE as G,
|
|
2732
|
+
HYPEREDGE_CODE as H,
|
|
2733
|
+
ImportReportBuilder as I,
|
|
2734
|
+
OPTION_IGNORED_CODE as J,
|
|
2735
|
+
PARENTS_DROPPED_CODE as K,
|
|
2736
|
+
LOSS as L,
|
|
2737
|
+
MISSING_ENDPOINT_CODE as M,
|
|
2738
|
+
NO_CAPABILITIES as N,
|
|
2739
|
+
OPTIONS_GAINED_CODE as O,
|
|
2740
|
+
PAIR_COLUMN as P,
|
|
2741
|
+
PARSE_ERROR_CODE as Q,
|
|
2742
|
+
PRECISION_CODE as R,
|
|
2743
|
+
RENAMED_CODE as S,
|
|
2744
|
+
ROLE_ASSUMED_CODE as T,
|
|
2745
|
+
ROLE_DROPPED_CODE as U,
|
|
2746
|
+
ROLE_TAKEN_CODE as V,
|
|
2747
|
+
SINK_OPTION_CODE as W,
|
|
2748
|
+
STORAGE_CLASS_CODE as X,
|
|
2749
|
+
STRAY_TEXT_CODE as Y,
|
|
2750
|
+
SYNTAX_CODE as Z,
|
|
2751
|
+
TEMPORAL_DROPPED_CODE as _,
|
|
2752
|
+
throwIfAborted as a,
|
|
2753
|
+
TEXT_INFERRED_CODE as a0,
|
|
2754
|
+
UNKNOWN_ATTR_TYPE_CODE as a1,
|
|
2755
|
+
UNKNOWN_ELEMENT_CODE as a2,
|
|
2756
|
+
UNKNOWN_PARENT_CODE as a3,
|
|
2757
|
+
UNKNOWN_TYPE_CODE as a4,
|
|
2758
|
+
WEIGHT_KEY_CLASH_CODE as a5,
|
|
2759
|
+
XML_ILLEGAL_CHAR_CODE as a6,
|
|
2760
|
+
XML_SYNTAX_CODE as a7,
|
|
2761
|
+
canonicalId as a8,
|
|
2762
|
+
capabilities as a9,
|
|
2763
|
+
reportUnusedOptions as aA,
|
|
2764
|
+
resolveExportOptions as aB,
|
|
2765
|
+
resolveImportOptions as aC,
|
|
2766
|
+
sanitizeIds as aD,
|
|
2767
|
+
toReadableStream as aE,
|
|
2768
|
+
isNameChar as aF,
|
|
2769
|
+
countMixedEdges as aG,
|
|
2770
|
+
weightFromValue as aH,
|
|
2771
|
+
uniqueColumnName as aI,
|
|
2772
|
+
TIME_TEXT_ROLE as aJ,
|
|
2773
|
+
TIME_TEXT_SUFFIX as aK,
|
|
2774
|
+
declareAttribute as aL,
|
|
2775
|
+
parseDeclaredTemporal as aM,
|
|
2776
|
+
parseDeclaredValue as aN,
|
|
2777
|
+
losesPrecision as aO,
|
|
2778
|
+
parseTimeText as aP,
|
|
2779
|
+
splitListText as aQ,
|
|
2780
|
+
timeTextCompanion as aR,
|
|
2781
|
+
parseDecimalText as aS,
|
|
2782
|
+
formatTimeValue as aT,
|
|
2783
|
+
mapDeclaredType as aU,
|
|
2784
|
+
joinListText as aV,
|
|
2785
|
+
formatTemporal as aW,
|
|
2786
|
+
DictHeuristic as aX,
|
|
2787
|
+
DICT_SAMPLE_ROWS as aY,
|
|
2788
|
+
takenIn as aZ,
|
|
2789
|
+
declareOn as a_,
|
|
2790
|
+
checkCapabilities as aa,
|
|
2791
|
+
coerceId as ab,
|
|
2792
|
+
coerceIdText as ac,
|
|
2793
|
+
collectBytes as ad,
|
|
2794
|
+
declareCompanion as ae,
|
|
2795
|
+
declareResolved as af,
|
|
2796
|
+
decodeChunks as ag,
|
|
2797
|
+
encodeChunks as ah,
|
|
2798
|
+
explicitWeights as ai,
|
|
2799
|
+
formatDecimal as aj,
|
|
2800
|
+
formatF32 as ak,
|
|
2801
|
+
formatF64 as al,
|
|
2802
|
+
formatGmlReal as am,
|
|
2803
|
+
formatInteger as an,
|
|
2804
|
+
inputLength as ao,
|
|
2805
|
+
isAbortError as ap,
|
|
2806
|
+
isCanonicalIntegerText as aq,
|
|
2807
|
+
isImportInput as ar,
|
|
2808
|
+
isNmtoken as as,
|
|
2809
|
+
isWeightField as at,
|
|
2810
|
+
joinText as au,
|
|
2811
|
+
mangleNmtoken as av,
|
|
2812
|
+
pairFolding as aw,
|
|
2813
|
+
parseWeightText as ax,
|
|
2814
|
+
readText as ay,
|
|
2815
|
+
reportSinkOptions as az,
|
|
2816
|
+
BAD_OPTIONS_CODE as b,
|
|
2817
|
+
COLUMN_RENAMED_LOSS_CODE as c,
|
|
2818
|
+
COUNT_HINT_CODE as d,
|
|
2819
|
+
DEFAULT_ERROR_LIMIT as e,
|
|
2820
|
+
DIRECTED_COLUMN as f,
|
|
2821
|
+
DIRECTION_FORCED_CODE as g,
|
|
2822
|
+
DIRECTION_REFUSED_CODE as h,
|
|
2823
|
+
DUPLICATE_EDGE_ID_CODE as i,
|
|
2824
|
+
DUPLICATE_KEY_CODE as j,
|
|
2825
|
+
DUPLICATE_NODE_CODE as k,
|
|
2826
|
+
DirectionResolver as l,
|
|
2827
|
+
EMPTY_INPUT_CODE as m,
|
|
2828
|
+
ID_MERGED_CODE as n,
|
|
2829
|
+
ID_TEXT_COLLISION_CODE as o,
|
|
2830
|
+
ID_TEXT_TYPE_CODE as p,
|
|
2831
|
+
INTEGRAL_F64_CODE as q,
|
|
2832
|
+
INVALID_UTF8_CODE as r,
|
|
2833
|
+
IdCoercer as s,
|
|
2834
|
+
textChunks as t,
|
|
2835
|
+
ImportError as u,
|
|
2836
|
+
LineReader as v,
|
|
2837
|
+
MISSING_ID_CODE as w,
|
|
2838
|
+
MIXED_DIRECTION_CODE as x,
|
|
2839
|
+
MULTIPLE_GRAPHS_CODE as y,
|
|
2840
|
+
MUTUAL_AS_UNDIRECTED_CODE as z
|
|
2841
|
+
};
|
|
2842
|
+
//# sourceMappingURL=writer-DxSKC7TL.js.map
|