@graphty/graph-io 0.3.40 → 0.3.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{choose-CS3fvGT1.js → choose-CwFQamH9.js} +2 -2
- package/dist/chunks/{choose-CS3fvGT1.js.map → choose-CwFQamH9.js.map} +1 -1
- package/dist/chunks/{constants-DqKZT6cH.js → constants-Cf4Nn6tw.js} +2 -2
- package/dist/chunks/{constants-DqKZT6cH.js.map → constants-Cf4Nn6tw.js.map} +1 -1
- package/dist/chunks/{escape-BJRLd3vJ.js → escape-DBb8zuNE.js} +2 -2
- package/dist/chunks/{escape-BJRLd3vJ.js.map → escape-DBb8zuNE.js.map} +1 -1
- package/dist/chunks/{export-B-aVCwyG.js → export-DD3Lvw-F.js} +173 -116
- package/dist/chunks/export-DD3Lvw-F.js.map +1 -0
- package/dist/chunks/{exporter-Bcp-l4Bo.js → exporter-BqtmlxNr.js} +545 -459
- package/dist/chunks/exporter-BqtmlxNr.js.map +1 -0
- package/dist/chunks/{exporter-6ocL_9x_.js → exporter-CS35R6Fg.js} +4 -4
- package/dist/chunks/{exporter-6ocL_9x_.js.map → exporter-CS35R6Fg.js.map} +1 -1
- package/dist/chunks/{exporter-BVXnV9-9.js → exporter-D8r1N2dU.js} +28 -16
- package/dist/chunks/exporter-D8r1N2dU.js.map +1 -0
- package/dist/chunks/{exporter-SFYkDzD0.js → exporter-NMplSnYn.js} +23 -20
- package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-NMplSnYn.js.map} +1 -1
- package/dist/chunks/{importer-CPqnIY8m.js → importer-BKC7AmCA.js} +42 -29
- package/dist/chunks/importer-BKC7AmCA.js.map +1 -0
- package/dist/chunks/{importer-D_srHyj6.js → importer-BdWHmlmG.js} +4 -4
- package/dist/chunks/{importer-D_srHyj6.js.map → importer-BdWHmlmG.js.map} +1 -1
- package/dist/chunks/{importer-AjwOJrn2.js → importer-BfbzWk6k.js} +202 -181
- package/dist/chunks/importer-BfbzWk6k.js.map +1 -0
- package/dist/chunks/{importer-BpuS3J8C.js → importer-BmaZcskq.js} +73 -60
- package/dist/chunks/importer-BmaZcskq.js.map +1 -0
- package/dist/chunks/{importer-DEp_fYjf.js → importer-DviEsHy4.js} +92 -64
- package/dist/chunks/importer-DviEsHy4.js.map +1 -0
- package/dist/chunks/{importer-DcKB97kW.js → importer-S-abxJHn.js} +5 -5
- package/dist/chunks/{importer-DcKB97kW.js.map → importer-S-abxJHn.js.map} +1 -1
- package/dist/chunks/{importer-BJlGAOht.js → importer-r29cWnEf.js} +3 -3
- package/dist/chunks/{importer-BJlGAOht.js.map → importer-r29cWnEf.js.map} +1 -1
- package/dist/chunks/{json-elements-DpO6AXIc.js → json-elements-DdF-etPT.js} +3 -3
- package/dist/chunks/{json-elements-DpO6AXIc.js.map → json-elements-DdF-etPT.js.map} +1 -1
- package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-BC0zyPhj.js} +2 -2
- package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-BC0zyPhj.js.map} +1 -1
- package/dist/chunks/{records-r3SFHz11.js → records-Uc3LkABx.js} +15 -12
- package/dist/chunks/records-Uc3LkABx.js.map +1 -0
- package/dist/chunks/{text-Bjfa1xYL.js → text-Bh9SaAob.js} +20 -11
- package/dist/chunks/text-Bh9SaAob.js.map +1 -0
- package/dist/csv.js +67 -49
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +1 -1
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +195 -97
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +170 -149
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +26 -26
- package/dist/graphml.js +2 -2
- package/dist/json.js +1 -1
- package/dist/neo4j.js +103 -65
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +34 -9
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +180 -102
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/text.d.ts +7 -0
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +15 -7
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +36 -19
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +77 -25
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +16 -6
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +18 -10
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +11 -8
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +65 -37
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +138 -50
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +51 -35
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +103 -58
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +17 -8
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +34 -24
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +17 -13
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/tree.js +23 -14
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +22 -10
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +38 -21
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +536 -343
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/obographs.js +29 -17
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +73 -43
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +37 -16
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +21 -11
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts.map +1 -1
- package/dist/src/formats/obo/exporter.js +18 -7
- package/dist/src/formats/obo/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +142 -73
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +26 -15
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +85 -58
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +7 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +79 -40
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +38 -12
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +38 -18
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/sniff.js +109 -57
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/common/attributes.ts +59 -19
- package/src/common/input.ts +216 -102
- package/src/common/text.ts +24 -15
- package/src/common/zip.ts +47 -18
- package/src/formats/csv/exporter.ts +103 -39
- package/src/formats/csv/importer.ts +17 -6
- package/src/formats/csv/records.ts +19 -10
- package/src/formats/cys/tables.ts +11 -10
- package/src/formats/gexf/exporter.ts +67 -36
- package/src/formats/gexf/importer.ts +173 -63
- package/src/formats/gml/exporter.ts +78 -82
- package/src/formats/gml/importer.ts +119 -68
- package/src/formats/gml/syntax.ts +18 -8
- package/src/formats/graphml/exporter.ts +44 -24
- package/src/formats/graphml/importer.ts +18 -11
- package/src/formats/graphml/tree.ts +22 -12
- package/src/formats/graphml/yfiles.ts +23 -10
- package/src/formats/json/exporter.ts +40 -21
- package/src/formats/json/importer.ts +701 -385
- package/src/formats/json/obographs.ts +35 -15
- package/src/formats/neo4j/exporter.ts +79 -41
- package/src/formats/neo4j/header.ts +50 -16
- package/src/formats/neo4j/importer.ts +21 -10
- package/src/formats/obo/exporter.ts +34 -17
- package/src/formats/pajek/exporter.ts +180 -117
- package/src/formats/pajek/importer.ts +27 -15
- package/src/formats/pajek/syntax.ts +99 -58
- package/src/formats/xgmml/emit.ts +88 -40
- package/src/formats/xgmml/exporter.ts +40 -12
- package/src/formats/xgmml/importer.ts +45 -23
- package/src/sniff.ts +115 -59
- package/dist/chunks/export-B-aVCwyG.js.map +0 -1
- package/dist/chunks/exporter-BVXnV9-9.js.map +0 -1
- package/dist/chunks/exporter-Bcp-l4Bo.js.map +0 -1
- package/dist/chunks/importer-AjwOJrn2.js.map +0 -1
- package/dist/chunks/importer-BpuS3J8C.js.map +0 -1
- package/dist/chunks/importer-CPqnIY8m.js.map +0 -1
- package/dist/chunks/importer-DEp_fYjf.js.map +0 -1
- package/dist/chunks/records-r3SFHz11.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
|
@@ -420,6 +420,20 @@ function note(code: string, message: string, column: string | null = null, count
|
|
|
420
420
|
return Object.freeze({ code, message, column, count });
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
/**
|
|
424
|
+
* Count a kind of json value once per row.
|
|
425
|
+
* @param seen - the flags already counted for this row
|
|
426
|
+
* @param flag - the flag of the kind
|
|
427
|
+
* @param stats - the counters
|
|
428
|
+
* @param counter - the counter of the kind
|
|
429
|
+
*/
|
|
430
|
+
function countOnce(seen: SeenFlags, flag: keyof SeenFlags, stats: JsonStats, counter: keyof JsonStats): void {
|
|
431
|
+
if (!seen[flag]) {
|
|
432
|
+
seen[flag] = true;
|
|
433
|
+
stats[counter]++;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
423
437
|
/**
|
|
424
438
|
* Inspect a JSON value for what GML cannot write exactly.
|
|
425
439
|
* @param value - the value
|
|
@@ -429,24 +443,15 @@ function note(code: string, message: string, column: string | null = null, count
|
|
|
429
443
|
*/
|
|
430
444
|
function inspectJson(value: unknown, stats: JsonStats, inArray: boolean, seen: SeenFlags): void {
|
|
431
445
|
if (value === null) {
|
|
432
|
-
|
|
433
|
-
seen.nulls = true;
|
|
434
|
-
stats.nulls++;
|
|
435
|
-
}
|
|
446
|
+
countOnce(seen, "nulls", stats, "nulls");
|
|
436
447
|
return;
|
|
437
448
|
}
|
|
438
449
|
switch (typeof value) {
|
|
439
450
|
case "number":
|
|
440
|
-
|
|
441
|
-
seen.numbers = true;
|
|
442
|
-
stats.numbers++;
|
|
443
|
-
}
|
|
451
|
+
countOnce(seen, "numbers", stats, "numbers");
|
|
444
452
|
return;
|
|
445
453
|
case "boolean":
|
|
446
|
-
|
|
447
|
-
seen.booleans = true;
|
|
448
|
-
stats.booleans++;
|
|
449
|
-
}
|
|
454
|
+
countOnce(seen, "booleans", stats, "booleans");
|
|
450
455
|
return;
|
|
451
456
|
case "string":
|
|
452
457
|
return;
|
|
@@ -454,9 +459,8 @@ function inspectJson(value: unknown, stats: JsonStats, inArray: boolean, seen: S
|
|
|
454
459
|
break;
|
|
455
460
|
}
|
|
456
461
|
if (Array.isArray(value)) {
|
|
457
|
-
if (inArray
|
|
458
|
-
seen
|
|
459
|
-
stats.nestedArrays++;
|
|
462
|
+
if (inArray) {
|
|
463
|
+
countOnce(seen, "nested", stats, "nestedArrays");
|
|
460
464
|
}
|
|
461
465
|
for (const item of value) {
|
|
462
466
|
inspectJson(item, stats, true, seen);
|
|
@@ -472,6 +476,29 @@ function inspectJson(value: unknown, stats: JsonStats, inArray: boolean, seen: S
|
|
|
472
476
|
}
|
|
473
477
|
}
|
|
474
478
|
|
|
479
|
+
/**
|
|
480
|
+
* A row count in words.
|
|
481
|
+
* @param count - the count
|
|
482
|
+
* @returns the count and the word row, pluralised
|
|
483
|
+
*/
|
|
484
|
+
function rowCount(count: number): string {
|
|
485
|
+
return `${count} row${plural(count)}`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Record a note for a counter that is not zero.
|
|
490
|
+
* @param notes - where to record
|
|
491
|
+
* @param count - the counter
|
|
492
|
+
* @param code - the note code
|
|
493
|
+
* @param text - the note message
|
|
494
|
+
* @param column - the column name
|
|
495
|
+
*/
|
|
496
|
+
function pushCounted(notes: LossNote[], count: number, code: string, text: string, column: string): void {
|
|
497
|
+
if (count > 0) {
|
|
498
|
+
notes.push(note(code, text, column, count));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
475
502
|
/**
|
|
476
503
|
* The GML-specific notes of one table's json columns.
|
|
477
504
|
* @param columns - the written columns
|
|
@@ -487,73 +514,42 @@ function jsonNotes(columns: readonly WrittenColumn[], label: string, plan: GmlEx
|
|
|
487
514
|
}
|
|
488
515
|
const { name } = column.meta;
|
|
489
516
|
const where = `${label} column "${name}"`;
|
|
490
|
-
|
|
491
|
-
notes
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
stats.arrays,
|
|
527
|
-
),
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
if (stats.nestedArrays > 0) {
|
|
531
|
-
notes.push(
|
|
532
|
-
note(
|
|
533
|
-
NESTED_ARRAY_CODE,
|
|
534
|
-
`${where} holds arrays nested in arrays in ${stats.nestedArrays} row${plural(stats.nestedArrays)}; GML cannot write them and the save fails`,
|
|
535
|
-
name,
|
|
536
|
-
stats.nestedArrays,
|
|
537
|
-
),
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
if (stats.invalidKeys > 0) {
|
|
541
|
-
notes.push(
|
|
542
|
-
plan.mangle
|
|
543
|
-
? note(
|
|
544
|
-
KEY_MANGLED_CODE,
|
|
545
|
-
`${where}: ${stats.invalidKeys} record key${plural(stats.invalidKeys)} ${agree(stats.invalidKeys, "is", "are")} not GML keys and are rewritten`,
|
|
546
|
-
name,
|
|
547
|
-
stats.invalidKeys,
|
|
548
|
-
)
|
|
549
|
-
: note(
|
|
550
|
-
INVALID_KEY_CODE,
|
|
551
|
-
`${where}: ${stats.invalidKeys} record key${plural(stats.invalidKeys)} ${agree(stats.invalidKeys, "is", "are")} not GML keys; the save fails unless sanitizeKeys is "mangle"`,
|
|
552
|
-
name,
|
|
553
|
-
stats.invalidKeys,
|
|
554
|
-
),
|
|
555
|
-
);
|
|
556
|
-
}
|
|
517
|
+
const counted = (count: number, code: string, text: string): void => {
|
|
518
|
+
pushCounted(notes, count, code, text, name);
|
|
519
|
+
};
|
|
520
|
+
counted(
|
|
521
|
+
stats.numbers,
|
|
522
|
+
RECORD_NUMBER_TYPE_CODE,
|
|
523
|
+
`${where} holds numbers in ${rowCount(stats.numbers)}; GML records cannot keep the int / real distinction`,
|
|
524
|
+
);
|
|
525
|
+
counted(
|
|
526
|
+
stats.booleans,
|
|
527
|
+
RECORD_BOOLEAN_CODE,
|
|
528
|
+
`${where} holds booleans in ${rowCount(stats.booleans)}; written as 1 / 0`,
|
|
529
|
+
);
|
|
530
|
+
counted(
|
|
531
|
+
stats.nulls,
|
|
532
|
+
RECORD_NULL_CODE,
|
|
533
|
+
`${where} holds nulls in ${rowCount(stats.nulls)}; GML has no null, the key is omitted`,
|
|
534
|
+
);
|
|
535
|
+
counted(
|
|
536
|
+
stats.arrays,
|
|
537
|
+
JSON_ARRAY_CODE,
|
|
538
|
+
`${where} holds arrays as values in ${rowCount(stats.arrays)}; written as repeated keys, they re-import as a list`,
|
|
539
|
+
);
|
|
540
|
+
counted(
|
|
541
|
+
stats.nestedArrays,
|
|
542
|
+
NESTED_ARRAY_CODE,
|
|
543
|
+
`${where} holds arrays nested in arrays in ${rowCount(stats.nestedArrays)}; GML cannot write them and the save fails`,
|
|
544
|
+
);
|
|
545
|
+
const keys = `${stats.invalidKeys} record key${plural(stats.invalidKeys)} ${agree(stats.invalidKeys, "is", "are")} not GML keys`;
|
|
546
|
+
counted(
|
|
547
|
+
stats.invalidKeys,
|
|
548
|
+
plan.mangle ? KEY_MANGLED_CODE : INVALID_KEY_CODE,
|
|
549
|
+
plan.mangle
|
|
550
|
+
? `${where}: ${keys} and are rewritten`
|
|
551
|
+
: `${where}: ${keys}; the save fails unless sanitizeKeys is "mangle"`,
|
|
552
|
+
);
|
|
557
553
|
}
|
|
558
554
|
}
|
|
559
555
|
|
|
@@ -518,31 +518,50 @@ class GmlImport {
|
|
|
518
518
|
if (key === "graph" && !record && scalarGraph < 0) {
|
|
519
519
|
scalarGraph = v;
|
|
520
520
|
}
|
|
521
|
-
|
|
522
|
-
if (this.graphCount++ === this.which) {
|
|
523
|
-
this.graphOpen = v;
|
|
524
|
-
this.scanGraph(v);
|
|
525
|
-
}
|
|
526
|
-
} else if (key === "Creator" && !record && this.creatorToken < 0) {
|
|
527
|
-
this.creatorToken = v;
|
|
528
|
-
} else if (key === "Version" && !record && this.versionToken < 0) {
|
|
529
|
-
this.versionToken = v;
|
|
530
|
-
} else {
|
|
531
|
-
this.observe(this.topSchema, "graph", key, v, TOP_SEQ);
|
|
532
|
-
}
|
|
521
|
+
this.scanTopPair(key, record, v);
|
|
533
522
|
}
|
|
534
523
|
if (this.graphOpen < 0) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
524
|
+
this.failNoGraph(t, scalarGraph);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Handle one top-level pair other than a scalar `graph`.
|
|
530
|
+
* @param key - the key
|
|
531
|
+
* @param record - whether the value is a `[ ... ]` block
|
|
532
|
+
* @param v - the value token
|
|
533
|
+
*/
|
|
534
|
+
private scanTopPair(key: string, record: boolean, v: number): void {
|
|
535
|
+
if (key === "graph" && record) {
|
|
536
|
+
if (this.graphCount++ === this.which) {
|
|
537
|
+
this.graphOpen = v;
|
|
538
|
+
this.scanGraph(v);
|
|
541
539
|
}
|
|
542
|
-
|
|
540
|
+
} else if (key === "Creator" && !record && this.creatorToken < 0) {
|
|
541
|
+
this.creatorToken = v;
|
|
542
|
+
} else if (key === "Version" && !record && this.versionToken < 0) {
|
|
543
|
+
this.versionToken = v;
|
|
544
|
+
} else {
|
|
545
|
+
this.observe(this.topSchema, "graph", key, v, TOP_SEQ);
|
|
543
546
|
}
|
|
544
547
|
}
|
|
545
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Fail because the input holds no graph block.
|
|
551
|
+
* @param t - the tokens
|
|
552
|
+
* @param scalarGraph - the token of a scalar `graph` key, or -1
|
|
553
|
+
*/
|
|
554
|
+
private failNoGraph(t: ReturnType<GmlImport["requireTokens"]>, scalarGraph: number): void {
|
|
555
|
+
if (scalarGraph >= 0) {
|
|
556
|
+
this.report.fail(
|
|
557
|
+
NO_GRAPH_CODE,
|
|
558
|
+
`the graph key at line ${t.line[scalarGraph]} holds ${describeValue(t, scalarGraph)}, not a [ ... ] block; the input contains no graph block`,
|
|
559
|
+
{ line: t.line[scalarGraph] },
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
this.report.fail(NO_GRAPH_CODE, "the input contains no graph [ ... ] block", { line: null });
|
|
563
|
+
}
|
|
564
|
+
|
|
546
565
|
/**
|
|
547
566
|
* Walk the graph block for the schema.
|
|
548
567
|
* @param open - the index of the block's `[`
|
|
@@ -592,31 +611,43 @@ class GmlImport {
|
|
|
592
611
|
const close = t.match[open];
|
|
593
612
|
const seq = this.seq++;
|
|
594
613
|
const schema = domain === "node" ? this.nodeSchema : this.edgeSchema;
|
|
595
|
-
const { weightFrom, restoreMangledIds } = this.options;
|
|
596
614
|
for (let p = open + 1; p < close; p = t.nextPair(p)) {
|
|
597
615
|
const key = t.textOf(p);
|
|
598
616
|
const v = p + 1;
|
|
599
|
-
if (domain
|
|
600
|
-
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (key === ORIGINAL_ID_KEY && restoreMangledIds) {
|
|
604
|
-
this.hasOriginalId = true;
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
} else {
|
|
608
|
-
if (key === "source" || key === "target" || key === "directed") {
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
if (key === weightFrom) {
|
|
612
|
-
this.weightKinds |= kindBit(t, v);
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
617
|
+
if (this.skipsKey(domain, key, v)) {
|
|
618
|
+
continue;
|
|
615
619
|
}
|
|
616
620
|
this.observe(schema, domain, key, v, seq);
|
|
617
621
|
}
|
|
618
622
|
}
|
|
619
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Whether a key of a node or edge block is structural (id, endpoints, weight, original id), not
|
|
626
|
+
* a column; notes the original id and the weight's kinds on the way.
|
|
627
|
+
* @param domain - node or edge
|
|
628
|
+
* @param key - the key
|
|
629
|
+
* @param v - the value token
|
|
630
|
+
* @returns true when the key is not a column
|
|
631
|
+
*/
|
|
632
|
+
private skipsKey(domain: "node" | "edge", key: string, v: number): boolean {
|
|
633
|
+
const { weightFrom, restoreMangledIds } = this.options;
|
|
634
|
+
if (domain === "node") {
|
|
635
|
+
if (key === ORIGINAL_ID_KEY && restoreMangledIds) {
|
|
636
|
+
this.hasOriginalId = true;
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
return key === "id";
|
|
640
|
+
}
|
|
641
|
+
if (key === "source" || key === "target" || key === "directed") {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
if (key === weightFrom) {
|
|
645
|
+
this.weightKinds |= kindBit(this.requireTokens(), v);
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
|
|
620
651
|
/**
|
|
621
652
|
* Record one value of one key in a table's schema.
|
|
622
653
|
* @param schema - the table's schema
|
|
@@ -816,26 +847,12 @@ class GmlImport {
|
|
|
816
847
|
}
|
|
817
848
|
|
|
818
849
|
/**
|
|
819
|
-
*
|
|
850
|
+
* Warn about a key that mixes numbers and strings, or holds integers beyond 2^53.
|
|
820
851
|
* @param domain - the table
|
|
821
852
|
* @param entry - the schema entry
|
|
822
|
-
* @param
|
|
823
|
-
* @returns the plan
|
|
853
|
+
* @param asText - whether the integers are kept as text
|
|
824
854
|
*/
|
|
825
|
-
private
|
|
826
|
-
// long: "string" keeps every digit of an integer key that holds values beyond 2^53, as text
|
|
827
|
-
const asText = entry.unsafe && this.options.long === "string" && kindOf(entry) === "int";
|
|
828
|
-
const kind = asText ? "string" : kindOf(entry);
|
|
829
|
-
let scalar: ScalarDtype = dtypeOfKind(kind, entry);
|
|
830
|
-
if (
|
|
831
|
-
scalar === "string" &&
|
|
832
|
-
!entry.list &&
|
|
833
|
-
entry.dict !== null &&
|
|
834
|
-
entry.kinds === KIND_STRING &&
|
|
835
|
-
entry.dict.decide() === "dict"
|
|
836
|
-
) {
|
|
837
|
-
scalar = "dict";
|
|
838
|
-
}
|
|
855
|
+
private warnKinds(domain: "node" | "edge" | "graph", entry: KeySchema, asText: boolean): void {
|
|
839
856
|
if (
|
|
840
857
|
(entry.kinds & KIND_STRING) !== 0 &&
|
|
841
858
|
(entry.kinds & (KIND_INT | KIND_REAL)) !== 0 &&
|
|
@@ -858,6 +875,54 @@ class GmlImport {
|
|
|
858
875
|
`${PRECISION_CODE}:${domain}:${entry.key}`,
|
|
859
876
|
);
|
|
860
877
|
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* The plan of a node `graphics` record: the plan plus its position and rest columns.
|
|
882
|
+
* @param plan - the record's plan
|
|
883
|
+
* @param entry - the schema entry
|
|
884
|
+
* @returns the plan with its position (and rest) plans
|
|
885
|
+
*/
|
|
886
|
+
private graphicsPlan(plan: ColumnPlan, entry: KeySchema): ColumnPlan {
|
|
887
|
+
const taken = (candidate: string): boolean =>
|
|
888
|
+
this.nodeSchema.has(candidate) || this.sink.nodeColumn(candidate) !== INVALID_INDEX;
|
|
889
|
+
const position: ColumnPlan = {
|
|
890
|
+
...plan,
|
|
891
|
+
name: uniqueColumnName("position", "graphics", taken),
|
|
892
|
+
dtype: "f64",
|
|
893
|
+
itemDtype: null,
|
|
894
|
+
kind: "real",
|
|
895
|
+
list: false,
|
|
896
|
+
role: "position",
|
|
897
|
+
};
|
|
898
|
+
const rest: ColumnPlan | null = entry.rest
|
|
899
|
+
? { ...plan, dtype: "json", itemDtype: null, kind: "record", list: false }
|
|
900
|
+
: null;
|
|
901
|
+
return { ...plan, position, rest };
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* The plan of one schema entry.
|
|
906
|
+
* @param domain - the table
|
|
907
|
+
* @param entry - the schema entry
|
|
908
|
+
* @param top - whether the key is a top-level one (graph table)
|
|
909
|
+
* @returns the plan
|
|
910
|
+
*/
|
|
911
|
+
private planOf(domain: "node" | "edge" | "graph", entry: KeySchema, top = false): ColumnPlan {
|
|
912
|
+
// long: "string" keeps every digit of an integer key that holds values beyond 2^53, as text
|
|
913
|
+
const asText = entry.unsafe && this.options.long === "string" && kindOf(entry) === "int";
|
|
914
|
+
const kind = asText ? "string" : kindOf(entry);
|
|
915
|
+
let scalar: ScalarDtype = dtypeOfKind(kind, entry);
|
|
916
|
+
if (
|
|
917
|
+
scalar === "string" &&
|
|
918
|
+
!entry.list &&
|
|
919
|
+
entry.dict !== null &&
|
|
920
|
+
entry.kinds === KIND_STRING &&
|
|
921
|
+
entry.dict.decide() === "dict"
|
|
922
|
+
) {
|
|
923
|
+
scalar = "dict";
|
|
924
|
+
}
|
|
925
|
+
this.warnKinds(domain, entry, asText);
|
|
861
926
|
const name = domain === "graph" ? this.uniqueGraphName(entry.key) : entry.key;
|
|
862
927
|
let itemDtype: ScalarDtype | null = null;
|
|
863
928
|
if (entry.list) {
|
|
@@ -880,21 +945,7 @@ class GmlImport {
|
|
|
880
945
|
seq: Number.NEGATIVE_INFINITY,
|
|
881
946
|
};
|
|
882
947
|
if (domain === "node" && entry.key === "graphics" && this.positions && (entry.kinds & KIND_RECORD) !== 0) {
|
|
883
|
-
|
|
884
|
-
this.nodeSchema.has(candidate) || this.sink.nodeColumn(candidate) !== INVALID_INDEX;
|
|
885
|
-
const position: ColumnPlan = {
|
|
886
|
-
...plan,
|
|
887
|
-
name: uniqueColumnName("position", "graphics", taken),
|
|
888
|
-
dtype: "f64",
|
|
889
|
-
itemDtype: null,
|
|
890
|
-
kind: "real",
|
|
891
|
-
list: false,
|
|
892
|
-
role: "position",
|
|
893
|
-
};
|
|
894
|
-
const rest: ColumnPlan | null = entry.rest
|
|
895
|
-
? { ...plan, dtype: "json", itemDtype: null, kind: "record", list: false }
|
|
896
|
-
: null;
|
|
897
|
-
return { ...plan, position, rest };
|
|
948
|
+
return this.graphicsPlan(plan, entry);
|
|
898
949
|
}
|
|
899
950
|
return plan;
|
|
900
951
|
}
|
|
@@ -583,14 +583,7 @@ export function parseRecord(tokens: GmlTokens, open: number): Record<string, unk
|
|
|
583
583
|
const key = tokens.textOf(p);
|
|
584
584
|
const v = p + 1;
|
|
585
585
|
if (tokens.kind[v] === TOKEN_STRING) {
|
|
586
|
-
|
|
587
|
-
if (text === LIST_START_MARKER) {
|
|
588
|
-
frame.record[key] = [];
|
|
589
|
-
frame.listKeys ??= new Set();
|
|
590
|
-
frame.listKeys.add(key);
|
|
591
|
-
continue;
|
|
592
|
-
}
|
|
593
|
-
storeValue(frame, key, text === EMPTY_LIST_TEXT || text === EMPTY_TUPLE_TEXT ? [] : text);
|
|
586
|
+
storeString(frame, key, tokens.stringOf(v));
|
|
594
587
|
} else if (tokens.kind[v] === TOKEN_OPEN) {
|
|
595
588
|
stack.push({
|
|
596
589
|
record: Object.create(null) as Record<string, unknown>,
|
|
@@ -606,6 +599,23 @@ export function parseRecord(tokens: GmlTokens, open: number): Record<string, unk
|
|
|
606
599
|
return root;
|
|
607
600
|
}
|
|
608
601
|
|
|
602
|
+
/**
|
|
603
|
+
* Store a string value: the list-start marker opens an empty list for the key, the empty list and
|
|
604
|
+
* tuple texts store an empty list, anything else stores the text.
|
|
605
|
+
* @param frame - the open record
|
|
606
|
+
* @param key - the key
|
|
607
|
+
* @param text - the string
|
|
608
|
+
*/
|
|
609
|
+
function storeString(frame: RecordFrame, key: string, text: string): void {
|
|
610
|
+
if (text === LIST_START_MARKER) {
|
|
611
|
+
frame.record[key] = [];
|
|
612
|
+
frame.listKeys ??= new Set();
|
|
613
|
+
frame.listKeys.add(key);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
storeValue(frame, key, text === EMPTY_LIST_TEXT || text === EMPTY_TUPLE_TEXT ? [] : text);
|
|
617
|
+
}
|
|
618
|
+
|
|
609
619
|
/**
|
|
610
620
|
* Store one value under a key: appended to the key's list when the key is a list, paired with the
|
|
611
621
|
* earlier value when the key repeats, set otherwise.
|
|
@@ -1153,6 +1153,30 @@ function writeTreeData(keyId: string, value: unknown, indent: string, out: strin
|
|
|
1153
1153
|
out.push(`\n${indent}</data>`);
|
|
1154
1154
|
}
|
|
1155
1155
|
|
|
1156
|
+
/**
|
|
1157
|
+
* Append the `<default>` element of a key.
|
|
1158
|
+
* @param key - the key
|
|
1159
|
+
* @param column - the key's column
|
|
1160
|
+
* @param inner - the indentation of the element
|
|
1161
|
+
* @param parts - where to append
|
|
1162
|
+
*/
|
|
1163
|
+
function pushDefault(key: KeyPlan, column: Column, inner: string, parts: string[]): void {
|
|
1164
|
+
if (key.yfilesType === null) {
|
|
1165
|
+
parts.push(`\n${inner}<default>${escapeXmlText(defaultText(column, key.attrType) ?? "")}</default>`);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const value = column.meta.default;
|
|
1169
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1170
|
+
parts.push(`\n${inner}<default>`);
|
|
1171
|
+
writeXmlTree(value, `${inner} `, parts);
|
|
1172
|
+
parts.push(`\n${inner}</default>`);
|
|
1173
|
+
} else {
|
|
1174
|
+
const tree: string[] = [];
|
|
1175
|
+
writeXmlTree(value, inner, tree);
|
|
1176
|
+
parts.push(`\n${inner}<default>${tree.join("")}</default>`);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1156
1180
|
/**
|
|
1157
1181
|
* The `<key>` element of a plan.
|
|
1158
1182
|
* @param key - the key plan
|
|
@@ -1183,20 +1207,7 @@ function keyElement(key: KeyPlan, indent: string): string {
|
|
|
1183
1207
|
parts.push(`\n${inner}<desc>${escapeXmlText(desc)}</desc>`);
|
|
1184
1208
|
}
|
|
1185
1209
|
if (hasDefault) {
|
|
1186
|
-
|
|
1187
|
-
const value = column.meta.default;
|
|
1188
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1189
|
-
parts.push(`\n${inner}<default>`);
|
|
1190
|
-
writeXmlTree(value, `${inner} `, parts);
|
|
1191
|
-
parts.push(`\n${inner}</default>`);
|
|
1192
|
-
} else {
|
|
1193
|
-
const tree: string[] = [];
|
|
1194
|
-
writeXmlTree(value, inner, tree);
|
|
1195
|
-
parts.push(`\n${inner}<default>${tree.join("")}</default>`);
|
|
1196
|
-
}
|
|
1197
|
-
} else {
|
|
1198
|
-
parts.push(`\n${inner}<default>${escapeXmlText(defaultText(column, key.attrType) ?? "")}</default>`);
|
|
1199
|
-
}
|
|
1210
|
+
pushDefault(key, column, inner, parts);
|
|
1200
1211
|
}
|
|
1201
1212
|
parts.push(`\n${indent}</key>\n`);
|
|
1202
1213
|
return parts.join("");
|
|
@@ -1307,11 +1318,13 @@ function* writeGraphml(snapshot: GraphSnapshot, plan: Plan): Generator<string, v
|
|
|
1307
1318
|
// nodes in containment order, children nested under their parent
|
|
1308
1319
|
const { hierarchy } = plan;
|
|
1309
1320
|
const nested = hierarchy.childList.length > 0;
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1321
|
+
const walk: SubtreeWalk = {
|
|
1322
|
+
stackNode: [],
|
|
1323
|
+
stackCursor: [],
|
|
1324
|
+
written: nested ? new Uint8Array(snapshot.nodeCount) : null,
|
|
1325
|
+
};
|
|
1313
1326
|
for (const rootNode of hierarchy.roots) {
|
|
1314
|
-
yield* writeNodeSubtree(rootNode, plan, ids, i2, step,
|
|
1327
|
+
yield* writeNodeSubtree(rootNode, plan, ids, i2, step, walk);
|
|
1315
1328
|
}
|
|
1316
1329
|
|
|
1317
1330
|
// edges: primaries only, in index order
|
|
@@ -1378,6 +1391,16 @@ function portText(column: Column | null, e: number): string | null {
|
|
|
1378
1391
|
return typeof value === "string" ? value : String(value);
|
|
1379
1392
|
}
|
|
1380
1393
|
|
|
1394
|
+
/** The state a subtree walk reuses from one top-level node to the next. */
|
|
1395
|
+
interface SubtreeWalk {
|
|
1396
|
+
/** A reusable stack of open nodes. */
|
|
1397
|
+
readonly stackNode: number[];
|
|
1398
|
+
/** A reusable stack of child cursors. */
|
|
1399
|
+
readonly stackCursor: number[];
|
|
1400
|
+
/** Nodes already written (null when nothing is nested). */
|
|
1401
|
+
readonly written: Uint8Array | null;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1381
1404
|
/**
|
|
1382
1405
|
* Write a top-level node and, nested, every node contained in it (iteratively, so a deep chain
|
|
1383
1406
|
* cannot overflow the call stack).
|
|
@@ -1386,9 +1409,7 @@ function portText(column: Column | null, e: number): string | null {
|
|
|
1386
1409
|
* @param ids - the sanitised ids
|
|
1387
1410
|
* @param indent - the indentation of top-level nodes
|
|
1388
1411
|
* @param step - one indentation step
|
|
1389
|
-
* @param
|
|
1390
|
-
* @param stackCursor - a reusable stack of child cursors
|
|
1391
|
-
* @param written - nodes already written (null when nothing is nested)
|
|
1412
|
+
* @param walk - the reusable walk state
|
|
1392
1413
|
* @yields the node elements
|
|
1393
1414
|
* @returns nothing
|
|
1394
1415
|
*/
|
|
@@ -1398,10 +1419,9 @@ function* writeNodeSubtree(
|
|
|
1398
1419
|
ids: SanitizedIds,
|
|
1399
1420
|
indent: string,
|
|
1400
1421
|
step: string,
|
|
1401
|
-
|
|
1402
|
-
stackCursor: number[],
|
|
1403
|
-
written: Uint8Array | null,
|
|
1422
|
+
walk: SubtreeWalk,
|
|
1404
1423
|
): Generator<string, void, undefined> {
|
|
1424
|
+
const { stackNode, stackCursor, written } = walk;
|
|
1405
1425
|
const { hierarchy } = plan;
|
|
1406
1426
|
const hasChildren = (u: number): boolean => hierarchy.childStart[u + 1] > hierarchy.childStart[u];
|
|
1407
1427
|
const openNode = (u: number, depth: number): string => {
|
|
@@ -1126,9 +1126,6 @@ class GraphmlReader implements XmlHandler {
|
|
|
1126
1126
|
known: ReadonlySet<string>,
|
|
1127
1127
|
line: number,
|
|
1128
1128
|
): void {
|
|
1129
|
-
if (attrs.size === 0) {
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
1129
|
for (const name of attrs.keys()) {
|
|
1133
1130
|
if (known.has(name) || name === "xmlns" || /^(xmlns|xml|xsi):/.test(name)) {
|
|
1134
1131
|
continue;
|
|
@@ -1150,14 +1147,7 @@ class GraphmlReader implements XmlHandler {
|
|
|
1150
1147
|
if (names.size > MAX_UNKNOWN_ATTRIBUTE_NAMES && !hint) {
|
|
1151
1148
|
continue;
|
|
1152
1149
|
}
|
|
1153
|
-
|
|
1154
|
-
if (hint) {
|
|
1155
|
-
message = `the <${kind}> parse hint ${name} is ignored`;
|
|
1156
|
-
} else if (SCHEMA_ATTRIBUTES.has(name)) {
|
|
1157
|
-
message = `the <${kind}> attribute ${name} is not kept`;
|
|
1158
|
-
} else {
|
|
1159
|
-
message = `the <${kind}> attribute ${name} is not a GraphML attribute; it is not kept`;
|
|
1160
|
-
}
|
|
1150
|
+
const message = unreadAttributeMessage(kind, name, hint);
|
|
1161
1151
|
this.report.warning("unsupported", code, message, { line, element: name });
|
|
1162
1152
|
}
|
|
1163
1153
|
}
|
|
@@ -2729,3 +2719,20 @@ function unknownElementMessage(name: string): string {
|
|
|
2729
2719
|
? `element <${name}> is not allowed at this place in a GraphML file and was skipped`
|
|
2730
2720
|
: `element <${name}> is not GraphML and was skipped`;
|
|
2731
2721
|
}
|
|
2722
|
+
|
|
2723
|
+
/**
|
|
2724
|
+
* The warning text for an attribute the importer does not keep.
|
|
2725
|
+
* @param kind - the element name
|
|
2726
|
+
* @param name - the attribute name
|
|
2727
|
+
* @param hint - whether the attribute is a `parse.*` hint
|
|
2728
|
+
* @returns the message
|
|
2729
|
+
*/
|
|
2730
|
+
function unreadAttributeMessage(kind: string, name: string, hint: boolean): string {
|
|
2731
|
+
if (hint) {
|
|
2732
|
+
return `the <${kind}> parse hint ${name} is ignored`;
|
|
2733
|
+
}
|
|
2734
|
+
if (SCHEMA_ATTRIBUTES.has(name)) {
|
|
2735
|
+
return `the <${kind}> attribute ${name} is not kept`;
|
|
2736
|
+
}
|
|
2737
|
+
return `the <${kind}> attribute ${name} is not a GraphML attribute; it is not kept`;
|
|
2738
|
+
}
|