@graphty/graph-io 0.3.40 → 0.3.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/chunks/{children-CL3Cy0ez.js → children-CA4coBi0.js} +36 -27
- package/dist/chunks/children-CA4coBi0.js.map +1 -0
- package/dist/chunks/{choose-CS3fvGT1.js → choose-DvpbdRuz.js} +2 -2
- package/dist/chunks/{choose-CS3fvGT1.js.map → choose-DvpbdRuz.js.map} +1 -1
- package/dist/chunks/{constants-DqKZT6cH.js → constants-fT3E8JYY.js} +2 -2
- package/dist/chunks/{constants-DqKZT6cH.js.map → constants-fT3E8JYY.js.map} +1 -1
- package/dist/chunks/{escape-BJRLd3vJ.js → escape-B62ZGM_a.js} +2 -2
- package/dist/chunks/{escape-BJRLd3vJ.js.map → escape-B62ZGM_a.js.map} +1 -1
- package/dist/chunks/{export-B-aVCwyG.js → export-rKLeGQMo.js} +404 -243
- package/dist/chunks/export-rKLeGQMo.js.map +1 -0
- package/dist/chunks/{exporter-6ocL_9x_.js → exporter-8hLNemFk.js} +4 -4
- package/dist/chunks/{exporter-6ocL_9x_.js.map → exporter-8hLNemFk.js.map} +1 -1
- package/dist/chunks/{exporter-BVXnV9-9.js → exporter-BWwEuOPT.js} +29 -17
- package/dist/chunks/exporter-BWwEuOPT.js.map +1 -0
- package/dist/chunks/{exporter-Bcp-l4Bo.js → exporter-CpRyHZ3H.js} +574 -475
- package/dist/chunks/exporter-CpRyHZ3H.js.map +1 -0
- package/dist/chunks/{exporter-SFYkDzD0.js → exporter-yLxKBvLE.js} +23 -20
- package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-yLxKBvLE.js.map} +1 -1
- package/dist/chunks/{importer-DcKB97kW.js → importer-BbNb7eNg.js} +111 -71
- package/dist/chunks/importer-BbNb7eNg.js.map +1 -0
- package/dist/chunks/{importer-D_srHyj6.js → importer-C98dpSH4.js} +5 -5
- package/dist/chunks/{importer-D_srHyj6.js.map → importer-C98dpSH4.js.map} +1 -1
- package/dist/chunks/{importer-BJlGAOht.js → importer-D794XUTe.js} +3 -3
- package/dist/chunks/{importer-BJlGAOht.js.map → importer-D794XUTe.js.map} +1 -1
- package/dist/chunks/{importer-AjwOJrn2.js → importer-Dz7tI8Sa.js} +202 -181
- package/dist/chunks/importer-Dz7tI8Sa.js.map +1 -0
- package/dist/chunks/{importer-DEp_fYjf.js → importer-Ht7LNg8L.js} +92 -64
- package/dist/chunks/importer-Ht7LNg8L.js.map +1 -0
- package/dist/chunks/{importer-CPqnIY8m.js → importer-WrGRZb0N.js} +42 -29
- package/dist/chunks/importer-WrGRZb0N.js.map +1 -0
- package/dist/chunks/{importer-BpuS3J8C.js → importer-xZ-3suY1.js} +74 -61
- package/dist/chunks/importer-xZ-3suY1.js.map +1 -0
- package/dist/chunks/{json-elements-DpO6AXIc.js → json-elements-DKSqpNW1.js} +3 -3
- package/dist/chunks/{json-elements-DpO6AXIc.js.map → json-elements-DKSqpNW1.js.map} +1 -1
- package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-CtRXjg_i.js} +2 -2
- package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-CtRXjg_i.js.map} +1 -1
- package/dist/chunks/{records-r3SFHz11.js → records-Cu4bxYOm.js} +69 -36
- package/dist/chunks/records-Cu4bxYOm.js.map +1 -0
- package/dist/chunks/{text-Bjfa1xYL.js → text-DHOJXnED.js} +20 -11
- package/dist/chunks/text-DHOJXnED.js.map +1 -0
- package/dist/csv.js +323 -241
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +1 -1
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +195 -97
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +170 -149
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +27 -27
- package/dist/graphml.js +2 -2
- package/dist/json.js +1 -1
- package/dist/neo4j.js +103 -65
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/children.d.ts.map +1 -1
- package/dist/src/children.js +50 -27
- package/dist/src/children.js.map +1 -1
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +86 -42
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +358 -184
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/lists.js +30 -15
- package/dist/src/common/lists.js.map +1 -1
- package/dist/src/common/text.d.ts +7 -0
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +15 -7
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +36 -19
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +288 -146
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +129 -80
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +78 -36
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +11 -8
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.d.ts +39 -0
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +112 -66
- package/dist/src/formats/dot/tokenizer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +65 -37
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +138 -50
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +51 -35
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +103 -58
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +17 -8
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +34 -24
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +17 -13
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/tree.js +23 -14
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +22 -10
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +54 -20
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +38 -21
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +536 -343
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/obographs.js +29 -17
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +73 -43
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +37 -16
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +21 -11
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts.map +1 -1
- package/dist/src/formats/obo/exporter.js +18 -7
- package/dist/src/formats/obo/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +142 -73
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +26 -15
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +85 -58
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +7 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +79 -40
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +38 -12
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +38 -18
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/sniff.js +109 -57
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/children.ts +54 -27
- package/src/common/attributes.ts +118 -49
- package/src/common/input.ts +439 -197
- package/src/common/lists.ts +29 -15
- package/src/common/text.ts +24 -15
- package/src/common/zip.ts +47 -18
- package/src/formats/csv/exporter.ts +377 -175
- package/src/formats/csv/importer.ts +155 -84
- package/src/formats/csv/records.ts +90 -35
- package/src/formats/cys/tables.ts +11 -10
- package/src/formats/dot/tokenizer.ts +118 -68
- package/src/formats/gexf/exporter.ts +67 -36
- package/src/formats/gexf/importer.ts +173 -63
- package/src/formats/gml/exporter.ts +78 -82
- package/src/formats/gml/importer.ts +119 -68
- package/src/formats/gml/syntax.ts +18 -8
- package/src/formats/graphml/exporter.ts +44 -24
- package/src/formats/graphml/importer.ts +18 -11
- package/src/formats/graphml/tree.ts +22 -12
- package/src/formats/graphml/yfiles.ts +23 -10
- package/src/formats/json/dialect.ts +61 -22
- package/src/formats/json/exporter.ts +40 -21
- package/src/formats/json/importer.ts +701 -385
- package/src/formats/json/obographs.ts +35 -15
- package/src/formats/neo4j/exporter.ts +79 -41
- package/src/formats/neo4j/header.ts +50 -16
- package/src/formats/neo4j/importer.ts +21 -10
- package/src/formats/obo/exporter.ts +34 -17
- package/src/formats/pajek/exporter.ts +180 -117
- package/src/formats/pajek/importer.ts +27 -15
- package/src/formats/pajek/syntax.ts +99 -58
- package/src/formats/xgmml/emit.ts +88 -40
- package/src/formats/xgmml/exporter.ts +40 -12
- package/src/formats/xgmml/importer.ts +45 -23
- package/src/sniff.ts +115 -59
- package/dist/chunks/children-CL3Cy0ez.js.map +0 -1
- package/dist/chunks/export-B-aVCwyG.js.map +0 -1
- package/dist/chunks/exporter-BVXnV9-9.js.map +0 -1
- package/dist/chunks/exporter-Bcp-l4Bo.js.map +0 -1
- package/dist/chunks/importer-AjwOJrn2.js.map +0 -1
- package/dist/chunks/importer-BpuS3J8C.js.map +0 -1
- package/dist/chunks/importer-CPqnIY8m.js.map +0 -1
- package/dist/chunks/importer-DEp_fYjf.js.map +0 -1
- package/dist/chunks/importer-DcKB97kW.js.map +0 -1
- package/dist/chunks/records-r3SFHz11.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
|
@@ -385,6 +385,13 @@ interface AdjacencyPlan {
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
/** Everything one import call shares between its tables. */
|
|
388
|
+
/** A row read before the plan exists, kept to be pushed once it does. */
|
|
389
|
+
interface PendingRow {
|
|
390
|
+
readonly row: string[];
|
|
391
|
+
readonly quoted: readonly boolean[];
|
|
392
|
+
readonly line: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
388
395
|
interface ImportState {
|
|
389
396
|
readonly sink: GraphSink;
|
|
390
397
|
readonly report: ImportReportBuilder;
|
|
@@ -455,6 +462,43 @@ function isPadded(text: string, quoted: boolean | undefined): boolean {
|
|
|
455
462
|
*/
|
|
456
463
|
function resolveCsvOptions(options: (CsvImportOptions & CommonImportOptions) | undefined): ResolvedCsvOptions {
|
|
457
464
|
const o: CsvImportOptions = options ?? {};
|
|
465
|
+
checkDialectOptions(o);
|
|
466
|
+
for (const name of ["sourceColumn", "targetColumn", "idColumn", "labelColumn"] as const) {
|
|
467
|
+
checkColumnRef(name, o[name]);
|
|
468
|
+
}
|
|
469
|
+
if (o.typeColumn !== null) {
|
|
470
|
+
checkColumnRef("typeColumn", o.typeColumn);
|
|
471
|
+
}
|
|
472
|
+
if (o.table === "adjacency") {
|
|
473
|
+
checkAdjacencyOptions(o);
|
|
474
|
+
}
|
|
475
|
+
if (o.rowNumberIds !== undefined && typeof o.rowNumberIds !== "boolean") {
|
|
476
|
+
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: expected a boolean", {
|
|
477
|
+
option: "rowNumberIds",
|
|
478
|
+
found: o.rowNumberIds,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
delimiter: o.delimiter ?? null,
|
|
483
|
+
decimalComma: o.decimal === ",",
|
|
484
|
+
header: o.header ?? "auto",
|
|
485
|
+
table: o.table ?? "auto",
|
|
486
|
+
sourceColumn: o.sourceColumn ?? null,
|
|
487
|
+
targetColumn: o.targetColumn ?? null,
|
|
488
|
+
typeColumn: o.typeColumn,
|
|
489
|
+
idColumn: o.idColumn ?? null,
|
|
490
|
+
labelColumn: o.labelColumn ?? null,
|
|
491
|
+
nodes: nodesInput(o.nodes),
|
|
492
|
+
rowNumberIds: o.rowNumberIds ?? false,
|
|
493
|
+
explicitDirected: typeof options?.defaultDirected === "boolean" ? options.defaultDirected : null,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Check the dialect and table options: delimiter, decimal, header and table.
|
|
499
|
+
* @param o - the caller's CSV options
|
|
500
|
+
*/
|
|
501
|
+
function checkDialectOptions(o: CsvImportOptions): void {
|
|
458
502
|
if (o.delimiter !== undefined && (typeof o.delimiter !== "string" || o.delimiter.length === 0)) {
|
|
459
503
|
throw new GraphFormatError("E_UNSUPPORTED", "option delimiter: expected a non-empty string", {
|
|
460
504
|
option: "delimiter",
|
|
@@ -489,48 +533,27 @@ function resolveCsvOptions(options: (CsvImportOptions & CommonImportOptions) | u
|
|
|
489
533
|
found: o.table,
|
|
490
534
|
});
|
|
491
535
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (o.rowNumberIds === true) {
|
|
508
|
-
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: an adjacency table names its nodes", {
|
|
509
|
-
option: "rowNumberIds",
|
|
510
|
-
found: o.rowNumberIds,
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Check the options an adjacency table has no use for: column options and rowNumberIds.
|
|
540
|
+
* @param o - the caller's CSV options
|
|
541
|
+
*/
|
|
542
|
+
function checkAdjacencyOptions(o: CsvImportOptions): void {
|
|
543
|
+
for (const name of ["sourceColumn", "targetColumn", "typeColumn", "idColumn", "labelColumn"] as const) {
|
|
544
|
+
if (o[name] !== undefined) {
|
|
545
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: an adjacency table has no columns`, {
|
|
546
|
+
option: name,
|
|
547
|
+
found: o[name],
|
|
511
548
|
});
|
|
512
549
|
}
|
|
513
550
|
}
|
|
514
|
-
if (o.rowNumberIds
|
|
515
|
-
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds:
|
|
551
|
+
if (o.rowNumberIds === true) {
|
|
552
|
+
throw new GraphFormatError("E_UNSUPPORTED", "option rowNumberIds: an adjacency table names its nodes", {
|
|
516
553
|
option: "rowNumberIds",
|
|
517
554
|
found: o.rowNumberIds,
|
|
518
555
|
});
|
|
519
556
|
}
|
|
520
|
-
return {
|
|
521
|
-
delimiter: o.delimiter ?? null,
|
|
522
|
-
decimalComma: o.decimal === ",",
|
|
523
|
-
header: o.header ?? "auto",
|
|
524
|
-
table: o.table ?? "auto",
|
|
525
|
-
sourceColumn: o.sourceColumn ?? null,
|
|
526
|
-
targetColumn: o.targetColumn ?? null,
|
|
527
|
-
typeColumn: o.typeColumn,
|
|
528
|
-
idColumn: o.idColumn ?? null,
|
|
529
|
-
labelColumn: o.labelColumn ?? null,
|
|
530
|
-
nodes: nodesInput(o.nodes),
|
|
531
|
-
rowNumberIds: o.rowNumberIds ?? false,
|
|
532
|
-
explicitDirected: typeof options?.defaultDirected === "boolean" ? options.defaultDirected : null,
|
|
533
|
-
};
|
|
534
557
|
}
|
|
535
558
|
|
|
536
559
|
/**
|
|
@@ -575,6 +598,22 @@ function isUnset(text: string, quoted: boolean | undefined): boolean {
|
|
|
575
598
|
return quoted !== true && isBlank(text);
|
|
576
599
|
}
|
|
577
600
|
|
|
601
|
+
/**
|
|
602
|
+
* The number of set neighbor cells of an adjacency row (every cell after the first).
|
|
603
|
+
* @param row - the cells
|
|
604
|
+
* @param quoted - whether each cell was quoted
|
|
605
|
+
* @returns the set neighbor cells
|
|
606
|
+
*/
|
|
607
|
+
function setNeighborCells(row: readonly string[], quoted: readonly boolean[]): number {
|
|
608
|
+
let neighbors = 0;
|
|
609
|
+
for (let k = 1; k < row.length; k++) {
|
|
610
|
+
if (!isUnset(row[k], quoted[k])) {
|
|
611
|
+
neighbors++;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return neighbors;
|
|
615
|
+
}
|
|
616
|
+
|
|
578
617
|
/** Rows between two checks of the cancellation signal on an in-memory input. */
|
|
579
618
|
const ABORT_CHECK_INTERVAL = 64;
|
|
580
619
|
|
|
@@ -807,43 +846,12 @@ class TableReader {
|
|
|
807
846
|
: first.value;
|
|
808
847
|
const firstLine = this.reader.line;
|
|
809
848
|
const firstQuoted = this.reader.quoted.slice(0, firstRow.length);
|
|
810
|
-
const pending:
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
mode = false;
|
|
817
|
-
} else if (mode === "auto" && this.rowNumbers) {
|
|
818
|
-
mode = true;
|
|
819
|
-
}
|
|
820
|
-
if (mode === "auto") {
|
|
821
|
-
const second = await iterator.next();
|
|
822
|
-
const secondRow: string[] | null = second.done ? null : second.value;
|
|
823
|
-
const secondLine = this.reader.line;
|
|
824
|
-
const secondQuoted = this.reader.quoted.slice(0, secondRow?.length ?? 0);
|
|
825
|
-
// a column option naming a cell of the first row makes that row the header
|
|
826
|
-
header = looksLikeHeader(firstRow, secondRow) || this.namesExplicitColumn(firstRow);
|
|
827
|
-
if (!header) {
|
|
828
|
-
pending.push({ row: firstRow, quoted: firstQuoted, line: firstLine });
|
|
829
|
-
}
|
|
830
|
-
if (secondRow !== null) {
|
|
831
|
-
pending.push({ row: secondRow, quoted: secondQuoted, line: secondLine });
|
|
832
|
-
}
|
|
833
|
-
} else {
|
|
834
|
-
header = mode;
|
|
835
|
-
if (!header) {
|
|
836
|
-
pending.push({ row: firstRow, quoted: firstQuoted, line: firstLine });
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
if (this.rowNumbers && !header) {
|
|
840
|
-
report.warning(
|
|
841
|
-
"unsupported",
|
|
842
|
-
OPTION_IGNORED_CODE,
|
|
843
|
-
"option rowNumberIds ignored: a headerless node table takes its ids from column 0",
|
|
844
|
-
{ line: firstLine, element: "rowNumberIds" },
|
|
845
|
-
);
|
|
846
|
-
}
|
|
849
|
+
const pending: PendingRow[] = [];
|
|
850
|
+
const header = await this.decideHeader(
|
|
851
|
+
iterator,
|
|
852
|
+
{ row: firstRow, quoted: firstQuoted, line: firstLine },
|
|
853
|
+
pending,
|
|
854
|
+
);
|
|
847
855
|
const names = header ? uniqueNames(headerNames(firstRow), report, firstLine) : positionalNames(firstRow.length);
|
|
848
856
|
const last = firstRow.length - 1;
|
|
849
857
|
const optionalLast = header && last > 0 && isUnset(firstRow[last], firstQuoted[last]);
|
|
@@ -873,6 +881,80 @@ class TableReader {
|
|
|
873
881
|
for (const { row, quoted, line } of pending) {
|
|
874
882
|
this.processRow(row, quoted, line);
|
|
875
883
|
}
|
|
884
|
+
await this.readRemainingRows(iterator);
|
|
885
|
+
if (this.dataRows === 0 && header) {
|
|
886
|
+
report.warning("missing-value", NO_DATA_ROWS_CODE, "the table has a header and no data rows", {
|
|
887
|
+
line: firstLine,
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
this.reportReaderFindings();
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Decide whether the first row is the header, reading the second row when the header option is
|
|
895
|
+
* "auto", and queue the rows read so far that are data.
|
|
896
|
+
* @param iterator - the record iterator, past the first row
|
|
897
|
+
* @param first - the first row
|
|
898
|
+
* @param pending - receives the data rows read so far, in order
|
|
899
|
+
* @returns whether the first row is the header
|
|
900
|
+
*/
|
|
901
|
+
private async decideHeader(
|
|
902
|
+
iterator: AsyncGenerator<string[], void, undefined>,
|
|
903
|
+
first: PendingRow,
|
|
904
|
+
pending: PendingRow[],
|
|
905
|
+
): Promise<boolean> {
|
|
906
|
+
const mode = this.headerMode();
|
|
907
|
+
let second: PendingRow | null = null;
|
|
908
|
+
let header: boolean;
|
|
909
|
+
if (mode === "auto") {
|
|
910
|
+
const next = await iterator.next();
|
|
911
|
+
second = next.done
|
|
912
|
+
? null
|
|
913
|
+
: { row: next.value, quoted: this.reader.quoted.slice(0, next.value.length), line: this.reader.line };
|
|
914
|
+
// a column option naming a cell of the first row makes that row the header
|
|
915
|
+
header = looksLikeHeader(first.row, second?.row ?? null) || this.namesExplicitColumn(first.row);
|
|
916
|
+
} else {
|
|
917
|
+
header = mode;
|
|
918
|
+
}
|
|
919
|
+
if (!header) {
|
|
920
|
+
pending.push(first);
|
|
921
|
+
}
|
|
922
|
+
if (second !== null) {
|
|
923
|
+
pending.push(second);
|
|
924
|
+
}
|
|
925
|
+
if (this.rowNumbers && !header) {
|
|
926
|
+
this.state.report.warning(
|
|
927
|
+
"unsupported",
|
|
928
|
+
OPTION_IGNORED_CODE,
|
|
929
|
+
"option rowNumberIds ignored: a headerless node table takes its ids from column 0",
|
|
930
|
+
{ line: first.line, element: "rowNumberIds" },
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
return header;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* The header option as this table reads it: an adjacency table has no header unless the caller
|
|
938
|
+
* says so (its rows vary in width); a table numbered by rowNumberIds has one, since a headerless
|
|
939
|
+
* table takes its ids from column 0.
|
|
940
|
+
* @returns true, false or "auto"
|
|
941
|
+
*/
|
|
942
|
+
private headerMode(): boolean | "auto" {
|
|
943
|
+
const mode = this.state.csv.header;
|
|
944
|
+
if (mode === "auto" && this.kind === "adjacency") {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
if (mode === "auto" && this.rowNumbers) {
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
return mode;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Push every row after the header and the rows read to decide it, then finish the column writers.
|
|
955
|
+
* @param iterator - the record iterator
|
|
956
|
+
*/
|
|
957
|
+
private async readRemainingRows(iterator: AsyncGenerator<string[], void, undefined>): Promise<void> {
|
|
876
958
|
const { signal } = this.state.common;
|
|
877
959
|
let sinceCheck = 0;
|
|
878
960
|
for (;;) {
|
|
@@ -889,12 +971,6 @@ class TableReader {
|
|
|
889
971
|
for (const writer of this.writers) {
|
|
890
972
|
writer?.finish();
|
|
891
973
|
}
|
|
892
|
-
if (this.dataRows === 0 && header) {
|
|
893
|
-
report.warning("missing-value", NO_DATA_ROWS_CODE, "the table has a header and no data rows", {
|
|
894
|
-
line: firstLine,
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
this.reportReaderFindings();
|
|
898
974
|
}
|
|
899
975
|
|
|
900
976
|
/**
|
|
@@ -1520,12 +1596,7 @@ class TableReader {
|
|
|
1520
1596
|
const { where } = this;
|
|
1521
1597
|
where.line = line;
|
|
1522
1598
|
where.element = null;
|
|
1523
|
-
|
|
1524
|
-
for (let k = 1; k < row.length; k++) {
|
|
1525
|
-
if (!isUnset(row[k], quoted[k])) {
|
|
1526
|
-
neighbors++;
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1599
|
+
const neighbors = setNeighborCells(row, quoted);
|
|
1529
1600
|
if (isUnset(row[0], quoted[0])) {
|
|
1530
1601
|
report.error("missing-value", MISSING_ID_CODE, `blank node cell`, { line });
|
|
1531
1602
|
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
|
|
@@ -577,14 +586,8 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
577
586
|
}
|
|
578
587
|
const pieces: string[] = [];
|
|
579
588
|
let length = 0;
|
|
580
|
-
let breaks = 0;
|
|
581
|
-
let lastWasCr = false;
|
|
582
589
|
let sniffed = false;
|
|
583
|
-
|
|
584
|
-
const commentCodes = (this.syntax.comments ?? []).map((c) => c.codePointAt(0));
|
|
585
|
-
let leading = commentCodes.length > 0;
|
|
586
|
-
let lineStart = true;
|
|
587
|
-
let inComment = false;
|
|
590
|
+
const preview = new PreviewBreaks((this.syntax.comments ?? []).map((c) => c.codePointAt(0)));
|
|
588
591
|
for await (const chunk of source) {
|
|
589
592
|
if (sniffed) {
|
|
590
593
|
yield chunk;
|
|
@@ -592,24 +595,8 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
592
595
|
}
|
|
593
596
|
pieces.push(chunk);
|
|
594
597
|
length += chunk.length;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (c === LF || c === CR) {
|
|
598
|
-
if (!(c === LF && lastWasCr) && !inComment) {
|
|
599
|
-
breaks++;
|
|
600
|
-
}
|
|
601
|
-
inComment = false;
|
|
602
|
-
lineStart = true;
|
|
603
|
-
} else {
|
|
604
|
-
if (lineStart && leading) {
|
|
605
|
-
inComment = commentCodes.includes(c);
|
|
606
|
-
leading = inComment;
|
|
607
|
-
}
|
|
608
|
-
lineStart = false;
|
|
609
|
-
}
|
|
610
|
-
lastWasCr = c === CR;
|
|
611
|
-
}
|
|
612
|
-
if (breaks < PREVIEW_ROWS && length < PREVIEW_CHARS) {
|
|
598
|
+
preview.scan(chunk);
|
|
599
|
+
if (preview.breaks < PREVIEW_ROWS && length < PREVIEW_CHARS) {
|
|
613
600
|
continue;
|
|
614
601
|
}
|
|
615
602
|
const text = pieces.join("");
|
|
@@ -659,6 +646,74 @@ export class RecordReader implements AsyncIterable<number> {
|
|
|
659
646
|
}
|
|
660
647
|
}
|
|
661
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Counts the line breaks of the text buffered for the delimiter sniff, up to PREVIEW_ROWS. The
|
|
651
|
+
* leading comment lines are left out of the sniff, so they do not count toward its rows.
|
|
652
|
+
*/
|
|
653
|
+
class PreviewBreaks {
|
|
654
|
+
/** The line breaks counted so far (at most PREVIEW_ROWS). */
|
|
655
|
+
breaks = 0;
|
|
656
|
+
|
|
657
|
+
private readonly commentCodes: readonly (number | undefined)[];
|
|
658
|
+
|
|
659
|
+
private lastWasCr = false;
|
|
660
|
+
|
|
661
|
+
private leading: boolean;
|
|
662
|
+
|
|
663
|
+
private lineStart = true;
|
|
664
|
+
|
|
665
|
+
private inComment = false;
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Start counting at the start of the input.
|
|
669
|
+
* @param commentCodes - the code points of the comment characters
|
|
670
|
+
*/
|
|
671
|
+
constructor(commentCodes: readonly (number | undefined)[]) {
|
|
672
|
+
this.commentCodes = commentCodes;
|
|
673
|
+
this.leading = commentCodes.length > 0;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Count the line breaks of the next chunk, stopping at PREVIEW_ROWS.
|
|
678
|
+
* @param chunk - the chunk
|
|
679
|
+
*/
|
|
680
|
+
scan(chunk: string): void {
|
|
681
|
+
for (let i = 0; i < chunk.length && this.breaks < PREVIEW_ROWS; i++) {
|
|
682
|
+
const c = chunk.charCodeAt(i);
|
|
683
|
+
if (c === LF || c === CR) {
|
|
684
|
+
this.lineBreak(c);
|
|
685
|
+
} else {
|
|
686
|
+
this.character(c);
|
|
687
|
+
}
|
|
688
|
+
this.lastWasCr = c === CR;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* A line break: counted unless it is the LF of a CR LF or ends a leading comment line.
|
|
694
|
+
* @param c - LF or CR
|
|
695
|
+
*/
|
|
696
|
+
private lineBreak(c: number): void {
|
|
697
|
+
if (!(c === LF && this.lastWasCr) && !this.inComment) {
|
|
698
|
+
this.breaks++;
|
|
699
|
+
}
|
|
700
|
+
this.inComment = false;
|
|
701
|
+
this.lineStart = true;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Any other character: the first of a line decides whether a leading comment line starts.
|
|
706
|
+
* @param c - the character code
|
|
707
|
+
*/
|
|
708
|
+
private character(c: number): void {
|
|
709
|
+
if (this.lineStart && this.leading) {
|
|
710
|
+
this.inComment = this.commentCodes.includes(c);
|
|
711
|
+
this.leading = this.inComment;
|
|
712
|
+
}
|
|
713
|
+
this.lineStart = false;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
662
717
|
/**
|
|
663
718
|
* The record state machine of RecordReader, resumable at every record end so the reader can
|
|
664
719
|
* yield a record before the next one overwrites the shared cell arrays. Every state lives on the
|
|
@@ -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,
|