@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 v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js 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;
|
|
@@ -2522,15 +2522,29 @@ function createDiffEngine(options = {}) {
|
|
|
2522
2522
|
engine.Diff_Timeout = timeout;
|
|
2523
2523
|
return engine;
|
|
2524
2524
|
}
|
|
2525
|
-
function tokenize(text2) {
|
|
2525
|
+
function tokenize(text2, atomicChars = null) {
|
|
2526
2526
|
const tokens = [];
|
|
2527
|
+
const pushWord = (word) => {
|
|
2528
|
+
if (!atomicChars || atomicChars.size === 0) {
|
|
2529
|
+
tokens.push(word);
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
let start = 0;
|
|
2533
|
+
for (let index = 0; index < word.length; index++) {
|
|
2534
|
+
if (!atomicChars.has(word[index])) continue;
|
|
2535
|
+
if (index > start) tokens.push(word.slice(start, index));
|
|
2536
|
+
tokens.push(word[index]);
|
|
2537
|
+
start = index + 1;
|
|
2538
|
+
}
|
|
2539
|
+
if (start < word.length) tokens.push(word.slice(start));
|
|
2540
|
+
};
|
|
2527
2541
|
const leading = text2.match(/^\s+/);
|
|
2528
2542
|
if (leading) tokens.push(leading[0]);
|
|
2529
2543
|
const regex = /(\S+)(\s*)/g;
|
|
2530
2544
|
regex.lastIndex = leading?.[0].length || 0;
|
|
2531
2545
|
let match;
|
|
2532
2546
|
while ((match = regex.exec(text2)) !== null) {
|
|
2533
|
-
if (match[1])
|
|
2547
|
+
if (match[1]) pushWord(match[1]);
|
|
2534
2548
|
if (match[2]) tokens.push(match[2]);
|
|
2535
2549
|
}
|
|
2536
2550
|
return tokens;
|
|
@@ -2558,8 +2572,8 @@ function wordsToChars(text1, text2, options = {}) {
|
|
|
2558
2572
|
}
|
|
2559
2573
|
return { chars, tokenIds };
|
|
2560
2574
|
}
|
|
2561
|
-
const tokens1 = tokenize(text1);
|
|
2562
|
-
const tokens2 = tokenize(text2);
|
|
2575
|
+
const tokens1 = tokenize(text1, options.atomicChars);
|
|
2576
|
+
const tokens2 = tokenize(text2, options.atomicChars);
|
|
2563
2577
|
const encoded1 = mapTokensToChars(tokens1);
|
|
2564
2578
|
const encoded2 = mapTokensToChars(tokens2);
|
|
2565
2579
|
return {
|
|
@@ -7717,6 +7731,19 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
7717
7731
|
pendingReplacementEvent = null;
|
|
7718
7732
|
}
|
|
7719
7733
|
}
|
|
7734
|
+
for (const offset of Array.from(sentinelMapByStart.keys()).sort((a, b) => a - b)) {
|
|
7735
|
+
for (const sentinel of sentinelMapByStart.get(offset)) {
|
|
7736
|
+
if (!sentinel.isCommentMarker || emittedCommentMarkers.has(sentinel.node)) continue;
|
|
7737
|
+
emittedCommentMarkers.add(sentinel.node);
|
|
7738
|
+
if (isWordElement(sentinel.node, "commentReference")) {
|
|
7739
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
7740
|
+
run.appendChild(sentinel.node.cloneNode(true));
|
|
7741
|
+
currentParagraph.appendChild(run);
|
|
7742
|
+
} else {
|
|
7743
|
+
currentParagraph.appendChild(sentinel.node.cloneNode(true));
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7720
7747
|
if (generateRedlines) {
|
|
7721
7748
|
containerFragments.forEach((fragment) => {
|
|
7722
7749
|
Array.from(fragment.childNodes).forEach((node) => {
|
|
@@ -7951,7 +7978,11 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
7951
7978
|
if (mapping.paragraphs.length === 0) {
|
|
7952
7979
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
7953
7980
|
}
|
|
7954
|
-
const
|
|
7981
|
+
const wordDiffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText, {
|
|
7982
|
+
...diffOptions,
|
|
7983
|
+
atomicChars: new Set(mapping.referenceMap.keys())
|
|
7984
|
+
});
|
|
7985
|
+
const diffs = keepUnchangedTextOutsideWrappers(anchorSharedReferences(wordDiffs, mapping.referenceMap), mapping);
|
|
7955
7986
|
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
7956
7987
|
xmlDoc,
|
|
7957
7988
|
diffs,
|
|
@@ -7963,6 +7994,109 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
7963
7994
|
options
|
|
7964
7995
|
));
|
|
7965
7996
|
}
|
|
7997
|
+
function forEachChangeGroup(diffs, onEqual, onGroup) {
|
|
7998
|
+
let index = 0;
|
|
7999
|
+
while (index < diffs.length) {
|
|
8000
|
+
if (diffs[index][0] === 0) {
|
|
8001
|
+
onEqual(diffs[index]);
|
|
8002
|
+
index++;
|
|
8003
|
+
continue;
|
|
8004
|
+
}
|
|
8005
|
+
const group = [];
|
|
8006
|
+
while (index < diffs.length && diffs[index][0] !== 0) group.push(diffs[index++]);
|
|
8007
|
+
onGroup(
|
|
8008
|
+
group,
|
|
8009
|
+
group.filter(([op]) => op === -1).map(([, text2]) => text2).join(""),
|
|
8010
|
+
group.filter(([op]) => op === 1).map(([, text2]) => text2).join("")
|
|
8011
|
+
);
|
|
8012
|
+
}
|
|
8013
|
+
}
|
|
8014
|
+
function pushChange(result, deleted, inserted) {
|
|
8015
|
+
if (deleted === inserted) {
|
|
8016
|
+
if (deleted) result.push([0, deleted]);
|
|
8017
|
+
return;
|
|
8018
|
+
}
|
|
8019
|
+
if (deleted) result.push([-1, deleted]);
|
|
8020
|
+
if (inserted) result.push([1, inserted]);
|
|
8021
|
+
}
|
|
8022
|
+
function anchorSharedReferences(diffs, referenceMap) {
|
|
8023
|
+
if (referenceMap.size === 0) return diffs;
|
|
8024
|
+
const result = [];
|
|
8025
|
+
forEachChangeGroup(diffs, (part) => result.push(part), (group, deleted, inserted) => {
|
|
8026
|
+
const deletedRefs = [...deleted].filter((char) => referenceMap.has(char) && inserted.includes(char));
|
|
8027
|
+
const insertedRefs = [...inserted].filter((char) => deletedRefs.includes(char));
|
|
8028
|
+
if (deletedRefs.length === 0 || deletedRefs.join("") !== insertedRefs.join("")) {
|
|
8029
|
+
result.push(...group);
|
|
8030
|
+
return;
|
|
8031
|
+
}
|
|
8032
|
+
let deletedStart = 0;
|
|
8033
|
+
let insertedStart = 0;
|
|
8034
|
+
for (const ref of deletedRefs) {
|
|
8035
|
+
const deletedAt = deleted.indexOf(ref, deletedStart);
|
|
8036
|
+
const insertedAt = inserted.indexOf(ref, insertedStart);
|
|
8037
|
+
pushChange(result, deleted.slice(deletedStart, deletedAt), inserted.slice(insertedStart, insertedAt));
|
|
8038
|
+
result.push([0, ref]);
|
|
8039
|
+
deletedStart = deletedAt + 1;
|
|
8040
|
+
insertedStart = insertedAt + 1;
|
|
8041
|
+
}
|
|
8042
|
+
pushChange(result, deleted.slice(deletedStart), inserted.slice(insertedStart));
|
|
8043
|
+
});
|
|
8044
|
+
return result;
|
|
8045
|
+
}
|
|
8046
|
+
function commonPrefixLength(left, right) {
|
|
8047
|
+
const limit = Math.min(left.length, right.length);
|
|
8048
|
+
let length = 0;
|
|
8049
|
+
while (length < limit && left[length] === right[length]) length++;
|
|
8050
|
+
return length;
|
|
8051
|
+
}
|
|
8052
|
+
function commonSuffixLength(left, right, limit) {
|
|
8053
|
+
let length = 0;
|
|
8054
|
+
while (length < limit && left[left.length - 1 - length] === right[right.length - 1 - length]) length++;
|
|
8055
|
+
return length;
|
|
8056
|
+
}
|
|
8057
|
+
function keepUnchangedTextOutsideWrappers(diffs, mapping) {
|
|
8058
|
+
const wrapperAt = (index) => mapping.getRunProperties(index).wrapper || null;
|
|
8059
|
+
const result = [];
|
|
8060
|
+
let originalOffset = 0;
|
|
8061
|
+
forEachChangeGroup(diffs, (part) => {
|
|
8062
|
+
result.push(part);
|
|
8063
|
+
originalOffset += part[1].length;
|
|
8064
|
+
}, (group, deleted, inserted) => {
|
|
8065
|
+
const start = originalOffset;
|
|
8066
|
+
const end = start + deleted.length;
|
|
8067
|
+
originalOffset = end;
|
|
8068
|
+
const isWrapperBoundary = (index) => index > start && index < end && wrapperAt(index - 1) !== wrapperAt(index);
|
|
8069
|
+
let prefix = 0;
|
|
8070
|
+
let suffix = 0;
|
|
8071
|
+
if (deleted && inserted) {
|
|
8072
|
+
const sharedSuffix = commonSuffixLength(deleted, inserted, Math.min(deleted.length, inserted.length));
|
|
8073
|
+
for (let length = sharedSuffix; length > 0; length--) {
|
|
8074
|
+
if (isWrapperBoundary(end - length)) {
|
|
8075
|
+
suffix = length;
|
|
8076
|
+
break;
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
const sharedPrefix = Math.min(
|
|
8080
|
+
commonPrefixLength(deleted, inserted),
|
|
8081
|
+
Math.min(deleted.length, inserted.length) - suffix
|
|
8082
|
+
);
|
|
8083
|
+
for (let length = sharedPrefix; length > 0; length--) {
|
|
8084
|
+
if (isWrapperBoundary(start + length)) {
|
|
8085
|
+
prefix = length;
|
|
8086
|
+
break;
|
|
8087
|
+
}
|
|
8088
|
+
}
|
|
8089
|
+
}
|
|
8090
|
+
if (prefix === 0 && suffix === 0) {
|
|
8091
|
+
result.push(...group);
|
|
8092
|
+
return;
|
|
8093
|
+
}
|
|
8094
|
+
if (prefix > 0) result.push([0, deleted.slice(0, prefix)]);
|
|
8095
|
+
pushChange(result, deleted.slice(prefix, deleted.length - suffix), inserted.slice(prefix, inserted.length - suffix));
|
|
8096
|
+
if (suffix > 0) result.push([0, deleted.slice(deleted.length - suffix)]);
|
|
8097
|
+
});
|
|
8098
|
+
return result;
|
|
8099
|
+
}
|
|
7966
8100
|
|
|
7967
8101
|
// engine/table-mode.js
|
|
7968
8102
|
function noChanges(serializer, xmlDoc) {
|
|
@@ -11511,6 +11645,260 @@ function ingestWordOoxmlToMarkdownResult(ooxml) {
|
|
|
11511
11645
|
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
11512
11646
|
}
|
|
11513
11647
|
|
|
11648
|
+
// services/comment-builders.js
|
|
11649
|
+
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
11650
|
+
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
11651
|
+
function createCommentParaId(commentId) {
|
|
11652
|
+
const numeric = Number.parseInt(String(commentId), 10);
|
|
11653
|
+
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
11654
|
+
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
11655
|
+
}
|
|
11656
|
+
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
11657
|
+
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
11658
|
+
const escapedContent = escapeXml(content);
|
|
11659
|
+
const escapedAuthor = escapeXml(author);
|
|
11660
|
+
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
11661
|
+
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
11662
|
+
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
11663
|
+
</w:p>
|
|
11664
|
+
</w:comment>`;
|
|
11665
|
+
}
|
|
11666
|
+
function buildCommentsPartXml(comments) {
|
|
11667
|
+
if (!comments || comments.length === 0) {
|
|
11668
|
+
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
11669
|
+
}
|
|
11670
|
+
const commentElements = comments.map(
|
|
11671
|
+
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
11672
|
+
).join("\n ");
|
|
11673
|
+
return `<w:comments xmlns:w="${NS_W}">
|
|
11674
|
+
${commentElements}
|
|
11675
|
+
</w:comments>`;
|
|
11676
|
+
}
|
|
11677
|
+
function buildCommentsExtendedPartXml(entries) {
|
|
11678
|
+
const body = (entries || []).map((entry) => {
|
|
11679
|
+
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
11680
|
+
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
11681
|
+
}).join("");
|
|
11682
|
+
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
11683
|
+
}
|
|
11684
|
+
|
|
11685
|
+
// services/comment-thread-parts.js
|
|
11686
|
+
var attrOf = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
11687
|
+
function commentThreadParagraph(commentElement) {
|
|
11688
|
+
const paragraphs = Array.from(commentElement?.getElementsByTagNameNS?.("*", "p") || []);
|
|
11689
|
+
return paragraphs.length ? paragraphs[paragraphs.length - 1] : null;
|
|
11690
|
+
}
|
|
11691
|
+
function commentParaId(commentElement) {
|
|
11692
|
+
return attrOf(commentThreadParagraph(commentElement), "w14:paraId", "paraId").toUpperCase();
|
|
11693
|
+
}
|
|
11694
|
+
function usedParaIds(commentsDoc, extendedDoc) {
|
|
11695
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11696
|
+
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
11697
|
+
const id = attrOf(p, "w14:paraId", "paraId");
|
|
11698
|
+
if (id) ids.add(id.toUpperCase());
|
|
11699
|
+
}
|
|
11700
|
+
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
11701
|
+
const id = attrOf(ex, "w15:paraId", "paraId");
|
|
11702
|
+
if (id) ids.add(id.toUpperCase());
|
|
11703
|
+
}
|
|
11704
|
+
return ids;
|
|
11705
|
+
}
|
|
11706
|
+
function allocateParaId(commentId, occupied) {
|
|
11707
|
+
let candidate = createCommentParaId(commentId);
|
|
11708
|
+
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
11709
|
+
while (occupied.has(candidate)) {
|
|
11710
|
+
value = value + 1 >>> 0;
|
|
11711
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
11712
|
+
}
|
|
11713
|
+
occupied.add(candidate);
|
|
11714
|
+
return candidate;
|
|
11715
|
+
}
|
|
11716
|
+
var parseDoc = (xml, partName) => {
|
|
11717
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
11718
|
+
if (!parsed.doc || parsed.error) return { error: { code: "PARSE_ERROR", message: `Could not parse ${partName}: ${parsed.error?.message || "invalid XML"}` } };
|
|
11719
|
+
return { doc: parsed.doc };
|
|
11720
|
+
};
|
|
11721
|
+
function applyThreadResolutionToParts({ commentsXml, commentsExtendedXml = null, commentId, resolved = true }) {
|
|
11722
|
+
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "Resolving a comment requires an existing word/comments.xml part." } };
|
|
11723
|
+
const commentsParsed = parseDoc(commentsXml, "word/comments.xml");
|
|
11724
|
+
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
11725
|
+
const commentsDoc = commentsParsed.doc;
|
|
11726
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
11727
|
+
const comments = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
11728
|
+
const target = comments.find((node) => idOf(node) === String(commentId));
|
|
11729
|
+
if (!target) return { status: "error", error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` } };
|
|
11730
|
+
let extendedDoc;
|
|
11731
|
+
if (commentsExtendedXml) {
|
|
11732
|
+
const parsed = parseDoc(commentsExtendedXml, "word/commentsExtended.xml");
|
|
11733
|
+
if (parsed.error) return { status: "error", error: parsed.error };
|
|
11734
|
+
extendedDoc = parsed.doc;
|
|
11735
|
+
} else {
|
|
11736
|
+
extendedDoc = parseDoc(`<w15:commentsEx xmlns:w15="${NS_W15}"/>`, "word/commentsExtended.xml").doc;
|
|
11737
|
+
}
|
|
11738
|
+
const root = extendedDoc.documentElement;
|
|
11739
|
+
const entryFor = (paraId) => Array.from(root.getElementsByTagNameNS("*", "commentEx")).find((node) => attrOf(node, "w15:paraId", "paraId").toUpperCase() === paraId) || null;
|
|
11740
|
+
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
11741
|
+
let commentsChanged = false;
|
|
11742
|
+
const keyOf = (node) => {
|
|
11743
|
+
let paraId = commentParaId(node);
|
|
11744
|
+
if (!paraId) {
|
|
11745
|
+
paraId = allocateParaId(idOf(node), occupied);
|
|
11746
|
+
commentThreadParagraph(node)?.setAttributeNS(NS_W14, "w14:paraId", paraId);
|
|
11747
|
+
commentsChanged = true;
|
|
11748
|
+
}
|
|
11749
|
+
return paraId;
|
|
11750
|
+
};
|
|
11751
|
+
const targetKey = keyOf(target);
|
|
11752
|
+
const targetEntry = entryFor(targetKey);
|
|
11753
|
+
const rootKey = targetEntry && attrOf(targetEntry, "w15:paraIdParent", "paraIdParent").toUpperCase() || targetKey;
|
|
11754
|
+
const threadKeys = /* @__PURE__ */ new Set([rootKey, targetKey]);
|
|
11755
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentEx"))) {
|
|
11756
|
+
if (attrOf(entry, "w15:paraIdParent", "paraIdParent").toUpperCase() === rootKey) threadKeys.add(attrOf(entry, "w15:paraId", "paraId").toUpperCase());
|
|
11757
|
+
}
|
|
11758
|
+
const doneValue = resolved ? "1" : "0";
|
|
11759
|
+
let extendedChanged = !commentsExtendedXml;
|
|
11760
|
+
for (const key of threadKeys) {
|
|
11761
|
+
let entry = entryFor(key);
|
|
11762
|
+
if (!entry) {
|
|
11763
|
+
entry = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
11764
|
+
entry.setAttributeNS(NS_W15, "w15:paraId", key);
|
|
11765
|
+
if (key !== rootKey) entry.setAttributeNS(NS_W15, "w15:paraIdParent", rootKey);
|
|
11766
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
11767
|
+
root.appendChild(entry);
|
|
11768
|
+
extendedChanged = true;
|
|
11769
|
+
} else if (attrOf(entry, "w15:done", "done") !== doneValue) {
|
|
11770
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
11771
|
+
extendedChanged = true;
|
|
11772
|
+
}
|
|
11773
|
+
}
|
|
11774
|
+
const byKey = new Map(comments.map((node) => [commentParaId(node), node]));
|
|
11775
|
+
const commentIds = [...threadKeys].map((key) => byKey.get(key)).filter(Boolean).map(idOf);
|
|
11776
|
+
const rootComment = byKey.get(rootKey);
|
|
11777
|
+
const serializer = createSerializer();
|
|
11778
|
+
const hasChanges = extendedChanged || commentsChanged;
|
|
11779
|
+
return {
|
|
11780
|
+
status: "ok",
|
|
11781
|
+
hasChanges,
|
|
11782
|
+
resolved,
|
|
11783
|
+
threadRootId: rootComment ? idOf(rootComment) : String(commentId),
|
|
11784
|
+
commentIds,
|
|
11785
|
+
...hasChanges ? { commentsExtendedXml: serializer.serializeToString(extendedDoc) } : {},
|
|
11786
|
+
...commentsChanged ? { commentsXml: serializer.serializeToString(commentsDoc) } : {}
|
|
11787
|
+
};
|
|
11788
|
+
}
|
|
11789
|
+
var NS_W_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
11790
|
+
function anchorReplyInDocument(xmlDoc, memberIds, newId) {
|
|
11791
|
+
const wanted = new Set(memberIds.map(String));
|
|
11792
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
11793
|
+
const all = (name) => Array.from(xmlDoc.getElementsByTagNameNS("*", name)).filter((node) => wanted.has(idOf(node)));
|
|
11794
|
+
const starts = all("commentRangeStart");
|
|
11795
|
+
const references = all("commentReference");
|
|
11796
|
+
if (!starts.length || !references.length) return false;
|
|
11797
|
+
const lastStart = starts[starts.length - 1];
|
|
11798
|
+
const lastReference = references[references.length - 1];
|
|
11799
|
+
const referenceRun = lastReference.parentNode?.localName === "r" ? lastReference.parentNode : lastReference;
|
|
11800
|
+
const make = (name, parent) => {
|
|
11801
|
+
const element = xmlDoc.createElementNS(NS_W_MAIN, `w:${name}`);
|
|
11802
|
+
element.setAttributeNS(NS_W_MAIN, "w:id", String(newId));
|
|
11803
|
+
if (parent) parent.appendChild(element);
|
|
11804
|
+
return element;
|
|
11805
|
+
};
|
|
11806
|
+
const insertAfter = (anchor, node) => anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
11807
|
+
insertAfter(lastStart, make("commentRangeStart"));
|
|
11808
|
+
const end = make("commentRangeEnd");
|
|
11809
|
+
const run = xmlDoc.createElementNS(NS_W_MAIN, "w:r");
|
|
11810
|
+
run.appendChild(xmlDoc.createElementNS(NS_W_MAIN, "w:rPr"));
|
|
11811
|
+
make("commentReference", run);
|
|
11812
|
+
insertAfter(referenceRun, end);
|
|
11813
|
+
insertAfter(end, run);
|
|
11814
|
+
return true;
|
|
11815
|
+
}
|
|
11816
|
+
var NS_W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
|
|
11817
|
+
var NS_W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
|
|
11818
|
+
var durableIdFor = (paraId, taken) => {
|
|
11819
|
+
let value = (Number.parseInt(paraId, 16) ^ 625341585) & 2147483646;
|
|
11820
|
+
let candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
11821
|
+
while (taken.has(candidate)) {
|
|
11822
|
+
value = value + 2 & 2147483646;
|
|
11823
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
11824
|
+
}
|
|
11825
|
+
taken.add(candidate);
|
|
11826
|
+
return candidate;
|
|
11827
|
+
};
|
|
11828
|
+
function reconcileCommentSiblingParts({ commentsXml, commentsIdsXml = null, commentsExtensibleXml = null }) {
|
|
11829
|
+
const out = {};
|
|
11830
|
+
if (!commentsXml || !commentsIdsXml && !commentsExtensibleXml) return out;
|
|
11831
|
+
const commentsParsed = parseOoxmlSafe(commentsXml, "application/xml");
|
|
11832
|
+
if (!commentsParsed.doc || commentsParsed.error) return out;
|
|
11833
|
+
const serializer = createSerializer();
|
|
11834
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
11835
|
+
for (const node of Array.from(commentsParsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
11836
|
+
const key = commentParaId(node);
|
|
11837
|
+
if (key) byKey.set(key, node);
|
|
11838
|
+
}
|
|
11839
|
+
let idsDoc = null;
|
|
11840
|
+
const durableByKey = /* @__PURE__ */ new Map();
|
|
11841
|
+
const taken = /* @__PURE__ */ new Set();
|
|
11842
|
+
let idsChanged = false;
|
|
11843
|
+
if (commentsIdsXml) {
|
|
11844
|
+
const parsed = parseOoxmlSafe(commentsIdsXml, "application/xml");
|
|
11845
|
+
if (!parsed.doc || parsed.error) return out;
|
|
11846
|
+
idsDoc = parsed.doc;
|
|
11847
|
+
const root = idsDoc.documentElement;
|
|
11848
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentId"))) {
|
|
11849
|
+
const key = attrOf(entry, "w16cid:paraId", "paraId").toUpperCase();
|
|
11850
|
+
const durable = attrOf(entry, "w16cid:durableId", "durableId").toUpperCase();
|
|
11851
|
+
if (durable) taken.add(durable);
|
|
11852
|
+
if (!byKey.has(key)) {
|
|
11853
|
+
root.removeChild(entry);
|
|
11854
|
+
idsChanged = true;
|
|
11855
|
+
continue;
|
|
11856
|
+
}
|
|
11857
|
+
durableByKey.set(key, durable);
|
|
11858
|
+
}
|
|
11859
|
+
for (const key of byKey.keys()) {
|
|
11860
|
+
if (durableByKey.has(key)) continue;
|
|
11861
|
+
const durable = durableIdFor(key, taken);
|
|
11862
|
+
const entry = idsDoc.createElementNS(NS_W16CID, "w16cid:commentId");
|
|
11863
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:paraId", key);
|
|
11864
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:durableId", durable);
|
|
11865
|
+
root.appendChild(entry);
|
|
11866
|
+
durableByKey.set(key, durable);
|
|
11867
|
+
idsChanged = true;
|
|
11868
|
+
}
|
|
11869
|
+
}
|
|
11870
|
+
let extDoc = null;
|
|
11871
|
+
let extChanged = false;
|
|
11872
|
+
if (commentsExtensibleXml && idsDoc) {
|
|
11873
|
+
const parsed = parseOoxmlSafe(commentsExtensibleXml, "application/xml");
|
|
11874
|
+
if (parsed.doc && !parsed.error) {
|
|
11875
|
+
extDoc = parsed.doc;
|
|
11876
|
+
const root = extDoc.documentElement;
|
|
11877
|
+
const live = new Set(durableByKey.values());
|
|
11878
|
+
const present = /* @__PURE__ */ new Set();
|
|
11879
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentExtensible"))) {
|
|
11880
|
+
const durable = attrOf(entry, "w16cex:durableId", "durableId").toUpperCase();
|
|
11881
|
+
if (!live.has(durable)) {
|
|
11882
|
+
root.removeChild(entry);
|
|
11883
|
+
extChanged = true;
|
|
11884
|
+
} else present.add(durable);
|
|
11885
|
+
}
|
|
11886
|
+
for (const [key, durable] of durableByKey) {
|
|
11887
|
+
if (present.has(durable)) continue;
|
|
11888
|
+
const date = attrOf(byKey.get(key), "w:date", "date");
|
|
11889
|
+
const entry = extDoc.createElementNS(NS_W16CEX, "w16cex:commentExtensible");
|
|
11890
|
+
entry.setAttributeNS(NS_W16CEX, "w16cex:durableId", durable);
|
|
11891
|
+
if (date) entry.setAttributeNS(NS_W16CEX, "w16cex:dateUtc", date);
|
|
11892
|
+
root.appendChild(entry);
|
|
11893
|
+
extChanged = true;
|
|
11894
|
+
}
|
|
11895
|
+
}
|
|
11896
|
+
}
|
|
11897
|
+
if (idsChanged) out.commentsIdsXml = serializer.serializeToString(idsDoc);
|
|
11898
|
+
if (extChanged) out.commentsExtensibleXml = serializer.serializeToString(extDoc);
|
|
11899
|
+
return out;
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11514
11902
|
// core/sha256.js
|
|
11515
11903
|
var K = new Uint32Array([
|
|
11516
11904
|
1116352408,
|
|
@@ -12003,7 +12391,7 @@ function readCommentDefinitions(commentsDoc) {
|
|
|
12003
12391
|
author: attr2(comment, "author") || null,
|
|
12004
12392
|
date: attr2(comment, "date") || null,
|
|
12005
12393
|
text: paragraphs.map((p) => extractCanonicalParagraphText(p)).join("\n"),
|
|
12006
|
-
paraId:
|
|
12394
|
+
paraId: commentParaId(comment) || null
|
|
12007
12395
|
});
|
|
12008
12396
|
}
|
|
12009
12397
|
return result;
|
|
@@ -12234,43 +12622,6 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
12234
12622
|
};
|
|
12235
12623
|
}
|
|
12236
12624
|
|
|
12237
|
-
// services/comment-builders.js
|
|
12238
|
-
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
12239
|
-
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
12240
|
-
function createCommentParaId(commentId) {
|
|
12241
|
-
const numeric = Number.parseInt(String(commentId), 10);
|
|
12242
|
-
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
12243
|
-
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
12244
|
-
}
|
|
12245
|
-
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
12246
|
-
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
12247
|
-
const escapedContent = escapeXml(content);
|
|
12248
|
-
const escapedAuthor = escapeXml(author);
|
|
12249
|
-
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
12250
|
-
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
12251
|
-
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
12252
|
-
</w:p>
|
|
12253
|
-
</w:comment>`;
|
|
12254
|
-
}
|
|
12255
|
-
function buildCommentsPartXml(comments) {
|
|
12256
|
-
if (!comments || comments.length === 0) {
|
|
12257
|
-
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
12258
|
-
}
|
|
12259
|
-
const commentElements = comments.map(
|
|
12260
|
-
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
12261
|
-
).join("\n ");
|
|
12262
|
-
return `<w:comments xmlns:w="${NS_W}">
|
|
12263
|
-
${commentElements}
|
|
12264
|
-
</w:comments>`;
|
|
12265
|
-
}
|
|
12266
|
-
function buildCommentsExtendedPartXml(entries) {
|
|
12267
|
-
const body = (entries || []).map((entry) => {
|
|
12268
|
-
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
12269
|
-
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
12270
|
-
}).join("");
|
|
12271
|
-
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
12272
|
-
}
|
|
12273
|
-
|
|
12274
12625
|
// services/comment-locator.js
|
|
12275
12626
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
12276
12627
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -12731,29 +13082,7 @@ function parseRequired(xml, partName) {
|
|
|
12731
13082
|
}
|
|
12732
13083
|
return { doc: parsed.doc };
|
|
12733
13084
|
}
|
|
12734
|
-
function
|
|
12735
|
-
const ids = /* @__PURE__ */ new Set();
|
|
12736
|
-
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
12737
|
-
const id = attr3(p, "w14:paraId", "paraId");
|
|
12738
|
-
if (id) ids.add(id.toUpperCase());
|
|
12739
|
-
}
|
|
12740
|
-
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
12741
|
-
const id = attr3(ex, "w15:paraId", "paraId");
|
|
12742
|
-
if (id) ids.add(id.toUpperCase());
|
|
12743
|
-
}
|
|
12744
|
-
return ids;
|
|
12745
|
-
}
|
|
12746
|
-
function allocateParaId(commentId, occupied) {
|
|
12747
|
-
let candidate = createCommentParaId(commentId);
|
|
12748
|
-
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
12749
|
-
while (occupied.has(candidate)) {
|
|
12750
|
-
value = value + 1 >>> 0;
|
|
12751
|
-
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
12752
|
-
}
|
|
12753
|
-
occupied.add(candidate);
|
|
12754
|
-
return candidate;
|
|
12755
|
-
}
|
|
12756
|
-
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
13085
|
+
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, documentXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
12757
13086
|
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "A comment reply requires an existing word/comments.xml part." } };
|
|
12758
13087
|
const commentsParsed = parseRequired(commentsXml, "word/comments.xml");
|
|
12759
13088
|
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
@@ -12767,15 +13096,31 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
12767
13096
|
extendedDoc = parsed.doc;
|
|
12768
13097
|
}
|
|
12769
13098
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
12770
|
-
const
|
|
13099
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/w14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
13100
|
+
const parentParagraph = commentThreadParagraph(parent);
|
|
12771
13101
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
12772
|
-
let parentParaId =
|
|
13102
|
+
let parentParaId = commentParaId(parent);
|
|
12773
13103
|
if (!parentParaId) {
|
|
12774
13104
|
parentParaId = allocateParaId(parentCommentId, occupied);
|
|
12775
13105
|
parentParagraph.setAttributeNS(NS_W14, "w14:paraId", parentParaId);
|
|
12776
13106
|
} else {
|
|
12777
13107
|
parentParaId = parentParaId.toUpperCase();
|
|
12778
13108
|
}
|
|
13109
|
+
for (const node of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
13110
|
+
if (attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId) {
|
|
13111
|
+
const root2 = attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase();
|
|
13112
|
+
if (root2) parentParaId = root2;
|
|
13113
|
+
break;
|
|
13114
|
+
}
|
|
13115
|
+
}
|
|
13116
|
+
const threadCommentIds = [];
|
|
13117
|
+
const commentNodes = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
13118
|
+
const parentOf = new Map(Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || []).map((node) => [attr3(node, "w15:paraId", "paraId").toUpperCase(), attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase()]));
|
|
13119
|
+
for (const node of commentNodes) {
|
|
13120
|
+
const key = commentParaId(node);
|
|
13121
|
+
if (key === parentParaId || parentOf.get(key) === parentParaId) threadCommentIds.push(attr3(node, "w:id", "id"));
|
|
13122
|
+
}
|
|
13123
|
+
const threadRootCommentId = attr3(commentNodes.find((node) => commentParaId(node) === parentParaId), "w:id", "id") || String(parentCommentId);
|
|
12779
13124
|
const replyParaId = allocateParaId(commentId, occupied);
|
|
12780
13125
|
const replyParsed = parseRequired(`<w:comments xmlns:w="${NS_W6}" xmlns:w14="${NS_W14}">${buildCommentElement(commentId, author, commentContent, date, replyParaId)}</w:comments>`, "reply comment");
|
|
12781
13126
|
commentsDoc.documentElement.appendChild(commentsDoc.importNode(replyParsed.doc.documentElement.firstChild, true));
|
|
@@ -12784,16 +13129,18 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
12784
13129
|
}
|
|
12785
13130
|
const root = extendedDoc.documentElement;
|
|
12786
13131
|
const entries = Array.from(root.getElementsByTagNameNS("*", "commentEx"));
|
|
12787
|
-
|
|
13132
|
+
const rootEntry = entries.find((node) => attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId);
|
|
13133
|
+
if (!rootEntry) {
|
|
12788
13134
|
const parentEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
12789
13135
|
parentEx.setAttributeNS(NS_W15, "w15:paraId", parentParaId);
|
|
12790
13136
|
parentEx.setAttributeNS(NS_W15, "w15:done", "0");
|
|
12791
13137
|
root.appendChild(parentEx);
|
|
12792
13138
|
}
|
|
13139
|
+
const threadDone = attr3(rootEntry, "w15:done", "done") === "1" ? "1" : "0";
|
|
12793
13140
|
const replyEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
12794
13141
|
replyEx.setAttributeNS(NS_W15, "w15:paraId", replyParaId);
|
|
12795
13142
|
replyEx.setAttributeNS(NS_W15, "w15:paraIdParent", parentParaId);
|
|
12796
|
-
replyEx.setAttributeNS(NS_W15, "w15:done",
|
|
13143
|
+
replyEx.setAttributeNS(NS_W15, "w15:done", threadDone);
|
|
12797
13144
|
root.appendChild(replyEx);
|
|
12798
13145
|
const serializer = createSerializer();
|
|
12799
13146
|
return {
|
|
@@ -12806,7 +13153,9 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
12806
13153
|
commentId,
|
|
12807
13154
|
parentCommentId: String(parentCommentId),
|
|
12808
13155
|
paraId: replyParaId,
|
|
12809
|
-
parentParaId
|
|
13156
|
+
parentParaId,
|
|
13157
|
+
threadRootCommentId,
|
|
13158
|
+
threadCommentIds: threadCommentIds.length ? threadCommentIds : [threadRootCommentId]
|
|
12810
13159
|
};
|
|
12811
13160
|
}
|
|
12812
13161
|
|
|
@@ -12871,19 +13220,19 @@ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
|
12871
13220
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
12872
13221
|
const doc = parseOoxml(ooxmlString);
|
|
12873
13222
|
if (!doc) return ooxmlString;
|
|
12874
|
-
const
|
|
12875
|
-
const runs = doc.getElementsByTagNameNS(
|
|
12876
|
-
const insertions = doc.getElementsByTagNameNS(
|
|
13223
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
13224
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
13225
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
12877
13226
|
const allRuns = [...Array.from(runs)];
|
|
12878
13227
|
for (const ins of insertions) {
|
|
12879
|
-
const insideRuns = ins.getElementsByTagNameNS(
|
|
13228
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
12880
13229
|
allRuns.push(...Array.from(insideRuns));
|
|
12881
13230
|
}
|
|
12882
13231
|
for (const run of allRuns) {
|
|
12883
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
13232
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
12884
13233
|
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
12885
13234
|
if (runText.includes(targetText) || runText === targetText) {
|
|
12886
|
-
const rPrElements = run.getElementsByTagNameNS(
|
|
13235
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
12887
13236
|
if (rPrElements.length > 0) {
|
|
12888
13237
|
const rPr = rPrElements[0];
|
|
12889
13238
|
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
@@ -12918,7 +13267,7 @@ var HIGHLIGHT_COLOR_MAP = {
|
|
|
12918
13267
|
"white": "white"
|
|
12919
13268
|
};
|
|
12920
13269
|
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
12921
|
-
const
|
|
13270
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
12922
13271
|
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
12923
13272
|
const generateRedlines = options?.generateRedlines ?? false;
|
|
12924
13273
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -12937,10 +13286,10 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
12937
13286
|
}
|
|
12938
13287
|
});
|
|
12939
13288
|
}
|
|
12940
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(
|
|
13289
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
12941
13290
|
Array.from(existingHighlight).forEach(removeNode2);
|
|
12942
13291
|
const highlightEl = createWordElement(doc, "w:highlight");
|
|
12943
|
-
highlightEl.setAttributeNS(
|
|
13292
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
12944
13293
|
rPrElement.appendChild(highlightEl);
|
|
12945
13294
|
if (generateRedlines && previousRPrState) {
|
|
12946
13295
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
@@ -12949,7 +13298,7 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
12949
13298
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
12950
13299
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
12951
13300
|
rPrChange.appendChild(previousRPrState);
|
|
12952
|
-
const existingChange = rPrElement.getElementsByTagNameNS(
|
|
13301
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
12953
13302
|
Array.from(existingChange).forEach(removeNode2);
|
|
12954
13303
|
rPrElement.appendChild(rPrChange);
|
|
12955
13304
|
}
|
|
@@ -12966,12 +13315,12 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
12966
13315
|
} else {
|
|
12967
13316
|
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
12968
13317
|
}
|
|
12969
|
-
const
|
|
13318
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
12970
13319
|
const getRunText = (run) => {
|
|
12971
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
13320
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
12972
13321
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
12973
13322
|
};
|
|
12974
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(
|
|
13323
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
12975
13324
|
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
12976
13325
|
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
12977
13326
|
const clonedRun = sourceRun.cloneNode(true);
|
|
@@ -12980,14 +13329,14 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
12980
13329
|
} else {
|
|
12981
13330
|
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
12982
13331
|
}
|
|
12983
|
-
const textNodes = clonedRun.getElementsByTagNameNS(
|
|
13332
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
12984
13333
|
Array.from(textNodes).forEach(removeNode2);
|
|
12985
13334
|
const newText = createWordElement(doc, "w:t");
|
|
12986
13335
|
newText.setAttribute("xml:space", "preserve");
|
|
12987
13336
|
newText.textContent = text2;
|
|
12988
13337
|
clonedRun.appendChild(newText);
|
|
12989
13338
|
if (shouldHighlight) {
|
|
12990
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(
|
|
13339
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
12991
13340
|
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
12992
13341
|
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
12993
13342
|
if (existingRPr) {
|
|
@@ -13036,20 +13385,68 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
13036
13385
|
return serializeOoxml(doc);
|
|
13037
13386
|
}
|
|
13038
13387
|
|
|
13388
|
+
// services/package-parts.js
|
|
13389
|
+
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
13390
|
+
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
13391
|
+
var PART_SPECS = Object.freeze([
|
|
13392
|
+
{ id: "document", path: "word/document.xml", pathPattern: null, contentType: `${WORD_ML}.document.main+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/officeDocument`, relTarget: null, verified: true },
|
|
13393
|
+
{ id: "numbering", path: "word/numbering.xml", pathPattern: null, contentType: `${WORD_ML}.numbering+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/numbering`, relTarget: "numbering.xml", verified: false },
|
|
13394
|
+
{ id: "comments", path: "word/comments.xml", pathPattern: null, contentType: `${WORD_ML}.comments+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/comments`, relTarget: "comments.xml", verified: true },
|
|
13395
|
+
{
|
|
13396
|
+
id: "commentsExtended",
|
|
13397
|
+
path: "word/commentsExtended.xml",
|
|
13398
|
+
pathPattern: null,
|
|
13399
|
+
contentType: `${WORD_ML}.commentsExtended+xml`,
|
|
13400
|
+
// Written by this package through 0.8.0; Word does not recognize it and offers to repair the file.
|
|
13401
|
+
legacyContentTypes: ["application/vnd.ms-word.commentsExtended+xml"],
|
|
13402
|
+
relType: "http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
|
|
13403
|
+
relTarget: "commentsExtended.xml",
|
|
13404
|
+
verified: true
|
|
13405
|
+
},
|
|
13406
|
+
{ 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 },
|
|
13407
|
+
{ 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 },
|
|
13408
|
+
// Not yet produced by any Word fixture (Word only writes people.xml for signed-in authors): unverified.
|
|
13409
|
+
{ 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 },
|
|
13410
|
+
{ 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 },
|
|
13411
|
+
{ 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 }
|
|
13412
|
+
].map((spec) => Object.freeze(spec)));
|
|
13413
|
+
var BY_ID = new Map(PART_SPECS.map((spec) => [spec.id, spec]));
|
|
13414
|
+
function getPartSpec(id) {
|
|
13415
|
+
const spec = BY_ID.get(id);
|
|
13416
|
+
if (!spec) throw new Error(`Unknown package part: ${id}`);
|
|
13417
|
+
return spec;
|
|
13418
|
+
}
|
|
13419
|
+
function toEntryName(partName) {
|
|
13420
|
+
return String(partName || "").replace(/^\/+/, "");
|
|
13421
|
+
}
|
|
13422
|
+
function findPartSpecByPartName(partName) {
|
|
13423
|
+
const name = toEntryName(partName);
|
|
13424
|
+
const lower = name.toLowerCase();
|
|
13425
|
+
return PART_SPECS.find((spec) => spec.path ? spec.path.toLowerCase() === lower : spec.pathPattern?.test(name)) || null;
|
|
13426
|
+
}
|
|
13427
|
+
function correctedContentTypeFor(partName, actualContentType) {
|
|
13428
|
+
const spec = findPartSpecByPartName(partName);
|
|
13429
|
+
if (!spec || !spec.legacyContentTypes.includes(actualContentType)) return null;
|
|
13430
|
+
return spec.contentType;
|
|
13431
|
+
}
|
|
13432
|
+
|
|
13039
13433
|
// services/standalone-docx-plumbing.js
|
|
13040
13434
|
var NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
13041
13435
|
var NS_CT = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
13042
13436
|
var NS_RELS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
13043
|
-
var
|
|
13044
|
-
var
|
|
13045
|
-
var
|
|
13046
|
-
var
|
|
13047
|
-
var
|
|
13048
|
-
var
|
|
13049
|
-
var
|
|
13050
|
-
var
|
|
13051
|
-
var
|
|
13052
|
-
var
|
|
13437
|
+
var NUMBERING = getPartSpec("numbering");
|
|
13438
|
+
var COMMENTS = getPartSpec("comments");
|
|
13439
|
+
var COMMENTS_EXTENDED = getPartSpec("commentsExtended");
|
|
13440
|
+
var NUMBERING_REL_TYPE = NUMBERING.relType;
|
|
13441
|
+
var NUMBERING_CONTENT_TYPE = NUMBERING.contentType;
|
|
13442
|
+
var COMMENTS_REL_TYPE = COMMENTS.relType;
|
|
13443
|
+
var COMMENTS_CONTENT_TYPE = COMMENTS.contentType;
|
|
13444
|
+
var COMMENTS_EXTENDED_REL_TYPE = COMMENTS_EXTENDED.relType;
|
|
13445
|
+
var COMMENTS_EXTENDED_CONTENT_TYPE = COMMENTS_EXTENDED.contentType;
|
|
13446
|
+
var DOCUMENT_PATH = getPartSpec("document").path;
|
|
13447
|
+
var NUMBERING_PATH = NUMBERING.path;
|
|
13448
|
+
var COMMENTS_PATH = COMMENTS.path;
|
|
13449
|
+
var COMMENTS_EXTENDED_PATH = COMMENTS_EXTENDED.path;
|
|
13053
13450
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
13054
13451
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
13055
13452
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
@@ -13337,6 +13734,22 @@ async function ensureCommentsExtendedArtifactsInZip(zip, commentsExtendedXml, op
|
|
|
13337
13734
|
}
|
|
13338
13735
|
}
|
|
13339
13736
|
}
|
|
13737
|
+
async function repairKnownContentTypes(zip) {
|
|
13738
|
+
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
13739
|
+
if (!ctText) return [];
|
|
13740
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
13741
|
+
const repairs = [];
|
|
13742
|
+
for (const override of Array.from(ctDoc.getElementsByTagNameNS("*", "Override"))) {
|
|
13743
|
+
const partName = override.getAttribute("PartName") || "";
|
|
13744
|
+
const actual = override.getAttribute("ContentType") || "";
|
|
13745
|
+
const expected = correctedContentTypeFor(partName, actual);
|
|
13746
|
+
if (!expected) continue;
|
|
13747
|
+
override.setAttribute("ContentType", expected);
|
|
13748
|
+
repairs.push({ partName, from: actual, to: expected });
|
|
13749
|
+
}
|
|
13750
|
+
if (repairs.length > 0) zip.file(CONTENT_TYPES_PATH, createSerializer().serializeToString(ctDoc));
|
|
13751
|
+
return repairs;
|
|
13752
|
+
}
|
|
13340
13753
|
async function validateDocxPackage(zip) {
|
|
13341
13754
|
const documentXml = await readZipText(zip, DOCUMENT_PATH);
|
|
13342
13755
|
if (!documentXml) {
|
|
@@ -13418,9 +13831,8 @@ async function validateDocxPackage(zip) {
|
|
|
13418
13831
|
const paraIdToCommentId = /* @__PURE__ */ new Map();
|
|
13419
13832
|
for (const comment of Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
13420
13833
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
13421
|
-
const
|
|
13422
|
-
|
|
13423
|
-
if (paraId) paraIdToCommentId.set(paraId.toUpperCase(), id);
|
|
13834
|
+
const paraId = commentParaId(comment);
|
|
13835
|
+
if (paraId) paraIdToCommentId.set(paraId, id);
|
|
13424
13836
|
}
|
|
13425
13837
|
const knownParaIds = new Set(paraIdToCommentId.keys());
|
|
13426
13838
|
const extendedParaIds = /* @__PURE__ */ new Set();
|
|
@@ -13481,10 +13893,39 @@ async function validateDocxPackage(zip) {
|
|
|
13481
13893
|
throw new Error("Validation failed: comments rel missing");
|
|
13482
13894
|
}
|
|
13483
13895
|
}
|
|
13896
|
+
{
|
|
13897
|
+
const relationshipById = /* @__PURE__ */ new Map();
|
|
13898
|
+
for (const rel of Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
13899
|
+
relationshipById.set(rel.getAttribute("Id") || "", { type: rel.getAttribute("Type") || "", target: rel.getAttribute("Target") || "" });
|
|
13900
|
+
}
|
|
13901
|
+
const headerSpec = getPartSpec("header");
|
|
13902
|
+
const footerSpec = getPartSpec("footer");
|
|
13903
|
+
const overrideTypeByPart = new Map(Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).map((override) => [(override.getAttribute("PartName") || "").toLowerCase(), override.getAttribute("ContentType") || ""]));
|
|
13904
|
+
for (const reference of [
|
|
13905
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "headerReference")),
|
|
13906
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "footerReference"))
|
|
13907
|
+
]) {
|
|
13908
|
+
const relId = reference.getAttributeNS?.("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") || reference.getAttribute("r:id") || "";
|
|
13909
|
+
const spec = reference.localName === "headerReference" ? headerSpec : footerSpec;
|
|
13910
|
+
const relationship = relationshipById.get(relId);
|
|
13911
|
+
if (!relationship) throw new Error(`Validation failed: ${reference.localName} ${relId || "(no r:id)"} has no document relationship`);
|
|
13912
|
+
if (relationship.type !== spec.relType) throw new Error(`Validation failed: ${reference.localName} ${relId} points at a relationship of the wrong type (${relationship.type})`);
|
|
13913
|
+
const partName = relationship.target.startsWith("/") ? relationship.target : `/word/${relationship.target}`;
|
|
13914
|
+
const partXml = await readZipText(zip, partName.slice(1));
|
|
13915
|
+
if (!partXml) throw new Error(`Validation failed: ${reference.localName} ${relId} targets a missing part: ${partName}`);
|
|
13916
|
+
parseXmlStrictStandalone(partXml, partName.slice(1));
|
|
13917
|
+
const contentType = overrideTypeByPart.get(partName.toLowerCase());
|
|
13918
|
+
if (contentType !== spec.contentType) throw new Error(`Validation failed: ${partName} content type is ${contentType ? `'${contentType}'` : "missing"}; Word expects '${spec.contentType}'`);
|
|
13919
|
+
}
|
|
13920
|
+
}
|
|
13484
13921
|
if (commentsExtendedXml) {
|
|
13485
|
-
const
|
|
13486
|
-
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
13922
|
+
const extendedOverride = Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).find(
|
|
13923
|
+
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
13487
13924
|
);
|
|
13925
|
+
if (extendedOverride && extendedOverride.getAttribute("ContentType") !== COMMENTS_EXTENDED_CONTENT_TYPE) {
|
|
13926
|
+
throw new Error(`Validation failed: commentsExtended CT override has wrong content type '${extendedOverride.getAttribute("ContentType")}' (Word expects '${COMMENTS_EXTENDED_CONTENT_TYPE}')`);
|
|
13927
|
+
}
|
|
13928
|
+
const hasContentType = !!extendedOverride;
|
|
13488
13929
|
const hasRelationship = Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship")).some(
|
|
13489
13930
|
(rel) => (rel.getAttribute("Type") || "") === COMMENTS_EXTENDED_REL_TYPE
|
|
13490
13931
|
);
|
|
@@ -13848,6 +14289,156 @@ function reconcileReceiptsAgainstOutput(parts, receipts) {
|
|
|
13848
14289
|
return { valid: true };
|
|
13849
14290
|
}
|
|
13850
14291
|
|
|
14292
|
+
// services/headers-footers.js
|
|
14293
|
+
var NS_W8 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
14294
|
+
var NS_R2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
14295
|
+
var REVISION_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo", "rPrChange", "pPrChange", "sectPrChange", "tblPrChange", "tblGridChange", "tcPrChange", "trPrChange", "numberingChange"]);
|
|
14296
|
+
var REVISION_CONTENT_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
14297
|
+
var attrOf2 = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
14298
|
+
function resolveTarget(target) {
|
|
14299
|
+
const clean = String(target || "");
|
|
14300
|
+
if (clean.startsWith("/")) return clean.replace(/^\/+/, "");
|
|
14301
|
+
const parts = ["word"];
|
|
14302
|
+
for (const segment of clean.split("/")) {
|
|
14303
|
+
if (segment === "..") parts.pop();
|
|
14304
|
+
else if (segment && segment !== ".") parts.push(segment);
|
|
14305
|
+
}
|
|
14306
|
+
return parts.join("/");
|
|
14307
|
+
}
|
|
14308
|
+
function discoverHeaderFooterParts({ documentXml, relsXml = null, settingsXml = null }) {
|
|
14309
|
+
if (!documentXml || !relsXml) return [];
|
|
14310
|
+
const documentParsed = parseOoxmlSafe(documentXml, "application/xml");
|
|
14311
|
+
const relsParsed = parseOoxmlSafe(relsXml, "application/xml");
|
|
14312
|
+
if (!documentParsed.doc || documentParsed.error || !relsParsed.doc || relsParsed.error) return [];
|
|
14313
|
+
const targetByRelId = /* @__PURE__ */ new Map();
|
|
14314
|
+
for (const rel of Array.from(relsParsed.doc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
14315
|
+
targetByRelId.set(rel.getAttribute("Id") || "", rel.getAttribute("Target") || "");
|
|
14316
|
+
}
|
|
14317
|
+
const evenAndOdd = !!settingsXml && /<w:evenAndOddHeaders\b(?![^>]*w:val="(?:0|false|off)")/.test(settingsXml);
|
|
14318
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
14319
|
+
const inForce = /* @__PURE__ */ new Map();
|
|
14320
|
+
const sectPrs = Array.from(documentParsed.doc.getElementsByTagNameNS(NS_W8, "sectPr"));
|
|
14321
|
+
sectPrs.forEach((sectPr, sectionIndex) => {
|
|
14322
|
+
const titlePg = Array.from(sectPr.childNodes || []).some((child) => child.localName === "titlePg" && !/^(0|false|off)$/i.test(attrOf2(child, "w:val", "val")));
|
|
14323
|
+
for (const ref of Array.from(sectPr.childNodes || [])) {
|
|
14324
|
+
if (ref.nodeType !== 1 || ref.localName !== "headerReference" && ref.localName !== "footerReference") continue;
|
|
14325
|
+
const relId = ref.getAttributeNS?.(NS_R2, "id") || ref.getAttribute("r:id") || "";
|
|
14326
|
+
const target = targetByRelId.get(relId);
|
|
14327
|
+
if (!target) continue;
|
|
14328
|
+
const path = resolveTarget(target);
|
|
14329
|
+
const kind = ref.localName === "headerReference" ? "header" : "footer";
|
|
14330
|
+
const type = attrOf2(ref, "w:type", "type") || "default";
|
|
14331
|
+
const entry = byPath.get(path) || { path, kind, type, sections: [], applies: [], titlePgSections: 0 };
|
|
14332
|
+
if (!entry.sections.includes(sectionIndex)) entry.sections.push(sectionIndex);
|
|
14333
|
+
if (titlePg) entry.titlePgSections += 1;
|
|
14334
|
+
byPath.set(path, entry);
|
|
14335
|
+
inForce.set(`${kind}:${type}`, path);
|
|
14336
|
+
}
|
|
14337
|
+
for (const path of inForce.values()) {
|
|
14338
|
+
const entry = byPath.get(path);
|
|
14339
|
+
if (entry && !entry.applies.includes(sectionIndex)) entry.applies.push(sectionIndex);
|
|
14340
|
+
}
|
|
14341
|
+
});
|
|
14342
|
+
return Array.from(byPath.values()).map((entry) => ({
|
|
14343
|
+
path: entry.path,
|
|
14344
|
+
kind: entry.kind,
|
|
14345
|
+
type: entry.type,
|
|
14346
|
+
sections: entry.sections,
|
|
14347
|
+
appliesToSections: entry.applies,
|
|
14348
|
+
sharedBySections: entry.applies.length > 1,
|
|
14349
|
+
// first-page parts only show with w:titlePg; even parts only with w:evenAndOddHeaders in settings.xml.
|
|
14350
|
+
active: entry.type === "first" ? entry.titlePgSections > 0 : entry.type === "even" ? evenAndOdd : true
|
|
14351
|
+
}));
|
|
14352
|
+
}
|
|
14353
|
+
function resolvePartSelector(parts, selector) {
|
|
14354
|
+
const describe = (part) => ({ path: part.path, kind: part.kind, type: part.type, sections: part.sections, appliesToSections: part.appliesToSections });
|
|
14355
|
+
if (typeof selector === "string") {
|
|
14356
|
+
const wanted = selector.replace(/^\/+/, "").toLowerCase();
|
|
14357
|
+
const match = parts.find((part) => part.path.toLowerCase() === wanted || part.path.toLowerCase() === `word/${wanted}`);
|
|
14358
|
+
if (match) return { part: match };
|
|
14359
|
+
return { error: { code: "PART_NOT_FOUND", message: `No header/footer part '${selector}' is referenced by the document.`, candidates: parts.map(describe) } };
|
|
14360
|
+
}
|
|
14361
|
+
if (!selector || typeof selector !== "object" || selector.kind !== "header" && selector.kind !== "footer") {
|
|
14362
|
+
return { error: { code: "INVALID_OPERATION", message: 'part must be a part path or { kind: "header"|"footer", type?: "default"|"first"|"even", section?: number }.' } };
|
|
14363
|
+
}
|
|
14364
|
+
const type = selector.type || "default";
|
|
14365
|
+
if (!["default", "first", "even"].includes(type)) {
|
|
14366
|
+
return { error: { code: "INVALID_OPERATION", message: `part.type must be default, first or even (got '${type}').` } };
|
|
14367
|
+
}
|
|
14368
|
+
let matches = parts.filter((part) => part.kind === selector.kind && part.type === type);
|
|
14369
|
+
if (Number.isInteger(selector.section)) matches = matches.filter((part) => part.appliesToSections.includes(selector.section));
|
|
14370
|
+
if (matches.length === 1) return { part: matches[0] };
|
|
14371
|
+
if (matches.length === 0) {
|
|
14372
|
+
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) } };
|
|
14373
|
+
}
|
|
14374
|
+
return { error: { code: "PART_AMBIGUOUS", message: `Several ${type} ${selector.kind} parts exist; add "section" or use the part path.`, candidates: matches.map(describe) } };
|
|
14375
|
+
}
|
|
14376
|
+
function isHeaderFooterPath(path) {
|
|
14377
|
+
const spec = findPartSpecByPartName(path);
|
|
14378
|
+
return spec?.id === "header" || spec?.id === "footer";
|
|
14379
|
+
}
|
|
14380
|
+
function countRevisionsInsideFields(xml) {
|
|
14381
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
14382
|
+
if (!parsed.doc || parsed.error) return 0;
|
|
14383
|
+
let depth = 0;
|
|
14384
|
+
let count = 0;
|
|
14385
|
+
const visit = (node) => {
|
|
14386
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
14387
|
+
if (child.nodeType !== 1) continue;
|
|
14388
|
+
const name = child.localName;
|
|
14389
|
+
if (name === "fldChar") {
|
|
14390
|
+
const type = attrOf2(child, "w:fldCharType", "fldCharType");
|
|
14391
|
+
if (type === "begin") depth += 1;
|
|
14392
|
+
else if (type === "end") depth = Math.max(0, depth - 1);
|
|
14393
|
+
continue;
|
|
14394
|
+
}
|
|
14395
|
+
if (REVISION_CONTENT_ELEMENTS.has(name) && depth > 0) count += 1;
|
|
14396
|
+
if (name === "fldSimple") {
|
|
14397
|
+
depth += 1;
|
|
14398
|
+
visit(child);
|
|
14399
|
+
depth -= 1;
|
|
14400
|
+
continue;
|
|
14401
|
+
}
|
|
14402
|
+
visit(child);
|
|
14403
|
+
}
|
|
14404
|
+
};
|
|
14405
|
+
visit(parsed.doc.documentElement);
|
|
14406
|
+
return count;
|
|
14407
|
+
}
|
|
14408
|
+
function collectRevisionIds(xml) {
|
|
14409
|
+
const ids = /* @__PURE__ */ new Set();
|
|
14410
|
+
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]);
|
|
14411
|
+
return ids;
|
|
14412
|
+
}
|
|
14413
|
+
function renumberCollidingRevisionIds(xml, taken) {
|
|
14414
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
14415
|
+
if (!parsed.doc || parsed.error) return { xml, renumbered: 0, idMap: /* @__PURE__ */ new Map() };
|
|
14416
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
14417
|
+
let next = 1 + Math.max(0, ...Array.from(taken, (id) => Number(id)).filter(Number.isFinite));
|
|
14418
|
+
const used = new Set(taken);
|
|
14419
|
+
let renumbered = 0;
|
|
14420
|
+
const visit = (node) => {
|
|
14421
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
14422
|
+
if (child.nodeType !== 1) continue;
|
|
14423
|
+
if (REVISION_ELEMENTS.has(child.localName)) {
|
|
14424
|
+
const id = attrOf2(child, "w:id", "id");
|
|
14425
|
+
if (id && used.has(id)) {
|
|
14426
|
+
while (used.has(String(next))) next += 1;
|
|
14427
|
+
child.setAttributeNS(NS_W8, "w:id", String(next));
|
|
14428
|
+
idMap.set(id, String(next));
|
|
14429
|
+
used.add(String(next));
|
|
14430
|
+
renumbered += 1;
|
|
14431
|
+
} else if (id) {
|
|
14432
|
+
used.add(id);
|
|
14433
|
+
}
|
|
14434
|
+
}
|
|
14435
|
+
visit(child);
|
|
14436
|
+
}
|
|
14437
|
+
};
|
|
14438
|
+
visit(parsed.doc.documentElement);
|
|
14439
|
+
return renumbered > 0 ? { xml: createSerializer().serializeToString(parsed.doc), renumbered, idMap } : { xml, renumbered: 0, idMap };
|
|
14440
|
+
}
|
|
14441
|
+
|
|
13851
14442
|
// services/error-recovery.js
|
|
13852
14443
|
var ERROR_RECOVERY_VERSION = 1;
|
|
13853
14444
|
var RULES = Object.freeze({
|
|
@@ -13885,6 +14476,12 @@ var RULES = Object.freeze({
|
|
|
13885
14476
|
STRUCTURED_CONTENT_INVALID: ["request", "request_fixable", "change_request"],
|
|
13886
14477
|
UNSUPPORTED_INSERTION_AFFINITY: ["request", "request_fixable", "change_request"],
|
|
13887
14478
|
EXISTING_REVISIONS: ["target-safety", "policy_choice_required", "set_option"],
|
|
14479
|
+
PART_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
14480
|
+
PART_AMBIGUOUS: ["target-resolution", "candidate_selection_required", "choose_candidate"],
|
|
14481
|
+
COMMENT_IN_HEADER_FOOTER: ["request", "request_fixable", "change_request"],
|
|
14482
|
+
FIELD_EDIT_REFUSED: ["target-safety", "request_fixable", "change_request"],
|
|
14483
|
+
COMMENT_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
14484
|
+
PARENT_ANCHOR_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
13888
14485
|
COMMENTED_CONTENT_MERGE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
13889
14486
|
COMMENTED_CONTENT_DELETE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
13890
14487
|
OVERLAPPING_SOURCE_TARGETS: ["batch-compilation", "source_conflict", "consolidate_operations"],
|
|
@@ -14337,6 +14934,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
14337
14934
|
"delete",
|
|
14338
14935
|
"comment",
|
|
14339
14936
|
"comment_reply",
|
|
14937
|
+
"comment_resolve",
|
|
14340
14938
|
"highlight"
|
|
14341
14939
|
]);
|
|
14342
14940
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
@@ -14355,11 +14953,14 @@ function isRecord(value) {
|
|
|
14355
14953
|
function nonEmptyString(value) {
|
|
14356
14954
|
return typeof value === "string" && value.trim().length > 0;
|
|
14357
14955
|
}
|
|
14956
|
+
function isCommentThreadOperationKind(kind) {
|
|
14957
|
+
return kind === "comment_reply" || kind === "comment_resolve";
|
|
14958
|
+
}
|
|
14358
14959
|
function getCanonicalOperationType(operation) {
|
|
14359
14960
|
const type = operation?.type;
|
|
14360
14961
|
if (type === "insert" && operation?.target?.revisionView === "rejected") return "rejected-insert";
|
|
14361
14962
|
if (type === "restore") return "restore";
|
|
14362
|
-
if (type === "comment" || type === "comment_reply" || type === "highlight") return type;
|
|
14963
|
+
if (type === "comment" || type === "comment_reply" || type === "comment_resolve" || type === "highlight") return type;
|
|
14363
14964
|
if (type === "paragraph-format") return "paragraph-format";
|
|
14364
14965
|
if (type === "character-format" || type === "format" && (operation?.textToFormat != null || operation?.properties != null)) return "format";
|
|
14365
14966
|
return "redline";
|
|
@@ -14479,7 +15080,7 @@ function validateDocumentOperation(operation) {
|
|
|
14479
15080
|
};
|
|
14480
15081
|
}
|
|
14481
15082
|
const target = normalized.targetDescriptor;
|
|
14482
|
-
if (normalized.operationKind
|
|
15083
|
+
if (!isCommentThreadOperationKind(normalized.operationKind) && !nonEmptyString(target.text) && target.index == null && !target.paragraphId && !target.fingerprint && !target.captureRef) {
|
|
14483
15084
|
return {
|
|
14484
15085
|
valid: false,
|
|
14485
15086
|
error: {
|
|
@@ -14706,6 +15307,14 @@ function validateDocumentOperation(operation) {
|
|
|
14706
15307
|
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment reply operations require a non-empty "commentContent" field.' } };
|
|
14707
15308
|
}
|
|
14708
15309
|
}
|
|
15310
|
+
if (normalized.operationKind === "comment_resolve") {
|
|
15311
|
+
if (!nonEmptyString(String(normalized.commentId ?? ""))) {
|
|
15312
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve operations require a "commentId".' } };
|
|
15313
|
+
}
|
|
15314
|
+
if (normalized.resolved != null && typeof normalized.resolved !== "boolean") {
|
|
15315
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve "resolved" must be a boolean when provided.' } };
|
|
15316
|
+
}
|
|
15317
|
+
}
|
|
14709
15318
|
if (normalized.operationKind === "highlight") {
|
|
14710
15319
|
if (!nonEmptyString(normalized.textToHighlight)) {
|
|
14711
15320
|
return {
|
|
@@ -15185,7 +15794,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
15185
15794
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
15186
15795
|
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
15187
15796
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
15188
|
-
if (validation.valid && operation.operationKind !== "comment_reply" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
15797
|
+
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
15189
15798
|
try {
|
|
15190
15799
|
const start = resolveDescriptor(
|
|
15191
15800
|
xmlDoc,
|
|
@@ -15686,12 +16295,12 @@ function resolveTargetFromCapture(xmlDoc, session, targetDescriptor, _opType = "
|
|
|
15686
16295
|
}
|
|
15687
16296
|
|
|
15688
16297
|
// services/document-operation-mutations.js
|
|
15689
|
-
var
|
|
16298
|
+
var NS_W9 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
15690
16299
|
var NS_W142 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
15691
16300
|
function getCommentIdsInElement(element) {
|
|
15692
16301
|
const ids = /* @__PURE__ */ new Set();
|
|
15693
16302
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
15694
|
-
for (const node of Array.from(element?.getElementsByTagNameNS?.(
|
|
16303
|
+
for (const node of Array.from(element?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
15695
16304
|
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
15696
16305
|
if (id !== "") ids.add(id);
|
|
15697
16306
|
}
|
|
@@ -15905,7 +16514,7 @@ function normalizeBodySectionOrder(xmlDoc) {
|
|
|
15905
16514
|
normalizeBodySectionOrderStandalone(xmlDoc);
|
|
15906
16515
|
}
|
|
15907
16516
|
function removeProofErrNodes(paragraph) {
|
|
15908
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
16517
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "proofErr") || [])) {
|
|
15909
16518
|
node.parentNode?.removeChild(node);
|
|
15910
16519
|
}
|
|
15911
16520
|
}
|
|
@@ -15916,12 +16525,12 @@ function preprocessRedlineTargetParagraph(targetParagraph) {
|
|
|
15916
16525
|
function getDirectWordChild4(element, localName2) {
|
|
15917
16526
|
if (!element) return null;
|
|
15918
16527
|
return Array.from(element.childNodes || []).find(
|
|
15919
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
16528
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === localName2
|
|
15920
16529
|
) || null;
|
|
15921
16530
|
}
|
|
15922
16531
|
function computeTableIndexInDocument(xmlDoc, tableElement) {
|
|
15923
16532
|
if (!xmlDoc || !tableElement) return null;
|
|
15924
|
-
const tables = Array.from(xmlDoc.getElementsByTagNameNS(
|
|
16533
|
+
const tables = Array.from(xmlDoc.getElementsByTagNameNS(NS_W9, "tbl"));
|
|
15925
16534
|
const idx = tables.indexOf(tableElement);
|
|
15926
16535
|
return idx >= 0 ? idx + 1 : null;
|
|
15927
16536
|
}
|
|
@@ -15971,7 +16580,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
15971
16580
|
markParagraphMarkInserted(xmlDoc, paragraph, author);
|
|
15972
16581
|
}
|
|
15973
16582
|
const run = createWordElement(xmlDoc, "w:r");
|
|
15974
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
16583
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
15975
16584
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
15976
16585
|
if (anchorRunPr) {
|
|
15977
16586
|
run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
@@ -15996,7 +16605,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
15996
16605
|
}
|
|
15997
16606
|
function serializeParagraphRangeAsDocument(paragraphs, serializer) {
|
|
15998
16607
|
const paragraphXml = (paragraphs || []).map((paragraph) => serializer.serializeToString(paragraph)).join("");
|
|
15999
|
-
return `<w:document xmlns:w="${
|
|
16608
|
+
return `<w:document xmlns:w="${NS_W9}"><w:body>${paragraphXml}</w:body></w:document>`;
|
|
16000
16609
|
}
|
|
16001
16610
|
function applyExplicitRangeListInsertions({
|
|
16002
16611
|
xmlDoc,
|
|
@@ -16065,7 +16674,7 @@ function buildEmptyParagraphTemplateFromAnchor(xmlDoc, anchorParagraph) {
|
|
|
16065
16674
|
const anchorPPr = getDirectWordChild4(anchorParagraph, "pPr");
|
|
16066
16675
|
if (anchorPPr) paragraph.appendChild(clonePropertiesWithoutRevisionHistory(anchorPPr));
|
|
16067
16676
|
const run = createWordElement(xmlDoc, "w:r");
|
|
16068
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
16677
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
16069
16678
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
16070
16679
|
if (anchorRunPr) run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
16071
16680
|
const textNode = createWordElement(xmlDoc, "w:t");
|
|
@@ -16088,7 +16697,7 @@ function wrapParagraphContentInInsertion(xmlDoc, paragraph, revisionMetadata, au
|
|
|
16088
16697
|
ins.setAttribute("w:author", metadata.author);
|
|
16089
16698
|
ins.setAttribute("w:date", metadata.date);
|
|
16090
16699
|
for (const child of Array.from(paragraph.childNodes || [])) {
|
|
16091
|
-
if (child?.nodeType === 1 && child.namespaceURI ===
|
|
16700
|
+
if (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
16092
16701
|
ins.appendChild(child.cloneNode(true));
|
|
16093
16702
|
}
|
|
16094
16703
|
wrappedParagraph.appendChild(ins);
|
|
@@ -16112,7 +16721,7 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
16112
16721
|
if (typeof markdownResult?.oxml === "string") {
|
|
16113
16722
|
const extracted = extractReplacementNodes(markdownResult.oxml);
|
|
16114
16723
|
sourceParagraph = (extracted.replacementNodes || []).find(
|
|
16115
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
16724
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p"
|
|
16116
16725
|
) || null;
|
|
16117
16726
|
}
|
|
16118
16727
|
if (!sourceParagraph) {
|
|
@@ -16144,7 +16753,7 @@ function collectNumberingIdsFromNodes(nodes) {
|
|
|
16144
16753
|
}
|
|
16145
16754
|
function directDeletionCarrierText(carrier) {
|
|
16146
16755
|
return Array.from(carrier?.childNodes || []).reduce((text2, child) => {
|
|
16147
|
-
return text2 + (child?.nodeType === 1 && child.namespaceURI ===
|
|
16756
|
+
return text2 + (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r" ? getRunContentPieces(child).map((piece) => piece.text).join("") : "");
|
|
16148
16757
|
}, "");
|
|
16149
16758
|
}
|
|
16150
16759
|
function findOccurrenceOffset(text2, needle, occurrence) {
|
|
@@ -16186,9 +16795,9 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
16186
16795
|
}
|
|
16187
16796
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
16188
16797
|
if (child.nodeType !== 1) continue;
|
|
16189
|
-
if (!(child.namespaceURI ===
|
|
16798
|
+
if (!(child.namespaceURI === NS_W9 && child.localName === "r")) return true;
|
|
16190
16799
|
for (const runChild of Array.from(child.childNodes || [])) {
|
|
16191
|
-
if (runChild.nodeType !== 1 || runChild.namespaceURI !==
|
|
16800
|
+
if (runChild.nodeType !== 1 || runChild.namespaceURI !== NS_W9) continue;
|
|
16192
16801
|
if (!["rPr", "delText", "t", "tab", "br", "cr", "noBreakHyphen", "softHyphen"].includes(runChild.localName)) return true;
|
|
16193
16802
|
}
|
|
16194
16803
|
}
|
|
@@ -16197,7 +16806,7 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
16197
16806
|
function insertedRunPropertiesAtDeletionOffset(carrier, localOffset) {
|
|
16198
16807
|
let offset = 0;
|
|
16199
16808
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
16200
|
-
if (!(child?.nodeType === 1 && child.namespaceURI ===
|
|
16809
|
+
if (!(child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r")) continue;
|
|
16201
16810
|
const length = getRunTextLength(getRunContentPieces(child));
|
|
16202
16811
|
if (localOffset <= offset + length) {
|
|
16203
16812
|
return clonePropertiesWithoutRevisionHistory(getDirectWordChild4(child, "rPr"));
|
|
@@ -16227,7 +16836,7 @@ function normalizedAuthor2(author) {
|
|
|
16227
16836
|
function directWordParagraphSibling(paragraph, direction = "nextSibling") {
|
|
16228
16837
|
let cursor = paragraph?.[direction] || null;
|
|
16229
16838
|
while (cursor) {
|
|
16230
|
-
if (cursor.nodeType === 1 && cursor.namespaceURI ===
|
|
16839
|
+
if (cursor.nodeType === 1 && cursor.namespaceURI === NS_W9 && cursor.localName === "p") return cursor;
|
|
16231
16840
|
cursor = cursor[direction] || null;
|
|
16232
16841
|
}
|
|
16233
16842
|
return null;
|
|
@@ -16237,7 +16846,7 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
16237
16846
|
const pPr = createWordElement(xmlDoc, "w:pPr");
|
|
16238
16847
|
if (!sourcePPr) return pPr;
|
|
16239
16848
|
for (const child of Array.from(sourcePPr.childNodes || [])) {
|
|
16240
|
-
if (child.nodeType !== 1 || child.namespaceURI !==
|
|
16849
|
+
if (child.nodeType !== 1 || child.namespaceURI !== NS_W9) continue;
|
|
16241
16850
|
if (!RESTORATION_PPR_ALLOWLIST.has(child.localName)) continue;
|
|
16242
16851
|
pPr.appendChild(xmlDoc.importNode(child, true));
|
|
16243
16852
|
}
|
|
@@ -16246,14 +16855,14 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
16246
16855
|
function collectRestorationAnchorWarnings(sourceParagraph) {
|
|
16247
16856
|
const warnings = [];
|
|
16248
16857
|
const bookmarkNames = /* @__PURE__ */ new Set();
|
|
16249
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
16858
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, "bookmarkStart") || [])) {
|
|
16250
16859
|
const name = wordAttribute4(node, "name") || "(unnamed)";
|
|
16251
16860
|
bookmarkNames.add(name);
|
|
16252
16861
|
}
|
|
16253
16862
|
for (const name of bookmarkNames) warnings.push(`RESTORATION_DROPPED_BOOKMARK:${name}`);
|
|
16254
16863
|
const commentIds = /* @__PURE__ */ new Set();
|
|
16255
16864
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
16256
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
16865
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
16257
16866
|
const id = wordAttribute4(node, "id");
|
|
16258
16867
|
if (id !== "") commentIds.add(id);
|
|
16259
16868
|
}
|
|
@@ -16270,24 +16879,24 @@ function removeRestorationAnchors(paragraph) {
|
|
|
16270
16879
|
"commentReference"
|
|
16271
16880
|
];
|
|
16272
16881
|
for (const localName2 of anchorNames) {
|
|
16273
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
16882
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
16274
16883
|
node.parentNode?.removeChild(node);
|
|
16275
16884
|
}
|
|
16276
16885
|
}
|
|
16277
|
-
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
16278
|
-
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI ===
|
|
16886
|
+
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "r") || [])) {
|
|
16887
|
+
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName !== "rPr");
|
|
16279
16888
|
if (!meaningful) run.parentNode?.removeChild(run);
|
|
16280
16889
|
}
|
|
16281
16890
|
}
|
|
16282
16891
|
function buildRejectedRestorationTemplate(xmlDoc, sourceParagraph, serializer) {
|
|
16283
16892
|
const rejected = rejectTrackedChangesInOoxml(serializer.serializeToString(sourceParagraph), { allAuthors: true });
|
|
16284
16893
|
const parsed = parseOoxmlSafe(rejected.oxml, "application/xml");
|
|
16285
|
-
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(
|
|
16894
|
+
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(NS_W9, "p")?.[0] || null;
|
|
16286
16895
|
if (!rejectedParagraph) return null;
|
|
16287
16896
|
const paragraph = createWordElement(xmlDoc, "w:p");
|
|
16288
16897
|
paragraph.appendChild(createSanitizedRestorationPPr(xmlDoc, sourceParagraph));
|
|
16289
16898
|
for (const child of Array.from(rejectedParagraph.childNodes || [])) {
|
|
16290
|
-
if (child.nodeType === 1 && child.namespaceURI ===
|
|
16899
|
+
if (child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
16291
16900
|
paragraph.appendChild(xmlDoc.importNode(child, true));
|
|
16292
16901
|
}
|
|
16293
16902
|
removeRestorationAnchors(paragraph);
|
|
@@ -16308,7 +16917,7 @@ async function editRestorationTemplate(xmlDoc, template, modifiedText, author, s
|
|
|
16308
16917
|
);
|
|
16309
16918
|
if (result?.status === "error" || typeof result?.oxml !== "string") return null;
|
|
16310
16919
|
const extracted = extractReplacementNodes(result.oxml);
|
|
16311
|
-
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
16920
|
+
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p");
|
|
16312
16921
|
return paragraph ? xmlDoc.importNode(paragraph, true) : null;
|
|
16313
16922
|
}
|
|
16314
16923
|
function allocateFreshParagraphId(xmlDoc, operationSession) {
|
|
@@ -16479,7 +17088,7 @@ async function insertIntoRejectedDeletedText(documentXml, targetText, anchor, mo
|
|
|
16479
17088
|
}
|
|
16480
17089
|
const insertionOffset = anchorStart + anchor.offset;
|
|
16481
17090
|
const carriers = Array.from(paragraph.childNodes || []).filter(
|
|
16482
|
-
(node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
17091
|
+
(node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "del"
|
|
16483
17092
|
);
|
|
16484
17093
|
if (carriers.some((carrier) => hasUnsupportedDeletionSplitMarkup(paragraph, carrier))) {
|
|
16485
17094
|
return {
|
|
@@ -17136,7 +17745,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17136
17745
|
};
|
|
17137
17746
|
}
|
|
17138
17747
|
if (!allSame) {
|
|
17139
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
17748
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
17140
17749
|
if (hasMove) {
|
|
17141
17750
|
return {
|
|
17142
17751
|
documentXml,
|
|
@@ -17151,8 +17760,8 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17151
17760
|
}
|
|
17152
17761
|
}
|
|
17153
17762
|
} else {
|
|
17154
|
-
const hasDel = targetParagraph.getElementsByTagNameNS(
|
|
17155
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
17763
|
+
const hasDel = targetParagraph.getElementsByTagNameNS(NS_W9, "del").length > 0;
|
|
17764
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
17156
17765
|
if (hasDel) {
|
|
17157
17766
|
return {
|
|
17158
17767
|
documentXml,
|
|
@@ -17671,7 +18280,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17671
18280
|
const targetParagraph = resolved.paragraph;
|
|
17672
18281
|
const collectVisibleRuns = () => {
|
|
17673
18282
|
const runEntries2 = [];
|
|
17674
|
-
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(
|
|
18283
|
+
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(NS_W9, "r"));
|
|
17675
18284
|
for (const run of allRuns) {
|
|
17676
18285
|
let parent = run.parentNode;
|
|
17677
18286
|
let isDel = false;
|
|
@@ -17763,7 +18372,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17763
18372
|
const targetRuns = finalRuns.filter((span) => span.start >= matchStart && span.end <= matchEnd);
|
|
17764
18373
|
let anyChanged = false;
|
|
17765
18374
|
for (const entry of targetRuns) {
|
|
17766
|
-
const rPr = entry.run.getElementsByTagNameNS(
|
|
18375
|
+
const rPr = entry.run.getElementsByTagNameNS(NS_W9, "rPr")[0] || null;
|
|
17767
18376
|
if (checkRunPropertiesChanged(rPr, properties)) {
|
|
17768
18377
|
anyChanged = true;
|
|
17769
18378
|
break;
|
|
@@ -17774,7 +18383,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17774
18383
|
}
|
|
17775
18384
|
for (const entry of targetRuns) {
|
|
17776
18385
|
const run = entry.run;
|
|
17777
|
-
let rPr = run.getElementsByTagNameNS(
|
|
18386
|
+
let rPr = run.getElementsByTagNameNS(NS_W9, "rPr")[0];
|
|
17778
18387
|
if (!rPr) {
|
|
17779
18388
|
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
17780
18389
|
run.insertBefore(rPr, run.firstChild);
|
|
@@ -17783,7 +18392,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17783
18392
|
let insideSameAuthorIns = false;
|
|
17784
18393
|
while (insAncestor && insAncestor !== targetParagraph) {
|
|
17785
18394
|
if (insAncestor.nodeType === 1 && (insAncestor.localName === "ins" || insAncestor.nodeName === "w:ins")) {
|
|
17786
|
-
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(
|
|
18395
|
+
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(NS_W9, "author");
|
|
17787
18396
|
if (insAuthor === (author || getDefaultAuthor())) {
|
|
17788
18397
|
insideSameAuthorIns = true;
|
|
17789
18398
|
}
|
|
@@ -17831,7 +18440,7 @@ async function applyParagraphFormatToParagraphByExactText(documentXml, targetTex
|
|
|
17831
18440
|
return { documentXml, hasChanges: false, status: "error", error: resolved?.error || { code: "TARGET_NOT_FOUND", message: "Target paragraph not found." } };
|
|
17832
18441
|
}
|
|
17833
18442
|
const targetParagraph = resolved.paragraph;
|
|
17834
|
-
let pPr = targetParagraph.getElementsByTagNameNS(
|
|
18443
|
+
let pPr = targetParagraph.getElementsByTagNameNS(NS_W9, "pPr")[0];
|
|
17835
18444
|
if (!pPr) {
|
|
17836
18445
|
pPr = createWordElement(xmlDoc, "w:pPr");
|
|
17837
18446
|
targetParagraph.insertBefore(pPr, targetParagraph.firstChild);
|
|
@@ -17879,7 +18488,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
17879
18488
|
}
|
|
17880
18489
|
let operation = validation.operation || normalizeDocumentOperation(op);
|
|
17881
18490
|
const authorUsed = resolveDocumentOperationAuthor(operation, author, getDefaultAuthor());
|
|
17882
|
-
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
18491
|
+
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
17883
18492
|
return {
|
|
17884
18493
|
documentXml,
|
|
17885
18494
|
hasChanges: false,
|
|
@@ -18046,6 +18655,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18046
18655
|
result = applyCommentReplyToParts({
|
|
18047
18656
|
commentsXml: existingCommentsXml,
|
|
18048
18657
|
commentsExtendedXml: existingExtendedXml,
|
|
18658
|
+
documentXml,
|
|
18049
18659
|
parentCommentId: operation.parentCommentId,
|
|
18050
18660
|
commentId,
|
|
18051
18661
|
commentContent: operation.commentContent,
|
|
@@ -18053,8 +18663,30 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18053
18663
|
date: operation.date || (/* @__PURE__ */ new Date()).toISOString()
|
|
18054
18664
|
});
|
|
18055
18665
|
result.documentXml = documentXml;
|
|
18056
|
-
if (result.
|
|
18666
|
+
if (result.status !== "error" && result.hasChanges) {
|
|
18667
|
+
if (!session.document || !anchorReplyInDocument(session.document, result.threadCommentIds, commentId)) {
|
|
18668
|
+
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.` } };
|
|
18669
|
+
} else if (session.receiptCollector) {
|
|
18670
|
+
session.receiptCollector.recordComment(commentId);
|
|
18671
|
+
}
|
|
18672
|
+
}
|
|
18057
18673
|
}
|
|
18674
|
+
} else if (operation.operationKind === "comment_resolve") {
|
|
18675
|
+
const resolution = applyThreadResolutionToParts({
|
|
18676
|
+
commentsXml: session.commentsXml || runtimeContext?.commentsXml || options.commentsXml,
|
|
18677
|
+
commentsExtendedXml: session.commentsExtendedXml || runtimeContext?.commentsExtendedXml || options.commentsExtendedXml,
|
|
18678
|
+
commentId: operation.commentId,
|
|
18679
|
+
resolved: operation.resolved !== false
|
|
18680
|
+
});
|
|
18681
|
+
result = resolution.status === "error" ? { documentXml, hasChanges: false, status: "error", error: resolution.error } : {
|
|
18682
|
+
documentXml,
|
|
18683
|
+
hasChanges: resolution.hasChanges,
|
|
18684
|
+
...resolution.commentsXml ? { commentsXml: resolution.commentsXml, commentsXmlMode: "replace" } : {},
|
|
18685
|
+
...resolution.commentsExtendedXml ? { commentsExtendedXml: resolution.commentsExtendedXml, commentsExtendedXmlMode: "replace" } : {},
|
|
18686
|
+
resolved: resolution.resolved,
|
|
18687
|
+
threadRootId: resolution.threadRootId,
|
|
18688
|
+
commentIds: resolution.commentIds
|
|
18689
|
+
};
|
|
18058
18690
|
} else if (operation.operationKind === "highlight") {
|
|
18059
18691
|
result = await applyHighlightToParagraphByExactText(
|
|
18060
18692
|
documentXml,
|
|
@@ -18195,7 +18827,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18195
18827
|
...resolutionCapture
|
|
18196
18828
|
};
|
|
18197
18829
|
}
|
|
18198
|
-
session.markMutationCommitted(
|
|
18830
|
+
session.markMutationCommitted(true);
|
|
18199
18831
|
if (operation._compiledSourceId && session.sourceTargetRegistry) {
|
|
18200
18832
|
session.sourceTargetRegistry.commitMutation(
|
|
18201
18833
|
operation._compiledSourceId,
|
|
@@ -18308,9 +18940,9 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18308
18940
|
}
|
|
18309
18941
|
|
|
18310
18942
|
// services/batch-operation-orchestrator.js
|
|
18311
|
-
var
|
|
18943
|
+
var NS_W10 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
18312
18944
|
function operationTargetPriority(op) {
|
|
18313
|
-
return op?.type === "comment" || op?.type === "comment_reply" ? 0 : 1;
|
|
18945
|
+
return op?.type === "comment" || op?.type === "comment_reply" || op?.type === "comment_resolve" ? 0 : 1;
|
|
18314
18946
|
}
|
|
18315
18947
|
function buildOperationDependencyPlan(operations = []) {
|
|
18316
18948
|
const list = Array.isArray(operations) ? operations : [];
|
|
@@ -18385,7 +19017,7 @@ function buildOperationDependencyPlan(operations = []) {
|
|
|
18385
19017
|
inDegrees[i2]++;
|
|
18386
19018
|
}
|
|
18387
19019
|
const kind = normalizeDocumentOperation(op).operationKind;
|
|
18388
|
-
if (kind !== "comment" && kind !== "comment_reply") {
|
|
19020
|
+
if (kind !== "comment" && kind !== "comment_reply" && kind !== "comment_resolve") {
|
|
18389
19021
|
if (!mutatingCaptureConsumers.has(ref)) mutatingCaptureConsumers.set(ref, []);
|
|
18390
19022
|
mutatingCaptureConsumers.get(ref).push({
|
|
18391
19023
|
index: i2,
|
|
@@ -18453,9 +19085,9 @@ function mergeCommentsXml(existingXml, incomingXml) {
|
|
|
18453
19085
|
const serializer = createSerializer();
|
|
18454
19086
|
const existingRoot = existingDoc.documentElement;
|
|
18455
19087
|
const existingIds = new Set(
|
|
18456
|
-
Array.from(existingDoc.getElementsByTagNameNS(
|
|
19088
|
+
Array.from(existingDoc.getElementsByTagNameNS(NS_W10, "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
18457
19089
|
);
|
|
18458
|
-
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(
|
|
19090
|
+
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(NS_W10, "comment"))) {
|
|
18459
19091
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
18460
19092
|
if (id && existingIds.has(id)) continue;
|
|
18461
19093
|
existingRoot.appendChild(existingDoc.importNode(comment, true));
|
|
@@ -18616,7 +19248,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
18616
19248
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
18617
19249
|
}
|
|
18618
19250
|
session.runtimeContext = context;
|
|
18619
|
-
if (sourceOperations.some((op) => op?.type === "comment_reply")) {
|
|
19251
|
+
if (sourceOperations.some((op) => op?.type === "comment_reply" || op?.type === "comment_resolve")) {
|
|
18620
19252
|
session.commentsXml = context.commentsXml || options.commentsXml || null;
|
|
18621
19253
|
session.commentsExtendedXml = context.commentsExtendedXml || options.commentsExtendedXml || null;
|
|
18622
19254
|
session.commentsXmlMode = "replace";
|
|
@@ -18689,6 +19321,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
18689
19321
|
...result.resolvedTarget ? { resolvedTarget: result.resolvedTarget } : {},
|
|
18690
19322
|
...result.resolvedAnchor ? { resolvedAnchor: result.resolvedAnchor } : {},
|
|
18691
19323
|
...result.change ? { change: result.change } : {},
|
|
19324
|
+
...result.operationType === "comment_resolve" && !isError ? { resolved: result.resolved, threadRootId: result.threadRootId, commentIds: result.commentIds } : {},
|
|
18692
19325
|
...Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {},
|
|
18693
19326
|
...result.error ? { error: result.error } : {},
|
|
18694
19327
|
...result.receipt ? { receipt: result.receipt } : {}
|
|
@@ -18965,6 +19598,26 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
18965
19598
|
}
|
|
18966
19599
|
commentsRequired = commentsRequired || operation.operationKind === "comment" || operation.operationKind === "comment_reply";
|
|
18967
19600
|
numberingRequired = numberingRequired || operationNeedsNumbering(operation);
|
|
19601
|
+
if (operation.operationKind === "comment_resolve") {
|
|
19602
|
+
const commentId = String(operation.commentId);
|
|
19603
|
+
results.push(options._existingCommentDetails?.[commentId] ? {
|
|
19604
|
+
index: index + 1,
|
|
19605
|
+
type: sourceOperation.type,
|
|
19606
|
+
operationType: operation.operationKind,
|
|
19607
|
+
status: "ready",
|
|
19608
|
+
authorUsed,
|
|
19609
|
+
resolvedBy: "comment_id",
|
|
19610
|
+
commentId
|
|
19611
|
+
} : {
|
|
19612
|
+
index: index + 1,
|
|
19613
|
+
type: sourceOperation.type,
|
|
19614
|
+
operationType: operation.operationKind,
|
|
19615
|
+
status: "error",
|
|
19616
|
+
authorUsed,
|
|
19617
|
+
error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` }
|
|
19618
|
+
});
|
|
19619
|
+
continue;
|
|
19620
|
+
}
|
|
18968
19621
|
if (operation.operationKind === "comment_reply") {
|
|
18969
19622
|
const parentId = String(operation.parentCommentId);
|
|
18970
19623
|
const parent = options._existingCommentDetails?.[parentId];
|
|
@@ -20116,6 +20769,15 @@ var text = (entries, path) => {
|
|
|
20116
20769
|
if (typeof entry === "string") return entry;
|
|
20117
20770
|
return textDecoder2.decode(entry);
|
|
20118
20771
|
};
|
|
20772
|
+
function reconcileCommentSiblings(working) {
|
|
20773
|
+
const updated = reconcileCommentSiblingParts({
|
|
20774
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
20775
|
+
commentsIdsXml: text(working, "word/commentsIds.xml"),
|
|
20776
|
+
commentsExtensibleXml: text(working, "word/commentsExtensible.xml")
|
|
20777
|
+
});
|
|
20778
|
+
if (updated.commentsIdsXml) working.set("word/commentsIds.xml", textEncoder4.encode(updated.commentsIdsXml));
|
|
20779
|
+
if (updated.commentsExtensibleXml) working.set("word/commentsExtensible.xml", textEncoder4.encode(updated.commentsExtensibleXml));
|
|
20780
|
+
}
|
|
20119
20781
|
var cloneEntries = (entries) => new Map([...entries].map(([name, data]) => [name, new Uint8Array(data)]));
|
|
20120
20782
|
function toBufferCompatible(uint8Arr) {
|
|
20121
20783
|
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
@@ -20183,6 +20845,7 @@ function computePackageRevisionToken(input) {
|
|
|
20183
20845
|
entries
|
|
20184
20846
|
});
|
|
20185
20847
|
}
|
|
20848
|
+
var COMMENT_OPERATION_TYPES = /* @__PURE__ */ new Set(["comment", "comment_reply", "comment_resolve"]);
|
|
20186
20849
|
function nextCommentId(entries) {
|
|
20187
20850
|
const ids = `${text(entries, "word/document.xml") || ""} ${text(entries, "word/comments.xml") || ""}`.match(/(?:w:)?id=["'](\d+)["']/g) || [];
|
|
20188
20851
|
let next = ids.reduce((max2, token) => Math.max(max2, Number(token.match(/\d+/)?.[0] || 0)), 0) + 1;
|
|
@@ -20201,7 +20864,7 @@ function existingCommentDetails(entries) {
|
|
|
20201
20864
|
id,
|
|
20202
20865
|
author: comment.getAttribute("w:author") || comment.getAttribute("author") || "",
|
|
20203
20866
|
text: String(comment.textContent || "").trim(),
|
|
20204
|
-
paraId:
|
|
20867
|
+
paraId: commentParaId(comment) || null
|
|
20205
20868
|
};
|
|
20206
20869
|
}
|
|
20207
20870
|
return details;
|
|
@@ -20232,13 +20895,34 @@ var DocxDocument = class {
|
|
|
20232
20895
|
}
|
|
20233
20896
|
inspect(options = {}) {
|
|
20234
20897
|
const digestFn = options.digestFn || ((bytes) => sha256(bytes));
|
|
20235
|
-
|
|
20898
|
+
const inspection = inspectDocumentParts({
|
|
20236
20899
|
documentXml: text(this.entries, "word/document.xml"),
|
|
20237
20900
|
commentsXml: text(this.entries, "word/comments.xml"),
|
|
20238
20901
|
commentsExtendedXml: text(this.entries, "word/commentsExtended.xml"),
|
|
20239
20902
|
numberingXml: text(this.entries, "word/numbering.xml"),
|
|
20240
20903
|
stylesXml: text(this.entries, "word/styles.xml")
|
|
20241
20904
|
}, { ...options, digestFn });
|
|
20905
|
+
if (inspection.status === "error") return inspection;
|
|
20906
|
+
return { ...inspection, headersFooters: this.inspectHeadersFooters() };
|
|
20907
|
+
}
|
|
20908
|
+
/**
|
|
20909
|
+
* Header and footer parts the document uses, with their paragraph text, so operations can target them with
|
|
20910
|
+
* `part`. `hasFields` marks parts containing PAGE/NUMPAGES-style fields (edit around them, not inside).
|
|
20911
|
+
*/
|
|
20912
|
+
inspectHeadersFooters() {
|
|
20913
|
+
return discoverHeaderFooterParts({
|
|
20914
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
20915
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
20916
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
20917
|
+
}).map((part) => {
|
|
20918
|
+
const xml = text(this.entries, part.path) || "";
|
|
20919
|
+
const partInspection = xml ? inspectDocumentParts({ documentXml: xml }) : { paragraphs: [] };
|
|
20920
|
+
return {
|
|
20921
|
+
...part,
|
|
20922
|
+
hasFields: /<w:fldChar\b|<w:fldSimple\b/.test(xml),
|
|
20923
|
+
paragraphs: (partInspection.paragraphs || []).map((paragraph) => ({ index: paragraph.index, text: paragraph.text }))
|
|
20924
|
+
};
|
|
20925
|
+
});
|
|
20242
20926
|
}
|
|
20243
20927
|
getRevisionToken() {
|
|
20244
20928
|
return computePackageRevisionToken(this.entries);
|
|
@@ -20247,12 +20931,51 @@ var DocxDocument = class {
|
|
|
20247
20931
|
return this.getRevisionToken();
|
|
20248
20932
|
}
|
|
20249
20933
|
preflight(operations, author = getDefaultAuthor(), options = {}) {
|
|
20250
|
-
|
|
20934
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
20935
|
+
const runBody = (ops) => preflightOperations(
|
|
20251
20936
|
text(this.entries, "word/document.xml"),
|
|
20252
|
-
|
|
20937
|
+
ops,
|
|
20253
20938
|
author || getDefaultAuthor(),
|
|
20254
20939
|
{ ...options, _existingCommentDetails: existingCommentDetails(this.entries) }
|
|
20255
20940
|
);
|
|
20941
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) return runBody(operations);
|
|
20942
|
+
const parts = this.inspectHeadersFooters();
|
|
20943
|
+
const bodyOps = [];
|
|
20944
|
+
const results = [];
|
|
20945
|
+
const conflicts = [];
|
|
20946
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
20947
|
+
let valid = true;
|
|
20948
|
+
list.forEach((operation, i2) => {
|
|
20949
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
20950
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
20951
|
+
return;
|
|
20952
|
+
}
|
|
20953
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
20954
|
+
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;
|
|
20955
|
+
if (refusal) {
|
|
20956
|
+
valid = false;
|
|
20957
|
+
results.push({ index: i2 + 1, type: operation.type || "redline", status: "error", error: normalizeErrorWithRecovery({ ...refusal, operationIndex: i2 + 1 }) });
|
|
20958
|
+
return;
|
|
20959
|
+
}
|
|
20960
|
+
const { part: _selector, ...rest } = operation;
|
|
20961
|
+
const partResult = preflightOperations(text(this.entries, resolved.part.path), [rest], author || getDefaultAuthor(), { ...options, _existingCommentDetails: {} });
|
|
20962
|
+
valid = valid && partResult.valid;
|
|
20963
|
+
for (const result of partResult.results || []) results.push({ ...result, index: i2 + 1, part: resolved.part.path });
|
|
20964
|
+
conflicts.push(...partResult.conflicts || []);
|
|
20965
|
+
for (const name of partResult.authorsUsed || []) authorsUsed.add(name);
|
|
20966
|
+
});
|
|
20967
|
+
let requiredArtifacts = { comments: false, numbering: false };
|
|
20968
|
+
if (bodyOps.length) {
|
|
20969
|
+
const body = runBody(bodyOps.map((item) => item.operation));
|
|
20970
|
+
valid = valid && body.valid;
|
|
20971
|
+
for (const result of body.results || []) results.push({ ...result, index: bodyOps[result.index - 1].origIndex + 1 });
|
|
20972
|
+
conflicts.push(...body.conflicts || []);
|
|
20973
|
+
for (const name of body.authorsUsed || []) authorsUsed.add(name);
|
|
20974
|
+
requiredArtifacts = body.requiredArtifacts || requiredArtifacts;
|
|
20975
|
+
}
|
|
20976
|
+
results.sort((a, b) => a.index - b.index);
|
|
20977
|
+
const ok = valid && conflicts.length === 0;
|
|
20978
|
+
return { valid: ok, status: ok ? "ok" : "error", results, conflicts, authorsUsed: [...authorsUsed], requiredArtifacts };
|
|
20256
20979
|
}
|
|
20257
20980
|
toUint8Array() {
|
|
20258
20981
|
return zipDocx(this.entries);
|
|
@@ -20260,7 +20983,240 @@ var DocxDocument = class {
|
|
|
20260
20983
|
toBuffer() {
|
|
20261
20984
|
return toBufferCompatible(this.toUint8Array());
|
|
20262
20985
|
}
|
|
20986
|
+
/**
|
|
20987
|
+
* Applies operations to the document. Operations without `part` target the body (word/document.xml).
|
|
20988
|
+
* An operation with `part` targets one header or footer part:
|
|
20989
|
+
* `{ part: { kind: 'footer', type: 'default', section: 0 } | 'word/footer2.xml', target: { exactText }, ... }`.
|
|
20990
|
+
* Discover parts with `inspect().headersFooters`.
|
|
20991
|
+
*/
|
|
20263
20992
|
async applyOperations(operations, options = {}) {
|
|
20993
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
20994
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) {
|
|
20995
|
+
return this.applyBodyOperations(operations, options);
|
|
20996
|
+
}
|
|
20997
|
+
return this.applyOperationsWithParts(list, options);
|
|
20998
|
+
}
|
|
20999
|
+
async applyOperationsWithParts(operations, options = {}) {
|
|
21000
|
+
const author = options.author || getDefaultAuthor();
|
|
21001
|
+
const atomic = options.atomic === true;
|
|
21002
|
+
const snapshotEntries = this.entries;
|
|
21003
|
+
const snapshotBytes = this.originalBytes;
|
|
21004
|
+
const total = operations.length;
|
|
21005
|
+
const restore = () => {
|
|
21006
|
+
this.entries = snapshotEntries;
|
|
21007
|
+
this.originalBytes = snapshotBytes;
|
|
21008
|
+
};
|
|
21009
|
+
const original = () => ({
|
|
21010
|
+
uint8Array: snapshotBytes,
|
|
21011
|
+
toUint8Array: () => new Uint8Array(snapshotBytes),
|
|
21012
|
+
buffer: toBufferCompatible(snapshotBytes),
|
|
21013
|
+
toBuffer: () => toBufferCompatible(snapshotBytes)
|
|
21014
|
+
});
|
|
21015
|
+
const refuse = (error2, results2 = [], receipts2 = [], extra = {}) => ({
|
|
21016
|
+
status: "error",
|
|
21017
|
+
hasChanges: false,
|
|
21018
|
+
written: false,
|
|
21019
|
+
rolledBack: true,
|
|
21020
|
+
results: results2,
|
|
21021
|
+
receipts: receipts2,
|
|
21022
|
+
executionOrder: [],
|
|
21023
|
+
authorsUsed: [],
|
|
21024
|
+
artifactsChanged: [],
|
|
21025
|
+
error: normalizeErrorWithRecovery(error2),
|
|
21026
|
+
retryPlan: createRetryPlan({ atomic: true, rolledBack: true, results: results2, receipts: receipts2, operationCount: total }),
|
|
21027
|
+
validation: { originalIssues: [], generatedIssues: [] },
|
|
21028
|
+
...extra,
|
|
21029
|
+
...original()
|
|
21030
|
+
});
|
|
21031
|
+
if (options.expectedRevision) {
|
|
21032
|
+
const precondition = await this.applyBodyOperations([], { expectedRevision: options.expectedRevision });
|
|
21033
|
+
if (precondition.status === "error") return precondition;
|
|
21034
|
+
}
|
|
21035
|
+
const parts = discoverHeaderFooterParts({
|
|
21036
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
21037
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
21038
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
21039
|
+
});
|
|
21040
|
+
const bodyOps = [];
|
|
21041
|
+
const partOps = [];
|
|
21042
|
+
for (let i2 = 0; i2 < operations.length; i2++) {
|
|
21043
|
+
const operation = operations[i2];
|
|
21044
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
21045
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
21046
|
+
continue;
|
|
21047
|
+
}
|
|
21048
|
+
if (COMMENT_OPERATION_TYPES.has(operation.type)) {
|
|
21049
|
+
return refuse({ code: "COMMENT_IN_HEADER_FOOTER", message: `Operation ${i2 + 1}: Word does not support comments in headers or footers.`, operationIndex: i2 + 1 });
|
|
21050
|
+
}
|
|
21051
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
21052
|
+
if (resolved.error) return refuse({ ...resolved.error, message: `Operation ${i2 + 1}: ${resolved.error.message}`, operationIndex: i2 + 1 });
|
|
21053
|
+
const { part: _selector, ...rest } = operation;
|
|
21054
|
+
partOps.push({ operation: rest, origIndex: i2, part: resolved.part });
|
|
21055
|
+
}
|
|
21056
|
+
const results = [];
|
|
21057
|
+
const receipts = [];
|
|
21058
|
+
const executionOrder = [];
|
|
21059
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
21060
|
+
const originalIssues = [];
|
|
21061
|
+
let anyChange = false;
|
|
21062
|
+
let anyFailure = false;
|
|
21063
|
+
let bodyResult = null;
|
|
21064
|
+
if (bodyOps.length) {
|
|
21065
|
+
bodyResult = await this.applyBodyOperations(bodyOps.map((item) => item.operation), { ...options, expectedRevision: void 0 });
|
|
21066
|
+
const toOriginal = (index) => bodyOps[index - 1]?.origIndex + 1;
|
|
21067
|
+
for (const result of bodyResult.results || []) {
|
|
21068
|
+
results.push({
|
|
21069
|
+
...result,
|
|
21070
|
+
index: toOriginal(result.index),
|
|
21071
|
+
...result.receipt ? { receipt: { ...result.receipt, operationIndex: toOriginal(result.receipt.operationIndex) } } : {}
|
|
21072
|
+
});
|
|
21073
|
+
}
|
|
21074
|
+
for (const receipt of bodyResult.receipts || []) receipts.push({ ...receipt, operationIndex: toOriginal(receipt.operationIndex) });
|
|
21075
|
+
for (const index of bodyResult.executionOrder || []) executionOrder.push(toOriginal(index));
|
|
21076
|
+
for (const name of bodyResult.authorsUsed || []) authorsUsed.add(name);
|
|
21077
|
+
originalIssues.push(...bodyResult.validation?.originalIssues || []);
|
|
21078
|
+
if (bodyResult.status === "error" || bodyResult.rolledBack) {
|
|
21079
|
+
anyFailure = true;
|
|
21080
|
+
if (atomic) {
|
|
21081
|
+
restore();
|
|
21082
|
+
return { ...bodyResult, results, receipts, executionOrder, written: false, artifactsChanged: [], rolledBack: true, ...original() };
|
|
21083
|
+
}
|
|
21084
|
+
}
|
|
21085
|
+
anyChange = bodyResult.hasChanges === true;
|
|
21086
|
+
}
|
|
21087
|
+
const working = cloneEntries(this.entries);
|
|
21088
|
+
const zip = new MemoryZip(working);
|
|
21089
|
+
const changedParts = /* @__PURE__ */ new Set();
|
|
21090
|
+
const { expectedRevision: _ignored, ...runnerOptions } = options;
|
|
21091
|
+
const failPart = (item, error2) => {
|
|
21092
|
+
anyFailure = true;
|
|
21093
|
+
const failure = normalizeErrorWithRecovery({ ...error2, operationIndex: item.origIndex + 1 });
|
|
21094
|
+
results.push({ index: item.origIndex + 1, type: item.operation.type || "redline", status: "error", authorUsed: author, part: item.part.path, error: failure });
|
|
21095
|
+
};
|
|
21096
|
+
for (const item of partOps) {
|
|
21097
|
+
const path = item.part.path;
|
|
21098
|
+
const xml = text(working, path);
|
|
21099
|
+
if (!xml) {
|
|
21100
|
+
failPart(item, { code: "PART_NOT_FOUND", message: `Part '${path}' is referenced but missing from the package.` });
|
|
21101
|
+
if (atomic) break;
|
|
21102
|
+
continue;
|
|
21103
|
+
}
|
|
21104
|
+
const numberingText = text(working, "word/numbering.xml");
|
|
21105
|
+
const outcome = await applyOperationsToDocumentXml(xml, [item.operation], author, { numberingIdState: createDynamicNumberingIdState(numberingText || void 0) }, {
|
|
21106
|
+
...runnerOptions,
|
|
21107
|
+
atomic: true,
|
|
21108
|
+
strictTargets: options.strictTargets !== false,
|
|
21109
|
+
_existingCommentDetails: {}
|
|
21110
|
+
});
|
|
21111
|
+
const single = outcome.results?.[0];
|
|
21112
|
+
if (outcome.rolledBack || outcome.status === "error") {
|
|
21113
|
+
failPart(item, single?.error || outcome.error || { code: "OPERATION_FAILED", message: "Operation failed." });
|
|
21114
|
+
if (atomic) break;
|
|
21115
|
+
continue;
|
|
21116
|
+
}
|
|
21117
|
+
if (!outcome.hasChanges) {
|
|
21118
|
+
results.push({ ...single, index: item.origIndex + 1, part: path });
|
|
21119
|
+
continue;
|
|
21120
|
+
}
|
|
21121
|
+
if (countRevisionsInsideFields(outcome.documentXml) > countRevisionsInsideFields(xml)) {
|
|
21122
|
+
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.` });
|
|
21123
|
+
if (atomic) break;
|
|
21124
|
+
continue;
|
|
21125
|
+
}
|
|
21126
|
+
const taken = /* @__PURE__ */ new Set();
|
|
21127
|
+
for (const [name, data] of working) {
|
|
21128
|
+
if (name === path || !(name === "word/document.xml" || isHeaderFooterPath(name))) continue;
|
|
21129
|
+
for (const id of collectRevisionIds(textDecoder2.decode(data))) taken.add(id);
|
|
21130
|
+
}
|
|
21131
|
+
const renumbered = renumberCollidingRevisionIds(outcome.documentXml, taken);
|
|
21132
|
+
working.set(path, textEncoder4.encode(renumbered.xml));
|
|
21133
|
+
const describeReceipt = (receipt) => receipt?.revisionItems ? { ...receipt, revisionItems: receipt.revisionItems.map((revision) => ({ ...revision, id: renumbered.idMap.get(String(revision.id)) ?? revision.id, partName: path })) } : receipt;
|
|
21134
|
+
if (outcome.numberingXmlParts?.length) {
|
|
21135
|
+
await ensureNumberingArtifactsInZip(zip, outcome.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
21136
|
+
}
|
|
21137
|
+
changedParts.add(path);
|
|
21138
|
+
anyChange = true;
|
|
21139
|
+
results.push({
|
|
21140
|
+
...single,
|
|
21141
|
+
index: item.origIndex + 1,
|
|
21142
|
+
part: path,
|
|
21143
|
+
partSections: item.part.appliesToSections,
|
|
21144
|
+
...single?.receipt ? { receipt: { ...describeReceipt(single.receipt), operationIndex: item.origIndex + 1 } } : {}
|
|
21145
|
+
});
|
|
21146
|
+
if (outcome.receipts?.[0]) receipts.push({ ...describeReceipt(outcome.receipts[0]), operationIndex: item.origIndex + 1 });
|
|
21147
|
+
executionOrder.push(item.origIndex + 1);
|
|
21148
|
+
for (const name of outcome.authorsUsed || []) authorsUsed.add(name);
|
|
21149
|
+
}
|
|
21150
|
+
const generatedIssues = [];
|
|
21151
|
+
if (!(atomic && anyFailure)) {
|
|
21152
|
+
for (const path of changedParts) {
|
|
21153
|
+
const before = validateRedlineOoxml(text(this.entries, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
21154
|
+
const after = validateRedlineOoxml(text(working, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
21155
|
+
generatedIssues.push(...validationErrors(subtractValidationIssueMultiset(after, before)));
|
|
21156
|
+
}
|
|
21157
|
+
if (options.validate !== false && changedParts.size) {
|
|
21158
|
+
try {
|
|
21159
|
+
await validateDocxPackage(zip);
|
|
21160
|
+
} catch (error2) {
|
|
21161
|
+
generatedIssues.push({ source: "package", code: "PACKAGE_VALIDATION", severity: "error", message: error2.message });
|
|
21162
|
+
}
|
|
21163
|
+
}
|
|
21164
|
+
}
|
|
21165
|
+
if (atomic && anyFailure || generatedIssues.length) {
|
|
21166
|
+
restore();
|
|
21167
|
+
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." };
|
|
21168
|
+
const rolledBackResults = results.map((result) => result.receipt ? { ...result, receipt: { ...result.receipt, committed: false, finalDisposition: "rolled_back" } } : result).sort((a, b) => a.index - b.index);
|
|
21169
|
+
const rolledBackReceipts = receipts.map((receipt) => ({ ...receipt, committed: false, finalDisposition: "rolled_back" }));
|
|
21170
|
+
return refuse(error2, rolledBackResults, rolledBackReceipts, { validation: { originalIssues, generatedIssues }, issues: generatedIssues });
|
|
21171
|
+
}
|
|
21172
|
+
results.sort((a, b) => a.index - b.index);
|
|
21173
|
+
receipts.sort((a, b) => a.operationIndex - b.operationIndex);
|
|
21174
|
+
const status = !anyFailure ? "ok" : anyChange ? "partial" : "error";
|
|
21175
|
+
const failureError = anyFailure ? { error: normalizeErrorWithRecovery({ code: "BATCH_OPERATION_FAILED", message: "One or more operations failed; the others were applied." }) } : {};
|
|
21176
|
+
if (!changedParts.size) {
|
|
21177
|
+
const buf = toBufferCompatible(this.originalBytes);
|
|
21178
|
+
return {
|
|
21179
|
+
...bodyResult || {},
|
|
21180
|
+
results,
|
|
21181
|
+
receipts,
|
|
21182
|
+
executionOrder,
|
|
21183
|
+
authorsUsed: [...authorsUsed],
|
|
21184
|
+
status,
|
|
21185
|
+
hasChanges: anyChange,
|
|
21186
|
+
written: bodyResult?.written === true,
|
|
21187
|
+
artifactsChanged: bodyResult?.artifactsChanged || [],
|
|
21188
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
21189
|
+
...failureError,
|
|
21190
|
+
uint8Array: this.originalBytes,
|
|
21191
|
+
toUint8Array: () => new Uint8Array(this.originalBytes),
|
|
21192
|
+
buffer: buf,
|
|
21193
|
+
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
21194
|
+
};
|
|
21195
|
+
}
|
|
21196
|
+
this.entries = working;
|
|
21197
|
+
const outputBytes = this.toUint8Array();
|
|
21198
|
+
this.originalBytes = outputBytes;
|
|
21199
|
+
const artifactsChanged = [...working].filter(([name, data]) => !snapshotEntries.has(name) || !areByteArraysEqual(data, snapshotEntries.get(name))).map(([name]) => name);
|
|
21200
|
+
return {
|
|
21201
|
+
...bodyResult || {},
|
|
21202
|
+
status,
|
|
21203
|
+
hasChanges: true,
|
|
21204
|
+
written: true,
|
|
21205
|
+
results,
|
|
21206
|
+
receipts,
|
|
21207
|
+
executionOrder,
|
|
21208
|
+
authorsUsed: [...authorsUsed],
|
|
21209
|
+
artifactsChanged,
|
|
21210
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
21211
|
+
...failureError,
|
|
21212
|
+
uint8Array: outputBytes,
|
|
21213
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
21214
|
+
buffer: toBufferCompatible(outputBytes),
|
|
21215
|
+
inspection: this.inspect(),
|
|
21216
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
21217
|
+
};
|
|
21218
|
+
}
|
|
21219
|
+
async applyBodyOperations(operations, options = {}) {
|
|
20264
21220
|
const failedApply = (error2) => {
|
|
20265
21221
|
const results = [];
|
|
20266
21222
|
const receipts = [];
|
|
@@ -20371,18 +21327,21 @@ var DocxDocument = class {
|
|
|
20371
21327
|
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
20372
21328
|
};
|
|
20373
21329
|
}
|
|
20374
|
-
|
|
21330
|
+
const bodyChanged = (result.results || []).some((item) => item?.status !== "error" && item?.operationType !== "comment_resolve");
|
|
21331
|
+
if (bodyChanged) working.set("word/document.xml", textEncoder4.encode(result.documentXml));
|
|
20375
21332
|
await ensureNumberingArtifactsInZip(zip, result.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
20376
21333
|
const existingCommentsXml = text(working, "word/comments.xml");
|
|
20377
21334
|
const commentsXmlForPackaging = result.commentsXml || existingCommentsXml;
|
|
20378
21335
|
await ensureCommentsArtifactsInZip(zip, commentsXmlForPackaging, {
|
|
20379
21336
|
replaceExisting: result.commentsXmlMode === "replace" || !result.commentsXml && !!existingCommentsXml
|
|
20380
21337
|
});
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20384
|
-
|
|
20385
|
-
}
|
|
21338
|
+
if (result.commentsExtendedXml) {
|
|
21339
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, {
|
|
21340
|
+
replaceExisting: result.commentsExtendedXmlMode === "replace"
|
|
21341
|
+
});
|
|
21342
|
+
}
|
|
21343
|
+
await repairKnownContentTypes(zip);
|
|
21344
|
+
reconcileCommentSiblings(working);
|
|
20386
21345
|
if (options.validate !== false) {
|
|
20387
21346
|
const generated = validateRedlineOoxml(result.documentXml);
|
|
20388
21347
|
const outputIssues = generated.issues.map((issue) => ({ source: "word/document.xml", ...issue }));
|
|
@@ -20451,13 +21410,25 @@ var DocxDocument = class {
|
|
|
20451
21410
|
const transform = action === "accept" ? acceptTrackedChangesInOoxml : action === "reject" ? rejectTrackedChangesInOoxml : null;
|
|
20452
21411
|
if (!transform) return packageFailure(this.originalBytes, "INVALID_ACTION", `Unknown revision action: ${action}`);
|
|
20453
21412
|
const sourceBytes = this.originalBytes;
|
|
21413
|
+
const originalEntries = this.entries;
|
|
20454
21414
|
const working = cloneEntries(this.entries);
|
|
20455
21415
|
const zip = new MemoryZip(working);
|
|
20456
21416
|
const result = transform(text(working, "word/document.xml"), { author: options.author, allAuthors: options.allAuthors === true });
|
|
20457
21417
|
if (result.status === "error" || result.error) {
|
|
20458
21418
|
return packageFailure(sourceBytes, result.error?.code || "REVISION_OPERATION_FAILED", result.error?.message || "Revision operation failed.");
|
|
20459
21419
|
}
|
|
20460
|
-
|
|
21420
|
+
const partUpdates = /* @__PURE__ */ new Map();
|
|
21421
|
+
for (const part of this.inspectHeadersFooters()) {
|
|
21422
|
+
const partXml = text(working, part.path);
|
|
21423
|
+
if (!partXml) continue;
|
|
21424
|
+
const partResult = transform(partXml, { author: options.author, allAuthors: options.allAuthors === true });
|
|
21425
|
+
if (partResult.status === "error" || partResult.error) {
|
|
21426
|
+
return packageFailure(sourceBytes, partResult.error?.code || "REVISION_OPERATION_FAILED", `${part.path}: ${partResult.error?.message || "Revision operation failed."}`);
|
|
21427
|
+
}
|
|
21428
|
+
if (partResult.hasChanges) partUpdates.set(part.path, partResult.oxml);
|
|
21429
|
+
}
|
|
21430
|
+
const hasChanges = result.hasChanges || partUpdates.size > 0;
|
|
21431
|
+
if (!hasChanges) {
|
|
20461
21432
|
const buf = toBufferCompatible(sourceBytes);
|
|
20462
21433
|
return {
|
|
20463
21434
|
...result,
|
|
@@ -20470,8 +21441,10 @@ var DocxDocument = class {
|
|
|
20470
21441
|
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
20471
21442
|
};
|
|
20472
21443
|
}
|
|
20473
|
-
working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
21444
|
+
if (result.hasChanges) working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
21445
|
+
for (const [path, xml] of partUpdates) working.set(path, textEncoder4.encode(xml));
|
|
20474
21446
|
try {
|
|
21447
|
+
await repairKnownContentTypes(zip);
|
|
20475
21448
|
if (options.validate !== false) await validateDocxPackage(zip);
|
|
20476
21449
|
} catch (error2) {
|
|
20477
21450
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
@@ -20484,13 +21457,71 @@ var DocxDocument = class {
|
|
|
20484
21457
|
...result,
|
|
20485
21458
|
status: "ok",
|
|
20486
21459
|
written: true,
|
|
20487
|
-
|
|
21460
|
+
hasChanges: true,
|
|
21461
|
+
artifactsChanged: [...working].filter(([name, data]) => !originalEntries.has(name) || !areByteArraysEqual(data, originalEntries.get(name))).map(([name]) => name),
|
|
20488
21462
|
uint8Array: outputBytes,
|
|
20489
21463
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
20490
21464
|
buffer: outputBuf,
|
|
20491
21465
|
toBuffer: () => toBufferCompatible(outputBytes)
|
|
20492
21466
|
};
|
|
20493
21467
|
}
|
|
21468
|
+
/**
|
|
21469
|
+
* Resolves (or reopens) the comment thread containing `commentId`.
|
|
21470
|
+
* Resolved is a thread-level state in Word, so the root and every reply are updated together.
|
|
21471
|
+
*/
|
|
21472
|
+
async resolveComment(commentId, options = {}) {
|
|
21473
|
+
const sourceBytes = this.originalBytes;
|
|
21474
|
+
const working = cloneEntries(this.entries);
|
|
21475
|
+
const resolved = options.resolved !== false;
|
|
21476
|
+
const noChange = (extra) => {
|
|
21477
|
+
const buf = toBufferCompatible(sourceBytes);
|
|
21478
|
+
return {
|
|
21479
|
+
status: "ok",
|
|
21480
|
+
hasChanges: false,
|
|
21481
|
+
written: false,
|
|
21482
|
+
artifactsChanged: [],
|
|
21483
|
+
...extra,
|
|
21484
|
+
uint8Array: sourceBytes,
|
|
21485
|
+
toUint8Array: () => new Uint8Array(sourceBytes),
|
|
21486
|
+
buffer: buf,
|
|
21487
|
+
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
21488
|
+
};
|
|
21489
|
+
};
|
|
21490
|
+
const result = applyThreadResolutionToParts({
|
|
21491
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
21492
|
+
commentsExtendedXml: text(working, "word/commentsExtended.xml"),
|
|
21493
|
+
commentId,
|
|
21494
|
+
resolved
|
|
21495
|
+
});
|
|
21496
|
+
if (result.status === "error") return packageFailure(sourceBytes, result.error.code, result.error.message);
|
|
21497
|
+
if (!result.hasChanges) return noChange({ resolved, threadRootId: result.threadRootId, commentIds: result.commentIds });
|
|
21498
|
+
const zip = new MemoryZip(working);
|
|
21499
|
+
try {
|
|
21500
|
+
if (result.commentsXml) working.set("word/comments.xml", textEncoder4.encode(result.commentsXml));
|
|
21501
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, { replaceExisting: true });
|
|
21502
|
+
await repairKnownContentTypes(zip);
|
|
21503
|
+
if (options.validate !== false) await validateDocxPackage(zip);
|
|
21504
|
+
} catch (error2) {
|
|
21505
|
+
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
21506
|
+
}
|
|
21507
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
21508
|
+
this.entries = working;
|
|
21509
|
+
const outputBytes = this.toUint8Array();
|
|
21510
|
+
this.originalBytes = outputBytes;
|
|
21511
|
+
return {
|
|
21512
|
+
status: "ok",
|
|
21513
|
+
hasChanges: true,
|
|
21514
|
+
written: true,
|
|
21515
|
+
resolved,
|
|
21516
|
+
threadRootId: result.threadRootId,
|
|
21517
|
+
commentIds: result.commentIds,
|
|
21518
|
+
artifactsChanged,
|
|
21519
|
+
uint8Array: outputBytes,
|
|
21520
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
21521
|
+
buffer: toBufferCompatible(outputBytes),
|
|
21522
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
21523
|
+
};
|
|
21524
|
+
}
|
|
20494
21525
|
async deleteComments(options = {}) {
|
|
20495
21526
|
const sourceBytes = this.originalBytes;
|
|
20496
21527
|
const working = cloneEntries(this.entries);
|
|
@@ -20514,7 +21545,13 @@ var DocxDocument = class {
|
|
|
20514
21545
|
if (!parsed.doc || parsed.error) {
|
|
20515
21546
|
return packageFailure(sourceBytes, "PARSE_ERROR", parsed.error?.message || "Could not parse comments.xml.");
|
|
20516
21547
|
}
|
|
20517
|
-
const
|
|
21548
|
+
const requestedIds = Array.isArray(options.ids) ? new Set(options.ids.map(String)) : null;
|
|
21549
|
+
if (requestedIds) {
|
|
21550
|
+
const known = new Set(Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")));
|
|
21551
|
+
const missing = [...requestedIds].filter((id) => !known.has(id));
|
|
21552
|
+
if (missing.length) return packageFailure(sourceBytes, "COMMENT_NOT_FOUND", `Comment id(s) not found: ${missing.join(", ")}.`);
|
|
21553
|
+
}
|
|
21554
|
+
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;
|
|
20518
21555
|
const ids = new Set(
|
|
20519
21556
|
Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).filter(matches).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
20520
21557
|
);
|
|
@@ -20543,9 +21580,8 @@ var DocxDocument = class {
|
|
|
20543
21580
|
const idByParaId = /* @__PURE__ */ new Map();
|
|
20544
21581
|
for (const comment of Array.from(parsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
20545
21582
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
20546
|
-
const
|
|
20547
|
-
|
|
20548
|
-
if (paraId) idByParaId.set(paraId.toUpperCase(), id);
|
|
21583
|
+
const paraId = commentParaId(comment);
|
|
21584
|
+
if (paraId) idByParaId.set(paraId, id);
|
|
20549
21585
|
}
|
|
20550
21586
|
for (const [paraId, id] of idByParaId) {
|
|
20551
21587
|
if (ids.has(id)) removedParaIds.add(paraId);
|
|
@@ -20564,8 +21600,8 @@ var DocxDocument = class {
|
|
|
20564
21600
|
}
|
|
20565
21601
|
}
|
|
20566
21602
|
}
|
|
20567
|
-
const
|
|
20568
|
-
const remainingParsed = parseOoxmlSafe(
|
|
21603
|
+
const remainingXml = requestedIds ? commentsXml : deleteCommentsByAuthorInOoxml(commentsXml, { author: options.author, allAuthors: options.allAuthors === true }).oxml;
|
|
21604
|
+
const remainingParsed = parseOoxmlSafe(remainingXml, "application/xml");
|
|
20569
21605
|
if (!remainingParsed.doc || remainingParsed.error) {
|
|
20570
21606
|
return packageFailure(sourceBytes, "PARSE_ERROR", remainingParsed.error?.message || "Could not parse updated comments.xml.");
|
|
20571
21607
|
}
|
|
@@ -20601,10 +21637,14 @@ var DocxDocument = class {
|
|
|
20601
21637
|
}
|
|
20602
21638
|
working.set("word/document.xml", textEncoder4.encode(serializer.serializeToString(documentParsed.doc)));
|
|
20603
21639
|
try {
|
|
20604
|
-
|
|
21640
|
+
reconcileCommentSiblings(working);
|
|
21641
|
+
const finalZip = new MemoryZip(working);
|
|
21642
|
+
await repairKnownContentTypes(finalZip);
|
|
21643
|
+
if (options.validate !== false) await validateDocxPackage(finalZip);
|
|
20605
21644
|
} catch (error2) {
|
|
20606
21645
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
20607
21646
|
}
|
|
21647
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
20608
21648
|
this.entries = working;
|
|
20609
21649
|
const outputBytes = this.toUint8Array();
|
|
20610
21650
|
this.originalBytes = outputBytes;
|
|
@@ -20615,7 +21655,7 @@ var DocxDocument = class {
|
|
|
20615
21655
|
written: true,
|
|
20616
21656
|
commentsRemoved: ids.size,
|
|
20617
21657
|
referencesRemoved,
|
|
20618
|
-
artifactsChanged
|
|
21658
|
+
artifactsChanged,
|
|
20619
21659
|
uint8Array: outputBytes,
|
|
20620
21660
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
20621
21661
|
buffer: outputBuf,
|
|
@@ -20871,6 +21911,7 @@ export {
|
|
|
20871
21911
|
rejectTrackedChangesInOoxml,
|
|
20872
21912
|
remapNumberingPayloadForDocument,
|
|
20873
21913
|
removeFormattingFromRPr,
|
|
21914
|
+
repairKnownContentTypes,
|
|
20874
21915
|
reserveNextNumberingId,
|
|
20875
21916
|
reserveNextNumberingIdPair,
|
|
20876
21917
|
resolveParagraphRangeByRefs,
|