@graphty/graph-io 0.3.41 → 0.3.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/chunks/{children-CL3Cy0ez.js → children-CA4coBi0.js} +36 -27
- package/dist/chunks/children-CA4coBi0.js.map +1 -0
- package/dist/chunks/{choose-CwFQamH9.js → choose-DvpbdRuz.js} +2 -2
- package/dist/chunks/{choose-CwFQamH9.js.map → choose-DvpbdRuz.js.map} +1 -1
- package/dist/chunks/{constants-Cf4Nn6tw.js → constants-fT3E8JYY.js} +2 -2
- package/dist/chunks/{constants-Cf4Nn6tw.js.map → constants-fT3E8JYY.js.map} +1 -1
- package/dist/chunks/{escape-DBb8zuNE.js → escape-B62ZGM_a.js} +2 -2
- package/dist/chunks/{escape-DBb8zuNE.js.map → escape-B62ZGM_a.js.map} +1 -1
- package/dist/chunks/{export-DD3Lvw-F.js → export-rKLeGQMo.js} +232 -128
- package/dist/chunks/export-rKLeGQMo.js.map +1 -0
- package/dist/chunks/{exporter-CS35R6Fg.js → exporter-8hLNemFk.js} +4 -4
- package/dist/chunks/{exporter-CS35R6Fg.js.map → exporter-8hLNemFk.js.map} +1 -1
- package/dist/chunks/{exporter-D8r1N2dU.js → exporter-BWwEuOPT.js} +5 -5
- package/dist/chunks/{exporter-D8r1N2dU.js.map → exporter-BWwEuOPT.js.map} +1 -1
- package/dist/chunks/{exporter-BqtmlxNr.js → exporter-RVtXVetw.js} +36 -22
- package/dist/chunks/exporter-RVtXVetw.js.map +1 -0
- package/dist/chunks/{exporter-NMplSnYn.js → exporter-yLxKBvLE.js} +3 -3
- package/dist/chunks/{exporter-NMplSnYn.js.map → exporter-yLxKBvLE.js.map} +1 -1
- package/dist/chunks/{importer-S-abxJHn.js → importer-BbNb7eNg.js} +111 -71
- package/dist/chunks/importer-BbNb7eNg.js.map +1 -0
- package/dist/chunks/{importer-BdWHmlmG.js → importer-C98dpSH4.js} +5 -5
- package/dist/chunks/{importer-BdWHmlmG.js.map → importer-C98dpSH4.js.map} +1 -1
- package/dist/chunks/{importer-r29cWnEf.js → importer-D794XUTe.js} +3 -3
- package/dist/chunks/{importer-r29cWnEf.js.map → importer-D794XUTe.js.map} +1 -1
- package/dist/chunks/{importer-BfbzWk6k.js → importer-Dz7tI8Sa.js} +5 -5
- package/dist/chunks/{importer-BfbzWk6k.js.map → importer-Dz7tI8Sa.js.map} +1 -1
- package/dist/chunks/{importer-DviEsHy4.js → importer-Ht7LNg8L.js} +4 -4
- package/dist/chunks/{importer-DviEsHy4.js.map → importer-Ht7LNg8L.js.map} +1 -1
- package/dist/chunks/{importer-BKC7AmCA.js → importer-WrGRZb0N.js} +6 -6
- package/dist/chunks/{importer-BKC7AmCA.js.map → importer-WrGRZb0N.js.map} +1 -1
- package/dist/chunks/{importer-BmaZcskq.js → importer-xZ-3suY1.js} +5 -5
- package/dist/chunks/{importer-BmaZcskq.js.map → importer-xZ-3suY1.js.map} +1 -1
- package/dist/chunks/{json-elements-DdF-etPT.js → json-elements-DKSqpNW1.js} +3 -3
- package/dist/chunks/{json-elements-DdF-etPT.js.map → json-elements-DKSqpNW1.js.map} +1 -1
- package/dist/chunks/{ontology-export-BC0zyPhj.js → ontology-export-CtRXjg_i.js} +2 -2
- package/dist/chunks/{ontology-export-BC0zyPhj.js.map → ontology-export-CtRXjg_i.js.map} +1 -1
- package/dist/chunks/{records-Uc3LkABx.js → records-Cu4bxYOm.js} +56 -26
- package/dist/chunks/records-Cu4bxYOm.js.map +1 -0
- package/dist/chunks/{text-Bh9SaAob.js → text-DHOJXnED.js} +2 -2
- package/dist/chunks/{text-Bh9SaAob.js.map → text-DHOJXnED.js.map} +1 -1
- package/dist/csv.js +260 -196
- 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 +5 -4
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +3 -3
- package/dist/graph-io.js +27 -27
- package/dist/graphml.js +2 -2
- package/dist/json.js +1 -1
- package/dist/neo4j.js +3 -3
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/children.d.ts.map +1 -1
- package/dist/src/children.js +50 -27
- package/dist/src/children.js.map +1 -1
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +52 -33
- 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 +178 -82
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/lists.js +30 -15
- package/dist/src/common/lists.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +211 -121
- 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 +113 -74
- 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 +60 -26
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.d.ts +39 -0
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +112 -66
- package/dist/src/formats/dot/tokenizer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +3 -2
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +54 -20
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +2 -1
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/children.ts +54 -27
- package/src/common/attributes.ts +59 -30
- package/src/common/input.ts +223 -95
- package/src/common/lists.ts +29 -15
- package/src/formats/csv/exporter.ts +274 -136
- package/src/formats/csv/importer.ts +138 -78
- package/src/formats/csv/records.ts +71 -25
- package/src/formats/dot/tokenizer.ts +118 -68
- package/src/formats/gexf/exporter.ts +3 -2
- package/src/formats/json/dialect.ts +61 -22
- package/src/formats/json/importer.ts +2 -1
- package/dist/chunks/children-CL3Cy0ez.js.map +0 -1
- package/dist/chunks/export-DD3Lvw-F.js.map +0 -1
- package/dist/chunks/exporter-BqtmlxNr.js.map +0 -1
- package/dist/chunks/importer-S-abxJHn.js.map +0 -1
- package/dist/chunks/records-Uc3LkABx.js.map +0 -1
package/dist/csv.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { b as plural, bU as DICT_SAMPLE_ROWS, ac as DictHeuristic, R as ROLE_TAKEN_CODE$1, I as ID_MERGED_CODE$1, m as DUPLICATE_EDGE_ID_CODE$1, n as DUPLICATE_NODE_CODE$1, t as MISSING_ID_CODE$1, s as MISSING_ENDPOINT_CODE$1, E as EMPTY_INPUT_CODE$1, al as resolveImportOptions, a8 as ImportReportBuilder, ao as reportSinkOptions, an as reportUnusedOptions, aq as DirectionResolver, ar as IdCoercer, am as throwIfAborted, O as OPTION_IGNORED_CODE, aK as parseWeightText, a0 as uniqueColumnName, ay as RENAMED_CODE, a1 as declareResolved, c as capabilities, L as LOSS, a as checkCapabilities, r as resolveExportOptions, p as pairFolding,
|
|
2
|
-
import { i as inferTextDtype, T as TextCellWriter, p as parseTextCell, W as WIDENING_UNSUPPORTED_CODE } from "./chunks/text-
|
|
1
|
+
import { b as plural, bU as DICT_SAMPLE_ROWS, ac as DictHeuristic, R as ROLE_TAKEN_CODE$1, I as ID_MERGED_CODE$1, m as DUPLICATE_EDGE_ID_CODE$1, n as DUPLICATE_NODE_CODE$1, t as MISSING_ID_CODE$1, s as MISSING_ENDPOINT_CODE$1, E as EMPTY_INPUT_CODE$1, al as resolveImportOptions, a8 as ImportReportBuilder, ao as reportSinkOptions, an as reportUnusedOptions, aq as DirectionResolver, ar as IdCoercer, am as throwIfAborted, O as OPTION_IGNORED_CODE, aK as parseWeightText, a0 as uniqueColumnName, ay as RENAMED_CODE, a1 as declareResolved, c as capabilities, L as LOSS, a as checkCapabilities, r as resolveExportOptions, p as pairFolding, $ as agree, e as explicitWeights, b_ as otherFormatDialect, bD as countMixedEdges, bS as joinListText, aG as formatInteger, aF as formatDecimal, aH as canonicalId, M as MIXED_DIRECTION_CODE, D as DIRECTION_FORCED_CODE, d as DIRECTION_REFUSED_CODE, S as SINK_OPTION_CODE, C as COLUMN_RENAMED_CODE, w as UNKNOWN_ENCODING_CODE, x as ENCODING_FALLBACK_CODE, y as INVALID_ENCODING_CODE, z as INVALID_UTF8_CODE, P as PRECISION_CODE, F as ELEMENT_ISSUE, G as INPUT_ISSUE } from "./chunks/export-rKLeGQMo.js";
|
|
2
|
+
import { i as inferTextDtype, T as TextCellWriter, p as parseTextCell, W as WIDENING_UNSUPPORTED_CODE } from "./chunks/text-DHOJXnED.js";
|
|
3
3
|
import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
|
|
4
|
-
import { s as stripLeadingComments, a as sniffNewline, b as sniffDelimiter, D as DELIMITER_CANDIDATES,
|
|
5
|
-
import { o as quoteCsvCell } from "./chunks/escape-
|
|
4
|
+
import { s as stripLeadingComments, a as sniffNewline, b as sniffDelimiter, D as DELIMITER_CANDIDATES, C as CsvRecordReader, d as splitRecords, c as checkRecordSyntax, B as BAD_QUOTE_CODE, U as UNCLOSED_QUOTE_CODE } from "./chunks/records-Cu4bxYOm.js";
|
|
5
|
+
import { o as quoteCsvCell } from "./chunks/escape-B62ZGM_a.js";
|
|
6
6
|
import { j as joinText, e as encodeChunks } from "./chunks/writer-DxjgHwSe.js";
|
|
7
7
|
const SOURCE_NAMES = Object.freeze([
|
|
8
8
|
"source",
|
|
@@ -336,6 +336,38 @@ function isPadded(text, quoted) {
|
|
|
336
336
|
}
|
|
337
337
|
function resolveCsvOptions(options) {
|
|
338
338
|
const o = options ?? {};
|
|
339
|
+
checkDialectOptions(o);
|
|
340
|
+
for (const name of ["sourceColumn", "targetColumn", "idColumn", "labelColumn"]) {
|
|
341
|
+
checkColumnRef(name, o[name]);
|
|
342
|
+
}
|
|
343
|
+
if (o.typeColumn !== null) {
|
|
344
|
+
checkColumnRef("typeColumn", o.typeColumn);
|
|
345
|
+
}
|
|
346
|
+
if (o.table === "adjacency") {
|
|
347
|
+
checkAdjacencyOptions(o);
|
|
348
|
+
}
|
|
349
|
+
if (o.rowNumberIds !== void 0 && typeof o.rowNumberIds !== "boolean") {
|
|
350
|
+
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: expected a boolean", {
|
|
351
|
+
option: "rowNumberIds",
|
|
352
|
+
found: o.rowNumberIds
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
delimiter: o.delimiter ?? null,
|
|
357
|
+
decimalComma: o.decimal === ",",
|
|
358
|
+
header: o.header ?? "auto",
|
|
359
|
+
table: o.table ?? "auto",
|
|
360
|
+
sourceColumn: o.sourceColumn ?? null,
|
|
361
|
+
targetColumn: o.targetColumn ?? null,
|
|
362
|
+
typeColumn: o.typeColumn,
|
|
363
|
+
idColumn: o.idColumn ?? null,
|
|
364
|
+
labelColumn: o.labelColumn ?? null,
|
|
365
|
+
nodes: nodesInput(o.nodes),
|
|
366
|
+
rowNumberIds: o.rowNumberIds ?? false,
|
|
367
|
+
explicitDirected: typeof options?.defaultDirected === "boolean" ? options.defaultDirected : null
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function checkDialectOptions(o) {
|
|
339
371
|
if (o.delimiter !== void 0 && (typeof o.delimiter !== "string" || o.delimiter.length === 0)) {
|
|
340
372
|
throw new GraphFormatError("E_UNSUPPORTED", "option delimiter: expected a non-empty string", {
|
|
341
373
|
option: "delimiter",
|
|
@@ -369,48 +401,22 @@ function resolveCsvOptions(options) {
|
|
|
369
401
|
found: o.table
|
|
370
402
|
});
|
|
371
403
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
for (const name of ["sourceColumn", "targetColumn", "typeColumn", "idColumn", "labelColumn"]) {
|
|
380
|
-
if (o[name] !== void 0) {
|
|
381
|
-
throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: an adjacency table has no columns`, {
|
|
382
|
-
option: name,
|
|
383
|
-
found: o[name]
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
if (o.rowNumberIds === true) {
|
|
388
|
-
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: an adjacency table names its nodes", {
|
|
389
|
-
option: "rowNumberIds",
|
|
390
|
-
found: o.rowNumberIds
|
|
404
|
+
}
|
|
405
|
+
function checkAdjacencyOptions(o) {
|
|
406
|
+
for (const name of ["sourceColumn", "targetColumn", "typeColumn", "idColumn", "labelColumn"]) {
|
|
407
|
+
if (o[name] !== void 0) {
|
|
408
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: an adjacency table has no columns`, {
|
|
409
|
+
option: name,
|
|
410
|
+
found: o[name]
|
|
391
411
|
});
|
|
392
412
|
}
|
|
393
413
|
}
|
|
394
|
-
if (o.rowNumberIds
|
|
395
|
-
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds:
|
|
414
|
+
if (o.rowNumberIds === true) {
|
|
415
|
+
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: an adjacency table names its nodes", {
|
|
396
416
|
option: "rowNumberIds",
|
|
397
417
|
found: o.rowNumberIds
|
|
398
418
|
});
|
|
399
419
|
}
|
|
400
|
-
return {
|
|
401
|
-
delimiter: o.delimiter ?? null,
|
|
402
|
-
decimalComma: o.decimal === ",",
|
|
403
|
-
header: o.header ?? "auto",
|
|
404
|
-
table: o.table ?? "auto",
|
|
405
|
-
sourceColumn: o.sourceColumn ?? null,
|
|
406
|
-
targetColumn: o.targetColumn ?? null,
|
|
407
|
-
typeColumn: o.typeColumn,
|
|
408
|
-
idColumn: o.idColumn ?? null,
|
|
409
|
-
labelColumn: o.labelColumn ?? null,
|
|
410
|
-
nodes: nodesInput(o.nodes),
|
|
411
|
-
rowNumberIds: o.rowNumberIds ?? false,
|
|
412
|
-
explicitDirected: typeof options?.defaultDirected === "boolean" ? options.defaultDirected : null
|
|
413
|
-
};
|
|
414
420
|
}
|
|
415
421
|
function checkColumnRef(name, value) {
|
|
416
422
|
if (value === void 0) {
|
|
@@ -573,39 +579,11 @@ class TableReader {
|
|
|
573
579
|
const firstLine = this.reader.line;
|
|
574
580
|
const firstQuoted = this.reader.quoted.slice(0, firstRow.length);
|
|
575
581
|
const pending = [];
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
mode = true;
|
|
582
|
-
}
|
|
583
|
-
if (mode === "auto") {
|
|
584
|
-
const second = await iterator.next();
|
|
585
|
-
const secondRow = second.done ? null : second.value;
|
|
586
|
-
const secondLine = this.reader.line;
|
|
587
|
-
const secondQuoted = this.reader.quoted.slice(0, secondRow?.length ?? 0);
|
|
588
|
-
header = looksLikeHeader(firstRow, secondRow) || this.namesExplicitColumn(firstRow);
|
|
589
|
-
if (!header) {
|
|
590
|
-
pending.push({ row: firstRow, quoted: firstQuoted, line: firstLine });
|
|
591
|
-
}
|
|
592
|
-
if (secondRow !== null) {
|
|
593
|
-
pending.push({ row: secondRow, quoted: secondQuoted, line: secondLine });
|
|
594
|
-
}
|
|
595
|
-
} else {
|
|
596
|
-
header = mode;
|
|
597
|
-
if (!header) {
|
|
598
|
-
pending.push({ row: firstRow, quoted: firstQuoted, line: firstLine });
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
if (this.rowNumbers && !header) {
|
|
602
|
-
report.warning(
|
|
603
|
-
"unsupported",
|
|
604
|
-
OPTION_IGNORED_CODE,
|
|
605
|
-
"option rowNumberIds ignored: a headerless node table takes its ids from column 0",
|
|
606
|
-
{ line: firstLine, element: "rowNumberIds" }
|
|
607
|
-
);
|
|
608
|
-
}
|
|
582
|
+
const header = await this.decideHeader(
|
|
583
|
+
iterator,
|
|
584
|
+
{ row: firstRow, quoted: firstQuoted, line: firstLine },
|
|
585
|
+
pending
|
|
586
|
+
);
|
|
609
587
|
const names = header ? uniqueNames(headerNames(firstRow), report, firstLine) : positionalNames(firstRow.length);
|
|
610
588
|
const last = firstRow.length - 1;
|
|
611
589
|
const optionalLast = header && last > 0 && isUnset(firstRow[last], firstQuoted[last]);
|
|
@@ -635,6 +613,70 @@ class TableReader {
|
|
|
635
613
|
for (const { row, quoted, line } of pending) {
|
|
636
614
|
this.processRow(row, quoted, line);
|
|
637
615
|
}
|
|
616
|
+
await this.readRemainingRows(iterator);
|
|
617
|
+
if (this.dataRows === 0 && header) {
|
|
618
|
+
report.warning("missing-value", NO_DATA_ROWS_CODE, "the table has a header and no data rows", {
|
|
619
|
+
line: firstLine
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
this.reportReaderFindings();
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Decide whether the first row is the header, reading the second row when the header option is
|
|
626
|
+
* "auto", and queue the rows read so far that are data.
|
|
627
|
+
* @param iterator - the record iterator, past the first row
|
|
628
|
+
* @param first - the first row
|
|
629
|
+
* @param pending - receives the data rows read so far, in order
|
|
630
|
+
* @returns whether the first row is the header
|
|
631
|
+
*/
|
|
632
|
+
async decideHeader(iterator, first, pending) {
|
|
633
|
+
const mode = this.headerMode();
|
|
634
|
+
let second = null;
|
|
635
|
+
let header;
|
|
636
|
+
if (mode === "auto") {
|
|
637
|
+
const next = await iterator.next();
|
|
638
|
+
second = next.done ? null : { row: next.value, quoted: this.reader.quoted.slice(0, next.value.length), line: this.reader.line };
|
|
639
|
+
header = looksLikeHeader(first.row, second?.row ?? null) || this.namesExplicitColumn(first.row);
|
|
640
|
+
} else {
|
|
641
|
+
header = mode;
|
|
642
|
+
}
|
|
643
|
+
if (!header) {
|
|
644
|
+
pending.push(first);
|
|
645
|
+
}
|
|
646
|
+
if (second !== null) {
|
|
647
|
+
pending.push(second);
|
|
648
|
+
}
|
|
649
|
+
if (this.rowNumbers && !header) {
|
|
650
|
+
this.state.report.warning(
|
|
651
|
+
"unsupported",
|
|
652
|
+
OPTION_IGNORED_CODE,
|
|
653
|
+
"option rowNumberIds ignored: a headerless node table takes its ids from column 0",
|
|
654
|
+
{ line: first.line, element: "rowNumberIds" }
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
return header;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* The header option as this table reads it: an adjacency table has no header unless the caller
|
|
661
|
+
* says so (its rows vary in width); a table numbered by rowNumberIds has one, since a headerless
|
|
662
|
+
* table takes its ids from column 0.
|
|
663
|
+
* @returns true, false or "auto"
|
|
664
|
+
*/
|
|
665
|
+
headerMode() {
|
|
666
|
+
const mode = this.state.csv.header;
|
|
667
|
+
if (mode === "auto" && this.kind === "adjacency") {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
if (mode === "auto" && this.rowNumbers) {
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
return mode;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Push every row after the header and the rows read to decide it, then finish the column writers.
|
|
677
|
+
* @param iterator - the record iterator
|
|
678
|
+
*/
|
|
679
|
+
async readRemainingRows(iterator) {
|
|
638
680
|
const { signal } = this.state.common;
|
|
639
681
|
let sinceCheck = 0;
|
|
640
682
|
for (; ; ) {
|
|
@@ -651,12 +693,6 @@ class TableReader {
|
|
|
651
693
|
for (const writer of this.writers) {
|
|
652
694
|
writer?.finish();
|
|
653
695
|
}
|
|
654
|
-
if (this.dataRows === 0 && header) {
|
|
655
|
-
report.warning("missing-value", NO_DATA_ROWS_CODE, "the table has a header and no data rows", {
|
|
656
|
-
line: firstLine
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
this.reportReaderFindings();
|
|
660
696
|
}
|
|
661
697
|
/**
|
|
662
698
|
* Whether a row is Excel's delimiter directive (`sep=;`) under a delimiter the caller gave (a
|
|
@@ -1991,6 +2027,44 @@ function planExport(snapshot, options, values) {
|
|
|
1991
2027
|
edgeRows.push(e);
|
|
1992
2028
|
}
|
|
1993
2029
|
}
|
|
2030
|
+
directionNotes(snapshot, csv, edgeRows.length, note);
|
|
2031
|
+
if (folding.mutualCount > 0 && csv.table !== "nodes") {
|
|
2032
|
+
note(
|
|
2033
|
+
CSV_LOSS.MUTUAL_EXPANDED,
|
|
2034
|
+
`${folding.mutualCount} mutual pair${plural(folding.mutualCount)} ${agree(folding.mutualCount, "is", "are")} written as two directed rows; the mutual mark is lost`,
|
|
2035
|
+
null,
|
|
2036
|
+
folding.mutualCount
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
if (csv.table === "edges") {
|
|
2040
|
+
noteNodeCoverage(snapshot, edgeRows, note);
|
|
2041
|
+
}
|
|
2042
|
+
const weights = explicitWeights(snapshot);
|
|
2043
|
+
const { edgeId, edgeLabel, edgeColumns } = planEdgeColumns(snapshot, csv, edgeRows, values, note);
|
|
2044
|
+
const { nodeLabel, nodeColumns } = planNodeColumns(snapshot, csv, values, note);
|
|
2045
|
+
if (csv.table !== "nodes") {
|
|
2046
|
+
nodeTableNote(
|
|
2047
|
+
[...nodeLabel === null ? [] : [nodeLabel.meta.name], ...nodeColumns.map((c) => c.column.meta.name)],
|
|
2048
|
+
csv.table,
|
|
2049
|
+
notes
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
return {
|
|
2053
|
+
csv,
|
|
2054
|
+
common,
|
|
2055
|
+
notes,
|
|
2056
|
+
edgeRows,
|
|
2057
|
+
folding,
|
|
2058
|
+
edgeId,
|
|
2059
|
+
edgeLabel,
|
|
2060
|
+
weights,
|
|
2061
|
+
edgeColumns,
|
|
2062
|
+
nodeLabel,
|
|
2063
|
+
nodeColumns,
|
|
2064
|
+
idText
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
function directionNotes(snapshot, csv, edgeRowCount, note) {
|
|
1994
2068
|
if (csv.table !== "nodes" && (csv.dialect.type === null || csv.table === "adjacency")) {
|
|
1995
2069
|
const where = csv.table === "adjacency" ? "an adjacency table" : "the generic dialect";
|
|
1996
2070
|
const mixed = countMixedEdges(snapshot);
|
|
@@ -2009,7 +2083,7 @@ function planExport(snapshot, options, values) {
|
|
|
2009
2083
|
mixed
|
|
2010
2084
|
);
|
|
2011
2085
|
}
|
|
2012
|
-
} else if (csv.table === "edges" && !snapshot.directed &&
|
|
2086
|
+
} else if (csv.table === "edges" && !snapshot.directed && edgeRowCount === 0) {
|
|
2013
2087
|
note(
|
|
2014
2088
|
CSV_LOSS.DIRECTION_DROPPED,
|
|
2015
2089
|
"the direction lives in the Type cell of each edge row; an undirected graph without edges reads back as directed",
|
|
@@ -2017,18 +2091,8 @@ function planExport(snapshot, options, values) {
|
|
|
2017
2091
|
0
|
|
2018
2092
|
);
|
|
2019
2093
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
CSV_LOSS.MUTUAL_EXPANDED,
|
|
2023
|
-
`${folding.mutualCount} mutual pair${plural(folding.mutualCount)} ${agree(folding.mutualCount, "is", "are")} written as two directed rows; the mutual mark is lost`,
|
|
2024
|
-
null,
|
|
2025
|
-
folding.mutualCount
|
|
2026
|
-
);
|
|
2027
|
-
}
|
|
2028
|
-
if (csv.table === "edges") {
|
|
2029
|
-
noteNodeCoverage(snapshot, edgeRows, note);
|
|
2030
|
-
}
|
|
2031
|
-
const weights = explicitWeights(snapshot);
|
|
2094
|
+
}
|
|
2095
|
+
function planEdgeColumns(snapshot, csv, edgeRows, values, note) {
|
|
2032
2096
|
const edgeId = snapshot.edges.byRole("id");
|
|
2033
2097
|
const edgeLabel = snapshot.edges.byRole("label");
|
|
2034
2098
|
const edgeReserved = new Set([csv.dialect.source, csv.dialect.target, csv.dialect.weight].map(lower));
|
|
@@ -2061,6 +2125,9 @@ function planExport(snapshot, options, values) {
|
|
|
2061
2125
|
checkValues(column, "edge", edgeRows, note);
|
|
2062
2126
|
}
|
|
2063
2127
|
}
|
|
2128
|
+
return { edgeId, edgeLabel, edgeColumns };
|
|
2129
|
+
}
|
|
2130
|
+
function planNodeColumns(snapshot, csv, values, note) {
|
|
2064
2131
|
const nodeLabel = snapshot.nodes.byRole("label");
|
|
2065
2132
|
const nodeReserved = /* @__PURE__ */ new Set([lower(csv.dialect.id)]);
|
|
2066
2133
|
const nodeRows = [];
|
|
@@ -2077,34 +2144,13 @@ function planExport(snapshot, options, values) {
|
|
|
2077
2144
|
nodeLabel,
|
|
2078
2145
|
csv.table === "nodes" ? note : null
|
|
2079
2146
|
);
|
|
2080
|
-
if (csv.table === "nodes") {
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
checkValues(column, "node", nodeRows, note);
|
|
2085
|
-
}
|
|
2147
|
+
if (csv.table === "nodes" && values) {
|
|
2148
|
+
checkRoleColumn(nodeLabel, "label", LABEL_NAMES, "node", nodeRows, note);
|
|
2149
|
+
for (const { column } of nodeColumns) {
|
|
2150
|
+
checkValues(column, "node", nodeRows, note);
|
|
2086
2151
|
}
|
|
2087
|
-
} else {
|
|
2088
|
-
nodeTableNote(
|
|
2089
|
-
[...nodeLabel === null ? [] : [nodeLabel.meta.name], ...nodeColumns.map((c) => c.column.meta.name)],
|
|
2090
|
-
csv.table,
|
|
2091
|
-
notes
|
|
2092
|
-
);
|
|
2093
2152
|
}
|
|
2094
|
-
return {
|
|
2095
|
-
csv,
|
|
2096
|
-
common,
|
|
2097
|
-
notes,
|
|
2098
|
-
edgeRows,
|
|
2099
|
-
folding,
|
|
2100
|
-
edgeId,
|
|
2101
|
-
edgeLabel,
|
|
2102
|
-
weights,
|
|
2103
|
-
edgeColumns,
|
|
2104
|
-
nodeLabel,
|
|
2105
|
-
nodeColumns,
|
|
2106
|
-
idText
|
|
2107
|
-
};
|
|
2153
|
+
return { nodeLabel, nodeColumns };
|
|
2108
2154
|
}
|
|
2109
2155
|
function noteNodeCoverage(snapshot, edgeRows, note) {
|
|
2110
2156
|
const { nodeCount } = snapshot;
|
|
@@ -2236,20 +2282,7 @@ function checkValues(column, domain, rows, note, roleColumn = false) {
|
|
|
2236
2282
|
const { name, dtype } = column.meta;
|
|
2237
2283
|
const label = `${domain} column "${name}"`;
|
|
2238
2284
|
if (column.dtype === "f32" || column.dtype === "f64") {
|
|
2239
|
-
|
|
2240
|
-
const { components } = column.meta;
|
|
2241
|
-
const { data } = column;
|
|
2242
|
-
for (const row of rows) {
|
|
2243
|
-
if (!column.isSet(row)) {
|
|
2244
|
-
continue;
|
|
2245
|
-
}
|
|
2246
|
-
for (let k = 0; k < components; k++) {
|
|
2247
|
-
if (!Number.isFinite(data[row * components + k])) {
|
|
2248
|
-
nonFinite++;
|
|
2249
|
-
break;
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2285
|
+
const nonFinite = countNonFinite(column, rows);
|
|
2253
2286
|
if (nonFinite > 0) {
|
|
2254
2287
|
note(
|
|
2255
2288
|
CSV_LOSS.NONFINITE,
|
|
@@ -2263,15 +2296,50 @@ function checkValues(column, domain, rows, note, roleColumn = false) {
|
|
|
2263
2296
|
if (dtype !== "string" && dtype !== "dict") {
|
|
2264
2297
|
return;
|
|
2265
2298
|
}
|
|
2299
|
+
const scan = scanText(column, rows);
|
|
2300
|
+
if (roleColumn || scan.widest < 0) {
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
const readsAsDict = scan.candidate && scan.heuristic.decide() === "dict";
|
|
2304
|
+
if (scan.widest < WIDENING.string && !readsAsDict) {
|
|
2305
|
+
note(CSV_LOSS.TEXT_INFERRED, `${label}: every value reads back as ${TEXT_DTYPES[scan.widest]}`, name, scan.set);
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
if (dtype === "dict" && !readsAsDict) {
|
|
2309
|
+
note(
|
|
2310
|
+
CSV_LOSS.STORAGE_CLASS_CHANGED,
|
|
2311
|
+
`${label}: reads back as string (cardinality too high for a dict)`,
|
|
2312
|
+
name,
|
|
2313
|
+
null
|
|
2314
|
+
);
|
|
2315
|
+
} else if (dtype === "string" && readsAsDict) {
|
|
2316
|
+
note(CSV_LOSS.STORAGE_CLASS_CHANGED, `${label}: reads back as dict (low cardinality)`, name, null);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
function countNonFinite(column, rows) {
|
|
2320
|
+
let nonFinite = 0;
|
|
2321
|
+
const { components } = column.meta;
|
|
2322
|
+
const { data } = column;
|
|
2323
|
+
for (const row of rows) {
|
|
2324
|
+
if (!column.isSet(row)) {
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
for (let k = 0; k < components; k++) {
|
|
2328
|
+
if (!Number.isFinite(data[row * components + k])) {
|
|
2329
|
+
nonFinite++;
|
|
2330
|
+
break;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return nonFinite;
|
|
2335
|
+
}
|
|
2336
|
+
function scanText(column, rows) {
|
|
2266
2337
|
let widest = -1;
|
|
2267
2338
|
let candidate = true;
|
|
2268
2339
|
let set = 0;
|
|
2269
2340
|
const heuristic = new DictHeuristic(DICT_SAMPLE_ROWS);
|
|
2270
2341
|
for (const row of rows) {
|
|
2271
|
-
|
|
2272
|
-
continue;
|
|
2273
|
-
}
|
|
2274
|
-
const text = column.value(row);
|
|
2342
|
+
const text = column.isSet(row) ? column.value(row) : null;
|
|
2275
2343
|
if (typeof text !== "string") {
|
|
2276
2344
|
continue;
|
|
2277
2345
|
}
|
|
@@ -2279,30 +2347,11 @@ function checkValues(column, domain, rows, note, roleColumn = false) {
|
|
|
2279
2347
|
const kind = inferTextDtype(text);
|
|
2280
2348
|
widest = Math.max(widest, WIDENING[kind]);
|
|
2281
2349
|
if (!heuristic.decided) {
|
|
2282
|
-
|
|
2283
|
-
candidate = false;
|
|
2284
|
-
}
|
|
2350
|
+
candidate &&= kind === "string";
|
|
2285
2351
|
heuristic.observe(text);
|
|
2286
2352
|
}
|
|
2287
2353
|
}
|
|
2288
|
-
|
|
2289
|
-
return;
|
|
2290
|
-
}
|
|
2291
|
-
const readsAsDict = candidate && heuristic.decide() === "dict";
|
|
2292
|
-
if (widest < WIDENING.string && !readsAsDict) {
|
|
2293
|
-
note(CSV_LOSS.TEXT_INFERRED, `${label}: every value reads back as ${TEXT_DTYPES[widest]}`, name, set);
|
|
2294
|
-
return;
|
|
2295
|
-
}
|
|
2296
|
-
if (dtype === "dict" && !readsAsDict) {
|
|
2297
|
-
note(
|
|
2298
|
-
CSV_LOSS.STORAGE_CLASS_CHANGED,
|
|
2299
|
-
`${label}: reads back as string (cardinality too high for a dict)`,
|
|
2300
|
-
name,
|
|
2301
|
-
null
|
|
2302
|
-
);
|
|
2303
|
-
} else if (dtype === "string" && readsAsDict) {
|
|
2304
|
-
note(CSV_LOSS.STORAGE_CLASS_CHANGED, `${label}: reads back as dict (low cardinality)`, name, null);
|
|
2305
|
-
}
|
|
2354
|
+
return { widest, candidate, set, heuristic };
|
|
2306
2355
|
}
|
|
2307
2356
|
function typeText(snapshot, plan, e) {
|
|
2308
2357
|
if (!snapshot.directed || !plan.folding.sourceDirected(e)) {
|
|
@@ -2384,59 +2433,54 @@ function* adjacencyLines(snapshot, plan) {
|
|
|
2384
2433
|
yield* singles(snapshot.nodeCount - 1);
|
|
2385
2434
|
}
|
|
2386
2435
|
function* lines(snapshot, plan) {
|
|
2387
|
-
|
|
2388
|
-
if (csv.table === "adjacency") {
|
|
2436
|
+
if (plan.csv.table === "adjacency") {
|
|
2389
2437
|
yield* adjacencyLines(snapshot, plan);
|
|
2390
|
-
|
|
2438
|
+
} else if (plan.csv.table === "nodes") {
|
|
2439
|
+
yield* nodeLines(snapshot, plan);
|
|
2440
|
+
} else {
|
|
2441
|
+
yield* edgeLines(snapshot, plan);
|
|
2391
2442
|
}
|
|
2392
|
-
|
|
2443
|
+
}
|
|
2444
|
+
function cellWriters(delimiter) {
|
|
2393
2445
|
const quote = (text) => quoteCsvCell(text, delimiter);
|
|
2394
2446
|
const cell = (column, row) => {
|
|
2395
2447
|
const text = cellText(column, row);
|
|
2396
2448
|
return text === null ? "" : quote(text);
|
|
2397
2449
|
};
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
yield headers2.map(quote).join(delimiter) + newline;
|
|
2408
|
-
}
|
|
2409
|
-
for (let i = 0; i < snapshot.nodeCount; i++) {
|
|
2410
|
-
const cells = [quote(plan.idText(i))];
|
|
2411
|
-
if (plan.nodeLabel !== null) {
|
|
2412
|
-
cells.push(cell(plan.nodeLabel, i));
|
|
2413
|
-
}
|
|
2414
|
-
for (const { column } of plan.nodeColumns) {
|
|
2415
|
-
cells.push(cell(column, i));
|
|
2416
|
-
}
|
|
2417
|
-
yield cells.join(delimiter) + newline;
|
|
2418
|
-
}
|
|
2419
|
-
return;
|
|
2420
|
-
}
|
|
2421
|
-
const headers = [csv.dialect.source, csv.dialect.target];
|
|
2422
|
-
if (csv.dialect.type !== null) {
|
|
2423
|
-
headers.push(csv.dialect.type);
|
|
2424
|
-
}
|
|
2425
|
-
if (plan.edgeId !== null) {
|
|
2426
|
-
headers.push(plan.edgeId.meta.name);
|
|
2427
|
-
}
|
|
2428
|
-
if (plan.edgeLabel !== null) {
|
|
2429
|
-
headers.push(plan.edgeLabel.meta.name);
|
|
2430
|
-
}
|
|
2431
|
-
if (plan.weights.weighted) {
|
|
2432
|
-
headers.push(csv.dialect.weight);
|
|
2450
|
+
return { quote, cell };
|
|
2451
|
+
}
|
|
2452
|
+
function* nodeLines(snapshot, plan) {
|
|
2453
|
+
const { csv } = plan;
|
|
2454
|
+
const { delimiter, newline } = csv;
|
|
2455
|
+
const { quote, cell } = cellWriters(delimiter);
|
|
2456
|
+
const headers = [csv.dialect.id];
|
|
2457
|
+
if (plan.nodeLabel !== null) {
|
|
2458
|
+
headers.push(plan.nodeLabel.meta.name);
|
|
2433
2459
|
}
|
|
2434
|
-
for (const { header } of plan.
|
|
2460
|
+
for (const { header } of plan.nodeColumns) {
|
|
2435
2461
|
headers.push(header);
|
|
2436
2462
|
}
|
|
2437
2463
|
if (csv.header) {
|
|
2438
2464
|
yield headers.map(quote).join(delimiter) + newline;
|
|
2439
2465
|
}
|
|
2466
|
+
for (let i = 0; i < snapshot.nodeCount; i++) {
|
|
2467
|
+
const cells = [quote(plan.idText(i))];
|
|
2468
|
+
if (plan.nodeLabel !== null) {
|
|
2469
|
+
cells.push(cell(plan.nodeLabel, i));
|
|
2470
|
+
}
|
|
2471
|
+
for (const { column } of plan.nodeColumns) {
|
|
2472
|
+
cells.push(cell(column, i));
|
|
2473
|
+
}
|
|
2474
|
+
yield cells.join(delimiter) + newline;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
function* edgeLines(snapshot, plan) {
|
|
2478
|
+
const { csv } = plan;
|
|
2479
|
+
const { delimiter, newline } = csv;
|
|
2480
|
+
const { quote, cell } = cellWriters(delimiter);
|
|
2481
|
+
if (csv.header) {
|
|
2482
|
+
yield edgeHeaders(plan).map(quote).join(delimiter) + newline;
|
|
2483
|
+
}
|
|
2440
2484
|
const list = snapshot.edgeList();
|
|
2441
2485
|
for (const e of plan.edgeRows) {
|
|
2442
2486
|
const cells = [quote(plan.idText(list.src[e])), quote(plan.idText(list.dst[e]))];
|
|
@@ -2458,6 +2502,26 @@ function* lines(snapshot, plan) {
|
|
|
2458
2502
|
yield cells.join(delimiter) + newline;
|
|
2459
2503
|
}
|
|
2460
2504
|
}
|
|
2505
|
+
function edgeHeaders(plan) {
|
|
2506
|
+
const { dialect } = plan.csv;
|
|
2507
|
+
const headers = [dialect.source, dialect.target];
|
|
2508
|
+
if (dialect.type !== null) {
|
|
2509
|
+
headers.push(dialect.type);
|
|
2510
|
+
}
|
|
2511
|
+
if (plan.edgeId !== null) {
|
|
2512
|
+
headers.push(plan.edgeId.meta.name);
|
|
2513
|
+
}
|
|
2514
|
+
if (plan.edgeLabel !== null) {
|
|
2515
|
+
headers.push(plan.edgeLabel.meta.name);
|
|
2516
|
+
}
|
|
2517
|
+
if (plan.weights.weighted) {
|
|
2518
|
+
headers.push(dialect.weight);
|
|
2519
|
+
}
|
|
2520
|
+
for (const { header } of plan.edgeColumns) {
|
|
2521
|
+
headers.push(header);
|
|
2522
|
+
}
|
|
2523
|
+
return headers;
|
|
2524
|
+
}
|
|
2461
2525
|
function prepare(snapshot, options) {
|
|
2462
2526
|
const plan = planExport(snapshot, options, false);
|
|
2463
2527
|
const collision = plan.notes.find((n) => n.code === CSV_LOSS.ID_TEXT_COLLISION);
|