@graphty/graph-io 0.3.40 → 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-BJRLd3vJ.js → escape-DBb8zuNE.js} +2 -2
- package/dist/chunks/{escape-BJRLd3vJ.js.map → escape-DBb8zuNE.js.map} +1 -1
- 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-Bcp-l4Bo.js → exporter-BqtmlxNr.js} +545 -459
- package/dist/chunks/exporter-BqtmlxNr.js.map +1 -0
- package/dist/chunks/{exporter-6ocL_9x_.js → exporter-CS35R6Fg.js} +4 -4
- package/dist/chunks/{exporter-6ocL_9x_.js.map → exporter-CS35R6Fg.js.map} +1 -1
- package/dist/chunks/{exporter-BVXnV9-9.js → exporter-D8r1N2dU.js} +28 -16
- 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-CPqnIY8m.js → importer-BKC7AmCA.js} +42 -29
- package/dist/chunks/importer-BKC7AmCA.js.map +1 -0
- package/dist/chunks/{importer-D_srHyj6.js → importer-BdWHmlmG.js} +4 -4
- package/dist/chunks/{importer-D_srHyj6.js.map → importer-BdWHmlmG.js.map} +1 -1
- package/dist/chunks/{importer-AjwOJrn2.js → importer-BfbzWk6k.js} +202 -181
- package/dist/chunks/importer-BfbzWk6k.js.map +1 -0
- package/dist/chunks/{importer-BpuS3J8C.js → importer-BmaZcskq.js} +73 -60
- package/dist/chunks/importer-BmaZcskq.js.map +1 -0
- package/dist/chunks/{importer-DEp_fYjf.js → importer-DviEsHy4.js} +92 -64
- package/dist/chunks/importer-DviEsHy4.js.map +1 -0
- package/dist/chunks/{importer-DcKB97kW.js → importer-S-abxJHn.js} +5 -5
- package/dist/chunks/{importer-DcKB97kW.js.map → importer-S-abxJHn.js.map} +1 -1
- package/dist/chunks/{importer-BJlGAOht.js → importer-r29cWnEf.js} +3 -3
- package/dist/chunks/{importer-BJlGAOht.js.map → importer-r29cWnEf.js.map} +1 -1
- package/dist/chunks/{json-elements-DpO6AXIc.js → json-elements-DdF-etPT.js} +3 -3
- package/dist/chunks/{json-elements-DpO6AXIc.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-r3SFHz11.js → records-Uc3LkABx.js} +15 -12
- 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 +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 +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/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 +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 +18 -10
- 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/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/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/common/attributes.ts +59 -19
- package/src/common/input.ts +216 -102
- package/src/common/text.ts +24 -15
- 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 +19 -10
- package/src/formats/cys/tables.ts +11 -10
- 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/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/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/records-r3SFHz11.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
|
@@ -575,6 +575,22 @@ function isUnset(text: string, quoted: boolean | undefined): boolean {
|
|
|
575
575
|
return quoted !== true && isBlank(text);
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
+
/**
|
|
579
|
+
* The number of set neighbor cells of an adjacency row (every cell after the first).
|
|
580
|
+
* @param row - the cells
|
|
581
|
+
* @param quoted - whether each cell was quoted
|
|
582
|
+
* @returns the set neighbor cells
|
|
583
|
+
*/
|
|
584
|
+
function setNeighborCells(row: readonly string[], quoted: readonly boolean[]): number {
|
|
585
|
+
let neighbors = 0;
|
|
586
|
+
for (let k = 1; k < row.length; k++) {
|
|
587
|
+
if (!isUnset(row[k], quoted[k])) {
|
|
588
|
+
neighbors++;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return neighbors;
|
|
592
|
+
}
|
|
593
|
+
|
|
578
594
|
/** Rows between two checks of the cancellation signal on an in-memory input. */
|
|
579
595
|
const ABORT_CHECK_INTERVAL = 64;
|
|
580
596
|
|
|
@@ -1520,12 +1536,7 @@ class TableReader {
|
|
|
1520
1536
|
const { where } = this;
|
|
1521
1537
|
where.line = line;
|
|
1522
1538
|
where.element = null;
|
|
1523
|
-
|
|
1524
|
-
for (let k = 1; k < row.length; k++) {
|
|
1525
|
-
if (!isUnset(row[k], quoted[k])) {
|
|
1526
|
-
neighbors++;
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1539
|
+
const neighbors = setNeighborCells(row, quoted);
|
|
1529
1540
|
if (isUnset(row[0], quoted[0])) {
|
|
1530
1541
|
report.error("missing-value", MISSING_ID_CODE, `blank node cell`, { line });
|
|
1531
1542
|
counts.skippedNodes++;
|
|
@@ -410,16 +410,7 @@ export function sniffDelimiter(
|
|
|
410
410
|
if (rows.length === 0) {
|
|
411
411
|
continue;
|
|
412
412
|
}
|
|
413
|
-
|
|
414
|
-
let delta = 0;
|
|
415
|
-
for (let i = 0; i < rows.length; i++) {
|
|
416
|
-
const count = rows[i].length;
|
|
417
|
-
total += count;
|
|
418
|
-
if (i > 0) {
|
|
419
|
-
delta += Math.abs(count - rows[i - 1].length);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
const average = total / rows.length;
|
|
413
|
+
const { average, delta } = rowWidths(rows);
|
|
423
414
|
if (average > 1.99 && delta < bestDelta) {
|
|
424
415
|
best = delimiter;
|
|
425
416
|
bestDelta = delta;
|
|
@@ -428,6 +419,24 @@ export function sniffDelimiter(
|
|
|
428
419
|
return best;
|
|
429
420
|
}
|
|
430
421
|
|
|
422
|
+
/**
|
|
423
|
+
* The average width of the rows and the sum of the width changes from each row to the next.
|
|
424
|
+
* @param rows - the rows (at least one)
|
|
425
|
+
* @returns the average and the delta
|
|
426
|
+
*/
|
|
427
|
+
function rowWidths(rows: readonly (readonly string[])[]): { average: number; delta: number } {
|
|
428
|
+
let total = 0;
|
|
429
|
+
let delta = 0;
|
|
430
|
+
for (let i = 0; i < rows.length; i++) {
|
|
431
|
+
const count = rows[i].length;
|
|
432
|
+
total += count;
|
|
433
|
+
if (i > 0) {
|
|
434
|
+
delta += Math.abs(count - rows[i - 1].length);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { average: total / rows.length, delta };
|
|
438
|
+
}
|
|
439
|
+
|
|
431
440
|
/**
|
|
432
441
|
* Whether a parsed row is a blank line: one cell holding only whitespace.
|
|
433
442
|
* @param row - the row
|
|
@@ -217,24 +217,25 @@ export function cellAtt(
|
|
|
217
217
|
namespace: string | null,
|
|
218
218
|
hidden: boolean,
|
|
219
219
|
): AttRec | null {
|
|
220
|
+
const cell = { line, namespace, hidden };
|
|
220
221
|
if (text.startsWith("=")) {
|
|
221
|
-
return att(column.name, column.list ? "List" : column.type, null, text,
|
|
222
|
+
return att(column.name, column.list ? "List" : column.type, null, text, true, cell);
|
|
222
223
|
}
|
|
223
224
|
if (!column.list) {
|
|
224
225
|
if (text.length === 0 && column.type !== "String") {
|
|
225
226
|
return null;
|
|
226
227
|
}
|
|
227
|
-
return att(column.name, column.type, null, text,
|
|
228
|
+
return att(column.name, column.type, null, text, false, cell);
|
|
228
229
|
}
|
|
229
230
|
if (text.length === 0 && column.type !== "String") {
|
|
230
231
|
return null;
|
|
231
232
|
}
|
|
232
|
-
const list = att(column.name, "List", column.type, null,
|
|
233
|
+
const list = att(column.name, "List", column.type, null, false, cell);
|
|
233
234
|
for (const item of javaSplitLines(text)) {
|
|
234
235
|
if (item.length === 0 && column.type !== "String") {
|
|
235
236
|
continue;
|
|
236
237
|
}
|
|
237
|
-
list.children.push(att(column.name, column.type, null, item,
|
|
238
|
+
list.children.push(att(column.name, column.type, null, item, false, cell));
|
|
238
239
|
}
|
|
239
240
|
return list;
|
|
240
241
|
}
|
|
@@ -262,10 +263,11 @@ function javaSplitLines(text: string): string[] {
|
|
|
262
263
|
* @param cyType - the Cytoscape type
|
|
263
264
|
* @param elementType - a list's item type, or null
|
|
264
265
|
* @param value - the value, or null
|
|
265
|
-
* @param line - the line
|
|
266
|
-
* @param namespace - the table namespace, or null
|
|
267
|
-
* @param hidden - a hidden column
|
|
268
266
|
* @param equation - the value is a formula
|
|
267
|
+
* @param cell - where the cell sits
|
|
268
|
+
* @param cell.line - the line
|
|
269
|
+
* @param cell.namespace - the table namespace, or null
|
|
270
|
+
* @param cell.hidden - a hidden column
|
|
269
271
|
* @returns the record
|
|
270
272
|
*/
|
|
271
273
|
function att(
|
|
@@ -273,11 +275,10 @@ function att(
|
|
|
273
275
|
cyType: string,
|
|
274
276
|
elementType: string | null,
|
|
275
277
|
value: string | null,
|
|
276
|
-
line: number,
|
|
277
|
-
namespace: string | null,
|
|
278
|
-
hidden: boolean,
|
|
279
278
|
equation: boolean,
|
|
279
|
+
cell: { readonly line: number; readonly namespace: string | null; readonly hidden: boolean },
|
|
280
280
|
): AttRec {
|
|
281
|
+
const { line, namespace, hidden } = cell;
|
|
281
282
|
return {
|
|
282
283
|
name,
|
|
283
284
|
type: null,
|
|
@@ -1247,6 +1247,27 @@ function lifetimeAttrs(roles: RoleColumns, row: number, plan: ExportPlan): strin
|
|
|
1247
1247
|
return out;
|
|
1248
1248
|
}
|
|
1249
1249
|
|
|
1250
|
+
/**
|
|
1251
|
+
* The `start` and `end` attributes of one `<spell>`.
|
|
1252
|
+
* @param s - the start, or a non-finite value for an unbounded start
|
|
1253
|
+
* @param e - the end, or a non-finite value for an unbounded end
|
|
1254
|
+
* @param open - the open-bound bits
|
|
1255
|
+
* @param plan - the plan
|
|
1256
|
+
* @returns the attribute text, each with a leading space
|
|
1257
|
+
*/
|
|
1258
|
+
function spellBounds(s: number, e: number, open: number, plan: ExportPlan): string {
|
|
1259
|
+
let attrs = "";
|
|
1260
|
+
if (Number.isFinite(s)) {
|
|
1261
|
+
const name = (open & OPEN_START) === 0 ? "start" : "startopen";
|
|
1262
|
+
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(s, plan.timeFormat))}"`;
|
|
1263
|
+
}
|
|
1264
|
+
if (Number.isFinite(e)) {
|
|
1265
|
+
const name = (open & OPEN_END) === 0 ? "end" : "endopen";
|
|
1266
|
+
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(e, plan.timeFormat))}"`;
|
|
1267
|
+
}
|
|
1268
|
+
return attrs;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1250
1271
|
/**
|
|
1251
1272
|
* The `<spells>` element of one element: its spells column, plus (1.2) its timestamps as [t, t].
|
|
1252
1273
|
* In 1.2 a spell whose spells.open bits mark a bound open writes that bound as `startopen` /
|
|
@@ -1276,16 +1297,7 @@ function spellsElement(roles: RoleColumns, row: number, plan: ExportPlan, indent
|
|
|
1276
1297
|
}
|
|
1277
1298
|
let out = `${indent}<spells>\n`;
|
|
1278
1299
|
for (const [s, e, open] of pairs) {
|
|
1279
|
-
|
|
1280
|
-
if (Number.isFinite(s)) {
|
|
1281
|
-
const name = (open & OPEN_START) === 0 ? "start" : "startopen";
|
|
1282
|
-
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(s, plan.timeFormat))}"`;
|
|
1283
|
-
}
|
|
1284
|
-
if (Number.isFinite(e)) {
|
|
1285
|
-
const name = (open & OPEN_END) === 0 ? "end" : "endopen";
|
|
1286
|
-
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(e, plan.timeFormat))}"`;
|
|
1287
|
-
}
|
|
1288
|
-
out += `${indent} <spell${attrs}/>\n`;
|
|
1300
|
+
out += `${indent} <spell${spellBounds(s, e, open, plan)}/>\n`;
|
|
1289
1301
|
}
|
|
1290
1302
|
return `${out}${indent}</spells>\n`;
|
|
1291
1303
|
}
|
|
@@ -1676,23 +1688,12 @@ function metaElement(meta: GraphSnapshot["meta"]): string {
|
|
|
1676
1688
|
}
|
|
1677
1689
|
|
|
1678
1690
|
/**
|
|
1679
|
-
* The `<graph>`
|
|
1691
|
+
* The `idtype` attribute of the `<graph>` tag.
|
|
1680
1692
|
* @param snapshot - the snapshot
|
|
1681
|
-
* @
|
|
1682
|
-
* @returns the line
|
|
1693
|
+
* @returns the attribute text with a leading space, or an empty string
|
|
1683
1694
|
*/
|
|
1684
|
-
function
|
|
1685
|
-
|
|
1686
|
-
let attrs = ` defaultedgetype="${defaultEdgeType(snapshot)}"`;
|
|
1687
|
-
let mode = meta.mode ?? "static";
|
|
1688
|
-
if (plan.temporal && mode === "static") {
|
|
1689
|
-
mode = "dynamic";
|
|
1690
|
-
}
|
|
1691
|
-
if (plan.version === "1.2" && mode === "slice") {
|
|
1692
|
-
mode = "dynamic";
|
|
1693
|
-
}
|
|
1694
|
-
attrs += ` mode="${mode}"`;
|
|
1695
|
-
let { idType } = meta;
|
|
1695
|
+
function graphIdTypeAttr(snapshot: GraphSnapshot): string {
|
|
1696
|
+
let { idType } = snapshot.meta;
|
|
1696
1697
|
if (idType === null) {
|
|
1697
1698
|
const { kind } = snapshot.ids;
|
|
1698
1699
|
if (kind === "string") {
|
|
@@ -1701,17 +1702,16 @@ function graphStart(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
|
1701
1702
|
idType = "integer";
|
|
1702
1703
|
}
|
|
1703
1704
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
}
|
|
1705
|
+
return idType === "integer" || idType === "string" ? ` idtype="${idType}"` : "";
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* The `start`, `end` and `timestamp` attributes of the `<graph>` tag.
|
|
1710
|
+
* @param snapshot - the snapshot
|
|
1711
|
+
* @param plan - the plan
|
|
1712
|
+
* @returns the attribute text with leading spaces, or an empty string
|
|
1713
|
+
*/
|
|
1714
|
+
function graphHeaderAttrs(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
1715
1715
|
const timestamp = graphExtraText(snapshot, "timestamp");
|
|
1716
1716
|
const header: Record<string, string | null> = {
|
|
1717
1717
|
start: graphExtraText(snapshot, "start"),
|
|
@@ -1724,11 +1724,42 @@ function graphStart(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
|
1724
1724
|
header.end ??= timestamp;
|
|
1725
1725
|
header.timestamp = null;
|
|
1726
1726
|
}
|
|
1727
|
+
let attrs = "";
|
|
1727
1728
|
for (const [key, value] of Object.entries(header)) {
|
|
1728
1729
|
if (value !== null) {
|
|
1729
1730
|
attrs += ` ${key}="${escapeXmlAttribute(value)}"`;
|
|
1730
1731
|
}
|
|
1731
1732
|
}
|
|
1733
|
+
return attrs;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* The `<graph>` start tag with its header attributes.
|
|
1738
|
+
* @param snapshot - the snapshot
|
|
1739
|
+
* @param plan - the plan
|
|
1740
|
+
* @returns the line
|
|
1741
|
+
*/
|
|
1742
|
+
function graphStart(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
1743
|
+
const { meta } = snapshot;
|
|
1744
|
+
let attrs = ` defaultedgetype="${defaultEdgeType(snapshot)}"`;
|
|
1745
|
+
let mode = meta.mode ?? "static";
|
|
1746
|
+
if (plan.temporal && mode === "static") {
|
|
1747
|
+
mode = "dynamic";
|
|
1748
|
+
}
|
|
1749
|
+
if (plan.version === "1.2" && mode === "slice") {
|
|
1750
|
+
mode = "dynamic";
|
|
1751
|
+
}
|
|
1752
|
+
attrs += ` mode="${mode}"`;
|
|
1753
|
+
attrs += graphIdTypeAttr(snapshot);
|
|
1754
|
+
if (plan.timeFormat !== null) {
|
|
1755
|
+
attrs += ` timeformat="${plan.timeFormat}"`;
|
|
1756
|
+
} else if (plan.temporal) {
|
|
1757
|
+
attrs += ' timeformat="double"';
|
|
1758
|
+
}
|
|
1759
|
+
if (plan.version === "1.3" && plan.timeRepresentation !== null) {
|
|
1760
|
+
attrs += ` timerepresentation="${plan.timeRepresentation}"`;
|
|
1761
|
+
}
|
|
1762
|
+
attrs += graphHeaderAttrs(snapshot, plan);
|
|
1732
1763
|
return ` <graph${attrs}>\n`;
|
|
1733
1764
|
}
|
|
1734
1765
|
|
|
@@ -1328,20 +1328,15 @@ class GexfReader implements XmlHandler {
|
|
|
1328
1328
|
}
|
|
1329
1329
|
|
|
1330
1330
|
/**
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
* @param
|
|
1331
|
+
* Whether an attribute was already declared (warning that the first declaration is kept), and
|
|
1332
|
+
* a warning when values that use it came before its declaration.
|
|
1333
|
+
* @param domain - node or edge
|
|
1334
|
+
* @param id - the attribute id
|
|
1335
|
+
* @param where - the report location
|
|
1336
|
+
* @returns true when the declaration is a duplicate and must be skipped
|
|
1335
1337
|
*/
|
|
1336
|
-
private
|
|
1337
|
-
const { report
|
|
1338
|
-
const { domain, dynamic, attrs } = pending;
|
|
1339
|
-
const id = attrs.get("id");
|
|
1340
|
-
const where = { line: pending.line, element: id ?? null };
|
|
1341
|
-
if (id === undefined) {
|
|
1342
|
-
report.error("validation-error", ATTRIBUTE_ID_CODE, "<attribute> without an id", where);
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1338
|
+
private isDuplicateAttribute(domain: "node" | "edge", id: string, where: IssueLocation): boolean {
|
|
1339
|
+
const { report } = this;
|
|
1345
1340
|
const tracked = domain === "node" ? this.nodeAttrs : this.edgeAttrs;
|
|
1346
1341
|
if (tracked.has(id) || (domain === "edge" && this.weightAttr !== null && this.weightAttr.id === id)) {
|
|
1347
1342
|
report.warning(
|
|
@@ -1350,7 +1345,7 @@ class GexfReader implements XmlHandler {
|
|
|
1350
1345
|
`${domain} attribute "${id}" is declared twice; the first declaration is kept`,
|
|
1351
1346
|
where,
|
|
1352
1347
|
);
|
|
1353
|
-
return;
|
|
1348
|
+
return true;
|
|
1354
1349
|
}
|
|
1355
1350
|
if (this.unknownAttributes.has(`${domain}:${id}`)) {
|
|
1356
1351
|
report.warning(
|
|
@@ -1360,6 +1355,68 @@ class GexfReader implements XmlHandler {
|
|
|
1360
1355
|
where,
|
|
1361
1356
|
);
|
|
1362
1357
|
}
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Whether an edge attribute becomes THE weight: the first attribute of the weight's title does
|
|
1363
|
+
* (declared here), a later one only warns and stays a plain column.
|
|
1364
|
+
* @param attribute - the attribute's domain, id, display name, title and type
|
|
1365
|
+
* @param attribute.domain - node or edge
|
|
1366
|
+
* @param attribute.id - the attribute id
|
|
1367
|
+
* @param attribute.name - the column name
|
|
1368
|
+
* @param attribute.title - the declared title, or null
|
|
1369
|
+
* @param attribute.type - the declared type, or null
|
|
1370
|
+
* @param where - the report location
|
|
1371
|
+
* @returns true when the attribute was declared as the weight
|
|
1372
|
+
*/
|
|
1373
|
+
private claimsWeight(
|
|
1374
|
+
attribute: { domain: "node" | "edge"; id: string; name: string; title: string | null; type: string | null },
|
|
1375
|
+
where: IssueLocation,
|
|
1376
|
+
): boolean {
|
|
1377
|
+
const { report, sink, options } = this;
|
|
1378
|
+
const { domain, id, name, title, type } = attribute;
|
|
1379
|
+
if (domain !== "edge" || !isWeightField(name, options.weightFrom)) {
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
if (this.weightAttr !== null) {
|
|
1383
|
+
// the first attribute of that title stays THE weight; this one becomes a plain column
|
|
1384
|
+
report.warning(
|
|
1385
|
+
"validation-error",
|
|
1386
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
1387
|
+
`edge attribute "${id}" is also titled ${name}; attribute "${this.weightAttr.id}" is the weight, "${id}" is kept as a column`,
|
|
1388
|
+
where,
|
|
1389
|
+
);
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1392
|
+
const declared = declareAttribute({ format: GEXF_FORMAT, id, title, type, long: options.long });
|
|
1393
|
+
for (const issue of declared.issues) {
|
|
1394
|
+
report.warning(issue.category, issue.code, issue.message, where);
|
|
1395
|
+
}
|
|
1396
|
+
this.weightAttr = { id, spec: declared.spec, table: INVALID_INDEX as ExtensionHandle };
|
|
1397
|
+
sink.setMeta({ weightOrigin: { format: GEXF_FORMAT, id, title, type, namespace: null } });
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Declare one `<attribute id title type>` with its `<default>` and `<options>` (design
|
|
1403
|
+
* sections 5.1, 5.5, 5.6). The edge attribute titled `weightFrom` is the weight and gets no
|
|
1404
|
+
* column; its declaration is recorded in meta.weightOrigin.
|
|
1405
|
+
* @param pending - the attribute as read
|
|
1406
|
+
*/
|
|
1407
|
+
private declareOne(pending: PendingAttribute): void {
|
|
1408
|
+
const { report, sink, options } = this;
|
|
1409
|
+
const { domain, dynamic, attrs } = pending;
|
|
1410
|
+
const id = attrs.get("id");
|
|
1411
|
+
const where = { line: pending.line, element: id ?? null };
|
|
1412
|
+
if (id === undefined) {
|
|
1413
|
+
report.error("validation-error", ATTRIBUTE_ID_CODE, "<attribute> without an id", where);
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const tracked = domain === "node" ? this.nodeAttrs : this.edgeAttrs;
|
|
1417
|
+
if (this.isDuplicateAttribute(domain, id, where)) {
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1363
1420
|
const title = attrs.get("title") ?? null;
|
|
1364
1421
|
const type = attrs.get("type") ?? null;
|
|
1365
1422
|
if (type === null) {
|
|
@@ -1371,21 +1428,7 @@ class GexfReader implements XmlHandler {
|
|
|
1371
1428
|
);
|
|
1372
1429
|
}
|
|
1373
1430
|
const name = title !== null && title.length > 0 ? title : id;
|
|
1374
|
-
if (
|
|
1375
|
-
// the first attribute of that title stays THE weight; this one becomes a plain column
|
|
1376
|
-
report.warning(
|
|
1377
|
-
"validation-error",
|
|
1378
|
-
DUPLICATE_ATTRIBUTE_CODE,
|
|
1379
|
-
`edge attribute "${id}" is also titled ${name}; attribute "${this.weightAttr.id}" is the weight, "${id}" is kept as a column`,
|
|
1380
|
-
where,
|
|
1381
|
-
);
|
|
1382
|
-
} else if (domain === "edge" && isWeightField(name, options.weightFrom)) {
|
|
1383
|
-
const declared = declareAttribute({ format: GEXF_FORMAT, id, title, type, long: options.long });
|
|
1384
|
-
for (const issue of declared.issues) {
|
|
1385
|
-
report.warning(issue.category, issue.code, issue.message, where);
|
|
1386
|
-
}
|
|
1387
|
-
this.weightAttr = { id, spec: declared.spec, table: INVALID_INDEX as ExtensionHandle };
|
|
1388
|
-
sink.setMeta({ weightOrigin: { format: GEXF_FORMAT, id, title, type, namespace: null } });
|
|
1431
|
+
if (this.claimsWeight({ domain, id, name, title, type }, where)) {
|
|
1389
1432
|
return;
|
|
1390
1433
|
}
|
|
1391
1434
|
const declaredNames = domain === "node" ? this.declaredNodeNames : this.declaredEdgeNames;
|
|
@@ -1717,6 +1760,15 @@ class GexfReader implements XmlHandler {
|
|
|
1717
1760
|
report.recordError(err, ref.where);
|
|
1718
1761
|
}
|
|
1719
1762
|
}
|
|
1763
|
+
this.resolveParentsLists(lists);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* Resolve the `<parents>` lists read so far into the parents column.
|
|
1768
|
+
* @param lists - the queued `<parents>` lists
|
|
1769
|
+
*/
|
|
1770
|
+
private resolveParentsLists(lists: readonly (typeof this.parentsRefs)[number][]): void {
|
|
1771
|
+
const { sink, report } = this;
|
|
1720
1772
|
for (const ref of lists) {
|
|
1721
1773
|
const indices: number[] = [];
|
|
1722
1774
|
for (const id of ref.ids) {
|
|
@@ -1763,7 +1815,7 @@ class GexfReader implements XmlHandler {
|
|
|
1763
1815
|
* @param line - the line
|
|
1764
1816
|
*/
|
|
1765
1817
|
private beginEdge(attrs: ReadonlyMap<string, string>, line: number): void {
|
|
1766
|
-
const { report, sink
|
|
1818
|
+
const { report, sink } = this;
|
|
1767
1819
|
this.checkAbort();
|
|
1768
1820
|
this.ctx.push(Ctx.Edge);
|
|
1769
1821
|
this.edge = null;
|
|
@@ -1816,18 +1868,7 @@ class GexfReader implements XmlHandler {
|
|
|
1816
1868
|
try {
|
|
1817
1869
|
const source = this.coerceId(sourceText, where);
|
|
1818
1870
|
const target = this.coerceId(targetText, where);
|
|
1819
|
-
|
|
1820
|
-
if (sink.indexOf(source) === INVALID_INDEX) {
|
|
1821
|
-
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${sourceText}"`, {
|
|
1822
|
-
id: source,
|
|
1823
|
-
});
|
|
1824
|
-
}
|
|
1825
|
-
if (sink.indexOf(target) === INVALID_INDEX) {
|
|
1826
|
-
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${targetText}"`, {
|
|
1827
|
-
id: target,
|
|
1828
|
-
});
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1871
|
+
this.requireDeclaredEndpoints(source, target, sourceText, targetText);
|
|
1831
1872
|
const weight = this.xmlWeight(attrs, where);
|
|
1832
1873
|
const before = sink.edgeCount;
|
|
1833
1874
|
index = this.resolver.addEdge(source, target, kind, weight, where);
|
|
@@ -1846,6 +1887,47 @@ class GexfReader implements XmlHandler {
|
|
|
1846
1887
|
frame.mirror = this.resolver.lastMirror;
|
|
1847
1888
|
frame.xmlWeight = attrs.get("weight") ?? null;
|
|
1848
1889
|
this.edge = frame;
|
|
1890
|
+
this.writeEdgeColumns(attrs, index, idText, where);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Throw when addMissingNodes is off and an endpoint names an undeclared node.
|
|
1895
|
+
* @param source - the coerced source id
|
|
1896
|
+
* @param target - the coerced target id
|
|
1897
|
+
* @param sourceText - the source as written
|
|
1898
|
+
* @param targetText - the target as written
|
|
1899
|
+
*/
|
|
1900
|
+
private requireDeclaredEndpoints(source: NodeId, target: NodeId, sourceText: string, targetText: string): void {
|
|
1901
|
+
const { sink, options } = this;
|
|
1902
|
+
if (options.addMissingNodes) {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (sink.indexOf(source) === INVALID_INDEX) {
|
|
1906
|
+
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${sourceText}"`, {
|
|
1907
|
+
id: source,
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
if (sink.indexOf(target) === INVALID_INDEX) {
|
|
1911
|
+
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${targetText}"`, {
|
|
1912
|
+
id: target,
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
/**
|
|
1918
|
+
* Write the id, label, kind and lifetime columns of an added edge; a failure is reported.
|
|
1919
|
+
* @param attrs - the edge's attributes
|
|
1920
|
+
* @param index - the edge's index
|
|
1921
|
+
* @param idText - the edge's id, or undefined
|
|
1922
|
+
* @param where - the edge's location
|
|
1923
|
+
*/
|
|
1924
|
+
private writeEdgeColumns(
|
|
1925
|
+
attrs: ReadonlyMap<string, string>,
|
|
1926
|
+
index: number,
|
|
1927
|
+
idText: string | undefined,
|
|
1928
|
+
where: IssueLocation,
|
|
1929
|
+
): void {
|
|
1930
|
+
const { report, sink } = this;
|
|
1849
1931
|
try {
|
|
1850
1932
|
if (idText !== undefined) {
|
|
1851
1933
|
this.edgeIdColumn.set(sink, report, index, idText, where);
|
|
@@ -2555,35 +2637,18 @@ class GexfReader implements XmlHandler {
|
|
|
2555
2637
|
case "color":
|
|
2556
2638
|
columns.color.set(sink, report, index, parseColor(attrs), where);
|
|
2557
2639
|
break;
|
|
2558
|
-
case "position":
|
|
2559
|
-
|
|
2560
|
-
const y = vizNumber(attrs, "y");
|
|
2561
|
-
const z = attrs.has("z") ? vizNumber(attrs, "z") : 0;
|
|
2562
|
-
this.positionColumn ??= new LazyColumn("node", {
|
|
2563
|
-
...NODE_DECLS.position,
|
|
2564
|
-
extra: { ...NODE_DECLS.position.extra, sourceDims: attrs.has("z") ? 3 : 2 },
|
|
2565
|
-
});
|
|
2566
|
-
this.positionColumn.set(sink, report, index, [x, y, z], where);
|
|
2640
|
+
case "position":
|
|
2641
|
+
this.writePosition(attrs, index, where);
|
|
2567
2642
|
break;
|
|
2568
|
-
}
|
|
2569
2643
|
case "size":
|
|
2570
2644
|
this.sizeColumn.set(sink, report, index, vizNumber(attrs, "value"), where);
|
|
2571
2645
|
break;
|
|
2572
2646
|
case "thickness":
|
|
2573
2647
|
this.thicknessColumn.set(sink, report, index, vizNumber(attrs, "value"), where);
|
|
2574
2648
|
break;
|
|
2575
|
-
case "shape":
|
|
2576
|
-
|
|
2577
|
-
if (value === undefined) {
|
|
2578
|
-
throw new GraphFormatError("E_COLUMN_TYPE", "<viz:shape> without a value", { element: name });
|
|
2579
|
-
}
|
|
2580
|
-
columns.shape.set(sink, report, index, value, where);
|
|
2581
|
-
const uri = attrs.get("uri");
|
|
2582
|
-
if (domain === "node" && uri !== undefined) {
|
|
2583
|
-
this.shapeUriColumn.set(sink, report, index, uri, where);
|
|
2584
|
-
}
|
|
2649
|
+
case "shape":
|
|
2650
|
+
this.writeShape(name, attrs, domain, index, where);
|
|
2585
2651
|
break;
|
|
2586
|
-
}
|
|
2587
2652
|
default:
|
|
2588
2653
|
break;
|
|
2589
2654
|
}
|
|
@@ -2595,6 +2660,51 @@ class GexfReader implements XmlHandler {
|
|
|
2595
2660
|
}
|
|
2596
2661
|
}
|
|
2597
2662
|
|
|
2663
|
+
/**
|
|
2664
|
+
* Write a `<viz:position>`.
|
|
2665
|
+
* @param attrs - its attributes
|
|
2666
|
+
* @param index - the node's index
|
|
2667
|
+
* @param where - the node's location
|
|
2668
|
+
*/
|
|
2669
|
+
private writePosition(attrs: ReadonlyMap<string, string>, index: number, where: IssueLocation): void {
|
|
2670
|
+
const x = vizNumber(attrs, "x");
|
|
2671
|
+
const y = vizNumber(attrs, "y");
|
|
2672
|
+
const z = attrs.has("z") ? vizNumber(attrs, "z") : 0;
|
|
2673
|
+
this.positionColumn ??= new LazyColumn("node", {
|
|
2674
|
+
...NODE_DECLS.position,
|
|
2675
|
+
extra: { ...NODE_DECLS.position.extra, sourceDims: attrs.has("z") ? 3 : 2 },
|
|
2676
|
+
});
|
|
2677
|
+
this.positionColumn.set(this.sink, this.report, index, [x, y, z], where);
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
/**
|
|
2681
|
+
* Write a `<viz:shape>` and, on a node, its `uri`.
|
|
2682
|
+
* @param name - the element name as written
|
|
2683
|
+
* @param attrs - its attributes
|
|
2684
|
+
* @param domain - node or edge
|
|
2685
|
+
* @param index - the element's index
|
|
2686
|
+
* @param where - the element's location
|
|
2687
|
+
*/
|
|
2688
|
+
private writeShape(
|
|
2689
|
+
name: string,
|
|
2690
|
+
attrs: ReadonlyMap<string, string>,
|
|
2691
|
+
domain: "node" | "edge",
|
|
2692
|
+
index: number,
|
|
2693
|
+
where: IssueLocation,
|
|
2694
|
+
): void {
|
|
2695
|
+
const { sink, report } = this;
|
|
2696
|
+
const columns = domain === "node" ? this.nodeColumns : this.edgeColumns;
|
|
2697
|
+
const value = attrs.get("value");
|
|
2698
|
+
if (value === undefined) {
|
|
2699
|
+
throw new GraphFormatError("E_COLUMN_TYPE", "<viz:shape> without a value", { element: name });
|
|
2700
|
+
}
|
|
2701
|
+
columns.shape.set(sink, report, index, value, where);
|
|
2702
|
+
const uri = attrs.get("uri");
|
|
2703
|
+
if (domain === "node" && uri !== undefined) {
|
|
2704
|
+
this.shapeUriColumn.set(sink, report, index, uri, where);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2598
2708
|
// ---------------------------------------------------------------- helpers
|
|
2599
2709
|
|
|
2600
2710
|
/**
|