@graphty/graph-io 0.3.17 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +64 -3
  3. package/dist/chunks/{escape-scjHxjpr.js → escape-CWExcecC.js} +3 -2
  4. package/dist/chunks/{escape-scjHxjpr.js.map → escape-CWExcecC.js.map} +1 -1
  5. package/dist/chunks/exporter-CbMZyVt-.js +735 -0
  6. package/dist/chunks/exporter-CbMZyVt-.js.map +1 -0
  7. package/dist/chunks/importer-BAP4PrxR.js +2194 -0
  8. package/dist/chunks/importer-BAP4PrxR.js.map +1 -0
  9. package/dist/chunks/{importer-Xg07gk7p.js → importer-BNFuV1-K.js} +5 -3
  10. package/dist/chunks/{importer-Xg07gk7p.js.map → importer-BNFuV1-K.js.map} +1 -1
  11. package/dist/chunks/{importer-BmOl9gLW.js → importer-BVGtU1NA.js} +5 -3
  12. package/dist/chunks/{importer-BmOl9gLW.js.map → importer-BVGtU1NA.js.map} +1 -1
  13. package/dist/chunks/{importer-ruMWWvVs.js → importer-BW-Ft2ps.js} +5 -3
  14. package/dist/chunks/{importer-ruMWWvVs.js.map → importer-BW-Ft2ps.js.map} +1 -1
  15. package/dist/chunks/{importer-B6rKRxzv.js → importer-Bm1_5vPS.js} +781 -104
  16. package/dist/chunks/importer-Bm1_5vPS.js.map +1 -0
  17. package/dist/chunks/importer-ByPGO-09.js +1614 -0
  18. package/dist/chunks/importer-ByPGO-09.js.map +1 -0
  19. package/dist/chunks/importer-DOepkgnG.js +1713 -0
  20. package/dist/chunks/importer-DOepkgnG.js.map +1 -0
  21. package/dist/chunks/json-elements-CZY1wiZh.js +779 -0
  22. package/dist/chunks/json-elements-CZY1wiZh.js.map +1 -0
  23. package/dist/chunks/ontology-BnrJ4I98.js +113 -0
  24. package/dist/chunks/ontology-BnrJ4I98.js.map +1 -0
  25. package/dist/chunks/{records-DSpbTE5s.js → records-BzNicMsf.js} +2 -2
  26. package/dist/chunks/{records-DSpbTE5s.js.map → records-BzNicMsf.js.map} +1 -1
  27. package/dist/chunks/{writer-DHHfHn11.js → report-BcWboivV.js} +106 -928
  28. package/dist/chunks/report-BcWboivV.js.map +1 -0
  29. package/dist/chunks/weights-CwISIpCP.js +176 -0
  30. package/dist/chunks/weights-CwISIpCP.js.map +1 -0
  31. package/dist/chunks/writer-DQiKgQJc.js +656 -0
  32. package/dist/chunks/writer-DQiKgQJc.js.map +1 -0
  33. package/dist/csv.js +5 -3
  34. package/dist/csv.js.map +1 -1
  35. package/dist/cx.d.ts +1 -0
  36. package/dist/cx.js +6 -0
  37. package/dist/cx.js.map +1 -0
  38. package/dist/cx2.d.ts +1 -0
  39. package/dist/cx2.js +10 -0
  40. package/dist/cx2.js.map +1 -0
  41. package/dist/dot.js +1 -1
  42. package/dist/gexf.js +15 -3
  43. package/dist/gexf.js.map +1 -1
  44. package/dist/gml.js +5 -3
  45. package/dist/gml.js.map +1 -1
  46. package/dist/graph-io.js +158 -383
  47. package/dist/graph-io.js.map +1 -1
  48. package/dist/graphml.js +1 -1
  49. package/dist/json.js +1 -1
  50. package/dist/neo4j.js +5 -3
  51. package/dist/neo4j.js.map +1 -1
  52. package/dist/obo.d.ts +1 -0
  53. package/dist/obo.js +6 -0
  54. package/dist/obo.js.map +1 -0
  55. package/dist/pajek.js +1 -1
  56. package/dist/src/common/json-elements.d.ts +286 -0
  57. package/dist/src/common/json-elements.d.ts.map +1 -0
  58. package/dist/src/common/json-elements.js +926 -0
  59. package/dist/src/common/json-elements.js.map +1 -0
  60. package/dist/src/common/ontology.d.ts +59 -0
  61. package/dist/src/common/ontology.d.ts.map +1 -0
  62. package/dist/src/common/ontology.js +147 -0
  63. package/dist/src/common/ontology.js.map +1 -0
  64. package/dist/src/formats/cx/importer.d.ts +133 -0
  65. package/dist/src/formats/cx/importer.d.ts.map +1 -0
  66. package/dist/src/formats/cx/importer.js +2220 -0
  67. package/dist/src/formats/cx/importer.js.map +1 -0
  68. package/dist/src/formats/cx/index.d.ts +7 -0
  69. package/dist/src/formats/cx/index.d.ts.map +1 -0
  70. package/dist/src/formats/cx/index.js +7 -0
  71. package/dist/src/formats/cx/index.js.map +1 -0
  72. package/dist/src/formats/cx2/exporter.d.ts +59 -0
  73. package/dist/src/formats/cx2/exporter.d.ts.map +1 -0
  74. package/dist/src/formats/cx2/exporter.js +864 -0
  75. package/dist/src/formats/cx2/exporter.js.map +1 -0
  76. package/dist/src/formats/cx2/importer.d.ts +169 -0
  77. package/dist/src/formats/cx2/importer.d.ts.map +1 -0
  78. package/dist/src/formats/cx2/importer.js +1652 -0
  79. package/dist/src/formats/cx2/importer.js.map +1 -0
  80. package/dist/src/formats/cx2/index.d.ts +7 -0
  81. package/dist/src/formats/cx2/index.d.ts.map +1 -0
  82. package/dist/src/formats/cx2/index.js +7 -0
  83. package/dist/src/formats/cx2/index.js.map +1 -0
  84. package/dist/src/formats/gexf/exporter.d.ts +2 -0
  85. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  86. package/dist/src/formats/gexf/exporter.js +6 -0
  87. package/dist/src/formats/gexf/exporter.js.map +1 -1
  88. package/dist/src/formats/gexf/importer.d.ts +2 -4
  89. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  90. package/dist/src/formats/gexf/importer.js +2 -4
  91. package/dist/src/formats/gexf/importer.js.map +1 -1
  92. package/dist/src/formats/gexf/index.d.ts +1 -1
  93. package/dist/src/formats/gexf/index.js +2 -2
  94. package/dist/src/formats/gexf/index.js.map +1 -1
  95. package/dist/src/formats/gml/exporter.js +1 -1
  96. package/dist/src/formats/gml/exporter.js.map +1 -1
  97. package/dist/src/formats/json/dialect.d.ts +8 -6
  98. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  99. package/dist/src/formats/json/dialect.js +26 -3
  100. package/dist/src/formats/json/dialect.js.map +1 -1
  101. package/dist/src/formats/json/importer.d.ts +324 -7
  102. package/dist/src/formats/json/importer.d.ts.map +1 -1
  103. package/dist/src/formats/json/importer.js +159 -126
  104. package/dist/src/formats/json/importer.js.map +1 -1
  105. package/dist/src/formats/json/obographs.d.ts +21 -0
  106. package/dist/src/formats/json/obographs.d.ts.map +1 -0
  107. package/dist/src/formats/json/obographs.js +476 -0
  108. package/dist/src/formats/json/obographs.js.map +1 -0
  109. package/dist/src/formats/obo/importer.d.ts +90 -0
  110. package/dist/src/formats/obo/importer.d.ts.map +1 -0
  111. package/dist/src/formats/obo/importer.js +1248 -0
  112. package/dist/src/formats/obo/importer.js.map +1 -0
  113. package/dist/src/formats/obo/index.d.ts +7 -0
  114. package/dist/src/formats/obo/index.d.ts.map +1 -0
  115. package/dist/src/formats/obo/index.js +7 -0
  116. package/dist/src/formats/obo/index.js.map +1 -0
  117. package/dist/src/formats/obo/syntax.d.ts +121 -0
  118. package/dist/src/formats/obo/syntax.d.ts.map +1 -0
  119. package/dist/src/formats/obo/syntax.js +424 -0
  120. package/dist/src/formats/obo/syntax.js.map +1 -0
  121. package/dist/src/index.d.ts +3 -0
  122. package/dist/src/index.d.ts.map +1 -1
  123. package/dist/src/index.js +3 -0
  124. package/dist/src/index.js.map +1 -1
  125. package/dist/src/registry.d.ts.map +1 -1
  126. package/dist/src/registry.js +8 -1
  127. package/dist/src/registry.js.map +1 -1
  128. package/dist/src/sniff.d.ts +1 -1
  129. package/dist/src/sniff.d.ts.map +1 -1
  130. package/dist/src/sniff.js +25 -3
  131. package/dist/src/sniff.js.map +1 -1
  132. package/package.json +16 -1
  133. package/src/common/json-elements.ts +1147 -0
  134. package/src/common/ontology.ts +169 -0
  135. package/src/formats/cx/importer.ts +2733 -0
  136. package/src/formats/cx/index.ts +7 -0
  137. package/src/formats/cx2/exporter.ts +1036 -0
  138. package/src/formats/cx2/importer.ts +2187 -0
  139. package/src/formats/cx2/index.ts +7 -0
  140. package/src/formats/gexf/exporter.ts +11 -0
  141. package/src/formats/gexf/importer.ts +2 -2
  142. package/src/formats/gexf/index.ts +1 -1
  143. package/src/formats/gml/exporter.ts +1 -1
  144. package/src/formats/json/dialect.ts +37 -7
  145. package/src/formats/json/importer.ts +210 -132
  146. package/src/formats/json/obographs.ts +563 -0
  147. package/src/formats/obo/importer.ts +1695 -0
  148. package/src/formats/obo/index.ts +7 -0
  149. package/src/formats/obo/syntax.ts +466 -0
  150. package/src/index.ts +11 -0
  151. package/src/registry.ts +8 -1
  152. package/src/sniff.ts +48 -5
  153. package/dist/chunks/importer-B6rKRxzv.js.map +0 -1
  154. package/dist/chunks/writer-DHHfHn11.js.map +0 -1
@@ -1,5 +1,9 @@
1
1
  import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
2
- import { ar as capabilities, M as LOSS, aO as joinText, aB as encodeChunks, as as checkCapabilities, aV as resolveExportOptions, a_ as countMixedEdges, aC as explicitWeights, aQ as pairFolding, aE as formatF32, aF as formatF64, aj as UNKNOWN_ENCODING_CODE, u as ENCODING_FALLBACK_CODE, F as INVALID_ENCODING_CODE, J as INVALID_UTF8_CODE, Y as OPTION_IGNORED_CODE, a6 as SINK_OPTION_CODE, H as HYPEREDGE_CODE, R as MULTIPLE_GRAPHS_CODE, w as ID_MERGED_CODE, o as DUPLICATE_EDGE_ID_CODE, q as DUPLICATE_NODE_CODE, ak as UNKNOWN_PARENT_CODE, e as BAD_VALUE_CODE, O as MISSING_ENDPOINT_CODE, P as MISSING_ID_CODE, ac as SYNTAX_CODE, s as EMPTY_INPUT_CODE, aW as resolveImportOptions, I as ImportReportBuilder, aS as readText, aT as reportSinkOptions, aU as reportUnusedOptions, a as throwIfAborted, K as IdCoercer, r as DirectionResolver, a$ as weightFromValue, b0 as uniqueColumnName } from "./writer-DHHfHn11.js";
2
+ import { aB as resolveExportOptions, ax as pairFolding, ar as declareResolved, af as UNKNOWN_ENCODING_CODE, r as ENCODING_FALLBACK_CODE, y as INVALID_ENCODING_CODE, z as INVALID_UTF8_CODE, U as OPTION_IGNORED_CODE, a2 as SINK_OPTION_CODE, H as HYPEREDGE_CODE, ac as TOO_LARGE_CODE, D as DANGLING_REFERENCE_CODE, A as AMBIGUOUS_GRAPH_NAME_CODE, G as GRAPH_NOT_FOUND_CODE, O as MULTIPLE_GRAPHS_CODE, u as ID_MERGED_CODE, m as DUPLICATE_EDGE_ID_CODE, o as DUPLICATE_NODE_CODE, ag as UNKNOWN_PARENT_CODE, d as BAD_VALUE_CODE, M as MISSING_ENDPOINT_CODE, K as MISSING_ID_CODE, a8 as SYNTAX_CODE, q as EMPTY_INPUT_CODE, aC as resolveImportOptions, I as ImportReportBuilder, ay as readText, aD as textChunks, az as reportSinkOptions, aA as reportUnusedOptions, t as throwIfAborted, F as IdCoercer, p as DirectionResolver, aE as uniqueColumnName, an as chooseGraph } from "./report-BcWboivV.js";
3
+ import { c as capabilities, L as LOSS, j as joinText, e as encodeChunks, a as checkCapabilities, g as countMixedEdges } from "./writer-DQiKgQJc.js";
4
+ import { e as explicitWeights, a as formatF32, b as formatF64, w as weightFromValue } from "./weights-CwISIpCP.js";
5
+ import { M as MAYBE_UNSAFE_INTEGER, e as rewriteNumbers, g as reviveNonstandard } from "./json-elements-CZY1wiZh.js";
6
+ import { a as OBOGRAPHS_PREDICATE_TAGS, c as compactOboIri, o as oboColumnDecl, P as PLACEHOLDER_COLUMN, s as synonymScopeOf, O as OBO_NODE_COLUMNS } from "./ontology-BnrJ4I98.js";
3
7
  const JSON_DIALECTS = Object.freeze([
4
8
  "node-link",
5
9
  "d3",
@@ -11,7 +15,8 @@ const JSON_DIALECTS = Object.freeze([
11
15
  const JSON_IMPORT_DIALECTS = Object.freeze([
12
16
  ...JSON_DIALECTS,
13
17
  "adjacency",
14
- "tree"
18
+ "tree",
19
+ "obographs"
15
20
  ]);
16
21
  const META_KEY = "json";
17
22
  function isJsonObject(value) {
@@ -41,7 +46,7 @@ function sniffJsonDialect(root) {
41
46
  return "jgf";
42
47
  }
43
48
  if (Array.isArray(root.graphs)) {
44
- return "jgf";
49
+ return isOboGraph(firstJsonObject(root.graphs)) ? "obographs" : "jgf";
45
50
  }
46
51
  if (!hasKey(root, "edges") && !hasKey(root, "links")) {
47
52
  if (hasKey(root, "nodes") && hasKey(root, "adjacency")) {
@@ -70,6 +75,15 @@ function sniffJsonDialect(root) {
70
75
  const bare = !hasKey(root, "directed") && !hasKey(root, "multigraph") && !hasKey(root, "graph");
71
76
  return bare && hasKey(root, "links") ? "d3" : "node-link";
72
77
  }
78
+ const OBOGRAPHS_NODE_TYPES = /* @__PURE__ */ new Set(["CLASS", "INDIVIDUAL", "PROPERTY"]);
79
+ function isOboGraph(graph) {
80
+ if (graph === null) {
81
+ return false;
82
+ }
83
+ const isEdge = (e) => isJsonObject(e) && (hasKey(e, "obj") && (hasKey(e, "sub") || hasKey(e, "subj")) || hasKey(e, "pred"));
84
+ const isNode = (n) => isJsonObject(n) && (hasKey(n, "lbl") || hasKey(n, "meta") || OBOGRAPHS_NODE_TYPES.has(n.type));
85
+ return Array.isArray(graph.edges) && graph.edges.some(isEdge) || Array.isArray(graph.nodes) && graph.nodes.some(isNode);
86
+ }
73
87
  function firstJsonObject(value) {
74
88
  if (!Array.isArray(value)) {
75
89
  return null;
@@ -107,7 +121,9 @@ const DIALECT_DEFAULT_DIRECTED = Object.freeze({
107
121
  vis: false,
108
122
  // networkx adjacency_data declares `directed`; tree_graph always builds a DiGraph
109
123
  adjacency: false,
110
- tree: true
124
+ tree: true,
125
+ // OBO Graphs edges point from the subject (child) to the object (parent)
126
+ obographs: true
111
127
  });
112
128
  const JSON_DTYPES = Object.freeze(["f64", "i32", "bool", "string"]);
113
129
  const COMMON = {
@@ -1356,6 +1372,643 @@ const jsonExporter = Object.freeze({
1356
1372
  function jsonCapabilities(dialect) {
1357
1373
  return dialectCapabilities(dialect);
1358
1374
  }
1375
+ const GRAPH_FORMATS = Object.freeze([
1376
+ "json",
1377
+ "graphml",
1378
+ "gexf",
1379
+ "csv",
1380
+ "gml",
1381
+ "dot",
1382
+ "pajek",
1383
+ "neo4j",
1384
+ "cx2",
1385
+ "cx",
1386
+ "obo"
1387
+ ]);
1388
+ const SNIFF_HEAD_BYTES = 8192;
1389
+ function extensionOf(filename) {
1390
+ const base = filename.slice(Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\")) + 1);
1391
+ const dot = base.lastIndexOf(".");
1392
+ if (dot <= 0 || dot === base.length - 1) {
1393
+ return null;
1394
+ }
1395
+ return base.slice(dot).toLowerCase();
1396
+ }
1397
+ function normalizeMimeType(mimeType) {
1398
+ const semicolon = mimeType.indexOf(";");
1399
+ return (semicolon < 0 ? mimeType : mimeType.slice(0, semicolon)).trim().toLowerCase();
1400
+ }
1401
+ function headBytes(head) {
1402
+ if (typeof head === "string") {
1403
+ return new TextEncoder().encode(head.slice(0, SNIFF_HEAD_BYTES)).subarray(0, SNIFF_HEAD_BYTES);
1404
+ }
1405
+ if (head.byteLength >= 2 && (head[0] === 255 && head[1] === 254 || head[0] === 254 && head[1] === 255)) {
1406
+ const encoding = head[0] === 255 ? "utf-16le" : "utf-16be";
1407
+ return headBytes(new TextDecoder(encoding).decode(head.subarray(0, SNIFF_HEAD_BYTES)));
1408
+ }
1409
+ return head.byteLength > SNIFF_HEAD_BYTES ? head.subarray(0, SNIFF_HEAD_BYTES) : head;
1410
+ }
1411
+ function rankFormats(hints, importers) {
1412
+ const extension = typeof hints.filename === "string" ? extensionOf(hints.filename) : null;
1413
+ const mime = typeof hints.mimeType === "string" ? normalizeMimeType(hints.mimeType) : null;
1414
+ const head = hints.head === void 0 || hints.head === null ? null : headBytes(hints.head);
1415
+ const candidates = [];
1416
+ let rank = 0;
1417
+ for (const importer of importers) {
1418
+ const extensionMatch = extension !== null && importer.extensions.some((e) => e.toLowerCase() === extension);
1419
+ const mimeMatch = mime !== null && importer.mimeTypes.some((m) => m.toLowerCase() === mime);
1420
+ let content = 0;
1421
+ if (head !== null && head.byteLength > 0 && typeof importer.sniff === "function") {
1422
+ content = clamp(importer.sniff(head));
1423
+ }
1424
+ let confidence;
1425
+ if (content > 0) {
1426
+ confidence = 0.5 + 0.35 * content + (extensionMatch ? 0.1 : 0) + (mimeMatch ? 0.05 : 0);
1427
+ } else if (extensionMatch || mimeMatch) {
1428
+ confidence = (extensionMatch ? 0.3 : 0) + (mimeMatch ? 0.1 : 0);
1429
+ } else {
1430
+ continue;
1431
+ }
1432
+ const dialect = importer.format === "json" && head !== null ? sniffJsonDialectHead(head) : null;
1433
+ const result = Object.freeze({
1434
+ format: importer.format,
1435
+ confidence: Math.min(1, confidence),
1436
+ content,
1437
+ extension: extensionMatch,
1438
+ mimeType: mimeMatch,
1439
+ dialect
1440
+ });
1441
+ candidates.push({ result, rank: rank++ });
1442
+ }
1443
+ candidates.sort((a, b) => b.result.confidence - a.result.confidence || a.rank - b.rank);
1444
+ return candidates.map((c) => c.result);
1445
+ }
1446
+ function sniffFormat(hints, importers) {
1447
+ const ranked = rankFormats(hints, importers);
1448
+ return ranked.length > 0 ? ranked[0] : null;
1449
+ }
1450
+ function sniffJsonDialectHead(head) {
1451
+ const text = typeof head === "string" ? head : new TextDecoder("utf-8", { fatal: false }).decode(head);
1452
+ const body = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
1453
+ const trimmed = body.trimStart();
1454
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
1455
+ return null;
1456
+ }
1457
+ let root;
1458
+ try {
1459
+ root = JSON.parse(trimmed);
1460
+ } catch {
1461
+ root = skeletonOf(trimmed);
1462
+ }
1463
+ return sniffJsonDialect(root);
1464
+ }
1465
+ function skeletonOf(text) {
1466
+ const keys = scanKeys(text);
1467
+ if (text.startsWith("[")) {
1468
+ const first = keys.get("nodes[0]");
1469
+ return first === void 0 ? null : [objectOf(first)];
1470
+ }
1471
+ const rootKeys = keys.get("");
1472
+ if (rootKeys === void 0) {
1473
+ return null;
1474
+ }
1475
+ const root = {};
1476
+ for (const key of rootKeys) {
1477
+ switch (key) {
1478
+ case "graph":
1479
+ case "options":
1480
+ root[key] = objectOf(keys.get(key));
1481
+ break;
1482
+ case "graphs": {
1483
+ const nodes = keys.get("graphs[0].nodes[]");
1484
+ const edges = keys.get("graphs[0].edges[]");
1485
+ const graph = {};
1486
+ if (nodes !== void 0) {
1487
+ graph.nodes = [objectOf(nodes)];
1488
+ }
1489
+ if (edges !== void 0) {
1490
+ graph.edges = [objectOf(edges)];
1491
+ }
1492
+ root[key] = nodes === void 0 && edges === void 0 ? [] : [graph];
1493
+ break;
1494
+ }
1495
+ case "nodes":
1496
+ case "edges":
1497
+ case "links": {
1498
+ const first = keys.get(`${key}[0]`);
1499
+ root[key] = first === void 0 ? [] : [objectOf(first)];
1500
+ break;
1501
+ }
1502
+ default:
1503
+ root[key] = true;
1504
+ break;
1505
+ }
1506
+ }
1507
+ return root;
1508
+ }
1509
+ function objectOf(names) {
1510
+ const out = {};
1511
+ if (names !== void 0) {
1512
+ for (const name of names) {
1513
+ out[name] = name === "attributes" || name === "data" ? {} : true;
1514
+ }
1515
+ }
1516
+ return out;
1517
+ }
1518
+ function scanKeys(text) {
1519
+ const found = /* @__PURE__ */ new Map();
1520
+ const kinds = [];
1521
+ const labels = [];
1522
+ const counts = [];
1523
+ let pendingKey = null;
1524
+ let expectingKey = false;
1525
+ const pathOf = () => {
1526
+ const depth = kinds.length;
1527
+ if (depth === 1) {
1528
+ return kinds[0] === "object" ? "" : null;
1529
+ }
1530
+ if (depth === 2 && kinds[0] === "array" && kinds[1] === "object" && labels[1] === "0") {
1531
+ return "nodes[0]";
1532
+ }
1533
+ if (depth === 2 && kinds[0] === "object" && kinds[1] === "object") {
1534
+ return labels[1] === "graph" || labels[1] === "options" ? labels[1] : null;
1535
+ }
1536
+ if (depth === 5 && kinds.join() === "object,array,object,array,object" && labels[1] === "graphs" && labels[2] === "0" && (labels[3] === "nodes" || labels[3] === "edges")) {
1537
+ return labels[3] === "nodes" ? "graphs[0].nodes[]" : "graphs[0].edges[]";
1538
+ }
1539
+ if (depth === 3 && kinds[0] === "object" && kinds[1] === "array" && kinds[2] === "object") {
1540
+ const section = labels[1];
1541
+ if ((section === "nodes" || section === "edges" || section === "links") && labels[2] === "0") {
1542
+ return `${section}[0]`;
1543
+ }
1544
+ }
1545
+ return null;
1546
+ };
1547
+ let i = 0;
1548
+ const n = text.length;
1549
+ while (i < n) {
1550
+ const ch = text[i];
1551
+ if (ch === '"') {
1552
+ const end = scanString(text, i + 1);
1553
+ if (end < 0) {
1554
+ break;
1555
+ }
1556
+ const literal = text.slice(i + 1, end);
1557
+ i = end + 1;
1558
+ if (expectingKey) {
1559
+ pendingKey = decodeKey(literal);
1560
+ expectingKey = false;
1561
+ }
1562
+ continue;
1563
+ }
1564
+ if (ch === "{" || ch === "[") {
1565
+ let label = "";
1566
+ if (kinds.length > 0) {
1567
+ label = kinds[kinds.length - 1] === "object" ? pendingKey ?? "" : String(counts[counts.length - 1]);
1568
+ }
1569
+ kinds.push(ch === "{" ? "object" : "array");
1570
+ labels.push(label);
1571
+ counts.push(0);
1572
+ expectingKey = ch === "{";
1573
+ pendingKey = null;
1574
+ } else if (ch === "}" || ch === "]") {
1575
+ kinds.pop();
1576
+ labels.pop();
1577
+ counts.pop();
1578
+ expectingKey = false;
1579
+ pendingKey = null;
1580
+ } else if (ch === ":") {
1581
+ if (pendingKey !== null) {
1582
+ const path = pathOf();
1583
+ if (path !== null) {
1584
+ let set = found.get(path);
1585
+ if (set === void 0) {
1586
+ set = /* @__PURE__ */ new Set();
1587
+ found.set(path, set);
1588
+ }
1589
+ set.add(pendingKey);
1590
+ }
1591
+ }
1592
+ } else if (ch === ",") {
1593
+ const top = kinds.length - 1;
1594
+ if (top >= 0) {
1595
+ counts[top]++;
1596
+ expectingKey = kinds[top] === "object";
1597
+ }
1598
+ pendingKey = null;
1599
+ }
1600
+ i++;
1601
+ }
1602
+ return found;
1603
+ }
1604
+ function scanString(text, from) {
1605
+ let i = from;
1606
+ while (i < text.length) {
1607
+ const ch = text[i];
1608
+ if (ch === "\\") {
1609
+ i += 2;
1610
+ continue;
1611
+ }
1612
+ if (ch === '"') {
1613
+ return i;
1614
+ }
1615
+ i++;
1616
+ }
1617
+ return -1;
1618
+ }
1619
+ function decodeKey(literal) {
1620
+ if (!literal.includes("\\")) {
1621
+ return literal;
1622
+ }
1623
+ try {
1624
+ return JSON.parse(`"${literal}"`);
1625
+ } catch {
1626
+ return literal;
1627
+ }
1628
+ }
1629
+ function clamp(value) {
1630
+ if (!(value > 0)) {
1631
+ return 0;
1632
+ }
1633
+ return value > 1 ? 1 : value;
1634
+ }
1635
+ const NODE_KEYS = /* @__PURE__ */ new Set(["id", "lbl", "type", "propertyType", "meta"]);
1636
+ const META_KEYS = /* @__PURE__ */ new Set([
1637
+ "definition",
1638
+ "comments",
1639
+ "subsets",
1640
+ "xrefs",
1641
+ "synonyms",
1642
+ "basicPropertyValues",
1643
+ "deprecated"
1644
+ ]);
1645
+ const FRAME_TYPES = Object.freeze({
1646
+ CLASS: "Term",
1647
+ INDIVIDUAL: "Instance",
1648
+ PROPERTY: "Typedef"
1649
+ });
1650
+ const BUILTIN_PREDICATES = Object.freeze({
1651
+ is_a: "is_a",
1652
+ subPropertyOf: "is_a",
1653
+ type: "instance_of",
1654
+ inverseOf: "inverse_of"
1655
+ });
1656
+ const PROPERTY_PREDICATES = /* @__PURE__ */ new Set(["subPropertyOf", "inverseOf"]);
1657
+ class ColumnWriter {
1658
+ /**
1659
+ * Create a writer.
1660
+ * @param ctx - the import context
1661
+ */
1662
+ constructor(ctx) {
1663
+ this.handles = /* @__PURE__ */ new Map();
1664
+ this.ctx = ctx;
1665
+ }
1666
+ /**
1667
+ * Write a node cell of the vocabulary.
1668
+ * @param name - the column
1669
+ * @param row - the node index
1670
+ * @param value - the value
1671
+ */
1672
+ node(name, row, value) {
1673
+ this.ctx.sink.setNodeValue(this.handle("node", name), row, value);
1674
+ }
1675
+ /**
1676
+ * Write an edge cell of the vocabulary.
1677
+ * @param name - the column
1678
+ * @param row - the edge index
1679
+ * @param value - the value
1680
+ */
1681
+ edge(name, row, value) {
1682
+ this.ctx.sink.setEdgeValue(this.handle("edge", name), row, value);
1683
+ }
1684
+ /**
1685
+ * The handle of a column, declared on first use.
1686
+ * @param domain - node or edge
1687
+ * @param name - the column
1688
+ * @returns the handle
1689
+ */
1690
+ handle(domain, name) {
1691
+ const key = `${domain}:${name}`;
1692
+ const cached = this.handles.get(key);
1693
+ if (cached !== void 0) {
1694
+ return cached;
1695
+ }
1696
+ const { handle } = declareResolved(this.ctx.sink, domain, oboColumnDecl(domain, name), this.ctx.report);
1697
+ this.handles.set(key, handle);
1698
+ return handle;
1699
+ }
1700
+ }
1701
+ function vocabularyOf(nodes, oboIds) {
1702
+ const shorthands = /* @__PURE__ */ new Map();
1703
+ const propertyIds = /* @__PURE__ */ new Set();
1704
+ for (const node of nodes) {
1705
+ if (!isJsonObject(node) || typeof node.id !== "string" || node.type !== "PROPERTY") {
1706
+ continue;
1707
+ }
1708
+ propertyIds.add(node.id);
1709
+ const shorthand = basicValues(node.meta).find((pv) => OBOGRAPHS_PREDICATE_TAGS.get(pv.pred) === "shorthand");
1710
+ if (shorthand !== void 0) {
1711
+ shorthands.set(node.id, shorthand.val);
1712
+ }
1713
+ }
1714
+ const compact = (iri) => oboIds === "curie" ? compactOboIri(iri) : iri;
1715
+ return {
1716
+ id: (iri) => oboIds === "curie" ? shorthands.get(iri) ?? compactOboIri(iri) : iri,
1717
+ relation: (pred) => BUILTIN_PREDICATES[pred] ?? shorthands.get(pred) ?? compact(pred),
1718
+ isProperty: (iri) => propertyIds.has(iri),
1719
+ properties: {}
1720
+ };
1721
+ }
1722
+ function basicValues(meta) {
1723
+ if (!isJsonObject(meta) || !Array.isArray(meta.basicPropertyValues)) {
1724
+ return [];
1725
+ }
1726
+ return meta.basicPropertyValues.filter(
1727
+ (pv) => isJsonObject(pv) && typeof pv.pred === "string" && typeof pv.val === "string"
1728
+ );
1729
+ }
1730
+ function strings(value) {
1731
+ if (!Array.isArray(value)) {
1732
+ return [];
1733
+ }
1734
+ return value.flatMap((item) => {
1735
+ if (typeof item === "string") {
1736
+ return [item];
1737
+ }
1738
+ return isJsonObject(item) && typeof item.val === "string" ? [item.val] : [];
1739
+ });
1740
+ }
1741
+ function importObographs(ctx, root) {
1742
+ const graph = chosenGraph(ctx, root, "OBO Graphs");
1743
+ const nodes = sectionOf(ctx, graph, "nodes");
1744
+ const edges = sectionOf(ctx, graph, "edges");
1745
+ const vocabulary = vocabularyOf(nodes, ctx.json.oboIds);
1746
+ const columns = new ColumnWriter(ctx);
1747
+ ctx.reportNodeIdFrom("obographs", "the node ids");
1748
+ ctx.setHeader(true);
1749
+ ctx.sink.reserve(nodes.length, edges.length);
1750
+ for (let i = 0; i < nodes.length; i++) {
1751
+ readNode(ctx, columns, vocabulary, nodes[i], `nodes[${i}]`);
1752
+ }
1753
+ const propertyEdges = readEdges(ctx, columns, vocabulary, edges);
1754
+ const graphMeta = {};
1755
+ for (const key of Object.keys(graph)) {
1756
+ if (key !== "nodes" && key !== "edges") {
1757
+ graphMeta[key] = graph[key];
1758
+ }
1759
+ }
1760
+ const document = {};
1761
+ for (const key of Object.keys(root)) {
1762
+ if (key !== "graphs") {
1763
+ document[key] = root[key];
1764
+ }
1765
+ }
1766
+ const label = typeof graph.lbl === "string" ? graph.lbl : null;
1767
+ const name = label ?? (typeof graph.id === "string" ? graph.id : null);
1768
+ ctx.sink.setMeta({
1769
+ sourceFormat: "json",
1770
+ name,
1771
+ extra: {
1772
+ json: { dialect: "obographs" },
1773
+ obographs: {
1774
+ ids: ctx.json.oboIds,
1775
+ graph: graphMeta,
1776
+ document,
1777
+ properties: vocabulary.properties,
1778
+ propertyEdges
1779
+ }
1780
+ }
1781
+ });
1782
+ }
1783
+ function sectionOf(ctx, graph, key) {
1784
+ const value = graph[key];
1785
+ if (value === void 0 || value === null) {
1786
+ return [];
1787
+ }
1788
+ if (!Array.isArray(value)) {
1789
+ return ctx.report.fail(JSON_ISSUE.SHAPE, `an OBO Graphs graph's ${key} must be an array`, { element: key });
1790
+ }
1791
+ return value;
1792
+ }
1793
+ function readNode(ctx, columns, vocabulary, record, element) {
1794
+ if (!isJsonObject(record)) {
1795
+ ctx.badElement("node", element);
1796
+ return;
1797
+ }
1798
+ if (typeof record.id !== "string") {
1799
+ if (record.id === void 0) {
1800
+ ctx.coerceId(void 0, element);
1801
+ } else {
1802
+ badValue(ctx, `${element}.id must be a string; the node is skipped`);
1803
+ }
1804
+ ctx.countSkipped("node");
1805
+ return;
1806
+ }
1807
+ if (record.type === "PROPERTY" && ctx.json.typedefs === "metadata") {
1808
+ const shorthand = vocabulary.id(record.id) === record.id ? void 0 : vocabulary.id(record.id);
1809
+ const entry = {};
1810
+ for (const [key, value] of Object.entries({
1811
+ lbl: record.lbl,
1812
+ propertyType: record.propertyType,
1813
+ shorthand,
1814
+ meta: record.meta
1815
+ })) {
1816
+ if (value !== void 0) {
1817
+ entry[key] = value;
1818
+ }
1819
+ }
1820
+ vocabulary.properties[record.id] = entry;
1821
+ return;
1822
+ }
1823
+ const id = ctx.coerceId(vocabulary.id(record.id), element);
1824
+ const row = id === null ? -1 : ctx.pushNode(id, element);
1825
+ if (row < 0) {
1826
+ return;
1827
+ }
1828
+ try {
1829
+ writeNode(ctx, columns, vocabulary, record, row, element);
1830
+ } catch (err) {
1831
+ ctx.report.recordError(err, { element: record.id });
1832
+ }
1833
+ }
1834
+ function writeNode(ctx, columns, vocabulary, record, row, element) {
1835
+ const unrecognized = {};
1836
+ const text = (key, column) => {
1837
+ const value = record[key];
1838
+ if (typeof value === "string") {
1839
+ columns.node(column, row, column === "type" ? FRAME_TYPES[value] ?? value : value);
1840
+ } else if (value !== void 0 && value !== null) {
1841
+ badValue(ctx, `${element}.${key} must be a string`);
1842
+ }
1843
+ };
1844
+ text("type", "type");
1845
+ text("lbl", "name");
1846
+ text("propertyType", "propertyType");
1847
+ for (const key of Object.keys(record)) {
1848
+ if (!NODE_KEYS.has(key)) {
1849
+ unrecognized[key] = record[key];
1850
+ }
1851
+ }
1852
+ const { meta } = record;
1853
+ if (isJsonObject(meta)) {
1854
+ writeMeta(columns, vocabulary, meta, row, unrecognized);
1855
+ } else if (meta !== void 0 && meta !== null) {
1856
+ badValue(ctx, `${element}.meta must be an object`);
1857
+ }
1858
+ if (Object.keys(unrecognized).length > 0) {
1859
+ columns.node("obo.unrecognized", row, unrecognized);
1860
+ }
1861
+ }
1862
+ function writeMeta(columns, vocabulary, meta, row, unrecognized) {
1863
+ const { definition } = meta;
1864
+ if (isJsonObject(definition) && typeof definition.val === "string") {
1865
+ columns.node("def", row, definition.val);
1866
+ columns.node("def.xrefs", row, strings(definition.xrefs));
1867
+ }
1868
+ const comments = strings(meta.comments);
1869
+ if (comments.length > 0) {
1870
+ columns.node("comment", row, comments.join("\n"));
1871
+ }
1872
+ const subsets = strings(meta.subsets).map((s) => vocabulary.id(s));
1873
+ if (subsets.length > 0) {
1874
+ columns.node("subset", row, subsets);
1875
+ }
1876
+ const xrefs = strings(meta.xrefs);
1877
+ if (xrefs.length > 0) {
1878
+ columns.node("xref", row, xrefs);
1879
+ }
1880
+ if (Array.isArray(meta.synonyms) && meta.synonyms.length > 0) {
1881
+ columns.node(
1882
+ "synonym",
1883
+ row,
1884
+ meta.synonyms.filter(isJsonObject).map((s) => ({
1885
+ text: typeof s.val === "string" ? s.val : null,
1886
+ scope: typeof s.pred === "string" ? synonymScopeOf(s.pred) : null,
1887
+ type: typeof s.synonymType === "string" ? vocabulary.id(s.synonymType) : null,
1888
+ xrefs: strings(s.xrefs)
1889
+ }))
1890
+ );
1891
+ }
1892
+ if (typeof meta.deprecated === "boolean") {
1893
+ columns.node("is_obsolete", row, meta.deprecated);
1894
+ }
1895
+ writePropertyValues(columns, vocabulary, basicValues(meta), row);
1896
+ for (const key of Object.keys(meta)) {
1897
+ if (!META_KEYS.has(key)) {
1898
+ unrecognized[`meta.${key}`] = meta[key];
1899
+ }
1900
+ }
1901
+ }
1902
+ function writePropertyValues(columns, vocabulary, values, row) {
1903
+ const lists = /* @__PURE__ */ new Map();
1904
+ const others = [];
1905
+ for (const { pred, val } of values) {
1906
+ const tag = OBOGRAPHS_PREDICATE_TAGS.get(pred);
1907
+ if (tag === "shorthand") {
1908
+ continue;
1909
+ }
1910
+ if (tag === void 0) {
1911
+ others.push({ relation: vocabulary.id(pred), value: val, datatype: null });
1912
+ } else if (OBO_NODE_COLUMNS[tag].dtype === "list") {
1913
+ const list = lists.get(tag) ?? [];
1914
+ list.push(tag === "alt_id" ? val : vocabulary.id(val));
1915
+ lists.set(tag, list);
1916
+ } else {
1917
+ columns.node(tag, row, val);
1918
+ }
1919
+ }
1920
+ for (const [tag, list] of lists) {
1921
+ columns.node(tag, row, list);
1922
+ }
1923
+ if (others.length > 0) {
1924
+ columns.node("property_value", row, others);
1925
+ }
1926
+ }
1927
+ function badValue(ctx, message) {
1928
+ ctx.report.error("validation-error", JSON_ISSUE.BAD_VALUE, message);
1929
+ }
1930
+ function readEdges(ctx, columns, vocabulary, edges) {
1931
+ const { report, sink } = ctx;
1932
+ const propertyEdges = [];
1933
+ const dangling = [];
1934
+ let dropped = 0;
1935
+ const endpoint = (raw, element) => {
1936
+ const id = ctx.coerceId(vocabulary.id(raw), element);
1937
+ if (id === null || sink.indexOf(id) !== INVALID_INDEX) {
1938
+ return id;
1939
+ }
1940
+ dangling.push(raw);
1941
+ if (!ctx.options.addMissingNodes) {
1942
+ return null;
1943
+ }
1944
+ const row = ctx.pushNode(id, element);
1945
+ if (row >= 0) {
1946
+ columns.node(PLACEHOLDER_COLUMN, row, true);
1947
+ }
1948
+ return id;
1949
+ };
1950
+ for (let i = 0; i < edges.length; i++) {
1951
+ const element = `edges[${i}]`;
1952
+ const record = edges[i];
1953
+ if (!isJsonObject(record)) {
1954
+ ctx.badElement("edge", element);
1955
+ continue;
1956
+ }
1957
+ const subKey = hasKey(record, "sub") ? "sub" : "subj";
1958
+ if (subKey === "subj" && hasKey(record, "subj")) {
1959
+ report.warnOnce(
1960
+ "coercion",
1961
+ JSON_ISSUE.OBOGRAPHS_SUBJ,
1962
+ `${element} uses the outdated key subj; read as sub`,
1963
+ {
1964
+ element
1965
+ }
1966
+ );
1967
+ }
1968
+ const sub = record[subKey];
1969
+ const { obj, pred } = record;
1970
+ if (typeof sub !== "string" || typeof obj !== "string") {
1971
+ ctx.missingEndpoint(element, typeof sub === "string" ? "obj" : "sub");
1972
+ continue;
1973
+ }
1974
+ const between = typeof pred === "string" && PROPERTY_PREDICATES.has(pred) || vocabulary.isProperty(sub) || vocabulary.isProperty(obj);
1975
+ if (between && ctx.json.typedefs === "metadata") {
1976
+ propertyEdges.push(record);
1977
+ continue;
1978
+ }
1979
+ try {
1980
+ const u = endpoint(sub, `${element}.sub`);
1981
+ const v = endpoint(obj, `${element}.obj`);
1982
+ if (u === null || v === null) {
1983
+ dropped++;
1984
+ ctx.countSkipped("edge");
1985
+ continue;
1986
+ }
1987
+ const edge = ctx.pushEdge(u, v, "directed", void 0, element);
1988
+ if (typeof pred === "string") {
1989
+ columns.edge("relation", edge, vocabulary.relation(pred));
1990
+ } else {
1991
+ badValue(ctx, `${element} has no pred; its relation is unset`);
1992
+ }
1993
+ if (isJsonObject(record.meta)) {
1994
+ columns.edge("meta", edge, record.meta);
1995
+ }
1996
+ } catch (err) {
1997
+ ctx.skip(err, "edge", element);
1998
+ }
1999
+ }
2000
+ if (dangling.length > 0) {
2001
+ const shown = [...new Set(dangling)].slice(0, 5).join(", ");
2002
+ const action = ctx.options.addMissingNodes ? "each became a placeholder node (graphty.placeholder)" : `${dropped} edge(s) to them were dropped (addMissingNodes false)`;
2003
+ report.warning(
2004
+ "validation-error",
2005
+ JSON_ISSUE.DANGLING_REFERENCE,
2006
+ `${new Set(dangling).size} edge endpoint(s) missing from nodes: ${shown}; ${action}`,
2007
+ { element: dangling[0] }
2008
+ );
2009
+ }
2010
+ return propertyEdges;
2011
+ }
1359
2012
  const JSON_ISSUE = Object.freeze({
1360
2013
  /** The text is empty or whitespace (fatal). */
1361
2014
  EMPTY_INPUT: EMPTY_INPUT_CODE,
@@ -1391,8 +2044,18 @@ const JSON_ISSUE = Object.freeze({
1391
2044
  ID_MERGED: ID_MERGED_CODE,
1392
2045
  /** Edge ids of mixed JSON types were stored as text. */
1393
2046
  EDGE_ID_STRINGIFIED: "W_EDGE_ID_STRINGIFIED",
1394
- /** A JGF `graphs` array holds more than one graph; only `graphIndex` is read. */
2047
+ /** A JGF or OBO Graphs `graphs` array holds more than one graph; only the chosen one is read. */
1395
2048
  MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
2049
+ /** `graphIndex` is beyond the `graphs` array, or `graphName` names none of its graphs (fatal). */
2050
+ GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
2051
+ /** `graphName` names more than one graph of the `graphs` array (fatal). */
2052
+ AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE,
2053
+ /** obographs: an edge uses the outdated `subj` key of the OBO Graphs README; it is read as `sub`. */
2054
+ OBOGRAPHS_SUBJ: "W_JSON_OBOGRAPHS_SUBJ",
2055
+ /** obographs: an edge endpoint missing from `nodes` (a placeholder node is made, or the edge dropped under addMissingNodes false). */
2056
+ DANGLING_REFERENCE: DANGLING_REFERENCE_CODE,
2057
+ /** The document is longer than one JavaScript string can hold (fatal; category unsupported). */
2058
+ TOO_LARGE: TOO_LARGE_CODE,
1396
2059
  /** JGF hyperedges under the "error" policy. */
1397
2060
  HYPEREDGE: HYPEREDGE_CODE,
1398
2061
  /** JGF hyperedges skipped under the default "skip" policy. */
@@ -1475,6 +2138,14 @@ function resolveJsonOptions(options) {
1475
2138
  if (!Number.isInteger(graphIndex) || graphIndex < 0) {
1476
2139
  throw unsupportedOption("graphIndex", graphIndex, ["a non-negative integer"]);
1477
2140
  }
2141
+ const oboIds = o.oboIds ?? "curie";
2142
+ if (oboIds !== "curie" && oboIds !== "iri") {
2143
+ throw unsupportedOption("oboIds", oboIds, ["curie", "iri"]);
2144
+ }
2145
+ const typedefs = o.typedefs ?? "metadata";
2146
+ if (typedefs !== "metadata" && typedefs !== "nodes") {
2147
+ throw unsupportedOption("typedefs", typedefs, ["metadata", "nodes"]);
2148
+ }
1478
2149
  const nodesPath = pathOption("nodesPath", o.nodesPath);
1479
2150
  const edgesPath = pathOption("edgesPath", o.edgesPath);
1480
2151
  if ((nodesPath !== null || edgesPath !== null) && dialect !== "auto" && !PATH_DIALECTS.has(dialect)) {
@@ -1488,6 +2159,9 @@ function resolveJsonOptions(options) {
1488
2159
  targetKey: keyOption("targetKey", o.targetKey),
1489
2160
  indexLinks,
1490
2161
  graphIndex,
2162
+ choice: { graphIndex: o.graphIndex, graphName: o.graphName },
2163
+ oboIds,
2164
+ typedefs,
1491
2165
  nodesPath,
1492
2166
  edgesPath
1493
2167
  };
@@ -2059,7 +2733,7 @@ class ImportContext {
2059
2733
  */
2060
2734
  declareEdgeIds(edges, read, name, unique) {
2061
2735
  let numbers = 0;
2062
- let strings = 0;
2736
+ let strings2 = 0;
2063
2737
  for (const edge of edges) {
2064
2738
  if (!isJsonObject(edge)) {
2065
2739
  continue;
@@ -2068,16 +2742,16 @@ class ImportContext {
2068
2742
  if (typeof raw === "number") {
2069
2743
  numbers++;
2070
2744
  } else if (typeof raw === "string") {
2071
- strings++;
2745
+ strings2++;
2072
2746
  }
2073
2747
  }
2074
- if (numbers + strings === 0) {
2748
+ if (numbers + strings2 === 0) {
2075
2749
  return null;
2076
2750
  }
2077
- const dtype = strings === 0 ? "f64" : "string";
2751
+ const dtype = strings2 === 0 ? "f64" : "string";
2078
2752
  const columnName = uniqueColumnName(name, "id", (candidate) => this.edges.taken(candidate));
2079
2753
  const handle = this.edges.declare({ name: columnName, dtype, role: "id", nullable: true, unique });
2080
- const stringify = strings > 0 && numbers > 0;
2754
+ const stringify = strings2 > 0 && numbers > 0;
2081
2755
  const seen = unique ? /* @__PURE__ */ new Set() : null;
2082
2756
  if (stringify) {
2083
2757
  this.report.warning(
@@ -2180,84 +2854,7 @@ function parseDocument(text, report) {
2180
2854
  }
2181
2855
  return root;
2182
2856
  }
2183
- const MAYBE_UNSAFE_INTEGER = new RegExp("(?<![0-9.])[0-9]{16}");
2184
2857
  const BIG_INTEGERS_SHOWN = 10;
2185
- const NONSTANDARD_SENTINEL = `${String.fromCharCode(0)}graph-io:`;
2186
- const NONSTANDARD_TOKENS = [
2187
- ["-Infinity", -Infinity],
2188
- ["Infinity", Infinity],
2189
- ["NaN", NaN]
2190
- ];
2191
- const INTEGER_LITERAL = /^-?(0|[1-9][0-9]*)$/;
2192
- function rewriteNumbers(text) {
2193
- const parts = [];
2194
- const tokens = /* @__PURE__ */ new Set();
2195
- const bigIntegers = [];
2196
- const arrays = [];
2197
- let expectValue = true;
2198
- let copied = 0;
2199
- let i = 0;
2200
- const n = text.length;
2201
- while (i < n) {
2202
- const ch = text[i];
2203
- if (ch === '"') {
2204
- i++;
2205
- while (i < n && text[i] !== '"') {
2206
- i += text[i] === "\\" ? 2 : 1;
2207
- }
2208
- i++;
2209
- expectValue = false;
2210
- continue;
2211
- }
2212
- if (ch === "{" || ch === "[") {
2213
- arrays.push(ch === "[");
2214
- expectValue = ch === "[";
2215
- } else if (ch === "}" || ch === "]") {
2216
- arrays.pop();
2217
- expectValue = false;
2218
- } else if (ch === ":") {
2219
- expectValue = true;
2220
- } else if (ch === ",") {
2221
- expectValue = arrays.length > 0 && arrays[arrays.length - 1];
2222
- } else if (expectValue && ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
2223
- expectValue = false;
2224
- const token = NONSTANDARD_TOKENS.find(([word]) => text.startsWith(word, i));
2225
- let end = i;
2226
- let replacement = null;
2227
- if (token !== void 0) {
2228
- end = i + token[0].length;
2229
- tokens.add(token[0]);
2230
- replacement = JSON.stringify(`${NONSTANDARD_SENTINEL}${token[0]}`);
2231
- } else if (ch === "-" || ch >= "0" && ch <= "9") {
2232
- end = i + 1;
2233
- while (end < n && "0123456789+-.eE".includes(text[end])) {
2234
- end++;
2235
- }
2236
- const literal = text.slice(i, end);
2237
- if (INTEGER_LITERAL.test(literal) && !Number.isSafeInteger(Number(literal))) {
2238
- bigIntegers.push(literal);
2239
- replacement = `"${literal}"`;
2240
- }
2241
- }
2242
- if (replacement !== null) {
2243
- parts.push(text.slice(copied, i), replacement);
2244
- copied = end;
2245
- }
2246
- i = Math.max(end, i + 1);
2247
- continue;
2248
- }
2249
- i++;
2250
- }
2251
- parts.push(text.slice(copied));
2252
- return { text: parts.join(""), tokens, bigIntegers };
2253
- }
2254
- function reviveNonstandard(_key, value) {
2255
- if (typeof value === "string" && value.startsWith(NONSTANDARD_SENTINEL)) {
2256
- const found = NONSTANDARD_TOKENS.find(([word]) => word === value.slice(NONSTANDARD_SENTINEL.length));
2257
- return found === void 0 ? value : found[1];
2258
- }
2259
- return value;
2260
- }
2261
2858
  function detectDialect(root, forced, report) {
2262
2859
  if (forced !== "auto") {
2263
2860
  return forced;
@@ -3029,31 +3626,84 @@ function importJgf(ctx, root) {
3029
3626
  ctx.setMeta(shape, { name: label, ...ctx.weightOriginPatch() });
3030
3627
  }
3031
3628
  function jgfGraphOf(ctx, root) {
3629
+ return isJsonObject(root.graph) ? root.graph : chosenGraph(ctx, root, "JGF");
3630
+ }
3631
+ function chosenGraph(ctx, root, what) {
3032
3632
  const { report } = ctx;
3033
- if (isJsonObject(root.graph)) {
3034
- return root.graph;
3035
- }
3036
3633
  const graphs = arraySection(root.graphs, "graphs", report) ?? [];
3037
3634
  if (graphs.length === 0) {
3038
- report.fail(JSON_ISSUE.SHAPE, "a JGF document needs a graph object or a non-empty graphs array");
3635
+ report.fail(JSON_ISSUE.SHAPE, `a ${what} document needs a graph object or a non-empty graphs array`);
3039
3636
  }
3637
+ const index = ctx.json.all === true ? ctx.json.graphIndex : chooseGraph(graphs.map(graphNameOf), ctx.json.choice, report);
3040
3638
  if (graphs.length > 1 && ctx.json.all !== true) {
3041
3639
  report.warning(
3042
3640
  "unsupported",
3043
3641
  JSON_ISSUE.MULTIPLE_GRAPHS,
3044
- `the document holds ${graphs.length} graphs; only graphs[${ctx.json.graphIndex}] is read (${graphs.length - 1} skipped), importAll() reads every one`,
3642
+ `the document holds ${graphs.length} graphs; only graphs[${index}] is read (${graphs.length - 1} skipped), importAll() reads every one`,
3045
3643
  { element: "graphs" }
3046
3644
  );
3047
3645
  }
3048
- if (ctx.json.graphIndex >= graphs.length) {
3049
- report.fail(JSON_ISSUE.SHAPE, `graphIndex ${ctx.json.graphIndex} is beyond the ${graphs.length} graph(s)`);
3050
- }
3051
- const graph = graphs[ctx.json.graphIndex];
3646
+ const graph = graphs[index];
3052
3647
  if (!isJsonObject(graph)) {
3053
- return report.fail(JSON_ISSUE.SHAPE, `graphs[${ctx.json.graphIndex}] is not an object`);
3648
+ return report.fail(JSON_ISSUE.SHAPE, `graphs[${index}] is not an object`);
3054
3649
  }
3055
3650
  return graph;
3056
3651
  }
3652
+ function graphNameOf(graph) {
3653
+ if (!isJsonObject(graph)) {
3654
+ return null;
3655
+ }
3656
+ for (const key of ["id", "label", "lbl"]) {
3657
+ if (typeof graph[key] === "string") {
3658
+ return graph[key];
3659
+ }
3660
+ }
3661
+ return null;
3662
+ }
3663
+ function countOf(section) {
3664
+ if (section === void 0 || section === null) {
3665
+ return 0;
3666
+ }
3667
+ if (Array.isArray(section)) {
3668
+ return section.length;
3669
+ }
3670
+ return isJsonObject(section) ? Object.keys(section).length : null;
3671
+ }
3672
+ function listingsOf(root, dialect) {
3673
+ if ((dialect === "jgf" || dialect === "obographs") && isJsonObject(root)) {
3674
+ if (Array.isArray(root.graphs) && !isJsonObject(root.graph)) {
3675
+ return root.graphs.map((graph, index) => ({
3676
+ index,
3677
+ name: graphNameOf(graph),
3678
+ nodes: isJsonObject(graph) ? countOf(graph.nodes) : null,
3679
+ edges: isJsonObject(graph) ? countOf(graph.edges) : null
3680
+ }));
3681
+ }
3682
+ if (isJsonObject(root.graph)) {
3683
+ const { graph } = root;
3684
+ return [{ index: 0, name: graphNameOf(graph), nodes: countOf(graph.nodes), edges: countOf(graph.edges) }];
3685
+ }
3686
+ }
3687
+ return [{ index: 0, name: null, nodes: null, edges: null }];
3688
+ }
3689
+ const MAX_TEXT_LENGTH = 2 ** 29 - 24;
3690
+ async function readJsonText(input, report, options) {
3691
+ if (typeof input === "string") {
3692
+ return readText(input, report, options);
3693
+ }
3694
+ const parts = [];
3695
+ let length = 0;
3696
+ for await (const chunk of textChunks(input, report, options)) {
3697
+ length += chunk.length;
3698
+ if (length > MAX_TEXT_LENGTH) {
3699
+ const message = `the document is longer than ${MAX_TEXT_LENGTH} characters, the most one JavaScript string holds`;
3700
+ report.error("unsupported", JSON_ISSUE.TOO_LARGE, message);
3701
+ throw report.abort(message, { code: JSON_ISSUE.TOO_LARGE });
3702
+ }
3703
+ parts.push(chunk);
3704
+ }
3705
+ return parts.length === 1 ? parts[0] : parts.join("");
3706
+ }
3057
3707
  function pushJgfNode(ctx, id, record, labelColumn, element) {
3058
3708
  const index = ctx.pushNode(id, element);
3059
3709
  if (index < 0) {
@@ -3395,7 +4045,8 @@ const jsonImporter = Object.freeze({
3395
4045
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
3396
4046
  return 0;
3397
4047
  }
3398
- return SNIFF_KEYS.some((key) => trimmed.includes(key)) ? 0.9 : 0.5;
4048
+ const score = SNIFF_KEYS.some((key) => trimmed.includes(key)) ? 0.9 : 0.5;
4049
+ return trimmed.startsWith("[") && sniffJsonDialectHead(trimmed) !== "cytoscape" ? Math.min(score, 0.3) : score;
3399
4050
  },
3400
4051
  /**
3401
4052
  * Read a JSON graph document into the sink.
@@ -3408,11 +4059,27 @@ const jsonImporter = Object.freeze({
3408
4059
  const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
3409
4060
  const json = resolveJsonOptions(options);
3410
4061
  const report = new ImportReportBuilder("json", resolved.errorLimit);
3411
- const text = await readText(input, report, resolved);
4062
+ const text = await readJsonText(input, report, resolved);
3412
4063
  const { root, dialect } = documentOf(parseDocument(text, report), json, report);
3413
4064
  readGraph(root, dialect, sink, report, resolved, json, options);
3414
4065
  return report.finish();
3415
4066
  },
4067
+ /**
4068
+ * List the graphs of a JSON document without importing them: each entry of a JGF or OBO
4069
+ * Graphs `graphs` array with its name (`id`, else its label) and node and edge counts; any
4070
+ * other document holds one graph.
4071
+ * @param input - the text, bytes or stream
4072
+ * @param options - format-specific and common options
4073
+ * @returns one listing per graph
4074
+ */
4075
+ async listGraphs(input, options) {
4076
+ const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
4077
+ const json = resolveJsonOptions(options);
4078
+ const report = new ImportReportBuilder("json", resolved.errorLimit);
4079
+ const text = await readJsonText(input, report, resolved);
4080
+ const { root, dialect } = documentOf(parseDocument(text, report), json, report);
4081
+ return listingsOf(root, dialect);
4082
+ },
3416
4083
  /**
3417
4084
  * Read every graph of a JSON document: each entry of a JGF `graphs` array into its own sink;
3418
4085
  * any other document holds one graph.
@@ -3425,9 +4092,9 @@ const jsonImporter = Object.freeze({
3425
4092
  const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
3426
4093
  const json = resolveJsonOptions(options);
3427
4094
  const first = new ImportReportBuilder("json", resolved.errorLimit);
3428
- const text = await readText(input, first, resolved);
4095
+ const text = await readJsonText(input, first, resolved);
3429
4096
  const { root, dialect } = documentOf(parseDocument(text, first), json, first);
3430
- const graphs = dialect === "jgf" && isJsonObject(root) && !isJsonObject(root.graph) && Array.isArray(root.graphs) ? root.graphs.length : 1;
4097
+ const graphs = listingsOf(root, dialect).length;
3431
4098
  const reports = [];
3432
4099
  for (let i = 0; i < Math.max(graphs, 1); i++) {
3433
4100
  const report = i === 0 ? first : new ImportReportBuilder("json", resolved.errorLimit);
@@ -3439,7 +4106,7 @@ const jsonImporter = Object.freeze({
3439
4106
  });
3440
4107
  function readGraph(root, dialect, sink, report, resolved, json, options) {
3441
4108
  const ctx = new ImportContext(sink, report, resolved, json, options?.defaultDirected !== void 0);
3442
- reportSinkOptions(sink, options, report);
4109
+ reportSinkOptions(sink, options, report, dialect === "obographs");
3443
4110
  reportUnusedOptions(options, report, USED_OPTIONS);
3444
4111
  if (dialect === "cytoscape") {
3445
4112
  importCytoscape(ctx, root);
@@ -3467,6 +4134,9 @@ function readGraph(root, dialect, sink, report, resolved, json, options) {
3467
4134
  case "tree":
3468
4135
  importTree(ctx, doc);
3469
4136
  break;
4137
+ case "obographs":
4138
+ importObographs(ctx, doc);
4139
+ break;
3470
4140
  default: {
3471
4141
  const name = dialect;
3472
4142
  throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {
@@ -3478,13 +4148,20 @@ function readGraph(root, dialect, sink, report, resolved, json, options) {
3478
4148
  throwIfAborted(resolved.signal);
3479
4149
  }
3480
4150
  export {
4151
+ GRAPH_FORMATS as G,
3481
4152
  JSON_DIALECTS as J,
4153
+ SNIFF_HEAD_BYTES as S,
3482
4154
  JSON_ISSUE as a,
3483
4155
  JSON_LOSS as b,
3484
4156
  jsonExporter as c,
3485
4157
  dialectCapabilities as d,
3486
4158
  jsonImporter as e,
4159
+ extensionOf as f,
4160
+ sniffJsonDialectHead as g,
4161
+ headBytes as h,
3487
4162
  jsonCapabilities as j,
3488
- sniffJsonDialect as s
4163
+ normalizeMimeType as n,
4164
+ rankFormats as r,
4165
+ sniffFormat as s
3489
4166
  };
3490
- //# sourceMappingURL=importer-B6rKRxzv.js.map
4167
+ //# sourceMappingURL=importer-Bm1_5vPS.js.map