@graphty/graph-io 0.3.38 → 0.3.40
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-CsO24U-5.js → choose-CS3fvGT1.js} +2 -2
- package/dist/chunks/{choose-CsO24U-5.js.map → choose-CS3fvGT1.js.map} +1 -1
- package/dist/chunks/{constants-BPRiVyBu.js → constants-DqKZT6cH.js} +2 -2
- package/dist/chunks/{constants-BPRiVyBu.js.map → constants-DqKZT6cH.js.map} +1 -1
- package/dist/chunks/{escape-BRcyzD77.js → escape-BJRLd3vJ.js} +4 -3
- package/dist/chunks/escape-BJRLd3vJ.js.map +1 -0
- package/dist/chunks/{export-CTUlibzr.js → export-B-aVCwyG.js} +103 -92
- package/dist/chunks/export-B-aVCwyG.js.map +1 -0
- package/dist/chunks/{exporter-Bf5tZA0X.js → exporter-6ocL_9x_.js} +6 -5
- package/dist/chunks/exporter-6ocL_9x_.js.map +1 -0
- package/dist/chunks/{exporter-Bn4jdYfd.js → exporter-BVXnV9-9.js} +43 -30
- package/dist/chunks/exporter-BVXnV9-9.js.map +1 -0
- package/dist/chunks/{exporter-D-qQuRcG.js → exporter-Bcp-l4Bo.js} +6 -6
- package/dist/chunks/exporter-Bcp-l4Bo.js.map +1 -0
- package/dist/chunks/{exporter-F0vT919P.js → exporter-SFYkDzD0.js} +5 -4
- package/dist/chunks/exporter-SFYkDzD0.js.map +1 -0
- package/dist/chunks/{importer-Bgb9sgZZ.js → importer-AjwOJrn2.js} +6 -8
- package/dist/chunks/importer-AjwOJrn2.js.map +1 -0
- package/dist/chunks/{importer-BuzbaFFY.js → importer-BJlGAOht.js} +28 -13
- package/dist/chunks/importer-BJlGAOht.js.map +1 -0
- package/dist/chunks/{importer-DqMtyYLX.js → importer-BpuS3J8C.js} +12 -14
- package/dist/chunks/importer-BpuS3J8C.js.map +1 -0
- package/dist/chunks/{importer-CEuUmyPe.js → importer-CPqnIY8m.js} +6 -6
- package/dist/chunks/{importer-CEuUmyPe.js.map → importer-CPqnIY8m.js.map} +1 -1
- package/dist/chunks/{importer-yWUPR_nU.js → importer-DEp_fYjf.js} +30 -12
- package/dist/chunks/importer-DEp_fYjf.js.map +1 -0
- package/dist/chunks/{importer-CNMQnHDd.js → importer-D_srHyj6.js} +112 -62
- package/dist/chunks/importer-D_srHyj6.js.map +1 -0
- package/dist/chunks/{importer-oIUx9u_g.js → importer-DcKB97kW.js} +40 -18
- package/dist/chunks/importer-DcKB97kW.js.map +1 -0
- package/dist/chunks/{json-elements-AkglPS3m.js → json-elements-DpO6AXIc.js} +7 -5
- package/dist/chunks/{json-elements-AkglPS3m.js.map → json-elements-DpO6AXIc.js.map} +1 -1
- package/dist/chunks/{ontology-export-DDoG3egY.js → ontology-export-50L9DOV4.js} +2 -2
- package/dist/chunks/{ontology-export-DDoG3egY.js.map → ontology-export-50L9DOV4.js.map} +1 -1
- package/dist/chunks/{records-ChmWZGuz.js → records-r3SFHz11.js} +137 -96
- package/dist/chunks/records-r3SFHz11.js.map +1 -0
- package/dist/chunks/{text-Btw3sBe_.js → text-Bjfa1xYL.js} +2 -2
- package/dist/chunks/{text-Btw3sBe_.js.map → text-Bjfa1xYL.js.map} +1 -1
- package/dist/csv.js +4 -4
- 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 +6 -4
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +8 -6
- 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 +3 -3
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/direction.js +1 -1
- package/dist/src/common/direction.js.map +1 -1
- package/dist/src/common/export.d.ts.map +1 -1
- package/dist/src/common/export.js +136 -71
- package/dist/src/common/export.js.map +1 -1
- package/dist/src/common/input.js +1 -1
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +4 -2
- package/dist/src/common/json-elements.js.map +1 -1
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +2 -1
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +138 -100
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +108 -53
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +2 -1
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +28 -6
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/dot/importer.d.ts +6 -0
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +11 -6
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/names.d.ts +5 -0
- package/dist/src/formats/dot/names.d.ts.map +1 -1
- package/dist/src/formats/dot/names.js +5 -0
- package/dist/src/formats/dot/names.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +9 -9
- 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 +4 -2
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +6 -0
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +6 -3
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +2 -1
- 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 +5 -6
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/obo/importer.d.ts.map +1 -1
- package/dist/src/formats/obo/importer.js +2 -1
- package/dist/src/formats/obo/importer.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +4 -5
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +6 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +33 -10
- 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 +54 -22
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/sniff.js +1 -1
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/common/direction.ts +1 -1
- package/src/common/export.ts +190 -97
- package/src/common/input.ts +1 -1
- package/src/common/json-elements.ts +4 -2
- package/src/common/xml.ts +2 -1
- package/src/formats/csv/records.ts +143 -99
- package/src/formats/cx/importer.ts +140 -58
- package/src/formats/cx2/exporter.ts +2 -1
- package/src/formats/cx2/importer.ts +34 -10
- package/src/formats/dot/importer.ts +35 -4
- package/src/formats/dot/names.ts +6 -0
- package/src/formats/dot/tokenizer.ts +9 -9
- package/src/formats/gexf/exporter.ts +4 -2
- package/src/formats/gml/importer.ts +14 -3
- package/src/formats/graphml/exporter.ts +2 -3
- package/src/formats/graphml/importer.ts +5 -6
- package/src/formats/obo/importer.ts +2 -1
- package/src/formats/pajek/exporter.ts +6 -4
- package/src/formats/xgmml/emit.ts +34 -10
- package/src/formats/xgmml/exporter.ts +63 -23
- package/src/sniff.ts +1 -1
- package/dist/chunks/escape-BRcyzD77.js.map +0 -1
- package/dist/chunks/export-CTUlibzr.js.map +0 -1
- package/dist/chunks/exporter-Bf5tZA0X.js.map +0 -1
- package/dist/chunks/exporter-Bn4jdYfd.js.map +0 -1
- package/dist/chunks/exporter-D-qQuRcG.js.map +0 -1
- package/dist/chunks/exporter-F0vT919P.js.map +0 -1
- package/dist/chunks/importer-Bgb9sgZZ.js.map +0 -1
- package/dist/chunks/importer-BuzbaFFY.js.map +0 -1
- package/dist/chunks/importer-CNMQnHDd.js.map +0 -1
- package/dist/chunks/importer-DqMtyYLX.js.map +0 -1
- package/dist/chunks/importer-oIUx9u_g.js.map +0 -1
- package/dist/chunks/importer-yWUPR_nU.js.map +0 -1
- package/dist/chunks/records-ChmWZGuz.js.map +0 -1
package/src/common/export.ts
CHANGED
|
@@ -637,115 +637,208 @@ function checkColumns(
|
|
|
637
637
|
note: NoteFn,
|
|
638
638
|
): void {
|
|
639
639
|
for (const column of table) {
|
|
640
|
-
const {
|
|
641
|
-
const { name, role } = meta;
|
|
640
|
+
const { name, role } = column.meta;
|
|
642
641
|
if (role !== null && STRUCTURAL_ROLES.has(role)) {
|
|
643
642
|
continue;
|
|
644
643
|
}
|
|
645
644
|
const label = `${domain} column "${name}"`;
|
|
646
|
-
|
|
647
|
-
if (role !== null && roleNames !== undefined) {
|
|
648
|
-
const fixed = roleNames[role];
|
|
649
|
-
if (fixed !== undefined && fixed !== name) {
|
|
650
|
-
note(
|
|
651
|
-
LOSS.COLUMN_NAME_CHANGED,
|
|
652
|
-
`${label} (the ${ROLE_WORDS[role] ?? role}) is written as the format's own ${role} and reads back as "${fixed}"`,
|
|
653
|
-
name,
|
|
654
|
-
column.length - column.nullCount,
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
645
|
+
noteRenamedRole(column, label, roleNamesOf(domain, extras), note);
|
|
658
646
|
if (domain === "edge" && role === "id") {
|
|
659
647
|
continue;
|
|
660
648
|
}
|
|
661
|
-
|
|
662
|
-
if (
|
|
663
|
-
writesAttributes(domain, extras) === false &&
|
|
664
|
-
!named &&
|
|
665
|
-
!(role !== null && (extras.roles?.has(role) ?? false))
|
|
666
|
-
) {
|
|
649
|
+
if (isDroppedColumn(column, domain, extras)) {
|
|
667
650
|
note(LOSS.COLUMN_DROPPED, `${label} is not written`, name, column.length - column.nullCount);
|
|
668
651
|
continue;
|
|
669
652
|
}
|
|
670
|
-
if (
|
|
671
|
-
|
|
672
|
-
note(
|
|
673
|
-
LOSS.TEMPORAL_TEXT,
|
|
674
|
-
`${label} (the original text of a date or time column) cannot be written`,
|
|
675
|
-
name,
|
|
676
|
-
column.length - column.nullCount,
|
|
677
|
-
);
|
|
678
|
-
}
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (role === "position") {
|
|
682
|
-
if (!caps.positions) {
|
|
683
|
-
note(LOSS.POSITIONS, `${label} (positions) cannot be written`, name, column.length - column.nullCount);
|
|
684
|
-
} else if (extras.positionDtype !== undefined && meta.dtype !== extras.positionDtype) {
|
|
685
|
-
note(
|
|
686
|
-
LOSS.DTYPE,
|
|
687
|
-
`${label} (positions) is ${meta.dtype}; the format reads positions back as ${extras.positionDtype}`,
|
|
688
|
-
name,
|
|
689
|
-
column.length - column.nullCount,
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
continue;
|
|
693
|
-
}
|
|
694
|
-
if (role !== null && VIZ_ROLES.has(role)) {
|
|
695
|
-
if (!caps.viz) {
|
|
696
|
-
note(LOSS.VIZ, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
|
|
697
|
-
}
|
|
698
|
-
continue;
|
|
699
|
-
}
|
|
700
|
-
if (role !== null && HIERARCHY_ROLES.has(role)) {
|
|
701
|
-
if (!caps.hierarchy) {
|
|
702
|
-
note(LOSS.HIERARCHY, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
|
|
703
|
-
}
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
if (role !== null && TEMPORAL_ROLES.has(role)) {
|
|
707
|
-
if (caps.temporal === "none") {
|
|
708
|
-
note(LOSS.TEMPORAL, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
|
|
709
|
-
}
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
if (role === "spells") {
|
|
713
|
-
if (caps.temporal !== "spells" && caps.temporal !== "dynamic-values") {
|
|
714
|
-
note(LOSS.SPELLS, `${label} (spells) cannot be written`, name, column.length - column.nullCount);
|
|
715
|
-
}
|
|
716
|
-
continue;
|
|
717
|
-
}
|
|
718
|
-
if (role === "open" || role === "spellsOpen") {
|
|
719
|
-
// The open bits are written by the format's own temporal syntax when it has one; the
|
|
720
|
-
// column's u8 storage is never an attribute dtype, so no dtype note either way.
|
|
721
|
-
if (extras.openIntervals !== true) {
|
|
722
|
-
note(
|
|
723
|
-
LOSS.OPEN_INTERVAL,
|
|
724
|
-
`${label} (open intervals) cannot be written`,
|
|
725
|
-
name,
|
|
726
|
-
column.length - column.nullCount,
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
if (role !== null && !(extras.roles?.has(role) ?? false)) {
|
|
732
|
-
note(
|
|
733
|
-
LOSS.ROLE,
|
|
734
|
-
`${label} (${role}) is written as a plain attribute; the format has no ${role} slot and the role is lost`,
|
|
735
|
-
name,
|
|
736
|
-
column.length - column.nullCount,
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
if (meta.dynamic && caps.temporal !== "dynamic-values") {
|
|
740
|
-
note(LOSS.DYNAMIC_VALUES, `${label} is dynamic; only its static value can be written`, name, null);
|
|
741
|
-
}
|
|
742
|
-
checkDtype(column, label, caps, note);
|
|
743
|
-
if (meta.default !== undefined && !caps.defaults) {
|
|
744
|
-
note(LOSS.DEFAULT, `${label} declares a default; the format has no defaults`, name, null);
|
|
653
|
+
if (!checkSlotRole(column, label, caps, extras, note)) {
|
|
654
|
+
checkAttributeColumn(column, label, caps, extras, note);
|
|
745
655
|
}
|
|
746
|
-
|
|
747
|
-
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* The note of a role column the format writes under its own fixed name.
|
|
661
|
+
* @param column - the column
|
|
662
|
+
* @param label - the column's label for messages
|
|
663
|
+
* @param roleNames - the format's fixed names per role, if any
|
|
664
|
+
* @param note - the recorder
|
|
665
|
+
*/
|
|
666
|
+
function noteRenamedRole(
|
|
667
|
+
column: Column,
|
|
668
|
+
label: string,
|
|
669
|
+
roleNames: Readonly<Record<string, string>> | undefined,
|
|
670
|
+
note: NoteFn,
|
|
671
|
+
): void {
|
|
672
|
+
const { name, role } = column.meta;
|
|
673
|
+
if (role === null || roleNames === undefined) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const fixed = roleNames[role];
|
|
677
|
+
if (fixed !== undefined && fixed !== name) {
|
|
678
|
+
note(
|
|
679
|
+
LOSS.COLUMN_NAME_CHANGED,
|
|
680
|
+
`${label} (the ${ROLE_WORDS[role] ?? role}) is written as the format's own ${role} and reads back as "${fixed}"`,
|
|
681
|
+
name,
|
|
682
|
+
column.length - column.nullCount,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Whether a format that writes no attributes of this kind drops the column (it is neither named
|
|
689
|
+
* in CheckExtras.writtenColumns nor of a role the format has a slot for).
|
|
690
|
+
* @param column - the column
|
|
691
|
+
* @param domain - "node", "edge" or "graph"
|
|
692
|
+
* @param extras - format extras
|
|
693
|
+
* @returns true when the column is not written
|
|
694
|
+
*/
|
|
695
|
+
function isDroppedColumn(column: Column, domain: string, extras: CheckExtras): boolean {
|
|
696
|
+
const { name, role } = column.meta;
|
|
697
|
+
const named = extras.writtenColumns?.[domain as "node" | "edge"]?.includes(name) ?? false;
|
|
698
|
+
return (
|
|
699
|
+
writesAttributes(domain, extras) === false && !named && !(role !== null && (extras.roles?.has(role) ?? false))
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** How a format writes one slot role: what it loses when it cannot, and when it can. */
|
|
704
|
+
interface SlotRule {
|
|
705
|
+
readonly code: string;
|
|
706
|
+
/** What the message calls the column; the role itself when absent. */
|
|
707
|
+
readonly what?: string;
|
|
708
|
+
readonly written: (caps: ExportCapabilities, extras: CheckExtras) => boolean;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* The roles (other than position) a format writes in its own syntax rather than as an attribute.
|
|
713
|
+
* @returns the rules by role
|
|
714
|
+
*/
|
|
715
|
+
function slotRules(): ReadonlyMap<string, SlotRule> {
|
|
716
|
+
const rules = new Map<string, SlotRule>();
|
|
717
|
+
const add = (roles: Iterable<string>, rule: SlotRule): void => {
|
|
718
|
+
for (const role of roles) {
|
|
719
|
+
rules.set(role, rule);
|
|
748
720
|
}
|
|
721
|
+
};
|
|
722
|
+
add(["timeText"], {
|
|
723
|
+
code: LOSS.TEMPORAL_TEXT,
|
|
724
|
+
what: "the original text of a date or time column",
|
|
725
|
+
written: (_caps, extras) => extras.temporalText === true,
|
|
726
|
+
});
|
|
727
|
+
add(VIZ_ROLES, { code: LOSS.VIZ, written: (caps) => caps.viz });
|
|
728
|
+
add(HIERARCHY_ROLES, { code: LOSS.HIERARCHY, written: (caps) => caps.hierarchy });
|
|
729
|
+
add(TEMPORAL_ROLES, { code: LOSS.TEMPORAL, written: (caps) => caps.temporal !== "none" });
|
|
730
|
+
add(["spells"], {
|
|
731
|
+
code: LOSS.SPELLS,
|
|
732
|
+
what: "spells",
|
|
733
|
+
written: (caps) => caps.temporal === "spells" || caps.temporal === "dynamic-values",
|
|
734
|
+
});
|
|
735
|
+
// The open bits are written by the format's own temporal syntax when it has one; the
|
|
736
|
+
// column's u8 storage is never an attribute dtype, so no dtype note either way.
|
|
737
|
+
add(["open", "spellsOpen"], {
|
|
738
|
+
code: LOSS.OPEN_INTERVAL,
|
|
739
|
+
what: "open intervals",
|
|
740
|
+
written: (_caps, extras) => extras.openIntervals === true,
|
|
741
|
+
});
|
|
742
|
+
return rules;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const SLOT_RULES = slotRules();
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* The checks of a column whose role the format writes in its own syntax (positions, viz,
|
|
749
|
+
* hierarchy, temporal roles, spells, open intervals, original date text), not as an attribute.
|
|
750
|
+
* @param column - the column
|
|
751
|
+
* @param label - the column's label for messages
|
|
752
|
+
* @param caps - the capabilities
|
|
753
|
+
* @param extras - format extras
|
|
754
|
+
* @param note - the recorder
|
|
755
|
+
* @returns true when the role is one of those, so no attribute checks follow
|
|
756
|
+
*/
|
|
757
|
+
function checkSlotRole(
|
|
758
|
+
column: Column,
|
|
759
|
+
label: string,
|
|
760
|
+
caps: ExportCapabilities,
|
|
761
|
+
extras: CheckExtras,
|
|
762
|
+
note: NoteFn,
|
|
763
|
+
): boolean {
|
|
764
|
+
const { name, role } = column.meta;
|
|
765
|
+
if (role === "position") {
|
|
766
|
+
checkPositionColumn(column, label, caps, extras, note);
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
const rule = role === null ? undefined : SLOT_RULES.get(role);
|
|
770
|
+
if (role === null || rule === undefined) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
if (!rule.written(caps, extras)) {
|
|
774
|
+
note(rule.code, `${label} (${rule.what ?? role}) cannot be written`, name, column.length - column.nullCount);
|
|
775
|
+
}
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* The checks of a position column: whether the format has positions, and their dtype.
|
|
781
|
+
* @param column - the column
|
|
782
|
+
* @param label - the column's label for messages
|
|
783
|
+
* @param caps - the capabilities
|
|
784
|
+
* @param extras - format extras
|
|
785
|
+
* @param note - the recorder
|
|
786
|
+
*/
|
|
787
|
+
function checkPositionColumn(
|
|
788
|
+
column: Column,
|
|
789
|
+
label: string,
|
|
790
|
+
caps: ExportCapabilities,
|
|
791
|
+
extras: CheckExtras,
|
|
792
|
+
note: NoteFn,
|
|
793
|
+
): void {
|
|
794
|
+
const { meta } = column;
|
|
795
|
+
if (!caps.positions) {
|
|
796
|
+
note(LOSS.POSITIONS, `${label} (positions) cannot be written`, meta.name, column.length - column.nullCount);
|
|
797
|
+
} else if (extras.positionDtype !== undefined && meta.dtype !== extras.positionDtype) {
|
|
798
|
+
note(
|
|
799
|
+
LOSS.DTYPE,
|
|
800
|
+
`${label} (positions) is ${meta.dtype}; the format reads positions back as ${extras.positionDtype}`,
|
|
801
|
+
meta.name,
|
|
802
|
+
column.length - column.nullCount,
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* The checks of a column written as a plain attribute: a role the format has no slot for,
|
|
809
|
+
* dynamic values, the dtype, a declared default and declared options.
|
|
810
|
+
* @param column - the column
|
|
811
|
+
* @param label - the column's label for messages
|
|
812
|
+
* @param caps - the capabilities
|
|
813
|
+
* @param extras - format extras
|
|
814
|
+
* @param note - the recorder
|
|
815
|
+
*/
|
|
816
|
+
function checkAttributeColumn(
|
|
817
|
+
column: Column,
|
|
818
|
+
label: string,
|
|
819
|
+
caps: ExportCapabilities,
|
|
820
|
+
extras: CheckExtras,
|
|
821
|
+
note: NoteFn,
|
|
822
|
+
): void {
|
|
823
|
+
const { meta } = column;
|
|
824
|
+
const { name, role } = meta;
|
|
825
|
+
if (role !== null && !(extras.roles?.has(role) ?? false)) {
|
|
826
|
+
note(
|
|
827
|
+
LOSS.ROLE,
|
|
828
|
+
`${label} (${role}) is written as a plain attribute; the format has no ${role} slot and the role is lost`,
|
|
829
|
+
name,
|
|
830
|
+
column.length - column.nullCount,
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
if (meta.dynamic && caps.temporal !== "dynamic-values") {
|
|
834
|
+
note(LOSS.DYNAMIC_VALUES, `${label} is dynamic; only its static value can be written`, name, null);
|
|
835
|
+
}
|
|
836
|
+
checkDtype(column, label, caps, note);
|
|
837
|
+
if (meta.default !== undefined && !caps.defaults) {
|
|
838
|
+
note(LOSS.DEFAULT, `${label} declares a default; the format has no defaults`, name, null);
|
|
839
|
+
}
|
|
840
|
+
if (meta.options !== null && !caps.options) {
|
|
841
|
+
note(LOSS.OPTIONS, `${label} declares options; the format has no enumerations`, name, null);
|
|
749
842
|
}
|
|
750
843
|
}
|
|
751
844
|
|
package/src/common/input.ts
CHANGED
|
@@ -1007,7 +1007,7 @@ export function throwIfAborted(signal: AbortSignal | null | undefined): void {
|
|
|
1007
1007
|
throw abortError();
|
|
1008
1008
|
}
|
|
1009
1009
|
// the platform contract: the reason itself, whatever its type
|
|
1010
|
-
throw reason as Error;
|
|
1010
|
+
throw reason as Error; // NOSONAR(S3696): rethrows the abort signal's reason, as the platform does
|
|
1011
1011
|
}
|
|
1012
1012
|
|
|
1013
1013
|
/**
|
|
@@ -1493,10 +1493,11 @@ async function* blockElements(
|
|
|
1493
1493
|
* @category Plugin helpers
|
|
1494
1494
|
*/
|
|
1495
1495
|
export function reportTooDeep(report: ImportReportBuilder, event: Extract<AspectEvent, { kind: "deep" }>): void {
|
|
1496
|
+
const what = event.aspect === null ? "a member of the document" : `an element of "${event.aspect}"`;
|
|
1496
1497
|
report.error(
|
|
1497
1498
|
"parse-error",
|
|
1498
1499
|
BAD_ASPECT_BLOCK_CODE,
|
|
1499
|
-
`${
|
|
1500
|
+
`${what} is nested ${event.depth} levels deep, more than the ${MAX_ELEMENT_DEPTH} a graph keeps; skipped`,
|
|
1500
1501
|
{ line: event.line, element: event.aspect },
|
|
1501
1502
|
);
|
|
1502
1503
|
}
|
|
@@ -1893,7 +1894,8 @@ export class CxStructure {
|
|
|
1893
1894
|
}
|
|
1894
1895
|
const error = typeof value.error === "string" ? value.error : "";
|
|
1895
1896
|
if (value.success === false) {
|
|
1896
|
-
const
|
|
1897
|
+
const detail = error.length > 0 ? `: ${error}` : "";
|
|
1898
|
+
const message = `the producer marked the document as failed${detail}`;
|
|
1897
1899
|
this.report.error("validation-error", STATUS_FAILED_CODE, message, { line, element: "status" });
|
|
1898
1900
|
throw this.report.abort(message, { code: STATUS_FAILED_CODE });
|
|
1899
1901
|
}
|
package/src/common/xml.ts
CHANGED
|
@@ -416,7 +416,8 @@ export function decodeEntities(raw: string, line: number, repairs?: XmlRepairs):
|
|
|
416
416
|
const digits = name.slice(hex ? 2 : 1);
|
|
417
417
|
// XML 1.0 puts no limit on leading zeros (A is "A")
|
|
418
418
|
const ok = hex ? /^0*[\da-fA-F]{1,6}$/.test(digits) : /^0*\d{1,7}$/.test(digits);
|
|
419
|
-
const
|
|
419
|
+
const radix = hex ? 16 : 10;
|
|
420
|
+
const cp = ok ? Number.parseInt(digits, radix) : -1;
|
|
420
421
|
const low = cp >= 0xd800 && cp <= 0xdbff ? lowSurrogateAfter(raw, semi + 1, repairs) : null;
|
|
421
422
|
if (low !== null) {
|
|
422
423
|
repairs?.surrogatePair?.(lineAt(raw, amp, line));
|
|
@@ -739,125 +739,169 @@ class RecordScanner {
|
|
|
739
739
|
* @returns the position after the last character consumed
|
|
740
740
|
*/
|
|
741
741
|
scan(chunk: string, from: number): number {
|
|
742
|
-
const {
|
|
742
|
+
const { delimiterCode } = this;
|
|
743
743
|
const n = chunk.length;
|
|
744
744
|
let i = from;
|
|
745
745
|
this.segment = from;
|
|
746
746
|
while (i < n) {
|
|
747
747
|
const c = chunk.charCodeAt(i);
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
this.reader.leadingComments.push(this.comment);
|
|
752
|
-
this.reader.leadingCommentLines.push(this.line);
|
|
753
|
-
this.comment = "";
|
|
754
|
-
this.state = START;
|
|
755
|
-
this.endLine(c);
|
|
756
|
-
this.segment = i + 1;
|
|
757
|
-
}
|
|
748
|
+
const { state } = this;
|
|
749
|
+
if (state === COMMENT) {
|
|
750
|
+
this.commentChar(chunk, i, c);
|
|
758
751
|
i++;
|
|
759
752
|
continue;
|
|
760
753
|
}
|
|
761
|
-
if (
|
|
762
|
-
|
|
763
|
-
const q = chunk.indexOf(this.quoteText, i);
|
|
764
|
-
const end = q < 0 ? n : q;
|
|
765
|
-
if (end > i) {
|
|
766
|
-
this.countLineBreaks(chunk, i, end);
|
|
767
|
-
}
|
|
768
|
-
if (q < 0) {
|
|
769
|
-
i = n;
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
this.field = this.grow(this.field, chunk.slice(this.segment, q));
|
|
773
|
-
this.state = CLOSING;
|
|
774
|
-
this.lastWasCr = false;
|
|
775
|
-
i = q + 1;
|
|
754
|
+
if (state === QUOTED) {
|
|
755
|
+
i = this.quotedRun(chunk, i);
|
|
776
756
|
continue;
|
|
777
757
|
}
|
|
778
758
|
const isDelimiter = c === delimiterCode || (this.collapse && c === TAB);
|
|
779
|
-
if (
|
|
780
|
-
|
|
781
|
-
this.field = this.grow(this.field, this.quoteText);
|
|
782
|
-
this.segment = i + 1;
|
|
783
|
-
this.state = QUOTED;
|
|
784
|
-
this.lastWasCr = false;
|
|
785
|
-
i++;
|
|
786
|
-
continue;
|
|
787
|
-
}
|
|
788
|
-
if (!isDelimiter && c !== LF && c !== CR) {
|
|
789
|
-
this.reader.badQuote(this.startLine, this.count + 1);
|
|
790
|
-
}
|
|
791
|
-
this.state = AFTER_QUOTED;
|
|
792
|
-
this.segment = i;
|
|
793
|
-
// fall through to the delimiter / line-end handling below without consuming c
|
|
794
|
-
}
|
|
795
|
-
if (isDelimiter || c === LF || c === CR) {
|
|
796
|
-
if (c === LF && this.lastWasCr) {
|
|
797
|
-
// the second half of a CRLF already ended the record
|
|
798
|
-
this.lastWasCr = false;
|
|
799
|
-
this.segment = i + 1;
|
|
800
|
-
i++;
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
if (this.state === UNQUOTED) {
|
|
804
|
-
this.field = this.grow(this.field, chunk.slice(this.segment, i));
|
|
805
|
-
}
|
|
806
|
-
if (isDelimiter && this.collapse && this.state === START) {
|
|
807
|
-
// a run of whitespace, or indentation: one separator
|
|
808
|
-
this.lastWasCr = false;
|
|
809
|
-
this.segment = i + 1;
|
|
810
|
-
i++;
|
|
811
|
-
continue;
|
|
812
|
-
}
|
|
813
|
-
if (isDelimiter) {
|
|
814
|
-
this.push();
|
|
815
|
-
this.state = START;
|
|
816
|
-
this.lastWasCr = false;
|
|
817
|
-
this.segment = i + 1;
|
|
818
|
-
i++;
|
|
819
|
-
continue;
|
|
820
|
-
}
|
|
821
|
-
// a line ending right after a whitespace run in the whitespace dialect ends no field
|
|
822
|
-
if (!(this.state === START && (this.count === 0 || this.collapse))) {
|
|
823
|
-
this.push();
|
|
824
|
-
}
|
|
825
|
-
if (this.count > 0) {
|
|
826
|
-
this.ready = true;
|
|
827
|
-
this.recordLine = this.startLine;
|
|
828
|
-
}
|
|
829
|
-
this.state = START;
|
|
830
|
-
this.endLine(c);
|
|
831
|
-
this.segment = i + 1;
|
|
832
|
-
i++;
|
|
759
|
+
if (state === CLOSING || isDelimiter || c === LF || c === CR) {
|
|
760
|
+
i = this.boundary(chunk, i, c, isDelimiter);
|
|
833
761
|
if (this.ready) {
|
|
834
762
|
return i;
|
|
835
763
|
}
|
|
836
764
|
continue;
|
|
837
765
|
}
|
|
766
|
+
this.fieldChar(c, i);
|
|
767
|
+
i++;
|
|
768
|
+
}
|
|
769
|
+
return i;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* One character of a leading comment line: a line break ends the comment.
|
|
774
|
+
* @param chunk - the chunk
|
|
775
|
+
* @param i - the character's index
|
|
776
|
+
* @param c - the character's code
|
|
777
|
+
*/
|
|
778
|
+
private commentChar(chunk: string, i: number, c: number): void {
|
|
779
|
+
if (c !== LF && c !== CR) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
this.comment = this.grow(this.comment, chunk.slice(this.segment, i));
|
|
783
|
+
this.reader.leadingComments.push(this.comment);
|
|
784
|
+
this.reader.leadingCommentLines.push(this.line);
|
|
785
|
+
this.comment = "";
|
|
786
|
+
this.state = START;
|
|
787
|
+
this.endLine(c);
|
|
788
|
+
this.segment = i + 1;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Inside a quoted field: jump to the next quote; the run in between is field text whose line
|
|
793
|
+
* breaks are counted.
|
|
794
|
+
* @param chunk - the chunk
|
|
795
|
+
* @param i - where the run starts
|
|
796
|
+
* @returns the position after the quote, or the chunk end when there is none
|
|
797
|
+
*/
|
|
798
|
+
private quotedRun(chunk: string, i: number): number {
|
|
799
|
+
const q = chunk.indexOf(this.quoteText, i);
|
|
800
|
+
const end = q < 0 ? chunk.length : q;
|
|
801
|
+
if (end > i) {
|
|
802
|
+
this.countLineBreaks(chunk, i, end);
|
|
803
|
+
}
|
|
804
|
+
if (q < 0) {
|
|
805
|
+
return chunk.length;
|
|
806
|
+
}
|
|
807
|
+
this.field = this.grow(this.field, chunk.slice(this.segment, q));
|
|
808
|
+
this.state = CLOSING;
|
|
809
|
+
this.lastWasCr = false;
|
|
810
|
+
return q + 1;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* The character after a quote inside a quoted field, a delimiter or a line break: a doubled
|
|
815
|
+
* quote, the end of a field, or the end of a record.
|
|
816
|
+
* @param chunk - the chunk
|
|
817
|
+
* @param i - the character's index
|
|
818
|
+
* @param c - the character's code
|
|
819
|
+
* @param isDelimiter - whether it separates fields
|
|
820
|
+
* @returns the position after the character
|
|
821
|
+
*/
|
|
822
|
+
private boundary(chunk: string, i: number, c: number, isDelimiter: boolean): number {
|
|
823
|
+
if (this.state === CLOSING) {
|
|
824
|
+
if (c === this.quoteCode) {
|
|
825
|
+
this.field = this.grow(this.field, this.quoteText);
|
|
826
|
+
this.segment = i + 1;
|
|
827
|
+
this.state = QUOTED;
|
|
828
|
+
this.lastWasCr = false;
|
|
829
|
+
return i + 1;
|
|
830
|
+
}
|
|
831
|
+
if (!isDelimiter && c !== LF && c !== CR) {
|
|
832
|
+
this.reader.badQuote(this.startLine, this.count + 1);
|
|
833
|
+
}
|
|
834
|
+
this.state = AFTER_QUOTED;
|
|
835
|
+
this.segment = i;
|
|
836
|
+
}
|
|
837
|
+
if (c === LF && this.lastWasCr) {
|
|
838
|
+
// the second half of a CRLF already ended the record
|
|
838
839
|
this.lastWasCr = false;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
840
|
+
this.segment = i + 1;
|
|
841
|
+
return i + 1;
|
|
842
|
+
}
|
|
843
|
+
if (this.state === UNQUOTED) {
|
|
844
|
+
this.field = this.grow(this.field, chunk.slice(this.segment, i));
|
|
845
|
+
}
|
|
846
|
+
if (isDelimiter) {
|
|
847
|
+
// in the whitespace dialect a run of whitespace, or indentation, is one separator
|
|
848
|
+
if (!(this.collapse && this.state === START)) {
|
|
849
|
+
this.push();
|
|
850
|
+
this.state = START;
|
|
851
|
+
}
|
|
852
|
+
this.lastWasCr = false;
|
|
853
|
+
this.segment = i + 1;
|
|
854
|
+
return i + 1;
|
|
855
|
+
}
|
|
856
|
+
this.lineEnd(c);
|
|
857
|
+
this.segment = i + 1;
|
|
858
|
+
return i + 1;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* A line break that ends a record (or a blank line).
|
|
863
|
+
* @param c - the LF or CR
|
|
864
|
+
*/
|
|
865
|
+
private lineEnd(c: number): void {
|
|
866
|
+
// a line ending right after a whitespace run in the whitespace dialect ends no field
|
|
867
|
+
if (!(this.state === START && (this.count === 0 || this.collapse))) {
|
|
868
|
+
this.push();
|
|
869
|
+
}
|
|
870
|
+
if (this.count > 0) {
|
|
871
|
+
this.ready = true;
|
|
872
|
+
this.recordLine = this.startLine;
|
|
873
|
+
}
|
|
874
|
+
this.state = START;
|
|
875
|
+
this.endLine(c);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Any other character outside a quoted field: the start of a field or a comment, or field text.
|
|
880
|
+
* @param c - the character's code
|
|
881
|
+
* @param i - its index
|
|
882
|
+
*/
|
|
883
|
+
private fieldChar(c: number, i: number): void {
|
|
884
|
+
this.lastWasCr = false;
|
|
885
|
+
if (this.state !== START) {
|
|
886
|
+
if (c === this.quoteCode && this.state === UNQUOTED && this.reader.strayQuoteLine === 0) {
|
|
856
887
|
this.reader.strayQuoteLine = this.startLine;
|
|
857
888
|
}
|
|
858
|
-
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (this.leading && this.count === 0 && this.commentCodes.includes(c)) {
|
|
892
|
+
this.state = COMMENT;
|
|
893
|
+
this.segment = i;
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
this.leading = false;
|
|
897
|
+
if (c === this.quoteCode) {
|
|
898
|
+
this.state = QUOTED;
|
|
899
|
+
this.fieldQuoted = true;
|
|
900
|
+
this.segment = i + 1;
|
|
901
|
+
} else {
|
|
902
|
+
this.state = UNQUOTED;
|
|
903
|
+
this.segment = i;
|
|
859
904
|
}
|
|
860
|
-
return i;
|
|
861
905
|
}
|
|
862
906
|
|
|
863
907
|
/**
|