@beyondwork/docx-react-component 1.0.11 → 1.0.13
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 +35 -21
- package/src/api/public-types.ts +103 -1
- package/src/core/commands/formatting-commands.ts +742 -0
- package/src/core/commands/image-commands.ts +84 -2
- package/src/core/commands/structural-helpers.ts +309 -0
- package/src/core/commands/table-structure-commands.ts +721 -0
- package/src/core/commands/text-commands.ts +166 -1
- package/src/core/state/editor-state.ts +318 -9
- package/src/formats/xlsx/io/parse-sheet.ts +177 -7
- package/src/formats/xlsx/io/parse-styles.ts +2 -0
- package/src/formats/xlsx/io/xlsx-session.ts +18 -12
- package/src/formats/xlsx/model/sheet.ts +81 -1
- package/src/formats/xlsx/model/workbook.ts +10 -6
- 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 +200 -17
- package/src/runtime/table-commands.ts +79 -0
- package/src/runtime/table-schema.ts +9 -0
- package/src/ui/WordReviewEditor.tsx +708 -16
- package/src/ui-tailwind/editor-surface/pm-schema.ts +121 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +73 -7
- package/src/ui-tailwind/editor-surface/search-plugin.ts +76 -16
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +162 -14
- package/src/validation/compatibility-engine.ts +208 -0
package/src/io/docx-session.ts
CHANGED
|
@@ -26,6 +26,9 @@ import { readOpcPackage, type OpcPackage } from "./opc/package-reader.ts";
|
|
|
26
26
|
import { parseMainDocumentXml } from "./ooxml/parse-main-document.ts";
|
|
27
27
|
import { normalizeParsedTextDocument } from "./normalize/normalize-text.ts";
|
|
28
28
|
import {
|
|
29
|
+
CONTENT_TYPES_PATH,
|
|
30
|
+
PACKAGE_RELATIONSHIPS_PATH,
|
|
31
|
+
getRelationshipsPartPath,
|
|
29
32
|
normalizePartPath,
|
|
30
33
|
resolveRelationshipTarget,
|
|
31
34
|
type OpcRelationship,
|
|
@@ -58,6 +61,7 @@ import {
|
|
|
58
61
|
type ImportDiagnosticsResult,
|
|
59
62
|
} from "../validation/import-diagnostics.ts";
|
|
60
63
|
import type {
|
|
64
|
+
BlockNode,
|
|
61
65
|
FootnoteCollection,
|
|
62
66
|
HeaderDocument,
|
|
63
67
|
FooterDocument,
|
|
@@ -97,6 +101,7 @@ import {
|
|
|
97
101
|
WORD_FOOTNOTES_CONTENT_TYPE,
|
|
98
102
|
WORD_ENDNOTES_CONTENT_TYPE,
|
|
99
103
|
} from "./export/serialize-footnotes.ts";
|
|
104
|
+
import { createPersistedSourcePackage } from "./source-package-provenance.ts";
|
|
100
105
|
|
|
101
106
|
const MAIN_DOCUMENT_PATH = "/word/document.xml";
|
|
102
107
|
const NUMBERING_PART_PATH = "/word/numbering.xml";
|
|
@@ -104,8 +109,14 @@ const COMMENTS_PART_PATH = "/word/comments.xml";
|
|
|
104
109
|
const COMMENTS_EXTENDED_PART_PATH = "/word/commentsExtended.xml";
|
|
105
110
|
const COMMENTS_IDS_PART_PATH = "/word/commentsIds.xml";
|
|
106
111
|
const PEOPLE_PART_PATH = "/word/people.xml";
|
|
112
|
+
const APP_PROPERTIES_PART_PATH = "/docProps/app.xml";
|
|
113
|
+
const CORE_PROPERTIES_PART_PATH = "/docProps/core.xml";
|
|
107
114
|
const MAIN_DOCUMENT_CONTENT_TYPE =
|
|
108
115
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
|
|
116
|
+
const APP_PROPERTIES_CONTENT_TYPE =
|
|
117
|
+
"application/vnd.openxmlformats-officedocument.extended-properties+xml";
|
|
118
|
+
const CORE_PROPERTIES_CONTENT_TYPE =
|
|
119
|
+
"application/vnd.openxmlformats-package.core-properties+xml";
|
|
109
120
|
const NUMBERING_RELATIONSHIP_TYPE =
|
|
110
121
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
|
|
111
122
|
const COMMENTS_CONTENT_TYPE =
|
|
@@ -124,6 +135,12 @@ const COMMENTS_IDS_RELATIONSHIP_TYPE =
|
|
|
124
135
|
"http://schemas.microsoft.com/office/2016/09/relationships/commentsIds";
|
|
125
136
|
const PEOPLE_RELATIONSHIP_TYPE =
|
|
126
137
|
"http://schemas.microsoft.com/office/2011/relationships/people";
|
|
138
|
+
const APP_PROPERTIES_RELATIONSHIP_TYPE =
|
|
139
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
|
|
140
|
+
const CORE_PROPERTIES_RELATIONSHIP_TYPE =
|
|
141
|
+
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
|
|
142
|
+
const OFFICE_DOCUMENT_RELATIONSHIP_TYPE =
|
|
143
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
127
144
|
const HEADER_RELATIONSHIP_TYPE =
|
|
128
145
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
|
|
129
146
|
const FOOTER_RELATIONSHIP_TYPE =
|
|
@@ -155,6 +172,7 @@ export interface LoadedDocxEditorSession {
|
|
|
155
172
|
interface ImportedDocxState {
|
|
156
173
|
sourceBytes: Uint8Array;
|
|
157
174
|
sourcePackage: OpcPackage;
|
|
175
|
+
sourceDocumentPartPath: string;
|
|
158
176
|
sourceDocumentRelationships: readonly OpcRelationship[];
|
|
159
177
|
sourceDocumentAttributes: Record<string, string>;
|
|
160
178
|
sourceNumberingPartPath?: string;
|
|
@@ -216,7 +234,11 @@ export function loadDocxEditorSession(
|
|
|
216
234
|
);
|
|
217
235
|
}
|
|
218
236
|
|
|
219
|
-
const
|
|
237
|
+
const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
|
|
238
|
+
const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
|
|
239
|
+
sourcePackage,
|
|
240
|
+
mainDocumentPath,
|
|
241
|
+
);
|
|
220
242
|
if (brokenRelationshipIssues.length > 0) {
|
|
221
243
|
return createDiagnosticsSession(
|
|
222
244
|
options,
|
|
@@ -233,8 +255,7 @@ export function loadDocxEditorSession(
|
|
|
233
255
|
);
|
|
234
256
|
}
|
|
235
257
|
|
|
236
|
-
|
|
237
|
-
if (!documentPart) {
|
|
258
|
+
if (!mainDocumentPath) {
|
|
238
259
|
return createDiagnosticsSession(
|
|
239
260
|
options,
|
|
240
261
|
createPackageImportDiagnostics({
|
|
@@ -243,11 +264,30 @@ export function loadDocxEditorSession(
|
|
|
243
264
|
);
|
|
244
265
|
}
|
|
245
266
|
|
|
267
|
+
const documentPart = sourcePackage.parts.get(mainDocumentPath);
|
|
268
|
+
if (!documentPart) {
|
|
269
|
+
return createDiagnosticsSession(
|
|
270
|
+
options,
|
|
271
|
+
createPackageImportDiagnostics({
|
|
272
|
+
issue: createMissingPartIssue(mainDocumentPath),
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
if (documentPart.contentType !== MAIN_DOCUMENT_CONTENT_TYPE) {
|
|
277
|
+
return createDiagnosticsSession(
|
|
278
|
+
options,
|
|
279
|
+
createValidationImportDiagnostics({
|
|
280
|
+
message: `DOCX main document part ${mainDocumentPath} must use content type ${MAIN_DOCUMENT_CONTENT_TYPE}.`,
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
246
285
|
try {
|
|
247
286
|
const sourceDocumentXml = decodeUtf8(documentPart.bytes);
|
|
248
287
|
const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
|
|
249
288
|
const numberingPartPath = resolveDocumentRelatedPartPath(
|
|
250
289
|
sourcePackage,
|
|
290
|
+
mainDocumentPath,
|
|
251
291
|
documentPart.relationships,
|
|
252
292
|
NUMBERING_RELATIONSHIP_TYPE,
|
|
253
293
|
NUMBERING_PART_PATH,
|
|
@@ -262,27 +302,34 @@ export function loadDocxEditorSession(
|
|
|
262
302
|
sourceDocumentXml,
|
|
263
303
|
documentPart.relationships,
|
|
264
304
|
mediaParts,
|
|
265
|
-
|
|
305
|
+
mainDocumentPath,
|
|
266
306
|
);
|
|
267
307
|
const normalizedDocument = normalizeParsedTextDocument(
|
|
268
308
|
parsedDocument,
|
|
269
|
-
|
|
309
|
+
mainDocumentPath,
|
|
310
|
+
);
|
|
311
|
+
const commentsPartPath = resolveCommentsPartPath(
|
|
312
|
+
sourcePackage,
|
|
313
|
+
mainDocumentPath,
|
|
314
|
+
documentPart.relationships,
|
|
270
315
|
);
|
|
271
|
-
const commentsPartPath = resolveCommentsPartPath(sourcePackage, documentPart.relationships);
|
|
272
316
|
const commentsExtendedPartPath = resolveDocumentRelatedPartPath(
|
|
273
317
|
sourcePackage,
|
|
318
|
+
mainDocumentPath,
|
|
274
319
|
documentPart.relationships,
|
|
275
320
|
COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
|
|
276
321
|
COMMENTS_EXTENDED_PART_PATH,
|
|
277
322
|
);
|
|
278
323
|
const commentsIdsPartPath = resolveDocumentRelatedPartPath(
|
|
279
324
|
sourcePackage,
|
|
325
|
+
mainDocumentPath,
|
|
280
326
|
documentPart.relationships,
|
|
281
327
|
COMMENTS_IDS_RELATIONSHIP_TYPE,
|
|
282
328
|
COMMENTS_IDS_PART_PATH,
|
|
283
329
|
);
|
|
284
330
|
const peoplePartPath = resolveDocumentRelatedPartPath(
|
|
285
331
|
sourcePackage,
|
|
332
|
+
mainDocumentPath,
|
|
286
333
|
documentPart.relationships,
|
|
287
334
|
PEOPLE_RELATIONSHIP_TYPE,
|
|
288
335
|
PEOPLE_PART_PATH,
|
|
@@ -344,7 +391,7 @@ export function loadDocxEditorSession(
|
|
|
344
391
|
continue;
|
|
345
392
|
}
|
|
346
393
|
|
|
347
|
-
const partPath = resolveRelationshipTarget(
|
|
394
|
+
const partPath = resolveRelationshipTarget(mainDocumentPath, relationship);
|
|
348
395
|
const partBytes = sourcePackage.parts.get(partPath)?.bytes;
|
|
349
396
|
if (!partBytes) {
|
|
350
397
|
continue;
|
|
@@ -374,6 +421,7 @@ export function loadDocxEditorSession(
|
|
|
374
421
|
|
|
375
422
|
const footnotesPartPath = resolveDocumentRelatedPartPath(
|
|
376
423
|
sourcePackage,
|
|
424
|
+
mainDocumentPath,
|
|
377
425
|
documentPart.relationships,
|
|
378
426
|
FOOTNOTES_RELATIONSHIP_TYPE,
|
|
379
427
|
FOOTNOTES_PART_PATH,
|
|
@@ -383,6 +431,7 @@ export function loadDocxEditorSession(
|
|
|
383
431
|
)?.id;
|
|
384
432
|
const endnotesPartPath = resolveDocumentRelatedPartPath(
|
|
385
433
|
sourcePackage,
|
|
434
|
+
mainDocumentPath,
|
|
386
435
|
documentPart.relationships,
|
|
387
436
|
ENDNOTES_RELATIONSHIP_TYPE,
|
|
388
437
|
ENDNOTES_PART_PATH,
|
|
@@ -409,7 +458,7 @@ export function loadDocxEditorSession(
|
|
|
409
458
|
r.targetMode === "internal",
|
|
410
459
|
);
|
|
411
460
|
const themePartPath = themeRelationship
|
|
412
|
-
? resolveRelationshipTarget(
|
|
461
|
+
? resolveRelationshipTarget(mainDocumentPath, themeRelationship)
|
|
413
462
|
: undefined;
|
|
414
463
|
const parsedTheme =
|
|
415
464
|
themePartPath && sourcePackage.parts.has(themePartPath)
|
|
@@ -444,6 +493,7 @@ export function loadDocxEditorSession(
|
|
|
444
493
|
packageParts: {
|
|
445
494
|
...normalizedDocument.preservation.packageParts,
|
|
446
495
|
...collectPreservedPackageParts(sourcePackage, [
|
|
496
|
+
mainDocumentPath,
|
|
447
497
|
numberingPartPath,
|
|
448
498
|
commentsPartPath,
|
|
449
499
|
commentsExtendedPartPath,
|
|
@@ -454,6 +504,7 @@ export function loadDocxEditorSession(
|
|
|
454
504
|
},
|
|
455
505
|
diagnostics: {
|
|
456
506
|
warnings: [
|
|
507
|
+
...createBrokenRelationshipWarnings(sourcePackage, mainDocumentPath),
|
|
457
508
|
...normalizedDocument.diagnostics.warnings,
|
|
458
509
|
...normalizedRevisions.diagnostics.map((diagnostic, index) => ({
|
|
459
510
|
diagnosticId: `diagnostic:revision-import-${index + 1}`,
|
|
@@ -485,10 +536,12 @@ export function loadDocxEditorSession(
|
|
|
485
536
|
timestamp,
|
|
486
537
|
document,
|
|
487
538
|
compatibility: toPublicCompatibilityReport(compatibility),
|
|
539
|
+
sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
|
|
488
540
|
});
|
|
489
541
|
const importedState: ImportedDocxState = {
|
|
490
542
|
sourceBytes: new Uint8Array(sourceBytes),
|
|
491
543
|
sourcePackage,
|
|
544
|
+
sourceDocumentPartPath: mainDocumentPath,
|
|
492
545
|
sourceDocumentRelationships: documentPart.relationships,
|
|
493
546
|
sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
|
|
494
547
|
sourceNumberingPartPath: numberingPartPath,
|
|
@@ -736,7 +789,9 @@ function exportDocxEditorSession(
|
|
|
736
789
|
}
|
|
737
790
|
|
|
738
791
|
const exportSession = createExportSession(state.sourcePackage, [
|
|
739
|
-
|
|
792
|
+
state.sourceDocumentPartPath,
|
|
793
|
+
APP_PROPERTIES_PART_PATH,
|
|
794
|
+
CORE_PROPERTIES_PART_PATH,
|
|
740
795
|
numberingPartPath,
|
|
741
796
|
commentsPartPath,
|
|
742
797
|
commentsExtendedPartPath,
|
|
@@ -746,7 +801,7 @@ function exportDocxEditorSession(
|
|
|
746
801
|
]);
|
|
747
802
|
|
|
748
803
|
exportSession.replaceOwnedPart({
|
|
749
|
-
path:
|
|
804
|
+
path: state.sourceDocumentPartPath,
|
|
750
805
|
bytes: new TextEncoder().encode(annotatedDocument.documentXml),
|
|
751
806
|
contentType: MAIN_DOCUMENT_CONTENT_TYPE,
|
|
752
807
|
relationships: nextRelationships,
|
|
@@ -863,6 +918,8 @@ function exportDocxEditorSession(
|
|
|
863
918
|
}
|
|
864
919
|
}
|
|
865
920
|
|
|
921
|
+
ensureHostMetadataParts(exportSession, state.sourcePackage, currentDocument);
|
|
922
|
+
|
|
866
923
|
return {
|
|
867
924
|
bytes: exportSession.serialize(),
|
|
868
925
|
mimeType: DOCX_MIME_TYPE,
|
|
@@ -881,6 +938,19 @@ function createImportedCanonicalDocument(input: {
|
|
|
881
938
|
diagnostics: CanonicalDocumentEnvelope["diagnostics"];
|
|
882
939
|
review: CanonicalDocumentEnvelope["review"];
|
|
883
940
|
}): CanonicalDocumentEnvelope {
|
|
941
|
+
const paragraphStyles = Object.fromEntries(
|
|
942
|
+
[...collectReferencedParagraphStyleIds(input.content, input.subParts)]
|
|
943
|
+
.sort((left, right) => left.localeCompare(right))
|
|
944
|
+
.map((styleId) => [
|
|
945
|
+
styleId,
|
|
946
|
+
{
|
|
947
|
+
styleId,
|
|
948
|
+
displayName: styleId,
|
|
949
|
+
kind: "paragraph" as const,
|
|
950
|
+
isDefault: styleId === "Normal",
|
|
951
|
+
},
|
|
952
|
+
]),
|
|
953
|
+
);
|
|
884
954
|
return {
|
|
885
955
|
schemaVersion: "cds/1.0.0",
|
|
886
956
|
docId: createCanonicalDocumentId(input.documentId),
|
|
@@ -890,7 +960,7 @@ function createImportedCanonicalDocument(input: {
|
|
|
890
960
|
customProperties: {},
|
|
891
961
|
},
|
|
892
962
|
styles: {
|
|
893
|
-
paragraphs:
|
|
963
|
+
paragraphs: paragraphStyles,
|
|
894
964
|
characters: {},
|
|
895
965
|
tables: {},
|
|
896
966
|
},
|
|
@@ -904,15 +974,60 @@ function createImportedCanonicalDocument(input: {
|
|
|
904
974
|
};
|
|
905
975
|
}
|
|
906
976
|
|
|
977
|
+
function collectReferencedParagraphStyleIds(
|
|
978
|
+
content: CanonicalDocumentEnvelope["content"],
|
|
979
|
+
subParts?: SubPartsCatalog,
|
|
980
|
+
): Set<string> {
|
|
981
|
+
const styleIds = new Set<string>();
|
|
982
|
+
|
|
983
|
+
const visitBlocks = (blocks: ReadonlyArray<BlockNode>) => {
|
|
984
|
+
for (const block of blocks) {
|
|
985
|
+
if ("styleId" in block && typeof block.styleId === "string" && block.styleId.length > 0) {
|
|
986
|
+
styleIds.add(block.styleId);
|
|
987
|
+
}
|
|
988
|
+
if (block.type === "table") {
|
|
989
|
+
for (const row of block.rows) {
|
|
990
|
+
for (const cell of row.cells) {
|
|
991
|
+
visitBlocks(cell.children);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
} else if (block.type === "sdt" || block.type === "custom_xml") {
|
|
995
|
+
visitBlocks(block.children);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
visitBlocks(content.children);
|
|
1001
|
+
if (subParts) {
|
|
1002
|
+
for (const header of subParts.headers) {
|
|
1003
|
+
visitBlocks(header.blocks);
|
|
1004
|
+
}
|
|
1005
|
+
for (const footer of subParts.footers) {
|
|
1006
|
+
visitBlocks(footer.blocks);
|
|
1007
|
+
}
|
|
1008
|
+
if (subParts.footnoteCollection) {
|
|
1009
|
+
for (const note of Object.values(subParts.footnoteCollection.footnotes)) {
|
|
1010
|
+
visitBlocks(note.blocks);
|
|
1011
|
+
}
|
|
1012
|
+
for (const note of Object.values(subParts.footnoteCollection.endnotes)) {
|
|
1013
|
+
visitBlocks(note.blocks);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
return styleIds;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
907
1021
|
function createImportedSnapshot(input: {
|
|
908
1022
|
documentId: string;
|
|
909
1023
|
editorBuild: string;
|
|
910
1024
|
timestamp: string;
|
|
911
1025
|
document: CanonicalDocumentEnvelope;
|
|
912
1026
|
compatibility: PersistedEditorSnapshot["compatibility"];
|
|
1027
|
+
sourcePackage?: PersistedEditorSnapshot["sourcePackage"];
|
|
913
1028
|
}): PersistedEditorSnapshot {
|
|
914
1029
|
return {
|
|
915
|
-
snapshotVersion: "persisted-editor-snapshot/
|
|
1030
|
+
snapshotVersion: "persisted-editor-snapshot/2",
|
|
916
1031
|
schemaVersion: input.document.schemaVersion,
|
|
917
1032
|
documentId: input.documentId,
|
|
918
1033
|
docId: input.document.docId,
|
|
@@ -923,6 +1038,7 @@ function createImportedSnapshot(input: {
|
|
|
923
1038
|
canonicalDocument: input.document,
|
|
924
1039
|
compatibility: input.compatibility,
|
|
925
1040
|
warningLog: input.compatibility.warnings,
|
|
1041
|
+
sourcePackage: input.sourcePackage,
|
|
926
1042
|
};
|
|
927
1043
|
}
|
|
928
1044
|
|
|
@@ -1292,10 +1408,12 @@ function rangesIntersect(
|
|
|
1292
1408
|
|
|
1293
1409
|
function resolveCommentsPartPath(
|
|
1294
1410
|
sourcePackage: OpcPackage,
|
|
1411
|
+
sourceDocumentPartPath: string,
|
|
1295
1412
|
relationships: readonly OpcRelationship[],
|
|
1296
1413
|
): string | undefined {
|
|
1297
1414
|
return resolveDocumentRelatedPartPath(
|
|
1298
1415
|
sourcePackage,
|
|
1416
|
+
sourceDocumentPartPath,
|
|
1299
1417
|
relationships,
|
|
1300
1418
|
COMMENTS_RELATIONSHIP_TYPE,
|
|
1301
1419
|
COMMENTS_PART_PATH,
|
|
@@ -1304,6 +1422,7 @@ function resolveCommentsPartPath(
|
|
|
1304
1422
|
|
|
1305
1423
|
function resolveDocumentRelatedPartPath(
|
|
1306
1424
|
sourcePackage: OpcPackage,
|
|
1425
|
+
sourceDocumentPartPath: string,
|
|
1307
1426
|
relationships: readonly OpcRelationship[],
|
|
1308
1427
|
relationshipType: string,
|
|
1309
1428
|
fallbackPartPath: string,
|
|
@@ -1317,10 +1436,23 @@ function resolveDocumentRelatedPartPath(
|
|
|
1317
1436
|
return sourcePackage.parts.has(fallbackPartPath) ? fallbackPartPath : undefined;
|
|
1318
1437
|
}
|
|
1319
1438
|
|
|
1320
|
-
const targetPath = resolveRelationshipTarget(
|
|
1439
|
+
const targetPath = resolveRelationshipTarget(sourceDocumentPartPath, relationship);
|
|
1321
1440
|
return sourcePackage.parts.has(targetPath) ? targetPath : undefined;
|
|
1322
1441
|
}
|
|
1323
1442
|
|
|
1443
|
+
function resolveMainDocumentPartPath(sourcePackage: OpcPackage): string | undefined {
|
|
1444
|
+
const relationship = sourcePackage.manifest.packageRelationships.find(
|
|
1445
|
+
(candidate) =>
|
|
1446
|
+
candidate.type === OFFICE_DOCUMENT_RELATIONSHIP_TYPE &&
|
|
1447
|
+
candidate.targetMode === "internal",
|
|
1448
|
+
);
|
|
1449
|
+
if (relationship) {
|
|
1450
|
+
return resolveRelationshipTarget(null, relationship);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
return sourcePackage.parts.has(MAIN_DOCUMENT_PATH) ? MAIN_DOCUMENT_PATH : undefined;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1324
1456
|
function toRuntimeCommentRecords(
|
|
1325
1457
|
threads: readonly CommentThread[],
|
|
1326
1458
|
): Record<string, CommentThreadRecord> {
|
|
@@ -1547,6 +1679,7 @@ function hasNumberingEntries(catalog: NumberingCatalog): boolean {
|
|
|
1547
1679
|
|
|
1548
1680
|
function collectBrokenInternalRelationshipIssues(
|
|
1549
1681
|
sourcePackage: OpcPackage,
|
|
1682
|
+
mainDocumentPath?: string,
|
|
1550
1683
|
): ReturnType<typeof createBrokenRelationshipIssue>[] {
|
|
1551
1684
|
const brokenTargets = new Map<string, ReturnType<typeof createBrokenRelationshipIssue>>();
|
|
1552
1685
|
|
|
@@ -1556,7 +1689,15 @@ function collectBrokenInternalRelationshipIssues(
|
|
|
1556
1689
|
}
|
|
1557
1690
|
|
|
1558
1691
|
const target = resolveRelationshipTarget(null, relationship);
|
|
1559
|
-
if (
|
|
1692
|
+
if (
|
|
1693
|
+
!sourcePackage.parts.has(target) &&
|
|
1694
|
+
isFatalBrokenRelationship({
|
|
1695
|
+
relationshipSourcePath: null,
|
|
1696
|
+
relationship,
|
|
1697
|
+
targetPartPath: target,
|
|
1698
|
+
mainDocumentPath,
|
|
1699
|
+
})
|
|
1700
|
+
) {
|
|
1560
1701
|
brokenTargets.set(
|
|
1561
1702
|
`package:${relationship.id}:${target}`,
|
|
1562
1703
|
createBrokenRelationshipIssue({
|
|
@@ -1575,7 +1716,15 @@ function collectBrokenInternalRelationshipIssues(
|
|
|
1575
1716
|
}
|
|
1576
1717
|
|
|
1577
1718
|
const target = resolveRelationshipTarget(part.path, relationship);
|
|
1578
|
-
if (
|
|
1719
|
+
if (
|
|
1720
|
+
!sourcePackage.parts.has(target) &&
|
|
1721
|
+
isFatalBrokenRelationship({
|
|
1722
|
+
relationshipSourcePath: part.path,
|
|
1723
|
+
relationship,
|
|
1724
|
+
targetPartPath: target,
|
|
1725
|
+
mainDocumentPath,
|
|
1726
|
+
})
|
|
1727
|
+
) {
|
|
1579
1728
|
brokenTargets.set(
|
|
1580
1729
|
`${part.path}:${relationship.id}:${target}`,
|
|
1581
1730
|
createBrokenRelationshipIssue({
|
|
@@ -1604,6 +1753,82 @@ function summarizeBrokenRelationshipIssues(
|
|
|
1604
1753
|
.join(", ")}${issues.length > 3 ? ", ..." : ""}.`;
|
|
1605
1754
|
}
|
|
1606
1755
|
|
|
1756
|
+
function createBrokenRelationshipWarnings(
|
|
1757
|
+
sourcePackage: OpcPackage,
|
|
1758
|
+
mainDocumentPath?: string,
|
|
1759
|
+
): CanonicalDocumentEnvelope["diagnostics"]["warnings"] {
|
|
1760
|
+
const warnings = new Map<string, CanonicalDocumentEnvelope["diagnostics"]["warnings"][number]>();
|
|
1761
|
+
|
|
1762
|
+
for (const relationship of sourcePackage.manifest.packageRelationships) {
|
|
1763
|
+
if (relationship.targetMode !== "internal") {
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const target = resolveRelationshipTarget(null, relationship);
|
|
1768
|
+
if (
|
|
1769
|
+
sourcePackage.parts.has(target) ||
|
|
1770
|
+
isFatalBrokenRelationship({
|
|
1771
|
+
relationshipSourcePath: null,
|
|
1772
|
+
relationship,
|
|
1773
|
+
targetPartPath: target,
|
|
1774
|
+
mainDocumentPath,
|
|
1775
|
+
})
|
|
1776
|
+
) {
|
|
1777
|
+
continue;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
warnings.set(`package:${relationship.id}:${target}`, {
|
|
1781
|
+
diagnosticId: `diagnostic:broken-relationship-package-${relationship.id}`,
|
|
1782
|
+
warningId: `warning:broken-relationship:${relationship.id}`,
|
|
1783
|
+
source: "preservation",
|
|
1784
|
+
message: `DOCX package has unresolved internal relationships outside the editor-owned graph: ${target}.`,
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
for (const part of sourcePackage.parts.values()) {
|
|
1789
|
+
for (const relationship of part.relationships) {
|
|
1790
|
+
if (relationship.targetMode !== "internal") {
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
const target = resolveRelationshipTarget(part.path, relationship);
|
|
1795
|
+
if (
|
|
1796
|
+
sourcePackage.parts.has(target) ||
|
|
1797
|
+
isFatalBrokenRelationship({
|
|
1798
|
+
relationshipSourcePath: part.path,
|
|
1799
|
+
relationship,
|
|
1800
|
+
targetPartPath: target,
|
|
1801
|
+
mainDocumentPath,
|
|
1802
|
+
})
|
|
1803
|
+
) {
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
warnings.set(`${part.path}:${relationship.id}:${target}`, {
|
|
1808
|
+
diagnosticId: `diagnostic:broken-relationship-${relationship.id}`,
|
|
1809
|
+
warningId: `warning:broken-relationship:${relationship.id}`,
|
|
1810
|
+
source: "preservation",
|
|
1811
|
+
message: `DOCX package has unresolved internal relationships outside the editor-owned graph: ${target}.`,
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
return [...warnings.values()];
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function isFatalBrokenRelationship(input: {
|
|
1820
|
+
relationshipSourcePath: string | null;
|
|
1821
|
+
relationship: OpcRelationship;
|
|
1822
|
+
targetPartPath: string;
|
|
1823
|
+
mainDocumentPath?: string;
|
|
1824
|
+
}): boolean {
|
|
1825
|
+
return (
|
|
1826
|
+
input.relationshipSourcePath === null &&
|
|
1827
|
+
input.relationship.type === OFFICE_DOCUMENT_RELATIONSHIP_TYPE &&
|
|
1828
|
+
input.targetPartPath === input.mainDocumentPath
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1607
1832
|
function shouldPreservePackagePart(
|
|
1608
1833
|
partPath: string,
|
|
1609
1834
|
surfaceKind: OpcPackage["parts"] extends Map<string, infer T>
|
|
@@ -1618,7 +1843,6 @@ function shouldPreservePackagePart(
|
|
|
1618
1843
|
}
|
|
1619
1844
|
|
|
1620
1845
|
if (
|
|
1621
|
-
partPath === MAIN_DOCUMENT_PATH ||
|
|
1622
1846
|
ownedPartPaths.has(partPath) ||
|
|
1623
1847
|
partPath.startsWith("/word/media/") ||
|
|
1624
1848
|
CORE_NON_PRESERVED_PART_PATHS.has(partPath)
|
|
@@ -1700,13 +1924,7 @@ function isPackageImportError(error: unknown): boolean {
|
|
|
1700
1924
|
const CORE_NON_PRESERVED_PART_PATHS = new Set([
|
|
1701
1925
|
"/docProps/app.xml",
|
|
1702
1926
|
"/docProps/core.xml",
|
|
1703
|
-
"/docProps/custom.xml",
|
|
1704
|
-
"/word/fontTable.xml",
|
|
1705
1927
|
"/word/numbering.xml",
|
|
1706
|
-
"/word/settings.xml",
|
|
1707
|
-
"/word/styles.xml",
|
|
1708
|
-
"/word/stylesWithEffects.xml",
|
|
1709
|
-
"/word/webSettings.xml",
|
|
1710
1928
|
]);
|
|
1711
1929
|
|
|
1712
1930
|
function createCommentsRelationshipId(
|
|
@@ -1751,6 +1969,10 @@ function canReuseSourceBytesForCurrentDocument(
|
|
|
1751
1969
|
state: ImportedDocxState,
|
|
1752
1970
|
document: CanonicalDocumentEnvelope,
|
|
1753
1971
|
): boolean {
|
|
1972
|
+
if (requiresHostMetadataNormalization(state.sourcePackage, state.sourceDocumentPartPath)) {
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1754
1976
|
const commentThreads = Object.values(document.review.comments);
|
|
1755
1977
|
const hasLiveComments = commentThreads.some((thread) => thread.anchor.kind !== "detached");
|
|
1756
1978
|
if (!hasLiveComments) {
|
|
@@ -1764,3 +1986,151 @@ function canReuseSourceBytesForCurrentDocument(
|
|
|
1764
1986
|
state.sourcePeoplePartPath,
|
|
1765
1987
|
);
|
|
1766
1988
|
}
|
|
1989
|
+
|
|
1990
|
+
function requiresHostMetadataNormalization(
|
|
1991
|
+
sourcePackage: OpcPackage,
|
|
1992
|
+
sourceDocumentPartPath: string,
|
|
1993
|
+
): boolean {
|
|
1994
|
+
return (
|
|
1995
|
+
isSuspiciouslySkeletalWordPackage(sourcePackage, sourceDocumentPartPath) &&
|
|
1996
|
+
!hasHostSafeMetadataPackageStructure(sourcePackage)
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
function ensureHostMetadataParts(
|
|
2001
|
+
exportSession: ReturnType<typeof createExportSession>,
|
|
2002
|
+
sourcePackage: OpcPackage,
|
|
2003
|
+
document: CanonicalDocumentEnvelope,
|
|
2004
|
+
): void {
|
|
2005
|
+
const corePropertiesPart = sourcePackage.parts.get(CORE_PROPERTIES_PART_PATH);
|
|
2006
|
+
if (!corePropertiesPart || corePropertiesPart.contentType !== CORE_PROPERTIES_CONTENT_TYPE) {
|
|
2007
|
+
exportSession.replaceOwnedPart({
|
|
2008
|
+
path: CORE_PROPERTIES_PART_PATH,
|
|
2009
|
+
bytes: corePropertiesPart?.bytes ?? new TextEncoder().encode(buildCorePropertiesXml(document)),
|
|
2010
|
+
contentType: CORE_PROPERTIES_CONTENT_TYPE,
|
|
2011
|
+
compression: corePropertiesPart?.compression,
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
const appPropertiesPart = sourcePackage.parts.get(APP_PROPERTIES_PART_PATH);
|
|
2016
|
+
if (!appPropertiesPart || appPropertiesPart.contentType !== APP_PROPERTIES_CONTENT_TYPE) {
|
|
2017
|
+
exportSession.replaceOwnedPart({
|
|
2018
|
+
path: APP_PROPERTIES_PART_PATH,
|
|
2019
|
+
bytes: appPropertiesPart?.bytes ?? new TextEncoder().encode(buildAppPropertiesXml()),
|
|
2020
|
+
contentType: APP_PROPERTIES_CONTENT_TYPE,
|
|
2021
|
+
compression: appPropertiesPart?.compression,
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
exportSession.ensurePackageRelationship({
|
|
2026
|
+
type: CORE_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2027
|
+
target: CORE_PROPERTIES_PART_PATH,
|
|
2028
|
+
preferredId: "rIdDocPropsCore",
|
|
2029
|
+
});
|
|
2030
|
+
exportSession.ensurePackageRelationship({
|
|
2031
|
+
type: APP_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2032
|
+
target: APP_PROPERTIES_PART_PATH,
|
|
2033
|
+
preferredId: "rIdDocPropsApp",
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function hasHostSafeMetadataPackageStructure(sourcePackage: OpcPackage): boolean {
|
|
2038
|
+
const corePropertiesPart = sourcePackage.parts.get(CORE_PROPERTIES_PART_PATH);
|
|
2039
|
+
const appPropertiesPart = sourcePackage.parts.get(APP_PROPERTIES_PART_PATH);
|
|
2040
|
+
return (
|
|
2041
|
+
corePropertiesPart?.contentType === CORE_PROPERTIES_CONTENT_TYPE &&
|
|
2042
|
+
appPropertiesPart?.contentType === APP_PROPERTIES_CONTENT_TYPE &&
|
|
2043
|
+
hasPackageRelationshipTarget(
|
|
2044
|
+
sourcePackage,
|
|
2045
|
+
CORE_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2046
|
+
CORE_PROPERTIES_PART_PATH,
|
|
2047
|
+
) &&
|
|
2048
|
+
hasPackageRelationshipTarget(
|
|
2049
|
+
sourcePackage,
|
|
2050
|
+
APP_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2051
|
+
APP_PROPERTIES_PART_PATH,
|
|
2052
|
+
)
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function hasPackageRelationshipTarget(
|
|
2057
|
+
sourcePackage: OpcPackage,
|
|
2058
|
+
relationshipType: string,
|
|
2059
|
+
targetPartPath: string,
|
|
2060
|
+
): boolean {
|
|
2061
|
+
return sourcePackage.manifest.packageRelationships.some(
|
|
2062
|
+
(relationship) =>
|
|
2063
|
+
relationship.type === relationshipType &&
|
|
2064
|
+
relationship.targetMode === "internal" &&
|
|
2065
|
+
resolveRelationshipTarget(null, relationship) === targetPartPath,
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
function isSuspiciouslySkeletalWordPackage(
|
|
2070
|
+
sourcePackage: OpcPackage,
|
|
2071
|
+
sourceDocumentPartPath: string,
|
|
2072
|
+
): boolean {
|
|
2073
|
+
const allowedPaths = new Set<string>([
|
|
2074
|
+
CONTENT_TYPES_PATH,
|
|
2075
|
+
PACKAGE_RELATIONSHIPS_PATH,
|
|
2076
|
+
sourceDocumentPartPath,
|
|
2077
|
+
]);
|
|
2078
|
+
const relationshipsPartPath = getRelationshipsPartPath(sourceDocumentPartPath);
|
|
2079
|
+
if (relationshipsPartPath) {
|
|
2080
|
+
allowedPaths.add(relationshipsPartPath);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
return [...sourcePackage.parts.keys()].every((partPath) => allowedPaths.has(partPath));
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function buildCorePropertiesXml(document: CanonicalDocumentEnvelope): string {
|
|
2087
|
+
const { metadata } = document;
|
|
2088
|
+
const keywords =
|
|
2089
|
+
Array.isArray(metadata.keywords) && metadata.keywords.length > 0
|
|
2090
|
+
? metadata.keywords.join(", ")
|
|
2091
|
+
: undefined;
|
|
2092
|
+
const propertyLines = [
|
|
2093
|
+
xmlNode("dc:title", metadata.title),
|
|
2094
|
+
xmlNode("dc:subject", metadata.subject),
|
|
2095
|
+
xmlNode("dc:description", metadata.description),
|
|
2096
|
+
xmlNode("dc:language", metadata.language),
|
|
2097
|
+
xmlNode("cp:keywords", keywords),
|
|
2098
|
+
xmlNode("cp:category", metadata.category),
|
|
2099
|
+
xmlNode('dcterms:created xsi:type="dcterms:W3CDTF"', document.createdAt),
|
|
2100
|
+
xmlNode('dcterms:modified xsi:type="dcterms:W3CDTF"', document.updatedAt),
|
|
2101
|
+
].filter((line): line is string => Boolean(line));
|
|
2102
|
+
|
|
2103
|
+
return [
|
|
2104
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
2105
|
+
`<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">`,
|
|
2106
|
+
...propertyLines.map((line) => ` ${line}`),
|
|
2107
|
+
`</cp:coreProperties>`,
|
|
2108
|
+
].join("\n");
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
function buildAppPropertiesXml(): string {
|
|
2112
|
+
return [
|
|
2113
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
2114
|
+
`<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">`,
|
|
2115
|
+
` <Application>React OOXML Office</Application>`,
|
|
2116
|
+
` <AppVersion>1.0</AppVersion>`,
|
|
2117
|
+
`</Properties>`,
|
|
2118
|
+
].join("\n");
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
function xmlNode(tagName: string, value: string | undefined): string | undefined {
|
|
2122
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2123
|
+
return undefined;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
return `<${tagName}>${escapeXml(value)}</${tagName.split(" ", 1)[0]}>`;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function escapeXml(value: string): string {
|
|
2130
|
+
return value
|
|
2131
|
+
.replace(/&/g, "&")
|
|
2132
|
+
.replace(/</g, "<")
|
|
2133
|
+
.replace(/>/g, ">")
|
|
2134
|
+
.replace(/\"/g, """)
|
|
2135
|
+
.replace(/'/g, "'");
|
|
2136
|
+
}
|