@stll/folio-core 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/ai-edits/apply.d.ts +26 -1
- package/dist/ai-edits/apply.js +1519 -105
- package/dist/ai-edits/batch-claims.d.ts +130 -0
- package/dist/ai-edits/batch-claims.js +244 -0
- package/dist/ai-edits/blockRange.js +9 -3
- package/dist/ai-edits/character-boundaries.d.ts +43 -0
- package/dist/ai-edits/character-boundaries.js +95 -0
- package/dist/ai-edits/clean-text.d.ts +33 -3
- package/dist/ai-edits/clean-text.js +52 -6
- package/dist/ai-edits/comment-lifecycle.d.ts +18 -0
- package/dist/ai-edits/comment-lifecycle.js +52 -0
- package/dist/ai-edits/headless.d.ts +71 -7
- package/dist/ai-edits/headless.js +381 -41
- package/dist/ai-edits/index.d.ts +2 -1
- package/dist/ai-edits/index.js +2 -1
- package/dist/ai-edits/minimal-replacement.d.ts +21 -6
- package/dist/ai-edits/minimal-replacement.js +134 -15
- package/dist/ai-edits/newListNumbering.d.ts +23 -0
- package/dist/ai-edits/newListNumbering.js +94 -0
- package/dist/ai-edits/note-references.d.ts +96 -0
- package/dist/ai-edits/note-references.js +139 -0
- package/dist/ai-edits/pending-suggestions.d.ts +79 -0
- package/dist/ai-edits/pending-suggestions.js +306 -0
- package/dist/ai-edits/read.d.ts +1 -1
- package/dist/ai-edits/read.js +56 -43
- package/dist/ai-edits/result-validation.d.ts +29 -0
- package/dist/ai-edits/result-validation.js +164 -0
- package/dist/ai-edits/snapshot.d.ts +36 -3
- package/dist/ai-edits/snapshot.js +177 -23
- package/dist/ai-edits/table-cell-mutations.d.ts +1 -1
- package/dist/ai-edits/table-cell-mutations.js +4 -1
- package/dist/ai-edits/table-geometry.d.ts +1 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +38 -16
- package/dist/ai-edits/table-row-column-mutations.js +61 -26
- package/dist/ai-edits/types.d.ts +155 -16
- package/dist/compare/compare.d.ts +1 -1
- package/dist/compare/compare.js +65 -16
- package/dist/compare/content-alignment.js +13 -2
- package/dist/compare/content-types.d.ts +7 -0
- package/dist/compare/content.d.ts +4 -1
- package/dist/compare/content.js +6 -1
- package/dist/compare/formatting.js +2 -1
- package/dist/compare/inline-atoms.d.ts +21 -4
- package/dist/compare/inline-atoms.js +138 -16
- package/dist/compare/plan.js +10 -5
- package/dist/compare/scenario.js +14 -3
- package/dist/compare/section-boundary-properties.js +1 -1
- package/dist/compare/types.d.ts +4 -2
- package/dist/compare/types.js +2 -1
- package/dist/compare/verification.d.ts +1 -1
- package/dist/compare/verification.js +15 -3
- package/dist/controller/committedLayoutDiff.d.ts +25 -0
- package/dist/controller/committedLayoutDiff.js +71 -0
- package/dist/controller/contentControlWidgetController.d.ts +1 -1
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/fontReadiness.d.ts +70 -3
- package/dist/controller/fontReadiness.js +100 -10
- package/dist/controller/headerFooterEditorManager.js +6 -1
- package/dist/controller/layoutPipeline.d.ts +12 -10
- package/dist/controller/layoutPipeline.js +62 -9
- package/dist/controller/layoutRunOptions.d.ts +24 -0
- package/dist/controller/layoutRunOptions.js +17 -0
- package/dist/controller/layoutScheduler.d.ts +19 -14
- package/dist/controller/layoutScheduler.js +15 -12
- package/dist/controller/layoutSession.d.ts +26 -2
- package/dist/controller/layoutSession.js +1 -1
- package/dist/controller/noteEditorManager.js +8 -3
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +230 -21
- package/dist/docx/altChunk.d.ts +8 -0
- package/dist/docx/altChunk.js +17 -0
- package/dist/docx/attributeRemainder.d.ts +1 -1
- package/dist/docx/blockContentParser.d.ts +64 -1
- package/dist/docx/blockContentParser.js +17 -4
- package/dist/docx/blockCustomXmlShell.d.ts +7 -0
- package/dist/docx/blockCustomXmlShell.js +14 -0
- package/dist/docx/blockPlainText.js +7 -0
- package/dist/docx/commentAnchorIndex.js +1 -1
- package/dist/docx/commentRangeIntegrity.js +8 -3
- package/dist/docx/commentReferenceNormalization.js +1 -1
- package/dist/docx/commentReplyMarkers.js +20 -1
- package/dist/docx/compatibility.d.ts +1 -1
- package/dist/docx/compatibility.js +34 -13
- package/dist/docx/containerChildren.gen.d.ts +1 -1
- package/dist/docx/containerChildren.gen.js +1 -0
- package/dist/docx/documentParser.js +12 -12
- package/dist/docx/drawingIdNormalization.d.ts +3 -1
- package/dist/docx/drawingIdNormalization.js +6 -1
- package/dist/docx/ensureParaIds.js +119 -97
- package/dist/docx/footnoteParser.js +8 -4
- package/dist/docx/headerFooterParser.js +3 -3
- package/dist/docx/headerFooterReferenceNormalization.d.ts +20 -1
- package/dist/docx/headerFooterReferenceNormalization.js +52 -27
- package/dist/docx/hyperlinkParser.d.ts +1 -1
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/listNumberingInstances.d.ts +59 -0
- package/dist/docx/listNumberingInstances.js +249 -0
- package/dist/docx/normalizeBaseDirection.js +2 -1
- package/dist/docx/opaqueCarrier.d.ts +7 -0
- package/dist/docx/opaqueCarrier.js +38 -0
- package/dist/docx/packageParts.js +4 -1
- package/dist/docx/paragraphParser.js +91 -24
- package/dist/docx/paragraphPropertySource.d.ts +4 -2
- package/dist/docx/paragraphPropertySource.js +7 -1
- package/dist/docx/paragraphTraversal.js +1 -0
- package/dist/docx/parseWarningMessage.js +4 -1
- package/dist/docx/parser.js +173 -20
- package/dist/docx/rezip.js +27 -5
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sdtPropertiesPatch.js +3 -0
- package/dist/docx/selectiveSave.js +16 -9
- package/dist/docx/selectiveXmlPatch.js +106 -37
- package/dist/docx/serializer/blockCustomXmlSerializer.d.ts +5 -0
- package/dist/docx/serializer/blockCustomXmlSerializer.js +4 -0
- package/dist/docx/serializer/documentSerializer.js +2 -0
- package/dist/docx/serializer/headerFooterSerializer.js +2 -0
- package/dist/docx/serializer/noteSerializer.js +3 -1
- package/dist/docx/serializer/paragraphSerializer.js +33 -9
- package/dist/docx/serializer/tableSerializer.js +25 -38
- package/dist/docx/server/createBilingualDocument.js +3 -3
- package/dist/docx/tableParser.d.ts +157 -1
- package/dist/docx/tableParser.js +89 -10
- package/dist/docx/textBoxParser.js +1 -1
- package/dist/docx/unzip.d.ts +2 -1
- package/dist/docx/unzip.js +1 -1
- package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
- package/dist/docx/wordprocessingPrefixes.js +286 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +136 -0
- package/dist/i18n/messages/catalogs.gen.js +153 -17
- package/dist/i18n/messages/messages.gen.d.ts +8 -0
- package/dist/internal/compare/inline-presentation.d.ts +2 -1
- package/dist/internal/compare/inline-presentation.js +11 -10
- package/dist/internal/headlessRevisionResolutionGuard.d.ts +2 -8
- package/dist/internal/headlessRevisionResolutionGuard.js +2 -11
- package/dist/internal/indexedPositionMap.d.ts +11 -0
- package/dist/internal/indexedPositionMap.js +42 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
- package/dist/internal/revisionResolutionEdits.d.ts +13 -0
- package/dist/internal/revisionResolutionEdits.js +19 -0
- package/dist/internal/revisionResolutionInline.d.ts +24 -0
- package/dist/internal/{headlessRevisionResolution.js → revisionResolutionInline.js} +61 -43
- package/dist/internal/revisionResolutionStep.d.ts +34 -0
- package/dist/internal/revisionResolutionStep.js +282 -0
- package/dist/internal/revisionResolutionTracking.d.ts +11 -0
- package/dist/internal/revisionResolutionTracking.js +124 -0
- package/dist/internal/wholeStoryRevisionResolution.d.ts +56 -0
- package/dist/internal/wholeStoryRevisionResolution.js +530 -0
- package/dist/layout-bridge/convert/markupViewFlow.d.ts +20 -0
- package/dist/layout-bridge/convert/markupViewFlow.js +123 -0
- package/dist/layout-bridge/convert/tableConversion.js +1 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +10 -1
- package/dist/layout-engine/layoutInstrumentation.d.ts +9 -1
- package/dist/layout-engine/layoutInstrumentation.js +4 -1
- package/dist/layout-engine/measure/cache.d.ts +18 -1
- package/dist/layout-engine/measure/cache.js +31 -1
- package/dist/layout-engine/measure/font-metrics.worker.js +3 -7
- package/dist/layout-engine/measure/measureContainer.js +7 -47
- package/dist/layout-engine/measure/measureHelpers.d.ts +6 -1
- package/dist/layout-engine/measure/measureHelpers.js +22 -2
- package/dist/layout-engine/measure/measureWorker.js +2 -3
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +6 -7
- package/dist/layout-engine/measure/measureWorkerProtocol.js +1 -2
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/managers/ContextMenuManager.js +5 -2
- package/dist/managers/types.d.ts +3 -0
- package/dist/markdown/fromMarkdown.js +2 -1
- package/dist/markdown/internals.d.ts +11 -1
- package/dist/markdown/internals.js +24 -3
- package/dist/markdown/renderBlock.js +39 -9
- package/dist/markdown/renderParagraph.d.ts +13 -1
- package/dist/markdown/renderParagraph.js +85 -38
- package/dist/markdown/renderRuns.js +3 -10
- package/dist/markdown/renderTable.js +24 -12
- package/dist/markdown/trailers.js +2 -2
- package/dist/markdown/types.d.ts +29 -7
- package/dist/paged-layout/incrementalMeasure.d.ts +1 -2
- package/dist/paged-layout/incrementalMeasure.js +1 -9
- package/dist/paged-layout/transactionDirtyRange.d.ts +1 -3
- package/dist/paged-layout/transactionDirtyRange.js +29 -41
- package/dist/panel-layout.d.ts +83 -0
- package/dist/panel-layout.js +96 -0
- package/dist/prosemirror/anchoredTextBoxes.d.ts +37 -0
- package/dist/prosemirror/anchoredTextBoxes.js +156 -0
- package/dist/prosemirror/attrs/index.d.ts +6 -2
- package/dist/prosemirror/attrs/index.js +172 -1
- package/dist/prosemirror/commands/comments.d.ts +7 -8
- package/dist/prosemirror/commands/comments.js +241 -207
- package/dist/prosemirror/commands/contentControls.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
- package/dist/prosemirror/commands/formatting.d.ts +1 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/paragraph.d.ts +7 -1
- package/dist/prosemirror/commands/paragraph.js +9 -1
- package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +13 -0
- package/dist/prosemirror/commands/paragraphBookmarkJoin.js +22 -0
- package/dist/prosemirror/commands/pastePlainText.d.ts +1 -1
- package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
- package/dist/prosemirror/commands/resolveAllTableChanges.js +615 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.d.ts +15 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.js +46 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.d.ts +16 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.js +59 -0
- package/dist/prosemirror/commands/sectionBreak.js +2 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
- package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +181 -31
- package/dist/prosemirror/conversion/toProseDoc.js +39 -4
- package/dist/prosemirror/emptyFieldResultRuns.d.ts +18 -0
- package/dist/prosemirror/emptyFieldResultRuns.js +33 -0
- package/dist/prosemirror/extensions/ExtensionManager.d.ts +1 -1
- package/dist/prosemirror/extensions/ExtensionManager.js +3 -1
- package/dist/prosemirror/extensions/StarterKit.js +3 -1
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -0
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +3 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +161 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +19 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +33 -20
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +31 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +270 -99
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +3 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +2 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +126 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +21 -5
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +7 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +8 -2
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +10 -0
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +35 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.js +52 -0
- package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +4 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +19 -18
- package/dist/prosemirror/extensions/types.d.ts +4 -1
- package/dist/prosemirror/findReplaceSelection.js +1 -1
- package/dist/prosemirror/index.d.ts +3 -2
- package/dist/prosemirror/index.js +3 -2
- package/dist/prosemirror/indexedNodeLookup.d.ts +17 -0
- package/dist/prosemirror/indexedNodeLookup.js +103 -0
- package/dist/prosemirror/listAutoformatMarkers.d.ts +19 -0
- package/dist/prosemirror/listAutoformatMarkers.js +98 -0
- package/dist/prosemirror/listInstanceReferences.d.ts +12 -0
- package/dist/prosemirror/listInstanceReferences.js +72 -0
- package/dist/prosemirror/listLabels.d.ts +24 -0
- package/dist/prosemirror/listLabels.js +111 -0
- package/dist/prosemirror/listNumbering.d.ts +110 -0
- package/dist/prosemirror/listNumbering.js +345 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
- package/dist/prosemirror/listRenderingAttrs.js +8 -1
- package/dist/prosemirror/markupViewProjection.d.ts +95 -0
- package/dist/prosemirror/markupViewProjection.js +83 -0
- package/dist/prosemirror/numberingAttr.d.ts +9 -1
- package/dist/prosemirror/numberingAttr.js +9 -1
- package/dist/prosemirror/outlineLevelAttr.js +1 -1
- package/dist/prosemirror/paragraphIndentation.d.ts +10 -1
- package/dist/prosemirror/paragraphIndentation.js +29 -7
- package/dist/prosemirror/paragraphMarkJoin.d.ts +19 -0
- package/dist/prosemirror/paragraphMarkJoin.js +24 -0
- package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +1 -1
- package/dist/prosemirror/plugins/documentNumbering.d.ts +27 -1
- package/dist/prosemirror/plugins/documentNumbering.js +97 -11
- package/dist/prosemirror/plugins/documentStyles.d.ts +10 -1
- package/dist/prosemirror/plugins/documentStyles.js +14 -1
- package/dist/prosemirror/plugins/suggestionMode.d.ts +16 -2
- package/dist/prosemirror/plugins/suggestionMode.js +116 -52
- package/dist/prosemirror/plugins/templateDirectives.d.ts +1 -1
- package/dist/prosemirror/plugins/transactionInvariants.d.ts +32 -0
- package/dist/prosemirror/plugins/transactionInvariants.js +86 -0
- package/dist/prosemirror/positionSweep.d.ts +24 -0
- package/dist/prosemirror/positionSweep.js +222 -0
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +1 -1
- package/dist/prosemirror/rejoinRunCarriers.d.ts +1 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +33 -1
- package/dist/prosemirror/replacedAnnotations.js +170 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/index.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +42 -11
- package/dist/prosemirror/sectionMarkEdits.d.ts +20 -0
- package/dist/prosemirror/sectionMarkEdits.js +59 -0
- package/dist/prosemirror/storyListNumbering.d.ts +17 -0
- package/dist/prosemirror/storyListNumbering.js +102 -0
- package/dist/prosemirror/tableGridMutation.d.ts +48 -2
- package/dist/prosemirror/tableGridMutation.js +106 -1
- package/dist/prosemirror/validation.js +5 -1
- package/dist/redline.js +93 -9
- package/dist/render-dom/BodySelectionOverlay.d.ts +4 -1
- package/dist/render-dom/BodySelectionOverlay.js +3 -2
- package/dist/utils/findReplace.js +2 -2
- package/dist/utils/mergeDocumentContent.js +1 -1
- package/dist/utils/noteReferenceLabels.d.ts +36 -0
- package/dist/utils/noteReferenceLabels.js +32 -0
- package/dist/utils/replaceText.js +1 -1
- package/dist/version-comparison.d.ts +1 -0
- package/dist/version-comparison.js +1 -0
- package/package.json +3 -2
- package/dist/internal/headlessRevisionResolution.d.ts +0 -23
package/dist/docx/parser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { toArrayBuffer } from "../utils/docxInput.js";
|
|
2
2
|
import { loadFontsWithMapping } from "../utils/fontLoader.js";
|
|
3
3
|
import { MAX_PACKAGE_TIFF_PIXELS, convertTiffToPngDataUrl, isTiffMimeType } from "../utils/tiffConverter.js";
|
|
4
|
+
import { ALT_CHUNK_PLAIN_TEXT_LIMIT_BYTES, getAltChunkRelationshipId, isAltChunkMarkup } from "./altChunk.js";
|
|
4
5
|
import { DUPLICATE_COMMENT_ID_WARNING, normalizeCommentIds } from "./commentIdNormalization.js";
|
|
5
6
|
import { parseComments } from "./commentParser.js";
|
|
6
7
|
import { DANGLING_COMMENT_REFERENCE_WARNING, UNBALANCED_COMMENT_RANGE_WARNING, normalizeCommentReferences } from "./commentReferenceNormalization.js";
|
|
@@ -13,13 +14,14 @@ import { DocxEncryptionError } from "./encryption/errors.js";
|
|
|
13
14
|
import { parseFontTable } from "./fontTableParser.js";
|
|
14
15
|
import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
15
16
|
import { parseFooter, parseHeader } from "./headerFooterParser.js";
|
|
16
|
-
import { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
|
|
17
|
-
import { assignHeaderFooterVerbatimXml, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
|
|
17
|
+
import { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, assignHeaderFooterRoles, normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
|
|
18
|
+
import { assignHeaderFooterVerbatimXml, canReplayHeaderFooterVerbatim, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
|
|
18
19
|
import { extractMetafileRaster, isMetafileMimeType } from "./metafileRaster.js";
|
|
19
20
|
import { renderEmfSvg } from "./metafileSvg.js";
|
|
20
21
|
import { DocxModelValidationError, formatDocumentModelIssues, validateFolioDocumentModel } from "./modelValidation.js";
|
|
21
22
|
import { parseNumbering } from "./numberingParser.js";
|
|
22
23
|
import { UNNUMBERED_PARAGRAPH_WARNING, UNNUMBERED_STYLE_WARNING, normalizeNumberingReferences, normalizeStyleNumberingReferences } from "./numberingReferenceNormalization.js";
|
|
24
|
+
import { countOpaqueRevisionWrappers } from "./opaqueCarrier.js";
|
|
23
25
|
import { assignDocumentParagraphPropertySourceContract } from "./paragraphPropertySource.js";
|
|
24
26
|
import { createParseWarningCollector } from "./parseContext.js";
|
|
25
27
|
import { formatParseWarnings } from "./parseWarningMessage.js";
|
|
@@ -30,9 +32,10 @@ import { parseSettings } from "./settingsParser.js";
|
|
|
30
32
|
import { parseStylesPackage } from "./styleParser.js";
|
|
31
33
|
import { applyThemeFontLang, parseTheme } from "./themeParser.js";
|
|
32
34
|
import { UNBALANCED_MOVE_RANGE_WARNING, normalizeTrackedMoveRanges } from "./trackedMoveRangeNormalization.js";
|
|
33
|
-
import { getMediaMimeType, mediaToDataUrl, unzipDocx } from "./unzip.js";
|
|
35
|
+
import { getEntryUncompressedSize, getMediaMimeType, mediaToDataUrl, unzipDocx } from "./unzip.js";
|
|
36
|
+
import { getAttribute, getChildElements, getLocalName, parseXmlDocument } from "./xmlParser.js";
|
|
34
37
|
import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
|
|
35
|
-
import { TaggedError } from "better-result";
|
|
38
|
+
import { TaggedError, panic } from "better-result";
|
|
36
39
|
import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
|
|
37
40
|
//#region src/docx/parser.ts
|
|
38
41
|
/**
|
|
@@ -63,6 +66,101 @@ const sha256Hex = async (buffer) => {
|
|
|
63
66
|
for (const byte of bytes) digest += byte.toString(16).padStart(2, "0");
|
|
64
67
|
return digest;
|
|
65
68
|
};
|
|
69
|
+
const countAltChunks = (blocks) => {
|
|
70
|
+
let count = 0;
|
|
71
|
+
for (const block of blocks) switch (block.type) {
|
|
72
|
+
case "preservedBlock":
|
|
73
|
+
count += isAltChunkMarkup(block.xml) ? 1 : 0;
|
|
74
|
+
break;
|
|
75
|
+
case "table":
|
|
76
|
+
for (const row of block.rows) for (const cell of row.cells) count += countAltChunks(cell.content);
|
|
77
|
+
break;
|
|
78
|
+
case "blockSdt":
|
|
79
|
+
case "blockCustomXml":
|
|
80
|
+
count += countAltChunks(block.content);
|
|
81
|
+
break;
|
|
82
|
+
case "paragraph":
|
|
83
|
+
case "bookmarkStart":
|
|
84
|
+
case "bookmarkEnd": break;
|
|
85
|
+
default: panic(`Unsupported block while counting altChunks: ${JSON.stringify(block)}`);
|
|
86
|
+
}
|
|
87
|
+
return count;
|
|
88
|
+
};
|
|
89
|
+
const ALT_CHUNK_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk";
|
|
90
|
+
const ALT_CHUNK_STRICT_RELATIONSHIP_TYPE = "http://purl.oclc.org/ooxml/officeDocument/relationships/aFChunk";
|
|
91
|
+
const CONTENT_TYPES_NAMESPACE_URI = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
92
|
+
const contentTypeOf = (contentTypesXml, partPath) => {
|
|
93
|
+
if (!contentTypesXml) return;
|
|
94
|
+
const root = parseXmlDocument(contentTypesXml);
|
|
95
|
+
if (!root || root.namespaceUri !== CONTENT_TYPES_NAMESPACE_URI) return;
|
|
96
|
+
const extension = partPath.slice(partPath.lastIndexOf(".") + 1).toLowerCase();
|
|
97
|
+
let defaultContentType;
|
|
98
|
+
for (const child of getChildElements(root)) {
|
|
99
|
+
if (child.namespaceUri !== CONTENT_TYPES_NAMESPACE_URI) continue;
|
|
100
|
+
const name = getLocalName(child.name);
|
|
101
|
+
const contentType = getAttribute(child, null, "ContentType");
|
|
102
|
+
if (name === "Override" && getAttribute(child, null, "PartName") === `/${partPath}`) return contentType ?? void 0;
|
|
103
|
+
if (name === "Default" && getAttribute(child, null, "Extension")?.toLowerCase() === extension) defaultContentType = contentType ?? void 0;
|
|
104
|
+
}
|
|
105
|
+
return defaultContentType;
|
|
106
|
+
};
|
|
107
|
+
const extractAltChunkPlainText = async (xml, context) => {
|
|
108
|
+
const relationshipId = getAltChunkRelationshipId(xml);
|
|
109
|
+
const relationship = relationshipId === void 0 ? void 0 : context.relationships.get(relationshipId);
|
|
110
|
+
if (!relationship || relationship.targetMode === "External" || relationship.type !== ALT_CHUNK_RELATIONSHIP_TYPE && relationship.type !== ALT_CHUNK_STRICT_RELATIONSHIP_TYPE) return;
|
|
111
|
+
const partPath = resolveRelativePath(context.relationshipsPath, relationship.target);
|
|
112
|
+
if (contentTypeOf(context.raw.contentTypesXml, partPath)?.split(";")[0]?.trim().toLowerCase() !== "text/plain") return;
|
|
113
|
+
const file = context.raw.originalZip.file(partPath);
|
|
114
|
+
if (!file) return;
|
|
115
|
+
const declaredSize = getEntryUncompressedSize(file);
|
|
116
|
+
if (declaredSize === null || declaredSize > context.budget.remainingBytes) return void 0;
|
|
117
|
+
const bytes = await file.async("uint8array");
|
|
118
|
+
if (bytes.byteLength > context.budget.remainingBytes) return;
|
|
119
|
+
context.budget.remainingBytes -= bytes.byteLength;
|
|
120
|
+
try {
|
|
121
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
122
|
+
} catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const attachAltChunkPlainText = async (blocks, context) => {
|
|
127
|
+
for (const block of blocks) {
|
|
128
|
+
if (block.type === "preservedBlock" && isAltChunkMarkup(block.xml)) {
|
|
129
|
+
const readerText = await extractAltChunkPlainText(block.xml, context);
|
|
130
|
+
if (readerText !== void 0) block.readerText = readerText;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (block.type === "table") {
|
|
134
|
+
for (const row of block.rows) for (const cell of row.cells) await attachAltChunkPlainText(cell.content, context);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (block.type === "blockSdt" || block.type === "blockCustomXml") await attachAltChunkPlainText(block.content, context);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const countOpaqueRevisionCarriers = (blocks) => {
|
|
141
|
+
let count = 0;
|
|
142
|
+
for (const block of blocks) switch (block.type) {
|
|
143
|
+
case "preservedBlock":
|
|
144
|
+
count += countOpaqueRevisionWrappers(block.xml);
|
|
145
|
+
break;
|
|
146
|
+
case "table":
|
|
147
|
+
count += block.preserved?.children?.reduce((total, child) => total + countOpaqueRevisionWrappers(child.xml), 0) ?? 0;
|
|
148
|
+
for (const row of block.rows) {
|
|
149
|
+
count += row.preserved?.children?.reduce((total, child) => total + countOpaqueRevisionWrappers(child.xml), 0) ?? 0;
|
|
150
|
+
for (const cell of row.cells) count += countOpaqueRevisionCarriers(cell.content);
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case "blockSdt":
|
|
154
|
+
case "blockCustomXml":
|
|
155
|
+
count += countOpaqueRevisionCarriers(block.content);
|
|
156
|
+
break;
|
|
157
|
+
case "paragraph":
|
|
158
|
+
case "bookmarkStart":
|
|
159
|
+
case "bookmarkEnd": break;
|
|
160
|
+
default: panic(`Unsupported block while counting opaque revisions: ${JSON.stringify(block)}`);
|
|
161
|
+
}
|
|
162
|
+
return count;
|
|
163
|
+
};
|
|
66
164
|
/**
|
|
67
165
|
* Parse a DOCX file into a complete Document model
|
|
68
166
|
*
|
|
@@ -210,6 +308,11 @@ async function parseDocxWithPreviewBudget(input, options, previewBudget) {
|
|
|
210
308
|
code: DANGLING_FOOTER_REFERENCE_WARNING,
|
|
211
309
|
count: headerFooterReferenceNormalization.removedDanglingFooterReferences
|
|
212
310
|
});
|
|
311
|
+
assignHeaderFooterRoles({
|
|
312
|
+
documentBody,
|
|
313
|
+
...headers !== void 0 ? { headers } : {},
|
|
314
|
+
...footers !== void 0 ? { footers } : {}
|
|
315
|
+
});
|
|
213
316
|
const numberingReferenceNormalization = normalizeNumberingReferences({
|
|
214
317
|
documentBody,
|
|
215
318
|
numbering,
|
|
@@ -272,27 +375,77 @@ async function parseDocxWithPreviewBudget(input, options, previewBudget) {
|
|
|
272
375
|
} else onProgress("Skipping font loading", 95);
|
|
273
376
|
onProgress("Assembling document...", 95);
|
|
274
377
|
const properties = timeStage("coreProperties", () => parseCoreProperties(raw.corePropsXml));
|
|
378
|
+
const pkg = {
|
|
379
|
+
conformanceClass: detectDocxConformanceClass(raw.documentXml),
|
|
380
|
+
document: documentBody,
|
|
381
|
+
settings,
|
|
382
|
+
...styleDefinitions !== void 0 ? { styles: styleDefinitions } : {},
|
|
383
|
+
theme,
|
|
384
|
+
numbering: numbering.definitions,
|
|
385
|
+
...fontTable ? { fontTable } : {},
|
|
386
|
+
...headers !== void 0 ? { headers } : {},
|
|
387
|
+
...footers !== void 0 ? { footers } : {},
|
|
388
|
+
...footnotes !== void 0 ? { footnotes } : {},
|
|
389
|
+
...endnotes !== void 0 ? { endnotes } : {},
|
|
390
|
+
relationships: rels,
|
|
391
|
+
media,
|
|
392
|
+
...properties !== void 0 ? { properties } : {}
|
|
393
|
+
};
|
|
275
394
|
const document = {
|
|
276
|
-
package:
|
|
277
|
-
conformanceClass: detectDocxConformanceClass(raw.documentXml),
|
|
278
|
-
document: documentBody,
|
|
279
|
-
settings,
|
|
280
|
-
...styleDefinitions !== void 0 ? { styles: styleDefinitions } : {},
|
|
281
|
-
theme,
|
|
282
|
-
numbering: numbering.definitions,
|
|
283
|
-
...fontTable ? { fontTable } : {},
|
|
284
|
-
...headers !== void 0 ? { headers } : {},
|
|
285
|
-
...footers !== void 0 ? { footers } : {},
|
|
286
|
-
...footnotes !== void 0 ? { footnotes } : {},
|
|
287
|
-
...endnotes !== void 0 ? { endnotes } : {},
|
|
288
|
-
relationships: rels,
|
|
289
|
-
media,
|
|
290
|
-
...properties !== void 0 ? { properties } : {}
|
|
291
|
-
},
|
|
395
|
+
package: pkg,
|
|
292
396
|
originalBuffer: raw.originalBuffer,
|
|
293
397
|
...templateVariables !== void 0 ? { templateVariables } : {},
|
|
294
398
|
...requiredFonts.length > 0 ? { requiredFonts } : {}
|
|
295
399
|
};
|
|
400
|
+
const altChunkBudget = { remainingBytes: ALT_CHUNK_PLAIN_TEXT_LIMIT_BYTES };
|
|
401
|
+
await attachAltChunkPlainText(pkg.document.content, {
|
|
402
|
+
relationships: rels,
|
|
403
|
+
relationshipsPath: DOCUMENT_RELATIONSHIPS_PATH,
|
|
404
|
+
raw,
|
|
405
|
+
budget: altChunkBudget
|
|
406
|
+
});
|
|
407
|
+
const altChunkContextForPart = (partPath) => {
|
|
408
|
+
const relationshipsPath = getRelationshipsPathForPart(partPath);
|
|
409
|
+
const relationshipsXml = getMapCaseInsensitive(raw.allXml, relationshipsPath);
|
|
410
|
+
return {
|
|
411
|
+
relationships: relationshipsXml ? parseRelationships(relationshipsXml) : /* @__PURE__ */ new Map(),
|
|
412
|
+
relationshipsPath,
|
|
413
|
+
raw,
|
|
414
|
+
budget: altChunkBudget
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
for (const [rId, header] of pkg.headers ?? []) {
|
|
418
|
+
const relationship = rels.get(rId);
|
|
419
|
+
if (relationship?.target) {
|
|
420
|
+
const replayable = canReplayHeaderFooterVerbatim(header);
|
|
421
|
+
await attachAltChunkPlainText(header.content, altChunkContextForPart(getRelationshipPartPath(relationship.target)));
|
|
422
|
+
if (replayable) refreshHeaderFooterVerbatimFingerprint(header);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
for (const [rId, footer] of pkg.footers ?? []) {
|
|
426
|
+
const relationship = rels.get(rId);
|
|
427
|
+
if (relationship?.target) {
|
|
428
|
+
const replayable = canReplayHeaderFooterVerbatim(footer);
|
|
429
|
+
await attachAltChunkPlainText(footer.content, altChunkContextForPart(getRelationshipPartPath(relationship.target)));
|
|
430
|
+
if (replayable) refreshHeaderFooterVerbatimFingerprint(footer);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const footnoteContext = altChunkContextForPart("word/footnotes.xml");
|
|
434
|
+
for (const footnote of pkg.footnotes ?? []) await attachAltChunkPlainText(footnote.content, footnoteContext);
|
|
435
|
+
const endnoteContext = altChunkContextForPart("word/endnotes.xml");
|
|
436
|
+
for (const endnote of pkg.endnotes ?? []) await attachAltChunkPlainText(endnote.content, endnoteContext);
|
|
437
|
+
const altChunkCount = countAltChunks(pkg.document.content) + [...pkg.headers?.values() ?? []].reduce((total, part) => total + countAltChunks(part.content), 0) + [...pkg.footers?.values() ?? []].reduce((total, part) => total + countAltChunks(part.content), 0) + (pkg.footnotes ?? []).reduce((total, part) => total + countAltChunks(part.content), 0) + (pkg.endnotes ?? []).reduce((total, part) => total + countAltChunks(part.content), 0);
|
|
438
|
+
if (altChunkCount > 0) parseContext.warn({
|
|
439
|
+
code: PARSE_WARNING_CODES.altChunkUnsupported,
|
|
440
|
+
element: "w:altChunk",
|
|
441
|
+
count: altChunkCount
|
|
442
|
+
});
|
|
443
|
+
const opaqueRevisionCount = countOpaqueRevisionCarriers(pkg.document.content) + [...pkg.headers?.values() ?? []].reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0) + [...pkg.footers?.values() ?? []].reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0) + (pkg.footnotes ?? []).reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0) + (pkg.endnotes ?? []).reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0);
|
|
444
|
+
if (opaqueRevisionCount > 0) parseContext.warn({
|
|
445
|
+
code: PARSE_WARNING_CODES.revisionCarrierOpaque,
|
|
446
|
+
element: "w:ins/w:del/w:moveFrom/w:moveTo",
|
|
447
|
+
count: opaqueRevisionCount
|
|
448
|
+
});
|
|
296
449
|
assignDocumentParagraphPropertySourceContract(document, await paragraphPropertySourceDigest);
|
|
297
450
|
previews.enforce(previewBudget);
|
|
298
451
|
const validation = validateFolioDocumentModel(document);
|
package/dist/docx/rezip.js
CHANGED
|
@@ -26,10 +26,11 @@ import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
|
26
26
|
import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
27
27
|
import { serializeEndnotes, serializeFootnotes, serializeNewEndnotesPart, serializeNewFootnotesPart } from "./serializer/noteSerializer.js";
|
|
28
28
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
29
|
-
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
29
|
+
import { readRootNamespaceBindings, serializePartElement } from "./serializer/partNamespaces.js";
|
|
30
30
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
31
31
|
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
32
32
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
33
|
+
import { hasCanonicalWordprocessingPrefixes } from "./wordprocessingPrefixes.js";
|
|
33
34
|
import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
|
|
34
35
|
import { TaggedError, panic } from "better-result";
|
|
35
36
|
import { escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
|
|
@@ -363,7 +364,7 @@ function collectNewImages(blocks) {
|
|
|
363
364
|
for (const sub of item.content) if (sub.type === "run") collectFromRun(sub);
|
|
364
365
|
}
|
|
365
366
|
} else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) images.push(...collectNewImages(cell.content));
|
|
366
|
-
else if (block.type === "blockSdt") images.push(...collectNewImages(block.content));
|
|
367
|
+
else if (block.type === "blockSdt" || block.type === "blockCustomXml") images.push(...collectNewImages(block.content));
|
|
367
368
|
return images;
|
|
368
369
|
}
|
|
369
370
|
/** Map MIME type to file extension (inverse of getContentTypeForExtension) */
|
|
@@ -484,7 +485,7 @@ function collectHyperlinksWithoutRId(blocks) {
|
|
|
484
485
|
};
|
|
485
486
|
for (const block of blocks) if (block.type === "paragraph") collectInlineHyperlinks(block.content);
|
|
486
487
|
else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
|
|
487
|
-
else if (block.type === "blockSdt") hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
|
|
488
|
+
else if (block.type === "blockSdt" || block.type === "blockCustomXml") hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
|
|
488
489
|
return hyperlinks;
|
|
489
490
|
}
|
|
490
491
|
/** The selective save boundary must use the same resource census as full repack. */
|
|
@@ -1444,6 +1445,25 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
|
|
|
1444
1445
|
const STYLES_PART_PATH = "word/styles.xml";
|
|
1445
1446
|
const STYLES_CLOSE_ROOT = "</w:styles>";
|
|
1446
1447
|
/**
|
|
1448
|
+
* One serialized element with the namespace declarations its own prefixes
|
|
1449
|
+
* need, for splicing into a part whose root binds them differently (or not
|
|
1450
|
+
* at all). A prefix no table knows throws rather than being written unbound.
|
|
1451
|
+
*/
|
|
1452
|
+
const selfBoundFragment = (fragment, partPath) => {
|
|
1453
|
+
const openEnd = fragment.indexOf(">");
|
|
1454
|
+
const selfClosing = fragment[openEnd - 1] === "/";
|
|
1455
|
+
const rootName = /^<(?<name>[^\s/>]+)/u.exec(fragment)?.groups?.["name"];
|
|
1456
|
+
if (openEnd === -1 || rootName === void 0) throw new DocxPackageFidelityError(`Cannot splice a malformed element into ${partPath}`);
|
|
1457
|
+
return serializePartElement({
|
|
1458
|
+
partPath,
|
|
1459
|
+
rootName,
|
|
1460
|
+
rootAttributes: fragment.slice(rootName.length + 1, selfClosing ? openEnd - 1 : openEnd).trim(),
|
|
1461
|
+
baselinePrefixes: [],
|
|
1462
|
+
sourceBindings: void 0,
|
|
1463
|
+
body: selfClosing ? "" : fragment.slice(openEnd + 1, fragment.lastIndexOf("</"))
|
|
1464
|
+
});
|
|
1465
|
+
};
|
|
1466
|
+
/**
|
|
1447
1467
|
* The style table a package folio creates starts from: `docDefaults` and
|
|
1448
1468
|
* `Normal`, and nothing else. A document that carries its own style table
|
|
1449
1469
|
* replaces this part wholesale.
|
|
@@ -1532,7 +1552,8 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
|
|
|
1532
1552
|
if (!styles || styles.styles.length === 0) return;
|
|
1533
1553
|
const file = findNotePartEntry(originalZip, STYLES_PART_PATH);
|
|
1534
1554
|
const originalXml = file ? await file.async("text") : null;
|
|
1535
|
-
const
|
|
1555
|
+
const rootName = originalXml === null ? void 0 : /<(?![?!])(?<name>[^\s/>]+)/u.exec(originalXml)?.groups?.["name"];
|
|
1556
|
+
const rootClose = originalXml !== null && rootName !== void 0 && getLocalName(rootName) === "styles" ? originalXml.lastIndexOf(`</${rootName}>`) : -1;
|
|
1536
1557
|
if (file === null || originalXml === null || rootClose < 0) {
|
|
1537
1558
|
await materializeNewNotePart({
|
|
1538
1559
|
contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
|
|
@@ -1552,7 +1573,8 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
|
|
|
1552
1573
|
added.push(style);
|
|
1553
1574
|
}
|
|
1554
1575
|
if (added.length === 0) return;
|
|
1555
|
-
const
|
|
1576
|
+
const canonical = hasCanonicalWordprocessingPrefixes(originalXml);
|
|
1577
|
+
const patched = originalXml.slice(0, rootClose) + added.map((style) => canonical ? serializeStyle(style) : selfBoundFragment(serializeStyle(style), file.name)).join("") + originalXml.slice(rootClose);
|
|
1556
1578
|
newZip.file(file.name, patched, {
|
|
1557
1579
|
compression: "DEFLATE",
|
|
1558
1580
|
compressionOptions: { level: compressionLevel }
|
|
@@ -6,7 +6,7 @@ import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByLocalName, getAt
|
|
|
6
6
|
//#region src/docx/sdtProperties.ts
|
|
7
7
|
function parseListItems(el) {
|
|
8
8
|
const items = [];
|
|
9
|
-
for (const child of el.elements ?? []) if (child.type === "element" && (child.name
|
|
9
|
+
for (const child of el.elements ?? []) if (child.type === "element" && getLocalName(child.name ?? "") === "listItem") {
|
|
10
10
|
const displayText = getAttributeAnyPrefix(child, "displayText");
|
|
11
11
|
const value = getAttributeAnyPrefix(child, "value");
|
|
12
12
|
if (displayText === null && value === null) continue;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OOXML_NAMESPACE_SCOPE, findChild, getAttribute, parseXml } from "./xmlParser.js";
|
|
1
2
|
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
2
3
|
//#region src/docx/sdtPropertiesPatch.ts
|
|
3
4
|
/**
|
|
@@ -66,6 +67,8 @@ const withDateState = (xml, props) => {
|
|
|
66
67
|
const fullDate = props.dateValueISO;
|
|
67
68
|
const format = props.dateFormat;
|
|
68
69
|
if (fullDate === void 0 && format === void 0) return xml;
|
|
70
|
+
const capturedDate = parseXml(xml, OOXML_NAMESPACE_SCOPE).elements?.at(0);
|
|
71
|
+
if ((fullDate === void 0 || getAttribute(capturedDate, "w", "fullDate") === fullDate) && (format === void 0 || getAttribute(findChild(capturedDate, "w", "dateFormat"), "w", "val") === format)) return xml;
|
|
69
72
|
const attributes = (prefix, matched) => fullDate === void 0 ? matched : `${withoutAttr(matched, "fullDate")} ${prefix}:fullDate="${escapeXmlAttribute(fullDate)}"`;
|
|
70
73
|
const formatChild = (prefix) => format === void 0 ? "" : `<${prefix}:dateFormat ${prefix}:val="${escapeXmlAttribute(format)}"/>`;
|
|
71
74
|
const opened = /<(?<prefix>\w+):date\b(?<attrs>[^>]*)>(?<inner>[\s\S]*?)<\/\w+:date>/iu;
|
|
@@ -126,12 +126,15 @@ async function ensureCommentsExtendedPackaging(zip, updates) {
|
|
|
126
126
|
* (`w:nsid`/`w:tmpl`, custom formats, level sub-elements), so only the changed
|
|
127
127
|
* definitions are spliced by id; every other definition stays byte-exact.
|
|
128
128
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
129
|
+
* Unchanged numbering leaves the original part untouched. Numbering the
|
|
130
|
+
* splice cannot write (a package without a numbering part, or an edit it
|
|
131
|
+
* cannot split) answers `false`: the paragraphs may reference those
|
|
132
|
+
* definitions, so the caller falls back to a full repack rather than save a
|
|
133
|
+
* package whose `w:numPr` names an instance it does not define.
|
|
131
134
|
*/
|
|
132
135
|
async function patchNumberingPart(zip, doc, updates) {
|
|
133
136
|
const numbering = doc.package.numbering;
|
|
134
|
-
if (!numbering || numbering.abstractNums.length === 0 && numbering.nums.length === 0) return;
|
|
137
|
+
if (!numbering || numbering.abstractNums.length === 0 && numbering.nums.length === 0) return true;
|
|
135
138
|
const conventionalLowerPath = "word/numbering.xml";
|
|
136
139
|
let file = zip.file(conventionalLowerPath);
|
|
137
140
|
if (!file) {
|
|
@@ -140,15 +143,19 @@ async function patchNumberingPart(zip, doc, updates) {
|
|
|
140
143
|
break;
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
|
-
if (!file) return;
|
|
146
|
+
if (!file) return false;
|
|
144
147
|
const originalXml = await file.async("text");
|
|
148
|
+
const baselineXml = serializeNumberingXml(parseNumbering(originalXml).definitions);
|
|
149
|
+
const currentXml = serializeNumberingXml(numbering);
|
|
150
|
+
if (currentXml === baselineXml) return true;
|
|
145
151
|
const patched = patchNumberingDefinitions({
|
|
146
152
|
originalXml,
|
|
147
|
-
baselineXml
|
|
148
|
-
currentXml
|
|
153
|
+
baselineXml,
|
|
154
|
+
currentXml
|
|
149
155
|
});
|
|
150
|
-
if (patched === null
|
|
151
|
-
updates.set(file.name, patched);
|
|
156
|
+
if (patched === null) return false;
|
|
157
|
+
if (patched !== originalXml) updates.set(file.name, patched);
|
|
158
|
+
return true;
|
|
152
159
|
}
|
|
153
160
|
/**
|
|
154
161
|
* Attempt a selective save — patch only changed paragraphs in document.xml.
|
|
@@ -250,7 +257,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
250
257
|
}
|
|
251
258
|
}
|
|
252
259
|
if (!await patchCommentsExtended(zip, commentPlan, updates)) return null;
|
|
253
|
-
await patchNumberingPart(zip, doc, updates);
|
|
260
|
+
if (!await patchNumberingPart(zip, doc, updates)) return null;
|
|
254
261
|
for (const [path, xml] of await collectHeaderFooterUpdates(doc, zip)) updates.set(path, xml);
|
|
255
262
|
await queueSettingsUpdates(zip, updates);
|
|
256
263
|
const corePropsFile = zip.file("docProps/core.xml");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { patchBreaksCommentRangeBalance } from "./commentRangeIntegrity.js";
|
|
2
2
|
import { resolveParagraphIdentities } from "./paraIdAttribute.js";
|
|
3
3
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
4
|
+
import { PARAGRAPH_SCAN_NAMES, hasCanonicalWordprocessingPrefixes, matchCloseTag, matchOpenTag, resolveSelectiveScanPrefixes, splicesAsCanonical } from "./wordprocessingPrefixes.js";
|
|
4
5
|
import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findAttributeByNamespaceUri, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
|
|
5
6
|
//#region src/docx/selectiveXmlPatch.ts
|
|
6
7
|
/**
|
|
@@ -108,29 +109,6 @@ function buildParagraphOffsetIndex(xml) {
|
|
|
108
109
|
for (const [id, range] of ranges) if (seenCount.get(id) === 1) index.set(id, range);
|
|
109
110
|
return index;
|
|
110
111
|
}
|
|
111
|
-
/** The containers {@link scanParagraphs} tracks, by the literal tag the part writes. */
|
|
112
|
-
const TRACKED_CONTAINERS = [
|
|
113
|
-
{
|
|
114
|
-
name: "mc:Fallback",
|
|
115
|
-
key: "fallback"
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "mc:AlternateContent",
|
|
119
|
-
key: "alternateContent"
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: "w:txbxContent",
|
|
123
|
-
key: "textBox"
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
name: "w:tc",
|
|
127
|
-
key: "tableCell"
|
|
128
|
-
}
|
|
129
|
-
].map(({ name, key }) => ({
|
|
130
|
-
key,
|
|
131
|
-
open: `<${name}`,
|
|
132
|
-
close: `</${name}>`
|
|
133
|
-
}));
|
|
134
112
|
/**
|
|
135
113
|
* Every `<w:p>` element of `xml`, in the document order of its opening tags,
|
|
136
114
|
* with the `w14:paraId` that tag carries and the containers around it.
|
|
@@ -145,6 +123,8 @@ const TRACKED_CONTAINERS = [
|
|
|
145
123
|
* no splice can be built from it.
|
|
146
124
|
*/
|
|
147
125
|
function scanParagraphs(xml) {
|
|
126
|
+
const resolution = resolveSelectiveScanPrefixes(xml);
|
|
127
|
+
const mcPrefixes = resolution.type === "resolved" ? resolution.prefixes.mc : ["mc"];
|
|
148
128
|
const paragraphs = [];
|
|
149
129
|
const open = [];
|
|
150
130
|
const depth = {
|
|
@@ -153,6 +133,28 @@ function scanParagraphs(xml) {
|
|
|
153
133
|
textBox: 0,
|
|
154
134
|
tableCell: 0
|
|
155
135
|
};
|
|
136
|
+
const containers = [
|
|
137
|
+
{
|
|
138
|
+
key: "fallback",
|
|
139
|
+
prefixes: mcPrefixes,
|
|
140
|
+
name: "Fallback"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
key: "alternateContent",
|
|
144
|
+
prefixes: mcPrefixes,
|
|
145
|
+
name: "AlternateContent"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
key: "textBox",
|
|
149
|
+
prefixes: ["w"],
|
|
150
|
+
name: "txbxContent"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: "tableCell",
|
|
154
|
+
prefixes: ["w"],
|
|
155
|
+
name: "tc"
|
|
156
|
+
}
|
|
157
|
+
];
|
|
156
158
|
let pos = 0;
|
|
157
159
|
scan: while (pos < xml.length) {
|
|
158
160
|
const tagStart = xml.indexOf("<", pos);
|
|
@@ -165,13 +167,14 @@ function scanParagraphs(xml) {
|
|
|
165
167
|
pos = end;
|
|
166
168
|
continue;
|
|
167
169
|
}
|
|
168
|
-
for (const { key,
|
|
169
|
-
|
|
170
|
+
for (const { key, prefixes, name } of containers) {
|
|
171
|
+
const closeLength = matchCloseTag(xml, tagStart, prefixes, name);
|
|
172
|
+
if (closeLength !== -1) {
|
|
170
173
|
depth[key] = Math.max(0, depth[key] - 1);
|
|
171
|
-
pos = tagStart +
|
|
174
|
+
pos = tagStart + closeLength;
|
|
172
175
|
continue scan;
|
|
173
176
|
}
|
|
174
|
-
if (xml
|
|
177
|
+
if (matchOpenTag(xml, tagStart, prefixes, name) !== -1) {
|
|
175
178
|
const tagEnd = xml.indexOf(">", tagStart);
|
|
176
179
|
if (tagEnd === -1) break scan;
|
|
177
180
|
if (xml[tagEnd - 1] !== "/") depth[key] += 1;
|
|
@@ -333,10 +336,9 @@ const nearestSharedParaId = (own, other, paragraph, step) => {
|
|
|
333
336
|
* - an id-less paragraph whose story's ordinals do not line up.
|
|
334
337
|
*/
|
|
335
338
|
const routeChangedParagraphs = (originalXml, serializedXml, changedIds) => {
|
|
336
|
-
|
|
337
|
-
if (rootName !== void 0 && !rootName.startsWith("w:")) return {
|
|
339
|
+
if (!splicesAsCanonical(originalXml, PARAGRAPH_SCAN_NAMES)) return {
|
|
338
340
|
type: "refused",
|
|
339
|
-
reason: `non-canonical-wordprocessingml-prefix: ${
|
|
341
|
+
reason: `non-canonical-wordprocessingml-prefix: ${rootElementName(originalXml) ?? "unknown"}`
|
|
340
342
|
};
|
|
341
343
|
const original = addressableParagraphs(originalXml);
|
|
342
344
|
const serialized = addressableParagraphs(serializedXml);
|
|
@@ -423,9 +425,14 @@ const routeChangedParagraphs = (originalXml, serializedXml, changedIds) => {
|
|
|
423
425
|
newXml: authored ? newXml : withoutMintedIds(newXml, sourceXml)
|
|
424
426
|
});
|
|
425
427
|
}
|
|
428
|
+
const candidate = spliceXml(originalXml, splices);
|
|
429
|
+
if (candidate === null || !splicesAsCanonical(candidate, PARAGRAPH_SCAN_NAMES)) return {
|
|
430
|
+
type: "refused",
|
|
431
|
+
reason: "replacement-namespace-conflict"
|
|
432
|
+
};
|
|
426
433
|
return {
|
|
427
434
|
type: "routed",
|
|
428
|
-
|
|
435
|
+
xml: candidate
|
|
429
436
|
};
|
|
430
437
|
};
|
|
431
438
|
/**
|
|
@@ -459,7 +466,7 @@ function validatePatchSafety(originalXml, serializedXml, changedIds) {
|
|
|
459
466
|
function buildPatchedDocumentXml(originalXml, serializedXml, changedIds) {
|
|
460
467
|
if (changedIds.size === 0) return originalXml;
|
|
461
468
|
const routing = routeChangedParagraphs(originalXml, serializedXml, changedIds);
|
|
462
|
-
return routing.type === "refused" ? null :
|
|
469
|
+
return routing.type === "refused" ? null : routing.xml;
|
|
463
470
|
}
|
|
464
471
|
/**
|
|
465
472
|
* Build a patched note part (word/footnotes.xml / word/endnotes.xml) by
|
|
@@ -647,6 +654,40 @@ const paragraphRanges = (xml, wordPrefix) => {
|
|
|
647
654
|
}
|
|
648
655
|
return ranges;
|
|
649
656
|
};
|
|
657
|
+
const OPENING_PARA_ID = /^<[^>]*?\sw14:paraId="(?<id>[^"]*)"/u;
|
|
658
|
+
const openingParaId = (paragraph) => OPENING_PARA_ID.exec(paragraph)?.groups?.["id"];
|
|
659
|
+
/** A note's XML outside its paragraphs, one entry per gap between them. */
|
|
660
|
+
const noteGaps = (note, ranges) => {
|
|
661
|
+
const gaps = [];
|
|
662
|
+
let cursor = 0;
|
|
663
|
+
for (const range of ranges) {
|
|
664
|
+
gaps.push(note.slice(cursor, range.start));
|
|
665
|
+
cursor = range.end;
|
|
666
|
+
}
|
|
667
|
+
gaps.push(note.slice(cursor));
|
|
668
|
+
return gaps;
|
|
669
|
+
};
|
|
670
|
+
/**
|
|
671
|
+
* Whether a note's block structure differs between two serializations of it:
|
|
672
|
+
* a paragraph added, removed or replaced by another, or anything written
|
|
673
|
+
* between paragraphs changed — a table, a row, a cell's properties, a
|
|
674
|
+
* row-level revision. Paragraph-by-paragraph splicing pairs paragraphs by
|
|
675
|
+
* ordinal, so it is only faithful when neither happened; a note whose shape
|
|
676
|
+
* changed is rewritten whole. A paragraph the baseline has no id for pairs
|
|
677
|
+
* with any id, because that id was minted after reading, not changed.
|
|
678
|
+
*/
|
|
679
|
+
const noteShapeChanged = (baseline, current) => {
|
|
680
|
+
const baselineRanges = paragraphRanges(baseline.note, baseline.prefix);
|
|
681
|
+
const currentRanges = paragraphRanges(current.note, current.prefix);
|
|
682
|
+
if (baselineRanges.length !== currentRanges.length) return true;
|
|
683
|
+
const currentGaps = noteGaps(current.note, currentRanges);
|
|
684
|
+
if (noteGaps(baseline.note, baselineRanges).some((gap, index) => gap !== currentGaps[index])) return true;
|
|
685
|
+
return baselineRanges.some((range, index) => {
|
|
686
|
+
const baselineId = openingParaId(baseline.note.slice(range.start, range.end));
|
|
687
|
+
const currentRange = currentRanges[index];
|
|
688
|
+
return baselineId !== void 0 && baselineId !== openingParaId(current.note.slice(currentRange.start, currentRange.end));
|
|
689
|
+
});
|
|
690
|
+
};
|
|
650
691
|
const collectChangedNoteParaIds = (baselineXml, currentXml) => {
|
|
651
692
|
const changed = /* @__PURE__ */ new Set();
|
|
652
693
|
const baselineIds = collectParaIds(baselineXml);
|
|
@@ -681,6 +722,7 @@ const collectChangedNoteParaIds = (baselineXml, currentXml) => {
|
|
|
681
722
|
*/
|
|
682
723
|
function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, replacementXml, elementName, changedParaIds }) {
|
|
683
724
|
const currentElements = collectNoteElementSyntax(serializedXml, elementName);
|
|
725
|
+
const baselineElements = collectNoteElementSyntax(baselineXml, elementName);
|
|
684
726
|
const originalElements = collectNoteElementSyntax(originalXml, elementName);
|
|
685
727
|
const replacementElements = collectNoteElementSyntax(replacementXml, elementName);
|
|
686
728
|
const replacementXmlnsDeclarations = collectXmlnsFromOpeningTag(replacementXml);
|
|
@@ -716,7 +758,17 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
|
|
|
716
758
|
const originalParagraphs = paragraphRanges(originalNote, originalSyntax.elementPrefix);
|
|
717
759
|
const replacementParagraphs = paragraphRanges(replacementNote, replacementSyntax.elementPrefix);
|
|
718
760
|
const noteChangedParaIds = [...collectParaIds(currentNote).keys()].filter((paraId) => unroutedChangedParaIds.has(paraId));
|
|
719
|
-
|
|
761
|
+
const baselineSyntaxEntries = baselineElements.get(id);
|
|
762
|
+
const baselineSyntax = baselineSyntaxEntries?.length === 1 ? baselineSyntaxEntries[0] : void 0;
|
|
763
|
+
const baselineNote = baselineSyntax ? extractNoteElement(baselineXml, baselineSyntax, id) : null;
|
|
764
|
+
const reshaped = baselineSyntax !== void 0 && baselineNote !== null && baselineNote !== currentNote && noteShapeChanged({
|
|
765
|
+
note: baselineNote,
|
|
766
|
+
prefix: baselineSyntax.elementPrefix
|
|
767
|
+
}, {
|
|
768
|
+
note: currentNote,
|
|
769
|
+
prefix: currentSyntax.elementPrefix
|
|
770
|
+
});
|
|
771
|
+
if (noteChangedParaIds.length === 0 && !reshaped) continue;
|
|
720
772
|
const wholeNoteSplice = {
|
|
721
773
|
start: originalOffsets.start,
|
|
722
774
|
end: originalOffsets.end,
|
|
@@ -727,7 +779,7 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
|
|
|
727
779
|
})
|
|
728
780
|
};
|
|
729
781
|
noteSplices.push(wholeNoteSplice);
|
|
730
|
-
if (currentParagraphs.length !== originalParagraphs.length || currentParagraphs.length !== replacementParagraphs.length) {
|
|
782
|
+
if (reshaped || currentParagraphs.length !== originalParagraphs.length || currentParagraphs.length !== replacementParagraphs.length) {
|
|
731
783
|
for (const paraId of noteChangedParaIds) unroutedChangedParaIds.delete(paraId);
|
|
732
784
|
paragraphSplices.push(wholeNoteSplice);
|
|
733
785
|
continue;
|
|
@@ -892,8 +944,24 @@ function originalCustomNumFmtFormat(numFmtElement) {
|
|
|
892
944
|
*/
|
|
893
945
|
function collectXmlnsFromOpeningTag(elementXml) {
|
|
894
946
|
const out = {};
|
|
895
|
-
|
|
896
|
-
|
|
947
|
+
let tagStart = elementXml.indexOf("<");
|
|
948
|
+
while (tagStart !== -1 && (elementXml.startsWith("<?", tagStart) || elementXml.startsWith("<!--", tagStart))) {
|
|
949
|
+
const endMarker = elementXml.startsWith("<!--", tagStart) ? "-->" : "?>";
|
|
950
|
+
const end = elementXml.indexOf(endMarker, tagStart);
|
|
951
|
+
if (end === -1) return out;
|
|
952
|
+
tagStart = elementXml.indexOf("<", end + endMarker.length);
|
|
953
|
+
}
|
|
954
|
+
if (tagStart === -1) return out;
|
|
955
|
+
let tagEnd = tagStart + 1;
|
|
956
|
+
let quote = null;
|
|
957
|
+
while (tagEnd < elementXml.length) {
|
|
958
|
+
const character = elementXml[tagEnd];
|
|
959
|
+
if (character === quote) quote = null;
|
|
960
|
+
else if (quote === null && (character === "\"" || character === "'")) quote = character;
|
|
961
|
+
else if (quote === null && character === ">") break;
|
|
962
|
+
tagEnd += 1;
|
|
963
|
+
}
|
|
964
|
+
const openTag = elementXml.slice(tagStart, tagEnd);
|
|
897
965
|
const pattern = /\s(?<name>xmlns(?::[\w.-]+)?)="(?<uri>[^"]*)"/gu;
|
|
898
966
|
let match;
|
|
899
967
|
while ((match = pattern.exec(openTag)) !== null) out[match.groups["name"]] = match.groups["uri"];
|
|
@@ -1056,9 +1124,10 @@ const patchNumberingByNamespace = ({ original, currentXml, changed, added }) =>
|
|
|
1056
1124
|
const patchNumberingDefinitions = ({ originalXml, baselineXml, currentXml }) => {
|
|
1057
1125
|
const changed = collectChangedNumberingDefs(baselineXml, currentXml);
|
|
1058
1126
|
const added = collectAddedNumberingDefs(baselineXml, currentXml);
|
|
1127
|
+
if (changed.abstractNums.size === 0 && changed.nums.size === 0 && added.abstractNums.size === 0 && added.nums.size === 0) return originalXml;
|
|
1059
1128
|
const original = parseXmlDocument(originalXml);
|
|
1060
1129
|
if (!original || getLocalName(original.name) !== "numbering" || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(original) ?? "")) return null;
|
|
1061
|
-
if (getNamespacePrefix(original.name ?? "") !== "w") return patchNumberingByNamespace({
|
|
1130
|
+
if (getNamespacePrefix(original.name ?? "") !== "w" || !hasCanonicalWordprocessingPrefixes(originalXml)) return patchNumberingByNamespace({
|
|
1062
1131
|
original,
|
|
1063
1132
|
currentXml,
|
|
1064
1133
|
changed,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
//#region src/docx/serializer/blockCustomXmlSerializer.d.ts
|
|
3
|
+
declare const serializeBlockCustomXml: (block: document_d_exports.BlockCustomXml, serializeChild: (child: document_d_exports.BlockContent) => string) => string;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { serializeBlockCustomXml };
|