@graphty/graph-io 0.3.17 → 0.3.18

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 (91) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -3
  3. package/dist/chunks/{escape-scjHxjpr.js → escape-D-gZWO26.js} +3 -2
  4. package/dist/chunks/{escape-scjHxjpr.js.map → escape-D-gZWO26.js.map} +1 -1
  5. package/dist/chunks/{importer-Xg07gk7p.js → importer-Br_QeAeE.js} +4 -3
  6. package/dist/chunks/{importer-Xg07gk7p.js.map → importer-Br_QeAeE.js.map} +1 -1
  7. package/dist/chunks/{importer-ruMWWvVs.js → importer-DHagxvDD.js} +4 -3
  8. package/dist/chunks/{importer-ruMWWvVs.js.map → importer-DHagxvDD.js.map} +1 -1
  9. package/dist/chunks/{importer-B6rKRxzv.js → importer-Du5crN9l.js} +509 -25
  10. package/dist/chunks/importer-Du5crN9l.js.map +1 -0
  11. package/dist/chunks/importer-aNJfe0qu.js +1614 -0
  12. package/dist/chunks/importer-aNJfe0qu.js.map +1 -0
  13. package/dist/chunks/{importer-BmOl9gLW.js → importer-d0uQxFp6.js} +4 -3
  14. package/dist/chunks/{importer-BmOl9gLW.js.map → importer-d0uQxFp6.js.map} +1 -1
  15. package/dist/chunks/ontology-BnrJ4I98.js +113 -0
  16. package/dist/chunks/ontology-BnrJ4I98.js.map +1 -0
  17. package/dist/chunks/{records-DSpbTE5s.js → records-Bk9jgodz.js} +2 -2
  18. package/dist/chunks/{records-DSpbTE5s.js.map → records-Bk9jgodz.js.map} +1 -1
  19. package/dist/chunks/{writer-DHHfHn11.js → report-BOk0p5y8.js} +97 -919
  20. package/dist/chunks/report-BOk0p5y8.js.map +1 -0
  21. package/dist/chunks/writer-GAdltGmC.js +827 -0
  22. package/dist/chunks/writer-GAdltGmC.js.map +1 -0
  23. package/dist/csv.js +4 -3
  24. package/dist/csv.js.map +1 -1
  25. package/dist/dot.js +1 -1
  26. package/dist/gexf.js +3 -2
  27. package/dist/gexf.js.map +1 -1
  28. package/dist/gml.js +3 -2
  29. package/dist/gml.js.map +1 -1
  30. package/dist/graph-io.js +160 -141
  31. package/dist/graph-io.js.map +1 -1
  32. package/dist/graphml.js +1 -1
  33. package/dist/json.js +1 -1
  34. package/dist/neo4j.js +4 -3
  35. package/dist/neo4j.js.map +1 -1
  36. package/dist/obo.d.ts +1 -0
  37. package/dist/obo.js +6 -0
  38. package/dist/obo.js.map +1 -0
  39. package/dist/pajek.js +1 -1
  40. package/dist/src/common/ontology.d.ts +59 -0
  41. package/dist/src/common/ontology.d.ts.map +1 -0
  42. package/dist/src/common/ontology.js +147 -0
  43. package/dist/src/common/ontology.js.map +1 -0
  44. package/dist/src/formats/json/dialect.d.ts +8 -6
  45. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  46. package/dist/src/formats/json/dialect.js +26 -3
  47. package/dist/src/formats/json/dialect.js.map +1 -1
  48. package/dist/src/formats/json/importer.d.ts +324 -7
  49. package/dist/src/formats/json/importer.d.ts.map +1 -1
  50. package/dist/src/formats/json/importer.js +153 -22
  51. package/dist/src/formats/json/importer.js.map +1 -1
  52. package/dist/src/formats/json/obographs.d.ts +21 -0
  53. package/dist/src/formats/json/obographs.d.ts.map +1 -0
  54. package/dist/src/formats/json/obographs.js +476 -0
  55. package/dist/src/formats/json/obographs.js.map +1 -0
  56. package/dist/src/formats/obo/importer.d.ts +90 -0
  57. package/dist/src/formats/obo/importer.d.ts.map +1 -0
  58. package/dist/src/formats/obo/importer.js +1248 -0
  59. package/dist/src/formats/obo/importer.js.map +1 -0
  60. package/dist/src/formats/obo/index.d.ts +7 -0
  61. package/dist/src/formats/obo/index.d.ts.map +1 -0
  62. package/dist/src/formats/obo/index.js +7 -0
  63. package/dist/src/formats/obo/index.js.map +1 -0
  64. package/dist/src/formats/obo/syntax.d.ts +121 -0
  65. package/dist/src/formats/obo/syntax.d.ts.map +1 -0
  66. package/dist/src/formats/obo/syntax.js +424 -0
  67. package/dist/src/formats/obo/syntax.js.map +1 -0
  68. package/dist/src/index.d.ts +1 -0
  69. package/dist/src/index.d.ts.map +1 -1
  70. package/dist/src/index.js +1 -0
  71. package/dist/src/index.js.map +1 -1
  72. package/dist/src/registry.d.ts.map +1 -1
  73. package/dist/src/registry.js +3 -1
  74. package/dist/src/registry.js.map +1 -1
  75. package/dist/src/sniff.d.ts +1 -1
  76. package/dist/src/sniff.d.ts.map +1 -1
  77. package/dist/src/sniff.js +23 -3
  78. package/dist/src/sniff.js.map +1 -1
  79. package/package.json +6 -1
  80. package/src/common/ontology.ts +169 -0
  81. package/src/formats/json/dialect.ts +37 -7
  82. package/src/formats/json/importer.ts +204 -27
  83. package/src/formats/json/obographs.ts +563 -0
  84. package/src/formats/obo/importer.ts +1695 -0
  85. package/src/formats/obo/index.ts +7 -0
  86. package/src/formats/obo/syntax.ts +466 -0
  87. package/src/index.ts +1 -0
  88. package/src/registry.ts +3 -1
  89. package/src/sniff.ts +35 -5
  90. package/dist/chunks/importer-B6rKRxzv.js.map +0 -1
  91. package/dist/chunks/writer-DHHfHn11.js.map +0 -1
@@ -1,5 +1,7 @@
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 { aC as resolveExportOptions, ay as pairFolding, as as declareResolved, ag as UNKNOWN_ENCODING_CODE, s as ENCODING_FALLBACK_CODE, z as INVALID_ENCODING_CODE, F as INVALID_UTF8_CODE, V as OPTION_IGNORED_CODE, a3 as SINK_OPTION_CODE, H as HYPEREDGE_CODE, ad as TOO_LARGE_CODE, D as DANGLING_REFERENCE_CODE, A as AMBIGUOUS_GRAPH_NAME_CODE, G as GRAPH_NOT_FOUND_CODE, P as MULTIPLE_GRAPHS_CODE, v as ID_MERGED_CODE, n as DUPLICATE_EDGE_ID_CODE, p as DUPLICATE_NODE_CODE, ah as UNKNOWN_PARENT_CODE, e as BAD_VALUE_CODE, M as MISSING_ENDPOINT_CODE, N as MISSING_ID_CODE, a9 as SYNTAX_CODE, r as EMPTY_INPUT_CODE, aD as resolveImportOptions, I as ImportReportBuilder, az as readText, t as textChunks, aA as reportSinkOptions, aB as reportUnusedOptions, a as throwIfAborted, J as IdCoercer, q as DirectionResolver, aE as uniqueColumnName, ao as chooseGraph } from "./report-BOk0p5y8.js";
3
+ import { c as capabilities, L as LOSS, n as joinText, e as encodeChunks, a as checkCapabilities, r as countMixedEdges, f as explicitWeights, h as formatF32, i as formatF64, w as weightFromValue } from "./writer-GAdltGmC.js";
4
+ 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
5
  const JSON_DIALECTS = Object.freeze([
4
6
  "node-link",
5
7
  "d3",
@@ -11,7 +13,8 @@ const JSON_DIALECTS = Object.freeze([
11
13
  const JSON_IMPORT_DIALECTS = Object.freeze([
12
14
  ...JSON_DIALECTS,
13
15
  "adjacency",
14
- "tree"
16
+ "tree",
17
+ "obographs"
15
18
  ]);
16
19
  const META_KEY = "json";
17
20
  function isJsonObject(value) {
@@ -41,7 +44,7 @@ function sniffJsonDialect(root) {
41
44
  return "jgf";
42
45
  }
43
46
  if (Array.isArray(root.graphs)) {
44
- return "jgf";
47
+ return isOboGraph(firstJsonObject(root.graphs)) ? "obographs" : "jgf";
45
48
  }
46
49
  if (!hasKey(root, "edges") && !hasKey(root, "links")) {
47
50
  if (hasKey(root, "nodes") && hasKey(root, "adjacency")) {
@@ -70,6 +73,15 @@ function sniffJsonDialect(root) {
70
73
  const bare = !hasKey(root, "directed") && !hasKey(root, "multigraph") && !hasKey(root, "graph");
71
74
  return bare && hasKey(root, "links") ? "d3" : "node-link";
72
75
  }
76
+ const OBOGRAPHS_NODE_TYPES = /* @__PURE__ */ new Set(["CLASS", "INDIVIDUAL", "PROPERTY"]);
77
+ function isOboGraph(graph) {
78
+ if (graph === null) {
79
+ return false;
80
+ }
81
+ const isEdge = (e) => isJsonObject(e) && (hasKey(e, "obj") && (hasKey(e, "sub") || hasKey(e, "subj")) || hasKey(e, "pred"));
82
+ const isNode = (n) => isJsonObject(n) && (hasKey(n, "lbl") || hasKey(n, "meta") || OBOGRAPHS_NODE_TYPES.has(n.type));
83
+ return Array.isArray(graph.edges) && graph.edges.some(isEdge) || Array.isArray(graph.nodes) && graph.nodes.some(isNode);
84
+ }
73
85
  function firstJsonObject(value) {
74
86
  if (!Array.isArray(value)) {
75
87
  return null;
@@ -107,7 +119,9 @@ const DIALECT_DEFAULT_DIRECTED = Object.freeze({
107
119
  vis: false,
108
120
  // networkx adjacency_data declares `directed`; tree_graph always builds a DiGraph
109
121
  adjacency: false,
110
- tree: true
122
+ tree: true,
123
+ // OBO Graphs edges point from the subject (child) to the object (parent)
124
+ obographs: true
111
125
  });
112
126
  const JSON_DTYPES = Object.freeze(["f64", "i32", "bool", "string"]);
113
127
  const COMMON = {
@@ -1356,6 +1370,383 @@ const jsonExporter = Object.freeze({
1356
1370
  function jsonCapabilities(dialect) {
1357
1371
  return dialectCapabilities(dialect);
1358
1372
  }
1373
+ const NODE_KEYS = /* @__PURE__ */ new Set(["id", "lbl", "type", "propertyType", "meta"]);
1374
+ const META_KEYS = /* @__PURE__ */ new Set([
1375
+ "definition",
1376
+ "comments",
1377
+ "subsets",
1378
+ "xrefs",
1379
+ "synonyms",
1380
+ "basicPropertyValues",
1381
+ "deprecated"
1382
+ ]);
1383
+ const FRAME_TYPES = Object.freeze({
1384
+ CLASS: "Term",
1385
+ INDIVIDUAL: "Instance",
1386
+ PROPERTY: "Typedef"
1387
+ });
1388
+ const BUILTIN_PREDICATES = Object.freeze({
1389
+ is_a: "is_a",
1390
+ subPropertyOf: "is_a",
1391
+ type: "instance_of",
1392
+ inverseOf: "inverse_of"
1393
+ });
1394
+ const PROPERTY_PREDICATES = /* @__PURE__ */ new Set(["subPropertyOf", "inverseOf"]);
1395
+ class ColumnWriter {
1396
+ /**
1397
+ * Create a writer.
1398
+ * @param ctx - the import context
1399
+ */
1400
+ constructor(ctx) {
1401
+ this.handles = /* @__PURE__ */ new Map();
1402
+ this.ctx = ctx;
1403
+ }
1404
+ /**
1405
+ * Write a node cell of the vocabulary.
1406
+ * @param name - the column
1407
+ * @param row - the node index
1408
+ * @param value - the value
1409
+ */
1410
+ node(name, row, value) {
1411
+ this.ctx.sink.setNodeValue(this.handle("node", name), row, value);
1412
+ }
1413
+ /**
1414
+ * Write an edge cell of the vocabulary.
1415
+ * @param name - the column
1416
+ * @param row - the edge index
1417
+ * @param value - the value
1418
+ */
1419
+ edge(name, row, value) {
1420
+ this.ctx.sink.setEdgeValue(this.handle("edge", name), row, value);
1421
+ }
1422
+ /**
1423
+ * The handle of a column, declared on first use.
1424
+ * @param domain - node or edge
1425
+ * @param name - the column
1426
+ * @returns the handle
1427
+ */
1428
+ handle(domain, name) {
1429
+ const key = `${domain}:${name}`;
1430
+ const cached = this.handles.get(key);
1431
+ if (cached !== void 0) {
1432
+ return cached;
1433
+ }
1434
+ const { handle } = declareResolved(this.ctx.sink, domain, oboColumnDecl(domain, name), this.ctx.report);
1435
+ this.handles.set(key, handle);
1436
+ return handle;
1437
+ }
1438
+ }
1439
+ function vocabularyOf(nodes, oboIds) {
1440
+ const shorthands = /* @__PURE__ */ new Map();
1441
+ const propertyIds = /* @__PURE__ */ new Set();
1442
+ for (const node of nodes) {
1443
+ if (!isJsonObject(node) || typeof node.id !== "string" || node.type !== "PROPERTY") {
1444
+ continue;
1445
+ }
1446
+ propertyIds.add(node.id);
1447
+ const shorthand = basicValues(node.meta).find((pv) => OBOGRAPHS_PREDICATE_TAGS.get(pv.pred) === "shorthand");
1448
+ if (shorthand !== void 0) {
1449
+ shorthands.set(node.id, shorthand.val);
1450
+ }
1451
+ }
1452
+ const compact = (iri) => oboIds === "curie" ? compactOboIri(iri) : iri;
1453
+ return {
1454
+ id: (iri) => oboIds === "curie" ? shorthands.get(iri) ?? compactOboIri(iri) : iri,
1455
+ relation: (pred) => BUILTIN_PREDICATES[pred] ?? shorthands.get(pred) ?? compact(pred),
1456
+ isProperty: (iri) => propertyIds.has(iri),
1457
+ properties: {}
1458
+ };
1459
+ }
1460
+ function basicValues(meta) {
1461
+ if (!isJsonObject(meta) || !Array.isArray(meta.basicPropertyValues)) {
1462
+ return [];
1463
+ }
1464
+ return meta.basicPropertyValues.filter(
1465
+ (pv) => isJsonObject(pv) && typeof pv.pred === "string" && typeof pv.val === "string"
1466
+ );
1467
+ }
1468
+ function strings(value) {
1469
+ if (!Array.isArray(value)) {
1470
+ return [];
1471
+ }
1472
+ return value.flatMap((item) => {
1473
+ if (typeof item === "string") {
1474
+ return [item];
1475
+ }
1476
+ return isJsonObject(item) && typeof item.val === "string" ? [item.val] : [];
1477
+ });
1478
+ }
1479
+ function importObographs(ctx, root) {
1480
+ const graph = chosenGraph(ctx, root, "OBO Graphs");
1481
+ const nodes = sectionOf(ctx, graph, "nodes");
1482
+ const edges = sectionOf(ctx, graph, "edges");
1483
+ const vocabulary = vocabularyOf(nodes, ctx.json.oboIds);
1484
+ const columns = new ColumnWriter(ctx);
1485
+ ctx.reportNodeIdFrom("obographs", "the node ids");
1486
+ ctx.setHeader(true);
1487
+ ctx.sink.reserve(nodes.length, edges.length);
1488
+ for (let i = 0; i < nodes.length; i++) {
1489
+ readNode(ctx, columns, vocabulary, nodes[i], `nodes[${i}]`);
1490
+ }
1491
+ const propertyEdges = readEdges(ctx, columns, vocabulary, edges);
1492
+ const graphMeta = {};
1493
+ for (const key of Object.keys(graph)) {
1494
+ if (key !== "nodes" && key !== "edges") {
1495
+ graphMeta[key] = graph[key];
1496
+ }
1497
+ }
1498
+ const document = {};
1499
+ for (const key of Object.keys(root)) {
1500
+ if (key !== "graphs") {
1501
+ document[key] = root[key];
1502
+ }
1503
+ }
1504
+ const label = typeof graph.lbl === "string" ? graph.lbl : null;
1505
+ const name = label ?? (typeof graph.id === "string" ? graph.id : null);
1506
+ ctx.sink.setMeta({
1507
+ sourceFormat: "json",
1508
+ name,
1509
+ extra: {
1510
+ json: { dialect: "obographs" },
1511
+ obographs: {
1512
+ ids: ctx.json.oboIds,
1513
+ graph: graphMeta,
1514
+ document,
1515
+ properties: vocabulary.properties,
1516
+ propertyEdges
1517
+ }
1518
+ }
1519
+ });
1520
+ }
1521
+ function sectionOf(ctx, graph, key) {
1522
+ const value = graph[key];
1523
+ if (value === void 0 || value === null) {
1524
+ return [];
1525
+ }
1526
+ if (!Array.isArray(value)) {
1527
+ return ctx.report.fail(JSON_ISSUE.SHAPE, `an OBO Graphs graph's ${key} must be an array`, { element: key });
1528
+ }
1529
+ return value;
1530
+ }
1531
+ function readNode(ctx, columns, vocabulary, record, element) {
1532
+ if (!isJsonObject(record)) {
1533
+ ctx.badElement("node", element);
1534
+ return;
1535
+ }
1536
+ if (typeof record.id !== "string") {
1537
+ if (record.id === void 0) {
1538
+ ctx.coerceId(void 0, element);
1539
+ } else {
1540
+ badValue(ctx, `${element}.id must be a string; the node is skipped`);
1541
+ }
1542
+ ctx.countSkipped("node");
1543
+ return;
1544
+ }
1545
+ if (record.type === "PROPERTY" && ctx.json.typedefs === "metadata") {
1546
+ const shorthand = vocabulary.id(record.id) === record.id ? void 0 : vocabulary.id(record.id);
1547
+ const entry = {};
1548
+ for (const [key, value] of Object.entries({
1549
+ lbl: record.lbl,
1550
+ propertyType: record.propertyType,
1551
+ shorthand,
1552
+ meta: record.meta
1553
+ })) {
1554
+ if (value !== void 0) {
1555
+ entry[key] = value;
1556
+ }
1557
+ }
1558
+ vocabulary.properties[record.id] = entry;
1559
+ return;
1560
+ }
1561
+ const id = ctx.coerceId(vocabulary.id(record.id), element);
1562
+ const row = id === null ? -1 : ctx.pushNode(id, element);
1563
+ if (row < 0) {
1564
+ return;
1565
+ }
1566
+ try {
1567
+ writeNode(ctx, columns, vocabulary, record, row, element);
1568
+ } catch (err) {
1569
+ ctx.report.recordError(err, { element: record.id });
1570
+ }
1571
+ }
1572
+ function writeNode(ctx, columns, vocabulary, record, row, element) {
1573
+ const unrecognized = {};
1574
+ const text = (key, column) => {
1575
+ const value = record[key];
1576
+ if (typeof value === "string") {
1577
+ columns.node(column, row, column === "type" ? FRAME_TYPES[value] ?? value : value);
1578
+ } else if (value !== void 0 && value !== null) {
1579
+ badValue(ctx, `${element}.${key} must be a string`);
1580
+ }
1581
+ };
1582
+ text("type", "type");
1583
+ text("lbl", "name");
1584
+ text("propertyType", "propertyType");
1585
+ for (const key of Object.keys(record)) {
1586
+ if (!NODE_KEYS.has(key)) {
1587
+ unrecognized[key] = record[key];
1588
+ }
1589
+ }
1590
+ const { meta } = record;
1591
+ if (isJsonObject(meta)) {
1592
+ writeMeta(columns, vocabulary, meta, row, unrecognized);
1593
+ } else if (meta !== void 0 && meta !== null) {
1594
+ badValue(ctx, `${element}.meta must be an object`);
1595
+ }
1596
+ if (Object.keys(unrecognized).length > 0) {
1597
+ columns.node("obo.unrecognized", row, unrecognized);
1598
+ }
1599
+ }
1600
+ function writeMeta(columns, vocabulary, meta, row, unrecognized) {
1601
+ const { definition } = meta;
1602
+ if (isJsonObject(definition) && typeof definition.val === "string") {
1603
+ columns.node("def", row, definition.val);
1604
+ columns.node("def.xrefs", row, strings(definition.xrefs));
1605
+ }
1606
+ const comments = strings(meta.comments);
1607
+ if (comments.length > 0) {
1608
+ columns.node("comment", row, comments.join("\n"));
1609
+ }
1610
+ const subsets = strings(meta.subsets).map((s) => vocabulary.id(s));
1611
+ if (subsets.length > 0) {
1612
+ columns.node("subset", row, subsets);
1613
+ }
1614
+ const xrefs = strings(meta.xrefs);
1615
+ if (xrefs.length > 0) {
1616
+ columns.node("xref", row, xrefs);
1617
+ }
1618
+ if (Array.isArray(meta.synonyms) && meta.synonyms.length > 0) {
1619
+ columns.node(
1620
+ "synonym",
1621
+ row,
1622
+ meta.synonyms.filter(isJsonObject).map((s) => ({
1623
+ text: typeof s.val === "string" ? s.val : null,
1624
+ scope: typeof s.pred === "string" ? synonymScopeOf(s.pred) : null,
1625
+ type: typeof s.synonymType === "string" ? vocabulary.id(s.synonymType) : null,
1626
+ xrefs: strings(s.xrefs)
1627
+ }))
1628
+ );
1629
+ }
1630
+ if (typeof meta.deprecated === "boolean") {
1631
+ columns.node("is_obsolete", row, meta.deprecated);
1632
+ }
1633
+ writePropertyValues(columns, vocabulary, basicValues(meta), row);
1634
+ for (const key of Object.keys(meta)) {
1635
+ if (!META_KEYS.has(key)) {
1636
+ unrecognized[`meta.${key}`] = meta[key];
1637
+ }
1638
+ }
1639
+ }
1640
+ function writePropertyValues(columns, vocabulary, values, row) {
1641
+ const lists = /* @__PURE__ */ new Map();
1642
+ const others = [];
1643
+ for (const { pred, val } of values) {
1644
+ const tag = OBOGRAPHS_PREDICATE_TAGS.get(pred);
1645
+ if (tag === "shorthand") {
1646
+ continue;
1647
+ }
1648
+ if (tag === void 0) {
1649
+ others.push({ relation: vocabulary.id(pred), value: val, datatype: null });
1650
+ } else if (OBO_NODE_COLUMNS[tag].dtype === "list") {
1651
+ const list = lists.get(tag) ?? [];
1652
+ list.push(tag === "alt_id" ? val : vocabulary.id(val));
1653
+ lists.set(tag, list);
1654
+ } else {
1655
+ columns.node(tag, row, val);
1656
+ }
1657
+ }
1658
+ for (const [tag, list] of lists) {
1659
+ columns.node(tag, row, list);
1660
+ }
1661
+ if (others.length > 0) {
1662
+ columns.node("property_value", row, others);
1663
+ }
1664
+ }
1665
+ function badValue(ctx, message) {
1666
+ ctx.report.error("validation-error", JSON_ISSUE.BAD_VALUE, message);
1667
+ }
1668
+ function readEdges(ctx, columns, vocabulary, edges) {
1669
+ const { report, sink } = ctx;
1670
+ const propertyEdges = [];
1671
+ const dangling = [];
1672
+ let dropped = 0;
1673
+ const endpoint = (raw, element) => {
1674
+ const id = ctx.coerceId(vocabulary.id(raw), element);
1675
+ if (id === null || sink.indexOf(id) !== INVALID_INDEX) {
1676
+ return id;
1677
+ }
1678
+ dangling.push(raw);
1679
+ if (!ctx.options.addMissingNodes) {
1680
+ return null;
1681
+ }
1682
+ const row = ctx.pushNode(id, element);
1683
+ if (row >= 0) {
1684
+ columns.node(PLACEHOLDER_COLUMN, row, true);
1685
+ }
1686
+ return id;
1687
+ };
1688
+ for (let i = 0; i < edges.length; i++) {
1689
+ const element = `edges[${i}]`;
1690
+ const record = edges[i];
1691
+ if (!isJsonObject(record)) {
1692
+ ctx.badElement("edge", element);
1693
+ continue;
1694
+ }
1695
+ const subKey = hasKey(record, "sub") ? "sub" : "subj";
1696
+ if (subKey === "subj" && hasKey(record, "subj")) {
1697
+ report.warnOnce(
1698
+ "coercion",
1699
+ JSON_ISSUE.OBOGRAPHS_SUBJ,
1700
+ `${element} uses the outdated key subj; read as sub`,
1701
+ {
1702
+ element
1703
+ }
1704
+ );
1705
+ }
1706
+ const sub = record[subKey];
1707
+ const { obj, pred } = record;
1708
+ if (typeof sub !== "string" || typeof obj !== "string") {
1709
+ ctx.missingEndpoint(element, typeof sub === "string" ? "obj" : "sub");
1710
+ continue;
1711
+ }
1712
+ const between = typeof pred === "string" && PROPERTY_PREDICATES.has(pred) || vocabulary.isProperty(sub) || vocabulary.isProperty(obj);
1713
+ if (between && ctx.json.typedefs === "metadata") {
1714
+ propertyEdges.push(record);
1715
+ continue;
1716
+ }
1717
+ try {
1718
+ const u = endpoint(sub, `${element}.sub`);
1719
+ const v = endpoint(obj, `${element}.obj`);
1720
+ if (u === null || v === null) {
1721
+ dropped++;
1722
+ ctx.countSkipped("edge");
1723
+ continue;
1724
+ }
1725
+ const edge = ctx.pushEdge(u, v, "directed", void 0, element);
1726
+ if (typeof pred === "string") {
1727
+ columns.edge("relation", edge, vocabulary.relation(pred));
1728
+ } else {
1729
+ badValue(ctx, `${element} has no pred; its relation is unset`);
1730
+ }
1731
+ if (isJsonObject(record.meta)) {
1732
+ columns.edge("meta", edge, record.meta);
1733
+ }
1734
+ } catch (err) {
1735
+ ctx.skip(err, "edge", element);
1736
+ }
1737
+ }
1738
+ if (dangling.length > 0) {
1739
+ const shown = [...new Set(dangling)].slice(0, 5).join(", ");
1740
+ const action = ctx.options.addMissingNodes ? "each became a placeholder node (graphty.placeholder)" : `${dropped} edge(s) to them were dropped (addMissingNodes false)`;
1741
+ report.warning(
1742
+ "validation-error",
1743
+ JSON_ISSUE.DANGLING_REFERENCE,
1744
+ `${new Set(dangling).size} edge endpoint(s) missing from nodes: ${shown}; ${action}`,
1745
+ { element: dangling[0] }
1746
+ );
1747
+ }
1748
+ return propertyEdges;
1749
+ }
1359
1750
  const JSON_ISSUE = Object.freeze({
1360
1751
  /** The text is empty or whitespace (fatal). */
1361
1752
  EMPTY_INPUT: EMPTY_INPUT_CODE,
@@ -1391,8 +1782,18 @@ const JSON_ISSUE = Object.freeze({
1391
1782
  ID_MERGED: ID_MERGED_CODE,
1392
1783
  /** Edge ids of mixed JSON types were stored as text. */
1393
1784
  EDGE_ID_STRINGIFIED: "W_EDGE_ID_STRINGIFIED",
1394
- /** A JGF `graphs` array holds more than one graph; only `graphIndex` is read. */
1785
+ /** A JGF or OBO Graphs `graphs` array holds more than one graph; only the chosen one is read. */
1395
1786
  MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
1787
+ /** `graphIndex` is beyond the `graphs` array, or `graphName` names none of its graphs (fatal). */
1788
+ GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
1789
+ /** `graphName` names more than one graph of the `graphs` array (fatal). */
1790
+ AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE,
1791
+ /** obographs: an edge uses the outdated `subj` key of the OBO Graphs README; it is read as `sub`. */
1792
+ OBOGRAPHS_SUBJ: "W_JSON_OBOGRAPHS_SUBJ",
1793
+ /** obographs: an edge endpoint missing from `nodes` (a placeholder node is made, or the edge dropped under addMissingNodes false). */
1794
+ DANGLING_REFERENCE: DANGLING_REFERENCE_CODE,
1795
+ /** The document is longer than one JavaScript string can hold (fatal; category unsupported). */
1796
+ TOO_LARGE: TOO_LARGE_CODE,
1396
1797
  /** JGF hyperedges under the "error" policy. */
1397
1798
  HYPEREDGE: HYPEREDGE_CODE,
1398
1799
  /** JGF hyperedges skipped under the default "skip" policy. */
@@ -1475,6 +1876,14 @@ function resolveJsonOptions(options) {
1475
1876
  if (!Number.isInteger(graphIndex) || graphIndex < 0) {
1476
1877
  throw unsupportedOption("graphIndex", graphIndex, ["a non-negative integer"]);
1477
1878
  }
1879
+ const oboIds = o.oboIds ?? "curie";
1880
+ if (oboIds !== "curie" && oboIds !== "iri") {
1881
+ throw unsupportedOption("oboIds", oboIds, ["curie", "iri"]);
1882
+ }
1883
+ const typedefs = o.typedefs ?? "metadata";
1884
+ if (typedefs !== "metadata" && typedefs !== "nodes") {
1885
+ throw unsupportedOption("typedefs", typedefs, ["metadata", "nodes"]);
1886
+ }
1478
1887
  const nodesPath = pathOption("nodesPath", o.nodesPath);
1479
1888
  const edgesPath = pathOption("edgesPath", o.edgesPath);
1480
1889
  if ((nodesPath !== null || edgesPath !== null) && dialect !== "auto" && !PATH_DIALECTS.has(dialect)) {
@@ -1488,6 +1897,9 @@ function resolveJsonOptions(options) {
1488
1897
  targetKey: keyOption("targetKey", o.targetKey),
1489
1898
  indexLinks,
1490
1899
  graphIndex,
1900
+ choice: { graphIndex: o.graphIndex, graphName: o.graphName },
1901
+ oboIds,
1902
+ typedefs,
1491
1903
  nodesPath,
1492
1904
  edgesPath
1493
1905
  };
@@ -2059,7 +2471,7 @@ class ImportContext {
2059
2471
  */
2060
2472
  declareEdgeIds(edges, read, name, unique) {
2061
2473
  let numbers = 0;
2062
- let strings = 0;
2474
+ let strings2 = 0;
2063
2475
  for (const edge of edges) {
2064
2476
  if (!isJsonObject(edge)) {
2065
2477
  continue;
@@ -2068,16 +2480,16 @@ class ImportContext {
2068
2480
  if (typeof raw === "number") {
2069
2481
  numbers++;
2070
2482
  } else if (typeof raw === "string") {
2071
- strings++;
2483
+ strings2++;
2072
2484
  }
2073
2485
  }
2074
- if (numbers + strings === 0) {
2486
+ if (numbers + strings2 === 0) {
2075
2487
  return null;
2076
2488
  }
2077
- const dtype = strings === 0 ? "f64" : "string";
2489
+ const dtype = strings2 === 0 ? "f64" : "string";
2078
2490
  const columnName = uniqueColumnName(name, "id", (candidate) => this.edges.taken(candidate));
2079
2491
  const handle = this.edges.declare({ name: columnName, dtype, role: "id", nullable: true, unique });
2080
- const stringify = strings > 0 && numbers > 0;
2492
+ const stringify = strings2 > 0 && numbers > 0;
2081
2493
  const seen = unique ? /* @__PURE__ */ new Set() : null;
2082
2494
  if (stringify) {
2083
2495
  this.report.warning(
@@ -3029,31 +3441,84 @@ function importJgf(ctx, root) {
3029
3441
  ctx.setMeta(shape, { name: label, ...ctx.weightOriginPatch() });
3030
3442
  }
3031
3443
  function jgfGraphOf(ctx, root) {
3444
+ return isJsonObject(root.graph) ? root.graph : chosenGraph(ctx, root, "JGF");
3445
+ }
3446
+ function chosenGraph(ctx, root, what) {
3032
3447
  const { report } = ctx;
3033
- if (isJsonObject(root.graph)) {
3034
- return root.graph;
3035
- }
3036
3448
  const graphs = arraySection(root.graphs, "graphs", report) ?? [];
3037
3449
  if (graphs.length === 0) {
3038
- report.fail(JSON_ISSUE.SHAPE, "a JGF document needs a graph object or a non-empty graphs array");
3450
+ report.fail(JSON_ISSUE.SHAPE, `a ${what} document needs a graph object or a non-empty graphs array`);
3039
3451
  }
3452
+ const index = ctx.json.all === true ? ctx.json.graphIndex : chooseGraph(graphs.map(graphNameOf), ctx.json.choice, report);
3040
3453
  if (graphs.length > 1 && ctx.json.all !== true) {
3041
3454
  report.warning(
3042
3455
  "unsupported",
3043
3456
  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`,
3457
+ `the document holds ${graphs.length} graphs; only graphs[${index}] is read (${graphs.length - 1} skipped), importAll() reads every one`,
3045
3458
  { element: "graphs" }
3046
3459
  );
3047
3460
  }
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];
3461
+ const graph = graphs[index];
3052
3462
  if (!isJsonObject(graph)) {
3053
- return report.fail(JSON_ISSUE.SHAPE, `graphs[${ctx.json.graphIndex}] is not an object`);
3463
+ return report.fail(JSON_ISSUE.SHAPE, `graphs[${index}] is not an object`);
3054
3464
  }
3055
3465
  return graph;
3056
3466
  }
3467
+ function graphNameOf(graph) {
3468
+ if (!isJsonObject(graph)) {
3469
+ return null;
3470
+ }
3471
+ for (const key of ["id", "label", "lbl"]) {
3472
+ if (typeof graph[key] === "string") {
3473
+ return graph[key];
3474
+ }
3475
+ }
3476
+ return null;
3477
+ }
3478
+ function countOf(section) {
3479
+ if (section === void 0 || section === null) {
3480
+ return 0;
3481
+ }
3482
+ if (Array.isArray(section)) {
3483
+ return section.length;
3484
+ }
3485
+ return isJsonObject(section) ? Object.keys(section).length : null;
3486
+ }
3487
+ function listingsOf(root, dialect) {
3488
+ if ((dialect === "jgf" || dialect === "obographs") && isJsonObject(root)) {
3489
+ if (Array.isArray(root.graphs) && !isJsonObject(root.graph)) {
3490
+ return root.graphs.map((graph, index) => ({
3491
+ index,
3492
+ name: graphNameOf(graph),
3493
+ nodes: isJsonObject(graph) ? countOf(graph.nodes) : null,
3494
+ edges: isJsonObject(graph) ? countOf(graph.edges) : null
3495
+ }));
3496
+ }
3497
+ if (isJsonObject(root.graph)) {
3498
+ const { graph } = root;
3499
+ return [{ index: 0, name: graphNameOf(graph), nodes: countOf(graph.nodes), edges: countOf(graph.edges) }];
3500
+ }
3501
+ }
3502
+ return [{ index: 0, name: null, nodes: null, edges: null }];
3503
+ }
3504
+ const MAX_TEXT_LENGTH = 2 ** 29 - 24;
3505
+ async function readJsonText(input, report, options) {
3506
+ if (typeof input === "string") {
3507
+ return readText(input, report, options);
3508
+ }
3509
+ const parts = [];
3510
+ let length = 0;
3511
+ for await (const chunk of textChunks(input, report, options)) {
3512
+ length += chunk.length;
3513
+ if (length > MAX_TEXT_LENGTH) {
3514
+ const message = `the document is longer than ${MAX_TEXT_LENGTH} characters, the most one JavaScript string holds`;
3515
+ report.error("unsupported", JSON_ISSUE.TOO_LARGE, message);
3516
+ throw report.abort(message, { code: JSON_ISSUE.TOO_LARGE });
3517
+ }
3518
+ parts.push(chunk);
3519
+ }
3520
+ return parts.length === 1 ? parts[0] : parts.join("");
3521
+ }
3057
3522
  function pushJgfNode(ctx, id, record, labelColumn, element) {
3058
3523
  const index = ctx.pushNode(id, element);
3059
3524
  if (index < 0) {
@@ -3408,11 +3873,27 @@ const jsonImporter = Object.freeze({
3408
3873
  const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
3409
3874
  const json = resolveJsonOptions(options);
3410
3875
  const report = new ImportReportBuilder("json", resolved.errorLimit);
3411
- const text = await readText(input, report, resolved);
3876
+ const text = await readJsonText(input, report, resolved);
3412
3877
  const { root, dialect } = documentOf(parseDocument(text, report), json, report);
3413
3878
  readGraph(root, dialect, sink, report, resolved, json, options);
3414
3879
  return report.finish();
3415
3880
  },
3881
+ /**
3882
+ * List the graphs of a JSON document without importing them: each entry of a JGF or OBO
3883
+ * Graphs `graphs` array with its name (`id`, else its label) and node and edge counts; any
3884
+ * other document holds one graph.
3885
+ * @param input - the text, bytes or stream
3886
+ * @param options - format-specific and common options
3887
+ * @returns one listing per graph
3888
+ */
3889
+ async listGraphs(input, options) {
3890
+ const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
3891
+ const json = resolveJsonOptions(options);
3892
+ const report = new ImportReportBuilder("json", resolved.errorLimit);
3893
+ const text = await readJsonText(input, report, resolved);
3894
+ const { root, dialect } = documentOf(parseDocument(text, report), json, report);
3895
+ return listingsOf(root, dialect);
3896
+ },
3416
3897
  /**
3417
3898
  * Read every graph of a JSON document: each entry of a JGF `graphs` array into its own sink;
3418
3899
  * any other document holds one graph.
@@ -3425,9 +3906,9 @@ const jsonImporter = Object.freeze({
3425
3906
  const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
3426
3907
  const json = resolveJsonOptions(options);
3427
3908
  const first = new ImportReportBuilder("json", resolved.errorLimit);
3428
- const text = await readText(input, first, resolved);
3909
+ const text = await readJsonText(input, first, resolved);
3429
3910
  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;
3911
+ const graphs = listingsOf(root, dialect).length;
3431
3912
  const reports = [];
3432
3913
  for (let i = 0; i < Math.max(graphs, 1); i++) {
3433
3914
  const report = i === 0 ? first : new ImportReportBuilder("json", resolved.errorLimit);
@@ -3439,7 +3920,7 @@ const jsonImporter = Object.freeze({
3439
3920
  });
3440
3921
  function readGraph(root, dialect, sink, report, resolved, json, options) {
3441
3922
  const ctx = new ImportContext(sink, report, resolved, json, options?.defaultDirected !== void 0);
3442
- reportSinkOptions(sink, options, report);
3923
+ reportSinkOptions(sink, options, report, dialect === "obographs");
3443
3924
  reportUnusedOptions(options, report, USED_OPTIONS);
3444
3925
  if (dialect === "cytoscape") {
3445
3926
  importCytoscape(ctx, root);
@@ -3467,6 +3948,9 @@ function readGraph(root, dialect, sink, report, resolved, json, options) {
3467
3948
  case "tree":
3468
3949
  importTree(ctx, doc);
3469
3950
  break;
3951
+ case "obographs":
3952
+ importObographs(ctx, doc);
3953
+ break;
3470
3954
  default: {
3471
3955
  const name = dialect;
3472
3956
  throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {
@@ -3487,4 +3971,4 @@ export {
3487
3971
  jsonCapabilities as j,
3488
3972
  sniffJsonDialect as s
3489
3973
  };
3490
- //# sourceMappingURL=importer-B6rKRxzv.js.map
3974
+ //# sourceMappingURL=importer-Du5crN9l.js.map