@ansonlai/docx-redline-js 0.8.0 → 0.8.2
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 +553 -486
- package/README.md +331 -309
- package/dist/docx-redline-js.esm.js +1209 -168
- 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 +1209 -168
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +1209 -168
- 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-mode.js +183 -54
- 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/pipeline/diff-engine.js +274 -258
- 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/run-tests.mjs +14 -2
- 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 CommonJS bundle v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone CommonJS bundle v0.8.2 — 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;
|
|
@@ -8080,6 +8080,7 @@ __export(index_exports, {
|
|
|
8080
8080
|
rejectTrackedChangesInOoxml: () => rejectTrackedChangesInOoxml,
|
|
8081
8081
|
remapNumberingPayloadForDocument: () => remapNumberingPayloadForDocument,
|
|
8082
8082
|
removeFormattingFromRPr: () => removeFormattingFromRPr,
|
|
8083
|
+
repairKnownContentTypes: () => repairKnownContentTypes,
|
|
8083
8084
|
reserveNextNumberingId: () => reserveNextNumberingId,
|
|
8084
8085
|
reserveNextNumberingIdPair: () => reserveNextNumberingIdPair,
|
|
8085
8086
|
resolveParagraphRangeByRefs: () => resolveParagraphRangeByRefs,
|
|
@@ -9138,15 +9139,29 @@ function createDiffEngine(options = {}) {
|
|
|
9138
9139
|
engine.Diff_Timeout = timeout;
|
|
9139
9140
|
return engine;
|
|
9140
9141
|
}
|
|
9141
|
-
function tokenize(text2) {
|
|
9142
|
+
function tokenize(text2, atomicChars = null) {
|
|
9142
9143
|
const tokens = [];
|
|
9144
|
+
const pushWord = (word) => {
|
|
9145
|
+
if (!atomicChars || atomicChars.size === 0) {
|
|
9146
|
+
tokens.push(word);
|
|
9147
|
+
return;
|
|
9148
|
+
}
|
|
9149
|
+
let start = 0;
|
|
9150
|
+
for (let index = 0; index < word.length; index++) {
|
|
9151
|
+
if (!atomicChars.has(word[index])) continue;
|
|
9152
|
+
if (index > start) tokens.push(word.slice(start, index));
|
|
9153
|
+
tokens.push(word[index]);
|
|
9154
|
+
start = index + 1;
|
|
9155
|
+
}
|
|
9156
|
+
if (start < word.length) tokens.push(word.slice(start));
|
|
9157
|
+
};
|
|
9143
9158
|
const leading = text2.match(/^\s+/);
|
|
9144
9159
|
if (leading) tokens.push(leading[0]);
|
|
9145
9160
|
const regex = /(\S+)(\s*)/g;
|
|
9146
9161
|
regex.lastIndex = leading?.[0].length || 0;
|
|
9147
9162
|
let match;
|
|
9148
9163
|
while ((match = regex.exec(text2)) !== null) {
|
|
9149
|
-
if (match[1])
|
|
9164
|
+
if (match[1]) pushWord(match[1]);
|
|
9150
9165
|
if (match[2]) tokens.push(match[2]);
|
|
9151
9166
|
}
|
|
9152
9167
|
return tokens;
|
|
@@ -9174,8 +9189,8 @@ function wordsToChars(text1, text2, options = {}) {
|
|
|
9174
9189
|
}
|
|
9175
9190
|
return { chars, tokenIds };
|
|
9176
9191
|
}
|
|
9177
|
-
const tokens1 = tokenize(text1);
|
|
9178
|
-
const tokens2 = tokenize(text2);
|
|
9192
|
+
const tokens1 = tokenize(text1, options.atomicChars);
|
|
9193
|
+
const tokens2 = tokenize(text2, options.atomicChars);
|
|
9179
9194
|
const encoded1 = mapTokensToChars(tokens1);
|
|
9180
9195
|
const encoded2 = mapTokensToChars(tokens2);
|
|
9181
9196
|
return {
|
|
@@ -14333,6 +14348,19 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
14333
14348
|
pendingReplacementEvent = null;
|
|
14334
14349
|
}
|
|
14335
14350
|
}
|
|
14351
|
+
for (const offset of Array.from(sentinelMapByStart.keys()).sort((a, b) => a - b)) {
|
|
14352
|
+
for (const sentinel of sentinelMapByStart.get(offset)) {
|
|
14353
|
+
if (!sentinel.isCommentMarker || emittedCommentMarkers.has(sentinel.node)) continue;
|
|
14354
|
+
emittedCommentMarkers.add(sentinel.node);
|
|
14355
|
+
if (isWordElement(sentinel.node, "commentReference")) {
|
|
14356
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
14357
|
+
run.appendChild(sentinel.node.cloneNode(true));
|
|
14358
|
+
currentParagraph.appendChild(run);
|
|
14359
|
+
} else {
|
|
14360
|
+
currentParagraph.appendChild(sentinel.node.cloneNode(true));
|
|
14361
|
+
}
|
|
14362
|
+
}
|
|
14363
|
+
}
|
|
14336
14364
|
if (generateRedlines) {
|
|
14337
14365
|
containerFragments.forEach((fragment) => {
|
|
14338
14366
|
Array.from(fragment.childNodes).forEach((node) => {
|
|
@@ -14567,7 +14595,11 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
14567
14595
|
if (mapping.paragraphs.length === 0) {
|
|
14568
14596
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
14569
14597
|
}
|
|
14570
|
-
const
|
|
14598
|
+
const wordDiffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText, {
|
|
14599
|
+
...diffOptions,
|
|
14600
|
+
atomicChars: new Set(mapping.referenceMap.keys())
|
|
14601
|
+
});
|
|
14602
|
+
const diffs = keepUnchangedTextOutsideWrappers(anchorSharedReferences(wordDiffs, mapping.referenceMap), mapping);
|
|
14571
14603
|
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
14572
14604
|
xmlDoc,
|
|
14573
14605
|
diffs,
|
|
@@ -14579,6 +14611,109 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
14579
14611
|
options
|
|
14580
14612
|
));
|
|
14581
14613
|
}
|
|
14614
|
+
function forEachChangeGroup(diffs, onEqual, onGroup) {
|
|
14615
|
+
let index = 0;
|
|
14616
|
+
while (index < diffs.length) {
|
|
14617
|
+
if (diffs[index][0] === 0) {
|
|
14618
|
+
onEqual(diffs[index]);
|
|
14619
|
+
index++;
|
|
14620
|
+
continue;
|
|
14621
|
+
}
|
|
14622
|
+
const group = [];
|
|
14623
|
+
while (index < diffs.length && diffs[index][0] !== 0) group.push(diffs[index++]);
|
|
14624
|
+
onGroup(
|
|
14625
|
+
group,
|
|
14626
|
+
group.filter(([op]) => op === -1).map(([, text2]) => text2).join(""),
|
|
14627
|
+
group.filter(([op]) => op === 1).map(([, text2]) => text2).join("")
|
|
14628
|
+
);
|
|
14629
|
+
}
|
|
14630
|
+
}
|
|
14631
|
+
function pushChange(result, deleted, inserted) {
|
|
14632
|
+
if (deleted === inserted) {
|
|
14633
|
+
if (deleted) result.push([0, deleted]);
|
|
14634
|
+
return;
|
|
14635
|
+
}
|
|
14636
|
+
if (deleted) result.push([-1, deleted]);
|
|
14637
|
+
if (inserted) result.push([1, inserted]);
|
|
14638
|
+
}
|
|
14639
|
+
function anchorSharedReferences(diffs, referenceMap) {
|
|
14640
|
+
if (referenceMap.size === 0) return diffs;
|
|
14641
|
+
const result = [];
|
|
14642
|
+
forEachChangeGroup(diffs, (part) => result.push(part), (group, deleted, inserted) => {
|
|
14643
|
+
const deletedRefs = [...deleted].filter((char) => referenceMap.has(char) && inserted.includes(char));
|
|
14644
|
+
const insertedRefs = [...inserted].filter((char) => deletedRefs.includes(char));
|
|
14645
|
+
if (deletedRefs.length === 0 || deletedRefs.join("") !== insertedRefs.join("")) {
|
|
14646
|
+
result.push(...group);
|
|
14647
|
+
return;
|
|
14648
|
+
}
|
|
14649
|
+
let deletedStart = 0;
|
|
14650
|
+
let insertedStart = 0;
|
|
14651
|
+
for (const ref of deletedRefs) {
|
|
14652
|
+
const deletedAt = deleted.indexOf(ref, deletedStart);
|
|
14653
|
+
const insertedAt = inserted.indexOf(ref, insertedStart);
|
|
14654
|
+
pushChange(result, deleted.slice(deletedStart, deletedAt), inserted.slice(insertedStart, insertedAt));
|
|
14655
|
+
result.push([0, ref]);
|
|
14656
|
+
deletedStart = deletedAt + 1;
|
|
14657
|
+
insertedStart = insertedAt + 1;
|
|
14658
|
+
}
|
|
14659
|
+
pushChange(result, deleted.slice(deletedStart), inserted.slice(insertedStart));
|
|
14660
|
+
});
|
|
14661
|
+
return result;
|
|
14662
|
+
}
|
|
14663
|
+
function commonPrefixLength(left, right) {
|
|
14664
|
+
const limit = Math.min(left.length, right.length);
|
|
14665
|
+
let length = 0;
|
|
14666
|
+
while (length < limit && left[length] === right[length]) length++;
|
|
14667
|
+
return length;
|
|
14668
|
+
}
|
|
14669
|
+
function commonSuffixLength(left, right, limit) {
|
|
14670
|
+
let length = 0;
|
|
14671
|
+
while (length < limit && left[left.length - 1 - length] === right[right.length - 1 - length]) length++;
|
|
14672
|
+
return length;
|
|
14673
|
+
}
|
|
14674
|
+
function keepUnchangedTextOutsideWrappers(diffs, mapping) {
|
|
14675
|
+
const wrapperAt = (index) => mapping.getRunProperties(index).wrapper || null;
|
|
14676
|
+
const result = [];
|
|
14677
|
+
let originalOffset = 0;
|
|
14678
|
+
forEachChangeGroup(diffs, (part) => {
|
|
14679
|
+
result.push(part);
|
|
14680
|
+
originalOffset += part[1].length;
|
|
14681
|
+
}, (group, deleted, inserted) => {
|
|
14682
|
+
const start = originalOffset;
|
|
14683
|
+
const end = start + deleted.length;
|
|
14684
|
+
originalOffset = end;
|
|
14685
|
+
const isWrapperBoundary = (index) => index > start && index < end && wrapperAt(index - 1) !== wrapperAt(index);
|
|
14686
|
+
let prefix = 0;
|
|
14687
|
+
let suffix = 0;
|
|
14688
|
+
if (deleted && inserted) {
|
|
14689
|
+
const sharedSuffix = commonSuffixLength(deleted, inserted, Math.min(deleted.length, inserted.length));
|
|
14690
|
+
for (let length = sharedSuffix; length > 0; length--) {
|
|
14691
|
+
if (isWrapperBoundary(end - length)) {
|
|
14692
|
+
suffix = length;
|
|
14693
|
+
break;
|
|
14694
|
+
}
|
|
14695
|
+
}
|
|
14696
|
+
const sharedPrefix = Math.min(
|
|
14697
|
+
commonPrefixLength(deleted, inserted),
|
|
14698
|
+
Math.min(deleted.length, inserted.length) - suffix
|
|
14699
|
+
);
|
|
14700
|
+
for (let length = sharedPrefix; length > 0; length--) {
|
|
14701
|
+
if (isWrapperBoundary(start + length)) {
|
|
14702
|
+
prefix = length;
|
|
14703
|
+
break;
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14706
|
+
}
|
|
14707
|
+
if (prefix === 0 && suffix === 0) {
|
|
14708
|
+
result.push(...group);
|
|
14709
|
+
return;
|
|
14710
|
+
}
|
|
14711
|
+
if (prefix > 0) result.push([0, deleted.slice(0, prefix)]);
|
|
14712
|
+
pushChange(result, deleted.slice(prefix, deleted.length - suffix), inserted.slice(prefix, inserted.length - suffix));
|
|
14713
|
+
if (suffix > 0) result.push([0, deleted.slice(deleted.length - suffix)]);
|
|
14714
|
+
});
|
|
14715
|
+
return result;
|
|
14716
|
+
}
|
|
14582
14717
|
|
|
14583
14718
|
// engine/table-mode.js
|
|
14584
14719
|
function noChanges(serializer, xmlDoc) {
|
|
@@ -18127,6 +18262,260 @@ function ingestWordOoxmlToMarkdownResult(ooxml) {
|
|
|
18127
18262
|
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
18128
18263
|
}
|
|
18129
18264
|
|
|
18265
|
+
// services/comment-builders.js
|
|
18266
|
+
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18267
|
+
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18268
|
+
function createCommentParaId(commentId) {
|
|
18269
|
+
const numeric = Number.parseInt(String(commentId), 10);
|
|
18270
|
+
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18271
|
+
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18272
|
+
}
|
|
18273
|
+
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18274
|
+
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18275
|
+
const escapedContent = escapeXml(content);
|
|
18276
|
+
const escapedAuthor = escapeXml(author);
|
|
18277
|
+
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18278
|
+
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18279
|
+
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18280
|
+
</w:p>
|
|
18281
|
+
</w:comment>`;
|
|
18282
|
+
}
|
|
18283
|
+
function buildCommentsPartXml(comments) {
|
|
18284
|
+
if (!comments || comments.length === 0) {
|
|
18285
|
+
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18286
|
+
}
|
|
18287
|
+
const commentElements = comments.map(
|
|
18288
|
+
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18289
|
+
).join("\n ");
|
|
18290
|
+
return `<w:comments xmlns:w="${NS_W}">
|
|
18291
|
+
${commentElements}
|
|
18292
|
+
</w:comments>`;
|
|
18293
|
+
}
|
|
18294
|
+
function buildCommentsExtendedPartXml(entries) {
|
|
18295
|
+
const body = (entries || []).map((entry) => {
|
|
18296
|
+
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18297
|
+
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18298
|
+
}).join("");
|
|
18299
|
+
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18300
|
+
}
|
|
18301
|
+
|
|
18302
|
+
// services/comment-thread-parts.js
|
|
18303
|
+
var attrOf = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
18304
|
+
function commentThreadParagraph(commentElement) {
|
|
18305
|
+
const paragraphs = Array.from(commentElement?.getElementsByTagNameNS?.("*", "p") || []);
|
|
18306
|
+
return paragraphs.length ? paragraphs[paragraphs.length - 1] : null;
|
|
18307
|
+
}
|
|
18308
|
+
function commentParaId(commentElement) {
|
|
18309
|
+
return attrOf(commentThreadParagraph(commentElement), "w14:paraId", "paraId").toUpperCase();
|
|
18310
|
+
}
|
|
18311
|
+
function usedParaIds(commentsDoc, extendedDoc) {
|
|
18312
|
+
const ids = /* @__PURE__ */ new Set();
|
|
18313
|
+
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
18314
|
+
const id = attrOf(p, "w14:paraId", "paraId");
|
|
18315
|
+
if (id) ids.add(id.toUpperCase());
|
|
18316
|
+
}
|
|
18317
|
+
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
18318
|
+
const id = attrOf(ex, "w15:paraId", "paraId");
|
|
18319
|
+
if (id) ids.add(id.toUpperCase());
|
|
18320
|
+
}
|
|
18321
|
+
return ids;
|
|
18322
|
+
}
|
|
18323
|
+
function allocateParaId(commentId, occupied) {
|
|
18324
|
+
let candidate = createCommentParaId(commentId);
|
|
18325
|
+
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
18326
|
+
while (occupied.has(candidate)) {
|
|
18327
|
+
value = value + 1 >>> 0;
|
|
18328
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18329
|
+
}
|
|
18330
|
+
occupied.add(candidate);
|
|
18331
|
+
return candidate;
|
|
18332
|
+
}
|
|
18333
|
+
var parseDoc = (xml, partName) => {
|
|
18334
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
18335
|
+
if (!parsed.doc || parsed.error) return { error: { code: "PARSE_ERROR", message: `Could not parse ${partName}: ${parsed.error?.message || "invalid XML"}` } };
|
|
18336
|
+
return { doc: parsed.doc };
|
|
18337
|
+
};
|
|
18338
|
+
function applyThreadResolutionToParts({ commentsXml, commentsExtendedXml = null, commentId, resolved = true }) {
|
|
18339
|
+
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "Resolving a comment requires an existing word/comments.xml part." } };
|
|
18340
|
+
const commentsParsed = parseDoc(commentsXml, "word/comments.xml");
|
|
18341
|
+
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
18342
|
+
const commentsDoc = commentsParsed.doc;
|
|
18343
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18344
|
+
const comments = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
18345
|
+
const target = comments.find((node) => idOf(node) === String(commentId));
|
|
18346
|
+
if (!target) return { status: "error", error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` } };
|
|
18347
|
+
let extendedDoc;
|
|
18348
|
+
if (commentsExtendedXml) {
|
|
18349
|
+
const parsed = parseDoc(commentsExtendedXml, "word/commentsExtended.xml");
|
|
18350
|
+
if (parsed.error) return { status: "error", error: parsed.error };
|
|
18351
|
+
extendedDoc = parsed.doc;
|
|
18352
|
+
} else {
|
|
18353
|
+
extendedDoc = parseDoc(`<w15:commentsEx xmlns:w15="${NS_W15}"/>`, "word/commentsExtended.xml").doc;
|
|
18354
|
+
}
|
|
18355
|
+
const root = extendedDoc.documentElement;
|
|
18356
|
+
const entryFor = (paraId) => Array.from(root.getElementsByTagNameNS("*", "commentEx")).find((node) => attrOf(node, "w15:paraId", "paraId").toUpperCase() === paraId) || null;
|
|
18357
|
+
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
18358
|
+
let commentsChanged = false;
|
|
18359
|
+
const keyOf = (node) => {
|
|
18360
|
+
let paraId = commentParaId(node);
|
|
18361
|
+
if (!paraId) {
|
|
18362
|
+
paraId = allocateParaId(idOf(node), occupied);
|
|
18363
|
+
commentThreadParagraph(node)?.setAttributeNS(NS_W14, "w14:paraId", paraId);
|
|
18364
|
+
commentsChanged = true;
|
|
18365
|
+
}
|
|
18366
|
+
return paraId;
|
|
18367
|
+
};
|
|
18368
|
+
const targetKey = keyOf(target);
|
|
18369
|
+
const targetEntry = entryFor(targetKey);
|
|
18370
|
+
const rootKey = targetEntry && attrOf(targetEntry, "w15:paraIdParent", "paraIdParent").toUpperCase() || targetKey;
|
|
18371
|
+
const threadKeys = /* @__PURE__ */ new Set([rootKey, targetKey]);
|
|
18372
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentEx"))) {
|
|
18373
|
+
if (attrOf(entry, "w15:paraIdParent", "paraIdParent").toUpperCase() === rootKey) threadKeys.add(attrOf(entry, "w15:paraId", "paraId").toUpperCase());
|
|
18374
|
+
}
|
|
18375
|
+
const doneValue = resolved ? "1" : "0";
|
|
18376
|
+
let extendedChanged = !commentsExtendedXml;
|
|
18377
|
+
for (const key of threadKeys) {
|
|
18378
|
+
let entry = entryFor(key);
|
|
18379
|
+
if (!entry) {
|
|
18380
|
+
entry = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
18381
|
+
entry.setAttributeNS(NS_W15, "w15:paraId", key);
|
|
18382
|
+
if (key !== rootKey) entry.setAttributeNS(NS_W15, "w15:paraIdParent", rootKey);
|
|
18383
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18384
|
+
root.appendChild(entry);
|
|
18385
|
+
extendedChanged = true;
|
|
18386
|
+
} else if (attrOf(entry, "w15:done", "done") !== doneValue) {
|
|
18387
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18388
|
+
extendedChanged = true;
|
|
18389
|
+
}
|
|
18390
|
+
}
|
|
18391
|
+
const byKey = new Map(comments.map((node) => [commentParaId(node), node]));
|
|
18392
|
+
const commentIds = [...threadKeys].map((key) => byKey.get(key)).filter(Boolean).map(idOf);
|
|
18393
|
+
const rootComment = byKey.get(rootKey);
|
|
18394
|
+
const serializer = createSerializer();
|
|
18395
|
+
const hasChanges = extendedChanged || commentsChanged;
|
|
18396
|
+
return {
|
|
18397
|
+
status: "ok",
|
|
18398
|
+
hasChanges,
|
|
18399
|
+
resolved,
|
|
18400
|
+
threadRootId: rootComment ? idOf(rootComment) : String(commentId),
|
|
18401
|
+
commentIds,
|
|
18402
|
+
...hasChanges ? { commentsExtendedXml: serializer.serializeToString(extendedDoc) } : {},
|
|
18403
|
+
...commentsChanged ? { commentsXml: serializer.serializeToString(commentsDoc) } : {}
|
|
18404
|
+
};
|
|
18405
|
+
}
|
|
18406
|
+
var NS_W_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
18407
|
+
function anchorReplyInDocument(xmlDoc, memberIds, newId) {
|
|
18408
|
+
const wanted = new Set(memberIds.map(String));
|
|
18409
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18410
|
+
const all = (name) => Array.from(xmlDoc.getElementsByTagNameNS("*", name)).filter((node) => wanted.has(idOf(node)));
|
|
18411
|
+
const starts = all("commentRangeStart");
|
|
18412
|
+
const references = all("commentReference");
|
|
18413
|
+
if (!starts.length || !references.length) return false;
|
|
18414
|
+
const lastStart = starts[starts.length - 1];
|
|
18415
|
+
const lastReference = references[references.length - 1];
|
|
18416
|
+
const referenceRun = lastReference.parentNode?.localName === "r" ? lastReference.parentNode : lastReference;
|
|
18417
|
+
const make = (name, parent) => {
|
|
18418
|
+
const element = xmlDoc.createElementNS(NS_W_MAIN, `w:${name}`);
|
|
18419
|
+
element.setAttributeNS(NS_W_MAIN, "w:id", String(newId));
|
|
18420
|
+
if (parent) parent.appendChild(element);
|
|
18421
|
+
return element;
|
|
18422
|
+
};
|
|
18423
|
+
const insertAfter = (anchor, node) => anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
18424
|
+
insertAfter(lastStart, make("commentRangeStart"));
|
|
18425
|
+
const end = make("commentRangeEnd");
|
|
18426
|
+
const run = xmlDoc.createElementNS(NS_W_MAIN, "w:r");
|
|
18427
|
+
run.appendChild(xmlDoc.createElementNS(NS_W_MAIN, "w:rPr"));
|
|
18428
|
+
make("commentReference", run);
|
|
18429
|
+
insertAfter(referenceRun, end);
|
|
18430
|
+
insertAfter(end, run);
|
|
18431
|
+
return true;
|
|
18432
|
+
}
|
|
18433
|
+
var NS_W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
|
|
18434
|
+
var NS_W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
|
|
18435
|
+
var durableIdFor = (paraId, taken) => {
|
|
18436
|
+
let value = (Number.parseInt(paraId, 16) ^ 625341585) & 2147483646;
|
|
18437
|
+
let candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18438
|
+
while (taken.has(candidate)) {
|
|
18439
|
+
value = value + 2 & 2147483646;
|
|
18440
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18441
|
+
}
|
|
18442
|
+
taken.add(candidate);
|
|
18443
|
+
return candidate;
|
|
18444
|
+
};
|
|
18445
|
+
function reconcileCommentSiblingParts({ commentsXml, commentsIdsXml = null, commentsExtensibleXml = null }) {
|
|
18446
|
+
const out = {};
|
|
18447
|
+
if (!commentsXml || !commentsIdsXml && !commentsExtensibleXml) return out;
|
|
18448
|
+
const commentsParsed = parseOoxmlSafe(commentsXml, "application/xml");
|
|
18449
|
+
if (!commentsParsed.doc || commentsParsed.error) return out;
|
|
18450
|
+
const serializer = createSerializer();
|
|
18451
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
18452
|
+
for (const node of Array.from(commentsParsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
18453
|
+
const key = commentParaId(node);
|
|
18454
|
+
if (key) byKey.set(key, node);
|
|
18455
|
+
}
|
|
18456
|
+
let idsDoc = null;
|
|
18457
|
+
const durableByKey = /* @__PURE__ */ new Map();
|
|
18458
|
+
const taken = /* @__PURE__ */ new Set();
|
|
18459
|
+
let idsChanged = false;
|
|
18460
|
+
if (commentsIdsXml) {
|
|
18461
|
+
const parsed = parseOoxmlSafe(commentsIdsXml, "application/xml");
|
|
18462
|
+
if (!parsed.doc || parsed.error) return out;
|
|
18463
|
+
idsDoc = parsed.doc;
|
|
18464
|
+
const root = idsDoc.documentElement;
|
|
18465
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentId"))) {
|
|
18466
|
+
const key = attrOf(entry, "w16cid:paraId", "paraId").toUpperCase();
|
|
18467
|
+
const durable = attrOf(entry, "w16cid:durableId", "durableId").toUpperCase();
|
|
18468
|
+
if (durable) taken.add(durable);
|
|
18469
|
+
if (!byKey.has(key)) {
|
|
18470
|
+
root.removeChild(entry);
|
|
18471
|
+
idsChanged = true;
|
|
18472
|
+
continue;
|
|
18473
|
+
}
|
|
18474
|
+
durableByKey.set(key, durable);
|
|
18475
|
+
}
|
|
18476
|
+
for (const key of byKey.keys()) {
|
|
18477
|
+
if (durableByKey.has(key)) continue;
|
|
18478
|
+
const durable = durableIdFor(key, taken);
|
|
18479
|
+
const entry = idsDoc.createElementNS(NS_W16CID, "w16cid:commentId");
|
|
18480
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:paraId", key);
|
|
18481
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:durableId", durable);
|
|
18482
|
+
root.appendChild(entry);
|
|
18483
|
+
durableByKey.set(key, durable);
|
|
18484
|
+
idsChanged = true;
|
|
18485
|
+
}
|
|
18486
|
+
}
|
|
18487
|
+
let extDoc = null;
|
|
18488
|
+
let extChanged = false;
|
|
18489
|
+
if (commentsExtensibleXml && idsDoc) {
|
|
18490
|
+
const parsed = parseOoxmlSafe(commentsExtensibleXml, "application/xml");
|
|
18491
|
+
if (parsed.doc && !parsed.error) {
|
|
18492
|
+
extDoc = parsed.doc;
|
|
18493
|
+
const root = extDoc.documentElement;
|
|
18494
|
+
const live = new Set(durableByKey.values());
|
|
18495
|
+
const present = /* @__PURE__ */ new Set();
|
|
18496
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentExtensible"))) {
|
|
18497
|
+
const durable = attrOf(entry, "w16cex:durableId", "durableId").toUpperCase();
|
|
18498
|
+
if (!live.has(durable)) {
|
|
18499
|
+
root.removeChild(entry);
|
|
18500
|
+
extChanged = true;
|
|
18501
|
+
} else present.add(durable);
|
|
18502
|
+
}
|
|
18503
|
+
for (const [key, durable] of durableByKey) {
|
|
18504
|
+
if (present.has(durable)) continue;
|
|
18505
|
+
const date = attrOf(byKey.get(key), "w:date", "date");
|
|
18506
|
+
const entry = extDoc.createElementNS(NS_W16CEX, "w16cex:commentExtensible");
|
|
18507
|
+
entry.setAttributeNS(NS_W16CEX, "w16cex:durableId", durable);
|
|
18508
|
+
if (date) entry.setAttributeNS(NS_W16CEX, "w16cex:dateUtc", date);
|
|
18509
|
+
root.appendChild(entry);
|
|
18510
|
+
extChanged = true;
|
|
18511
|
+
}
|
|
18512
|
+
}
|
|
18513
|
+
}
|
|
18514
|
+
if (idsChanged) out.commentsIdsXml = serializer.serializeToString(idsDoc);
|
|
18515
|
+
if (extChanged) out.commentsExtensibleXml = serializer.serializeToString(extDoc);
|
|
18516
|
+
return out;
|
|
18517
|
+
}
|
|
18518
|
+
|
|
18130
18519
|
// core/sha256.js
|
|
18131
18520
|
var K = new Uint32Array([
|
|
18132
18521
|
1116352408,
|
|
@@ -18619,7 +19008,7 @@ function readCommentDefinitions(commentsDoc) {
|
|
|
18619
19008
|
author: attr2(comment, "author") || null,
|
|
18620
19009
|
date: attr2(comment, "date") || null,
|
|
18621
19010
|
text: paragraphs.map((p) => extractCanonicalParagraphText(p)).join("\n"),
|
|
18622
|
-
paraId:
|
|
19011
|
+
paraId: commentParaId(comment) || null
|
|
18623
19012
|
});
|
|
18624
19013
|
}
|
|
18625
19014
|
return result;
|
|
@@ -18850,43 +19239,6 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
18850
19239
|
};
|
|
18851
19240
|
}
|
|
18852
19241
|
|
|
18853
|
-
// services/comment-builders.js
|
|
18854
|
-
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18855
|
-
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18856
|
-
function createCommentParaId(commentId) {
|
|
18857
|
-
const numeric = Number.parseInt(String(commentId), 10);
|
|
18858
|
-
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18859
|
-
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18860
|
-
}
|
|
18861
|
-
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18862
|
-
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18863
|
-
const escapedContent = escapeXml(content);
|
|
18864
|
-
const escapedAuthor = escapeXml(author);
|
|
18865
|
-
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18866
|
-
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18867
|
-
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18868
|
-
</w:p>
|
|
18869
|
-
</w:comment>`;
|
|
18870
|
-
}
|
|
18871
|
-
function buildCommentsPartXml(comments) {
|
|
18872
|
-
if (!comments || comments.length === 0) {
|
|
18873
|
-
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18874
|
-
}
|
|
18875
|
-
const commentElements = comments.map(
|
|
18876
|
-
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18877
|
-
).join("\n ");
|
|
18878
|
-
return `<w:comments xmlns:w="${NS_W}">
|
|
18879
|
-
${commentElements}
|
|
18880
|
-
</w:comments>`;
|
|
18881
|
-
}
|
|
18882
|
-
function buildCommentsExtendedPartXml(entries) {
|
|
18883
|
-
const body = (entries || []).map((entry) => {
|
|
18884
|
-
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18885
|
-
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18886
|
-
}).join("");
|
|
18887
|
-
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18888
|
-
}
|
|
18889
|
-
|
|
18890
19242
|
// services/comment-locator.js
|
|
18891
19243
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
18892
19244
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -19347,29 +19699,7 @@ function parseRequired(xml, partName) {
|
|
|
19347
19699
|
}
|
|
19348
19700
|
return { doc: parsed.doc };
|
|
19349
19701
|
}
|
|
19350
|
-
function
|
|
19351
|
-
const ids = /* @__PURE__ */ new Set();
|
|
19352
|
-
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
19353
|
-
const id = attr3(p, "w14:paraId", "paraId");
|
|
19354
|
-
if (id) ids.add(id.toUpperCase());
|
|
19355
|
-
}
|
|
19356
|
-
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19357
|
-
const id = attr3(ex, "w15:paraId", "paraId");
|
|
19358
|
-
if (id) ids.add(id.toUpperCase());
|
|
19359
|
-
}
|
|
19360
|
-
return ids;
|
|
19361
|
-
}
|
|
19362
|
-
function allocateParaId(commentId, occupied) {
|
|
19363
|
-
let candidate = createCommentParaId(commentId);
|
|
19364
|
-
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
19365
|
-
while (occupied.has(candidate)) {
|
|
19366
|
-
value = value + 1 >>> 0;
|
|
19367
|
-
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
19368
|
-
}
|
|
19369
|
-
occupied.add(candidate);
|
|
19370
|
-
return candidate;
|
|
19371
|
-
}
|
|
19372
|
-
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19702
|
+
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, documentXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19373
19703
|
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "A comment reply requires an existing word/comments.xml part." } };
|
|
19374
19704
|
const commentsParsed = parseRequired(commentsXml, "word/comments.xml");
|
|
19375
19705
|
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
@@ -19383,15 +19713,31 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19383
19713
|
extendedDoc = parsed.doc;
|
|
19384
19714
|
}
|
|
19385
19715
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
19386
|
-
const
|
|
19716
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/w14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
19717
|
+
const parentParagraph = commentThreadParagraph(parent);
|
|
19387
19718
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
19388
|
-
let parentParaId =
|
|
19719
|
+
let parentParaId = commentParaId(parent);
|
|
19389
19720
|
if (!parentParaId) {
|
|
19390
19721
|
parentParaId = allocateParaId(parentCommentId, occupied);
|
|
19391
19722
|
parentParagraph.setAttributeNS(NS_W14, "w14:paraId", parentParaId);
|
|
19392
19723
|
} else {
|
|
19393
19724
|
parentParaId = parentParaId.toUpperCase();
|
|
19394
19725
|
}
|
|
19726
|
+
for (const node of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19727
|
+
if (attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId) {
|
|
19728
|
+
const root2 = attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase();
|
|
19729
|
+
if (root2) parentParaId = root2;
|
|
19730
|
+
break;
|
|
19731
|
+
}
|
|
19732
|
+
}
|
|
19733
|
+
const threadCommentIds = [];
|
|
19734
|
+
const commentNodes = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
19735
|
+
const parentOf = new Map(Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || []).map((node) => [attr3(node, "w15:paraId", "paraId").toUpperCase(), attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase()]));
|
|
19736
|
+
for (const node of commentNodes) {
|
|
19737
|
+
const key = commentParaId(node);
|
|
19738
|
+
if (key === parentParaId || parentOf.get(key) === parentParaId) threadCommentIds.push(attr3(node, "w:id", "id"));
|
|
19739
|
+
}
|
|
19740
|
+
const threadRootCommentId = attr3(commentNodes.find((node) => commentParaId(node) === parentParaId), "w:id", "id") || String(parentCommentId);
|
|
19395
19741
|
const replyParaId = allocateParaId(commentId, occupied);
|
|
19396
19742
|
const replyParsed = parseRequired(`<w:comments xmlns:w="${NS_W6}" xmlns:w14="${NS_W14}">${buildCommentElement(commentId, author, commentContent, date, replyParaId)}</w:comments>`, "reply comment");
|
|
19397
19743
|
commentsDoc.documentElement.appendChild(commentsDoc.importNode(replyParsed.doc.documentElement.firstChild, true));
|
|
@@ -19400,16 +19746,18 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19400
19746
|
}
|
|
19401
19747
|
const root = extendedDoc.documentElement;
|
|
19402
19748
|
const entries = Array.from(root.getElementsByTagNameNS("*", "commentEx"));
|
|
19403
|
-
|
|
19749
|
+
const rootEntry = entries.find((node) => attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId);
|
|
19750
|
+
if (!rootEntry) {
|
|
19404
19751
|
const parentEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19405
19752
|
parentEx.setAttributeNS(NS_W15, "w15:paraId", parentParaId);
|
|
19406
19753
|
parentEx.setAttributeNS(NS_W15, "w15:done", "0");
|
|
19407
19754
|
root.appendChild(parentEx);
|
|
19408
19755
|
}
|
|
19756
|
+
const threadDone = attr3(rootEntry, "w15:done", "done") === "1" ? "1" : "0";
|
|
19409
19757
|
const replyEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19410
19758
|
replyEx.setAttributeNS(NS_W15, "w15:paraId", replyParaId);
|
|
19411
19759
|
replyEx.setAttributeNS(NS_W15, "w15:paraIdParent", parentParaId);
|
|
19412
|
-
replyEx.setAttributeNS(NS_W15, "w15:done",
|
|
19760
|
+
replyEx.setAttributeNS(NS_W15, "w15:done", threadDone);
|
|
19413
19761
|
root.appendChild(replyEx);
|
|
19414
19762
|
const serializer = createSerializer();
|
|
19415
19763
|
return {
|
|
@@ -19422,7 +19770,9 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19422
19770
|
commentId,
|
|
19423
19771
|
parentCommentId: String(parentCommentId),
|
|
19424
19772
|
paraId: replyParaId,
|
|
19425
|
-
parentParaId
|
|
19773
|
+
parentParaId,
|
|
19774
|
+
threadRootCommentId,
|
|
19775
|
+
threadCommentIds: threadCommentIds.length ? threadCommentIds : [threadRootCommentId]
|
|
19426
19776
|
};
|
|
19427
19777
|
}
|
|
19428
19778
|
|
|
@@ -19487,19 +19837,19 @@ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
|
19487
19837
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
19488
19838
|
const doc = parseOoxml(ooxmlString);
|
|
19489
19839
|
if (!doc) return ooxmlString;
|
|
19490
|
-
const
|
|
19491
|
-
const runs = doc.getElementsByTagNameNS(
|
|
19492
|
-
const insertions = doc.getElementsByTagNameNS(
|
|
19840
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19841
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
19842
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
19493
19843
|
const allRuns = [...Array.from(runs)];
|
|
19494
19844
|
for (const ins of insertions) {
|
|
19495
|
-
const insideRuns = ins.getElementsByTagNameNS(
|
|
19845
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
19496
19846
|
allRuns.push(...Array.from(insideRuns));
|
|
19497
19847
|
}
|
|
19498
19848
|
for (const run of allRuns) {
|
|
19499
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19849
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19500
19850
|
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19501
19851
|
if (runText.includes(targetText) || runText === targetText) {
|
|
19502
|
-
const rPrElements = run.getElementsByTagNameNS(
|
|
19852
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19503
19853
|
if (rPrElements.length > 0) {
|
|
19504
19854
|
const rPr = rPrElements[0];
|
|
19505
19855
|
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
@@ -19534,7 +19884,7 @@ var HIGHLIGHT_COLOR_MAP = {
|
|
|
19534
19884
|
"white": "white"
|
|
19535
19885
|
};
|
|
19536
19886
|
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
19537
|
-
const
|
|
19887
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19538
19888
|
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
19539
19889
|
const generateRedlines = options?.generateRedlines ?? false;
|
|
19540
19890
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -19553,10 +19903,10 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19553
19903
|
}
|
|
19554
19904
|
});
|
|
19555
19905
|
}
|
|
19556
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(
|
|
19906
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
19557
19907
|
Array.from(existingHighlight).forEach(removeNode2);
|
|
19558
19908
|
const highlightEl = createWordElement(doc, "w:highlight");
|
|
19559
|
-
highlightEl.setAttributeNS(
|
|
19909
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
19560
19910
|
rPrElement.appendChild(highlightEl);
|
|
19561
19911
|
if (generateRedlines && previousRPrState) {
|
|
19562
19912
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
@@ -19565,7 +19915,7 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19565
19915
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
19566
19916
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
19567
19917
|
rPrChange.appendChild(previousRPrState);
|
|
19568
|
-
const existingChange = rPrElement.getElementsByTagNameNS(
|
|
19918
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
19569
19919
|
Array.from(existingChange).forEach(removeNode2);
|
|
19570
19920
|
rPrElement.appendChild(rPrChange);
|
|
19571
19921
|
}
|
|
@@ -19582,12 +19932,12 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19582
19932
|
} else {
|
|
19583
19933
|
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
19584
19934
|
}
|
|
19585
|
-
const
|
|
19935
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19586
19936
|
const getRunText = (run) => {
|
|
19587
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19937
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19588
19938
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19589
19939
|
};
|
|
19590
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(
|
|
19940
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
19591
19941
|
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
19592
19942
|
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
19593
19943
|
const clonedRun = sourceRun.cloneNode(true);
|
|
@@ -19596,14 +19946,14 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19596
19946
|
} else {
|
|
19597
19947
|
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
19598
19948
|
}
|
|
19599
|
-
const textNodes = clonedRun.getElementsByTagNameNS(
|
|
19949
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
19600
19950
|
Array.from(textNodes).forEach(removeNode2);
|
|
19601
19951
|
const newText = createWordElement(doc, "w:t");
|
|
19602
19952
|
newText.setAttribute("xml:space", "preserve");
|
|
19603
19953
|
newText.textContent = text2;
|
|
19604
19954
|
clonedRun.appendChild(newText);
|
|
19605
19955
|
if (shouldHighlight) {
|
|
19606
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(
|
|
19956
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19607
19957
|
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
19608
19958
|
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
19609
19959
|
if (existingRPr) {
|
|
@@ -19652,20 +20002,68 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19652
20002
|
return serializeOoxml(doc);
|
|
19653
20003
|
}
|
|
19654
20004
|
|
|
20005
|
+
// services/package-parts.js
|
|
20006
|
+
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
20007
|
+
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
20008
|
+
var PART_SPECS = Object.freeze([
|
|
20009
|
+
{ id: "document", path: "word/document.xml", pathPattern: null, contentType: `${WORD_ML}.document.main+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/officeDocument`, relTarget: null, verified: true },
|
|
20010
|
+
{ id: "numbering", path: "word/numbering.xml", pathPattern: null, contentType: `${WORD_ML}.numbering+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/numbering`, relTarget: "numbering.xml", verified: false },
|
|
20011
|
+
{ id: "comments", path: "word/comments.xml", pathPattern: null, contentType: `${WORD_ML}.comments+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/comments`, relTarget: "comments.xml", verified: true },
|
|
20012
|
+
{
|
|
20013
|
+
id: "commentsExtended",
|
|
20014
|
+
path: "word/commentsExtended.xml",
|
|
20015
|
+
pathPattern: null,
|
|
20016
|
+
contentType: `${WORD_ML}.commentsExtended+xml`,
|
|
20017
|
+
// Written by this package through 0.8.0; Word does not recognize it and offers to repair the file.
|
|
20018
|
+
legacyContentTypes: ["application/vnd.ms-word.commentsExtended+xml"],
|
|
20019
|
+
relType: "http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
|
|
20020
|
+
relTarget: "commentsExtended.xml",
|
|
20021
|
+
verified: true
|
|
20022
|
+
},
|
|
20023
|
+
{ 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 },
|
|
20024
|
+
{ 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 },
|
|
20025
|
+
// Not yet produced by any Word fixture (Word only writes people.xml for signed-in authors): unverified.
|
|
20026
|
+
{ 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 },
|
|
20027
|
+
{ 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 },
|
|
20028
|
+
{ 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 }
|
|
20029
|
+
].map((spec) => Object.freeze(spec)));
|
|
20030
|
+
var BY_ID = new Map(PART_SPECS.map((spec) => [spec.id, spec]));
|
|
20031
|
+
function getPartSpec(id) {
|
|
20032
|
+
const spec = BY_ID.get(id);
|
|
20033
|
+
if (!spec) throw new Error(`Unknown package part: ${id}`);
|
|
20034
|
+
return spec;
|
|
20035
|
+
}
|
|
20036
|
+
function toEntryName(partName) {
|
|
20037
|
+
return String(partName || "").replace(/^\/+/, "");
|
|
20038
|
+
}
|
|
20039
|
+
function findPartSpecByPartName(partName) {
|
|
20040
|
+
const name = toEntryName(partName);
|
|
20041
|
+
const lower = name.toLowerCase();
|
|
20042
|
+
return PART_SPECS.find((spec) => spec.path ? spec.path.toLowerCase() === lower : spec.pathPattern?.test(name)) || null;
|
|
20043
|
+
}
|
|
20044
|
+
function correctedContentTypeFor(partName, actualContentType) {
|
|
20045
|
+
const spec = findPartSpecByPartName(partName);
|
|
20046
|
+
if (!spec || !spec.legacyContentTypes.includes(actualContentType)) return null;
|
|
20047
|
+
return spec.contentType;
|
|
20048
|
+
}
|
|
20049
|
+
|
|
19655
20050
|
// services/standalone-docx-plumbing.js
|
|
19656
20051
|
var NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19657
20052
|
var NS_CT = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
19658
20053
|
var NS_RELS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
19659
|
-
var
|
|
19660
|
-
var
|
|
19661
|
-
var
|
|
19662
|
-
var
|
|
19663
|
-
var
|
|
19664
|
-
var
|
|
19665
|
-
var
|
|
19666
|
-
var
|
|
19667
|
-
var
|
|
19668
|
-
var
|
|
20054
|
+
var NUMBERING = getPartSpec("numbering");
|
|
20055
|
+
var COMMENTS = getPartSpec("comments");
|
|
20056
|
+
var COMMENTS_EXTENDED = getPartSpec("commentsExtended");
|
|
20057
|
+
var NUMBERING_REL_TYPE = NUMBERING.relType;
|
|
20058
|
+
var NUMBERING_CONTENT_TYPE = NUMBERING.contentType;
|
|
20059
|
+
var COMMENTS_REL_TYPE = COMMENTS.relType;
|
|
20060
|
+
var COMMENTS_CONTENT_TYPE = COMMENTS.contentType;
|
|
20061
|
+
var COMMENTS_EXTENDED_REL_TYPE = COMMENTS_EXTENDED.relType;
|
|
20062
|
+
var COMMENTS_EXTENDED_CONTENT_TYPE = COMMENTS_EXTENDED.contentType;
|
|
20063
|
+
var DOCUMENT_PATH = getPartSpec("document").path;
|
|
20064
|
+
var NUMBERING_PATH = NUMBERING.path;
|
|
20065
|
+
var COMMENTS_PATH = COMMENTS.path;
|
|
20066
|
+
var COMMENTS_EXTENDED_PATH = COMMENTS_EXTENDED.path;
|
|
19669
20067
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
19670
20068
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
19671
20069
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
@@ -19953,6 +20351,22 @@ async function ensureCommentsExtendedArtifactsInZip(zip, commentsExtendedXml, op
|
|
|
19953
20351
|
}
|
|
19954
20352
|
}
|
|
19955
20353
|
}
|
|
20354
|
+
async function repairKnownContentTypes(zip) {
|
|
20355
|
+
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
20356
|
+
if (!ctText) return [];
|
|
20357
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
20358
|
+
const repairs = [];
|
|
20359
|
+
for (const override of Array.from(ctDoc.getElementsByTagNameNS("*", "Override"))) {
|
|
20360
|
+
const partName = override.getAttribute("PartName") || "";
|
|
20361
|
+
const actual = override.getAttribute("ContentType") || "";
|
|
20362
|
+
const expected = correctedContentTypeFor(partName, actual);
|
|
20363
|
+
if (!expected) continue;
|
|
20364
|
+
override.setAttribute("ContentType", expected);
|
|
20365
|
+
repairs.push({ partName, from: actual, to: expected });
|
|
20366
|
+
}
|
|
20367
|
+
if (repairs.length > 0) zip.file(CONTENT_TYPES_PATH, createSerializer().serializeToString(ctDoc));
|
|
20368
|
+
return repairs;
|
|
20369
|
+
}
|
|
19956
20370
|
async function validateDocxPackage(zip) {
|
|
19957
20371
|
const documentXml = await readZipText(zip, DOCUMENT_PATH);
|
|
19958
20372
|
if (!documentXml) {
|
|
@@ -20034,9 +20448,8 @@ async function validateDocxPackage(zip) {
|
|
|
20034
20448
|
const paraIdToCommentId = /* @__PURE__ */ new Map();
|
|
20035
20449
|
for (const comment of Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
20036
20450
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
20037
|
-
const
|
|
20038
|
-
|
|
20039
|
-
if (paraId) paraIdToCommentId.set(paraId.toUpperCase(), id);
|
|
20451
|
+
const paraId = commentParaId(comment);
|
|
20452
|
+
if (paraId) paraIdToCommentId.set(paraId, id);
|
|
20040
20453
|
}
|
|
20041
20454
|
const knownParaIds = new Set(paraIdToCommentId.keys());
|
|
20042
20455
|
const extendedParaIds = /* @__PURE__ */ new Set();
|
|
@@ -20097,10 +20510,39 @@ async function validateDocxPackage(zip) {
|
|
|
20097
20510
|
throw new Error("Validation failed: comments rel missing");
|
|
20098
20511
|
}
|
|
20099
20512
|
}
|
|
20513
|
+
{
|
|
20514
|
+
const relationshipById = /* @__PURE__ */ new Map();
|
|
20515
|
+
for (const rel of Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20516
|
+
relationshipById.set(rel.getAttribute("Id") || "", { type: rel.getAttribute("Type") || "", target: rel.getAttribute("Target") || "" });
|
|
20517
|
+
}
|
|
20518
|
+
const headerSpec = getPartSpec("header");
|
|
20519
|
+
const footerSpec = getPartSpec("footer");
|
|
20520
|
+
const overrideTypeByPart = new Map(Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).map((override) => [(override.getAttribute("PartName") || "").toLowerCase(), override.getAttribute("ContentType") || ""]));
|
|
20521
|
+
for (const reference of [
|
|
20522
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "headerReference")),
|
|
20523
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "footerReference"))
|
|
20524
|
+
]) {
|
|
20525
|
+
const relId = reference.getAttributeNS?.("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") || reference.getAttribute("r:id") || "";
|
|
20526
|
+
const spec = reference.localName === "headerReference" ? headerSpec : footerSpec;
|
|
20527
|
+
const relationship = relationshipById.get(relId);
|
|
20528
|
+
if (!relationship) throw new Error(`Validation failed: ${reference.localName} ${relId || "(no r:id)"} has no document relationship`);
|
|
20529
|
+
if (relationship.type !== spec.relType) throw new Error(`Validation failed: ${reference.localName} ${relId} points at a relationship of the wrong type (${relationship.type})`);
|
|
20530
|
+
const partName = relationship.target.startsWith("/") ? relationship.target : `/word/${relationship.target}`;
|
|
20531
|
+
const partXml = await readZipText(zip, partName.slice(1));
|
|
20532
|
+
if (!partXml) throw new Error(`Validation failed: ${reference.localName} ${relId} targets a missing part: ${partName}`);
|
|
20533
|
+
parseXmlStrictStandalone(partXml, partName.slice(1));
|
|
20534
|
+
const contentType = overrideTypeByPart.get(partName.toLowerCase());
|
|
20535
|
+
if (contentType !== spec.contentType) throw new Error(`Validation failed: ${partName} content type is ${contentType ? `'${contentType}'` : "missing"}; Word expects '${spec.contentType}'`);
|
|
20536
|
+
}
|
|
20537
|
+
}
|
|
20100
20538
|
if (commentsExtendedXml) {
|
|
20101
|
-
const
|
|
20102
|
-
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
20539
|
+
const extendedOverride = Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).find(
|
|
20540
|
+
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
20103
20541
|
);
|
|
20542
|
+
if (extendedOverride && extendedOverride.getAttribute("ContentType") !== COMMENTS_EXTENDED_CONTENT_TYPE) {
|
|
20543
|
+
throw new Error(`Validation failed: commentsExtended CT override has wrong content type '${extendedOverride.getAttribute("ContentType")}' (Word expects '${COMMENTS_EXTENDED_CONTENT_TYPE}')`);
|
|
20544
|
+
}
|
|
20545
|
+
const hasContentType = !!extendedOverride;
|
|
20104
20546
|
const hasRelationship = Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship")).some(
|
|
20105
20547
|
(rel) => (rel.getAttribute("Type") || "") === COMMENTS_EXTENDED_REL_TYPE
|
|
20106
20548
|
);
|
|
@@ -20464,6 +20906,156 @@ function reconcileReceiptsAgainstOutput(parts, receipts) {
|
|
|
20464
20906
|
return { valid: true };
|
|
20465
20907
|
}
|
|
20466
20908
|
|
|
20909
|
+
// services/headers-footers.js
|
|
20910
|
+
var NS_W8 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20911
|
+
var NS_R2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
20912
|
+
var REVISION_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo", "rPrChange", "pPrChange", "sectPrChange", "tblPrChange", "tblGridChange", "tcPrChange", "trPrChange", "numberingChange"]);
|
|
20913
|
+
var REVISION_CONTENT_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
20914
|
+
var attrOf2 = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
20915
|
+
function resolveTarget(target) {
|
|
20916
|
+
const clean = String(target || "");
|
|
20917
|
+
if (clean.startsWith("/")) return clean.replace(/^\/+/, "");
|
|
20918
|
+
const parts = ["word"];
|
|
20919
|
+
for (const segment of clean.split("/")) {
|
|
20920
|
+
if (segment === "..") parts.pop();
|
|
20921
|
+
else if (segment && segment !== ".") parts.push(segment);
|
|
20922
|
+
}
|
|
20923
|
+
return parts.join("/");
|
|
20924
|
+
}
|
|
20925
|
+
function discoverHeaderFooterParts({ documentXml, relsXml = null, settingsXml = null }) {
|
|
20926
|
+
if (!documentXml || !relsXml) return [];
|
|
20927
|
+
const documentParsed = parseOoxmlSafe(documentXml, "application/xml");
|
|
20928
|
+
const relsParsed = parseOoxmlSafe(relsXml, "application/xml");
|
|
20929
|
+
if (!documentParsed.doc || documentParsed.error || !relsParsed.doc || relsParsed.error) return [];
|
|
20930
|
+
const targetByRelId = /* @__PURE__ */ new Map();
|
|
20931
|
+
for (const rel of Array.from(relsParsed.doc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20932
|
+
targetByRelId.set(rel.getAttribute("Id") || "", rel.getAttribute("Target") || "");
|
|
20933
|
+
}
|
|
20934
|
+
const evenAndOdd = !!settingsXml && /<w:evenAndOddHeaders\b(?![^>]*w:val="(?:0|false|off)")/.test(settingsXml);
|
|
20935
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
20936
|
+
const inForce = /* @__PURE__ */ new Map();
|
|
20937
|
+
const sectPrs = Array.from(documentParsed.doc.getElementsByTagNameNS(NS_W8, "sectPr"));
|
|
20938
|
+
sectPrs.forEach((sectPr, sectionIndex) => {
|
|
20939
|
+
const titlePg = Array.from(sectPr.childNodes || []).some((child) => child.localName === "titlePg" && !/^(0|false|off)$/i.test(attrOf2(child, "w:val", "val")));
|
|
20940
|
+
for (const ref of Array.from(sectPr.childNodes || [])) {
|
|
20941
|
+
if (ref.nodeType !== 1 || ref.localName !== "headerReference" && ref.localName !== "footerReference") continue;
|
|
20942
|
+
const relId = ref.getAttributeNS?.(NS_R2, "id") || ref.getAttribute("r:id") || "";
|
|
20943
|
+
const target = targetByRelId.get(relId);
|
|
20944
|
+
if (!target) continue;
|
|
20945
|
+
const path = resolveTarget(target);
|
|
20946
|
+
const kind = ref.localName === "headerReference" ? "header" : "footer";
|
|
20947
|
+
const type = attrOf2(ref, "w:type", "type") || "default";
|
|
20948
|
+
const entry = byPath.get(path) || { path, kind, type, sections: [], applies: [], titlePgSections: 0 };
|
|
20949
|
+
if (!entry.sections.includes(sectionIndex)) entry.sections.push(sectionIndex);
|
|
20950
|
+
if (titlePg) entry.titlePgSections += 1;
|
|
20951
|
+
byPath.set(path, entry);
|
|
20952
|
+
inForce.set(`${kind}:${type}`, path);
|
|
20953
|
+
}
|
|
20954
|
+
for (const path of inForce.values()) {
|
|
20955
|
+
const entry = byPath.get(path);
|
|
20956
|
+
if (entry && !entry.applies.includes(sectionIndex)) entry.applies.push(sectionIndex);
|
|
20957
|
+
}
|
|
20958
|
+
});
|
|
20959
|
+
return Array.from(byPath.values()).map((entry) => ({
|
|
20960
|
+
path: entry.path,
|
|
20961
|
+
kind: entry.kind,
|
|
20962
|
+
type: entry.type,
|
|
20963
|
+
sections: entry.sections,
|
|
20964
|
+
appliesToSections: entry.applies,
|
|
20965
|
+
sharedBySections: entry.applies.length > 1,
|
|
20966
|
+
// first-page parts only show with w:titlePg; even parts only with w:evenAndOddHeaders in settings.xml.
|
|
20967
|
+
active: entry.type === "first" ? entry.titlePgSections > 0 : entry.type === "even" ? evenAndOdd : true
|
|
20968
|
+
}));
|
|
20969
|
+
}
|
|
20970
|
+
function resolvePartSelector(parts, selector) {
|
|
20971
|
+
const describe = (part) => ({ path: part.path, kind: part.kind, type: part.type, sections: part.sections, appliesToSections: part.appliesToSections });
|
|
20972
|
+
if (typeof selector === "string") {
|
|
20973
|
+
const wanted = selector.replace(/^\/+/, "").toLowerCase();
|
|
20974
|
+
const match = parts.find((part) => part.path.toLowerCase() === wanted || part.path.toLowerCase() === `word/${wanted}`);
|
|
20975
|
+
if (match) return { part: match };
|
|
20976
|
+
return { error: { code: "PART_NOT_FOUND", message: `No header/footer part '${selector}' is referenced by the document.`, candidates: parts.map(describe) } };
|
|
20977
|
+
}
|
|
20978
|
+
if (!selector || typeof selector !== "object" || selector.kind !== "header" && selector.kind !== "footer") {
|
|
20979
|
+
return { error: { code: "INVALID_OPERATION", message: 'part must be a part path or { kind: "header"|"footer", type?: "default"|"first"|"even", section?: number }.' } };
|
|
20980
|
+
}
|
|
20981
|
+
const type = selector.type || "default";
|
|
20982
|
+
if (!["default", "first", "even"].includes(type)) {
|
|
20983
|
+
return { error: { code: "INVALID_OPERATION", message: `part.type must be default, first or even (got '${type}').` } };
|
|
20984
|
+
}
|
|
20985
|
+
let matches = parts.filter((part) => part.kind === selector.kind && part.type === type);
|
|
20986
|
+
if (Number.isInteger(selector.section)) matches = matches.filter((part) => part.appliesToSections.includes(selector.section));
|
|
20987
|
+
if (matches.length === 1) return { part: matches[0] };
|
|
20988
|
+
if (matches.length === 0) {
|
|
20989
|
+
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) } };
|
|
20990
|
+
}
|
|
20991
|
+
return { error: { code: "PART_AMBIGUOUS", message: `Several ${type} ${selector.kind} parts exist; add "section" or use the part path.`, candidates: matches.map(describe) } };
|
|
20992
|
+
}
|
|
20993
|
+
function isHeaderFooterPath(path) {
|
|
20994
|
+
const spec = findPartSpecByPartName(path);
|
|
20995
|
+
return spec?.id === "header" || spec?.id === "footer";
|
|
20996
|
+
}
|
|
20997
|
+
function countRevisionsInsideFields(xml) {
|
|
20998
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
20999
|
+
if (!parsed.doc || parsed.error) return 0;
|
|
21000
|
+
let depth = 0;
|
|
21001
|
+
let count = 0;
|
|
21002
|
+
const visit = (node) => {
|
|
21003
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
21004
|
+
if (child.nodeType !== 1) continue;
|
|
21005
|
+
const name = child.localName;
|
|
21006
|
+
if (name === "fldChar") {
|
|
21007
|
+
const type = attrOf2(child, "w:fldCharType", "fldCharType");
|
|
21008
|
+
if (type === "begin") depth += 1;
|
|
21009
|
+
else if (type === "end") depth = Math.max(0, depth - 1);
|
|
21010
|
+
continue;
|
|
21011
|
+
}
|
|
21012
|
+
if (REVISION_CONTENT_ELEMENTS.has(name) && depth > 0) count += 1;
|
|
21013
|
+
if (name === "fldSimple") {
|
|
21014
|
+
depth += 1;
|
|
21015
|
+
visit(child);
|
|
21016
|
+
depth -= 1;
|
|
21017
|
+
continue;
|
|
21018
|
+
}
|
|
21019
|
+
visit(child);
|
|
21020
|
+
}
|
|
21021
|
+
};
|
|
21022
|
+
visit(parsed.doc.documentElement);
|
|
21023
|
+
return count;
|
|
21024
|
+
}
|
|
21025
|
+
function collectRevisionIds(xml) {
|
|
21026
|
+
const ids = /* @__PURE__ */ new Set();
|
|
21027
|
+
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]);
|
|
21028
|
+
return ids;
|
|
21029
|
+
}
|
|
21030
|
+
function renumberCollidingRevisionIds(xml, taken) {
|
|
21031
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
21032
|
+
if (!parsed.doc || parsed.error) return { xml, renumbered: 0, idMap: /* @__PURE__ */ new Map() };
|
|
21033
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
21034
|
+
let next = 1 + Math.max(0, ...Array.from(taken, (id) => Number(id)).filter(Number.isFinite));
|
|
21035
|
+
const used = new Set(taken);
|
|
21036
|
+
let renumbered = 0;
|
|
21037
|
+
const visit = (node) => {
|
|
21038
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
21039
|
+
if (child.nodeType !== 1) continue;
|
|
21040
|
+
if (REVISION_ELEMENTS.has(child.localName)) {
|
|
21041
|
+
const id = attrOf2(child, "w:id", "id");
|
|
21042
|
+
if (id && used.has(id)) {
|
|
21043
|
+
while (used.has(String(next))) next += 1;
|
|
21044
|
+
child.setAttributeNS(NS_W8, "w:id", String(next));
|
|
21045
|
+
idMap.set(id, String(next));
|
|
21046
|
+
used.add(String(next));
|
|
21047
|
+
renumbered += 1;
|
|
21048
|
+
} else if (id) {
|
|
21049
|
+
used.add(id);
|
|
21050
|
+
}
|
|
21051
|
+
}
|
|
21052
|
+
visit(child);
|
|
21053
|
+
}
|
|
21054
|
+
};
|
|
21055
|
+
visit(parsed.doc.documentElement);
|
|
21056
|
+
return renumbered > 0 ? { xml: createSerializer().serializeToString(parsed.doc), renumbered, idMap } : { xml, renumbered: 0, idMap };
|
|
21057
|
+
}
|
|
21058
|
+
|
|
20467
21059
|
// services/error-recovery.js
|
|
20468
21060
|
var ERROR_RECOVERY_VERSION = 1;
|
|
20469
21061
|
var RULES = Object.freeze({
|
|
@@ -20501,6 +21093,12 @@ var RULES = Object.freeze({
|
|
|
20501
21093
|
STRUCTURED_CONTENT_INVALID: ["request", "request_fixable", "change_request"],
|
|
20502
21094
|
UNSUPPORTED_INSERTION_AFFINITY: ["request", "request_fixable", "change_request"],
|
|
20503
21095
|
EXISTING_REVISIONS: ["target-safety", "policy_choice_required", "set_option"],
|
|
21096
|
+
PART_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
21097
|
+
PART_AMBIGUOUS: ["target-resolution", "candidate_selection_required", "choose_candidate"],
|
|
21098
|
+
COMMENT_IN_HEADER_FOOTER: ["request", "request_fixable", "change_request"],
|
|
21099
|
+
FIELD_EDIT_REFUSED: ["target-safety", "request_fixable", "change_request"],
|
|
21100
|
+
COMMENT_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
21101
|
+
PARENT_ANCHOR_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20504
21102
|
COMMENTED_CONTENT_MERGE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20505
21103
|
COMMENTED_CONTENT_DELETE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20506
21104
|
OVERLAPPING_SOURCE_TARGETS: ["batch-compilation", "source_conflict", "consolidate_operations"],
|
|
@@ -20953,6 +21551,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
20953
21551
|
"delete",
|
|
20954
21552
|
"comment",
|
|
20955
21553
|
"comment_reply",
|
|
21554
|
+
"comment_resolve",
|
|
20956
21555
|
"highlight"
|
|
20957
21556
|
]);
|
|
20958
21557
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
@@ -20971,11 +21570,14 @@ function isRecord(value) {
|
|
|
20971
21570
|
function nonEmptyString(value) {
|
|
20972
21571
|
return typeof value === "string" && value.trim().length > 0;
|
|
20973
21572
|
}
|
|
21573
|
+
function isCommentThreadOperationKind(kind) {
|
|
21574
|
+
return kind === "comment_reply" || kind === "comment_resolve";
|
|
21575
|
+
}
|
|
20974
21576
|
function getCanonicalOperationType(operation) {
|
|
20975
21577
|
const type = operation?.type;
|
|
20976
21578
|
if (type === "insert" && operation?.target?.revisionView === "rejected") return "rejected-insert";
|
|
20977
21579
|
if (type === "restore") return "restore";
|
|
20978
|
-
if (type === "comment" || type === "comment_reply" || type === "highlight") return type;
|
|
21580
|
+
if (type === "comment" || type === "comment_reply" || type === "comment_resolve" || type === "highlight") return type;
|
|
20979
21581
|
if (type === "paragraph-format") return "paragraph-format";
|
|
20980
21582
|
if (type === "character-format" || type === "format" && (operation?.textToFormat != null || operation?.properties != null)) return "format";
|
|
20981
21583
|
return "redline";
|
|
@@ -21095,7 +21697,7 @@ function validateDocumentOperation(operation) {
|
|
|
21095
21697
|
};
|
|
21096
21698
|
}
|
|
21097
21699
|
const target = normalized.targetDescriptor;
|
|
21098
|
-
if (normalized.operationKind
|
|
21700
|
+
if (!isCommentThreadOperationKind(normalized.operationKind) && !nonEmptyString(target.text) && target.index == null && !target.paragraphId && !target.fingerprint && !target.captureRef) {
|
|
21099
21701
|
return {
|
|
21100
21702
|
valid: false,
|
|
21101
21703
|
error: {
|
|
@@ -21322,6 +21924,14 @@ function validateDocumentOperation(operation) {
|
|
|
21322
21924
|
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment reply operations require a non-empty "commentContent" field.' } };
|
|
21323
21925
|
}
|
|
21324
21926
|
}
|
|
21927
|
+
if (normalized.operationKind === "comment_resolve") {
|
|
21928
|
+
if (!nonEmptyString(String(normalized.commentId ?? ""))) {
|
|
21929
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve operations require a "commentId".' } };
|
|
21930
|
+
}
|
|
21931
|
+
if (normalized.resolved != null && typeof normalized.resolved !== "boolean") {
|
|
21932
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve "resolved" must be a boolean when provided.' } };
|
|
21933
|
+
}
|
|
21934
|
+
}
|
|
21325
21935
|
if (normalized.operationKind === "highlight") {
|
|
21326
21936
|
if (!nonEmptyString(normalized.textToHighlight)) {
|
|
21327
21937
|
return {
|
|
@@ -21801,7 +22411,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
21801
22411
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
21802
22412
|
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
21803
22413
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
21804
|
-
if (validation.valid && operation.operationKind !== "comment_reply" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
22414
|
+
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
21805
22415
|
try {
|
|
21806
22416
|
const start = resolveDescriptor(
|
|
21807
22417
|
xmlDoc,
|
|
@@ -22302,12 +22912,12 @@ function resolveTargetFromCapture(xmlDoc, session, targetDescriptor, _opType = "
|
|
|
22302
22912
|
}
|
|
22303
22913
|
|
|
22304
22914
|
// services/document-operation-mutations.js
|
|
22305
|
-
var
|
|
22915
|
+
var NS_W9 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
22306
22916
|
var NS_W142 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
22307
22917
|
function getCommentIdsInElement(element) {
|
|
22308
22918
|
const ids = /* @__PURE__ */ new Set();
|
|
22309
22919
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22310
|
-
for (const node of Array.from(element?.getElementsByTagNameNS?.(
|
|
22920
|
+
for (const node of Array.from(element?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22311
22921
|
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
22312
22922
|
if (id !== "") ids.add(id);
|
|
22313
22923
|
}
|
|
@@ -22521,7 +23131,7 @@ function normalizeBodySectionOrder(xmlDoc) {
|
|
|
22521
23131
|
normalizeBodySectionOrderStandalone(xmlDoc);
|
|
22522
23132
|
}
|
|
22523
23133
|
function removeProofErrNodes(paragraph) {
|
|
22524
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
23134
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "proofErr") || [])) {
|
|
22525
23135
|
node.parentNode?.removeChild(node);
|
|
22526
23136
|
}
|
|
22527
23137
|
}
|
|
@@ -22532,12 +23142,12 @@ function preprocessRedlineTargetParagraph(targetParagraph) {
|
|
|
22532
23142
|
function getDirectWordChild4(element, localName2) {
|
|
22533
23143
|
if (!element) return null;
|
|
22534
23144
|
return Array.from(element.childNodes || []).find(
|
|
22535
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
23145
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === localName2
|
|
22536
23146
|
) || null;
|
|
22537
23147
|
}
|
|
22538
23148
|
function computeTableIndexInDocument(xmlDoc, tableElement) {
|
|
22539
23149
|
if (!xmlDoc || !tableElement) return null;
|
|
22540
|
-
const tables = Array.from(xmlDoc.getElementsByTagNameNS(
|
|
23150
|
+
const tables = Array.from(xmlDoc.getElementsByTagNameNS(NS_W9, "tbl"));
|
|
22541
23151
|
const idx = tables.indexOf(tableElement);
|
|
22542
23152
|
return idx >= 0 ? idx + 1 : null;
|
|
22543
23153
|
}
|
|
@@ -22587,7 +23197,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22587
23197
|
markParagraphMarkInserted(xmlDoc, paragraph, author);
|
|
22588
23198
|
}
|
|
22589
23199
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22590
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
23200
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22591
23201
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22592
23202
|
if (anchorRunPr) {
|
|
22593
23203
|
run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
@@ -22612,7 +23222,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22612
23222
|
}
|
|
22613
23223
|
function serializeParagraphRangeAsDocument(paragraphs, serializer) {
|
|
22614
23224
|
const paragraphXml = (paragraphs || []).map((paragraph) => serializer.serializeToString(paragraph)).join("");
|
|
22615
|
-
return `<w:document xmlns:w="${
|
|
23225
|
+
return `<w:document xmlns:w="${NS_W9}"><w:body>${paragraphXml}</w:body></w:document>`;
|
|
22616
23226
|
}
|
|
22617
23227
|
function applyExplicitRangeListInsertions({
|
|
22618
23228
|
xmlDoc,
|
|
@@ -22681,7 +23291,7 @@ function buildEmptyParagraphTemplateFromAnchor(xmlDoc, anchorParagraph) {
|
|
|
22681
23291
|
const anchorPPr = getDirectWordChild4(anchorParagraph, "pPr");
|
|
22682
23292
|
if (anchorPPr) paragraph.appendChild(clonePropertiesWithoutRevisionHistory(anchorPPr));
|
|
22683
23293
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22684
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
23294
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22685
23295
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22686
23296
|
if (anchorRunPr) run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
22687
23297
|
const textNode = createWordElement(xmlDoc, "w:t");
|
|
@@ -22704,7 +23314,7 @@ function wrapParagraphContentInInsertion(xmlDoc, paragraph, revisionMetadata, au
|
|
|
22704
23314
|
ins.setAttribute("w:author", metadata.author);
|
|
22705
23315
|
ins.setAttribute("w:date", metadata.date);
|
|
22706
23316
|
for (const child of Array.from(paragraph.childNodes || [])) {
|
|
22707
|
-
if (child?.nodeType === 1 && child.namespaceURI ===
|
|
23317
|
+
if (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22708
23318
|
ins.appendChild(child.cloneNode(true));
|
|
22709
23319
|
}
|
|
22710
23320
|
wrappedParagraph.appendChild(ins);
|
|
@@ -22728,7 +23338,7 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
22728
23338
|
if (typeof markdownResult?.oxml === "string") {
|
|
22729
23339
|
const extracted = extractReplacementNodes(markdownResult.oxml);
|
|
22730
23340
|
sourceParagraph = (extracted.replacementNodes || []).find(
|
|
22731
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
23341
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p"
|
|
22732
23342
|
) || null;
|
|
22733
23343
|
}
|
|
22734
23344
|
if (!sourceParagraph) {
|
|
@@ -22760,7 +23370,7 @@ function collectNumberingIdsFromNodes(nodes) {
|
|
|
22760
23370
|
}
|
|
22761
23371
|
function directDeletionCarrierText(carrier) {
|
|
22762
23372
|
return Array.from(carrier?.childNodes || []).reduce((text2, child) => {
|
|
22763
|
-
return text2 + (child?.nodeType === 1 && child.namespaceURI ===
|
|
23373
|
+
return text2 + (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r" ? getRunContentPieces(child).map((piece) => piece.text).join("") : "");
|
|
22764
23374
|
}, "");
|
|
22765
23375
|
}
|
|
22766
23376
|
function findOccurrenceOffset(text2, needle, occurrence) {
|
|
@@ -22802,9 +23412,9 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22802
23412
|
}
|
|
22803
23413
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22804
23414
|
if (child.nodeType !== 1) continue;
|
|
22805
|
-
if (!(child.namespaceURI ===
|
|
23415
|
+
if (!(child.namespaceURI === NS_W9 && child.localName === "r")) return true;
|
|
22806
23416
|
for (const runChild of Array.from(child.childNodes || [])) {
|
|
22807
|
-
if (runChild.nodeType !== 1 || runChild.namespaceURI !==
|
|
23417
|
+
if (runChild.nodeType !== 1 || runChild.namespaceURI !== NS_W9) continue;
|
|
22808
23418
|
if (!["rPr", "delText", "t", "tab", "br", "cr", "noBreakHyphen", "softHyphen"].includes(runChild.localName)) return true;
|
|
22809
23419
|
}
|
|
22810
23420
|
}
|
|
@@ -22813,7 +23423,7 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22813
23423
|
function insertedRunPropertiesAtDeletionOffset(carrier, localOffset) {
|
|
22814
23424
|
let offset = 0;
|
|
22815
23425
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22816
|
-
if (!(child?.nodeType === 1 && child.namespaceURI ===
|
|
23426
|
+
if (!(child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r")) continue;
|
|
22817
23427
|
const length = getRunTextLength(getRunContentPieces(child));
|
|
22818
23428
|
if (localOffset <= offset + length) {
|
|
22819
23429
|
return clonePropertiesWithoutRevisionHistory(getDirectWordChild4(child, "rPr"));
|
|
@@ -22843,7 +23453,7 @@ function normalizedAuthor2(author) {
|
|
|
22843
23453
|
function directWordParagraphSibling(paragraph, direction = "nextSibling") {
|
|
22844
23454
|
let cursor = paragraph?.[direction] || null;
|
|
22845
23455
|
while (cursor) {
|
|
22846
|
-
if (cursor.nodeType === 1 && cursor.namespaceURI ===
|
|
23456
|
+
if (cursor.nodeType === 1 && cursor.namespaceURI === NS_W9 && cursor.localName === "p") return cursor;
|
|
22847
23457
|
cursor = cursor[direction] || null;
|
|
22848
23458
|
}
|
|
22849
23459
|
return null;
|
|
@@ -22853,7 +23463,7 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22853
23463
|
const pPr = createWordElement(xmlDoc, "w:pPr");
|
|
22854
23464
|
if (!sourcePPr) return pPr;
|
|
22855
23465
|
for (const child of Array.from(sourcePPr.childNodes || [])) {
|
|
22856
|
-
if (child.nodeType !== 1 || child.namespaceURI !==
|
|
23466
|
+
if (child.nodeType !== 1 || child.namespaceURI !== NS_W9) continue;
|
|
22857
23467
|
if (!RESTORATION_PPR_ALLOWLIST.has(child.localName)) continue;
|
|
22858
23468
|
pPr.appendChild(xmlDoc.importNode(child, true));
|
|
22859
23469
|
}
|
|
@@ -22862,14 +23472,14 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22862
23472
|
function collectRestorationAnchorWarnings(sourceParagraph) {
|
|
22863
23473
|
const warnings = [];
|
|
22864
23474
|
const bookmarkNames = /* @__PURE__ */ new Set();
|
|
22865
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23475
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, "bookmarkStart") || [])) {
|
|
22866
23476
|
const name = wordAttribute4(node, "name") || "(unnamed)";
|
|
22867
23477
|
bookmarkNames.add(name);
|
|
22868
23478
|
}
|
|
22869
23479
|
for (const name of bookmarkNames) warnings.push(`RESTORATION_DROPPED_BOOKMARK:${name}`);
|
|
22870
23480
|
const commentIds = /* @__PURE__ */ new Set();
|
|
22871
23481
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22872
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23482
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22873
23483
|
const id = wordAttribute4(node, "id");
|
|
22874
23484
|
if (id !== "") commentIds.add(id);
|
|
22875
23485
|
}
|
|
@@ -22886,24 +23496,24 @@ function removeRestorationAnchors(paragraph) {
|
|
|
22886
23496
|
"commentReference"
|
|
22887
23497
|
];
|
|
22888
23498
|
for (const localName2 of anchorNames) {
|
|
22889
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
23499
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22890
23500
|
node.parentNode?.removeChild(node);
|
|
22891
23501
|
}
|
|
22892
23502
|
}
|
|
22893
|
-
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
22894
|
-
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI ===
|
|
23503
|
+
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "r") || [])) {
|
|
23504
|
+
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName !== "rPr");
|
|
22895
23505
|
if (!meaningful) run.parentNode?.removeChild(run);
|
|
22896
23506
|
}
|
|
22897
23507
|
}
|
|
22898
23508
|
function buildRejectedRestorationTemplate(xmlDoc, sourceParagraph, serializer) {
|
|
22899
23509
|
const rejected = rejectTrackedChangesInOoxml(serializer.serializeToString(sourceParagraph), { allAuthors: true });
|
|
22900
23510
|
const parsed = parseOoxmlSafe(rejected.oxml, "application/xml");
|
|
22901
|
-
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(
|
|
23511
|
+
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(NS_W9, "p")?.[0] || null;
|
|
22902
23512
|
if (!rejectedParagraph) return null;
|
|
22903
23513
|
const paragraph = createWordElement(xmlDoc, "w:p");
|
|
22904
23514
|
paragraph.appendChild(createSanitizedRestorationPPr(xmlDoc, sourceParagraph));
|
|
22905
23515
|
for (const child of Array.from(rejectedParagraph.childNodes || [])) {
|
|
22906
|
-
if (child.nodeType === 1 && child.namespaceURI ===
|
|
23516
|
+
if (child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22907
23517
|
paragraph.appendChild(xmlDoc.importNode(child, true));
|
|
22908
23518
|
}
|
|
22909
23519
|
removeRestorationAnchors(paragraph);
|
|
@@ -22924,7 +23534,7 @@ async function editRestorationTemplate(xmlDoc, template, modifiedText, author, s
|
|
|
22924
23534
|
);
|
|
22925
23535
|
if (result?.status === "error" || typeof result?.oxml !== "string") return null;
|
|
22926
23536
|
const extracted = extractReplacementNodes(result.oxml);
|
|
22927
|
-
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23537
|
+
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p");
|
|
22928
23538
|
return paragraph ? xmlDoc.importNode(paragraph, true) : null;
|
|
22929
23539
|
}
|
|
22930
23540
|
function allocateFreshParagraphId(xmlDoc, operationSession) {
|
|
@@ -23095,7 +23705,7 @@ async function insertIntoRejectedDeletedText(documentXml, targetText, anchor, mo
|
|
|
23095
23705
|
}
|
|
23096
23706
|
const insertionOffset = anchorStart + anchor.offset;
|
|
23097
23707
|
const carriers = Array.from(paragraph.childNodes || []).filter(
|
|
23098
|
-
(node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23708
|
+
(node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "del"
|
|
23099
23709
|
);
|
|
23100
23710
|
if (carriers.some((carrier) => hasUnsupportedDeletionSplitMarkup(paragraph, carrier))) {
|
|
23101
23711
|
return {
|
|
@@ -23752,7 +24362,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23752
24362
|
};
|
|
23753
24363
|
}
|
|
23754
24364
|
if (!allSame) {
|
|
23755
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24365
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23756
24366
|
if (hasMove) {
|
|
23757
24367
|
return {
|
|
23758
24368
|
documentXml,
|
|
@@ -23767,8 +24377,8 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23767
24377
|
}
|
|
23768
24378
|
}
|
|
23769
24379
|
} else {
|
|
23770
|
-
const hasDel = targetParagraph.getElementsByTagNameNS(
|
|
23771
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24380
|
+
const hasDel = targetParagraph.getElementsByTagNameNS(NS_W9, "del").length > 0;
|
|
24381
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23772
24382
|
if (hasDel) {
|
|
23773
24383
|
return {
|
|
23774
24384
|
documentXml,
|
|
@@ -24287,7 +24897,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24287
24897
|
const targetParagraph = resolved.paragraph;
|
|
24288
24898
|
const collectVisibleRuns = () => {
|
|
24289
24899
|
const runEntries2 = [];
|
|
24290
|
-
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(
|
|
24900
|
+
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(NS_W9, "r"));
|
|
24291
24901
|
for (const run of allRuns) {
|
|
24292
24902
|
let parent = run.parentNode;
|
|
24293
24903
|
let isDel = false;
|
|
@@ -24379,7 +24989,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24379
24989
|
const targetRuns = finalRuns.filter((span) => span.start >= matchStart && span.end <= matchEnd);
|
|
24380
24990
|
let anyChanged = false;
|
|
24381
24991
|
for (const entry of targetRuns) {
|
|
24382
|
-
const rPr = entry.run.getElementsByTagNameNS(
|
|
24992
|
+
const rPr = entry.run.getElementsByTagNameNS(NS_W9, "rPr")[0] || null;
|
|
24383
24993
|
if (checkRunPropertiesChanged(rPr, properties)) {
|
|
24384
24994
|
anyChanged = true;
|
|
24385
24995
|
break;
|
|
@@ -24390,7 +25000,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24390
25000
|
}
|
|
24391
25001
|
for (const entry of targetRuns) {
|
|
24392
25002
|
const run = entry.run;
|
|
24393
|
-
let rPr = run.getElementsByTagNameNS(
|
|
25003
|
+
let rPr = run.getElementsByTagNameNS(NS_W9, "rPr")[0];
|
|
24394
25004
|
if (!rPr) {
|
|
24395
25005
|
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
24396
25006
|
run.insertBefore(rPr, run.firstChild);
|
|
@@ -24399,7 +25009,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24399
25009
|
let insideSameAuthorIns = false;
|
|
24400
25010
|
while (insAncestor && insAncestor !== targetParagraph) {
|
|
24401
25011
|
if (insAncestor.nodeType === 1 && (insAncestor.localName === "ins" || insAncestor.nodeName === "w:ins")) {
|
|
24402
|
-
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(
|
|
25012
|
+
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(NS_W9, "author");
|
|
24403
25013
|
if (insAuthor === (author || getDefaultAuthor())) {
|
|
24404
25014
|
insideSameAuthorIns = true;
|
|
24405
25015
|
}
|
|
@@ -24447,7 +25057,7 @@ async function applyParagraphFormatToParagraphByExactText(documentXml, targetTex
|
|
|
24447
25057
|
return { documentXml, hasChanges: false, status: "error", error: resolved?.error || { code: "TARGET_NOT_FOUND", message: "Target paragraph not found." } };
|
|
24448
25058
|
}
|
|
24449
25059
|
const targetParagraph = resolved.paragraph;
|
|
24450
|
-
let pPr = targetParagraph.getElementsByTagNameNS(
|
|
25060
|
+
let pPr = targetParagraph.getElementsByTagNameNS(NS_W9, "pPr")[0];
|
|
24451
25061
|
if (!pPr) {
|
|
24452
25062
|
pPr = createWordElement(xmlDoc, "w:pPr");
|
|
24453
25063
|
targetParagraph.insertBefore(pPr, targetParagraph.firstChild);
|
|
@@ -24495,7 +25105,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24495
25105
|
}
|
|
24496
25106
|
let operation = validation.operation || normalizeDocumentOperation(op);
|
|
24497
25107
|
const authorUsed = resolveDocumentOperationAuthor(operation, author, getDefaultAuthor());
|
|
24498
|
-
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
25108
|
+
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
24499
25109
|
return {
|
|
24500
25110
|
documentXml,
|
|
24501
25111
|
hasChanges: false,
|
|
@@ -24662,6 +25272,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24662
25272
|
result = applyCommentReplyToParts({
|
|
24663
25273
|
commentsXml: existingCommentsXml,
|
|
24664
25274
|
commentsExtendedXml: existingExtendedXml,
|
|
25275
|
+
documentXml,
|
|
24665
25276
|
parentCommentId: operation.parentCommentId,
|
|
24666
25277
|
commentId,
|
|
24667
25278
|
commentContent: operation.commentContent,
|
|
@@ -24669,8 +25280,30 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24669
25280
|
date: operation.date || (/* @__PURE__ */ new Date()).toISOString()
|
|
24670
25281
|
});
|
|
24671
25282
|
result.documentXml = documentXml;
|
|
24672
|
-
if (result.
|
|
25283
|
+
if (result.status !== "error" && result.hasChanges) {
|
|
25284
|
+
if (!session.document || !anchorReplyInDocument(session.document, result.threadCommentIds, commentId)) {
|
|
25285
|
+
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.` } };
|
|
25286
|
+
} else if (session.receiptCollector) {
|
|
25287
|
+
session.receiptCollector.recordComment(commentId);
|
|
25288
|
+
}
|
|
25289
|
+
}
|
|
24673
25290
|
}
|
|
25291
|
+
} else if (operation.operationKind === "comment_resolve") {
|
|
25292
|
+
const resolution = applyThreadResolutionToParts({
|
|
25293
|
+
commentsXml: session.commentsXml || runtimeContext?.commentsXml || options.commentsXml,
|
|
25294
|
+
commentsExtendedXml: session.commentsExtendedXml || runtimeContext?.commentsExtendedXml || options.commentsExtendedXml,
|
|
25295
|
+
commentId: operation.commentId,
|
|
25296
|
+
resolved: operation.resolved !== false
|
|
25297
|
+
});
|
|
25298
|
+
result = resolution.status === "error" ? { documentXml, hasChanges: false, status: "error", error: resolution.error } : {
|
|
25299
|
+
documentXml,
|
|
25300
|
+
hasChanges: resolution.hasChanges,
|
|
25301
|
+
...resolution.commentsXml ? { commentsXml: resolution.commentsXml, commentsXmlMode: "replace" } : {},
|
|
25302
|
+
...resolution.commentsExtendedXml ? { commentsExtendedXml: resolution.commentsExtendedXml, commentsExtendedXmlMode: "replace" } : {},
|
|
25303
|
+
resolved: resolution.resolved,
|
|
25304
|
+
threadRootId: resolution.threadRootId,
|
|
25305
|
+
commentIds: resolution.commentIds
|
|
25306
|
+
};
|
|
24674
25307
|
} else if (operation.operationKind === "highlight") {
|
|
24675
25308
|
result = await applyHighlightToParagraphByExactText(
|
|
24676
25309
|
documentXml,
|
|
@@ -24811,7 +25444,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24811
25444
|
...resolutionCapture
|
|
24812
25445
|
};
|
|
24813
25446
|
}
|
|
24814
|
-
session.markMutationCommitted(
|
|
25447
|
+
session.markMutationCommitted(true);
|
|
24815
25448
|
if (operation._compiledSourceId && session.sourceTargetRegistry) {
|
|
24816
25449
|
session.sourceTargetRegistry.commitMutation(
|
|
24817
25450
|
operation._compiledSourceId,
|
|
@@ -24924,9 +25557,9 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24924
25557
|
}
|
|
24925
25558
|
|
|
24926
25559
|
// services/batch-operation-orchestrator.js
|
|
24927
|
-
var
|
|
25560
|
+
var NS_W10 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
24928
25561
|
function operationTargetPriority(op) {
|
|
24929
|
-
return op?.type === "comment" || op?.type === "comment_reply" ? 0 : 1;
|
|
25562
|
+
return op?.type === "comment" || op?.type === "comment_reply" || op?.type === "comment_resolve" ? 0 : 1;
|
|
24930
25563
|
}
|
|
24931
25564
|
function buildOperationDependencyPlan(operations = []) {
|
|
24932
25565
|
const list = Array.isArray(operations) ? operations : [];
|
|
@@ -25001,7 +25634,7 @@ function buildOperationDependencyPlan(operations = []) {
|
|
|
25001
25634
|
inDegrees[i2]++;
|
|
25002
25635
|
}
|
|
25003
25636
|
const kind = normalizeDocumentOperation(op).operationKind;
|
|
25004
|
-
if (kind !== "comment" && kind !== "comment_reply") {
|
|
25637
|
+
if (kind !== "comment" && kind !== "comment_reply" && kind !== "comment_resolve") {
|
|
25005
25638
|
if (!mutatingCaptureConsumers.has(ref)) mutatingCaptureConsumers.set(ref, []);
|
|
25006
25639
|
mutatingCaptureConsumers.get(ref).push({
|
|
25007
25640
|
index: i2,
|
|
@@ -25069,9 +25702,9 @@ function mergeCommentsXml(existingXml, incomingXml) {
|
|
|
25069
25702
|
const serializer = createSerializer();
|
|
25070
25703
|
const existingRoot = existingDoc.documentElement;
|
|
25071
25704
|
const existingIds = new Set(
|
|
25072
|
-
Array.from(existingDoc.getElementsByTagNameNS(
|
|
25705
|
+
Array.from(existingDoc.getElementsByTagNameNS(NS_W10, "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
25073
25706
|
);
|
|
25074
|
-
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(
|
|
25707
|
+
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(NS_W10, "comment"))) {
|
|
25075
25708
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
25076
25709
|
if (id && existingIds.has(id)) continue;
|
|
25077
25710
|
existingRoot.appendChild(existingDoc.importNode(comment, true));
|
|
@@ -25232,7 +25865,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25232
25865
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
25233
25866
|
}
|
|
25234
25867
|
session.runtimeContext = context;
|
|
25235
|
-
if (sourceOperations.some((op) => op?.type === "comment_reply")) {
|
|
25868
|
+
if (sourceOperations.some((op) => op?.type === "comment_reply" || op?.type === "comment_resolve")) {
|
|
25236
25869
|
session.commentsXml = context.commentsXml || options.commentsXml || null;
|
|
25237
25870
|
session.commentsExtendedXml = context.commentsExtendedXml || options.commentsExtendedXml || null;
|
|
25238
25871
|
session.commentsXmlMode = "replace";
|
|
@@ -25305,6 +25938,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25305
25938
|
...result.resolvedTarget ? { resolvedTarget: result.resolvedTarget } : {},
|
|
25306
25939
|
...result.resolvedAnchor ? { resolvedAnchor: result.resolvedAnchor } : {},
|
|
25307
25940
|
...result.change ? { change: result.change } : {},
|
|
25941
|
+
...result.operationType === "comment_resolve" && !isError ? { resolved: result.resolved, threadRootId: result.threadRootId, commentIds: result.commentIds } : {},
|
|
25308
25942
|
...Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {},
|
|
25309
25943
|
...result.error ? { error: result.error } : {},
|
|
25310
25944
|
...result.receipt ? { receipt: result.receipt } : {}
|
|
@@ -25581,6 +26215,26 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
25581
26215
|
}
|
|
25582
26216
|
commentsRequired = commentsRequired || operation.operationKind === "comment" || operation.operationKind === "comment_reply";
|
|
25583
26217
|
numberingRequired = numberingRequired || operationNeedsNumbering(operation);
|
|
26218
|
+
if (operation.operationKind === "comment_resolve") {
|
|
26219
|
+
const commentId = String(operation.commentId);
|
|
26220
|
+
results.push(options._existingCommentDetails?.[commentId] ? {
|
|
26221
|
+
index: index + 1,
|
|
26222
|
+
type: sourceOperation.type,
|
|
26223
|
+
operationType: operation.operationKind,
|
|
26224
|
+
status: "ready",
|
|
26225
|
+
authorUsed,
|
|
26226
|
+
resolvedBy: "comment_id",
|
|
26227
|
+
commentId
|
|
26228
|
+
} : {
|
|
26229
|
+
index: index + 1,
|
|
26230
|
+
type: sourceOperation.type,
|
|
26231
|
+
operationType: operation.operationKind,
|
|
26232
|
+
status: "error",
|
|
26233
|
+
authorUsed,
|
|
26234
|
+
error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` }
|
|
26235
|
+
});
|
|
26236
|
+
continue;
|
|
26237
|
+
}
|
|
25584
26238
|
if (operation.operationKind === "comment_reply") {
|
|
25585
26239
|
const parentId = String(operation.parentCommentId);
|
|
25586
26240
|
const parent = options._existingCommentDetails?.[parentId];
|
|
@@ -26732,6 +27386,15 @@ var text = (entries, path) => {
|
|
|
26732
27386
|
if (typeof entry === "string") return entry;
|
|
26733
27387
|
return textDecoder2.decode(entry);
|
|
26734
27388
|
};
|
|
27389
|
+
function reconcileCommentSiblings(working) {
|
|
27390
|
+
const updated = reconcileCommentSiblingParts({
|
|
27391
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
27392
|
+
commentsIdsXml: text(working, "word/commentsIds.xml"),
|
|
27393
|
+
commentsExtensibleXml: text(working, "word/commentsExtensible.xml")
|
|
27394
|
+
});
|
|
27395
|
+
if (updated.commentsIdsXml) working.set("word/commentsIds.xml", textEncoder4.encode(updated.commentsIdsXml));
|
|
27396
|
+
if (updated.commentsExtensibleXml) working.set("word/commentsExtensible.xml", textEncoder4.encode(updated.commentsExtensibleXml));
|
|
27397
|
+
}
|
|
26735
27398
|
var cloneEntries = (entries) => new Map([...entries].map(([name, data]) => [name, new Uint8Array(data)]));
|
|
26736
27399
|
function toBufferCompatible(uint8Arr) {
|
|
26737
27400
|
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
@@ -26799,6 +27462,7 @@ function computePackageRevisionToken(input) {
|
|
|
26799
27462
|
entries
|
|
26800
27463
|
});
|
|
26801
27464
|
}
|
|
27465
|
+
var COMMENT_OPERATION_TYPES = /* @__PURE__ */ new Set(["comment", "comment_reply", "comment_resolve"]);
|
|
26802
27466
|
function nextCommentId(entries) {
|
|
26803
27467
|
const ids = `${text(entries, "word/document.xml") || ""} ${text(entries, "word/comments.xml") || ""}`.match(/(?:w:)?id=["'](\d+)["']/g) || [];
|
|
26804
27468
|
let next = ids.reduce((max2, token) => Math.max(max2, Number(token.match(/\d+/)?.[0] || 0)), 0) + 1;
|
|
@@ -26817,7 +27481,7 @@ function existingCommentDetails(entries) {
|
|
|
26817
27481
|
id,
|
|
26818
27482
|
author: comment.getAttribute("w:author") || comment.getAttribute("author") || "",
|
|
26819
27483
|
text: String(comment.textContent || "").trim(),
|
|
26820
|
-
paraId:
|
|
27484
|
+
paraId: commentParaId(comment) || null
|
|
26821
27485
|
};
|
|
26822
27486
|
}
|
|
26823
27487
|
return details;
|
|
@@ -26848,13 +27512,34 @@ var DocxDocument = class {
|
|
|
26848
27512
|
}
|
|
26849
27513
|
inspect(options = {}) {
|
|
26850
27514
|
const digestFn = options.digestFn || ((bytes) => sha256(bytes));
|
|
26851
|
-
|
|
27515
|
+
const inspection = inspectDocumentParts({
|
|
26852
27516
|
documentXml: text(this.entries, "word/document.xml"),
|
|
26853
27517
|
commentsXml: text(this.entries, "word/comments.xml"),
|
|
26854
27518
|
commentsExtendedXml: text(this.entries, "word/commentsExtended.xml"),
|
|
26855
27519
|
numberingXml: text(this.entries, "word/numbering.xml"),
|
|
26856
27520
|
stylesXml: text(this.entries, "word/styles.xml")
|
|
26857
27521
|
}, { ...options, digestFn });
|
|
27522
|
+
if (inspection.status === "error") return inspection;
|
|
27523
|
+
return { ...inspection, headersFooters: this.inspectHeadersFooters() };
|
|
27524
|
+
}
|
|
27525
|
+
/**
|
|
27526
|
+
* Header and footer parts the document uses, with their paragraph text, so operations can target them with
|
|
27527
|
+
* `part`. `hasFields` marks parts containing PAGE/NUMPAGES-style fields (edit around them, not inside).
|
|
27528
|
+
*/
|
|
27529
|
+
inspectHeadersFooters() {
|
|
27530
|
+
return discoverHeaderFooterParts({
|
|
27531
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27532
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27533
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27534
|
+
}).map((part) => {
|
|
27535
|
+
const xml = text(this.entries, part.path) || "";
|
|
27536
|
+
const partInspection = xml ? inspectDocumentParts({ documentXml: xml }) : { paragraphs: [] };
|
|
27537
|
+
return {
|
|
27538
|
+
...part,
|
|
27539
|
+
hasFields: /<w:fldChar\b|<w:fldSimple\b/.test(xml),
|
|
27540
|
+
paragraphs: (partInspection.paragraphs || []).map((paragraph) => ({ index: paragraph.index, text: paragraph.text }))
|
|
27541
|
+
};
|
|
27542
|
+
});
|
|
26858
27543
|
}
|
|
26859
27544
|
getRevisionToken() {
|
|
26860
27545
|
return computePackageRevisionToken(this.entries);
|
|
@@ -26863,12 +27548,51 @@ var DocxDocument = class {
|
|
|
26863
27548
|
return this.getRevisionToken();
|
|
26864
27549
|
}
|
|
26865
27550
|
preflight(operations, author = getDefaultAuthor(), options = {}) {
|
|
26866
|
-
|
|
27551
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27552
|
+
const runBody = (ops) => preflightOperations(
|
|
26867
27553
|
text(this.entries, "word/document.xml"),
|
|
26868
|
-
|
|
27554
|
+
ops,
|
|
26869
27555
|
author || getDefaultAuthor(),
|
|
26870
27556
|
{ ...options, _existingCommentDetails: existingCommentDetails(this.entries) }
|
|
26871
27557
|
);
|
|
27558
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) return runBody(operations);
|
|
27559
|
+
const parts = this.inspectHeadersFooters();
|
|
27560
|
+
const bodyOps = [];
|
|
27561
|
+
const results = [];
|
|
27562
|
+
const conflicts = [];
|
|
27563
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27564
|
+
let valid = true;
|
|
27565
|
+
list.forEach((operation, i2) => {
|
|
27566
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27567
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27568
|
+
return;
|
|
27569
|
+
}
|
|
27570
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27571
|
+
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;
|
|
27572
|
+
if (refusal) {
|
|
27573
|
+
valid = false;
|
|
27574
|
+
results.push({ index: i2 + 1, type: operation.type || "redline", status: "error", error: normalizeErrorWithRecovery({ ...refusal, operationIndex: i2 + 1 }) });
|
|
27575
|
+
return;
|
|
27576
|
+
}
|
|
27577
|
+
const { part: _selector, ...rest } = operation;
|
|
27578
|
+
const partResult = preflightOperations(text(this.entries, resolved.part.path), [rest], author || getDefaultAuthor(), { ...options, _existingCommentDetails: {} });
|
|
27579
|
+
valid = valid && partResult.valid;
|
|
27580
|
+
for (const result of partResult.results || []) results.push({ ...result, index: i2 + 1, part: resolved.part.path });
|
|
27581
|
+
conflicts.push(...partResult.conflicts || []);
|
|
27582
|
+
for (const name of partResult.authorsUsed || []) authorsUsed.add(name);
|
|
27583
|
+
});
|
|
27584
|
+
let requiredArtifacts = { comments: false, numbering: false };
|
|
27585
|
+
if (bodyOps.length) {
|
|
27586
|
+
const body = runBody(bodyOps.map((item) => item.operation));
|
|
27587
|
+
valid = valid && body.valid;
|
|
27588
|
+
for (const result of body.results || []) results.push({ ...result, index: bodyOps[result.index - 1].origIndex + 1 });
|
|
27589
|
+
conflicts.push(...body.conflicts || []);
|
|
27590
|
+
for (const name of body.authorsUsed || []) authorsUsed.add(name);
|
|
27591
|
+
requiredArtifacts = body.requiredArtifacts || requiredArtifacts;
|
|
27592
|
+
}
|
|
27593
|
+
results.sort((a, b) => a.index - b.index);
|
|
27594
|
+
const ok = valid && conflicts.length === 0;
|
|
27595
|
+
return { valid: ok, status: ok ? "ok" : "error", results, conflicts, authorsUsed: [...authorsUsed], requiredArtifacts };
|
|
26872
27596
|
}
|
|
26873
27597
|
toUint8Array() {
|
|
26874
27598
|
return zipDocx(this.entries);
|
|
@@ -26876,7 +27600,240 @@ var DocxDocument = class {
|
|
|
26876
27600
|
toBuffer() {
|
|
26877
27601
|
return toBufferCompatible(this.toUint8Array());
|
|
26878
27602
|
}
|
|
27603
|
+
/**
|
|
27604
|
+
* Applies operations to the document. Operations without `part` target the body (word/document.xml).
|
|
27605
|
+
* An operation with `part` targets one header or footer part:
|
|
27606
|
+
* `{ part: { kind: 'footer', type: 'default', section: 0 } | 'word/footer2.xml', target: { exactText }, ... }`.
|
|
27607
|
+
* Discover parts with `inspect().headersFooters`.
|
|
27608
|
+
*/
|
|
26879
27609
|
async applyOperations(operations, options = {}) {
|
|
27610
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27611
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) {
|
|
27612
|
+
return this.applyBodyOperations(operations, options);
|
|
27613
|
+
}
|
|
27614
|
+
return this.applyOperationsWithParts(list, options);
|
|
27615
|
+
}
|
|
27616
|
+
async applyOperationsWithParts(operations, options = {}) {
|
|
27617
|
+
const author = options.author || getDefaultAuthor();
|
|
27618
|
+
const atomic = options.atomic === true;
|
|
27619
|
+
const snapshotEntries = this.entries;
|
|
27620
|
+
const snapshotBytes = this.originalBytes;
|
|
27621
|
+
const total = operations.length;
|
|
27622
|
+
const restore = () => {
|
|
27623
|
+
this.entries = snapshotEntries;
|
|
27624
|
+
this.originalBytes = snapshotBytes;
|
|
27625
|
+
};
|
|
27626
|
+
const original = () => ({
|
|
27627
|
+
uint8Array: snapshotBytes,
|
|
27628
|
+
toUint8Array: () => new Uint8Array(snapshotBytes),
|
|
27629
|
+
buffer: toBufferCompatible(snapshotBytes),
|
|
27630
|
+
toBuffer: () => toBufferCompatible(snapshotBytes)
|
|
27631
|
+
});
|
|
27632
|
+
const refuse = (error2, results2 = [], receipts2 = [], extra = {}) => ({
|
|
27633
|
+
status: "error",
|
|
27634
|
+
hasChanges: false,
|
|
27635
|
+
written: false,
|
|
27636
|
+
rolledBack: true,
|
|
27637
|
+
results: results2,
|
|
27638
|
+
receipts: receipts2,
|
|
27639
|
+
executionOrder: [],
|
|
27640
|
+
authorsUsed: [],
|
|
27641
|
+
artifactsChanged: [],
|
|
27642
|
+
error: normalizeErrorWithRecovery(error2),
|
|
27643
|
+
retryPlan: createRetryPlan({ atomic: true, rolledBack: true, results: results2, receipts: receipts2, operationCount: total }),
|
|
27644
|
+
validation: { originalIssues: [], generatedIssues: [] },
|
|
27645
|
+
...extra,
|
|
27646
|
+
...original()
|
|
27647
|
+
});
|
|
27648
|
+
if (options.expectedRevision) {
|
|
27649
|
+
const precondition = await this.applyBodyOperations([], { expectedRevision: options.expectedRevision });
|
|
27650
|
+
if (precondition.status === "error") return precondition;
|
|
27651
|
+
}
|
|
27652
|
+
const parts = discoverHeaderFooterParts({
|
|
27653
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27654
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27655
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27656
|
+
});
|
|
27657
|
+
const bodyOps = [];
|
|
27658
|
+
const partOps = [];
|
|
27659
|
+
for (let i2 = 0; i2 < operations.length; i2++) {
|
|
27660
|
+
const operation = operations[i2];
|
|
27661
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27662
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27663
|
+
continue;
|
|
27664
|
+
}
|
|
27665
|
+
if (COMMENT_OPERATION_TYPES.has(operation.type)) {
|
|
27666
|
+
return refuse({ code: "COMMENT_IN_HEADER_FOOTER", message: `Operation ${i2 + 1}: Word does not support comments in headers or footers.`, operationIndex: i2 + 1 });
|
|
27667
|
+
}
|
|
27668
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27669
|
+
if (resolved.error) return refuse({ ...resolved.error, message: `Operation ${i2 + 1}: ${resolved.error.message}`, operationIndex: i2 + 1 });
|
|
27670
|
+
const { part: _selector, ...rest } = operation;
|
|
27671
|
+
partOps.push({ operation: rest, origIndex: i2, part: resolved.part });
|
|
27672
|
+
}
|
|
27673
|
+
const results = [];
|
|
27674
|
+
const receipts = [];
|
|
27675
|
+
const executionOrder = [];
|
|
27676
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27677
|
+
const originalIssues = [];
|
|
27678
|
+
let anyChange = false;
|
|
27679
|
+
let anyFailure = false;
|
|
27680
|
+
let bodyResult = null;
|
|
27681
|
+
if (bodyOps.length) {
|
|
27682
|
+
bodyResult = await this.applyBodyOperations(bodyOps.map((item) => item.operation), { ...options, expectedRevision: void 0 });
|
|
27683
|
+
const toOriginal = (index) => bodyOps[index - 1]?.origIndex + 1;
|
|
27684
|
+
for (const result of bodyResult.results || []) {
|
|
27685
|
+
results.push({
|
|
27686
|
+
...result,
|
|
27687
|
+
index: toOriginal(result.index),
|
|
27688
|
+
...result.receipt ? { receipt: { ...result.receipt, operationIndex: toOriginal(result.receipt.operationIndex) } } : {}
|
|
27689
|
+
});
|
|
27690
|
+
}
|
|
27691
|
+
for (const receipt of bodyResult.receipts || []) receipts.push({ ...receipt, operationIndex: toOriginal(receipt.operationIndex) });
|
|
27692
|
+
for (const index of bodyResult.executionOrder || []) executionOrder.push(toOriginal(index));
|
|
27693
|
+
for (const name of bodyResult.authorsUsed || []) authorsUsed.add(name);
|
|
27694
|
+
originalIssues.push(...bodyResult.validation?.originalIssues || []);
|
|
27695
|
+
if (bodyResult.status === "error" || bodyResult.rolledBack) {
|
|
27696
|
+
anyFailure = true;
|
|
27697
|
+
if (atomic) {
|
|
27698
|
+
restore();
|
|
27699
|
+
return { ...bodyResult, results, receipts, executionOrder, written: false, artifactsChanged: [], rolledBack: true, ...original() };
|
|
27700
|
+
}
|
|
27701
|
+
}
|
|
27702
|
+
anyChange = bodyResult.hasChanges === true;
|
|
27703
|
+
}
|
|
27704
|
+
const working = cloneEntries(this.entries);
|
|
27705
|
+
const zip = new MemoryZip(working);
|
|
27706
|
+
const changedParts = /* @__PURE__ */ new Set();
|
|
27707
|
+
const { expectedRevision: _ignored, ...runnerOptions } = options;
|
|
27708
|
+
const failPart = (item, error2) => {
|
|
27709
|
+
anyFailure = true;
|
|
27710
|
+
const failure = normalizeErrorWithRecovery({ ...error2, operationIndex: item.origIndex + 1 });
|
|
27711
|
+
results.push({ index: item.origIndex + 1, type: item.operation.type || "redline", status: "error", authorUsed: author, part: item.part.path, error: failure });
|
|
27712
|
+
};
|
|
27713
|
+
for (const item of partOps) {
|
|
27714
|
+
const path = item.part.path;
|
|
27715
|
+
const xml = text(working, path);
|
|
27716
|
+
if (!xml) {
|
|
27717
|
+
failPart(item, { code: "PART_NOT_FOUND", message: `Part '${path}' is referenced but missing from the package.` });
|
|
27718
|
+
if (atomic) break;
|
|
27719
|
+
continue;
|
|
27720
|
+
}
|
|
27721
|
+
const numberingText = text(working, "word/numbering.xml");
|
|
27722
|
+
const outcome = await applyOperationsToDocumentXml(xml, [item.operation], author, { numberingIdState: createDynamicNumberingIdState(numberingText || void 0) }, {
|
|
27723
|
+
...runnerOptions,
|
|
27724
|
+
atomic: true,
|
|
27725
|
+
strictTargets: options.strictTargets !== false,
|
|
27726
|
+
_existingCommentDetails: {}
|
|
27727
|
+
});
|
|
27728
|
+
const single = outcome.results?.[0];
|
|
27729
|
+
if (outcome.rolledBack || outcome.status === "error") {
|
|
27730
|
+
failPart(item, single?.error || outcome.error || { code: "OPERATION_FAILED", message: "Operation failed." });
|
|
27731
|
+
if (atomic) break;
|
|
27732
|
+
continue;
|
|
27733
|
+
}
|
|
27734
|
+
if (!outcome.hasChanges) {
|
|
27735
|
+
results.push({ ...single, index: item.origIndex + 1, part: path });
|
|
27736
|
+
continue;
|
|
27737
|
+
}
|
|
27738
|
+
if (countRevisionsInsideFields(outcome.documentXml) > countRevisionsInsideFields(xml)) {
|
|
27739
|
+
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.` });
|
|
27740
|
+
if (atomic) break;
|
|
27741
|
+
continue;
|
|
27742
|
+
}
|
|
27743
|
+
const taken = /* @__PURE__ */ new Set();
|
|
27744
|
+
for (const [name, data] of working) {
|
|
27745
|
+
if (name === path || !(name === "word/document.xml" || isHeaderFooterPath(name))) continue;
|
|
27746
|
+
for (const id of collectRevisionIds(textDecoder2.decode(data))) taken.add(id);
|
|
27747
|
+
}
|
|
27748
|
+
const renumbered = renumberCollidingRevisionIds(outcome.documentXml, taken);
|
|
27749
|
+
working.set(path, textEncoder4.encode(renumbered.xml));
|
|
27750
|
+
const describeReceipt = (receipt) => receipt?.revisionItems ? { ...receipt, revisionItems: receipt.revisionItems.map((revision) => ({ ...revision, id: renumbered.idMap.get(String(revision.id)) ?? revision.id, partName: path })) } : receipt;
|
|
27751
|
+
if (outcome.numberingXmlParts?.length) {
|
|
27752
|
+
await ensureNumberingArtifactsInZip(zip, outcome.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
27753
|
+
}
|
|
27754
|
+
changedParts.add(path);
|
|
27755
|
+
anyChange = true;
|
|
27756
|
+
results.push({
|
|
27757
|
+
...single,
|
|
27758
|
+
index: item.origIndex + 1,
|
|
27759
|
+
part: path,
|
|
27760
|
+
partSections: item.part.appliesToSections,
|
|
27761
|
+
...single?.receipt ? { receipt: { ...describeReceipt(single.receipt), operationIndex: item.origIndex + 1 } } : {}
|
|
27762
|
+
});
|
|
27763
|
+
if (outcome.receipts?.[0]) receipts.push({ ...describeReceipt(outcome.receipts[0]), operationIndex: item.origIndex + 1 });
|
|
27764
|
+
executionOrder.push(item.origIndex + 1);
|
|
27765
|
+
for (const name of outcome.authorsUsed || []) authorsUsed.add(name);
|
|
27766
|
+
}
|
|
27767
|
+
const generatedIssues = [];
|
|
27768
|
+
if (!(atomic && anyFailure)) {
|
|
27769
|
+
for (const path of changedParts) {
|
|
27770
|
+
const before = validateRedlineOoxml(text(this.entries, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27771
|
+
const after = validateRedlineOoxml(text(working, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27772
|
+
generatedIssues.push(...validationErrors(subtractValidationIssueMultiset(after, before)));
|
|
27773
|
+
}
|
|
27774
|
+
if (options.validate !== false && changedParts.size) {
|
|
27775
|
+
try {
|
|
27776
|
+
await validateDocxPackage(zip);
|
|
27777
|
+
} catch (error2) {
|
|
27778
|
+
generatedIssues.push({ source: "package", code: "PACKAGE_VALIDATION", severity: "error", message: error2.message });
|
|
27779
|
+
}
|
|
27780
|
+
}
|
|
27781
|
+
}
|
|
27782
|
+
if (atomic && anyFailure || generatedIssues.length) {
|
|
27783
|
+
restore();
|
|
27784
|
+
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." };
|
|
27785
|
+
const rolledBackResults = results.map((result) => result.receipt ? { ...result, receipt: { ...result.receipt, committed: false, finalDisposition: "rolled_back" } } : result).sort((a, b) => a.index - b.index);
|
|
27786
|
+
const rolledBackReceipts = receipts.map((receipt) => ({ ...receipt, committed: false, finalDisposition: "rolled_back" }));
|
|
27787
|
+
return refuse(error2, rolledBackResults, rolledBackReceipts, { validation: { originalIssues, generatedIssues }, issues: generatedIssues });
|
|
27788
|
+
}
|
|
27789
|
+
results.sort((a, b) => a.index - b.index);
|
|
27790
|
+
receipts.sort((a, b) => a.operationIndex - b.operationIndex);
|
|
27791
|
+
const status = !anyFailure ? "ok" : anyChange ? "partial" : "error";
|
|
27792
|
+
const failureError = anyFailure ? { error: normalizeErrorWithRecovery({ code: "BATCH_OPERATION_FAILED", message: "One or more operations failed; the others were applied." }) } : {};
|
|
27793
|
+
if (!changedParts.size) {
|
|
27794
|
+
const buf = toBufferCompatible(this.originalBytes);
|
|
27795
|
+
return {
|
|
27796
|
+
...bodyResult || {},
|
|
27797
|
+
results,
|
|
27798
|
+
receipts,
|
|
27799
|
+
executionOrder,
|
|
27800
|
+
authorsUsed: [...authorsUsed],
|
|
27801
|
+
status,
|
|
27802
|
+
hasChanges: anyChange,
|
|
27803
|
+
written: bodyResult?.written === true,
|
|
27804
|
+
artifactsChanged: bodyResult?.artifactsChanged || [],
|
|
27805
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27806
|
+
...failureError,
|
|
27807
|
+
uint8Array: this.originalBytes,
|
|
27808
|
+
toUint8Array: () => new Uint8Array(this.originalBytes),
|
|
27809
|
+
buffer: buf,
|
|
27810
|
+
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
27811
|
+
};
|
|
27812
|
+
}
|
|
27813
|
+
this.entries = working;
|
|
27814
|
+
const outputBytes = this.toUint8Array();
|
|
27815
|
+
this.originalBytes = outputBytes;
|
|
27816
|
+
const artifactsChanged = [...working].filter(([name, data]) => !snapshotEntries.has(name) || !areByteArraysEqual(data, snapshotEntries.get(name))).map(([name]) => name);
|
|
27817
|
+
return {
|
|
27818
|
+
...bodyResult || {},
|
|
27819
|
+
status,
|
|
27820
|
+
hasChanges: true,
|
|
27821
|
+
written: true,
|
|
27822
|
+
results,
|
|
27823
|
+
receipts,
|
|
27824
|
+
executionOrder,
|
|
27825
|
+
authorsUsed: [...authorsUsed],
|
|
27826
|
+
artifactsChanged,
|
|
27827
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27828
|
+
...failureError,
|
|
27829
|
+
uint8Array: outputBytes,
|
|
27830
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27831
|
+
buffer: toBufferCompatible(outputBytes),
|
|
27832
|
+
inspection: this.inspect(),
|
|
27833
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
27834
|
+
};
|
|
27835
|
+
}
|
|
27836
|
+
async applyBodyOperations(operations, options = {}) {
|
|
26880
27837
|
const failedApply = (error2) => {
|
|
26881
27838
|
const results = [];
|
|
26882
27839
|
const receipts = [];
|
|
@@ -26987,18 +27944,21 @@ var DocxDocument = class {
|
|
|
26987
27944
|
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
26988
27945
|
};
|
|
26989
27946
|
}
|
|
26990
|
-
|
|
27947
|
+
const bodyChanged = (result.results || []).some((item) => item?.status !== "error" && item?.operationType !== "comment_resolve");
|
|
27948
|
+
if (bodyChanged) working.set("word/document.xml", textEncoder4.encode(result.documentXml));
|
|
26991
27949
|
await ensureNumberingArtifactsInZip(zip, result.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
26992
27950
|
const existingCommentsXml = text(working, "word/comments.xml");
|
|
26993
27951
|
const commentsXmlForPackaging = result.commentsXml || existingCommentsXml;
|
|
26994
27952
|
await ensureCommentsArtifactsInZip(zip, commentsXmlForPackaging, {
|
|
26995
27953
|
replaceExisting: result.commentsXmlMode === "replace" || !result.commentsXml && !!existingCommentsXml
|
|
26996
27954
|
});
|
|
26997
|
-
|
|
26998
|
-
|
|
26999
|
-
|
|
27000
|
-
|
|
27001
|
-
}
|
|
27955
|
+
if (result.commentsExtendedXml) {
|
|
27956
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, {
|
|
27957
|
+
replaceExisting: result.commentsExtendedXmlMode === "replace"
|
|
27958
|
+
});
|
|
27959
|
+
}
|
|
27960
|
+
await repairKnownContentTypes(zip);
|
|
27961
|
+
reconcileCommentSiblings(working);
|
|
27002
27962
|
if (options.validate !== false) {
|
|
27003
27963
|
const generated = validateRedlineOoxml(result.documentXml);
|
|
27004
27964
|
const outputIssues = generated.issues.map((issue) => ({ source: "word/document.xml", ...issue }));
|
|
@@ -27067,13 +28027,25 @@ var DocxDocument = class {
|
|
|
27067
28027
|
const transform = action === "accept" ? acceptTrackedChangesInOoxml : action === "reject" ? rejectTrackedChangesInOoxml : null;
|
|
27068
28028
|
if (!transform) return packageFailure(this.originalBytes, "INVALID_ACTION", `Unknown revision action: ${action}`);
|
|
27069
28029
|
const sourceBytes = this.originalBytes;
|
|
28030
|
+
const originalEntries = this.entries;
|
|
27070
28031
|
const working = cloneEntries(this.entries);
|
|
27071
28032
|
const zip = new MemoryZip(working);
|
|
27072
28033
|
const result = transform(text(working, "word/document.xml"), { author: options.author, allAuthors: options.allAuthors === true });
|
|
27073
28034
|
if (result.status === "error" || result.error) {
|
|
27074
28035
|
return packageFailure(sourceBytes, result.error?.code || "REVISION_OPERATION_FAILED", result.error?.message || "Revision operation failed.");
|
|
27075
28036
|
}
|
|
27076
|
-
|
|
28037
|
+
const partUpdates = /* @__PURE__ */ new Map();
|
|
28038
|
+
for (const part of this.inspectHeadersFooters()) {
|
|
28039
|
+
const partXml = text(working, part.path);
|
|
28040
|
+
if (!partXml) continue;
|
|
28041
|
+
const partResult = transform(partXml, { author: options.author, allAuthors: options.allAuthors === true });
|
|
28042
|
+
if (partResult.status === "error" || partResult.error) {
|
|
28043
|
+
return packageFailure(sourceBytes, partResult.error?.code || "REVISION_OPERATION_FAILED", `${part.path}: ${partResult.error?.message || "Revision operation failed."}`);
|
|
28044
|
+
}
|
|
28045
|
+
if (partResult.hasChanges) partUpdates.set(part.path, partResult.oxml);
|
|
28046
|
+
}
|
|
28047
|
+
const hasChanges = result.hasChanges || partUpdates.size > 0;
|
|
28048
|
+
if (!hasChanges) {
|
|
27077
28049
|
const buf = toBufferCompatible(sourceBytes);
|
|
27078
28050
|
return {
|
|
27079
28051
|
...result,
|
|
@@ -27086,8 +28058,10 @@ var DocxDocument = class {
|
|
|
27086
28058
|
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
27087
28059
|
};
|
|
27088
28060
|
}
|
|
27089
|
-
working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
28061
|
+
if (result.hasChanges) working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
28062
|
+
for (const [path, xml] of partUpdates) working.set(path, textEncoder4.encode(xml));
|
|
27090
28063
|
try {
|
|
28064
|
+
await repairKnownContentTypes(zip);
|
|
27091
28065
|
if (options.validate !== false) await validateDocxPackage(zip);
|
|
27092
28066
|
} catch (error2) {
|
|
27093
28067
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
@@ -27100,13 +28074,71 @@ var DocxDocument = class {
|
|
|
27100
28074
|
...result,
|
|
27101
28075
|
status: "ok",
|
|
27102
28076
|
written: true,
|
|
27103
|
-
|
|
28077
|
+
hasChanges: true,
|
|
28078
|
+
artifactsChanged: [...working].filter(([name, data]) => !originalEntries.has(name) || !areByteArraysEqual(data, originalEntries.get(name))).map(([name]) => name),
|
|
27104
28079
|
uint8Array: outputBytes,
|
|
27105
28080
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27106
28081
|
buffer: outputBuf,
|
|
27107
28082
|
toBuffer: () => toBufferCompatible(outputBytes)
|
|
27108
28083
|
};
|
|
27109
28084
|
}
|
|
28085
|
+
/**
|
|
28086
|
+
* Resolves (or reopens) the comment thread containing `commentId`.
|
|
28087
|
+
* Resolved is a thread-level state in Word, so the root and every reply are updated together.
|
|
28088
|
+
*/
|
|
28089
|
+
async resolveComment(commentId, options = {}) {
|
|
28090
|
+
const sourceBytes = this.originalBytes;
|
|
28091
|
+
const working = cloneEntries(this.entries);
|
|
28092
|
+
const resolved = options.resolved !== false;
|
|
28093
|
+
const noChange = (extra) => {
|
|
28094
|
+
const buf = toBufferCompatible(sourceBytes);
|
|
28095
|
+
return {
|
|
28096
|
+
status: "ok",
|
|
28097
|
+
hasChanges: false,
|
|
28098
|
+
written: false,
|
|
28099
|
+
artifactsChanged: [],
|
|
28100
|
+
...extra,
|
|
28101
|
+
uint8Array: sourceBytes,
|
|
28102
|
+
toUint8Array: () => new Uint8Array(sourceBytes),
|
|
28103
|
+
buffer: buf,
|
|
28104
|
+
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
28105
|
+
};
|
|
28106
|
+
};
|
|
28107
|
+
const result = applyThreadResolutionToParts({
|
|
28108
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
28109
|
+
commentsExtendedXml: text(working, "word/commentsExtended.xml"),
|
|
28110
|
+
commentId,
|
|
28111
|
+
resolved
|
|
28112
|
+
});
|
|
28113
|
+
if (result.status === "error") return packageFailure(sourceBytes, result.error.code, result.error.message);
|
|
28114
|
+
if (!result.hasChanges) return noChange({ resolved, threadRootId: result.threadRootId, commentIds: result.commentIds });
|
|
28115
|
+
const zip = new MemoryZip(working);
|
|
28116
|
+
try {
|
|
28117
|
+
if (result.commentsXml) working.set("word/comments.xml", textEncoder4.encode(result.commentsXml));
|
|
28118
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, { replaceExisting: true });
|
|
28119
|
+
await repairKnownContentTypes(zip);
|
|
28120
|
+
if (options.validate !== false) await validateDocxPackage(zip);
|
|
28121
|
+
} catch (error2) {
|
|
28122
|
+
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
28123
|
+
}
|
|
28124
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
28125
|
+
this.entries = working;
|
|
28126
|
+
const outputBytes = this.toUint8Array();
|
|
28127
|
+
this.originalBytes = outputBytes;
|
|
28128
|
+
return {
|
|
28129
|
+
status: "ok",
|
|
28130
|
+
hasChanges: true,
|
|
28131
|
+
written: true,
|
|
28132
|
+
resolved,
|
|
28133
|
+
threadRootId: result.threadRootId,
|
|
28134
|
+
commentIds: result.commentIds,
|
|
28135
|
+
artifactsChanged,
|
|
28136
|
+
uint8Array: outputBytes,
|
|
28137
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
28138
|
+
buffer: toBufferCompatible(outputBytes),
|
|
28139
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
28140
|
+
};
|
|
28141
|
+
}
|
|
27110
28142
|
async deleteComments(options = {}) {
|
|
27111
28143
|
const sourceBytes = this.originalBytes;
|
|
27112
28144
|
const working = cloneEntries(this.entries);
|
|
@@ -27130,7 +28162,13 @@ var DocxDocument = class {
|
|
|
27130
28162
|
if (!parsed.doc || parsed.error) {
|
|
27131
28163
|
return packageFailure(sourceBytes, "PARSE_ERROR", parsed.error?.message || "Could not parse comments.xml.");
|
|
27132
28164
|
}
|
|
27133
|
-
const
|
|
28165
|
+
const requestedIds = Array.isArray(options.ids) ? new Set(options.ids.map(String)) : null;
|
|
28166
|
+
if (requestedIds) {
|
|
28167
|
+
const known = new Set(Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")));
|
|
28168
|
+
const missing = [...requestedIds].filter((id) => !known.has(id));
|
|
28169
|
+
if (missing.length) return packageFailure(sourceBytes, "COMMENT_NOT_FOUND", `Comment id(s) not found: ${missing.join(", ")}.`);
|
|
28170
|
+
}
|
|
28171
|
+
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;
|
|
27134
28172
|
const ids = new Set(
|
|
27135
28173
|
Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).filter(matches).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
27136
28174
|
);
|
|
@@ -27159,9 +28197,8 @@ var DocxDocument = class {
|
|
|
27159
28197
|
const idByParaId = /* @__PURE__ */ new Map();
|
|
27160
28198
|
for (const comment of Array.from(parsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
27161
28199
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
27162
|
-
const
|
|
27163
|
-
|
|
27164
|
-
if (paraId) idByParaId.set(paraId.toUpperCase(), id);
|
|
28200
|
+
const paraId = commentParaId(comment);
|
|
28201
|
+
if (paraId) idByParaId.set(paraId, id);
|
|
27165
28202
|
}
|
|
27166
28203
|
for (const [paraId, id] of idByParaId) {
|
|
27167
28204
|
if (ids.has(id)) removedParaIds.add(paraId);
|
|
@@ -27180,8 +28217,8 @@ var DocxDocument = class {
|
|
|
27180
28217
|
}
|
|
27181
28218
|
}
|
|
27182
28219
|
}
|
|
27183
|
-
const
|
|
27184
|
-
const remainingParsed = parseOoxmlSafe(
|
|
28220
|
+
const remainingXml = requestedIds ? commentsXml : deleteCommentsByAuthorInOoxml(commentsXml, { author: options.author, allAuthors: options.allAuthors === true }).oxml;
|
|
28221
|
+
const remainingParsed = parseOoxmlSafe(remainingXml, "application/xml");
|
|
27185
28222
|
if (!remainingParsed.doc || remainingParsed.error) {
|
|
27186
28223
|
return packageFailure(sourceBytes, "PARSE_ERROR", remainingParsed.error?.message || "Could not parse updated comments.xml.");
|
|
27187
28224
|
}
|
|
@@ -27217,10 +28254,14 @@ var DocxDocument = class {
|
|
|
27217
28254
|
}
|
|
27218
28255
|
working.set("word/document.xml", textEncoder4.encode(serializer.serializeToString(documentParsed.doc)));
|
|
27219
28256
|
try {
|
|
27220
|
-
|
|
28257
|
+
reconcileCommentSiblings(working);
|
|
28258
|
+
const finalZip = new MemoryZip(working);
|
|
28259
|
+
await repairKnownContentTypes(finalZip);
|
|
28260
|
+
if (options.validate !== false) await validateDocxPackage(finalZip);
|
|
27221
28261
|
} catch (error2) {
|
|
27222
28262
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
27223
28263
|
}
|
|
28264
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
27224
28265
|
this.entries = working;
|
|
27225
28266
|
const outputBytes = this.toUint8Array();
|
|
27226
28267
|
this.originalBytes = outputBytes;
|
|
@@ -27231,7 +28272,7 @@ var DocxDocument = class {
|
|
|
27231
28272
|
written: true,
|
|
27232
28273
|
commentsRemoved: ids.size,
|
|
27233
28274
|
referencesRemoved,
|
|
27234
|
-
artifactsChanged
|
|
28275
|
+
artifactsChanged,
|
|
27235
28276
|
uint8Array: outputBytes,
|
|
27236
28277
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27237
28278
|
buffer: outputBuf,
|