@graphty/graph-io 0.3.39 → 0.3.41
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/dist/chunks/{choose-CS3fvGT1.js → choose-CwFQamH9.js} +2 -2
- package/dist/chunks/{choose-CS3fvGT1.js.map → choose-CwFQamH9.js.map} +1 -1
- package/dist/chunks/{constants-DqKZT6cH.js → constants-Cf4Nn6tw.js} +2 -2
- package/dist/chunks/{constants-DqKZT6cH.js.map → constants-Cf4Nn6tw.js.map} +1 -1
- package/dist/chunks/{escape-BpYMQZVa.js → escape-DBb8zuNE.js} +4 -3
- package/dist/chunks/escape-DBb8zuNE.js.map +1 -0
- package/dist/chunks/{export-B-aVCwyG.js → export-DD3Lvw-F.js} +173 -116
- package/dist/chunks/export-DD3Lvw-F.js.map +1 -0
- package/dist/chunks/{exporter-CNJZF_BN.js → exporter-BqtmlxNr.js} +545 -459
- package/dist/chunks/exporter-BqtmlxNr.js.map +1 -0
- package/dist/chunks/{exporter-BqKs1Q12.js → exporter-CS35R6Fg.js} +6 -5
- package/dist/chunks/exporter-CS35R6Fg.js.map +1 -0
- package/dist/chunks/{exporter-DXqCPNrI.js → exporter-D8r1N2dU.js} +67 -42
- package/dist/chunks/exporter-D8r1N2dU.js.map +1 -0
- package/dist/chunks/{exporter-SFYkDzD0.js → exporter-NMplSnYn.js} +23 -20
- package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-NMplSnYn.js.map} +1 -1
- package/dist/chunks/{importer-CSt-gaYE.js → importer-BKC7AmCA.js} +42 -29
- package/dist/chunks/importer-BKC7AmCA.js.map +1 -0
- package/dist/chunks/{importer-DvFznt5-.js → importer-BdWHmlmG.js} +112 -62
- package/dist/chunks/importer-BdWHmlmG.js.map +1 -0
- package/dist/chunks/{importer-CqgYq4Ld.js → importer-BfbzWk6k.js} +202 -181
- package/dist/chunks/importer-BfbzWk6k.js.map +1 -0
- package/dist/chunks/{importer-PVXkeoaS.js → importer-BmaZcskq.js} +79 -66
- package/dist/chunks/importer-BmaZcskq.js.map +1 -0
- package/dist/chunks/{importer-B7Ir269L.js → importer-DviEsHy4.js} +118 -72
- package/dist/chunks/importer-DviEsHy4.js.map +1 -0
- package/dist/chunks/{importer--tQQsY4W.js → importer-S-abxJHn.js} +5 -5
- package/dist/chunks/{importer--tQQsY4W.js.map → importer-S-abxJHn.js.map} +1 -1
- package/dist/chunks/{importer-BfJ1jnNr.js → importer-r29cWnEf.js} +28 -13
- package/dist/chunks/importer-r29cWnEf.js.map +1 -0
- package/dist/chunks/{json-elements-CXs4z1iL.js → json-elements-DdF-etPT.js} +7 -5
- package/dist/chunks/{json-elements-CXs4z1iL.js.map → json-elements-DdF-etPT.js.map} +1 -1
- package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-BC0zyPhj.js} +2 -2
- package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-BC0zyPhj.js.map} +1 -1
- package/dist/chunks/{records-C9jd7yLD.js → records-Uc3LkABx.js} +150 -106
- package/dist/chunks/records-Uc3LkABx.js.map +1 -0
- package/dist/chunks/{text-Bjfa1xYL.js → text-Bh9SaAob.js} +20 -11
- package/dist/chunks/text-Bh9SaAob.js.map +1 -0
- package/dist/csv.js +67 -49
- 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 +199 -99
- 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 +26 -26
- 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/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +34 -9
- 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 +180 -102
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +4 -2
- package/dist/src/common/json-elements.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/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +2 -1
- package/dist/src/common/xml.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 +77 -25
- 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 +16 -6
- 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 +156 -110
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +108 -53
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +2 -1
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +28 -6
- package/dist/src/formats/cx2/importer.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/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +69 -39
- 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 +36 -25
- 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 +20 -15
- 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/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 +13 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +112 -50
- 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 +92 -34
- 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/common/attributes.ts +59 -19
- package/src/common/input.ts +216 -102
- package/src/common/json-elements.ts +4 -2
- package/src/common/text.ts +24 -15
- package/src/common/xml.ts +2 -1
- package/src/common/zip.ts +47 -18
- package/src/formats/csv/exporter.ts +103 -39
- package/src/formats/csv/importer.ts +17 -6
- package/src/formats/csv/records.ts +162 -109
- package/src/formats/cx/importer.ts +140 -58
- package/src/formats/cx2/exporter.ts +2 -1
- package/src/formats/cx2/importer.ts +34 -10
- package/src/formats/cys/tables.ts +11 -10
- package/src/formats/gexf/exporter.ts +71 -38
- 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 +46 -27
- package/src/formats/graphml/importer.ts +21 -13
- package/src/formats/graphml/tree.ts +22 -12
- package/src/formats/graphml/yfiles.ts +23 -10
- 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 +122 -50
- package/src/formats/xgmml/exporter.ts +103 -35
- package/src/formats/xgmml/importer.ts +45 -23
- package/src/sniff.ts +115 -59
- package/dist/chunks/escape-BpYMQZVa.js.map +0 -1
- package/dist/chunks/export-B-aVCwyG.js.map +0 -1
- package/dist/chunks/exporter-BqKs1Q12.js.map +0 -1
- package/dist/chunks/exporter-CNJZF_BN.js.map +0 -1
- package/dist/chunks/exporter-DXqCPNrI.js.map +0 -1
- package/dist/chunks/importer-B7Ir269L.js.map +0 -1
- package/dist/chunks/importer-BfJ1jnNr.js.map +0 -1
- package/dist/chunks/importer-CSt-gaYE.js.map +0 -1
- package/dist/chunks/importer-CqgYq4Ld.js.map +0 -1
- package/dist/chunks/importer-DvFznt5-.js.map +0 -1
- package/dist/chunks/importer-PVXkeoaS.js.map +0 -1
- package/dist/chunks/records-C9jd7yLD.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
|
@@ -207,6 +207,16 @@ function localRef(href: string): string | null {
|
|
|
207
207
|
return href.startsWith("#") ? href.slice(1) : null;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* The id a member names: its own id, else the id its local `#id` reference names.
|
|
212
|
+
* @param id - the member's id attribute, or null
|
|
213
|
+
* @param href - the member's xlink:href, or null
|
|
214
|
+
* @returns the id, or null when neither names one
|
|
215
|
+
*/
|
|
216
|
+
function memberRef(id: string | null, href: string | null): string | null {
|
|
217
|
+
return id ?? (href === null ? null : localRef(href));
|
|
218
|
+
}
|
|
219
|
+
|
|
210
220
|
/**
|
|
211
221
|
* The nodes and edges a graph holds: its direct members, references resolved.
|
|
212
222
|
* @param doc - the document
|
|
@@ -227,19 +237,7 @@ export function membersOf(
|
|
|
227
237
|
const edges: EdgeRec[] = [];
|
|
228
238
|
let dangling = 0;
|
|
229
239
|
for (const member of graph.members) {
|
|
230
|
-
|
|
231
|
-
if (member.kind === "node") {
|
|
232
|
-
nodes.push(member);
|
|
233
|
-
} else {
|
|
234
|
-
edges.push(member);
|
|
235
|
-
}
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
const id = localRef(member.href);
|
|
239
|
-
let target: NodeRec | EdgeRec | undefined;
|
|
240
|
-
if (id !== null) {
|
|
241
|
-
target = member.kind === "node" ? nodeById.get(id) : edgeById.get(id);
|
|
242
|
-
}
|
|
240
|
+
const target = resolveMember(member, nodeById, edgeById);
|
|
243
241
|
if (target === undefined) {
|
|
244
242
|
dangling++;
|
|
245
243
|
} else if (target.kind === "node") {
|
|
@@ -251,6 +249,29 @@ export function membersOf(
|
|
|
251
249
|
return { nodes, edges, dangling };
|
|
252
250
|
}
|
|
253
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
|
+
|
|
254
275
|
/**
|
|
255
276
|
* The first declaration of each id.
|
|
256
277
|
* @param records - node or edge records
|
|
@@ -266,6 +287,42 @@ function indexById<T extends NodeRec | EdgeRec>(records: readonly T[]): Map<stri
|
|
|
266
287
|
return out;
|
|
267
288
|
}
|
|
268
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
|
+
|
|
269
326
|
/** Per-node state while emitting. */
|
|
270
327
|
interface NodeRow {
|
|
271
328
|
readonly id: string;
|
|
@@ -584,12 +641,16 @@ export class XgmmlEmitter {
|
|
|
584
641
|
}
|
|
585
642
|
const lower = t.toLowerCase();
|
|
586
643
|
const readable = lower === "true" || lower === "yes" || lower === "false" || lower === "no";
|
|
644
|
+
let readAs: string;
|
|
645
|
+
if (readable) {
|
|
646
|
+
readAs = lower === "true" || lower === "yes" ? "1" : "0";
|
|
647
|
+
} else {
|
|
648
|
+
readAs = fallback ? "directed" : "undirected";
|
|
649
|
+
}
|
|
587
650
|
this.report.warnOnce(
|
|
588
651
|
"validation-error",
|
|
589
652
|
XGMML_ISSUE.BAD_DIRECTED,
|
|
590
|
-
|
|
591
|
-
? `${this.prefix()}directed="${text}" is not 0 or 1; read as ${lower === "true" || lower === "yes" ? "1" : "0"}`
|
|
592
|
-
: `${this.prefix()}directed="${text}" is not 0 or 1; read as ${fallback ? "directed" : "undirected"}`,
|
|
653
|
+
`${this.prefix()}directed="${text}" is not 0 or 1; read as ${readAs}`,
|
|
593
654
|
where,
|
|
594
655
|
`${XGMML_ISSUE.BAD_DIRECTED}:${text}`,
|
|
595
656
|
);
|
|
@@ -717,16 +778,7 @@ export class XgmmlEmitter {
|
|
|
717
778
|
if (graph.owner !== null && isGroup(graph.owner)) {
|
|
718
779
|
continue;
|
|
719
780
|
}
|
|
720
|
-
|
|
721
|
-
if (member.href === null) {
|
|
722
|
-
continue;
|
|
723
|
-
}
|
|
724
|
-
const id = localRef(member.href);
|
|
725
|
-
const known = id !== null && (member.kind === "node" ? this.rows.has(id) : edgeIds.has(id));
|
|
726
|
-
if (!known) {
|
|
727
|
-
dangling++;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
781
|
+
dangling += this.danglingReferences(graph, edgeIds);
|
|
730
782
|
}
|
|
731
783
|
if (dangling > 0) {
|
|
732
784
|
this.report.warning(
|
|
@@ -737,6 +789,27 @@ export class XgmmlEmitter {
|
|
|
737
789
|
}
|
|
738
790
|
}
|
|
739
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
|
+
|
|
740
813
|
/**
|
|
741
814
|
* Write the per-node values: label, attributes, XML attributes, graphics, positions, nested
|
|
742
815
|
* network pointers and subgraph memberships.
|
|
@@ -887,17 +960,17 @@ export class XgmmlEmitter {
|
|
|
887
960
|
});
|
|
888
961
|
}
|
|
889
962
|
|
|
890
|
-
/**
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
963
|
+
/**
|
|
964
|
+
* The names of the subgraphs each node row is a member of.
|
|
965
|
+
* @param subgraphs - the root-level subgraphs
|
|
966
|
+
* @returns the subgraph names by node row index
|
|
967
|
+
*/
|
|
968
|
+
private networkMembership(subgraphs: readonly GraphRec[]): Map<number, string[]> {
|
|
896
969
|
const member = new Map<number, string[]>();
|
|
897
970
|
for (const graph of subgraphs) {
|
|
898
971
|
const name = graph.id ?? graph.label ?? "";
|
|
899
972
|
for (const m of graph.members) {
|
|
900
|
-
const id = m.kind === "node" ? (m.id
|
|
973
|
+
const id = m.kind === "node" ? memberRef(m.id, m.href) : null;
|
|
901
974
|
const row = id === null ? undefined : this.rows.get(id);
|
|
902
975
|
if (row !== undefined && row.index >= 0) {
|
|
903
976
|
const list = member.get(row.index) ?? [];
|
|
@@ -906,6 +979,16 @@ export class XgmmlEmitter {
|
|
|
906
979
|
}
|
|
907
980
|
}
|
|
908
981
|
}
|
|
982
|
+
return member;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/** The `xgmml.networks` membership of nodes in root-level subgraphs of a generic document. */
|
|
986
|
+
private writeNetworks(): void {
|
|
987
|
+
const { subgraphs } = this.doc.root;
|
|
988
|
+
if (subgraphs.length === 0) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const member = this.networkMembership(subgraphs);
|
|
909
992
|
if (member.size === 0) {
|
|
910
993
|
return;
|
|
911
994
|
}
|
|
@@ -942,17 +1025,10 @@ export class XgmmlEmitter {
|
|
|
942
1025
|
this.decideNameLabel("edge", records);
|
|
943
1026
|
for (const record of records) {
|
|
944
1027
|
const where = { line: record.line, element: record.id ?? record.label };
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
if (dedupe !== null && key !== null) {
|
|
950
|
-
if (dedupe.has(key)) {
|
|
951
|
-
repeats++;
|
|
952
|
-
firstRepeat ??= where;
|
|
953
|
-
continue;
|
|
954
|
-
}
|
|
955
|
-
dedupe.add(key);
|
|
1028
|
+
if (isRepeat(dedupe, record)) {
|
|
1029
|
+
repeats++;
|
|
1030
|
+
firstRepeat ??= where;
|
|
1031
|
+
continue;
|
|
956
1032
|
}
|
|
957
1033
|
const index = this.addEdge(record, header, ids, where);
|
|
958
1034
|
this.checkAbort();
|
|
@@ -1220,13 +1296,9 @@ export class XgmmlEmitter {
|
|
|
1220
1296
|
}
|
|
1221
1297
|
}
|
|
1222
1298
|
for (const att of g.atts) {
|
|
1223
|
-
if (
|
|
1224
|
-
|
|
1225
|
-
}
|
|
1226
|
-
if (att.hasGraph && att.name === null) {
|
|
1227
|
-
continue;
|
|
1299
|
+
if (isGraphValueAtt(att)) {
|
|
1300
|
+
this.graphColumns.addAtt(0, att, where);
|
|
1228
1301
|
}
|
|
1229
|
-
this.graphColumns.addAtt(0, att, where);
|
|
1230
1302
|
}
|
|
1231
1303
|
this.graphColumns.write(this.sink, () => 0);
|
|
1232
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
|
}
|
|
@@ -761,6 +772,25 @@ function countNumericIds(snapshot: GraphSnapshot): number {
|
|
|
761
772
|
return n;
|
|
762
773
|
}
|
|
763
774
|
|
|
775
|
+
/**
|
|
776
|
+
* ` name="value"`, or nothing for a null value. The value is written as given, not escaped.
|
|
777
|
+
* @param name - the attribute name
|
|
778
|
+
* @param value - the attribute text, or null
|
|
779
|
+
* @returns the attribute with a leading space, or ""
|
|
780
|
+
*/
|
|
781
|
+
function optionalAttribute(name: string, value: string | null): string {
|
|
782
|
+
return value === null ? "" : ` ${name}="${value}"`;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* ` k="v"` for each entry, each value as text and escaped for an XML attribute.
|
|
787
|
+
* @param entries - the attribute names and values
|
|
788
|
+
* @returns the attributes, each with a leading space
|
|
789
|
+
*/
|
|
790
|
+
function attributeList(entries: readonly (readonly [string, unknown])[]): string {
|
|
791
|
+
return entries.map(([k, v]) => ` ${k}="${escapeXmlAttribute(String(v))}"`).join("");
|
|
792
|
+
}
|
|
793
|
+
|
|
764
794
|
/**
|
|
765
795
|
* The text of a scalar for an att value.
|
|
766
796
|
* @param value - the value
|
|
@@ -846,10 +876,10 @@ function* attsOf(
|
|
|
846
876
|
continue;
|
|
847
877
|
}
|
|
848
878
|
const name = `${indent}<att name="${escapeXmlAttribute(w.name)}"`;
|
|
849
|
-
const types =
|
|
879
|
+
const types = optionalAttribute("type", w.type) + optionalAttribute("cy:type", w.cyType);
|
|
850
880
|
const extra = `${column.meta.extra.hidden === true ? ' cy:hidden="1"' : ""}${column.meta.extra.equation === true ? ' cy:equation="1"' : ""}`;
|
|
851
881
|
if (!isSet) {
|
|
852
|
-
yield `${name}${types}${
|
|
882
|
+
yield `${name}${types}${optionalAttribute("cy:elementType", w.elementType)}${extra}/>\n`;
|
|
853
883
|
continue;
|
|
854
884
|
}
|
|
855
885
|
if (column.dtype === "list") {
|
|
@@ -895,7 +925,7 @@ function* graphicsOf(
|
|
|
895
925
|
if (attrs.length === 0 && nested.length === 0) {
|
|
896
926
|
return;
|
|
897
927
|
}
|
|
898
|
-
const head = `${indent}<graphics${attrs
|
|
928
|
+
const head = `${indent}<graphics${attributeList(attrs)}`;
|
|
899
929
|
if (nested.length === 0) {
|
|
900
930
|
yield `${head}/>\n`;
|
|
901
931
|
return;
|
|
@@ -907,7 +937,7 @@ function* graphicsOf(
|
|
|
907
937
|
for (const point of value) {
|
|
908
938
|
const entries =
|
|
909
939
|
typeof point === "object" && point !== null ? Object.entries(point as Record<string, unknown>) : [];
|
|
910
|
-
yield `${indent} <point${entries
|
|
940
|
+
yield `${indent} <point${attributeList(entries)}/>\n`;
|
|
911
941
|
}
|
|
912
942
|
yield `${indent} </Line>\n`;
|
|
913
943
|
} else {
|
|
@@ -942,7 +972,7 @@ function* jsonAtt(name: string | null, value: unknown, indent: string): Generato
|
|
|
942
972
|
entries.length <= 3 &&
|
|
943
973
|
entries.every(([k]) => /^[a-z]$/.test(k))
|
|
944
974
|
) {
|
|
945
|
-
yield `${indent}<att${nameAttr}${entries
|
|
975
|
+
yield `${indent}<att${nameAttr}${attributeList(entries)}/>\n`;
|
|
946
976
|
return;
|
|
947
977
|
}
|
|
948
978
|
yield `${indent}<att${nameAttr}>\n`;
|
|
@@ -985,18 +1015,7 @@ function cellOf(column: Column | null, row: number): unknown {
|
|
|
985
1015
|
* @returns the coordinates as XML attributes
|
|
986
1016
|
*/
|
|
987
1017
|
function coordsOf(plan: Plan, zColumn: Column | null, node: number): [string, string][] {
|
|
988
|
-
const out
|
|
989
|
-
const xyz = cellOf(plan.position, node);
|
|
990
|
-
if (xyz !== null) {
|
|
991
|
-
const [x, y, z] = Array.from(xyz as ArrayLike<number>);
|
|
992
|
-
const fmt = plan.position?.dtype === "f32" ? formatF32 : formatF64;
|
|
993
|
-
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
994
|
-
out.push(["x", fmt(x)], ["y", fmt(y === 0 ? 0 : -y)]);
|
|
995
|
-
if (zColumn === null && z !== undefined && z !== 0 && Number.isFinite(z)) {
|
|
996
|
-
out.push(["z", fmt(z)]);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1018
|
+
const out = positionCoordsOf(plan, zColumn, node);
|
|
1000
1019
|
const z = cellOf(zColumn, node);
|
|
1001
1020
|
if (typeof z === "number") {
|
|
1002
1021
|
out.push(["z", formatF64(z)]);
|
|
@@ -1004,6 +1023,65 @@ function coordsOf(plan: Plan, zColumn: Column | null, node: number): [string, st
|
|
|
1004
1023
|
return out;
|
|
1005
1024
|
}
|
|
1006
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
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* The group marker and the nested `<graph>` of a group node: its own id, label and atts, and an
|
|
1056
|
+
* `xlink:href` to each child.
|
|
1057
|
+
* @param snapshot - the snapshot
|
|
1058
|
+
* @param sub - the group's subgraph record (empty when it has none)
|
|
1059
|
+
* @param children - the child node indices
|
|
1060
|
+
* @param inner - the indentation inside the node
|
|
1061
|
+
* @yields the group's att elements
|
|
1062
|
+
* @returns nothing
|
|
1063
|
+
*/
|
|
1064
|
+
function* groupAttOf(
|
|
1065
|
+
snapshot: GraphSnapshot,
|
|
1066
|
+
sub: Record<string, unknown>,
|
|
1067
|
+
children: ArrayLike<number> & Iterable<number>,
|
|
1068
|
+
inner: string,
|
|
1069
|
+
): Generator<string, void, undefined> {
|
|
1070
|
+
yield `${inner}<att name="__isGroup" value="1" type="boolean" cy:type="Boolean" cy:hidden="1"/>\n`;
|
|
1071
|
+
const id = typeof sub.id === "string" ? ` id="${escapeXmlAttribute(sub.id)}"` : "";
|
|
1072
|
+
const lbl = typeof sub.label === "string" ? ` label="${escapeXmlAttribute(sub.label)}"` : "";
|
|
1073
|
+
yield `${inner}<att>\n${inner} <graph${id}${lbl}>\n`;
|
|
1074
|
+
const atts =
|
|
1075
|
+
typeof sub.atts === "object" && sub.atts !== null ? Object.entries(sub.atts as Record<string, unknown>) : [];
|
|
1076
|
+
for (const [key, value] of atts) {
|
|
1077
|
+
yield* jsonAtt(key, value, `${inner} `);
|
|
1078
|
+
}
|
|
1079
|
+
for (const child of children) {
|
|
1080
|
+
yield `${inner} <node xlink:href="#${escapeXmlAttribute(idText(snapshot.ids.idOf(child)))}"/>\n`;
|
|
1081
|
+
}
|
|
1082
|
+
yield `${inner} </graph>\n${inner}</att>\n`;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1007
1085
|
/**
|
|
1008
1086
|
* The XML of one node and its group graph, whose members are `xlink:href` references (every
|
|
1009
1087
|
* node is declared at the top level, in index order).
|
|
@@ -1022,7 +1100,8 @@ function* nodeOf(
|
|
|
1022
1100
|
): Generator<string, void, undefined> {
|
|
1023
1101
|
const labelCol = slot(plan.nodes, "label");
|
|
1024
1102
|
const label = cellOf(labelCol, node);
|
|
1025
|
-
|
|
1103
|
+
const labelAttr = label === null ? "" : ` label="${escapeXmlAttribute(scalarText(label))}"`;
|
|
1104
|
+
yield `${indent}<node id="${escapeXmlAttribute(idText(snapshot.ids.idOf(node)))}"${labelAttr}${xmlAttributesOf(plan.nodes, node)}>\n`;
|
|
1026
1105
|
const inner = `${indent} `;
|
|
1027
1106
|
yield* attsOf(plan.nodes, node, node === 0, plan, inner);
|
|
1028
1107
|
const children = plan.reachable[node] === 1 ? plan.hierarchy.childrenOf(node) : [];
|
|
@@ -1030,22 +1109,7 @@ function* nodeOf(
|
|
|
1030
1109
|
const pointer = cellOf(slot(plan.nodes, "pointer"), node);
|
|
1031
1110
|
const subgraph = cellOf(slot(plan.nodes, "subgraph"), node);
|
|
1032
1111
|
if (children.length > 0 || subgraph !== null) {
|
|
1033
|
-
|
|
1034
|
-
yield `${inner}<att name="__isGroup" value="1" type="boolean" cy:type="Boolean" cy:hidden="1"/>\n`;
|
|
1035
|
-
const id = typeof sub.id === "string" ? ` id="${escapeXmlAttribute(sub.id)}"` : "";
|
|
1036
|
-
const lbl = typeof sub.label === "string" ? ` label="${escapeXmlAttribute(sub.label)}"` : "";
|
|
1037
|
-
yield `${inner}<att>\n${inner} <graph${id}${lbl}>\n`;
|
|
1038
|
-
const atts =
|
|
1039
|
-
typeof sub.atts === "object" && sub.atts !== null
|
|
1040
|
-
? Object.entries(sub.atts as Record<string, unknown>)
|
|
1041
|
-
: [];
|
|
1042
|
-
for (const [key, value] of atts) {
|
|
1043
|
-
yield* jsonAtt(key, value, `${inner} `);
|
|
1044
|
-
}
|
|
1045
|
-
for (const child of children) {
|
|
1046
|
-
yield `${inner} <node xlink:href="#${escapeXmlAttribute(idText(snapshot.ids.idOf(child)))}"/>\n`;
|
|
1047
|
-
}
|
|
1048
|
-
yield `${inner} </graph>\n${inner}</att>\n`;
|
|
1112
|
+
yield* groupAttOf(snapshot, (subgraph ?? {}) as Record<string, unknown>, children, inner);
|
|
1049
1113
|
} else if (typeof nested === "string") {
|
|
1050
1114
|
yield `${inner}<att>\n${inner} <graph label="${escapeXmlAttribute(nested)}"/>\n${inner}</att>\n`;
|
|
1051
1115
|
} else if (typeof pointer === "string") {
|
|
@@ -1171,7 +1235,11 @@ function* edgesOf(snapshot: GraphSnapshot, plan: Plan): Generator<string, void,
|
|
|
1171
1235
|
const id = cellOf(plan.edgeId, e);
|
|
1172
1236
|
const label = cellOf(labelCol, e);
|
|
1173
1237
|
const weight = weights.text(e);
|
|
1174
|
-
|
|
1238
|
+
const idAttr = id === null ? "" : ` id="${escapeXmlAttribute(scalarText(id))}"`;
|
|
1239
|
+
const labelAttr = label === null ? "" : ` label="${escapeXmlAttribute(scalarText(label))}"`;
|
|
1240
|
+
const source = escapeXmlAttribute(idText(snapshot.ids.idOf(u)));
|
|
1241
|
+
const target = escapeXmlAttribute(idText(snapshot.ids.idOf(v)));
|
|
1242
|
+
yield ` <edge${idAttr}${labelAttr} source="${source}" target="${target}" cy:directed="${directed ? "1" : "0"}"${optionalAttribute("weight", weight)}${xmlAttributesOf(plan.edges, e)}>\n`;
|
|
1175
1243
|
yield* attsOf(plan.edges, e, first, plan, " ");
|
|
1176
1244
|
yield* graphicsOf(cellOf(graphicsCol, e) as Record<string, unknown> | null, [], " ");
|
|
1177
1245
|
yield " </edge>\n";
|
|
@@ -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
|