@stll/folio-core 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.d.ts +15 -0
- package/dist/ai-edits/apply.js +271 -48
- package/dist/ai-edits/headless.d.ts +47 -3
- package/dist/ai-edits/headless.js +53 -3
- package/dist/ai-edits/index.d.ts +1 -1
- package/dist/ai-edits/snapshot.d.ts +19 -1
- package/dist/ai-edits/snapshot.js +49 -5
- package/dist/ai-edits/table-geometry.d.ts +56 -0
- package/dist/ai-edits/table-geometry.js +217 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
- package/dist/ai-edits/table-row-column-mutations.js +12 -4
- package/dist/ai-edits/table-template.d.ts +60 -0
- package/dist/ai-edits/table-template.js +176 -0
- package/dist/ai-edits/types.d.ts +13 -6
- package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
- package/dist/compare/__fixtures__/body-sequence.js +94 -6
- package/dist/compare/compare.d.ts +14 -7
- package/dist/compare/compare.js +136 -38
- package/dist/compare/formatting.d.ts +1 -1
- package/dist/compare/formatting.js +38 -9
- package/dist/compare/plan.d.ts +31 -1
- package/dist/compare/plan.js +233 -66
- package/dist/compare/types.d.ts +9 -7
- package/dist/compare/types.js +7 -5
- package/dist/compare/verification.d.ts +31 -12
- package/dist/compare/verification.js +106 -18
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/headerFooterEditorManager.js +11 -9
- package/dist/controller/layoutPipeline.js +24 -3
- package/dist/display-list/build/watermarkPrimitives.js +15 -3
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +10 -1
- package/dist/document-operations.js +33 -5
- package/dist/docx/appVersionNormalization.d.ts +49 -0
- package/dist/docx/appVersionNormalization.js +74 -0
- package/dist/docx/blockContentParser.js +3 -2
- package/dist/docx/drawingUtils.js +4 -3
- package/dist/docx/headerFooterParser.js +8 -14
- package/dist/docx/paragraphParser.js +48 -2
- package/dist/docx/rezip.d.ts +10 -4
- package/dist/docx/rezip.js +54 -18
- package/dist/docx/runParser.js +9 -8
- package/dist/docx/sdtProperties.js +4 -3
- package/dist/docx/selectiveSave.js +7 -6
- package/dist/docx/serializer/commentSerializer.d.ts +1 -1
- package/dist/docx/serializer/commentSerializer.js +43 -19
- package/dist/docx/serializer/documentSerializer.d.ts +3 -1
- package/dist/docx/serializer/documentSerializer.js +42 -78
- package/dist/docx/serializer/fontTableSerializer.js +8 -5
- package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
- package/dist/docx/serializer/headerFooterSerializer.js +55 -31
- package/dist/docx/serializer/noteSerializer.js +34 -30
- package/dist/docx/serializer/numberingSerializer.js +8 -4
- package/dist/docx/serializer/paragraphSerializer.d.ts +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +22 -12
- package/dist/docx/serializer/partNamespaces.d.ts +78 -0
- package/dist/docx/serializer/partNamespaces.js +324 -0
- package/dist/docx/serializer/runSerializer.js +1 -1
- package/dist/docx/serializer/settingsSerializer.js +8 -2
- package/dist/docx/serializer/stylesSerializer.js +8 -5
- package/dist/docx/serializer/tableSerializer.js +83 -18
- package/dist/docx/serializer/themeSerializer.js +10 -2
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/settingsParser.js +3 -0
- package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
- package/dist/docx/strictValueEncodings.gen.js +275 -0
- package/dist/docx/tableParser.d.ts +0 -6
- package/dist/docx/tableParser.js +52 -7
- package/dist/docx/transitionalSpelling.d.ts +23 -0
- package/dist/docx/transitionalSpelling.js +36 -0
- package/dist/docx/universalMeasure.d.ts +20 -0
- package/dist/docx/universalMeasure.js +32 -0
- package/dist/docx/verbatimCapture.d.ts +20 -0
- package/dist/docx/verbatimCapture.js +131 -0
- package/dist/docx/vmlImageParser.js +4 -3
- package/dist/docx/watermarkParser.d.ts +2 -4
- package/dist/docx/watermarkParser.js +8 -9
- package/dist/docx/xmlParser.d.ts +23 -2
- package/dist/docx/xmlParser.js +43 -11
- package/dist/headless-layout.js +14 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -2
- package/dist/layout-bridge/convert/footnoteLayout.js +23 -10
- package/dist/layout-bridge/convert/headerFooterLayout.js +13 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +96 -13
- package/dist/layout-engine/index.js +11 -4
- package/dist/layout-engine/justifiedLineFit.d.ts +4 -4
- package/dist/layout-engine/justifiedLineFit.js +4 -4
- package/dist/layout-engine/measure/lineBreakProvider.js +1 -0
- package/dist/layout-engine/measure/measureBlocks.js +1 -1
- package/dist/layout-engine/measure/measureParagraph.js +29 -29
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderWatermark.js +11 -4
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/prosemirror/attrs/index.js +11 -0
- package/dist/prosemirror/commands/comments.js +24 -3
- package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
- package/dist/prosemirror/containerFinalParagraph.js +92 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +47 -9
- package/dist/prosemirror/conversion/toProseDoc.js +61 -14
- package/dist/prosemirror/extensions/core/DocExtension.js +7 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
- package/dist/prosemirror/schema/marks.d.ts +3 -9
- package/dist/prosemirror/schema/nodes.d.ts +22 -0
- package/dist/server.d.ts +1 -1
- package/dist/types/block-id.d.ts +12 -1
- package/dist/types/block-id.js +17 -1
- package/dist/utils/canonicalJson.d.ts +14 -0
- package/dist/utils/canonicalJson.js +19 -0
- package/dist/utils/fontResolver.js +51 -0
- package/dist/utils/formatToStyle.d.ts +1 -1
- package/dist/utils/formatToStyle.js +41 -1
- package/dist/watermark/index.js +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { TaggedError } from "better-result";
|
|
2
|
+
//#region src/docx/appVersionNormalization.ts
|
|
3
|
+
/**
|
|
4
|
+
* Keep the application version a package states about itself in the form the
|
|
5
|
+
* schema gives it.
|
|
6
|
+
*
|
|
7
|
+
* `AppVersion` in the extended-properties part is `XX.YYYY`: a one- or
|
|
8
|
+
* two-digit integer, a dot, and four digits. Producers exist that write a
|
|
9
|
+
* three-part version there instead, and folio copies `docProps/app.xml`
|
|
10
|
+
* through verbatim when it saves a document it did not create — so a package
|
|
11
|
+
* can carry a value with two dots in, and a save that copies it out hands a
|
|
12
|
+
* consumer a package it refuses to open at all.
|
|
13
|
+
*
|
|
14
|
+
* {@link appVersionInSchemaForm} is the one mapping, and it is a pure function
|
|
15
|
+
* of the value alone: it keeps the leading integer where the value opens with
|
|
16
|
+
* one the form allows, and writes the build digits the form requires. Nothing
|
|
17
|
+
* else in the part is touched, and a package that carries no extended
|
|
18
|
+
* properties keeps carrying none — synthesizing metadata a document never
|
|
19
|
+
* stated is a different decision.
|
|
20
|
+
*/
|
|
21
|
+
/** `XX.YYYY`: the only form the extended-properties `AppVersion` may take. */
|
|
22
|
+
const SCHEMA_FORM = /^\d{1,2}\.\d{4}$/u;
|
|
23
|
+
/** The leading integer, when the value opens with one the form allows. */
|
|
24
|
+
const LEADING_MAJOR = /^\d{1,2}(?!\d)/u;
|
|
25
|
+
/** Major version for a value that does not open with one. */
|
|
26
|
+
const DEFAULT_MAJOR = "1";
|
|
27
|
+
/**
|
|
28
|
+
* The build digits a rewritten value gets. A value the form rejects states no
|
|
29
|
+
* build this pass could carry over, so every rewrite lands on the same one.
|
|
30
|
+
*/
|
|
31
|
+
const CANONICAL_BUILD = "0000";
|
|
32
|
+
/**
|
|
33
|
+
* A value reached the package that the schema form does not accept.
|
|
34
|
+
*
|
|
35
|
+
* The normalization hands every value it writes to one choke point, which
|
|
36
|
+
* throws this rather than letting the package go out carrying a version a
|
|
37
|
+
* consumer refuses.
|
|
38
|
+
*/
|
|
39
|
+
var AppVersionSchemaError = class extends TaggedError("AppVersionSchemaError") {};
|
|
40
|
+
/**
|
|
41
|
+
* `value` when it already fits, and a value derived from it when it does not.
|
|
42
|
+
*
|
|
43
|
+
* The derivation reads the value and nothing else — not the package, not the
|
|
44
|
+
* clock, not folio's own version — so two saves of one document state the same
|
|
45
|
+
* application version.
|
|
46
|
+
*/
|
|
47
|
+
const appVersionInSchemaForm = (value) => {
|
|
48
|
+
if (SCHEMA_FORM.test(value)) return value;
|
|
49
|
+
const normalized = `${LEADING_MAJOR.exec(value.trim())?.[0] ?? DEFAULT_MAJOR}.${CANONICAL_BUILD}`;
|
|
50
|
+
if (!SCHEMA_FORM.test(normalized)) throw new AppVersionSchemaError({
|
|
51
|
+
message: `Derived application version ${normalized} is not of the form XX.YYYY`,
|
|
52
|
+
appVersion: value
|
|
53
|
+
});
|
|
54
|
+
return normalized;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The element and its content, with the prefix and attributes it was written
|
|
58
|
+
* with preserved: the replacement rewrites the value between the tags and
|
|
59
|
+
* leaves the rest of the part byte-identical.
|
|
60
|
+
*/
|
|
61
|
+
const APP_VERSION_ELEMENT = /(<(?:[^\s<>/:]+:)?AppVersion(?:\s[^<>]*)?>)([^<]*)(<\/(?:[^\s<>/:]+:)?AppVersion>)/u;
|
|
62
|
+
/**
|
|
63
|
+
* Rewrite the application version an extended-properties part states.
|
|
64
|
+
*
|
|
65
|
+
* Returns the part unchanged when the value already fits and when the part
|
|
66
|
+
* states no version at all, so a save of a document that never carried a
|
|
67
|
+
* malformed one is byte-identical.
|
|
68
|
+
*/
|
|
69
|
+
const normalizeAppVersionInExtendedProperties = (xml) => xml.replace(APP_VERSION_ELEMENT, (whole, open, value, close) => {
|
|
70
|
+
const replacement = appVersionInSchemaForm(value);
|
|
71
|
+
return replacement === value ? whole : `${open}${replacement}${close}`;
|
|
72
|
+
});
|
|
73
|
+
//#endregion
|
|
74
|
+
export { AppVersionSchemaError, appVersionInSchemaForm, normalizeAppVersionInExtendedProperties };
|
|
@@ -6,7 +6,8 @@ import { parseParagraph } from "./paragraphParser.js";
|
|
|
6
6
|
import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
|
|
7
7
|
import { parseSdtProperties } from "./sdtProperties.js";
|
|
8
8
|
import { parseTable } from "./tableParser.js";
|
|
9
|
-
import {
|
|
9
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
10
|
+
import { findChild, getChildElements, getLocalName, mergeXmlnsDeclarations } from "./xmlParser.js";
|
|
10
11
|
//#region src/docx/blockContentParser.ts
|
|
11
12
|
const computeListMarker = (paragraph, { numbering, listCounters, abstractCounters, restartedNumIds, previousList, siblingNumIdsByAbstractNumId }) => {
|
|
12
13
|
const listRendering = paragraph.listRendering;
|
|
@@ -192,7 +193,7 @@ const captureSdtSiblingMarkers = (sdt) => {
|
|
|
192
193
|
sawContent = true;
|
|
193
194
|
continue;
|
|
194
195
|
}
|
|
195
|
-
const xml =
|
|
196
|
+
const xml = captureVerbatimXml(ch);
|
|
196
197
|
if (sawContent) afterParts.push(xml);
|
|
197
198
|
else beforeParts.push(xml);
|
|
198
199
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ImageHorizontalAlignmentSchema, ImageHorizontalRelativeToSchema, ImageVerticalAlignmentSchema, ImageVerticalRelativeToSchema, ImageWrapTextSchema, ShapeOutlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
-
import {
|
|
2
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
3
|
+
import { findByFullName, findChildByLocalName, findChildrenByLocalName, getAttribute, getChildElements, getTextContent, parseNumericAttribute } from "./xmlParser.js";
|
|
3
4
|
//#region src/docx/drawingUtils.ts
|
|
4
5
|
/**
|
|
5
6
|
* Map OOXML scheme names to standard theme color slots.
|
|
@@ -162,7 +163,7 @@ function parseGradientFill(gradientFill) {
|
|
|
162
163
|
}
|
|
163
164
|
return {
|
|
164
165
|
type: "gradient",
|
|
165
|
-
rawXml:
|
|
166
|
+
rawXml: captureVerbatimXml(gradientFill),
|
|
166
167
|
gradient: {
|
|
167
168
|
type,
|
|
168
169
|
...angle !== void 0 ? { angle } : {},
|
|
@@ -177,7 +178,7 @@ function parseOutline(spPr) {
|
|
|
177
178
|
const ln = spPr ? findChildByLocalName(spPr, "ln") : null;
|
|
178
179
|
if (!ln) return;
|
|
179
180
|
if (findChildByLocalName(ln, "noFill")) return;
|
|
180
|
-
const outline = { rawXml:
|
|
181
|
+
const outline = { rawXml: captureVerbatimXml(ln) };
|
|
181
182
|
const w = getAttribute(ln, null, "w");
|
|
182
183
|
if (w) {
|
|
183
184
|
const parsed = Number.parseInt(w, 10);
|
|
@@ -31,27 +31,21 @@ function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = n
|
|
|
31
31
|
result.rawWatermarkXml = watermarkResult.rawParagraphXml;
|
|
32
32
|
result.watermarkBlockIndex = watermarkResult.blockIndex;
|
|
33
33
|
}
|
|
34
|
-
result.content = parseBlockContent(
|
|
34
|
+
result.content = parseBlockContent(rootElement, styles, theme, numbering, rels, media, {
|
|
35
35
|
inHeaderFooter: true,
|
|
36
36
|
rootXmlns: collectXmlnsDeclarations(rootElement)
|
|
37
37
|
});
|
|
38
|
+
if (watermarkResult) {
|
|
39
|
+
const host = result.content.at(watermarkResult.blockIndex);
|
|
40
|
+
if (host?.type === "paragraph") result.content[watermarkResult.blockIndex] = {
|
|
41
|
+
...host,
|
|
42
|
+
content: []
|
|
43
|
+
};
|
|
44
|
+
}
|
|
38
45
|
assignHeaderFooterVerbatimXml(result, headerXml);
|
|
39
46
|
return result;
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
|
-
* Return a shallow copy of `parent` whose `elements` array omits the
|
|
43
|
-
* single child reference `child`. Used to skip the watermark paragraph
|
|
44
|
-
* when feeding the header into `parseBlockContent` — without this the
|
|
45
|
-
* body parser would emit an empty placeholder paragraph where the
|
|
46
|
-
* watermark sits in the source.
|
|
47
|
-
*/
|
|
48
|
-
function withoutChild(parent, child) {
|
|
49
|
-
return {
|
|
50
|
-
...parent,
|
|
51
|
-
elements: (parent.elements ?? []).filter((el) => el !== child)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
49
|
* Parse a footer XML file (word/footer*.xml)
|
|
56
50
|
*
|
|
57
51
|
* @param footerXml - The raw XML content of the footer file
|
|
@@ -10,7 +10,8 @@ import { consolidateParagraphContent } from "./runConsolidator.js";
|
|
|
10
10
|
import { parseRun, parseRunProperties } from "./runParser.js";
|
|
11
11
|
import { parseSdtProperties } from "./sdtProperties.js";
|
|
12
12
|
import { parseSectionProperties } from "./sectionParser.js";
|
|
13
|
-
import {
|
|
13
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
14
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildren, getAttribute, getChildElements, getLocalName, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
14
15
|
import { panic } from "better-result";
|
|
15
16
|
import { PARAGRAPH_MARK_CHANGE_KINDS, normalizeRevisionId } from "@stll/docx-core/model";
|
|
16
17
|
//#region src/docx/paragraphParser.ts
|
|
@@ -762,6 +763,47 @@ function parseSimpleField(node, styles, theme, rels, media, rootXmlns = {}) {
|
|
|
762
763
|
function hasRunPayloadElement(runElement) {
|
|
763
764
|
return getChildElements(runElement).some((child) => !matchesName(child, "w", "rPr"));
|
|
764
765
|
}
|
|
766
|
+
const LEGACY_FORM_CHECKBOX_GLYPHS = {
|
|
767
|
+
checked: "☒",
|
|
768
|
+
unchecked: "☐"
|
|
769
|
+
};
|
|
770
|
+
const LEGACY_FORM_CHECKBOX_INSTRUCTION = "FORMCHECKBOX";
|
|
771
|
+
function getLegacyFormCheckboxDisplay(runElement) {
|
|
772
|
+
const fieldChar = findChild(runElement, "w", "fldChar");
|
|
773
|
+
const fieldData = fieldChar ? findChild(fieldChar, "w", "ffData") : null;
|
|
774
|
+
const checkBox = fieldData ? findChild(fieldData, "w", "checkBox") : null;
|
|
775
|
+
if (!checkBox) return;
|
|
776
|
+
const checked = findChild(checkBox, "w", "checked");
|
|
777
|
+
const defaultChecked = findChild(checkBox, "w", "default");
|
|
778
|
+
let isChecked = defaultChecked ? parseBooleanElement(defaultChecked) : false;
|
|
779
|
+
if (checked) isChecked = parseBooleanElement(checked);
|
|
780
|
+
const explicitSize = parseNumericAttribute(findChild(checkBox, "w", "size"), "w", "val");
|
|
781
|
+
const text = isChecked ? LEGACY_FORM_CHECKBOX_GLYPHS.checked : LEGACY_FORM_CHECKBOX_GLYPHS.unchecked;
|
|
782
|
+
if (explicitSize === void 0) return { text };
|
|
783
|
+
return {
|
|
784
|
+
text,
|
|
785
|
+
fontSize: explicitSize
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function createLegacyFormCheckboxResultRun(display, inheritedFormatting) {
|
|
789
|
+
const run = {
|
|
790
|
+
type: "run",
|
|
791
|
+
content: [{
|
|
792
|
+
type: "text",
|
|
793
|
+
text: display.text
|
|
794
|
+
}]
|
|
795
|
+
};
|
|
796
|
+
const hasInheritedFormatting = inheritedFormatting !== void 0 && Object.keys(inheritedFormatting).length > 0;
|
|
797
|
+
if (display.fontSize !== void 0) run.formatting = hasInheritedFormatting ? {
|
|
798
|
+
...inheritedFormatting,
|
|
799
|
+
fontSize: display.fontSize
|
|
800
|
+
} : { fontSize: display.fontSize };
|
|
801
|
+
else if (hasInheritedFormatting) run.formatting = inheritedFormatting;
|
|
802
|
+
return run;
|
|
803
|
+
}
|
|
804
|
+
function isLegacyFormCheckboxInstruction(instruction) {
|
|
805
|
+
return instruction.trim().split(/\s+/u).at(0)?.toUpperCase() === LEGACY_FORM_CHECKBOX_INSTRUCTION;
|
|
806
|
+
}
|
|
765
807
|
/**
|
|
766
808
|
* Parse all content within a paragraph
|
|
767
809
|
*
|
|
@@ -778,6 +820,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
778
820
|
let afterSeparator = false;
|
|
779
821
|
let complexFieldLock = false;
|
|
780
822
|
let complexFieldDirty = false;
|
|
823
|
+
let complexFieldFallbackDisplay;
|
|
781
824
|
let complexFieldFormatting;
|
|
782
825
|
for (const child of children) {
|
|
783
826
|
const localName = getLocalName(child.name);
|
|
@@ -789,6 +832,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
789
832
|
let hasFieldBegin = false;
|
|
790
833
|
let beginFldLock = false;
|
|
791
834
|
let beginDirty = false;
|
|
835
|
+
const beginFallbackDisplay = getLegacyFormCheckboxDisplay(runElement);
|
|
792
836
|
let hasFieldSeparate = false;
|
|
793
837
|
let hasFieldEnd = false;
|
|
794
838
|
let endOriginalValue;
|
|
@@ -815,6 +859,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
815
859
|
complexFieldResultRuns = [];
|
|
816
860
|
complexFieldLock = beginFldLock;
|
|
817
861
|
complexFieldDirty = beginDirty;
|
|
862
|
+
complexFieldFallbackDisplay = beginFallbackDisplay;
|
|
818
863
|
complexFieldFormatting = run.formatting;
|
|
819
864
|
}
|
|
820
865
|
if (inComplexField) {
|
|
@@ -839,6 +884,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
839
884
|
}
|
|
840
885
|
if (hasFieldEnd) {
|
|
841
886
|
let resultRuns = complexFieldResultRuns;
|
|
887
|
+
if (resultRuns.length === 0 && complexFieldFallbackDisplay !== void 0 && isLegacyFormCheckboxInstruction(complexFieldInstr)) resultRuns = [createLegacyFormCheckboxResultRun(complexFieldFallbackDisplay, complexFieldFormatting)];
|
|
842
888
|
if (resultRuns.length === 0 && !afterSeparator && endOriginalValue !== void 0) resultRuns = [{
|
|
843
889
|
type: "run",
|
|
844
890
|
content: [{
|
|
@@ -995,7 +1041,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
995
1041
|
case "oMath":
|
|
996
1042
|
case "oMathPara": {
|
|
997
1043
|
const isBlock = localName === "oMathPara";
|
|
998
|
-
const ommlXml =
|
|
1044
|
+
const ommlXml = captureVerbatimXml(child);
|
|
999
1045
|
const plainText = extractMathText(child);
|
|
1000
1046
|
const mathEq = {
|
|
1001
1047
|
type: "mathEquation",
|
package/dist/docx/rezip.d.ts
CHANGED
|
@@ -106,11 +106,11 @@ declare function updateMultipleFiles(originalBuffer: ArrayBuffer, updates: Map<s
|
|
|
106
106
|
* Apply file updates to an already-loaded JSZip instance and generate the output.
|
|
107
107
|
* Use this when the zip is already loaded to avoid a redundant decompression pass.
|
|
108
108
|
*
|
|
109
|
-
* This is the selective save's exit, so it owes the package the same
|
|
109
|
+
* This is the selective save's exit, so it owes the package the same passes
|
|
110
110
|
* {@link generateDocxZip} runs: a save that rewrites only the changed
|
|
111
111
|
* paragraphs still has to see the parts it left alone, both to know which
|
|
112
|
-
* revision ids are free and because an out-of-range paragraph id
|
|
113
|
-
* part it never touched.
|
|
112
|
+
* revision ids are free and because an out-of-range paragraph id, or a
|
|
113
|
+
* malformed application version, can sit in a part it never touched.
|
|
114
114
|
*/
|
|
115
115
|
declare function applyUpdatesToZip(zip: JSZip, updates: Map<string, string | ArrayBuffer>, options?: RepackOptions): Promise<ArrayBuffer>;
|
|
116
116
|
/**
|
|
@@ -159,7 +159,13 @@ declare function hasUnmaterializedHeaderFooter(doc: document_d_exports.Document)
|
|
|
159
159
|
* have been set without yet resolving in that header's rels).
|
|
160
160
|
*/
|
|
161
161
|
declare function hasModelDrivenPictureWatermark(doc: document_d_exports.Document): boolean;
|
|
162
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Re-serialize every header/footer the model still owns, keyed by part path.
|
|
164
|
+
*
|
|
165
|
+
* `sourceZip` supplies each part as it stands before the save so the rebuilt
|
|
166
|
+
* root can keep any prefix binding only the source document declared.
|
|
167
|
+
*/
|
|
168
|
+
declare function collectHeaderFooterUpdates(doc: document_d_exports.Document, sourceZip: JSZip): Promise<Map<string, string>>;
|
|
163
169
|
/** `word/Footnotes.xml` -> `word/_rels/Footnotes.xml.rels` (casing preserved). */
|
|
164
170
|
declare function notePartRelsPath(partPath: string): string;
|
|
165
171
|
/** Update existing core-property values without synthesizing absent metadata. */
|
package/dist/docx/rezip.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
|
|
2
|
+
import { normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
|
|
2
3
|
import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
|
|
3
4
|
import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
|
|
4
5
|
import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
@@ -16,6 +17,7 @@ import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
|
16
17
|
import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
17
18
|
import { serializeEndnotes, serializeFootnotes, serializeNewEndnotesPart, serializeNewFootnotesPart } from "./serializer/noteSerializer.js";
|
|
18
19
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
20
|
+
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
19
21
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
20
22
|
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
21
23
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
@@ -122,13 +124,12 @@ function assertDocumentPackageFidelity(originalDocumentXml, serializedDocumentXm
|
|
|
122
124
|
async function serializeCommentsToZip(doc, zip, compressionLevel) {
|
|
123
125
|
const comments = doc.package.document.comments ?? [];
|
|
124
126
|
const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
|
|
127
|
+
const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
|
|
125
128
|
if (comments.length === 0) {
|
|
126
|
-
if (!
|
|
127
|
-
const sourceCommentsXml = await sourceCommentsFile.async("text");
|
|
128
|
-
if (!hasCommentEntries(sourceCommentsXml)) return;
|
|
129
|
+
if (sourceCommentsXml === void 0 || !hasCommentEntries(sourceCommentsXml)) return;
|
|
129
130
|
}
|
|
130
131
|
ensureThreadedCommentParaIds(comments);
|
|
131
|
-
const commentsXml = serializeComments(comments);
|
|
132
|
+
const commentsXml = serializeComments(comments, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
132
133
|
zip.file(sourceCommentsFile?.name ?? "word/comments.xml", commentsXml, {
|
|
133
134
|
compression: "DEFLATE",
|
|
134
135
|
compressionOptions: { level: compressionLevel }
|
|
@@ -428,6 +429,30 @@ const normalizePackageIdsInZip = async (zip, compressionLevel) => {
|
|
|
428
429
|
compressionOptions: { level: compressionLevel }
|
|
429
430
|
});
|
|
430
431
|
};
|
|
432
|
+
const EXTENDED_PROPERTIES_PATH = "docProps/app.xml";
|
|
433
|
+
/**
|
|
434
|
+
* Bring the application version the package states about itself into the form
|
|
435
|
+
* the schema gives it. A package that holds no extended properties keeps
|
|
436
|
+
* holding none.
|
|
437
|
+
*/
|
|
438
|
+
const normalizeAppVersionInZip = async (zip, compressionLevel) => {
|
|
439
|
+
const extendedProperties = zip.file(EXTENDED_PROPERTIES_PATH);
|
|
440
|
+
if (!extendedProperties) return;
|
|
441
|
+
const xml = await extendedProperties.async("text");
|
|
442
|
+
const normalized = normalizeAppVersionInExtendedProperties(xml);
|
|
443
|
+
if (normalized !== xml) zip.file(EXTENDED_PROPERTIES_PATH, normalized, {
|
|
444
|
+
compression: "DEFLATE",
|
|
445
|
+
compressionOptions: { level: compressionLevel }
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Every pass a save owes the package as a whole, in one function so that the
|
|
450
|
+
* full repack and the selective save cannot drift into running different ones.
|
|
451
|
+
*/
|
|
452
|
+
const normalizePackageOnSave = async (zip, compressionLevel) => {
|
|
453
|
+
await normalizePackageIdsInZip(zip, compressionLevel);
|
|
454
|
+
await normalizeAppVersionInZip(zip, compressionLevel);
|
|
455
|
+
};
|
|
431
456
|
/**
|
|
432
457
|
* The single exit for a repacked package. Reconciliation runs here rather than
|
|
433
458
|
* at each caller so no save path can emit a package whose relationships or
|
|
@@ -435,7 +460,7 @@ const normalizePackageIdsInZip = async (zip, compressionLevel) => {
|
|
|
435
460
|
*/
|
|
436
461
|
const generateDocxZip = async (zip, compressionLevel) => {
|
|
437
462
|
await reconcilePackageReferences(zip, compressionLevel);
|
|
438
|
-
await
|
|
463
|
+
await normalizePackageOnSave(zip, compressionLevel);
|
|
439
464
|
return zip.generateAsync({
|
|
440
465
|
type: "arraybuffer",
|
|
441
466
|
compression: "DEFLATE",
|
|
@@ -470,14 +495,14 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
470
495
|
await processNewHyperlinks(parts, outputZip, compressionLevel);
|
|
471
496
|
assertValidFolioDocumentModel(document, "Cannot repack invalid DOCX document model");
|
|
472
497
|
applyReplyThreadMarkers(document);
|
|
473
|
-
const documentXml = serializeDocument(document);
|
|
498
|
+
const documentXml = serializeDocument(document, originalDocumentXml === void 0 ? void 0 : readRootNamespaceBindings(originalDocumentXml));
|
|
474
499
|
if (originalDocumentXml) assertDocumentPackageFidelity(originalDocumentXml, documentXml, document);
|
|
475
500
|
outputZip.file("word/document.xml", documentXml, {
|
|
476
501
|
compression: "DEFLATE",
|
|
477
502
|
compressionOptions: { level: compressionLevel }
|
|
478
503
|
});
|
|
479
504
|
await rebindWatermarkRelIds(document, outputZip, compressionLevel);
|
|
480
|
-
serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
505
|
+
await serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
481
506
|
await serializeNotesToZip({
|
|
482
507
|
doc: document,
|
|
483
508
|
originalZip,
|
|
@@ -559,14 +584,14 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
559
584
|
await processNewHyperlinks(parts, newZip, compressionLevel);
|
|
560
585
|
assertValidFolioDocumentModel(exportDocument, "Cannot repack invalid DOCX document model");
|
|
561
586
|
applyReplyThreadMarkers(exportDocument);
|
|
562
|
-
const documentXml = serializeDocument(exportDocument);
|
|
587
|
+
const documentXml = serializeDocument(exportDocument, rawContent.documentXml ? readRootNamespaceBindings(rawContent.documentXml) : void 0);
|
|
563
588
|
if (rawContent.documentXml) assertDocumentPackageFidelity(rawContent.documentXml, documentXml, exportDocument);
|
|
564
589
|
newZip.file("word/document.xml", documentXml, {
|
|
565
590
|
compression: "DEFLATE",
|
|
566
591
|
compressionOptions: { level: compressionLevel }
|
|
567
592
|
});
|
|
568
593
|
await rebindWatermarkRelIds(exportDocument, newZip, compressionLevel);
|
|
569
|
-
serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
594
|
+
await serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
570
595
|
await serializeNotesToZip({
|
|
571
596
|
doc: exportDocument,
|
|
572
597
|
originalZip: rawContent.originalZip,
|
|
@@ -775,11 +800,11 @@ async function updateMultipleFiles(originalBuffer, updates, options = {}) {
|
|
|
775
800
|
* Apply file updates to an already-loaded JSZip instance and generate the output.
|
|
776
801
|
* Use this when the zip is already loaded to avoid a redundant decompression pass.
|
|
777
802
|
*
|
|
778
|
-
* This is the selective save's exit, so it owes the package the same
|
|
803
|
+
* This is the selective save's exit, so it owes the package the same passes
|
|
779
804
|
* {@link generateDocxZip} runs: a save that rewrites only the changed
|
|
780
805
|
* paragraphs still has to see the parts it left alone, both to know which
|
|
781
|
-
* revision ids are free and because an out-of-range paragraph id
|
|
782
|
-
* part it never touched.
|
|
806
|
+
* revision ids are free and because an out-of-range paragraph id, or a
|
|
807
|
+
* malformed application version, can sit in a part it never touched.
|
|
783
808
|
*/
|
|
784
809
|
async function applyUpdatesToZip(zip, updates, options = {}) {
|
|
785
810
|
const { compressionLevel = 6 } = options;
|
|
@@ -787,7 +812,7 @@ async function applyUpdatesToZip(zip, updates, options = {}) {
|
|
|
787
812
|
compression: "DEFLATE",
|
|
788
813
|
compressionOptions: { level: compressionLevel }
|
|
789
814
|
});
|
|
790
|
-
await
|
|
815
|
+
await normalizePackageOnSave(zip, compressionLevel);
|
|
791
816
|
return await zip.generateAsync({
|
|
792
817
|
type: "arraybuffer",
|
|
793
818
|
compression: "DEFLATE",
|
|
@@ -1090,7 +1115,13 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
|
|
|
1090
1115
|
});
|
|
1091
1116
|
}
|
|
1092
1117
|
}
|
|
1093
|
-
|
|
1118
|
+
/**
|
|
1119
|
+
* Re-serialize every header/footer the model still owns, keyed by part path.
|
|
1120
|
+
*
|
|
1121
|
+
* `sourceZip` supplies each part as it stands before the save so the rebuilt
|
|
1122
|
+
* root can keep any prefix binding only the source document declared.
|
|
1123
|
+
*/
|
|
1124
|
+
async function collectHeaderFooterUpdates(doc, sourceZip) {
|
|
1094
1125
|
const updates = /* @__PURE__ */ new Map();
|
|
1095
1126
|
const rels = doc.package.relationships;
|
|
1096
1127
|
if (!rels) return updates;
|
|
@@ -1107,8 +1138,13 @@ function collectHeaderFooterUpdates(doc) {
|
|
|
1107
1138
|
for (const [rId, headerFooter] of map.entries()) {
|
|
1108
1139
|
const rel = rels.get(rId);
|
|
1109
1140
|
if (rel && rel.type === type && rel.target) {
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1141
|
+
const path = resolveRelativePath(documentRelsPath, rel.target);
|
|
1142
|
+
const sourceFile = findZipEntryCaseInsensitive(sourceZip, path.toLowerCase());
|
|
1143
|
+
const bindings = sourceFile ? readRootNamespaceBindings(await sourceFile.async("text")) : /* @__PURE__ */ new Map();
|
|
1144
|
+
updates.set(path, serializeHeaderFooter(headerFooter, {
|
|
1145
|
+
path,
|
|
1146
|
+
bindings
|
|
1147
|
+
}));
|
|
1112
1148
|
}
|
|
1113
1149
|
}
|
|
1114
1150
|
}
|
|
@@ -1117,9 +1153,9 @@ function collectHeaderFooterUpdates(doc) {
|
|
|
1117
1153
|
/**
|
|
1118
1154
|
* Serialize modified headers and footers into the ZIP
|
|
1119
1155
|
*/
|
|
1120
|
-
function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1156
|
+
async function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1121
1157
|
const compressionOptions = { level: compressionLevel };
|
|
1122
|
-
for (const [filename, xml] of collectHeaderFooterUpdates(doc)) zip.file(filename, xml, {
|
|
1158
|
+
for (const [filename, xml] of await collectHeaderFooterUpdates(doc, zip)) zip.file(filename, xml, {
|
|
1123
1159
|
compression: "DEFLATE",
|
|
1124
1160
|
compressionOptions
|
|
1125
1161
|
});
|
package/dist/docx/runParser.js
CHANGED
|
@@ -7,8 +7,9 @@ import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapePar
|
|
|
7
7
|
import { isTextBoxDrawing } from "./textBoxParser.js";
|
|
8
8
|
import { requiresXmlSpacePreserve } from "./textWhitespace.js";
|
|
9
9
|
import { resolveThemeFontRef } from "./themeParser.js";
|
|
10
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
10
11
|
import { parseVmlImageContent } from "./vmlImageParser.js";
|
|
11
|
-
import { cloneWithXmlnsDeclarations,
|
|
12
|
+
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
12
13
|
import { DRAWING_RAW_XML_MODES, normalizeRevisionId } from "@stll/docx-core/model";
|
|
13
14
|
//#region src/docx/runParser.ts
|
|
14
15
|
/**
|
|
@@ -489,7 +490,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
489
490
|
if (groupImage) return {
|
|
490
491
|
type: "drawing",
|
|
491
492
|
image: groupImage,
|
|
492
|
-
rawXml:
|
|
493
|
+
rawXml: captureVerbatimXml(element)
|
|
493
494
|
};
|
|
494
495
|
if (shouldPreserveRawShapeDrawing(element)) return {
|
|
495
496
|
type: "drawing",
|
|
@@ -502,7 +503,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
502
503
|
},
|
|
503
504
|
wrap: { type: "inline" }
|
|
504
505
|
},
|
|
505
|
-
rawXml:
|
|
506
|
+
rawXml: captureVerbatimXml(element),
|
|
506
507
|
rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
|
|
507
508
|
};
|
|
508
509
|
const shape = parseShapeFromDrawing(element);
|
|
@@ -516,7 +517,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
516
517
|
type: "drawing",
|
|
517
518
|
image
|
|
518
519
|
};
|
|
519
|
-
if (!image.src) drawing.rawXml =
|
|
520
|
+
if (!image.src) drawing.rawXml = captureVerbatimXml(element);
|
|
520
521
|
return drawing;
|
|
521
522
|
}
|
|
522
523
|
/**
|
|
@@ -593,7 +594,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
593
594
|
if (groupedChoiceDrawing) {
|
|
594
595
|
const groupedDrawing = parseDrawingContent(groupedChoiceDrawing, rels, media);
|
|
595
596
|
if (groupedDrawing?.type === "drawing" && groupedDrawing.image.src) {
|
|
596
|
-
groupedDrawing.rawXml =
|
|
597
|
+
groupedDrawing.rawXml = captureVerbatimXml(child);
|
|
597
598
|
contents.push(groupedDrawing);
|
|
598
599
|
break;
|
|
599
600
|
}
|
|
@@ -601,7 +602,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
601
602
|
const fallbackPict = fallbackEl ? getChildElements(fallbackEl).find((el) => getLocalName(el.name) === "pict") : void 0;
|
|
602
603
|
const fallbackVml = fallbackPict && !choiceTextBoxDrawing ? parseVmlImageContent(fallbackPict, rels, media, rootXmlns) : null;
|
|
603
604
|
if (fallbackVml?.image.src) {
|
|
604
|
-
fallbackVml.rawXml =
|
|
605
|
+
fallbackVml.rawXml = captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns));
|
|
605
606
|
contents.push(fallbackVml);
|
|
606
607
|
break;
|
|
607
608
|
}
|
|
@@ -611,13 +612,13 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
611
612
|
if (innerName === "drawing") {
|
|
612
613
|
const innerDrawing = parseDrawingContent(innerChild, rels, media);
|
|
613
614
|
if (innerDrawing) {
|
|
614
|
-
if (innerDrawing.type === "drawing" && (innerDrawing.rawXml !== void 0 || !innerDrawing.image.src)) innerDrawing.rawXml =
|
|
615
|
+
if (innerDrawing.type === "drawing" && (innerDrawing.rawXml !== void 0 || !innerDrawing.image.src)) innerDrawing.rawXml = captureVerbatimXml(child);
|
|
615
616
|
contents.push(innerDrawing);
|
|
616
617
|
}
|
|
617
618
|
} else if (innerName === "pict") {
|
|
618
619
|
const innerVml = parseVmlImageContent(innerChild, rels, media, rootXmlns);
|
|
619
620
|
if (innerVml) {
|
|
620
|
-
innerVml.rawXml =
|
|
621
|
+
innerVml.rawXml = captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns));
|
|
621
622
|
contents.push(innerVml);
|
|
622
623
|
}
|
|
623
624
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SdtLockSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
-
import {
|
|
2
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
3
|
+
import { findChild, getAttributeAnyPrefix, getLocalName, parseBooleanElement } from "./xmlParser.js";
|
|
3
4
|
//#region src/docx/sdtProperties.ts
|
|
4
5
|
function parseListItems(el) {
|
|
5
6
|
const items = [];
|
|
@@ -201,7 +202,7 @@ function rewriteOpenTagAttrPrefixes(raw, canonical, local) {
|
|
|
201
202
|
function parseSdtProperties(sdtPr, sdtEndPr) {
|
|
202
203
|
const props = { sdtType: "richText" };
|
|
203
204
|
if (sdtPr) {
|
|
204
|
-
props.rawPropertiesXml = normalizeWordPrefix(
|
|
205
|
+
props.rawPropertiesXml = normalizeWordPrefix(captureVerbatimXml(sdtPr), sdtPr);
|
|
205
206
|
for (const el of sdtPr.elements ?? []) {
|
|
206
207
|
if (el.type !== "element") continue;
|
|
207
208
|
const name = el.name ? getLocalName(el.name) : "";
|
|
@@ -283,7 +284,7 @@ function parseSdtProperties(sdtPr, sdtEndPr) {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
|
-
if (sdtEndPr) props.rawEndPropertiesXml = normalizeWordPrefix(
|
|
287
|
+
if (sdtEndPr) props.rawEndPropertiesXml = normalizeWordPrefix(captureVerbatimXml(sdtEndPr), sdtEndPr);
|
|
287
288
|
return props;
|
|
288
289
|
}
|
|
289
290
|
//#endregion
|
|
@@ -13,6 +13,7 @@ import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExten
|
|
|
13
13
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
14
14
|
import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
|
|
15
15
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
16
|
+
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
16
17
|
//#region src/docx/selectiveSave.ts
|
|
17
18
|
/**
|
|
18
19
|
* Check if document content has new images (data: URL without rId) or
|
|
@@ -209,7 +210,6 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
209
210
|
if (!validateFolioDocumentModel(doc).valid) return null;
|
|
210
211
|
const comments = doc.package.document.comments ?? [];
|
|
211
212
|
const hasComments = comments.length > 0;
|
|
212
|
-
const headerFooterUpdates = collectHeaderFooterUpdates(doc);
|
|
213
213
|
try {
|
|
214
214
|
const zip = await (await import("jszip")).default.loadAsync(originalBuffer);
|
|
215
215
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && isUnsafePackagePath(path)) return null;
|
|
@@ -228,15 +228,16 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
228
228
|
if (unrouted === null || unrouted.size > 0) return null;
|
|
229
229
|
}
|
|
230
230
|
if (bodyChangedIds.size > 0) {
|
|
231
|
-
const patchedDocXml = buildPatchedDocumentXml(originalDocXml, serializeDocument(doc), bodyChangedIds);
|
|
231
|
+
const patchedDocXml = buildPatchedDocumentXml(originalDocXml, serializeDocument(doc, readRootNamespaceBindings(originalDocXml)), bodyChangedIds);
|
|
232
232
|
if (!patchedDocXml) return null;
|
|
233
233
|
updates.set("word/document.xml", patchedDocXml);
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
-
const
|
|
237
|
-
if (hasComments ||
|
|
236
|
+
const sourceCommentsFile = zip.file("word/comments.xml");
|
|
237
|
+
if (hasComments || sourceCommentsFile) {
|
|
238
238
|
ensureThreadedCommentParaIds(comments);
|
|
239
|
-
|
|
239
|
+
const sourceBindings = sourceCommentsFile ? readRootNamespaceBindings(await sourceCommentsFile.async("text")) : void 0;
|
|
240
|
+
updates.set("word/comments.xml", serializeComments(comments, sourceBindings));
|
|
240
241
|
}
|
|
241
242
|
if (hasComments) {
|
|
242
243
|
const ctFile = zip.file("[Content_Types].xml");
|
|
@@ -256,7 +257,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
256
257
|
}
|
|
257
258
|
if (!await patchCommentsExtended(zip, comments, updates)) return null;
|
|
258
259
|
await patchNumberingPart(zip, doc, updates);
|
|
259
|
-
for (const [path, xml] of
|
|
260
|
+
for (const [path, xml] of await collectHeaderFooterUpdates(doc, zip)) updates.set(path, xml);
|
|
260
261
|
await queueSettingsUpdates(zip, updates);
|
|
261
262
|
const corePropsFile = zip.file("docProps/core.xml");
|
|
262
263
|
if (corePropsFile) {
|
|
@@ -7,7 +7,7 @@ import { Comment } from "../../types/content.js";
|
|
|
7
7
|
* comment — leaving the previous file in place would otherwise re-emit
|
|
8
8
|
* the orphaned comment threads on every save.
|
|
9
9
|
*/
|
|
10
|
-
declare function serializeComments(comments: Comment[]): string;
|
|
10
|
+
declare function serializeComments(comments: Comment[], sourceBindings?: ReadonlyMap<string, string>): string;
|
|
11
11
|
/**
|
|
12
12
|
* Assign a deterministic `w14:paraId` to the LAST paragraph of every comment
|
|
13
13
|
* that needs a commentsExtended entry (a reply, a reply's parent, or a resolved
|