@graphty/graph-io 0.3.3 → 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.
Files changed (42) hide show
  1. package/README.md +17 -4
  2. package/dist/chunks/{importer-BELrICWM.js → importer-BOxwmef_.js} +352 -19
  3. package/dist/chunks/importer-BOxwmef_.js.map +1 -0
  4. package/dist/graph-io.js +2 -2
  5. package/dist/graph-io.js.map +1 -1
  6. package/dist/json.js +1 -1
  7. package/dist/src/formats/json/dialect.d.ts +21 -4
  8. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  9. package/dist/src/formats/json/dialect.js +29 -4
  10. package/dist/src/formats/json/dialect.js.map +1 -1
  11. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  12. package/dist/src/formats/json/exporter.js +27 -7
  13. package/dist/src/formats/json/exporter.js.map +1 -1
  14. package/dist/src/formats/json/importer.d.ts +17 -4
  15. package/dist/src/formats/json/importer.d.ts.map +1 -1
  16. package/dist/src/formats/json/importer.js +356 -8
  17. package/dist/src/formats/json/importer.js.map +1 -1
  18. package/dist/src/formats/json/index.d.ts +1 -1
  19. package/dist/src/formats/json/index.d.ts.map +1 -1
  20. package/dist/src/formats/json/index.js +1 -1
  21. package/dist/src/formats/json/index.js.map +1 -1
  22. package/dist/src/index.d.ts +1 -1
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/sniff.d.ts +3 -3
  26. package/dist/src/sniff.d.ts.map +1 -1
  27. package/dist/src/sniff.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/common/escape.ts +5 -4
  30. package/src/formats/json/dialect.ts +40 -6
  31. package/src/formats/json/exporter.ts +28 -7
  32. package/src/formats/json/importer.ts +411 -21
  33. package/src/formats/json/index.ts +7 -1
  34. package/src/formats/neo4j/exporter.ts +43 -6
  35. package/src/formats/neo4j/importer.ts +56 -11
  36. package/src/formats/neo4j/index.ts +10 -3
  37. package/src/formats/pajek/exporter.ts +3 -1
  38. package/src/formats/pajek/importer.ts +284 -60
  39. package/src/formats/pajek/syntax.ts +98 -12
  40. package/src/index.ts +2 -0
  41. package/src/sniff.ts +3 -3
  42. package/dist/chunks/importer-BELrICWM.js.map +0 -1
package/README.md CHANGED
@@ -7,7 +7,8 @@
7
7
 
8
8
  Importers and exporters for the [@graphty/graph-format](https://www.npmjs.com/package/@graphty/graph-format)
9
9
  snapshot: GEXF, GraphML, GML, DOT (Graphviz), Pajek NET, CSV / TSV, JSON (NetworkX node-link, d3,
10
- JSON Graph Format, Cytoscape, graphology, vis.js) and Neo4j (`neo4j-admin import` CSV).
10
+ JSON Graph Format, Cytoscape, graphology, vis.js; NetworkX adjacency_data and tree_data are read
11
+ only) and Neo4j (`neo4j-admin import` CSV).
11
12
 
12
13
  Every importer streams its input into a `GraphSink` (a `GraphBuilder` or your own sink) one scalar at
13
14
  a time and reports what it could not represent instead of dropping it; every exporter says what it
@@ -195,7 +196,10 @@ losses and format rules, in addition to the table:
195
196
  a count the sink cannot reserve is fatal); `*Arcs` / `*Edges` sections give per-section
196
197
  direction; time intervals map to the spells role; vertex / line parameters are plain columns read
197
198
  through the 5.1 text grammar (`2.0` stays f64, lexical forms of a string column are kept); a
198
- `.paj` project file's `*Partition` / `*Vector` sections are skipped with an issue. Nodes are
199
+ `.paj` project file's `*Partition` / `*Vector` objects become the node columns `partition` (i32)
200
+ and `vector` (f64) (a second one `partition#2`, ...; their Pajek names in
201
+ `meta.extra.pajek.objects`), and other project sections (`*Events`, ...) are skipped with a
202
+ warning; a two-mode `*Vertices N N1` reads its `*Matrix` as N1 rows of N - N1 columns. Nodes are
199
203
  always written 1..N (`W_ID_RENUMBERED`: the id text is kept as the label of a node without a
200
204
  label value, a node with one loses its id; `W_PAJEK_LABEL_GAINED` when a line's parameters force
201
205
  a label); `sanitizeIds: "mangle"` also writes every renumbered vertex's original id as a
@@ -220,10 +224,19 @@ losses and format rules, in addition to the table:
220
224
  dialect has no slot for. Edge ids exist in JGF, Cytoscape, graphology and vis only; positions in
221
225
  Cytoscape only. A repeated node id is merged with `W_DUPLICATE_NODE`, a repeated edge id skipped
222
226
  with `E_DUPLICATE_EDGE_ID`; an out-of-range d3 index link is `E_BAD_INDEX`. Non-finite numbers
223
- are written as `null` and reported.
227
+ are written as `null` and reported. NetworkX `adjacency_data` (`nodes` plus an `adjacency` list
228
+ per node; an undirected file lists each edge from both ends and it is read once) and `tree_data`
229
+ (nested `id` / `children`, read as a directed tree) are read but not written: a re-export writes
230
+ node-link. The bare `NaN`, `Infinity` and `-Infinity` that Python's json module writes are read as
231
+ numbers (`W_JSON_NONSTANDARD_NUMBER`), and an integer literal beyond 2^53 keeps its exact digits as
232
+ a string (`W_JSON_BIG_INTEGER`), so two large ids never round to one; the exporter writes an
233
+ integral number that large in exponent form (`1e+20`) so it re-imports as a number.
224
234
  - **Neo4j**: `neo4j-admin import` headers (`:ID`, `:LABEL`, `:START_ID`, `:END_ID`, `:TYPE`, typed
225
235
  properties, id spaces, arrays); one file may hold several sections; a `weight` property becomes
226
- THE weight; a quoted empty `:ID` is the id `""`. Everything is directed (an undirected snapshot,
236
+ THE weight; a quoted empty `:ID` is the id `""`. A node of an id space (`:ID(Product)`) is stored
237
+ under the string id `Product:1`, with its id text in the `originalId` column and its space in
238
+ `idSpace`, so the same id in two spaces stays two nodes; `:START_ID(Space)` / `:END_ID(Space)`
239
+ resolve inside their space, and the exporter writes the id text back. Everything is directed (an undirected snapshot,
227
240
  or the folded pairs of a mixed one under `onMixedDirection: "directed"` / `"undirected"`, is
228
241
  written with a `W_NEO4J_UNDIRECTED_AS_DIRECTED` note); `.text` companions keep the source text of
229
242
  temporal values whose canonical form differs; a dict column reads back as string and a position
@@ -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, "nodes") && !hasKey(root, "edges") && !hasKey(root, "links")) {
39
- return null;
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
- if (f32) {
294
- return formatF32(value);
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
- return formatF64(value);
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" : JSON.stringify(ids.idOf(index));
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
- return Number.isFinite(weights.value(e)) ? weights.text(e) : "null";
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
- return typeof id === "number" && Object.is(id, -0) ? "0" : JSON.stringify(id);
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;
@@ -1378,6 +1405,10 @@ const JSON_ISSUE = Object.freeze({
1378
1405
  INVALID_ENCODING: INVALID_ENCODING_CODE,
1379
1406
  /** Bytes that are not UTF-8 and declare no encoding were read as windows-1252. */
1380
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",
1381
1412
  /** A declared encoding the platform cannot decode was ignored. */
1382
1413
  UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE
1383
1414
  });
@@ -1419,8 +1450,8 @@ const VIS_TARGET_KEYS = Object.freeze(["to"]);
1419
1450
  function resolveJsonOptions(options) {
1420
1451
  const o = options ?? {};
1421
1452
  const dialect = o.dialect ?? "auto";
1422
- if (dialect !== "auto" && !isJsonDialect(dialect)) {
1423
- throw unsupportedOption("dialect", dialect, [...JSON_DIALECTS, "auto"]);
1453
+ if (dialect !== "auto" && !isJsonImportDialect(dialect)) {
1454
+ throw unsupportedOption("dialect", dialect, [...JSON_IMPORT_DIALECTS, "auto"]);
1424
1455
  }
1425
1456
  const indexLinks = o.indexLinks ?? "auto";
1426
1457
  if (indexLinks !== "auto" && typeof indexLinks !== "boolean") {
@@ -1991,12 +2022,122 @@ function parseDocument(text, report) {
1991
2022
  if (text.trim().length === 0) {
1992
2023
  report.fail(JSON_ISSUE.EMPTY_INPUT, "the input is empty");
1993
2024
  }
2025
+ let root;
2026
+ let syntaxError = null;
1994
2027
  try {
1995
- return JSON.parse(text);
2028
+ root = JSON.parse(text);
1996
2029
  } catch (err) {
1997
- const message = err instanceof Error ? err.message : String(err);
1998
- return report.fail(JSON_ISSUE.SYNTAX, `invalid JSON: ${message}`);
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
+ );
1999
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;
2000
2141
  }
2001
2142
  function detectDialect(root, forced, report) {
2002
2143
  if (forced !== "auto") {
@@ -2017,7 +2158,7 @@ function detectDialect(root, forced, report) {
2017
2158
  }
2018
2159
  return report.fail(
2019
2160
  JSON_ISSUE.DIALECT,
2020
- "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)"
2021
2162
  );
2022
2163
  }
2023
2164
  function arraySection(value, what, report) {
@@ -2093,9 +2234,14 @@ function importNodeLink(ctx, root, dialect) {
2093
2234
  if (key !== "nodes" && key !== edgesKey && key !== "directed" && key !== "multigraph" && key !== "graph") {
2094
2235
  const value = root[key];
2095
2236
  const what = Array.isArray(value) ? `${String(value.length)} ${value.length === 1 ? "entry" : "entries"}` : describe(value);
2096
- report.warning("unsupported", JSON_ISSUE.UNREAD_KEY, `top-level key ${key} (${what}) is not read; dropped`, {
2097
- element: key
2098
- });
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
+ );
2099
2245
  }
2100
2246
  }
2101
2247
  const nodeList = nodes ?? [];
@@ -2345,6 +2491,187 @@ function importVis(ctx, root) {
2345
2491
  ctx.weightOriginPatch()
2346
2492
  );
2347
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
+ }
2348
2675
  function importGraphology(ctx, root) {
2349
2676
  const { report } = ctx;
2350
2677
  const nodes = arraySection(root.nodes, "nodes", report) ?? [];
@@ -3017,6 +3344,12 @@ function readGraph(root, dialect, sink, report, resolved, json, options) {
3017
3344
  case "vis":
3018
3345
  importVis(ctx, doc);
3019
3346
  break;
3347
+ case "adjacency":
3348
+ importAdjacency(ctx, doc);
3349
+ break;
3350
+ case "tree":
3351
+ importTree(ctx, doc);
3352
+ break;
3020
3353
  default: {
3021
3354
  const name = dialect;
3022
3355
  throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {
@@ -3037,4 +3370,4 @@ export {
3037
3370
  jsonCapabilities as j,
3038
3371
  sniffJsonDialect as s
3039
3372
  };
3040
- //# sourceMappingURL=importer-BELrICWM.js.map
3373
+ //# sourceMappingURL=importer-BOxwmef_.js.map