@graphty/graph-io 0.3.2 → 0.3.4
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/README.md +17 -4
- package/dist/chunks/{escape-CKied3Ri.js → escape-DXbOoLKl.js} +23 -5
- package/dist/chunks/{escape-CKied3Ri.js.map → escape-DXbOoLKl.js.map} +1 -1
- package/dist/chunks/{importer-DbMrAR_w.js → importer-B9qF4e32.js} +37 -2
- package/dist/chunks/importer-B9qF4e32.js.map +1 -0
- package/dist/chunks/{importer-DkzjTvHc.js → importer-BOxwmef_.js} +360 -16
- package/dist/chunks/importer-BOxwmef_.js.map +1 -0
- package/dist/chunks/{importer-D5pAsweZ.js → importer-C2caVvl6.js} +2 -2
- package/dist/chunks/{importer-D5pAsweZ.js.map → importer-C2caVvl6.js.map} +1 -1
- package/dist/chunks/{importer-C6QcIRIb.js → importer-xoHpRG5x.js} +2 -2
- package/dist/chunks/{importer-C6QcIRIb.js.map → importer-xoHpRG5x.js.map} +1 -1
- package/dist/csv.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +1 -1
- package/dist/gml.js +34 -13
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +9 -9
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +1 -1
- package/dist/json.js +1 -1
- package/dist/neo4j.js +60 -2
- package/dist/neo4j.js.map +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/escape.d.ts +4 -2
- package/dist/src/common/escape.d.ts.map +1 -1
- package/dist/src/common/escape.js +31 -5
- package/dist/src/common/escape.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +2 -0
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +19 -5
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +2 -0
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +3 -1
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts +2 -0
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +8 -1
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/constants.d.ts +4 -0
- package/dist/src/formats/graphml/constants.d.ts.map +1 -1
- package/dist/src/formats/graphml/constants.js +4 -0
- package/dist/src/formats/graphml/constants.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +28 -0
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts +21 -4
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +29 -4
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +27 -7
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +19 -4
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +369 -8
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/index.d.ts +1 -1
- package/dist/src/formats/json/index.d.ts.map +1 -1
- package/dist/src/formats/json/index.js +1 -1
- package/dist/src/formats/json/index.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts +5 -0
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +57 -1
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/neo4j/index.d.ts +2 -0
- package/dist/src/formats/neo4j/index.d.ts.map +1 -1
- package/dist/src/formats/neo4j/index.js +3 -1
- package/dist/src/formats/neo4j/index.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/sniff.d.ts +3 -3
- package/dist/src/sniff.d.ts.map +1 -1
- package/dist/src/sniff.js.map +1 -1
- package/package.json +2 -2
- package/src/common/escape.ts +5 -4
- package/src/formats/json/dialect.ts +40 -6
- package/src/formats/json/exporter.ts +28 -7
- package/src/formats/json/importer.ts +411 -21
- package/src/formats/json/index.ts +7 -1
- package/src/formats/neo4j/exporter.ts +43 -6
- package/src/formats/neo4j/importer.ts +56 -11
- package/src/formats/neo4j/index.ts +10 -3
- package/src/formats/pajek/exporter.ts +3 -1
- package/src/formats/pajek/importer.ts +284 -60
- package/src/formats/pajek/syntax.ts +98 -12
- package/src/index.ts +2 -0
- package/src/sniff.ts +3 -3
- package/dist/chunks/importer-DbMrAR_w.js.map +0 -1
- package/dist/chunks/importer-DkzjTvHc.js.map +0 -1
|
@@ -8,6 +8,11 @@ const JSON_DIALECTS = Object.freeze([
|
|
|
8
8
|
"graphology",
|
|
9
9
|
"vis"
|
|
10
10
|
]);
|
|
11
|
+
const JSON_IMPORT_DIALECTS = Object.freeze([
|
|
12
|
+
...JSON_DIALECTS,
|
|
13
|
+
"adjacency",
|
|
14
|
+
"tree"
|
|
15
|
+
]);
|
|
11
16
|
const META_KEY = "json";
|
|
12
17
|
function isJsonObject(value) {
|
|
13
18
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -18,6 +23,9 @@ function hasKey(record, key) {
|
|
|
18
23
|
function isJsonDialect(value) {
|
|
19
24
|
return typeof value === "string" && JSON_DIALECTS.includes(value);
|
|
20
25
|
}
|
|
26
|
+
function isJsonImportDialect(value) {
|
|
27
|
+
return typeof value === "string" && JSON_IMPORT_DIALECTS.includes(value);
|
|
28
|
+
}
|
|
21
29
|
function sniffJsonDialect(root) {
|
|
22
30
|
if (Array.isArray(root)) {
|
|
23
31
|
const first = firstJsonObject(root);
|
|
@@ -35,8 +43,13 @@ function sniffJsonDialect(root) {
|
|
|
35
43
|
if (Array.isArray(root.graphs)) {
|
|
36
44
|
return "jgf";
|
|
37
45
|
}
|
|
38
|
-
if (!hasKey(root, "
|
|
39
|
-
|
|
46
|
+
if (!hasKey(root, "edges") && !hasKey(root, "links")) {
|
|
47
|
+
if (hasKey(root, "nodes") && hasKey(root, "adjacency")) {
|
|
48
|
+
return "adjacency";
|
|
49
|
+
}
|
|
50
|
+
if (!hasKey(root, "nodes")) {
|
|
51
|
+
return hasKey(root, "children") ? "tree" : null;
|
|
52
|
+
}
|
|
40
53
|
}
|
|
41
54
|
const firstNode = firstJsonObject(root.nodes);
|
|
42
55
|
const firstEdge = firstJsonObject(hasKey(root, "edges") ? root.edges : root.links);
|
|
@@ -91,7 +104,10 @@ const DIALECT_DEFAULT_DIRECTED = Object.freeze({
|
|
|
91
104
|
jgf: true,
|
|
92
105
|
cytoscape: true,
|
|
93
106
|
graphology: true,
|
|
94
|
-
vis: false
|
|
107
|
+
vis: false,
|
|
108
|
+
// networkx adjacency_data declares `directed`; tree_graph always builds a DiGraph
|
|
109
|
+
adjacency: false,
|
|
110
|
+
tree: true
|
|
95
111
|
});
|
|
96
112
|
const JSON_DTYPES = Object.freeze(["f64", "i32", "bool", "string"]);
|
|
97
113
|
const COMMON = {
|
|
@@ -290,10 +306,17 @@ function numberText(value, f32, nonfinite) {
|
|
|
290
306
|
nonfinite.count++;
|
|
291
307
|
return "null";
|
|
292
308
|
}
|
|
293
|
-
|
|
294
|
-
|
|
309
|
+
return exponentIfUnsafe(f32 ? formatF32(value) : formatF64(value));
|
|
310
|
+
}
|
|
311
|
+
function exponentIfUnsafe(text) {
|
|
312
|
+
const match = /^(-?)([0-9]{16,})$/.exec(text);
|
|
313
|
+
if (match === null || Number.isSafeInteger(Number(text))) {
|
|
314
|
+
return text;
|
|
295
315
|
}
|
|
296
|
-
|
|
316
|
+
const [, sign, digits] = match;
|
|
317
|
+
const mantissa = digits.replace(/0+$/, "");
|
|
318
|
+
const fraction = mantissa.length > 1 ? `.${mantissa.slice(1)}` : "";
|
|
319
|
+
return `${sign}${mantissa[0]}${fraction}e+${digits.length - 1}`;
|
|
297
320
|
}
|
|
298
321
|
function valueText(value, f32, nonfinite) {
|
|
299
322
|
switch (typeof value) {
|
|
@@ -325,7 +348,7 @@ function cellText(column, row, ids, nonfinite) {
|
|
|
325
348
|
case "u32":
|
|
326
349
|
if (column.meta.refersTo === "node" && column.meta.components === 1) {
|
|
327
350
|
const index = column.data[row];
|
|
328
|
-
return index === INVALID_INDEX ? "null" :
|
|
351
|
+
return index === INVALID_INDEX ? "null" : idText(ids.idOf(index));
|
|
329
352
|
}
|
|
330
353
|
return valueText(column.value(row), false, nonfinite);
|
|
331
354
|
case "f32":
|
|
@@ -716,7 +739,8 @@ function planWeights(ctx, edges) {
|
|
|
716
739
|
if (!weights.isExplicit(e)) {
|
|
717
740
|
return null;
|
|
718
741
|
}
|
|
719
|
-
|
|
742
|
+
const text = Number.isFinite(weights.value(e)) ? weights.text(e) : "null";
|
|
743
|
+
return text === null ? null : exponentIfUnsafe(text);
|
|
720
744
|
};
|
|
721
745
|
}
|
|
722
746
|
function planDirection(ctx) {
|
|
@@ -890,7 +914,10 @@ function anySet(plans, row, slots) {
|
|
|
890
914
|
return plans.some((item) => slots.has(item.slot) && item.column.isSet(row));
|
|
891
915
|
}
|
|
892
916
|
function idText(id) {
|
|
893
|
-
|
|
917
|
+
if (typeof id === "number") {
|
|
918
|
+
return Object.is(id, -0) ? "0" : exponentIfUnsafe(String(id));
|
|
919
|
+
}
|
|
920
|
+
return JSON.stringify(id);
|
|
894
921
|
}
|
|
895
922
|
function edgeIdText(p, e, c) {
|
|
896
923
|
const column = p.edgeIds;
|
|
@@ -1366,6 +1393,8 @@ const JSON_ISSUE = Object.freeze({
|
|
|
1366
1393
|
HYPEREDGE_SHAPE: "E_HYPEREDGE_SHAPE",
|
|
1367
1394
|
/** The nodes have no id key at all; array positions became the ids. */
|
|
1368
1395
|
POSITIONAL_NODES: "W_POSITIONAL_NODES",
|
|
1396
|
+
/** A node-link / d3 top-level key the importer does not read (the other of edges / links, an unknown key); it is dropped. */
|
|
1397
|
+
UNREAD_KEY: "W_JSON_UNREAD_KEY",
|
|
1369
1398
|
/** A builder-policy option (addMissingNodes, duplicateEdges, selfLoops, weightDtype) differs from the sink's (the shared W_SINK_OPTION). */
|
|
1370
1399
|
SINK_OPTION: SINK_OPTION_CODE,
|
|
1371
1400
|
/** A common option the dialect has no use for (nodeIdFrom outside node-link, long, restoreMangledIds). */
|
|
@@ -1376,6 +1405,10 @@ const JSON_ISSUE = Object.freeze({
|
|
|
1376
1405
|
INVALID_ENCODING: INVALID_ENCODING_CODE,
|
|
1377
1406
|
/** Bytes that are not UTF-8 and declare no encoding were read as windows-1252. */
|
|
1378
1407
|
ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
|
|
1408
|
+
/** The document uses the non-standard tokens NaN / Infinity / -Infinity (Python's json writes them); read as numbers. */
|
|
1409
|
+
NONSTANDARD_NUMBER: "W_JSON_NONSTANDARD_NUMBER",
|
|
1410
|
+
/** Integer literals beyond 2^53 were read as their exact digits (strings), not as rounded numbers. */
|
|
1411
|
+
BIG_INTEGER: "W_JSON_BIG_INTEGER",
|
|
1379
1412
|
/** A declared encoding the platform cannot decode was ignored. */
|
|
1380
1413
|
UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE
|
|
1381
1414
|
});
|
|
@@ -1417,8 +1450,8 @@ const VIS_TARGET_KEYS = Object.freeze(["to"]);
|
|
|
1417
1450
|
function resolveJsonOptions(options) {
|
|
1418
1451
|
const o = options ?? {};
|
|
1419
1452
|
const dialect = o.dialect ?? "auto";
|
|
1420
|
-
if (dialect !== "auto" && !
|
|
1421
|
-
throw unsupportedOption("dialect", dialect, [...
|
|
1453
|
+
if (dialect !== "auto" && !isJsonImportDialect(dialect)) {
|
|
1454
|
+
throw unsupportedOption("dialect", dialect, [...JSON_IMPORT_DIALECTS, "auto"]);
|
|
1422
1455
|
}
|
|
1423
1456
|
const indexLinks = o.indexLinks ?? "auto";
|
|
1424
1457
|
if (indexLinks !== "auto" && typeof indexLinks !== "boolean") {
|
|
@@ -1989,12 +2022,122 @@ function parseDocument(text, report) {
|
|
|
1989
2022
|
if (text.trim().length === 0) {
|
|
1990
2023
|
report.fail(JSON_ISSUE.EMPTY_INPUT, "the input is empty");
|
|
1991
2024
|
}
|
|
2025
|
+
let root;
|
|
2026
|
+
let syntaxError = null;
|
|
1992
2027
|
try {
|
|
1993
|
-
|
|
2028
|
+
root = JSON.parse(text);
|
|
1994
2029
|
} catch (err) {
|
|
1995
|
-
|
|
1996
|
-
|
|
2030
|
+
syntaxError = err instanceof Error ? err.message : String(err);
|
|
2031
|
+
}
|
|
2032
|
+
if (syntaxError === null && !MAYBE_UNSAFE_INTEGER.test(text)) {
|
|
2033
|
+
return root;
|
|
2034
|
+
}
|
|
2035
|
+
const scan = rewriteNumbers(text);
|
|
2036
|
+
if (scan.tokens.size > 0 || scan.bigIntegers.length > 0) {
|
|
2037
|
+
try {
|
|
2038
|
+
root = JSON.parse(scan.text, scan.tokens.size > 0 ? reviveNonstandard : void 0);
|
|
2039
|
+
syntaxError = null;
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (syntaxError !== null) {
|
|
2044
|
+
return report.fail(JSON_ISSUE.SYNTAX, `invalid JSON: ${syntaxError}`);
|
|
2045
|
+
}
|
|
2046
|
+
if (scan.tokens.size > 0) {
|
|
2047
|
+
report.warning(
|
|
2048
|
+
"coercion",
|
|
2049
|
+
JSON_ISSUE.NONSTANDARD_NUMBER,
|
|
2050
|
+
`the document uses the non-standard token(s) ${[...scan.tokens].join(", ")}, which strict JSON does not allow; read as numbers`
|
|
2051
|
+
);
|
|
1997
2052
|
}
|
|
2053
|
+
if (scan.bigIntegers.length > 0) {
|
|
2054
|
+
const shown = scan.bigIntegers.slice(0, BIG_INTEGERS_SHOWN).join(", ");
|
|
2055
|
+
const more = scan.bigIntegers.length - BIG_INTEGERS_SHOWN;
|
|
2056
|
+
report.warning(
|
|
2057
|
+
"precision",
|
|
2058
|
+
JSON_ISSUE.BIG_INTEGER,
|
|
2059
|
+
`${scan.bigIntegers.length} integer(s) beyond 2^53 kept as text so no digit is lost: ${shown}${more > 0 ? ` and ${more} more` : ""}`
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
return root;
|
|
2063
|
+
}
|
|
2064
|
+
const MAYBE_UNSAFE_INTEGER = new RegExp("(?<![0-9.])[0-9]{16}");
|
|
2065
|
+
const BIG_INTEGERS_SHOWN = 10;
|
|
2066
|
+
const NONSTANDARD_SENTINEL = `${String.fromCharCode(0)}graph-io:`;
|
|
2067
|
+
const NONSTANDARD_TOKENS = [
|
|
2068
|
+
["-Infinity", -Infinity],
|
|
2069
|
+
["Infinity", Infinity],
|
|
2070
|
+
["NaN", NaN]
|
|
2071
|
+
];
|
|
2072
|
+
const INTEGER_LITERAL = /^-?(0|[1-9][0-9]*)$/;
|
|
2073
|
+
function rewriteNumbers(text) {
|
|
2074
|
+
const parts = [];
|
|
2075
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
2076
|
+
const bigIntegers = [];
|
|
2077
|
+
const arrays = [];
|
|
2078
|
+
let expectValue = true;
|
|
2079
|
+
let copied = 0;
|
|
2080
|
+
let i = 0;
|
|
2081
|
+
const n = text.length;
|
|
2082
|
+
while (i < n) {
|
|
2083
|
+
const ch = text[i];
|
|
2084
|
+
if (ch === '"') {
|
|
2085
|
+
i++;
|
|
2086
|
+
while (i < n && text[i] !== '"') {
|
|
2087
|
+
i += text[i] === "\\" ? 2 : 1;
|
|
2088
|
+
}
|
|
2089
|
+
i++;
|
|
2090
|
+
expectValue = false;
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
if (ch === "{" || ch === "[") {
|
|
2094
|
+
arrays.push(ch === "[");
|
|
2095
|
+
expectValue = ch === "[";
|
|
2096
|
+
} else if (ch === "}" || ch === "]") {
|
|
2097
|
+
arrays.pop();
|
|
2098
|
+
expectValue = false;
|
|
2099
|
+
} else if (ch === ":") {
|
|
2100
|
+
expectValue = true;
|
|
2101
|
+
} else if (ch === ",") {
|
|
2102
|
+
expectValue = arrays.length > 0 && arrays[arrays.length - 1];
|
|
2103
|
+
} else if (expectValue && ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
|
|
2104
|
+
expectValue = false;
|
|
2105
|
+
const token = NONSTANDARD_TOKENS.find(([word]) => text.startsWith(word, i));
|
|
2106
|
+
let end = i;
|
|
2107
|
+
let replacement = null;
|
|
2108
|
+
if (token !== void 0) {
|
|
2109
|
+
end = i + token[0].length;
|
|
2110
|
+
tokens.add(token[0]);
|
|
2111
|
+
replacement = JSON.stringify(`${NONSTANDARD_SENTINEL}${token[0]}`);
|
|
2112
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
2113
|
+
end = i + 1;
|
|
2114
|
+
while (end < n && "0123456789+-.eE".includes(text[end])) {
|
|
2115
|
+
end++;
|
|
2116
|
+
}
|
|
2117
|
+
const literal = text.slice(i, end);
|
|
2118
|
+
if (INTEGER_LITERAL.test(literal) && !Number.isSafeInteger(Number(literal))) {
|
|
2119
|
+
bigIntegers.push(literal);
|
|
2120
|
+
replacement = `"${literal}"`;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
if (replacement !== null) {
|
|
2124
|
+
parts.push(text.slice(copied, i), replacement);
|
|
2125
|
+
copied = end;
|
|
2126
|
+
}
|
|
2127
|
+
i = Math.max(end, i + 1);
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
i++;
|
|
2131
|
+
}
|
|
2132
|
+
parts.push(text.slice(copied));
|
|
2133
|
+
return { text: parts.join(""), tokens, bigIntegers };
|
|
2134
|
+
}
|
|
2135
|
+
function reviveNonstandard(_key, value) {
|
|
2136
|
+
if (typeof value === "string" && value.startsWith(NONSTANDARD_SENTINEL)) {
|
|
2137
|
+
const found = NONSTANDARD_TOKENS.find(([word]) => word === value.slice(NONSTANDARD_SENTINEL.length));
|
|
2138
|
+
return found === void 0 ? value : found[1];
|
|
2139
|
+
}
|
|
2140
|
+
return value;
|
|
1998
2141
|
}
|
|
1999
2142
|
function detectDialect(root, forced, report) {
|
|
2000
2143
|
if (forced !== "auto") {
|
|
@@ -2015,7 +2158,7 @@ function detectDialect(root, forced, report) {
|
|
|
2015
2158
|
}
|
|
2016
2159
|
return report.fail(
|
|
2017
2160
|
JSON_ISSUE.DIALECT,
|
|
2018
|
-
"no known dialect: expected nodes / links / edges (node-link), elements (Cytoscape) or graph (JGF)"
|
|
2161
|
+
"no known dialect: expected nodes / links / edges (node-link), nodes / adjacency (adjacency), children (tree), elements (Cytoscape) or graph (JGF)"
|
|
2019
2162
|
);
|
|
2020
2163
|
}
|
|
2021
2164
|
function arraySection(value, what, report) {
|
|
@@ -2087,6 +2230,20 @@ function importNodeLink(ctx, root, dialect) {
|
|
|
2087
2230
|
const multigraph = hasKey(root, "multigraph") ? flagOf(root.multigraph, "multigraph", false, report) : null;
|
|
2088
2231
|
ctx.setHeader(directed);
|
|
2089
2232
|
ctx.writeGraphDict(root.graph, "graph");
|
|
2233
|
+
for (const key of Object.keys(root)) {
|
|
2234
|
+
if (key !== "nodes" && key !== edgesKey && key !== "directed" && key !== "multigraph" && key !== "graph") {
|
|
2235
|
+
const value = root[key];
|
|
2236
|
+
const what = Array.isArray(value) ? `${String(value.length)} ${value.length === 1 ? "entry" : "entries"}` : describe(value);
|
|
2237
|
+
report.warning(
|
|
2238
|
+
"unsupported",
|
|
2239
|
+
JSON_ISSUE.UNREAD_KEY,
|
|
2240
|
+
`top-level key ${key} (${what}) is not read; dropped`,
|
|
2241
|
+
{
|
|
2242
|
+
element: key
|
|
2243
|
+
}
|
|
2244
|
+
);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2090
2247
|
const nodeList = nodes ?? [];
|
|
2091
2248
|
const edgeList = edges ?? [];
|
|
2092
2249
|
ctx.sink.reserve(nodeList.length, edgeList.length);
|
|
@@ -2334,6 +2491,187 @@ function importVis(ctx, root) {
|
|
|
2334
2491
|
ctx.weightOriginPatch()
|
|
2335
2492
|
);
|
|
2336
2493
|
}
|
|
2494
|
+
function importAdjacency(ctx, root) {
|
|
2495
|
+
const { report } = ctx;
|
|
2496
|
+
const nodes = arraySection(root.nodes, "nodes", report) ?? [];
|
|
2497
|
+
const adjacency = arraySection(root.adjacency, "adjacency", report);
|
|
2498
|
+
if (adjacency === null) {
|
|
2499
|
+
report.error(
|
|
2500
|
+
"missing-value",
|
|
2501
|
+
JSON_ISSUE.MISSING_SECTION,
|
|
2502
|
+
"the document has no adjacency array; the graph has no edges",
|
|
2503
|
+
{
|
|
2504
|
+
element: "adjacency"
|
|
2505
|
+
}
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
const directed = flagOf(root.directed, "directed", ctx.defaultDirected("adjacency"), report);
|
|
2509
|
+
const multigraph = hasKey(root, "multigraph") ? flagOf(root.multigraph, "multigraph", false, report) : null;
|
|
2510
|
+
ctx.setHeader(directed);
|
|
2511
|
+
ctx.writeGraphDict(isPairList(root.graph) ? Object.fromEntries(root.graph) : root.graph, "graph");
|
|
2512
|
+
for (const key of Object.keys(root)) {
|
|
2513
|
+
if (key !== "nodes" && key !== "adjacency" && key !== "directed" && key !== "multigraph" && key !== "graph") {
|
|
2514
|
+
report.warning(
|
|
2515
|
+
"unsupported",
|
|
2516
|
+
JSON_ISSUE.UNREAD_KEY,
|
|
2517
|
+
`top-level key ${key} (${describe(root[key])}) is not read; dropped`,
|
|
2518
|
+
{
|
|
2519
|
+
element: key
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
const lists = adjacency ?? [];
|
|
2525
|
+
ctx.sink.reserve(
|
|
2526
|
+
nodes.length,
|
|
2527
|
+
lists.reduce((sum, list) => sum + (Array.isArray(list) ? list.length : 0), 0)
|
|
2528
|
+
);
|
|
2529
|
+
const idKey = ctx.json.nodeIdKey ?? "id";
|
|
2530
|
+
ctx.reportNodeIdFrom("adjacency", `the ${JSON.stringify(idKey)} key`);
|
|
2531
|
+
const owners = [];
|
|
2532
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
2533
|
+
const element = `nodes[${i}]`;
|
|
2534
|
+
const record = nodes[i];
|
|
2535
|
+
let pushed = null;
|
|
2536
|
+
if (!isJsonObject(record)) {
|
|
2537
|
+
ctx.badElement("node", element);
|
|
2538
|
+
} else {
|
|
2539
|
+
const id = ctx.coerceId(hasKey(record, idKey) ? record[idKey] : void 0, element);
|
|
2540
|
+
if (id === null) {
|
|
2541
|
+
ctx.countSkipped("node");
|
|
2542
|
+
} else {
|
|
2543
|
+
const index = ctx.pushNode(id, element);
|
|
2544
|
+
if (index >= 0) {
|
|
2545
|
+
pushed = id;
|
|
2546
|
+
writeFlat(ctx, ctx.nodes, index, record, id, (key) => key !== idKey);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
owners.push(pushed);
|
|
2551
|
+
}
|
|
2552
|
+
throwIfAborted(ctx.options.signal);
|
|
2553
|
+
const kind = ctx.uniformKind();
|
|
2554
|
+
const { weightFrom } = ctx.options;
|
|
2555
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2556
|
+
for (let i = 0; i < lists.length; i++) {
|
|
2557
|
+
const element = `adjacency[${i}]`;
|
|
2558
|
+
const list = lists[i];
|
|
2559
|
+
if (!Array.isArray(list)) {
|
|
2560
|
+
report.error("validation-error", JSON_ISSUE.BAD_ELEMENT, `${element} is not an array`, { element });
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
const source = i < owners.length ? owners[i] : null;
|
|
2564
|
+
if (source === null) {
|
|
2565
|
+
const why = i < nodes.length ? `nodes[${i}] was skipped` : `there is no nodes[${i}]`;
|
|
2566
|
+
report.error(
|
|
2567
|
+
"missing-value",
|
|
2568
|
+
JSON_ISSUE.BAD_INDEX,
|
|
2569
|
+
`${element}: ${why}; its ${list.length} edge(s) are skipped`,
|
|
2570
|
+
{
|
|
2571
|
+
element
|
|
2572
|
+
}
|
|
2573
|
+
);
|
|
2574
|
+
report.counts.skippedEdges += list.length;
|
|
2575
|
+
continue;
|
|
2576
|
+
}
|
|
2577
|
+
for (let j = 0; j < list.length; j++) {
|
|
2578
|
+
const entry = `${element}[${j}]`;
|
|
2579
|
+
const record = list[j];
|
|
2580
|
+
if (!isJsonObject(record)) {
|
|
2581
|
+
ctx.badElement("edge", entry);
|
|
2582
|
+
continue;
|
|
2583
|
+
}
|
|
2584
|
+
if (!hasKey(record, idKey)) {
|
|
2585
|
+
ctx.missingEndpoint(entry, idKey);
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
try {
|
|
2589
|
+
const target = ctx.coerceId(record[idKey], `${entry}.${idKey}`);
|
|
2590
|
+
if (target === null) {
|
|
2591
|
+
ctx.countSkipped("edge");
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
if (!directed && isMirroredEntry(pending, source, target, record.key)) {
|
|
2595
|
+
continue;
|
|
2596
|
+
}
|
|
2597
|
+
const edge = ctx.pushEdge(source, target, kind, ctx.weightOf(record), entry);
|
|
2598
|
+
for (const key of Object.keys(record)) {
|
|
2599
|
+
if (key !== idKey && key !== weightFrom) {
|
|
2600
|
+
ctx.edges.write(edge, key, record[key], SUFFIX.data);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
} catch (err) {
|
|
2604
|
+
ctx.skip(err, "edge", entry);
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
ctx.setMeta({}, { declaredMultigraph: multigraph, ...ctx.weightOriginPatch() });
|
|
2609
|
+
}
|
|
2610
|
+
function isMirroredEntry(pending, source, target, key) {
|
|
2611
|
+
const a = JSON.stringify(source);
|
|
2612
|
+
const b = JSON.stringify(target);
|
|
2613
|
+
if (a === b) {
|
|
2614
|
+
return false;
|
|
2615
|
+
}
|
|
2616
|
+
const k = JSON.stringify(key ?? null);
|
|
2617
|
+
const mirror = `${b} ${a} ${k}`;
|
|
2618
|
+
const waiting = pending.get(mirror) ?? 0;
|
|
2619
|
+
if (waiting > 0) {
|
|
2620
|
+
pending.set(mirror, waiting - 1);
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
const own = `${a} ${b} ${k}`;
|
|
2624
|
+
pending.set(own, (pending.get(own) ?? 0) + 1);
|
|
2625
|
+
return false;
|
|
2626
|
+
}
|
|
2627
|
+
function isPairList(value) {
|
|
2628
|
+
return Array.isArray(value) && value.every((p) => Array.isArray(p) && p.length === 2 && typeof p[0] === "string");
|
|
2629
|
+
}
|
|
2630
|
+
function importTree(ctx, root) {
|
|
2631
|
+
const idKey = ctx.json.nodeIdKey ?? "id";
|
|
2632
|
+
ctx.setHeader(ctx.defaultDirected("tree"));
|
|
2633
|
+
ctx.reportNodeIdFrom("tree", `the ${JSON.stringify(idKey)} key`);
|
|
2634
|
+
const kind = ctx.uniformKind();
|
|
2635
|
+
const stack = [
|
|
2636
|
+
{ record: root, element: "root", parent: null }
|
|
2637
|
+
];
|
|
2638
|
+
for (let item = stack.pop(); item !== void 0; item = stack.pop()) {
|
|
2639
|
+
const { record, element, parent } = item;
|
|
2640
|
+
if (!isJsonObject(record)) {
|
|
2641
|
+
ctx.badElement("node", element);
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
let id = ctx.coerceId(hasKey(record, idKey) ? record[idKey] : void 0, element);
|
|
2645
|
+
if (id === null) {
|
|
2646
|
+
ctx.countSkipped("node");
|
|
2647
|
+
} else if (ctx.pushNode(id, element) < 0) {
|
|
2648
|
+
id = null;
|
|
2649
|
+
} else {
|
|
2650
|
+
writeFlat(ctx, ctx.nodes, ctx.sink.indexOf(id), record, id, (key) => key !== idKey && key !== "children");
|
|
2651
|
+
if (parent !== null) {
|
|
2652
|
+
try {
|
|
2653
|
+
ctx.pushEdge(parent, id, kind, void 0, element);
|
|
2654
|
+
} catch (err) {
|
|
2655
|
+
ctx.skip(err, "edge", element);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
const { children } = record;
|
|
2660
|
+
if (Array.isArray(children)) {
|
|
2661
|
+
for (let k = children.length - 1; k >= 0; k--) {
|
|
2662
|
+
stack.push({ record: children[k], element: `${element}.children[${k}]`, parent: id });
|
|
2663
|
+
}
|
|
2664
|
+
} else if (children !== void 0 && children !== null) {
|
|
2665
|
+
ctx.report.error(
|
|
2666
|
+
"validation-error",
|
|
2667
|
+
JSON_ISSUE.BAD_VALUE,
|
|
2668
|
+
`${element}: children must be an array, found ${describe(children)}`,
|
|
2669
|
+
{ element }
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
ctx.setMeta({});
|
|
2674
|
+
}
|
|
2337
2675
|
function importGraphology(ctx, root) {
|
|
2338
2676
|
const { report } = ctx;
|
|
2339
2677
|
const nodes = arraySection(root.nodes, "nodes", report) ?? [];
|
|
@@ -3006,6 +3344,12 @@ function readGraph(root, dialect, sink, report, resolved, json, options) {
|
|
|
3006
3344
|
case "vis":
|
|
3007
3345
|
importVis(ctx, doc);
|
|
3008
3346
|
break;
|
|
3347
|
+
case "adjacency":
|
|
3348
|
+
importAdjacency(ctx, doc);
|
|
3349
|
+
break;
|
|
3350
|
+
case "tree":
|
|
3351
|
+
importTree(ctx, doc);
|
|
3352
|
+
break;
|
|
3009
3353
|
default: {
|
|
3010
3354
|
const name = dialect;
|
|
3011
3355
|
throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {
|
|
@@ -3026,4 +3370,4 @@ export {
|
|
|
3026
3370
|
jsonCapabilities as j,
|
|
3027
3371
|
sniffJsonDialect as s
|
|
3028
3372
|
};
|
|
3029
|
-
//# sourceMappingURL=importer-
|
|
3373
|
+
//# sourceMappingURL=importer-BOxwmef_.js.map
|