@stll/folio-core 0.52.0 → 0.54.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 +1779 -141
- package/dist/ai-edits/batch-claims.d.ts +145 -0
- package/dist/ai-edits/batch-claims.js +275 -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 +91 -7
- package/dist/ai-edits/headless.js +520 -51
- 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 +6 -4
- package/dist/ai-edits/read.js +113 -50
- package/dist/ai-edits/result-validation.d.ts +29 -0
- package/dist/ai-edits/result-validation.js +164 -0
- package/dist/ai-edits/revisionStretches.d.ts +17 -0
- package/dist/ai-edits/revisionStretches.js +80 -0
- package/dist/ai-edits/snapshot.d.ts +36 -3
- package/dist/ai-edits/snapshot.js +180 -25
- package/dist/ai-edits/table-cell-mutations.d.ts +4 -2
- package/dist/ai-edits/table-cell-mutations.js +6 -2
- package/dist/ai-edits/table-geometry.d.ts +1 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +44 -17
- package/dist/ai-edits/table-row-column-mutations.js +86 -34
- 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.d.ts +5 -1
- package/dist/compare/content-alignment.js +52 -34
- 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/inline-provenance.js +1 -1
- 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 +65 -11
- 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/archiveInflation.d.ts +127 -0
- package/dist/docx/archiveInflation.js +181 -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/metadataPrivacy.js +24 -1
- package/dist/docx/normalizeBaseDirection.js +2 -1
- package/dist/docx/noteReferenceMark.d.ts +15 -0
- package/dist/docx/noteReferenceMark.js +48 -0
- 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 +96 -27
- package/dist/docx/paragraphPropertySource.d.ts +4 -2
- package/dist/docx/paragraphPropertySource.js +10 -2
- package/dist/docx/paragraphTraversal.js +1 -0
- package/dist/docx/parseWarningMessage.js +4 -1
- package/dist/docx/parser.js +175 -22
- package/dist/docx/rezip.js +31 -7
- 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.d.ts +3 -1
- package/dist/docx/selectiveXmlPatch.js +186 -39
- 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/boundedArchive.d.ts +8 -13
- package/dist/docx/server/boundedArchive.js +70 -59
- package/dist/docx/server/createBilingualDocument.js +3 -3
- package/dist/docx/server/extractDocxText.d.ts +8 -2
- package/dist/docx/server/extractDocxText.js +2 -2
- package/dist/docx/server/validateDocxConformance.d.ts +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -0
- 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 +20 -2
- package/dist/docx/unzip.js +136 -25
- package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
- package/dist/docx/wordprocessingPrefixes.js +286 -0
- package/dist/fonts/embeddedFonts.js +1 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +170 -34
- package/dist/i18n/messages/catalogs.gen.js +187 -51
- package/dist/i18n/messages/messages.gen.d.ts +10 -2
- 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 +600 -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/measureParagraph.js +2 -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/measure/paragraphMeasureShared.d.ts +12 -1
- package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
- 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/escape.d.ts +2 -2
- package/dist/markdown/escape.js +4 -5
- 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 +121 -25
- package/dist/markdown/renderParagraph.d.ts +13 -1
- package/dist/markdown/renderParagraph.js +85 -38
- package/dist/markdown/renderRuns.d.ts +10 -1
- package/dist/markdown/renderRuns.js +323 -69
- 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/clearRunColor.d.ts +6 -0
- package/dist/prosemirror/clearRunColor.js +46 -0
- package/dist/prosemirror/commands/comments.d.ts +7 -8
- package/dist/prosemirror/commands/comments.js +281 -212
- package/dist/prosemirror/commands/contentControls.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.js +1 -1
- package/dist/prosemirror/commands/formatting.d.ts +1 -1
- package/dist/prosemirror/commands/hyperlink.js +2 -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/propertyChangeScope.d.ts +3 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
- package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
- package/dist/prosemirror/commands/resolveAllTableChanges.js +620 -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 +73 -0
- package/dist/prosemirror/commands/sectionBreak.js +2 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +15 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
- package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
- package/dist/prosemirror/containerFinalParagraph.js +7 -6
- package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
- package/dist/prosemirror/conversion/fromProseDoc.js +177 -605
- package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
- package/dist/prosemirror/conversion/toProseDoc.js +70 -154
- package/dist/prosemirror/documentSchema.d.ts +8 -0
- package/dist/prosemirror/documentSchema.js +18 -0
- 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 +5 -1
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +116 -63
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +4 -1
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +167 -128
- 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/pastedHeadingStyles.js +1 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +124 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +33 -11
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
- package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +15 -8
- package/dist/prosemirror/extensions/marks/markUtils.js +25 -10
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +12 -0
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +46 -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/CommentReferenceExtension.js +2 -2
- package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
- 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 +121 -226
- package/dist/prosemirror/extensions/types.d.ts +11 -1
- package/dist/prosemirror/findReplaceSelection.js +1 -1
- package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
- package/dist/prosemirror/hyperlinkRemoval.js +129 -0
- package/dist/prosemirror/index.d.ts +5 -3
- package/dist/prosemirror/index.js +5 -3
- 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 +92 -0
- package/dist/prosemirror/listNumbering.js +330 -0
- package/dist/prosemirror/listRendering.d.ts +27 -0
- package/dist/prosemirror/listRendering.js +27 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
- package/dist/prosemirror/listRenderingAttrs.js +8 -1
- package/dist/prosemirror/markupViewNotes.d.ts +8 -0
- package/dist/prosemirror/markupViewNotes.js +49 -0
- package/dist/prosemirror/markupViewProjection.d.ts +97 -0
- package/dist/prosemirror/markupViewProjection.js +88 -0
- package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
- package/dist/prosemirror/noteReferenceReview.js +332 -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 +32 -7
- package/dist/prosemirror/paragraphMarkJoin.d.ts +21 -0
- package/dist/prosemirror/paragraphMarkJoin.js +26 -0
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
- package/dist/prosemirror/paragraphPropertyCarry.js +158 -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/documentStyleState.d.ts +26 -0
- package/dist/prosemirror/plugins/documentStyleState.js +32 -0
- package/dist/prosemirror/plugins/documentStyles.d.ts +3 -13
- package/dist/prosemirror/plugins/documentStyles.js +67 -17
- package/dist/prosemirror/plugins/index.d.ts +2 -1
- package/dist/prosemirror/plugins/index.js +2 -1
- package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
- package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
- package/dist/prosemirror/plugins/suggestionMode.d.ts +27 -3
- package/dist/prosemirror/plugins/suggestionMode.js +490 -63
- 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 +23 -3
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
- package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
- package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
- 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/runFormattingFromMarks.d.ts +18 -0
- package/dist/prosemirror/runFormattingFromMarks.js +567 -0
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
- package/dist/prosemirror/runFormattingReconciliation.js +10 -9
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/index.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +44 -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/styles/paragraphStyleCascade.d.ts +59 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
- package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
- package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
- package/dist/prosemirror/tableCellPaste.d.ts +74 -0
- package/dist/prosemirror/tableCellPaste.js +525 -0
- package/dist/prosemirror/tableGridMutation.d.ts +87 -2
- package/dist/prosemirror/tableGridMutation.js +255 -2
- package/dist/prosemirror/tableRunIn.d.ts +51 -0
- package/dist/prosemirror/tableRunIn.js +165 -0
- package/dist/prosemirror/textInput.js +11 -2
- package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
- package/dist/prosemirror/trackedRevisionPath.js +32 -0
- 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/server.d.ts +2 -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 +6 -5
- package/dist/internal/headlessRevisionResolution.d.ts +0 -23
package/dist/docx/unzip.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bytesToDataUrl } from "../utils/base64.js";
|
|
2
|
+
import { compressionRatioLimitFor, countCentralDirectoryRecords, createInflationBudget, exceedsCompressionRatio, getZipEntrySizes, inflateEntryWithinLimits, isStoredZipEntry } from "./archiveInflation.js";
|
|
2
3
|
import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "./encryption/containerFormat.js";
|
|
3
4
|
import { openDocxBuffer } from "./encryption/openEncryptedDocx.js";
|
|
4
5
|
import { decodeXmlBytes } from "./xmlEncoding.js";
|
|
@@ -60,6 +61,7 @@ const DEFAULT_UNZIP_LIMITS = {
|
|
|
60
61
|
maxMediaBytes: 25 * MEBIBYTE,
|
|
61
62
|
maxFontBytes: 10 * MEBIBYTE,
|
|
62
63
|
maxTotalUncompressedBytes: 250 * MEBIBYTE,
|
|
64
|
+
maxCompressionRatio: 200,
|
|
63
65
|
maxXmlElementsPerPart: FOLIO_XML_RESOURCE_LIMITS.maxElementsPerPart,
|
|
64
66
|
maxXmlAttributesPerPart: FOLIO_XML_RESOURCE_LIMITS.maxAttributesPerPart,
|
|
65
67
|
maxXmlElementsPerPackage: FOLIO_XML_RESOURCE_LIMITS.maxElementsPerPackage,
|
|
@@ -101,19 +103,23 @@ const ZIP_CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE = 33639248;
|
|
|
101
103
|
const ZIP_END_OF_CENTRAL_DIRECTORY_SIZE = 22;
|
|
102
104
|
const ZIP_END_OF_CENTRAL_DIRECTORY_WITH_COUNTS_SIZE = 12;
|
|
103
105
|
const ZIP_CENTRAL_DIRECTORY_FILE_HEADER_SIZE = 46;
|
|
106
|
+
/** Entries inflated at once; the rest wait for a slot. */
|
|
107
|
+
const EXTRACTION_CONCURRENCY = 6;
|
|
104
108
|
/**
|
|
105
109
|
* Extract all content from a DOCX file
|
|
106
110
|
*
|
|
107
111
|
* @param buffer - DOCX file as ArrayBuffer
|
|
108
112
|
* @returns Promise resolving to extracted content
|
|
109
113
|
*/
|
|
110
|
-
async function unzipDocx(buffer, options = {}) {
|
|
114
|
+
async function unzipDocx(buffer, options = {}, { verifyUnreadEntries = true } = {}) {
|
|
111
115
|
const limits = createUnzipLimits(options);
|
|
112
116
|
if (buffer.byteLength > limits.maxInputBytes) throw new DocxSecurityError("DOCX file exceeds the maximum allowed size");
|
|
113
117
|
const containerType = detectDocxContainerType(buffer);
|
|
114
118
|
const zipBuffer = containerType === DOCX_CONTAINER_TYPES.ZIP ? buffer : await openDocxBuffer(buffer, { password: options.password });
|
|
115
119
|
if (zipBuffer.byteLength > limits.maxInputBytes) throw new DocxSecurityError("DOCX file exceeds the maximum allowed size");
|
|
116
120
|
const wasEncrypted = containerType === DOCX_CONTAINER_TYPES.CFB;
|
|
121
|
+
const maxRecords = limits.maxFiles * 2;
|
|
122
|
+
if (countCentralDirectoryRecords(new Uint8Array(zipBuffer), maxRecords) > maxRecords) throw new DocxSecurityError("DOCX file contains too many entries");
|
|
117
123
|
const loaded = await loadDocxZip(zipBuffer, limits.maxFiles);
|
|
118
124
|
if (loaded.buffer.byteLength > limits.maxInputBytes) throw new DocxSecurityError("DOCX file exceeds the maximum allowed size");
|
|
119
125
|
const { zip } = loaded;
|
|
@@ -149,40 +155,78 @@ async function unzipDocx(buffer, options = {}) {
|
|
|
149
155
|
wasEncrypted
|
|
150
156
|
};
|
|
151
157
|
let totalUncompressedBytes = 0;
|
|
158
|
+
const inflationBudget = createInflationBudget(limits.maxTotalUncompressedBytes);
|
|
152
159
|
const extractionTasks = [];
|
|
160
|
+
const verifyEntry = (file, path) => {
|
|
161
|
+
if (!verifyUnreadEntries || isStoredZipEntry(file)) return;
|
|
162
|
+
extractionTasks.push(async () => {
|
|
163
|
+
await verifyEntryWithinLimits({
|
|
164
|
+
file,
|
|
165
|
+
path,
|
|
166
|
+
limits,
|
|
167
|
+
budget: inflationBudget
|
|
168
|
+
});
|
|
169
|
+
return null;
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
const inflate = async (file, maxEntryBytes) => await inflateEntryWithinLimits({
|
|
173
|
+
entry: file,
|
|
174
|
+
maxEntryBytes,
|
|
175
|
+
maxCompressionRatio: compressionRatioLimitFor(file.name, limits.maxCompressionRatio),
|
|
176
|
+
budget: inflationBudget
|
|
177
|
+
});
|
|
153
178
|
for (const [path, file] of entries) {
|
|
154
179
|
if (!isSafeDocxPath(path)) throw new DocxSecurityError("DOCX file contains an unsafe entry path");
|
|
155
|
-
const declaredSize =
|
|
180
|
+
const { compressedBytes, uncompressedBytes: declaredSize } = getZipEntrySizes(file);
|
|
156
181
|
if (declaredSize !== null) {
|
|
157
182
|
totalUncompressedBytes += declaredSize;
|
|
158
183
|
if (totalUncompressedBytes > limits.maxTotalUncompressedBytes) throw new DocxSecurityError("DOCX file expands beyond the maximum allowed size");
|
|
184
|
+
if (exceedsCompressionRatio({
|
|
185
|
+
inflatedBytes: declaredSize,
|
|
186
|
+
compressedBytes,
|
|
187
|
+
maxRatio: compressionRatioLimitFor(path, limits.maxCompressionRatio)
|
|
188
|
+
})) throw new DocxSecurityError(`DOCX entry exceeds the maximum compression ratio: ${path}`);
|
|
189
|
+
}
|
|
190
|
+
if (!isParsedDocxEntry(path)) {
|
|
191
|
+
verifyEntry(file, path);
|
|
192
|
+
continue;
|
|
159
193
|
}
|
|
160
|
-
if (!isParsedDocxEntry(path)) continue;
|
|
161
194
|
const lowerPath = path.toLowerCase();
|
|
162
195
|
if (lowerPath.endsWith(".xml") || lowerPath.endsWith(".rels")) {
|
|
163
196
|
assertEntrySize(path, declaredSize, limits.maxXmlBytes);
|
|
164
|
-
if (options.extractAllXml === false && !shouldExtractXmlPart(lowerPath))
|
|
165
|
-
|
|
166
|
-
|
|
197
|
+
if (options.extractAllXml === false && !shouldExtractXmlPart(lowerPath)) {
|
|
198
|
+
verifyEntry(file, path);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
extractionTasks.push(async () => {
|
|
202
|
+
const result = await inflate(file, limits.maxXmlBytes);
|
|
203
|
+
if (!result.ok) throw inflationLimitError(result.limit, path);
|
|
167
204
|
return {
|
|
168
205
|
type: "xml",
|
|
169
206
|
path,
|
|
170
207
|
lowerPath,
|
|
171
|
-
content: decodeXmlBytes(
|
|
208
|
+
content: decodeXmlBytes(result.bytes)
|
|
172
209
|
};
|
|
173
|
-
})
|
|
210
|
+
});
|
|
174
211
|
} else if (lowerPath.startsWith("word/media/")) {
|
|
175
212
|
const mimeType = getMediaMimeType(path);
|
|
176
|
-
if (!limits.allowedMediaMimeTypes.has(mimeType))
|
|
213
|
+
if (!limits.allowedMediaMimeTypes.has(mimeType)) {
|
|
214
|
+
verifyEntry(file, path);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
177
217
|
if (isEntryTooLarge(declaredSize, limits.maxMediaBytes)) {
|
|
178
218
|
content.warnings.push(`Skipped oversized media file: ${path}; original entry preserved for round-trip.`);
|
|
219
|
+
verifyEntry(file, path);
|
|
179
220
|
continue;
|
|
180
221
|
}
|
|
181
|
-
extractionTasks.push(
|
|
182
|
-
|
|
222
|
+
extractionTasks.push(async () => {
|
|
223
|
+
const result = await inflate(file, limits.maxMediaBytes);
|
|
224
|
+
if (!result.ok) {
|
|
225
|
+
if (result.limit !== "entry") throw inflationLimitError(result.limit, path);
|
|
183
226
|
content.warnings.push(`Skipped oversized media file: ${path}; original entry preserved for round-trip.`);
|
|
184
227
|
return null;
|
|
185
228
|
}
|
|
229
|
+
const binaryContent = result.bytes.buffer;
|
|
186
230
|
if (!isMediaContentAllowed(binaryContent, mimeType)) return null;
|
|
187
231
|
return {
|
|
188
232
|
type: "media",
|
|
@@ -190,21 +234,33 @@ async function unzipDocx(buffer, options = {}) {
|
|
|
190
234
|
mimeType,
|
|
191
235
|
content: binaryContent
|
|
192
236
|
};
|
|
193
|
-
})
|
|
237
|
+
});
|
|
194
238
|
} else if (lowerPath.startsWith("word/fonts/")) {
|
|
195
|
-
if (isEntryTooLarge(declaredSize, limits.maxFontBytes))
|
|
196
|
-
|
|
197
|
-
|
|
239
|
+
if (isEntryTooLarge(declaredSize, limits.maxFontBytes)) {
|
|
240
|
+
verifyEntry(file, path);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
extractionTasks.push(async () => {
|
|
244
|
+
const result = await inflate(file, limits.maxFontBytes);
|
|
245
|
+
if (!result.ok) {
|
|
246
|
+
if (result.limit !== "entry") throw inflationLimitError(result.limit, path);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
198
249
|
return {
|
|
199
250
|
type: "font",
|
|
200
251
|
path,
|
|
201
|
-
content:
|
|
252
|
+
content: result.bytes.buffer
|
|
202
253
|
};
|
|
203
|
-
})
|
|
204
|
-
}
|
|
254
|
+
});
|
|
255
|
+
} else verifyEntry(file, path);
|
|
205
256
|
}
|
|
257
|
+
if (exceedsCompressionRatio({
|
|
258
|
+
inflatedBytes: totalUncompressedBytes,
|
|
259
|
+
compressedBytes: loaded.buffer.byteLength,
|
|
260
|
+
maxRatio: limits.maxCompressionRatio
|
|
261
|
+
})) throw new DocxSecurityError("DOCX file exceeds the maximum compression ratio");
|
|
206
262
|
const xmlBudget = createXmlPackageBudget();
|
|
207
|
-
for (const extracted of await
|
|
263
|
+
for (const extracted of await runExtractionTasks(extractionTasks, inflationBudget)) {
|
|
208
264
|
if (!extracted) continue;
|
|
209
265
|
if (extracted.type === "xml") {
|
|
210
266
|
assignXmlContent(content, extracted, limits, xmlBudget);
|
|
@@ -262,6 +318,62 @@ function assignXmlContent(content, { path, lowerPath, content: xmlContent }, lim
|
|
|
262
318
|
content.footers.set(filename, xmlContent);
|
|
263
319
|
}
|
|
264
320
|
}
|
|
321
|
+
function inflationLimitError(limit, path) {
|
|
322
|
+
switch (limit) {
|
|
323
|
+
case "declared-size": return new DocxSecurityError(`DOCX entry inflates past its declared size: ${path}`);
|
|
324
|
+
case "compression-ratio": return new DocxSecurityError(`DOCX entry exceeds the maximum compression ratio: ${path}`);
|
|
325
|
+
case "entry": return new DocxSecurityError(`DOCX entry exceeds maximum size: ${path}`);
|
|
326
|
+
case "total": return new DocxSecurityError("DOCX file expands beyond the maximum allowed size");
|
|
327
|
+
case "aborted": return new DocxSecurityError(`DOCX entry was not read after another entry failed: ${path}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Run extraction tasks a few at a time, in order, keeping their results in
|
|
332
|
+
* task order. The first failure marks the shared budget aborted, which stops
|
|
333
|
+
* the streams still running at their next chunk and leaves the queued tasks
|
|
334
|
+
* unstarted.
|
|
335
|
+
*/
|
|
336
|
+
async function runExtractionTasks(tasks, budget) {
|
|
337
|
+
const results = Array.from({ length: tasks.length }, () => null);
|
|
338
|
+
let next = 0;
|
|
339
|
+
const worker = async () => {
|
|
340
|
+
while (next < tasks.length && !budget.aborted) {
|
|
341
|
+
const index = next;
|
|
342
|
+
next += 1;
|
|
343
|
+
const task = tasks[index];
|
|
344
|
+
if (!task) return;
|
|
345
|
+
try {
|
|
346
|
+
results[index] = await task();
|
|
347
|
+
} catch (error) {
|
|
348
|
+
budget.aborted = true;
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
await Promise.all(Array.from({ length: Math.min(EXTRACTION_CONCURRENCY, tasks.length) }, async () => await worker()));
|
|
354
|
+
return results;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Inflate an entry without keeping it, to bound what a later read of it can
|
|
358
|
+
* produce. A limit refuses the package. A corrupt entry does not: it was never
|
|
359
|
+
* going to be read here, a later read stops at the same byte, and refusing
|
|
360
|
+
* the whole document over a part folio does not model would be a regression.
|
|
361
|
+
*/
|
|
362
|
+
async function verifyEntryWithinLimits({ file, path, limits, budget }) {
|
|
363
|
+
let result;
|
|
364
|
+
try {
|
|
365
|
+
result = await inflateEntryWithinLimits({
|
|
366
|
+
entry: file,
|
|
367
|
+
maxEntryBytes: Number.POSITIVE_INFINITY,
|
|
368
|
+
maxCompressionRatio: compressionRatioLimitFor(path, limits.maxCompressionRatio),
|
|
369
|
+
budget,
|
|
370
|
+
retain: false
|
|
371
|
+
});
|
|
372
|
+
} catch {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (!result.ok) throw inflationLimitError(result.limit, path);
|
|
376
|
+
}
|
|
265
377
|
async function loadDocxZip(buffer, maxFiles) {
|
|
266
378
|
try {
|
|
267
379
|
return {
|
|
@@ -336,9 +448,12 @@ function findLastSignature(view, byteLength, signature) {
|
|
|
336
448
|
return -1;
|
|
337
449
|
}
|
|
338
450
|
function createUnzipLimits(options) {
|
|
451
|
+
const { maxCompressionRatio } = options;
|
|
452
|
+
if (maxCompressionRatio !== void 0 && (!Number.isSafeInteger(maxCompressionRatio) || maxCompressionRatio < 0)) throw new RangeError("DOCX compression ratio limit must be a non-negative safe integer");
|
|
339
453
|
return {
|
|
340
454
|
...DEFAULT_UNZIP_LIMITS,
|
|
341
455
|
...options,
|
|
456
|
+
maxCompressionRatio: maxCompressionRatio ?? DEFAULT_UNZIP_LIMITS.maxCompressionRatio,
|
|
342
457
|
allowedMediaMimeTypes: options.allowedMediaMimeTypes ? new Set(options.allowedMediaMimeTypes) : DEFAULT_UNZIP_LIMITS.allowedMediaMimeTypes
|
|
343
458
|
};
|
|
344
459
|
}
|
|
@@ -375,8 +490,7 @@ function isParsedDocxEntry(path) {
|
|
|
375
490
|
return lowerPath === "[content_types].xml" || lowerPath.startsWith("_rels/") || lowerPath.startsWith("docprops/") || lowerPath.startsWith("word/") || lowerPath.startsWith("customxml/");
|
|
376
491
|
}
|
|
377
492
|
function getEntryUncompressedSize(file) {
|
|
378
|
-
|
|
379
|
-
return typeof metadata?.uncompressedSize === "number" ? metadata.uncompressedSize : null;
|
|
493
|
+
return getZipEntrySizes(file).uncompressedBytes;
|
|
380
494
|
}
|
|
381
495
|
function assertEntrySize(path, declaredSize, maxBytes) {
|
|
382
496
|
if (isEntryTooLarge(declaredSize, maxBytes)) throw new DocxSecurityError(`DOCX entry exceeds maximum size: ${path}`);
|
|
@@ -384,9 +498,6 @@ function assertEntrySize(path, declaredSize, maxBytes) {
|
|
|
384
498
|
function isEntryTooLarge(declaredSize, maxBytes) {
|
|
385
499
|
return declaredSize !== null && declaredSize > maxBytes;
|
|
386
500
|
}
|
|
387
|
-
function assertExtractedSize(path, byteLength, maxBytes) {
|
|
388
|
-
if (byteLength > maxBytes) throw new DocxSecurityError(`DOCX entry exceeds maximum size: ${path}`);
|
|
389
|
-
}
|
|
390
501
|
function isMediaContentAllowed(data, mimeType) {
|
|
391
502
|
const bytes = new Uint8Array(data);
|
|
392
503
|
switch (mimeType) {
|
|
@@ -494,4 +605,4 @@ function getContentSummary(content) {
|
|
|
494
605
|
};
|
|
495
606
|
}
|
|
496
607
|
//#endregion
|
|
497
|
-
export { DocxSecurityError, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile, mediaToDataUrl, unzipDocx };
|
|
608
|
+
export { DocxSecurityError, extractFile, getContentSummary, getEntryUncompressedSize, getFileList, getMediaMimeType, hasFile, mediaToDataUrl, unzipDocx };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/docx/wordprocessingPrefixes.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Which prefixes a WordprocessingML part spells its namespaces with.
|
|
4
|
+
*
|
|
5
|
+
* A prefix is an alias: `<w:p>`, `<x:p>` and an unprefixed `<p>` under a
|
|
6
|
+
* default namespace are the same paragraph when the prefix (or the default)
|
|
7
|
+
* is bound to the WordprocessingML URI. The parser resolves names by URI, so
|
|
8
|
+
* every such package opens. The patchers that work on part XML as a string
|
|
9
|
+
* (`ensureParaIds`, the selective-save splices) find elements by their
|
|
10
|
+
* literal tags instead, and a literal `<w:p` finds nothing in a part that
|
|
11
|
+
* spells it `<x:p`: the patch reports success having touched nothing.
|
|
12
|
+
*
|
|
13
|
+
* {@link resolveWordprocessingPrefixes} is the one place those patchers ask
|
|
14
|
+
* how a part spells WordprocessingML, the `w14` extensions and
|
|
15
|
+
* markup compatibility (`mc`). A patcher either scans with every prefix the
|
|
16
|
+
* part binds, or checks the names its literal scan needs in their namespace
|
|
17
|
+
* scope. A prefix conflict on one of those names is refused.
|
|
18
|
+
*/
|
|
19
|
+
/** How one part spells the namespaces a string-level patcher scans for. */
|
|
20
|
+
type WordprocessingPrefixes = {
|
|
21
|
+
/**
|
|
22
|
+
* Prefixes bound to WordprocessingML (Transitional or Strict), `""` for the
|
|
23
|
+
* default namespace. Never empty: an undeclared part reads as `w`.
|
|
24
|
+
*/
|
|
25
|
+
main: readonly string[];
|
|
26
|
+
/** Prefixes bound to the `w14` extensions namespace. */
|
|
27
|
+
w14: readonly string[];
|
|
28
|
+
/** Whether a `w14` prefix is declared on the root (else `w14` is assumed). */
|
|
29
|
+
w14Declared: boolean;
|
|
30
|
+
/** Prefixes bound to markup compatibility (`mc`). */
|
|
31
|
+
mc: readonly string[];
|
|
32
|
+
/** Whether an `mc` prefix is declared on the root (else `mc` is assumed). */
|
|
33
|
+
mcDeclared: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The spelling folio's serializer writes: WordprocessingML only as `w`,
|
|
36
|
+
* `w14` and `mc` under their conventional prefixes. Splicing serializer
|
|
37
|
+
* output into a part is only sound when this holds.
|
|
38
|
+
*/
|
|
39
|
+
canonical: boolean;
|
|
40
|
+
};
|
|
41
|
+
type WordprocessingPrefixResolution = {
|
|
42
|
+
type: "resolved";
|
|
43
|
+
prefixes: WordprocessingPrefixes;
|
|
44
|
+
} | {
|
|
45
|
+
type: "unsupported";
|
|
46
|
+
reason: string;
|
|
47
|
+
};
|
|
48
|
+
declare const resolveWordprocessingPrefixes: (xml: string) => WordprocessingPrefixResolution;
|
|
49
|
+
/** Root spelling for a selective scan whose relevant nested names are checked separately. */
|
|
50
|
+
declare const resolveSelectiveScanPrefixes: (xml: string) => WordprocessingPrefixResolution;
|
|
51
|
+
/**
|
|
52
|
+
* Whether a patcher that reads `<w:…>` literally sees every element it looks
|
|
53
|
+
* for in `xml`, and may splice the serializer's `w:` markup into it.
|
|
54
|
+
*
|
|
55
|
+
* True for the canonical spelling. Also true when the part binds
|
|
56
|
+
* WordprocessingML under an extra prefix besides `w` but never spells the
|
|
57
|
+
* names the patcher scans under that alias, or binds markup compatibility
|
|
58
|
+
* under another prefix. Relevant nested names are checked in their own scope.
|
|
59
|
+
*/
|
|
60
|
+
declare const splicesAsCanonical: (xml: string, localNames: readonly string[]) => boolean;
|
|
61
|
+
/** The names the paragraph splices scan for: paragraphs, their containers and ids. */
|
|
62
|
+
declare const PARAGRAPH_SCAN_NAMES: readonly ["p", "tc", "txbxContent", "paraId", "textId"];
|
|
63
|
+
/** Whether `xml` spells WordprocessingML the way folio's serializer does. */
|
|
64
|
+
declare const hasCanonicalWordprocessingPrefixes: (xml: string) => boolean;
|
|
65
|
+
/** `<p`, `<w:p`, … — the open-tag literal of `localName` under `prefix`. */
|
|
66
|
+
declare const openTagLiteral: (prefix: string, localName: string) => string;
|
|
67
|
+
/** `</p>`, `</w:p>`, … — the close-tag literal of `localName` under `prefix`. */
|
|
68
|
+
declare const closeTagLiteral: (prefix: string, localName: string) => string;
|
|
69
|
+
/**
|
|
70
|
+
* Whether `xml` opens `localName` under one of `prefixes` at `start`, and the
|
|
71
|
+
* length of the literal that matched (`-1` when none did). The character after
|
|
72
|
+
* the name must end it, so `<w:pPr` is not `<w:p`.
|
|
73
|
+
*/
|
|
74
|
+
declare const matchOpenTag: (xml: string, start: number, prefixes: readonly string[], localName: string) => number;
|
|
75
|
+
/** The length of the close tag of `localName` under one of `prefixes` at `start`, or -1. */
|
|
76
|
+
declare const matchCloseTag: (xml: string, start: number, prefixes: readonly string[], localName: string) => number;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { PARAGRAPH_SCAN_NAMES, WordprocessingPrefixResolution, WordprocessingPrefixes, closeTagLiteral, hasCanonicalWordprocessingPrefixes, matchCloseTag, matchOpenTag, openTagLiteral, resolveSelectiveScanPrefixes, resolveWordprocessingPrefixes, splicesAsCanonical };
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, getLocalName, getNamespacePrefix, getNamespaceUri, parseXmlDocument, resolveAttributeNamespaceUri } from "./xmlParser.js";
|
|
2
|
+
//#region src/docx/wordprocessingPrefixes.ts
|
|
3
|
+
/**
|
|
4
|
+
* Which prefixes a WordprocessingML part spells its namespaces with.
|
|
5
|
+
*
|
|
6
|
+
* A prefix is an alias: `<w:p>`, `<x:p>` and an unprefixed `<p>` under a
|
|
7
|
+
* default namespace are the same paragraph when the prefix (or the default)
|
|
8
|
+
* is bound to the WordprocessingML URI. The parser resolves names by URI, so
|
|
9
|
+
* every such package opens. The patchers that work on part XML as a string
|
|
10
|
+
* (`ensureParaIds`, the selective-save splices) find elements by their
|
|
11
|
+
* literal tags instead, and a literal `<w:p` finds nothing in a part that
|
|
12
|
+
* spells it `<x:p`: the patch reports success having touched nothing.
|
|
13
|
+
*
|
|
14
|
+
* {@link resolveWordprocessingPrefixes} is the one place those patchers ask
|
|
15
|
+
* how a part spells WordprocessingML, the `w14` extensions and
|
|
16
|
+
* markup compatibility (`mc`). A patcher either scans with every prefix the
|
|
17
|
+
* part binds, or checks the names its literal scan needs in their namespace
|
|
18
|
+
* scope. A prefix conflict on one of those names is refused.
|
|
19
|
+
*/
|
|
20
|
+
const XMLNS = "xmlns";
|
|
21
|
+
const isWhitespace = (character) => character === " " || character === " " || character === "\n" || character === "\r";
|
|
22
|
+
/** End offset (exclusive) of the tag opening at `start`, skipping quoted values. */
|
|
23
|
+
const tagEnd = (xml, start) => {
|
|
24
|
+
let quote = null;
|
|
25
|
+
for (let index = start + 1; index < xml.length; index += 1) {
|
|
26
|
+
const character = xml[index];
|
|
27
|
+
if (quote !== null) {
|
|
28
|
+
if (character === quote) quote = null;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (character === "\"" || character === "'") quote = character;
|
|
32
|
+
else if (character === ">") return index + 1;
|
|
33
|
+
}
|
|
34
|
+
return -1;
|
|
35
|
+
};
|
|
36
|
+
/** The `xmlns` / `xmlns:*` declarations written in one start tag. */
|
|
37
|
+
const declarationsIn = (tag) => {
|
|
38
|
+
const declarations = [];
|
|
39
|
+
for (const match of tag.matchAll(/\sxmlns(?::(?<prefix>[^\s=/>]+))?\s*=\s*(?<quote>["'])(?<uri>[\s\S]*?)\k<quote>/gu)) declarations.push({
|
|
40
|
+
prefix: match.groups?.["prefix"] ?? "",
|
|
41
|
+
uri: match.groups?.["uri"] ?? ""
|
|
42
|
+
});
|
|
43
|
+
return declarations;
|
|
44
|
+
};
|
|
45
|
+
const OPAQUE_REGIONS = [
|
|
46
|
+
{
|
|
47
|
+
open: "<!--",
|
|
48
|
+
close: "-->"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
open: "<![CDATA[",
|
|
52
|
+
close: "]]>"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
open: "<?",
|
|
56
|
+
close: "?>"
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
/** Visit every start tag of `xml` that declares a namespace, root first. */
|
|
60
|
+
const visitDeclaringStartTags = (xml, visit) => {
|
|
61
|
+
let seenRoot = false;
|
|
62
|
+
let pos = 0;
|
|
63
|
+
scan: while (pos < xml.length) {
|
|
64
|
+
const start = xml.indexOf("<", pos);
|
|
65
|
+
if (start === -1) break;
|
|
66
|
+
for (const { open, close } of OPAQUE_REGIONS) if (xml.startsWith(open, start)) {
|
|
67
|
+
const closeStart = xml.indexOf(close, start + open.length);
|
|
68
|
+
if (closeStart === -1) return false;
|
|
69
|
+
pos = closeStart + close.length;
|
|
70
|
+
continue scan;
|
|
71
|
+
}
|
|
72
|
+
const next = xml[start + 1];
|
|
73
|
+
if (next === "!" || next === "/") {
|
|
74
|
+
const end = xml.indexOf(">", start);
|
|
75
|
+
if (end === -1) return false;
|
|
76
|
+
pos = end + 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const end = tagEnd(xml, start);
|
|
80
|
+
if (end === -1) return false;
|
|
81
|
+
const isRoot = !seenRoot;
|
|
82
|
+
seenRoot = true;
|
|
83
|
+
const tag = xml.slice(start, end);
|
|
84
|
+
if (isRoot || tag.includes(XMLNS)) visit(tag, isRoot);
|
|
85
|
+
pos = end;
|
|
86
|
+
}
|
|
87
|
+
return seenRoot;
|
|
88
|
+
};
|
|
89
|
+
const MAIN_URIS = WORDPROCESSINGML_NAMESPACE_URIS;
|
|
90
|
+
const W14_URI = NAMESPACES.w14;
|
|
91
|
+
const MC_URI = NAMESPACES.mc;
|
|
92
|
+
const NESTED_BINDINGS = {
|
|
93
|
+
strict: "strict",
|
|
94
|
+
scoped: "scoped"
|
|
95
|
+
};
|
|
96
|
+
const slotOf = (uri) => {
|
|
97
|
+
if (MAIN_URIS.has(uri)) return "main";
|
|
98
|
+
if (uri === W14_URI) return "w14";
|
|
99
|
+
if (uri === MC_URI) return "mc";
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
const CONVENTIONAL_PREFIX = {
|
|
103
|
+
main: "w",
|
|
104
|
+
w14: "w14",
|
|
105
|
+
mc: "mc"
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Resolve the prefixes `xml` binds to WordprocessingML, `w14` and `mc`.
|
|
109
|
+
*
|
|
110
|
+
* Root declarations decide. A namespace the root never declares is read under
|
|
111
|
+
* its conventional prefix, the tolerant reading of a malformed part. A declaration on
|
|
112
|
+
* a nested element is accepted only when it repeats a binding the scan already
|
|
113
|
+
* uses; anything else changes what a literal tag means partway through the
|
|
114
|
+
* part and is reported as unsupported.
|
|
115
|
+
*/
|
|
116
|
+
const resolvePrefixes = (xml, nestedBindings) => {
|
|
117
|
+
const bound = {
|
|
118
|
+
main: [],
|
|
119
|
+
w14: [],
|
|
120
|
+
mc: []
|
|
121
|
+
};
|
|
122
|
+
const rootPrefixes = /* @__PURE__ */ new Map();
|
|
123
|
+
const nested = [];
|
|
124
|
+
if (!visitDeclaringStartTags(xml, (tag, isRoot) => {
|
|
125
|
+
for (const declaration of declarationsIn(tag)) {
|
|
126
|
+
if (!isRoot) {
|
|
127
|
+
nested.push(declaration);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
rootPrefixes.set(declaration.prefix, declaration.uri);
|
|
131
|
+
const slot = slotOf(declaration.uri);
|
|
132
|
+
if (slot !== null) bound[slot].push(declaration.prefix);
|
|
133
|
+
}
|
|
134
|
+
})) return {
|
|
135
|
+
type: "unsupported",
|
|
136
|
+
reason: "no root element or unterminated markup"
|
|
137
|
+
};
|
|
138
|
+
const declared = {
|
|
139
|
+
main: bound.main.length > 0,
|
|
140
|
+
w14: bound.w14.length > 0,
|
|
141
|
+
mc: bound.mc.length > 0
|
|
142
|
+
};
|
|
143
|
+
for (const slot of [
|
|
144
|
+
"main",
|
|
145
|
+
"w14",
|
|
146
|
+
"mc"
|
|
147
|
+
]) {
|
|
148
|
+
if (declared[slot]) {
|
|
149
|
+
if (slot !== "main" && bound[slot].includes("")) return {
|
|
150
|
+
type: "unsupported",
|
|
151
|
+
reason: `${slot} is bound only as the default namespace`
|
|
152
|
+
};
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const conventional = CONVENTIONAL_PREFIX[slot];
|
|
156
|
+
if (rootPrefixes.has(conventional)) {
|
|
157
|
+
if (slot === "main") return {
|
|
158
|
+
type: "unsupported",
|
|
159
|
+
reason: `no WordprocessingML binding; ${conventional} is bound to another namespace`
|
|
160
|
+
};
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
bound[slot].push(conventional);
|
|
164
|
+
}
|
|
165
|
+
const inUse = /* @__PURE__ */ new Map();
|
|
166
|
+
for (const slot of [
|
|
167
|
+
"main",
|
|
168
|
+
"w14",
|
|
169
|
+
"mc"
|
|
170
|
+
]) for (const prefix of bound[slot]) inUse.set(prefix, slot);
|
|
171
|
+
for (const { prefix, uri } of nestedBindings === NESTED_BINDINGS.strict ? nested : []) {
|
|
172
|
+
const slot = slotOf(uri);
|
|
173
|
+
const spelling = prefix === "" ? "the default namespace" : `prefix ${prefix}`;
|
|
174
|
+
if (slot !== null) {
|
|
175
|
+
if (!bound[slot].includes(prefix)) return {
|
|
176
|
+
type: "unsupported",
|
|
177
|
+
reason: `a nested element binds ${slot} under ${spelling}`
|
|
178
|
+
};
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (inUse.has(prefix)) return {
|
|
182
|
+
type: "unsupported",
|
|
183
|
+
reason: `a nested element rebinds ${spelling} to ${uri === "" ? "no namespace" : uri}`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const only = (prefixes, prefix) => prefixes.length === 1 && prefixes[0] === prefix;
|
|
187
|
+
const canonical = only(bound.main, "w") && only(bound.w14, "w14") && only(bound.mc, "mc");
|
|
188
|
+
return {
|
|
189
|
+
type: "resolved",
|
|
190
|
+
prefixes: {
|
|
191
|
+
main: bound.main,
|
|
192
|
+
w14: bound.w14,
|
|
193
|
+
w14Declared: declared.w14,
|
|
194
|
+
mc: bound.mc,
|
|
195
|
+
mcDeclared: declared.mc,
|
|
196
|
+
canonical
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
const resolveWordprocessingPrefixes = (xml) => resolvePrefixes(xml, NESTED_BINDINGS.strict);
|
|
201
|
+
/** Root spelling for a selective scan whose relevant nested names are checked separately. */
|
|
202
|
+
const resolveSelectiveScanPrefixes = (xml) => resolvePrefixes(xml, NESTED_BINDINGS.scoped);
|
|
203
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
204
|
+
/**
|
|
205
|
+
* Whether a patcher that reads `<w:…>` literally sees every element it looks
|
|
206
|
+
* for in `xml`, and may splice the serializer's `w:` markup into it.
|
|
207
|
+
*
|
|
208
|
+
* True for the canonical spelling. Also true when the part binds
|
|
209
|
+
* WordprocessingML under an extra prefix besides `w` but never spells the
|
|
210
|
+
* names the patcher scans under that alias, or binds markup compatibility
|
|
211
|
+
* under another prefix. Relevant nested names are checked in their own scope.
|
|
212
|
+
*/
|
|
213
|
+
const splicesAsCanonical = (xml, localNames) => {
|
|
214
|
+
const resolution = resolveWordprocessingPrefixes(xml);
|
|
215
|
+
if (resolution.type === "resolved" && resolution.prefixes.canonical) return true;
|
|
216
|
+
const relaxed = resolveSelectiveScanPrefixes(xml);
|
|
217
|
+
if (relaxed.type !== "resolved") return false;
|
|
218
|
+
const { prefixes } = relaxed;
|
|
219
|
+
const only = (list, prefix) => list.length === 1 && list[0] === prefix;
|
|
220
|
+
const aliases = prefixes.main.filter((prefix) => prefix !== "w");
|
|
221
|
+
if (!prefixes.main.includes("w") || aliases.includes("") || !only(prefixes.w14, "w14") || prefixes.mc.length === 0) return false;
|
|
222
|
+
const names = localNames.map(escapeRegExp).join("|");
|
|
223
|
+
if (!aliases.every((alias) => !new RegExp(`[<\\s/]${escapeRegExp(alias)}:(?:${names})[\\s/>=]`, "u").test(xml))) return false;
|
|
224
|
+
const root = parseXmlDocument(xml);
|
|
225
|
+
if (!root) return false;
|
|
226
|
+
const mainNames = new Set(localNames.filter((name) => name !== "paraId" && name !== "textId"));
|
|
227
|
+
const idNames = /* @__PURE__ */ new Set(["paraId", "textId"]);
|
|
228
|
+
const mcNames = /* @__PURE__ */ new Set(["Fallback", "AlternateContent"]);
|
|
229
|
+
const visit = (element) => {
|
|
230
|
+
if (element.type !== "element") return true;
|
|
231
|
+
const name = element.name ?? "";
|
|
232
|
+
const local = getLocalName(name);
|
|
233
|
+
const prefix = getNamespacePrefix(name) ?? "";
|
|
234
|
+
const uri = getNamespaceUri(element) ?? "";
|
|
235
|
+
if (prefix === "mc" && uri !== MC_URI || mainNames.has(local) && WORDPROCESSINGML_NAMESPACE_URIS.has(uri) !== (prefix === "w") || mcNames.has(local) && uri === MC_URI !== prefixes.mc.includes(prefix)) return false;
|
|
236
|
+
for (const attribute of Object.keys(element.attributes ?? {})) {
|
|
237
|
+
const attributePrefix = getNamespacePrefix(attribute);
|
|
238
|
+
if (attributePrefix === "mc" && resolveAttributeNamespaceUri(element, attribute) !== MC_URI) return false;
|
|
239
|
+
if (!idNames.has(getLocalName(attribute))) continue;
|
|
240
|
+
if (resolveAttributeNamespaceUri(element, attribute) === W14_URI !== (attributePrefix === "w14")) return false;
|
|
241
|
+
}
|
|
242
|
+
return (element.elements ?? []).every(visit);
|
|
243
|
+
};
|
|
244
|
+
return visit(root);
|
|
245
|
+
};
|
|
246
|
+
/** The names the paragraph splices scan for: paragraphs, their containers and ids. */
|
|
247
|
+
const PARAGRAPH_SCAN_NAMES = [
|
|
248
|
+
"p",
|
|
249
|
+
"tc",
|
|
250
|
+
"txbxContent",
|
|
251
|
+
"paraId",
|
|
252
|
+
"textId"
|
|
253
|
+
];
|
|
254
|
+
/** Whether `xml` spells WordprocessingML the way folio's serializer does. */
|
|
255
|
+
const hasCanonicalWordprocessingPrefixes = (xml) => {
|
|
256
|
+
const resolution = resolveWordprocessingPrefixes(xml);
|
|
257
|
+
return resolution.type === "resolved" && resolution.prefixes.canonical;
|
|
258
|
+
};
|
|
259
|
+
/** `<p`, `<w:p`, … — the open-tag literal of `localName` under `prefix`. */
|
|
260
|
+
const openTagLiteral = (prefix, localName) => prefix === "" ? `<${localName}` : `<${prefix}:${localName}`;
|
|
261
|
+
/** `</p>`, `</w:p>`, … — the close-tag literal of `localName` under `prefix`. */
|
|
262
|
+
const closeTagLiteral = (prefix, localName) => prefix === "" ? `</${localName}>` : `</${prefix}:${localName}>`;
|
|
263
|
+
/**
|
|
264
|
+
* Whether `xml` opens `localName` under one of `prefixes` at `start`, and the
|
|
265
|
+
* length of the literal that matched (`-1` when none did). The character after
|
|
266
|
+
* the name must end it, so `<w:pPr` is not `<w:p`.
|
|
267
|
+
*/
|
|
268
|
+
const matchOpenTag = (xml, start, prefixes, localName) => {
|
|
269
|
+
for (const prefix of prefixes) {
|
|
270
|
+
const literal = openTagLiteral(prefix, localName);
|
|
271
|
+
if (!xml.startsWith(literal, start)) continue;
|
|
272
|
+
const after = xml[start + literal.length];
|
|
273
|
+
if (after === ">" || after === "/" || isWhitespace(after)) return literal.length;
|
|
274
|
+
}
|
|
275
|
+
return -1;
|
|
276
|
+
};
|
|
277
|
+
/** The length of the close tag of `localName` under one of `prefixes` at `start`, or -1. */
|
|
278
|
+
const matchCloseTag = (xml, start, prefixes, localName) => {
|
|
279
|
+
for (const prefix of prefixes) {
|
|
280
|
+
const literal = closeTagLiteral(prefix, localName);
|
|
281
|
+
if (xml.startsWith(literal, start)) return literal.length;
|
|
282
|
+
}
|
|
283
|
+
return -1;
|
|
284
|
+
};
|
|
285
|
+
//#endregion
|
|
286
|
+
export { PARAGRAPH_SCAN_NAMES, closeTagLiteral, hasCanonicalWordprocessingPrefixes, matchCloseTag, matchOpenTag, openTagLiteral, resolveSelectiveScanPrefixes, resolveWordprocessingPrefixes, splicesAsCanonical };
|
|
@@ -130,7 +130,7 @@ function findFontTableRels(allXml) {
|
|
|
130
130
|
* embedded fonts. See {@link getEmbeddedFontFaces} for `docNonce`.
|
|
131
131
|
*/
|
|
132
132
|
async function extractEmbeddedFonts(buffer, docNonce = generateHexId()) {
|
|
133
|
-
const raw = await unzipDocx(buffer);
|
|
133
|
+
const raw = await unzipDocx(buffer, {}, { verifyUnreadEntries: false });
|
|
134
134
|
return getEmbeddedFontFaces({
|
|
135
135
|
fontTableXml: raw.fontTableXml,
|
|
136
136
|
fontTableRelsXml: findFontTableRels(raw.allXml),
|