@stll/folio-core 0.54.2 → 0.56.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 +185 -33
- package/dist/ai-edits/headless.js +3 -2
- package/dist/ai-edits/types.d.ts +7 -1
- package/dist/compare/compare.js +1 -0
- package/dist/compare/content-types.d.ts +3 -15
- package/dist/compare/reproducible-package.d.ts +0 -17
- package/dist/compare/reproducible-package.js +12 -18
- package/dist/controller/canonicalClipboard.d.ts +23 -0
- package/dist/controller/canonicalClipboard.js +476 -0
- package/dist/controller/canonicalClipboardResources.d.ts +21 -0
- package/dist/controller/canonicalClipboardResources.js +382 -0
- package/dist/controller/canonicalComposition.d.ts +53 -0
- package/dist/controller/canonicalComposition.js +296 -0
- package/dist/controller/canonicalInlineProjection.d.ts +113 -0
- package/dist/controller/canonicalInlineProjection.js +393 -0
- package/dist/controller/canonicalInput.d.ts +54 -0
- package/dist/controller/canonicalInput.js +377 -0
- package/dist/controller/canonicalInputTimer.d.ts +7 -0
- package/dist/controller/canonicalInputTimer.js +11 -0
- package/dist/controller/canonicalOperations.d.ts +22 -0
- package/dist/controller/canonicalOperations.js +85 -0
- package/dist/controller/canonicalPublicOperations.d.ts +48 -0
- package/dist/controller/canonicalPublicOperations.js +556 -0
- package/dist/controller/canonicalReview.d.ts +12 -0
- package/dist/controller/canonicalReview.js +19 -0
- package/dist/controller/canonicalSession.d.ts +167 -0
- package/dist/controller/canonicalSession.js +1186 -0
- package/dist/controller/canonicalStoryEditor.d.ts +36 -0
- package/dist/controller/canonicalStoryEditor.js +87 -0
- package/dist/controller/canonicalStructure.d.ts +16 -0
- package/dist/controller/canonicalStructure.js +322 -0
- package/dist/controller/canonicalTocSelection.d.ts +11 -0
- package/dist/controller/canonicalTocSelection.js +43 -0
- package/dist/controller/folioEditor.d.ts +3 -10
- package/dist/controller/folioEditor.js +14 -4
- package/dist/controller/headerFooterEditorManager.d.ts +5 -0
- package/dist/controller/headerFooterEditorManager.js +72 -3
- package/dist/controller/hiddenEditorApi.d.ts +45 -0
- package/dist/controller/hiddenEditorApi.js +29 -8
- package/dist/controller/hiddenEditorManager.d.ts +13 -2
- package/dist/controller/hiddenEditorManager.js +639 -31
- package/dist/controller/layoutPipeline.d.ts +1 -1
- package/dist/controller/layoutPipeline.js +2 -0
- package/dist/controller/noteEditorManager.d.ts +5 -0
- package/dist/controller/noteEditorManager.js +114 -15
- package/dist/document-operations.d.ts +8 -1
- package/dist/document-operations.js +6 -4
- package/dist/docx/attributeRemainder.js +1 -0
- package/dist/docx/blockPlainText.d.ts +1 -1
- package/dist/docx/blockPlainText.js +11 -3
- package/dist/docx/canonicalResourceSave.d.ts +16 -0
- package/dist/docx/canonicalResourceSave.js +27 -0
- package/dist/docx/canonicalSave.d.ts +29 -0
- package/dist/docx/canonicalSave.js +101 -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 +74 -0
- package/dist/docx/contentTypeRegistry.d.ts +8 -0
- package/dist/docx/contentTypeRegistry.js +64 -0
- package/dist/docx/documentParser.d.ts +1 -13
- package/dist/docx/documentParser.js +1 -47
- package/dist/docx/ensureParaIds.d.ts +7 -1
- package/dist/docx/ensureParaIds.js +173 -215
- package/dist/docx/foldedListNumberFields.d.ts +85 -0
- package/dist/docx/foldedListNumberFields.js +267 -0
- package/dist/docx/footnoteParser.d.ts +1 -11
- package/dist/docx/footnoteParser.js +1 -16
- package/dist/docx/headerFooterParser.d.ts +1 -9
- package/dist/docx/headerFooterParser.js +1 -12
- package/dist/docx/headerFooterVerbatim.d.ts +39 -1
- package/dist/docx/headerFooterVerbatim.js +173 -2
- package/dist/docx/imageParser.d.ts +13 -1
- package/dist/docx/imageParser.js +20 -11
- package/dist/docx/metadataPrivacy.js +6 -1
- 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 +74 -19
- 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 +77 -22
- package/dist/docx/parser.js +3 -1
- package/dist/docx/removeHeaderFooterParts.js +17 -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 +54 -6
- package/dist/docx/rezip.js +835 -257
- 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 +22 -0
- package/dist/docx/saveDiagnostics.js +0 -0
- package/dist/docx/selectiveSave.d.ts +3 -1
- package/dist/docx/selectiveSave.js +36 -4
- package/dist/docx/selectiveXmlPatch.d.ts +16 -8
- package/dist/docx/selectiveXmlPatch.js +81 -7
- package/dist/docx/serializer/documentSerializer.d.ts +9 -2
- package/dist/docx/serializer/documentSerializer.js +29 -10
- 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/server/applyDocxXmlPatchProposal.js +14 -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 +31 -0
- package/dist/docx/storyBlockReplay.js +471 -0
- package/dist/docx/storyPlainText.d.ts +20 -0
- package/dist/docx/storyPlainText.js +23 -0
- package/dist/docx/streamingXmlParser.d.ts +22 -1
- package/dist/docx/streamingXmlParser.js +49 -3
- package/dist/docx/structuralXmlPatch.js +18 -23
- package/dist/docx/styleParser.d.ts +5 -1
- package/dist/docx/styleParser.js +1 -1
- package/dist/docx/tableParser.d.ts +1 -8
- package/dist/docx/tableParser.js +1 -28
- package/dist/docx/textBoxParser.d.ts +1 -5
- package/dist/docx/textBoxParser.js +1 -20
- package/dist/docx/unzip.js +6 -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/acceptedBlockProjection.d.ts +12 -0
- package/dist/internal/acceptedBlockProjection.js +45 -0
- 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 +9 -1
- package/dist/markdown/escape.d.ts +3 -1
- package/dist/markdown/escape.js +9 -2
- package/dist/markdown/renderBlock.js +20 -121
- package/dist/markdown/renderParagraph.js +13 -15
- package/dist/markdown/renderRuns.d.ts +7 -1
- package/dist/markdown/renderRuns.js +35 -15
- 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 +121 -0
- package/dist/prosemirror/canonicalCommands.js +35 -0
- package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
- package/dist/prosemirror/canonicalSelectionRange.js +15 -0
- package/dist/prosemirror/clipboardIntent.d.ts +7 -0
- package/dist/prosemirror/clipboardIntent.js +9 -0
- package/dist/prosemirror/commands/pageBreak.js +15 -1
- package/dist/prosemirror/commands/pastePlainText.js +2 -1
- package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
- package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
- package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
- package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
- package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +11 -4
- package/dist/prosemirror/conversion/toProseDoc.js +55 -14
- 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 +88 -48
- 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/HyperlinkExtension.js +56 -28
- 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 +72 -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/insertOperations.js +2 -1
- 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/paragraphPropertyCarry.d.ts +7 -1
- package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
- package/dist/prosemirror/plugins/suggestionMode.js +57 -7
- 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/canonicalCapabilities.d.ts +215 -0
- package/dist/types/canonicalCapabilities.js +212 -0
- package/dist/types/canonicalSave.d.ts +11 -0
- package/dist/types/canonicalSave.js +0 -0
- package/dist/types/content.d.ts +2 -2
- package/dist/types/docxSerialization.d.ts +14 -0
- package/dist/types/docxSerialization.js +7 -0
- package/dist/utils/createDocument.js +2 -2
- package/package.json +16 -4
|
@@ -3,6 +3,7 @@ import { FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, InvalidFolioDocxXmlPatchProposal
|
|
|
3
3
|
import { FOLIO_DOCX_CONFORMANCE_PROFILE, validateDocxConformance } from "./validateDocxConformance.js";
|
|
4
4
|
import { TaggedError, panic } from "better-result";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
+
import { writeZipPart } from "@stll/docx-core/zip";
|
|
6
7
|
import JSZip from "jszip";
|
|
7
8
|
//#region src/docx/server/applyDocxXmlPatchProposal.ts
|
|
8
9
|
const FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION = 1;
|
|
@@ -92,15 +93,19 @@ const applyReplacements = async ({ bytes, proposal }) => {
|
|
|
92
93
|
message: "An evaluated package part was unavailable during replacement.",
|
|
93
94
|
stage: "replace"
|
|
94
95
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
writeZipPart({
|
|
97
|
+
zip,
|
|
98
|
+
path: replacement.path,
|
|
99
|
+
data: new TextEncoder().encode(replacement.replacementXml),
|
|
100
|
+
options: {
|
|
101
|
+
binary: true,
|
|
102
|
+
date: current.date,
|
|
103
|
+
comment: current.comment,
|
|
104
|
+
unixPermissions: current.unixPermissions,
|
|
105
|
+
dosPermissions: current.dosPermissions,
|
|
106
|
+
compression: "DEFLATE",
|
|
107
|
+
compressionOptions: { level: 6 }
|
|
108
|
+
}
|
|
104
109
|
});
|
|
105
110
|
}
|
|
106
111
|
try {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
//#region src/docx/settingsHeaderFooterUpdate.d.ts
|
|
2
|
+
/** Null means the source is malformed or outside the supported namespace profiles. */
|
|
3
|
+
declare const updateEvenAndOddHeaders: (xml: string, enabled: boolean | undefined) => string | null;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { updateEvenAndOddHeaders };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { spliceXml } from "./selectiveXmlPatch.js";
|
|
2
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, getChildElements, getLocalName, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
|
|
3
|
+
import { panic } from "better-result";
|
|
4
|
+
import { SETTINGS_CHILDREN } from "@stll/docx-core/schema";
|
|
5
|
+
//#region src/docx/settingsHeaderFooterUpdate.ts
|
|
6
|
+
/** Preserve producer settings while updating the document-wide header/footer switch. */
|
|
7
|
+
const XML_TOKEN = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<(?:"[^"]*"|'[^']*'|[^'">])*>/gu;
|
|
8
|
+
const SETTINGS_CHILD_ORDINALS = new Map(SETTINGS_CHILDREN.map((name, index) => [name, index]));
|
|
9
|
+
const EVEN_AND_ODD_HEADERS_ORDER = SETTINGS_CHILD_ORDINALS.get("evenAndOddHeaders");
|
|
10
|
+
/** Null means the source is malformed or outside the supported namespace profiles. */
|
|
11
|
+
const updateEvenAndOddHeaders = (xml, enabled) => {
|
|
12
|
+
const root = parseXmlDocument(xml);
|
|
13
|
+
if (!root || getLocalName(root.name) !== "settings" || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(root) ?? "")) return null;
|
|
14
|
+
const children = getChildElements(root);
|
|
15
|
+
const spans = [];
|
|
16
|
+
const stack = [];
|
|
17
|
+
let childIndex = 0;
|
|
18
|
+
let childStart = -1;
|
|
19
|
+
let rootEnd = -1;
|
|
20
|
+
let rootOpenEnd = -1;
|
|
21
|
+
let selfClosingRootStart = -1;
|
|
22
|
+
let firstFollowingChildStart = -1;
|
|
23
|
+
if (EVEN_AND_ODD_HEADERS_ORDER === void 0) panic("Generated CT_Settings schema does not define evenAndOddHeaders");
|
|
24
|
+
for (const token of xml.matchAll(XML_TOKEN)) {
|
|
25
|
+
const tag = token[0];
|
|
26
|
+
if (tag.startsWith("<!") || tag.startsWith("<?")) continue;
|
|
27
|
+
const closing = tag.startsWith("</");
|
|
28
|
+
const name = tag.slice(closing ? 2 : 1).split(/[\s/>]/u).at(0);
|
|
29
|
+
if (!name) return null;
|
|
30
|
+
if (closing) {
|
|
31
|
+
if (stack.pop() !== name) return null;
|
|
32
|
+
if (stack.length === 1) {
|
|
33
|
+
const child = children[childIndex++];
|
|
34
|
+
if (!child || childStart < 0) return null;
|
|
35
|
+
if (getLocalName(child.name) === "evenAndOddHeaders" && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? "")) spans.push({
|
|
36
|
+
start: childStart,
|
|
37
|
+
end: token.index + tag.length,
|
|
38
|
+
newXml: ""
|
|
39
|
+
});
|
|
40
|
+
childStart = -1;
|
|
41
|
+
}
|
|
42
|
+
if (stack.length === 0) rootEnd = token.index;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (stack.length === 0) {
|
|
46
|
+
rootOpenEnd = token.index + tag.length;
|
|
47
|
+
if (tag.endsWith("/>")) selfClosingRootStart = token.index;
|
|
48
|
+
}
|
|
49
|
+
if (stack.length === 1) {
|
|
50
|
+
const child = children[childIndex];
|
|
51
|
+
if (child?.name !== name) return null;
|
|
52
|
+
if (firstFollowingChildStart < 0 && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? "") && (SETTINGS_CHILD_ORDINALS.get(getLocalName(child.name)) ?? -1) > EVEN_AND_ODD_HEADERS_ORDER) firstFollowingChildStart = token.index;
|
|
53
|
+
childStart = token.index;
|
|
54
|
+
if (tag.endsWith("/>")) {
|
|
55
|
+
childIndex += 1;
|
|
56
|
+
if (getLocalName(child.name) === "evenAndOddHeaders" && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? "")) spans.push({
|
|
57
|
+
start: token.index,
|
|
58
|
+
end: token.index + tag.length,
|
|
59
|
+
newXml: ""
|
|
60
|
+
});
|
|
61
|
+
childStart = -1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!tag.endsWith("/>")) stack.push(name);
|
|
65
|
+
}
|
|
66
|
+
if (stack.length !== 0 || childIndex !== children.length) return null;
|
|
67
|
+
const flag = enabled === void 0 ? "" : `<folioW:evenAndOddHeaders xmlns:folioW="${getNamespaceUri(root)}"${enabled ? "" : " folioW:val=\"0\""}/>`;
|
|
68
|
+
if (selfClosingRootStart >= 0) {
|
|
69
|
+
if (!flag) return xml;
|
|
70
|
+
return spliceXml(xml, [{
|
|
71
|
+
start: rootOpenEnd - 2,
|
|
72
|
+
end: rootOpenEnd,
|
|
73
|
+
newXml: `>${flag}</${root.name}>`
|
|
74
|
+
}]);
|
|
75
|
+
}
|
|
76
|
+
if (rootEnd < 0) return null;
|
|
77
|
+
const existingFlag = spans.at(0);
|
|
78
|
+
if (flag && existingFlag) existingFlag.newXml = flag;
|
|
79
|
+
else if (flag) {
|
|
80
|
+
const insertAt = firstFollowingChildStart >= 0 ? firstFollowingChildStart : rootEnd;
|
|
81
|
+
spans.push({
|
|
82
|
+
start: insertAt,
|
|
83
|
+
end: insertAt,
|
|
84
|
+
newXml: flag
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return spliceXml(xml, spans);
|
|
88
|
+
};
|
|
89
|
+
//#endregion
|
|
90
|
+
export { updateEvenAndOddHeaders };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { DEFAULT_TAB_STOP_TWIPS } from "@stll/docx-core/model";
|
|
2
3
|
//#region src/docx/settingsParser.d.ts
|
|
3
|
-
/** OOXML default per §17.6.13 when `w:defaultTabStop` is absent. */
|
|
4
|
-
declare const DEFAULT_TAB_STOP_TWIPS = 720;
|
|
5
4
|
declare function parseSettings(xml: string | null): document_d_exports.DocumentSettings;
|
|
6
5
|
//#endregion
|
|
7
6
|
export { DEFAULT_TAB_STOP_TWIPS, parseSettings };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { parseEndnoteProperties } from "./notePropertiesParser.js";
|
|
2
2
|
import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildren, getAttribute, parseBooleanElement, parseXmlDocument } from "./xmlParser.js";
|
|
3
|
+
import { DEFAULT_TAB_STOP_TWIPS, DEFAULT_TAB_STOP_TWIPS as DEFAULT_TAB_STOP_TWIPS$1 } from "@stll/docx-core/model";
|
|
3
4
|
//#region src/docx/settingsParser.ts
|
|
4
|
-
/** OOXML default per §17.6.13 when `w:defaultTabStop` is absent. */
|
|
5
|
-
const DEFAULT_TAB_STOP_TWIPS = 720;
|
|
6
5
|
/**
|
|
7
6
|
* Sanity cap on `w:defaultTabStop`. Word's maximum margin is ~22 inches
|
|
8
7
|
* (31 680 twips); anything past that is corruption or a hostile input and
|
|
@@ -111,13 +110,13 @@ function parseKinsokuOverride(root, name) {
|
|
|
111
110
|
};
|
|
112
111
|
}
|
|
113
112
|
function parseDefaultTabStop(root) {
|
|
114
|
-
if (!root) return
|
|
113
|
+
if (!root) return DEFAULT_TAB_STOP_TWIPS$1;
|
|
115
114
|
const el = findChild(root, "w", "defaultTabStop");
|
|
116
|
-
if (!el) return
|
|
115
|
+
if (!el) return DEFAULT_TAB_STOP_TWIPS$1;
|
|
117
116
|
const raw = getAttribute(el, "w", "val");
|
|
118
|
-
if (raw === null) return
|
|
117
|
+
if (raw === null) return DEFAULT_TAB_STOP_TWIPS$1;
|
|
119
118
|
const parsed = Number.parseInt(raw, 10);
|
|
120
|
-
if (!Number.isFinite(parsed) || parsed < MIN_TAB_STOP_TWIPS || parsed > MAX_TAB_STOP_TWIPS) return
|
|
119
|
+
if (!Number.isFinite(parsed) || parsed < MIN_TAB_STOP_TWIPS || parsed > MAX_TAB_STOP_TWIPS) return DEFAULT_TAB_STOP_TWIPS$1;
|
|
121
120
|
return parsed;
|
|
122
121
|
}
|
|
123
122
|
//#endregion
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/storyBlockReplay.d.ts
|
|
4
|
+
type MaterializeReplayFragmentOptions = {
|
|
5
|
+
xml: string;
|
|
6
|
+
element: XmlElement;
|
|
7
|
+
sourceNamespace: string;
|
|
8
|
+
generatedRoot?: XmlElement;
|
|
9
|
+
};
|
|
10
|
+
/** Bind a replay fragment once, retaining existing local declarations and compatibility tokens. */
|
|
11
|
+
declare const materializeReplayFragment: ({ xml, element, sourceNamespace, generatedRoot }: MaterializeReplayFragmentOptions) => string | null;
|
|
12
|
+
type StoryBlockReplayOptions = {
|
|
13
|
+
sourceXml: string;
|
|
14
|
+
baselineContent: readonly document_d_exports.BlockContent[];
|
|
15
|
+
currentContent: readonly document_d_exports.BlockContent[];
|
|
16
|
+
serializedXml: string;
|
|
17
|
+
ignoredChild?: string;
|
|
18
|
+
};
|
|
19
|
+
declare const buildStoryBlockReplay: (options: StoryBlockReplayOptions) => string | null;
|
|
20
|
+
/** Validate source/model correspondence before replaying an unchanged complete part. */
|
|
21
|
+
declare const canReplayDocumentSource: (sourceXml: string, baseline: document_d_exports.DocumentBody) => boolean;
|
|
22
|
+
type DocumentBlockReplayOptions = {
|
|
23
|
+
sourceXml: string;
|
|
24
|
+
serializedXml: string;
|
|
25
|
+
baseline: document_d_exports.DocumentBody;
|
|
26
|
+
current: document_d_exports.DocumentBody;
|
|
27
|
+
};
|
|
28
|
+
/** Replay body blocks and modeled document metadata inside the authored part shell. */
|
|
29
|
+
declare const buildDocumentBlockReplay: ({ sourceXml, serializedXml, baseline, current }: DocumentBlockReplayOptions) => string | null;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { buildDocumentBlockReplay, buildStoryBlockReplay, canReplayDocumentSource, materializeReplayFragment };
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
|
+
import { spliceXml } from "./selectiveXmlPatch.js";
|
|
3
|
+
import { getXmlSourceRange, parseStreamingXmlWithSourceRanges } from "./streamingXmlParser.js";
|
|
4
|
+
import { TRANSITIONAL_NAMESPACE_BY_STRICT_URI } from "./strictValueEncodings.gen.js";
|
|
5
|
+
import { captureSourceProfileXml, isSafeCapturedXmlDocument } from "./verbatimCapture.js";
|
|
6
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri } from "./xmlParser.js";
|
|
7
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
8
|
+
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
9
|
+
//#region src/docx/storyBlockReplay.ts
|
|
10
|
+
const BLOCK_ELEMENT_NAMES = {
|
|
11
|
+
paragraph: "p",
|
|
12
|
+
table: "tbl",
|
|
13
|
+
blockSdt: "sdt",
|
|
14
|
+
blockCustomXml: "customXml",
|
|
15
|
+
preservedBlock: null,
|
|
16
|
+
bookmarkStart: "bookmarkStart",
|
|
17
|
+
bookmarkEnd: "bookmarkEnd"
|
|
18
|
+
};
|
|
19
|
+
const modelMatchesElement = ({ value, element, mode }) => {
|
|
20
|
+
if (value === void 0) return false;
|
|
21
|
+
if (mode === "source" && getLocalName(element.name) === "AlternateContent" && getNamespaceUri(element) === "http://schemas.openxmlformats.org/markup-compatibility/2006" && (value.type === "paragraph" || value.type === "table")) return true;
|
|
22
|
+
const name = BLOCK_ELEMENT_NAMES[value.type];
|
|
23
|
+
if (name !== null) return getLocalName(element.name) === name && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "");
|
|
24
|
+
if (value.type !== "preservedBlock") return false;
|
|
25
|
+
const parsed = parseStreamingXmlWithSourceRanges(value.xml, element.namespaceScope);
|
|
26
|
+
if (parsed.status !== "parsed") return false;
|
|
27
|
+
const roots = getChildElements(parsed.value);
|
|
28
|
+
const root = roots.at(0);
|
|
29
|
+
const normalize = (uri) => uri === void 0 ? void 0 : TRANSITIONAL_NAMESPACE_BY_STRICT_URI.get(uri) ?? uri;
|
|
30
|
+
return roots.length === 1 && root !== void 0 && getLocalName(root.name) === getLocalName(element.name) && normalize(getNamespaceUri(root)) === normalize(getNamespaceUri(element));
|
|
31
|
+
};
|
|
32
|
+
const XML_TOKEN = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<(?:"[^"]*"|'[^']*'|[^'">])*>/gu;
|
|
33
|
+
const XML_ATTRIBUTE = /([^\s=<>/]+)(\s*=\s*)(["'])(.*?)\3/gsu;
|
|
34
|
+
const readStory = (xml, scope, ignoredChild) => {
|
|
35
|
+
const parsed = parseStreamingXmlWithSourceRanges(xml, scope);
|
|
36
|
+
if (parsed.status !== "parsed") return null;
|
|
37
|
+
const roots = getChildElements(parsed.value);
|
|
38
|
+
const root = roots.at(0);
|
|
39
|
+
if (roots.length !== 1 || !root || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(root) ?? "")) return null;
|
|
40
|
+
if (root.elements?.some((child) => child.type === "text" && String(child.text ?? "").trim() !== "")) return null;
|
|
41
|
+
const children = getChildElements(root);
|
|
42
|
+
const blocks = [];
|
|
43
|
+
for (const element of children) {
|
|
44
|
+
if (getLocalName(element.name) === ignoredChild && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "")) continue;
|
|
45
|
+
const range = getXmlSourceRange(element);
|
|
46
|
+
if (!range) return null;
|
|
47
|
+
blocks.push({
|
|
48
|
+
element,
|
|
49
|
+
...range
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const range = getXmlSourceRange(root);
|
|
53
|
+
return range ? {
|
|
54
|
+
root,
|
|
55
|
+
blocks,
|
|
56
|
+
range
|
|
57
|
+
} : null;
|
|
58
|
+
};
|
|
59
|
+
const effectiveBindings = (element) => {
|
|
60
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
61
|
+
let scope = element.namespaceScope;
|
|
62
|
+
while (scope) {
|
|
63
|
+
for (const [prefix, uri] of scope.bindings) if (!bindings.has(prefix)) bindings.set(prefix, uri);
|
|
64
|
+
scope = scope.parent;
|
|
65
|
+
}
|
|
66
|
+
return bindings;
|
|
67
|
+
};
|
|
68
|
+
/** Bind a replay fragment once, retaining existing local declarations and compatibility tokens. */
|
|
69
|
+
const materializeReplayFragment = ({ xml, element, sourceNamespace, generatedRoot }) => {
|
|
70
|
+
const strict = TRANSITIONAL_NAMESPACE_BY_STRICT_URI.has(sourceNamespace);
|
|
71
|
+
const first = [...xml.matchAll(XML_TOKEN)].at(0);
|
|
72
|
+
if (!first || first.index !== 0 || first[0].startsWith("<!") || first[0].startsWith("<?")) return null;
|
|
73
|
+
const opening = first[0];
|
|
74
|
+
const additions = [];
|
|
75
|
+
const bindings = effectiveBindings(element);
|
|
76
|
+
const markupCompatibilityUri = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
77
|
+
const ignorableUris = /* @__PURE__ */ new Set([markupCompatibilityUri]);
|
|
78
|
+
const inheritedIgnorable = generatedRoot === void 0 ? null : getAttributeByNamespaceUri(generatedRoot, ignorableUris, "Ignorable");
|
|
79
|
+
const localIgnorable = getAttributeByNamespaceUri(element, ignorableUris, "Ignorable");
|
|
80
|
+
const ignorable = [...new Set(`${inheritedIgnorable ?? ""} ${localIgnorable ?? ""}`.trim().split(/\s+/u).filter(Boolean))];
|
|
81
|
+
let ignorableAttribute;
|
|
82
|
+
if (ignorable.length > 0) {
|
|
83
|
+
const localAttribute = Object.keys(element.attributes ?? {}).find((name) => {
|
|
84
|
+
const colon = name.indexOf(":");
|
|
85
|
+
return colon > 0 && getLocalName(name) === "Ignorable" && bindings.get(name.slice(0, colon)) === markupCompatibilityUri;
|
|
86
|
+
});
|
|
87
|
+
const compatibilityPrefix = localAttribute?.slice(0, localAttribute.indexOf(":")) ?? [...bindings].find(([prefix, uri]) => prefix !== "" && uri === markupCompatibilityUri)?.[0];
|
|
88
|
+
if (compatibilityPrefix === void 0 || ignorable.some((prefix) => !bindings.has(prefix))) return null;
|
|
89
|
+
ignorableAttribute = `${compatibilityPrefix}:Ignorable`;
|
|
90
|
+
if (localIgnorable === null) additions.push(` ${ignorableAttribute}="${escapeXmlAttribute(ignorable.join(" "))}"`);
|
|
91
|
+
}
|
|
92
|
+
for (const [prefix, uri] of bindings) {
|
|
93
|
+
const name = prefix === "" ? "xmlns" : `xmlns:${prefix}`;
|
|
94
|
+
if (element.attributes?.[name] !== void 0) continue;
|
|
95
|
+
additions.push(` ${name}="${escapeXmlAttribute(uri)}"`);
|
|
96
|
+
}
|
|
97
|
+
let replacedOpening = opening.replace(XML_ATTRIBUTE, (match, name, assignment, quote) => name === ignorableAttribute ? `${name}${assignment}${quote}${escapeXmlAttribute(ignorable.join(" "))}${quote}` : match);
|
|
98
|
+
const close = opening.endsWith("/>") ? replacedOpening.length - 2 : replacedOpening.length - 1;
|
|
99
|
+
replacedOpening = replacedOpening.slice(0, close) + additions.join("") + replacedOpening.slice(close);
|
|
100
|
+
const materialized = replacedOpening + xml.slice(opening.length);
|
|
101
|
+
if (!isSafeCapturedXmlDocument(materialized)) return null;
|
|
102
|
+
if (!strict) return materialized;
|
|
103
|
+
const parsed = parseStreamingXmlWithSourceRanges(materialized);
|
|
104
|
+
if (parsed.status !== "parsed") return null;
|
|
105
|
+
const root = getChildElements(parsed.value).at(0);
|
|
106
|
+
return root === void 0 ? null : captureSourceProfileXml(root, DOCX_CONFORMANCE_CLASSES.STRICT);
|
|
107
|
+
};
|
|
108
|
+
const paragraphIdentity = (value) => value.type === "paragraph" ? value.paraId?.toUpperCase() : void 0;
|
|
109
|
+
const addCandidate = ({ map, key, index }) => {
|
|
110
|
+
const queue = map.get(key);
|
|
111
|
+
if (queue) queue.indices.push(index);
|
|
112
|
+
else map.set(key, {
|
|
113
|
+
indices: [index],
|
|
114
|
+
cursor: 0
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
const takeCandidate = (queue, used) => {
|
|
118
|
+
if (!queue) return void 0;
|
|
119
|
+
while (queue.cursor < queue.indices.length) {
|
|
120
|
+
const candidate = queue.indices[queue.cursor];
|
|
121
|
+
queue.cursor += 1;
|
|
122
|
+
if (candidate !== void 0 && !used.has(candidate)) return candidate;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/** Replace only mapped block ranges, retaining authored root syntax and every intervening gap. */
|
|
126
|
+
const replayBlocks = ({ sourceXml, baselineContent, currentContent, serializedXml, ignoredChild }, scope) => {
|
|
127
|
+
const source = readStory(sourceXml, scope?.source, ignoredChild);
|
|
128
|
+
const generated = readStory(serializedXml, scope?.generated, ignoredChild);
|
|
129
|
+
if (!source || !generated || getLocalName(source.root.name) !== getLocalName(generated.root.name) || source.blocks.length !== baselineContent.length || generated.blocks.length !== currentContent.length) return null;
|
|
130
|
+
if (source.blocks.some((block, index) => !modelMatchesElement({
|
|
131
|
+
value: baselineContent[index],
|
|
132
|
+
element: block.element,
|
|
133
|
+
mode: "source"
|
|
134
|
+
})) || generated.blocks.some((block, index) => !modelMatchesElement({
|
|
135
|
+
value: currentContent[index],
|
|
136
|
+
element: block.element,
|
|
137
|
+
mode: "generated"
|
|
138
|
+
}))) return null;
|
|
139
|
+
const generatedRoot = scope?.generatedRoot ?? generated.root;
|
|
140
|
+
const fingerprints = baselineContent.map(canonicalJson);
|
|
141
|
+
const identities = /* @__PURE__ */ new Map();
|
|
142
|
+
const byFingerprint = /* @__PURE__ */ new Map();
|
|
143
|
+
for (const [index, block] of baselineContent.entries()) {
|
|
144
|
+
const fingerprint = fingerprints[index];
|
|
145
|
+
if (fingerprint === void 0) return null;
|
|
146
|
+
addCandidate({
|
|
147
|
+
map: byFingerprint,
|
|
148
|
+
key: fingerprint,
|
|
149
|
+
index
|
|
150
|
+
});
|
|
151
|
+
const identity = paragraphIdentity(block);
|
|
152
|
+
if (identity !== void 0) addCandidate({
|
|
153
|
+
map: identities,
|
|
154
|
+
key: identity,
|
|
155
|
+
index
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const used = /* @__PURE__ */ new Set();
|
|
159
|
+
const fragments = [];
|
|
160
|
+
for (const [index, block] of currentContent.entries()) {
|
|
161
|
+
const fingerprint = canonicalJson(block);
|
|
162
|
+
const identity = paragraphIdentity(block);
|
|
163
|
+
let originalIndex = takeCandidate(identity === void 0 ? void 0 : identities.get(identity), used) ?? takeCandidate(byFingerprint.get(fingerprint), used);
|
|
164
|
+
if (originalIndex === void 0 && (block.type === "table" || block.type === "blockSdt" || block.type === "blockCustomXml") && !used.has(index)) {
|
|
165
|
+
if (baselineContent[index]?.type === block.type) originalIndex = index;
|
|
166
|
+
}
|
|
167
|
+
if (originalIndex !== void 0) used.add(originalIndex);
|
|
168
|
+
const original = originalIndex === void 0 ? void 0 : source.blocks[originalIndex];
|
|
169
|
+
if (originalIndex !== void 0 && original && fingerprints[originalIndex] === fingerprint) {
|
|
170
|
+
fragments.push(sourceXml.slice(original.start, original.end));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const replacement = generated.blocks[index];
|
|
174
|
+
if (!replacement) return null;
|
|
175
|
+
if (original && originalIndex !== void 0) {
|
|
176
|
+
const nested = replayNestedBlock({
|
|
177
|
+
sourceXml,
|
|
178
|
+
serializedXml,
|
|
179
|
+
sourceElement: original.element,
|
|
180
|
+
generatedElement: replacement.element,
|
|
181
|
+
baseline: baselineContent[originalIndex],
|
|
182
|
+
current: block,
|
|
183
|
+
generatedRoot
|
|
184
|
+
});
|
|
185
|
+
if (nested !== null) {
|
|
186
|
+
fragments.push(nested);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const fragment = materializeReplayFragment({
|
|
191
|
+
xml: serializedXml.slice(replacement.start, replacement.end),
|
|
192
|
+
element: replacement.element,
|
|
193
|
+
sourceNamespace: getNamespaceUri(source.root) ?? "",
|
|
194
|
+
generatedRoot
|
|
195
|
+
});
|
|
196
|
+
if (fragment === null) return null;
|
|
197
|
+
fragments.push(fragment);
|
|
198
|
+
}
|
|
199
|
+
const splices = source.blocks.map((block, index) => ({
|
|
200
|
+
start: block.start,
|
|
201
|
+
end: block.end,
|
|
202
|
+
newXml: index === source.blocks.length - 1 ? fragments.slice(index).join("") : fragments[index] ?? ""
|
|
203
|
+
}));
|
|
204
|
+
if (source.blocks.length === 0 && fragments.length > 0) {
|
|
205
|
+
const opening = [...sourceXml.slice(source.range.start, source.range.end).matchAll(XML_TOKEN)].at(0)?.[0];
|
|
206
|
+
if (!opening || !source.root.name) return null;
|
|
207
|
+
if (opening.endsWith("/>")) splices.push({
|
|
208
|
+
start: source.range.start,
|
|
209
|
+
end: source.range.end,
|
|
210
|
+
newXml: `${opening.slice(0, -2)}>${fragments.join("")}</${source.root.name}>`
|
|
211
|
+
});
|
|
212
|
+
else {
|
|
213
|
+
const ignored = ignoredChild === void 0 ? void 0 : getChildElements(source.root).find((child) => getLocalName(child.name) === ignoredChild && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? ""));
|
|
214
|
+
const close = (ignored && getXmlSourceRange(ignored)?.start) ?? sourceXml.lastIndexOf("</", source.range.end);
|
|
215
|
+
if (close < source.range.start) return null;
|
|
216
|
+
splices.push({
|
|
217
|
+
start: close,
|
|
218
|
+
end: close,
|
|
219
|
+
newXml: fragments.join("")
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return spliceXml(sourceXml, splices);
|
|
224
|
+
};
|
|
225
|
+
const buildStoryBlockReplay = (options) => replayBlocks(options);
|
|
226
|
+
const withoutChildren = (value, key) => Object.fromEntries(Object.entries(value).filter(([name]) => name !== key));
|
|
227
|
+
const wordChildren = (element, name) => getChildElements(element).filter((child) => getLocalName(child.name) === name && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? ""));
|
|
228
|
+
const replayNestedBlock = ({ sourceXml, serializedXml, sourceElement, generatedElement, baseline, current, generatedRoot }) => {
|
|
229
|
+
if (baseline === void 0 || baseline.type !== current.type) return null;
|
|
230
|
+
const sourceRange = getXmlSourceRange(sourceElement);
|
|
231
|
+
if (!sourceRange) return null;
|
|
232
|
+
const splices = [];
|
|
233
|
+
const nestedContent = (sourceContainer, generatedContainer, before, after) => {
|
|
234
|
+
const sourceContentRange = getXmlSourceRange(sourceContainer);
|
|
235
|
+
const generatedContentRange = getXmlSourceRange(generatedContainer);
|
|
236
|
+
if (!sourceContentRange || !generatedContentRange) return false;
|
|
237
|
+
const patched = replayBlocks({
|
|
238
|
+
sourceXml: sourceXml.slice(sourceContentRange.start, sourceContentRange.end),
|
|
239
|
+
serializedXml: serializedXml.slice(generatedContentRange.start, generatedContentRange.end),
|
|
240
|
+
baselineContent: before,
|
|
241
|
+
currentContent: after
|
|
242
|
+
}, {
|
|
243
|
+
source: sourceContainer.namespaceScope,
|
|
244
|
+
generated: generatedContainer.namespaceScope,
|
|
245
|
+
generatedRoot
|
|
246
|
+
});
|
|
247
|
+
if (patched === null) return false;
|
|
248
|
+
splices.push({
|
|
249
|
+
start: sourceContentRange.start - sourceRange.start,
|
|
250
|
+
end: sourceContentRange.end - sourceRange.start,
|
|
251
|
+
newXml: patched
|
|
252
|
+
});
|
|
253
|
+
return true;
|
|
254
|
+
};
|
|
255
|
+
switch (current.type) {
|
|
256
|
+
case "blockSdt":
|
|
257
|
+
case "blockCustomXml": {
|
|
258
|
+
if (baseline.type !== "blockSdt" && baseline.type !== "blockCustomXml" || canonicalJson(withoutChildren(baseline, "content")) !== canonicalJson(withoutChildren(current, "content"))) return null;
|
|
259
|
+
if (current.type === "blockCustomXml") {
|
|
260
|
+
if (!nestedContent(sourceElement, generatedElement, baseline.content, current.content)) return null;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
const sourceChildren = wordChildren(sourceElement, "sdtContent");
|
|
264
|
+
const generatedChildren = wordChildren(generatedElement, "sdtContent");
|
|
265
|
+
const sourceContainer = sourceChildren.at(0);
|
|
266
|
+
const generatedContainer = generatedChildren.at(0);
|
|
267
|
+
if (sourceChildren.length !== 1 || generatedChildren.length !== 1 || !sourceContainer || !generatedContainer || !nestedContent(sourceContainer, generatedContainer, baseline.content, current.content)) return null;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case "table": {
|
|
271
|
+
if (baseline.type !== "table" || baseline.rows.length !== current.rows.length || canonicalJson(withoutChildren(baseline, "rows")) !== canonicalJson(withoutChildren(current, "rows"))) return null;
|
|
272
|
+
const sourceRows = wordChildren(sourceElement, "tr");
|
|
273
|
+
const generatedRows = wordChildren(generatedElement, "tr");
|
|
274
|
+
if (sourceRows.length !== current.rows.length || generatedRows.length !== current.rows.length) return null;
|
|
275
|
+
for (const [rowIndex, row] of current.rows.entries()) {
|
|
276
|
+
const beforeRow = baseline.rows[rowIndex];
|
|
277
|
+
const sourceRow = sourceRows[rowIndex];
|
|
278
|
+
const generatedRow = generatedRows[rowIndex];
|
|
279
|
+
if (beforeRow === void 0 || beforeRow.cells.length !== row.cells.length || canonicalJson(withoutChildren(beforeRow, "cells")) !== canonicalJson(withoutChildren(row, "cells")) || !sourceRow || !generatedRow) return null;
|
|
280
|
+
const sourceCells = wordChildren(sourceRow, "tc");
|
|
281
|
+
const generatedCells = wordChildren(generatedRow, "tc");
|
|
282
|
+
if (sourceCells.length !== row.cells.length || generatedCells.length !== row.cells.length) return null;
|
|
283
|
+
for (const [cellIndex, cell] of row.cells.entries()) {
|
|
284
|
+
const beforeCell = beforeRow.cells[cellIndex];
|
|
285
|
+
const sourceCell = sourceCells[cellIndex];
|
|
286
|
+
const generatedCell = generatedCells[cellIndex];
|
|
287
|
+
if (beforeCell === void 0 || canonicalJson(withoutChildren(beforeCell, "content")) !== canonicalJson(withoutChildren(cell, "content")) || !sourceCell || !generatedCell) return null;
|
|
288
|
+
if (!replayCellContent({
|
|
289
|
+
sourceXml,
|
|
290
|
+
serializedXml,
|
|
291
|
+
sourceCell,
|
|
292
|
+
generatedCell,
|
|
293
|
+
before: beforeCell.content,
|
|
294
|
+
after: cell.content,
|
|
295
|
+
sourceRangeStart: sourceRange.start,
|
|
296
|
+
splices,
|
|
297
|
+
generatedRoot
|
|
298
|
+
})) return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case "paragraph":
|
|
304
|
+
case "preservedBlock":
|
|
305
|
+
case "bookmarkStart":
|
|
306
|
+
case "bookmarkEnd": return null;
|
|
307
|
+
default: return current;
|
|
308
|
+
}
|
|
309
|
+
return spliceXml(sourceXml.slice(sourceRange.start, sourceRange.end), splices);
|
|
310
|
+
};
|
|
311
|
+
const replayCellContent = ({ sourceXml, serializedXml, sourceCell, generatedCell, before, after, sourceRangeStart, splices, generatedRoot }) => {
|
|
312
|
+
const sourceRange = getXmlSourceRange(sourceCell);
|
|
313
|
+
const generatedRange = getXmlSourceRange(generatedCell);
|
|
314
|
+
if (!sourceRange || !generatedRange) return false;
|
|
315
|
+
const sourceProperties = wordChildren(sourceCell, "tcPr");
|
|
316
|
+
const generatedProperties = wordChildren(generatedCell, "tcPr");
|
|
317
|
+
if (sourceProperties.length > 1 || generatedProperties.length > 1) return false;
|
|
318
|
+
const sourceProperty = sourceProperties.at(0);
|
|
319
|
+
const generatedProperty = generatedProperties.at(0);
|
|
320
|
+
const sourceView = sourceXml.slice(sourceRange.start, sourceRange.end);
|
|
321
|
+
let generatedView = serializedXml.slice(generatedRange.start, generatedRange.end);
|
|
322
|
+
const baselineContent = [...before];
|
|
323
|
+
const currentContent = [...after];
|
|
324
|
+
if (sourceProperty !== void 0) {
|
|
325
|
+
if (getChildElements(sourceCell).at(0) !== sourceProperty || !generatedProperty || getChildElements(generatedCell).at(0) !== generatedProperty) return false;
|
|
326
|
+
const range = getXmlSourceRange(sourceProperty);
|
|
327
|
+
if (!range) return false;
|
|
328
|
+
const propertyXml = materializeReplayFragment({
|
|
329
|
+
xml: sourceXml.slice(range.start, range.end),
|
|
330
|
+
element: sourceProperty,
|
|
331
|
+
sourceNamespace: getNamespaceUri(sourceCell) ?? ""
|
|
332
|
+
});
|
|
333
|
+
if (propertyXml === null) return false;
|
|
334
|
+
const property = {
|
|
335
|
+
type: "preservedBlock",
|
|
336
|
+
xml: propertyXml
|
|
337
|
+
};
|
|
338
|
+
baselineContent.unshift(property);
|
|
339
|
+
currentContent.unshift(property);
|
|
340
|
+
} else if (generatedProperty !== void 0) {
|
|
341
|
+
const range = getXmlSourceRange(generatedProperty);
|
|
342
|
+
if (!range) return false;
|
|
343
|
+
const stripped = spliceXml(generatedView, [{
|
|
344
|
+
start: range.start - generatedRange.start,
|
|
345
|
+
end: range.end - generatedRange.start,
|
|
346
|
+
newXml: ""
|
|
347
|
+
}]);
|
|
348
|
+
if (stripped === null) return false;
|
|
349
|
+
generatedView = stripped;
|
|
350
|
+
}
|
|
351
|
+
const patched = replayBlocks({
|
|
352
|
+
sourceXml: sourceView,
|
|
353
|
+
serializedXml: generatedView,
|
|
354
|
+
baselineContent,
|
|
355
|
+
currentContent
|
|
356
|
+
}, {
|
|
357
|
+
source: sourceCell.namespaceScope,
|
|
358
|
+
generated: generatedCell.namespaceScope,
|
|
359
|
+
generatedRoot
|
|
360
|
+
});
|
|
361
|
+
if (patched === null) return false;
|
|
362
|
+
splices.push({
|
|
363
|
+
start: sourceRange.start - sourceRangeStart,
|
|
364
|
+
end: sourceRange.end - sourceRangeStart,
|
|
365
|
+
newXml: patched
|
|
366
|
+
});
|
|
367
|
+
return true;
|
|
368
|
+
};
|
|
369
|
+
const readDocumentStory = (xml) => {
|
|
370
|
+
const document = readStory(xml);
|
|
371
|
+
if (!document || getLocalName(document.root.name) !== "document") return null;
|
|
372
|
+
const bodies = wordChildren(document.root, "body");
|
|
373
|
+
const body = bodies.at(0);
|
|
374
|
+
if (bodies.length !== 1 || !body || wordChildren(body, "sectPr").length > 1 || wordChildren(document.root, "background").length > 1) return null;
|
|
375
|
+
const range = getXmlSourceRange(body);
|
|
376
|
+
return range ? {
|
|
377
|
+
document,
|
|
378
|
+
body,
|
|
379
|
+
range
|
|
380
|
+
} : null;
|
|
381
|
+
};
|
|
382
|
+
/** Validate source/model correspondence before replaying an unchanged complete part. */
|
|
383
|
+
const canReplayDocumentSource = (sourceXml, baseline) => {
|
|
384
|
+
const source = readDocumentStory(sourceXml);
|
|
385
|
+
if (!source) return false;
|
|
386
|
+
const body = readStory(sourceXml.slice(source.range.start, source.range.end), source.body.namespaceScope, "sectPr");
|
|
387
|
+
return body !== null && body.blocks.length === baseline.content.length && body.blocks.every((block, index) => modelMatchesElement({
|
|
388
|
+
value: baseline.content[index],
|
|
389
|
+
element: block.element,
|
|
390
|
+
mode: "source"
|
|
391
|
+
}));
|
|
392
|
+
};
|
|
393
|
+
/** Replay body blocks and modeled document metadata inside the authored part shell. */
|
|
394
|
+
const buildDocumentBlockReplay = ({ sourceXml, serializedXml, baseline, current }) => {
|
|
395
|
+
const sourceStory = readDocumentStory(sourceXml);
|
|
396
|
+
const generatedStory = readDocumentStory(serializedXml);
|
|
397
|
+
if (!sourceStory || !generatedStory) return null;
|
|
398
|
+
const { body: sourceBody, range: sourceRange } = sourceStory;
|
|
399
|
+
const { document: generated, body: generatedBody, range: generatedRange } = generatedStory;
|
|
400
|
+
let bodyXml = replayBlocks({
|
|
401
|
+
sourceXml: sourceXml.slice(sourceRange.start, sourceRange.end),
|
|
402
|
+
serializedXml: serializedXml.slice(generatedRange.start, generatedRange.end),
|
|
403
|
+
baselineContent: baseline.content,
|
|
404
|
+
currentContent: current.content,
|
|
405
|
+
ignoredChild: "sectPr"
|
|
406
|
+
}, {
|
|
407
|
+
source: sourceBody.namespaceScope,
|
|
408
|
+
generated: generatedBody.namespaceScope,
|
|
409
|
+
generatedRoot: generated.root
|
|
410
|
+
});
|
|
411
|
+
if (bodyXml === null) return null;
|
|
412
|
+
const replaceMetadata = (xml, sourceContainer, generatedContainer, name) => {
|
|
413
|
+
const before = wordChildren(sourceContainer, name);
|
|
414
|
+
const after = wordChildren(generatedContainer, name);
|
|
415
|
+
if (before.length > 1 || after.length > 1) return null;
|
|
416
|
+
const old = before.at(0);
|
|
417
|
+
const next = after.at(0);
|
|
418
|
+
const containerRange = getXmlSourceRange(sourceContainer);
|
|
419
|
+
const oldRange = old && getXmlSourceRange(old);
|
|
420
|
+
const nextRange = next && getXmlSourceRange(next);
|
|
421
|
+
if (!containerRange || old && !oldRange || next && !nextRange) return null;
|
|
422
|
+
const replacement = next && nextRange ? materializeReplayFragment({
|
|
423
|
+
xml: serializedXml.slice(nextRange.start, nextRange.end),
|
|
424
|
+
element: next,
|
|
425
|
+
sourceNamespace: getNamespaceUri(sourceContainer) ?? "",
|
|
426
|
+
generatedRoot: generated.root
|
|
427
|
+
}) : "";
|
|
428
|
+
if (replacement === null) return null;
|
|
429
|
+
if (oldRange) return spliceXml(xml, [{
|
|
430
|
+
start: oldRange.start - containerRange.start,
|
|
431
|
+
end: oldRange.end - containerRange.start,
|
|
432
|
+
newXml: replacement
|
|
433
|
+
}]);
|
|
434
|
+
if (!next) return xml;
|
|
435
|
+
const insertion = name === "background" ? [...xml.matchAll(XML_TOKEN)].at(0)?.[0].length ?? -1 : xml.lastIndexOf("</");
|
|
436
|
+
if (insertion < 0) return null;
|
|
437
|
+
return spliceXml(xml, [{
|
|
438
|
+
start: insertion,
|
|
439
|
+
end: insertion,
|
|
440
|
+
newXml: replacement
|
|
441
|
+
}]);
|
|
442
|
+
};
|
|
443
|
+
if (canonicalJson(baseline.finalSectionProperties) !== canonicalJson(current.finalSectionProperties)) {
|
|
444
|
+
const reparsed = parseStreamingXmlWithSourceRanges(bodyXml, sourceBody.namespaceScope);
|
|
445
|
+
if (reparsed.status !== "parsed") return null;
|
|
446
|
+
const root = getChildElements(reparsed.value).at(0);
|
|
447
|
+
if (!root) return null;
|
|
448
|
+
bodyXml = replaceMetadata(bodyXml, root, generatedBody, "sectPr");
|
|
449
|
+
if (bodyXml === null) return null;
|
|
450
|
+
}
|
|
451
|
+
let result = spliceXml(sourceXml, [{
|
|
452
|
+
start: sourceRange.start,
|
|
453
|
+
end: sourceRange.end,
|
|
454
|
+
newXml: bodyXml
|
|
455
|
+
}]);
|
|
456
|
+
if (result === null) return null;
|
|
457
|
+
if (canonicalJson(baseline.background) !== canonicalJson(current.background)) {
|
|
458
|
+
const reparsed = readStory(result);
|
|
459
|
+
if (!reparsed) return null;
|
|
460
|
+
const replaced = replaceMetadata(result.slice(reparsed.range.start, reparsed.range.end), reparsed.root, generated.root, "background");
|
|
461
|
+
if (replaced === null) return null;
|
|
462
|
+
result = spliceXml(result, [{
|
|
463
|
+
start: reparsed.range.start,
|
|
464
|
+
end: reparsed.range.end,
|
|
465
|
+
newXml: replaced
|
|
466
|
+
}]);
|
|
467
|
+
}
|
|
468
|
+
return result;
|
|
469
|
+
};
|
|
470
|
+
//#endregion
|
|
471
|
+
export { buildDocumentBlockReplay, buildStoryBlockReplay, canReplayDocumentSource, materializeReplayFragment };
|