@stll/folio-core 0.54.1 → 0.55.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/dist/ai-edits/apply.d.ts +1 -1
- package/dist/ai-edits/apply.js +40 -32
- package/dist/ai-edits/headless.d.ts +1 -2
- package/dist/ai-edits/headless.js +13 -27
- package/dist/ai-edits/read.d.ts +1 -0
- package/dist/ai-edits/read.js +7 -2
- package/dist/ai-edits/table-mutation-plan.d.ts +2 -1
- package/dist/ai-edits/table-mutation-plan.js +39 -1
- package/dist/ai-suggestions/apply.d.ts +1 -1
- package/dist/ai-suggestions/apply.js +3 -2
- package/dist/compare/compare.js +2 -1
- package/dist/compare/content-types.d.ts +3 -15
- package/dist/controller/canonicalComposition.d.ts +36 -0
- package/dist/controller/canonicalComposition.js +168 -0
- package/dist/controller/canonicalInput.d.ts +45 -0
- package/dist/controller/canonicalInput.js +338 -0
- package/dist/controller/canonicalOperations.d.ts +22 -0
- package/dist/controller/canonicalOperations.js +85 -0
- package/dist/controller/canonicalReview.d.ts +12 -0
- package/dist/controller/canonicalReview.js +19 -0
- package/dist/controller/canonicalSession.d.ts +149 -0
- package/dist/controller/canonicalSession.js +1038 -0
- package/dist/controller/canonicalStoryEditor.d.ts +34 -0
- package/dist/controller/canonicalStoryEditor.js +82 -0
- package/dist/controller/canonicalStructure.d.ts +16 -0
- package/dist/controller/canonicalStructure.js +269 -0
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/folioEditor.js +9 -0
- package/dist/controller/headerFooterEditorManager.d.ts +4 -0
- package/dist/controller/headerFooterEditorManager.js +71 -3
- package/dist/controller/hiddenEditorApi.d.ts +35 -0
- package/dist/controller/hiddenEditorApi.js +25 -8
- package/dist/controller/hiddenEditorManager.d.ts +11 -2
- package/dist/controller/hiddenEditorManager.js +456 -30
- package/dist/controller/layoutPipeline.d.ts +1 -1
- package/dist/controller/layoutPipeline.js +2 -0
- package/dist/controller/noteEditorManager.d.ts +4 -0
- package/dist/controller/noteEditorManager.js +113 -15
- package/dist/docx/attributeRemainder.js +1 -0
- package/dist/docx/bookmarkIds.d.ts +11 -0
- package/dist/docx/bookmarkIds.js +33 -0
- package/dist/docx/canonicalSessionInput.d.ts +11 -0
- package/dist/docx/canonicalSessionInput.js +27 -0
- package/dist/docx/canonicalStoryRepack.d.ts +10 -0
- package/dist/docx/canonicalStoryRepack.js +41 -0
- package/dist/docx/contentTypeRegistry.d.ts +8 -0
- package/dist/docx/contentTypeRegistry.js +64 -0
- package/dist/docx/documentParser.d.ts +14 -1
- package/dist/docx/documentParser.js +17 -4
- package/dist/docx/drawingIdNormalization.js +23 -8
- package/dist/docx/ensureParaIds.d.ts +12 -1
- package/dist/docx/ensureParaIds.js +173 -213
- package/dist/docx/foldedListNumberFields.d.ts +85 -0
- package/dist/docx/foldedListNumberFields.js +267 -0
- package/dist/docx/headerFooterVerbatim.d.ts +14 -1
- package/dist/docx/headerFooterVerbatim.js +95 -2
- package/dist/docx/listNumberingInstances.js +11 -9
- package/dist/docx/noteIds.d.ts +9 -0
- package/dist/docx/noteIds.js +17 -0
- package/dist/docx/noteReferenceMark.d.ts +3 -1
- package/dist/docx/noteReferenceMark.js +15 -14
- package/dist/docx/numberingIds.d.ts +14 -0
- package/dist/docx/numberingIds.js +16 -0
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +17 -3
- package/dist/docx/numericIdAllocator.d.ts +12 -0
- package/dist/docx/numericIdAllocator.js +33 -0
- package/dist/docx/numericIdNormalization.d.ts +16 -0
- package/dist/docx/numericIdNormalization.js +174 -0
- package/dist/docx/packageParts.d.ts +3 -1
- package/dist/docx/packageParts.js +65 -16
- package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
- package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
- package/dist/docx/paragraphParser.js +101 -53
- package/dist/docx/paragraphPropertySource.d.ts +15 -1
- package/dist/docx/paragraphPropertySource.js +76 -22
- package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
- package/dist/docx/parser.js +19 -6
- package/dist/docx/rasterMime.d.ts +64 -0
- package/dist/docx/rasterMime.js +116 -0
- package/dist/docx/replyToComment.js +13 -6
- package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
- package/dist/docx/reviewHistoryNamespace.js +4 -0
- package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
- package/dist/docx/reviewResolutionProvenance.js +153 -0
- package/dist/docx/rezip.d.ts +7 -3
- package/dist/docx/rezip.js +121 -52
- package/dist/docx/runConsolidator.d.ts +2 -58
- package/dist/docx/runConsolidator.js +3 -175
- package/dist/docx/runParser.d.ts +2 -1
- package/dist/docx/runParser.js +156 -116
- package/dist/docx/saveDiagnostics.d.ts +11 -0
- package/dist/docx/saveDiagnostics.js +0 -0
- package/dist/docx/selectiveSave.d.ts +2 -1
- package/dist/docx/selectiveSave.js +14 -1
- package/dist/docx/selectiveXmlPatch.d.ts +16 -8
- package/dist/docx/selectiveXmlPatch.js +74 -2
- package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
- package/dist/docx/serializer/headerFooterSerializer.js +19 -11
- package/dist/docx/serializer/noteSerializer.d.ts +2 -2
- package/dist/docx/serializer/noteSerializer.js +24 -8
- package/dist/docx/serializer/paragraphSerializer.js +41 -4
- package/dist/docx/serializer/partNamespaces.js +6 -3
- package/dist/docx/serializer/runSerializer.js +8 -5
- package/dist/docx/serializer/trackedChangeAttributes.js +7 -1
- package/dist/docx/server/build.js +14 -3
- package/dist/docx/server/createBilingualDocument.js +10 -9
- package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
- package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
- package/dist/docx/settingsParser.d.ts +1 -2
- package/dist/docx/settingsParser.js +5 -6
- package/dist/docx/storyBlockReplay.d.ts +11 -0
- package/dist/docx/storyBlockReplay.js +367 -0
- package/dist/docx/streamingXmlParser.d.ts +31 -1
- package/dist/docx/streamingXmlParser.js +136 -21
- package/dist/docx/structuralXmlPatch.js +8 -1
- package/dist/docx/unzip.d.ts +3 -1
- package/dist/docx/unzip.js +36 -16
- package/dist/docx/verbatimCapture.d.ts +3 -1
- package/dist/docx/verbatimCapture.js +49 -6
- package/dist/i18n/messages/ar.gen.d.ts +5 -0
- package/dist/i18n/messages/ar.gen.js +656 -0
- package/dist/i18n/messages/cs.gen.d.ts +5 -0
- package/dist/i18n/messages/cs.gen.js +656 -0
- package/dist/i18n/messages/de.gen.d.ts +5 -0
- package/dist/i18n/messages/de.gen.js +656 -0
- package/dist/i18n/messages/en.gen.d.ts +5 -0
- package/dist/i18n/messages/en.gen.js +656 -0
- package/dist/i18n/messages/es.gen.d.ts +5 -0
- package/dist/i18n/messages/es.gen.js +656 -0
- package/dist/i18n/messages/et.gen.d.ts +5 -0
- package/dist/i18n/messages/et.gen.js +656 -0
- package/dist/i18n/messages/fr.gen.d.ts +5 -0
- package/dist/i18n/messages/fr.gen.js +656 -0
- package/dist/i18n/messages/he.gen.d.ts +5 -0
- package/dist/i18n/messages/he.gen.js +656 -0
- package/dist/i18n/messages/hi.gen.d.ts +5 -0
- package/dist/i18n/messages/hi.gen.js +656 -0
- package/dist/i18n/messages/hu.gen.d.ts +5 -0
- package/dist/i18n/messages/hu.gen.js +656 -0
- package/dist/i18n/messages/locales.d.ts +5 -0
- package/dist/i18n/messages/locales.js +22 -0
- package/dist/i18n/messages/lt.gen.d.ts +5 -0
- package/dist/i18n/messages/lt.gen.js +656 -0
- package/dist/i18n/messages/lv.gen.d.ts +5 -0
- package/dist/i18n/messages/lv.gen.js +656 -0
- package/dist/i18n/messages/pl.gen.d.ts +5 -0
- package/dist/i18n/messages/pl.gen.js +656 -0
- package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
- package/dist/i18n/messages/pt-BR.gen.js +656 -0
- package/dist/i18n/messages/sk.gen.d.ts +5 -0
- package/dist/i18n/messages/sk.gen.js +656 -0
- package/dist/i18n/messages/tr.gen.d.ts +5 -0
- package/dist/i18n/messages/tr.gen.js +656 -0
- package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
- package/dist/i18n/messages/zh-CN.gen.js +656 -0
- package/dist/i18n/messages.d.ts +2 -3
- package/dist/i18n/messages.js +1 -19
- package/dist/internal/noteMarkerAttrs.d.ts +8 -0
- package/dist/internal/noteMarkerAttrs.js +16 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
- package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
- package/dist/internal/prosemirrorAttrBoundary.js +50 -0
- package/dist/layout-bridge/convert/fixedTableColumnWidths.d.ts +6 -0
- package/dist/layout-bridge/convert/fixedTableColumnWidths.js +23 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
- package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
- package/dist/layout-bridge/convert/noteReferences.js +1 -6
- package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
- package/dist/layout-bridge/convert/tableConversion.js +4 -1
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
- package/dist/layout-painter/index.js +2 -5
- package/dist/managers/DocumentLoaderManager.d.ts +1 -0
- package/dist/managers/DocumentLoaderManager.js +8 -1
- package/dist/markdown/renderBlock.js +15 -1
- package/dist/markdown/renderParagraph.js +9 -13
- package/dist/markdown/renderRuns.js +3 -2
- package/dist/markdown/renderTable.js +2 -0
- package/dist/markdown/types.d.ts +3 -9
- package/dist/paged-layout/editorScrollRoot.d.ts +15 -0
- package/dist/paged-layout/editorScrollRoot.js +72 -0
- package/dist/paged-layout/scrollToPmPosition.js +8 -22
- package/dist/prosemirror/attrs/index.d.ts +2 -12
- package/dist/prosemirror/attrs/index.js +22 -48
- package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
- package/dist/prosemirror/canonicalCommands.d.ts +97 -0
- package/dist/prosemirror/canonicalCommands.js +28 -0
- package/dist/prosemirror/commands/clearParagraphIndent.d.ts +6 -0
- package/dist/prosemirror/commands/clearParagraphIndent.js +25 -0
- package/dist/prosemirror/commands/pageBreak.js +15 -1
- package/dist/prosemirror/commentIdAllocator.d.ts +15 -22
- package/dist/prosemirror/commentIdAllocator.js +50 -25
- package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
- package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
- package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -3
- package/dist/prosemirror/conversion/toProseDoc.js +51 -11
- package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
- package/dist/prosemirror/executeEditorCommand.js +26 -0
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/HistoryExtension.js +97 -5
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +53 -20
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +22 -28
- package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
- package/dist/prosemirror/extensions/marks/markUtils.js +70 -8
- package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
- package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -6
- package/dist/prosemirror/listMarker.d.ts +1 -0
- package/dist/prosemirror/listMarker.js +16 -7
- package/dist/prosemirror/listNumbering.js +26 -5
- package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
- package/dist/prosemirror/listRenderingAttrs.js +3 -0
- package/dist/prosemirror/markupViewNotes.js +3 -2
- package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
- package/dist/prosemirror/noteReferenceReview.js +5 -3
- package/dist/prosemirror/plugins/revisionIds.d.ts +23 -19
- package/dist/prosemirror/plugins/revisionIds.js +110 -29
- package/dist/prosemirror/plugins/suggestionMode.d.ts +2 -2
- package/dist/prosemirror/plugins/suggestionMode.js +246 -42
- package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
- package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
- package/dist/prosemirror/runFormattingFromMarks.js +15 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
- package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
- package/dist/prosemirror/schema/marks.d.ts +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +10 -2
- package/dist/prosemirror/storyListNumbering.js +11 -9
- package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
- package/dist/prosemirror/textInput.d.ts +6 -1
- package/dist/prosemirror/textInput.js +14 -3
- package/dist/prosemirror/trackedRevisionPath.js +2 -1
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
- package/dist/prosemirror/utils/visualLineNavigation.js +6 -8
- package/dist/prosemirror/validation.js +4 -0
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/createDocument.js +2 -2
- package/dist/utils/mergeDocumentContent.d.ts +2 -2
- package/dist/utils/mergeDocumentContent.js +11 -8
- package/package.json +14 -2
package/dist/docx/rezip.js
CHANGED
|
@@ -4,6 +4,7 @@ import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
|
|
|
4
4
|
import { appVersionInSchemaForm, normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
|
|
5
5
|
import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
|
|
6
6
|
import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
|
|
7
|
+
import { registerContentTypeParts } from "./contentTypeRegistry.js";
|
|
7
8
|
import { readDocumentSectionFacts } from "./documentSectionFacts.js";
|
|
8
9
|
import { normalizeDrawingIds } from "./drawingIdNormalization.js";
|
|
9
10
|
import { rebindDrawingImageRelationship } from "./drawingRelationships.js";
|
|
@@ -15,6 +16,7 @@ import { parseNumbering } from "./numberingParser.js";
|
|
|
15
16
|
import { paragraphNumberingReferenceId as paragraphNumberingReferenceId$1 } from "./numberingReference.js";
|
|
16
17
|
import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
|
|
17
18
|
import { normalizeParaIdRangeInXmlParts } from "./paraIdRangeNormalization.js";
|
|
19
|
+
import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
|
|
18
20
|
import { BLOCK_TREE_DESCENT, visitBlockTreeRecords } from "./paragraphTraversal.js";
|
|
19
21
|
import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
|
|
20
22
|
import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
|
|
@@ -30,10 +32,12 @@ import { readRootNamespaceBindings, serializePartElement } from "./serializer/pa
|
|
|
30
32
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
31
33
|
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
32
34
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
35
|
+
import { updateEvenAndOddHeaders } from "./settingsHeaderFooterUpdate.js";
|
|
36
|
+
import { parseSettings } from "./settingsParser.js";
|
|
33
37
|
import { hasCanonicalWordprocessingPrefixes } from "./wordprocessingPrefixes.js";
|
|
34
38
|
import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
|
|
35
39
|
import { TaggedError, panic } from "better-result";
|
|
36
|
-
import { escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
|
|
40
|
+
import { assertValidOoxmlNumericIds, escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
|
|
37
41
|
import { mintRelationshipId } from "@stll/docx-core/model";
|
|
38
42
|
import JSZip from "jszip";
|
|
39
43
|
//#region src/docx/rezip.ts
|
|
@@ -529,12 +533,19 @@ async function processNewHyperlinks(parts, zip, compressionLevel) {
|
|
|
529
533
|
*/
|
|
530
534
|
const normalizePackageIdsInZip = async (zip, compressionLevel) => {
|
|
531
535
|
const xmlParts = /* @__PURE__ */ new Map();
|
|
532
|
-
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.startsWith("word/") && path.endsWith(".xml"))
|
|
536
|
+
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.startsWith("word/") && path.endsWith(".xml")) {
|
|
537
|
+
const xml = await file.async("text");
|
|
538
|
+
assertValidOoxmlNumericIds(xml, path);
|
|
539
|
+
xmlParts.set(path, xml);
|
|
540
|
+
}
|
|
533
541
|
const normalizedParts = normalizeParaIdRangeInXmlParts(normalizeRevisionIdsInXmlParts(xmlParts));
|
|
534
|
-
for (const [path, xml] of normalizedParts) if (xml !== xmlParts.get(path))
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
542
|
+
for (const [path, xml] of normalizedParts) if (xml !== xmlParts.get(path)) {
|
|
543
|
+
assertValidOoxmlNumericIds(xml, path);
|
|
544
|
+
zip.file(path, xml, {
|
|
545
|
+
compression: "DEFLATE",
|
|
546
|
+
compressionOptions: { level: compressionLevel }
|
|
547
|
+
});
|
|
548
|
+
}
|
|
538
549
|
};
|
|
539
550
|
const EXTENDED_PROPERTIES_PATH = "docProps/app.xml";
|
|
540
551
|
/**
|
|
@@ -628,8 +639,8 @@ const normalizeExportDrawingIds = ({ package: docxPackage }) => {
|
|
|
628
639
|
...docxPackage.endnotes !== void 0 ? { endnotes: docxPackage.endnotes } : {}
|
|
629
640
|
});
|
|
630
641
|
};
|
|
631
|
-
const finishRepack = async ({ document, originalZip, outputZip, originalDocument, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, sectionEndpointRemoval, sectionReferenceRemovals }) => {
|
|
632
|
-
await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
|
|
642
|
+
const finishRepack = async ({ document, originalZip, outputZip, originalDocument, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, onDiagnostic, sectionEndpointRemoval, sectionReferenceRemovals }) => {
|
|
643
|
+
document = await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
|
|
633
644
|
await materializeEmbeddedMedia({
|
|
634
645
|
document,
|
|
635
646
|
zip: outputZip,
|
|
@@ -660,7 +671,12 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
660
671
|
compressionOptions: { level: compressionLevel }
|
|
661
672
|
});
|
|
662
673
|
await rebindWatermarkRelIds(document, outputZip, compressionLevel);
|
|
663
|
-
await serializeHeadersFootersToZip(
|
|
674
|
+
await serializeHeadersFootersToZip({
|
|
675
|
+
doc: document,
|
|
676
|
+
zip: outputZip,
|
|
677
|
+
compressionLevel,
|
|
678
|
+
onDiagnostic
|
|
679
|
+
});
|
|
664
680
|
await serializeNotesToZip({
|
|
665
681
|
doc: document,
|
|
666
682
|
originalZip,
|
|
@@ -670,6 +686,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
670
686
|
});
|
|
671
687
|
await serializeNumberingIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
672
688
|
await serializeAddedStylesIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
689
|
+
await serializeHeaderFooterSettingsIntoZip(document, outputZip, compressionLevel);
|
|
673
690
|
await serializeCommentsToZip(document, outputZip, compressionLevel);
|
|
674
691
|
await dropAttachedTemplateReference(outputZip, compressionLevel);
|
|
675
692
|
if (updateModifiedDate && originalCorePropertiesXml) {
|
|
@@ -700,6 +717,7 @@ async function repackDocxWithSectionEndpointRemoval({ document: doc, options, se
|
|
|
700
717
|
originalCorePropertiesXml,
|
|
701
718
|
compressionLevel,
|
|
702
719
|
updateModifiedDate,
|
|
720
|
+
onDiagnostic: options.onDiagnostic,
|
|
703
721
|
...modifiedBy !== void 0 ? { modifiedBy } : {},
|
|
704
722
|
...changedNoteParaIds !== void 0 ? { changedNoteParaIds } : {},
|
|
705
723
|
...sectionEndpointRemoval !== void 0 ? { sectionEndpointRemoval } : {},
|
|
@@ -726,7 +744,7 @@ function repackDocx(doc, options = {}) {
|
|
|
726
744
|
*/
|
|
727
745
|
async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
728
746
|
const { compressionLevel = 6, updateModifiedDate = true, modifiedBy, changedNoteParaIds } = options;
|
|
729
|
-
|
|
747
|
+
let exportDocument = withoutOrphanCommentRanges(doc);
|
|
730
748
|
const newZip = new JSZip();
|
|
731
749
|
for (const [path, file] of Object.entries(rawContent.originalZip.files)) {
|
|
732
750
|
if (file.dir) {
|
|
@@ -740,7 +758,7 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
740
758
|
compressionOptions: { level: compressionLevel }
|
|
741
759
|
});
|
|
742
760
|
}
|
|
743
|
-
await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
|
|
761
|
+
exportDocument = await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
|
|
744
762
|
await materializeEmbeddedMedia({
|
|
745
763
|
document: exportDocument,
|
|
746
764
|
zip: newZip,
|
|
@@ -763,7 +781,13 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
763
781
|
compressionOptions: { level: compressionLevel }
|
|
764
782
|
});
|
|
765
783
|
await rebindWatermarkRelIds(exportDocument, newZip, compressionLevel);
|
|
766
|
-
await serializeHeadersFootersToZip(
|
|
784
|
+
await serializeHeadersFootersToZip({
|
|
785
|
+
doc: exportDocument,
|
|
786
|
+
zip: newZip,
|
|
787
|
+
compressionLevel,
|
|
788
|
+
onDiagnostic: options.onDiagnostic
|
|
789
|
+
});
|
|
790
|
+
await serializeHeaderFooterSettingsIntoZip(exportDocument, newZip, compressionLevel);
|
|
767
791
|
await serializeNotesToZip({
|
|
768
792
|
doc: exportDocument,
|
|
769
793
|
originalZip: rawContent.originalZip,
|
|
@@ -1108,11 +1132,19 @@ function findMaxHeaderFooterNum(zip, prefix) {
|
|
|
1108
1132
|
* relationship.
|
|
1109
1133
|
*/
|
|
1110
1134
|
async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
1111
|
-
if (!
|
|
1112
|
-
doc
|
|
1113
|
-
|
|
1135
|
+
if (!doc.package.headers?.size && !doc.package.footers?.size) return doc;
|
|
1136
|
+
doc = {
|
|
1137
|
+
...doc,
|
|
1138
|
+
package: {
|
|
1139
|
+
...doc.package,
|
|
1140
|
+
relationships: new Map(doc.package.relationships),
|
|
1141
|
+
...doc.package.headers === void 0 ? {} : { headers: new Map(doc.package.headers) },
|
|
1142
|
+
...doc.package.footers === void 0 ? {} : { footers: new Map(doc.package.footers) }
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
const rels = doc.package.relationships ?? panic("The export owns its relationship map.");
|
|
1114
1146
|
const relEntries = [];
|
|
1115
|
-
const
|
|
1147
|
+
const contentTypeParts = [];
|
|
1116
1148
|
let maxHeaderNum = findMaxHeaderFooterNum(zip, "header");
|
|
1117
1149
|
let maxFooterNum = findMaxHeaderFooterNum(zip, "footer");
|
|
1118
1150
|
const relsPath = "word/_rels/document.xml.rels";
|
|
@@ -1135,8 +1167,18 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1135
1167
|
const materialize = (map, relType, prefix, contentType, isHeader) => {
|
|
1136
1168
|
if (!map) return;
|
|
1137
1169
|
for (const rId of [...map.keys()]) {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1170
|
+
const registered = zipRels.get(rId);
|
|
1171
|
+
const existing = registered ?? rels.get(rId);
|
|
1172
|
+
if (existing && existing.type === relType && existing.target) {
|
|
1173
|
+
rels.set(rId, existing);
|
|
1174
|
+
const path = resolveRelativePath(relsPath, existing.target);
|
|
1175
|
+
contentTypeParts.push({
|
|
1176
|
+
partName: `/${path}`,
|
|
1177
|
+
contentType
|
|
1178
|
+
});
|
|
1179
|
+
if (!registered) relEntries.push(`<Relationship Id="${escapeXmlAttribute(rId)}" Type="${relType}" Target="${escapeXmlAttribute(existing.target)}"/>`);
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1140
1182
|
let effectiveRId = rId;
|
|
1141
1183
|
if (existing) {
|
|
1142
1184
|
effectiveRId = `rId${++maxRId}`;
|
|
@@ -1145,6 +1187,12 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1145
1187
|
map.delete(rId);
|
|
1146
1188
|
map.set(effectiveRId, headerFooter);
|
|
1147
1189
|
}
|
|
1190
|
+
const body = doc.package.document;
|
|
1191
|
+
doc.package.document = {
|
|
1192
|
+
...body,
|
|
1193
|
+
content: body.content.map((block) => block.type === "paragraph" && block.sectionProperties !== void 0 ? cloneParagraphWithPropertySource(block, { sectionProperties: structuredClone(block.sectionProperties) }) : block),
|
|
1194
|
+
...body.finalSectionProperties === void 0 ? {} : { finalSectionProperties: structuredClone(body.finalSectionProperties) }
|
|
1195
|
+
};
|
|
1148
1196
|
for (const block of doc.package.document.content) if (block.type === "paragraph") remapRefs(isHeader ? block.sectionProperties?.headerReferences : block.sectionProperties?.footerReferences, rId, effectiveRId);
|
|
1149
1197
|
const finalProps = doc.package.document.finalSectionProperties;
|
|
1150
1198
|
remapRefs(isHeader ? finalProps?.headerReferences : finalProps?.footerReferences, rId, effectiveRId);
|
|
@@ -1156,32 +1204,29 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1156
1204
|
target: filename
|
|
1157
1205
|
});
|
|
1158
1206
|
relEntries.push(`<Relationship Id="${escapeXmlAttribute(effectiveRId)}" Type="${relType}" Target="${filename}"/>`);
|
|
1159
|
-
|
|
1207
|
+
contentTypeParts.push({
|
|
1208
|
+
partName: `/word/${filename}`,
|
|
1209
|
+
contentType
|
|
1210
|
+
});
|
|
1160
1211
|
}
|
|
1161
1212
|
};
|
|
1162
1213
|
materialize(doc.package.headers, RELATIONSHIP_TYPES.header, "header", HEADER_CONTENT_TYPE, true);
|
|
1163
1214
|
materialize(doc.package.footers, RELATIONSHIP_TYPES.footer, "footer", FOOTER_CONTENT_TYPE, false);
|
|
1164
|
-
if (relEntries.length === 0) return;
|
|
1165
1215
|
const compressionOptions = { level: compressionLevel };
|
|
1166
|
-
zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
1216
|
+
if (relEntries.length > 0) zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
1167
1217
|
compression: "DEFLATE",
|
|
1168
1218
|
compressionOptions
|
|
1169
1219
|
});
|
|
1170
1220
|
const ctFile = zip.file("[Content_Types].xml");
|
|
1171
1221
|
if (ctFile) {
|
|
1172
|
-
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1222
|
+
const original = await ctFile.async("text");
|
|
1223
|
+
const updated = registerContentTypeParts(original, contentTypeParts);
|
|
1224
|
+
if (updated !== original) zip.file("[Content_Types].xml", updated, {
|
|
1225
|
+
compression: "DEFLATE",
|
|
1226
|
+
compressionOptions
|
|
1176
1227
|
});
|
|
1177
|
-
if (missing.length > 0) {
|
|
1178
|
-
ctXml = ctXml.replace("</Types>", `${missing.join("")}</Types>`);
|
|
1179
|
-
zip.file("[Content_Types].xml", ctXml, {
|
|
1180
|
-
compression: "DEFLATE",
|
|
1181
|
-
compressionOptions
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
1228
|
}
|
|
1229
|
+
return doc;
|
|
1185
1230
|
}
|
|
1186
1231
|
/**
|
|
1187
1232
|
* A picture watermark is "model-driven" when its raw VML was cleared (so the
|
|
@@ -1292,13 +1337,7 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
|
|
|
1292
1337
|
});
|
|
1293
1338
|
}
|
|
1294
1339
|
}
|
|
1295
|
-
|
|
1296
|
-
* Re-serialize every header/footer the model still owns, keyed by part path.
|
|
1297
|
-
*
|
|
1298
|
-
* `sourceZip` supplies each part as it stands before the save so the rebuilt
|
|
1299
|
-
* root can keep any prefix binding only the source document declared.
|
|
1300
|
-
*/
|
|
1301
|
-
async function collectHeaderFooterUpdates(doc, sourceZip) {
|
|
1340
|
+
async function collectHeaderFooterUpdates(doc, { sourceZip, onDiagnostic }) {
|
|
1302
1341
|
const updates = /* @__PURE__ */ new Map();
|
|
1303
1342
|
const rels = doc.package.relationships;
|
|
1304
1343
|
if (!rels) return updates;
|
|
@@ -1320,19 +1359,21 @@ async function collectHeaderFooterUpdates(doc, sourceZip) {
|
|
|
1320
1359
|
const bindings = sourceFile ? readRootNamespaceBindings(await sourceFile.async("text")) : /* @__PURE__ */ new Map();
|
|
1321
1360
|
updates.set(path, serializeHeaderFooter(headerFooter, {
|
|
1322
1361
|
path,
|
|
1323
|
-
bindings
|
|
1362
|
+
bindings,
|
|
1363
|
+
onDiagnostic,
|
|
1364
|
+
originalBuffer: doc.originalBuffer
|
|
1324
1365
|
}));
|
|
1325
1366
|
}
|
|
1326
1367
|
}
|
|
1327
1368
|
}
|
|
1328
1369
|
return updates;
|
|
1329
1370
|
}
|
|
1330
|
-
|
|
1331
|
-
* Serialize modified headers and footers into the ZIP
|
|
1332
|
-
*/
|
|
1333
|
-
async function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1371
|
+
async function serializeHeadersFootersToZip({ doc, zip, compressionLevel, onDiagnostic }) {
|
|
1334
1372
|
const compressionOptions = { level: compressionLevel };
|
|
1335
|
-
for (const [filename, xml] of await collectHeaderFooterUpdates(doc,
|
|
1373
|
+
for (const [filename, xml] of await collectHeaderFooterUpdates(doc, {
|
|
1374
|
+
sourceZip: zip,
|
|
1375
|
+
onDiagnostic
|
|
1376
|
+
})) zip.file(filename, xml, {
|
|
1336
1377
|
compression: "DEFLATE",
|
|
1337
1378
|
compressionOptions
|
|
1338
1379
|
});
|
|
@@ -1343,7 +1384,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1343
1384
|
if (footnotes.length > 0 || originalFootnotes) if (originalFootnotes) await patchNotePartIntoZip({
|
|
1344
1385
|
conventionalLowerPath: "word/footnotes.xml",
|
|
1345
1386
|
currentXml: serializeFootnotes(footnotes),
|
|
1346
|
-
replacementXml: serializeNewFootnotesPart(footnotes),
|
|
1387
|
+
replacementXml: serializeNewFootnotesPart(footnotes, doc),
|
|
1347
1388
|
baselineFrom: (xml) => serializeFootnotes(parseFootnotes(xml).getNormalFootnotes()),
|
|
1348
1389
|
elementName: "footnote",
|
|
1349
1390
|
changedNoteParaIds,
|
|
@@ -1356,7 +1397,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1356
1397
|
newZip,
|
|
1357
1398
|
partPath: "word/footnotes.xml",
|
|
1358
1399
|
relationshipType: RELATIONSHIP_TYPES.footnotes,
|
|
1359
|
-
serializedPart: serializeNewFootnotesPart(footnotes),
|
|
1400
|
+
serializedPart: serializeNewFootnotesPart(footnotes, doc),
|
|
1360
1401
|
compressionLevel
|
|
1361
1402
|
});
|
|
1362
1403
|
const endnotes = doc.package.endnotes ?? [];
|
|
@@ -1364,7 +1405,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1364
1405
|
if (endnotes.length > 0 || originalEndnotes) if (originalEndnotes) await patchNotePartIntoZip({
|
|
1365
1406
|
conventionalLowerPath: "word/endnotes.xml",
|
|
1366
1407
|
currentXml: serializeEndnotes(endnotes),
|
|
1367
|
-
replacementXml: serializeNewEndnotesPart(endnotes),
|
|
1408
|
+
replacementXml: serializeNewEndnotesPart(endnotes, doc),
|
|
1368
1409
|
baselineFrom: (xml) => serializeEndnotes(parseEndnotes(xml).getNormalEndnotes()),
|
|
1369
1410
|
elementName: "endnote",
|
|
1370
1411
|
changedNoteParaIds,
|
|
@@ -1377,7 +1418,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1377
1418
|
newZip,
|
|
1378
1419
|
partPath: "word/endnotes.xml",
|
|
1379
1420
|
relationshipType: RELATIONSHIP_TYPES.endnotes,
|
|
1380
|
-
serializedPart: serializeNewEndnotesPart(endnotes),
|
|
1421
|
+
serializedPart: serializeNewEndnotesPart(endnotes, doc),
|
|
1381
1422
|
compressionLevel
|
|
1382
1423
|
});
|
|
1383
1424
|
}
|
|
@@ -1406,6 +1447,32 @@ async function materializeNewNotePart({ contentType, newZip, partPath, relations
|
|
|
1406
1447
|
compressionOptions
|
|
1407
1448
|
});
|
|
1408
1449
|
}
|
|
1450
|
+
/** Only this modeled switch changes on story lifecycle edits; preserve all other settings. */
|
|
1451
|
+
async function serializeHeaderFooterSettingsIntoZip(doc, zip, compressionLevel) {
|
|
1452
|
+
if (!doc.package.settings) return;
|
|
1453
|
+
const enabled = doc.package.settings.evenAndOddHeaders;
|
|
1454
|
+
const file = zip.file(SETTINGS_PART);
|
|
1455
|
+
if (!file) {
|
|
1456
|
+
if (enabled === void 0) return;
|
|
1457
|
+
await materializeNewNotePart({
|
|
1458
|
+
contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
|
|
1459
|
+
newZip: zip,
|
|
1460
|
+
partPath: "word/settings.xml",
|
|
1461
|
+
relationshipType: RELATIONSHIP_TYPES.settings,
|
|
1462
|
+
serializedPart: serializeSettingsXml(doc.package.settings),
|
|
1463
|
+
compressionLevel
|
|
1464
|
+
});
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
const xml = await file.async("text");
|
|
1468
|
+
if (parseSettings(xml).evenAndOddHeaders === enabled) return;
|
|
1469
|
+
const patched = updateEvenAndOddHeaders(xml, enabled);
|
|
1470
|
+
if (patched === null) throw new DocxPackageFidelityError("Cannot update malformed header/footer settings");
|
|
1471
|
+
zip.file(file.name, patched, {
|
|
1472
|
+
compression: "DEFLATE",
|
|
1473
|
+
compressionOptions: { level: compressionLevel }
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1409
1476
|
const numberingBaselines = /* @__PURE__ */ new WeakMap();
|
|
1410
1477
|
async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLevel) {
|
|
1411
1478
|
const numbering = doc.package.numbering;
|
|
@@ -1600,7 +1667,7 @@ const notePartXmlFor = ({ patch, originalXml, replacementXml, hasDirtyParagraph,
|
|
|
1600
1667
|
return null;
|
|
1601
1668
|
};
|
|
1602
1669
|
switch (patch.type) {
|
|
1603
|
-
case "patched": return patch.xml === originalXml ?
|
|
1670
|
+
case "patched": return patch.xml === originalXml ? null : patch.xml;
|
|
1604
1671
|
case "refused": switch (patch.reason) {
|
|
1605
1672
|
case "comment-range-balance": return replacementXml;
|
|
1606
1673
|
case "unroutable-paragraph": return keepOriginal();
|
|
@@ -1800,7 +1867,8 @@ async function createDocx(doc, properties = {}) {
|
|
|
1800
1867
|
originalDocument: source.document,
|
|
1801
1868
|
originalCorePropertiesXml: source.corePropertiesXml,
|
|
1802
1869
|
compressionLevel: 6,
|
|
1803
|
-
updateModifiedDate: true
|
|
1870
|
+
updateModifiedDate: true,
|
|
1871
|
+
onDiagnostic: properties.onDiagnostic
|
|
1804
1872
|
});
|
|
1805
1873
|
}
|
|
1806
1874
|
const zip = await createDocumentSeedZip(doc, properties);
|
|
@@ -1811,7 +1879,8 @@ async function createDocx(doc, properties = {}) {
|
|
|
1811
1879
|
originalDocument: void 0,
|
|
1812
1880
|
originalCorePropertiesXml: await zip.file("docProps/core.xml")?.async("text"),
|
|
1813
1881
|
compressionLevel: 6,
|
|
1814
|
-
updateModifiedDate: true
|
|
1882
|
+
updateModifiedDate: true,
|
|
1883
|
+
onDiagnostic: properties.onDiagnostic
|
|
1815
1884
|
});
|
|
1816
1885
|
}
|
|
1817
1886
|
const createDocumentSeedZip = async (doc, properties) => {
|
|
@@ -1,62 +1,6 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { formattingEquals, isTextOnlyRun, runsMergeable } from "@stll/docx-core/ops";
|
|
2
3
|
//#region src/docx/runConsolidator.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Every `TextFormatting` field, named so the comparison below is total.
|
|
5
|
-
*
|
|
6
|
-
* A field this comparison forgets is a field one of two merged runs loses,
|
|
7
|
-
* silently and at parse time. Naming them turns a field added to the model
|
|
8
|
-
* without a comparison into a compile error rather than a fidelity defect.
|
|
9
|
-
*/
|
|
10
|
-
type ComparedTextFormattingField = "bold" | "boldCs" | "italic" | "italicCs" | "underline" | "strike" | "doubleStrike" | "vertAlign" | "smallCaps" | "allCaps" | "hidden" | "noProof" | "color" | "highlight" | "shading" | "fontSize" | "fontSizeCs" | "fontFamily" | "language" | "spacing" | "position" | "scale" | "kerning" | "effect" | "emphasisMark" | "emboss" | "imprint" | "outline" | "shadow" | "rtl" | "cs" | "styleId" | "preserved";
|
|
11
|
-
type ComparedTextFormatting = document_d_exports.ExhaustiveFields<document_d_exports.TextFormatting, ComparedTextFormattingField>;
|
|
12
|
-
/**
|
|
13
|
-
* Check if two TextFormatting objects are equivalent
|
|
14
|
-
*
|
|
15
|
-
* Uses deep comparison of all properties to determine if runs
|
|
16
|
-
* can be merged without losing formatting information.
|
|
17
|
-
*/
|
|
18
|
-
declare function formattingEquals(a: ComparedTextFormatting | undefined, b: ComparedTextFormatting | undefined): boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Check if a run contains only text content
|
|
21
|
-
* (runs with special content like images, fields, etc. should not be merged)
|
|
22
|
-
*/
|
|
23
|
-
declare function isTextOnlyRun(run: document_d_exports.Run): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Check if a run can be merged with another run
|
|
26
|
-
* Runs with breaks, tabs, images, fields, etc. act as merge boundaries
|
|
27
|
-
*/
|
|
28
|
-
declare function canMergeRun(run: document_d_exports.Run): boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Every `Run` field, named by what a merge does with it.
|
|
31
|
-
*
|
|
32
|
-
* The merged run is the survivor spread whole, so a field added to the model
|
|
33
|
-
* is carried over from one side by default. Naming each one here forces the
|
|
34
|
-
* next field to state whether that default is right before it can compile.
|
|
35
|
-
*/
|
|
36
|
-
type MergeDecidedRunField =
|
|
37
|
-
/** Identical for every run. */
|
|
38
|
-
"type" |
|
|
39
|
-
/** Concatenated; see {@link mergeRunContent}. */
|
|
40
|
-
"content" |
|
|
41
|
-
/** Must be equal, and the survivor keeps it. */
|
|
42
|
-
"formatting" |
|
|
43
|
-
/** Must be equal, and the survivor keeps it. */
|
|
44
|
-
"preservedAttributes" |
|
|
45
|
-
/** Refuses the merge outright; see {@link canMergeRun}. */
|
|
46
|
-
"propertyChanges";
|
|
47
|
-
type MergeDecidedRun = document_d_exports.ExhaustiveFields<document_d_exports.Run, MergeDecidedRunField>;
|
|
48
|
-
/**
|
|
49
|
-
* May two adjacent runs become one?
|
|
50
|
-
*
|
|
51
|
-
* Every consolidation site asks this one question, because a merge that any
|
|
52
|
-
* one site decides differently is a merge the next parse undoes. A run holds
|
|
53
|
-
* three records a merged run can hold only one of: its typed formatting, the
|
|
54
|
-
* attributes `w:r` carried that the model has no field for, and the `w:rPr`
|
|
55
|
-
* children no reader took a value from. Merging two runs that disagree on any
|
|
56
|
-
* of them discards the loser's copy, which is how both runs' `w:rsid*` used to
|
|
57
|
-
* vanish at parse time — before the editor, before any gate could see it.
|
|
58
|
-
*/
|
|
59
|
-
declare function runsMergeable(a: MergeDecidedRun, b: MergeDecidedRun): boolean;
|
|
60
4
|
/**
|
|
61
5
|
* Consolidate an array of runs by merging consecutive runs with identical formatting
|
|
62
6
|
*
|
|
@@ -93,4 +37,4 @@ declare function getConsolidationStats(originalParagraphs: document_d_exports.Pa
|
|
|
93
37
|
reductionPercentage: number;
|
|
94
38
|
};
|
|
95
39
|
//#endregion
|
|
96
|
-
export {
|
|
40
|
+
export { consolidateParagraph, consolidateParagraphContent, consolidateRuns, countRuns, formattingEquals, getConsolidationStats, isTextOnlyRun, runsMergeable };
|
|
@@ -1,180 +1,8 @@
|
|
|
1
1
|
import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
|
|
2
2
|
import { runHoldsPayload } from "./runPayload.js";
|
|
3
|
+
import { formattingEquals, isTextOnlyRun, mergeRunContent, runsMergeable, runsMergeable as runsMergeable$1 } from "@stll/docx-core/ops";
|
|
3
4
|
//#region src/docx/runConsolidator.ts
|
|
4
5
|
/**
|
|
5
|
-
* Check if two TextFormatting objects are equivalent
|
|
6
|
-
*
|
|
7
|
-
* Uses deep comparison of all properties to determine if runs
|
|
8
|
-
* can be merged without losing formatting information.
|
|
9
|
-
*/
|
|
10
|
-
function formattingEquals(a, b) {
|
|
11
|
-
if (!a && !b) return true;
|
|
12
|
-
if (!a || !b) return false;
|
|
13
|
-
if (a.bold !== b.bold) return false;
|
|
14
|
-
if (a.boldCs !== b.boldCs) return false;
|
|
15
|
-
if (a.italic !== b.italic) return false;
|
|
16
|
-
if (a.italicCs !== b.italicCs) return false;
|
|
17
|
-
if (a.strike !== b.strike) return false;
|
|
18
|
-
if (a.doubleStrike !== b.doubleStrike) return false;
|
|
19
|
-
if (a.smallCaps !== b.smallCaps) return false;
|
|
20
|
-
if (a.allCaps !== b.allCaps) return false;
|
|
21
|
-
if (a.hidden !== b.hidden) return false;
|
|
22
|
-
if (a.noProof !== b.noProof) return false;
|
|
23
|
-
if (a.emboss !== b.emboss) return false;
|
|
24
|
-
if (a.imprint !== b.imprint) return false;
|
|
25
|
-
if (a.outline !== b.outline) return false;
|
|
26
|
-
if (a.shadow !== b.shadow) return false;
|
|
27
|
-
if (a.rtl !== b.rtl) return false;
|
|
28
|
-
if (a.cs !== b.cs) return false;
|
|
29
|
-
if (a.fontSize !== b.fontSize) return false;
|
|
30
|
-
if (a.fontSizeCs !== b.fontSizeCs) return false;
|
|
31
|
-
if (a.spacing !== b.spacing) return false;
|
|
32
|
-
if (a.position !== b.position) return false;
|
|
33
|
-
if (a.scale !== b.scale) return false;
|
|
34
|
-
if (a.kerning !== b.kerning) return false;
|
|
35
|
-
if (a.vertAlign !== b.vertAlign) return false;
|
|
36
|
-
if (a.highlight !== b.highlight) return false;
|
|
37
|
-
if (a.effect !== b.effect) return false;
|
|
38
|
-
if (a.emphasisMark !== b.emphasisMark) return false;
|
|
39
|
-
if (a.styleId !== b.styleId) return false;
|
|
40
|
-
if (!underlineEquals(a.underline, b.underline)) return false;
|
|
41
|
-
if (!colorEquals(a.color, b.color)) return false;
|
|
42
|
-
if (!shadingEquals(a.shading, b.shading)) return false;
|
|
43
|
-
if (!fontFamilyEquals(a.fontFamily, b.fontFamily)) return false;
|
|
44
|
-
if (!languageEquals(a.language, b.language)) return false;
|
|
45
|
-
if (!preservedMarkupEquals(a.preserved, b.preserved)) return false;
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Compare two verbatim sinks position by position.
|
|
50
|
-
*
|
|
51
|
-
* The sink is ordered — by `index`, then by source order within an index — and
|
|
52
|
-
* the order is what puts the markup back between the same modelled siblings,
|
|
53
|
-
* so equality is by sequence, not by set.
|
|
54
|
-
*/
|
|
55
|
-
function preservedMarkupEquals(a, b) {
|
|
56
|
-
const left = a?.children ?? [];
|
|
57
|
-
const right = b?.children ?? [];
|
|
58
|
-
if (left.length !== right.length) return false;
|
|
59
|
-
return left.every(({ index, xml }, position) => {
|
|
60
|
-
const other = right[position];
|
|
61
|
-
return other !== void 0 && other.index === index && other.xml === xml;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Compare two attribute remainders as sets.
|
|
66
|
-
*
|
|
67
|
-
* An element cannot carry one expanded name twice, so (namespace, name) orders
|
|
68
|
-
* a remainder totally and the sorted keys compare exactly. Order is a set
|
|
69
|
-
* comparison rather than a sequence one because attribute order in XML says
|
|
70
|
-
* nothing: two runs that spelled the same attributes in a different order
|
|
71
|
-
* carry the same remainder and may still become one run.
|
|
72
|
-
*/
|
|
73
|
-
function preservedAttributesEqual(a, b) {
|
|
74
|
-
const left = canonicalRemainderKeys(a);
|
|
75
|
-
const right = canonicalRemainderKeys(b);
|
|
76
|
-
return left.length === right.length && left.every((key, index) => key === right[index]);
|
|
77
|
-
}
|
|
78
|
-
/** `\u0000` cannot appear in an XML name, namespace URI or attribute value. */
|
|
79
|
-
const REMAINDER_KEY_SEPARATOR = "\0";
|
|
80
|
-
const canonicalRemainderKeys = (attributes) => (attributes ?? []).map(({ namespace, name, value }) => [
|
|
81
|
-
namespace ?? "",
|
|
82
|
-
name,
|
|
83
|
-
value
|
|
84
|
-
].join(REMAINDER_KEY_SEPARATOR)).sort();
|
|
85
|
-
function languageEquals(a, b) {
|
|
86
|
-
return a?.val === b?.val && a?.eastAsia === b?.eastAsia && a?.bidi === b?.bidi;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Compare underline settings
|
|
90
|
-
*/
|
|
91
|
-
function underlineEquals(a, b) {
|
|
92
|
-
if (!a && !b) return true;
|
|
93
|
-
if (!a || !b) return false;
|
|
94
|
-
if (a.style !== b.style) return false;
|
|
95
|
-
return colorEquals(a.color, b.color);
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Compare color values
|
|
99
|
-
*/
|
|
100
|
-
function colorEquals(a, b) {
|
|
101
|
-
if (!a && !b) return true;
|
|
102
|
-
if (!a || !b) return false;
|
|
103
|
-
return a.rgb === b.rgb && a.auto === b.auto && a.themeColor === b.themeColor && a.themeTint === b.themeTint && a.themeShade === b.themeShade;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Compare shading properties
|
|
107
|
-
*/
|
|
108
|
-
function shadingEquals(a, b) {
|
|
109
|
-
if (!a && !b) return true;
|
|
110
|
-
if (!a || !b) return false;
|
|
111
|
-
if (a.pattern !== b.pattern) return false;
|
|
112
|
-
if (!colorEquals(a.color, b.color)) return false;
|
|
113
|
-
if (!colorEquals(a.fill, b.fill)) return false;
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Compare font family settings
|
|
118
|
-
*/
|
|
119
|
-
function fontFamilyEquals(a, b) {
|
|
120
|
-
if (!a && !b) return true;
|
|
121
|
-
if (!a || !b) return false;
|
|
122
|
-
return a.ascii === b.ascii && a.hAnsi === b.hAnsi && a.eastAsia === b.eastAsia && a.cs === b.cs && a.asciiTheme === b.asciiTheme && a.hAnsiTheme === b.hAnsiTheme && a.eastAsiaTheme === b.eastAsiaTheme && a.csTheme === b.csTheme;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Check if a run contains only text content
|
|
126
|
-
* (runs with special content like images, fields, etc. should not be merged)
|
|
127
|
-
*/
|
|
128
|
-
function isTextOnlyRun(run) {
|
|
129
|
-
return run.content.every((c) => c.type === "text" || c.type === "softHyphen" || c.type === "noBreakHyphen");
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Check if run content can be merged (simple text types)
|
|
133
|
-
*/
|
|
134
|
-
function isMergeableContent(content) {
|
|
135
|
-
return content.type === "text" || content.type === "softHyphen" || content.type === "noBreakHyphen";
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Check if a run can be merged with another run
|
|
139
|
-
* Runs with breaks, tabs, images, fields, etc. act as merge boundaries
|
|
140
|
-
*/
|
|
141
|
-
function canMergeRun(run) {
|
|
142
|
-
if (run.propertyChanges && run.propertyChanges.length > 0) return false;
|
|
143
|
-
if (!runHoldsPayload(run)) return false;
|
|
144
|
-
return run.content.every(isMergeableContent);
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* May two adjacent runs become one?
|
|
148
|
-
*
|
|
149
|
-
* Every consolidation site asks this one question, because a merge that any
|
|
150
|
-
* one site decides differently is a merge the next parse undoes. A run holds
|
|
151
|
-
* three records a merged run can hold only one of: its typed formatting, the
|
|
152
|
-
* attributes `w:r` carried that the model has no field for, and the `w:rPr`
|
|
153
|
-
* children no reader took a value from. Merging two runs that disagree on any
|
|
154
|
-
* of them discards the loser's copy, which is how both runs' `w:rsid*` used to
|
|
155
|
-
* vanish at parse time — before the editor, before any gate could see it.
|
|
156
|
-
*/
|
|
157
|
-
function runsMergeable(a, b) {
|
|
158
|
-
return canMergeRun(a) && canMergeRun(b) && formattingEquals(a.formatting, b.formatting) && preservedAttributesEqual(a.preservedAttributes, b.preservedAttributes);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Merge the content of two runs into a single content array
|
|
162
|
-
*/
|
|
163
|
-
function mergeRunContent(content1, content2) {
|
|
164
|
-
const result = [];
|
|
165
|
-
for (const c of content1) result.push(c);
|
|
166
|
-
const lastText = result.at(-1);
|
|
167
|
-
const firstText = content2[0];
|
|
168
|
-
if (lastText?.type === "text" && firstText?.type === "text") {
|
|
169
|
-
result[result.length - 1] = {
|
|
170
|
-
type: "text",
|
|
171
|
-
text: lastText.text + firstText.text
|
|
172
|
-
};
|
|
173
|
-
for (let i = 1; i < content2.length; i++) result.push(content2[i]);
|
|
174
|
-
} else for (const c of content2) result.push(c);
|
|
175
|
-
return result;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
6
|
* Consolidate an array of runs by merging consecutive runs with identical formatting
|
|
179
7
|
*
|
|
180
8
|
* @param runs - Array of runs to consolidate
|
|
@@ -200,7 +28,7 @@ function consolidateRuns(runs) {
|
|
|
200
28
|
};
|
|
201
29
|
continue;
|
|
202
30
|
}
|
|
203
|
-
if (runsMergeable(current, run)) current = {
|
|
31
|
+
if (runsMergeable$1(current, run)) current = {
|
|
204
32
|
...current,
|
|
205
33
|
content: mergeRunContent(current.content, run.content)
|
|
206
34
|
};
|
|
@@ -276,4 +104,4 @@ function getConsolidationStats(originalParagraphs, consolidatedParagraphs) {
|
|
|
276
104
|
};
|
|
277
105
|
}
|
|
278
106
|
//#endregion
|
|
279
|
-
export {
|
|
107
|
+
export { consolidateParagraph, consolidateParagraphContent, consolidateRuns, countRuns, formattingEquals, getConsolidationStats, isTextOnlyRun, runsMergeable };
|
package/dist/docx/runParser.d.ts
CHANGED
|
@@ -60,7 +60,8 @@ declare const isBaselineVertAlign: (vertAlign: document_d_exports.TextFormatting
|
|
|
60
60
|
* it beat are not kept.
|
|
61
61
|
*
|
|
62
62
|
* An element that states nothing at all still yields a record: `undefined` here
|
|
63
|
-
* means the owner carried no `w:rPr`,
|
|
63
|
+
* means the owner carried no `w:rPr`, or a paragraph-mark carrier contained
|
|
64
|
+
* only metadata owned by the paragraph. An authored empty set remains {}.
|
|
64
65
|
*/
|
|
65
66
|
declare function parseRunProperties(rPr: XmlElement | null, theme: document_d_exports.Theme | null, owner: RunPropertyOwner): document_d_exports.TextFormatting | undefined;
|
|
66
67
|
/**
|