@beyondwork/docx-react-component 1.0.11 → 1.0.12
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 +8 -2
- package/package.json +6 -11
- package/src/api/public-types.ts +31 -1
- package/src/core/state/editor-state.ts +318 -9
- package/src/io/docx-session.ts +392 -22
- package/src/io/export/export-session.ts +55 -0
- package/src/io/export/serialize-footnotes.ts +5 -20
- package/src/io/export/serialize-headers-footers.ts +5 -31
- package/src/io/export/serialize-main-document.ts +78 -5
- package/src/io/normalize/normalize-text.ts +90 -1
- package/src/io/ooxml/parse-footnotes.ts +68 -5
- package/src/io/ooxml/parse-headers-footers.ts +67 -9
- package/src/io/ooxml/parse-main-document.ts +169 -6
- package/src/io/opc/package-reader.ts +3 -3
- package/src/io/source-package-provenance.ts +241 -0
- package/src/model/canonical-document.ts +450 -2
- package/src/model/cds-1.0.0.ts +5 -2
- package/src/model/snapshot.ts +190 -19
- package/src/preservation/package-preservation.ts +0 -7
- package/src/runtime/document-runtime.ts +7 -1
- package/src/runtime/read-only-diagnostics-runtime.ts +1 -1
- package/src/runtime/surface-projection.ts +199 -17
- package/src/ui/WordReviewEditor.tsx +189 -14
- package/src/ui-tailwind/editor-surface/pm-schema.ts +121 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +66 -2
- package/src/validation/compatibility-engine.ts +208 -0
|
@@ -494,7 +494,12 @@ export type TextMark =
|
|
|
494
494
|
| { type: "imprint" }
|
|
495
495
|
| { type: "shadow" }
|
|
496
496
|
| { type: "position"; val: number }
|
|
497
|
-
| { type: "textFill"; xml: string }
|
|
497
|
+
| { type: "textFill"; xml: string }
|
|
498
|
+
| { type: "fontFamily"; val: string }
|
|
499
|
+
| { type: "fontSize"; val: number }
|
|
500
|
+
| { type: "textColor"; color: string }
|
|
501
|
+
| { type: "smallCaps" }
|
|
502
|
+
| { type: "allCaps" };
|
|
498
503
|
|
|
499
504
|
export interface HardBreakNode {
|
|
500
505
|
type: "hard_break";
|
|
@@ -847,6 +852,7 @@ export function validateCanonicalDocument(
|
|
|
847
852
|
if (record.subParts !== undefined) {
|
|
848
853
|
validateSubPartsCatalog(record.subParts, "$.subParts", issues);
|
|
849
854
|
}
|
|
855
|
+
validateDocumentReferences(record, issues);
|
|
850
856
|
|
|
851
857
|
return issues;
|
|
852
858
|
}
|
|
@@ -1091,6 +1097,9 @@ function validateDocumentNode(
|
|
|
1091
1097
|
validateDocumentNode(child, `${path}.children[${index}]`, issues),
|
|
1092
1098
|
);
|
|
1093
1099
|
}
|
|
1100
|
+
if (type === "paragraph" && record.styleId !== undefined) {
|
|
1101
|
+
expectDomainString(record.styleId, "styleId", `${path}.styleId`, issues);
|
|
1102
|
+
}
|
|
1094
1103
|
if (type === "paragraph" && record.numbering !== undefined) {
|
|
1095
1104
|
const numbering = asPlainObject(record.numbering, `${path}.numbering`, issues);
|
|
1096
1105
|
if (numbering) {
|
|
@@ -1100,8 +1109,17 @@ function validateDocumentNode(
|
|
|
1100
1109
|
`${path}.numbering.numberingInstanceId`,
|
|
1101
1110
|
issues,
|
|
1102
1111
|
);
|
|
1112
|
+
if (typeof numbering.level !== "number") {
|
|
1113
|
+
issues.push({
|
|
1114
|
+
path: `${path}.numbering.level`,
|
|
1115
|
+
message: "level must be a number.",
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1103
1118
|
}
|
|
1104
1119
|
}
|
|
1120
|
+
if (type === "hyperlink") {
|
|
1121
|
+
expectString(record.href, `${path}.href`, issues);
|
|
1122
|
+
}
|
|
1105
1123
|
return;
|
|
1106
1124
|
case "alt_chunk":
|
|
1107
1125
|
expectDomainString(record.relationshipId, "relationshipId", `${path}.relationshipId`, issues);
|
|
@@ -1160,6 +1178,8 @@ function validateDocumentNode(
|
|
|
1160
1178
|
}
|
|
1161
1179
|
return;
|
|
1162
1180
|
case "field":
|
|
1181
|
+
expectString(record.fieldType, `${path}.fieldType`, issues);
|
|
1182
|
+
expectString(record.instruction, `${path}.instruction`, issues);
|
|
1163
1183
|
if (!Array.isArray(record.children)) {
|
|
1164
1184
|
issues.push({ path: `${path}.children`, message: "children must be an array." });
|
|
1165
1185
|
} else {
|
|
@@ -1178,6 +1198,13 @@ function validateDocumentNode(
|
|
|
1178
1198
|
case "section_break":
|
|
1179
1199
|
return;
|
|
1180
1200
|
case "text":
|
|
1201
|
+
if (typeof record.text !== "string") {
|
|
1202
|
+
issues.push({
|
|
1203
|
+
path: `${path}.text`,
|
|
1204
|
+
message: "text must be a string.",
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
return;
|
|
1181
1208
|
case "hard_break":
|
|
1182
1209
|
case "column_break":
|
|
1183
1210
|
case "tab":
|
|
@@ -1190,7 +1217,12 @@ function validateDocumentNode(
|
|
|
1190
1217
|
return;
|
|
1191
1218
|
case "footnote_ref":
|
|
1192
1219
|
expectString(record.noteId, `${path}.noteId`, issues);
|
|
1193
|
-
|
|
1220
|
+
if (record.noteKind !== "footnote" && record.noteKind !== "endnote") {
|
|
1221
|
+
issues.push({
|
|
1222
|
+
path: `${path}.noteKind`,
|
|
1223
|
+
message: "noteKind must be 'footnote' or 'endnote'.",
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1194
1226
|
return;
|
|
1195
1227
|
case "chart_preview":
|
|
1196
1228
|
case "smartart_preview":
|
|
@@ -1798,11 +1830,363 @@ function validateAnchor(
|
|
|
1798
1830
|
|
|
1799
1831
|
if (kind === "range") {
|
|
1800
1832
|
validateRange(record.range, `${path}.range`, issues);
|
|
1833
|
+
validateBoundaryAssoc(record.assoc, `${path}.assoc`, issues);
|
|
1834
|
+
} else if (kind === "node") {
|
|
1835
|
+
if (typeof record.at !== "number") {
|
|
1836
|
+
issues.push({
|
|
1837
|
+
path: `${path}.at`,
|
|
1838
|
+
message: "node anchors must contain a numeric at value.",
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
if (record.assoc !== -1 && record.assoc !== 1) {
|
|
1842
|
+
issues.push({
|
|
1843
|
+
path: `${path}.assoc`,
|
|
1844
|
+
message: "node anchor assoc must be -1 or 1.",
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1801
1847
|
} else if (kind === "detached") {
|
|
1802
1848
|
validateRange(record.lastKnownRange, `${path}.lastKnownRange`, issues);
|
|
1849
|
+
if (
|
|
1850
|
+
record.reason !== "deleted" &&
|
|
1851
|
+
record.reason !== "invalidatedByStructureChange" &&
|
|
1852
|
+
record.reason !== "importAmbiguity"
|
|
1853
|
+
) {
|
|
1854
|
+
issues.push({
|
|
1855
|
+
path: `${path}.reason`,
|
|
1856
|
+
message: "detached anchor reason must be deleted, invalidatedByStructureChange, or importAmbiguity.",
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
} else {
|
|
1860
|
+
issues.push({
|
|
1861
|
+
path: `${path}.kind`,
|
|
1862
|
+
message: "anchor kind must be range, node, or detached.",
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function validateBoundaryAssoc(
|
|
1868
|
+
value: unknown,
|
|
1869
|
+
path: string,
|
|
1870
|
+
issues: ModelValidationIssue[],
|
|
1871
|
+
): void {
|
|
1872
|
+
const record = asPlainObject(value, path, issues);
|
|
1873
|
+
if (!record) {
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
if (record.start !== -1 && record.start !== 1) {
|
|
1878
|
+
issues.push({
|
|
1879
|
+
path: `${path}.start`,
|
|
1880
|
+
message: "assoc.start must be -1 or 1.",
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
if (record.end !== -1 && record.end !== 1) {
|
|
1884
|
+
issues.push({
|
|
1885
|
+
path: `${path}.end`,
|
|
1886
|
+
message: "assoc.end must be -1 or 1.",
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
function validateDocumentReferences(
|
|
1892
|
+
record: Record<string, unknown>,
|
|
1893
|
+
issues: ModelValidationIssue[],
|
|
1894
|
+
): void {
|
|
1895
|
+
const paragraphStyleIds = new Set(
|
|
1896
|
+
Object.keys(asPlainObject(asPlainObject(record.styles, "$.styles", [])?.paragraphs, "$.styles.paragraphs", []) ?? {}),
|
|
1897
|
+
);
|
|
1898
|
+
const numberingInstanceIds = new Set(
|
|
1899
|
+
Object.keys(asPlainObject(asPlainObject(record.numbering, "$.numbering", [])?.instances, "$.numbering.instances", []) ?? {}),
|
|
1900
|
+
);
|
|
1901
|
+
const abstractNumberingIds = new Set(
|
|
1902
|
+
Object.keys(asPlainObject(asPlainObject(record.numbering, "$.numbering", [])?.abstractDefinitions, "$.numbering.abstractDefinitions", []) ?? {}),
|
|
1903
|
+
);
|
|
1904
|
+
const mediaIds = new Set(
|
|
1905
|
+
Object.keys(asPlainObject(asPlainObject(record.media, "$.media", [])?.items, "$.media.items", []) ?? {}),
|
|
1906
|
+
);
|
|
1907
|
+
const warningIds = new Set(
|
|
1908
|
+
(Array.isArray(asPlainObject(record.diagnostics, "$.diagnostics", [])?.warnings)
|
|
1909
|
+
? (asPlainObject(record.diagnostics, "$.diagnostics", [])?.warnings as Array<Record<string, unknown>>)
|
|
1910
|
+
: []
|
|
1911
|
+
).flatMap((warning) => typeof warning.warningId === "string" ? [warning.warningId] : []),
|
|
1912
|
+
);
|
|
1913
|
+
const fragmentIds = new Set(
|
|
1914
|
+
Object.keys(asPlainObject(asPlainObject(record.preservation, "$.preservation", [])?.opaqueFragments, "$.preservation.opaqueFragments", []) ?? {}),
|
|
1915
|
+
);
|
|
1916
|
+
const noteIds = collectNoteIds(record);
|
|
1917
|
+
|
|
1918
|
+
const numberingInstances = asPlainObject(
|
|
1919
|
+
asPlainObject(record.numbering, "$.numbering", [])?.instances,
|
|
1920
|
+
"$.numbering.instances",
|
|
1921
|
+
[],
|
|
1922
|
+
);
|
|
1923
|
+
if (numberingInstances) {
|
|
1924
|
+
for (const [instanceId, instance] of Object.entries(numberingInstances)) {
|
|
1925
|
+
const instanceRecord = asPlainObject(instance, `$.numbering.instances.${instanceId}`, []);
|
|
1926
|
+
if (
|
|
1927
|
+
instanceRecord &&
|
|
1928
|
+
typeof instanceRecord.abstractNumberingId === "string" &&
|
|
1929
|
+
!abstractNumberingIds.has(instanceRecord.abstractNumberingId)
|
|
1930
|
+
) {
|
|
1931
|
+
issues.push({
|
|
1932
|
+
path: `$.numbering.instances.${instanceId}.abstractNumberingId`,
|
|
1933
|
+
message: "abstractNumberingId must reference an existing abstract numbering definition.",
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
validateDocumentNodeReferences(record.content, "$.content", issues, {
|
|
1940
|
+
paragraphStyleIds,
|
|
1941
|
+
numberingInstanceIds,
|
|
1942
|
+
mediaIds,
|
|
1943
|
+
warningIds,
|
|
1944
|
+
fragmentIds,
|
|
1945
|
+
noteIds,
|
|
1946
|
+
});
|
|
1947
|
+
validateSubPartReferences(record.subParts, "$.subParts", issues, {
|
|
1948
|
+
paragraphStyleIds,
|
|
1949
|
+
numberingInstanceIds,
|
|
1950
|
+
mediaIds,
|
|
1951
|
+
warningIds,
|
|
1952
|
+
fragmentIds,
|
|
1953
|
+
noteIds,
|
|
1954
|
+
});
|
|
1955
|
+
validateReviewReferences(record.review, "$.review", issues, warningIds);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
function validateDocumentNodeReferences(
|
|
1959
|
+
value: unknown,
|
|
1960
|
+
path: string,
|
|
1961
|
+
issues: ModelValidationIssue[],
|
|
1962
|
+
references: {
|
|
1963
|
+
paragraphStyleIds: ReadonlySet<string>;
|
|
1964
|
+
numberingInstanceIds: ReadonlySet<string>;
|
|
1965
|
+
mediaIds: ReadonlySet<string>;
|
|
1966
|
+
warningIds: ReadonlySet<string>;
|
|
1967
|
+
fragmentIds: ReadonlySet<string>;
|
|
1968
|
+
noteIds: ReadonlySet<string>;
|
|
1969
|
+
},
|
|
1970
|
+
): void {
|
|
1971
|
+
const record = asPlainObject(value, path, issues);
|
|
1972
|
+
if (!record) {
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
const type = typeof record.type === "string" ? record.type : undefined;
|
|
1977
|
+
if (type === "paragraph") {
|
|
1978
|
+
if (
|
|
1979
|
+
typeof record.styleId === "string" &&
|
|
1980
|
+
!references.paragraphStyleIds.has(record.styleId)
|
|
1981
|
+
) {
|
|
1982
|
+
issues.push({
|
|
1983
|
+
path: `${path}.styleId`,
|
|
1984
|
+
message: "styleId must reference an existing paragraph style definition.",
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
const numbering = asPlainObject(record.numbering, `${path}.numbering`, []);
|
|
1988
|
+
if (
|
|
1989
|
+
numbering &&
|
|
1990
|
+
typeof numbering.numberingInstanceId === "string" &&
|
|
1991
|
+
!references.numberingInstanceIds.has(numbering.numberingInstanceId)
|
|
1992
|
+
) {
|
|
1993
|
+
issues.push({
|
|
1994
|
+
path: `${path}.numbering.numberingInstanceId`,
|
|
1995
|
+
message: "numberingInstanceId must reference an existing numbering instance.",
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
} else if (type === "image") {
|
|
1999
|
+
if (typeof record.mediaId === "string" && !references.mediaIds.has(record.mediaId)) {
|
|
2000
|
+
issues.push({
|
|
2001
|
+
path: `${path}.mediaId`,
|
|
2002
|
+
message: "mediaId must reference an existing media catalog item.",
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
} else if (type === "opaque_inline" || type === "opaque_block") {
|
|
2006
|
+
if (typeof record.fragmentId === "string" && !references.fragmentIds.has(record.fragmentId)) {
|
|
2007
|
+
issues.push({
|
|
2008
|
+
path: `${path}.fragmentId`,
|
|
2009
|
+
message: "fragmentId must reference an existing opaque fragment.",
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
if (typeof record.warningId === "string" && !references.warningIds.has(record.warningId)) {
|
|
2013
|
+
issues.push({
|
|
2014
|
+
path: `${path}.warningId`,
|
|
2015
|
+
message: "warningId must reference an existing diagnostic warning.",
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
} else if (type === "footnote_ref") {
|
|
2019
|
+
if (typeof record.noteId === "string" && !references.noteIds.has(`${record.noteKind}:${record.noteId}`)) {
|
|
2020
|
+
issues.push({
|
|
2021
|
+
path: `${path}.noteId`,
|
|
2022
|
+
message: "noteId must reference an existing footnote or endnote definition.",
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
if (Array.isArray(record.children)) {
|
|
2028
|
+
record.children.forEach((child, index) =>
|
|
2029
|
+
validateDocumentNodeReferences(child, `${path}.children[${index}]`, issues, references),
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
if (Array.isArray(record.rows)) {
|
|
2033
|
+
record.rows.forEach((row, index) =>
|
|
2034
|
+
validateDocumentNodeReferences(row, `${path}.rows[${index}]`, issues, references),
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
if (Array.isArray(record.cells)) {
|
|
2038
|
+
record.cells.forEach((cell, index) =>
|
|
2039
|
+
validateDocumentNodeReferences(cell, `${path}.cells[${index}]`, issues, references),
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
function validateReviewReferences(
|
|
2045
|
+
value: unknown,
|
|
2046
|
+
path: string,
|
|
2047
|
+
issues: ModelValidationIssue[],
|
|
2048
|
+
warningIds: ReadonlySet<string>,
|
|
2049
|
+
): void {
|
|
2050
|
+
const record = asPlainObject(value, path, issues);
|
|
2051
|
+
if (!record) {
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
const comments = asPlainObject(record.comments, `${path}.comments`, []);
|
|
2056
|
+
if (comments) {
|
|
2057
|
+
for (const [commentId, thread] of Object.entries(comments)) {
|
|
2058
|
+
const threadRecord = asPlainObject(thread, `${path}.comments.${commentId}`, []);
|
|
2059
|
+
if (!threadRecord || !Array.isArray(threadRecord.warningIds)) {
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
threadRecord.warningIds.forEach((warningId, index) => {
|
|
2063
|
+
if (typeof warningId === "string" && !warningIds.has(warningId)) {
|
|
2064
|
+
issues.push({
|
|
2065
|
+
path: `${path}.comments.${commentId}.warningIds[${index}]`,
|
|
2066
|
+
message: "warningIds must reference existing diagnostic warnings.",
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const revisions = asPlainObject(record.revisions, `${path}.revisions`, []);
|
|
2074
|
+
if (revisions) {
|
|
2075
|
+
for (const [revisionId, revision] of Object.entries(revisions)) {
|
|
2076
|
+
const revisionRecord = asPlainObject(revision, `${path}.revisions.${revisionId}`, []);
|
|
2077
|
+
if (!revisionRecord || !Array.isArray(revisionRecord.warningIds)) {
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
revisionRecord.warningIds.forEach((warningId, index) => {
|
|
2081
|
+
if (typeof warningId === "string" && !warningIds.has(warningId)) {
|
|
2082
|
+
issues.push({
|
|
2083
|
+
path: `${path}.revisions.${revisionId}.warningIds[${index}]`,
|
|
2084
|
+
message: "warningIds must reference existing diagnostic warnings.",
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function validateSubPartReferences(
|
|
2093
|
+
value: unknown,
|
|
2094
|
+
path: string,
|
|
2095
|
+
issues: ModelValidationIssue[],
|
|
2096
|
+
references: {
|
|
2097
|
+
paragraphStyleIds: ReadonlySet<string>;
|
|
2098
|
+
numberingInstanceIds: ReadonlySet<string>;
|
|
2099
|
+
mediaIds: ReadonlySet<string>;
|
|
2100
|
+
warningIds: ReadonlySet<string>;
|
|
2101
|
+
fragmentIds: ReadonlySet<string>;
|
|
2102
|
+
noteIds: ReadonlySet<string>;
|
|
2103
|
+
},
|
|
2104
|
+
): void {
|
|
2105
|
+
if (value === undefined) {
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
const record = asPlainObject(value, path, issues);
|
|
2109
|
+
if (!record) {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
const headers = Array.isArray(record.headers) ? record.headers : [];
|
|
2114
|
+
headers.forEach((header, index) => {
|
|
2115
|
+
const headerRecord = asPlainObject(header, `${path}.headers[${index}]`, []);
|
|
2116
|
+
if (!headerRecord || !Array.isArray(headerRecord.blocks)) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
headerRecord.blocks.forEach((block, blockIndex) =>
|
|
2120
|
+
validateDocumentNodeReferences(block, `${path}.headers[${index}].blocks[${blockIndex}]`, issues, references),
|
|
2121
|
+
);
|
|
2122
|
+
});
|
|
2123
|
+
|
|
2124
|
+
const footers = Array.isArray(record.footers) ? record.footers : [];
|
|
2125
|
+
footers.forEach((footer, index) => {
|
|
2126
|
+
const footerRecord = asPlainObject(footer, `${path}.footers[${index}]`, []);
|
|
2127
|
+
if (!footerRecord || !Array.isArray(footerRecord.blocks)) {
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
footerRecord.blocks.forEach((block, blockIndex) =>
|
|
2131
|
+
validateDocumentNodeReferences(block, `${path}.footers[${index}].blocks[${blockIndex}]`, issues, references),
|
|
2132
|
+
);
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
if (record.footnoteCollection !== undefined) {
|
|
2136
|
+
const footnoteCollection = asPlainObject(record.footnoteCollection, `${path}.footnoteCollection`, []);
|
|
2137
|
+
const footnotes = asPlainObject(footnoteCollection?.footnotes, `${path}.footnoteCollection.footnotes`, []);
|
|
2138
|
+
if (footnotes) {
|
|
2139
|
+
for (const [noteId, note] of Object.entries(footnotes)) {
|
|
2140
|
+
const noteRecord = asPlainObject(note, `${path}.footnoteCollection.footnotes.${noteId}`, []);
|
|
2141
|
+
if (!noteRecord || !Array.isArray(noteRecord.blocks)) {
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
2144
|
+
noteRecord.blocks.forEach((block, blockIndex) =>
|
|
2145
|
+
validateDocumentNodeReferences(
|
|
2146
|
+
block,
|
|
2147
|
+
`${path}.footnoteCollection.footnotes.${noteId}.blocks[${blockIndex}]`,
|
|
2148
|
+
issues,
|
|
2149
|
+
references,
|
|
2150
|
+
),
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
const endnotes = asPlainObject(footnoteCollection?.endnotes, `${path}.footnoteCollection.endnotes`, []);
|
|
2156
|
+
if (endnotes) {
|
|
2157
|
+
for (const [noteId, note] of Object.entries(endnotes)) {
|
|
2158
|
+
const noteRecord = asPlainObject(note, `${path}.footnoteCollection.endnotes.${noteId}`, []);
|
|
2159
|
+
if (!noteRecord || !Array.isArray(noteRecord.blocks)) {
|
|
2160
|
+
continue;
|
|
2161
|
+
}
|
|
2162
|
+
noteRecord.blocks.forEach((block, blockIndex) =>
|
|
2163
|
+
validateDocumentNodeReferences(
|
|
2164
|
+
block,
|
|
2165
|
+
`${path}.footnoteCollection.endnotes.${noteId}.blocks[${blockIndex}]`,
|
|
2166
|
+
issues,
|
|
2167
|
+
references,
|
|
2168
|
+
),
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
1803
2172
|
}
|
|
1804
2173
|
}
|
|
1805
2174
|
|
|
2175
|
+
function collectNoteIds(record: Record<string, unknown>): Set<string> {
|
|
2176
|
+
const noteIds = new Set<string>();
|
|
2177
|
+
const subParts = asPlainObject(record.subParts, "$.subParts", []);
|
|
2178
|
+
const footnoteCollection = asPlainObject(subParts?.footnoteCollection, "$.subParts.footnoteCollection", []);
|
|
2179
|
+
const footnotes = asPlainObject(footnoteCollection?.footnotes, "$.subParts.footnoteCollection.footnotes", []);
|
|
2180
|
+
if (footnotes) {
|
|
2181
|
+
Object.keys(footnotes).forEach((noteId) => noteIds.add(`footnote:${noteId}`));
|
|
2182
|
+
}
|
|
2183
|
+
const endnotes = asPlainObject(footnoteCollection?.endnotes, "$.subParts.footnoteCollection.endnotes", []);
|
|
2184
|
+
if (endnotes) {
|
|
2185
|
+
Object.keys(endnotes).forEach((noteId) => noteIds.add(`endnote:${noteId}`));
|
|
2186
|
+
}
|
|
2187
|
+
return noteIds;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
1806
2190
|
function validateRange(
|
|
1807
2191
|
value: unknown,
|
|
1808
2192
|
path: string,
|
|
@@ -1857,6 +2241,9 @@ function validateSubPartsCatalog(
|
|
|
1857
2241
|
path: string,
|
|
1858
2242
|
issues: ModelValidationIssue[],
|
|
1859
2243
|
): void {
|
|
2244
|
+
if (value === undefined) {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
1860
2247
|
const record = asPlainObject(value, path, issues);
|
|
1861
2248
|
if (!record) {
|
|
1862
2249
|
return;
|
|
@@ -1872,6 +2259,13 @@ function validateSubPartsCatalog(
|
|
|
1872
2259
|
expectString(headerRecord.variant, `${path}.headers[${index}].variant`, issues);
|
|
1873
2260
|
expectString(headerRecord.partPath, `${path}.headers[${index}].partPath`, issues);
|
|
1874
2261
|
expectString(headerRecord.relationshipId, `${path}.headers[${index}].relationshipId`, issues);
|
|
2262
|
+
if (!Array.isArray(headerRecord.blocks)) {
|
|
2263
|
+
issues.push({ path: `${path}.headers[${index}].blocks`, message: "blocks must be an array." });
|
|
2264
|
+
} else {
|
|
2265
|
+
headerRecord.blocks.forEach((block, blockIndex) =>
|
|
2266
|
+
validateDocumentNode(block, `${path}.headers[${index}].blocks[${blockIndex}]`, issues),
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
1875
2269
|
}
|
|
1876
2270
|
});
|
|
1877
2271
|
}
|
|
@@ -1887,10 +2281,64 @@ function validateSubPartsCatalog(
|
|
|
1887
2281
|
expectString(footerRecord.variant, `${path}.footers[${index}].variant`, issues);
|
|
1888
2282
|
expectString(footerRecord.partPath, `${path}.footers[${index}].partPath`, issues);
|
|
1889
2283
|
expectString(footerRecord.relationshipId, `${path}.footers[${index}].relationshipId`, issues);
|
|
2284
|
+
if (!Array.isArray(footerRecord.blocks)) {
|
|
2285
|
+
issues.push({ path: `${path}.footers[${index}].blocks`, message: "blocks must be an array." });
|
|
2286
|
+
} else {
|
|
2287
|
+
footerRecord.blocks.forEach((block, blockIndex) =>
|
|
2288
|
+
validateDocumentNode(block, `${path}.footers[${index}].blocks[${blockIndex}]`, issues),
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
1890
2291
|
}
|
|
1891
2292
|
});
|
|
1892
2293
|
}
|
|
1893
2294
|
}
|
|
2295
|
+
|
|
2296
|
+
if (record.footnoteCollection !== undefined) {
|
|
2297
|
+
const footnoteCollection = asPlainObject(record.footnoteCollection, `${path}.footnoteCollection`, issues);
|
|
2298
|
+
if (footnoteCollection) {
|
|
2299
|
+
validateSubPartNoteMap(footnoteCollection.footnotes, "footnote", `${path}.footnoteCollection.footnotes`, issues);
|
|
2300
|
+
validateSubPartNoteMap(footnoteCollection.endnotes, "endnote", `${path}.footnoteCollection.endnotes`, issues);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
function validateSubPartNoteMap(
|
|
2306
|
+
value: unknown,
|
|
2307
|
+
expectedKind: "footnote" | "endnote",
|
|
2308
|
+
path: string,
|
|
2309
|
+
issues: ModelValidationIssue[],
|
|
2310
|
+
): void {
|
|
2311
|
+
const record = asPlainObject(value, path, issues);
|
|
2312
|
+
if (!record) {
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
for (const [noteId, note] of Object.entries(record)) {
|
|
2317
|
+
const noteRecord = asPlainObject(note, `${path}.${noteId}`, issues);
|
|
2318
|
+
if (!noteRecord) {
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
expectString(noteRecord.noteId, `${path}.${noteId}.noteId`, issues);
|
|
2322
|
+
if (noteRecord.noteId !== noteId) {
|
|
2323
|
+
issues.push({
|
|
2324
|
+
path: `${path}.${noteId}.noteId`,
|
|
2325
|
+
message: "noteId must match the map key.",
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
if (noteRecord.kind !== expectedKind) {
|
|
2329
|
+
issues.push({
|
|
2330
|
+
path: `${path}.${noteId}.kind`,
|
|
2331
|
+
message: `kind must be ${expectedKind}.`,
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
if (!Array.isArray(noteRecord.blocks)) {
|
|
2335
|
+
issues.push({ path: `${path}.${noteId}.blocks`, message: "blocks must be an array." });
|
|
2336
|
+
} else {
|
|
2337
|
+
noteRecord.blocks.forEach((block, blockIndex) =>
|
|
2338
|
+
validateDocumentNode(block, `${path}.${noteId}.blocks[${blockIndex}]`, issues),
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
1894
2342
|
}
|
|
1895
2343
|
|
|
1896
2344
|
function expectDomainString(
|
package/src/model/cds-1.0.0.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export const CDS_SCHEMA_VERSION = "cds/1.0.0" as const;
|
|
2
|
-
export const
|
|
2
|
+
export const LEGACY_PERSISTED_EDITOR_SNAPSHOT_VERSION =
|
|
3
3
|
"persisted-editor-snapshot/1" as const;
|
|
4
|
+
export const PERSISTED_EDITOR_SNAPSHOT_VERSION =
|
|
5
|
+
"persisted-editor-snapshot/2" as const;
|
|
4
6
|
export const COMPATIBILITY_REPORT_VERSION = "compatibility-report/1" as const;
|
|
5
7
|
|
|
6
8
|
export type CDS_SchemaVersion = typeof CDS_SCHEMA_VERSION;
|
|
7
9
|
export type PersistedEditorSnapshotVersion =
|
|
8
|
-
typeof
|
|
10
|
+
| typeof LEGACY_PERSISTED_EDITOR_SNAPSHOT_VERSION
|
|
11
|
+
| typeof PERSISTED_EDITOR_SNAPSHOT_VERSION;
|
|
9
12
|
export type CompatibilityReportVersion = typeof COMPATIBILITY_REPORT_VERSION;
|
|
10
13
|
|
|
11
14
|
export type Id = string;
|