@graphty/graph-io 0.3.40 → 0.3.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/chunks/{children-CL3Cy0ez.js → children-CA4coBi0.js} +36 -27
- package/dist/chunks/children-CA4coBi0.js.map +1 -0
- package/dist/chunks/{choose-CS3fvGT1.js → choose-DvpbdRuz.js} +2 -2
- package/dist/chunks/{choose-CS3fvGT1.js.map → choose-DvpbdRuz.js.map} +1 -1
- package/dist/chunks/{constants-DqKZT6cH.js → constants-fT3E8JYY.js} +2 -2
- package/dist/chunks/{constants-DqKZT6cH.js.map → constants-fT3E8JYY.js.map} +1 -1
- package/dist/chunks/{escape-BJRLd3vJ.js → escape-B62ZGM_a.js} +2 -2
- package/dist/chunks/{escape-BJRLd3vJ.js.map → escape-B62ZGM_a.js.map} +1 -1
- package/dist/chunks/{export-B-aVCwyG.js → export-rKLeGQMo.js} +404 -243
- package/dist/chunks/export-rKLeGQMo.js.map +1 -0
- package/dist/chunks/{exporter-6ocL_9x_.js → exporter-8hLNemFk.js} +4 -4
- package/dist/chunks/{exporter-6ocL_9x_.js.map → exporter-8hLNemFk.js.map} +1 -1
- package/dist/chunks/{exporter-BVXnV9-9.js → exporter-BWwEuOPT.js} +29 -17
- package/dist/chunks/exporter-BWwEuOPT.js.map +1 -0
- package/dist/chunks/{exporter-Bcp-l4Bo.js → exporter-CpRyHZ3H.js} +574 -475
- package/dist/chunks/exporter-CpRyHZ3H.js.map +1 -0
- package/dist/chunks/{exporter-SFYkDzD0.js → exporter-yLxKBvLE.js} +23 -20
- package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-yLxKBvLE.js.map} +1 -1
- package/dist/chunks/{importer-DcKB97kW.js → importer-BbNb7eNg.js} +111 -71
- package/dist/chunks/importer-BbNb7eNg.js.map +1 -0
- package/dist/chunks/{importer-D_srHyj6.js → importer-C98dpSH4.js} +5 -5
- package/dist/chunks/{importer-D_srHyj6.js.map → importer-C98dpSH4.js.map} +1 -1
- package/dist/chunks/{importer-BJlGAOht.js → importer-D794XUTe.js} +3 -3
- package/dist/chunks/{importer-BJlGAOht.js.map → importer-D794XUTe.js.map} +1 -1
- package/dist/chunks/{importer-AjwOJrn2.js → importer-Dz7tI8Sa.js} +202 -181
- package/dist/chunks/importer-Dz7tI8Sa.js.map +1 -0
- package/dist/chunks/{importer-DEp_fYjf.js → importer-Ht7LNg8L.js} +92 -64
- package/dist/chunks/importer-Ht7LNg8L.js.map +1 -0
- package/dist/chunks/{importer-CPqnIY8m.js → importer-WrGRZb0N.js} +42 -29
- package/dist/chunks/importer-WrGRZb0N.js.map +1 -0
- package/dist/chunks/{importer-BpuS3J8C.js → importer-xZ-3suY1.js} +74 -61
- package/dist/chunks/importer-xZ-3suY1.js.map +1 -0
- package/dist/chunks/{json-elements-DpO6AXIc.js → json-elements-DKSqpNW1.js} +3 -3
- package/dist/chunks/{json-elements-DpO6AXIc.js.map → json-elements-DKSqpNW1.js.map} +1 -1
- package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-CtRXjg_i.js} +2 -2
- package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-CtRXjg_i.js.map} +1 -1
- package/dist/chunks/{records-r3SFHz11.js → records-Cu4bxYOm.js} +69 -36
- package/dist/chunks/records-Cu4bxYOm.js.map +1 -0
- package/dist/chunks/{text-Bjfa1xYL.js → text-DHOJXnED.js} +20 -11
- package/dist/chunks/text-DHOJXnED.js.map +1 -0
- package/dist/csv.js +323 -241
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +1 -1
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +195 -97
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +170 -149
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +27 -27
- package/dist/graphml.js +2 -2
- package/dist/json.js +1 -1
- package/dist/neo4j.js +103 -65
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/children.d.ts.map +1 -1
- package/dist/src/children.js +50 -27
- package/dist/src/children.js.map +1 -1
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +86 -42
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +358 -184
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/lists.js +30 -15
- package/dist/src/common/lists.js.map +1 -1
- package/dist/src/common/text.d.ts +7 -0
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +15 -7
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +36 -19
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +288 -146
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +129 -80
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +78 -36
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +11 -8
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.d.ts +39 -0
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +112 -66
- package/dist/src/formats/dot/tokenizer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +65 -37
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +138 -50
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +51 -35
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +103 -58
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +17 -8
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +34 -24
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +17 -13
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/tree.js +23 -14
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +22 -10
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +54 -20
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +38 -21
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +536 -343
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/obographs.js +29 -17
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +73 -43
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +37 -16
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +21 -11
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts.map +1 -1
- package/dist/src/formats/obo/exporter.js +18 -7
- package/dist/src/formats/obo/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +142 -73
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +26 -15
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +85 -58
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +7 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +79 -40
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +38 -12
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +38 -18
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/sniff.js +109 -57
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/children.ts +54 -27
- package/src/common/attributes.ts +118 -49
- package/src/common/input.ts +439 -197
- package/src/common/lists.ts +29 -15
- package/src/common/text.ts +24 -15
- package/src/common/zip.ts +47 -18
- package/src/formats/csv/exporter.ts +377 -175
- package/src/formats/csv/importer.ts +155 -84
- package/src/formats/csv/records.ts +90 -35
- package/src/formats/cys/tables.ts +11 -10
- package/src/formats/dot/tokenizer.ts +118 -68
- package/src/formats/gexf/exporter.ts +67 -36
- package/src/formats/gexf/importer.ts +173 -63
- package/src/formats/gml/exporter.ts +78 -82
- package/src/formats/gml/importer.ts +119 -68
- package/src/formats/gml/syntax.ts +18 -8
- package/src/formats/graphml/exporter.ts +44 -24
- package/src/formats/graphml/importer.ts +18 -11
- package/src/formats/graphml/tree.ts +22 -12
- package/src/formats/graphml/yfiles.ts +23 -10
- package/src/formats/json/dialect.ts +61 -22
- package/src/formats/json/exporter.ts +40 -21
- package/src/formats/json/importer.ts +701 -385
- package/src/formats/json/obographs.ts +35 -15
- package/src/formats/neo4j/exporter.ts +79 -41
- package/src/formats/neo4j/header.ts +50 -16
- package/src/formats/neo4j/importer.ts +21 -10
- package/src/formats/obo/exporter.ts +34 -17
- package/src/formats/pajek/exporter.ts +180 -117
- package/src/formats/pajek/importer.ts +27 -15
- package/src/formats/pajek/syntax.ts +99 -58
- package/src/formats/xgmml/emit.ts +88 -40
- package/src/formats/xgmml/exporter.ts +40 -12
- package/src/formats/xgmml/importer.ts +45 -23
- package/src/sniff.ts +115 -59
- package/dist/chunks/children-CL3Cy0ez.js.map +0 -1
- package/dist/chunks/export-B-aVCwyG.js.map +0 -1
- package/dist/chunks/exporter-BVXnV9-9.js.map +0 -1
- package/dist/chunks/exporter-Bcp-l4Bo.js.map +0 -1
- package/dist/chunks/importer-AjwOJrn2.js.map +0 -1
- package/dist/chunks/importer-BpuS3J8C.js.map +0 -1
- package/dist/chunks/importer-CPqnIY8m.js.map +0 -1
- package/dist/chunks/importer-DEp_fYjf.js.map +0 -1
- package/dist/chunks/importer-DcKB97kW.js.map +0 -1
- package/dist/chunks/records-r3SFHz11.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
|
@@ -237,19 +237,7 @@ export function membersOf(
|
|
|
237
237
|
const edges: EdgeRec[] = [];
|
|
238
238
|
let dangling = 0;
|
|
239
239
|
for (const member of graph.members) {
|
|
240
|
-
|
|
241
|
-
if (member.kind === "node") {
|
|
242
|
-
nodes.push(member);
|
|
243
|
-
} else {
|
|
244
|
-
edges.push(member);
|
|
245
|
-
}
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
const id = localRef(member.href);
|
|
249
|
-
let target: NodeRec | EdgeRec | undefined;
|
|
250
|
-
if (id !== null) {
|
|
251
|
-
target = member.kind === "node" ? nodeById.get(id) : edgeById.get(id);
|
|
252
|
-
}
|
|
240
|
+
const target = resolveMember(member, nodeById, edgeById);
|
|
253
241
|
if (target === undefined) {
|
|
254
242
|
dangling++;
|
|
255
243
|
} else if (target.kind === "node") {
|
|
@@ -261,6 +249,29 @@ export function membersOf(
|
|
|
261
249
|
return { nodes, edges, dangling };
|
|
262
250
|
}
|
|
263
251
|
|
|
252
|
+
/**
|
|
253
|
+
* The record a graph member stands for: itself when declared in place, else the node or edge its
|
|
254
|
+
* local `#id` reference names.
|
|
255
|
+
* @param member - the member
|
|
256
|
+
* @param nodeById - the first node declaration of each id
|
|
257
|
+
* @param edgeById - the first edge declaration of each id
|
|
258
|
+
* @returns the record, or undefined for a cross-file, malformed or dangling reference
|
|
259
|
+
*/
|
|
260
|
+
function resolveMember(
|
|
261
|
+
member: NodeRec | EdgeRec,
|
|
262
|
+
nodeById: ReadonlyMap<string, NodeRec>,
|
|
263
|
+
edgeById: ReadonlyMap<string, EdgeRec>,
|
|
264
|
+
): NodeRec | EdgeRec | undefined {
|
|
265
|
+
if (member.href === null) {
|
|
266
|
+
return member;
|
|
267
|
+
}
|
|
268
|
+
const id = localRef(member.href);
|
|
269
|
+
if (id === null) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
return member.kind === "node" ? nodeById.get(id) : edgeById.get(id);
|
|
273
|
+
}
|
|
274
|
+
|
|
264
275
|
/**
|
|
265
276
|
* The first declaration of each id.
|
|
266
277
|
* @param records - node or edge records
|
|
@@ -276,6 +287,42 @@ function indexById<T extends NodeRec | EdgeRec>(records: readonly T[]): Map<stri
|
|
|
276
287
|
return out;
|
|
277
288
|
}
|
|
278
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Whether an edge repeats one already seen, remembering it otherwise: a repeat has the same id,
|
|
292
|
+
* or without one the same label and endpoints.
|
|
293
|
+
* @param seen - the keys seen so far, or null when repeats are kept
|
|
294
|
+
* @param record - the edge
|
|
295
|
+
* @returns true for a repeat
|
|
296
|
+
*/
|
|
297
|
+
function isRepeat(seen: Set<string> | null, record: EdgeRec): boolean {
|
|
298
|
+
if (seen === null) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const key =
|
|
302
|
+
record.id ?? (record.label === null ? null : JSON.stringify([record.label, record.source, record.target]));
|
|
303
|
+
if (key === null) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
if (seen.has(key)) {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
seen.add(key);
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Whether a graph's att is one of its values: not RDF metadata, not the unnamed holder of a
|
|
315
|
+
* nested graph.
|
|
316
|
+
* @param att - the att
|
|
317
|
+
* @returns true to write it to the graph table
|
|
318
|
+
*/
|
|
319
|
+
function isGraphValueAtt(att: AttRec): boolean {
|
|
320
|
+
if (att.name !== null && META_ATTS.has(att.name)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
return !(att.hasGraph && att.name === null);
|
|
324
|
+
}
|
|
325
|
+
|
|
279
326
|
/** Per-node state while emitting. */
|
|
280
327
|
interface NodeRow {
|
|
281
328
|
readonly id: string;
|
|
@@ -731,16 +778,7 @@ export class XgmmlEmitter {
|
|
|
731
778
|
if (graph.owner !== null && isGroup(graph.owner)) {
|
|
732
779
|
continue;
|
|
733
780
|
}
|
|
734
|
-
|
|
735
|
-
if (member.href === null) {
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
const id = localRef(member.href);
|
|
739
|
-
const known = id !== null && (member.kind === "node" ? this.rows.has(id) : edgeIds.has(id));
|
|
740
|
-
if (!known) {
|
|
741
|
-
dangling++;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
781
|
+
dangling += this.danglingReferences(graph, edgeIds);
|
|
744
782
|
}
|
|
745
783
|
if (dangling > 0) {
|
|
746
784
|
this.report.warning(
|
|
@@ -751,6 +789,27 @@ export class XgmmlEmitter {
|
|
|
751
789
|
}
|
|
752
790
|
}
|
|
753
791
|
|
|
792
|
+
/**
|
|
793
|
+
* How many `xlink:href` members of a graph name no node or edge of the document.
|
|
794
|
+
* @param graph - the graph
|
|
795
|
+
* @param edgeIds - the id of every edge
|
|
796
|
+
* @returns the count
|
|
797
|
+
*/
|
|
798
|
+
private danglingReferences(graph: GraphRec, edgeIds: ReadonlySet<string | null>): number {
|
|
799
|
+
let dangling = 0;
|
|
800
|
+
for (const member of graph.members) {
|
|
801
|
+
if (member.href === null) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const id = localRef(member.href);
|
|
805
|
+
const known = id !== null && (member.kind === "node" ? this.rows.has(id) : edgeIds.has(id));
|
|
806
|
+
if (!known) {
|
|
807
|
+
dangling++;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return dangling;
|
|
811
|
+
}
|
|
812
|
+
|
|
754
813
|
/**
|
|
755
814
|
* Write the per-node values: label, attributes, XML attributes, graphics, positions, nested
|
|
756
815
|
* network pointers and subgraph memberships.
|
|
@@ -966,17 +1025,10 @@ export class XgmmlEmitter {
|
|
|
966
1025
|
this.decideNameLabel("edge", records);
|
|
967
1026
|
for (const record of records) {
|
|
968
1027
|
const where = { line: record.line, element: record.id ?? record.label };
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
if (dedupe !== null && key !== null) {
|
|
974
|
-
if (dedupe.has(key)) {
|
|
975
|
-
repeats++;
|
|
976
|
-
firstRepeat ??= where;
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
dedupe.add(key);
|
|
1028
|
+
if (isRepeat(dedupe, record)) {
|
|
1029
|
+
repeats++;
|
|
1030
|
+
firstRepeat ??= where;
|
|
1031
|
+
continue;
|
|
980
1032
|
}
|
|
981
1033
|
const index = this.addEdge(record, header, ids, where);
|
|
982
1034
|
this.checkAbort();
|
|
@@ -1244,13 +1296,9 @@ export class XgmmlEmitter {
|
|
|
1244
1296
|
}
|
|
1245
1297
|
}
|
|
1246
1298
|
for (const att of g.atts) {
|
|
1247
|
-
if (
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
if (att.hasGraph && att.name === null) {
|
|
1251
|
-
continue;
|
|
1299
|
+
if (isGraphValueAtt(att)) {
|
|
1300
|
+
this.graphColumns.addAtt(0, att, where);
|
|
1252
1301
|
}
|
|
1253
|
-
this.graphColumns.addAtt(0, att, where);
|
|
1254
1302
|
}
|
|
1255
1303
|
this.graphColumns.write(this.sink, () => 0);
|
|
1256
1304
|
const json: Record<string, unknown> = { ...this.extras.graphJson };
|
|
@@ -394,6 +394,17 @@ function slotOf(column: Column, domain: "graph" | "node" | "edge", hasLabelRole:
|
|
|
394
394
|
if (domain !== "node") {
|
|
395
395
|
return "att";
|
|
396
396
|
}
|
|
397
|
+
return nodeSlotOf(column);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Where a node column goes when it is no XML attribute, label or graphics: one of the node-only
|
|
402
|
+
* slots, else a typed att.
|
|
403
|
+
* @param column - the column
|
|
404
|
+
* @returns the slot
|
|
405
|
+
*/
|
|
406
|
+
function nodeSlotOf(column: Column): ColumnWrite["kind"] {
|
|
407
|
+
const { meta } = column;
|
|
397
408
|
if (meta.name === Z_COLUMN && meta.dtype === "f64" && meta.role === null) {
|
|
398
409
|
return "z";
|
|
399
410
|
}
|
|
@@ -1004,18 +1015,7 @@ function cellOf(column: Column | null, row: number): unknown {
|
|
|
1004
1015
|
* @returns the coordinates as XML attributes
|
|
1005
1016
|
*/
|
|
1006
1017
|
function coordsOf(plan: Plan, zColumn: Column | null, node: number): [string, string][] {
|
|
1007
|
-
const out
|
|
1008
|
-
const xyz = cellOf(plan.position, node);
|
|
1009
|
-
if (xyz !== null) {
|
|
1010
|
-
const [x, y, z] = Array.from(xyz as ArrayLike<number>);
|
|
1011
|
-
const fmt = plan.position?.dtype === "f32" ? formatF32 : formatF64;
|
|
1012
|
-
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
1013
|
-
out.push(["x", fmt(x)], ["y", fmt(y === 0 ? 0 : -y)]);
|
|
1014
|
-
if (zColumn === null && z !== undefined && z !== 0 && Number.isFinite(z)) {
|
|
1015
|
-
out.push(["z", fmt(z)]);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1018
|
+
const out = positionCoordsOf(plan, zColumn, node);
|
|
1019
1019
|
const z = cellOf(zColumn, node);
|
|
1020
1020
|
if (typeof z === "number") {
|
|
1021
1021
|
out.push(["z", formatF64(z)]);
|
|
@@ -1023,6 +1023,34 @@ function coordsOf(plan: Plan, zColumn: Column | null, node: number): [string, st
|
|
|
1023
1023
|
return out;
|
|
1024
1024
|
}
|
|
1025
1025
|
|
|
1026
|
+
/**
|
|
1027
|
+
* The graphics coordinates a node's position gives: x, y negated back to screen coordinates, and
|
|
1028
|
+
* z when no z column carries it.
|
|
1029
|
+
* @param plan - the plan
|
|
1030
|
+
* @param zColumn - the z column, or null
|
|
1031
|
+
* @param node - the node
|
|
1032
|
+
* @returns the coordinates as XML attributes (none for an unset or non-finite position)
|
|
1033
|
+
*/
|
|
1034
|
+
function positionCoordsOf(plan: Plan, zColumn: Column | null, node: number): [string, string][] {
|
|
1035
|
+
const xyz = cellOf(plan.position, node);
|
|
1036
|
+
if (xyz === null) {
|
|
1037
|
+
return [];
|
|
1038
|
+
}
|
|
1039
|
+
const [x, y, z] = Array.from(xyz as ArrayLike<number>);
|
|
1040
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
1041
|
+
return [];
|
|
1042
|
+
}
|
|
1043
|
+
const fmt = plan.position?.dtype === "f32" ? formatF32 : formatF64;
|
|
1044
|
+
const out: [string, string][] = [
|
|
1045
|
+
["x", fmt(x)],
|
|
1046
|
+
["y", fmt(y === 0 ? 0 : -y)],
|
|
1047
|
+
];
|
|
1048
|
+
if (zColumn === null && z !== undefined && z !== 0 && Number.isFinite(z)) {
|
|
1049
|
+
out.push(["z", fmt(z)]);
|
|
1050
|
+
}
|
|
1051
|
+
return out;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1026
1054
|
/**
|
|
1027
1055
|
* The group marker and the nested `<graph>` of a group node: its own id, label and atts, and an
|
|
1028
1056
|
* `xlink:href` to each child.
|
|
@@ -174,23 +174,7 @@ export async function parseXgmml(
|
|
|
174
174
|
common: ResolvedImportOptions,
|
|
175
175
|
options: XgmmlImportOptions | undefined,
|
|
176
176
|
): Promise<XgmmlDocument> {
|
|
177
|
-
const repairs
|
|
178
|
-
bareAmpersand: booleanOption("repairBareAmpersands", options?.repairBareAmpersands, false)
|
|
179
|
-
? (line): void => {
|
|
180
|
-
report.warning("parse-error", XGMML_ISSUE.AMPERSAND_REPAIRED, "a bare & was read as &", { line });
|
|
181
|
-
}
|
|
182
|
-
: undefined,
|
|
183
|
-
surrogatePair: booleanOption("pairSurrogateReferences", options?.pairSurrogateReferences, false)
|
|
184
|
-
? (line): void => {
|
|
185
|
-
report.warning(
|
|
186
|
-
"parse-error",
|
|
187
|
-
XGMML_ISSUE.SURROGATE_PAIRED,
|
|
188
|
-
"two surrogate character references were joined into one character",
|
|
189
|
-
{ line },
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
: undefined,
|
|
193
|
-
};
|
|
177
|
+
const repairs = repairsOf(options, report);
|
|
194
178
|
const parser = new XgmmlParser(report);
|
|
195
179
|
const seen = { content: false };
|
|
196
180
|
try {
|
|
@@ -200,12 +184,7 @@ export async function parseXgmml(
|
|
|
200
184
|
repairs,
|
|
201
185
|
);
|
|
202
186
|
} catch (err) {
|
|
203
|
-
|
|
204
|
-
if (!seen.content) {
|
|
205
|
-
report.fail(XGMML_ISSUE.EMPTY_INPUT, "the input is empty");
|
|
206
|
-
}
|
|
207
|
-
report.fail(XGMML_ISSUE.XML_SYNTAX, err.message, { line: err.line });
|
|
208
|
-
}
|
|
187
|
+
failOnSyntax(err, seen.content, report);
|
|
209
188
|
throw err;
|
|
210
189
|
}
|
|
211
190
|
const doc = parser.document();
|
|
@@ -220,6 +199,49 @@ export async function parseXgmml(
|
|
|
220
199
|
return doc;
|
|
221
200
|
}
|
|
222
201
|
|
|
202
|
+
/**
|
|
203
|
+
* The XML repairs the options switch on, each reporting where it repaired.
|
|
204
|
+
* @param options - the XGMML options
|
|
205
|
+
* @param report - the report
|
|
206
|
+
* @returns the repairs
|
|
207
|
+
*/
|
|
208
|
+
function repairsOf(options: XgmmlImportOptions | undefined, report: ImportReportBuilder): XmlRepairs {
|
|
209
|
+
return {
|
|
210
|
+
bareAmpersand: booleanOption("repairBareAmpersands", options?.repairBareAmpersands, false)
|
|
211
|
+
? (line): void => {
|
|
212
|
+
report.warning("parse-error", XGMML_ISSUE.AMPERSAND_REPAIRED, "a bare & was read as &", { line });
|
|
213
|
+
}
|
|
214
|
+
: undefined,
|
|
215
|
+
surrogatePair: booleanOption("pairSurrogateReferences", options?.pairSurrogateReferences, false)
|
|
216
|
+
? (line): void => {
|
|
217
|
+
report.warning(
|
|
218
|
+
"parse-error",
|
|
219
|
+
XGMML_ISSUE.SURROGATE_PAIRED,
|
|
220
|
+
"two surrogate character references were joined into one character",
|
|
221
|
+
{ line },
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
: undefined,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Fail the report on an XML syntax error: an input with no content is empty, any other is
|
|
230
|
+
* malformed. Any other error is left to the caller.
|
|
231
|
+
* @param err - the error the tokenizer threw
|
|
232
|
+
* @param content - whether non-whitespace text was seen
|
|
233
|
+
* @param report - the report
|
|
234
|
+
*/
|
|
235
|
+
function failOnSyntax(err: unknown, content: boolean, report: ImportReportBuilder): void {
|
|
236
|
+
if (!(err instanceof XmlSyntaxError)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (!content) {
|
|
240
|
+
report.fail(XGMML_ISSUE.EMPTY_INPUT, "the input is empty");
|
|
241
|
+
}
|
|
242
|
+
report.fail(XGMML_ISSUE.XML_SYNTAX, err.message, { line: err.line });
|
|
243
|
+
}
|
|
244
|
+
|
|
223
245
|
/**
|
|
224
246
|
* Pass text chunks through while noting non-whitespace content.
|
|
225
247
|
* @param chunks - the chunks
|
package/src/sniff.ts
CHANGED
|
@@ -356,40 +356,57 @@ function skeletonOf(text: string): unknown {
|
|
|
356
356
|
}
|
|
357
357
|
const root: Record<string, unknown> = {};
|
|
358
358
|
for (const key of rootKeys) {
|
|
359
|
-
|
|
360
|
-
case "graph":
|
|
361
|
-
case "options":
|
|
362
|
-
root[key] = objectOf(keys.get(key));
|
|
363
|
-
break;
|
|
364
|
-
case "graphs": {
|
|
365
|
-
// the keys of any node or edge of the first graph: OBO Graphs or JGF (design 1.6)
|
|
366
|
-
const nodes = keys.get("graphs[0].nodes[]");
|
|
367
|
-
const edges = keys.get("graphs[0].edges[]");
|
|
368
|
-
const graph: Record<string, unknown> = {};
|
|
369
|
-
if (nodes !== undefined) {
|
|
370
|
-
graph.nodes = [objectOf(nodes)];
|
|
371
|
-
}
|
|
372
|
-
if (edges !== undefined) {
|
|
373
|
-
graph.edges = [objectOf(edges)];
|
|
374
|
-
}
|
|
375
|
-
root[key] = nodes === undefined && edges === undefined ? [] : [graph];
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
case "nodes":
|
|
379
|
-
case "edges":
|
|
380
|
-
case "links": {
|
|
381
|
-
const first = keys.get(`${key}[0]`);
|
|
382
|
-
root[key] = first === undefined ? [] : [objectOf(first)];
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
default:
|
|
386
|
-
root[key] = true;
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
359
|
+
root[key] = placeholderOf(key, keys);
|
|
389
360
|
}
|
|
390
361
|
return root;
|
|
391
362
|
}
|
|
392
363
|
|
|
364
|
+
/**
|
|
365
|
+
* The placeholder value of a root key: the shape the dialect rule tests that key for.
|
|
366
|
+
* @param key - the root key
|
|
367
|
+
* @param keys - the key sets by path
|
|
368
|
+
* @returns the placeholder
|
|
369
|
+
*/
|
|
370
|
+
function placeholderOf(key: string, keys: ReadonlyMap<KeyPath, Set<string>>): unknown {
|
|
371
|
+
switch (key) {
|
|
372
|
+
case "graph":
|
|
373
|
+
case "options":
|
|
374
|
+
return objectOf(keys.get(key));
|
|
375
|
+
case "graphs":
|
|
376
|
+
return graphsPlaceholder(keys);
|
|
377
|
+
case "nodes":
|
|
378
|
+
case "edges":
|
|
379
|
+
case "links": {
|
|
380
|
+
const first = keys.get(`${key}[0]`);
|
|
381
|
+
return first === undefined ? [] : [objectOf(first)];
|
|
382
|
+
}
|
|
383
|
+
default:
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The placeholder of `graphs`: the keys of any node or edge of the first graph, which tell OBO
|
|
390
|
+
* Graphs from JGF (design 1.6).
|
|
391
|
+
* @param keys - the key sets by path
|
|
392
|
+
* @returns `[]` when neither was seen, else one graph holding them
|
|
393
|
+
*/
|
|
394
|
+
function graphsPlaceholder(keys: ReadonlyMap<KeyPath, Set<string>>): unknown[] {
|
|
395
|
+
const nodes = keys.get("graphs[0].nodes[]");
|
|
396
|
+
const edges = keys.get("graphs[0].edges[]");
|
|
397
|
+
if (nodes === undefined && edges === undefined) {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
const graph: Record<string, unknown> = {};
|
|
401
|
+
if (nodes !== undefined) {
|
|
402
|
+
graph.nodes = [objectOf(nodes)];
|
|
403
|
+
}
|
|
404
|
+
if (edges !== undefined) {
|
|
405
|
+
graph.edges = [objectOf(edges)];
|
|
406
|
+
}
|
|
407
|
+
return [graph];
|
|
408
|
+
}
|
|
409
|
+
|
|
393
410
|
/**
|
|
394
411
|
* An object holding placeholder values for a key set: `{}` for the keys the rule tests with
|
|
395
412
|
* isJsonObject (`attributes`, `data`), `true` otherwise.
|
|
@@ -424,34 +441,6 @@ function scanKeys(text: string): Map<KeyPath, Set<string>> {
|
|
|
424
441
|
const counts: number[] = [];
|
|
425
442
|
let pendingKey: string | null = null;
|
|
426
443
|
let expectingKey = false;
|
|
427
|
-
const pathOf = (): KeyPath | null => {
|
|
428
|
-
const depth = kinds.length;
|
|
429
|
-
if (depth === 1) {
|
|
430
|
-
return kinds[0] === "object" ? "" : null;
|
|
431
|
-
}
|
|
432
|
-
if (depth === 2 && kinds[0] === "array" && kinds[1] === "object" && labels[1] === "0") {
|
|
433
|
-
return "nodes[0]";
|
|
434
|
-
}
|
|
435
|
-
if (depth === 2 && kinds[0] === "object" && kinds[1] === "object") {
|
|
436
|
-
return labels[1] === "graph" || labels[1] === "options" ? labels[1] : null;
|
|
437
|
-
}
|
|
438
|
-
if (
|
|
439
|
-
depth === 5 &&
|
|
440
|
-
kinds.join() === "object,array,object,array,object" &&
|
|
441
|
-
labels[1] === "graphs" &&
|
|
442
|
-
labels[2] === "0" &&
|
|
443
|
-
(labels[3] === "nodes" || labels[3] === "edges")
|
|
444
|
-
) {
|
|
445
|
-
return labels[3] === "nodes" ? "graphs[0].nodes[]" : "graphs[0].edges[]";
|
|
446
|
-
}
|
|
447
|
-
if (depth === 3 && kinds[0] === "object" && kinds[1] === "array" && kinds[2] === "object") {
|
|
448
|
-
const section = labels[1];
|
|
449
|
-
if ((section === "nodes" || section === "edges" || section === "links") && labels[2] === "0") {
|
|
450
|
-
return `${section}[0]`;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
return null;
|
|
454
|
-
};
|
|
455
444
|
let i = 0;
|
|
456
445
|
const n = text.length;
|
|
457
446
|
while (i < n) {
|
|
@@ -487,7 +476,7 @@ function scanKeys(text: string): Map<KeyPath, Set<string>> {
|
|
|
487
476
|
pendingKey = null;
|
|
488
477
|
} else if (ch === ":") {
|
|
489
478
|
if (pendingKey !== null) {
|
|
490
|
-
const path =
|
|
479
|
+
const path = keyPathOf(kinds, labels);
|
|
491
480
|
if (path !== null) {
|
|
492
481
|
let set = found.get(path);
|
|
493
482
|
if (set === undefined) {
|
|
@@ -510,6 +499,73 @@ function scanKeys(text: string): Map<KeyPath, Set<string>> {
|
|
|
510
499
|
return found;
|
|
511
500
|
}
|
|
512
501
|
|
|
502
|
+
/**
|
|
503
|
+
* The watched path of the innermost open object, from the container stack.
|
|
504
|
+
* @param kinds - the kind of each open container, outermost first
|
|
505
|
+
* @param labels - the key or index each open container sits under
|
|
506
|
+
* @returns the path, or null when keys there are not watched
|
|
507
|
+
*/
|
|
508
|
+
function keyPathOf(kinds: readonly ("object" | "array")[], labels: readonly string[]): KeyPath | null {
|
|
509
|
+
switch (kinds.length) {
|
|
510
|
+
case 1:
|
|
511
|
+
return kinds[0] === "object" ? "" : null;
|
|
512
|
+
case 2:
|
|
513
|
+
return nestedObjectPath(kinds, labels);
|
|
514
|
+
case 3:
|
|
515
|
+
return sectionElementPath(kinds, labels);
|
|
516
|
+
case 5:
|
|
517
|
+
return graphsMemberPath(kinds, labels);
|
|
518
|
+
default:
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* At depth 2: the first element of a top-level array, or the `graph` / `options` object.
|
|
525
|
+
* @param kinds - the kind of each open container, outermost first
|
|
526
|
+
* @param labels - the key or index each open container sits under
|
|
527
|
+
* @returns the path, or null
|
|
528
|
+
*/
|
|
529
|
+
function nestedObjectPath(kinds: readonly ("object" | "array")[], labels: readonly string[]): KeyPath | null {
|
|
530
|
+
if (kinds[0] === "array" && kinds[1] === "object" && labels[1] === "0") {
|
|
531
|
+
return "nodes[0]";
|
|
532
|
+
}
|
|
533
|
+
if (kinds[0] === "object" && kinds[1] === "object") {
|
|
534
|
+
return labels[1] === "graph" || labels[1] === "options" ? labels[1] : null;
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* At depth 3: the first element of the root's `nodes`, `edges` or `links` array.
|
|
541
|
+
* @param kinds - the kind of each open container, outermost first
|
|
542
|
+
* @param labels - the key or index each open container sits under
|
|
543
|
+
* @returns the path, or null
|
|
544
|
+
*/
|
|
545
|
+
function sectionElementPath(kinds: readonly ("object" | "array")[], labels: readonly string[]): KeyPath | null {
|
|
546
|
+
if (kinds[0] !== "object" || kinds[1] !== "array" || kinds[2] !== "object" || labels[2] !== "0") {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
const section = labels[1];
|
|
550
|
+
return section === "nodes" || section === "edges" || section === "links" ? `${section}[0]` : null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* At depth 5: any node or edge of the first graph of the root's `graphs` array.
|
|
555
|
+
* @param kinds - the kind of each open container, outermost first
|
|
556
|
+
* @param labels - the key or index each open container sits under
|
|
557
|
+
* @returns the path, or null
|
|
558
|
+
*/
|
|
559
|
+
function graphsMemberPath(kinds: readonly ("object" | "array")[], labels: readonly string[]): KeyPath | null {
|
|
560
|
+
if (kinds.join() !== "object,array,object,array,object" || labels[1] !== "graphs" || labels[2] !== "0") {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
if (labels[3] === "nodes") {
|
|
564
|
+
return "graphs[0].nodes[]";
|
|
565
|
+
}
|
|
566
|
+
return labels[3] === "edges" ? "graphs[0].edges[]" : null;
|
|
567
|
+
}
|
|
568
|
+
|
|
513
569
|
/**
|
|
514
570
|
* The index of the closing quote of a JSON string starting after its opening quote.
|
|
515
571
|
* @param text - the text
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"children-CL3Cy0ez.js","sources":["../../src/children.ts"],"sourcesContent":["/**\n * The children CSR over a containment column: containment\n * (GEXF `pid` / `<parents>`, Cytoscape `parent`, DOT clusters, GraphML nested graphs) is a node\n * column with the `parent` role (u32, refersTo node) or the `parents` role (list of u32) and never\n * enters the CSR of the snapshot. Importers and exporters that need to walk containers top-down\n * (nested subgraphs, cluster blocks, parent lists) build this inverse index once: for every node\n * `u`, `children[rowPtr[u] .. rowPtr[u + 1])` are its children in ascending index order, `roots`\n * are the nodes with no parent, and `depthFirst()` is the pre-order the nested writers use.\n *\n * The helper is deliberately outside the core (7.1: it computes a property the arrays do not\n * already imply and only io needs it). A reference is ignored when it is unset, INVALID_INDEX, out\n * of range, or the node itself; a cycle (a parent chain that never reaches a root) is reported\n * through `unreachable` and its members are appended to the depth-first order in index order so\n * every node is written exactly once.\n */\n\nimport { type Column, type GraphSnapshot, INVALID_INDEX } from \"@graphty/graph-format\";\n\n/**\n * The role of the column a children CSR is built over.\n * @category Plugin helpers\n */\nexport type ContainmentRole = \"parent\" | \"parents\";\n\n/**\n * How the CSR is built: from a role column of the snapshot (default `parent`, then `parents`) or an explicit column.\n * @category Plugin helpers\n */\nexport interface ChildrenOptions {\n /**\n * The column to read: a role name (`parent` for the u32 single-parent column, `parents` for\n * the list column) or a column object of either shape; \"auto\" (default) takes the `parent`\n * role column when the snapshot has one, else `parents`, else no containment; null builds\n * the empty CSR (every node a root) whatever the snapshot holds.\n */\n readonly column?: ContainmentRole | Column | \"auto\" | null | undefined;\n}\n\n/**\n * One depth-first traversal of the containment forest.\n * @category Plugin helpers\n */\nexport interface DepthFirstOrder {\n /** Every node index exactly once: roots in index order, each followed by its descendants, cycle members last. */\n readonly order: Uint32Array;\n /** The depth of every node in the traversal (0 for a root or a cycle member forced to the top). */\n readonly depth: Uint32Array;\n /** Whether `order` differs from `0..n-1`. */\n readonly reordered: boolean;\n}\n\n/**\n * The children CSR of a snapshot's containment column: the inverse\n * of the parent relation as a compressed sparse row structure over node indices.\n * @category Plugin helpers\n */\nexport class ChildrenCsr {\n /** The number of nodes (the row count of the CSR). */\n readonly nodeCount: number;\n /** `rowPtr[u] .. rowPtr[u + 1]` is the range of `children` holding the children of `u`; length nodeCount + 1. */\n readonly rowPtr: Uint32Array;\n /** Child node indices, ascending within each parent's range. */\n readonly children: Uint32Array;\n /** The nodes with no (valid) parent, in index order. */\n readonly roots: Uint32Array;\n /** Every (child, parent) reference kept; equal to `children.length`. */\n readonly edgeCount: number;\n /** References dropped because they were INVALID_INDEX, out of range or self-references. */\n readonly dropped: number;\n /** Nodes whose parent chain never reaches a root (members of a cycle), counted once each. */\n readonly unreachable: number;\n /** The column the CSR was built from, or null when the snapshot has no containment. */\n readonly column: Column | null;\n\n private readonly parentCount: Uint32Array;\n\n /**\n * Build the CSR; use childrenCsr() rather than the constructor.\n * @param nodeCount - the node count\n * @param rowPtr - the row pointers\n * @param children - the child list\n * @param parentCount - parents per node (0 for a root)\n * @param dropped - references ignored\n * @param column - the source column, or null\n */\n constructor(\n nodeCount: number,\n rowPtr: Uint32Array,\n children: Uint32Array,\n parentCount: Uint32Array,\n dropped: number,\n column: Column | null,\n ) {\n this.nodeCount = nodeCount;\n this.rowPtr = rowPtr;\n this.children = children;\n this.parentCount = parentCount;\n this.edgeCount = children.length;\n this.dropped = dropped;\n this.column = column;\n let rootCount = 0;\n for (let u = 0; u < nodeCount; u++) {\n if (parentCount[u] === 0) {\n rootCount++;\n }\n }\n const roots = new Uint32Array(rootCount);\n let r = 0;\n for (let u = 0; u < nodeCount; u++) {\n if (parentCount[u] === 0) {\n roots[r++] = u;\n }\n }\n this.roots = roots;\n this.unreachable = nodeCount - this.reachableCount();\n }\n\n /**\n * The children of a node, as a view into `children` (do not modify).\n * @param node - the node index\n * @returns the child indices in ascending order; empty for a leaf\n */\n childrenOf(node: number): Uint32Array {\n return this.children.subarray(this.rowPtr[node], this.rowPtr[node + 1]);\n }\n\n /**\n * How many children a node has.\n * @param node - the node index\n * @returns the count\n */\n childCount(node: number): number {\n return this.rowPtr[node + 1] - this.rowPtr[node];\n }\n\n /**\n * Whether a node is a container (has at least one child).\n * @param node - the node index\n * @returns true when it has children\n */\n hasChildren(node: number): boolean {\n return this.rowPtr[node + 1] > this.rowPtr[node];\n }\n\n /**\n * How many parents a node has: 0 for a root, 1 under a `parent` column, any number under `parents`.\n * @param node - the node index\n * @returns the parent count\n */\n parentCountOf(node: number): number {\n return this.parentCount[node];\n }\n\n /**\n * Whether a node has no parent.\n * @param node - the node index\n * @returns true for a root\n */\n isRoot(node: number): boolean {\n return this.parentCount[node] === 0;\n }\n\n /**\n * Whether the snapshot has any containment at all.\n * @returns true when at least one child reference was kept\n */\n isEmpty(): boolean {\n return this.edgeCount === 0;\n }\n\n /**\n * A pre-order depth-first traversal from the roots in index order (children in ascending\n * order), then every node not yet visited (a cycle member, or a multi-parent node reached\n * only through a cycle) in index order at depth 0. Each node appears exactly once, so nested\n * writers emit every element once even for a `parents` column that shares a child.\n * @returns the order, the depth of every node and whether the order differs from index order\n */\n depthFirst(): DepthFirstOrder {\n const n = this.nodeCount;\n const order = new Uint32Array(n);\n const depth = new Uint32Array(n);\n const visited = new Uint8Array(n);\n const stackNode: number[] = [];\n const stackCursor: number[] = [];\n let position = 0;\n let reordered = false;\n const place = (u: number, d: number): void => {\n visited[u] = 1;\n if (u !== position) {\n reordered = true;\n }\n order[position++] = u;\n depth[u] = d;\n };\n const visit = (root: number): void => {\n place(root, 0);\n stackNode.push(root);\n stackCursor.push(this.rowPtr[root]);\n while (stackNode.length > 0) {\n const top = stackNode.length - 1;\n const u = stackNode[top];\n const cursor = stackCursor[top];\n if (cursor >= this.rowPtr[u + 1]) {\n stackNode.pop();\n stackCursor.pop();\n continue;\n }\n stackCursor[top] = cursor + 1;\n const v = this.children[cursor];\n if (visited[v] === 1) {\n continue;\n }\n place(v, stackNode.length);\n stackNode.push(v);\n stackCursor.push(this.rowPtr[v]);\n }\n };\n for (const root of this.roots) {\n visit(root);\n }\n for (let u = 0; u < n; u++) {\n if (visited[u] === 0) {\n visit(u);\n }\n }\n return { order, depth, reordered };\n }\n\n /**\n * The nodes reachable from the roots by following child references.\n * @returns how many nodes a traversal from the roots reaches\n */\n private reachableCount(): number {\n const visited = new Uint8Array(this.nodeCount);\n // every node is pushed at most once, so a stack of nodeCount entries never overflows\n const stack = new Uint32Array(this.nodeCount);\n let top = 0;\n let count = 0;\n for (const root of this.roots) {\n visited[root] = 1;\n count++;\n stack[top++] = root;\n while (top > 0) {\n const u = stack[--top];\n for (let k = this.rowPtr[u]; k < this.rowPtr[u + 1]; k++) {\n const v = this.children[k];\n if (visited[v] === 0) {\n visited[v] = 1;\n count++;\n stack[top++] = v;\n }\n }\n }\n }\n return count;\n }\n}\n\n/**\n * Build the children CSR of a snapshot.\n * @param snapshot - the snapshot\n * @param options - which column to read; the `parent` role, then `parents`, by default\n * @returns the CSR; empty (every node a root) when the snapshot has no containment column\n * @category Plugin helpers\n */\nexport function childrenCsr(snapshot: GraphSnapshot, options: ChildrenOptions = {}): ChildrenCsr {\n const column = resolveColumn(snapshot, options.column === undefined ? \"auto\" : options.column);\n return childrenFromColumn(snapshot.nodeCount, column);\n}\n\n/**\n * Build the children CSR over one containment column without a snapshot (an importer that holds\n * the column before freezing, a test).\n * @param nodeCount - the node count the references index into\n * @param column - a u32 `parent`-shaped column, a list-of-u32 `parents`-shaped column, or null for none\n * @returns the CSR\n * @category Plugin helpers\n */\nexport function childrenFromColumn(nodeCount: number, column: Column | null): ChildrenCsr {\n const rowPtr = new Uint32Array(nodeCount + 1);\n const parentCount = new Uint32Array(nodeCount);\n let dropped = 0;\n const references: number[] = [];\n if (column !== null) {\n const rows = Math.min(column.length, nodeCount);\n const keep = (child: number, parent: number): void => {\n if (!Number.isInteger(parent) || parent === INVALID_INDEX || parent >= nodeCount || parent === child) {\n dropped++;\n return;\n }\n references.push(child, parent);\n rowPtr[parent + 1]++;\n parentCount[child]++;\n };\n if (column.dtype === \"u32\") {\n const { data } = column;\n for (let i = 0; i < rows; i++) {\n if (column.isSet(i)) {\n keep(i, data[i]);\n }\n }\n } else if (column.dtype === \"list\") {\n for (let i = 0; i < rows; i++) {\n if (!column.isSet(i)) {\n continue;\n }\n for (const item of column.sliceOf(i)) {\n if (typeof item === \"number\") {\n keep(i, item);\n } else {\n dropped++;\n }\n }\n }\n } else {\n throw new TypeError(\n `a containment column must be u32 or a list of u32, found ${column.dtype} column \"${column.meta.name}\"`,\n );\n }\n }\n for (let u = 0; u < nodeCount; u++) {\n rowPtr[u + 1] += rowPtr[u];\n }\n const children = new Uint32Array(rowPtr[nodeCount]);\n const fill = rowPtr.slice(0, nodeCount);\n // references were collected in ascending child order, so each parent's range fills ascending\n for (let k = 0; k < references.length; k += 2) {\n children[fill[references[k + 1]]++] = references[k];\n }\n return new ChildrenCsr(nodeCount, rowPtr, children, parentCount, dropped, column);\n}\n\n/**\n * The column a ChildrenOptions selects.\n * @param snapshot - the snapshot\n * @param choice - the option value\n * @returns the column, or null when the snapshot has none of that shape\n */\nfunction resolveColumn(snapshot: GraphSnapshot, choice: ContainmentRole | Column | \"auto\" | null): Column | null {\n if (typeof choice !== \"string\") {\n return choice;\n }\n if (choice === \"parent\" || choice === \"parents\") {\n return snapshot.nodes.byRole(choice);\n }\n return snapshot.nodes.byRole(\"parent\") ?? snapshot.nodes.byRole(\"parents\");\n}\n"],"names":[],"mappings":"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"}
|