@stll/folio-core 0.59.0 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/compat/eigenpal.d.ts +2 -2
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/canonicalSession.js +3 -2
- package/dist/controller/canonicalStructure.js +10 -7
- package/dist/controller/fontReadiness.js +1 -1
- package/dist/controller/layoutPipeline.js +7 -1
- package/dist/controller/layoutSession.d.ts +2 -0
- package/dist/display-list/build/fontTable.js +7 -5
- package/dist/display-list/build/furniture.d.ts +1 -0
- package/dist/display-list/build/furniture.js +1 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +2 -1
- package/dist/display-list/build/watermarkPrimitives.js +9 -3
- package/dist/display-list/editor/pageRenderer.js +1 -0
- package/dist/docx/bookmarkIdNormalization.d.ts +10 -0
- package/dist/docx/bookmarkIdNormalization.js +100 -0
- package/dist/docx/bookmarkParser.js +4 -2
- package/dist/docx/importedIdCollisions.d.ts +136 -0
- package/dist/docx/importedIdCollisions.js +215 -0
- package/dist/docx/numberingParser.js +2 -2
- package/dist/docx/paragraphPropertySource.js +1 -2
- package/dist/docx/parseContext.d.ts +2 -1
- package/dist/docx/parseContext.js +1 -0
- package/dist/docx/parseWarningMessage.js +1 -0
- package/dist/docx/parser.js +5 -1
- package/dist/docx/relsParser.js +1 -1
- package/dist/docx/rezip.js +2 -1
- package/dist/docx/selectedXmlIdentityScan.d.ts +10 -0
- package/dist/docx/selectedXmlIdentityScan.js +36 -0
- package/dist/docx/selectiveSave.js +5 -4
- package/dist/docx/selectiveXmlPatch.js +44 -52
- package/dist/docx/serializer/commentSerializer.d.ts +3 -1
- package/dist/docx/serializer/commentSerializer.js +11 -2
- package/dist/docx/serializer/runSerializer.js +2 -2
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +1 -3
- package/dist/docx/serializer/trackedChangeAttributes.js +1 -11
- package/dist/fonts/embeddedFontFingerprint.d.ts +5 -0
- package/dist/fonts/embeddedFontFingerprint.js +18 -0
- package/dist/fonts/embeddedFonts.d.ts +1 -1
- package/dist/fonts/embeddedFonts.js +1 -1
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/headless-layout.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +2 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/paragraphConversion.js +8 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +25 -1
- package/dist/layout-engine/measure/cache.js +39 -7
- package/dist/layout-engine/measure/lineTypography.js +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +1 -0
- package/dist/layout-engine/measure/measureContainer.js +3 -4
- package/dist/layout-engine/measure/measureHelpers.d.ts +6 -9
- package/dist/layout-engine/measure/measureHelpers.js +21 -18
- package/dist/layout-engine/measure/measureParagraph.js +2 -0
- package/dist/layout-engine/measure/measureTypes.d.ts +2 -0
- package/dist/layout-engine/types.d.ts +4 -1
- package/dist/layout-painter/renderPage.d.ts +1 -0
- package/dist/layout-painter/renderPage.js +11 -3
- package/dist/layout-painter/renderParagraph.js +13 -3
- package/dist/layout-painter/renderWatermark.d.ts +1 -0
- package/dist/layout-painter/renderWatermark.js +7 -3
- package/dist/managers/editorShortcuts.d.ts +2 -1
- package/dist/managers/editorShortcuts.js +2 -1
- package/dist/prosemirror/canonicalSelectionRange.d.ts +4 -1
- package/dist/prosemirror/canonicalSelectionRange.js +9 -3
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +8 -2
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +39 -3
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.d.ts +8 -2
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +4 -3
- package/dist/prosemirror/extensions/features/ImageDragExtension.js +4 -3
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +4 -3
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +9 -3
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +6 -5
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +8 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +11 -3
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/markUtils.js +1 -1
- package/dist/prosemirror/pluginKeyOwner.d.ts +13 -0
- package/dist/prosemirror/pluginKeyOwner.js +35 -0
- package/dist/prosemirror/plugins/aiCitationDecorations.d.ts +11 -4
- package/dist/prosemirror/plugins/aiCitationDecorations.js +16 -3
- package/dist/prosemirror/plugins/aiSuggestionDecorations.d.ts +11 -4
- package/dist/prosemirror/plugins/aiSuggestionDecorations.js +18 -3
- package/dist/prosemirror/plugins/anonymizationDecorations.d.ts +11 -11
- package/dist/prosemirror/plugins/anonymizationDecorations.js +23 -13
- package/dist/prosemirror/plugins/autocompleteSuggestion.d.ts +8 -2
- package/dist/prosemirror/plugins/autocompleteSuggestion.js +27 -5
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +8 -2
- package/dist/prosemirror/plugins/contentControlWidgets.js +4 -3
- package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +6 -5
- package/dist/prosemirror/plugins/createDocScanPlugin.js +1 -1
- package/dist/prosemirror/plugins/documentNumbering.js +5 -3
- package/dist/prosemirror/plugins/documentStyleState.d.ts +8 -2
- package/dist/prosemirror/plugins/documentStyleState.js +2 -2
- package/dist/prosemirror/plugins/documentStyles.js +1 -1
- package/dist/prosemirror/plugins/selectionTracker.d.ts +8 -2
- package/dist/prosemirror/plugins/selectionTracker.js +4 -3
- package/dist/prosemirror/plugins/suggestionMode.d.ts +8 -2
- package/dist/prosemirror/plugins/suggestionMode.js +4 -3
- package/dist/prosemirror/plugins/templateDirectives.d.ts +8 -2
- package/dist/prosemirror/plugins/templateDirectives.js +19 -4
- package/dist/prosemirror/plugins/templatePreviewValues.d.ts +8 -2
- package/dist/prosemirror/plugins/templatePreviewValues.js +27 -5
- package/dist/prosemirror/plugins/templateSlashMenu.d.ts +8 -2
- package/dist/prosemirror/plugins/templateSlashMenu.js +23 -5
- package/dist/prosemirror/plugins/transactionInvariants.js +23 -3
- package/dist/utils/fontLoader.d.ts +2 -2
- package/dist/utils/fontLoader.js +2 -7
- package/dist/utils/fontResolver.d.ts +7 -3
- package/dist/utils/fontResolver.js +6 -8
- package/package.json +24 -8
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { paraIdAttribute, textIdAttribute } from "./paraIdAttribute.js";
|
|
2
|
+
import { REVISION_ELEMENT_NAMES } from "./revisionIdNormalization.js";
|
|
3
|
+
import { scanSelectedStreamingXmlElements } from "./selectedXmlIdentityScan.js";
|
|
4
|
+
import { OOXML_NAMESPACES } from "./serializer/partNamespaces.js";
|
|
5
|
+
import { toTransitionalNamespaceUri } from "./transitionalSpelling.js";
|
|
6
|
+
import { resolveNamespaceUri } from "./xmlNamespaceContext.js";
|
|
7
|
+
import { NAMESPACES, getLocalName, getNamespaceUri } from "./xmlParser.js";
|
|
8
|
+
import { panic } from "better-result";
|
|
9
|
+
import { IMPORTED_ID_COLLISION_KINDS, PARSE_WARNING_CODES } from "@stll/docx-core/model";
|
|
10
|
+
//#region src/docx/importedIdCollisions.ts
|
|
11
|
+
const WORD_2010_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
12
|
+
const PACKAGE_RELATIONSHIP_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
13
|
+
const paragraphIdentityForKind = (kind, element) => {
|
|
14
|
+
switch (kind) {
|
|
15
|
+
case IMPORTED_ID_COLLISION_KINDS.paraId: return paraIdAttribute(element);
|
|
16
|
+
case IMPORTED_ID_COLLISION_KINDS.textId: return textIdAttribute(element);
|
|
17
|
+
default: return;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** Definition sites only: paired markers and references never claim an identity. */
|
|
21
|
+
const IMPORTED_ID_COLLISION_REGISTRY = {
|
|
22
|
+
bookmark: {
|
|
23
|
+
kind: IMPORTED_ID_COLLISION_KINDS.bookmark,
|
|
24
|
+
scope: "part",
|
|
25
|
+
sites: [{
|
|
26
|
+
namespace: NAMESPACES.w,
|
|
27
|
+
elements: /* @__PURE__ */ new Set(["bookmarkStart"])
|
|
28
|
+
}],
|
|
29
|
+
attribute: "id",
|
|
30
|
+
attributeNamespace: NAMESPACES.w,
|
|
31
|
+
encoding: "decimal"
|
|
32
|
+
},
|
|
33
|
+
comment: {
|
|
34
|
+
kind: IMPORTED_ID_COLLISION_KINDS.comment,
|
|
35
|
+
scope: "part",
|
|
36
|
+
sites: [{
|
|
37
|
+
namespace: NAMESPACES.w,
|
|
38
|
+
elements: /* @__PURE__ */ new Set(["comment"])
|
|
39
|
+
}],
|
|
40
|
+
attribute: "id",
|
|
41
|
+
attributeNamespace: NAMESPACES.w,
|
|
42
|
+
encoding: "decimal"
|
|
43
|
+
},
|
|
44
|
+
footnote: {
|
|
45
|
+
kind: IMPORTED_ID_COLLISION_KINDS.footnote,
|
|
46
|
+
scope: "part",
|
|
47
|
+
sites: [{
|
|
48
|
+
namespace: NAMESPACES.w,
|
|
49
|
+
elements: /* @__PURE__ */ new Set(["footnote"])
|
|
50
|
+
}],
|
|
51
|
+
attribute: "id",
|
|
52
|
+
attributeNamespace: NAMESPACES.w,
|
|
53
|
+
encoding: "decimal"
|
|
54
|
+
},
|
|
55
|
+
endnote: {
|
|
56
|
+
kind: IMPORTED_ID_COLLISION_KINDS.endnote,
|
|
57
|
+
scope: "part",
|
|
58
|
+
sites: [{
|
|
59
|
+
namespace: NAMESPACES.w,
|
|
60
|
+
elements: /* @__PURE__ */ new Set(["endnote"])
|
|
61
|
+
}],
|
|
62
|
+
attribute: "id",
|
|
63
|
+
attributeNamespace: NAMESPACES.w,
|
|
64
|
+
encoding: "decimal"
|
|
65
|
+
},
|
|
66
|
+
revision: {
|
|
67
|
+
kind: IMPORTED_ID_COLLISION_KINDS.revision,
|
|
68
|
+
scope: "package",
|
|
69
|
+
sites: [{
|
|
70
|
+
namespace: NAMESPACES.w,
|
|
71
|
+
elements: REVISION_ELEMENT_NAMES
|
|
72
|
+
}],
|
|
73
|
+
attribute: "id",
|
|
74
|
+
attributeNamespace: NAMESPACES.w,
|
|
75
|
+
encoding: "decimal"
|
|
76
|
+
},
|
|
77
|
+
paraId: {
|
|
78
|
+
kind: IMPORTED_ID_COLLISION_KINDS.paraId,
|
|
79
|
+
scope: "part",
|
|
80
|
+
sites: [
|
|
81
|
+
{
|
|
82
|
+
namespace: NAMESPACES.w,
|
|
83
|
+
elements: /* @__PURE__ */ new Set(["p"])
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
namespace: NAMESPACES.w15,
|
|
87
|
+
elements: /* @__PURE__ */ new Set(["commentEx"])
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
namespace: OOXML_NAMESPACES.w16cex.uri,
|
|
91
|
+
elements: /* @__PURE__ */ new Set(["comment"])
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
attribute: "paraId",
|
|
95
|
+
attributeNamespace: WORD_2010_NAMESPACE,
|
|
96
|
+
encoding: "hex"
|
|
97
|
+
},
|
|
98
|
+
textId: {
|
|
99
|
+
kind: IMPORTED_ID_COLLISION_KINDS.textId,
|
|
100
|
+
scope: "part",
|
|
101
|
+
sites: [{
|
|
102
|
+
namespace: NAMESPACES.w,
|
|
103
|
+
elements: /* @__PURE__ */ new Set(["p"])
|
|
104
|
+
}],
|
|
105
|
+
attribute: "textId",
|
|
106
|
+
attributeNamespace: WORD_2010_NAMESPACE,
|
|
107
|
+
encoding: "hex"
|
|
108
|
+
},
|
|
109
|
+
numId: {
|
|
110
|
+
kind: IMPORTED_ID_COLLISION_KINDS.numId,
|
|
111
|
+
scope: "part",
|
|
112
|
+
sites: [{
|
|
113
|
+
namespace: NAMESPACES.w,
|
|
114
|
+
elements: /* @__PURE__ */ new Set(["num"])
|
|
115
|
+
}],
|
|
116
|
+
attribute: "numId",
|
|
117
|
+
attributeNamespace: NAMESPACES.w,
|
|
118
|
+
encoding: "decimal"
|
|
119
|
+
},
|
|
120
|
+
abstractNumId: {
|
|
121
|
+
kind: IMPORTED_ID_COLLISION_KINDS.abstractNumId,
|
|
122
|
+
scope: "part",
|
|
123
|
+
sites: [{
|
|
124
|
+
namespace: NAMESPACES.w,
|
|
125
|
+
elements: /* @__PURE__ */ new Set(["abstractNum"])
|
|
126
|
+
}],
|
|
127
|
+
attribute: "abstractNumId",
|
|
128
|
+
attributeNamespace: NAMESPACES.w,
|
|
129
|
+
encoding: "decimal"
|
|
130
|
+
},
|
|
131
|
+
drawing: {
|
|
132
|
+
kind: IMPORTED_ID_COLLISION_KINDS.drawing,
|
|
133
|
+
scope: "package",
|
|
134
|
+
sites: [{
|
|
135
|
+
namespace: NAMESPACES.wp,
|
|
136
|
+
elements: /* @__PURE__ */ new Set(["docPr"])
|
|
137
|
+
}],
|
|
138
|
+
attribute: "id",
|
|
139
|
+
attributeNamespace: null,
|
|
140
|
+
encoding: "decimal"
|
|
141
|
+
},
|
|
142
|
+
relationship: {
|
|
143
|
+
kind: IMPORTED_ID_COLLISION_KINDS.relationship,
|
|
144
|
+
scope: "part",
|
|
145
|
+
sites: [{
|
|
146
|
+
namespace: PACKAGE_RELATIONSHIP_NAMESPACE,
|
|
147
|
+
elements: /* @__PURE__ */ new Set(["Relationship"])
|
|
148
|
+
}],
|
|
149
|
+
attribute: "Id",
|
|
150
|
+
attributeNamespace: null,
|
|
151
|
+
encoding: "string"
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const normalizedIdentity = (value, encoding) => {
|
|
155
|
+
if (encoding === "string") return value;
|
|
156
|
+
if (encoding === "hex") return /^[0-9a-f]{8}$/iu.test(value) ? value.toUpperCase() : void 0;
|
|
157
|
+
const spelling = value.trim();
|
|
158
|
+
if (!/^[+-]?\d+$/u.test(spelling)) return void 0;
|
|
159
|
+
const digits = spelling.replace(/^[+-]?0*/u, "");
|
|
160
|
+
return digits === "" ? "0" : `${spelling.startsWith("-") ? "-" : ""}${digits}`;
|
|
161
|
+
};
|
|
162
|
+
/** Reports repeated source definitions without changing the package or choosing a winner. */
|
|
163
|
+
const diagnoseImportedIdCollisions = (parts, context) => {
|
|
164
|
+
const rules = Object.values(IMPORTED_ID_COLLISION_REGISTRY);
|
|
165
|
+
const packageSeen = /* @__PURE__ */ new Map();
|
|
166
|
+
for (const [part, xml] of parts) {
|
|
167
|
+
const partSeen = /* @__PURE__ */ new Map();
|
|
168
|
+
const scoped = context.scoped({ part });
|
|
169
|
+
scanSelectedStreamingXmlElements(xml, ({ element, attributeValueSpans }) => {
|
|
170
|
+
if (element.name === void 0) return;
|
|
171
|
+
const namespace = toTransitionalNamespaceUri(getNamespaceUri(element) ?? "");
|
|
172
|
+
const localName = getLocalName(element.name);
|
|
173
|
+
for (const rule of rules) {
|
|
174
|
+
const kind = rule.kind;
|
|
175
|
+
if (!rule.sites.some((site) => namespace === site.namespace && site.elements.has(localName))) continue;
|
|
176
|
+
const paragraphIdentity = paragraphIdentityForKind(kind, element);
|
|
177
|
+
const seen = rule.scope === "package" ? packageSeen : partSeen;
|
|
178
|
+
for (const [name, rawValue] of Object.entries(element.attributes ?? {})) {
|
|
179
|
+
if (getLocalName(name) !== rule.attribute || rawValue === void 0) continue;
|
|
180
|
+
const colon = name.indexOf(":");
|
|
181
|
+
const attributeNamespace = colon < 0 ? null : toTransitionalNamespaceUri(resolveNamespaceUri(element.namespaceScope, name.slice(0, colon)) ?? "");
|
|
182
|
+
if (rule.encoding === "hex") {
|
|
183
|
+
if (String(rawValue) !== paragraphIdentity) continue;
|
|
184
|
+
} else if (attributeNamespace !== rule.attributeNamespace) continue;
|
|
185
|
+
const value = String(rawValue);
|
|
186
|
+
const identity = normalizedIdentity(value, rule.encoding);
|
|
187
|
+
if (identity === void 0) continue;
|
|
188
|
+
const key = `${kind}:${identity}`;
|
|
189
|
+
const span = attributeValueSpans.get(name);
|
|
190
|
+
if (span === void 0) panic("Missing imported identity source span");
|
|
191
|
+
const offset = span.start;
|
|
192
|
+
const first = seen.get(key);
|
|
193
|
+
if (first === void 0) {
|
|
194
|
+
seen.set(key, {
|
|
195
|
+
part,
|
|
196
|
+
offset
|
|
197
|
+
});
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
scoped.warn({
|
|
201
|
+
code: PARSE_WARNING_CODES.duplicateImportedId,
|
|
202
|
+
identityKind: kind,
|
|
203
|
+
element: element.name,
|
|
204
|
+
at: `offset ${offset}`,
|
|
205
|
+
value,
|
|
206
|
+
detail: `${kind} identity repeats the definition at offset ${first.offset} in ${first.part}.`
|
|
207
|
+
});
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
//#endregion
|
|
215
|
+
export { IMPORTED_ID_COLLISION_REGISTRY, diagnoseImportedIdCollisions };
|
|
@@ -398,9 +398,9 @@ function getCachedNumberingMap(definitions) {
|
|
|
398
398
|
*/
|
|
399
399
|
function createNumberingMap(definitions) {
|
|
400
400
|
const abstractMap = /* @__PURE__ */ new Map();
|
|
401
|
-
for (const abs of definitions.abstractNums) abstractMap.set(abs.abstractNumId, abs);
|
|
401
|
+
for (const abs of definitions.abstractNums) if (!abstractMap.has(abs.abstractNumId)) abstractMap.set(abs.abstractNumId, abs);
|
|
402
402
|
const numMap = /* @__PURE__ */ new Map();
|
|
403
|
-
for (const num of definitions.nums) numMap.set(num.numId, num);
|
|
403
|
+
for (const num of definitions.nums) if (!numMap.has(num.numId)) numMap.set(num.numId, num);
|
|
404
404
|
return {
|
|
405
405
|
definitions,
|
|
406
406
|
getLevel(numId, ilvl) {
|
|
@@ -5,7 +5,6 @@ import { parseParagraphProperties } from "./paragraphProperties.js";
|
|
|
5
5
|
import { visitDocxParagraphs } from "./paragraphTraversal.js";
|
|
6
6
|
import { parseXmlDocument } from "./xmlParser.js";
|
|
7
7
|
import { TaggedError, panic } from "better-result";
|
|
8
|
-
import { PluginKey } from "prosemirror-state";
|
|
9
8
|
//#region src/docx/paragraphPropertySource.ts
|
|
10
9
|
const paragraphPropertySourceEmissionFingerprint = Symbol("paragraphPropertySourceEmissionFingerprint");
|
|
11
10
|
const paragraphPropertySourceIndentationBaseline = Symbol("paragraphPropertySourceIndentationBaseline");
|
|
@@ -793,7 +792,7 @@ const createProseParagraphWithPropertySource = (nodeType, sourceParagraph, optio
|
|
|
793
792
|
linkProseParagraphPropertySource(paragraph, sourceParagraph);
|
|
794
793
|
return paragraph;
|
|
795
794
|
};
|
|
796
|
-
const paragraphPropertySourceTransfersKey =
|
|
795
|
+
const paragraphPropertySourceTransfersKey = "stll.folio.meta.paragraphPropertySourceTransfers";
|
|
797
796
|
/** Join adjacent paragraphs when revision semantics deliberately select the right owner. */
|
|
798
797
|
const joinProseParagraphsWithRightPropertySource = ({ attrs, pos, transaction }) => {
|
|
799
798
|
const $pos = transaction.doc.resolve(pos);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ParseWarning, ParseWarningCode } from "@stll/docx-core/model";
|
|
1
|
+
import { ImportedIdCollisionKind, ParseWarning, ParseWarningCode } from "@stll/docx-core/model";
|
|
2
2
|
//#region src/docx/parseContext.d.ts
|
|
3
3
|
/** What a call site states; the collector supplies the rest. */
|
|
4
4
|
type ParseWarningReport = {
|
|
5
5
|
code: ParseWarningCode;
|
|
6
|
+
identityKind?: ImportedIdCollisionKind;
|
|
6
7
|
/** The element as written, prefix included. */
|
|
7
8
|
element?: string;
|
|
8
9
|
/** The best position this part can name, e.g. `style "Heading1"`. */
|
|
@@ -30,6 +30,7 @@ const createParseWarningCollector = (part = PACKAGE_PART) => {
|
|
|
30
30
|
retainedByCode.set(report.code, kept + 1);
|
|
31
31
|
retained.push({
|
|
32
32
|
code: report.code,
|
|
33
|
+
...report.identityKind === void 0 ? {} : { identityKind: report.identityKind },
|
|
33
34
|
location: {
|
|
34
35
|
...location,
|
|
35
36
|
...report.element === void 0 ? {} : { element: report.element },
|
|
@@ -22,6 +22,7 @@ const PARSE_WARNING_MESSAGES = {
|
|
|
22
22
|
[PARSE_WARNING_CODES.documentPartMissing]: () => "No document.xml found in DOCX",
|
|
23
23
|
[PARSE_WARNING_CODES.documentModelIssue]: (warning) => warning.detail ?? "The document model reported an issue.",
|
|
24
24
|
[PARSE_WARNING_CODES.duplicateCommentId]: (warning) => `Dropped ${plural(warning.count, "comment")} repeating a w:id another comment already defines.`,
|
|
25
|
+
[PARSE_WARNING_CODES.duplicateImportedId]: (warning) => `Repeated imported identity${quoted(warning.value)}${where(warning)}: ${warning.detail ?? "multiple definitions share an identity."}`,
|
|
25
26
|
[PARSE_WARNING_CODES.missingCommentId]: (warning) => `Dropped ${plural(warning.count, "comment")} with no readable w:id.`,
|
|
26
27
|
[PARSE_WARNING_CODES.duplicateNoteId]: (warning) => `Dropped ${plural(warning.count, "note")} repeating a w:id another note already defines.`,
|
|
27
28
|
[PARSE_WARNING_CODES.danglingCommentReference]: (warning) => `Removed ${plural(warning.count, "dangling comment reference marker")} whose comments.xml entries are missing.`,
|
package/dist/docx/parser.js
CHANGED
|
@@ -2,6 +2,7 @@ import { toArrayBuffer } from "../utils/docxInput.js";
|
|
|
2
2
|
import { loadFontsWithMapping } from "../utils/fontLoader.js";
|
|
3
3
|
import { MAX_PACKAGE_TIFF_PIXELS, convertTiffToPngDataUrl, isTiffMimeType } from "../utils/tiffConverter.js";
|
|
4
4
|
import { ALT_CHUNK_PLAIN_TEXT_LIMIT_BYTES, getAltChunkRelationshipId, isAltChunkMarkup } from "./altChunk.js";
|
|
5
|
+
import { normalizeBookmarkIdsInXmlParts } from "./bookmarkIdNormalization.js";
|
|
5
6
|
import { DUPLICATE_COMMENT_ID_WARNING, normalizeCommentIds } from "./commentIdNormalization.js";
|
|
6
7
|
import { parseComments } from "./commentParser.js";
|
|
7
8
|
import { DANGLING_COMMENT_REFERENCE_WARNING, UNBALANCED_COMMENT_RANGE_WARNING, normalizeCommentReferences } from "./commentReferenceNormalization.js";
|
|
@@ -16,6 +17,7 @@ import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
|
16
17
|
import { parseFooter, parseHeader } from "./headerFooterParser.js";
|
|
17
18
|
import { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, assignHeaderFooterRoles, normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
|
|
18
19
|
import { assignHeaderFooterVerbatimXml, canReplayHeaderFooterVerbatim, captureDocumentSourceBaseline, captureHeaderFooterPackageBaselines, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
|
|
20
|
+
import { diagnoseImportedIdCollisions } from "./importedIdCollisions.js";
|
|
19
21
|
import { extractMetafileRaster, isMetafileMimeType } from "./metafileRaster.js";
|
|
20
22
|
import { renderEmfSvg } from "./metafileSvg.js";
|
|
21
23
|
import { DocxModelValidationError, formatDocumentModelIssues, validateFolioDocumentModel } from "./modelValidation.js";
|
|
@@ -34,7 +36,7 @@ import { parseSettings } from "./settingsParser.js";
|
|
|
34
36
|
import { parseStylesPackage } from "./styleParser.js";
|
|
35
37
|
import { applyThemeFontLang, parseTheme } from "./themeParser.js";
|
|
36
38
|
import { UNBALANCED_MOVE_RANGE_WARNING, normalizeTrackedMoveRanges } from "./trackedMoveRangeNormalization.js";
|
|
37
|
-
import { getEntryUncompressedSize, getMediaMimeType, mediaToDataUrl, unzipDocx } from "./unzip.js";
|
|
39
|
+
import { getEntryUncompressedSize, getMediaMimeType, mediaToDataUrl, replaceRawDocxXmlParts, unzipDocx } from "./unzip.js";
|
|
38
40
|
import { getAttribute, getChildElements, getLocalName, parseXml, parseXmlDocument } from "./xmlParser.js";
|
|
39
41
|
import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
|
|
40
42
|
import { TaggedError, panic } from "better-result";
|
|
@@ -192,6 +194,8 @@ async function parseDocxWithPreviewBudget(input, options, previewBudget) {
|
|
|
192
194
|
password,
|
|
193
195
|
extractAllXml: false
|
|
194
196
|
}));
|
|
197
|
+
diagnoseImportedIdCollisions(raw.allXml, parseContext);
|
|
198
|
+
await replaceRawDocxXmlParts(raw, normalizeBookmarkIdsInXmlParts(raw.allXml));
|
|
195
199
|
const repairedDocumentTree = await normalizeRawDocxNumericIds(raw);
|
|
196
200
|
const paragraphPropertySourceDigest = sha256Hex(raw.originalBuffer);
|
|
197
201
|
if (raw.wasEncrypted) parseContext.warn({ code: PARSE_WARNING_CODES.packageDecrypted });
|
package/dist/docx/relsParser.js
CHANGED
|
@@ -86,7 +86,7 @@ function parseRelationships(relsXml) {
|
|
|
86
86
|
};
|
|
87
87
|
if (targetMode === "External") relationship.targetMode = "External";
|
|
88
88
|
else if (targetMode === "Internal") relationship.targetMode = "Internal";
|
|
89
|
-
map.set(id, relationship);
|
|
89
|
+
if (!map.has(id)) map.set(id, relationship);
|
|
90
90
|
}
|
|
91
91
|
return map;
|
|
92
92
|
}
|
package/dist/docx/rezip.js
CHANGED
|
@@ -25,7 +25,7 @@ import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./r
|
|
|
25
25
|
import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
|
|
26
26
|
import { normalizeRevisionIdsInXmlParts } from "./revisionIdNormalization.js";
|
|
27
27
|
import { buildPatchedNotePartXml, collectChangedNoteParaIds, collectParaIds, patchNumberingDefinitions, spliceXml } from "./selectiveXmlPatch.js";
|
|
28
|
-
import { hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
28
|
+
import { canReplayCommentsPart, hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
29
29
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
30
30
|
import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
31
31
|
import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
@@ -201,6 +201,7 @@ async function serializeCommentsToZip(doc, zip, compressionLevel) {
|
|
|
201
201
|
const comments = doc.package.document.comments ?? [];
|
|
202
202
|
const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
|
|
203
203
|
const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
|
|
204
|
+
if (sourceCommentsXml !== void 0 && canReplayCommentsPart(doc, sourceCommentsXml)) return;
|
|
204
205
|
if (comments.length === 0) {
|
|
205
206
|
const ownsCommentsPart = hasOwnedCommentsPart(doc);
|
|
206
207
|
if (sourceCommentsXml === void 0 ? !ownsCommentsPart : !hasCommentEntries(sourceCommentsXml)) return;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { scanStreamingXmlElements } from "./streamingXmlParser.js";
|
|
2
|
+
//#region src/docx/selectedXmlIdentityScan.d.ts
|
|
3
|
+
/** Identity census follows the parser's first-Choice, otherwise-Fallback policy. */
|
|
4
|
+
declare const scanSelectedStreamingXmlElements: (xml: string, visitor: Parameters<typeof scanStreamingXmlElements>[1]) => {
|
|
5
|
+
status: "unsupported";
|
|
6
|
+
} | {
|
|
7
|
+
status: "scanned";
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { scanSelectedStreamingXmlElements };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { scanStreamingXmlElements } from "./streamingXmlParser.js";
|
|
2
|
+
import { NAMESPACES, getLocalName, getNamespaceUri, selectAlternateContentBranch } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/selectedXmlIdentityScan.ts
|
|
4
|
+
const isCompatibilityElement = (element, name) => getNamespaceUri(element) === NAMESPACES.mc && getLocalName(element.name) === name;
|
|
5
|
+
/** Identity census follows the parser's first-Choice, otherwise-Fallback policy. */
|
|
6
|
+
const scanSelectedStreamingXmlElements = (xml, visitor) => {
|
|
7
|
+
if (!xml.includes("AlternateContent")) return scanStreamingXmlElements(xml, visitor);
|
|
8
|
+
const alternatives = /* @__PURE__ */ new Map();
|
|
9
|
+
const positions = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
const scanned = scanStreamingXmlElements(xml, ({ element, parent, nameEnd }) => {
|
|
11
|
+
positions.set(element, nameEnd);
|
|
12
|
+
if (isCompatibilityElement(element, "AlternateContent")) {
|
|
13
|
+
element.elements = [];
|
|
14
|
+
alternatives.set(nameEnd, element);
|
|
15
|
+
}
|
|
16
|
+
if (!parent || !isCompatibilityElement(parent, "AlternateContent")) return;
|
|
17
|
+
parent.elements?.push(element);
|
|
18
|
+
});
|
|
19
|
+
if (scanned.status === "unsupported") return scanned;
|
|
20
|
+
const selectedPositions = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const [position, alternative] of alternatives) {
|
|
22
|
+
const selected = selectAlternateContentBranch(alternative);
|
|
23
|
+
selectedPositions.set(position, selected === void 0 ? void 0 : positions.get(selected));
|
|
24
|
+
}
|
|
25
|
+
const active = /* @__PURE__ */ new WeakMap();
|
|
26
|
+
return scanStreamingXmlElements(xml, (entry) => {
|
|
27
|
+
const { element, parent, nameEnd } = entry;
|
|
28
|
+
positions.set(element, nameEnd);
|
|
29
|
+
let included = parent === void 0 || active.get(parent) === true;
|
|
30
|
+
if (parent && isCompatibilityElement(parent, "AlternateContent")) included &&= nameEnd === selectedPositions.get(positions.get(parent) ?? -1);
|
|
31
|
+
active.set(element, included);
|
|
32
|
+
if (included) visitor(entry);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { scanSelectedStreamingXmlElements };
|
|
@@ -10,7 +10,7 @@ import { RELATIONSHIP_TYPES } from "./relsParser.js";
|
|
|
10
10
|
import { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_PART_LOWER, addCommentsExtendedOverride, addCommentsExtendedRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, hasUnmaterializedHyperlinkBindings, hasUnmaterializedInlineResources, planAddedStyles, publishCanonicalImageResources, updateCoreProperties, withoutAttachedTemplate } from "./rezip.js";
|
|
11
11
|
import "./selectiveSaveFlags.js";
|
|
12
12
|
import { buildPatchedDocumentXml, buildPatchedNoteXml, collectAddedNumberingDefs, collectParaIds, patchNumberingDefinitions } from "./selectiveXmlPatch.js";
|
|
13
|
-
import { hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
13
|
+
import { canReplayCommentsPart, hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
14
14
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
15
15
|
import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
|
|
16
16
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
@@ -262,8 +262,9 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
262
262
|
}
|
|
263
263
|
const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
|
|
264
264
|
const commentsPartPath = sourceCommentsFile?.name ?? "word/comments.xml";
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
|
|
266
|
+
const commentsUnchanged = sourceCommentsXml !== void 0 && canReplayCommentsPart(doc, sourceCommentsXml);
|
|
267
|
+
if (!commentsUnchanged && (hasComments || ownsCommentsPart || sourceCommentsFile)) {
|
|
267
268
|
const commentsXml = serializeComments(commentPlan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
268
269
|
if (commentsXml !== sourceCommentsXml) updates.set(commentsPartPath, commentsXml);
|
|
269
270
|
}
|
|
@@ -283,7 +284,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
|
-
if (!await patchCommentsExtended(zip, commentPlan, updates)) return null;
|
|
287
|
+
if (!commentsUnchanged && !await patchCommentsExtended(zip, commentPlan, updates)) return null;
|
|
287
288
|
if (!await patchNumberingPart(zip, doc, updates)) return null;
|
|
288
289
|
const styles = await planAddedStyles(doc, zip);
|
|
289
290
|
switch (styles.type) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { patchBreaksCommentRangeBalance } from "./commentRangeIntegrity.js";
|
|
2
2
|
import { resolveParagraphIdentities } from "./paraIdAttribute.js";
|
|
3
3
|
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
4
|
+
import { scanStreamingXmlElements } from "./streamingXmlParser.js";
|
|
4
5
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
5
6
|
import { PARAGRAPH_SCAN_NAMES, hasCanonicalWordprocessingPrefixes, matchCloseTag, matchOpenTag, resolveSelectiveScanPrefixes, splicesAsCanonical } from "./wordprocessingPrefixes.js";
|
|
6
7
|
import { resolveNamespaceUri } from "./xmlNamespaceContext.js";
|
|
@@ -650,36 +651,32 @@ function extractElementByIdAttr(xml, openLiteral, closeTag, idAttr, id) {
|
|
|
650
651
|
const qualifiedName = (prefix, localName) => prefix.length === 0 ? localName : `${prefix}:${localName}`;
|
|
651
652
|
const collectNoteElementSyntax = (xml, elementName) => {
|
|
652
653
|
const byId = /* @__PURE__ */ new Map();
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (
|
|
654
|
+
let root;
|
|
655
|
+
scanStreamingXmlElements(xml, ({ element, parent, nameEnd }) => {
|
|
656
|
+
if (parent === void 0) {
|
|
657
|
+
root = element;
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (parent !== root || getLocalName(element.name) !== elementName || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "")) return;
|
|
656
661
|
const idAttribute = findAttributeByNamespaceUri(element, WORDPROCESSINGML_NAMESPACE_URIS, "id");
|
|
657
|
-
if (!element.name || !idAttribute)
|
|
662
|
+
if (!element.name || !idAttribute) return;
|
|
663
|
+
const spelling = idAttribute.value.trim();
|
|
664
|
+
if (!/^[+-]?\d+$/u.test(spelling) || !Number.isSafeInteger(Number(spelling))) return;
|
|
665
|
+
const id = String(Number(spelling));
|
|
666
|
+
const offsets = scanElementRange(xml, nameEnd - element.name.length - 1, `<${element.name}`, `</${element.name}>`);
|
|
667
|
+
if (!offsets) return;
|
|
658
668
|
const syntax = {
|
|
659
|
-
|
|
660
|
-
idAttributeName: idAttribute.name,
|
|
669
|
+
offsets,
|
|
661
670
|
elementPrefix: getNamespacePrefix(element.name) ?? "",
|
|
662
671
|
attributePrefix: getNamespacePrefix(idAttribute.name) ?? ""
|
|
663
672
|
};
|
|
664
|
-
const entries = byId.get(
|
|
673
|
+
const entries = byId.get(id);
|
|
665
674
|
if (entries) entries.push(syntax);
|
|
666
|
-
else byId.set(
|
|
667
|
-
}
|
|
675
|
+
else byId.set(id, [syntax]);
|
|
676
|
+
});
|
|
668
677
|
return byId;
|
|
669
678
|
};
|
|
670
|
-
const
|
|
671
|
-
openLiteral: `<${syntax.elementName}`,
|
|
672
|
-
closeTag: `</${syntax.elementName}>`,
|
|
673
|
-
idAttr: syntax.idAttributeName
|
|
674
|
-
});
|
|
675
|
-
const extractNoteElement = (xml, syntax, id) => {
|
|
676
|
-
const { openLiteral, closeTag, idAttr } = syntaxLiteral(syntax);
|
|
677
|
-
return extractElementByIdAttr(xml, openLiteral, closeTag, idAttr, id);
|
|
678
|
-
};
|
|
679
|
-
const findNoteElement = (xml, syntax, id) => {
|
|
680
|
-
const { openLiteral, closeTag, idAttr } = syntaxLiteral(syntax);
|
|
681
|
-
return findElementByIdAttr(xml, openLiteral, closeTag, idAttr, id);
|
|
682
|
-
};
|
|
679
|
+
const extractNoteElement = (xml, syntax) => xml.slice(syntax.offsets.start, syntax.offsets.end);
|
|
683
680
|
const rewriteWordprocessingPrefixes = (xml, { source, target, sourceXmlnsDeclarations }) => {
|
|
684
681
|
let rewritten = "";
|
|
685
682
|
let quote = null;
|
|
@@ -824,7 +821,7 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
|
|
|
824
821
|
const replacementSyntaxEntries = replacementElements.get(id);
|
|
825
822
|
if (originalSyntaxEntries === void 0) {
|
|
826
823
|
const replacementSyntax = currentSyntaxEntries.length === 1 && replacementSyntaxEntries?.length === 1 ? replacementSyntaxEntries[0] : void 0;
|
|
827
|
-
const replacementNote = replacementSyntax ? extractNoteElement(replacementXml, replacementSyntax
|
|
824
|
+
const replacementNote = replacementSyntax ? extractNoteElement(replacementXml, replacementSyntax) : null;
|
|
828
825
|
if (!replacementSyntax || !replacementNote) return {
|
|
829
826
|
type: "refused",
|
|
830
827
|
reason: "unroutable-paragraph"
|
|
@@ -836,24 +833,20 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
|
|
|
836
833
|
});
|
|
837
834
|
continue;
|
|
838
835
|
}
|
|
839
|
-
if (currentSyntaxEntries.length !== 1 || originalSyntaxEntries?.length
|
|
836
|
+
if (currentSyntaxEntries.length !== 1 || !originalSyntaxEntries?.length || replacementSyntaxEntries?.length !== 1) return {
|
|
840
837
|
type: "refused",
|
|
841
838
|
reason: "unroutable-paragraph"
|
|
842
839
|
};
|
|
843
840
|
const currentSyntax = currentSyntaxEntries[0];
|
|
844
|
-
const originalSyntax = originalSyntaxEntries
|
|
841
|
+
const originalSyntax = originalSyntaxEntries.at(0);
|
|
845
842
|
const replacementSyntax = replacementSyntaxEntries[0];
|
|
846
843
|
if (!currentSyntax || !originalSyntax || !replacementSyntax) return {
|
|
847
844
|
type: "refused",
|
|
848
845
|
reason: "unroutable-paragraph"
|
|
849
846
|
};
|
|
850
|
-
const currentNote = extractNoteElement(serializedXml, currentSyntax
|
|
851
|
-
const originalOffsets =
|
|
852
|
-
const replacementNote = extractNoteElement(replacementXml, replacementSyntax
|
|
853
|
-
if (!currentNote || !originalOffsets || !replacementNote) return {
|
|
854
|
-
type: "refused",
|
|
855
|
-
reason: "unroutable-paragraph"
|
|
856
|
-
};
|
|
847
|
+
const currentNote = extractNoteElement(serializedXml, currentSyntax);
|
|
848
|
+
const originalOffsets = originalSyntax.offsets;
|
|
849
|
+
const replacementNote = extractNoteElement(replacementXml, replacementSyntax);
|
|
857
850
|
const originalNote = originalXml.slice(originalOffsets.start, originalOffsets.end);
|
|
858
851
|
const currentParagraphs = paragraphRanges(currentNote, currentSyntax.elementPrefix);
|
|
859
852
|
const originalParagraphs = paragraphRanges(originalNote, originalSyntax.elementPrefix);
|
|
@@ -861,7 +854,7 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
|
|
|
861
854
|
const noteChangedParaIds = [...collectParaIds(currentNote).keys()].filter((paraId) => unroutedChangedParaIds.has(paraId));
|
|
862
855
|
const baselineSyntaxEntries = baselineElements.get(id);
|
|
863
856
|
const baselineSyntax = baselineSyntaxEntries?.length === 1 ? baselineSyntaxEntries[0] : void 0;
|
|
864
|
-
const baselineNote = baselineSyntax ? extractNoteElement(baselineXml, baselineSyntax
|
|
857
|
+
const baselineNote = baselineSyntax ? extractNoteElement(baselineXml, baselineSyntax) : null;
|
|
865
858
|
const reshaped = baselineSyntax !== void 0 && baselineNote !== null && baselineNote !== currentNote && noteShapeChanged({
|
|
866
859
|
note: baselineNote,
|
|
867
860
|
prefix: baselineSyntax.elementPrefix
|
|
@@ -910,26 +903,29 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
|
|
|
910
903
|
for (const [id, baselineSyntaxEntries] of baselineElements) {
|
|
911
904
|
if (currentElements.has(id) || baselineSyntaxEntries.length !== 1) continue;
|
|
912
905
|
const originalSyntaxEntries = originalElements.get(id);
|
|
913
|
-
|
|
914
|
-
const originalOffsets = originalSyntax && findNoteElement(originalXml, originalSyntax, id);
|
|
915
|
-
if (!originalOffsets) return {
|
|
906
|
+
if (!originalSyntaxEntries?.length) return {
|
|
916
907
|
type: "refused",
|
|
917
908
|
reason: "unroutable-paragraph"
|
|
918
909
|
};
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
910
|
+
for (const { offsets } of originalSyntaxEntries) {
|
|
911
|
+
const removal = {
|
|
912
|
+
...offsets,
|
|
913
|
+
newXml: ""
|
|
914
|
+
};
|
|
915
|
+
paragraphSplices.push(removal);
|
|
916
|
+
noteSplices.push(removal);
|
|
917
|
+
}
|
|
926
918
|
}
|
|
927
919
|
if (unroutedChangedParaIds.size > 0) return {
|
|
928
920
|
type: "refused",
|
|
929
921
|
reason: "unroutable-paragraph"
|
|
930
922
|
};
|
|
931
923
|
if (addedNotes.length > 0) {
|
|
932
|
-
const appended = appendNotesSplice(
|
|
924
|
+
const appended = appendNotesSplice({
|
|
925
|
+
originalElements,
|
|
926
|
+
addedNotes,
|
|
927
|
+
sourceXmlnsDeclarations: replacementXmlnsDeclarations
|
|
928
|
+
});
|
|
933
929
|
if (!appended) return {
|
|
934
930
|
type: "refused",
|
|
935
931
|
reason: "unroutable-paragraph"
|
|
@@ -951,15 +947,11 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
|
|
|
951
947
|
xml: wholeNotes
|
|
952
948
|
};
|
|
953
949
|
}
|
|
954
|
-
|
|
955
|
-
* Insert new notes after the part's last note, spelled with that note's
|
|
956
|
-
* prefixes. Null when the part holds no note to follow.
|
|
957
|
-
*/
|
|
958
|
-
const appendNotesSplice = (originalXml, originalElements, addedNotes, { sourceXmlnsDeclarations }) => {
|
|
950
|
+
const appendNotesSplice = ({ originalElements, addedNotes, sourceXmlnsDeclarations }) => {
|
|
959
951
|
let last = null;
|
|
960
|
-
for (const
|
|
961
|
-
const offsets =
|
|
962
|
-
if (
|
|
952
|
+
for (const entries of originalElements.values()) for (const syntax of entries) {
|
|
953
|
+
const offsets = syntax.offsets;
|
|
954
|
+
if (!last || offsets.end > last.end) last = {
|
|
963
955
|
end: offsets.end,
|
|
964
956
|
syntax
|
|
965
957
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
2
|
import { Comment } from "../../types/content.js";
|
|
3
3
|
//#region src/docx/serializer/commentSerializer.d.ts
|
|
4
|
+
/** An unrelated body edit must retain unmodelled duplicate comment definitions. */
|
|
5
|
+
declare const canReplayCommentsPart: (document: document_d_exports.Document, sourceXml: string) => boolean;
|
|
4
6
|
/** An owned empty part survives deletion of its final thread. */
|
|
5
7
|
declare const hasOwnedCommentsPart: (document: document_d_exports.Document) => boolean;
|
|
6
8
|
/**
|
|
@@ -55,4 +57,4 @@ declare function serializeComments({ order, byId }: CommentPartPlan, sourceBindi
|
|
|
55
57
|
*/
|
|
56
58
|
declare function serializeCommentsExtended(plan: CommentPartPlan): string | null;
|
|
57
59
|
//#endregion
|
|
58
|
-
export { CommentPartPlan, hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended };
|
|
60
|
+
export { CommentPartPlan, canReplayCommentsPart, hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { canonicalJson } from "../../utils/canonicalJson.js";
|
|
1
2
|
import { deterministicHexId } from "../../utils/hexId.js";
|
|
2
3
|
import { commentThreadParaId } from "../commentThreadKey.js";
|
|
3
4
|
import { serializeWithPreservedChildren } from "../containerChildren.js";
|
|
5
|
+
import { getDocumentSourceBaseline } from "../headerFooterVerbatim.js";
|
|
4
6
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
5
7
|
import { serializePartElement } from "./partNamespaces.js";
|
|
6
8
|
import { serializeTextFormatting } from "./textFormattingSerializer.js";
|
|
7
|
-
import {
|
|
9
|
+
import { Result } from "better-result";
|
|
10
|
+
import { assertValidOoxmlNumericIds, escapeXmlAttribute } from "@stll/docx-core";
|
|
8
11
|
import { COMMENT_PART_RELATIONSHIPS } from "@stll/docx-core/model";
|
|
9
12
|
//#region src/docx/serializer/commentSerializer.ts
|
|
10
13
|
/**
|
|
@@ -12,6 +15,12 @@ import { COMMENT_PART_RELATIONSHIPS } from "@stll/docx-core/model";
|
|
|
12
15
|
*
|
|
13
16
|
* Serializes Comment[] to OOXML comments.xml format.
|
|
14
17
|
*/
|
|
18
|
+
/** An unrelated body edit must retain unmodelled duplicate comment definitions. */
|
|
19
|
+
const canReplayCommentsPart = (document, sourceXml) => {
|
|
20
|
+
if (Result.try(() => assertValidOoxmlNumericIds(sourceXml, "word/comments.xml")).isErr()) return false;
|
|
21
|
+
const baseline = getDocumentSourceBaseline(document);
|
|
22
|
+
return baseline.type === "captured" && canonicalJson(document.package.document.comments ?? []) === canonicalJson(baseline.body.comments ?? []);
|
|
23
|
+
};
|
|
15
24
|
/** An owned empty part survives deletion of its final thread. */
|
|
16
25
|
const hasOwnedCommentsPart = (document) => [...document.package.relationships?.values() ?? []].some(({ type }) => type === COMMENT_PART_RELATIONSHIPS.comments.type);
|
|
17
26
|
const DEFAULT_ANNOTATION_REFERENCE_PROPERTIES = "<w:rPr><w:rStyle w:val=\"CommentReference\"/></w:rPr>";
|
|
@@ -200,4 +209,4 @@ function serializeCommentsExtended(plan) {
|
|
|
200
209
|
});
|
|
201
210
|
}
|
|
202
211
|
//#endregion
|
|
203
|
-
export { hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended };
|
|
212
|
+
export { canReplayCommentsPart, hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended };
|