@beyondwork/docx-react-component 1.0.28 → 1.0.29
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/canonical-document-BLEbzL2J.d.cts +844 -0
- package/dist/canonical-document-BLEbzL2J.d.ts +844 -0
- package/dist/chunk-2FJS5GZM.js +763 -0
- package/dist/chunk-2FJS5GZM.js.map +1 -0
- package/{src/core/commands/section-layout-commands.ts → dist/chunk-2OQBZS3F.js} +106 -340
- package/dist/chunk-2OQBZS3F.js.map +1 -0
- package/dist/chunk-2S7W4KFO.js +127 -0
- package/dist/chunk-2S7W4KFO.js.map +1 -0
- package/dist/chunk-2TG72QSW.js +3874 -0
- package/dist/chunk-2TG72QSW.js.map +1 -0
- package/{src/core/commands/table-structure-commands.ts → dist/chunk-36QNIZBO.js} +126 -315
- package/dist/chunk-36QNIZBO.js.map +1 -0
- package/dist/chunk-4AQOYAW4.js +3069 -0
- package/dist/chunk-4AQOYAW4.js.map +1 -0
- package/dist/chunk-4D5EWJ3P.js +77 -0
- package/dist/chunk-4D5EWJ3P.js.map +1 -0
- package/dist/chunk-5FN54NDH.js +2257 -0
- package/dist/chunk-5FN54NDH.js.map +1 -0
- package/dist/chunk-BOYGQYRQ.js +7306 -0
- package/dist/chunk-BOYGQYRQ.js.map +1 -0
- package/dist/chunk-CN3XMECL.js +212 -0
- package/dist/chunk-CN3XMECL.js.map +1 -0
- package/dist/chunk-EBI3BX6U.js +164 -0
- package/dist/chunk-EBI3BX6U.js.map +1 -0
- package/dist/chunk-EILUG3VB.js +1275 -0
- package/dist/chunk-EILUG3VB.js.map +1 -0
- package/dist/chunk-FUDY333O.js +70 -0
- package/dist/chunk-FUDY333O.js.map +1 -0
- package/dist/chunk-GBVOWFIK.js +1237 -0
- package/dist/chunk-GBVOWFIK.js.map +1 -0
- package/dist/chunk-H4TQ3H3Y.js +262 -0
- package/dist/chunk-H4TQ3H3Y.js.map +1 -0
- package/{src/core/commands/style-commands.ts → dist/chunk-JGB3IXZO.js} +40 -113
- package/dist/chunk-JGB3IXZO.js.map +1 -0
- package/dist/chunk-KD2QRQPY.js +4342 -0
- package/dist/chunk-KD2QRQPY.js.map +1 -0
- package/dist/chunk-KLMXQVYK.js +369 -0
- package/dist/chunk-KLMXQVYK.js.map +1 -0
- package/dist/chunk-KZUG5KFQ.js +214 -0
- package/dist/chunk-KZUG5KFQ.js.map +1 -0
- package/{src/core/state/text-transaction.ts → dist/chunk-QDAQ4CJU.js} +79 -236
- package/dist/chunk-QDAQ4CJU.js.map +1 -0
- package/{src/legal/bookmarks.ts → dist/chunk-RMH72RZI.js} +44 -130
- package/dist/chunk-RMH72RZI.js.map +1 -0
- package/dist/chunk-SWKWQZXM.js +117 -0
- package/dist/chunk-SWKWQZXM.js.map +1 -0
- package/{src/core/commands/formatting-commands.ts → dist/chunk-TJBP2K4T.js} +196 -536
- package/dist/chunk-TJBP2K4T.js.map +1 -0
- package/dist/chunk-TLCEAQDQ.js +542 -0
- package/dist/chunk-TLCEAQDQ.js.map +1 -0
- package/{src/core/commands/text-commands.ts → dist/chunk-UZXBISGO.js} +86 -142
- package/dist/chunk-UZXBISGO.js.map +1 -0
- package/dist/chunk-WGBAKP3Q.js +3220 -0
- package/dist/chunk-WGBAKP3Q.js.map +1 -0
- package/dist/compare/index.cjs +5475 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +114 -0
- package/dist/compare/index.d.ts +114 -0
- package/dist/compare/index.js +731 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/core/commands/formatting-commands.cjs +828 -0
- package/dist/core/commands/formatting-commands.cjs.map +1 -0
- package/dist/core/commands/formatting-commands.d.cts +63 -0
- package/dist/core/commands/formatting-commands.d.ts +63 -0
- package/dist/core/commands/formatting-commands.js +37 -0
- package/dist/core/commands/formatting-commands.js.map +1 -0
- package/dist/core/commands/image-commands.cjs +2023 -0
- package/dist/core/commands/image-commands.cjs.map +1 -0
- package/dist/core/commands/image-commands.d.cts +58 -0
- package/dist/core/commands/image-commands.d.ts +58 -0
- package/dist/core/commands/image-commands.js +18 -0
- package/dist/core/commands/image-commands.js.map +1 -0
- package/dist/core/commands/section-layout-commands.cjs +477 -0
- package/dist/core/commands/section-layout-commands.cjs.map +1 -0
- package/dist/core/commands/section-layout-commands.d.cts +62 -0
- package/dist/core/commands/section-layout-commands.d.ts +62 -0
- package/dist/core/commands/section-layout-commands.js +21 -0
- package/dist/core/commands/section-layout-commands.js.map +1 -0
- package/dist/core/commands/style-commands.cjs +214 -0
- package/dist/core/commands/style-commands.cjs.map +1 -0
- package/dist/core/commands/style-commands.d.cts +13 -0
- package/dist/core/commands/style-commands.d.ts +13 -0
- package/dist/core/commands/style-commands.js +9 -0
- package/dist/core/commands/style-commands.js.map +1 -0
- package/dist/core/commands/table-structure-commands.cjs +1883 -0
- package/dist/core/commands/table-structure-commands.cjs.map +1 -0
- package/dist/core/commands/table-structure-commands.d.cts +59 -0
- package/dist/core/commands/table-structure-commands.d.ts +59 -0
- package/dist/core/commands/table-structure-commands.js +12 -0
- package/dist/core/commands/table-structure-commands.js.map +1 -0
- package/dist/core/commands/text-commands.cjs +2391 -0
- package/dist/core/commands/text-commands.cjs.map +1 -0
- package/dist/core/commands/text-commands.d.cts +24 -0
- package/dist/core/commands/text-commands.d.ts +24 -0
- package/dist/core/commands/text-commands.js +28 -0
- package/dist/core/commands/text-commands.js.map +1 -0
- package/dist/core/selection/mapping.cjs +200 -0
- package/dist/core/selection/mapping.cjs.map +1 -0
- package/dist/core/selection/mapping.d.cts +2 -0
- package/dist/core/selection/mapping.d.ts +2 -0
- package/dist/core/selection/mapping.js +31 -0
- package/dist/core/selection/mapping.js.map +1 -0
- package/dist/core/state/editor-state.cjs +2278 -0
- package/dist/core/state/editor-state.cjs.map +1 -0
- package/dist/core/state/editor-state.d.cts +2 -0
- package/dist/core/state/editor-state.d.ts +2 -0
- package/dist/core/state/editor-state.js +26 -0
- package/dist/core/state/editor-state.js.map +1 -0
- package/dist/index.cjs +38553 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +7856 -0
- package/dist/index.js.map +1 -0
- package/dist/io/docx-session.cjs +16236 -0
- package/dist/io/docx-session.cjs.map +1 -0
- package/dist/io/docx-session.d.cts +21 -0
- package/dist/io/docx-session.d.ts +21 -0
- package/dist/io/docx-session.js +18 -0
- package/dist/io/docx-session.js.map +1 -0
- package/dist/legal/index.cjs +3900 -0
- package/dist/legal/index.cjs.map +1 -0
- package/dist/legal/index.d.cts +86 -0
- package/dist/legal/index.d.ts +86 -0
- package/dist/legal/index.js +616 -0
- package/dist/legal/index.js.map +1 -0
- package/dist/public-types-7ZL_94cz.d.ts +1573 -0
- package/dist/public-types-CeMaDueh.d.cts +1573 -0
- package/dist/public-types.cjs +19 -0
- package/dist/public-types.cjs.map +1 -0
- package/dist/public-types.d.cts +2 -0
- package/dist/public-types.d.ts +2 -0
- package/dist/public-types.js +1 -0
- package/dist/public-types.js.map +1 -0
- package/dist/runtime/document-runtime.cjs +11140 -0
- package/dist/runtime/document-runtime.cjs.map +1 -0
- package/dist/runtime/document-runtime.d.cts +231 -0
- package/dist/runtime/document-runtime.d.ts +231 -0
- package/dist/runtime/document-runtime.js +21 -0
- package/dist/runtime/document-runtime.js.map +1 -0
- package/dist/structural-helpers-CilgOVhh.d.cts +10 -0
- package/dist/structural-helpers-q0Gd-eBN.d.ts +10 -0
- package/dist/ui-tailwind/editor-surface/search-plugin.cjs +313 -0
- package/dist/ui-tailwind/editor-surface/search-plugin.cjs.map +1 -0
- package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +67 -0
- package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +67 -0
- package/dist/ui-tailwind/editor-surface/search-plugin.js +23 -0
- package/dist/ui-tailwind/editor-surface/search-plugin.js.map +1 -0
- package/dist/ui-tailwind/index.cjs +4833 -0
- package/dist/ui-tailwind/index.cjs.map +1 -0
- package/dist/ui-tailwind/index.d.cts +617 -0
- package/dist/ui-tailwind/index.d.ts +617 -0
- package/dist/ui-tailwind/index.js +575 -0
- package/dist/ui-tailwind/index.js.map +1 -0
- package/package.json +61 -41
- package/src/README.md +0 -85
- package/src/api/README.md +0 -26
- package/src/api/public-types.ts +0 -1421
- package/src/api/session-state.ts +0 -60
- package/src/compare/diff-engine.ts +0 -623
- package/src/compare/export-redlines.ts +0 -280
- package/src/compare/index.ts +0 -25
- package/src/compare/snapshot.ts +0 -97
- package/src/component-inventory.md +0 -99
- package/src/core/README.md +0 -10
- package/src/core/commands/README.md +0 -3
- package/src/core/commands/image-commands.ts +0 -373
- package/src/core/commands/index.ts +0 -1757
- package/src/core/commands/list-commands.ts +0 -565
- package/src/core/commands/paragraph-layout-commands.ts +0 -339
- package/src/core/commands/review-commands.ts +0 -108
- package/src/core/commands/structural-helpers.ts +0 -309
- package/src/core/schema/README.md +0 -3
- package/src/core/schema/text-schema.ts +0 -516
- package/src/core/search/search-text.ts +0 -357
- package/src/core/selection/README.md +0 -3
- package/src/core/selection/mapping.ts +0 -289
- package/src/core/selection/review-anchors.ts +0 -183
- package/src/core/state/README.md +0 -3
- package/src/core/state/editor-state.ts +0 -892
- package/src/formats/xlsx/io/parse-shared-strings.ts +0 -41
- package/src/formats/xlsx/io/parse-sheet.ts +0 -459
- package/src/formats/xlsx/io/parse-styles.ts +0 -59
- package/src/formats/xlsx/io/parse-workbook.ts +0 -75
- package/src/formats/xlsx/io/serialize-shared-strings.ts +0 -72
- package/src/formats/xlsx/io/serialize-sheet.ts +0 -333
- package/src/formats/xlsx/io/serialize-styles.ts +0 -98
- package/src/formats/xlsx/io/serialize-workbook.ts +0 -429
- package/src/formats/xlsx/io/xlsx-session.ts +0 -314
- package/src/formats/xlsx/model/cell.ts +0 -189
- package/src/formats/xlsx/model/sheet.ts +0 -326
- package/src/formats/xlsx/model/styles.ts +0 -118
- package/src/formats/xlsx/model/workbook.ts +0 -453
- package/src/formats/xlsx/runtime/cell-commands.ts +0 -567
- package/src/formats/xlsx/runtime/sheet-commands.ts +0 -206
- package/src/formats/xlsx/runtime/workbook-runtime.ts +0 -177
- package/src/formats/xlsx/runtime/workbook-transaction.ts +0 -822
- package/src/index.ts +0 -101
- package/src/io/README.md +0 -10
- package/src/io/docx-session.ts +0 -2882
- package/src/io/export/README.md +0 -3
- package/src/io/export/export-session.ts +0 -220
- package/src/io/export/minimal-docx.ts +0 -115
- package/src/io/export/reattach-preserved-parts.ts +0 -54
- package/src/io/export/serialize-comments.ts +0 -947
- package/src/io/export/serialize-footnotes.ts +0 -399
- package/src/io/export/serialize-headers-footers.ts +0 -372
- package/src/io/export/serialize-main-document.ts +0 -1376
- package/src/io/export/serialize-numbering.ts +0 -118
- package/src/io/export/serialize-revisions.ts +0 -389
- package/src/io/export/serialize-runtime-revisions.ts +0 -269
- package/src/io/export/serialize-tables.ts +0 -174
- package/src/io/export/split-review-boundaries.ts +0 -356
- package/src/io/normalize/README.md +0 -3
- package/src/io/normalize/normalize-text.ts +0 -639
- package/src/io/ooxml/README.md +0 -3
- package/src/io/ooxml/highlight-colors.ts +0 -39
- package/src/io/ooxml/numbering-sentinels.ts +0 -44
- package/src/io/ooxml/parse-comments.ts +0 -846
- package/src/io/ooxml/parse-complex-content.ts +0 -287
- package/src/io/ooxml/parse-fields.ts +0 -834
- package/src/io/ooxml/parse-footnotes.ts +0 -896
- package/src/io/ooxml/parse-headers-footers.ts +0 -1169
- package/src/io/ooxml/parse-inline-media.ts +0 -461
- package/src/io/ooxml/parse-main-document.ts +0 -2877
- package/src/io/ooxml/parse-numbering.ts +0 -432
- package/src/io/ooxml/parse-revisions.ts +0 -931
- package/src/io/ooxml/parse-settings.ts +0 -184
- package/src/io/ooxml/parse-shapes.ts +0 -296
- package/src/io/ooxml/parse-styles.ts +0 -463
- package/src/io/ooxml/parse-tables.ts +0 -618
- package/src/io/ooxml/parse-theme.ts +0 -346
- package/src/io/ooxml/part-manifest.ts +0 -136
- package/src/io/ooxml/revision-boundaries.ts +0 -351
- package/src/io/opc/README.md +0 -3
- package/src/io/opc/corrupt-package.ts +0 -166
- package/src/io/opc/docx-package.ts +0 -74
- package/src/io/opc/package-reader.ts +0 -325
- package/src/io/opc/package-writer.ts +0 -273
- package/src/io/source-package-provenance.ts +0 -241
- package/src/legal/cross-references.ts +0 -414
- package/src/legal/defined-terms.ts +0 -203
- package/src/legal/index.ts +0 -32
- package/src/legal/signature-blocks.ts +0 -259
- package/src/model/README.md +0 -3
- package/src/model/canonical-document.ts +0 -2632
- package/src/model/cds-1.0.0.ts +0 -212
- package/src/model/snapshot.ts +0 -649
- package/src/preservation/README.md +0 -3
- package/src/preservation/markup-compatibility.ts +0 -48
- package/src/preservation/opaque-fragment-store.ts +0 -89
- package/src/preservation/opaque-region.ts +0 -233
- package/src/preservation/package-preservation.ts +0 -113
- package/src/preservation/preserved-part-manifest.ts +0 -56
- package/src/preservation/relationship-retention.ts +0 -57
- package/src/preservation/store.ts +0 -185
- package/src/review/README.md +0 -16
- package/src/review/store/README.md +0 -3
- package/src/review/store/comment-anchors.ts +0 -70
- package/src/review/store/comment-remapping.ts +0 -154
- package/src/review/store/comment-store.ts +0 -331
- package/src/review/store/comment-thread.ts +0 -109
- package/src/review/store/revision-actions.ts +0 -394
- package/src/review/store/revision-store.ts +0 -312
- package/src/review/store/revision-types.ts +0 -171
- package/src/review/store/runtime-comment-store.ts +0 -43
- package/src/runtime/README.md +0 -3
- package/src/runtime/ai-action-policy.ts +0 -764
- package/src/runtime/document-layout.ts +0 -332
- package/src/runtime/document-navigation.ts +0 -603
- package/src/runtime/document-runtime.ts +0 -3159
- package/src/runtime/document-search.ts +0 -145
- package/src/runtime/numbering-prefix.ts +0 -216
- package/src/runtime/page-layout-estimation.ts +0 -212
- package/src/runtime/read-only-diagnostics-runtime.ts +0 -241
- package/src/runtime/review-runtime.ts +0 -44
- package/src/runtime/revision-runtime.ts +0 -107
- package/src/runtime/session-capabilities.ts +0 -192
- package/src/runtime/story-context.ts +0 -164
- package/src/runtime/story-targeting.ts +0 -162
- package/src/runtime/surface-projection.ts +0 -1357
- package/src/runtime/table-commands.ts +0 -173
- package/src/runtime/table-schema.ts +0 -309
- package/src/runtime/view-state.ts +0 -477
- package/src/runtime/virtualized-rendering.ts +0 -258
- package/src/runtime/workflow-markup.ts +0 -353
- package/src/ui/README.md +0 -30
- package/src/ui/WordReviewEditor.tsx +0 -4086
- package/src/ui/browser-export.ts +0 -52
- package/src/ui/comments/README.md +0 -3
- package/src/ui/compatibility/README.md +0 -3
- package/src/ui/editor-command-bag.ts +0 -120
- package/src/ui/editor-runtime-boundary.ts +0 -1457
- package/src/ui/editor-shell-view.tsx +0 -142
- package/src/ui/editor-surface/README.md +0 -3
- package/src/ui/editor-surface-controller.tsx +0 -61
- package/src/ui/headless/comment-decoration-model.ts +0 -124
- package/src/ui/headless/preserve-editor-selection.ts +0 -5
- package/src/ui/headless/revision-decoration-model.ts +0 -128
- package/src/ui/headless/selection-helpers.ts +0 -54
- package/src/ui/headless/selection-toolbar-model.ts +0 -34
- package/src/ui/headless/use-editor-keyboard.ts +0 -103
- package/src/ui/review/README.md +0 -3
- package/src/ui/runtime-snapshot-selectors.ts +0 -197
- package/src/ui/shared/revision-filters.ts +0 -31
- package/src/ui/status/README.md +0 -3
- package/src/ui/theme/README.md +0 -3
- package/src/ui/toolbar/README.md +0 -3
- package/src/ui/workflow-surface-blocked-rails.ts +0 -94
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +0 -64
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +0 -129
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +0 -114
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +0 -34
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +0 -386
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +0 -186
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +0 -139
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +0 -128
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +0 -58
- package/src/ui-tailwind/chrome/use-before-unload.ts +0 -20
- package/src/ui-tailwind/editor-surface/perf-probe.ts +0 -179
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +0 -184
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +0 -31
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +0 -427
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +0 -123
- package/src/ui-tailwind/editor-surface/pm-schema.ts +0 -876
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +0 -504
- package/src/ui-tailwind/editor-surface/search-plugin.ts +0 -168
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +0 -61
- package/src/ui-tailwind/editor-surface/tw-caret.tsx +0 -12
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +0 -150
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +0 -129
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +0 -58
- package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +0 -151
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +0 -944
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +0 -111
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -436
- package/src/ui-tailwind/index.ts +0 -62
- package/src/ui-tailwind/page-chrome-model.ts +0 -27
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +0 -406
- package/src/ui-tailwind/review/tw-health-panel.tsx +0 -149
- package/src/ui-tailwind/review/tw-review-rail.tsx +0 -120
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +0 -164
- package/src/ui-tailwind/status/tw-status-bar.tsx +0 -61
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +0 -52
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +0 -1064
- package/src/ui-tailwind/tw-review-workspace.tsx +0 -1417
- package/src/validation/README.md +0 -3
- package/src/validation/compatibility-engine.ts +0 -634
- package/src/validation/compatibility-report.ts +0 -161
- package/src/validation/diagnostics.ts +0 -204
- package/src/validation/docx-comment-proof.ts +0 -707
- package/src/validation/import-diagnostics.ts +0 -128
- package/src/validation/low-priority-word-surfaces.ts +0 -373
- /package/{src → dist}/ui-tailwind/theme/editor-theme.css +0 -0
package/src/io/docx-session.ts
DELETED
|
@@ -1,2882 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CompatibilityReport as PublicCompatibilityReport,
|
|
3
|
-
EditorError,
|
|
4
|
-
EditorSessionState,
|
|
5
|
-
EditorWarning as PublicEditorWarning,
|
|
6
|
-
EditorAnchorProjection as PublicEditorAnchorProjection,
|
|
7
|
-
ExportDocxOptions,
|
|
8
|
-
ExportResult,
|
|
9
|
-
PersistedEditorSnapshot,
|
|
10
|
-
ProtectionRange,
|
|
11
|
-
ProtectionSnapshot,
|
|
12
|
-
} from "../api/public-types.ts";
|
|
13
|
-
import { editorSessionStateFromPersistedSnapshot } from "../api/session-state.ts";
|
|
14
|
-
import type {
|
|
15
|
-
CanonicalDocumentEnvelope,
|
|
16
|
-
CompatibilityFeatureEntry as InternalCompatibilityFeatureEntry,
|
|
17
|
-
CompatibilityReport as InternalCompatibilityReport,
|
|
18
|
-
CommentThreadRecord,
|
|
19
|
-
EditorError as InternalEditorError,
|
|
20
|
-
RevisionRecord as RuntimeRevisionRecord,
|
|
21
|
-
EditorWarning as InternalEditorWarning,
|
|
22
|
-
} from "../core/state/editor-state.ts";
|
|
23
|
-
import { createCanonicalDocumentId } from "../core/state/editor-state.ts";
|
|
24
|
-
import {
|
|
25
|
-
createDetachedAnchor,
|
|
26
|
-
type EditorAnchorProjection as InternalEditorAnchorProjection,
|
|
27
|
-
} from "../core/selection/mapping.ts";
|
|
28
|
-
import { DOCX_MIME_TYPE } from "./opc/docx-package.ts";
|
|
29
|
-
import { readOpcPackage, type OpcPackage } from "./opc/package-reader.ts";
|
|
30
|
-
import {
|
|
31
|
-
parseMainDocumentXml,
|
|
32
|
-
type ParsedBlockNode,
|
|
33
|
-
type ParsedInlineNode,
|
|
34
|
-
type ParsedPermStartInlineNode,
|
|
35
|
-
} from "./ooxml/parse-main-document.ts";
|
|
36
|
-
import { normalizeParsedTextDocument } from "./normalize/normalize-text.ts";
|
|
37
|
-
import {
|
|
38
|
-
CONTENT_TYPES_PATH,
|
|
39
|
-
PACKAGE_RELATIONSHIPS_PATH,
|
|
40
|
-
getRelationshipsPartPath,
|
|
41
|
-
normalizePartPath,
|
|
42
|
-
resolveRelationshipTarget,
|
|
43
|
-
type OpcRelationship,
|
|
44
|
-
} from "./ooxml/part-manifest.ts";
|
|
45
|
-
import {
|
|
46
|
-
classifyCorruptPackageError,
|
|
47
|
-
createBrokenRelationshipIssue,
|
|
48
|
-
createMissingPartIssue,
|
|
49
|
-
} from "./opc/corrupt-package.ts";
|
|
50
|
-
import { createExportSession } from "./export/export-session.ts";
|
|
51
|
-
import { serializeMainDocument } from "./export/serialize-main-document.ts";
|
|
52
|
-
import { parseRevisionsFromDocumentXml, type ParsedRevisionsResult } from "./ooxml/parse-revisions.ts";
|
|
53
|
-
import { parseCommentsFromOoxml } from "./ooxml/parse-comments.ts";
|
|
54
|
-
import { parseNumberingXml } from "./ooxml/parse-numbering.ts";
|
|
55
|
-
import {
|
|
56
|
-
createCommentExportIdMap,
|
|
57
|
-
mapParagraphBoundaries,
|
|
58
|
-
serializeCommentAnchorsIntoDocumentXml,
|
|
59
|
-
serializeMergedCommentsXml,
|
|
60
|
-
} from "./export/serialize-comments.ts";
|
|
61
|
-
import { splitDocumentAtReviewBoundaries } from "./export/split-review-boundaries.ts";
|
|
62
|
-
import { serializeRuntimeRevisionsIntoDocumentXml } from "./export/serialize-runtime-revisions.ts";
|
|
63
|
-
import { createCommentStoreFromRuntimeComments } from "../review/store/runtime-comment-store.ts";
|
|
64
|
-
import type { CommentThread } from "../review/store/comment-store.ts";
|
|
65
|
-
import type { RevisionRecord as ReviewRevisionRecord } from "../review/store/revision-types.ts";
|
|
66
|
-
import { getRevisionActionability } from "../review/store/revision-types.ts";
|
|
67
|
-
import { buildCompatibilityReport } from "../validation/compatibility-engine.ts";
|
|
68
|
-
import {
|
|
69
|
-
createPackageImportDiagnostics,
|
|
70
|
-
createValidationImportDiagnostics,
|
|
71
|
-
type ImportDiagnosticsResult,
|
|
72
|
-
} from "../validation/import-diagnostics.ts";
|
|
73
|
-
import type {
|
|
74
|
-
BlockNode,
|
|
75
|
-
FootnoteCollection,
|
|
76
|
-
HeaderDocument,
|
|
77
|
-
FooterDocument,
|
|
78
|
-
MediaCatalog,
|
|
79
|
-
NumberingCatalog,
|
|
80
|
-
OpaqueFragmentRecord,
|
|
81
|
-
PreservedPackagePart,
|
|
82
|
-
SubPartsCatalog,
|
|
83
|
-
} from "../model/canonical-document.ts";
|
|
84
|
-
import { createCanonicalDocumentSignature } from "../model/canonical-document.ts";
|
|
85
|
-
import type {
|
|
86
|
-
CommentImportDiagnostic,
|
|
87
|
-
ImportedCommentDefinition,
|
|
88
|
-
ParsedCommentsResult,
|
|
89
|
-
} from "./ooxml/parse-comments.ts";
|
|
90
|
-
import { createReadOnlyDiagnosticsRuntime } from "../runtime/read-only-diagnostics-runtime.ts";
|
|
91
|
-
import {
|
|
92
|
-
WORD_NUMBERING_CONTENT_TYPE,
|
|
93
|
-
hasSerializableNumberingEntries,
|
|
94
|
-
serializeNumberingXml,
|
|
95
|
-
} from "./export/serialize-numbering.ts";
|
|
96
|
-
import {
|
|
97
|
-
parseHeaderFooterReferences,
|
|
98
|
-
parseHeaderXml,
|
|
99
|
-
parseFooterXml,
|
|
100
|
-
} from "./ooxml/parse-headers-footers.ts";
|
|
101
|
-
import { parseFootnotesXml, parseEndnotesXml } from "./ooxml/parse-footnotes.ts";
|
|
102
|
-
import { parseThemeXml } from "./ooxml/parse-theme.ts";
|
|
103
|
-
import { resolveTheme } from "./ooxml/parse-theme.ts";
|
|
104
|
-
import { parseSettingsXml } from "./ooxml/parse-settings.ts";
|
|
105
|
-
import { parseStylesXml, type ParseStylesResult } from "./ooxml/parse-styles.ts";
|
|
106
|
-
import {
|
|
107
|
-
serializeHeaderXml,
|
|
108
|
-
serializeFooterXml,
|
|
109
|
-
WORD_HEADER_CONTENT_TYPE,
|
|
110
|
-
WORD_FOOTER_CONTENT_TYPE,
|
|
111
|
-
} from "./export/serialize-headers-footers.ts";
|
|
112
|
-
import {
|
|
113
|
-
serializeFootnotesXml,
|
|
114
|
-
serializeEndnotesXml,
|
|
115
|
-
WORD_FOOTNOTES_CONTENT_TYPE,
|
|
116
|
-
WORD_ENDNOTES_CONTENT_TYPE,
|
|
117
|
-
} from "./export/serialize-footnotes.ts";
|
|
118
|
-
import { createPersistedSourcePackage } from "./source-package-provenance.ts";
|
|
119
|
-
import { validatePersistedEditorSnapshot } from "../model/snapshot.ts";
|
|
120
|
-
import {
|
|
121
|
-
createSyntheticDocxNullNumberingCatalog,
|
|
122
|
-
DOCX_NULL_NUMBERING_INSTANCE_ID,
|
|
123
|
-
} from "./ooxml/numbering-sentinels.ts";
|
|
124
|
-
|
|
125
|
-
const MAIN_DOCUMENT_PATH = "/word/document.xml";
|
|
126
|
-
const NUMBERING_PART_PATH = "/word/numbering.xml";
|
|
127
|
-
const COMMENTS_PART_PATH = "/word/comments.xml";
|
|
128
|
-
const COMMENTS_EXTENDED_PART_PATH = "/word/commentsExtended.xml";
|
|
129
|
-
const COMMENTS_IDS_PART_PATH = "/word/commentsIds.xml";
|
|
130
|
-
const PEOPLE_PART_PATH = "/word/people.xml";
|
|
131
|
-
const APP_PROPERTIES_PART_PATH = "/docProps/app.xml";
|
|
132
|
-
const CORE_PROPERTIES_PART_PATH = "/docProps/core.xml";
|
|
133
|
-
const MAIN_DOCUMENT_CONTENT_TYPE =
|
|
134
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
|
|
135
|
-
const APP_PROPERTIES_CONTENT_TYPE =
|
|
136
|
-
"application/vnd.openxmlformats-officedocument.extended-properties+xml";
|
|
137
|
-
const CORE_PROPERTIES_CONTENT_TYPE =
|
|
138
|
-
"application/vnd.openxmlformats-package.core-properties+xml";
|
|
139
|
-
const NUMBERING_RELATIONSHIP_TYPE =
|
|
140
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
|
|
141
|
-
const COMMENTS_CONTENT_TYPE =
|
|
142
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml";
|
|
143
|
-
const COMMENTS_EXTENDED_CONTENT_TYPE =
|
|
144
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml";
|
|
145
|
-
const COMMENTS_IDS_CONTENT_TYPE =
|
|
146
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml";
|
|
147
|
-
const PEOPLE_CONTENT_TYPE =
|
|
148
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.people+xml";
|
|
149
|
-
const COMMENTS_RELATIONSHIP_TYPE =
|
|
150
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
|
|
151
|
-
const COMMENTS_EXTENDED_RELATIONSHIP_TYPE =
|
|
152
|
-
"http://schemas.microsoft.com/office/2011/relationships/commentsExtended";
|
|
153
|
-
const COMMENTS_IDS_RELATIONSHIP_TYPE =
|
|
154
|
-
"http://schemas.microsoft.com/office/2016/09/relationships/commentsIds";
|
|
155
|
-
const PEOPLE_RELATIONSHIP_TYPE =
|
|
156
|
-
"http://schemas.microsoft.com/office/2011/relationships/people";
|
|
157
|
-
const APP_PROPERTIES_RELATIONSHIP_TYPE =
|
|
158
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
|
|
159
|
-
const CORE_PROPERTIES_RELATIONSHIP_TYPE =
|
|
160
|
-
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
|
|
161
|
-
const OFFICE_DOCUMENT_RELATIONSHIP_TYPE =
|
|
162
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
163
|
-
const HEADER_RELATIONSHIP_TYPE =
|
|
164
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
|
|
165
|
-
const FOOTER_RELATIONSHIP_TYPE =
|
|
166
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
|
|
167
|
-
const FOOTNOTES_RELATIONSHIP_TYPE =
|
|
168
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
|
|
169
|
-
const ENDNOTES_RELATIONSHIP_TYPE =
|
|
170
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes";
|
|
171
|
-
const SETTINGS_RELATIONSHIP_TYPE =
|
|
172
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings";
|
|
173
|
-
const STYLES_RELATIONSHIP_TYPE =
|
|
174
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
|
175
|
-
const STYLES_PART_PATH = "/word/styles.xml";
|
|
176
|
-
const FOOTNOTES_PART_PATH = "/word/footnotes.xml";
|
|
177
|
-
const ENDNOTES_PART_PATH = "/word/endnotes.xml";
|
|
178
|
-
const SETTINGS_PART_PATH = "/word/settings.xml";
|
|
179
|
-
|
|
180
|
-
interface LoadDocxEditorSessionOptions {
|
|
181
|
-
documentId: string;
|
|
182
|
-
sourceLabel?: string;
|
|
183
|
-
bytes: Uint8Array | ArrayBuffer;
|
|
184
|
-
editorBuild?: string;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export interface LoadedDocxEditorSession {
|
|
188
|
-
initialSessionState: EditorSessionState;
|
|
189
|
-
initialSnapshot: PersistedEditorSnapshot;
|
|
190
|
-
fatalError?: EditorError;
|
|
191
|
-
readOnly: boolean;
|
|
192
|
-
protectionSnapshot: ProtectionSnapshot;
|
|
193
|
-
exportDocx: (
|
|
194
|
-
sessionState: EditorSessionState | PersistedEditorSnapshot,
|
|
195
|
-
options?: ExportDocxOptions,
|
|
196
|
-
) => Promise<ExportResult>;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
interface ImportedDocxState {
|
|
200
|
-
sourceBytes: Uint8Array;
|
|
201
|
-
sourcePackage: OpcPackage;
|
|
202
|
-
sourceDocumentPartPath: string;
|
|
203
|
-
sourceDocumentRelationships: readonly OpcRelationship[];
|
|
204
|
-
sourceDocumentAttributes: Record<string, string>;
|
|
205
|
-
sourceNumberingPartPath?: string;
|
|
206
|
-
sourceNumberingRelationshipId?: string;
|
|
207
|
-
sourceCommentsPartPath?: string;
|
|
208
|
-
sourceCommentsRelationshipId?: string;
|
|
209
|
-
sourceCommentsRootTag?: string;
|
|
210
|
-
sourceCommentsExtendedPartPath?: string;
|
|
211
|
-
sourceCommentsExtendedRelationshipId?: string;
|
|
212
|
-
sourceCommentsExtendedRootTag?: string;
|
|
213
|
-
sourceCommentsIdsPartPath?: string;
|
|
214
|
-
sourceCommentsIdsRelationshipId?: string;
|
|
215
|
-
sourceCommentsIdsRootTag?: string;
|
|
216
|
-
sourcePeoplePartPath?: string;
|
|
217
|
-
sourcePeopleRelationshipId?: string;
|
|
218
|
-
sourcePeopleRootTag?: string;
|
|
219
|
-
sourcePeopleAuthors: readonly string[];
|
|
220
|
-
protectionSnapshot: ProtectionSnapshot;
|
|
221
|
-
preservedCommentDefinitions: readonly ImportedCommentDefinition[];
|
|
222
|
-
blockingCommentDiagnostics: readonly CommentImportDiagnostic[];
|
|
223
|
-
initialCanonicalSignature: string;
|
|
224
|
-
sourceSubPartPaths: {
|
|
225
|
-
headers: Array<{ partPath: string; relationshipId: string }>;
|
|
226
|
-
footers: Array<{ partPath: string; relationshipId: string }>;
|
|
227
|
-
footnotesPartPath?: string;
|
|
228
|
-
footnotesRelationshipId?: string;
|
|
229
|
-
endnotesPartPath?: string;
|
|
230
|
-
endnotesRelationshipId?: string;
|
|
231
|
-
themePartPath?: string;
|
|
232
|
-
themeRelationshipId?: string;
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
interface NormalizedImportedCommentsResult extends ParsedCommentsResult {
|
|
237
|
-
preservedDefinitions: readonly ImportedCommentDefinition[];
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const BLOCKING_COMMENT_DIAGNOSTIC_CODES = new Set<CommentImportDiagnostic["code"]>([
|
|
241
|
-
"missing_comment_definition",
|
|
242
|
-
"missing_anchor_reference",
|
|
243
|
-
"multi_paragraph_anchor_preserve_only",
|
|
244
|
-
"opaque_anchor_preserve_only",
|
|
245
|
-
"preserve_only_revision_overlap",
|
|
246
|
-
]);
|
|
247
|
-
|
|
248
|
-
export function loadDocxEditorSession(
|
|
249
|
-
options: LoadDocxEditorSessionOptions,
|
|
250
|
-
): LoadedDocxEditorSession {
|
|
251
|
-
const editorBuild =
|
|
252
|
-
typeof options.editorBuild === "string" && options.editorBuild.length > 0
|
|
253
|
-
? options.editorBuild
|
|
254
|
-
: "dev";
|
|
255
|
-
const sourceBytes = toUint8Array(options.bytes);
|
|
256
|
-
let sourcePackage: OpcPackage;
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
sourcePackage = readOpcPackage(sourceBytes);
|
|
260
|
-
} catch (error) {
|
|
261
|
-
return createDiagnosticsSession(
|
|
262
|
-
options,
|
|
263
|
-
createPackageImportDiagnostics({
|
|
264
|
-
issue: classifyCorruptPackageError(error),
|
|
265
|
-
}),
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
|
|
270
|
-
const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
|
|
271
|
-
sourcePackage,
|
|
272
|
-
mainDocumentPath,
|
|
273
|
-
);
|
|
274
|
-
if (brokenRelationshipIssues.length > 0) {
|
|
275
|
-
return createDiagnosticsSession(
|
|
276
|
-
options,
|
|
277
|
-
createPackageImportDiagnostics({
|
|
278
|
-
issue: {
|
|
279
|
-
...brokenRelationshipIssues[0],
|
|
280
|
-
message: summarizeBrokenRelationshipIssues(brokenRelationshipIssues),
|
|
281
|
-
details: {
|
|
282
|
-
issueCount: brokenRelationshipIssues.length,
|
|
283
|
-
targets: brokenRelationshipIssues.map((issue) => issue.targetPartPath).filter(Boolean),
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
}),
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (!mainDocumentPath) {
|
|
291
|
-
return createDiagnosticsSession(
|
|
292
|
-
options,
|
|
293
|
-
createPackageImportDiagnostics({
|
|
294
|
-
issue: createMissingPartIssue(MAIN_DOCUMENT_PATH),
|
|
295
|
-
}),
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const documentPart = sourcePackage.parts.get(mainDocumentPath);
|
|
300
|
-
if (!documentPart) {
|
|
301
|
-
return createDiagnosticsSession(
|
|
302
|
-
options,
|
|
303
|
-
createPackageImportDiagnostics({
|
|
304
|
-
issue: createMissingPartIssue(mainDocumentPath),
|
|
305
|
-
}),
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
if (documentPart.contentType !== MAIN_DOCUMENT_CONTENT_TYPE) {
|
|
309
|
-
return createDiagnosticsSession(
|
|
310
|
-
options,
|
|
311
|
-
createValidationImportDiagnostics({
|
|
312
|
-
message: `DOCX main document part ${mainDocumentPath} must use content type ${MAIN_DOCUMENT_CONTENT_TYPE}.`,
|
|
313
|
-
}),
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
const sourceDocumentXml = decodeUtf8(documentPart.bytes);
|
|
319
|
-
const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
|
|
320
|
-
const numberingPartPath = resolveDocumentRelatedPartPath(
|
|
321
|
-
sourcePackage,
|
|
322
|
-
mainDocumentPath,
|
|
323
|
-
documentPart.relationships,
|
|
324
|
-
NUMBERING_RELATIONSHIP_TYPE,
|
|
325
|
-
NUMBERING_PART_PATH,
|
|
326
|
-
);
|
|
327
|
-
const parsedNumbering = numberingPartPath
|
|
328
|
-
? parseNumberingXml(
|
|
329
|
-
decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
|
|
330
|
-
)
|
|
331
|
-
: createEmptyNumberingCatalog();
|
|
332
|
-
const mediaParts = collectInlineMediaParts(sourcePackage);
|
|
333
|
-
const parsedDocument = parseMainDocumentXml(
|
|
334
|
-
sourceDocumentXml,
|
|
335
|
-
documentPart.relationships,
|
|
336
|
-
mediaParts,
|
|
337
|
-
mainDocumentPath,
|
|
338
|
-
);
|
|
339
|
-
const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
|
|
340
|
-
const normalizedDocument = normalizeParsedTextDocument(
|
|
341
|
-
parsedDocument,
|
|
342
|
-
mainDocumentPath,
|
|
343
|
-
);
|
|
344
|
-
const commentsPartPath = resolveCommentsPartPath(
|
|
345
|
-
sourcePackage,
|
|
346
|
-
mainDocumentPath,
|
|
347
|
-
documentPart.relationships,
|
|
348
|
-
);
|
|
349
|
-
const commentsExtendedPartPath = resolveDocumentRelatedPartPath(
|
|
350
|
-
sourcePackage,
|
|
351
|
-
mainDocumentPath,
|
|
352
|
-
documentPart.relationships,
|
|
353
|
-
COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
|
|
354
|
-
COMMENTS_EXTENDED_PART_PATH,
|
|
355
|
-
);
|
|
356
|
-
const commentsIdsPartPath = resolveDocumentRelatedPartPath(
|
|
357
|
-
sourcePackage,
|
|
358
|
-
mainDocumentPath,
|
|
359
|
-
documentPart.relationships,
|
|
360
|
-
COMMENTS_IDS_RELATIONSHIP_TYPE,
|
|
361
|
-
COMMENTS_IDS_PART_PATH,
|
|
362
|
-
);
|
|
363
|
-
const peoplePartPath = resolveDocumentRelatedPartPath(
|
|
364
|
-
sourcePackage,
|
|
365
|
-
mainDocumentPath,
|
|
366
|
-
documentPart.relationships,
|
|
367
|
-
PEOPLE_RELATIONSHIP_TYPE,
|
|
368
|
-
PEOPLE_PART_PATH,
|
|
369
|
-
);
|
|
370
|
-
const parsedComments = commentsPartPath
|
|
371
|
-
? parseCommentsFromOoxml(
|
|
372
|
-
sourceDocumentXml,
|
|
373
|
-
{
|
|
374
|
-
commentsXml: decodeUtf8(sourcePackage.parts.get(commentsPartPath)?.bytes ?? new Uint8Array()),
|
|
375
|
-
commentsExtendedXml: decodeUtf8(
|
|
376
|
-
sourcePackage.parts.get(commentsExtendedPartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
377
|
-
),
|
|
378
|
-
commentsIdsXml: decodeUtf8(
|
|
379
|
-
sourcePackage.parts.get(commentsIdsPartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
380
|
-
),
|
|
381
|
-
peopleXml: decodeUtf8(
|
|
382
|
-
sourcePackage.parts.get(peoplePartPath ?? "")?.bytes ?? new Uint8Array(),
|
|
383
|
-
),
|
|
384
|
-
},
|
|
385
|
-
)
|
|
386
|
-
: {
|
|
387
|
-
threads: [] as CommentThread[],
|
|
388
|
-
diagnostics: [] as CommentImportDiagnostic[],
|
|
389
|
-
definitions: [] as ImportedCommentDefinition[],
|
|
390
|
-
sourceRootTag: undefined,
|
|
391
|
-
sourceExtendedRootTag: undefined,
|
|
392
|
-
sourceIdsRootTag: undefined,
|
|
393
|
-
sourcePeopleRootTag: undefined,
|
|
394
|
-
peopleAuthors: [] as string[],
|
|
395
|
-
};
|
|
396
|
-
const normalizedRevisions = normalizeImportedRevisionRecords(
|
|
397
|
-
importedRevisions,
|
|
398
|
-
normalizedDocument.content,
|
|
399
|
-
normalizedDocument.preservation.opaqueFragments,
|
|
400
|
-
);
|
|
401
|
-
const normalizedComments = normalizeImportedCommentThreads(
|
|
402
|
-
parsedComments,
|
|
403
|
-
normalizedDocument.preservation.opaqueFragments,
|
|
404
|
-
normalizedRevisions.revisions,
|
|
405
|
-
);
|
|
406
|
-
const subPartOpaqueState = createSubPartOpaqueImportState(
|
|
407
|
-
normalizedDocument.preservation.opaqueFragments,
|
|
408
|
-
normalizedDocument.diagnostics.warnings,
|
|
409
|
-
);
|
|
410
|
-
// ---- Parse sub-parts: headers, footers, footnotes, endnotes, theme ----
|
|
411
|
-
const headerFooterRefs = parseHeaderFooterReferences(sourceDocumentXml);
|
|
412
|
-
const parsedHeaders: HeaderDocument[] = [];
|
|
413
|
-
const parsedFooters: FooterDocument[] = [];
|
|
414
|
-
const sourceHeaderPaths: Array<{ partPath: string; relationshipId: string }> = [];
|
|
415
|
-
const sourceFooterPaths: Array<{ partPath: string; relationshipId: string }> = [];
|
|
416
|
-
const seenSubPartKeys = new Set<string>();
|
|
417
|
-
|
|
418
|
-
for (const ref of headerFooterRefs) {
|
|
419
|
-
const dedupeKey = `${ref.kind}:${ref.variant}:${ref.relationshipId}`;
|
|
420
|
-
if (seenSubPartKeys.has(dedupeKey)) {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
seenSubPartKeys.add(dedupeKey);
|
|
424
|
-
|
|
425
|
-
const relationship = documentPart.relationships.find(
|
|
426
|
-
(r) => r.id === ref.relationshipId && r.targetMode === "internal",
|
|
427
|
-
);
|
|
428
|
-
if (!relationship) {
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const partPath = resolveRelationshipTarget(mainDocumentPath, relationship);
|
|
433
|
-
const partBytes = sourcePackage.parts.get(partPath)?.bytes;
|
|
434
|
-
if (!partBytes) {
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const xml = decodeUtf8(partBytes);
|
|
439
|
-
if (ref.kind === "header") {
|
|
440
|
-
const parsed = parseHeaderXml(xml);
|
|
441
|
-
parsedHeaders.push({
|
|
442
|
-
variant: ref.variant,
|
|
443
|
-
partPath,
|
|
444
|
-
relationshipId: ref.relationshipId,
|
|
445
|
-
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
446
|
-
blocks: normalizeSubPartOpaqueBlocks(
|
|
447
|
-
parsed.blocks,
|
|
448
|
-
normalizedDocument.preservation.opaqueFragments,
|
|
449
|
-
normalizedDocument.diagnostics.warnings,
|
|
450
|
-
partPath,
|
|
451
|
-
subPartOpaqueState,
|
|
452
|
-
),
|
|
453
|
-
});
|
|
454
|
-
sourceHeaderPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
455
|
-
} else {
|
|
456
|
-
const parsed = parseFooterXml(xml);
|
|
457
|
-
parsedFooters.push({
|
|
458
|
-
variant: ref.variant,
|
|
459
|
-
partPath,
|
|
460
|
-
relationshipId: ref.relationshipId,
|
|
461
|
-
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
462
|
-
blocks: normalizeSubPartOpaqueBlocks(
|
|
463
|
-
parsed.blocks,
|
|
464
|
-
normalizedDocument.preservation.opaqueFragments,
|
|
465
|
-
normalizedDocument.diagnostics.warnings,
|
|
466
|
-
partPath,
|
|
467
|
-
subPartOpaqueState,
|
|
468
|
-
),
|
|
469
|
-
});
|
|
470
|
-
sourceFooterPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const footnotesPartPath = resolveDocumentRelatedPartPath(
|
|
475
|
-
sourcePackage,
|
|
476
|
-
mainDocumentPath,
|
|
477
|
-
documentPart.relationships,
|
|
478
|
-
FOOTNOTES_RELATIONSHIP_TYPE,
|
|
479
|
-
FOOTNOTES_PART_PATH,
|
|
480
|
-
);
|
|
481
|
-
const footnotesRelationshipId = documentPart.relationships.find(
|
|
482
|
-
(r) => r.type === FOOTNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
|
|
483
|
-
)?.id;
|
|
484
|
-
const endnotesPartPath = resolveDocumentRelatedPartPath(
|
|
485
|
-
sourcePackage,
|
|
486
|
-
mainDocumentPath,
|
|
487
|
-
documentPart.relationships,
|
|
488
|
-
ENDNOTES_RELATIONSHIP_TYPE,
|
|
489
|
-
ENDNOTES_PART_PATH,
|
|
490
|
-
);
|
|
491
|
-
const endnotesRelationshipId = documentPart.relationships.find(
|
|
492
|
-
(r) => r.type === ENDNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
|
|
493
|
-
)?.id;
|
|
494
|
-
|
|
495
|
-
let footnoteCollection: FootnoteCollection | undefined;
|
|
496
|
-
if (footnotesPartPath) {
|
|
497
|
-
footnoteCollection = parseFootnotesXml(
|
|
498
|
-
decodeUtf8(sourcePackage.parts.get(footnotesPartPath)?.bytes ?? new Uint8Array()),
|
|
499
|
-
);
|
|
500
|
-
normalizeFootnoteCollectionOpaqueBlocks(
|
|
501
|
-
footnoteCollection,
|
|
502
|
-
"footnote",
|
|
503
|
-
normalizedDocument.preservation.opaqueFragments,
|
|
504
|
-
normalizedDocument.diagnostics.warnings,
|
|
505
|
-
footnotesPartPath,
|
|
506
|
-
subPartOpaqueState,
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
if (endnotesPartPath) {
|
|
510
|
-
footnoteCollection = parseEndnotesXml(
|
|
511
|
-
decodeUtf8(sourcePackage.parts.get(endnotesPartPath)?.bytes ?? new Uint8Array()),
|
|
512
|
-
footnoteCollection,
|
|
513
|
-
);
|
|
514
|
-
normalizeFootnoteCollectionOpaqueBlocks(
|
|
515
|
-
footnoteCollection,
|
|
516
|
-
"endnote",
|
|
517
|
-
normalizedDocument.preservation.opaqueFragments,
|
|
518
|
-
normalizedDocument.diagnostics.warnings,
|
|
519
|
-
endnotesPartPath,
|
|
520
|
-
subPartOpaqueState,
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const themeRelationship = documentPart.relationships.find(
|
|
525
|
-
(r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" &&
|
|
526
|
-
r.targetMode === "internal",
|
|
527
|
-
);
|
|
528
|
-
const themePartPath = themeRelationship
|
|
529
|
-
? resolveRelationshipTarget(mainDocumentPath, themeRelationship)
|
|
530
|
-
: undefined;
|
|
531
|
-
const parsedTheme =
|
|
532
|
-
themePartPath && sourcePackage.parts.has(themePartPath)
|
|
533
|
-
? parseThemeXml(
|
|
534
|
-
decodeUtf8(sourcePackage.parts.get(themePartPath)?.bytes ?? new Uint8Array()),
|
|
535
|
-
)
|
|
536
|
-
: undefined;
|
|
537
|
-
const resolvedTheme = parsedTheme ? resolveTheme(parsedTheme) : undefined;
|
|
538
|
-
const settingsPartPath = resolveDocumentRelatedPartPath(
|
|
539
|
-
sourcePackage,
|
|
540
|
-
mainDocumentPath,
|
|
541
|
-
documentPart.relationships,
|
|
542
|
-
SETTINGS_RELATIONSHIP_TYPE,
|
|
543
|
-
SETTINGS_PART_PATH,
|
|
544
|
-
);
|
|
545
|
-
const parsedSettings =
|
|
546
|
-
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
547
|
-
? parseSettingsXml(
|
|
548
|
-
decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
|
|
549
|
-
)
|
|
550
|
-
: undefined;
|
|
551
|
-
const settingsXmlForProtection =
|
|
552
|
-
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
553
|
-
? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
|
|
554
|
-
: "";
|
|
555
|
-
const documentProtection = extractDocumentProtection(settingsXmlForProtection);
|
|
556
|
-
const importedProtectionSnapshot = buildProtectionSnapshot(documentProtection, protectionRanges);
|
|
557
|
-
|
|
558
|
-
// ---- Parse styles.xml for canonical style catalog ----
|
|
559
|
-
const stylesPartPath = resolveDocumentRelatedPartPath(
|
|
560
|
-
sourcePackage,
|
|
561
|
-
mainDocumentPath,
|
|
562
|
-
documentPart.relationships,
|
|
563
|
-
STYLES_RELATIONSHIP_TYPE,
|
|
564
|
-
STYLES_PART_PATH,
|
|
565
|
-
);
|
|
566
|
-
const parsedStyles =
|
|
567
|
-
stylesPartPath && sourcePackage.parts.has(stylesPartPath)
|
|
568
|
-
? parseStylesXml(
|
|
569
|
-
decodeUtf8(sourcePackage.parts.get(stylesPartPath)?.bytes ?? new Uint8Array()),
|
|
570
|
-
)
|
|
571
|
-
: parseStylesXml("");
|
|
572
|
-
|
|
573
|
-
const subParts: SubPartsCatalog | undefined =
|
|
574
|
-
parsedHeaders.length > 0 ||
|
|
575
|
-
parsedFooters.length > 0 ||
|
|
576
|
-
footnoteCollection !== undefined ||
|
|
577
|
-
parsedTheme !== undefined ||
|
|
578
|
-
normalizedDocument.finalSectionProperties !== undefined ||
|
|
579
|
-
resolvedTheme !== undefined ||
|
|
580
|
-
parsedSettings !== undefined
|
|
581
|
-
? {
|
|
582
|
-
headers: parsedHeaders,
|
|
583
|
-
footers: parsedFooters,
|
|
584
|
-
...(footnoteCollection !== undefined ? { footnoteCollection } : {}),
|
|
585
|
-
...(parsedTheme !== undefined ? { theme: parsedTheme } : {}),
|
|
586
|
-
...(normalizedDocument.finalSectionProperties !== undefined
|
|
587
|
-
? { finalSectionProperties: normalizedDocument.finalSectionProperties }
|
|
588
|
-
: {}),
|
|
589
|
-
...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
|
|
590
|
-
...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
|
|
591
|
-
}
|
|
592
|
-
: undefined;
|
|
593
|
-
|
|
594
|
-
const timestamp = new Date().toISOString();
|
|
595
|
-
const document = createImportedCanonicalDocument({
|
|
596
|
-
documentId: options.documentId,
|
|
597
|
-
timestamp,
|
|
598
|
-
numbering: parsedNumbering,
|
|
599
|
-
media: normalizedDocument.media,
|
|
600
|
-
content: normalizedDocument.content,
|
|
601
|
-
subParts,
|
|
602
|
-
parsedStyles,
|
|
603
|
-
preservation: {
|
|
604
|
-
...normalizedDocument.preservation,
|
|
605
|
-
packageParts: {
|
|
606
|
-
...normalizedDocument.preservation.packageParts,
|
|
607
|
-
...collectPreservedPackageParts(sourcePackage, [
|
|
608
|
-
mainDocumentPath,
|
|
609
|
-
numberingPartPath,
|
|
610
|
-
commentsPartPath,
|
|
611
|
-
commentsExtendedPartPath,
|
|
612
|
-
commentsIdsPartPath,
|
|
613
|
-
peoplePartPath,
|
|
614
|
-
]),
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
diagnostics: {
|
|
618
|
-
warnings: [
|
|
619
|
-
...createBrokenRelationshipWarnings(sourcePackage, mainDocumentPath),
|
|
620
|
-
...normalizedDocument.diagnostics.warnings,
|
|
621
|
-
...normalizedRevisions.diagnostics.map((diagnostic, index) => ({
|
|
622
|
-
diagnosticId: `diagnostic:revision-import-${index + 1}`,
|
|
623
|
-
warningId: `warning:revision-import-${diagnostic.revisionId}`,
|
|
624
|
-
source: "review" as const,
|
|
625
|
-
message: diagnostic.message,
|
|
626
|
-
})),
|
|
627
|
-
...normalizedComments.diagnostics.map((diagnostic, index) => ({
|
|
628
|
-
diagnosticId: `diagnostic:comment-import-${index + 1}`,
|
|
629
|
-
warningId: `warning:comment-import-${diagnostic.commentId}`,
|
|
630
|
-
source: "review" as const,
|
|
631
|
-
message: diagnostic.message,
|
|
632
|
-
})),
|
|
633
|
-
],
|
|
634
|
-
errors: [],
|
|
635
|
-
},
|
|
636
|
-
review: {
|
|
637
|
-
comments: toRuntimeCommentRecords(normalizedComments.threads),
|
|
638
|
-
revisions: toRuntimeRevisionRecords(normalizedRevisions.revisions),
|
|
639
|
-
},
|
|
640
|
-
});
|
|
641
|
-
const compatibility = buildCompatibilityReport({
|
|
642
|
-
document,
|
|
643
|
-
generatedAt: timestamp,
|
|
644
|
-
});
|
|
645
|
-
const snapshot = createImportedSnapshot({
|
|
646
|
-
documentId: options.documentId,
|
|
647
|
-
editorBuild,
|
|
648
|
-
timestamp,
|
|
649
|
-
document,
|
|
650
|
-
compatibility: toPublicCompatibilityReport(compatibility),
|
|
651
|
-
protectionSnapshot: importedProtectionSnapshot,
|
|
652
|
-
sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
|
|
653
|
-
});
|
|
654
|
-
const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
|
|
655
|
-
if (snapshotIssues.length > 0) {
|
|
656
|
-
const firstIssue = snapshotIssues[0];
|
|
657
|
-
return createDiagnosticsSession(
|
|
658
|
-
options,
|
|
659
|
-
createValidationImportDiagnostics({
|
|
660
|
-
message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
|
|
661
|
-
source: "import",
|
|
662
|
-
details: {
|
|
663
|
-
issueCount: snapshotIssues.length,
|
|
664
|
-
firstIssuePath: firstIssue?.path,
|
|
665
|
-
},
|
|
666
|
-
}),
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
const initialSessionState = editorSessionStateFromPersistedSnapshot(snapshot);
|
|
670
|
-
const importedState: ImportedDocxState = {
|
|
671
|
-
sourceBytes: new Uint8Array(sourceBytes),
|
|
672
|
-
sourcePackage,
|
|
673
|
-
sourceDocumentPartPath: mainDocumentPath,
|
|
674
|
-
sourceDocumentRelationships: documentPart.relationships,
|
|
675
|
-
sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
|
|
676
|
-
sourceNumberingPartPath: numberingPartPath,
|
|
677
|
-
sourceNumberingRelationshipId: documentPart.relationships.find(
|
|
678
|
-
(relationship) =>
|
|
679
|
-
relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
|
|
680
|
-
relationship.targetMode === "internal",
|
|
681
|
-
)?.id,
|
|
682
|
-
sourceCommentsPartPath: commentsPartPath,
|
|
683
|
-
sourceCommentsRelationshipId: documentPart.relationships.find(
|
|
684
|
-
(relationship) =>
|
|
685
|
-
relationship.type === COMMENTS_RELATIONSHIP_TYPE &&
|
|
686
|
-
relationship.targetMode === "internal",
|
|
687
|
-
)?.id,
|
|
688
|
-
sourceCommentsRootTag: normalizedComments.sourceRootTag,
|
|
689
|
-
sourceCommentsExtendedPartPath: commentsExtendedPartPath,
|
|
690
|
-
sourceCommentsExtendedRelationshipId: documentPart.relationships.find(
|
|
691
|
-
(relationship) =>
|
|
692
|
-
relationship.type === COMMENTS_EXTENDED_RELATIONSHIP_TYPE &&
|
|
693
|
-
relationship.targetMode === "internal",
|
|
694
|
-
)?.id,
|
|
695
|
-
sourceCommentsExtendedRootTag: normalizedComments.sourceExtendedRootTag,
|
|
696
|
-
sourceCommentsIdsPartPath: commentsIdsPartPath,
|
|
697
|
-
sourceCommentsIdsRelationshipId: documentPart.relationships.find(
|
|
698
|
-
(relationship) =>
|
|
699
|
-
relationship.type === COMMENTS_IDS_RELATIONSHIP_TYPE &&
|
|
700
|
-
relationship.targetMode === "internal",
|
|
701
|
-
)?.id,
|
|
702
|
-
sourceCommentsIdsRootTag: normalizedComments.sourceIdsRootTag,
|
|
703
|
-
sourcePeoplePartPath: peoplePartPath,
|
|
704
|
-
sourcePeopleRelationshipId: documentPart.relationships.find(
|
|
705
|
-
(relationship) =>
|
|
706
|
-
relationship.type === PEOPLE_RELATIONSHIP_TYPE &&
|
|
707
|
-
relationship.targetMode === "internal",
|
|
708
|
-
)?.id,
|
|
709
|
-
sourcePeopleRootTag: normalizedComments.sourcePeopleRootTag,
|
|
710
|
-
sourcePeopleAuthors: normalizedComments.peopleAuthors,
|
|
711
|
-
protectionSnapshot: buildProtectionSnapshot(documentProtection, protectionRanges),
|
|
712
|
-
preservedCommentDefinitions: normalizedComments.preservedDefinitions,
|
|
713
|
-
blockingCommentDiagnostics: normalizedComments.diagnostics.filter((diagnostic) =>
|
|
714
|
-
BLOCKING_COMMENT_DIAGNOSTIC_CODES.has(diagnostic.code),
|
|
715
|
-
),
|
|
716
|
-
initialCanonicalSignature: serializeCanonicalDocumentForExport(document),
|
|
717
|
-
sourceSubPartPaths: {
|
|
718
|
-
headers: sourceHeaderPaths,
|
|
719
|
-
footers: sourceFooterPaths,
|
|
720
|
-
footnotesPartPath,
|
|
721
|
-
footnotesRelationshipId,
|
|
722
|
-
endnotesPartPath,
|
|
723
|
-
endnotesRelationshipId,
|
|
724
|
-
themePartPath,
|
|
725
|
-
themeRelationshipId: themeRelationship?.id,
|
|
726
|
-
},
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
return {
|
|
730
|
-
initialSessionState,
|
|
731
|
-
initialSnapshot: snapshot,
|
|
732
|
-
readOnly: false,
|
|
733
|
-
protectionSnapshot: importedProtectionSnapshot,
|
|
734
|
-
exportDocx: async (nextSessionState, exportOptions) =>
|
|
735
|
-
exportDocxEditorSession(importedState, nextSessionState, exportOptions),
|
|
736
|
-
};
|
|
737
|
-
} catch (error) {
|
|
738
|
-
return createDiagnosticsSession(
|
|
739
|
-
options,
|
|
740
|
-
createImportDiagnosticsFromError(error),
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
function exportDocxEditorSession(
|
|
746
|
-
state: ImportedDocxState,
|
|
747
|
-
sessionStateOrSnapshot: EditorSessionState | PersistedEditorSnapshot,
|
|
748
|
-
options?: ExportDocxOptions,
|
|
749
|
-
): ExportResult {
|
|
750
|
-
const sessionState = toEditorSessionState(sessionStateOrSnapshot);
|
|
751
|
-
|
|
752
|
-
if (sessionState.compatibility.blockExport) {
|
|
753
|
-
throw new Error("DOCX export is blocked by the current compatibility report.");
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
const currentDocument = sessionState.canonicalDocument as CanonicalDocumentEnvelope;
|
|
757
|
-
const signatureMatch = serializeCanonicalDocumentForExport(currentDocument) ===
|
|
758
|
-
state.initialCanonicalSignature;
|
|
759
|
-
const canReuse = canReuseSourceBytesForCurrentDocument(state, currentDocument);
|
|
760
|
-
const commentCount = Object.keys(currentDocument.review?.comments ?? {}).length;
|
|
761
|
-
|
|
762
|
-
if (signatureMatch && canReuse) {
|
|
763
|
-
return {
|
|
764
|
-
bytes: new Uint8Array(state.sourceBytes),
|
|
765
|
-
mimeType: DOCX_MIME_TYPE,
|
|
766
|
-
fileName: options?.fileName ?? `${sessionState.documentId}.docx`,
|
|
767
|
-
delivery: {
|
|
768
|
-
mode: "exported-bytes-only",
|
|
769
|
-
},
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
const preservedCommentIds = new Set(
|
|
773
|
-
state.preservedCommentDefinitions.map((definition) => definition.commentId),
|
|
774
|
-
);
|
|
775
|
-
const blockingCommentCount = Math.max(
|
|
776
|
-
state.blockingCommentDiagnostics.length,
|
|
777
|
-
preservedCommentIds.size,
|
|
778
|
-
);
|
|
779
|
-
if (blockingCommentCount > 0) {
|
|
780
|
-
throw new Error(
|
|
781
|
-
`DOCX export is blocked because ${blockingCommentCount} preserve-only comment anchors cannot be safely remapped after runtime edits.`,
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
const currentRevisions = toReviewRevisionRecords(currentDocument.review.revisions);
|
|
785
|
-
const actionableRevisions = currentRevisions.filter(
|
|
786
|
-
(revision) => getRevisionActionability(revision) === "actionable",
|
|
787
|
-
);
|
|
788
|
-
const secondaryStoryActionableRevisions = actionableRevisions.filter(
|
|
789
|
-
(revision) => revision.metadata.storyTarget?.kind && revision.metadata.storyTarget.kind !== "main",
|
|
790
|
-
);
|
|
791
|
-
if (secondaryStoryActionableRevisions.length > 0) {
|
|
792
|
-
throw new Error(
|
|
793
|
-
`DOCX export is blocked because ${secondaryStoryActionableRevisions.length} secondary-story tracked changes cannot yet be serialized safely.`,
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
const commentThreads = Object.values(
|
|
797
|
-
createCommentStoreFromRuntimeComments(currentDocument.review.comments).threads,
|
|
798
|
-
);
|
|
799
|
-
const ownedCommentThreads = commentThreads.filter(
|
|
800
|
-
(thread) => !preservedCommentIds.has(thread.commentId),
|
|
801
|
-
);
|
|
802
|
-
const serialized = serializeMainDocument(
|
|
803
|
-
splitDocumentAtReviewBoundaries(
|
|
804
|
-
currentDocument.content as never,
|
|
805
|
-
ownedCommentThreads,
|
|
806
|
-
actionableRevisions,
|
|
807
|
-
) as never,
|
|
808
|
-
currentDocument.preservation as never,
|
|
809
|
-
state.sourceDocumentRelationships,
|
|
810
|
-
{
|
|
811
|
-
documentAttributes: state.sourceDocumentAttributes,
|
|
812
|
-
media: currentDocument.media as MediaCatalog,
|
|
813
|
-
finalSectionProperties: currentDocument.subParts?.finalSectionProperties,
|
|
814
|
-
},
|
|
815
|
-
);
|
|
816
|
-
const revisionDocument = serializeRuntimeRevisionsIntoDocumentXml(
|
|
817
|
-
serialized.documentXml,
|
|
818
|
-
actionableRevisions,
|
|
819
|
-
serialized.paragraphBoundaries,
|
|
820
|
-
);
|
|
821
|
-
if (revisionDocument.skippedRevisionIds.length > 0) {
|
|
822
|
-
throw new Error(
|
|
823
|
-
`DOCX export is blocked because ${revisionDocument.skippedRevisionIds.length} active revisions overlap unsupported serialization boundaries.`,
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
const strippedDocumentXml = stripCommentMarkup(
|
|
828
|
-
revisionDocument.documentXml,
|
|
829
|
-
ownedCommentThreads.map((thread) => thread.commentId),
|
|
830
|
-
);
|
|
831
|
-
const exportCommentIds = createCommentExportIdMap(
|
|
832
|
-
ownedCommentThreads,
|
|
833
|
-
state.preservedCommentDefinitions,
|
|
834
|
-
);
|
|
835
|
-
const serializedComments = serializeMergedCommentsXml(ownedCommentThreads, {
|
|
836
|
-
exportCommentIds,
|
|
837
|
-
preservedDefinitions: state.preservedCommentDefinitions,
|
|
838
|
-
sourceRootTag: state.sourceCommentsRootTag,
|
|
839
|
-
sourceExtendedRootTag: state.sourceCommentsExtendedRootTag,
|
|
840
|
-
sourceIdsRootTag: state.sourceCommentsIdsRootTag,
|
|
841
|
-
sourcePeopleRootTag: state.sourcePeopleRootTag,
|
|
842
|
-
peopleAuthors: state.sourcePeopleAuthors,
|
|
843
|
-
});
|
|
844
|
-
const annotatedDocument = serializeCommentAnchorsIntoDocumentXml(
|
|
845
|
-
strippedDocumentXml,
|
|
846
|
-
ownedCommentThreads,
|
|
847
|
-
undefined,
|
|
848
|
-
{
|
|
849
|
-
exportCommentIds,
|
|
850
|
-
},
|
|
851
|
-
);
|
|
852
|
-
const protectedDocumentXml = serializeProtectionRangesIntoDocumentXml(
|
|
853
|
-
annotatedDocument.documentXml,
|
|
854
|
-
state.protectionSnapshot,
|
|
855
|
-
);
|
|
856
|
-
const blockingSkippedCommentIds = annotatedDocument.skippedCommentIds.filter((commentId) => {
|
|
857
|
-
const thread = ownedCommentThreads.find((candidate) => candidate.commentId === commentId);
|
|
858
|
-
return !thread || thread.anchor.kind !== "detached";
|
|
859
|
-
});
|
|
860
|
-
if (blockingSkippedCommentIds.length > 0) {
|
|
861
|
-
throw new Error(
|
|
862
|
-
`DOCX export is blocked because ${blockingSkippedCommentIds.length} comments no longer map to serializable ranges.`,
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
const commentsPartPath =
|
|
866
|
-
state.sourceCommentsPartPath ?? COMMENTS_PART_PATH;
|
|
867
|
-
const commentsExtendedPartPath =
|
|
868
|
-
state.sourceCommentsExtendedPartPath ?? COMMENTS_EXTENDED_PART_PATH;
|
|
869
|
-
const commentsIdsPartPath =
|
|
870
|
-
state.sourceCommentsIdsPartPath ?? COMMENTS_IDS_PART_PATH;
|
|
871
|
-
const peoplePartPath =
|
|
872
|
-
state.sourcePeoplePartPath ?? PEOPLE_PART_PATH;
|
|
873
|
-
const numberingPartPath =
|
|
874
|
-
state.sourceNumberingPartPath ?? NUMBERING_PART_PATH;
|
|
875
|
-
const serializedNumberingXml = hasSerializableNumberingEntries(
|
|
876
|
-
currentDocument.numbering as NumberingCatalog,
|
|
877
|
-
)
|
|
878
|
-
? serializeNumberingXml(currentDocument.numbering as NumberingCatalog)
|
|
879
|
-
: undefined;
|
|
880
|
-
const nextRelationships = withDocumentRelatedParts(
|
|
881
|
-
serialized.relationships,
|
|
882
|
-
[
|
|
883
|
-
{
|
|
884
|
-
relationshipType: NUMBERING_RELATIONSHIP_TYPE,
|
|
885
|
-
partPath: numberingPartPath,
|
|
886
|
-
existingRelationshipId: state.sourceNumberingRelationshipId,
|
|
887
|
-
include:
|
|
888
|
-
Boolean(serializedNumberingXml) ||
|
|
889
|
-
Boolean(state.sourceNumberingPartPath),
|
|
890
|
-
},
|
|
891
|
-
{
|
|
892
|
-
relationshipType: COMMENTS_RELATIONSHIP_TYPE,
|
|
893
|
-
partPath: commentsPartPath,
|
|
894
|
-
existingRelationshipId: state.sourceCommentsRelationshipId,
|
|
895
|
-
include:
|
|
896
|
-
serializedComments.serializedCommentIds.length > 0 ||
|
|
897
|
-
Boolean(state.sourceCommentsPartPath),
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
relationshipType: COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
|
|
901
|
-
partPath: commentsExtendedPartPath,
|
|
902
|
-
existingRelationshipId: state.sourceCommentsExtendedRelationshipId,
|
|
903
|
-
include:
|
|
904
|
-
Boolean(serializedComments.commentsExtendedXml) ||
|
|
905
|
-
Boolean(state.sourceCommentsExtendedPartPath),
|
|
906
|
-
},
|
|
907
|
-
{
|
|
908
|
-
relationshipType: COMMENTS_IDS_RELATIONSHIP_TYPE,
|
|
909
|
-
partPath: commentsIdsPartPath,
|
|
910
|
-
existingRelationshipId: state.sourceCommentsIdsRelationshipId,
|
|
911
|
-
include:
|
|
912
|
-
Boolean(serializedComments.commentsIdsXml) ||
|
|
913
|
-
Boolean(state.sourceCommentsIdsPartPath),
|
|
914
|
-
},
|
|
915
|
-
{
|
|
916
|
-
relationshipType: PEOPLE_RELATIONSHIP_TYPE,
|
|
917
|
-
partPath: peoplePartPath,
|
|
918
|
-
existingRelationshipId: state.sourcePeopleRelationshipId,
|
|
919
|
-
include:
|
|
920
|
-
Boolean(serializedComments.peopleXml) ||
|
|
921
|
-
Boolean(state.sourcePeoplePartPath),
|
|
922
|
-
},
|
|
923
|
-
],
|
|
924
|
-
);
|
|
925
|
-
|
|
926
|
-
const exportedSubParts = currentDocument.subParts as SubPartsCatalog | undefined;
|
|
927
|
-
const subPartOwnedPaths: string[] = [];
|
|
928
|
-
if (exportedSubParts) {
|
|
929
|
-
for (const header of exportedSubParts.headers) {
|
|
930
|
-
subPartOwnedPaths.push(header.partPath);
|
|
931
|
-
}
|
|
932
|
-
for (const footer of exportedSubParts.footers) {
|
|
933
|
-
subPartOwnedPaths.push(footer.partPath);
|
|
934
|
-
}
|
|
935
|
-
if (exportedSubParts.footnoteCollection) {
|
|
936
|
-
if (state.sourceSubPartPaths.footnotesPartPath) {
|
|
937
|
-
subPartOwnedPaths.push(state.sourceSubPartPaths.footnotesPartPath);
|
|
938
|
-
}
|
|
939
|
-
if (state.sourceSubPartPaths.endnotesPartPath) {
|
|
940
|
-
subPartOwnedPaths.push(state.sourceSubPartPaths.endnotesPartPath);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
if (exportedSubParts.theme && state.sourceSubPartPaths.themePartPath) {
|
|
944
|
-
subPartOwnedPaths.push(state.sourceSubPartPaths.themePartPath);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
const exportSession = createExportSession(state.sourcePackage, [
|
|
949
|
-
state.sourceDocumentPartPath,
|
|
950
|
-
APP_PROPERTIES_PART_PATH,
|
|
951
|
-
CORE_PROPERTIES_PART_PATH,
|
|
952
|
-
numberingPartPath,
|
|
953
|
-
commentsPartPath,
|
|
954
|
-
commentsExtendedPartPath,
|
|
955
|
-
commentsIdsPartPath,
|
|
956
|
-
peoplePartPath,
|
|
957
|
-
...subPartOwnedPaths,
|
|
958
|
-
]);
|
|
959
|
-
|
|
960
|
-
exportSession.replaceOwnedPart({
|
|
961
|
-
path: state.sourceDocumentPartPath,
|
|
962
|
-
bytes: new TextEncoder().encode(protectedDocumentXml),
|
|
963
|
-
contentType: MAIN_DOCUMENT_CONTENT_TYPE,
|
|
964
|
-
relationships: nextRelationships,
|
|
965
|
-
});
|
|
966
|
-
|
|
967
|
-
if (serializedNumberingXml || state.sourceNumberingPartPath) {
|
|
968
|
-
exportSession.replaceOwnedPart({
|
|
969
|
-
path: numberingPartPath,
|
|
970
|
-
bytes: new TextEncoder().encode(
|
|
971
|
-
serializedNumberingXml ??
|
|
972
|
-
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"></w:numbering>`,
|
|
973
|
-
),
|
|
974
|
-
contentType:
|
|
975
|
-
state.sourcePackage.parts.get(numberingPartPath)?.contentType ??
|
|
976
|
-
WORD_NUMBERING_CONTENT_TYPE,
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
if (serializedComments.serializedCommentIds.length > 0 || state.sourceCommentsPartPath) {
|
|
981
|
-
exportSession.replaceOwnedPart({
|
|
982
|
-
path: commentsPartPath,
|
|
983
|
-
bytes: new TextEncoder().encode(serializedComments.commentsXml),
|
|
984
|
-
contentType:
|
|
985
|
-
state.sourcePackage.parts.get(commentsPartPath)?.contentType ?? COMMENTS_CONTENT_TYPE,
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
if (serializedComments.commentsExtendedXml || state.sourceCommentsExtendedPartPath) {
|
|
990
|
-
exportSession.replaceOwnedPart({
|
|
991
|
-
path: commentsExtendedPartPath,
|
|
992
|
-
bytes: new TextEncoder().encode(
|
|
993
|
-
serializedComments.commentsExtendedXml ?? `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w15:commentsEx xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"></w15:commentsEx>`,
|
|
994
|
-
),
|
|
995
|
-
contentType:
|
|
996
|
-
state.sourcePackage.parts.get(commentsExtendedPartPath)?.contentType ??
|
|
997
|
-
COMMENTS_EXTENDED_CONTENT_TYPE,
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
if (serializedComments.commentsIdsXml || state.sourceCommentsIdsPartPath) {
|
|
1002
|
-
exportSession.replaceOwnedPart({
|
|
1003
|
-
path: commentsIdsPartPath,
|
|
1004
|
-
bytes: new TextEncoder().encode(
|
|
1005
|
-
serializedComments.commentsIdsXml ?? `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w16cid:commentsIds xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"></w16cid:commentsIds>`,
|
|
1006
|
-
),
|
|
1007
|
-
contentType:
|
|
1008
|
-
state.sourcePackage.parts.get(commentsIdsPartPath)?.contentType ??
|
|
1009
|
-
COMMENTS_IDS_CONTENT_TYPE,
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
if (serializedComments.peopleXml || state.sourcePeoplePartPath) {
|
|
1014
|
-
exportSession.replaceOwnedPart({
|
|
1015
|
-
path: peoplePartPath,
|
|
1016
|
-
bytes: new TextEncoder().encode(
|
|
1017
|
-
serializedComments.peopleXml ?? `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w15:people xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"></w15:people>`,
|
|
1018
|
-
),
|
|
1019
|
-
contentType:
|
|
1020
|
-
state.sourcePackage.parts.get(peoplePartPath)?.contentType ??
|
|
1021
|
-
PEOPLE_CONTENT_TYPE,
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
if (exportedSubParts) {
|
|
1026
|
-
for (const header of exportedSubParts.headers) {
|
|
1027
|
-
exportSession.replaceOwnedPart({
|
|
1028
|
-
path: header.partPath,
|
|
1029
|
-
bytes: new TextEncoder().encode(serializeHeaderXml(header)),
|
|
1030
|
-
contentType:
|
|
1031
|
-
state.sourcePackage.parts.get(header.partPath)?.contentType ?? WORD_HEADER_CONTENT_TYPE,
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
for (const footer of exportedSubParts.footers) {
|
|
1035
|
-
exportSession.replaceOwnedPart({
|
|
1036
|
-
path: footer.partPath,
|
|
1037
|
-
bytes: new TextEncoder().encode(serializeFooterXml(footer)),
|
|
1038
|
-
contentType:
|
|
1039
|
-
state.sourcePackage.parts.get(footer.partPath)?.contentType ?? WORD_FOOTER_CONTENT_TYPE,
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
if (exportedSubParts.footnoteCollection) {
|
|
1043
|
-
if (state.sourceSubPartPaths.footnotesPartPath) {
|
|
1044
|
-
exportSession.replaceOwnedPart({
|
|
1045
|
-
path: state.sourceSubPartPaths.footnotesPartPath,
|
|
1046
|
-
bytes: new TextEncoder().encode(serializeFootnotesXml(exportedSubParts.footnoteCollection)),
|
|
1047
|
-
contentType:
|
|
1048
|
-
state.sourcePackage.parts.get(state.sourceSubPartPaths.footnotesPartPath)?.contentType ??
|
|
1049
|
-
WORD_FOOTNOTES_CONTENT_TYPE,
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
if (state.sourceSubPartPaths.endnotesPartPath) {
|
|
1053
|
-
exportSession.replaceOwnedPart({
|
|
1054
|
-
path: state.sourceSubPartPaths.endnotesPartPath,
|
|
1055
|
-
bytes: new TextEncoder().encode(serializeEndnotesXml(exportedSubParts.footnoteCollection)),
|
|
1056
|
-
contentType:
|
|
1057
|
-
state.sourcePackage.parts.get(state.sourceSubPartPaths.endnotesPartPath)?.contentType ??
|
|
1058
|
-
WORD_ENDNOTES_CONTENT_TYPE,
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
if (exportedSubParts.theme && state.sourceSubPartPaths.themePartPath) {
|
|
1063
|
-
const sourceThemePart = state.sourcePackage.parts.get(state.sourceSubPartPaths.themePartPath);
|
|
1064
|
-
if (sourceThemePart) {
|
|
1065
|
-
exportSession.replaceOwnedPart({
|
|
1066
|
-
path: state.sourceSubPartPaths.themePartPath,
|
|
1067
|
-
bytes: sourceThemePart.bytes,
|
|
1068
|
-
contentType:
|
|
1069
|
-
sourceThemePart.contentType ??
|
|
1070
|
-
"application/vnd.openxmlformats-officedocument.theme+xml",
|
|
1071
|
-
relationships: sourceThemePart.relationships,
|
|
1072
|
-
compression: sourceThemePart.compression,
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
ensureHostMetadataParts(exportSession, state.sourcePackage, currentDocument);
|
|
1079
|
-
|
|
1080
|
-
return {
|
|
1081
|
-
bytes: exportSession.serialize(),
|
|
1082
|
-
mimeType: DOCX_MIME_TYPE,
|
|
1083
|
-
fileName: options?.fileName ?? `${sessionState.documentId}.docx`,
|
|
1084
|
-
delivery: {
|
|
1085
|
-
mode: "exported-bytes-only",
|
|
1086
|
-
},
|
|
1087
|
-
};
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
function toEditorSessionState(
|
|
1091
|
-
sessionStateOrSnapshot: EditorSessionState | PersistedEditorSnapshot,
|
|
1092
|
-
): EditorSessionState {
|
|
1093
|
-
return "sessionVersion" in sessionStateOrSnapshot
|
|
1094
|
-
? sessionStateOrSnapshot
|
|
1095
|
-
: editorSessionStateFromPersistedSnapshot(sessionStateOrSnapshot);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
function createImportedCanonicalDocument(input: {
|
|
1099
|
-
documentId: string;
|
|
1100
|
-
timestamp: string;
|
|
1101
|
-
numbering: CanonicalDocumentEnvelope["numbering"];
|
|
1102
|
-
media: CanonicalDocumentEnvelope["media"];
|
|
1103
|
-
content: CanonicalDocumentEnvelope["content"];
|
|
1104
|
-
subParts?: SubPartsCatalog;
|
|
1105
|
-
parsedStyles?: ParseStylesResult;
|
|
1106
|
-
preservation: CanonicalDocumentEnvelope["preservation"];
|
|
1107
|
-
diagnostics: CanonicalDocumentEnvelope["diagnostics"];
|
|
1108
|
-
review: CanonicalDocumentEnvelope["review"];
|
|
1109
|
-
}): CanonicalDocumentEnvelope {
|
|
1110
|
-
const numbering = ensureImportedNumberingCatalogSupportsContent(
|
|
1111
|
-
input.numbering,
|
|
1112
|
-
input.content,
|
|
1113
|
-
);
|
|
1114
|
-
|
|
1115
|
-
// Use package-backed style catalog when available; fall back to synthetic
|
|
1116
|
-
// styles derived from referenced styleId values when styles.xml is missing
|
|
1117
|
-
// or could not be parsed.
|
|
1118
|
-
const styles = buildStylesCatalog(input.parsedStyles, input.content, input.subParts);
|
|
1119
|
-
|
|
1120
|
-
return {
|
|
1121
|
-
schemaVersion: "cds/1.0.0",
|
|
1122
|
-
docId: createCanonicalDocumentId(input.documentId),
|
|
1123
|
-
createdAt: input.timestamp,
|
|
1124
|
-
updatedAt: input.timestamp,
|
|
1125
|
-
metadata: {
|
|
1126
|
-
customProperties: {},
|
|
1127
|
-
},
|
|
1128
|
-
styles,
|
|
1129
|
-
numbering,
|
|
1130
|
-
media: input.media,
|
|
1131
|
-
content: input.content,
|
|
1132
|
-
review: input.review,
|
|
1133
|
-
preservation: input.preservation,
|
|
1134
|
-
diagnostics: input.diagnostics,
|
|
1135
|
-
...(input.subParts !== undefined ? { subParts: input.subParts } : {}),
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
type SubPartOpaqueImportState = {
|
|
1140
|
-
nextFragmentIndex: number;
|
|
1141
|
-
nextWarningIndex: number;
|
|
1142
|
-
nextDiagnosticIndex: number;
|
|
1143
|
-
cursor: number;
|
|
1144
|
-
};
|
|
1145
|
-
|
|
1146
|
-
function createSubPartOpaqueImportState(
|
|
1147
|
-
opaqueFragments: Record<string, OpaqueFragmentRecord>,
|
|
1148
|
-
warnings: CanonicalDocumentEnvelope["diagnostics"]["warnings"],
|
|
1149
|
-
): SubPartOpaqueImportState {
|
|
1150
|
-
const maxByPrefix = (values: Iterable<string>, prefix: string): number => {
|
|
1151
|
-
let max = 0;
|
|
1152
|
-
for (const value of values) {
|
|
1153
|
-
const match = new RegExp(`^${prefix}(\\d+)$`).exec(value);
|
|
1154
|
-
if (!match) {
|
|
1155
|
-
continue;
|
|
1156
|
-
}
|
|
1157
|
-
const parsed = Number.parseInt(match[1] ?? "0", 10);
|
|
1158
|
-
if (Number.isFinite(parsed) && parsed > max) {
|
|
1159
|
-
max = parsed;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
return max;
|
|
1163
|
-
};
|
|
1164
|
-
|
|
1165
|
-
const maxCursor = Object.values(opaqueFragments).reduce(
|
|
1166
|
-
(currentMax, fragment) => Math.max(currentMax, fragment.lastKnownRange?.to ?? 0),
|
|
1167
|
-
0,
|
|
1168
|
-
);
|
|
1169
|
-
|
|
1170
|
-
return {
|
|
1171
|
-
nextFragmentIndex:
|
|
1172
|
-
maxByPrefix(Object.keys(opaqueFragments), "fragment:import-") + 1,
|
|
1173
|
-
nextWarningIndex:
|
|
1174
|
-
maxByPrefix(
|
|
1175
|
-
[
|
|
1176
|
-
...Object.values(opaqueFragments).map((fragment) => fragment.warningId),
|
|
1177
|
-
...warnings.map((warning) => warning.warningId),
|
|
1178
|
-
],
|
|
1179
|
-
"warning:import-",
|
|
1180
|
-
) + 1,
|
|
1181
|
-
nextDiagnosticIndex:
|
|
1182
|
-
maxByPrefix(warnings.map((warning) => warning.diagnosticId), "diagnostic:import-") + 1,
|
|
1183
|
-
cursor: maxCursor,
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
function normalizeSubPartOpaqueBlocks(
|
|
1188
|
-
blocks: BlockNode[],
|
|
1189
|
-
opaqueFragments: Record<string, OpaqueFragmentRecord>,
|
|
1190
|
-
warnings: CanonicalDocumentEnvelope["diagnostics"]["warnings"],
|
|
1191
|
-
packagePartName: string,
|
|
1192
|
-
state: SubPartOpaqueImportState,
|
|
1193
|
-
): BlockNode[] {
|
|
1194
|
-
return blocks.map((block) => {
|
|
1195
|
-
if (block.type !== "opaque_block" || typeof block.rawXml !== "string") {
|
|
1196
|
-
return block;
|
|
1197
|
-
}
|
|
1198
|
-
return recordImportedOpaqueBlock(
|
|
1199
|
-
block.rawXml,
|
|
1200
|
-
opaqueFragments,
|
|
1201
|
-
warnings,
|
|
1202
|
-
packagePartName,
|
|
1203
|
-
state,
|
|
1204
|
-
);
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
function normalizeFootnoteCollectionOpaqueBlocks(
|
|
1209
|
-
collection: FootnoteCollection | undefined,
|
|
1210
|
-
kind: "footnote" | "endnote",
|
|
1211
|
-
opaqueFragments: Record<string, OpaqueFragmentRecord>,
|
|
1212
|
-
warnings: CanonicalDocumentEnvelope["diagnostics"]["warnings"],
|
|
1213
|
-
packagePartName: string,
|
|
1214
|
-
state: SubPartOpaqueImportState,
|
|
1215
|
-
): void {
|
|
1216
|
-
if (!collection) {
|
|
1217
|
-
return;
|
|
1218
|
-
}
|
|
1219
|
-
const notes = kind === "footnote" ? collection.footnotes : collection.endnotes;
|
|
1220
|
-
for (const definition of Object.values(notes)) {
|
|
1221
|
-
definition.blocks = normalizeSubPartOpaqueBlocks(
|
|
1222
|
-
definition.blocks,
|
|
1223
|
-
opaqueFragments,
|
|
1224
|
-
warnings,
|
|
1225
|
-
packagePartName,
|
|
1226
|
-
state,
|
|
1227
|
-
);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
function recordImportedOpaqueBlock(
|
|
1232
|
-
rawXml: string,
|
|
1233
|
-
opaqueFragments: Record<string, OpaqueFragmentRecord>,
|
|
1234
|
-
warnings: CanonicalDocumentEnvelope["diagnostics"]["warnings"],
|
|
1235
|
-
packagePartName: string,
|
|
1236
|
-
state: SubPartOpaqueImportState,
|
|
1237
|
-
): BlockNode {
|
|
1238
|
-
const fragmentId = `fragment:import-${state.nextFragmentIndex}`;
|
|
1239
|
-
state.nextFragmentIndex += 1;
|
|
1240
|
-
const warningId = `warning:import-${state.nextWarningIndex}`;
|
|
1241
|
-
state.nextWarningIndex += 1;
|
|
1242
|
-
const diagnosticId = `diagnostic:import-${state.nextDiagnosticIndex}`;
|
|
1243
|
-
state.nextDiagnosticIndex += 1;
|
|
1244
|
-
|
|
1245
|
-
const rangeStart = state.cursor;
|
|
1246
|
-
const rangeEnd = state.cursor + 1;
|
|
1247
|
-
state.cursor = rangeEnd;
|
|
1248
|
-
|
|
1249
|
-
opaqueFragments[fragmentId] = {
|
|
1250
|
-
fragmentId,
|
|
1251
|
-
payloadKind: "xml-subtree",
|
|
1252
|
-
payloadReference: rawXml,
|
|
1253
|
-
featureClass: "preserve-only",
|
|
1254
|
-
lastKnownRange: {
|
|
1255
|
-
from: rangeStart,
|
|
1256
|
-
to: rangeEnd,
|
|
1257
|
-
},
|
|
1258
|
-
warningId,
|
|
1259
|
-
packagePartName,
|
|
1260
|
-
};
|
|
1261
|
-
warnings.push({
|
|
1262
|
-
diagnosticId,
|
|
1263
|
-
warningId,
|
|
1264
|
-
source: "import",
|
|
1265
|
-
message: "Unsupported sub-part OOXML was preserved as an opaque placeholder.",
|
|
1266
|
-
});
|
|
1267
|
-
|
|
1268
|
-
return {
|
|
1269
|
-
type: "opaque_block",
|
|
1270
|
-
fragmentId,
|
|
1271
|
-
warningId,
|
|
1272
|
-
rawXml,
|
|
1273
|
-
};
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
// Canonical model styleId validation pattern — styleIds that don't match
|
|
1277
|
-
// are excluded from the catalog to avoid snapshot validation failures.
|
|
1278
|
-
const VALID_STYLE_ID = /^[A-Za-z_][A-Za-z0-9._-]{0,127}$/;
|
|
1279
|
-
|
|
1280
|
-
function buildStylesCatalog(
|
|
1281
|
-
parsedStyles: ParseStylesResult | undefined,
|
|
1282
|
-
content: CanonicalDocumentEnvelope["content"],
|
|
1283
|
-
subParts?: SubPartsCatalog,
|
|
1284
|
-
): CanonicalDocumentEnvelope["styles"] {
|
|
1285
|
-
if (parsedStyles?.fromPackage) {
|
|
1286
|
-
// Package-backed catalog: filter entries whose styleId does not satisfy
|
|
1287
|
-
// the canonical model pattern (e.g. numeric-only ids from Word).
|
|
1288
|
-
const catalog = filterValidStyleIds(parsedStyles.catalog);
|
|
1289
|
-
|
|
1290
|
-
// Merge in any referenced styleIds that the package styles.xml did not
|
|
1291
|
-
// define (rare but defensive).
|
|
1292
|
-
const referencedIds = collectReferencedParagraphStyleIds(content, subParts);
|
|
1293
|
-
for (const styleId of referencedIds) {
|
|
1294
|
-
if (!catalog.paragraphs[styleId] && VALID_STYLE_ID.test(styleId)) {
|
|
1295
|
-
catalog.paragraphs[styleId] = {
|
|
1296
|
-
styleId,
|
|
1297
|
-
displayName: styleId,
|
|
1298
|
-
kind: "paragraph",
|
|
1299
|
-
isDefault: styleId === "Normal",
|
|
1300
|
-
};
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
return {
|
|
1304
|
-
...catalog,
|
|
1305
|
-
fromPackage: true,
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// Synthetic fallback: no styles.xml available
|
|
1310
|
-
const paragraphStyles = Object.fromEntries(
|
|
1311
|
-
[...collectReferencedParagraphStyleIds(content, subParts)]
|
|
1312
|
-
.sort((left, right) => left.localeCompare(right))
|
|
1313
|
-
.filter((styleId) => VALID_STYLE_ID.test(styleId))
|
|
1314
|
-
.map((styleId) => [
|
|
1315
|
-
styleId,
|
|
1316
|
-
{
|
|
1317
|
-
styleId,
|
|
1318
|
-
displayName: styleId,
|
|
1319
|
-
kind: "paragraph" as const,
|
|
1320
|
-
isDefault: styleId === "Normal",
|
|
1321
|
-
},
|
|
1322
|
-
]),
|
|
1323
|
-
);
|
|
1324
|
-
return {
|
|
1325
|
-
paragraphs: paragraphStyles,
|
|
1326
|
-
characters: {},
|
|
1327
|
-
tables: {},
|
|
1328
|
-
fromPackage: false,
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
function filterValidStyleIds(
|
|
1333
|
-
catalog: CanonicalDocumentEnvelope["styles"],
|
|
1334
|
-
): CanonicalDocumentEnvelope["styles"] {
|
|
1335
|
-
const filterRecord = <T extends { styleId: string }>(
|
|
1336
|
-
record: Record<string, T>,
|
|
1337
|
-
): Record<string, T> => {
|
|
1338
|
-
const result: Record<string, T> = {};
|
|
1339
|
-
for (const [key, value] of Object.entries(record)) {
|
|
1340
|
-
if (VALID_STYLE_ID.test(key)) {
|
|
1341
|
-
result[key] = value;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
return result;
|
|
1345
|
-
};
|
|
1346
|
-
|
|
1347
|
-
return {
|
|
1348
|
-
paragraphs: filterRecord(catalog.paragraphs),
|
|
1349
|
-
characters: filterRecord(catalog.characters),
|
|
1350
|
-
tables: filterRecord(catalog.tables),
|
|
1351
|
-
...(catalog.latentStyles ? { latentStyles: catalog.latentStyles } : {}),
|
|
1352
|
-
...(catalog.fromPackage !== undefined ? { fromPackage: catalog.fromPackage } : {}),
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
function ensureImportedNumberingCatalogSupportsContent(
|
|
1357
|
-
catalog: NumberingCatalog,
|
|
1358
|
-
content: CanonicalDocumentEnvelope["content"],
|
|
1359
|
-
): NumberingCatalog {
|
|
1360
|
-
if (
|
|
1361
|
-
catalog.instances[DOCX_NULL_NUMBERING_INSTANCE_ID] ||
|
|
1362
|
-
!collectReferencedNumberingInstanceIds(content).has(DOCX_NULL_NUMBERING_INSTANCE_ID)
|
|
1363
|
-
) {
|
|
1364
|
-
return catalog;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
const syntheticNullCatalog = createSyntheticDocxNullNumberingCatalog();
|
|
1368
|
-
return {
|
|
1369
|
-
abstractDefinitions: {
|
|
1370
|
-
...catalog.abstractDefinitions,
|
|
1371
|
-
...syntheticNullCatalog.abstractDefinitions,
|
|
1372
|
-
},
|
|
1373
|
-
instances: {
|
|
1374
|
-
...catalog.instances,
|
|
1375
|
-
...syntheticNullCatalog.instances,
|
|
1376
|
-
},
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
function collectReferencedNumberingInstanceIds(
|
|
1381
|
-
content: CanonicalDocumentEnvelope["content"],
|
|
1382
|
-
): Set<string> {
|
|
1383
|
-
const numberingInstanceIds = new Set<string>();
|
|
1384
|
-
|
|
1385
|
-
const visitBlocks = (blocks: ReadonlyArray<BlockNode>) => {
|
|
1386
|
-
for (const block of blocks) {
|
|
1387
|
-
if (block.type === "paragraph" && block.numbering?.numberingInstanceId) {
|
|
1388
|
-
numberingInstanceIds.add(block.numbering.numberingInstanceId);
|
|
1389
|
-
}
|
|
1390
|
-
if (block.type === "table") {
|
|
1391
|
-
for (const row of block.rows) {
|
|
1392
|
-
for (const cell of row.cells) {
|
|
1393
|
-
visitBlocks(cell.children);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
} else if (block.type === "sdt" || block.type === "custom_xml") {
|
|
1397
|
-
visitBlocks(block.children);
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
};
|
|
1401
|
-
|
|
1402
|
-
visitBlocks(content.children);
|
|
1403
|
-
return numberingInstanceIds;
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
function collectReferencedParagraphStyleIds(
|
|
1407
|
-
content: CanonicalDocumentEnvelope["content"],
|
|
1408
|
-
subParts?: SubPartsCatalog,
|
|
1409
|
-
): Set<string> {
|
|
1410
|
-
const styleIds = new Set<string>();
|
|
1411
|
-
|
|
1412
|
-
const visitBlocks = (blocks: ReadonlyArray<BlockNode>) => {
|
|
1413
|
-
for (const block of blocks) {
|
|
1414
|
-
if ("styleId" in block && typeof block.styleId === "string" && block.styleId.length > 0) {
|
|
1415
|
-
styleIds.add(block.styleId);
|
|
1416
|
-
}
|
|
1417
|
-
if (block.type === "table") {
|
|
1418
|
-
for (const row of block.rows) {
|
|
1419
|
-
for (const cell of row.cells) {
|
|
1420
|
-
visitBlocks(cell.children);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
} else if (block.type === "sdt" || block.type === "custom_xml") {
|
|
1424
|
-
visitBlocks(block.children);
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
};
|
|
1428
|
-
|
|
1429
|
-
visitBlocks(content.children);
|
|
1430
|
-
if (subParts) {
|
|
1431
|
-
for (const header of subParts.headers) {
|
|
1432
|
-
visitBlocks(header.blocks);
|
|
1433
|
-
}
|
|
1434
|
-
for (const footer of subParts.footers) {
|
|
1435
|
-
visitBlocks(footer.blocks);
|
|
1436
|
-
}
|
|
1437
|
-
if (subParts.footnoteCollection) {
|
|
1438
|
-
for (const note of Object.values(subParts.footnoteCollection.footnotes)) {
|
|
1439
|
-
visitBlocks(note.blocks);
|
|
1440
|
-
}
|
|
1441
|
-
for (const note of Object.values(subParts.footnoteCollection.endnotes)) {
|
|
1442
|
-
visitBlocks(note.blocks);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
return styleIds;
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
function createImportedSnapshot(input: {
|
|
1451
|
-
documentId: string;
|
|
1452
|
-
editorBuild: string;
|
|
1453
|
-
timestamp: string;
|
|
1454
|
-
document: CanonicalDocumentEnvelope;
|
|
1455
|
-
compatibility: PersistedEditorSnapshot["compatibility"];
|
|
1456
|
-
protectionSnapshot: ProtectionSnapshot;
|
|
1457
|
-
sourcePackage?: PersistedEditorSnapshot["sourcePackage"];
|
|
1458
|
-
}): PersistedEditorSnapshot {
|
|
1459
|
-
return {
|
|
1460
|
-
snapshotVersion: "persisted-editor-snapshot/2",
|
|
1461
|
-
schemaVersion: input.document.schemaVersion,
|
|
1462
|
-
documentId: input.documentId,
|
|
1463
|
-
docId: input.document.docId,
|
|
1464
|
-
createdAt: input.document.createdAt,
|
|
1465
|
-
updatedAt: input.document.updatedAt,
|
|
1466
|
-
savedAt: input.timestamp,
|
|
1467
|
-
editorBuild: input.editorBuild,
|
|
1468
|
-
canonicalDocument: input.document,
|
|
1469
|
-
compatibility: input.compatibility,
|
|
1470
|
-
warningLog: input.compatibility.warnings,
|
|
1471
|
-
protectionSnapshot: input.protectionSnapshot,
|
|
1472
|
-
sourcePackage: input.sourcePackage,
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
function toPublicAnchorProjection(
|
|
1477
|
-
anchor: InternalEditorAnchorProjection,
|
|
1478
|
-
): PublicEditorAnchorProjection {
|
|
1479
|
-
switch (anchor.kind) {
|
|
1480
|
-
case "range":
|
|
1481
|
-
return {
|
|
1482
|
-
kind: "range",
|
|
1483
|
-
from: anchor.range.from,
|
|
1484
|
-
to: anchor.range.to,
|
|
1485
|
-
assoc: anchor.assoc,
|
|
1486
|
-
};
|
|
1487
|
-
case "node":
|
|
1488
|
-
return {
|
|
1489
|
-
kind: "node",
|
|
1490
|
-
at: anchor.at,
|
|
1491
|
-
assoc: anchor.assoc,
|
|
1492
|
-
};
|
|
1493
|
-
case "detached":
|
|
1494
|
-
return {
|
|
1495
|
-
kind: "detached",
|
|
1496
|
-
lastKnownRange: anchor.lastKnownRange,
|
|
1497
|
-
reason: anchor.reason,
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
function toPublicCompatibilityFeatureEntry(entry: InternalCompatibilityFeatureEntry) {
|
|
1503
|
-
return {
|
|
1504
|
-
...entry,
|
|
1505
|
-
affectedAnchor: entry.affectedAnchor
|
|
1506
|
-
? toPublicAnchorProjection(entry.affectedAnchor)
|
|
1507
|
-
: undefined,
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
function toPublicWarning(warning: InternalEditorWarning): PublicEditorWarning {
|
|
1512
|
-
return {
|
|
1513
|
-
...warning,
|
|
1514
|
-
affectedAnchor: warning.affectedAnchor
|
|
1515
|
-
? toPublicAnchorProjection(warning.affectedAnchor)
|
|
1516
|
-
: undefined,
|
|
1517
|
-
};
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
function toPublicError(error: InternalEditorError): EditorError {
|
|
1521
|
-
return { ...error };
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
function toPublicCompatibilityReport(
|
|
1525
|
-
report: InternalCompatibilityReport,
|
|
1526
|
-
): PublicCompatibilityReport {
|
|
1527
|
-
return {
|
|
1528
|
-
reportVersion: report.reportVersion,
|
|
1529
|
-
generatedAt: report.generatedAt,
|
|
1530
|
-
blockExport: report.blockExport,
|
|
1531
|
-
featureEntries: report.featureEntries.map((entry) =>
|
|
1532
|
-
toPublicCompatibilityFeatureEntry(entry),
|
|
1533
|
-
),
|
|
1534
|
-
warnings: report.warnings.map((warning) => toPublicWarning(warning)),
|
|
1535
|
-
errors: report.errors.map((error) => toPublicError(error)),
|
|
1536
|
-
};
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
function createDiagnosticsSession(
|
|
1540
|
-
options: LoadDocxEditorSessionOptions,
|
|
1541
|
-
diagnostics: ImportDiagnosticsResult,
|
|
1542
|
-
): LoadedDocxEditorSession {
|
|
1543
|
-
const timestamp = new Date().toISOString();
|
|
1544
|
-
const editorBuild =
|
|
1545
|
-
typeof options.editorBuild === "string" && options.editorBuild.length > 0
|
|
1546
|
-
? options.editorBuild
|
|
1547
|
-
: "dev";
|
|
1548
|
-
const runtime = createReadOnlyDiagnosticsRuntime({
|
|
1549
|
-
documentId: options.documentId,
|
|
1550
|
-
sourceLabel: options.sourceLabel,
|
|
1551
|
-
editorBuild,
|
|
1552
|
-
generatedAt: timestamp,
|
|
1553
|
-
diagnostics,
|
|
1554
|
-
});
|
|
1555
|
-
const initialSnapshot = runtime.getPersistedSnapshot();
|
|
1556
|
-
const initialSessionState = editorSessionStateFromPersistedSnapshot(initialSnapshot);
|
|
1557
|
-
|
|
1558
|
-
return {
|
|
1559
|
-
initialSessionState,
|
|
1560
|
-
initialSnapshot,
|
|
1561
|
-
fatalError: diagnostics.fatalError,
|
|
1562
|
-
readOnly: true,
|
|
1563
|
-
protectionSnapshot: EMPTY_PROTECTION_SNAPSHOT,
|
|
1564
|
-
exportDocx: async (_sessionState, exportOptions) => runtime.exportDocx(exportOptions),
|
|
1565
|
-
};
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
function createImportDiagnosticsFromError(error: unknown): ImportDiagnosticsResult {
|
|
1569
|
-
if (isPackageImportError(error)) {
|
|
1570
|
-
return createPackageImportDiagnostics({
|
|
1571
|
-
issue: classifyCorruptPackageError(error),
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
return createValidationImportDiagnostics({
|
|
1576
|
-
message:
|
|
1577
|
-
error instanceof Error
|
|
1578
|
-
? error.message
|
|
1579
|
-
: "DOCX import failed during validation.",
|
|
1580
|
-
});
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
function normalizeImportedRevisionRecords(
|
|
1584
|
-
parsed: ParsedRevisionsResult,
|
|
1585
|
-
content: CanonicalDocumentEnvelope["content"],
|
|
1586
|
-
opaqueFragments: Record<string, OpaqueFragmentRecord>,
|
|
1587
|
-
): ParsedRevisionsResult {
|
|
1588
|
-
const opaqueRanges = Object.values(opaqueFragments).map((fragment) => fragment.lastKnownRange);
|
|
1589
|
-
const paragraphRanges = collectCanonicalParagraphRanges(content);
|
|
1590
|
-
if (opaqueRanges.length === 0) {
|
|
1591
|
-
return {
|
|
1592
|
-
...parsed,
|
|
1593
|
-
revisions: parsed.revisions.map((revision) => {
|
|
1594
|
-
if (revision.anchor.kind !== "range" || revision.metadata.preserveOnlyReason) {
|
|
1595
|
-
return revision;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
const preserveOnlyReason = getStructuralPreserveOnlyReason(
|
|
1599
|
-
revision,
|
|
1600
|
-
paragraphRanges,
|
|
1601
|
-
);
|
|
1602
|
-
if (!preserveOnlyReason) {
|
|
1603
|
-
return revision;
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
return {
|
|
1607
|
-
...revision,
|
|
1608
|
-
metadata: {
|
|
1609
|
-
...revision.metadata,
|
|
1610
|
-
preserveOnlyReason,
|
|
1611
|
-
},
|
|
1612
|
-
};
|
|
1613
|
-
}),
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
return {
|
|
1618
|
-
...parsed,
|
|
1619
|
-
revisions: parsed.revisions.map((revision) => {
|
|
1620
|
-
const { anchor } = revision;
|
|
1621
|
-
if (anchor.kind !== "range" || revision.metadata.preserveOnlyReason) {
|
|
1622
|
-
return revision;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
const preserveOnlyReason =
|
|
1626
|
-
getStructuralPreserveOnlyReason(revision, paragraphRanges) ??
|
|
1627
|
-
(opaqueRanges.some((range) => rangesIntersect(range, anchor.range))
|
|
1628
|
-
? "Imported revision overlaps preserve-only OOXML and remains preserve-only."
|
|
1629
|
-
: undefined);
|
|
1630
|
-
|
|
1631
|
-
if (!preserveOnlyReason) {
|
|
1632
|
-
return revision;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
return {
|
|
1636
|
-
...revision,
|
|
1637
|
-
metadata: {
|
|
1638
|
-
...revision.metadata,
|
|
1639
|
-
preserveOnlyReason,
|
|
1640
|
-
},
|
|
1641
|
-
};
|
|
1642
|
-
}),
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
function normalizeImportedCommentThreads(
|
|
1647
|
-
parsed: ParsedCommentsResult,
|
|
1648
|
-
opaqueFragments: Record<string, OpaqueFragmentRecord>,
|
|
1649
|
-
revisions: readonly ReviewRevisionRecord[],
|
|
1650
|
-
): NormalizedImportedCommentsResult {
|
|
1651
|
-
const opaqueRanges = Object.values(opaqueFragments).map((fragment) => fragment.lastKnownRange);
|
|
1652
|
-
const preserveOnlyRevisionRanges = revisions.flatMap((revision) => {
|
|
1653
|
-
const { anchor } = revision;
|
|
1654
|
-
if (
|
|
1655
|
-
anchor.kind !== "range" ||
|
|
1656
|
-
typeof revision.metadata.preserveOnlyReason !== "string" ||
|
|
1657
|
-
revision.metadata.preserveOnlyReason.length === 0
|
|
1658
|
-
) {
|
|
1659
|
-
return [];
|
|
1660
|
-
}
|
|
1661
|
-
return [anchor.range];
|
|
1662
|
-
});
|
|
1663
|
-
const preserveOnlyCommentIds = new Set(parsed.diagnostics.map((diagnostic) => diagnostic.commentId));
|
|
1664
|
-
const additionalDiagnostics: CommentImportDiagnostic[] = [];
|
|
1665
|
-
const normalizedThreads: CommentThread[] = parsed.threads.map((thread) => {
|
|
1666
|
-
const { anchor } = thread;
|
|
1667
|
-
if (anchor.kind !== "range") {
|
|
1668
|
-
preserveOnlyCommentIds.add(thread.commentId);
|
|
1669
|
-
return thread;
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
const opaqueOverlap = opaqueRanges.some((range) => rangesIntersect(range, anchor.range));
|
|
1673
|
-
if (opaqueOverlap) {
|
|
1674
|
-
preserveOnlyCommentIds.add(thread.commentId);
|
|
1675
|
-
additionalDiagnostics.push({
|
|
1676
|
-
commentId: thread.commentId,
|
|
1677
|
-
code: "opaque_anchor_preserve_only",
|
|
1678
|
-
message:
|
|
1679
|
-
"Comment anchor intersects preserve-only OOXML content. Thread is visible but detached; anchor cannot be safely remapped.",
|
|
1680
|
-
featureClass: "preserve-only",
|
|
1681
|
-
detachedReason: "opaque-region" as const,
|
|
1682
|
-
actionabilityNote: "The comment body is preserved. The anchor overlaps opaque content that the editor cannot safely modify.",
|
|
1683
|
-
});
|
|
1684
|
-
return {
|
|
1685
|
-
...thread,
|
|
1686
|
-
anchor: createDetachedAnchor(anchor.range, "importAmbiguity"),
|
|
1687
|
-
status: "detached",
|
|
1688
|
-
};
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
const preserveOnlyRevisionOverlap = preserveOnlyRevisionRanges.some((range) =>
|
|
1692
|
-
rangesIntersect(range, anchor.range),
|
|
1693
|
-
);
|
|
1694
|
-
if (preserveOnlyRevisionOverlap) {
|
|
1695
|
-
preserveOnlyCommentIds.add(thread.commentId);
|
|
1696
|
-
additionalDiagnostics.push({
|
|
1697
|
-
commentId: thread.commentId,
|
|
1698
|
-
code: "preserve_only_revision_overlap",
|
|
1699
|
-
message:
|
|
1700
|
-
"Comment anchor overlaps preserve-only review markup. Thread is visible but detached; anchor cannot be safely remapped during editing.",
|
|
1701
|
-
featureClass: "preserve-only",
|
|
1702
|
-
detachedReason: "revision-overlap" as const,
|
|
1703
|
-
actionabilityNote: "The comment body is preserved. The anchor overlaps preserve-only revision markup that the editor cannot safely modify.",
|
|
1704
|
-
});
|
|
1705
|
-
return {
|
|
1706
|
-
...thread,
|
|
1707
|
-
anchor: createDetachedAnchor(anchor.range, "importAmbiguity"),
|
|
1708
|
-
status: "detached",
|
|
1709
|
-
};
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
return thread;
|
|
1713
|
-
});
|
|
1714
|
-
|
|
1715
|
-
return {
|
|
1716
|
-
...parsed,
|
|
1717
|
-
threads: normalizedThreads,
|
|
1718
|
-
diagnostics: [...parsed.diagnostics, ...additionalDiagnostics],
|
|
1719
|
-
definitions: parsed.definitions,
|
|
1720
|
-
preservedDefinitions: parsed.definitions.filter((definition) =>
|
|
1721
|
-
preserveOnlyCommentIds.has(
|
|
1722
|
-
resolveDefinitionRootCommentId(definition, parsed.definitions),
|
|
1723
|
-
),
|
|
1724
|
-
),
|
|
1725
|
-
};
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
function resolveDefinitionRootCommentId(
|
|
1729
|
-
definition: ImportedCommentDefinition,
|
|
1730
|
-
definitions: readonly ImportedCommentDefinition[],
|
|
1731
|
-
): string {
|
|
1732
|
-
if (!definition.parentParaId) {
|
|
1733
|
-
return definition.commentId;
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
const definitionsByParaId = new Map(
|
|
1737
|
-
definitions
|
|
1738
|
-
.filter((candidate) => typeof candidate.paraId === "string")
|
|
1739
|
-
.map((candidate) => [candidate.paraId!, candidate]),
|
|
1740
|
-
);
|
|
1741
|
-
const visited = new Set<string>();
|
|
1742
|
-
let current: ImportedCommentDefinition | undefined = definition;
|
|
1743
|
-
|
|
1744
|
-
while (current?.parentParaId) {
|
|
1745
|
-
if (visited.has(current.parentParaId)) {
|
|
1746
|
-
break;
|
|
1747
|
-
}
|
|
1748
|
-
visited.add(current.parentParaId);
|
|
1749
|
-
const parent = definitionsByParaId.get(current.parentParaId);
|
|
1750
|
-
if (!parent) {
|
|
1751
|
-
break;
|
|
1752
|
-
}
|
|
1753
|
-
current = parent;
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
return current?.commentId ?? definition.commentId;
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
function getStructuralPreserveOnlyReason(
|
|
1760
|
-
revision: ReviewRevisionRecord,
|
|
1761
|
-
paragraphRanges: ReadonlyArray<{ start: number; end: number }>,
|
|
1762
|
-
): string | undefined {
|
|
1763
|
-
const form = revision.metadata.importedRevisionForm;
|
|
1764
|
-
const { anchor } = revision;
|
|
1765
|
-
if (!form || anchor.kind !== "range") {
|
|
1766
|
-
return undefined;
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
if (
|
|
1770
|
-
(form === "run-insertion" || form === "run-deletion") &&
|
|
1771
|
-
anchor.range.from === anchor.range.to
|
|
1772
|
-
) {
|
|
1773
|
-
return "Imported zero-width run revision remains preserve-only.";
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
if (form === "paragraph-insertion" || form === "paragraph-deletion") {
|
|
1777
|
-
const paragraphBoundary = paragraphRanges.find(
|
|
1778
|
-
(boundary) =>
|
|
1779
|
-
boundary.end === anchor.range.from ||
|
|
1780
|
-
(anchor.range.from >= boundary.start &&
|
|
1781
|
-
anchor.range.from <= boundary.end),
|
|
1782
|
-
);
|
|
1783
|
-
return paragraphBoundary
|
|
1784
|
-
? undefined
|
|
1785
|
-
: "Imported revision spans paragraph-level structure and remains preserve-only.";
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
const paragraphBoundary = paragraphRanges.find(
|
|
1789
|
-
(boundary) =>
|
|
1790
|
-
anchor.range.from >= boundary.start &&
|
|
1791
|
-
anchor.range.to <= boundary.end,
|
|
1792
|
-
);
|
|
1793
|
-
return paragraphBoundary
|
|
1794
|
-
? undefined
|
|
1795
|
-
: "Imported revision spans structural boundaries and remains preserve-only.";
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
function collectCanonicalParagraphRanges(
|
|
1799
|
-
content: CanonicalDocumentEnvelope["content"],
|
|
1800
|
-
): Array<{ start: number; end: number }> {
|
|
1801
|
-
const ranges: Array<{ start: number; end: number }> = [];
|
|
1802
|
-
let cursor = 0;
|
|
1803
|
-
let previousWasParagraph = false;
|
|
1804
|
-
|
|
1805
|
-
for (const block of content.children) {
|
|
1806
|
-
if (block.type === "paragraph") {
|
|
1807
|
-
if (previousWasParagraph) {
|
|
1808
|
-
cursor += 1;
|
|
1809
|
-
}
|
|
1810
|
-
const start = cursor;
|
|
1811
|
-
cursor += measureCanonicalParagraph(block);
|
|
1812
|
-
ranges.push({ start, end: cursor });
|
|
1813
|
-
previousWasParagraph = true;
|
|
1814
|
-
continue;
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
cursor += 1;
|
|
1818
|
-
previousWasParagraph = false;
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
return ranges;
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
function measureCanonicalParagraph(paragraph: CanonicalDocumentEnvelope["content"]["children"][number] & { type: "paragraph" }): number {
|
|
1825
|
-
return paragraph.children.reduce<number>((size, child) => {
|
|
1826
|
-
if (child.type === "text") {
|
|
1827
|
-
return size + child.text.length;
|
|
1828
|
-
}
|
|
1829
|
-
if (child.type === "hyperlink") {
|
|
1830
|
-
return (
|
|
1831
|
-
size +
|
|
1832
|
-
child.children.reduce<number>((childSize, entry) => {
|
|
1833
|
-
if (entry.type === "text") {
|
|
1834
|
-
return childSize + entry.text.length;
|
|
1835
|
-
}
|
|
1836
|
-
return childSize + 1;
|
|
1837
|
-
}, 0)
|
|
1838
|
-
);
|
|
1839
|
-
}
|
|
1840
|
-
return size + 1;
|
|
1841
|
-
}, 0);
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
function rangesIntersect(
|
|
1845
|
-
left: { from: number; to: number },
|
|
1846
|
-
right: { from: number; to: number },
|
|
1847
|
-
): boolean {
|
|
1848
|
-
return left.from < right.to && right.from < left.to;
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
function resolveCommentsPartPath(
|
|
1852
|
-
sourcePackage: OpcPackage,
|
|
1853
|
-
sourceDocumentPartPath: string,
|
|
1854
|
-
relationships: readonly OpcRelationship[],
|
|
1855
|
-
): string | undefined {
|
|
1856
|
-
return resolveDocumentRelatedPartPath(
|
|
1857
|
-
sourcePackage,
|
|
1858
|
-
sourceDocumentPartPath,
|
|
1859
|
-
relationships,
|
|
1860
|
-
COMMENTS_RELATIONSHIP_TYPE,
|
|
1861
|
-
COMMENTS_PART_PATH,
|
|
1862
|
-
);
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
function resolveDocumentRelatedPartPath(
|
|
1866
|
-
sourcePackage: OpcPackage,
|
|
1867
|
-
sourceDocumentPartPath: string,
|
|
1868
|
-
relationships: readonly OpcRelationship[],
|
|
1869
|
-
relationshipType: string,
|
|
1870
|
-
fallbackPartPath: string,
|
|
1871
|
-
): string | undefined {
|
|
1872
|
-
const relationship = relationships.find(
|
|
1873
|
-
(candidate) =>
|
|
1874
|
-
candidate.type === relationshipType &&
|
|
1875
|
-
candidate.targetMode === "internal",
|
|
1876
|
-
);
|
|
1877
|
-
if (!relationship) {
|
|
1878
|
-
return sourcePackage.parts.has(fallbackPartPath) ? fallbackPartPath : undefined;
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
const targetPath = resolveRelationshipTarget(sourceDocumentPartPath, relationship);
|
|
1882
|
-
return sourcePackage.parts.has(targetPath) ? targetPath : undefined;
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
function resolveMainDocumentPartPath(sourcePackage: OpcPackage): string | undefined {
|
|
1886
|
-
const relationship = sourcePackage.manifest.packageRelationships.find(
|
|
1887
|
-
(candidate) =>
|
|
1888
|
-
candidate.type === OFFICE_DOCUMENT_RELATIONSHIP_TYPE &&
|
|
1889
|
-
candidate.targetMode === "internal",
|
|
1890
|
-
);
|
|
1891
|
-
if (relationship) {
|
|
1892
|
-
return resolveRelationshipTarget(null, relationship);
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
return sourcePackage.parts.has(MAIN_DOCUMENT_PATH) ? MAIN_DOCUMENT_PATH : undefined;
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
function toRuntimeCommentRecords(
|
|
1899
|
-
threads: readonly CommentThread[],
|
|
1900
|
-
): Record<string, CommentThreadRecord> {
|
|
1901
|
-
return Object.fromEntries(
|
|
1902
|
-
threads.map((thread) => {
|
|
1903
|
-
return [
|
|
1904
|
-
thread.commentId,
|
|
1905
|
-
{
|
|
1906
|
-
commentId: thread.commentId,
|
|
1907
|
-
body: thread.entries.map((entry) => entry.body).join("\n"),
|
|
1908
|
-
anchor: thread.anchor,
|
|
1909
|
-
createdBy: thread.createdBy,
|
|
1910
|
-
authorId: thread.createdBy,
|
|
1911
|
-
createdAt: thread.createdAt,
|
|
1912
|
-
entries: thread.entries.map((entry) => ({
|
|
1913
|
-
entryId: entry.entryId,
|
|
1914
|
-
authorId: entry.authorId,
|
|
1915
|
-
body: entry.body,
|
|
1916
|
-
createdAt: entry.createdAt,
|
|
1917
|
-
metadata: entry.metadata
|
|
1918
|
-
? {
|
|
1919
|
-
ooxmlCommentId: entry.metadata.ooxmlCommentId,
|
|
1920
|
-
paraId: entry.metadata.paraId,
|
|
1921
|
-
parentParaId: entry.metadata.parentParaId,
|
|
1922
|
-
durableId: entry.metadata.durableId,
|
|
1923
|
-
initials: entry.metadata.initials,
|
|
1924
|
-
}
|
|
1925
|
-
: undefined,
|
|
1926
|
-
})),
|
|
1927
|
-
status: thread.status,
|
|
1928
|
-
resolution: thread.resolution
|
|
1929
|
-
? {
|
|
1930
|
-
resolvedAt: thread.resolution.resolvedAt,
|
|
1931
|
-
resolvedBy: thread.resolution.resolvedBy,
|
|
1932
|
-
}
|
|
1933
|
-
: undefined,
|
|
1934
|
-
resolvedAt: thread.resolution?.resolvedAt,
|
|
1935
|
-
warningIds: [...thread.warningIds],
|
|
1936
|
-
isResolved: thread.status === "resolved",
|
|
1937
|
-
metadata: thread.metadata
|
|
1938
|
-
? {
|
|
1939
|
-
source: thread.metadata.source,
|
|
1940
|
-
rootOoxmlCommentId: thread.metadata.rootOoxmlCommentId,
|
|
1941
|
-
rootParaId: thread.metadata.rootParaId,
|
|
1942
|
-
}
|
|
1943
|
-
: undefined,
|
|
1944
|
-
} satisfies CommentThreadRecord,
|
|
1945
|
-
];
|
|
1946
|
-
}),
|
|
1947
|
-
);
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
function toRuntimeRevisionRecords(
|
|
1951
|
-
revisions: readonly ReviewRevisionRecord[],
|
|
1952
|
-
): Record<string, RuntimeRevisionRecord> {
|
|
1953
|
-
return Object.fromEntries(
|
|
1954
|
-
revisions.map((revision) => [
|
|
1955
|
-
revision.revisionId,
|
|
1956
|
-
{
|
|
1957
|
-
changeId: revision.revisionId,
|
|
1958
|
-
kind: revision.kind,
|
|
1959
|
-
anchor: revision.anchor,
|
|
1960
|
-
authorId: revision.authorId,
|
|
1961
|
-
createdAt: revision.createdAt,
|
|
1962
|
-
warningIds: [...revision.warningIds],
|
|
1963
|
-
metadata: {
|
|
1964
|
-
source: revision.metadata.source,
|
|
1965
|
-
storyTarget: revision.metadata.storyTarget,
|
|
1966
|
-
preserveOnlyReason: revision.metadata.preserveOnlyReason,
|
|
1967
|
-
importedRevisionForm: revision.metadata.importedRevisionForm,
|
|
1968
|
-
originalRevisionType: revision.metadata.originalRevisionType,
|
|
1969
|
-
ooxmlRevisionId: revision.metadata.ooxmlRevisionId,
|
|
1970
|
-
},
|
|
1971
|
-
status: revision.status === "active" ? "open" : revision.status,
|
|
1972
|
-
} satisfies RuntimeRevisionRecord,
|
|
1973
|
-
]),
|
|
1974
|
-
);
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
function toReviewRevisionRecords(
|
|
1978
|
-
revisions: CanonicalDocumentEnvelope["review"]["revisions"],
|
|
1979
|
-
): ReviewRevisionRecord[] {
|
|
1980
|
-
return Object.values(revisions).map((revision) => ({
|
|
1981
|
-
revisionId: revision.changeId,
|
|
1982
|
-
kind: revision.kind,
|
|
1983
|
-
anchor: revision.anchor,
|
|
1984
|
-
authorId: revision.authorId ?? "unknown",
|
|
1985
|
-
createdAt: revision.createdAt,
|
|
1986
|
-
warningIds: [...(revision.warningIds ?? [])],
|
|
1987
|
-
metadata: {
|
|
1988
|
-
source: revision.metadata?.source ?? "runtime",
|
|
1989
|
-
storyTarget: revision.metadata?.storyTarget,
|
|
1990
|
-
preserveOnlyReason: revision.metadata?.preserveOnlyReason,
|
|
1991
|
-
importedRevisionForm: revision.metadata?.importedRevisionForm,
|
|
1992
|
-
originalRevisionType: revision.metadata?.originalRevisionType,
|
|
1993
|
-
ooxmlRevisionId: revision.metadata?.ooxmlRevisionId,
|
|
1994
|
-
},
|
|
1995
|
-
status: revision.status === "open" ? "active" : revision.status,
|
|
1996
|
-
}));
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
export function stripCommentMarkup(
|
|
2000
|
-
documentXml: string,
|
|
2001
|
-
ownedCommentIds: readonly string[],
|
|
2002
|
-
): string {
|
|
2003
|
-
if (ownedCommentIds.length === 0) {
|
|
2004
|
-
return documentXml;
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
let output = documentXml;
|
|
2008
|
-
for (const commentId of ownedCommentIds) {
|
|
2009
|
-
const escapedCommentId = escapeRegExp(commentId);
|
|
2010
|
-
const attributePattern = `(?:w:id|id)=["']${escapedCommentId}["']`;
|
|
2011
|
-
output = output
|
|
2012
|
-
.replace(
|
|
2013
|
-
new RegExp(`<w:commentRangeStart\\b[^>]*${attributePattern}[^>]*/>`, "gu"),
|
|
2014
|
-
"",
|
|
2015
|
-
)
|
|
2016
|
-
.replace(
|
|
2017
|
-
new RegExp(`<w:commentRangeEnd\\b[^>]*${attributePattern}[^>]*/>`, "gu"),
|
|
2018
|
-
"",
|
|
2019
|
-
)
|
|
2020
|
-
.replace(
|
|
2021
|
-
new RegExp(
|
|
2022
|
-
`<w:r\\b[^>]*>(?:(?!<w:t\\b|</w:r>)[\\s\\S])*?<w:commentReference\\b[^>]*${attributePattern}[^>]*/>(?:(?!<w:t\\b|</w:r>)[\\s\\S])*?</w:r>`,
|
|
2023
|
-
"gu",
|
|
2024
|
-
),
|
|
2025
|
-
"",
|
|
2026
|
-
);
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
return output;
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
function escapeRegExp(value: string): string {
|
|
2033
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
function withDocumentRelatedParts(
|
|
2037
|
-
relationships: readonly OpcRelationship[],
|
|
2038
|
-
relatedParts: ReadonlyArray<{
|
|
2039
|
-
relationshipType: string;
|
|
2040
|
-
partPath: string;
|
|
2041
|
-
existingRelationshipId?: string;
|
|
2042
|
-
include: boolean;
|
|
2043
|
-
}>,
|
|
2044
|
-
): OpcRelationship[] {
|
|
2045
|
-
const filteredTypes = new Set(relatedParts.map((part) => part.relationshipType));
|
|
2046
|
-
const nextRelationships = relationships
|
|
2047
|
-
.filter((relationship) => !filteredTypes.has(relationship.type))
|
|
2048
|
-
.map(cloneRelationship);
|
|
2049
|
-
|
|
2050
|
-
for (const part of relatedParts) {
|
|
2051
|
-
if (!part.include) {
|
|
2052
|
-
continue;
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
nextRelationships.push({
|
|
2056
|
-
id: part.existingRelationshipId ?? createCommentsRelationshipId(nextRelationships),
|
|
2057
|
-
type: part.relationshipType,
|
|
2058
|
-
target: toDocumentRelativeTarget(part.partPath),
|
|
2059
|
-
targetMode: "internal",
|
|
2060
|
-
});
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
return nextRelationships;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
function collectPreservedPackageParts(
|
|
2067
|
-
sourcePackage: OpcPackage,
|
|
2068
|
-
ownedPartPaths: ReadonlyArray<string | undefined>,
|
|
2069
|
-
): Record<string, PreservedPackagePart> {
|
|
2070
|
-
const normalizedOwnedPaths = new Set(
|
|
2071
|
-
ownedPartPaths
|
|
2072
|
-
.filter((value): value is string => typeof value === "string")
|
|
2073
|
-
.map((path) => normalizePartPath(path)),
|
|
2074
|
-
);
|
|
2075
|
-
return Object.fromEntries(
|
|
2076
|
-
[...sourcePackage.parts.values()]
|
|
2077
|
-
.filter((part) =>
|
|
2078
|
-
shouldPreservePackagePart(part.path, part.surfaceKind, normalizedOwnedPaths),
|
|
2079
|
-
)
|
|
2080
|
-
.map((part) => [
|
|
2081
|
-
part.path,
|
|
2082
|
-
{
|
|
2083
|
-
packagePartName: part.path,
|
|
2084
|
-
contentType: part.contentType ?? "application/octet-stream",
|
|
2085
|
-
relationshipIds: findRelationshipIdsTargetingPart(sourcePackage, part.path),
|
|
2086
|
-
} satisfies PreservedPackagePart,
|
|
2087
|
-
]),
|
|
2088
|
-
);
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
function collectInlineMediaParts(
|
|
2092
|
-
sourcePackage: OpcPackage,
|
|
2093
|
-
): ReadonlyMap<string, { path: string; contentType: string }> {
|
|
2094
|
-
return new Map(
|
|
2095
|
-
[...sourcePackage.parts.values()]
|
|
2096
|
-
.filter(
|
|
2097
|
-
(part) =>
|
|
2098
|
-
part.path.startsWith("/word/media/") && typeof part.contentType === "string",
|
|
2099
|
-
)
|
|
2100
|
-
.map((part) => [
|
|
2101
|
-
part.path,
|
|
2102
|
-
{
|
|
2103
|
-
path: part.path,
|
|
2104
|
-
contentType: part.contentType ?? "application/octet-stream",
|
|
2105
|
-
},
|
|
2106
|
-
]),
|
|
2107
|
-
);
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
function createEmptyNumberingCatalog(): NumberingCatalog {
|
|
2111
|
-
return {
|
|
2112
|
-
abstractDefinitions: {},
|
|
2113
|
-
instances: {},
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
function collectBrokenInternalRelationshipIssues(
|
|
2118
|
-
sourcePackage: OpcPackage,
|
|
2119
|
-
mainDocumentPath?: string,
|
|
2120
|
-
): ReturnType<typeof createBrokenRelationshipIssue>[] {
|
|
2121
|
-
const brokenTargets = new Map<string, ReturnType<typeof createBrokenRelationshipIssue>>();
|
|
2122
|
-
|
|
2123
|
-
for (const relationship of sourcePackage.manifest.packageRelationships) {
|
|
2124
|
-
if (relationship.targetMode !== "internal") {
|
|
2125
|
-
continue;
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
const target = resolveRelationshipTarget(null, relationship);
|
|
2129
|
-
if (
|
|
2130
|
-
!sourcePackage.parts.has(target) &&
|
|
2131
|
-
isFatalBrokenRelationship({
|
|
2132
|
-
relationshipSourcePath: null,
|
|
2133
|
-
relationship,
|
|
2134
|
-
targetPartPath: target,
|
|
2135
|
-
mainDocumentPath,
|
|
2136
|
-
})
|
|
2137
|
-
) {
|
|
2138
|
-
brokenTargets.set(
|
|
2139
|
-
`package:${relationship.id}:${target}`,
|
|
2140
|
-
createBrokenRelationshipIssue({
|
|
2141
|
-
relationshipSourcePath: null,
|
|
2142
|
-
relationshipId: relationship.id,
|
|
2143
|
-
targetPartPath: target,
|
|
2144
|
-
}),
|
|
2145
|
-
);
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
for (const part of sourcePackage.parts.values()) {
|
|
2150
|
-
for (const relationship of part.relationships) {
|
|
2151
|
-
if (relationship.targetMode !== "internal") {
|
|
2152
|
-
continue;
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
const target = resolveRelationshipTarget(part.path, relationship);
|
|
2156
|
-
if (
|
|
2157
|
-
!sourcePackage.parts.has(target) &&
|
|
2158
|
-
isFatalBrokenRelationship({
|
|
2159
|
-
relationshipSourcePath: part.path,
|
|
2160
|
-
relationship,
|
|
2161
|
-
targetPartPath: target,
|
|
2162
|
-
mainDocumentPath,
|
|
2163
|
-
})
|
|
2164
|
-
) {
|
|
2165
|
-
brokenTargets.set(
|
|
2166
|
-
`${part.path}:${relationship.id}:${target}`,
|
|
2167
|
-
createBrokenRelationshipIssue({
|
|
2168
|
-
relationshipSourcePath: part.path,
|
|
2169
|
-
relationshipId: relationship.id,
|
|
2170
|
-
targetPartPath: target,
|
|
2171
|
-
}),
|
|
2172
|
-
);
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
return [...brokenTargets.values()].sort((left, right) =>
|
|
2178
|
-
`${left.relationshipSourcePath ?? ""}:${left.relationshipId}:${left.targetPartPath}`.localeCompare(
|
|
2179
|
-
`${right.relationshipSourcePath ?? ""}:${right.relationshipId}:${right.targetPartPath}`,
|
|
2180
|
-
),
|
|
2181
|
-
);
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
function summarizeBrokenRelationshipIssues(
|
|
2185
|
-
issues: readonly ReturnType<typeof createBrokenRelationshipIssue>[],
|
|
2186
|
-
): string {
|
|
2187
|
-
return `DOCX package has unresolved internal relationships: ${issues
|
|
2188
|
-
.map((issue) => issue.targetPartPath ?? issue.message)
|
|
2189
|
-
.slice(0, 3)
|
|
2190
|
-
.join(", ")}${issues.length > 3 ? ", ..." : ""}.`;
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
function createBrokenRelationshipWarnings(
|
|
2194
|
-
sourcePackage: OpcPackage,
|
|
2195
|
-
mainDocumentPath?: string,
|
|
2196
|
-
): CanonicalDocumentEnvelope["diagnostics"]["warnings"] {
|
|
2197
|
-
const warnings = new Map<string, CanonicalDocumentEnvelope["diagnostics"]["warnings"][number]>();
|
|
2198
|
-
|
|
2199
|
-
for (const relationship of sourcePackage.manifest.packageRelationships) {
|
|
2200
|
-
if (relationship.targetMode !== "internal") {
|
|
2201
|
-
continue;
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
const target = resolveRelationshipTarget(null, relationship);
|
|
2205
|
-
if (
|
|
2206
|
-
sourcePackage.parts.has(target) ||
|
|
2207
|
-
isFatalBrokenRelationship({
|
|
2208
|
-
relationshipSourcePath: null,
|
|
2209
|
-
relationship,
|
|
2210
|
-
targetPartPath: target,
|
|
2211
|
-
mainDocumentPath,
|
|
2212
|
-
})
|
|
2213
|
-
) {
|
|
2214
|
-
continue;
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
|
-
warnings.set(`package:${relationship.id}:${target}`, {
|
|
2218
|
-
diagnosticId: `diagnostic:broken-relationship-package-${relationship.id}`,
|
|
2219
|
-
warningId: `warning:broken-relationship:${relationship.id}`,
|
|
2220
|
-
source: "preservation",
|
|
2221
|
-
message: `DOCX package has unresolved internal relationships outside the editor-owned graph: ${target}.`,
|
|
2222
|
-
});
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
for (const part of sourcePackage.parts.values()) {
|
|
2226
|
-
for (const relationship of part.relationships) {
|
|
2227
|
-
if (relationship.targetMode !== "internal") {
|
|
2228
|
-
continue;
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
const target = resolveRelationshipTarget(part.path, relationship);
|
|
2232
|
-
if (
|
|
2233
|
-
sourcePackage.parts.has(target) ||
|
|
2234
|
-
isFatalBrokenRelationship({
|
|
2235
|
-
relationshipSourcePath: part.path,
|
|
2236
|
-
relationship,
|
|
2237
|
-
targetPartPath: target,
|
|
2238
|
-
mainDocumentPath,
|
|
2239
|
-
})
|
|
2240
|
-
) {
|
|
2241
|
-
continue;
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
warnings.set(`${part.path}:${relationship.id}:${target}`, {
|
|
2245
|
-
diagnosticId: `diagnostic:broken-relationship-${relationship.id}`,
|
|
2246
|
-
warningId: `warning:broken-relationship:${relationship.id}`,
|
|
2247
|
-
source: "preservation",
|
|
2248
|
-
message: `DOCX package has unresolved internal relationships outside the editor-owned graph: ${target}.`,
|
|
2249
|
-
});
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
return [...warnings.values()];
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
function isFatalBrokenRelationship(input: {
|
|
2257
|
-
relationshipSourcePath: string | null;
|
|
2258
|
-
relationship: OpcRelationship;
|
|
2259
|
-
targetPartPath: string;
|
|
2260
|
-
mainDocumentPath?: string;
|
|
2261
|
-
}): boolean {
|
|
2262
|
-
return (
|
|
2263
|
-
input.relationshipSourcePath === null &&
|
|
2264
|
-
input.relationship.type === OFFICE_DOCUMENT_RELATIONSHIP_TYPE &&
|
|
2265
|
-
input.targetPartPath === input.mainDocumentPath
|
|
2266
|
-
);
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
function shouldPreservePackagePart(
|
|
2270
|
-
partPath: string,
|
|
2271
|
-
surfaceKind: OpcPackage["parts"] extends Map<string, infer T>
|
|
2272
|
-
? T extends { surfaceKind: infer U }
|
|
2273
|
-
? U
|
|
2274
|
-
: never
|
|
2275
|
-
: never,
|
|
2276
|
-
ownedPartPaths: ReadonlySet<string>,
|
|
2277
|
-
): boolean {
|
|
2278
|
-
if (surfaceKind !== "content") {
|
|
2279
|
-
return false;
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
if (
|
|
2283
|
-
ownedPartPaths.has(partPath) ||
|
|
2284
|
-
partPath.startsWith("/word/media/") ||
|
|
2285
|
-
CORE_NON_PRESERVED_PART_PATHS.has(partPath)
|
|
2286
|
-
) {
|
|
2287
|
-
return false;
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
return true;
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2293
|
-
function findRelationshipIdsTargetingPart(
|
|
2294
|
-
sourcePackage: OpcPackage,
|
|
2295
|
-
targetPartPath: string,
|
|
2296
|
-
): string[] {
|
|
2297
|
-
const ids = new Set<string>();
|
|
2298
|
-
|
|
2299
|
-
for (const relationship of sourcePackage.manifest.packageRelationships) {
|
|
2300
|
-
if (
|
|
2301
|
-
relationship.targetMode === "internal" &&
|
|
2302
|
-
resolveRelationshipTarget(null, relationship) === targetPartPath
|
|
2303
|
-
) {
|
|
2304
|
-
ids.add(relationship.id);
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
|
|
2308
|
-
for (const part of sourcePackage.parts.values()) {
|
|
2309
|
-
for (const relationship of part.relationships) {
|
|
2310
|
-
if (
|
|
2311
|
-
relationship.targetMode === "internal" &&
|
|
2312
|
-
resolveRelationshipTarget(part.path, relationship) === targetPartPath
|
|
2313
|
-
) {
|
|
2314
|
-
ids.add(relationship.id);
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
|
-
return [...ids].sort();
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
function extractDocumentRootAttributes(documentXml: string): Record<string, string> {
|
|
2323
|
-
const match = documentXml.match(
|
|
2324
|
-
/<(?:[A-Za-z_][A-Za-z0-9:._-]*:)?document\b([^>]*)>/u,
|
|
2325
|
-
);
|
|
2326
|
-
if (!match) {
|
|
2327
|
-
return {};
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
const attributes: Record<string, string> = {};
|
|
2331
|
-
const pattern = /([A-Za-z_][A-Za-z0-9:._-]*)\s*=\s*("([^"]*)"|'([^']*)')/gu;
|
|
2332
|
-
for (const attributeMatch of (match[1] ?? "").matchAll(pattern)) {
|
|
2333
|
-
const name = attributeMatch[1];
|
|
2334
|
-
const value = attributeMatch[3] ?? attributeMatch[4] ?? "";
|
|
2335
|
-
if (!name) {
|
|
2336
|
-
continue;
|
|
2337
|
-
}
|
|
2338
|
-
attributes[name] = value;
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
return attributes;
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
function isPackageImportError(error: unknown): boolean {
|
|
2345
|
-
if (!(error instanceof Error)) {
|
|
2346
|
-
return false;
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
const normalized = error.message.toLowerCase();
|
|
2350
|
-
return (
|
|
2351
|
-
normalized.includes("zip") ||
|
|
2352
|
-
normalized.includes("opc package") ||
|
|
2353
|
-
normalized.includes("compression") ||
|
|
2354
|
-
normalized.includes("relationship") ||
|
|
2355
|
-
normalized.includes("/[content_types].xml") ||
|
|
2356
|
-
normalized.includes("/word/document.xml") ||
|
|
2357
|
-
normalized.includes("xml")
|
|
2358
|
-
);
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
const CORE_NON_PRESERVED_PART_PATHS = new Set([
|
|
2362
|
-
"/docProps/app.xml",
|
|
2363
|
-
"/docProps/core.xml",
|
|
2364
|
-
"/word/numbering.xml",
|
|
2365
|
-
]);
|
|
2366
|
-
|
|
2367
|
-
function createCommentsRelationshipId(
|
|
2368
|
-
relationships: readonly OpcRelationship[],
|
|
2369
|
-
): string {
|
|
2370
|
-
let nextIndex = 1;
|
|
2371
|
-
while (relationships.some((relationship) => relationship.id === `rIdComments${nextIndex}`)) {
|
|
2372
|
-
nextIndex += 1;
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
return `rIdComments${nextIndex}`;
|
|
2376
|
-
}
|
|
2377
|
-
|
|
2378
|
-
function toDocumentRelativeTarget(partPath: string): string {
|
|
2379
|
-
const normalized = normalizePartPath(partPath);
|
|
2380
|
-
return normalized.startsWith("/word/") ? normalized.slice("/word/".length) : normalized.slice(1);
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
function cloneRelationship(relationship: OpcRelationship): OpcRelationship {
|
|
2384
|
-
return { ...relationship };
|
|
2385
|
-
}
|
|
2386
|
-
|
|
2387
|
-
const UTF8_DECODER = new TextDecoder("utf-8");
|
|
2388
|
-
|
|
2389
|
-
function decodeUtf8(bytes: Uint8Array | undefined): string {
|
|
2390
|
-
if (!bytes) {
|
|
2391
|
-
return "";
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
return UTF8_DECODER.decode(bytes);
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
function toUint8Array(bytes: Uint8Array | ArrayBuffer): Uint8Array {
|
|
2398
|
-
return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
|
-
function serializeCanonicalDocumentForExport(document: CanonicalDocumentEnvelope): string {
|
|
2402
|
-
return createCanonicalDocumentSignature(document);
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
function canReuseSourceBytesForCurrentDocument(
|
|
2406
|
-
state: ImportedDocxState,
|
|
2407
|
-
document: CanonicalDocumentEnvelope,
|
|
2408
|
-
): boolean {
|
|
2409
|
-
if (requiresHostMetadataNormalization(state.sourcePackage, state.sourceDocumentPartPath)) {
|
|
2410
|
-
return false;
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
const commentThreads = Object.values(document.review.comments);
|
|
2414
|
-
const hasLiveComments = commentThreads.some((thread) => thread.anchor.kind !== "detached");
|
|
2415
|
-
if (!hasLiveComments) {
|
|
2416
|
-
return true;
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
return Boolean(
|
|
2420
|
-
state.sourceCommentsPartPath &&
|
|
2421
|
-
state.sourceCommentsExtendedPartPath &&
|
|
2422
|
-
state.sourceCommentsIdsPartPath &&
|
|
2423
|
-
state.sourcePeoplePartPath,
|
|
2424
|
-
);
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
function requiresHostMetadataNormalization(
|
|
2428
|
-
sourcePackage: OpcPackage,
|
|
2429
|
-
sourceDocumentPartPath: string,
|
|
2430
|
-
): boolean {
|
|
2431
|
-
return (
|
|
2432
|
-
isSuspiciouslySkeletalWordPackage(sourcePackage, sourceDocumentPartPath) &&
|
|
2433
|
-
!hasHostSafeMetadataPackageStructure(sourcePackage)
|
|
2434
|
-
);
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
function ensureHostMetadataParts(
|
|
2438
|
-
exportSession: ReturnType<typeof createExportSession>,
|
|
2439
|
-
sourcePackage: OpcPackage,
|
|
2440
|
-
document: CanonicalDocumentEnvelope,
|
|
2441
|
-
): void {
|
|
2442
|
-
const corePropertiesPart = sourcePackage.parts.get(CORE_PROPERTIES_PART_PATH);
|
|
2443
|
-
if (!corePropertiesPart || corePropertiesPart.contentType !== CORE_PROPERTIES_CONTENT_TYPE) {
|
|
2444
|
-
exportSession.replaceOwnedPart({
|
|
2445
|
-
path: CORE_PROPERTIES_PART_PATH,
|
|
2446
|
-
bytes: corePropertiesPart?.bytes ?? new TextEncoder().encode(buildCorePropertiesXml(document)),
|
|
2447
|
-
contentType: CORE_PROPERTIES_CONTENT_TYPE,
|
|
2448
|
-
compression: corePropertiesPart?.compression,
|
|
2449
|
-
});
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
const appPropertiesPart = sourcePackage.parts.get(APP_PROPERTIES_PART_PATH);
|
|
2453
|
-
if (!appPropertiesPart || appPropertiesPart.contentType !== APP_PROPERTIES_CONTENT_TYPE) {
|
|
2454
|
-
exportSession.replaceOwnedPart({
|
|
2455
|
-
path: APP_PROPERTIES_PART_PATH,
|
|
2456
|
-
bytes: appPropertiesPart?.bytes ?? new TextEncoder().encode(buildAppPropertiesXml()),
|
|
2457
|
-
contentType: APP_PROPERTIES_CONTENT_TYPE,
|
|
2458
|
-
compression: appPropertiesPart?.compression,
|
|
2459
|
-
});
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
exportSession.ensurePackageRelationship({
|
|
2463
|
-
type: CORE_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2464
|
-
target: CORE_PROPERTIES_PART_PATH,
|
|
2465
|
-
preferredId: "rIdDocPropsCore",
|
|
2466
|
-
});
|
|
2467
|
-
exportSession.ensurePackageRelationship({
|
|
2468
|
-
type: APP_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2469
|
-
target: APP_PROPERTIES_PART_PATH,
|
|
2470
|
-
preferredId: "rIdDocPropsApp",
|
|
2471
|
-
});
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
function hasHostSafeMetadataPackageStructure(sourcePackage: OpcPackage): boolean {
|
|
2475
|
-
const corePropertiesPart = sourcePackage.parts.get(CORE_PROPERTIES_PART_PATH);
|
|
2476
|
-
const appPropertiesPart = sourcePackage.parts.get(APP_PROPERTIES_PART_PATH);
|
|
2477
|
-
return (
|
|
2478
|
-
corePropertiesPart?.contentType === CORE_PROPERTIES_CONTENT_TYPE &&
|
|
2479
|
-
appPropertiesPart?.contentType === APP_PROPERTIES_CONTENT_TYPE &&
|
|
2480
|
-
hasPackageRelationshipTarget(
|
|
2481
|
-
sourcePackage,
|
|
2482
|
-
CORE_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2483
|
-
CORE_PROPERTIES_PART_PATH,
|
|
2484
|
-
) &&
|
|
2485
|
-
hasPackageRelationshipTarget(
|
|
2486
|
-
sourcePackage,
|
|
2487
|
-
APP_PROPERTIES_RELATIONSHIP_TYPE,
|
|
2488
|
-
APP_PROPERTIES_PART_PATH,
|
|
2489
|
-
)
|
|
2490
|
-
);
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
function hasPackageRelationshipTarget(
|
|
2494
|
-
sourcePackage: OpcPackage,
|
|
2495
|
-
relationshipType: string,
|
|
2496
|
-
targetPartPath: string,
|
|
2497
|
-
): boolean {
|
|
2498
|
-
return sourcePackage.manifest.packageRelationships.some(
|
|
2499
|
-
(relationship) =>
|
|
2500
|
-
relationship.type === relationshipType &&
|
|
2501
|
-
relationship.targetMode === "internal" &&
|
|
2502
|
-
resolveRelationshipTarget(null, relationship) === targetPartPath,
|
|
2503
|
-
);
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
function isSuspiciouslySkeletalWordPackage(
|
|
2507
|
-
sourcePackage: OpcPackage,
|
|
2508
|
-
sourceDocumentPartPath: string,
|
|
2509
|
-
): boolean {
|
|
2510
|
-
const allowedPaths = new Set<string>([
|
|
2511
|
-
CONTENT_TYPES_PATH,
|
|
2512
|
-
PACKAGE_RELATIONSHIPS_PATH,
|
|
2513
|
-
sourceDocumentPartPath,
|
|
2514
|
-
]);
|
|
2515
|
-
const relationshipsPartPath = getRelationshipsPartPath(sourceDocumentPartPath);
|
|
2516
|
-
if (relationshipsPartPath) {
|
|
2517
|
-
allowedPaths.add(relationshipsPartPath);
|
|
2518
|
-
}
|
|
2519
|
-
|
|
2520
|
-
return [...sourcePackage.parts.keys()].every((partPath) => allowedPaths.has(partPath));
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
function buildCorePropertiesXml(document: CanonicalDocumentEnvelope): string {
|
|
2524
|
-
const { metadata } = document;
|
|
2525
|
-
const keywords =
|
|
2526
|
-
Array.isArray(metadata.keywords) && metadata.keywords.length > 0
|
|
2527
|
-
? metadata.keywords.join(", ")
|
|
2528
|
-
: undefined;
|
|
2529
|
-
const propertyLines = [
|
|
2530
|
-
xmlNode("dc:title", metadata.title),
|
|
2531
|
-
xmlNode("dc:subject", metadata.subject),
|
|
2532
|
-
xmlNode("dc:description", metadata.description),
|
|
2533
|
-
xmlNode("dc:language", metadata.language),
|
|
2534
|
-
xmlNode("cp:keywords", keywords),
|
|
2535
|
-
xmlNode("cp:category", metadata.category),
|
|
2536
|
-
xmlNode('dcterms:created xsi:type="dcterms:W3CDTF"', document.createdAt),
|
|
2537
|
-
xmlNode('dcterms:modified xsi:type="dcterms:W3CDTF"', document.updatedAt),
|
|
2538
|
-
].filter((line): line is string => Boolean(line));
|
|
2539
|
-
|
|
2540
|
-
return [
|
|
2541
|
-
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
2542
|
-
`<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">`,
|
|
2543
|
-
...propertyLines.map((line) => ` ${line}`),
|
|
2544
|
-
`</cp:coreProperties>`,
|
|
2545
|
-
].join("\n");
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
|
-
function buildAppPropertiesXml(): string {
|
|
2549
|
-
return [
|
|
2550
|
-
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
2551
|
-
`<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">`,
|
|
2552
|
-
` <Application>React OOXML Office</Application>`,
|
|
2553
|
-
` <AppVersion>1.0</AppVersion>`,
|
|
2554
|
-
`</Properties>`,
|
|
2555
|
-
].join("\n");
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
function xmlNode(tagName: string, value: string | undefined): string | undefined {
|
|
2559
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
2560
|
-
return undefined;
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
return `<${tagName}>${escapeXml(value)}</${tagName.split(" ", 1)[0]}>`;
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
|
-
function serializeProtectionRangesIntoDocumentXml(
|
|
2567
|
-
documentXml: string,
|
|
2568
|
-
protection: ProtectionSnapshot,
|
|
2569
|
-
paragraphs = mapParagraphBoundaries(documentXml),
|
|
2570
|
-
): string {
|
|
2571
|
-
if (protection.ranges.length === 0) {
|
|
2572
|
-
return documentXml;
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
const insertions = new Map<number, string[]>();
|
|
2576
|
-
|
|
2577
|
-
for (const range of protection.ranges) {
|
|
2578
|
-
if (typeof range.start !== "number" || typeof range.end !== "number") {
|
|
2579
|
-
continue;
|
|
2580
|
-
}
|
|
2581
|
-
const rangeStart = range.start;
|
|
2582
|
-
const rangeEnd = range.end;
|
|
2583
|
-
|
|
2584
|
-
const startParagraph = paragraphs.find(
|
|
2585
|
-
(candidate) => rangeStart >= candidate.start && rangeStart <= candidate.end,
|
|
2586
|
-
);
|
|
2587
|
-
const endParagraph = paragraphs.find(
|
|
2588
|
-
(candidate) => rangeEnd >= candidate.start && rangeEnd <= candidate.end,
|
|
2589
|
-
);
|
|
2590
|
-
if (!startParagraph || !endParagraph) {
|
|
2591
|
-
continue;
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
const startIndex =
|
|
2595
|
-
startParagraph.boundaries.get(rangeStart) ??
|
|
2596
|
-
findNearestBoundaryIndex(startParagraph.boundaries, rangeStart, "backward");
|
|
2597
|
-
const endIndex =
|
|
2598
|
-
endParagraph.boundaries.get(rangeEnd) ??
|
|
2599
|
-
findNearestBoundaryIndex(endParagraph.boundaries, rangeEnd, "forward");
|
|
2600
|
-
if (startIndex === undefined || endIndex === undefined) {
|
|
2601
|
-
continue;
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
const permStartXml = [
|
|
2605
|
-
`<w:permStart`,
|
|
2606
|
-
` w:id="${escapeXmlAttribute(range.rangeId)}"`,
|
|
2607
|
-
range.editorGroup ? ` w:edGrp="${escapeXmlAttribute(range.editorGroup)}"` : "",
|
|
2608
|
-
range.editor ? ` w:ed="${escapeXmlAttribute(range.editor)}"` : "",
|
|
2609
|
-
`/>`,
|
|
2610
|
-
].join("");
|
|
2611
|
-
const permEndXml = `<w:permEnd w:id="${escapeXmlAttribute(range.rangeId)}"/>`;
|
|
2612
|
-
|
|
2613
|
-
pushProtectionInsertion(insertions, startIndex, permStartXml);
|
|
2614
|
-
pushProtectionInsertion(insertions, endIndex, permEndXml);
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
if (insertions.size === 0) {
|
|
2618
|
-
return documentXml;
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
const parts: string[] = [];
|
|
2622
|
-
let cursor = 0;
|
|
2623
|
-
for (const [index, snippets] of [...insertions.entries()].sort(([left], [right]) => left - right)) {
|
|
2624
|
-
parts.push(documentXml.slice(cursor, index));
|
|
2625
|
-
parts.push(...snippets);
|
|
2626
|
-
cursor = index;
|
|
2627
|
-
}
|
|
2628
|
-
parts.push(documentXml.slice(cursor));
|
|
2629
|
-
return parts.join("");
|
|
2630
|
-
}
|
|
2631
|
-
|
|
2632
|
-
function pushProtectionInsertion(
|
|
2633
|
-
insertions: Map<number, string[]>,
|
|
2634
|
-
index: number,
|
|
2635
|
-
xml: string,
|
|
2636
|
-
): void {
|
|
2637
|
-
const existing = insertions.get(index);
|
|
2638
|
-
if (existing) {
|
|
2639
|
-
existing.push(xml);
|
|
2640
|
-
return;
|
|
2641
|
-
}
|
|
2642
|
-
insertions.set(index, [xml]);
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
function findNearestBoundaryIndex(
|
|
2646
|
-
boundaries: Map<number, number>,
|
|
2647
|
-
position: number,
|
|
2648
|
-
direction: "backward" | "forward",
|
|
2649
|
-
): number | undefined {
|
|
2650
|
-
const ordered = [...boundaries.entries()].sort(([left], [right]) => left - right);
|
|
2651
|
-
if (direction === "backward") {
|
|
2652
|
-
for (let index = ordered.length - 1; index >= 0; index -= 1) {
|
|
2653
|
-
const [boundaryPos, boundaryIndex] = ordered[index]!;
|
|
2654
|
-
if (boundaryPos <= position) {
|
|
2655
|
-
return boundaryIndex;
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
return undefined;
|
|
2659
|
-
}
|
|
2660
|
-
for (const [boundaryPos, boundaryIndex] of ordered) {
|
|
2661
|
-
if (boundaryPos >= position) {
|
|
2662
|
-
return boundaryIndex;
|
|
2663
|
-
}
|
|
2664
|
-
}
|
|
2665
|
-
return undefined;
|
|
2666
|
-
}
|
|
2667
|
-
|
|
2668
|
-
function escapeXml(value: string): string {
|
|
2669
|
-
return value
|
|
2670
|
-
.replace(/&/g, "&")
|
|
2671
|
-
.replace(/</g, "<")
|
|
2672
|
-
.replace(/>/g, ">")
|
|
2673
|
-
.replace(/\"/g, """)
|
|
2674
|
-
.replace(/'/g, "'");
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
function escapeXmlAttribute(value: string): string {
|
|
2678
|
-
return value
|
|
2679
|
-
.replace(/&/g, "&")
|
|
2680
|
-
.replace(/</g, "<")
|
|
2681
|
-
.replace(/>/g, ">")
|
|
2682
|
-
.replace(/"/g, """);
|
|
2683
|
-
}
|
|
2684
|
-
|
|
2685
|
-
// ---------------------------------------------------------------------------
|
|
2686
|
-
// Protection range extraction
|
|
2687
|
-
// ---------------------------------------------------------------------------
|
|
2688
|
-
|
|
2689
|
-
const EMPTY_PROTECTION_SNAPSHOT: ProtectionSnapshot = {
|
|
2690
|
-
hasDocumentProtection: false,
|
|
2691
|
-
enforcementActive: false,
|
|
2692
|
-
ranges: [],
|
|
2693
|
-
enforcedRangeCount: 0,
|
|
2694
|
-
preservedRangeCount: 0,
|
|
2695
|
-
};
|
|
2696
|
-
|
|
2697
|
-
interface DocumentProtectionMeta {
|
|
2698
|
-
editType?: string;
|
|
2699
|
-
enforcement: boolean;
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
function extractDocumentProtection(settingsXml: string): DocumentProtectionMeta {
|
|
2703
|
-
if (!settingsXml) return { enforcement: false };
|
|
2704
|
-
const match = settingsXml.match(/<w:documentProtection\b([^/>]*)\/?>/);
|
|
2705
|
-
if (!match) return { enforcement: false };
|
|
2706
|
-
const attrs = match[1];
|
|
2707
|
-
const editTypeMatch = attrs.match(/w:edit="([^"]*)"/);
|
|
2708
|
-
const enforcementMatch = attrs.match(/w:enforcement="([^"]*)"/);
|
|
2709
|
-
const editType = editTypeMatch?.[1];
|
|
2710
|
-
const enforcement = enforcementMatch?.[1] === "1" || enforcementMatch?.[1] === "true";
|
|
2711
|
-
return { editType, enforcement };
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
function extractProtectionRanges(blocks: readonly ParsedBlockNode[]): ProtectionRange[] {
|
|
2715
|
-
const ranges: ProtectionRange[] = [];
|
|
2716
|
-
const openRanges = new Map<string, Omit<ProtectionRange, "end">>();
|
|
2717
|
-
collectProtectionRangesFromBlocks(blocks, ranges, openRanges, 0);
|
|
2718
|
-
return ranges;
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
function collectProtectionRangesFromBlocks(
|
|
2722
|
-
blocks: readonly ParsedBlockNode[],
|
|
2723
|
-
ranges: ProtectionRange[],
|
|
2724
|
-
openRanges: Map<string, Omit<ProtectionRange, "end">>,
|
|
2725
|
-
cursor: number,
|
|
2726
|
-
): number {
|
|
2727
|
-
let nextCursor = cursor;
|
|
2728
|
-
let previousParagraph = false;
|
|
2729
|
-
|
|
2730
|
-
for (const block of blocks) {
|
|
2731
|
-
if (block.type === "paragraph") {
|
|
2732
|
-
if (previousParagraph) {
|
|
2733
|
-
nextCursor += 1;
|
|
2734
|
-
}
|
|
2735
|
-
nextCursor = collectProtectionRangesFromInlines(
|
|
2736
|
-
block.children,
|
|
2737
|
-
ranges,
|
|
2738
|
-
openRanges,
|
|
2739
|
-
nextCursor,
|
|
2740
|
-
);
|
|
2741
|
-
previousParagraph = true;
|
|
2742
|
-
continue;
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
if (block.type === "table") {
|
|
2746
|
-
nextCursor += 1;
|
|
2747
|
-
previousParagraph = false;
|
|
2748
|
-
for (const row of block.rows) {
|
|
2749
|
-
for (const cell of row.cells) {
|
|
2750
|
-
nextCursor = collectProtectionRangesFromBlocks(
|
|
2751
|
-
cell.children,
|
|
2752
|
-
ranges,
|
|
2753
|
-
openRanges,
|
|
2754
|
-
nextCursor,
|
|
2755
|
-
);
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
continue;
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
|
-
if (block.type === "sdt" || block.type === "custom_xml") {
|
|
2762
|
-
nextCursor = collectProtectionRangesFromBlocks(
|
|
2763
|
-
block.children,
|
|
2764
|
-
ranges,
|
|
2765
|
-
openRanges,
|
|
2766
|
-
nextCursor,
|
|
2767
|
-
);
|
|
2768
|
-
previousParagraph = false;
|
|
2769
|
-
continue;
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
nextCursor += 1;
|
|
2773
|
-
previousParagraph = false;
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
return nextCursor;
|
|
2777
|
-
}
|
|
2778
|
-
|
|
2779
|
-
function collectProtectionRangesFromInlines(
|
|
2780
|
-
nodes: readonly ParsedInlineNode[],
|
|
2781
|
-
ranges: ProtectionRange[],
|
|
2782
|
-
openRanges: Map<string, Omit<ProtectionRange, "end">>,
|
|
2783
|
-
cursor: number,
|
|
2784
|
-
): number {
|
|
2785
|
-
let nextCursor = cursor;
|
|
2786
|
-
|
|
2787
|
-
for (const node of nodes) {
|
|
2788
|
-
if (node.type === "perm_start") {
|
|
2789
|
-
openRanges.set(node.rangeId, {
|
|
2790
|
-
rangeId: node.rangeId,
|
|
2791
|
-
start: nextCursor,
|
|
2792
|
-
...(node.editorGroup ? { editorGroup: node.editorGroup } : {}),
|
|
2793
|
-
...(node.editor ? { editor: node.editor } : {}),
|
|
2794
|
-
enforced: false,
|
|
2795
|
-
enforcementReason:
|
|
2796
|
-
"preserve-only: runtime does not yet enforce permission range boundaries",
|
|
2797
|
-
});
|
|
2798
|
-
continue;
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
|
-
if (node.type === "perm_end") {
|
|
2802
|
-
const openRange = openRanges.get(node.rangeId);
|
|
2803
|
-
if (openRange) {
|
|
2804
|
-
ranges.push({
|
|
2805
|
-
...openRange,
|
|
2806
|
-
end: nextCursor,
|
|
2807
|
-
});
|
|
2808
|
-
openRanges.delete(node.rangeId);
|
|
2809
|
-
}
|
|
2810
|
-
continue;
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
|
-
nextCursor += measureParsedInlineNode(node);
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
return nextCursor;
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
|
-
function measureParsedInlineNode(node: ParsedInlineNode): number {
|
|
2820
|
-
switch (node.type) {
|
|
2821
|
-
case "text":
|
|
2822
|
-
return node.text.length;
|
|
2823
|
-
case "tab":
|
|
2824
|
-
case "hard_break":
|
|
2825
|
-
case "column_break":
|
|
2826
|
-
case "footnote_ref":
|
|
2827
|
-
case "image":
|
|
2828
|
-
case "bookmark_start":
|
|
2829
|
-
case "bookmark_end":
|
|
2830
|
-
return 1;
|
|
2831
|
-
case "hyperlink":
|
|
2832
|
-
return node.children.reduce((size, child) => size + measureParsedInlineNode(child), 0);
|
|
2833
|
-
case "field": {
|
|
2834
|
-
const content = parseMainDocumentXml(
|
|
2835
|
-
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p>${node.contentXml}</w:p></w:body></w:document>`,
|
|
2836
|
-
);
|
|
2837
|
-
if (content.blocks[0]?.type === "paragraph") {
|
|
2838
|
-
return content.blocks[0].children.reduce(
|
|
2839
|
-
(size, child) => size + measureParsedInlineNode(child),
|
|
2840
|
-
0,
|
|
2841
|
-
);
|
|
2842
|
-
}
|
|
2843
|
-
return 1;
|
|
2844
|
-
}
|
|
2845
|
-
default:
|
|
2846
|
-
return 1;
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
function buildProtectionSnapshot(
|
|
2851
|
-
documentProtection: DocumentProtectionMeta,
|
|
2852
|
-
ranges: ProtectionRange[],
|
|
2853
|
-
): ProtectionSnapshot {
|
|
2854
|
-
const hasDocumentProtection =
|
|
2855
|
-
documentProtection.editType !== undefined || documentProtection.enforcement;
|
|
2856
|
-
const enforceRanges =
|
|
2857
|
-
documentProtection.editType === "readOnly" || documentProtection.editType === "comments";
|
|
2858
|
-
const normalizedRanges = ranges.map((range) => {
|
|
2859
|
-
const canEnforce =
|
|
2860
|
-
hasDocumentProtection &&
|
|
2861
|
-
documentProtection.enforcement &&
|
|
2862
|
-
enforceRanges &&
|
|
2863
|
-
typeof range.start === "number" &&
|
|
2864
|
-
typeof range.end === "number" &&
|
|
2865
|
-
range.end >= range.start;
|
|
2866
|
-
return {
|
|
2867
|
-
...range,
|
|
2868
|
-
enforced: canEnforce,
|
|
2869
|
-
enforcementReason: canEnforce
|
|
2870
|
-
? "runtime-enforced: permission range is mapped to canonical positions"
|
|
2871
|
-
: "preserve-only: runtime does not yet enforce permission range boundaries",
|
|
2872
|
-
};
|
|
2873
|
-
});
|
|
2874
|
-
return {
|
|
2875
|
-
hasDocumentProtection,
|
|
2876
|
-
editType: documentProtection.editType,
|
|
2877
|
-
enforcementActive: documentProtection.enforcement,
|
|
2878
|
-
ranges: normalizedRanges,
|
|
2879
|
-
enforcedRangeCount: normalizedRanges.filter((r) => r.enforced).length,
|
|
2880
|
-
preservedRangeCount: normalizedRanges.filter((r) => !r.enforced).length,
|
|
2881
|
-
};
|
|
2882
|
-
}
|