@beyondwork/docx-react-component 1.0.29 → 1.0.30
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/package.json +65 -96
- package/src/README.md +85 -0
- package/src/api/README.md +26 -0
- package/src/api/public-types.ts +1952 -0
- package/src/api/session-state.ts +62 -0
- package/src/compare/diff-engine.ts +623 -0
- package/src/compare/export-redlines.ts +280 -0
- package/src/compare/index.ts +25 -0
- package/src/compare/snapshot.ts +97 -0
- package/src/component-inventory.md +99 -0
- package/src/core/README.md +10 -0
- package/src/core/commands/README.md +3 -0
- package/{dist/chunk-TJBP2K4T.js → src/core/commands/formatting-commands.ts} +536 -196
- package/src/core/commands/image-commands.ts +373 -0
- package/src/core/commands/index.ts +1879 -0
- package/src/core/commands/list-commands.ts +565 -0
- package/src/core/commands/paragraph-layout-commands.ts +339 -0
- package/src/core/commands/review-commands.ts +108 -0
- package/{dist/core/commands/section-layout-commands.cjs → src/core/commands/section-layout-commands.ts} +340 -137
- package/src/core/commands/structural-helpers.ts +309 -0
- package/{dist/core/commands/style-commands.cjs → src/core/commands/style-commands.ts} +113 -65
- package/src/core/commands/table-structure-commands.ts +854 -0
- package/{dist/chunk-UZXBISGO.js → src/core/commands/text-commands.ts} +142 -86
- package/src/core/schema/README.md +3 -0
- package/src/core/schema/text-schema.ts +516 -0
- package/src/core/search/search-text.ts +357 -0
- package/src/core/selection/README.md +3 -0
- package/src/core/selection/mapping.ts +289 -0
- package/src/core/selection/review-anchors.ts +183 -0
- package/src/core/state/README.md +3 -0
- package/src/core/state/editor-state.ts +892 -0
- package/src/core/state/text-transaction.ts +869 -0
- package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
- package/src/formats/xlsx/io/parse-sheet.ts +459 -0
- package/src/formats/xlsx/io/parse-styles.ts +59 -0
- package/src/formats/xlsx/io/parse-workbook.ts +75 -0
- package/src/formats/xlsx/io/serialize-shared-strings.ts +72 -0
- package/src/formats/xlsx/io/serialize-sheet.ts +333 -0
- package/src/formats/xlsx/io/serialize-styles.ts +98 -0
- package/src/formats/xlsx/io/serialize-workbook.ts +429 -0
- package/src/formats/xlsx/io/xlsx-session.ts +314 -0
- package/src/formats/xlsx/model/cell.ts +189 -0
- package/src/formats/xlsx/model/sheet.ts +326 -0
- package/src/formats/xlsx/model/styles.ts +118 -0
- package/src/formats/xlsx/model/workbook.ts +453 -0
- package/src/formats/xlsx/runtime/cell-commands.ts +567 -0
- package/src/formats/xlsx/runtime/sheet-commands.ts +206 -0
- package/src/formats/xlsx/runtime/workbook-runtime.ts +177 -0
- package/src/formats/xlsx/runtime/workbook-transaction.ts +822 -0
- package/src/index.ts +142 -0
- package/src/io/README.md +10 -0
- package/src/io/docx-session.ts +3175 -0
- package/src/io/export/README.md +3 -0
- package/src/io/export/export-session.ts +220 -0
- package/src/io/export/minimal-docx.ts +115 -0
- package/src/io/export/reattach-preserved-parts.ts +54 -0
- package/src/io/export/serialize-comments.ts +947 -0
- package/src/io/export/serialize-footnotes.ts +394 -0
- package/src/io/export/serialize-headers-footers.ts +368 -0
- package/src/io/export/serialize-main-document.ts +1342 -0
- package/src/io/export/serialize-numbering.ts +218 -0
- package/src/io/export/serialize-revisions.ts +389 -0
- package/src/io/export/serialize-runtime-revisions.ts +463 -0
- package/src/io/export/serialize-tables.ts +174 -0
- package/src/io/export/split-review-boundaries.ts +356 -0
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/README.md +3 -0
- package/src/io/normalize/normalize-text.ts +670 -0
- package/src/io/ooxml/README.md +3 -0
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/numbering-sentinels.ts +44 -0
- package/src/io/ooxml/parse-comments.ts +852 -0
- package/src/io/ooxml/parse-complex-content.ts +287 -0
- package/src/io/ooxml/parse-fields.ts +834 -0
- package/src/io/ooxml/parse-footnotes.ts +952 -0
- package/src/io/ooxml/parse-headers-footers.ts +1212 -0
- package/src/io/ooxml/parse-inline-media.ts +461 -0
- package/src/io/ooxml/parse-main-document.ts +2947 -0
- package/src/io/ooxml/parse-numbering.ts +747 -0
- package/src/io/ooxml/parse-revisions.ts +1045 -0
- package/src/io/ooxml/parse-settings.ts +184 -0
- package/src/io/ooxml/parse-shapes.ts +296 -0
- package/src/io/ooxml/parse-styles.ts +639 -0
- package/src/io/ooxml/parse-tables.ts +627 -0
- package/src/io/ooxml/parse-theme.ts +346 -0
- package/src/io/ooxml/part-manifest.ts +136 -0
- package/src/io/ooxml/revision-boundaries.ts +475 -0
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/io/opc/README.md +3 -0
- package/src/io/opc/corrupt-package.ts +166 -0
- package/src/io/opc/docx-package.ts +74 -0
- package/src/io/opc/package-reader.ts +325 -0
- package/src/io/opc/package-writer.ts +273 -0
- package/src/io/source-package-provenance.ts +241 -0
- package/{dist/chunk-RMH72RZI.js → src/legal/bookmarks.ts} +130 -44
- package/src/legal/cross-references.ts +414 -0
- package/src/legal/defined-terms.ts +203 -0
- package/src/legal/index.ts +32 -0
- package/src/legal/signature-blocks.ts +259 -0
- package/src/model/README.md +3 -0
- package/src/model/canonical-document.ts +2722 -0
- package/src/model/cds-1.0.0.ts +212 -0
- package/src/model/snapshot.ts +760 -0
- package/src/preservation/README.md +3 -0
- package/src/preservation/markup-compatibility.ts +48 -0
- package/src/preservation/opaque-fragment-store.ts +89 -0
- package/src/preservation/opaque-region.ts +233 -0
- package/src/preservation/package-preservation.ts +113 -0
- package/src/preservation/preserved-part-manifest.ts +56 -0
- package/src/preservation/relationship-retention.ts +57 -0
- package/src/preservation/store.ts +255 -0
- package/src/review/README.md +16 -0
- package/src/review/store/README.md +3 -0
- package/src/review/store/comment-anchors.ts +70 -0
- package/src/review/store/comment-remapping.ts +154 -0
- package/src/review/store/comment-store.ts +349 -0
- package/src/review/store/comment-thread.ts +109 -0
- package/src/review/store/revision-actions.ts +423 -0
- package/src/review/store/revision-store.ts +323 -0
- package/src/review/store/revision-types.ts +182 -0
- package/src/review/store/runtime-comment-store.ts +43 -0
- package/src/runtime/README.md +3 -0
- package/src/runtime/ai-action-policy.ts +764 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +616 -0
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +4055 -0
- package/src/runtime/document-search.ts +145 -0
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +244 -0
- package/src/runtime/page-layout-estimation.ts +305 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +241 -0
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/review-runtime.ts +44 -0
- package/src/runtime/revision-runtime.ts +107 -0
- package/src/runtime/session-capabilities.ts +192 -0
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +1553 -0
- package/src/runtime/table-commands.ts +173 -0
- package/src/runtime/table-schema.ts +309 -0
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +493 -0
- package/src/runtime/virtualized-rendering.ts +258 -0
- package/src/runtime/workflow-markup.ts +393 -0
- package/src/ui/README.md +30 -0
- package/src/ui/WordReviewEditor.tsx +5268 -0
- package/src/ui/browser-export.ts +52 -0
- package/src/ui/comments/README.md +3 -0
- package/src/ui/compatibility/README.md +3 -0
- package/src/ui/editor-command-bag.ts +127 -0
- package/src/ui/editor-runtime-boundary.ts +1558 -0
- package/src/ui/editor-shell-view.tsx +144 -0
- package/src/ui/editor-surface/README.md +3 -0
- package/src/ui/editor-surface-controller.tsx +66 -0
- package/src/ui/headless/comment-decoration-model.ts +124 -0
- package/src/ui/headless/preserve-editor-selection.ts +5 -0
- package/src/ui/headless/revision-decoration-model.ts +128 -0
- package/src/ui/headless/selection-helpers.ts +54 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +11 -0
- package/src/ui/headless/use-editor-keyboard.ts +103 -0
- package/src/ui/review/README.md +3 -0
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui/shared/revision-filters.ts +31 -0
- package/src/ui/status/README.md +3 -0
- package/src/ui/theme/README.md +3 -0
- package/src/ui/toolbar/README.md +3 -0
- package/src/ui/workflow-surface-blocked-rails.ts +94 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +64 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +121 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +30 -0
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +365 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +186 -0
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +139 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +200 -0
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
- package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +189 -0
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +411 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +927 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +567 -0
- package/src/ui-tailwind/editor-surface/search-plugin.ts +168 -0
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +65 -0
- package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +129 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +58 -0
- package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +1047 -0
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +503 -0
- package/src/ui-tailwind/index.ts +62 -0
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +406 -0
- package/src/ui-tailwind/review/tw-health-panel.tsx +149 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +122 -0
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
- package/src/ui-tailwind/status/tw-status-bar.tsx +65 -0
- package/{dist → src}/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +52 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +1133 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +1460 -0
- package/src/validation/README.md +3 -0
- package/src/validation/compatibility-engine.ts +878 -0
- package/src/validation/compatibility-report.ts +161 -0
- package/src/validation/diagnostics.ts +204 -0
- package/src/validation/docx-comment-proof.ts +720 -0
- package/src/validation/import-diagnostics.ts +128 -0
- package/src/validation/low-priority-word-surfaces.ts +373 -0
- package/dist/canonical-document-BLEbzL2J.d.cts +0 -844
- package/dist/canonical-document-BLEbzL2J.d.ts +0 -844
- package/dist/chunk-2FJS5GZM.js +0 -763
- package/dist/chunk-2FJS5GZM.js.map +0 -1
- package/dist/chunk-2OQBZS3F.js +0 -446
- package/dist/chunk-2OQBZS3F.js.map +0 -1
- package/dist/chunk-2S7W4KFO.js +0 -127
- package/dist/chunk-2S7W4KFO.js.map +0 -1
- package/dist/chunk-2TG72QSW.js +0 -3874
- package/dist/chunk-2TG72QSW.js.map +0 -1
- package/dist/chunk-36QNIZBO.js +0 -532
- package/dist/chunk-36QNIZBO.js.map +0 -1
- package/dist/chunk-4AQOYAW4.js +0 -3069
- package/dist/chunk-4AQOYAW4.js.map +0 -1
- package/dist/chunk-4D5EWJ3P.js +0 -77
- package/dist/chunk-4D5EWJ3P.js.map +0 -1
- package/dist/chunk-5FN54NDH.js +0 -2257
- package/dist/chunk-5FN54NDH.js.map +0 -1
- package/dist/chunk-BOYGQYRQ.js +0 -7306
- package/dist/chunk-BOYGQYRQ.js.map +0 -1
- package/dist/chunk-CN3XMECL.js +0 -212
- package/dist/chunk-CN3XMECL.js.map +0 -1
- package/dist/chunk-EBI3BX6U.js +0 -164
- package/dist/chunk-EBI3BX6U.js.map +0 -1
- package/dist/chunk-EILUG3VB.js +0 -1275
- package/dist/chunk-EILUG3VB.js.map +0 -1
- package/dist/chunk-FUDY333O.js +0 -70
- package/dist/chunk-FUDY333O.js.map +0 -1
- package/dist/chunk-GBVOWFIK.js +0 -1237
- package/dist/chunk-GBVOWFIK.js.map +0 -1
- package/dist/chunk-H4TQ3H3Y.js +0 -262
- package/dist/chunk-H4TQ3H3Y.js.map +0 -1
- package/dist/chunk-JGB3IXZO.js +0 -189
- package/dist/chunk-JGB3IXZO.js.map +0 -1
- package/dist/chunk-KD2QRQPY.js +0 -4342
- package/dist/chunk-KD2QRQPY.js.map +0 -1
- package/dist/chunk-KLMXQVYK.js +0 -369
- package/dist/chunk-KLMXQVYK.js.map +0 -1
- package/dist/chunk-KZUG5KFQ.js +0 -214
- package/dist/chunk-KZUG5KFQ.js.map +0 -1
- package/dist/chunk-QDAQ4CJU.js +0 -345
- package/dist/chunk-QDAQ4CJU.js.map +0 -1
- package/dist/chunk-RMH72RZI.js.map +0 -1
- package/dist/chunk-SWKWQZXM.js +0 -117
- package/dist/chunk-SWKWQZXM.js.map +0 -1
- package/dist/chunk-TJBP2K4T.js.map +0 -1
- package/dist/chunk-TLCEAQDQ.js +0 -542
- package/dist/chunk-TLCEAQDQ.js.map +0 -1
- package/dist/chunk-UZXBISGO.js.map +0 -1
- package/dist/chunk-WGBAKP3Q.js +0 -3220
- package/dist/chunk-WGBAKP3Q.js.map +0 -1
- package/dist/compare/index.cjs +0 -5475
- package/dist/compare/index.cjs.map +0 -1
- package/dist/compare/index.d.cts +0 -114
- package/dist/compare/index.d.ts +0 -114
- package/dist/compare/index.js +0 -731
- package/dist/compare/index.js.map +0 -1
- package/dist/core/commands/formatting-commands.cjs +0 -828
- package/dist/core/commands/formatting-commands.cjs.map +0 -1
- package/dist/core/commands/formatting-commands.d.cts +0 -63
- package/dist/core/commands/formatting-commands.d.ts +0 -63
- package/dist/core/commands/formatting-commands.js +0 -37
- package/dist/core/commands/formatting-commands.js.map +0 -1
- package/dist/core/commands/image-commands.cjs +0 -2023
- package/dist/core/commands/image-commands.cjs.map +0 -1
- package/dist/core/commands/image-commands.d.cts +0 -58
- package/dist/core/commands/image-commands.d.ts +0 -58
- package/dist/core/commands/image-commands.js +0 -18
- package/dist/core/commands/image-commands.js.map +0 -1
- package/dist/core/commands/section-layout-commands.cjs.map +0 -1
- package/dist/core/commands/section-layout-commands.d.cts +0 -62
- package/dist/core/commands/section-layout-commands.d.ts +0 -62
- package/dist/core/commands/section-layout-commands.js +0 -21
- package/dist/core/commands/section-layout-commands.js.map +0 -1
- package/dist/core/commands/style-commands.cjs.map +0 -1
- package/dist/core/commands/style-commands.d.cts +0 -13
- package/dist/core/commands/style-commands.d.ts +0 -13
- package/dist/core/commands/style-commands.js +0 -9
- package/dist/core/commands/style-commands.js.map +0 -1
- package/dist/core/commands/table-structure-commands.cjs +0 -1883
- package/dist/core/commands/table-structure-commands.cjs.map +0 -1
- package/dist/core/commands/table-structure-commands.d.cts +0 -59
- package/dist/core/commands/table-structure-commands.d.ts +0 -59
- package/dist/core/commands/table-structure-commands.js +0 -12
- package/dist/core/commands/table-structure-commands.js.map +0 -1
- package/dist/core/commands/text-commands.cjs +0 -2391
- package/dist/core/commands/text-commands.cjs.map +0 -1
- package/dist/core/commands/text-commands.d.cts +0 -24
- package/dist/core/commands/text-commands.d.ts +0 -24
- package/dist/core/commands/text-commands.js +0 -28
- package/dist/core/commands/text-commands.js.map +0 -1
- package/dist/core/selection/mapping.cjs +0 -200
- package/dist/core/selection/mapping.cjs.map +0 -1
- package/dist/core/selection/mapping.d.cts +0 -2
- package/dist/core/selection/mapping.d.ts +0 -2
- package/dist/core/selection/mapping.js +0 -31
- package/dist/core/selection/mapping.js.map +0 -1
- package/dist/core/state/editor-state.cjs +0 -2278
- package/dist/core/state/editor-state.cjs.map +0 -1
- package/dist/core/state/editor-state.d.cts +0 -2
- package/dist/core/state/editor-state.d.ts +0 -2
- package/dist/core/state/editor-state.js +0 -26
- package/dist/core/state/editor-state.js.map +0 -1
- package/dist/index.cjs +0 -38553
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -15
- package/dist/index.d.ts +0 -15
- package/dist/index.js +0 -7856
- package/dist/index.js.map +0 -1
- package/dist/io/docx-session.cjs +0 -16236
- package/dist/io/docx-session.cjs.map +0 -1
- package/dist/io/docx-session.d.cts +0 -21
- package/dist/io/docx-session.d.ts +0 -21
- package/dist/io/docx-session.js +0 -18
- package/dist/io/docx-session.js.map +0 -1
- package/dist/legal/index.cjs +0 -3900
- package/dist/legal/index.cjs.map +0 -1
- package/dist/legal/index.d.cts +0 -86
- package/dist/legal/index.d.ts +0 -86
- package/dist/legal/index.js +0 -616
- package/dist/legal/index.js.map +0 -1
- package/dist/public-types-7ZL_94cz.d.ts +0 -1573
- package/dist/public-types-CeMaDueh.d.cts +0 -1573
- package/dist/public-types.cjs +0 -19
- package/dist/public-types.cjs.map +0 -1
- package/dist/public-types.d.cts +0 -2
- package/dist/public-types.d.ts +0 -2
- package/dist/public-types.js +0 -1
- package/dist/public-types.js.map +0 -1
- package/dist/runtime/document-runtime.cjs +0 -11140
- package/dist/runtime/document-runtime.cjs.map +0 -1
- package/dist/runtime/document-runtime.d.cts +0 -231
- package/dist/runtime/document-runtime.d.ts +0 -231
- package/dist/runtime/document-runtime.js +0 -21
- package/dist/runtime/document-runtime.js.map +0 -1
- package/dist/structural-helpers-CilgOVhh.d.cts +0 -10
- package/dist/structural-helpers-q0Gd-eBN.d.ts +0 -10
- package/dist/ui-tailwind/editor-surface/search-plugin.cjs +0 -313
- package/dist/ui-tailwind/editor-surface/search-plugin.cjs.map +0 -1
- package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +0 -67
- package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +0 -67
- package/dist/ui-tailwind/editor-surface/search-plugin.js +0 -23
- package/dist/ui-tailwind/editor-surface/search-plugin.js.map +0 -1
- package/dist/ui-tailwind/index.cjs +0 -4833
- package/dist/ui-tailwind/index.cjs.map +0 -1
- package/dist/ui-tailwind/index.d.cts +0 -617
- package/dist/ui-tailwind/index.d.ts +0 -617
- package/dist/ui-tailwind/index.js +0 -575
- package/dist/ui-tailwind/index.js.map +0 -1
|
@@ -0,0 +1,2722 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CDS_SCHEMA_VERSION,
|
|
3
|
+
type ISO8601DateTime,
|
|
4
|
+
type ModelValidationIssue,
|
|
5
|
+
type UUID,
|
|
6
|
+
asPlainObject,
|
|
7
|
+
assertValid,
|
|
8
|
+
expectExactString,
|
|
9
|
+
expectIso8601UtcTimestamp,
|
|
10
|
+
expectString,
|
|
11
|
+
expectStringAllowEmpty,
|
|
12
|
+
expectUuid,
|
|
13
|
+
stableStringify,
|
|
14
|
+
} from "./cds-1.0.0.ts";
|
|
15
|
+
|
|
16
|
+
const CANONICAL_DOCUMENT_TOP_LEVEL_KEYS = [
|
|
17
|
+
"schemaVersion",
|
|
18
|
+
"docId",
|
|
19
|
+
"createdAt",
|
|
20
|
+
"updatedAt",
|
|
21
|
+
"metadata",
|
|
22
|
+
"styles",
|
|
23
|
+
"numbering",
|
|
24
|
+
"media",
|
|
25
|
+
"content",
|
|
26
|
+
"review",
|
|
27
|
+
"preservation",
|
|
28
|
+
"diagnostics",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
const CANONICAL_DOCUMENT_OPTIONAL_KEYS = ["subParts", "fieldRegistry"] as const;
|
|
32
|
+
|
|
33
|
+
const ID_PATTERNS = {
|
|
34
|
+
styleId: /^[A-Za-z_][A-Za-z0-9._-]{0,127}$/,
|
|
35
|
+
abstractNumberingId: /^abstract-num:[A-Za-z0-9._-]{1,120}$/,
|
|
36
|
+
numberingInstanceId: /^num:[A-Za-z0-9._-]{1,120}$/,
|
|
37
|
+
mediaId: /^media:[A-Za-z0-9._/-]{1,120}$/,
|
|
38
|
+
commentId:
|
|
39
|
+
/^(?:comment:[A-Za-z0-9._-]{1,120}|comment-[A-Za-z0-9._-]{1,120}|[0-9]{1,18})$/,
|
|
40
|
+
revisionId:
|
|
41
|
+
/^(?:revision:[A-Za-z0-9._-]{1,120}|change-[A-Za-z0-9._-]{1,120})$/,
|
|
42
|
+
fragmentId: /^fragment:[A-Za-z0-9._-]{1,120}$/,
|
|
43
|
+
warningId: /^warning:[A-Za-z0-9._:-]{1,120}$/,
|
|
44
|
+
diagnosticId: /^diagnostic:[A-Za-z0-9._-]{1,120}$/,
|
|
45
|
+
packagePartName: /^\/[A-Za-z0-9_.\-\/]+\.[A-Za-z0-9]+$/,
|
|
46
|
+
relationshipId: /^rId[A-Za-z0-9._-]{1,120}$/,
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
type StableIdDomain = keyof typeof ID_PATTERNS;
|
|
50
|
+
|
|
51
|
+
export interface CanonicalDocument {
|
|
52
|
+
schemaVersion: typeof CDS_SCHEMA_VERSION;
|
|
53
|
+
docId: UUID;
|
|
54
|
+
createdAt: ISO8601DateTime;
|
|
55
|
+
updatedAt: ISO8601DateTime;
|
|
56
|
+
metadata: DocumentMetadata;
|
|
57
|
+
styles: StylesCatalog;
|
|
58
|
+
numbering: NumberingCatalog;
|
|
59
|
+
media: MediaCatalog;
|
|
60
|
+
content: DocumentRootNode;
|
|
61
|
+
review: ReviewStore;
|
|
62
|
+
preservation: PreservationStore;
|
|
63
|
+
diagnostics: DiagnosticStore;
|
|
64
|
+
subParts?: SubPartsCatalog;
|
|
65
|
+
/** Package-backed field registry for supported field families. */
|
|
66
|
+
fieldRegistry?: FieldRegistry;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface DocumentMetadata {
|
|
70
|
+
title?: string;
|
|
71
|
+
subject?: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
language?: string;
|
|
74
|
+
keywords?: string[];
|
|
75
|
+
category?: string;
|
|
76
|
+
importMode?: string;
|
|
77
|
+
customProperties: Record<string, string>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface StylesCatalog {
|
|
81
|
+
paragraphs: Record<string, ParagraphStyleDefinition>;
|
|
82
|
+
characters: Record<string, CharacterStyleDefinition>;
|
|
83
|
+
tables: Record<string, TableStyleDefinition>;
|
|
84
|
+
latentStyles?: Record<string, LatentStyleDefinition>;
|
|
85
|
+
fromPackage?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ParagraphStyleDefinition {
|
|
89
|
+
styleId: string;
|
|
90
|
+
basedOn?: string;
|
|
91
|
+
nextStyle?: string;
|
|
92
|
+
outlineLevel?: number;
|
|
93
|
+
numbering?: ParagraphStyleNumberingReference;
|
|
94
|
+
displayName: string;
|
|
95
|
+
kind: "paragraph";
|
|
96
|
+
isDefault: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ParagraphStyleNumberingReference {
|
|
100
|
+
numberingInstanceId: string;
|
|
101
|
+
level?: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface CharacterStyleDefinition {
|
|
105
|
+
styleId: string;
|
|
106
|
+
basedOn?: string;
|
|
107
|
+
displayName: string;
|
|
108
|
+
kind: "character";
|
|
109
|
+
isDefault: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface TableStyleDefinition {
|
|
113
|
+
styleId: string;
|
|
114
|
+
basedOn?: string;
|
|
115
|
+
displayName: string;
|
|
116
|
+
kind: "table";
|
|
117
|
+
isDefault: boolean;
|
|
118
|
+
formatting?: TableStyleFormatting;
|
|
119
|
+
conditionalFormatting?: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface LatentStyleDefinition {
|
|
123
|
+
name: string;
|
|
124
|
+
locked?: boolean;
|
|
125
|
+
semiHidden?: boolean;
|
|
126
|
+
unhideWhenUsed?: boolean;
|
|
127
|
+
qFormat?: boolean;
|
|
128
|
+
uiPriority?: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface NumberingCatalog {
|
|
132
|
+
abstractDefinitions: Record<string, AbstractNumberingDefinition>;
|
|
133
|
+
instances: Record<string, NumberingInstance>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface AbstractNumberingDefinition {
|
|
137
|
+
abstractNumberingId: string;
|
|
138
|
+
levels: NumberingLevelDefinition[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface NumberingLevelParagraphGeometry {
|
|
142
|
+
justification?: "left" | "center" | "right" | "both" | "distribute";
|
|
143
|
+
spacing?: ParagraphSpacing;
|
|
144
|
+
indentation?: ParagraphIndentation;
|
|
145
|
+
tabStops?: TabStop[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface NumberingLevelDefinition {
|
|
149
|
+
level: number;
|
|
150
|
+
format: string;
|
|
151
|
+
text: string;
|
|
152
|
+
startAt?: number;
|
|
153
|
+
paragraphStyleId?: string;
|
|
154
|
+
isLegalNumbering?: boolean;
|
|
155
|
+
suffix?: "tab" | "space" | "nothing";
|
|
156
|
+
paragraphGeometry?: NumberingLevelParagraphGeometry;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface NumberingLevelOverrideDefinition {
|
|
160
|
+
level: number;
|
|
161
|
+
format?: string;
|
|
162
|
+
text?: string;
|
|
163
|
+
startAt?: number;
|
|
164
|
+
paragraphStyleId?: string;
|
|
165
|
+
isLegalNumbering?: boolean;
|
|
166
|
+
suffix?: "tab" | "space" | "nothing";
|
|
167
|
+
paragraphGeometry?: NumberingLevelParagraphGeometry;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface NumberingInstance {
|
|
171
|
+
numberingInstanceId: string;
|
|
172
|
+
abstractNumberingId: string;
|
|
173
|
+
overrides: NumberingLevelOverride[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface NumberingLevelOverride {
|
|
177
|
+
level: number;
|
|
178
|
+
startAt?: number;
|
|
179
|
+
levelDefinition?: NumberingLevelOverrideDefinition;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface MediaCatalog {
|
|
183
|
+
items: Record<string, MediaItem>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface MediaItem {
|
|
187
|
+
mediaId: string;
|
|
188
|
+
contentType: string;
|
|
189
|
+
filename: string;
|
|
190
|
+
relationshipId?: string;
|
|
191
|
+
packagePartName: string;
|
|
192
|
+
altText?: string;
|
|
193
|
+
display?: "inline" | "floating";
|
|
194
|
+
widthEmu?: number;
|
|
195
|
+
heightEmu?: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---- Sub-part canonical types ----
|
|
199
|
+
|
|
200
|
+
export type HeaderFooterVariant = "default" | "first" | "even";
|
|
201
|
+
|
|
202
|
+
export interface HeaderDocument {
|
|
203
|
+
variant: HeaderFooterVariant;
|
|
204
|
+
partPath: string;
|
|
205
|
+
relationshipId: string;
|
|
206
|
+
blocks: BlockNode[];
|
|
207
|
+
sectionIndex?: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface FooterDocument {
|
|
211
|
+
variant: HeaderFooterVariant;
|
|
212
|
+
partPath: string;
|
|
213
|
+
relationshipId: string;
|
|
214
|
+
blocks: BlockNode[];
|
|
215
|
+
sectionIndex?: number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface FootnoteDefinition {
|
|
219
|
+
noteId: string;
|
|
220
|
+
kind: "footnote" | "endnote";
|
|
221
|
+
blocks: BlockNode[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface FootnoteCollection {
|
|
225
|
+
footnotes: Record<string, FootnoteDefinition>;
|
|
226
|
+
endnotes: Record<string, FootnoteDefinition>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface ThemeColorScheme {
|
|
230
|
+
name: string;
|
|
231
|
+
colors: Record<string, string>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface ThemeFontScheme {
|
|
235
|
+
name: string;
|
|
236
|
+
majorFont?: string;
|
|
237
|
+
minorFont?: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface ThemeDefinition {
|
|
241
|
+
name?: string;
|
|
242
|
+
colorScheme?: ThemeColorScheme;
|
|
243
|
+
fontScheme?: ThemeFontScheme;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface DocumentSettings {
|
|
247
|
+
evenAndOddHeaders?: boolean;
|
|
248
|
+
zoomLevel?: "pageWidth" | "onePage" | number;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface SubPartsCatalog {
|
|
252
|
+
headers: HeaderDocument[];
|
|
253
|
+
footers: FooterDocument[];
|
|
254
|
+
footnoteCollection?: FootnoteCollection;
|
|
255
|
+
theme?: ThemeDefinition;
|
|
256
|
+
finalSectionProperties?: SectionProperties;
|
|
257
|
+
resolvedTheme?: ResolvedTheme;
|
|
258
|
+
settings?: DocumentSettings;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface ResolvedTheme {
|
|
262
|
+
colors: Record<string, string>;
|
|
263
|
+
majorFont?: string;
|
|
264
|
+
minorFont?: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---- Inline footnote reference node ----
|
|
268
|
+
|
|
269
|
+
export interface FootnoteRefNode {
|
|
270
|
+
type: "footnote_ref";
|
|
271
|
+
noteId: string;
|
|
272
|
+
noteKind: "footnote" | "endnote";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export type DocumentNode =
|
|
276
|
+
| DocumentRootNode
|
|
277
|
+
| ParagraphNode
|
|
278
|
+
| TableNode
|
|
279
|
+
| TableRowNode
|
|
280
|
+
| TableCellNode
|
|
281
|
+
| SdtNode
|
|
282
|
+
| CustomXmlNode
|
|
283
|
+
| AltChunkNode
|
|
284
|
+
| TextNode
|
|
285
|
+
| HardBreakNode
|
|
286
|
+
| TabNode
|
|
287
|
+
| ColumnBreakNode
|
|
288
|
+
| SymbolNode
|
|
289
|
+
| HyperlinkNode
|
|
290
|
+
| ImageNode
|
|
291
|
+
| FieldNode
|
|
292
|
+
| BookmarkStartNode
|
|
293
|
+
| BookmarkEndNode
|
|
294
|
+
| SectionBreakNode
|
|
295
|
+
| OpaqueInlineNode
|
|
296
|
+
| OpaqueBlockNode
|
|
297
|
+
| FootnoteRefNode
|
|
298
|
+
| ChartPreviewNode
|
|
299
|
+
| SmartArtPreviewNode
|
|
300
|
+
| ShapeNode
|
|
301
|
+
| WordArtNode
|
|
302
|
+
| VmlShapeNode;
|
|
303
|
+
|
|
304
|
+
export interface DocumentRootNode {
|
|
305
|
+
type: "doc";
|
|
306
|
+
children: BlockNode[];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export type BlockNode =
|
|
310
|
+
| ParagraphNode
|
|
311
|
+
| TableNode
|
|
312
|
+
| SdtNode
|
|
313
|
+
| CustomXmlNode
|
|
314
|
+
| AltChunkNode
|
|
315
|
+
| SectionBreakNode
|
|
316
|
+
| OpaqueBlockNode;
|
|
317
|
+
|
|
318
|
+
export interface ParagraphSpacing {
|
|
319
|
+
before?: number;
|
|
320
|
+
after?: number;
|
|
321
|
+
line?: number;
|
|
322
|
+
lineRule?: "auto" | "exact" | "atLeast";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export interface ParagraphIndentation {
|
|
326
|
+
left?: number;
|
|
327
|
+
right?: number;
|
|
328
|
+
firstLine?: number;
|
|
329
|
+
hanging?: number;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface TabStop {
|
|
333
|
+
position: number;
|
|
334
|
+
align: "left" | "center" | "right" | "decimal" | "num" | "bar" | "clear";
|
|
335
|
+
leader?: "none" | "dot" | "hyphen" | "underscore" | "heavy" | "middleDot";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface ParagraphBorders {
|
|
339
|
+
top?: BorderSpec;
|
|
340
|
+
left?: BorderSpec;
|
|
341
|
+
bottom?: BorderSpec;
|
|
342
|
+
right?: BorderSpec;
|
|
343
|
+
bar?: BorderSpec;
|
|
344
|
+
between?: BorderSpec;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export interface ParagraphShading {
|
|
348
|
+
fill?: string;
|
|
349
|
+
color?: string;
|
|
350
|
+
val?: string;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface ParagraphNode {
|
|
354
|
+
type: "paragraph";
|
|
355
|
+
styleId?: string;
|
|
356
|
+
numbering?: {
|
|
357
|
+
numberingInstanceId: string;
|
|
358
|
+
level: number;
|
|
359
|
+
};
|
|
360
|
+
alignment?: "left" | "center" | "right" | "both" | "distribute";
|
|
361
|
+
spacing?: ParagraphSpacing;
|
|
362
|
+
contextualSpacing?: boolean;
|
|
363
|
+
indentation?: ParagraphIndentation;
|
|
364
|
+
tabStops?: TabStop[];
|
|
365
|
+
keepNext?: boolean;
|
|
366
|
+
keepLines?: boolean;
|
|
367
|
+
outlineLevel?: number;
|
|
368
|
+
pageBreakBefore?: boolean;
|
|
369
|
+
widowControl?: boolean;
|
|
370
|
+
borders?: ParagraphBorders;
|
|
371
|
+
shading?: ParagraphShading;
|
|
372
|
+
bidi?: boolean;
|
|
373
|
+
suppressLineNumbers?: boolean;
|
|
374
|
+
cnfStyle?: string;
|
|
375
|
+
children: InlineNode[];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export interface BorderSpec {
|
|
379
|
+
value?: string;
|
|
380
|
+
size?: number;
|
|
381
|
+
space?: number;
|
|
382
|
+
color?: string;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export interface TableBorders {
|
|
386
|
+
top?: BorderSpec;
|
|
387
|
+
left?: BorderSpec;
|
|
388
|
+
bottom?: BorderSpec;
|
|
389
|
+
right?: BorderSpec;
|
|
390
|
+
insideH?: BorderSpec;
|
|
391
|
+
insideV?: BorderSpec;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export interface TableCellBorders {
|
|
395
|
+
top?: BorderSpec;
|
|
396
|
+
left?: BorderSpec;
|
|
397
|
+
bottom?: BorderSpec;
|
|
398
|
+
right?: BorderSpec;
|
|
399
|
+
insideH?: BorderSpec;
|
|
400
|
+
insideV?: BorderSpec;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface TableWidth {
|
|
404
|
+
value: number;
|
|
405
|
+
type: "dxa" | "auto" | "pct" | "nil";
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export interface CellShading {
|
|
409
|
+
fill?: string;
|
|
410
|
+
color?: string;
|
|
411
|
+
val?: string;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export interface TableCellMargins {
|
|
415
|
+
top?: number;
|
|
416
|
+
left?: number;
|
|
417
|
+
bottom?: number;
|
|
418
|
+
right?: number;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface TableLook {
|
|
422
|
+
val?: string;
|
|
423
|
+
firstRow?: boolean;
|
|
424
|
+
lastRow?: boolean;
|
|
425
|
+
firstColumn?: boolean;
|
|
426
|
+
lastColumn?: boolean;
|
|
427
|
+
noHBand?: boolean;
|
|
428
|
+
noVBand?: boolean;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export type TableStyleConditionalRegion =
|
|
432
|
+
| "firstRow"
|
|
433
|
+
| "lastRow"
|
|
434
|
+
| "firstColumn"
|
|
435
|
+
| "lastColumn"
|
|
436
|
+
| "band1Horz"
|
|
437
|
+
| "band2Horz"
|
|
438
|
+
| "band1Vert"
|
|
439
|
+
| "band2Vert";
|
|
440
|
+
|
|
441
|
+
export interface TableStyleFormatting {
|
|
442
|
+
table?: {
|
|
443
|
+
width?: TableWidth;
|
|
444
|
+
alignment?: "left" | "center" | "right";
|
|
445
|
+
borders?: TableBorders;
|
|
446
|
+
cellMargins?: TableCellMargins;
|
|
447
|
+
tblLook?: TableLook;
|
|
448
|
+
};
|
|
449
|
+
row?: {
|
|
450
|
+
height?: number;
|
|
451
|
+
heightRule?: "auto" | "atLeast" | "exact";
|
|
452
|
+
isHeader?: boolean;
|
|
453
|
+
};
|
|
454
|
+
cell?: {
|
|
455
|
+
width?: TableWidth;
|
|
456
|
+
borders?: TableCellBorders;
|
|
457
|
+
shading?: CellShading;
|
|
458
|
+
verticalAlign?: "top" | "center" | "bottom";
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export interface TableNode {
|
|
463
|
+
type: "table";
|
|
464
|
+
styleId?: string;
|
|
465
|
+
propertiesXml?: string;
|
|
466
|
+
gridColumns: number[];
|
|
467
|
+
rows: TableRowNode[];
|
|
468
|
+
width?: TableWidth;
|
|
469
|
+
alignment?: "left" | "center" | "right";
|
|
470
|
+
borders?: TableBorders;
|
|
471
|
+
cellMargins?: TableCellMargins;
|
|
472
|
+
tblLook?: TableLook;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export interface TableRowNode {
|
|
476
|
+
type: "table_row";
|
|
477
|
+
propertiesXml?: string;
|
|
478
|
+
cells: TableCellNode[];
|
|
479
|
+
gridBefore?: number;
|
|
480
|
+
widthBefore?: TableWidth;
|
|
481
|
+
gridAfter?: number;
|
|
482
|
+
widthAfter?: TableWidth;
|
|
483
|
+
height?: number;
|
|
484
|
+
heightRule?: "auto" | "atLeast" | "exact";
|
|
485
|
+
isHeader?: boolean;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export interface TableCellNode {
|
|
489
|
+
type: "table_cell";
|
|
490
|
+
propertiesXml?: string;
|
|
491
|
+
gridSpan?: number;
|
|
492
|
+
verticalMerge?: "restart" | "continue";
|
|
493
|
+
children: BlockNode[];
|
|
494
|
+
width?: TableWidth;
|
|
495
|
+
borders?: TableCellBorders;
|
|
496
|
+
shading?: CellShading;
|
|
497
|
+
verticalAlign?: "top" | "center" | "bottom";
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export interface SdtCheckboxState {
|
|
501
|
+
checked: boolean;
|
|
502
|
+
checkedChar?: string;
|
|
503
|
+
uncheckedChar?: string;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export interface SdtDatePickerState {
|
|
507
|
+
fullDate?: string;
|
|
508
|
+
dateFormat?: string;
|
|
509
|
+
lid?: string;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export interface SdtDropdownListItem {
|
|
513
|
+
displayText?: string;
|
|
514
|
+
value: string;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export interface SdtNode {
|
|
518
|
+
type: "sdt";
|
|
519
|
+
properties: {
|
|
520
|
+
sdtType?: string;
|
|
521
|
+
alias?: string;
|
|
522
|
+
tag?: string;
|
|
523
|
+
lock?: string;
|
|
524
|
+
propertiesXml?: string;
|
|
525
|
+
checkbox?: SdtCheckboxState;
|
|
526
|
+
datePicker?: SdtDatePickerState;
|
|
527
|
+
dropdownList?: SdtDropdownListItem[];
|
|
528
|
+
comboBox?: SdtDropdownListItem[];
|
|
529
|
+
showingPlcHdr?: boolean;
|
|
530
|
+
};
|
|
531
|
+
children: BlockNode[];
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export interface CustomXmlNode {
|
|
535
|
+
type: "custom_xml";
|
|
536
|
+
uri?: string;
|
|
537
|
+
element?: string;
|
|
538
|
+
rawXml?: string;
|
|
539
|
+
children: BlockNode[];
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export interface AltChunkNode {
|
|
543
|
+
type: "alt_chunk";
|
|
544
|
+
relationshipId: string;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Supported field families that receive first-class canonical treatment.
|
|
549
|
+
* These families have stable registry IDs, dependency metadata, and
|
|
550
|
+
* runtime-owned refresh behavior.
|
|
551
|
+
*/
|
|
552
|
+
export type SupportedFieldFamily = "REF" | "PAGEREF" | "NOTEREF" | "TOC";
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Unsupported field families that remain preserve-only.
|
|
556
|
+
* They survive round-trip but do not participate in runtime refresh.
|
|
557
|
+
*/
|
|
558
|
+
export type PreserveOnlyFieldFamily =
|
|
559
|
+
| "PAGE"
|
|
560
|
+
| "NUMPAGES"
|
|
561
|
+
| "DATE"
|
|
562
|
+
| "TIME"
|
|
563
|
+
| "AUTHOR"
|
|
564
|
+
| "FILENAME"
|
|
565
|
+
| "MERGEFIELD"
|
|
566
|
+
| "IF"
|
|
567
|
+
| "SEQ"
|
|
568
|
+
| "INDEX"
|
|
569
|
+
| "TC"
|
|
570
|
+
| "STYLEREF"
|
|
571
|
+
| "FORMULA"
|
|
572
|
+
| "UNKNOWN";
|
|
573
|
+
|
|
574
|
+
export type FieldFamily = SupportedFieldFamily | PreserveOnlyFieldFamily;
|
|
575
|
+
|
|
576
|
+
/** Runtime refresh status for a field instance. */
|
|
577
|
+
export type FieldRefreshStatus =
|
|
578
|
+
| "current"
|
|
579
|
+
| "stale"
|
|
580
|
+
| "unresolvable"
|
|
581
|
+
| "preserve-only";
|
|
582
|
+
|
|
583
|
+
export interface FieldNode {
|
|
584
|
+
type: "field";
|
|
585
|
+
fieldType: "simple" | "complex";
|
|
586
|
+
instruction: string;
|
|
587
|
+
children: InlineNode[];
|
|
588
|
+
/** Classified field family. Undefined for legacy snapshots. */
|
|
589
|
+
fieldFamily?: FieldFamily;
|
|
590
|
+
/** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
|
|
591
|
+
fieldTarget?: string;
|
|
592
|
+
/** Runtime refresh status. Undefined for legacy or preserve-only fields. */
|
|
593
|
+
refreshStatus?: FieldRefreshStatus;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ─── Field registry ─────────────────────────────────────────────────────────
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Package-backed field registry that catalogs every field instance in the
|
|
600
|
+
* document, grouped by supported vs preserve-only families.
|
|
601
|
+
*
|
|
602
|
+
* Supported field entries carry dependency metadata (bookmark targets) and
|
|
603
|
+
* participate in deterministic refresh. Preserve-only entries survive
|
|
604
|
+
* round-trip but are not refreshable.
|
|
605
|
+
*/
|
|
606
|
+
export interface FieldRegistry {
|
|
607
|
+
/** Supported field instances that participate in refresh. */
|
|
608
|
+
supported: FieldRegistryEntry[];
|
|
609
|
+
/** Preserve-only field instances cataloged for round-trip safety. */
|
|
610
|
+
preserveOnly: FieldRegistryEntry[];
|
|
611
|
+
/** Generated TOC structure extracted from heading-driven TOC fields. */
|
|
612
|
+
tocStructure?: TocStructure;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export interface FieldRegistryEntry {
|
|
616
|
+
/** Stable document-order index of this field (0-based). */
|
|
617
|
+
fieldIndex: number;
|
|
618
|
+
/** Classified field family. */
|
|
619
|
+
fieldFamily: FieldFamily;
|
|
620
|
+
/** Whether the field is in the supported refresh slice. */
|
|
621
|
+
supported: boolean;
|
|
622
|
+
/** Field instruction text. */
|
|
623
|
+
instruction: string;
|
|
624
|
+
/** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
|
|
625
|
+
fieldTarget?: string;
|
|
626
|
+
/** Current display text extracted from field content. */
|
|
627
|
+
displayText: string;
|
|
628
|
+
/** Paragraph index in document order where this field appears. */
|
|
629
|
+
paragraphIndex: number;
|
|
630
|
+
/** Runtime refresh status. */
|
|
631
|
+
refreshStatus: FieldRefreshStatus;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Generated table-of-contents structure extracted from TOC fields and
|
|
636
|
+
* heading-styled paragraphs in the document.
|
|
637
|
+
*/
|
|
638
|
+
export interface TocStructure {
|
|
639
|
+
/** The raw TOC field instruction (e.g. "TOC \\o \"1-3\" \\h"). */
|
|
640
|
+
instruction: string;
|
|
641
|
+
/** Heading level range the TOC covers. */
|
|
642
|
+
levelRange: { from: number; to: number };
|
|
643
|
+
/** Ordered TOC entries derived from heading paragraphs. */
|
|
644
|
+
entries: TocEntry[];
|
|
645
|
+
/** Whether the TOC content is current with the heading structure. */
|
|
646
|
+
status: "current" | "stale";
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export interface TocEntry {
|
|
650
|
+
/** Heading text. */
|
|
651
|
+
text: string;
|
|
652
|
+
/** Heading outline level (1-9). */
|
|
653
|
+
level: number;
|
|
654
|
+
/** Paragraph index of the heading in document order. */
|
|
655
|
+
paragraphIndex: number;
|
|
656
|
+
/** Style ID of the heading paragraph, if available. */
|
|
657
|
+
styleId?: string;
|
|
658
|
+
/** Bookmark name anchoring this heading, if present. */
|
|
659
|
+
bookmarkName?: string;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export interface BookmarkStartNode {
|
|
663
|
+
type: "bookmark_start";
|
|
664
|
+
bookmarkId: string;
|
|
665
|
+
name: string;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export interface BookmarkEndNode {
|
|
669
|
+
type: "bookmark_end";
|
|
670
|
+
bookmarkId: string;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export interface SectionBreakNode {
|
|
674
|
+
type: "section_break";
|
|
675
|
+
sectionPropertiesXml?: string;
|
|
676
|
+
/**
|
|
677
|
+
* @deprecated Legacy field from older snapshots. New exports should use
|
|
678
|
+
* sectionPropertiesXml and only contain raw <w:sectPr> content.
|
|
679
|
+
*/
|
|
680
|
+
propertiesXml?: string;
|
|
681
|
+
sectionProperties?: SectionProperties;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export interface SectionProperties {
|
|
685
|
+
pageSize?: PageSize;
|
|
686
|
+
pageMargins?: PageMargins;
|
|
687
|
+
columns?: ColumnProperties;
|
|
688
|
+
pageNumbering?: PageNumbering;
|
|
689
|
+
lineNumbering?: SectionLineNumbering;
|
|
690
|
+
pageBorders?: SectionPageBorders;
|
|
691
|
+
documentGrid?: SectionDocumentGrid;
|
|
692
|
+
headerReferences?: HeaderFooterReference[];
|
|
693
|
+
footerReferences?: HeaderFooterReference[];
|
|
694
|
+
sectionType?: "continuous" | "nextPage" | "evenPage" | "oddPage" | "nextColumn";
|
|
695
|
+
titlePage?: boolean;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export interface PageSize {
|
|
699
|
+
width: number;
|
|
700
|
+
height: number;
|
|
701
|
+
orientation?: "portrait" | "landscape";
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export interface PageMargins {
|
|
705
|
+
top: number;
|
|
706
|
+
right: number;
|
|
707
|
+
bottom: number;
|
|
708
|
+
left: number;
|
|
709
|
+
header?: number;
|
|
710
|
+
footer?: number;
|
|
711
|
+
gutter?: number;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export interface ColumnProperties {
|
|
715
|
+
count?: number;
|
|
716
|
+
space?: number;
|
|
717
|
+
equalWidth?: boolean;
|
|
718
|
+
columns?: Array<{ width: number; space?: number }>;
|
|
719
|
+
separator?: boolean;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export interface PageNumbering {
|
|
723
|
+
format?: string;
|
|
724
|
+
start?: number;
|
|
725
|
+
chapStyle?: string;
|
|
726
|
+
chapSep?: string;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export interface SectionLineNumbering {
|
|
730
|
+
countBy?: number;
|
|
731
|
+
start?: number;
|
|
732
|
+
distance?: number;
|
|
733
|
+
restart?: "newPage" | "newSection" | "continuous";
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export interface SectionPageBorders {
|
|
737
|
+
top?: BorderSpec;
|
|
738
|
+
left?: BorderSpec;
|
|
739
|
+
bottom?: BorderSpec;
|
|
740
|
+
right?: BorderSpec;
|
|
741
|
+
offsetFrom?: "page" | "text";
|
|
742
|
+
display?: "allPages" | "firstPage" | "notFirstPage";
|
|
743
|
+
zOrder?: "front" | "back";
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export interface SectionDocumentGrid {
|
|
747
|
+
type?: "default" | "lines" | "linesAndChars" | "snapToChars";
|
|
748
|
+
linePitch?: number;
|
|
749
|
+
charSpace?: number;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export interface HeaderFooterReference {
|
|
753
|
+
variant: HeaderFooterVariant;
|
|
754
|
+
relationshipId: string;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export type InlineNode =
|
|
758
|
+
| TextNode
|
|
759
|
+
| HardBreakNode
|
|
760
|
+
| ColumnBreakNode
|
|
761
|
+
| TabNode
|
|
762
|
+
| SymbolNode
|
|
763
|
+
| HyperlinkNode
|
|
764
|
+
| ImageNode
|
|
765
|
+
| FieldNode
|
|
766
|
+
| BookmarkStartNode
|
|
767
|
+
| BookmarkEndNode
|
|
768
|
+
| OpaqueInlineNode
|
|
769
|
+
| FootnoteRefNode
|
|
770
|
+
| ChartPreviewNode
|
|
771
|
+
| SmartArtPreviewNode
|
|
772
|
+
| ShapeNode
|
|
773
|
+
| WordArtNode
|
|
774
|
+
| VmlShapeNode;
|
|
775
|
+
|
|
776
|
+
export interface TextNode {
|
|
777
|
+
type: "text";
|
|
778
|
+
text: string;
|
|
779
|
+
marks?: TextMark[];
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export type TextMark =
|
|
783
|
+
| { type: "bold" }
|
|
784
|
+
| { type: "italic" }
|
|
785
|
+
| { type: "underline" }
|
|
786
|
+
| { type: "strikethrough" }
|
|
787
|
+
| { type: "doubleStrikethrough" }
|
|
788
|
+
| { type: "vanish" }
|
|
789
|
+
| { type: "lang"; val: string }
|
|
790
|
+
| { type: "highlight"; color: string; val: string }
|
|
791
|
+
| { type: "backgroundColor"; color: string }
|
|
792
|
+
| { type: "charSpacing"; val: number }
|
|
793
|
+
| { type: "kerning"; val: number }
|
|
794
|
+
| { type: "emboss" }
|
|
795
|
+
| { type: "imprint" }
|
|
796
|
+
| { type: "shadow" }
|
|
797
|
+
| { type: "position"; val: number }
|
|
798
|
+
| { type: "textFill"; xml: string }
|
|
799
|
+
| { type: "fontFamily"; val: string }
|
|
800
|
+
| { type: "fontSize"; val: number }
|
|
801
|
+
| { type: "textColor"; color: string }
|
|
802
|
+
| { type: "smallCaps" }
|
|
803
|
+
| { type: "allCaps" };
|
|
804
|
+
|
|
805
|
+
export interface HardBreakNode {
|
|
806
|
+
type: "hard_break";
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
export interface ColumnBreakNode {
|
|
810
|
+
type: "column_break";
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export interface TabNode {
|
|
814
|
+
type: "tab";
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
export interface SymbolNode {
|
|
818
|
+
type: "symbol";
|
|
819
|
+
char: string;
|
|
820
|
+
font?: string;
|
|
821
|
+
marks?: TextMark[];
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
export interface HyperlinkNode {
|
|
825
|
+
type: "hyperlink";
|
|
826
|
+
href: string;
|
|
827
|
+
children: Array<TextNode | HardBreakNode | ColumnBreakNode | TabNode | SymbolNode>;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export interface ImageNode {
|
|
831
|
+
type: "image";
|
|
832
|
+
mediaId: string;
|
|
833
|
+
altText?: string;
|
|
834
|
+
placementXml?: string;
|
|
835
|
+
display?: "inline" | "floating";
|
|
836
|
+
floating?: FloatingImageProperties;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export interface FloatingImageProperties {
|
|
840
|
+
horizontalPosition?: FloatingAxisPosition;
|
|
841
|
+
verticalPosition?: FloatingAxisPosition;
|
|
842
|
+
wrap?: "none" | "square" | "tight" | "through" | "topAndBottom";
|
|
843
|
+
behindDoc?: boolean;
|
|
844
|
+
layoutInCell?: boolean;
|
|
845
|
+
allowOverlap?: boolean;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export interface FloatingAxisPosition {
|
|
849
|
+
relativeFrom?: string;
|
|
850
|
+
align?: string;
|
|
851
|
+
offset?: number;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export interface OpaqueInlineNode {
|
|
855
|
+
type: "opaque_inline";
|
|
856
|
+
fragmentId: string;
|
|
857
|
+
warningId: string;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// ---- Complex rendering inline nodes (read-only previews) ----
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Read-only preview of a chart (c:chart). The original drawing XML is stored in
|
|
864
|
+
* rawXml for lossless round-trip export. If a fallback image was present in
|
|
865
|
+
* mc:AlternateContent it is referenced by previewMediaId.
|
|
866
|
+
*/
|
|
867
|
+
export interface ChartPreviewNode {
|
|
868
|
+
type: "chart_preview";
|
|
869
|
+
previewMediaId?: string;
|
|
870
|
+
rawXml: string;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Read-only preview of a SmartArt diagram (dgm:*). The original drawing XML is
|
|
875
|
+
* stored in rawXml for lossless round-trip export.
|
|
876
|
+
*/
|
|
877
|
+
export interface SmartArtPreviewNode {
|
|
878
|
+
type: "smartart_preview";
|
|
879
|
+
previewMediaId?: string;
|
|
880
|
+
rawXml: string;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Read-only rendering of a wps:wsp WordprocessingShape. Text content is
|
|
885
|
+
* extracted for display. The original drawing XML is preserved in rawXml.
|
|
886
|
+
*/
|
|
887
|
+
export interface ShapeNode {
|
|
888
|
+
type: "shape";
|
|
889
|
+
text?: string;
|
|
890
|
+
geometry?: string;
|
|
891
|
+
isTextBox?: boolean;
|
|
892
|
+
rawXml: string;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Read-only rendering of WordArt — a wps:wsp shape with a text-geometry preset.
|
|
897
|
+
* Text is extracted for display. The original drawing XML is preserved in rawXml.
|
|
898
|
+
*/
|
|
899
|
+
export interface WordArtNode {
|
|
900
|
+
type: "wordart";
|
|
901
|
+
text: string;
|
|
902
|
+
geometry?: string;
|
|
903
|
+
rawXml: string;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Read-only rendering of a VML shape (v:shape, v:rect, v:textbox) from a w:pict
|
|
908
|
+
* element. Text is extracted for display. The original w:pict XML is preserved
|
|
909
|
+
* in rawXml for lossless round-trip export.
|
|
910
|
+
*/
|
|
911
|
+
export interface VmlShapeNode {
|
|
912
|
+
type: "vml_shape";
|
|
913
|
+
text?: string;
|
|
914
|
+
shapeType?: string;
|
|
915
|
+
rawXml: string;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
export interface OpaqueBlockNode {
|
|
919
|
+
type: "opaque_block";
|
|
920
|
+
fragmentId: string;
|
|
921
|
+
warningId: string;
|
|
922
|
+
rawXml?: string;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export interface DocRange {
|
|
926
|
+
from: number;
|
|
927
|
+
to: number;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
export interface BoundaryAssoc {
|
|
931
|
+
start: -1 | 1;
|
|
932
|
+
end: -1 | 1;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export type CanonicalAnchor =
|
|
936
|
+
| {
|
|
937
|
+
kind: "range";
|
|
938
|
+
range: DocRange;
|
|
939
|
+
assoc: BoundaryAssoc;
|
|
940
|
+
}
|
|
941
|
+
| {
|
|
942
|
+
kind: "node";
|
|
943
|
+
at: number;
|
|
944
|
+
assoc: -1 | 1;
|
|
945
|
+
}
|
|
946
|
+
| {
|
|
947
|
+
kind: "detached";
|
|
948
|
+
lastKnownRange: DocRange;
|
|
949
|
+
reason: "deleted" | "invalidatedByStructureChange" | "importAmbiguity";
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
export interface ReviewStore {
|
|
953
|
+
comments: Record<string, CommentThread>;
|
|
954
|
+
revisions: Record<string, RevisionRecord>;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
export interface CommentThread {
|
|
958
|
+
commentId: string;
|
|
959
|
+
status: "open" | "resolved" | "detached";
|
|
960
|
+
anchor: CanonicalAnchor;
|
|
961
|
+
createdAt: ISO8601DateTime;
|
|
962
|
+
createdBy?: string;
|
|
963
|
+
authorId?: string;
|
|
964
|
+
body?: string;
|
|
965
|
+
entries?: CommentEntry[];
|
|
966
|
+
resolution?: CommentResolution;
|
|
967
|
+
resolvedAt?: ISO8601DateTime;
|
|
968
|
+
warningIds: string[];
|
|
969
|
+
isResolved?: boolean;
|
|
970
|
+
metadata?: CommentThreadMetadata;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
export interface CommentEntry {
|
|
974
|
+
entryId: string;
|
|
975
|
+
authorId: string;
|
|
976
|
+
createdAt: ISO8601DateTime;
|
|
977
|
+
body: string;
|
|
978
|
+
metadata?: CommentEntryMetadata;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
export interface CommentEntryMetadata {
|
|
982
|
+
ooxmlCommentId?: string;
|
|
983
|
+
paraId?: string;
|
|
984
|
+
parentParaId?: string;
|
|
985
|
+
durableId?: string;
|
|
986
|
+
initials?: string;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export interface CommentResolution {
|
|
990
|
+
resolvedAt: ISO8601DateTime;
|
|
991
|
+
resolvedBy: string;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
export interface CommentThreadMetadata {
|
|
995
|
+
source?: "runtime" | "import";
|
|
996
|
+
rootOoxmlCommentId?: string;
|
|
997
|
+
rootParaId?: string;
|
|
998
|
+
detachedReason?: "incomplete-markers" | "multi-paragraph" | "opaque-region" | "revision-overlap";
|
|
999
|
+
actionabilityNote?: string;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export interface RevisionPropertyChangeData {
|
|
1003
|
+
xmlTag: "pPrChange" | "sectPrChange" | "tblPrChange" | "rPrChange";
|
|
1004
|
+
beforeXml: string;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
export interface RevisionMoveData {
|
|
1008
|
+
moveId: string;
|
|
1009
|
+
direction: "from" | "to";
|
|
1010
|
+
linkedRevisionId?: string;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export type RevisionStoryTargetRecord =
|
|
1014
|
+
| { kind: "main" }
|
|
1015
|
+
| {
|
|
1016
|
+
kind: "header";
|
|
1017
|
+
relationshipId: string;
|
|
1018
|
+
variant: "default" | "first" | "even";
|
|
1019
|
+
sectionIndex?: number;
|
|
1020
|
+
}
|
|
1021
|
+
| {
|
|
1022
|
+
kind: "footer";
|
|
1023
|
+
relationshipId: string;
|
|
1024
|
+
variant: "default" | "first" | "even";
|
|
1025
|
+
sectionIndex?: number;
|
|
1026
|
+
}
|
|
1027
|
+
| { kind: "footnote"; noteId: string }
|
|
1028
|
+
| { kind: "endnote"; noteId: string };
|
|
1029
|
+
|
|
1030
|
+
export interface RevisionRecord {
|
|
1031
|
+
changeId: string;
|
|
1032
|
+
kind: "insertion" | "deletion" | "formatting" | "move" | "property-change";
|
|
1033
|
+
anchor: CanonicalAnchor;
|
|
1034
|
+
authorId?: string;
|
|
1035
|
+
createdAt: ISO8601DateTime;
|
|
1036
|
+
warningIds?: string[];
|
|
1037
|
+
metadata?: RevisionMetadataRecord;
|
|
1038
|
+
status: "open" | "accepted" | "rejected" | "detached";
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
export interface RevisionMetadataRecord {
|
|
1042
|
+
source?: "runtime" | "import";
|
|
1043
|
+
storyTarget?: RevisionStoryTargetRecord;
|
|
1044
|
+
preserveOnlyReason?: string;
|
|
1045
|
+
suggestionId?: string;
|
|
1046
|
+
semanticKind?:
|
|
1047
|
+
| "insertion"
|
|
1048
|
+
| "deletion"
|
|
1049
|
+
| "replacement"
|
|
1050
|
+
| "formatting-change"
|
|
1051
|
+
| "paragraph-property-change"
|
|
1052
|
+
| "structural-change"
|
|
1053
|
+
| "object-change";
|
|
1054
|
+
linkedRevisionIds?: string[];
|
|
1055
|
+
predecessorSuggestionId?: string;
|
|
1056
|
+
importedRevisionForm?:
|
|
1057
|
+
| "run-insertion"
|
|
1058
|
+
| "run-deletion"
|
|
1059
|
+
| "paragraph-insertion"
|
|
1060
|
+
| "paragraph-deletion";
|
|
1061
|
+
originalRevisionType?: string;
|
|
1062
|
+
ooxmlRevisionId?: string;
|
|
1063
|
+
propertyChangeData?: RevisionPropertyChangeData;
|
|
1064
|
+
moveData?: RevisionMoveData;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
export interface PreservationStore {
|
|
1068
|
+
opaqueFragments: Record<string, OpaqueFragmentRecord>;
|
|
1069
|
+
packageParts: Record<string, PreservedPackagePart>;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
export interface OpaqueFragmentRecord {
|
|
1073
|
+
fragmentId: string;
|
|
1074
|
+
payloadKind: "xml-subtree" | "package-part";
|
|
1075
|
+
payloadReference: string;
|
|
1076
|
+
featureClass: "preserve-only";
|
|
1077
|
+
lastKnownRange: DocRange;
|
|
1078
|
+
warningId: string;
|
|
1079
|
+
packagePartName?: string;
|
|
1080
|
+
relationshipId?: string;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
export interface PreservedPackagePart {
|
|
1084
|
+
packagePartName: string;
|
|
1085
|
+
contentType: string;
|
|
1086
|
+
relationshipIds: string[];
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
export interface DiagnosticStore {
|
|
1090
|
+
warnings: DiagnosticWarningEntry[];
|
|
1091
|
+
errors: DiagnosticErrorEntry[];
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export interface DiagnosticWarningEntry {
|
|
1095
|
+
diagnosticId: string;
|
|
1096
|
+
warningId: string;
|
|
1097
|
+
source:
|
|
1098
|
+
| "import"
|
|
1099
|
+
| "runtime"
|
|
1100
|
+
| "review"
|
|
1101
|
+
| "preservation"
|
|
1102
|
+
| "validation"
|
|
1103
|
+
| "export";
|
|
1104
|
+
message: string;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
export interface DiagnosticErrorEntry {
|
|
1108
|
+
diagnosticId: string;
|
|
1109
|
+
code:
|
|
1110
|
+
| "load_failed"
|
|
1111
|
+
| "import_failed"
|
|
1112
|
+
| "export_failed"
|
|
1113
|
+
| "package_corrupt"
|
|
1114
|
+
| "validation_failed"
|
|
1115
|
+
| "datastore_failed"
|
|
1116
|
+
| "internal_invariant";
|
|
1117
|
+
message: string;
|
|
1118
|
+
isFatal: boolean;
|
|
1119
|
+
source: "import" | "runtime" | "validation" | "datastore" | "host" | "export";
|
|
1120
|
+
details?: unknown;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
export function createCanonicalDocument(
|
|
1124
|
+
input: Omit<CanonicalDocument, "schemaVersion">,
|
|
1125
|
+
): CanonicalDocument {
|
|
1126
|
+
const document: CanonicalDocument = {
|
|
1127
|
+
schemaVersion: CDS_SCHEMA_VERSION,
|
|
1128
|
+
...input,
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
assertCanonicalDocument(document);
|
|
1132
|
+
return document;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
export function serializeCanonicalDocument(document: CanonicalDocument): string {
|
|
1136
|
+
assertCanonicalDocument(document);
|
|
1137
|
+
return stableStringify(document);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
export function createCanonicalDocumentSignature(document: unknown): string {
|
|
1141
|
+
return stableStringify(document);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
export function parseCanonicalDocument(json: string): CanonicalDocument {
|
|
1145
|
+
const parsed = JSON.parse(json) as unknown;
|
|
1146
|
+
assertCanonicalDocument(parsed);
|
|
1147
|
+
return parsed;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
export function projectCanonicalDocument(
|
|
1151
|
+
document: CanonicalDocument,
|
|
1152
|
+
): CanonicalDocument {
|
|
1153
|
+
assertCanonicalDocument(document);
|
|
1154
|
+
return JSON.parse(stableStringify(document)) as CanonicalDocument;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
export function assertCanonicalDocument(
|
|
1158
|
+
value: unknown,
|
|
1159
|
+
): asserts value is CanonicalDocument {
|
|
1160
|
+
const issues = validateCanonicalDocument(value);
|
|
1161
|
+
assertValid(issues, "Invalid canonical document.");
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
export function validateCanonicalDocument(
|
|
1165
|
+
value: unknown,
|
|
1166
|
+
): ModelValidationIssue[] {
|
|
1167
|
+
const issues: ModelValidationIssue[] = [];
|
|
1168
|
+
const record = asPlainObject(value, "$", issues);
|
|
1169
|
+
if (!record) {
|
|
1170
|
+
return issues;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
validateExactObjectKeys(record, CANONICAL_DOCUMENT_TOP_LEVEL_KEYS, "$", issues, CANONICAL_DOCUMENT_OPTIONAL_KEYS);
|
|
1174
|
+
expectExactString(record.schemaVersion, CDS_SCHEMA_VERSION, "$.schemaVersion", issues);
|
|
1175
|
+
expectUuid(record.docId, "$.docId", issues);
|
|
1176
|
+
expectIso8601UtcTimestamp(record.createdAt, "$.createdAt", issues);
|
|
1177
|
+
expectIso8601UtcTimestamp(record.updatedAt, "$.updatedAt", issues);
|
|
1178
|
+
|
|
1179
|
+
validateMetadata(record.metadata, "$.metadata", issues);
|
|
1180
|
+
validateStylesCatalog(record.styles, "$.styles", issues);
|
|
1181
|
+
validateNumberingCatalog(record.numbering, "$.numbering", issues);
|
|
1182
|
+
validateMediaCatalog(record.media, "$.media", issues);
|
|
1183
|
+
validateDocumentNode(record.content, "$.content", issues);
|
|
1184
|
+
validateReviewStore(record.review, "$.review", issues);
|
|
1185
|
+
validatePreservationStore(record.preservation, "$.preservation", issues);
|
|
1186
|
+
validateDiagnosticStore(record.diagnostics, "$.diagnostics", issues);
|
|
1187
|
+
if (record.subParts !== undefined) {
|
|
1188
|
+
validateSubPartsCatalog(record.subParts, "$.subParts", issues);
|
|
1189
|
+
}
|
|
1190
|
+
validateDocumentReferences(record, issues);
|
|
1191
|
+
|
|
1192
|
+
return issues;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function validateMetadata(
|
|
1196
|
+
value: unknown,
|
|
1197
|
+
path: string,
|
|
1198
|
+
issues: ModelValidationIssue[],
|
|
1199
|
+
): void {
|
|
1200
|
+
const record = asPlainObject(value, path, issues);
|
|
1201
|
+
if (!record) {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const customProperties = asPlainObject(record.customProperties, `${path}.customProperties`, issues);
|
|
1206
|
+
if (!customProperties) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
for (const [propertyKey, propertyValue] of Object.entries(customProperties)) {
|
|
1211
|
+
if (typeof propertyValue !== "string") {
|
|
1212
|
+
issues.push({
|
|
1213
|
+
path: `${path}.customProperties.${propertyKey}`,
|
|
1214
|
+
message: "customProperties values must be strings.",
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function validateStylesCatalog(
|
|
1221
|
+
value: unknown,
|
|
1222
|
+
path: string,
|
|
1223
|
+
issues: ModelValidationIssue[],
|
|
1224
|
+
): void {
|
|
1225
|
+
const record = asPlainObject(value, path, issues);
|
|
1226
|
+
if (!record) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
validateStyleMap(record.paragraphs, `${path}.paragraphs`, issues);
|
|
1231
|
+
validateStyleMap(record.characters, `${path}.characters`, issues);
|
|
1232
|
+
validateStyleMap(record.tables, `${path}.tables`, issues);
|
|
1233
|
+
if (record.latentStyles !== undefined) {
|
|
1234
|
+
validateLatentStyleMap(record.latentStyles, `${path}.latentStyles`, issues);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function validateStyleMap(
|
|
1239
|
+
value: unknown,
|
|
1240
|
+
path: string,
|
|
1241
|
+
issues: ModelValidationIssue[],
|
|
1242
|
+
): void {
|
|
1243
|
+
const record = asPlainObject(value, path, issues);
|
|
1244
|
+
if (!record) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
for (const [styleId, definition] of Object.entries(record)) {
|
|
1249
|
+
expectDomainString(styleId, "styleId", `${path}.${styleId}`, issues);
|
|
1250
|
+
const definitionRecord = asPlainObject(definition, `${path}.${styleId}`, issues);
|
|
1251
|
+
if (!definitionRecord) {
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
if (definitionRecord.styleId !== styleId) {
|
|
1255
|
+
issues.push({
|
|
1256
|
+
path: `${path}.${styleId}.styleId`,
|
|
1257
|
+
message: "styleId must match the map key.",
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function validateLatentStyleMap(
|
|
1264
|
+
value: unknown,
|
|
1265
|
+
path: string,
|
|
1266
|
+
issues: ModelValidationIssue[],
|
|
1267
|
+
): void {
|
|
1268
|
+
const record = asPlainObject(value, path, issues);
|
|
1269
|
+
if (!record) {
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
for (const [styleName, definition] of Object.entries(record)) {
|
|
1274
|
+
const definitionRecord = asPlainObject(definition, `${path}.${styleName}`, issues);
|
|
1275
|
+
if (!definitionRecord) {
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
if (definitionRecord.name !== styleName) {
|
|
1279
|
+
issues.push({
|
|
1280
|
+
path: `${path}.${styleName}.name`,
|
|
1281
|
+
message: "name must match the map key.",
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
if (definitionRecord.uiPriority !== undefined && typeof definitionRecord.uiPriority !== "number") {
|
|
1285
|
+
issues.push({
|
|
1286
|
+
path: `${path}.${styleName}.uiPriority`,
|
|
1287
|
+
message: "uiPriority must be a number.",
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
function validateNumberingCatalog(
|
|
1294
|
+
value: unknown,
|
|
1295
|
+
path: string,
|
|
1296
|
+
issues: ModelValidationIssue[],
|
|
1297
|
+
): void {
|
|
1298
|
+
const record = asPlainObject(value, path, issues);
|
|
1299
|
+
if (!record) {
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const abstractDefinitions = asPlainObject(
|
|
1304
|
+
record.abstractDefinitions,
|
|
1305
|
+
`${path}.abstractDefinitions`,
|
|
1306
|
+
issues,
|
|
1307
|
+
);
|
|
1308
|
+
if (abstractDefinitions) {
|
|
1309
|
+
for (const [abstractId, definition] of Object.entries(abstractDefinitions)) {
|
|
1310
|
+
expectDomainString(
|
|
1311
|
+
abstractId,
|
|
1312
|
+
"abstractNumberingId",
|
|
1313
|
+
`${path}.abstractDefinitions.${abstractId}`,
|
|
1314
|
+
issues,
|
|
1315
|
+
);
|
|
1316
|
+
const definitionRecord = asPlainObject(
|
|
1317
|
+
definition,
|
|
1318
|
+
`${path}.abstractDefinitions.${abstractId}`,
|
|
1319
|
+
issues,
|
|
1320
|
+
);
|
|
1321
|
+
if (definitionRecord && definitionRecord.abstractNumberingId !== abstractId) {
|
|
1322
|
+
issues.push({
|
|
1323
|
+
path: `${path}.abstractDefinitions.${abstractId}.abstractNumberingId`,
|
|
1324
|
+
message: "abstractNumberingId must match the map key.",
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
const instances = asPlainObject(record.instances, `${path}.instances`, issues);
|
|
1331
|
+
if (instances) {
|
|
1332
|
+
for (const [instanceId, instance] of Object.entries(instances)) {
|
|
1333
|
+
expectDomainString(
|
|
1334
|
+
instanceId,
|
|
1335
|
+
"numberingInstanceId",
|
|
1336
|
+
`${path}.instances.${instanceId}`,
|
|
1337
|
+
issues,
|
|
1338
|
+
);
|
|
1339
|
+
const instanceRecord = asPlainObject(
|
|
1340
|
+
instance,
|
|
1341
|
+
`${path}.instances.${instanceId}`,
|
|
1342
|
+
issues,
|
|
1343
|
+
);
|
|
1344
|
+
if (!instanceRecord) {
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
if (instanceRecord.numberingInstanceId !== instanceId) {
|
|
1348
|
+
issues.push({
|
|
1349
|
+
path: `${path}.instances.${instanceId}.numberingInstanceId`,
|
|
1350
|
+
message: "numberingInstanceId must match the map key.",
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
expectDomainString(
|
|
1354
|
+
instanceRecord.abstractNumberingId,
|
|
1355
|
+
"abstractNumberingId",
|
|
1356
|
+
`${path}.instances.${instanceId}.abstractNumberingId`,
|
|
1357
|
+
issues,
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function validateMediaCatalog(
|
|
1364
|
+
value: unknown,
|
|
1365
|
+
path: string,
|
|
1366
|
+
issues: ModelValidationIssue[],
|
|
1367
|
+
): void {
|
|
1368
|
+
const record = asPlainObject(value, path, issues);
|
|
1369
|
+
if (!record) {
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const items = asPlainObject(record.items, `${path}.items`, issues);
|
|
1374
|
+
if (!items) {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
for (const [mediaId, item] of Object.entries(items)) {
|
|
1379
|
+
expectDomainString(mediaId, "mediaId", `${path}.items.${mediaId}`, issues);
|
|
1380
|
+
const itemRecord = asPlainObject(item, `${path}.items.${mediaId}`, issues);
|
|
1381
|
+
if (!itemRecord) {
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
if (itemRecord.mediaId !== mediaId) {
|
|
1385
|
+
issues.push({
|
|
1386
|
+
path: `${path}.items.${mediaId}.mediaId`,
|
|
1387
|
+
message: "mediaId must match the map key.",
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
expectDomainString(
|
|
1391
|
+
itemRecord.packagePartName,
|
|
1392
|
+
"packagePartName",
|
|
1393
|
+
`${path}.items.${mediaId}.packagePartName`,
|
|
1394
|
+
issues,
|
|
1395
|
+
);
|
|
1396
|
+
if (itemRecord.relationshipId !== undefined) {
|
|
1397
|
+
expectDomainString(
|
|
1398
|
+
itemRecord.relationshipId,
|
|
1399
|
+
"relationshipId",
|
|
1400
|
+
`${path}.items.${mediaId}.relationshipId`,
|
|
1401
|
+
issues,
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
function validateDocumentNode(
|
|
1408
|
+
value: unknown,
|
|
1409
|
+
path: string,
|
|
1410
|
+
issues: ModelValidationIssue[],
|
|
1411
|
+
): void {
|
|
1412
|
+
const record = asPlainObject(value, path, issues);
|
|
1413
|
+
if (!record) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
const type = expectString(record.type, `${path}.type`, issues);
|
|
1418
|
+
if (!type) {
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
switch (type) {
|
|
1423
|
+
case "doc":
|
|
1424
|
+
case "paragraph":
|
|
1425
|
+
case "sdt":
|
|
1426
|
+
case "custom_xml":
|
|
1427
|
+
case "hyperlink":
|
|
1428
|
+
if (!Array.isArray(record.children)) {
|
|
1429
|
+
issues.push({ path: `${path}.children`, message: "children must be an array." });
|
|
1430
|
+
} else {
|
|
1431
|
+
record.children.forEach((child, index) =>
|
|
1432
|
+
validateDocumentNode(child, `${path}.children[${index}]`, issues),
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
if (type === "paragraph" && record.styleId !== undefined) {
|
|
1436
|
+
expectDomainString(record.styleId, "styleId", `${path}.styleId`, issues);
|
|
1437
|
+
}
|
|
1438
|
+
if (type === "paragraph" && record.numbering !== undefined) {
|
|
1439
|
+
const numbering = asPlainObject(record.numbering, `${path}.numbering`, issues);
|
|
1440
|
+
if (numbering) {
|
|
1441
|
+
expectDomainString(
|
|
1442
|
+
numbering.numberingInstanceId,
|
|
1443
|
+
"numberingInstanceId",
|
|
1444
|
+
`${path}.numbering.numberingInstanceId`,
|
|
1445
|
+
issues,
|
|
1446
|
+
);
|
|
1447
|
+
if (typeof numbering.level !== "number") {
|
|
1448
|
+
issues.push({
|
|
1449
|
+
path: `${path}.numbering.level`,
|
|
1450
|
+
message: "level must be a number.",
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (type === "hyperlink") {
|
|
1456
|
+
expectString(record.href, `${path}.href`, issues);
|
|
1457
|
+
}
|
|
1458
|
+
return;
|
|
1459
|
+
case "alt_chunk":
|
|
1460
|
+
expectDomainString(record.relationshipId, "relationshipId", `${path}.relationshipId`, issues);
|
|
1461
|
+
return;
|
|
1462
|
+
case "image":
|
|
1463
|
+
expectDomainString(record.mediaId, "mediaId", `${path}.mediaId`, issues);
|
|
1464
|
+
if (record.placementXml !== undefined) {
|
|
1465
|
+
expectString(record.placementXml, `${path}.placementXml`, issues);
|
|
1466
|
+
}
|
|
1467
|
+
if (record.display !== undefined) {
|
|
1468
|
+
const display = expectString(record.display, `${path}.display`, issues);
|
|
1469
|
+
if (display && display !== "inline" && display !== "floating") {
|
|
1470
|
+
issues.push({
|
|
1471
|
+
path: `${path}.display`,
|
|
1472
|
+
message: "display must be 'inline' or 'floating'.",
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (record.floating !== undefined) {
|
|
1477
|
+
validateFloatingImageProperties(record.floating, `${path}.floating`, issues);
|
|
1478
|
+
}
|
|
1479
|
+
return;
|
|
1480
|
+
case "opaque_inline":
|
|
1481
|
+
case "opaque_block":
|
|
1482
|
+
expectDomainString(record.fragmentId, "fragmentId", `${path}.fragmentId`, issues);
|
|
1483
|
+
expectDomainString(record.warningId, "warningId", `${path}.warningId`, issues);
|
|
1484
|
+
if (record.rawXml !== undefined) {
|
|
1485
|
+
expectString(record.rawXml, `${path}.rawXml`, issues);
|
|
1486
|
+
}
|
|
1487
|
+
return;
|
|
1488
|
+
case "table":
|
|
1489
|
+
if (!Array.isArray(record.gridColumns)) {
|
|
1490
|
+
issues.push({ path: `${path}.gridColumns`, message: "gridColumns must be an array." });
|
|
1491
|
+
}
|
|
1492
|
+
if (!Array.isArray(record.rows)) {
|
|
1493
|
+
issues.push({ path: `${path}.rows`, message: "rows must be an array." });
|
|
1494
|
+
} else {
|
|
1495
|
+
record.rows.forEach((row, rowIndex) =>
|
|
1496
|
+
validateDocumentNode(row, `${path}.rows[${rowIndex}]`, issues),
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
return;
|
|
1500
|
+
case "table_row":
|
|
1501
|
+
if (!Array.isArray(record.cells)) {
|
|
1502
|
+
issues.push({ path: `${path}.cells`, message: "cells must be an array." });
|
|
1503
|
+
} else {
|
|
1504
|
+
record.cells.forEach((cell, cellIndex) =>
|
|
1505
|
+
validateDocumentNode(cell, `${path}.cells[${cellIndex}]`, issues),
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
return;
|
|
1509
|
+
case "table_cell":
|
|
1510
|
+
if (!Array.isArray(record.children)) {
|
|
1511
|
+
issues.push({ path: `${path}.children`, message: "children must be an array." });
|
|
1512
|
+
} else {
|
|
1513
|
+
record.children.forEach((child, childIndex) =>
|
|
1514
|
+
validateDocumentNode(child, `${path}.children[${childIndex}]`, issues),
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
return;
|
|
1518
|
+
case "field":
|
|
1519
|
+
expectString(record.fieldType, `${path}.fieldType`, issues);
|
|
1520
|
+
expectString(record.instruction, `${path}.instruction`, issues);
|
|
1521
|
+
if (!Array.isArray(record.children)) {
|
|
1522
|
+
issues.push({ path: `${path}.children`, message: "children must be an array." });
|
|
1523
|
+
} else {
|
|
1524
|
+
record.children.forEach((child, index) =>
|
|
1525
|
+
validateDocumentNode(child, `${path}.children[${index}]`, issues),
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
return;
|
|
1529
|
+
case "bookmark_start":
|
|
1530
|
+
expectString(record.bookmarkId, `${path}.bookmarkId`, issues);
|
|
1531
|
+
expectString(record.name, `${path}.name`, issues);
|
|
1532
|
+
return;
|
|
1533
|
+
case "bookmark_end":
|
|
1534
|
+
expectString(record.bookmarkId, `${path}.bookmarkId`, issues);
|
|
1535
|
+
return;
|
|
1536
|
+
case "section_break":
|
|
1537
|
+
return;
|
|
1538
|
+
case "text":
|
|
1539
|
+
if (typeof record.text !== "string") {
|
|
1540
|
+
issues.push({
|
|
1541
|
+
path: `${path}.text`,
|
|
1542
|
+
message: "text must be a string.",
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
return;
|
|
1546
|
+
case "hard_break":
|
|
1547
|
+
case "column_break":
|
|
1548
|
+
case "tab":
|
|
1549
|
+
return;
|
|
1550
|
+
case "symbol":
|
|
1551
|
+
expectString(record.char, `${path}.char`, issues);
|
|
1552
|
+
if (record.font !== undefined) {
|
|
1553
|
+
expectString(record.font, `${path}.font`, issues);
|
|
1554
|
+
}
|
|
1555
|
+
return;
|
|
1556
|
+
case "footnote_ref":
|
|
1557
|
+
expectString(record.noteId, `${path}.noteId`, issues);
|
|
1558
|
+
if (record.noteKind !== "footnote" && record.noteKind !== "endnote") {
|
|
1559
|
+
issues.push({
|
|
1560
|
+
path: `${path}.noteKind`,
|
|
1561
|
+
message: "noteKind must be 'footnote' or 'endnote'.",
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
return;
|
|
1565
|
+
case "chart_preview":
|
|
1566
|
+
case "smartart_preview":
|
|
1567
|
+
case "shape":
|
|
1568
|
+
case "wordart":
|
|
1569
|
+
case "vml_shape":
|
|
1570
|
+
expectString(record.rawXml, `${path}.rawXml`, issues);
|
|
1571
|
+
return;
|
|
1572
|
+
default:
|
|
1573
|
+
issues.push({
|
|
1574
|
+
path: `${path}.type`,
|
|
1575
|
+
message: `Unsupported node type ${JSON.stringify(type)}.`,
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
function validateFloatingImageProperties(
|
|
1581
|
+
value: unknown,
|
|
1582
|
+
path: string,
|
|
1583
|
+
issues: ModelValidationIssue[],
|
|
1584
|
+
): void {
|
|
1585
|
+
const record = asPlainObject(value, path, issues);
|
|
1586
|
+
if (!record) {
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (record.horizontalPosition !== undefined) {
|
|
1591
|
+
validateFloatingAxisPosition(record.horizontalPosition, `${path}.horizontalPosition`, issues);
|
|
1592
|
+
}
|
|
1593
|
+
if (record.verticalPosition !== undefined) {
|
|
1594
|
+
validateFloatingAxisPosition(record.verticalPosition, `${path}.verticalPosition`, issues);
|
|
1595
|
+
}
|
|
1596
|
+
if (record.wrap !== undefined) {
|
|
1597
|
+
const wrap = expectString(record.wrap, `${path}.wrap`, issues);
|
|
1598
|
+
if (
|
|
1599
|
+
wrap &&
|
|
1600
|
+
wrap !== "none" &&
|
|
1601
|
+
wrap !== "square" &&
|
|
1602
|
+
wrap !== "tight" &&
|
|
1603
|
+
wrap !== "through" &&
|
|
1604
|
+
wrap !== "topAndBottom"
|
|
1605
|
+
) {
|
|
1606
|
+
issues.push({
|
|
1607
|
+
path: `${path}.wrap`,
|
|
1608
|
+
message: "wrap must be one of none, square, tight, through, or topAndBottom.",
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function validateFloatingAxisPosition(
|
|
1615
|
+
value: unknown,
|
|
1616
|
+
path: string,
|
|
1617
|
+
issues: ModelValidationIssue[],
|
|
1618
|
+
): void {
|
|
1619
|
+
const record = asPlainObject(value, path, issues);
|
|
1620
|
+
if (!record) {
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
if (record.relativeFrom !== undefined) {
|
|
1625
|
+
expectString(record.relativeFrom, `${path}.relativeFrom`, issues);
|
|
1626
|
+
}
|
|
1627
|
+
if (record.align !== undefined) {
|
|
1628
|
+
expectString(record.align, `${path}.align`, issues);
|
|
1629
|
+
}
|
|
1630
|
+
if (record.offset !== undefined && typeof record.offset !== "number") {
|
|
1631
|
+
issues.push({
|
|
1632
|
+
path: `${path}.offset`,
|
|
1633
|
+
message: "offset must be a number.",
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function validateReviewStore(
|
|
1639
|
+
value: unknown,
|
|
1640
|
+
path: string,
|
|
1641
|
+
issues: ModelValidationIssue[],
|
|
1642
|
+
): void {
|
|
1643
|
+
const record = asPlainObject(value, path, issues);
|
|
1644
|
+
if (!record) {
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
const comments = asPlainObject(record.comments, `${path}.comments`, issues);
|
|
1649
|
+
if (comments) {
|
|
1650
|
+
for (const [commentId, thread] of Object.entries(comments)) {
|
|
1651
|
+
expectDomainString(commentId, "commentId", `${path}.comments.${commentId}`, issues);
|
|
1652
|
+
const threadRecord = asPlainObject(thread, `${path}.comments.${commentId}`, issues);
|
|
1653
|
+
if (!threadRecord) {
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
if (threadRecord.commentId !== commentId) {
|
|
1657
|
+
issues.push({
|
|
1658
|
+
path: `${path}.comments.${commentId}.commentId`,
|
|
1659
|
+
message: "commentId must match the map key.",
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
validateAnchor(threadRecord.anchor, `${path}.comments.${commentId}.anchor`, issues);
|
|
1663
|
+
expectIso8601UtcTimestamp(
|
|
1664
|
+
threadRecord.createdAt,
|
|
1665
|
+
`${path}.comments.${commentId}.createdAt`,
|
|
1666
|
+
issues,
|
|
1667
|
+
);
|
|
1668
|
+
validateCommentStatus(threadRecord.status, `${path}.comments.${commentId}.status`, issues);
|
|
1669
|
+
if (threadRecord.createdBy !== undefined) {
|
|
1670
|
+
expectString(
|
|
1671
|
+
threadRecord.createdBy,
|
|
1672
|
+
`${path}.comments.${commentId}.createdBy`,
|
|
1673
|
+
issues,
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
if (threadRecord.authorId !== undefined) {
|
|
1677
|
+
expectString(
|
|
1678
|
+
threadRecord.authorId,
|
|
1679
|
+
`${path}.comments.${commentId}.authorId`,
|
|
1680
|
+
issues,
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
if (threadRecord.body !== undefined) {
|
|
1684
|
+
expectStringAllowEmpty(
|
|
1685
|
+
threadRecord.body,
|
|
1686
|
+
`${path}.comments.${commentId}.body`,
|
|
1687
|
+
issues,
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
if (!Array.isArray(threadRecord.warningIds)) {
|
|
1691
|
+
issues.push({
|
|
1692
|
+
path: `${path}.comments.${commentId}.warningIds`,
|
|
1693
|
+
message: "warningIds must be an array.",
|
|
1694
|
+
});
|
|
1695
|
+
} else {
|
|
1696
|
+
threadRecord.warningIds.forEach((warningId, index) =>
|
|
1697
|
+
expectDomainString(
|
|
1698
|
+
warningId,
|
|
1699
|
+
"warningId",
|
|
1700
|
+
`${path}.comments.${commentId}.warningIds[${index}]`,
|
|
1701
|
+
issues,
|
|
1702
|
+
),
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
if (threadRecord.entries !== undefined) {
|
|
1706
|
+
validateCommentEntries(
|
|
1707
|
+
threadRecord.entries,
|
|
1708
|
+
`${path}.comments.${commentId}.entries`,
|
|
1709
|
+
issues,
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
if (threadRecord.resolution !== undefined) {
|
|
1713
|
+
validateCommentResolution(
|
|
1714
|
+
threadRecord.resolution,
|
|
1715
|
+
`${path}.comments.${commentId}.resolution`,
|
|
1716
|
+
issues,
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
if (threadRecord.resolvedAt !== undefined) {
|
|
1720
|
+
expectIso8601UtcTimestamp(
|
|
1721
|
+
threadRecord.resolvedAt,
|
|
1722
|
+
`${path}.comments.${commentId}.resolvedAt`,
|
|
1723
|
+
issues,
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
if (
|
|
1727
|
+
threadRecord.isResolved !== undefined &&
|
|
1728
|
+
typeof threadRecord.isResolved !== "boolean"
|
|
1729
|
+
) {
|
|
1730
|
+
issues.push({
|
|
1731
|
+
path: `${path}.comments.${commentId}.isResolved`,
|
|
1732
|
+
message: "isResolved must be a boolean.",
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
if (threadRecord.metadata !== undefined) {
|
|
1736
|
+
validateCommentThreadMetadata(
|
|
1737
|
+
threadRecord.metadata,
|
|
1738
|
+
`${path}.comments.${commentId}.metadata`,
|
|
1739
|
+
issues,
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
const revisions = asPlainObject(record.revisions, `${path}.revisions`, issues);
|
|
1746
|
+
if (revisions) {
|
|
1747
|
+
for (const [revisionId, revision] of Object.entries(revisions)) {
|
|
1748
|
+
expectDomainString(revisionId, "revisionId", `${path}.revisions.${revisionId}`, issues);
|
|
1749
|
+
const revisionRecord = asPlainObject(
|
|
1750
|
+
revision,
|
|
1751
|
+
`${path}.revisions.${revisionId}`,
|
|
1752
|
+
issues,
|
|
1753
|
+
);
|
|
1754
|
+
if (!revisionRecord) {
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
if (revisionRecord.changeId !== revisionId) {
|
|
1758
|
+
issues.push({
|
|
1759
|
+
path: `${path}.revisions.${revisionId}.changeId`,
|
|
1760
|
+
message: "changeId must match the map key.",
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
validateAnchor(revisionRecord.anchor, `${path}.revisions.${revisionId}.anchor`, issues);
|
|
1764
|
+
validateRevisionKind(revisionRecord.kind, `${path}.revisions.${revisionId}.kind`, issues);
|
|
1765
|
+
validateRevisionStatus(
|
|
1766
|
+
revisionRecord.status,
|
|
1767
|
+
`${path}.revisions.${revisionId}.status`,
|
|
1768
|
+
issues,
|
|
1769
|
+
);
|
|
1770
|
+
expectIso8601UtcTimestamp(
|
|
1771
|
+
revisionRecord.createdAt,
|
|
1772
|
+
`${path}.revisions.${revisionId}.createdAt`,
|
|
1773
|
+
issues,
|
|
1774
|
+
);
|
|
1775
|
+
if (revisionRecord.authorId !== undefined) {
|
|
1776
|
+
expectString(
|
|
1777
|
+
revisionRecord.authorId,
|
|
1778
|
+
`${path}.revisions.${revisionId}.authorId`,
|
|
1779
|
+
issues,
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
if (revisionRecord.warningIds !== undefined) {
|
|
1783
|
+
validateWarningIds(
|
|
1784
|
+
revisionRecord.warningIds,
|
|
1785
|
+
`${path}.revisions.${revisionId}.warningIds`,
|
|
1786
|
+
issues,
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
if (revisionRecord.metadata !== undefined) {
|
|
1790
|
+
validateRevisionMetadata(
|
|
1791
|
+
revisionRecord.metadata,
|
|
1792
|
+
`${path}.revisions.${revisionId}.metadata`,
|
|
1793
|
+
issues,
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function validateCommentStatus(
|
|
1801
|
+
value: unknown,
|
|
1802
|
+
path: string,
|
|
1803
|
+
issues: ModelValidationIssue[],
|
|
1804
|
+
): void {
|
|
1805
|
+
if (value === "open" || value === "resolved" || value === "detached") {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
issues.push({
|
|
1809
|
+
path,
|
|
1810
|
+
message: "status must be one of open, resolved, or detached.",
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function validateCommentEntries(
|
|
1815
|
+
value: unknown,
|
|
1816
|
+
path: string,
|
|
1817
|
+
issues: ModelValidationIssue[],
|
|
1818
|
+
): void {
|
|
1819
|
+
if (!Array.isArray(value)) {
|
|
1820
|
+
issues.push({
|
|
1821
|
+
path,
|
|
1822
|
+
message: "entries must be an array.",
|
|
1823
|
+
});
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
value.forEach((entry, index) => {
|
|
1828
|
+
const record = asPlainObject(entry, `${path}[${index}]`, issues);
|
|
1829
|
+
if (!record) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
expectString(record.entryId, `${path}[${index}].entryId`, issues);
|
|
1833
|
+
expectString(record.authorId, `${path}[${index}].authorId`, issues);
|
|
1834
|
+
expectStringAllowEmpty(record.body, `${path}[${index}].body`, issues);
|
|
1835
|
+
expectIso8601UtcTimestamp(record.createdAt, `${path}[${index}].createdAt`, issues);
|
|
1836
|
+
if (record.metadata !== undefined) {
|
|
1837
|
+
validateCommentEntryMetadata(record.metadata, `${path}[${index}].metadata`, issues);
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
function validateCommentEntryMetadata(
|
|
1843
|
+
value: unknown,
|
|
1844
|
+
path: string,
|
|
1845
|
+
issues: ModelValidationIssue[],
|
|
1846
|
+
): void {
|
|
1847
|
+
const record = asPlainObject(value, path, issues);
|
|
1848
|
+
if (!record) {
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
for (const field of [
|
|
1853
|
+
"ooxmlCommentId",
|
|
1854
|
+
"paraId",
|
|
1855
|
+
"parentParaId",
|
|
1856
|
+
"durableId",
|
|
1857
|
+
"initials",
|
|
1858
|
+
] as const) {
|
|
1859
|
+
if (record[field] !== undefined) {
|
|
1860
|
+
expectString(record[field], `${path}.${field}`, issues);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
function validateCommentResolution(
|
|
1866
|
+
value: unknown,
|
|
1867
|
+
path: string,
|
|
1868
|
+
issues: ModelValidationIssue[],
|
|
1869
|
+
): void {
|
|
1870
|
+
const record = asPlainObject(value, path, issues);
|
|
1871
|
+
if (!record) {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
expectIso8601UtcTimestamp(record.resolvedAt, `${path}.resolvedAt`, issues);
|
|
1876
|
+
expectString(record.resolvedBy, `${path}.resolvedBy`, issues);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function validateCommentThreadMetadata(
|
|
1880
|
+
value: unknown,
|
|
1881
|
+
path: string,
|
|
1882
|
+
issues: ModelValidationIssue[],
|
|
1883
|
+
): void {
|
|
1884
|
+
const record = asPlainObject(value, path, issues);
|
|
1885
|
+
if (!record) {
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
if (
|
|
1890
|
+
record.source !== undefined &&
|
|
1891
|
+
record.source !== "runtime" &&
|
|
1892
|
+
record.source !== "import"
|
|
1893
|
+
) {
|
|
1894
|
+
issues.push({
|
|
1895
|
+
path: `${path}.source`,
|
|
1896
|
+
message: "source must be either runtime or import.",
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
if (record.rootOoxmlCommentId !== undefined) {
|
|
1900
|
+
expectString(record.rootOoxmlCommentId, `${path}.rootOoxmlCommentId`, issues);
|
|
1901
|
+
}
|
|
1902
|
+
if (record.rootParaId !== undefined) {
|
|
1903
|
+
expectString(record.rootParaId, `${path}.rootParaId`, issues);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
function validateRevisionKind(
|
|
1908
|
+
value: unknown,
|
|
1909
|
+
path: string,
|
|
1910
|
+
issues: ModelValidationIssue[],
|
|
1911
|
+
): void {
|
|
1912
|
+
if (
|
|
1913
|
+
value === "insertion" ||
|
|
1914
|
+
value === "deletion" ||
|
|
1915
|
+
value === "formatting" ||
|
|
1916
|
+
value === "move" ||
|
|
1917
|
+
value === "property-change"
|
|
1918
|
+
) {
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
issues.push({
|
|
1922
|
+
path,
|
|
1923
|
+
message: "kind must be insertion, deletion, formatting, move, or property-change.",
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
function validateRevisionStatus(
|
|
1928
|
+
value: unknown,
|
|
1929
|
+
path: string,
|
|
1930
|
+
issues: ModelValidationIssue[],
|
|
1931
|
+
): void {
|
|
1932
|
+
if (
|
|
1933
|
+
value === "open" ||
|
|
1934
|
+
value === "accepted" ||
|
|
1935
|
+
value === "rejected" ||
|
|
1936
|
+
value === "detached"
|
|
1937
|
+
) {
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
issues.push({
|
|
1941
|
+
path,
|
|
1942
|
+
message: "status must be one of open, accepted, rejected, or detached.",
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function validateWarningIds(
|
|
1947
|
+
value: unknown,
|
|
1948
|
+
path: string,
|
|
1949
|
+
issues: ModelValidationIssue[],
|
|
1950
|
+
): void {
|
|
1951
|
+
if (!Array.isArray(value)) {
|
|
1952
|
+
issues.push({
|
|
1953
|
+
path,
|
|
1954
|
+
message: "warningIds must be an array.",
|
|
1955
|
+
});
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
value.forEach((warningId, index) =>
|
|
1960
|
+
expectDomainString(warningId, "warningId", `${path}[${index}]`, issues),
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
function validateRevisionMetadata(
|
|
1965
|
+
value: unknown,
|
|
1966
|
+
path: string,
|
|
1967
|
+
issues: ModelValidationIssue[],
|
|
1968
|
+
): void {
|
|
1969
|
+
const record = asPlainObject(value, path, issues);
|
|
1970
|
+
if (!record) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
if (
|
|
1975
|
+
record.source !== undefined &&
|
|
1976
|
+
record.source !== "runtime" &&
|
|
1977
|
+
record.source !== "import"
|
|
1978
|
+
) {
|
|
1979
|
+
issues.push({
|
|
1980
|
+
path: `${path}.source`,
|
|
1981
|
+
message: "source must be either runtime or import.",
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
for (const field of [
|
|
1985
|
+
"preserveOnlyReason",
|
|
1986
|
+
"suggestionId",
|
|
1987
|
+
"semanticKind",
|
|
1988
|
+
"predecessorSuggestionId",
|
|
1989
|
+
"importedRevisionForm",
|
|
1990
|
+
"originalRevisionType",
|
|
1991
|
+
"ooxmlRevisionId",
|
|
1992
|
+
] as const) {
|
|
1993
|
+
if (record[field] !== undefined) {
|
|
1994
|
+
expectString(record[field], `${path}.${field}`, issues);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
if (record.linkedRevisionIds !== undefined) {
|
|
1999
|
+
if (!Array.isArray(record.linkedRevisionIds)) {
|
|
2000
|
+
issues.push({
|
|
2001
|
+
path: `${path}.linkedRevisionIds`,
|
|
2002
|
+
message: "linkedRevisionIds must be an array of strings.",
|
|
2003
|
+
});
|
|
2004
|
+
} else {
|
|
2005
|
+
for (const [index, value] of record.linkedRevisionIds.entries()) {
|
|
2006
|
+
expectString(value, `${path}.linkedRevisionIds.${index}`, issues);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
if (record.propertyChangeData !== undefined) {
|
|
2012
|
+
const pcd = asPlainObject(record.propertyChangeData, `${path}.propertyChangeData`, issues);
|
|
2013
|
+
if (pcd) {
|
|
2014
|
+
expectString(pcd.xmlTag, `${path}.propertyChangeData.xmlTag`, issues);
|
|
2015
|
+
expectString(pcd.beforeXml, `${path}.propertyChangeData.beforeXml`, issues);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
if (record.moveData !== undefined) {
|
|
2020
|
+
const md = asPlainObject(record.moveData, `${path}.moveData`, issues);
|
|
2021
|
+
if (md) {
|
|
2022
|
+
expectString(md.moveId, `${path}.moveData.moveId`, issues);
|
|
2023
|
+
expectString(md.direction, `${path}.moveData.direction`, issues);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function validatePreservationStore(
|
|
2029
|
+
value: unknown,
|
|
2030
|
+
path: string,
|
|
2031
|
+
issues: ModelValidationIssue[],
|
|
2032
|
+
): void {
|
|
2033
|
+
const record = asPlainObject(value, path, issues);
|
|
2034
|
+
if (!record) {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
const opaqueFragments = asPlainObject(
|
|
2039
|
+
record.opaqueFragments,
|
|
2040
|
+
`${path}.opaqueFragments`,
|
|
2041
|
+
issues,
|
|
2042
|
+
);
|
|
2043
|
+
if (opaqueFragments) {
|
|
2044
|
+
for (const [fragmentId, fragment] of Object.entries(opaqueFragments)) {
|
|
2045
|
+
expectDomainString(fragmentId, "fragmentId", `${path}.opaqueFragments.${fragmentId}`, issues);
|
|
2046
|
+
const fragmentRecord = asPlainObject(
|
|
2047
|
+
fragment,
|
|
2048
|
+
`${path}.opaqueFragments.${fragmentId}`,
|
|
2049
|
+
issues,
|
|
2050
|
+
);
|
|
2051
|
+
if (!fragmentRecord) {
|
|
2052
|
+
continue;
|
|
2053
|
+
}
|
|
2054
|
+
if (fragmentRecord.fragmentId !== fragmentId) {
|
|
2055
|
+
issues.push({
|
|
2056
|
+
path: `${path}.opaqueFragments.${fragmentId}.fragmentId`,
|
|
2057
|
+
message: "fragmentId must match the map key.",
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
validateRange(
|
|
2061
|
+
fragmentRecord.lastKnownRange,
|
|
2062
|
+
`${path}.opaqueFragments.${fragmentId}.lastKnownRange`,
|
|
2063
|
+
issues,
|
|
2064
|
+
);
|
|
2065
|
+
expectDomainString(
|
|
2066
|
+
fragmentRecord.warningId,
|
|
2067
|
+
"warningId",
|
|
2068
|
+
`${path}.opaqueFragments.${fragmentId}.warningId`,
|
|
2069
|
+
issues,
|
|
2070
|
+
);
|
|
2071
|
+
if (fragmentRecord.packagePartName !== undefined) {
|
|
2072
|
+
expectDomainString(
|
|
2073
|
+
fragmentRecord.packagePartName,
|
|
2074
|
+
"packagePartName",
|
|
2075
|
+
`${path}.opaqueFragments.${fragmentId}.packagePartName`,
|
|
2076
|
+
issues,
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
if (fragmentRecord.relationshipId !== undefined) {
|
|
2080
|
+
expectDomainString(
|
|
2081
|
+
fragmentRecord.relationshipId,
|
|
2082
|
+
"relationshipId",
|
|
2083
|
+
`${path}.opaqueFragments.${fragmentId}.relationshipId`,
|
|
2084
|
+
issues,
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
const packageParts = asPlainObject(record.packageParts, `${path}.packageParts`, issues);
|
|
2091
|
+
if (packageParts) {
|
|
2092
|
+
for (const [packagePartName, packagePart] of Object.entries(packageParts)) {
|
|
2093
|
+
expectDomainString(
|
|
2094
|
+
packagePartName,
|
|
2095
|
+
"packagePartName",
|
|
2096
|
+
`${path}.packageParts.${packagePartName}`,
|
|
2097
|
+
issues,
|
|
2098
|
+
);
|
|
2099
|
+
const packagePartRecord = asPlainObject(
|
|
2100
|
+
packagePart,
|
|
2101
|
+
`${path}.packageParts.${packagePartName}`,
|
|
2102
|
+
issues,
|
|
2103
|
+
);
|
|
2104
|
+
if (!packagePartRecord) {
|
|
2105
|
+
continue;
|
|
2106
|
+
}
|
|
2107
|
+
if (packagePartRecord.packagePartName !== packagePartName) {
|
|
2108
|
+
issues.push({
|
|
2109
|
+
path: `${path}.packageParts.${packagePartName}.packagePartName`,
|
|
2110
|
+
message: "packagePartName must match the map key.",
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
if (Array.isArray(packagePartRecord.relationshipIds)) {
|
|
2114
|
+
packagePartRecord.relationshipIds.forEach((relationshipId, index) =>
|
|
2115
|
+
expectDomainString(
|
|
2116
|
+
relationshipId,
|
|
2117
|
+
"relationshipId",
|
|
2118
|
+
`${path}.packageParts.${packagePartName}.relationshipIds[${index}]`,
|
|
2119
|
+
issues,
|
|
2120
|
+
),
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
function validateDiagnosticStore(
|
|
2128
|
+
value: unknown,
|
|
2129
|
+
path: string,
|
|
2130
|
+
issues: ModelValidationIssue[],
|
|
2131
|
+
): void {
|
|
2132
|
+
const record = asPlainObject(value, path, issues);
|
|
2133
|
+
if (!record) {
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
if (Array.isArray(record.warnings)) {
|
|
2138
|
+
record.warnings.forEach((warning, index) => {
|
|
2139
|
+
const warningRecord = asPlainObject(warning, `${path}.warnings[${index}]`, issues);
|
|
2140
|
+
if (!warningRecord) {
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
expectDomainString(
|
|
2144
|
+
warningRecord.diagnosticId,
|
|
2145
|
+
"diagnosticId",
|
|
2146
|
+
`${path}.warnings[${index}].diagnosticId`,
|
|
2147
|
+
issues,
|
|
2148
|
+
);
|
|
2149
|
+
expectDomainString(
|
|
2150
|
+
warningRecord.warningId,
|
|
2151
|
+
"warningId",
|
|
2152
|
+
`${path}.warnings[${index}].warningId`,
|
|
2153
|
+
issues,
|
|
2154
|
+
);
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
if (Array.isArray(record.errors)) {
|
|
2159
|
+
record.errors.forEach((error, index) => {
|
|
2160
|
+
const errorRecord = asPlainObject(error, `${path}.errors[${index}]`, issues);
|
|
2161
|
+
if (!errorRecord) {
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
expectDomainString(
|
|
2165
|
+
errorRecord.diagnosticId,
|
|
2166
|
+
"diagnosticId",
|
|
2167
|
+
`${path}.errors[${index}].diagnosticId`,
|
|
2168
|
+
issues,
|
|
2169
|
+
);
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
function validateAnchor(
|
|
2175
|
+
value: unknown,
|
|
2176
|
+
path: string,
|
|
2177
|
+
issues: ModelValidationIssue[],
|
|
2178
|
+
): void {
|
|
2179
|
+
const record = asPlainObject(value, path, issues);
|
|
2180
|
+
if (!record) {
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
const kind = expectString(record.kind, `${path}.kind`, issues);
|
|
2185
|
+
if (!kind) {
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
if (kind === "range") {
|
|
2190
|
+
validateRange(record.range, `${path}.range`, issues);
|
|
2191
|
+
validateBoundaryAssoc(record.assoc, `${path}.assoc`, issues);
|
|
2192
|
+
} else if (kind === "node") {
|
|
2193
|
+
if (typeof record.at !== "number") {
|
|
2194
|
+
issues.push({
|
|
2195
|
+
path: `${path}.at`,
|
|
2196
|
+
message: "node anchors must contain a numeric at value.",
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
if (record.assoc !== -1 && record.assoc !== 1) {
|
|
2200
|
+
issues.push({
|
|
2201
|
+
path: `${path}.assoc`,
|
|
2202
|
+
message: "node anchor assoc must be -1 or 1.",
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
} else if (kind === "detached") {
|
|
2206
|
+
validateRange(record.lastKnownRange, `${path}.lastKnownRange`, issues);
|
|
2207
|
+
if (
|
|
2208
|
+
record.reason !== "deleted" &&
|
|
2209
|
+
record.reason !== "invalidatedByStructureChange" &&
|
|
2210
|
+
record.reason !== "importAmbiguity"
|
|
2211
|
+
) {
|
|
2212
|
+
issues.push({
|
|
2213
|
+
path: `${path}.reason`,
|
|
2214
|
+
message: "detached anchor reason must be deleted, invalidatedByStructureChange, or importAmbiguity.",
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
} else {
|
|
2218
|
+
issues.push({
|
|
2219
|
+
path: `${path}.kind`,
|
|
2220
|
+
message: "anchor kind must be range, node, or detached.",
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
function validateBoundaryAssoc(
|
|
2226
|
+
value: unknown,
|
|
2227
|
+
path: string,
|
|
2228
|
+
issues: ModelValidationIssue[],
|
|
2229
|
+
): void {
|
|
2230
|
+
const record = asPlainObject(value, path, issues);
|
|
2231
|
+
if (!record) {
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
if (record.start !== -1 && record.start !== 1) {
|
|
2236
|
+
issues.push({
|
|
2237
|
+
path: `${path}.start`,
|
|
2238
|
+
message: "assoc.start must be -1 or 1.",
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
if (record.end !== -1 && record.end !== 1) {
|
|
2242
|
+
issues.push({
|
|
2243
|
+
path: `${path}.end`,
|
|
2244
|
+
message: "assoc.end must be -1 or 1.",
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
function validateDocumentReferences(
|
|
2250
|
+
record: Record<string, unknown>,
|
|
2251
|
+
issues: ModelValidationIssue[],
|
|
2252
|
+
): void {
|
|
2253
|
+
const paragraphStyleIds = new Set(
|
|
2254
|
+
Object.keys(asPlainObject(asPlainObject(record.styles, "$.styles", [])?.paragraphs, "$.styles.paragraphs", []) ?? {}),
|
|
2255
|
+
);
|
|
2256
|
+
const numberingInstanceIds = new Set(
|
|
2257
|
+
Object.keys(asPlainObject(asPlainObject(record.numbering, "$.numbering", [])?.instances, "$.numbering.instances", []) ?? {}),
|
|
2258
|
+
);
|
|
2259
|
+
const abstractNumberingIds = new Set(
|
|
2260
|
+
Object.keys(asPlainObject(asPlainObject(record.numbering, "$.numbering", [])?.abstractDefinitions, "$.numbering.abstractDefinitions", []) ?? {}),
|
|
2261
|
+
);
|
|
2262
|
+
const mediaIds = new Set(
|
|
2263
|
+
Object.keys(asPlainObject(asPlainObject(record.media, "$.media", [])?.items, "$.media.items", []) ?? {}),
|
|
2264
|
+
);
|
|
2265
|
+
const warningIds = new Set(
|
|
2266
|
+
(Array.isArray(asPlainObject(record.diagnostics, "$.diagnostics", [])?.warnings)
|
|
2267
|
+
? (asPlainObject(record.diagnostics, "$.diagnostics", [])?.warnings as Array<Record<string, unknown>>)
|
|
2268
|
+
: []
|
|
2269
|
+
).flatMap((warning) => typeof warning.warningId === "string" ? [warning.warningId] : []),
|
|
2270
|
+
);
|
|
2271
|
+
const fragmentIds = new Set(
|
|
2272
|
+
Object.keys(asPlainObject(asPlainObject(record.preservation, "$.preservation", [])?.opaqueFragments, "$.preservation.opaqueFragments", []) ?? {}),
|
|
2273
|
+
);
|
|
2274
|
+
const noteIds = collectNoteIds(record);
|
|
2275
|
+
|
|
2276
|
+
const numberingInstances = asPlainObject(
|
|
2277
|
+
asPlainObject(record.numbering, "$.numbering", [])?.instances,
|
|
2278
|
+
"$.numbering.instances",
|
|
2279
|
+
[],
|
|
2280
|
+
);
|
|
2281
|
+
if (numberingInstances) {
|
|
2282
|
+
for (const [instanceId, instance] of Object.entries(numberingInstances)) {
|
|
2283
|
+
const instanceRecord = asPlainObject(instance, `$.numbering.instances.${instanceId}`, []);
|
|
2284
|
+
if (
|
|
2285
|
+
instanceRecord &&
|
|
2286
|
+
typeof instanceRecord.abstractNumberingId === "string" &&
|
|
2287
|
+
!abstractNumberingIds.has(instanceRecord.abstractNumberingId)
|
|
2288
|
+
) {
|
|
2289
|
+
issues.push({
|
|
2290
|
+
path: `$.numbering.instances.${instanceId}.abstractNumberingId`,
|
|
2291
|
+
message: "abstractNumberingId must reference an existing abstract numbering definition.",
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
validateDocumentNodeReferences(record.content, "$.content", issues, {
|
|
2298
|
+
paragraphStyleIds,
|
|
2299
|
+
numberingInstanceIds,
|
|
2300
|
+
mediaIds,
|
|
2301
|
+
warningIds,
|
|
2302
|
+
fragmentIds,
|
|
2303
|
+
noteIds,
|
|
2304
|
+
});
|
|
2305
|
+
validateSubPartReferences(record.subParts, "$.subParts", issues, {
|
|
2306
|
+
paragraphStyleIds,
|
|
2307
|
+
numberingInstanceIds,
|
|
2308
|
+
mediaIds,
|
|
2309
|
+
warningIds,
|
|
2310
|
+
fragmentIds,
|
|
2311
|
+
noteIds,
|
|
2312
|
+
});
|
|
2313
|
+
validateReviewReferences(record.review, "$.review", issues, warningIds);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
function validateDocumentNodeReferences(
|
|
2317
|
+
value: unknown,
|
|
2318
|
+
path: string,
|
|
2319
|
+
issues: ModelValidationIssue[],
|
|
2320
|
+
references: {
|
|
2321
|
+
paragraphStyleIds: ReadonlySet<string>;
|
|
2322
|
+
numberingInstanceIds: ReadonlySet<string>;
|
|
2323
|
+
mediaIds: ReadonlySet<string>;
|
|
2324
|
+
warningIds: ReadonlySet<string>;
|
|
2325
|
+
fragmentIds: ReadonlySet<string>;
|
|
2326
|
+
noteIds: ReadonlySet<string>;
|
|
2327
|
+
},
|
|
2328
|
+
): void {
|
|
2329
|
+
const record = asPlainObject(value, path, issues);
|
|
2330
|
+
if (!record) {
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
const type = typeof record.type === "string" ? record.type : undefined;
|
|
2335
|
+
if (type === "paragraph") {
|
|
2336
|
+
if (
|
|
2337
|
+
typeof record.styleId === "string" &&
|
|
2338
|
+
!references.paragraphStyleIds.has(record.styleId)
|
|
2339
|
+
) {
|
|
2340
|
+
issues.push({
|
|
2341
|
+
path: `${path}.styleId`,
|
|
2342
|
+
message: "styleId must reference an existing paragraph style definition.",
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
const numbering = asPlainObject(record.numbering, `${path}.numbering`, []);
|
|
2346
|
+
if (
|
|
2347
|
+
numbering &&
|
|
2348
|
+
typeof numbering.numberingInstanceId === "string" &&
|
|
2349
|
+
!references.numberingInstanceIds.has(numbering.numberingInstanceId)
|
|
2350
|
+
) {
|
|
2351
|
+
issues.push({
|
|
2352
|
+
path: `${path}.numbering.numberingInstanceId`,
|
|
2353
|
+
message: "numberingInstanceId must reference an existing numbering instance.",
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
} else if (type === "image") {
|
|
2357
|
+
if (typeof record.mediaId === "string" && !references.mediaIds.has(record.mediaId)) {
|
|
2358
|
+
issues.push({
|
|
2359
|
+
path: `${path}.mediaId`,
|
|
2360
|
+
message: "mediaId must reference an existing media catalog item.",
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
} else if (type === "opaque_inline" || type === "opaque_block") {
|
|
2364
|
+
if (typeof record.fragmentId === "string" && !references.fragmentIds.has(record.fragmentId)) {
|
|
2365
|
+
issues.push({
|
|
2366
|
+
path: `${path}.fragmentId`,
|
|
2367
|
+
message: "fragmentId must reference an existing opaque fragment.",
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
if (typeof record.warningId === "string" && !references.warningIds.has(record.warningId)) {
|
|
2371
|
+
issues.push({
|
|
2372
|
+
path: `${path}.warningId`,
|
|
2373
|
+
message: "warningId must reference an existing diagnostic warning.",
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
} else if (type === "footnote_ref") {
|
|
2377
|
+
if (typeof record.noteId === "string" && !references.noteIds.has(`${record.noteKind}:${record.noteId}`)) {
|
|
2378
|
+
issues.push({
|
|
2379
|
+
path: `${path}.noteId`,
|
|
2380
|
+
message: "noteId must reference an existing footnote or endnote definition.",
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
if (Array.isArray(record.children)) {
|
|
2386
|
+
record.children.forEach((child, index) =>
|
|
2387
|
+
validateDocumentNodeReferences(child, `${path}.children[${index}]`, issues, references),
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
if (Array.isArray(record.rows)) {
|
|
2391
|
+
record.rows.forEach((row, index) =>
|
|
2392
|
+
validateDocumentNodeReferences(row, `${path}.rows[${index}]`, issues, references),
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
if (Array.isArray(record.cells)) {
|
|
2396
|
+
record.cells.forEach((cell, index) =>
|
|
2397
|
+
validateDocumentNodeReferences(cell, `${path}.cells[${index}]`, issues, references),
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function validateReviewReferences(
|
|
2403
|
+
value: unknown,
|
|
2404
|
+
path: string,
|
|
2405
|
+
issues: ModelValidationIssue[],
|
|
2406
|
+
warningIds: ReadonlySet<string>,
|
|
2407
|
+
): void {
|
|
2408
|
+
const record = asPlainObject(value, path, issues);
|
|
2409
|
+
if (!record) {
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
const comments = asPlainObject(record.comments, `${path}.comments`, []);
|
|
2414
|
+
if (comments) {
|
|
2415
|
+
for (const [commentId, thread] of Object.entries(comments)) {
|
|
2416
|
+
const threadRecord = asPlainObject(thread, `${path}.comments.${commentId}`, []);
|
|
2417
|
+
if (!threadRecord || !Array.isArray(threadRecord.warningIds)) {
|
|
2418
|
+
continue;
|
|
2419
|
+
}
|
|
2420
|
+
threadRecord.warningIds.forEach((warningId, index) => {
|
|
2421
|
+
if (typeof warningId === "string" && !warningIds.has(warningId)) {
|
|
2422
|
+
issues.push({
|
|
2423
|
+
path: `${path}.comments.${commentId}.warningIds[${index}]`,
|
|
2424
|
+
message: "warningIds must reference existing diagnostic warnings.",
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
const revisions = asPlainObject(record.revisions, `${path}.revisions`, []);
|
|
2432
|
+
if (revisions) {
|
|
2433
|
+
for (const [revisionId, revision] of Object.entries(revisions)) {
|
|
2434
|
+
const revisionRecord = asPlainObject(revision, `${path}.revisions.${revisionId}`, []);
|
|
2435
|
+
if (!revisionRecord || !Array.isArray(revisionRecord.warningIds)) {
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
revisionRecord.warningIds.forEach((warningId, index) => {
|
|
2439
|
+
if (typeof warningId === "string" && !warningIds.has(warningId)) {
|
|
2440
|
+
issues.push({
|
|
2441
|
+
path: `${path}.revisions.${revisionId}.warningIds[${index}]`,
|
|
2442
|
+
message: "warningIds must reference existing diagnostic warnings.",
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function validateSubPartReferences(
|
|
2451
|
+
value: unknown,
|
|
2452
|
+
path: string,
|
|
2453
|
+
issues: ModelValidationIssue[],
|
|
2454
|
+
references: {
|
|
2455
|
+
paragraphStyleIds: ReadonlySet<string>;
|
|
2456
|
+
numberingInstanceIds: ReadonlySet<string>;
|
|
2457
|
+
mediaIds: ReadonlySet<string>;
|
|
2458
|
+
warningIds: ReadonlySet<string>;
|
|
2459
|
+
fragmentIds: ReadonlySet<string>;
|
|
2460
|
+
noteIds: ReadonlySet<string>;
|
|
2461
|
+
},
|
|
2462
|
+
): void {
|
|
2463
|
+
if (value === undefined) {
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const record = asPlainObject(value, path, issues);
|
|
2467
|
+
if (!record) {
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
const headers = Array.isArray(record.headers) ? record.headers : [];
|
|
2472
|
+
headers.forEach((header, index) => {
|
|
2473
|
+
const headerRecord = asPlainObject(header, `${path}.headers[${index}]`, []);
|
|
2474
|
+
if (!headerRecord || !Array.isArray(headerRecord.blocks)) {
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
headerRecord.blocks.forEach((block, blockIndex) =>
|
|
2478
|
+
validateDocumentNodeReferences(block, `${path}.headers[${index}].blocks[${blockIndex}]`, issues, references),
|
|
2479
|
+
);
|
|
2480
|
+
});
|
|
2481
|
+
|
|
2482
|
+
const footers = Array.isArray(record.footers) ? record.footers : [];
|
|
2483
|
+
footers.forEach((footer, index) => {
|
|
2484
|
+
const footerRecord = asPlainObject(footer, `${path}.footers[${index}]`, []);
|
|
2485
|
+
if (!footerRecord || !Array.isArray(footerRecord.blocks)) {
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
footerRecord.blocks.forEach((block, blockIndex) =>
|
|
2489
|
+
validateDocumentNodeReferences(block, `${path}.footers[${index}].blocks[${blockIndex}]`, issues, references),
|
|
2490
|
+
);
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
if (record.footnoteCollection !== undefined) {
|
|
2494
|
+
const footnoteCollection = asPlainObject(record.footnoteCollection, `${path}.footnoteCollection`, []);
|
|
2495
|
+
const footnotes = asPlainObject(footnoteCollection?.footnotes, `${path}.footnoteCollection.footnotes`, []);
|
|
2496
|
+
if (footnotes) {
|
|
2497
|
+
for (const [noteId, note] of Object.entries(footnotes)) {
|
|
2498
|
+
const noteRecord = asPlainObject(note, `${path}.footnoteCollection.footnotes.${noteId}`, []);
|
|
2499
|
+
if (!noteRecord || !Array.isArray(noteRecord.blocks)) {
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
noteRecord.blocks.forEach((block, blockIndex) =>
|
|
2503
|
+
validateDocumentNodeReferences(
|
|
2504
|
+
block,
|
|
2505
|
+
`${path}.footnoteCollection.footnotes.${noteId}.blocks[${blockIndex}]`,
|
|
2506
|
+
issues,
|
|
2507
|
+
references,
|
|
2508
|
+
),
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
const endnotes = asPlainObject(footnoteCollection?.endnotes, `${path}.footnoteCollection.endnotes`, []);
|
|
2514
|
+
if (endnotes) {
|
|
2515
|
+
for (const [noteId, note] of Object.entries(endnotes)) {
|
|
2516
|
+
const noteRecord = asPlainObject(note, `${path}.footnoteCollection.endnotes.${noteId}`, []);
|
|
2517
|
+
if (!noteRecord || !Array.isArray(noteRecord.blocks)) {
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
noteRecord.blocks.forEach((block, blockIndex) =>
|
|
2521
|
+
validateDocumentNodeReferences(
|
|
2522
|
+
block,
|
|
2523
|
+
`${path}.footnoteCollection.endnotes.${noteId}.blocks[${blockIndex}]`,
|
|
2524
|
+
issues,
|
|
2525
|
+
references,
|
|
2526
|
+
),
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
function collectNoteIds(record: Record<string, unknown>): Set<string> {
|
|
2534
|
+
const noteIds = new Set<string>();
|
|
2535
|
+
const subParts = asPlainObject(record.subParts, "$.subParts", []);
|
|
2536
|
+
const footnoteCollection = asPlainObject(subParts?.footnoteCollection, "$.subParts.footnoteCollection", []);
|
|
2537
|
+
const footnotes = asPlainObject(footnoteCollection?.footnotes, "$.subParts.footnoteCollection.footnotes", []);
|
|
2538
|
+
if (footnotes) {
|
|
2539
|
+
Object.keys(footnotes).forEach((noteId) => noteIds.add(`footnote:${noteId}`));
|
|
2540
|
+
}
|
|
2541
|
+
const endnotes = asPlainObject(footnoteCollection?.endnotes, "$.subParts.footnoteCollection.endnotes", []);
|
|
2542
|
+
if (endnotes) {
|
|
2543
|
+
Object.keys(endnotes).forEach((noteId) => noteIds.add(`endnote:${noteId}`));
|
|
2544
|
+
}
|
|
2545
|
+
return noteIds;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function validateRange(
|
|
2549
|
+
value: unknown,
|
|
2550
|
+
path: string,
|
|
2551
|
+
issues: ModelValidationIssue[],
|
|
2552
|
+
): void {
|
|
2553
|
+
const record = asPlainObject(value, path, issues);
|
|
2554
|
+
if (!record) {
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
if (typeof record.from !== "number" || typeof record.to !== "number") {
|
|
2559
|
+
issues.push({
|
|
2560
|
+
path,
|
|
2561
|
+
message: "Range must contain numeric from and to values.",
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
function validateExactObjectKeys(
|
|
2567
|
+
record: Record<string, unknown>,
|
|
2568
|
+
expectedKeys: readonly string[],
|
|
2569
|
+
path: string,
|
|
2570
|
+
issues: ModelValidationIssue[],
|
|
2571
|
+
allowedOptionalKeys?: readonly string[],
|
|
2572
|
+
): void {
|
|
2573
|
+
const actualKeys = new Set(Object.keys(record));
|
|
2574
|
+
const expected = new Set(expectedKeys);
|
|
2575
|
+
const optional = new Set(allowedOptionalKeys ?? []);
|
|
2576
|
+
|
|
2577
|
+
for (const expectedKey of expectedKeys) {
|
|
2578
|
+
if (!actualKeys.has(expectedKey)) {
|
|
2579
|
+
issues.push({
|
|
2580
|
+
path,
|
|
2581
|
+
message: `Missing required key ${JSON.stringify(expectedKey)}.`,
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
for (const actualKey of actualKeys) {
|
|
2587
|
+
if (!expected.has(actualKey) && !optional.has(actualKey)) {
|
|
2588
|
+
issues.push({
|
|
2589
|
+
path: `${path}.${actualKey}`,
|
|
2590
|
+
message:
|
|
2591
|
+
"Unexpected canonical document key. Render/UI state is not part of the canonical envelope.",
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function validateSubPartsCatalog(
|
|
2598
|
+
value: unknown,
|
|
2599
|
+
path: string,
|
|
2600
|
+
issues: ModelValidationIssue[],
|
|
2601
|
+
): void {
|
|
2602
|
+
if (value === undefined) {
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const record = asPlainObject(value, path, issues);
|
|
2606
|
+
if (!record) {
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
if (record.headers !== undefined) {
|
|
2611
|
+
if (!Array.isArray(record.headers)) {
|
|
2612
|
+
issues.push({ path: `${path}.headers`, message: "headers must be an array." });
|
|
2613
|
+
} else {
|
|
2614
|
+
record.headers.forEach((header, index) => {
|
|
2615
|
+
const headerRecord = asPlainObject(header, `${path}.headers[${index}]`, issues);
|
|
2616
|
+
if (headerRecord) {
|
|
2617
|
+
expectString(headerRecord.variant, `${path}.headers[${index}].variant`, issues);
|
|
2618
|
+
expectString(headerRecord.partPath, `${path}.headers[${index}].partPath`, issues);
|
|
2619
|
+
expectString(headerRecord.relationshipId, `${path}.headers[${index}].relationshipId`, issues);
|
|
2620
|
+
if (!Array.isArray(headerRecord.blocks)) {
|
|
2621
|
+
issues.push({ path: `${path}.headers[${index}].blocks`, message: "blocks must be an array." });
|
|
2622
|
+
} else {
|
|
2623
|
+
headerRecord.blocks.forEach((block, blockIndex) =>
|
|
2624
|
+
validateDocumentNode(block, `${path}.headers[${index}].blocks[${blockIndex}]`, issues),
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
if (record.footers !== undefined) {
|
|
2633
|
+
if (!Array.isArray(record.footers)) {
|
|
2634
|
+
issues.push({ path: `${path}.footers`, message: "footers must be an array." });
|
|
2635
|
+
} else {
|
|
2636
|
+
record.footers.forEach((footer, index) => {
|
|
2637
|
+
const footerRecord = asPlainObject(footer, `${path}.footers[${index}]`, issues);
|
|
2638
|
+
if (footerRecord) {
|
|
2639
|
+
expectString(footerRecord.variant, `${path}.footers[${index}].variant`, issues);
|
|
2640
|
+
expectString(footerRecord.partPath, `${path}.footers[${index}].partPath`, issues);
|
|
2641
|
+
expectString(footerRecord.relationshipId, `${path}.footers[${index}].relationshipId`, issues);
|
|
2642
|
+
if (!Array.isArray(footerRecord.blocks)) {
|
|
2643
|
+
issues.push({ path: `${path}.footers[${index}].blocks`, message: "blocks must be an array." });
|
|
2644
|
+
} else {
|
|
2645
|
+
footerRecord.blocks.forEach((block, blockIndex) =>
|
|
2646
|
+
validateDocumentNode(block, `${path}.footers[${index}].blocks[${blockIndex}]`, issues),
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
if (record.footnoteCollection !== undefined) {
|
|
2655
|
+
const footnoteCollection = asPlainObject(record.footnoteCollection, `${path}.footnoteCollection`, issues);
|
|
2656
|
+
if (footnoteCollection) {
|
|
2657
|
+
validateSubPartNoteMap(footnoteCollection.footnotes, "footnote", `${path}.footnoteCollection.footnotes`, issues);
|
|
2658
|
+
validateSubPartNoteMap(footnoteCollection.endnotes, "endnote", `${path}.footnoteCollection.endnotes`, issues);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
function validateSubPartNoteMap(
|
|
2664
|
+
value: unknown,
|
|
2665
|
+
expectedKind: "footnote" | "endnote",
|
|
2666
|
+
path: string,
|
|
2667
|
+
issues: ModelValidationIssue[],
|
|
2668
|
+
): void {
|
|
2669
|
+
const record = asPlainObject(value, path, issues);
|
|
2670
|
+
if (!record) {
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
for (const [noteId, note] of Object.entries(record)) {
|
|
2675
|
+
const noteRecord = asPlainObject(note, `${path}.${noteId}`, issues);
|
|
2676
|
+
if (!noteRecord) {
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
expectString(noteRecord.noteId, `${path}.${noteId}.noteId`, issues);
|
|
2680
|
+
if (noteRecord.noteId !== noteId) {
|
|
2681
|
+
issues.push({
|
|
2682
|
+
path: `${path}.${noteId}.noteId`,
|
|
2683
|
+
message: "noteId must match the map key.",
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
if (noteRecord.kind !== expectedKind) {
|
|
2687
|
+
issues.push({
|
|
2688
|
+
path: `${path}.${noteId}.kind`,
|
|
2689
|
+
message: `kind must be ${expectedKind}.`,
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
if (!Array.isArray(noteRecord.blocks)) {
|
|
2693
|
+
issues.push({ path: `${path}.${noteId}.blocks`, message: "blocks must be an array." });
|
|
2694
|
+
} else {
|
|
2695
|
+
noteRecord.blocks.forEach((block, blockIndex) =>
|
|
2696
|
+
validateDocumentNode(block, `${path}.${noteId}.blocks[${blockIndex}]`, issues),
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
function expectDomainString(
|
|
2703
|
+
value: unknown,
|
|
2704
|
+
domain: StableIdDomain,
|
|
2705
|
+
path: string,
|
|
2706
|
+
issues: ModelValidationIssue[],
|
|
2707
|
+
): string | null {
|
|
2708
|
+
const stableId = expectString(value, path, issues);
|
|
2709
|
+
if (!stableId) {
|
|
2710
|
+
return null;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
if (!ID_PATTERNS[domain].test(stableId)) {
|
|
2714
|
+
issues.push({
|
|
2715
|
+
path,
|
|
2716
|
+
message: `Expected a valid ${domain}.`,
|
|
2717
|
+
});
|
|
2718
|
+
return null;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
return stableId;
|
|
2722
|
+
}
|