@stll/folio-core 0.54.2 → 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.js +22 -1
- package/dist/ai-edits/headless.js +1 -0
- 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/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/ensureParaIds.d.ts +7 -1
- package/dist/docx/ensureParaIds.js +163 -211
- 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/noteIds.d.ts +5 -0
- package/dist/docx/noteIds.js +6 -2
- package/dist/docx/noteReferenceMark.d.ts +3 -1
- package/dist/docx/noteReferenceMark.js +15 -14
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +17 -3
- 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/parser.js +2 -1
- 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 +107 -45
- 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 +4 -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 +2 -1
- package/dist/docx/serializer/runSerializer.js +3 -1
- 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 +22 -1
- package/dist/docx/streamingXmlParser.js +49 -3
- package/dist/docx/structuralXmlPatch.js +8 -1
- 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/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/toFlowBlocks.d.ts +2 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
- 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/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/pageBreak.js +15 -1
- 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/ParagraphExtension.js +50 -19
- 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/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/suggestionMode.js +50 -2
- 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/textBoxAnchorAttrs.d.ts +2 -1
- package/dist/prosemirror/textInput.js +5 -1
- 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/validation.js +4 -0
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/createDocument.js +2 -2
- 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,6 +32,8 @@ 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";
|
|
@@ -635,8 +639,8 @@ const normalizeExportDrawingIds = ({ package: docxPackage }) => {
|
|
|
635
639
|
...docxPackage.endnotes !== void 0 ? { endnotes: docxPackage.endnotes } : {}
|
|
636
640
|
});
|
|
637
641
|
};
|
|
638
|
-
const finishRepack = async ({ document, originalZip, outputZip, originalDocument, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, sectionEndpointRemoval, sectionReferenceRemovals }) => {
|
|
639
|
-
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);
|
|
640
644
|
await materializeEmbeddedMedia({
|
|
641
645
|
document,
|
|
642
646
|
zip: outputZip,
|
|
@@ -667,7 +671,12 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
667
671
|
compressionOptions: { level: compressionLevel }
|
|
668
672
|
});
|
|
669
673
|
await rebindWatermarkRelIds(document, outputZip, compressionLevel);
|
|
670
|
-
await serializeHeadersFootersToZip(
|
|
674
|
+
await serializeHeadersFootersToZip({
|
|
675
|
+
doc: document,
|
|
676
|
+
zip: outputZip,
|
|
677
|
+
compressionLevel,
|
|
678
|
+
onDiagnostic
|
|
679
|
+
});
|
|
671
680
|
await serializeNotesToZip({
|
|
672
681
|
doc: document,
|
|
673
682
|
originalZip,
|
|
@@ -677,6 +686,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
677
686
|
});
|
|
678
687
|
await serializeNumberingIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
679
688
|
await serializeAddedStylesIntoZip(document, originalZip, outputZip, compressionLevel);
|
|
689
|
+
await serializeHeaderFooterSettingsIntoZip(document, outputZip, compressionLevel);
|
|
680
690
|
await serializeCommentsToZip(document, outputZip, compressionLevel);
|
|
681
691
|
await dropAttachedTemplateReference(outputZip, compressionLevel);
|
|
682
692
|
if (updateModifiedDate && originalCorePropertiesXml) {
|
|
@@ -707,6 +717,7 @@ async function repackDocxWithSectionEndpointRemoval({ document: doc, options, se
|
|
|
707
717
|
originalCorePropertiesXml,
|
|
708
718
|
compressionLevel,
|
|
709
719
|
updateModifiedDate,
|
|
720
|
+
onDiagnostic: options.onDiagnostic,
|
|
710
721
|
...modifiedBy !== void 0 ? { modifiedBy } : {},
|
|
711
722
|
...changedNoteParaIds !== void 0 ? { changedNoteParaIds } : {},
|
|
712
723
|
...sectionEndpointRemoval !== void 0 ? { sectionEndpointRemoval } : {},
|
|
@@ -733,7 +744,7 @@ function repackDocx(doc, options = {}) {
|
|
|
733
744
|
*/
|
|
734
745
|
async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
735
746
|
const { compressionLevel = 6, updateModifiedDate = true, modifiedBy, changedNoteParaIds } = options;
|
|
736
|
-
|
|
747
|
+
let exportDocument = withoutOrphanCommentRanges(doc);
|
|
737
748
|
const newZip = new JSZip();
|
|
738
749
|
for (const [path, file] of Object.entries(rawContent.originalZip.files)) {
|
|
739
750
|
if (file.dir) {
|
|
@@ -747,7 +758,7 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
747
758
|
compressionOptions: { level: compressionLevel }
|
|
748
759
|
});
|
|
749
760
|
}
|
|
750
|
-
await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
|
|
761
|
+
exportDocument = await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
|
|
751
762
|
await materializeEmbeddedMedia({
|
|
752
763
|
document: exportDocument,
|
|
753
764
|
zip: newZip,
|
|
@@ -770,7 +781,13 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
770
781
|
compressionOptions: { level: compressionLevel }
|
|
771
782
|
});
|
|
772
783
|
await rebindWatermarkRelIds(exportDocument, newZip, compressionLevel);
|
|
773
|
-
await serializeHeadersFootersToZip(
|
|
784
|
+
await serializeHeadersFootersToZip({
|
|
785
|
+
doc: exportDocument,
|
|
786
|
+
zip: newZip,
|
|
787
|
+
compressionLevel,
|
|
788
|
+
onDiagnostic: options.onDiagnostic
|
|
789
|
+
});
|
|
790
|
+
await serializeHeaderFooterSettingsIntoZip(exportDocument, newZip, compressionLevel);
|
|
774
791
|
await serializeNotesToZip({
|
|
775
792
|
doc: exportDocument,
|
|
776
793
|
originalZip: rawContent.originalZip,
|
|
@@ -1115,11 +1132,19 @@ function findMaxHeaderFooterNum(zip, prefix) {
|
|
|
1115
1132
|
* relationship.
|
|
1116
1133
|
*/
|
|
1117
1134
|
async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
1118
|
-
if (!
|
|
1119
|
-
doc
|
|
1120
|
-
|
|
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.");
|
|
1121
1146
|
const relEntries = [];
|
|
1122
|
-
const
|
|
1147
|
+
const contentTypeParts = [];
|
|
1123
1148
|
let maxHeaderNum = findMaxHeaderFooterNum(zip, "header");
|
|
1124
1149
|
let maxFooterNum = findMaxHeaderFooterNum(zip, "footer");
|
|
1125
1150
|
const relsPath = "word/_rels/document.xml.rels";
|
|
@@ -1142,8 +1167,18 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1142
1167
|
const materialize = (map, relType, prefix, contentType, isHeader) => {
|
|
1143
1168
|
if (!map) return;
|
|
1144
1169
|
for (const rId of [...map.keys()]) {
|
|
1145
|
-
const
|
|
1146
|
-
|
|
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
|
+
}
|
|
1147
1182
|
let effectiveRId = rId;
|
|
1148
1183
|
if (existing) {
|
|
1149
1184
|
effectiveRId = `rId${++maxRId}`;
|
|
@@ -1152,6 +1187,12 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1152
1187
|
map.delete(rId);
|
|
1153
1188
|
map.set(effectiveRId, headerFooter);
|
|
1154
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
|
+
};
|
|
1155
1196
|
for (const block of doc.package.document.content) if (block.type === "paragraph") remapRefs(isHeader ? block.sectionProperties?.headerReferences : block.sectionProperties?.footerReferences, rId, effectiveRId);
|
|
1156
1197
|
const finalProps = doc.package.document.finalSectionProperties;
|
|
1157
1198
|
remapRefs(isHeader ? finalProps?.headerReferences : finalProps?.footerReferences, rId, effectiveRId);
|
|
@@ -1163,32 +1204,29 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1163
1204
|
target: filename
|
|
1164
1205
|
});
|
|
1165
1206
|
relEntries.push(`<Relationship Id="${escapeXmlAttribute(effectiveRId)}" Type="${relType}" Target="${filename}"/>`);
|
|
1166
|
-
|
|
1207
|
+
contentTypeParts.push({
|
|
1208
|
+
partName: `/word/${filename}`,
|
|
1209
|
+
contentType
|
|
1210
|
+
});
|
|
1167
1211
|
}
|
|
1168
1212
|
};
|
|
1169
1213
|
materialize(doc.package.headers, RELATIONSHIP_TYPES.header, "header", HEADER_CONTENT_TYPE, true);
|
|
1170
1214
|
materialize(doc.package.footers, RELATIONSHIP_TYPES.footer, "footer", FOOTER_CONTENT_TYPE, false);
|
|
1171
|
-
if (relEntries.length === 0) return;
|
|
1172
1215
|
const compressionOptions = { level: compressionLevel };
|
|
1173
|
-
zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
1216
|
+
if (relEntries.length > 0) zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
1174
1217
|
compression: "DEFLATE",
|
|
1175
1218
|
compressionOptions
|
|
1176
1219
|
});
|
|
1177
1220
|
const ctFile = zip.file("[Content_Types].xml");
|
|
1178
1221
|
if (ctFile) {
|
|
1179
|
-
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
|
1183
1227
|
});
|
|
1184
|
-
if (missing.length > 0) {
|
|
1185
|
-
ctXml = ctXml.replace("</Types>", `${missing.join("")}</Types>`);
|
|
1186
|
-
zip.file("[Content_Types].xml", ctXml, {
|
|
1187
|
-
compression: "DEFLATE",
|
|
1188
|
-
compressionOptions
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
1228
|
}
|
|
1229
|
+
return doc;
|
|
1192
1230
|
}
|
|
1193
1231
|
/**
|
|
1194
1232
|
* A picture watermark is "model-driven" when its raw VML was cleared (so the
|
|
@@ -1299,13 +1337,7 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
|
|
|
1299
1337
|
});
|
|
1300
1338
|
}
|
|
1301
1339
|
}
|
|
1302
|
-
|
|
1303
|
-
* Re-serialize every header/footer the model still owns, keyed by part path.
|
|
1304
|
-
*
|
|
1305
|
-
* `sourceZip` supplies each part as it stands before the save so the rebuilt
|
|
1306
|
-
* root can keep any prefix binding only the source document declared.
|
|
1307
|
-
*/
|
|
1308
|
-
async function collectHeaderFooterUpdates(doc, sourceZip) {
|
|
1340
|
+
async function collectHeaderFooterUpdates(doc, { sourceZip, onDiagnostic }) {
|
|
1309
1341
|
const updates = /* @__PURE__ */ new Map();
|
|
1310
1342
|
const rels = doc.package.relationships;
|
|
1311
1343
|
if (!rels) return updates;
|
|
@@ -1327,19 +1359,21 @@ async function collectHeaderFooterUpdates(doc, sourceZip) {
|
|
|
1327
1359
|
const bindings = sourceFile ? readRootNamespaceBindings(await sourceFile.async("text")) : /* @__PURE__ */ new Map();
|
|
1328
1360
|
updates.set(path, serializeHeaderFooter(headerFooter, {
|
|
1329
1361
|
path,
|
|
1330
|
-
bindings
|
|
1362
|
+
bindings,
|
|
1363
|
+
onDiagnostic,
|
|
1364
|
+
originalBuffer: doc.originalBuffer
|
|
1331
1365
|
}));
|
|
1332
1366
|
}
|
|
1333
1367
|
}
|
|
1334
1368
|
}
|
|
1335
1369
|
return updates;
|
|
1336
1370
|
}
|
|
1337
|
-
|
|
1338
|
-
* Serialize modified headers and footers into the ZIP
|
|
1339
|
-
*/
|
|
1340
|
-
async function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1371
|
+
async function serializeHeadersFootersToZip({ doc, zip, compressionLevel, onDiagnostic }) {
|
|
1341
1372
|
const compressionOptions = { level: compressionLevel };
|
|
1342
|
-
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, {
|
|
1343
1377
|
compression: "DEFLATE",
|
|
1344
1378
|
compressionOptions
|
|
1345
1379
|
});
|
|
@@ -1350,7 +1384,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1350
1384
|
if (footnotes.length > 0 || originalFootnotes) if (originalFootnotes) await patchNotePartIntoZip({
|
|
1351
1385
|
conventionalLowerPath: "word/footnotes.xml",
|
|
1352
1386
|
currentXml: serializeFootnotes(footnotes),
|
|
1353
|
-
replacementXml: serializeNewFootnotesPart(footnotes),
|
|
1387
|
+
replacementXml: serializeNewFootnotesPart(footnotes, doc),
|
|
1354
1388
|
baselineFrom: (xml) => serializeFootnotes(parseFootnotes(xml).getNormalFootnotes()),
|
|
1355
1389
|
elementName: "footnote",
|
|
1356
1390
|
changedNoteParaIds,
|
|
@@ -1363,7 +1397,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1363
1397
|
newZip,
|
|
1364
1398
|
partPath: "word/footnotes.xml",
|
|
1365
1399
|
relationshipType: RELATIONSHIP_TYPES.footnotes,
|
|
1366
|
-
serializedPart: serializeNewFootnotesPart(footnotes),
|
|
1400
|
+
serializedPart: serializeNewFootnotesPart(footnotes, doc),
|
|
1367
1401
|
compressionLevel
|
|
1368
1402
|
});
|
|
1369
1403
|
const endnotes = doc.package.endnotes ?? [];
|
|
@@ -1371,7 +1405,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1371
1405
|
if (endnotes.length > 0 || originalEndnotes) if (originalEndnotes) await patchNotePartIntoZip({
|
|
1372
1406
|
conventionalLowerPath: "word/endnotes.xml",
|
|
1373
1407
|
currentXml: serializeEndnotes(endnotes),
|
|
1374
|
-
replacementXml: serializeNewEndnotesPart(endnotes),
|
|
1408
|
+
replacementXml: serializeNewEndnotesPart(endnotes, doc),
|
|
1375
1409
|
baselineFrom: (xml) => serializeEndnotes(parseEndnotes(xml).getNormalEndnotes()),
|
|
1376
1410
|
elementName: "endnote",
|
|
1377
1411
|
changedNoteParaIds,
|
|
@@ -1384,7 +1418,7 @@ async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel,
|
|
|
1384
1418
|
newZip,
|
|
1385
1419
|
partPath: "word/endnotes.xml",
|
|
1386
1420
|
relationshipType: RELATIONSHIP_TYPES.endnotes,
|
|
1387
|
-
serializedPart: serializeNewEndnotesPart(endnotes),
|
|
1421
|
+
serializedPart: serializeNewEndnotesPart(endnotes, doc),
|
|
1388
1422
|
compressionLevel
|
|
1389
1423
|
});
|
|
1390
1424
|
}
|
|
@@ -1413,6 +1447,32 @@ async function materializeNewNotePart({ contentType, newZip, partPath, relations
|
|
|
1413
1447
|
compressionOptions
|
|
1414
1448
|
});
|
|
1415
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
|
+
}
|
|
1416
1476
|
const numberingBaselines = /* @__PURE__ */ new WeakMap();
|
|
1417
1477
|
async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLevel) {
|
|
1418
1478
|
const numbering = doc.package.numbering;
|
|
@@ -1807,7 +1867,8 @@ async function createDocx(doc, properties = {}) {
|
|
|
1807
1867
|
originalDocument: source.document,
|
|
1808
1868
|
originalCorePropertiesXml: source.corePropertiesXml,
|
|
1809
1869
|
compressionLevel: 6,
|
|
1810
|
-
updateModifiedDate: true
|
|
1870
|
+
updateModifiedDate: true,
|
|
1871
|
+
onDiagnostic: properties.onDiagnostic
|
|
1811
1872
|
});
|
|
1812
1873
|
}
|
|
1813
1874
|
const zip = await createDocumentSeedZip(doc, properties);
|
|
@@ -1818,7 +1879,8 @@ async function createDocx(doc, properties = {}) {
|
|
|
1818
1879
|
originalDocument: void 0,
|
|
1819
1880
|
originalCorePropertiesXml: await zip.file("docProps/core.xml")?.async("text"),
|
|
1820
1881
|
compressionLevel: 6,
|
|
1821
|
-
updateModifiedDate: true
|
|
1882
|
+
updateModifiedDate: true,
|
|
1883
|
+
onDiagnostic: properties.onDiagnostic
|
|
1822
1884
|
});
|
|
1823
1885
|
}
|
|
1824
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
|
/**
|