@ansonlai/docx-redline-js 0.8.0 → 0.8.1
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/CHANGELOG.md +539 -486
- package/README.md +331 -309
- package/dist/docx-redline-js.esm.js +1083 -163
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +107 -107
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +1083 -163
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +1083 -163
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +69 -8
- package/docs/schemas/document-operations.schema.json +29 -0
- package/document/docx-document.js +416 -22
- package/engine/reconstruction-writer.js +18 -0
- package/index.d.ts +598 -575
- package/index.js +349 -348
- package/node/cli-help.js +5 -5
- package/node/cli.js +760 -754
- package/node/index.d.ts +1 -1
- package/package.json +2 -1
- package/scripts/generate-word-comment-edge-case-fixture.ps1 +58 -0
- package/scripts/generate-word-comment-thread-fixture.ps1 +36 -0
- package/scripts/generate-word-header-footer-fixture.ps1 +32 -0
- package/scripts/generate-word-multi-section-fixture.ps1 +27 -0
- package/scripts/generate-word-package-integrity-cases.mjs +141 -0
- package/scripts/generate-word-thread-state-fixtures.ps1 +50 -0
- package/scripts/word-com-package-integrity.ps1 +98 -0
- package/services/batch-operation-orchestrator.js +262 -259
- package/services/comment-replies.js +96 -95
- package/services/comment-thread-parts.js +280 -0
- package/services/document-inspection.js +366 -365
- package/services/document-operation-applier.js +230 -202
- package/services/document-operation-contract.js +17 -2
- package/services/error-recovery.js +6 -0
- package/services/headers-footers.js +206 -0
- package/services/operation-batch-compiler.js +1 -0
- package/services/operation-preflight.js +231 -218
- package/services/package-parts.js +80 -0
- package/services/standalone-docx-plumbing.js +631 -569
- package/services/standalone-operation-runner.d.ts +19 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js standalone bundle v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone bundle v0.8.1 — https://github.com/AnsonLai/docx-redline-js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -14196,6 +14196,19 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
14196
14196
|
pendingReplacementEvent = null;
|
|
14197
14197
|
}
|
|
14198
14198
|
}
|
|
14199
|
+
for (const offset of Array.from(sentinelMapByStart.keys()).sort((a, b) => a - b)) {
|
|
14200
|
+
for (const sentinel of sentinelMapByStart.get(offset)) {
|
|
14201
|
+
if (!sentinel.isCommentMarker || emittedCommentMarkers.has(sentinel.node)) continue;
|
|
14202
|
+
emittedCommentMarkers.add(sentinel.node);
|
|
14203
|
+
if (isWordElement(sentinel.node, "commentReference")) {
|
|
14204
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
14205
|
+
run.appendChild(sentinel.node.cloneNode(true));
|
|
14206
|
+
currentParagraph.appendChild(run);
|
|
14207
|
+
} else {
|
|
14208
|
+
currentParagraph.appendChild(sentinel.node.cloneNode(true));
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
}
|
|
14199
14212
|
if (generateRedlines) {
|
|
14200
14213
|
containerFragments.forEach((fragment) => {
|
|
14201
14214
|
Array.from(fragment.childNodes).forEach((node) => {
|
|
@@ -17990,6 +18003,260 @@ function ingestWordOoxmlToMarkdownResult(ooxml) {
|
|
|
17990
18003
|
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
17991
18004
|
}
|
|
17992
18005
|
|
|
18006
|
+
// services/comment-builders.js
|
|
18007
|
+
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18008
|
+
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18009
|
+
function createCommentParaId(commentId) {
|
|
18010
|
+
const numeric = Number.parseInt(String(commentId), 10);
|
|
18011
|
+
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18012
|
+
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18013
|
+
}
|
|
18014
|
+
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18015
|
+
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18016
|
+
const escapedContent = escapeXml(content);
|
|
18017
|
+
const escapedAuthor = escapeXml(author);
|
|
18018
|
+
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18019
|
+
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18020
|
+
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18021
|
+
</w:p>
|
|
18022
|
+
</w:comment>`;
|
|
18023
|
+
}
|
|
18024
|
+
function buildCommentsPartXml(comments) {
|
|
18025
|
+
if (!comments || comments.length === 0) {
|
|
18026
|
+
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18027
|
+
}
|
|
18028
|
+
const commentElements = comments.map(
|
|
18029
|
+
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18030
|
+
).join("\n ");
|
|
18031
|
+
return `<w:comments xmlns:w="${NS_W}">
|
|
18032
|
+
${commentElements}
|
|
18033
|
+
</w:comments>`;
|
|
18034
|
+
}
|
|
18035
|
+
function buildCommentsExtendedPartXml(entries) {
|
|
18036
|
+
const body = (entries || []).map((entry) => {
|
|
18037
|
+
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18038
|
+
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18039
|
+
}).join("");
|
|
18040
|
+
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18041
|
+
}
|
|
18042
|
+
|
|
18043
|
+
// services/comment-thread-parts.js
|
|
18044
|
+
var attrOf = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
18045
|
+
function commentThreadParagraph(commentElement) {
|
|
18046
|
+
const paragraphs = Array.from(commentElement?.getElementsByTagNameNS?.("*", "p") || []);
|
|
18047
|
+
return paragraphs.length ? paragraphs[paragraphs.length - 1] : null;
|
|
18048
|
+
}
|
|
18049
|
+
function commentParaId(commentElement) {
|
|
18050
|
+
return attrOf(commentThreadParagraph(commentElement), "w14:paraId", "paraId").toUpperCase();
|
|
18051
|
+
}
|
|
18052
|
+
function usedParaIds(commentsDoc, extendedDoc) {
|
|
18053
|
+
const ids = /* @__PURE__ */ new Set();
|
|
18054
|
+
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
18055
|
+
const id = attrOf(p, "w14:paraId", "paraId");
|
|
18056
|
+
if (id) ids.add(id.toUpperCase());
|
|
18057
|
+
}
|
|
18058
|
+
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
18059
|
+
const id = attrOf(ex, "w15:paraId", "paraId");
|
|
18060
|
+
if (id) ids.add(id.toUpperCase());
|
|
18061
|
+
}
|
|
18062
|
+
return ids;
|
|
18063
|
+
}
|
|
18064
|
+
function allocateParaId(commentId, occupied) {
|
|
18065
|
+
let candidate = createCommentParaId(commentId);
|
|
18066
|
+
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
18067
|
+
while (occupied.has(candidate)) {
|
|
18068
|
+
value = value + 1 >>> 0;
|
|
18069
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18070
|
+
}
|
|
18071
|
+
occupied.add(candidate);
|
|
18072
|
+
return candidate;
|
|
18073
|
+
}
|
|
18074
|
+
var parseDoc = (xml, partName) => {
|
|
18075
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
18076
|
+
if (!parsed.doc || parsed.error) return { error: { code: "PARSE_ERROR", message: `Could not parse ${partName}: ${parsed.error?.message || "invalid XML"}` } };
|
|
18077
|
+
return { doc: parsed.doc };
|
|
18078
|
+
};
|
|
18079
|
+
function applyThreadResolutionToParts({ commentsXml, commentsExtendedXml = null, commentId, resolved = true }) {
|
|
18080
|
+
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "Resolving a comment requires an existing word/comments.xml part." } };
|
|
18081
|
+
const commentsParsed = parseDoc(commentsXml, "word/comments.xml");
|
|
18082
|
+
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
18083
|
+
const commentsDoc = commentsParsed.doc;
|
|
18084
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18085
|
+
const comments = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
18086
|
+
const target = comments.find((node) => idOf(node) === String(commentId));
|
|
18087
|
+
if (!target) return { status: "error", error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` } };
|
|
18088
|
+
let extendedDoc;
|
|
18089
|
+
if (commentsExtendedXml) {
|
|
18090
|
+
const parsed = parseDoc(commentsExtendedXml, "word/commentsExtended.xml");
|
|
18091
|
+
if (parsed.error) return { status: "error", error: parsed.error };
|
|
18092
|
+
extendedDoc = parsed.doc;
|
|
18093
|
+
} else {
|
|
18094
|
+
extendedDoc = parseDoc(`<w15:commentsEx xmlns:w15="${NS_W15}"/>`, "word/commentsExtended.xml").doc;
|
|
18095
|
+
}
|
|
18096
|
+
const root = extendedDoc.documentElement;
|
|
18097
|
+
const entryFor = (paraId) => Array.from(root.getElementsByTagNameNS("*", "commentEx")).find((node) => attrOf(node, "w15:paraId", "paraId").toUpperCase() === paraId) || null;
|
|
18098
|
+
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
18099
|
+
let commentsChanged = false;
|
|
18100
|
+
const keyOf = (node) => {
|
|
18101
|
+
let paraId = commentParaId(node);
|
|
18102
|
+
if (!paraId) {
|
|
18103
|
+
paraId = allocateParaId(idOf(node), occupied);
|
|
18104
|
+
commentThreadParagraph(node)?.setAttributeNS(NS_W14, "w14:paraId", paraId);
|
|
18105
|
+
commentsChanged = true;
|
|
18106
|
+
}
|
|
18107
|
+
return paraId;
|
|
18108
|
+
};
|
|
18109
|
+
const targetKey = keyOf(target);
|
|
18110
|
+
const targetEntry = entryFor(targetKey);
|
|
18111
|
+
const rootKey = targetEntry && attrOf(targetEntry, "w15:paraIdParent", "paraIdParent").toUpperCase() || targetKey;
|
|
18112
|
+
const threadKeys = /* @__PURE__ */ new Set([rootKey, targetKey]);
|
|
18113
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentEx"))) {
|
|
18114
|
+
if (attrOf(entry, "w15:paraIdParent", "paraIdParent").toUpperCase() === rootKey) threadKeys.add(attrOf(entry, "w15:paraId", "paraId").toUpperCase());
|
|
18115
|
+
}
|
|
18116
|
+
const doneValue = resolved ? "1" : "0";
|
|
18117
|
+
let extendedChanged = !commentsExtendedXml;
|
|
18118
|
+
for (const key of threadKeys) {
|
|
18119
|
+
let entry = entryFor(key);
|
|
18120
|
+
if (!entry) {
|
|
18121
|
+
entry = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
18122
|
+
entry.setAttributeNS(NS_W15, "w15:paraId", key);
|
|
18123
|
+
if (key !== rootKey) entry.setAttributeNS(NS_W15, "w15:paraIdParent", rootKey);
|
|
18124
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18125
|
+
root.appendChild(entry);
|
|
18126
|
+
extendedChanged = true;
|
|
18127
|
+
} else if (attrOf(entry, "w15:done", "done") !== doneValue) {
|
|
18128
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18129
|
+
extendedChanged = true;
|
|
18130
|
+
}
|
|
18131
|
+
}
|
|
18132
|
+
const byKey = new Map(comments.map((node) => [commentParaId(node), node]));
|
|
18133
|
+
const commentIds = [...threadKeys].map((key) => byKey.get(key)).filter(Boolean).map(idOf);
|
|
18134
|
+
const rootComment = byKey.get(rootKey);
|
|
18135
|
+
const serializer = createSerializer();
|
|
18136
|
+
const hasChanges = extendedChanged || commentsChanged;
|
|
18137
|
+
return {
|
|
18138
|
+
status: "ok",
|
|
18139
|
+
hasChanges,
|
|
18140
|
+
resolved,
|
|
18141
|
+
threadRootId: rootComment ? idOf(rootComment) : String(commentId),
|
|
18142
|
+
commentIds,
|
|
18143
|
+
...hasChanges ? { commentsExtendedXml: serializer.serializeToString(extendedDoc) } : {},
|
|
18144
|
+
...commentsChanged ? { commentsXml: serializer.serializeToString(commentsDoc) } : {}
|
|
18145
|
+
};
|
|
18146
|
+
}
|
|
18147
|
+
var NS_W_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
18148
|
+
function anchorReplyInDocument(xmlDoc, memberIds, newId) {
|
|
18149
|
+
const wanted = new Set(memberIds.map(String));
|
|
18150
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18151
|
+
const all = (name) => Array.from(xmlDoc.getElementsByTagNameNS("*", name)).filter((node) => wanted.has(idOf(node)));
|
|
18152
|
+
const starts = all("commentRangeStart");
|
|
18153
|
+
const references = all("commentReference");
|
|
18154
|
+
if (!starts.length || !references.length) return false;
|
|
18155
|
+
const lastStart = starts[starts.length - 1];
|
|
18156
|
+
const lastReference = references[references.length - 1];
|
|
18157
|
+
const referenceRun = lastReference.parentNode?.localName === "r" ? lastReference.parentNode : lastReference;
|
|
18158
|
+
const make = (name, parent) => {
|
|
18159
|
+
const element = xmlDoc.createElementNS(NS_W_MAIN, `w:${name}`);
|
|
18160
|
+
element.setAttributeNS(NS_W_MAIN, "w:id", String(newId));
|
|
18161
|
+
if (parent) parent.appendChild(element);
|
|
18162
|
+
return element;
|
|
18163
|
+
};
|
|
18164
|
+
const insertAfter = (anchor, node) => anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
18165
|
+
insertAfter(lastStart, make("commentRangeStart"));
|
|
18166
|
+
const end = make("commentRangeEnd");
|
|
18167
|
+
const run = xmlDoc.createElementNS(NS_W_MAIN, "w:r");
|
|
18168
|
+
run.appendChild(xmlDoc.createElementNS(NS_W_MAIN, "w:rPr"));
|
|
18169
|
+
make("commentReference", run);
|
|
18170
|
+
insertAfter(referenceRun, end);
|
|
18171
|
+
insertAfter(end, run);
|
|
18172
|
+
return true;
|
|
18173
|
+
}
|
|
18174
|
+
var NS_W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
|
|
18175
|
+
var NS_W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
|
|
18176
|
+
var durableIdFor = (paraId, taken) => {
|
|
18177
|
+
let value = (Number.parseInt(paraId, 16) ^ 625341585) & 2147483646;
|
|
18178
|
+
let candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18179
|
+
while (taken.has(candidate)) {
|
|
18180
|
+
value = value + 2 & 2147483646;
|
|
18181
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18182
|
+
}
|
|
18183
|
+
taken.add(candidate);
|
|
18184
|
+
return candidate;
|
|
18185
|
+
};
|
|
18186
|
+
function reconcileCommentSiblingParts({ commentsXml, commentsIdsXml = null, commentsExtensibleXml = null }) {
|
|
18187
|
+
const out = {};
|
|
18188
|
+
if (!commentsXml || !commentsIdsXml && !commentsExtensibleXml) return out;
|
|
18189
|
+
const commentsParsed = parseOoxmlSafe(commentsXml, "application/xml");
|
|
18190
|
+
if (!commentsParsed.doc || commentsParsed.error) return out;
|
|
18191
|
+
const serializer = createSerializer();
|
|
18192
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
18193
|
+
for (const node of Array.from(commentsParsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
18194
|
+
const key = commentParaId(node);
|
|
18195
|
+
if (key) byKey.set(key, node);
|
|
18196
|
+
}
|
|
18197
|
+
let idsDoc = null;
|
|
18198
|
+
const durableByKey = /* @__PURE__ */ new Map();
|
|
18199
|
+
const taken = /* @__PURE__ */ new Set();
|
|
18200
|
+
let idsChanged = false;
|
|
18201
|
+
if (commentsIdsXml) {
|
|
18202
|
+
const parsed = parseOoxmlSafe(commentsIdsXml, "application/xml");
|
|
18203
|
+
if (!parsed.doc || parsed.error) return out;
|
|
18204
|
+
idsDoc = parsed.doc;
|
|
18205
|
+
const root = idsDoc.documentElement;
|
|
18206
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentId"))) {
|
|
18207
|
+
const key = attrOf(entry, "w16cid:paraId", "paraId").toUpperCase();
|
|
18208
|
+
const durable = attrOf(entry, "w16cid:durableId", "durableId").toUpperCase();
|
|
18209
|
+
if (durable) taken.add(durable);
|
|
18210
|
+
if (!byKey.has(key)) {
|
|
18211
|
+
root.removeChild(entry);
|
|
18212
|
+
idsChanged = true;
|
|
18213
|
+
continue;
|
|
18214
|
+
}
|
|
18215
|
+
durableByKey.set(key, durable);
|
|
18216
|
+
}
|
|
18217
|
+
for (const key of byKey.keys()) {
|
|
18218
|
+
if (durableByKey.has(key)) continue;
|
|
18219
|
+
const durable = durableIdFor(key, taken);
|
|
18220
|
+
const entry = idsDoc.createElementNS(NS_W16CID, "w16cid:commentId");
|
|
18221
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:paraId", key);
|
|
18222
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:durableId", durable);
|
|
18223
|
+
root.appendChild(entry);
|
|
18224
|
+
durableByKey.set(key, durable);
|
|
18225
|
+
idsChanged = true;
|
|
18226
|
+
}
|
|
18227
|
+
}
|
|
18228
|
+
let extDoc = null;
|
|
18229
|
+
let extChanged = false;
|
|
18230
|
+
if (commentsExtensibleXml && idsDoc) {
|
|
18231
|
+
const parsed = parseOoxmlSafe(commentsExtensibleXml, "application/xml");
|
|
18232
|
+
if (parsed.doc && !parsed.error) {
|
|
18233
|
+
extDoc = parsed.doc;
|
|
18234
|
+
const root = extDoc.documentElement;
|
|
18235
|
+
const live = new Set(durableByKey.values());
|
|
18236
|
+
const present = /* @__PURE__ */ new Set();
|
|
18237
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentExtensible"))) {
|
|
18238
|
+
const durable = attrOf(entry, "w16cex:durableId", "durableId").toUpperCase();
|
|
18239
|
+
if (!live.has(durable)) {
|
|
18240
|
+
root.removeChild(entry);
|
|
18241
|
+
extChanged = true;
|
|
18242
|
+
} else present.add(durable);
|
|
18243
|
+
}
|
|
18244
|
+
for (const [key, durable] of durableByKey) {
|
|
18245
|
+
if (present.has(durable)) continue;
|
|
18246
|
+
const date = attrOf(byKey.get(key), "w:date", "date");
|
|
18247
|
+
const entry = extDoc.createElementNS(NS_W16CEX, "w16cex:commentExtensible");
|
|
18248
|
+
entry.setAttributeNS(NS_W16CEX, "w16cex:durableId", durable);
|
|
18249
|
+
if (date) entry.setAttributeNS(NS_W16CEX, "w16cex:dateUtc", date);
|
|
18250
|
+
root.appendChild(entry);
|
|
18251
|
+
extChanged = true;
|
|
18252
|
+
}
|
|
18253
|
+
}
|
|
18254
|
+
}
|
|
18255
|
+
if (idsChanged) out.commentsIdsXml = serializer.serializeToString(idsDoc);
|
|
18256
|
+
if (extChanged) out.commentsExtensibleXml = serializer.serializeToString(extDoc);
|
|
18257
|
+
return out;
|
|
18258
|
+
}
|
|
18259
|
+
|
|
17993
18260
|
// core/sha256.js
|
|
17994
18261
|
var K = new Uint32Array([
|
|
17995
18262
|
1116352408,
|
|
@@ -18482,7 +18749,7 @@ function readCommentDefinitions(commentsDoc) {
|
|
|
18482
18749
|
author: attr2(comment, "author") || null,
|
|
18483
18750
|
date: attr2(comment, "date") || null,
|
|
18484
18751
|
text: paragraphs.map((p) => extractCanonicalParagraphText(p)).join("\n"),
|
|
18485
|
-
paraId:
|
|
18752
|
+
paraId: commentParaId(comment) || null
|
|
18486
18753
|
});
|
|
18487
18754
|
}
|
|
18488
18755
|
return result;
|
|
@@ -18713,43 +18980,6 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
18713
18980
|
};
|
|
18714
18981
|
}
|
|
18715
18982
|
|
|
18716
|
-
// services/comment-builders.js
|
|
18717
|
-
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18718
|
-
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18719
|
-
function createCommentParaId(commentId) {
|
|
18720
|
-
const numeric = Number.parseInt(String(commentId), 10);
|
|
18721
|
-
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18722
|
-
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18723
|
-
}
|
|
18724
|
-
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18725
|
-
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18726
|
-
const escapedContent = escapeXml(content);
|
|
18727
|
-
const escapedAuthor = escapeXml(author);
|
|
18728
|
-
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18729
|
-
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18730
|
-
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18731
|
-
</w:p>
|
|
18732
|
-
</w:comment>`;
|
|
18733
|
-
}
|
|
18734
|
-
function buildCommentsPartXml(comments) {
|
|
18735
|
-
if (!comments || comments.length === 0) {
|
|
18736
|
-
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18737
|
-
}
|
|
18738
|
-
const commentElements = comments.map(
|
|
18739
|
-
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18740
|
-
).join("\n ");
|
|
18741
|
-
return `<w:comments xmlns:w="${NS_W}">
|
|
18742
|
-
${commentElements}
|
|
18743
|
-
</w:comments>`;
|
|
18744
|
-
}
|
|
18745
|
-
function buildCommentsExtendedPartXml(entries) {
|
|
18746
|
-
const body = (entries || []).map((entry) => {
|
|
18747
|
-
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18748
|
-
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18749
|
-
}).join("");
|
|
18750
|
-
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18751
|
-
}
|
|
18752
|
-
|
|
18753
18983
|
// services/comment-locator.js
|
|
18754
18984
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
18755
18985
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -19210,29 +19440,7 @@ function parseRequired(xml, partName) {
|
|
|
19210
19440
|
}
|
|
19211
19441
|
return { doc: parsed.doc };
|
|
19212
19442
|
}
|
|
19213
|
-
function
|
|
19214
|
-
const ids = /* @__PURE__ */ new Set();
|
|
19215
|
-
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
19216
|
-
const id = attr3(p, "w14:paraId", "paraId");
|
|
19217
|
-
if (id) ids.add(id.toUpperCase());
|
|
19218
|
-
}
|
|
19219
|
-
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19220
|
-
const id = attr3(ex, "w15:paraId", "paraId");
|
|
19221
|
-
if (id) ids.add(id.toUpperCase());
|
|
19222
|
-
}
|
|
19223
|
-
return ids;
|
|
19224
|
-
}
|
|
19225
|
-
function allocateParaId(commentId, occupied) {
|
|
19226
|
-
let candidate = createCommentParaId(commentId);
|
|
19227
|
-
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
19228
|
-
while (occupied.has(candidate)) {
|
|
19229
|
-
value = value + 1 >>> 0;
|
|
19230
|
-
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
19231
|
-
}
|
|
19232
|
-
occupied.add(candidate);
|
|
19233
|
-
return candidate;
|
|
19234
|
-
}
|
|
19235
|
-
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19443
|
+
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, documentXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19236
19444
|
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "A comment reply requires an existing word/comments.xml part." } };
|
|
19237
19445
|
const commentsParsed = parseRequired(commentsXml, "word/comments.xml");
|
|
19238
19446
|
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
@@ -19246,15 +19454,31 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19246
19454
|
extendedDoc = parsed.doc;
|
|
19247
19455
|
}
|
|
19248
19456
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
19249
|
-
const
|
|
19457
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/w14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
19458
|
+
const parentParagraph = commentThreadParagraph(parent);
|
|
19250
19459
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
19251
|
-
let parentParaId =
|
|
19460
|
+
let parentParaId = commentParaId(parent);
|
|
19252
19461
|
if (!parentParaId) {
|
|
19253
19462
|
parentParaId = allocateParaId(parentCommentId, occupied);
|
|
19254
19463
|
parentParagraph.setAttributeNS(NS_W14, "w14:paraId", parentParaId);
|
|
19255
19464
|
} else {
|
|
19256
19465
|
parentParaId = parentParaId.toUpperCase();
|
|
19257
19466
|
}
|
|
19467
|
+
for (const node of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19468
|
+
if (attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId) {
|
|
19469
|
+
const root2 = attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase();
|
|
19470
|
+
if (root2) parentParaId = root2;
|
|
19471
|
+
break;
|
|
19472
|
+
}
|
|
19473
|
+
}
|
|
19474
|
+
const threadCommentIds = [];
|
|
19475
|
+
const commentNodes = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
19476
|
+
const parentOf = new Map(Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || []).map((node) => [attr3(node, "w15:paraId", "paraId").toUpperCase(), attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase()]));
|
|
19477
|
+
for (const node of commentNodes) {
|
|
19478
|
+
const key = commentParaId(node);
|
|
19479
|
+
if (key === parentParaId || parentOf.get(key) === parentParaId) threadCommentIds.push(attr3(node, "w:id", "id"));
|
|
19480
|
+
}
|
|
19481
|
+
const threadRootCommentId = attr3(commentNodes.find((node) => commentParaId(node) === parentParaId), "w:id", "id") || String(parentCommentId);
|
|
19258
19482
|
const replyParaId = allocateParaId(commentId, occupied);
|
|
19259
19483
|
const replyParsed = parseRequired(`<w:comments xmlns:w="${NS_W6}" xmlns:w14="${NS_W14}">${buildCommentElement(commentId, author, commentContent, date, replyParaId)}</w:comments>`, "reply comment");
|
|
19260
19484
|
commentsDoc.documentElement.appendChild(commentsDoc.importNode(replyParsed.doc.documentElement.firstChild, true));
|
|
@@ -19263,16 +19487,18 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19263
19487
|
}
|
|
19264
19488
|
const root = extendedDoc.documentElement;
|
|
19265
19489
|
const entries = Array.from(root.getElementsByTagNameNS("*", "commentEx"));
|
|
19266
|
-
|
|
19490
|
+
const rootEntry = entries.find((node) => attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId);
|
|
19491
|
+
if (!rootEntry) {
|
|
19267
19492
|
const parentEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19268
19493
|
parentEx.setAttributeNS(NS_W15, "w15:paraId", parentParaId);
|
|
19269
19494
|
parentEx.setAttributeNS(NS_W15, "w15:done", "0");
|
|
19270
19495
|
root.appendChild(parentEx);
|
|
19271
19496
|
}
|
|
19497
|
+
const threadDone = attr3(rootEntry, "w15:done", "done") === "1" ? "1" : "0";
|
|
19272
19498
|
const replyEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19273
19499
|
replyEx.setAttributeNS(NS_W15, "w15:paraId", replyParaId);
|
|
19274
19500
|
replyEx.setAttributeNS(NS_W15, "w15:paraIdParent", parentParaId);
|
|
19275
|
-
replyEx.setAttributeNS(NS_W15, "w15:done",
|
|
19501
|
+
replyEx.setAttributeNS(NS_W15, "w15:done", threadDone);
|
|
19276
19502
|
root.appendChild(replyEx);
|
|
19277
19503
|
const serializer = createSerializer();
|
|
19278
19504
|
return {
|
|
@@ -19285,7 +19511,9 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19285
19511
|
commentId,
|
|
19286
19512
|
parentCommentId: String(parentCommentId),
|
|
19287
19513
|
paraId: replyParaId,
|
|
19288
|
-
parentParaId
|
|
19514
|
+
parentParaId,
|
|
19515
|
+
threadRootCommentId,
|
|
19516
|
+
threadCommentIds: threadCommentIds.length ? threadCommentIds : [threadRootCommentId]
|
|
19289
19517
|
};
|
|
19290
19518
|
}
|
|
19291
19519
|
|
|
@@ -19350,19 +19578,19 @@ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
|
19350
19578
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
19351
19579
|
const doc = parseOoxml(ooxmlString);
|
|
19352
19580
|
if (!doc) return ooxmlString;
|
|
19353
|
-
const
|
|
19354
|
-
const runs = doc.getElementsByTagNameNS(
|
|
19355
|
-
const insertions = doc.getElementsByTagNameNS(
|
|
19581
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19582
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
19583
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
19356
19584
|
const allRuns = [...Array.from(runs)];
|
|
19357
19585
|
for (const ins of insertions) {
|
|
19358
|
-
const insideRuns = ins.getElementsByTagNameNS(
|
|
19586
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
19359
19587
|
allRuns.push(...Array.from(insideRuns));
|
|
19360
19588
|
}
|
|
19361
19589
|
for (const run of allRuns) {
|
|
19362
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19590
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19363
19591
|
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19364
19592
|
if (runText.includes(targetText) || runText === targetText) {
|
|
19365
|
-
const rPrElements = run.getElementsByTagNameNS(
|
|
19593
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19366
19594
|
if (rPrElements.length > 0) {
|
|
19367
19595
|
const rPr = rPrElements[0];
|
|
19368
19596
|
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
@@ -19397,7 +19625,7 @@ var HIGHLIGHT_COLOR_MAP = {
|
|
|
19397
19625
|
"white": "white"
|
|
19398
19626
|
};
|
|
19399
19627
|
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
19400
|
-
const
|
|
19628
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19401
19629
|
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
19402
19630
|
const generateRedlines = options?.generateRedlines ?? false;
|
|
19403
19631
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -19416,10 +19644,10 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19416
19644
|
}
|
|
19417
19645
|
});
|
|
19418
19646
|
}
|
|
19419
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(
|
|
19647
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
19420
19648
|
Array.from(existingHighlight).forEach(removeNode2);
|
|
19421
19649
|
const highlightEl = createWordElement(doc, "w:highlight");
|
|
19422
|
-
highlightEl.setAttributeNS(
|
|
19650
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
19423
19651
|
rPrElement.appendChild(highlightEl);
|
|
19424
19652
|
if (generateRedlines && previousRPrState) {
|
|
19425
19653
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
@@ -19428,7 +19656,7 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19428
19656
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
19429
19657
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
19430
19658
|
rPrChange.appendChild(previousRPrState);
|
|
19431
|
-
const existingChange = rPrElement.getElementsByTagNameNS(
|
|
19659
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
19432
19660
|
Array.from(existingChange).forEach(removeNode2);
|
|
19433
19661
|
rPrElement.appendChild(rPrChange);
|
|
19434
19662
|
}
|
|
@@ -19445,12 +19673,12 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19445
19673
|
} else {
|
|
19446
19674
|
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
19447
19675
|
}
|
|
19448
|
-
const
|
|
19676
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19449
19677
|
const getRunText = (run) => {
|
|
19450
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19678
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19451
19679
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19452
19680
|
};
|
|
19453
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(
|
|
19681
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
19454
19682
|
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
19455
19683
|
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
19456
19684
|
const clonedRun = sourceRun.cloneNode(true);
|
|
@@ -19459,14 +19687,14 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19459
19687
|
} else {
|
|
19460
19688
|
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
19461
19689
|
}
|
|
19462
|
-
const textNodes = clonedRun.getElementsByTagNameNS(
|
|
19690
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
19463
19691
|
Array.from(textNodes).forEach(removeNode2);
|
|
19464
19692
|
const newText = createWordElement(doc, "w:t");
|
|
19465
19693
|
newText.setAttribute("xml:space", "preserve");
|
|
19466
19694
|
newText.textContent = text2;
|
|
19467
19695
|
clonedRun.appendChild(newText);
|
|
19468
19696
|
if (shouldHighlight) {
|
|
19469
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(
|
|
19697
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19470
19698
|
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
19471
19699
|
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
19472
19700
|
if (existingRPr) {
|
|
@@ -19515,20 +19743,68 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19515
19743
|
return serializeOoxml(doc);
|
|
19516
19744
|
}
|
|
19517
19745
|
|
|
19746
|
+
// services/package-parts.js
|
|
19747
|
+
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
19748
|
+
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
19749
|
+
var PART_SPECS = Object.freeze([
|
|
19750
|
+
{ id: "document", path: "word/document.xml", pathPattern: null, contentType: `${WORD_ML}.document.main+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/officeDocument`, relTarget: null, verified: true },
|
|
19751
|
+
{ id: "numbering", path: "word/numbering.xml", pathPattern: null, contentType: `${WORD_ML}.numbering+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/numbering`, relTarget: "numbering.xml", verified: false },
|
|
19752
|
+
{ id: "comments", path: "word/comments.xml", pathPattern: null, contentType: `${WORD_ML}.comments+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/comments`, relTarget: "comments.xml", verified: true },
|
|
19753
|
+
{
|
|
19754
|
+
id: "commentsExtended",
|
|
19755
|
+
path: "word/commentsExtended.xml",
|
|
19756
|
+
pathPattern: null,
|
|
19757
|
+
contentType: `${WORD_ML}.commentsExtended+xml`,
|
|
19758
|
+
// Written by this package through 0.8.0; Word does not recognize it and offers to repair the file.
|
|
19759
|
+
legacyContentTypes: ["application/vnd.ms-word.commentsExtended+xml"],
|
|
19760
|
+
relType: "http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
|
|
19761
|
+
relTarget: "commentsExtended.xml",
|
|
19762
|
+
verified: true
|
|
19763
|
+
},
|
|
19764
|
+
{ id: "commentsIds", path: "word/commentsIds.xml", pathPattern: null, contentType: `${WORD_ML}.commentsIds+xml`, legacyContentTypes: [], relType: "http://schemas.microsoft.com/office/2016/09/relationships/commentsIds", relTarget: "commentsIds.xml", verified: true },
|
|
19765
|
+
{ id: "commentsExtensible", path: "word/commentsExtensible.xml", pathPattern: null, contentType: `${WORD_ML}.commentsExtensible+xml`, legacyContentTypes: [], relType: "http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible", relTarget: "commentsExtensible.xml", verified: true },
|
|
19766
|
+
// Not yet produced by any Word fixture (Word only writes people.xml for signed-in authors): unverified.
|
|
19767
|
+
{ id: "people", path: "word/people.xml", pathPattern: null, contentType: `${WORD_ML}.people+xml`, legacyContentTypes: [], relType: "http://schemas.microsoft.com/office/2011/relationships/people", relTarget: "people.xml", verified: false },
|
|
19768
|
+
{ id: "header", path: null, pathPattern: /^word\/header\d+\.xml$/i, contentType: `${WORD_ML}.header+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/header`, relTarget: null, verified: true },
|
|
19769
|
+
{ id: "footer", path: null, pathPattern: /^word\/footer\d+\.xml$/i, contentType: `${WORD_ML}.footer+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/footer`, relTarget: null, verified: true }
|
|
19770
|
+
].map((spec) => Object.freeze(spec)));
|
|
19771
|
+
var BY_ID = new Map(PART_SPECS.map((spec) => [spec.id, spec]));
|
|
19772
|
+
function getPartSpec(id) {
|
|
19773
|
+
const spec = BY_ID.get(id);
|
|
19774
|
+
if (!spec) throw new Error(`Unknown package part: ${id}`);
|
|
19775
|
+
return spec;
|
|
19776
|
+
}
|
|
19777
|
+
function toEntryName(partName) {
|
|
19778
|
+
return String(partName || "").replace(/^\/+/, "");
|
|
19779
|
+
}
|
|
19780
|
+
function findPartSpecByPartName(partName) {
|
|
19781
|
+
const name = toEntryName(partName);
|
|
19782
|
+
const lower = name.toLowerCase();
|
|
19783
|
+
return PART_SPECS.find((spec) => spec.path ? spec.path.toLowerCase() === lower : spec.pathPattern?.test(name)) || null;
|
|
19784
|
+
}
|
|
19785
|
+
function correctedContentTypeFor(partName, actualContentType) {
|
|
19786
|
+
const spec = findPartSpecByPartName(partName);
|
|
19787
|
+
if (!spec || !spec.legacyContentTypes.includes(actualContentType)) return null;
|
|
19788
|
+
return spec.contentType;
|
|
19789
|
+
}
|
|
19790
|
+
|
|
19518
19791
|
// services/standalone-docx-plumbing.js
|
|
19519
19792
|
var NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19520
19793
|
var NS_CT = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
19521
19794
|
var NS_RELS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
19522
|
-
var
|
|
19523
|
-
var
|
|
19524
|
-
var
|
|
19525
|
-
var
|
|
19526
|
-
var
|
|
19527
|
-
var
|
|
19528
|
-
var
|
|
19529
|
-
var
|
|
19530
|
-
var
|
|
19531
|
-
var
|
|
19795
|
+
var NUMBERING = getPartSpec("numbering");
|
|
19796
|
+
var COMMENTS = getPartSpec("comments");
|
|
19797
|
+
var COMMENTS_EXTENDED = getPartSpec("commentsExtended");
|
|
19798
|
+
var NUMBERING_REL_TYPE = NUMBERING.relType;
|
|
19799
|
+
var NUMBERING_CONTENT_TYPE = NUMBERING.contentType;
|
|
19800
|
+
var COMMENTS_REL_TYPE = COMMENTS.relType;
|
|
19801
|
+
var COMMENTS_CONTENT_TYPE = COMMENTS.contentType;
|
|
19802
|
+
var COMMENTS_EXTENDED_REL_TYPE = COMMENTS_EXTENDED.relType;
|
|
19803
|
+
var COMMENTS_EXTENDED_CONTENT_TYPE = COMMENTS_EXTENDED.contentType;
|
|
19804
|
+
var DOCUMENT_PATH = getPartSpec("document").path;
|
|
19805
|
+
var NUMBERING_PATH = NUMBERING.path;
|
|
19806
|
+
var COMMENTS_PATH = COMMENTS.path;
|
|
19807
|
+
var COMMENTS_EXTENDED_PATH = COMMENTS_EXTENDED.path;
|
|
19532
19808
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
19533
19809
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
19534
19810
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
@@ -19816,6 +20092,22 @@ async function ensureCommentsExtendedArtifactsInZip(zip, commentsExtendedXml, op
|
|
|
19816
20092
|
}
|
|
19817
20093
|
}
|
|
19818
20094
|
}
|
|
20095
|
+
async function repairKnownContentTypes(zip) {
|
|
20096
|
+
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
20097
|
+
if (!ctText) return [];
|
|
20098
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
20099
|
+
const repairs = [];
|
|
20100
|
+
for (const override of Array.from(ctDoc.getElementsByTagNameNS("*", "Override"))) {
|
|
20101
|
+
const partName = override.getAttribute("PartName") || "";
|
|
20102
|
+
const actual = override.getAttribute("ContentType") || "";
|
|
20103
|
+
const expected = correctedContentTypeFor(partName, actual);
|
|
20104
|
+
if (!expected) continue;
|
|
20105
|
+
override.setAttribute("ContentType", expected);
|
|
20106
|
+
repairs.push({ partName, from: actual, to: expected });
|
|
20107
|
+
}
|
|
20108
|
+
if (repairs.length > 0) zip.file(CONTENT_TYPES_PATH, createSerializer().serializeToString(ctDoc));
|
|
20109
|
+
return repairs;
|
|
20110
|
+
}
|
|
19819
20111
|
async function validateDocxPackage(zip) {
|
|
19820
20112
|
const documentXml = await readZipText(zip, DOCUMENT_PATH);
|
|
19821
20113
|
if (!documentXml) {
|
|
@@ -19897,9 +20189,8 @@ async function validateDocxPackage(zip) {
|
|
|
19897
20189
|
const paraIdToCommentId = /* @__PURE__ */ new Map();
|
|
19898
20190
|
for (const comment of Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
19899
20191
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
19900
|
-
const
|
|
19901
|
-
|
|
19902
|
-
if (paraId) paraIdToCommentId.set(paraId.toUpperCase(), id);
|
|
20192
|
+
const paraId = commentParaId(comment);
|
|
20193
|
+
if (paraId) paraIdToCommentId.set(paraId, id);
|
|
19903
20194
|
}
|
|
19904
20195
|
const knownParaIds = new Set(paraIdToCommentId.keys());
|
|
19905
20196
|
const extendedParaIds = /* @__PURE__ */ new Set();
|
|
@@ -19960,10 +20251,39 @@ async function validateDocxPackage(zip) {
|
|
|
19960
20251
|
throw new Error("Validation failed: comments rel missing");
|
|
19961
20252
|
}
|
|
19962
20253
|
}
|
|
20254
|
+
{
|
|
20255
|
+
const relationshipById = /* @__PURE__ */ new Map();
|
|
20256
|
+
for (const rel of Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20257
|
+
relationshipById.set(rel.getAttribute("Id") || "", { type: rel.getAttribute("Type") || "", target: rel.getAttribute("Target") || "" });
|
|
20258
|
+
}
|
|
20259
|
+
const headerSpec = getPartSpec("header");
|
|
20260
|
+
const footerSpec = getPartSpec("footer");
|
|
20261
|
+
const overrideTypeByPart = new Map(Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).map((override) => [(override.getAttribute("PartName") || "").toLowerCase(), override.getAttribute("ContentType") || ""]));
|
|
20262
|
+
for (const reference of [
|
|
20263
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "headerReference")),
|
|
20264
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "footerReference"))
|
|
20265
|
+
]) {
|
|
20266
|
+
const relId = reference.getAttributeNS?.("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") || reference.getAttribute("r:id") || "";
|
|
20267
|
+
const spec = reference.localName === "headerReference" ? headerSpec : footerSpec;
|
|
20268
|
+
const relationship = relationshipById.get(relId);
|
|
20269
|
+
if (!relationship) throw new Error(`Validation failed: ${reference.localName} ${relId || "(no r:id)"} has no document relationship`);
|
|
20270
|
+
if (relationship.type !== spec.relType) throw new Error(`Validation failed: ${reference.localName} ${relId} points at a relationship of the wrong type (${relationship.type})`);
|
|
20271
|
+
const partName = relationship.target.startsWith("/") ? relationship.target : `/word/${relationship.target}`;
|
|
20272
|
+
const partXml = await readZipText(zip, partName.slice(1));
|
|
20273
|
+
if (!partXml) throw new Error(`Validation failed: ${reference.localName} ${relId} targets a missing part: ${partName}`);
|
|
20274
|
+
parseXmlStrictStandalone(partXml, partName.slice(1));
|
|
20275
|
+
const contentType = overrideTypeByPart.get(partName.toLowerCase());
|
|
20276
|
+
if (contentType !== spec.contentType) throw new Error(`Validation failed: ${partName} content type is ${contentType ? `'${contentType}'` : "missing"}; Word expects '${spec.contentType}'`);
|
|
20277
|
+
}
|
|
20278
|
+
}
|
|
19963
20279
|
if (commentsExtendedXml) {
|
|
19964
|
-
const
|
|
19965
|
-
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
20280
|
+
const extendedOverride = Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).find(
|
|
20281
|
+
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
19966
20282
|
);
|
|
20283
|
+
if (extendedOverride && extendedOverride.getAttribute("ContentType") !== COMMENTS_EXTENDED_CONTENT_TYPE) {
|
|
20284
|
+
throw new Error(`Validation failed: commentsExtended CT override has wrong content type '${extendedOverride.getAttribute("ContentType")}' (Word expects '${COMMENTS_EXTENDED_CONTENT_TYPE}')`);
|
|
20285
|
+
}
|
|
20286
|
+
const hasContentType = !!extendedOverride;
|
|
19967
20287
|
const hasRelationship = Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship")).some(
|
|
19968
20288
|
(rel) => (rel.getAttribute("Type") || "") === COMMENTS_EXTENDED_REL_TYPE
|
|
19969
20289
|
);
|
|
@@ -20327,6 +20647,156 @@ function reconcileReceiptsAgainstOutput(parts, receipts) {
|
|
|
20327
20647
|
return { valid: true };
|
|
20328
20648
|
}
|
|
20329
20649
|
|
|
20650
|
+
// services/headers-footers.js
|
|
20651
|
+
var NS_W8 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20652
|
+
var NS_R2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
20653
|
+
var REVISION_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo", "rPrChange", "pPrChange", "sectPrChange", "tblPrChange", "tblGridChange", "tcPrChange", "trPrChange", "numberingChange"]);
|
|
20654
|
+
var REVISION_CONTENT_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
20655
|
+
var attrOf2 = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
20656
|
+
function resolveTarget(target) {
|
|
20657
|
+
const clean = String(target || "");
|
|
20658
|
+
if (clean.startsWith("/")) return clean.replace(/^\/+/, "");
|
|
20659
|
+
const parts = ["word"];
|
|
20660
|
+
for (const segment of clean.split("/")) {
|
|
20661
|
+
if (segment === "..") parts.pop();
|
|
20662
|
+
else if (segment && segment !== ".") parts.push(segment);
|
|
20663
|
+
}
|
|
20664
|
+
return parts.join("/");
|
|
20665
|
+
}
|
|
20666
|
+
function discoverHeaderFooterParts({ documentXml, relsXml = null, settingsXml = null }) {
|
|
20667
|
+
if (!documentXml || !relsXml) return [];
|
|
20668
|
+
const documentParsed = parseOoxmlSafe(documentXml, "application/xml");
|
|
20669
|
+
const relsParsed = parseOoxmlSafe(relsXml, "application/xml");
|
|
20670
|
+
if (!documentParsed.doc || documentParsed.error || !relsParsed.doc || relsParsed.error) return [];
|
|
20671
|
+
const targetByRelId = /* @__PURE__ */ new Map();
|
|
20672
|
+
for (const rel of Array.from(relsParsed.doc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20673
|
+
targetByRelId.set(rel.getAttribute("Id") || "", rel.getAttribute("Target") || "");
|
|
20674
|
+
}
|
|
20675
|
+
const evenAndOdd = !!settingsXml && /<w:evenAndOddHeaders\b(?![^>]*w:val="(?:0|false|off)")/.test(settingsXml);
|
|
20676
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
20677
|
+
const inForce = /* @__PURE__ */ new Map();
|
|
20678
|
+
const sectPrs = Array.from(documentParsed.doc.getElementsByTagNameNS(NS_W8, "sectPr"));
|
|
20679
|
+
sectPrs.forEach((sectPr, sectionIndex) => {
|
|
20680
|
+
const titlePg = Array.from(sectPr.childNodes || []).some((child) => child.localName === "titlePg" && !/^(0|false|off)$/i.test(attrOf2(child, "w:val", "val")));
|
|
20681
|
+
for (const ref of Array.from(sectPr.childNodes || [])) {
|
|
20682
|
+
if (ref.nodeType !== 1 || ref.localName !== "headerReference" && ref.localName !== "footerReference") continue;
|
|
20683
|
+
const relId = ref.getAttributeNS?.(NS_R2, "id") || ref.getAttribute("r:id") || "";
|
|
20684
|
+
const target = targetByRelId.get(relId);
|
|
20685
|
+
if (!target) continue;
|
|
20686
|
+
const path = resolveTarget(target);
|
|
20687
|
+
const kind = ref.localName === "headerReference" ? "header" : "footer";
|
|
20688
|
+
const type = attrOf2(ref, "w:type", "type") || "default";
|
|
20689
|
+
const entry = byPath.get(path) || { path, kind, type, sections: [], applies: [], titlePgSections: 0 };
|
|
20690
|
+
if (!entry.sections.includes(sectionIndex)) entry.sections.push(sectionIndex);
|
|
20691
|
+
if (titlePg) entry.titlePgSections += 1;
|
|
20692
|
+
byPath.set(path, entry);
|
|
20693
|
+
inForce.set(`${kind}:${type}`, path);
|
|
20694
|
+
}
|
|
20695
|
+
for (const path of inForce.values()) {
|
|
20696
|
+
const entry = byPath.get(path);
|
|
20697
|
+
if (entry && !entry.applies.includes(sectionIndex)) entry.applies.push(sectionIndex);
|
|
20698
|
+
}
|
|
20699
|
+
});
|
|
20700
|
+
return Array.from(byPath.values()).map((entry) => ({
|
|
20701
|
+
path: entry.path,
|
|
20702
|
+
kind: entry.kind,
|
|
20703
|
+
type: entry.type,
|
|
20704
|
+
sections: entry.sections,
|
|
20705
|
+
appliesToSections: entry.applies,
|
|
20706
|
+
sharedBySections: entry.applies.length > 1,
|
|
20707
|
+
// first-page parts only show with w:titlePg; even parts only with w:evenAndOddHeaders in settings.xml.
|
|
20708
|
+
active: entry.type === "first" ? entry.titlePgSections > 0 : entry.type === "even" ? evenAndOdd : true
|
|
20709
|
+
}));
|
|
20710
|
+
}
|
|
20711
|
+
function resolvePartSelector(parts, selector) {
|
|
20712
|
+
const describe = (part) => ({ path: part.path, kind: part.kind, type: part.type, sections: part.sections, appliesToSections: part.appliesToSections });
|
|
20713
|
+
if (typeof selector === "string") {
|
|
20714
|
+
const wanted = selector.replace(/^\/+/, "").toLowerCase();
|
|
20715
|
+
const match = parts.find((part) => part.path.toLowerCase() === wanted || part.path.toLowerCase() === `word/${wanted}`);
|
|
20716
|
+
if (match) return { part: match };
|
|
20717
|
+
return { error: { code: "PART_NOT_FOUND", message: `No header/footer part '${selector}' is referenced by the document.`, candidates: parts.map(describe) } };
|
|
20718
|
+
}
|
|
20719
|
+
if (!selector || typeof selector !== "object" || selector.kind !== "header" && selector.kind !== "footer") {
|
|
20720
|
+
return { error: { code: "INVALID_OPERATION", message: 'part must be a part path or { kind: "header"|"footer", type?: "default"|"first"|"even", section?: number }.' } };
|
|
20721
|
+
}
|
|
20722
|
+
const type = selector.type || "default";
|
|
20723
|
+
if (!["default", "first", "even"].includes(type)) {
|
|
20724
|
+
return { error: { code: "INVALID_OPERATION", message: `part.type must be default, first or even (got '${type}').` } };
|
|
20725
|
+
}
|
|
20726
|
+
let matches = parts.filter((part) => part.kind === selector.kind && part.type === type);
|
|
20727
|
+
if (Number.isInteger(selector.section)) matches = matches.filter((part) => part.appliesToSections.includes(selector.section));
|
|
20728
|
+
if (matches.length === 1) return { part: matches[0] };
|
|
20729
|
+
if (matches.length === 0) {
|
|
20730
|
+
return { error: { code: "PART_NOT_FOUND", message: `The document has no ${type} ${selector.kind}${Number.isInteger(selector.section) ? ` for section ${selector.section}` : ""}.`, candidates: parts.map(describe) } };
|
|
20731
|
+
}
|
|
20732
|
+
return { error: { code: "PART_AMBIGUOUS", message: `Several ${type} ${selector.kind} parts exist; add "section" or use the part path.`, candidates: matches.map(describe) } };
|
|
20733
|
+
}
|
|
20734
|
+
function isHeaderFooterPath(path) {
|
|
20735
|
+
const spec = findPartSpecByPartName(path);
|
|
20736
|
+
return spec?.id === "header" || spec?.id === "footer";
|
|
20737
|
+
}
|
|
20738
|
+
function countRevisionsInsideFields(xml) {
|
|
20739
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
20740
|
+
if (!parsed.doc || parsed.error) return 0;
|
|
20741
|
+
let depth = 0;
|
|
20742
|
+
let count = 0;
|
|
20743
|
+
const visit = (node) => {
|
|
20744
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
20745
|
+
if (child.nodeType !== 1) continue;
|
|
20746
|
+
const name = child.localName;
|
|
20747
|
+
if (name === "fldChar") {
|
|
20748
|
+
const type = attrOf2(child, "w:fldCharType", "fldCharType");
|
|
20749
|
+
if (type === "begin") depth += 1;
|
|
20750
|
+
else if (type === "end") depth = Math.max(0, depth - 1);
|
|
20751
|
+
continue;
|
|
20752
|
+
}
|
|
20753
|
+
if (REVISION_CONTENT_ELEMENTS.has(name) && depth > 0) count += 1;
|
|
20754
|
+
if (name === "fldSimple") {
|
|
20755
|
+
depth += 1;
|
|
20756
|
+
visit(child);
|
|
20757
|
+
depth -= 1;
|
|
20758
|
+
continue;
|
|
20759
|
+
}
|
|
20760
|
+
visit(child);
|
|
20761
|
+
}
|
|
20762
|
+
};
|
|
20763
|
+
visit(parsed.doc.documentElement);
|
|
20764
|
+
return count;
|
|
20765
|
+
}
|
|
20766
|
+
function collectRevisionIds(xml) {
|
|
20767
|
+
const ids = /* @__PURE__ */ new Set();
|
|
20768
|
+
for (const match of String(xml || "").matchAll(/<w:(?:ins|del|moveFrom|moveTo|rPrChange|pPrChange|sectPrChange|tblPrChange|tblGridChange|tcPrChange|trPrChange|numberingChange)\b[^>]*?\bw:id="(\d+)"/g)) ids.add(match[1]);
|
|
20769
|
+
return ids;
|
|
20770
|
+
}
|
|
20771
|
+
function renumberCollidingRevisionIds(xml, taken) {
|
|
20772
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
20773
|
+
if (!parsed.doc || parsed.error) return { xml, renumbered: 0, idMap: /* @__PURE__ */ new Map() };
|
|
20774
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
20775
|
+
let next = 1 + Math.max(0, ...Array.from(taken, (id) => Number(id)).filter(Number.isFinite));
|
|
20776
|
+
const used = new Set(taken);
|
|
20777
|
+
let renumbered = 0;
|
|
20778
|
+
const visit = (node) => {
|
|
20779
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
20780
|
+
if (child.nodeType !== 1) continue;
|
|
20781
|
+
if (REVISION_ELEMENTS.has(child.localName)) {
|
|
20782
|
+
const id = attrOf2(child, "w:id", "id");
|
|
20783
|
+
if (id && used.has(id)) {
|
|
20784
|
+
while (used.has(String(next))) next += 1;
|
|
20785
|
+
child.setAttributeNS(NS_W8, "w:id", String(next));
|
|
20786
|
+
idMap.set(id, String(next));
|
|
20787
|
+
used.add(String(next));
|
|
20788
|
+
renumbered += 1;
|
|
20789
|
+
} else if (id) {
|
|
20790
|
+
used.add(id);
|
|
20791
|
+
}
|
|
20792
|
+
}
|
|
20793
|
+
visit(child);
|
|
20794
|
+
}
|
|
20795
|
+
};
|
|
20796
|
+
visit(parsed.doc.documentElement);
|
|
20797
|
+
return renumbered > 0 ? { xml: createSerializer().serializeToString(parsed.doc), renumbered, idMap } : { xml, renumbered: 0, idMap };
|
|
20798
|
+
}
|
|
20799
|
+
|
|
20330
20800
|
// services/error-recovery.js
|
|
20331
20801
|
var ERROR_RECOVERY_VERSION = 1;
|
|
20332
20802
|
var RULES = Object.freeze({
|
|
@@ -20364,6 +20834,12 @@ var RULES = Object.freeze({
|
|
|
20364
20834
|
STRUCTURED_CONTENT_INVALID: ["request", "request_fixable", "change_request"],
|
|
20365
20835
|
UNSUPPORTED_INSERTION_AFFINITY: ["request", "request_fixable", "change_request"],
|
|
20366
20836
|
EXISTING_REVISIONS: ["target-safety", "policy_choice_required", "set_option"],
|
|
20837
|
+
PART_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20838
|
+
PART_AMBIGUOUS: ["target-resolution", "candidate_selection_required", "choose_candidate"],
|
|
20839
|
+
COMMENT_IN_HEADER_FOOTER: ["request", "request_fixable", "change_request"],
|
|
20840
|
+
FIELD_EDIT_REFUSED: ["target-safety", "request_fixable", "change_request"],
|
|
20841
|
+
COMMENT_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20842
|
+
PARENT_ANCHOR_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20367
20843
|
COMMENTED_CONTENT_MERGE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20368
20844
|
COMMENTED_CONTENT_DELETE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20369
20845
|
OVERLAPPING_SOURCE_TARGETS: ["batch-compilation", "source_conflict", "consolidate_operations"],
|
|
@@ -20816,6 +21292,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
20816
21292
|
"delete",
|
|
20817
21293
|
"comment",
|
|
20818
21294
|
"comment_reply",
|
|
21295
|
+
"comment_resolve",
|
|
20819
21296
|
"highlight"
|
|
20820
21297
|
]);
|
|
20821
21298
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
@@ -20834,11 +21311,14 @@ function isRecord(value) {
|
|
|
20834
21311
|
function nonEmptyString(value) {
|
|
20835
21312
|
return typeof value === "string" && value.trim().length > 0;
|
|
20836
21313
|
}
|
|
21314
|
+
function isCommentThreadOperationKind(kind) {
|
|
21315
|
+
return kind === "comment_reply" || kind === "comment_resolve";
|
|
21316
|
+
}
|
|
20837
21317
|
function getCanonicalOperationType(operation) {
|
|
20838
21318
|
const type = operation?.type;
|
|
20839
21319
|
if (type === "insert" && operation?.target?.revisionView === "rejected") return "rejected-insert";
|
|
20840
21320
|
if (type === "restore") return "restore";
|
|
20841
|
-
if (type === "comment" || type === "comment_reply" || type === "highlight") return type;
|
|
21321
|
+
if (type === "comment" || type === "comment_reply" || type === "comment_resolve" || type === "highlight") return type;
|
|
20842
21322
|
if (type === "paragraph-format") return "paragraph-format";
|
|
20843
21323
|
if (type === "character-format" || type === "format" && (operation?.textToFormat != null || operation?.properties != null)) return "format";
|
|
20844
21324
|
return "redline";
|
|
@@ -20958,7 +21438,7 @@ function validateDocumentOperation(operation) {
|
|
|
20958
21438
|
};
|
|
20959
21439
|
}
|
|
20960
21440
|
const target = normalized.targetDescriptor;
|
|
20961
|
-
if (normalized.operationKind
|
|
21441
|
+
if (!isCommentThreadOperationKind(normalized.operationKind) && !nonEmptyString(target.text) && target.index == null && !target.paragraphId && !target.fingerprint && !target.captureRef) {
|
|
20962
21442
|
return {
|
|
20963
21443
|
valid: false,
|
|
20964
21444
|
error: {
|
|
@@ -21185,6 +21665,14 @@ function validateDocumentOperation(operation) {
|
|
|
21185
21665
|
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment reply operations require a non-empty "commentContent" field.' } };
|
|
21186
21666
|
}
|
|
21187
21667
|
}
|
|
21668
|
+
if (normalized.operationKind === "comment_resolve") {
|
|
21669
|
+
if (!nonEmptyString(String(normalized.commentId ?? ""))) {
|
|
21670
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve operations require a "commentId".' } };
|
|
21671
|
+
}
|
|
21672
|
+
if (normalized.resolved != null && typeof normalized.resolved !== "boolean") {
|
|
21673
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve "resolved" must be a boolean when provided.' } };
|
|
21674
|
+
}
|
|
21675
|
+
}
|
|
21188
21676
|
if (normalized.operationKind === "highlight") {
|
|
21189
21677
|
if (!nonEmptyString(normalized.textToHighlight)) {
|
|
21190
21678
|
return {
|
|
@@ -21664,7 +22152,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
21664
22152
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
21665
22153
|
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
21666
22154
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
21667
|
-
if (validation.valid && operation.operationKind !== "comment_reply" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
22155
|
+
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
21668
22156
|
try {
|
|
21669
22157
|
const start = resolveDescriptor(
|
|
21670
22158
|
xmlDoc,
|
|
@@ -22165,12 +22653,12 @@ function resolveTargetFromCapture(xmlDoc, session, targetDescriptor, _opType = "
|
|
|
22165
22653
|
}
|
|
22166
22654
|
|
|
22167
22655
|
// services/document-operation-mutations.js
|
|
22168
|
-
var
|
|
22656
|
+
var NS_W9 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
22169
22657
|
var NS_W142 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
22170
22658
|
function getCommentIdsInElement(element) {
|
|
22171
22659
|
const ids = /* @__PURE__ */ new Set();
|
|
22172
22660
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22173
|
-
for (const node of Array.from(element?.getElementsByTagNameNS?.(
|
|
22661
|
+
for (const node of Array.from(element?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22174
22662
|
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
22175
22663
|
if (id !== "") ids.add(id);
|
|
22176
22664
|
}
|
|
@@ -22384,7 +22872,7 @@ function normalizeBodySectionOrder(xmlDoc) {
|
|
|
22384
22872
|
normalizeBodySectionOrderStandalone(xmlDoc);
|
|
22385
22873
|
}
|
|
22386
22874
|
function removeProofErrNodes(paragraph) {
|
|
22387
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
22875
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "proofErr") || [])) {
|
|
22388
22876
|
node.parentNode?.removeChild(node);
|
|
22389
22877
|
}
|
|
22390
22878
|
}
|
|
@@ -22395,12 +22883,12 @@ function preprocessRedlineTargetParagraph(targetParagraph) {
|
|
|
22395
22883
|
function getDirectWordChild4(element, localName2) {
|
|
22396
22884
|
if (!element) return null;
|
|
22397
22885
|
return Array.from(element.childNodes || []).find(
|
|
22398
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
22886
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === localName2
|
|
22399
22887
|
) || null;
|
|
22400
22888
|
}
|
|
22401
22889
|
function computeTableIndexInDocument(xmlDoc, tableElement) {
|
|
22402
22890
|
if (!xmlDoc || !tableElement) return null;
|
|
22403
|
-
const tables = Array.from(xmlDoc.getElementsByTagNameNS(
|
|
22891
|
+
const tables = Array.from(xmlDoc.getElementsByTagNameNS(NS_W9, "tbl"));
|
|
22404
22892
|
const idx = tables.indexOf(tableElement);
|
|
22405
22893
|
return idx >= 0 ? idx + 1 : null;
|
|
22406
22894
|
}
|
|
@@ -22450,7 +22938,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22450
22938
|
markParagraphMarkInserted(xmlDoc, paragraph, author);
|
|
22451
22939
|
}
|
|
22452
22940
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22453
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
22941
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22454
22942
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22455
22943
|
if (anchorRunPr) {
|
|
22456
22944
|
run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
@@ -22475,7 +22963,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22475
22963
|
}
|
|
22476
22964
|
function serializeParagraphRangeAsDocument(paragraphs, serializer) {
|
|
22477
22965
|
const paragraphXml = (paragraphs || []).map((paragraph) => serializer.serializeToString(paragraph)).join("");
|
|
22478
|
-
return `<w:document xmlns:w="${
|
|
22966
|
+
return `<w:document xmlns:w="${NS_W9}"><w:body>${paragraphXml}</w:body></w:document>`;
|
|
22479
22967
|
}
|
|
22480
22968
|
function applyExplicitRangeListInsertions({
|
|
22481
22969
|
xmlDoc,
|
|
@@ -22544,7 +23032,7 @@ function buildEmptyParagraphTemplateFromAnchor(xmlDoc, anchorParagraph) {
|
|
|
22544
23032
|
const anchorPPr = getDirectWordChild4(anchorParagraph, "pPr");
|
|
22545
23033
|
if (anchorPPr) paragraph.appendChild(clonePropertiesWithoutRevisionHistory(anchorPPr));
|
|
22546
23034
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22547
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
23035
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22548
23036
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22549
23037
|
if (anchorRunPr) run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
22550
23038
|
const textNode = createWordElement(xmlDoc, "w:t");
|
|
@@ -22567,7 +23055,7 @@ function wrapParagraphContentInInsertion(xmlDoc, paragraph, revisionMetadata, au
|
|
|
22567
23055
|
ins.setAttribute("w:author", metadata.author);
|
|
22568
23056
|
ins.setAttribute("w:date", metadata.date);
|
|
22569
23057
|
for (const child of Array.from(paragraph.childNodes || [])) {
|
|
22570
|
-
if (child?.nodeType === 1 && child.namespaceURI ===
|
|
23058
|
+
if (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22571
23059
|
ins.appendChild(child.cloneNode(true));
|
|
22572
23060
|
}
|
|
22573
23061
|
wrappedParagraph.appendChild(ins);
|
|
@@ -22591,7 +23079,7 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
22591
23079
|
if (typeof markdownResult?.oxml === "string") {
|
|
22592
23080
|
const extracted = extractReplacementNodes(markdownResult.oxml);
|
|
22593
23081
|
sourceParagraph = (extracted.replacementNodes || []).find(
|
|
22594
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
23082
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p"
|
|
22595
23083
|
) || null;
|
|
22596
23084
|
}
|
|
22597
23085
|
if (!sourceParagraph) {
|
|
@@ -22623,7 +23111,7 @@ function collectNumberingIdsFromNodes(nodes) {
|
|
|
22623
23111
|
}
|
|
22624
23112
|
function directDeletionCarrierText(carrier) {
|
|
22625
23113
|
return Array.from(carrier?.childNodes || []).reduce((text2, child) => {
|
|
22626
|
-
return text2 + (child?.nodeType === 1 && child.namespaceURI ===
|
|
23114
|
+
return text2 + (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r" ? getRunContentPieces(child).map((piece) => piece.text).join("") : "");
|
|
22627
23115
|
}, "");
|
|
22628
23116
|
}
|
|
22629
23117
|
function findOccurrenceOffset(text2, needle, occurrence) {
|
|
@@ -22665,9 +23153,9 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22665
23153
|
}
|
|
22666
23154
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22667
23155
|
if (child.nodeType !== 1) continue;
|
|
22668
|
-
if (!(child.namespaceURI ===
|
|
23156
|
+
if (!(child.namespaceURI === NS_W9 && child.localName === "r")) return true;
|
|
22669
23157
|
for (const runChild of Array.from(child.childNodes || [])) {
|
|
22670
|
-
if (runChild.nodeType !== 1 || runChild.namespaceURI !==
|
|
23158
|
+
if (runChild.nodeType !== 1 || runChild.namespaceURI !== NS_W9) continue;
|
|
22671
23159
|
if (!["rPr", "delText", "t", "tab", "br", "cr", "noBreakHyphen", "softHyphen"].includes(runChild.localName)) return true;
|
|
22672
23160
|
}
|
|
22673
23161
|
}
|
|
@@ -22676,7 +23164,7 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22676
23164
|
function insertedRunPropertiesAtDeletionOffset(carrier, localOffset) {
|
|
22677
23165
|
let offset = 0;
|
|
22678
23166
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22679
|
-
if (!(child?.nodeType === 1 && child.namespaceURI ===
|
|
23167
|
+
if (!(child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r")) continue;
|
|
22680
23168
|
const length = getRunTextLength(getRunContentPieces(child));
|
|
22681
23169
|
if (localOffset <= offset + length) {
|
|
22682
23170
|
return clonePropertiesWithoutRevisionHistory(getDirectWordChild4(child, "rPr"));
|
|
@@ -22706,7 +23194,7 @@ function normalizedAuthor2(author) {
|
|
|
22706
23194
|
function directWordParagraphSibling(paragraph, direction = "nextSibling") {
|
|
22707
23195
|
let cursor = paragraph?.[direction] || null;
|
|
22708
23196
|
while (cursor) {
|
|
22709
|
-
if (cursor.nodeType === 1 && cursor.namespaceURI ===
|
|
23197
|
+
if (cursor.nodeType === 1 && cursor.namespaceURI === NS_W9 && cursor.localName === "p") return cursor;
|
|
22710
23198
|
cursor = cursor[direction] || null;
|
|
22711
23199
|
}
|
|
22712
23200
|
return null;
|
|
@@ -22716,7 +23204,7 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22716
23204
|
const pPr = createWordElement(xmlDoc, "w:pPr");
|
|
22717
23205
|
if (!sourcePPr) return pPr;
|
|
22718
23206
|
for (const child of Array.from(sourcePPr.childNodes || [])) {
|
|
22719
|
-
if (child.nodeType !== 1 || child.namespaceURI !==
|
|
23207
|
+
if (child.nodeType !== 1 || child.namespaceURI !== NS_W9) continue;
|
|
22720
23208
|
if (!RESTORATION_PPR_ALLOWLIST.has(child.localName)) continue;
|
|
22721
23209
|
pPr.appendChild(xmlDoc.importNode(child, true));
|
|
22722
23210
|
}
|
|
@@ -22725,14 +23213,14 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22725
23213
|
function collectRestorationAnchorWarnings(sourceParagraph) {
|
|
22726
23214
|
const warnings = [];
|
|
22727
23215
|
const bookmarkNames = /* @__PURE__ */ new Set();
|
|
22728
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23216
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, "bookmarkStart") || [])) {
|
|
22729
23217
|
const name = wordAttribute4(node, "name") || "(unnamed)";
|
|
22730
23218
|
bookmarkNames.add(name);
|
|
22731
23219
|
}
|
|
22732
23220
|
for (const name of bookmarkNames) warnings.push(`RESTORATION_DROPPED_BOOKMARK:${name}`);
|
|
22733
23221
|
const commentIds = /* @__PURE__ */ new Set();
|
|
22734
23222
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22735
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23223
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22736
23224
|
const id = wordAttribute4(node, "id");
|
|
22737
23225
|
if (id !== "") commentIds.add(id);
|
|
22738
23226
|
}
|
|
@@ -22749,24 +23237,24 @@ function removeRestorationAnchors(paragraph) {
|
|
|
22749
23237
|
"commentReference"
|
|
22750
23238
|
];
|
|
22751
23239
|
for (const localName2 of anchorNames) {
|
|
22752
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
23240
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22753
23241
|
node.parentNode?.removeChild(node);
|
|
22754
23242
|
}
|
|
22755
23243
|
}
|
|
22756
|
-
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
22757
|
-
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI ===
|
|
23244
|
+
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "r") || [])) {
|
|
23245
|
+
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName !== "rPr");
|
|
22758
23246
|
if (!meaningful) run.parentNode?.removeChild(run);
|
|
22759
23247
|
}
|
|
22760
23248
|
}
|
|
22761
23249
|
function buildRejectedRestorationTemplate(xmlDoc, sourceParagraph, serializer) {
|
|
22762
23250
|
const rejected = rejectTrackedChangesInOoxml(serializer.serializeToString(sourceParagraph), { allAuthors: true });
|
|
22763
23251
|
const parsed = parseOoxmlSafe(rejected.oxml, "application/xml");
|
|
22764
|
-
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(
|
|
23252
|
+
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(NS_W9, "p")?.[0] || null;
|
|
22765
23253
|
if (!rejectedParagraph) return null;
|
|
22766
23254
|
const paragraph = createWordElement(xmlDoc, "w:p");
|
|
22767
23255
|
paragraph.appendChild(createSanitizedRestorationPPr(xmlDoc, sourceParagraph));
|
|
22768
23256
|
for (const child of Array.from(rejectedParagraph.childNodes || [])) {
|
|
22769
|
-
if (child.nodeType === 1 && child.namespaceURI ===
|
|
23257
|
+
if (child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22770
23258
|
paragraph.appendChild(xmlDoc.importNode(child, true));
|
|
22771
23259
|
}
|
|
22772
23260
|
removeRestorationAnchors(paragraph);
|
|
@@ -22787,7 +23275,7 @@ async function editRestorationTemplate(xmlDoc, template, modifiedText, author, s
|
|
|
22787
23275
|
);
|
|
22788
23276
|
if (result?.status === "error" || typeof result?.oxml !== "string") return null;
|
|
22789
23277
|
const extracted = extractReplacementNodes(result.oxml);
|
|
22790
|
-
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23278
|
+
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p");
|
|
22791
23279
|
return paragraph ? xmlDoc.importNode(paragraph, true) : null;
|
|
22792
23280
|
}
|
|
22793
23281
|
function allocateFreshParagraphId(xmlDoc, operationSession) {
|
|
@@ -22958,7 +23446,7 @@ async function insertIntoRejectedDeletedText(documentXml, targetText, anchor, mo
|
|
|
22958
23446
|
}
|
|
22959
23447
|
const insertionOffset = anchorStart + anchor.offset;
|
|
22960
23448
|
const carriers = Array.from(paragraph.childNodes || []).filter(
|
|
22961
|
-
(node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23449
|
+
(node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "del"
|
|
22962
23450
|
);
|
|
22963
23451
|
if (carriers.some((carrier) => hasUnsupportedDeletionSplitMarkup(paragraph, carrier))) {
|
|
22964
23452
|
return {
|
|
@@ -23615,7 +24103,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23615
24103
|
};
|
|
23616
24104
|
}
|
|
23617
24105
|
if (!allSame) {
|
|
23618
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24106
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23619
24107
|
if (hasMove) {
|
|
23620
24108
|
return {
|
|
23621
24109
|
documentXml,
|
|
@@ -23630,8 +24118,8 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23630
24118
|
}
|
|
23631
24119
|
}
|
|
23632
24120
|
} else {
|
|
23633
|
-
const hasDel = targetParagraph.getElementsByTagNameNS(
|
|
23634
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24121
|
+
const hasDel = targetParagraph.getElementsByTagNameNS(NS_W9, "del").length > 0;
|
|
24122
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23635
24123
|
if (hasDel) {
|
|
23636
24124
|
return {
|
|
23637
24125
|
documentXml,
|
|
@@ -24150,7 +24638,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24150
24638
|
const targetParagraph = resolved.paragraph;
|
|
24151
24639
|
const collectVisibleRuns = () => {
|
|
24152
24640
|
const runEntries2 = [];
|
|
24153
|
-
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(
|
|
24641
|
+
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(NS_W9, "r"));
|
|
24154
24642
|
for (const run of allRuns) {
|
|
24155
24643
|
let parent = run.parentNode;
|
|
24156
24644
|
let isDel = false;
|
|
@@ -24242,7 +24730,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24242
24730
|
const targetRuns = finalRuns.filter((span) => span.start >= matchStart && span.end <= matchEnd);
|
|
24243
24731
|
let anyChanged = false;
|
|
24244
24732
|
for (const entry of targetRuns) {
|
|
24245
|
-
const rPr = entry.run.getElementsByTagNameNS(
|
|
24733
|
+
const rPr = entry.run.getElementsByTagNameNS(NS_W9, "rPr")[0] || null;
|
|
24246
24734
|
if (checkRunPropertiesChanged(rPr, properties)) {
|
|
24247
24735
|
anyChanged = true;
|
|
24248
24736
|
break;
|
|
@@ -24253,7 +24741,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24253
24741
|
}
|
|
24254
24742
|
for (const entry of targetRuns) {
|
|
24255
24743
|
const run = entry.run;
|
|
24256
|
-
let rPr = run.getElementsByTagNameNS(
|
|
24744
|
+
let rPr = run.getElementsByTagNameNS(NS_W9, "rPr")[0];
|
|
24257
24745
|
if (!rPr) {
|
|
24258
24746
|
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
24259
24747
|
run.insertBefore(rPr, run.firstChild);
|
|
@@ -24262,7 +24750,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24262
24750
|
let insideSameAuthorIns = false;
|
|
24263
24751
|
while (insAncestor && insAncestor !== targetParagraph) {
|
|
24264
24752
|
if (insAncestor.nodeType === 1 && (insAncestor.localName === "ins" || insAncestor.nodeName === "w:ins")) {
|
|
24265
|
-
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(
|
|
24753
|
+
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(NS_W9, "author");
|
|
24266
24754
|
if (insAuthor === (author || getDefaultAuthor())) {
|
|
24267
24755
|
insideSameAuthorIns = true;
|
|
24268
24756
|
}
|
|
@@ -24310,7 +24798,7 @@ async function applyParagraphFormatToParagraphByExactText(documentXml, targetTex
|
|
|
24310
24798
|
return { documentXml, hasChanges: false, status: "error", error: resolved?.error || { code: "TARGET_NOT_FOUND", message: "Target paragraph not found." } };
|
|
24311
24799
|
}
|
|
24312
24800
|
const targetParagraph = resolved.paragraph;
|
|
24313
|
-
let pPr = targetParagraph.getElementsByTagNameNS(
|
|
24801
|
+
let pPr = targetParagraph.getElementsByTagNameNS(NS_W9, "pPr")[0];
|
|
24314
24802
|
if (!pPr) {
|
|
24315
24803
|
pPr = createWordElement(xmlDoc, "w:pPr");
|
|
24316
24804
|
targetParagraph.insertBefore(pPr, targetParagraph.firstChild);
|
|
@@ -24358,7 +24846,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24358
24846
|
}
|
|
24359
24847
|
let operation = validation.operation || normalizeDocumentOperation(op);
|
|
24360
24848
|
const authorUsed = resolveDocumentOperationAuthor(operation, author, getDefaultAuthor());
|
|
24361
|
-
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
24849
|
+
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
24362
24850
|
return {
|
|
24363
24851
|
documentXml,
|
|
24364
24852
|
hasChanges: false,
|
|
@@ -24525,6 +25013,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24525
25013
|
result = applyCommentReplyToParts({
|
|
24526
25014
|
commentsXml: existingCommentsXml,
|
|
24527
25015
|
commentsExtendedXml: existingExtendedXml,
|
|
25016
|
+
documentXml,
|
|
24528
25017
|
parentCommentId: operation.parentCommentId,
|
|
24529
25018
|
commentId,
|
|
24530
25019
|
commentContent: operation.commentContent,
|
|
@@ -24532,8 +25021,30 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24532
25021
|
date: operation.date || (/* @__PURE__ */ new Date()).toISOString()
|
|
24533
25022
|
});
|
|
24534
25023
|
result.documentXml = documentXml;
|
|
24535
|
-
if (result.
|
|
25024
|
+
if (result.status !== "error" && result.hasChanges) {
|
|
25025
|
+
if (!session.document || !anchorReplyInDocument(session.document, result.threadCommentIds, commentId)) {
|
|
25026
|
+
result = { documentXml, hasChanges: false, status: "error", error: { code: "PARENT_ANCHOR_NOT_FOUND", message: `Comment '${operation.parentCommentId}' has no anchor in the document body, so a reply cannot be placed.` } };
|
|
25027
|
+
} else if (session.receiptCollector) {
|
|
25028
|
+
session.receiptCollector.recordComment(commentId);
|
|
25029
|
+
}
|
|
25030
|
+
}
|
|
24536
25031
|
}
|
|
25032
|
+
} else if (operation.operationKind === "comment_resolve") {
|
|
25033
|
+
const resolution = applyThreadResolutionToParts({
|
|
25034
|
+
commentsXml: session.commentsXml || runtimeContext?.commentsXml || options.commentsXml,
|
|
25035
|
+
commentsExtendedXml: session.commentsExtendedXml || runtimeContext?.commentsExtendedXml || options.commentsExtendedXml,
|
|
25036
|
+
commentId: operation.commentId,
|
|
25037
|
+
resolved: operation.resolved !== false
|
|
25038
|
+
});
|
|
25039
|
+
result = resolution.status === "error" ? { documentXml, hasChanges: false, status: "error", error: resolution.error } : {
|
|
25040
|
+
documentXml,
|
|
25041
|
+
hasChanges: resolution.hasChanges,
|
|
25042
|
+
...resolution.commentsXml ? { commentsXml: resolution.commentsXml, commentsXmlMode: "replace" } : {},
|
|
25043
|
+
...resolution.commentsExtendedXml ? { commentsExtendedXml: resolution.commentsExtendedXml, commentsExtendedXmlMode: "replace" } : {},
|
|
25044
|
+
resolved: resolution.resolved,
|
|
25045
|
+
threadRootId: resolution.threadRootId,
|
|
25046
|
+
commentIds: resolution.commentIds
|
|
25047
|
+
};
|
|
24537
25048
|
} else if (operation.operationKind === "highlight") {
|
|
24538
25049
|
result = await applyHighlightToParagraphByExactText(
|
|
24539
25050
|
documentXml,
|
|
@@ -24674,7 +25185,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24674
25185
|
...resolutionCapture
|
|
24675
25186
|
};
|
|
24676
25187
|
}
|
|
24677
|
-
session.markMutationCommitted(
|
|
25188
|
+
session.markMutationCommitted(true);
|
|
24678
25189
|
if (operation._compiledSourceId && session.sourceTargetRegistry) {
|
|
24679
25190
|
session.sourceTargetRegistry.commitMutation(
|
|
24680
25191
|
operation._compiledSourceId,
|
|
@@ -24787,9 +25298,9 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24787
25298
|
}
|
|
24788
25299
|
|
|
24789
25300
|
// services/batch-operation-orchestrator.js
|
|
24790
|
-
var
|
|
25301
|
+
var NS_W10 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
24791
25302
|
function operationTargetPriority(op) {
|
|
24792
|
-
return op?.type === "comment" || op?.type === "comment_reply" ? 0 : 1;
|
|
25303
|
+
return op?.type === "comment" || op?.type === "comment_reply" || op?.type === "comment_resolve" ? 0 : 1;
|
|
24793
25304
|
}
|
|
24794
25305
|
function buildOperationDependencyPlan(operations = []) {
|
|
24795
25306
|
const list = Array.isArray(operations) ? operations : [];
|
|
@@ -24864,7 +25375,7 @@ function buildOperationDependencyPlan(operations = []) {
|
|
|
24864
25375
|
inDegrees[i2]++;
|
|
24865
25376
|
}
|
|
24866
25377
|
const kind = normalizeDocumentOperation(op).operationKind;
|
|
24867
|
-
if (kind !== "comment" && kind !== "comment_reply") {
|
|
25378
|
+
if (kind !== "comment" && kind !== "comment_reply" && kind !== "comment_resolve") {
|
|
24868
25379
|
if (!mutatingCaptureConsumers.has(ref)) mutatingCaptureConsumers.set(ref, []);
|
|
24869
25380
|
mutatingCaptureConsumers.get(ref).push({
|
|
24870
25381
|
index: i2,
|
|
@@ -24932,9 +25443,9 @@ function mergeCommentsXml(existingXml, incomingXml) {
|
|
|
24932
25443
|
const serializer = createSerializer();
|
|
24933
25444
|
const existingRoot = existingDoc.documentElement;
|
|
24934
25445
|
const existingIds = new Set(
|
|
24935
|
-
Array.from(existingDoc.getElementsByTagNameNS(
|
|
25446
|
+
Array.from(existingDoc.getElementsByTagNameNS(NS_W10, "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
24936
25447
|
);
|
|
24937
|
-
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(
|
|
25448
|
+
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(NS_W10, "comment"))) {
|
|
24938
25449
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
24939
25450
|
if (id && existingIds.has(id)) continue;
|
|
24940
25451
|
existingRoot.appendChild(existingDoc.importNode(comment, true));
|
|
@@ -25095,7 +25606,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25095
25606
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
25096
25607
|
}
|
|
25097
25608
|
session.runtimeContext = context;
|
|
25098
|
-
if (sourceOperations.some((op) => op?.type === "comment_reply")) {
|
|
25609
|
+
if (sourceOperations.some((op) => op?.type === "comment_reply" || op?.type === "comment_resolve")) {
|
|
25099
25610
|
session.commentsXml = context.commentsXml || options.commentsXml || null;
|
|
25100
25611
|
session.commentsExtendedXml = context.commentsExtendedXml || options.commentsExtendedXml || null;
|
|
25101
25612
|
session.commentsXmlMode = "replace";
|
|
@@ -25168,6 +25679,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25168
25679
|
...result.resolvedTarget ? { resolvedTarget: result.resolvedTarget } : {},
|
|
25169
25680
|
...result.resolvedAnchor ? { resolvedAnchor: result.resolvedAnchor } : {},
|
|
25170
25681
|
...result.change ? { change: result.change } : {},
|
|
25682
|
+
...result.operationType === "comment_resolve" && !isError ? { resolved: result.resolved, threadRootId: result.threadRootId, commentIds: result.commentIds } : {},
|
|
25171
25683
|
...Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {},
|
|
25172
25684
|
...result.error ? { error: result.error } : {},
|
|
25173
25685
|
...result.receipt ? { receipt: result.receipt } : {}
|
|
@@ -25444,6 +25956,26 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
25444
25956
|
}
|
|
25445
25957
|
commentsRequired = commentsRequired || operation.operationKind === "comment" || operation.operationKind === "comment_reply";
|
|
25446
25958
|
numberingRequired = numberingRequired || operationNeedsNumbering(operation);
|
|
25959
|
+
if (operation.operationKind === "comment_resolve") {
|
|
25960
|
+
const commentId = String(operation.commentId);
|
|
25961
|
+
results.push(options._existingCommentDetails?.[commentId] ? {
|
|
25962
|
+
index: index + 1,
|
|
25963
|
+
type: sourceOperation.type,
|
|
25964
|
+
operationType: operation.operationKind,
|
|
25965
|
+
status: "ready",
|
|
25966
|
+
authorUsed,
|
|
25967
|
+
resolvedBy: "comment_id",
|
|
25968
|
+
commentId
|
|
25969
|
+
} : {
|
|
25970
|
+
index: index + 1,
|
|
25971
|
+
type: sourceOperation.type,
|
|
25972
|
+
operationType: operation.operationKind,
|
|
25973
|
+
status: "error",
|
|
25974
|
+
authorUsed,
|
|
25975
|
+
error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` }
|
|
25976
|
+
});
|
|
25977
|
+
continue;
|
|
25978
|
+
}
|
|
25447
25979
|
if (operation.operationKind === "comment_reply") {
|
|
25448
25980
|
const parentId = String(operation.parentCommentId);
|
|
25449
25981
|
const parent = options._existingCommentDetails?.[parentId];
|
|
@@ -26595,6 +27127,15 @@ var text = (entries, path) => {
|
|
|
26595
27127
|
if (typeof entry === "string") return entry;
|
|
26596
27128
|
return textDecoder2.decode(entry);
|
|
26597
27129
|
};
|
|
27130
|
+
function reconcileCommentSiblings(working) {
|
|
27131
|
+
const updated = reconcileCommentSiblingParts({
|
|
27132
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
27133
|
+
commentsIdsXml: text(working, "word/commentsIds.xml"),
|
|
27134
|
+
commentsExtensibleXml: text(working, "word/commentsExtensible.xml")
|
|
27135
|
+
});
|
|
27136
|
+
if (updated.commentsIdsXml) working.set("word/commentsIds.xml", textEncoder4.encode(updated.commentsIdsXml));
|
|
27137
|
+
if (updated.commentsExtensibleXml) working.set("word/commentsExtensible.xml", textEncoder4.encode(updated.commentsExtensibleXml));
|
|
27138
|
+
}
|
|
26598
27139
|
var cloneEntries = (entries) => new Map([...entries].map(([name, data]) => [name, new Uint8Array(data)]));
|
|
26599
27140
|
function toBufferCompatible(uint8Arr) {
|
|
26600
27141
|
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
@@ -26662,6 +27203,7 @@ function computePackageRevisionToken(input) {
|
|
|
26662
27203
|
entries
|
|
26663
27204
|
});
|
|
26664
27205
|
}
|
|
27206
|
+
var COMMENT_OPERATION_TYPES = /* @__PURE__ */ new Set(["comment", "comment_reply", "comment_resolve"]);
|
|
26665
27207
|
function nextCommentId(entries) {
|
|
26666
27208
|
const ids = `${text(entries, "word/document.xml") || ""} ${text(entries, "word/comments.xml") || ""}`.match(/(?:w:)?id=["'](\d+)["']/g) || [];
|
|
26667
27209
|
let next = ids.reduce((max2, token) => Math.max(max2, Number(token.match(/\d+/)?.[0] || 0)), 0) + 1;
|
|
@@ -26680,7 +27222,7 @@ function existingCommentDetails(entries) {
|
|
|
26680
27222
|
id,
|
|
26681
27223
|
author: comment.getAttribute("w:author") || comment.getAttribute("author") || "",
|
|
26682
27224
|
text: String(comment.textContent || "").trim(),
|
|
26683
|
-
paraId:
|
|
27225
|
+
paraId: commentParaId(comment) || null
|
|
26684
27226
|
};
|
|
26685
27227
|
}
|
|
26686
27228
|
return details;
|
|
@@ -26711,13 +27253,34 @@ var DocxDocument = class {
|
|
|
26711
27253
|
}
|
|
26712
27254
|
inspect(options = {}) {
|
|
26713
27255
|
const digestFn = options.digestFn || ((bytes) => sha256(bytes));
|
|
26714
|
-
|
|
27256
|
+
const inspection = inspectDocumentParts({
|
|
26715
27257
|
documentXml: text(this.entries, "word/document.xml"),
|
|
26716
27258
|
commentsXml: text(this.entries, "word/comments.xml"),
|
|
26717
27259
|
commentsExtendedXml: text(this.entries, "word/commentsExtended.xml"),
|
|
26718
27260
|
numberingXml: text(this.entries, "word/numbering.xml"),
|
|
26719
27261
|
stylesXml: text(this.entries, "word/styles.xml")
|
|
26720
27262
|
}, { ...options, digestFn });
|
|
27263
|
+
if (inspection.status === "error") return inspection;
|
|
27264
|
+
return { ...inspection, headersFooters: this.inspectHeadersFooters() };
|
|
27265
|
+
}
|
|
27266
|
+
/**
|
|
27267
|
+
* Header and footer parts the document uses, with their paragraph text, so operations can target them with
|
|
27268
|
+
* `part`. `hasFields` marks parts containing PAGE/NUMPAGES-style fields (edit around them, not inside).
|
|
27269
|
+
*/
|
|
27270
|
+
inspectHeadersFooters() {
|
|
27271
|
+
return discoverHeaderFooterParts({
|
|
27272
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27273
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27274
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27275
|
+
}).map((part) => {
|
|
27276
|
+
const xml = text(this.entries, part.path) || "";
|
|
27277
|
+
const partInspection = xml ? inspectDocumentParts({ documentXml: xml }) : { paragraphs: [] };
|
|
27278
|
+
return {
|
|
27279
|
+
...part,
|
|
27280
|
+
hasFields: /<w:fldChar\b|<w:fldSimple\b/.test(xml),
|
|
27281
|
+
paragraphs: (partInspection.paragraphs || []).map((paragraph) => ({ index: paragraph.index, text: paragraph.text }))
|
|
27282
|
+
};
|
|
27283
|
+
});
|
|
26721
27284
|
}
|
|
26722
27285
|
getRevisionToken() {
|
|
26723
27286
|
return computePackageRevisionToken(this.entries);
|
|
@@ -26726,12 +27289,51 @@ var DocxDocument = class {
|
|
|
26726
27289
|
return this.getRevisionToken();
|
|
26727
27290
|
}
|
|
26728
27291
|
preflight(operations, author = getDefaultAuthor(), options = {}) {
|
|
26729
|
-
|
|
27292
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27293
|
+
const runBody = (ops) => preflightOperations(
|
|
26730
27294
|
text(this.entries, "word/document.xml"),
|
|
26731
|
-
|
|
27295
|
+
ops,
|
|
26732
27296
|
author || getDefaultAuthor(),
|
|
26733
27297
|
{ ...options, _existingCommentDetails: existingCommentDetails(this.entries) }
|
|
26734
27298
|
);
|
|
27299
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) return runBody(operations);
|
|
27300
|
+
const parts = this.inspectHeadersFooters();
|
|
27301
|
+
const bodyOps = [];
|
|
27302
|
+
const results = [];
|
|
27303
|
+
const conflicts = [];
|
|
27304
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27305
|
+
let valid = true;
|
|
27306
|
+
list.forEach((operation, i2) => {
|
|
27307
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27308
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27309
|
+
return;
|
|
27310
|
+
}
|
|
27311
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27312
|
+
const refusal = COMMENT_OPERATION_TYPES.has(operation.type) ? { code: "COMMENT_IN_HEADER_FOOTER", message: "Word does not support comments in headers or footers." } : resolved.error;
|
|
27313
|
+
if (refusal) {
|
|
27314
|
+
valid = false;
|
|
27315
|
+
results.push({ index: i2 + 1, type: operation.type || "redline", status: "error", error: normalizeErrorWithRecovery({ ...refusal, operationIndex: i2 + 1 }) });
|
|
27316
|
+
return;
|
|
27317
|
+
}
|
|
27318
|
+
const { part: _selector, ...rest } = operation;
|
|
27319
|
+
const partResult = preflightOperations(text(this.entries, resolved.part.path), [rest], author || getDefaultAuthor(), { ...options, _existingCommentDetails: {} });
|
|
27320
|
+
valid = valid && partResult.valid;
|
|
27321
|
+
for (const result of partResult.results || []) results.push({ ...result, index: i2 + 1, part: resolved.part.path });
|
|
27322
|
+
conflicts.push(...partResult.conflicts || []);
|
|
27323
|
+
for (const name of partResult.authorsUsed || []) authorsUsed.add(name);
|
|
27324
|
+
});
|
|
27325
|
+
let requiredArtifacts = { comments: false, numbering: false };
|
|
27326
|
+
if (bodyOps.length) {
|
|
27327
|
+
const body = runBody(bodyOps.map((item) => item.operation));
|
|
27328
|
+
valid = valid && body.valid;
|
|
27329
|
+
for (const result of body.results || []) results.push({ ...result, index: bodyOps[result.index - 1].origIndex + 1 });
|
|
27330
|
+
conflicts.push(...body.conflicts || []);
|
|
27331
|
+
for (const name of body.authorsUsed || []) authorsUsed.add(name);
|
|
27332
|
+
requiredArtifacts = body.requiredArtifacts || requiredArtifacts;
|
|
27333
|
+
}
|
|
27334
|
+
results.sort((a, b) => a.index - b.index);
|
|
27335
|
+
const ok = valid && conflicts.length === 0;
|
|
27336
|
+
return { valid: ok, status: ok ? "ok" : "error", results, conflicts, authorsUsed: [...authorsUsed], requiredArtifacts };
|
|
26735
27337
|
}
|
|
26736
27338
|
toUint8Array() {
|
|
26737
27339
|
return zipDocx(this.entries);
|
|
@@ -26739,7 +27341,240 @@ var DocxDocument = class {
|
|
|
26739
27341
|
toBuffer() {
|
|
26740
27342
|
return toBufferCompatible(this.toUint8Array());
|
|
26741
27343
|
}
|
|
27344
|
+
/**
|
|
27345
|
+
* Applies operations to the document. Operations without `part` target the body (word/document.xml).
|
|
27346
|
+
* An operation with `part` targets one header or footer part:
|
|
27347
|
+
* `{ part: { kind: 'footer', type: 'default', section: 0 } | 'word/footer2.xml', target: { exactText }, ... }`.
|
|
27348
|
+
* Discover parts with `inspect().headersFooters`.
|
|
27349
|
+
*/
|
|
26742
27350
|
async applyOperations(operations, options = {}) {
|
|
27351
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27352
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) {
|
|
27353
|
+
return this.applyBodyOperations(operations, options);
|
|
27354
|
+
}
|
|
27355
|
+
return this.applyOperationsWithParts(list, options);
|
|
27356
|
+
}
|
|
27357
|
+
async applyOperationsWithParts(operations, options = {}) {
|
|
27358
|
+
const author = options.author || getDefaultAuthor();
|
|
27359
|
+
const atomic = options.atomic === true;
|
|
27360
|
+
const snapshotEntries = this.entries;
|
|
27361
|
+
const snapshotBytes = this.originalBytes;
|
|
27362
|
+
const total = operations.length;
|
|
27363
|
+
const restore = () => {
|
|
27364
|
+
this.entries = snapshotEntries;
|
|
27365
|
+
this.originalBytes = snapshotBytes;
|
|
27366
|
+
};
|
|
27367
|
+
const original = () => ({
|
|
27368
|
+
uint8Array: snapshotBytes,
|
|
27369
|
+
toUint8Array: () => new Uint8Array(snapshotBytes),
|
|
27370
|
+
buffer: toBufferCompatible(snapshotBytes),
|
|
27371
|
+
toBuffer: () => toBufferCompatible(snapshotBytes)
|
|
27372
|
+
});
|
|
27373
|
+
const refuse = (error2, results2 = [], receipts2 = [], extra = {}) => ({
|
|
27374
|
+
status: "error",
|
|
27375
|
+
hasChanges: false,
|
|
27376
|
+
written: false,
|
|
27377
|
+
rolledBack: true,
|
|
27378
|
+
results: results2,
|
|
27379
|
+
receipts: receipts2,
|
|
27380
|
+
executionOrder: [],
|
|
27381
|
+
authorsUsed: [],
|
|
27382
|
+
artifactsChanged: [],
|
|
27383
|
+
error: normalizeErrorWithRecovery(error2),
|
|
27384
|
+
retryPlan: createRetryPlan({ atomic: true, rolledBack: true, results: results2, receipts: receipts2, operationCount: total }),
|
|
27385
|
+
validation: { originalIssues: [], generatedIssues: [] },
|
|
27386
|
+
...extra,
|
|
27387
|
+
...original()
|
|
27388
|
+
});
|
|
27389
|
+
if (options.expectedRevision) {
|
|
27390
|
+
const precondition = await this.applyBodyOperations([], { expectedRevision: options.expectedRevision });
|
|
27391
|
+
if (precondition.status === "error") return precondition;
|
|
27392
|
+
}
|
|
27393
|
+
const parts = discoverHeaderFooterParts({
|
|
27394
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27395
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27396
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27397
|
+
});
|
|
27398
|
+
const bodyOps = [];
|
|
27399
|
+
const partOps = [];
|
|
27400
|
+
for (let i2 = 0; i2 < operations.length; i2++) {
|
|
27401
|
+
const operation = operations[i2];
|
|
27402
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27403
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27404
|
+
continue;
|
|
27405
|
+
}
|
|
27406
|
+
if (COMMENT_OPERATION_TYPES.has(operation.type)) {
|
|
27407
|
+
return refuse({ code: "COMMENT_IN_HEADER_FOOTER", message: `Operation ${i2 + 1}: Word does not support comments in headers or footers.`, operationIndex: i2 + 1 });
|
|
27408
|
+
}
|
|
27409
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27410
|
+
if (resolved.error) return refuse({ ...resolved.error, message: `Operation ${i2 + 1}: ${resolved.error.message}`, operationIndex: i2 + 1 });
|
|
27411
|
+
const { part: _selector, ...rest } = operation;
|
|
27412
|
+
partOps.push({ operation: rest, origIndex: i2, part: resolved.part });
|
|
27413
|
+
}
|
|
27414
|
+
const results = [];
|
|
27415
|
+
const receipts = [];
|
|
27416
|
+
const executionOrder = [];
|
|
27417
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27418
|
+
const originalIssues = [];
|
|
27419
|
+
let anyChange = false;
|
|
27420
|
+
let anyFailure = false;
|
|
27421
|
+
let bodyResult = null;
|
|
27422
|
+
if (bodyOps.length) {
|
|
27423
|
+
bodyResult = await this.applyBodyOperations(bodyOps.map((item) => item.operation), { ...options, expectedRevision: void 0 });
|
|
27424
|
+
const toOriginal = (index) => bodyOps[index - 1]?.origIndex + 1;
|
|
27425
|
+
for (const result of bodyResult.results || []) {
|
|
27426
|
+
results.push({
|
|
27427
|
+
...result,
|
|
27428
|
+
index: toOriginal(result.index),
|
|
27429
|
+
...result.receipt ? { receipt: { ...result.receipt, operationIndex: toOriginal(result.receipt.operationIndex) } } : {}
|
|
27430
|
+
});
|
|
27431
|
+
}
|
|
27432
|
+
for (const receipt of bodyResult.receipts || []) receipts.push({ ...receipt, operationIndex: toOriginal(receipt.operationIndex) });
|
|
27433
|
+
for (const index of bodyResult.executionOrder || []) executionOrder.push(toOriginal(index));
|
|
27434
|
+
for (const name of bodyResult.authorsUsed || []) authorsUsed.add(name);
|
|
27435
|
+
originalIssues.push(...bodyResult.validation?.originalIssues || []);
|
|
27436
|
+
if (bodyResult.status === "error" || bodyResult.rolledBack) {
|
|
27437
|
+
anyFailure = true;
|
|
27438
|
+
if (atomic) {
|
|
27439
|
+
restore();
|
|
27440
|
+
return { ...bodyResult, results, receipts, executionOrder, written: false, artifactsChanged: [], rolledBack: true, ...original() };
|
|
27441
|
+
}
|
|
27442
|
+
}
|
|
27443
|
+
anyChange = bodyResult.hasChanges === true;
|
|
27444
|
+
}
|
|
27445
|
+
const working = cloneEntries(this.entries);
|
|
27446
|
+
const zip = new MemoryZip(working);
|
|
27447
|
+
const changedParts = /* @__PURE__ */ new Set();
|
|
27448
|
+
const { expectedRevision: _ignored, ...runnerOptions } = options;
|
|
27449
|
+
const failPart = (item, error2) => {
|
|
27450
|
+
anyFailure = true;
|
|
27451
|
+
const failure = normalizeErrorWithRecovery({ ...error2, operationIndex: item.origIndex + 1 });
|
|
27452
|
+
results.push({ index: item.origIndex + 1, type: item.operation.type || "redline", status: "error", authorUsed: author, part: item.part.path, error: failure });
|
|
27453
|
+
};
|
|
27454
|
+
for (const item of partOps) {
|
|
27455
|
+
const path = item.part.path;
|
|
27456
|
+
const xml = text(working, path);
|
|
27457
|
+
if (!xml) {
|
|
27458
|
+
failPart(item, { code: "PART_NOT_FOUND", message: `Part '${path}' is referenced but missing from the package.` });
|
|
27459
|
+
if (atomic) break;
|
|
27460
|
+
continue;
|
|
27461
|
+
}
|
|
27462
|
+
const numberingText = text(working, "word/numbering.xml");
|
|
27463
|
+
const outcome = await applyOperationsToDocumentXml(xml, [item.operation], author, { numberingIdState: createDynamicNumberingIdState(numberingText || void 0) }, {
|
|
27464
|
+
...runnerOptions,
|
|
27465
|
+
atomic: true,
|
|
27466
|
+
strictTargets: options.strictTargets !== false,
|
|
27467
|
+
_existingCommentDetails: {}
|
|
27468
|
+
});
|
|
27469
|
+
const single = outcome.results?.[0];
|
|
27470
|
+
if (outcome.rolledBack || outcome.status === "error") {
|
|
27471
|
+
failPart(item, single?.error || outcome.error || { code: "OPERATION_FAILED", message: "Operation failed." });
|
|
27472
|
+
if (atomic) break;
|
|
27473
|
+
continue;
|
|
27474
|
+
}
|
|
27475
|
+
if (!outcome.hasChanges) {
|
|
27476
|
+
results.push({ ...single, index: item.origIndex + 1, part: path });
|
|
27477
|
+
continue;
|
|
27478
|
+
}
|
|
27479
|
+
if (countRevisionsInsideFields(outcome.documentXml) > countRevisionsInsideFields(xml)) {
|
|
27480
|
+
failPart(item, { code: "FIELD_EDIT_REFUSED", message: `The edit would change a field (for example PAGE or NUMPAGES) in ${path}. Word recomputes fields; edit the text around them instead.` });
|
|
27481
|
+
if (atomic) break;
|
|
27482
|
+
continue;
|
|
27483
|
+
}
|
|
27484
|
+
const taken = /* @__PURE__ */ new Set();
|
|
27485
|
+
for (const [name, data] of working) {
|
|
27486
|
+
if (name === path || !(name === "word/document.xml" || isHeaderFooterPath(name))) continue;
|
|
27487
|
+
for (const id of collectRevisionIds(textDecoder2.decode(data))) taken.add(id);
|
|
27488
|
+
}
|
|
27489
|
+
const renumbered = renumberCollidingRevisionIds(outcome.documentXml, taken);
|
|
27490
|
+
working.set(path, textEncoder4.encode(renumbered.xml));
|
|
27491
|
+
const describeReceipt = (receipt) => receipt?.revisionItems ? { ...receipt, revisionItems: receipt.revisionItems.map((revision) => ({ ...revision, id: renumbered.idMap.get(String(revision.id)) ?? revision.id, partName: path })) } : receipt;
|
|
27492
|
+
if (outcome.numberingXmlParts?.length) {
|
|
27493
|
+
await ensureNumberingArtifactsInZip(zip, outcome.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
27494
|
+
}
|
|
27495
|
+
changedParts.add(path);
|
|
27496
|
+
anyChange = true;
|
|
27497
|
+
results.push({
|
|
27498
|
+
...single,
|
|
27499
|
+
index: item.origIndex + 1,
|
|
27500
|
+
part: path,
|
|
27501
|
+
partSections: item.part.appliesToSections,
|
|
27502
|
+
...single?.receipt ? { receipt: { ...describeReceipt(single.receipt), operationIndex: item.origIndex + 1 } } : {}
|
|
27503
|
+
});
|
|
27504
|
+
if (outcome.receipts?.[0]) receipts.push({ ...describeReceipt(outcome.receipts[0]), operationIndex: item.origIndex + 1 });
|
|
27505
|
+
executionOrder.push(item.origIndex + 1);
|
|
27506
|
+
for (const name of outcome.authorsUsed || []) authorsUsed.add(name);
|
|
27507
|
+
}
|
|
27508
|
+
const generatedIssues = [];
|
|
27509
|
+
if (!(atomic && anyFailure)) {
|
|
27510
|
+
for (const path of changedParts) {
|
|
27511
|
+
const before = validateRedlineOoxml(text(this.entries, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27512
|
+
const after = validateRedlineOoxml(text(working, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27513
|
+
generatedIssues.push(...validationErrors(subtractValidationIssueMultiset(after, before)));
|
|
27514
|
+
}
|
|
27515
|
+
if (options.validate !== false && changedParts.size) {
|
|
27516
|
+
try {
|
|
27517
|
+
await validateDocxPackage(zip);
|
|
27518
|
+
} catch (error2) {
|
|
27519
|
+
generatedIssues.push({ source: "package", code: "PACKAGE_VALIDATION", severity: "error", message: error2.message });
|
|
27520
|
+
}
|
|
27521
|
+
}
|
|
27522
|
+
}
|
|
27523
|
+
if (atomic && anyFailure || generatedIssues.length) {
|
|
27524
|
+
restore();
|
|
27525
|
+
const error2 = generatedIssues.length ? { code: "PACKAGE_OPERATION_FAILED", message: `Applied operations introduced invalid markup (${[...new Set(generatedIssues.map((issue) => issue.code))].join(", ")}).`, issues: generatedIssues } : { code: "BATCH_OPERATION_FAILED", message: "Atomic batch rolled back because one or more operations failed." };
|
|
27526
|
+
const rolledBackResults = results.map((result) => result.receipt ? { ...result, receipt: { ...result.receipt, committed: false, finalDisposition: "rolled_back" } } : result).sort((a, b) => a.index - b.index);
|
|
27527
|
+
const rolledBackReceipts = receipts.map((receipt) => ({ ...receipt, committed: false, finalDisposition: "rolled_back" }));
|
|
27528
|
+
return refuse(error2, rolledBackResults, rolledBackReceipts, { validation: { originalIssues, generatedIssues }, issues: generatedIssues });
|
|
27529
|
+
}
|
|
27530
|
+
results.sort((a, b) => a.index - b.index);
|
|
27531
|
+
receipts.sort((a, b) => a.operationIndex - b.operationIndex);
|
|
27532
|
+
const status = !anyFailure ? "ok" : anyChange ? "partial" : "error";
|
|
27533
|
+
const failureError = anyFailure ? { error: normalizeErrorWithRecovery({ code: "BATCH_OPERATION_FAILED", message: "One or more operations failed; the others were applied." }) } : {};
|
|
27534
|
+
if (!changedParts.size) {
|
|
27535
|
+
const buf = toBufferCompatible(this.originalBytes);
|
|
27536
|
+
return {
|
|
27537
|
+
...bodyResult || {},
|
|
27538
|
+
results,
|
|
27539
|
+
receipts,
|
|
27540
|
+
executionOrder,
|
|
27541
|
+
authorsUsed: [...authorsUsed],
|
|
27542
|
+
status,
|
|
27543
|
+
hasChanges: anyChange,
|
|
27544
|
+
written: bodyResult?.written === true,
|
|
27545
|
+
artifactsChanged: bodyResult?.artifactsChanged || [],
|
|
27546
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27547
|
+
...failureError,
|
|
27548
|
+
uint8Array: this.originalBytes,
|
|
27549
|
+
toUint8Array: () => new Uint8Array(this.originalBytes),
|
|
27550
|
+
buffer: buf,
|
|
27551
|
+
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
27552
|
+
};
|
|
27553
|
+
}
|
|
27554
|
+
this.entries = working;
|
|
27555
|
+
const outputBytes = this.toUint8Array();
|
|
27556
|
+
this.originalBytes = outputBytes;
|
|
27557
|
+
const artifactsChanged = [...working].filter(([name, data]) => !snapshotEntries.has(name) || !areByteArraysEqual(data, snapshotEntries.get(name))).map(([name]) => name);
|
|
27558
|
+
return {
|
|
27559
|
+
...bodyResult || {},
|
|
27560
|
+
status,
|
|
27561
|
+
hasChanges: true,
|
|
27562
|
+
written: true,
|
|
27563
|
+
results,
|
|
27564
|
+
receipts,
|
|
27565
|
+
executionOrder,
|
|
27566
|
+
authorsUsed: [...authorsUsed],
|
|
27567
|
+
artifactsChanged,
|
|
27568
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27569
|
+
...failureError,
|
|
27570
|
+
uint8Array: outputBytes,
|
|
27571
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27572
|
+
buffer: toBufferCompatible(outputBytes),
|
|
27573
|
+
inspection: this.inspect(),
|
|
27574
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
27575
|
+
};
|
|
27576
|
+
}
|
|
27577
|
+
async applyBodyOperations(operations, options = {}) {
|
|
26743
27578
|
const failedApply = (error2) => {
|
|
26744
27579
|
const results = [];
|
|
26745
27580
|
const receipts = [];
|
|
@@ -26850,18 +27685,21 @@ var DocxDocument = class {
|
|
|
26850
27685
|
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
26851
27686
|
};
|
|
26852
27687
|
}
|
|
26853
|
-
|
|
27688
|
+
const bodyChanged = (result.results || []).some((item) => item?.status !== "error" && item?.operationType !== "comment_resolve");
|
|
27689
|
+
if (bodyChanged) working.set("word/document.xml", textEncoder4.encode(result.documentXml));
|
|
26854
27690
|
await ensureNumberingArtifactsInZip(zip, result.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
26855
27691
|
const existingCommentsXml = text(working, "word/comments.xml");
|
|
26856
27692
|
const commentsXmlForPackaging = result.commentsXml || existingCommentsXml;
|
|
26857
27693
|
await ensureCommentsArtifactsInZip(zip, commentsXmlForPackaging, {
|
|
26858
27694
|
replaceExisting: result.commentsXmlMode === "replace" || !result.commentsXml && !!existingCommentsXml
|
|
26859
27695
|
});
|
|
26860
|
-
|
|
26861
|
-
|
|
26862
|
-
|
|
26863
|
-
|
|
26864
|
-
}
|
|
27696
|
+
if (result.commentsExtendedXml) {
|
|
27697
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, {
|
|
27698
|
+
replaceExisting: result.commentsExtendedXmlMode === "replace"
|
|
27699
|
+
});
|
|
27700
|
+
}
|
|
27701
|
+
await repairKnownContentTypes(zip);
|
|
27702
|
+
reconcileCommentSiblings(working);
|
|
26865
27703
|
if (options.validate !== false) {
|
|
26866
27704
|
const generated = validateRedlineOoxml(result.documentXml);
|
|
26867
27705
|
const outputIssues = generated.issues.map((issue) => ({ source: "word/document.xml", ...issue }));
|
|
@@ -26930,13 +27768,25 @@ var DocxDocument = class {
|
|
|
26930
27768
|
const transform = action === "accept" ? acceptTrackedChangesInOoxml : action === "reject" ? rejectTrackedChangesInOoxml : null;
|
|
26931
27769
|
if (!transform) return packageFailure(this.originalBytes, "INVALID_ACTION", `Unknown revision action: ${action}`);
|
|
26932
27770
|
const sourceBytes = this.originalBytes;
|
|
27771
|
+
const originalEntries = this.entries;
|
|
26933
27772
|
const working = cloneEntries(this.entries);
|
|
26934
27773
|
const zip = new MemoryZip(working);
|
|
26935
27774
|
const result = transform(text(working, "word/document.xml"), { author: options.author, allAuthors: options.allAuthors === true });
|
|
26936
27775
|
if (result.status === "error" || result.error) {
|
|
26937
27776
|
return packageFailure(sourceBytes, result.error?.code || "REVISION_OPERATION_FAILED", result.error?.message || "Revision operation failed.");
|
|
26938
27777
|
}
|
|
26939
|
-
|
|
27778
|
+
const partUpdates = /* @__PURE__ */ new Map();
|
|
27779
|
+
for (const part of this.inspectHeadersFooters()) {
|
|
27780
|
+
const partXml = text(working, part.path);
|
|
27781
|
+
if (!partXml) continue;
|
|
27782
|
+
const partResult = transform(partXml, { author: options.author, allAuthors: options.allAuthors === true });
|
|
27783
|
+
if (partResult.status === "error" || partResult.error) {
|
|
27784
|
+
return packageFailure(sourceBytes, partResult.error?.code || "REVISION_OPERATION_FAILED", `${part.path}: ${partResult.error?.message || "Revision operation failed."}`);
|
|
27785
|
+
}
|
|
27786
|
+
if (partResult.hasChanges) partUpdates.set(part.path, partResult.oxml);
|
|
27787
|
+
}
|
|
27788
|
+
const hasChanges = result.hasChanges || partUpdates.size > 0;
|
|
27789
|
+
if (!hasChanges) {
|
|
26940
27790
|
const buf = toBufferCompatible(sourceBytes);
|
|
26941
27791
|
return {
|
|
26942
27792
|
...result,
|
|
@@ -26949,8 +27799,10 @@ var DocxDocument = class {
|
|
|
26949
27799
|
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
26950
27800
|
};
|
|
26951
27801
|
}
|
|
26952
|
-
working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
27802
|
+
if (result.hasChanges) working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
27803
|
+
for (const [path, xml] of partUpdates) working.set(path, textEncoder4.encode(xml));
|
|
26953
27804
|
try {
|
|
27805
|
+
await repairKnownContentTypes(zip);
|
|
26954
27806
|
if (options.validate !== false) await validateDocxPackage(zip);
|
|
26955
27807
|
} catch (error2) {
|
|
26956
27808
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
@@ -26963,13 +27815,71 @@ var DocxDocument = class {
|
|
|
26963
27815
|
...result,
|
|
26964
27816
|
status: "ok",
|
|
26965
27817
|
written: true,
|
|
26966
|
-
|
|
27818
|
+
hasChanges: true,
|
|
27819
|
+
artifactsChanged: [...working].filter(([name, data]) => !originalEntries.has(name) || !areByteArraysEqual(data, originalEntries.get(name))).map(([name]) => name),
|
|
26967
27820
|
uint8Array: outputBytes,
|
|
26968
27821
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
26969
27822
|
buffer: outputBuf,
|
|
26970
27823
|
toBuffer: () => toBufferCompatible(outputBytes)
|
|
26971
27824
|
};
|
|
26972
27825
|
}
|
|
27826
|
+
/**
|
|
27827
|
+
* Resolves (or reopens) the comment thread containing `commentId`.
|
|
27828
|
+
* Resolved is a thread-level state in Word, so the root and every reply are updated together.
|
|
27829
|
+
*/
|
|
27830
|
+
async resolveComment(commentId, options = {}) {
|
|
27831
|
+
const sourceBytes = this.originalBytes;
|
|
27832
|
+
const working = cloneEntries(this.entries);
|
|
27833
|
+
const resolved = options.resolved !== false;
|
|
27834
|
+
const noChange = (extra) => {
|
|
27835
|
+
const buf = toBufferCompatible(sourceBytes);
|
|
27836
|
+
return {
|
|
27837
|
+
status: "ok",
|
|
27838
|
+
hasChanges: false,
|
|
27839
|
+
written: false,
|
|
27840
|
+
artifactsChanged: [],
|
|
27841
|
+
...extra,
|
|
27842
|
+
uint8Array: sourceBytes,
|
|
27843
|
+
toUint8Array: () => new Uint8Array(sourceBytes),
|
|
27844
|
+
buffer: buf,
|
|
27845
|
+
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
27846
|
+
};
|
|
27847
|
+
};
|
|
27848
|
+
const result = applyThreadResolutionToParts({
|
|
27849
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
27850
|
+
commentsExtendedXml: text(working, "word/commentsExtended.xml"),
|
|
27851
|
+
commentId,
|
|
27852
|
+
resolved
|
|
27853
|
+
});
|
|
27854
|
+
if (result.status === "error") return packageFailure(sourceBytes, result.error.code, result.error.message);
|
|
27855
|
+
if (!result.hasChanges) return noChange({ resolved, threadRootId: result.threadRootId, commentIds: result.commentIds });
|
|
27856
|
+
const zip = new MemoryZip(working);
|
|
27857
|
+
try {
|
|
27858
|
+
if (result.commentsXml) working.set("word/comments.xml", textEncoder4.encode(result.commentsXml));
|
|
27859
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, { replaceExisting: true });
|
|
27860
|
+
await repairKnownContentTypes(zip);
|
|
27861
|
+
if (options.validate !== false) await validateDocxPackage(zip);
|
|
27862
|
+
} catch (error2) {
|
|
27863
|
+
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
27864
|
+
}
|
|
27865
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
27866
|
+
this.entries = working;
|
|
27867
|
+
const outputBytes = this.toUint8Array();
|
|
27868
|
+
this.originalBytes = outputBytes;
|
|
27869
|
+
return {
|
|
27870
|
+
status: "ok",
|
|
27871
|
+
hasChanges: true,
|
|
27872
|
+
written: true,
|
|
27873
|
+
resolved,
|
|
27874
|
+
threadRootId: result.threadRootId,
|
|
27875
|
+
commentIds: result.commentIds,
|
|
27876
|
+
artifactsChanged,
|
|
27877
|
+
uint8Array: outputBytes,
|
|
27878
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27879
|
+
buffer: toBufferCompatible(outputBytes),
|
|
27880
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
27881
|
+
};
|
|
27882
|
+
}
|
|
26973
27883
|
async deleteComments(options = {}) {
|
|
26974
27884
|
const sourceBytes = this.originalBytes;
|
|
26975
27885
|
const working = cloneEntries(this.entries);
|
|
@@ -26993,7 +27903,13 @@ var DocxDocument = class {
|
|
|
26993
27903
|
if (!parsed.doc || parsed.error) {
|
|
26994
27904
|
return packageFailure(sourceBytes, "PARSE_ERROR", parsed.error?.message || "Could not parse comments.xml.");
|
|
26995
27905
|
}
|
|
26996
|
-
const
|
|
27906
|
+
const requestedIds = Array.isArray(options.ids) ? new Set(options.ids.map(String)) : null;
|
|
27907
|
+
if (requestedIds) {
|
|
27908
|
+
const known = new Set(Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")));
|
|
27909
|
+
const missing = [...requestedIds].filter((id) => !known.has(id));
|
|
27910
|
+
if (missing.length) return packageFailure(sourceBytes, "COMMENT_NOT_FOUND", `Comment id(s) not found: ${missing.join(", ")}.`);
|
|
27911
|
+
}
|
|
27912
|
+
const matches = (comment) => requestedIds ? requestedIds.has(comment.getAttribute("w:id") || comment.getAttribute("id")) : options.allAuthors === true || (comment.getAttribute("w:author") || comment.getAttribute("author")) === options.author;
|
|
26997
27913
|
const ids = new Set(
|
|
26998
27914
|
Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).filter(matches).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
26999
27915
|
);
|
|
@@ -27022,9 +27938,8 @@ var DocxDocument = class {
|
|
|
27022
27938
|
const idByParaId = /* @__PURE__ */ new Map();
|
|
27023
27939
|
for (const comment of Array.from(parsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
27024
27940
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
27025
|
-
const
|
|
27026
|
-
|
|
27027
|
-
if (paraId) idByParaId.set(paraId.toUpperCase(), id);
|
|
27941
|
+
const paraId = commentParaId(comment);
|
|
27942
|
+
if (paraId) idByParaId.set(paraId, id);
|
|
27028
27943
|
}
|
|
27029
27944
|
for (const [paraId, id] of idByParaId) {
|
|
27030
27945
|
if (ids.has(id)) removedParaIds.add(paraId);
|
|
@@ -27043,8 +27958,8 @@ var DocxDocument = class {
|
|
|
27043
27958
|
}
|
|
27044
27959
|
}
|
|
27045
27960
|
}
|
|
27046
|
-
const
|
|
27047
|
-
const remainingParsed = parseOoxmlSafe(
|
|
27961
|
+
const remainingXml = requestedIds ? commentsXml : deleteCommentsByAuthorInOoxml(commentsXml, { author: options.author, allAuthors: options.allAuthors === true }).oxml;
|
|
27962
|
+
const remainingParsed = parseOoxmlSafe(remainingXml, "application/xml");
|
|
27048
27963
|
if (!remainingParsed.doc || remainingParsed.error) {
|
|
27049
27964
|
return packageFailure(sourceBytes, "PARSE_ERROR", remainingParsed.error?.message || "Could not parse updated comments.xml.");
|
|
27050
27965
|
}
|
|
@@ -27080,10 +27995,14 @@ var DocxDocument = class {
|
|
|
27080
27995
|
}
|
|
27081
27996
|
working.set("word/document.xml", textEncoder4.encode(serializer.serializeToString(documentParsed.doc)));
|
|
27082
27997
|
try {
|
|
27083
|
-
|
|
27998
|
+
reconcileCommentSiblings(working);
|
|
27999
|
+
const finalZip = new MemoryZip(working);
|
|
28000
|
+
await repairKnownContentTypes(finalZip);
|
|
28001
|
+
if (options.validate !== false) await validateDocxPackage(finalZip);
|
|
27084
28002
|
} catch (error2) {
|
|
27085
28003
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
27086
28004
|
}
|
|
28005
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
27087
28006
|
this.entries = working;
|
|
27088
28007
|
const outputBytes = this.toUint8Array();
|
|
27089
28008
|
this.originalBytes = outputBytes;
|
|
@@ -27094,7 +28013,7 @@ var DocxDocument = class {
|
|
|
27094
28013
|
written: true,
|
|
27095
28014
|
commentsRemoved: ids.size,
|
|
27096
28015
|
referencesRemoved,
|
|
27097
|
-
artifactsChanged
|
|
28016
|
+
artifactsChanged,
|
|
27098
28017
|
uint8Array: outputBytes,
|
|
27099
28018
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27100
28019
|
buffer: outputBuf,
|
|
@@ -27350,6 +28269,7 @@ export {
|
|
|
27350
28269
|
rejectTrackedChangesInOoxml,
|
|
27351
28270
|
remapNumberingPayloadForDocument,
|
|
27352
28271
|
removeFormattingFromRPr,
|
|
28272
|
+
repairKnownContentTypes,
|
|
27353
28273
|
reserveNextNumberingId,
|
|
27354
28274
|
reserveNextNumberingIdPair,
|
|
27355
28275
|
resolveParagraphRangeByRefs,
|