@graphty/graph-io 0.3.9 → 0.3.10
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 +29 -5
- package/dist/chunks/{escape-BjmIaFFo.js → escape-D1f9-cwf.js} +4 -4
- package/dist/chunks/{escape-BjmIaFFo.js.map → escape-D1f9-cwf.js.map} +1 -1
- package/dist/chunks/{importer-BddBW3sf.js → importer-9PxhqH4v.js} +336 -92
- package/dist/chunks/importer-9PxhqH4v.js.map +1 -0
- package/dist/chunks/{importer-DfI8POXb.js → importer-B8lsjFWx.js} +6 -5
- package/dist/chunks/importer-B8lsjFWx.js.map +1 -0
- package/dist/chunks/{importer-BtHFICnf.js → importer-C3HcYWAn.js} +5 -3
- package/dist/chunks/importer-C3HcYWAn.js.map +1 -0
- package/dist/chunks/{importer-BOxwmef_.js → importer-C7mnGdr_.js} +125 -8
- package/dist/chunks/importer-C7mnGdr_.js.map +1 -0
- package/dist/chunks/{records-BKSMowhR.js → records-IHsCfv7s.js} +20 -7
- package/dist/chunks/records-IHsCfv7s.js.map +1 -0
- package/dist/chunks/{writer-BdMak4_J.js → writer-BtWpUaiH.js} +4 -4
- package/dist/chunks/{writer-BdMak4_J.js.map → writer-BtWpUaiH.js.map} +1 -1
- package/dist/csv.js +239 -18
- package/dist/csv.js.map +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +72 -33
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +44 -23
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +11 -11
- package/dist/graphml.js +1 -1
- package/dist/json.js +1 -1
- package/dist/neo4j.js +5 -4
- package/dist/neo4j.js.map +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/export.js +1 -1
- package/dist/src/common/export.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts +15 -4
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +125 -6
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts +36 -4
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +169 -8
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts +14 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +28 -7
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +2 -0
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +36 -10
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts +9 -4
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +44 -21
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +1 -1
- package/dist/src/formats/gexf/index.d.ts.map +1 -1
- package/dist/src/formats/gexf/index.js +1 -1
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gexf/schema.d.ts +2 -0
- package/dist/src/formats/gexf/schema.d.ts.map +1 -1
- package/dist/src/formats/gexf/schema.js +18 -0
- package/dist/src/formats/gexf/schema.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +1 -0
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +7 -4
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +32 -19
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +3 -1
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +4 -2
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/graphml/constants.d.ts +2 -0
- package/dist/src/formats/graphml/constants.d.ts.map +1 -1
- package/dist/src/formats/graphml/constants.js +2 -0
- package/dist/src/formats/graphml/constants.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +14 -2
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts +2 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +91 -27
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts +62 -0
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -0
- package/dist/src/formats/graphml/yfiles.js +269 -0
- package/dist/src/formats/graphml/yfiles.js.map +1 -0
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +9 -1
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +14 -0
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +165 -4
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +1 -0
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +3 -2
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/package.json +2 -2
- package/src/common/export.ts +1 -1
- package/src/formats/csv/exporter.ts +140 -10
- package/src/formats/csv/importer.ts +207 -18
- package/src/formats/csv/records.ts +41 -6
- package/src/formats/dot/exporter.ts +2 -0
- package/src/formats/gexf/exporter.ts +40 -11
- package/src/formats/gexf/importer.ts +47 -25
- package/src/formats/gexf/index.ts +1 -1
- package/src/formats/gexf/schema.ts +18 -0
- package/src/formats/gml/exporter.ts +1 -0
- package/src/formats/gml/importer.ts +47 -24
- package/src/formats/gml/index.ts +4 -1
- package/src/formats/graphml/constants.ts +2 -0
- package/src/formats/graphml/exporter.ts +21 -2
- package/src/formats/graphml/importer.ts +99 -28
- package/src/formats/graphml/yfiles.ts +292 -0
- package/src/formats/json/exporter.ts +9 -1
- package/src/formats/json/importer.ts +202 -4
- package/src/formats/neo4j/exporter.ts +1 -0
- package/src/formats/pajek/exporter.ts +3 -1
- package/dist/chunks/importer-BOxwmef_.js.map +0 -1
- package/dist/chunks/importer-BddBW3sf.js.map +0 -1
- package/dist/chunks/importer-BtHFICnf.js.map +0 -1
- package/dist/chunks/importer-DfI8POXb.js.map +0 -1
- package/dist/chunks/records-BKSMowhR.js.map +0 -1
package/dist/csv.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a$ as DICT_SAMPLE_ROWS, a_ as DictHeuristic, X as ROLE_TAKEN_CODE$1, o as ID_MERGED_CODE$1, i as DUPLICATE_EDGE_ID_CODE$1, k as DUPLICATE_NODE_CODE$1, y as MISSING_ID_CODE$1, M as MISSING_ENDPOINT_CODE$1, m as EMPTY_INPUT_CODE$1, aF as resolveImportOptions, I as ImportReportBuilder, aC as reportSinkOptions, aD as reportUnusedOptions, l as DirectionResolver, v as IdCoercer, a as throwIfAborted, aA as parseWeightText, aL as uniqueColumnName, U as RENAMED_CODE, ai as declareResolved, ac as capabilities, L as LOSS, ax as joinText, ak as encodeChunks, ad as checkCapabilities, aE as resolveExportOptions, az as pairFolding, aJ as countMixedEdges, al as explicitWeights, aY as joinListText, aq as formatInteger, am as formatDecimal, ab as canonicalId, z as MIXED_DIRECTION_CODE, g as DIRECTION_FORCED_CODE, h as DIRECTION_REFUSED_CODE, Y as SINK_OPTION_CODE, P as OPTION_IGNORED_CODE, C as COLUMN_RENAMED_CODE, a5 as UNKNOWN_ENCODING_CODE, n as ENCODING_FALLBACK_CODE, s as INVALID_ENCODING_CODE, u as INVALID_UTF8_CODE } from "./chunks/writer-
|
|
1
|
+
import { a$ as DICT_SAMPLE_ROWS, a_ as DictHeuristic, X as ROLE_TAKEN_CODE$1, o as ID_MERGED_CODE$1, i as DUPLICATE_EDGE_ID_CODE$1, k as DUPLICATE_NODE_CODE$1, y as MISSING_ID_CODE$1, M as MISSING_ENDPOINT_CODE$1, m as EMPTY_INPUT_CODE$1, aF as resolveImportOptions, I as ImportReportBuilder, aC as reportSinkOptions, aD as reportUnusedOptions, l as DirectionResolver, v as IdCoercer, a as throwIfAborted, aA as parseWeightText, aL as uniqueColumnName, U as RENAMED_CODE, ai as declareResolved, ac as capabilities, L as LOSS, ax as joinText, ak as encodeChunks, ad as checkCapabilities, aE as resolveExportOptions, az as pairFolding, aJ as countMixedEdges, al as explicitWeights, aY as joinListText, aq as formatInteger, am as formatDecimal, ab as canonicalId, z as MIXED_DIRECTION_CODE, g as DIRECTION_FORCED_CODE, h as DIRECTION_REFUSED_CODE, Y as SINK_OPTION_CODE, P as OPTION_IGNORED_CODE, C as COLUMN_RENAMED_CODE, a5 as UNKNOWN_ENCODING_CODE, n as ENCODING_FALLBACK_CODE, s as INVALID_ENCODING_CODE, u as INVALID_UTF8_CODE } from "./chunks/writer-BtWpUaiH.js";
|
|
2
2
|
import { i as inferTextDtype, T as TextCellWriter, p as parseTextCell, W as WIDENING_UNSUPPORTED_CODE } from "./chunks/text-Dr0Ifpag.js";
|
|
3
3
|
import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
|
|
4
|
-
import { s as sniffNewline, a as sniffDelimiter, C as CsvRecordReader, B as BAD_QUOTE_CODE, U as UNCLOSED_QUOTE_CODE } from "./chunks/records-
|
|
5
|
-
import {
|
|
4
|
+
import { s as sniffNewline, a as sniffDelimiter, C as CsvRecordReader, D as DELIMITER_CANDIDATES, B as BAD_QUOTE_CODE, U as UNCLOSED_QUOTE_CODE } from "./chunks/records-IHsCfv7s.js";
|
|
5
|
+
import { m as quoteCsvCell } from "./chunks/escape-D1f9-cwf.js";
|
|
6
6
|
const SOURCE_NAMES = Object.freeze([
|
|
7
7
|
"source",
|
|
8
8
|
"src",
|
|
@@ -277,7 +277,7 @@ const ID_MERGED_CODE = ID_MERGED_CODE$1;
|
|
|
277
277
|
const COLUMN_MISSING_CODE = "W_CSV_COLUMN_MISSING";
|
|
278
278
|
const ROLE_TAKEN_CODE = ROLE_TAKEN_CODE$1;
|
|
279
279
|
const DUPLICATE_EDGE_ID_CODE = DUPLICATE_EDGE_ID_CODE$1;
|
|
280
|
-
const TABLE_MODES = /* @__PURE__ */ new Set(["edges", "nodes", "auto"]);
|
|
280
|
+
const TABLE_MODES = /* @__PURE__ */ new Set(["edges", "nodes", "adjacency", "auto"]);
|
|
281
281
|
const USED_OPTIONS = /* @__PURE__ */ new Set([
|
|
282
282
|
"ids",
|
|
283
283
|
"addMissingNodes",
|
|
@@ -330,7 +330,7 @@ function resolveCsvOptions(options) {
|
|
|
330
330
|
});
|
|
331
331
|
}
|
|
332
332
|
if (o.table !== void 0 && !TABLE_MODES.has(o.table)) {
|
|
333
|
-
throw new GraphFormatError("E_UNSUPPORTED", 'option table: expected "edges", "nodes" or "auto"', {
|
|
333
|
+
throw new GraphFormatError("E_UNSUPPORTED", 'option table: expected "edges", "nodes", "adjacency" or "auto"', {
|
|
334
334
|
option: "table",
|
|
335
335
|
found: o.table
|
|
336
336
|
});
|
|
@@ -341,6 +341,28 @@ function resolveCsvOptions(options) {
|
|
|
341
341
|
if (o.typeColumn !== null) {
|
|
342
342
|
checkColumnRef("typeColumn", o.typeColumn);
|
|
343
343
|
}
|
|
344
|
+
if (o.table === "adjacency") {
|
|
345
|
+
for (const name of ["sourceColumn", "targetColumn", "typeColumn", "idColumn"]) {
|
|
346
|
+
if (o[name] !== void 0) {
|
|
347
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: an adjacency table has no columns`, {
|
|
348
|
+
option: name,
|
|
349
|
+
found: o[name]
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (o.rowNumberIds === true) {
|
|
354
|
+
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: an adjacency table names its nodes", {
|
|
355
|
+
option: "rowNumberIds",
|
|
356
|
+
found: o.rowNumberIds
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (o.rowNumberIds !== void 0 && typeof o.rowNumberIds !== "boolean") {
|
|
361
|
+
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: expected a boolean", {
|
|
362
|
+
option: "rowNumberIds",
|
|
363
|
+
found: o.rowNumberIds
|
|
364
|
+
});
|
|
365
|
+
}
|
|
344
366
|
return {
|
|
345
367
|
delimiter: o.delimiter ?? null,
|
|
346
368
|
header: o.header ?? "auto",
|
|
@@ -349,7 +371,8 @@ function resolveCsvOptions(options) {
|
|
|
349
371
|
targetColumn: o.targetColumn ?? null,
|
|
350
372
|
typeColumn: o.typeColumn,
|
|
351
373
|
idColumn: o.idColumn ?? null,
|
|
352
|
-
nodes: o.nodes ?? null
|
|
374
|
+
nodes: o.nodes ?? null,
|
|
375
|
+
rowNumberIds: o.rowNumberIds ?? false
|
|
353
376
|
};
|
|
354
377
|
}
|
|
355
378
|
function checkColumnRef(name, value) {
|
|
@@ -417,6 +440,25 @@ function parseKind(text) {
|
|
|
417
440
|
return null;
|
|
418
441
|
}
|
|
419
442
|
}
|
|
443
|
+
function splitNeighbour(text) {
|
|
444
|
+
const colon = text.lastIndexOf(":");
|
|
445
|
+
if (colon < 0) {
|
|
446
|
+
return { id: text, weight: void 0 };
|
|
447
|
+
}
|
|
448
|
+
const suffix = text.slice(colon + 1);
|
|
449
|
+
if (suffix.length === 0) {
|
|
450
|
+
return { id: text.slice(0, colon), weight: void 0 };
|
|
451
|
+
}
|
|
452
|
+
const weight = numberOrNull(suffix);
|
|
453
|
+
return weight === null ? { id: text, weight: void 0 } : { id: text.slice(0, colon), weight };
|
|
454
|
+
}
|
|
455
|
+
function numberOrNull(text) {
|
|
456
|
+
try {
|
|
457
|
+
return parseWeightText(text) ?? null;
|
|
458
|
+
} catch {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
420
462
|
class TableReader {
|
|
421
463
|
/**
|
|
422
464
|
* Create a table reader.
|
|
@@ -436,9 +478,12 @@ class TableReader {
|
|
|
436
478
|
this.where = { line: null, element: null };
|
|
437
479
|
this.state = state;
|
|
438
480
|
this.kind = kind;
|
|
481
|
+
this.rowNumbers = state.csv.rowNumberIds && (kind === "nodes" || kind === "auto" && state.csv.nodes === null);
|
|
439
482
|
const readerOptions = {
|
|
440
483
|
delimiter: state.csv.delimiter,
|
|
441
484
|
comments: COMMENT_CHARS,
|
|
485
|
+
// field counts say nothing about an adjacency table, whose rows vary in width
|
|
486
|
+
skipQuoteErrors: kind === "adjacency",
|
|
442
487
|
signal: state.common.signal,
|
|
443
488
|
onProgress: progress ? state.common.onProgress : null,
|
|
444
489
|
encoding: state.common.encoding
|
|
@@ -463,12 +508,20 @@ class TableReader {
|
|
|
463
508
|
async readRows(iterator) {
|
|
464
509
|
const { report } = this.state;
|
|
465
510
|
const first = await iterator.next();
|
|
511
|
+
if (first.done && this.kind === "adjacency") {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
466
514
|
const firstRow = first.done ? report.fail(EMPTY_INPUT_CODE, "the input is empty: no header row and no records") : first.value;
|
|
467
515
|
const firstLine = this.reader.line;
|
|
468
516
|
const firstQuoted = this.reader.quoted.slice(0, firstRow.length);
|
|
469
517
|
const pending = [];
|
|
470
518
|
let header;
|
|
471
|
-
|
|
519
|
+
let mode = this.state.csv.header;
|
|
520
|
+
if (mode === "auto" && this.kind === "adjacency") {
|
|
521
|
+
mode = false;
|
|
522
|
+
} else if (mode === "auto" && this.rowNumbers) {
|
|
523
|
+
mode = true;
|
|
524
|
+
}
|
|
472
525
|
if (mode === "auto") {
|
|
473
526
|
const second = await iterator.next();
|
|
474
527
|
const secondRow = second.done ? null : second.value;
|
|
@@ -527,6 +580,9 @@ class TableReader {
|
|
|
527
580
|
*/
|
|
528
581
|
resolvePlan(names, header, line) {
|
|
529
582
|
const { csv, report } = this.state;
|
|
583
|
+
if (this.kind === "adjacency") {
|
|
584
|
+
return { kind: "adjacency", names: [] };
|
|
585
|
+
}
|
|
530
586
|
const width = names.length;
|
|
531
587
|
let source = -1;
|
|
532
588
|
let target = -1;
|
|
@@ -565,7 +621,7 @@ class TableReader {
|
|
|
565
621
|
);
|
|
566
622
|
}
|
|
567
623
|
const idResolves = header ? findColumn(names, ID_NAMES) >= 0 : width >= 1;
|
|
568
|
-
if (this.kind === "auto" && csv.idColumn === null && !idResolves) {
|
|
624
|
+
if (this.kind === "auto" && csv.idColumn === null && !idResolves && !this.rowNumbers) {
|
|
569
625
|
report.fail(
|
|
570
626
|
NO_ENDPOINT_COLUMNS_CODE,
|
|
571
627
|
`no source / target columns and no id column in the header (${shown}); the input is neither an edge table nor a node table`,
|
|
@@ -674,6 +730,10 @@ class TableReader {
|
|
|
674
730
|
id = -1;
|
|
675
731
|
break;
|
|
676
732
|
default:
|
|
733
|
+
if (idColumn < 0 && this.rowNumbers) {
|
|
734
|
+
id = -1;
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
677
737
|
if (idColumn < 0) {
|
|
678
738
|
report.fail(
|
|
679
739
|
NO_ID_COLUMN_CODE,
|
|
@@ -695,7 +755,8 @@ class TableReader {
|
|
|
695
755
|
attributes.push(i);
|
|
696
756
|
}
|
|
697
757
|
}
|
|
698
|
-
|
|
758
|
+
const rowNumber = id < 0 && common.nodeIdFrom !== "index";
|
|
759
|
+
return { kind: "nodes", names, width: names.length, id, rowNumber, label, attributes };
|
|
699
760
|
}
|
|
700
761
|
/**
|
|
701
762
|
* Declare the role columns and create a writer per attribute column.
|
|
@@ -703,6 +764,9 @@ class TableReader {
|
|
|
703
764
|
*/
|
|
704
765
|
prepareColumns(line) {
|
|
705
766
|
const plan = this.requirePlan();
|
|
767
|
+
if (plan.kind === "adjacency") {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
706
770
|
const { sink, report } = this.state;
|
|
707
771
|
const domain = plan.kind === "edges" ? "edge" : "node";
|
|
708
772
|
const origin = { format: "csv" };
|
|
@@ -752,8 +816,72 @@ class TableReader {
|
|
|
752
816
|
this.dataRows++;
|
|
753
817
|
if (plan.kind === "edges") {
|
|
754
818
|
this.processEdgeRow(plan, row, quoted, line);
|
|
755
|
-
} else {
|
|
819
|
+
} else if (plan.kind === "nodes") {
|
|
756
820
|
this.processNodeRow(plan, row, quoted, line);
|
|
821
|
+
} else {
|
|
822
|
+
this.processAdjacencyRow(row, quoted, line);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Push one adjacency row: the node, then one edge per set neighbour cell, in row order.
|
|
827
|
+
* @param row - the cells
|
|
828
|
+
* @param quoted - whether each cell was quoted
|
|
829
|
+
* @param line - the row's line
|
|
830
|
+
*/
|
|
831
|
+
processAdjacencyRow(row, quoted, line) {
|
|
832
|
+
const { report, sink, resolver, common } = this.state;
|
|
833
|
+
const { counts } = report;
|
|
834
|
+
const { where } = this;
|
|
835
|
+
where.line = line;
|
|
836
|
+
where.element = null;
|
|
837
|
+
let neighbours = 0;
|
|
838
|
+
for (let k = 1; k < row.length; k++) {
|
|
839
|
+
if (!isUnset(row[k], quoted[k])) {
|
|
840
|
+
neighbours++;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (isUnset(row[0], quoted[0])) {
|
|
844
|
+
report.error("missing-value", MISSING_ID_CODE, `line ${line}: blank node cell`, { line });
|
|
845
|
+
counts.skippedNodes++;
|
|
846
|
+
counts.skippedEdges += neighbours;
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const kind = this.state.commentDirected ?? common.defaultDirected ? "directed" : "undirected";
|
|
850
|
+
if (!this.state.headerSet) {
|
|
851
|
+
this.state.headerSet = true;
|
|
852
|
+
resolver.setHeader(kind !== "undirected", where);
|
|
853
|
+
}
|
|
854
|
+
let source;
|
|
855
|
+
try {
|
|
856
|
+
where.element = row[0];
|
|
857
|
+
source = this.coerce(row[0]);
|
|
858
|
+
if (sink.indexOf(source) === INVALID_INDEX) {
|
|
859
|
+
counts.nodes++;
|
|
860
|
+
}
|
|
861
|
+
sink.addNode(source);
|
|
862
|
+
} catch (err) {
|
|
863
|
+
report.recordError(err, where);
|
|
864
|
+
counts.skippedNodes++;
|
|
865
|
+
counts.skippedEdges += neighbours;
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
for (let k = 1; k < row.length; k++) {
|
|
869
|
+
if (isUnset(row[k], quoted[k])) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
where.element = row[k];
|
|
873
|
+
try {
|
|
874
|
+
const cell = splitNeighbour(row[k]);
|
|
875
|
+
const target = this.coerce(cell.id);
|
|
876
|
+
const targetNew = sink.indexOf(target) === INVALID_INDEX;
|
|
877
|
+
const before = sink.edgeCount;
|
|
878
|
+
resolver.addEdge(source, target, kind, common.weightFrom === null ? void 0 : cell.weight, where);
|
|
879
|
+
counts.edges += sink.edgeCount - before;
|
|
880
|
+
counts.nodes += targetNew ? 1 : 0;
|
|
881
|
+
} catch (err) {
|
|
882
|
+
report.recordError(err, where);
|
|
883
|
+
counts.skippedEdges++;
|
|
884
|
+
}
|
|
757
885
|
}
|
|
758
886
|
}
|
|
759
887
|
/**
|
|
@@ -884,7 +1012,7 @@ class TableReader {
|
|
|
884
1012
|
where.element = idText;
|
|
885
1013
|
let index;
|
|
886
1014
|
try {
|
|
887
|
-
const id = plan.id >= 0 ? this.coerce(idText) : ordinal;
|
|
1015
|
+
const id = plan.id >= 0 || plan.rowNumber ? this.coerce(idText) : ordinal;
|
|
888
1016
|
if (sink.indexOf(id) !== INVALID_INDEX) {
|
|
889
1017
|
report.warning(
|
|
890
1018
|
"merged",
|
|
@@ -1069,7 +1197,9 @@ const CSV_LOSS = Object.freeze({
|
|
|
1069
1197
|
/** The edge table carries no node without an edge: isolated nodes vanish on re-import. */
|
|
1070
1198
|
ISOLATED_NODES: "W_CSV_ISOLATED_NODES",
|
|
1071
1199
|
/** The edge table lists nodes by first appearance; the node order (and indices) change on re-import. */
|
|
1072
|
-
NODE_ORDER: "W_CSV_NODE_ORDER"
|
|
1200
|
+
NODE_ORDER: "W_CSV_NODE_ORDER",
|
|
1201
|
+
/** An adjacency table holds no edge column but the weight: edge ids, labels and attributes are not written. */
|
|
1202
|
+
EDGE_COLUMNS: "W_CSV_EDGE_COLUMNS"
|
|
1073
1203
|
});
|
|
1074
1204
|
const CSV_CAPABILITIES = capabilities({
|
|
1075
1205
|
mixedDirection: true,
|
|
@@ -1102,7 +1232,8 @@ const SKIPPED_ROLES = /* @__PURE__ */ new Set([
|
|
|
1102
1232
|
"timestamp",
|
|
1103
1233
|
"timestamps",
|
|
1104
1234
|
"spells",
|
|
1105
|
-
"open"
|
|
1235
|
+
"open",
|
|
1236
|
+
"spellsOpen"
|
|
1106
1237
|
]);
|
|
1107
1238
|
function resolveCsvExportOptions(options) {
|
|
1108
1239
|
const o = options ?? {};
|
|
@@ -1112,8 +1243,8 @@ function resolveCsvExportOptions(options) {
|
|
|
1112
1243
|
found: o.dialect
|
|
1113
1244
|
});
|
|
1114
1245
|
}
|
|
1115
|
-
if (o.table !== void 0 && o.table !== "edges" && o.table !== "nodes") {
|
|
1116
|
-
throw new GraphFormatError("E_UNSUPPORTED", 'option table: expected "edges" or "
|
|
1246
|
+
if (o.table !== void 0 && o.table !== "edges" && o.table !== "nodes" && o.table !== "adjacency") {
|
|
1247
|
+
throw new GraphFormatError("E_UNSUPPORTED", 'option table: expected "edges", "nodes" or "adjacency"', {
|
|
1117
1248
|
option: "table",
|
|
1118
1249
|
found: o.table
|
|
1119
1250
|
});
|
|
@@ -1131,6 +1262,12 @@ function resolveCsvExportOptions(options) {
|
|
|
1131
1262
|
found: o.newline
|
|
1132
1263
|
});
|
|
1133
1264
|
}
|
|
1265
|
+
if (o.table === "adjacency" && o.header === true) {
|
|
1266
|
+
throw new GraphFormatError("E_UNSUPPORTED", "option header: an adjacency table has no header row", {
|
|
1267
|
+
option: "header",
|
|
1268
|
+
found: o.header
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1134
1271
|
if (o.header !== void 0 && typeof o.header !== "boolean") {
|
|
1135
1272
|
throw new GraphFormatError("E_UNSUPPORTED", "option header: expected a boolean", {
|
|
1136
1273
|
option: "header",
|
|
@@ -1255,19 +1392,20 @@ function planExport(snapshot, options, values) {
|
|
|
1255
1392
|
edgeRows.push(e);
|
|
1256
1393
|
}
|
|
1257
1394
|
}
|
|
1258
|
-
if (csv.dialect.type === null) {
|
|
1395
|
+
if (csv.dialect.type === null || csv.table === "adjacency") {
|
|
1396
|
+
const where = csv.table === "adjacency" ? "an adjacency table" : "the generic dialect";
|
|
1259
1397
|
const mixed = countMixedEdges(snapshot);
|
|
1260
1398
|
if (!snapshot.directed) {
|
|
1261
1399
|
note(
|
|
1262
1400
|
CSV_LOSS.DIRECTION_DROPPED,
|
|
1263
|
-
|
|
1401
|
+
`${where} has no direction column; ${snapshot.edgeCount} undirected edge(s) read back as directed unless the importer is told otherwise`,
|
|
1264
1402
|
null,
|
|
1265
1403
|
snapshot.edgeCount
|
|
1266
1404
|
);
|
|
1267
1405
|
} else if (mixed > 0) {
|
|
1268
1406
|
note(
|
|
1269
1407
|
CSV_LOSS.DIRECTION_DROPPED,
|
|
1270
|
-
|
|
1408
|
+
`${where} has no direction column; ${mixed} undirected edge(s) of a mixed graph read back as one directed edge each`,
|
|
1271
1409
|
null,
|
|
1272
1410
|
mixed
|
|
1273
1411
|
);
|
|
@@ -1306,6 +1444,17 @@ function planExport(snapshot, options, values) {
|
|
|
1306
1444
|
edgeLabel,
|
|
1307
1445
|
csv.table === "edges" ? note : null
|
|
1308
1446
|
);
|
|
1447
|
+
if (csv.table === "adjacency") {
|
|
1448
|
+
const dropped = edgeColumns.length + (edgeId === null ? 0 : 1) + (edgeLabel === null ? 0 : 1);
|
|
1449
|
+
if (dropped > 0) {
|
|
1450
|
+
note(
|
|
1451
|
+
CSV_LOSS.EDGE_COLUMNS,
|
|
1452
|
+
`${dropped} edge column(s) are not written: an adjacency table holds the weight only`,
|
|
1453
|
+
null,
|
|
1454
|
+
dropped
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1309
1458
|
if (csv.table === "edges" && values) {
|
|
1310
1459
|
checkRoleColumn(edgeId, "id", EDGE_ID_NAMES, "edge", edgeRows, note);
|
|
1311
1460
|
checkRoleColumn(edgeLabel, "label", LABEL_NAMES, "edge", edgeRows, note);
|
|
@@ -1556,8 +1705,80 @@ function typeText(snapshot, plan, e) {
|
|
|
1556
1705
|
}
|
|
1557
1706
|
return "Directed";
|
|
1558
1707
|
}
|
|
1708
|
+
function neighbourCell(id, weight) {
|
|
1709
|
+
if (weight !== null) {
|
|
1710
|
+
return `${id}:${weight}`;
|
|
1711
|
+
}
|
|
1712
|
+
return id.includes(":") ? `${id}:` : id;
|
|
1713
|
+
}
|
|
1714
|
+
function* adjacencyLines(snapshot, plan) {
|
|
1715
|
+
const { delimiter, newline } = plan.csv;
|
|
1716
|
+
const forced = (text) => `"${text.replace(/"/g, '""')}"`;
|
|
1717
|
+
const quote = (text) => DELIMITER_CANDIDATES.some((c) => text.includes(c)) ? forced(text) : quoteCsvCell(text, delimiter);
|
|
1718
|
+
const first = (text) => /^[#%]/.test(text) ? forced(text) : quote(text);
|
|
1719
|
+
const list = snapshot.edgeList();
|
|
1720
|
+
const { edgeRows, weights } = plan;
|
|
1721
|
+
const seen = new Uint8Array(snapshot.nodeCount);
|
|
1722
|
+
let next = 0;
|
|
1723
|
+
const advance = () => {
|
|
1724
|
+
while (next < seen.length && seen[next] === 1) {
|
|
1725
|
+
next++;
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
function* singles(upTo) {
|
|
1729
|
+
for (let i = next; i <= upTo; i++) {
|
|
1730
|
+
if (seen[i] === 0) {
|
|
1731
|
+
seen[i] = 1;
|
|
1732
|
+
yield first(plan.idText(i)) + newline;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
advance();
|
|
1736
|
+
}
|
|
1737
|
+
let r = 0;
|
|
1738
|
+
while (r < edgeRows.length) {
|
|
1739
|
+
const source = list.src[edgeRows[r]];
|
|
1740
|
+
let end = r;
|
|
1741
|
+
while (end < edgeRows.length && list.src[edgeRows[end]] === source) {
|
|
1742
|
+
end++;
|
|
1743
|
+
}
|
|
1744
|
+
const fresh = [];
|
|
1745
|
+
let highest = -1;
|
|
1746
|
+
let inOrder = true;
|
|
1747
|
+
const mark = (v) => {
|
|
1748
|
+
if (seen[v] === 0) {
|
|
1749
|
+
seen[v] = 1;
|
|
1750
|
+
inOrder &&= v === next + fresh.length;
|
|
1751
|
+
fresh.push(v);
|
|
1752
|
+
highest = Math.max(highest, v);
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
mark(source);
|
|
1756
|
+
for (let k = r; k < end; k++) {
|
|
1757
|
+
mark(list.dst[edgeRows[k]]);
|
|
1758
|
+
}
|
|
1759
|
+
if (!inOrder) {
|
|
1760
|
+
for (const v of fresh) {
|
|
1761
|
+
seen[v] = 0;
|
|
1762
|
+
}
|
|
1763
|
+
yield* singles(highest);
|
|
1764
|
+
}
|
|
1765
|
+
advance();
|
|
1766
|
+
const cells = [first(plan.idText(source))];
|
|
1767
|
+
for (let k = r; k < end; k++) {
|
|
1768
|
+
const e = edgeRows[k];
|
|
1769
|
+
cells.push(quote(neighbourCell(plan.idText(list.dst[e]), weights.weighted ? weights.text(e) : null)));
|
|
1770
|
+
}
|
|
1771
|
+
yield cells.join(delimiter) + newline;
|
|
1772
|
+
r = end;
|
|
1773
|
+
}
|
|
1774
|
+
yield* singles(snapshot.nodeCount - 1);
|
|
1775
|
+
}
|
|
1559
1776
|
function* lines(snapshot, plan) {
|
|
1560
1777
|
const { csv } = plan;
|
|
1778
|
+
if (csv.table === "adjacency") {
|
|
1779
|
+
yield* adjacencyLines(snapshot, plan);
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1561
1782
|
const { delimiter, newline } = csv;
|
|
1562
1783
|
const quote = (text) => quoteCsvCell(text, delimiter);
|
|
1563
1784
|
const cell = (column, row) => {
|