@ansonlai/docx-redline-js 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +539 -486
- package/README.md +331 -309
- package/dist/docx-redline-js.esm.js +1083 -163
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +107 -107
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +1083 -163
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +1083 -163
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +69 -8
- package/docs/schemas/document-operations.schema.json +29 -0
- package/document/docx-document.js +416 -22
- package/engine/reconstruction-writer.js +18 -0
- package/index.d.ts +598 -575
- package/index.js +349 -348
- package/node/cli-help.js +5 -5
- package/node/cli.js +760 -754
- package/node/index.d.ts +1 -1
- package/package.json +2 -1
- package/scripts/generate-word-comment-edge-case-fixture.ps1 +58 -0
- package/scripts/generate-word-comment-thread-fixture.ps1 +36 -0
- package/scripts/generate-word-header-footer-fixture.ps1 +32 -0
- package/scripts/generate-word-multi-section-fixture.ps1 +27 -0
- package/scripts/generate-word-package-integrity-cases.mjs +141 -0
- package/scripts/generate-word-thread-state-fixtures.ps1 +50 -0
- package/scripts/word-com-package-integrity.ps1 +98 -0
- package/services/batch-operation-orchestrator.js +262 -259
- package/services/comment-replies.js +96 -95
- package/services/comment-thread-parts.js +280 -0
- package/services/document-inspection.js +366 -365
- package/services/document-operation-applier.js +230 -202
- package/services/document-operation-contract.js +17 -2
- package/services/error-recovery.js +6 -0
- package/services/headers-footers.js +206 -0
- package/services/operation-batch-compiler.js +1 -0
- package/services/operation-preflight.js +231 -218
- package/services/package-parts.js +80 -0
- package/services/standalone-docx-plumbing.js +631 -569
- package/services/standalone-operation-runner.d.ts +19 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js v0.8.1 — https://github.com/AnsonLai/docx-redline-js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -7717,6 +7717,19 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
7717
7717
|
pendingReplacementEvent = null;
|
|
7718
7718
|
}
|
|
7719
7719
|
}
|
|
7720
|
+
for (const offset of Array.from(sentinelMapByStart.keys()).sort((a, b) => a - b)) {
|
|
7721
|
+
for (const sentinel of sentinelMapByStart.get(offset)) {
|
|
7722
|
+
if (!sentinel.isCommentMarker || emittedCommentMarkers.has(sentinel.node)) continue;
|
|
7723
|
+
emittedCommentMarkers.add(sentinel.node);
|
|
7724
|
+
if (isWordElement(sentinel.node, "commentReference")) {
|
|
7725
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
7726
|
+
run.appendChild(sentinel.node.cloneNode(true));
|
|
7727
|
+
currentParagraph.appendChild(run);
|
|
7728
|
+
} else {
|
|
7729
|
+
currentParagraph.appendChild(sentinel.node.cloneNode(true));
|
|
7730
|
+
}
|
|
7731
|
+
}
|
|
7732
|
+
}
|
|
7720
7733
|
if (generateRedlines) {
|
|
7721
7734
|
containerFragments.forEach((fragment) => {
|
|
7722
7735
|
Array.from(fragment.childNodes).forEach((node) => {
|
|
@@ -11511,6 +11524,260 @@ function ingestWordOoxmlToMarkdownResult(ooxml) {
|
|
|
11511
11524
|
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
11512
11525
|
}
|
|
11513
11526
|
|
|
11527
|
+
// services/comment-builders.js
|
|
11528
|
+
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
11529
|
+
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
11530
|
+
function createCommentParaId(commentId) {
|
|
11531
|
+
const numeric = Number.parseInt(String(commentId), 10);
|
|
11532
|
+
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
11533
|
+
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
11534
|
+
}
|
|
11535
|
+
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
11536
|
+
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
11537
|
+
const escapedContent = escapeXml(content);
|
|
11538
|
+
const escapedAuthor = escapeXml(author);
|
|
11539
|
+
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
11540
|
+
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
11541
|
+
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
11542
|
+
</w:p>
|
|
11543
|
+
</w:comment>`;
|
|
11544
|
+
}
|
|
11545
|
+
function buildCommentsPartXml(comments) {
|
|
11546
|
+
if (!comments || comments.length === 0) {
|
|
11547
|
+
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
11548
|
+
}
|
|
11549
|
+
const commentElements = comments.map(
|
|
11550
|
+
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
11551
|
+
).join("\n ");
|
|
11552
|
+
return `<w:comments xmlns:w="${NS_W}">
|
|
11553
|
+
${commentElements}
|
|
11554
|
+
</w:comments>`;
|
|
11555
|
+
}
|
|
11556
|
+
function buildCommentsExtendedPartXml(entries) {
|
|
11557
|
+
const body = (entries || []).map((entry) => {
|
|
11558
|
+
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
11559
|
+
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
11560
|
+
}).join("");
|
|
11561
|
+
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
11562
|
+
}
|
|
11563
|
+
|
|
11564
|
+
// services/comment-thread-parts.js
|
|
11565
|
+
var attrOf = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
11566
|
+
function commentThreadParagraph(commentElement) {
|
|
11567
|
+
const paragraphs = Array.from(commentElement?.getElementsByTagNameNS?.("*", "p") || []);
|
|
11568
|
+
return paragraphs.length ? paragraphs[paragraphs.length - 1] : null;
|
|
11569
|
+
}
|
|
11570
|
+
function commentParaId(commentElement) {
|
|
11571
|
+
return attrOf(commentThreadParagraph(commentElement), "w14:paraId", "paraId").toUpperCase();
|
|
11572
|
+
}
|
|
11573
|
+
function usedParaIds(commentsDoc, extendedDoc) {
|
|
11574
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11575
|
+
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
11576
|
+
const id = attrOf(p, "w14:paraId", "paraId");
|
|
11577
|
+
if (id) ids.add(id.toUpperCase());
|
|
11578
|
+
}
|
|
11579
|
+
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
11580
|
+
const id = attrOf(ex, "w15:paraId", "paraId");
|
|
11581
|
+
if (id) ids.add(id.toUpperCase());
|
|
11582
|
+
}
|
|
11583
|
+
return ids;
|
|
11584
|
+
}
|
|
11585
|
+
function allocateParaId(commentId, occupied) {
|
|
11586
|
+
let candidate = createCommentParaId(commentId);
|
|
11587
|
+
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
11588
|
+
while (occupied.has(candidate)) {
|
|
11589
|
+
value = value + 1 >>> 0;
|
|
11590
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
11591
|
+
}
|
|
11592
|
+
occupied.add(candidate);
|
|
11593
|
+
return candidate;
|
|
11594
|
+
}
|
|
11595
|
+
var parseDoc = (xml, partName) => {
|
|
11596
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
11597
|
+
if (!parsed.doc || parsed.error) return { error: { code: "PARSE_ERROR", message: `Could not parse ${partName}: ${parsed.error?.message || "invalid XML"}` } };
|
|
11598
|
+
return { doc: parsed.doc };
|
|
11599
|
+
};
|
|
11600
|
+
function applyThreadResolutionToParts({ commentsXml, commentsExtendedXml = null, commentId, resolved = true }) {
|
|
11601
|
+
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "Resolving a comment requires an existing word/comments.xml part." } };
|
|
11602
|
+
const commentsParsed = parseDoc(commentsXml, "word/comments.xml");
|
|
11603
|
+
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
11604
|
+
const commentsDoc = commentsParsed.doc;
|
|
11605
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
11606
|
+
const comments = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
11607
|
+
const target = comments.find((node) => idOf(node) === String(commentId));
|
|
11608
|
+
if (!target) return { status: "error", error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` } };
|
|
11609
|
+
let extendedDoc;
|
|
11610
|
+
if (commentsExtendedXml) {
|
|
11611
|
+
const parsed = parseDoc(commentsExtendedXml, "word/commentsExtended.xml");
|
|
11612
|
+
if (parsed.error) return { status: "error", error: parsed.error };
|
|
11613
|
+
extendedDoc = parsed.doc;
|
|
11614
|
+
} else {
|
|
11615
|
+
extendedDoc = parseDoc(`<w15:commentsEx xmlns:w15="${NS_W15}"/>`, "word/commentsExtended.xml").doc;
|
|
11616
|
+
}
|
|
11617
|
+
const root = extendedDoc.documentElement;
|
|
11618
|
+
const entryFor = (paraId) => Array.from(root.getElementsByTagNameNS("*", "commentEx")).find((node) => attrOf(node, "w15:paraId", "paraId").toUpperCase() === paraId) || null;
|
|
11619
|
+
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
11620
|
+
let commentsChanged = false;
|
|
11621
|
+
const keyOf = (node) => {
|
|
11622
|
+
let paraId = commentParaId(node);
|
|
11623
|
+
if (!paraId) {
|
|
11624
|
+
paraId = allocateParaId(idOf(node), occupied);
|
|
11625
|
+
commentThreadParagraph(node)?.setAttributeNS(NS_W14, "w14:paraId", paraId);
|
|
11626
|
+
commentsChanged = true;
|
|
11627
|
+
}
|
|
11628
|
+
return paraId;
|
|
11629
|
+
};
|
|
11630
|
+
const targetKey = keyOf(target);
|
|
11631
|
+
const targetEntry = entryFor(targetKey);
|
|
11632
|
+
const rootKey = targetEntry && attrOf(targetEntry, "w15:paraIdParent", "paraIdParent").toUpperCase() || targetKey;
|
|
11633
|
+
const threadKeys = /* @__PURE__ */ new Set([rootKey, targetKey]);
|
|
11634
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentEx"))) {
|
|
11635
|
+
if (attrOf(entry, "w15:paraIdParent", "paraIdParent").toUpperCase() === rootKey) threadKeys.add(attrOf(entry, "w15:paraId", "paraId").toUpperCase());
|
|
11636
|
+
}
|
|
11637
|
+
const doneValue = resolved ? "1" : "0";
|
|
11638
|
+
let extendedChanged = !commentsExtendedXml;
|
|
11639
|
+
for (const key of threadKeys) {
|
|
11640
|
+
let entry = entryFor(key);
|
|
11641
|
+
if (!entry) {
|
|
11642
|
+
entry = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
11643
|
+
entry.setAttributeNS(NS_W15, "w15:paraId", key);
|
|
11644
|
+
if (key !== rootKey) entry.setAttributeNS(NS_W15, "w15:paraIdParent", rootKey);
|
|
11645
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
11646
|
+
root.appendChild(entry);
|
|
11647
|
+
extendedChanged = true;
|
|
11648
|
+
} else if (attrOf(entry, "w15:done", "done") !== doneValue) {
|
|
11649
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
11650
|
+
extendedChanged = true;
|
|
11651
|
+
}
|
|
11652
|
+
}
|
|
11653
|
+
const byKey = new Map(comments.map((node) => [commentParaId(node), node]));
|
|
11654
|
+
const commentIds = [...threadKeys].map((key) => byKey.get(key)).filter(Boolean).map(idOf);
|
|
11655
|
+
const rootComment = byKey.get(rootKey);
|
|
11656
|
+
const serializer = createSerializer();
|
|
11657
|
+
const hasChanges = extendedChanged || commentsChanged;
|
|
11658
|
+
return {
|
|
11659
|
+
status: "ok",
|
|
11660
|
+
hasChanges,
|
|
11661
|
+
resolved,
|
|
11662
|
+
threadRootId: rootComment ? idOf(rootComment) : String(commentId),
|
|
11663
|
+
commentIds,
|
|
11664
|
+
...hasChanges ? { commentsExtendedXml: serializer.serializeToString(extendedDoc) } : {},
|
|
11665
|
+
...commentsChanged ? { commentsXml: serializer.serializeToString(commentsDoc) } : {}
|
|
11666
|
+
};
|
|
11667
|
+
}
|
|
11668
|
+
var NS_W_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
11669
|
+
function anchorReplyInDocument(xmlDoc, memberIds, newId) {
|
|
11670
|
+
const wanted = new Set(memberIds.map(String));
|
|
11671
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
11672
|
+
const all = (name) => Array.from(xmlDoc.getElementsByTagNameNS("*", name)).filter((node) => wanted.has(idOf(node)));
|
|
11673
|
+
const starts = all("commentRangeStart");
|
|
11674
|
+
const references = all("commentReference");
|
|
11675
|
+
if (!starts.length || !references.length) return false;
|
|
11676
|
+
const lastStart = starts[starts.length - 1];
|
|
11677
|
+
const lastReference = references[references.length - 1];
|
|
11678
|
+
const referenceRun = lastReference.parentNode?.localName === "r" ? lastReference.parentNode : lastReference;
|
|
11679
|
+
const make = (name, parent) => {
|
|
11680
|
+
const element = xmlDoc.createElementNS(NS_W_MAIN, `w:${name}`);
|
|
11681
|
+
element.setAttributeNS(NS_W_MAIN, "w:id", String(newId));
|
|
11682
|
+
if (parent) parent.appendChild(element);
|
|
11683
|
+
return element;
|
|
11684
|
+
};
|
|
11685
|
+
const insertAfter = (anchor, node) => anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
11686
|
+
insertAfter(lastStart, make("commentRangeStart"));
|
|
11687
|
+
const end = make("commentRangeEnd");
|
|
11688
|
+
const run = xmlDoc.createElementNS(NS_W_MAIN, "w:r");
|
|
11689
|
+
run.appendChild(xmlDoc.createElementNS(NS_W_MAIN, "w:rPr"));
|
|
11690
|
+
make("commentReference", run);
|
|
11691
|
+
insertAfter(referenceRun, end);
|
|
11692
|
+
insertAfter(end, run);
|
|
11693
|
+
return true;
|
|
11694
|
+
}
|
|
11695
|
+
var NS_W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
|
|
11696
|
+
var NS_W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
|
|
11697
|
+
var durableIdFor = (paraId, taken) => {
|
|
11698
|
+
let value = (Number.parseInt(paraId, 16) ^ 625341585) & 2147483646;
|
|
11699
|
+
let candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
11700
|
+
while (taken.has(candidate)) {
|
|
11701
|
+
value = value + 2 & 2147483646;
|
|
11702
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
11703
|
+
}
|
|
11704
|
+
taken.add(candidate);
|
|
11705
|
+
return candidate;
|
|
11706
|
+
};
|
|
11707
|
+
function reconcileCommentSiblingParts({ commentsXml, commentsIdsXml = null, commentsExtensibleXml = null }) {
|
|
11708
|
+
const out = {};
|
|
11709
|
+
if (!commentsXml || !commentsIdsXml && !commentsExtensibleXml) return out;
|
|
11710
|
+
const commentsParsed = parseOoxmlSafe(commentsXml, "application/xml");
|
|
11711
|
+
if (!commentsParsed.doc || commentsParsed.error) return out;
|
|
11712
|
+
const serializer = createSerializer();
|
|
11713
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
11714
|
+
for (const node of Array.from(commentsParsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
11715
|
+
const key = commentParaId(node);
|
|
11716
|
+
if (key) byKey.set(key, node);
|
|
11717
|
+
}
|
|
11718
|
+
let idsDoc = null;
|
|
11719
|
+
const durableByKey = /* @__PURE__ */ new Map();
|
|
11720
|
+
const taken = /* @__PURE__ */ new Set();
|
|
11721
|
+
let idsChanged = false;
|
|
11722
|
+
if (commentsIdsXml) {
|
|
11723
|
+
const parsed = parseOoxmlSafe(commentsIdsXml, "application/xml");
|
|
11724
|
+
if (!parsed.doc || parsed.error) return out;
|
|
11725
|
+
idsDoc = parsed.doc;
|
|
11726
|
+
const root = idsDoc.documentElement;
|
|
11727
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentId"))) {
|
|
11728
|
+
const key = attrOf(entry, "w16cid:paraId", "paraId").toUpperCase();
|
|
11729
|
+
const durable = attrOf(entry, "w16cid:durableId", "durableId").toUpperCase();
|
|
11730
|
+
if (durable) taken.add(durable);
|
|
11731
|
+
if (!byKey.has(key)) {
|
|
11732
|
+
root.removeChild(entry);
|
|
11733
|
+
idsChanged = true;
|
|
11734
|
+
continue;
|
|
11735
|
+
}
|
|
11736
|
+
durableByKey.set(key, durable);
|
|
11737
|
+
}
|
|
11738
|
+
for (const key of byKey.keys()) {
|
|
11739
|
+
if (durableByKey.has(key)) continue;
|
|
11740
|
+
const durable = durableIdFor(key, taken);
|
|
11741
|
+
const entry = idsDoc.createElementNS(NS_W16CID, "w16cid:commentId");
|
|
11742
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:paraId", key);
|
|
11743
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:durableId", durable);
|
|
11744
|
+
root.appendChild(entry);
|
|
11745
|
+
durableByKey.set(key, durable);
|
|
11746
|
+
idsChanged = true;
|
|
11747
|
+
}
|
|
11748
|
+
}
|
|
11749
|
+
let extDoc = null;
|
|
11750
|
+
let extChanged = false;
|
|
11751
|
+
if (commentsExtensibleXml && idsDoc) {
|
|
11752
|
+
const parsed = parseOoxmlSafe(commentsExtensibleXml, "application/xml");
|
|
11753
|
+
if (parsed.doc && !parsed.error) {
|
|
11754
|
+
extDoc = parsed.doc;
|
|
11755
|
+
const root = extDoc.documentElement;
|
|
11756
|
+
const live = new Set(durableByKey.values());
|
|
11757
|
+
const present = /* @__PURE__ */ new Set();
|
|
11758
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentExtensible"))) {
|
|
11759
|
+
const durable = attrOf(entry, "w16cex:durableId", "durableId").toUpperCase();
|
|
11760
|
+
if (!live.has(durable)) {
|
|
11761
|
+
root.removeChild(entry);
|
|
11762
|
+
extChanged = true;
|
|
11763
|
+
} else present.add(durable);
|
|
11764
|
+
}
|
|
11765
|
+
for (const [key, durable] of durableByKey) {
|
|
11766
|
+
if (present.has(durable)) continue;
|
|
11767
|
+
const date = attrOf(byKey.get(key), "w:date", "date");
|
|
11768
|
+
const entry = extDoc.createElementNS(NS_W16CEX, "w16cex:commentExtensible");
|
|
11769
|
+
entry.setAttributeNS(NS_W16CEX, "w16cex:durableId", durable);
|
|
11770
|
+
if (date) entry.setAttributeNS(NS_W16CEX, "w16cex:dateUtc", date);
|
|
11771
|
+
root.appendChild(entry);
|
|
11772
|
+
extChanged = true;
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
if (idsChanged) out.commentsIdsXml = serializer.serializeToString(idsDoc);
|
|
11777
|
+
if (extChanged) out.commentsExtensibleXml = serializer.serializeToString(extDoc);
|
|
11778
|
+
return out;
|
|
11779
|
+
}
|
|
11780
|
+
|
|
11514
11781
|
// core/sha256.js
|
|
11515
11782
|
var K = new Uint32Array([
|
|
11516
11783
|
1116352408,
|
|
@@ -12003,7 +12270,7 @@ function readCommentDefinitions(commentsDoc) {
|
|
|
12003
12270
|
author: attr2(comment, "author") || null,
|
|
12004
12271
|
date: attr2(comment, "date") || null,
|
|
12005
12272
|
text: paragraphs.map((p) => extractCanonicalParagraphText(p)).join("\n"),
|
|
12006
|
-
paraId:
|
|
12273
|
+
paraId: commentParaId(comment) || null
|
|
12007
12274
|
});
|
|
12008
12275
|
}
|
|
12009
12276
|
return result;
|
|
@@ -12234,43 +12501,6 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
12234
12501
|
};
|
|
12235
12502
|
}
|
|
12236
12503
|
|
|
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
12504
|
// services/comment-locator.js
|
|
12275
12505
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
12276
12506
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -12731,29 +12961,7 @@ function parseRequired(xml, partName) {
|
|
|
12731
12961
|
}
|
|
12732
12962
|
return { doc: parsed.doc };
|
|
12733
12963
|
}
|
|
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() }) {
|
|
12964
|
+
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, documentXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
12757
12965
|
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "A comment reply requires an existing word/comments.xml part." } };
|
|
12758
12966
|
const commentsParsed = parseRequired(commentsXml, "word/comments.xml");
|
|
12759
12967
|
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
@@ -12767,15 +12975,31 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
12767
12975
|
extendedDoc = parsed.doc;
|
|
12768
12976
|
}
|
|
12769
12977
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
12770
|
-
const
|
|
12978
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/w14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
12979
|
+
const parentParagraph = commentThreadParagraph(parent);
|
|
12771
12980
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
12772
|
-
let parentParaId =
|
|
12981
|
+
let parentParaId = commentParaId(parent);
|
|
12773
12982
|
if (!parentParaId) {
|
|
12774
12983
|
parentParaId = allocateParaId(parentCommentId, occupied);
|
|
12775
12984
|
parentParagraph.setAttributeNS(NS_W14, "w14:paraId", parentParaId);
|
|
12776
12985
|
} else {
|
|
12777
12986
|
parentParaId = parentParaId.toUpperCase();
|
|
12778
12987
|
}
|
|
12988
|
+
for (const node of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
12989
|
+
if (attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId) {
|
|
12990
|
+
const root2 = attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase();
|
|
12991
|
+
if (root2) parentParaId = root2;
|
|
12992
|
+
break;
|
|
12993
|
+
}
|
|
12994
|
+
}
|
|
12995
|
+
const threadCommentIds = [];
|
|
12996
|
+
const commentNodes = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
12997
|
+
const parentOf = new Map(Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || []).map((node) => [attr3(node, "w15:paraId", "paraId").toUpperCase(), attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase()]));
|
|
12998
|
+
for (const node of commentNodes) {
|
|
12999
|
+
const key = commentParaId(node);
|
|
13000
|
+
if (key === parentParaId || parentOf.get(key) === parentParaId) threadCommentIds.push(attr3(node, "w:id", "id"));
|
|
13001
|
+
}
|
|
13002
|
+
const threadRootCommentId = attr3(commentNodes.find((node) => commentParaId(node) === parentParaId), "w:id", "id") || String(parentCommentId);
|
|
12779
13003
|
const replyParaId = allocateParaId(commentId, occupied);
|
|
12780
13004
|
const replyParsed = parseRequired(`<w:comments xmlns:w="${NS_W6}" xmlns:w14="${NS_W14}">${buildCommentElement(commentId, author, commentContent, date, replyParaId)}</w:comments>`, "reply comment");
|
|
12781
13005
|
commentsDoc.documentElement.appendChild(commentsDoc.importNode(replyParsed.doc.documentElement.firstChild, true));
|
|
@@ -12784,16 +13008,18 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
12784
13008
|
}
|
|
12785
13009
|
const root = extendedDoc.documentElement;
|
|
12786
13010
|
const entries = Array.from(root.getElementsByTagNameNS("*", "commentEx"));
|
|
12787
|
-
|
|
13011
|
+
const rootEntry = entries.find((node) => attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId);
|
|
13012
|
+
if (!rootEntry) {
|
|
12788
13013
|
const parentEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
12789
13014
|
parentEx.setAttributeNS(NS_W15, "w15:paraId", parentParaId);
|
|
12790
13015
|
parentEx.setAttributeNS(NS_W15, "w15:done", "0");
|
|
12791
13016
|
root.appendChild(parentEx);
|
|
12792
13017
|
}
|
|
13018
|
+
const threadDone = attr3(rootEntry, "w15:done", "done") === "1" ? "1" : "0";
|
|
12793
13019
|
const replyEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
12794
13020
|
replyEx.setAttributeNS(NS_W15, "w15:paraId", replyParaId);
|
|
12795
13021
|
replyEx.setAttributeNS(NS_W15, "w15:paraIdParent", parentParaId);
|
|
12796
|
-
replyEx.setAttributeNS(NS_W15, "w15:done",
|
|
13022
|
+
replyEx.setAttributeNS(NS_W15, "w15:done", threadDone);
|
|
12797
13023
|
root.appendChild(replyEx);
|
|
12798
13024
|
const serializer = createSerializer();
|
|
12799
13025
|
return {
|
|
@@ -12806,7 +13032,9 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
12806
13032
|
commentId,
|
|
12807
13033
|
parentCommentId: String(parentCommentId),
|
|
12808
13034
|
paraId: replyParaId,
|
|
12809
|
-
parentParaId
|
|
13035
|
+
parentParaId,
|
|
13036
|
+
threadRootCommentId,
|
|
13037
|
+
threadCommentIds: threadCommentIds.length ? threadCommentIds : [threadRootCommentId]
|
|
12810
13038
|
};
|
|
12811
13039
|
}
|
|
12812
13040
|
|
|
@@ -12871,19 +13099,19 @@ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
|
12871
13099
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
12872
13100
|
const doc = parseOoxml(ooxmlString);
|
|
12873
13101
|
if (!doc) return ooxmlString;
|
|
12874
|
-
const
|
|
12875
|
-
const runs = doc.getElementsByTagNameNS(
|
|
12876
|
-
const insertions = doc.getElementsByTagNameNS(
|
|
13102
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
13103
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
13104
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
12877
13105
|
const allRuns = [...Array.from(runs)];
|
|
12878
13106
|
for (const ins of insertions) {
|
|
12879
|
-
const insideRuns = ins.getElementsByTagNameNS(
|
|
13107
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
12880
13108
|
allRuns.push(...Array.from(insideRuns));
|
|
12881
13109
|
}
|
|
12882
13110
|
for (const run of allRuns) {
|
|
12883
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
13111
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
12884
13112
|
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
12885
13113
|
if (runText.includes(targetText) || runText === targetText) {
|
|
12886
|
-
const rPrElements = run.getElementsByTagNameNS(
|
|
13114
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
12887
13115
|
if (rPrElements.length > 0) {
|
|
12888
13116
|
const rPr = rPrElements[0];
|
|
12889
13117
|
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
@@ -12918,7 +13146,7 @@ var HIGHLIGHT_COLOR_MAP = {
|
|
|
12918
13146
|
"white": "white"
|
|
12919
13147
|
};
|
|
12920
13148
|
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
12921
|
-
const
|
|
13149
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
12922
13150
|
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
12923
13151
|
const generateRedlines = options?.generateRedlines ?? false;
|
|
12924
13152
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -12937,10 +13165,10 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
12937
13165
|
}
|
|
12938
13166
|
});
|
|
12939
13167
|
}
|
|
12940
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(
|
|
13168
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
12941
13169
|
Array.from(existingHighlight).forEach(removeNode2);
|
|
12942
13170
|
const highlightEl = createWordElement(doc, "w:highlight");
|
|
12943
|
-
highlightEl.setAttributeNS(
|
|
13171
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
12944
13172
|
rPrElement.appendChild(highlightEl);
|
|
12945
13173
|
if (generateRedlines && previousRPrState) {
|
|
12946
13174
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
@@ -12949,7 +13177,7 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
12949
13177
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
12950
13178
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
12951
13179
|
rPrChange.appendChild(previousRPrState);
|
|
12952
|
-
const existingChange = rPrElement.getElementsByTagNameNS(
|
|
13180
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
12953
13181
|
Array.from(existingChange).forEach(removeNode2);
|
|
12954
13182
|
rPrElement.appendChild(rPrChange);
|
|
12955
13183
|
}
|
|
@@ -12966,12 +13194,12 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
12966
13194
|
} else {
|
|
12967
13195
|
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
12968
13196
|
}
|
|
12969
|
-
const
|
|
13197
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
12970
13198
|
const getRunText = (run) => {
|
|
12971
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
13199
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
12972
13200
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
12973
13201
|
};
|
|
12974
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(
|
|
13202
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
12975
13203
|
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
12976
13204
|
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
12977
13205
|
const clonedRun = sourceRun.cloneNode(true);
|
|
@@ -12980,14 +13208,14 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
12980
13208
|
} else {
|
|
12981
13209
|
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
12982
13210
|
}
|
|
12983
|
-
const textNodes = clonedRun.getElementsByTagNameNS(
|
|
13211
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
12984
13212
|
Array.from(textNodes).forEach(removeNode2);
|
|
12985
13213
|
const newText = createWordElement(doc, "w:t");
|
|
12986
13214
|
newText.setAttribute("xml:space", "preserve");
|
|
12987
13215
|
newText.textContent = text2;
|
|
12988
13216
|
clonedRun.appendChild(newText);
|
|
12989
13217
|
if (shouldHighlight) {
|
|
12990
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(
|
|
13218
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
12991
13219
|
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
12992
13220
|
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
12993
13221
|
if (existingRPr) {
|
|
@@ -13036,20 +13264,68 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
13036
13264
|
return serializeOoxml(doc);
|
|
13037
13265
|
}
|
|
13038
13266
|
|
|
13267
|
+
// services/package-parts.js
|
|
13268
|
+
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
13269
|
+
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
13270
|
+
var PART_SPECS = Object.freeze([
|
|
13271
|
+
{ id: "document", path: "word/document.xml", pathPattern: null, contentType: `${WORD_ML}.document.main+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/officeDocument`, relTarget: null, verified: true },
|
|
13272
|
+
{ id: "numbering", path: "word/numbering.xml", pathPattern: null, contentType: `${WORD_ML}.numbering+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/numbering`, relTarget: "numbering.xml", verified: false },
|
|
13273
|
+
{ id: "comments", path: "word/comments.xml", pathPattern: null, contentType: `${WORD_ML}.comments+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/comments`, relTarget: "comments.xml", verified: true },
|
|
13274
|
+
{
|
|
13275
|
+
id: "commentsExtended",
|
|
13276
|
+
path: "word/commentsExtended.xml",
|
|
13277
|
+
pathPattern: null,
|
|
13278
|
+
contentType: `${WORD_ML}.commentsExtended+xml`,
|
|
13279
|
+
// Written by this package through 0.8.0; Word does not recognize it and offers to repair the file.
|
|
13280
|
+
legacyContentTypes: ["application/vnd.ms-word.commentsExtended+xml"],
|
|
13281
|
+
relType: "http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
|
|
13282
|
+
relTarget: "commentsExtended.xml",
|
|
13283
|
+
verified: true
|
|
13284
|
+
},
|
|
13285
|
+
{ 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 },
|
|
13286
|
+
{ 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 },
|
|
13287
|
+
// Not yet produced by any Word fixture (Word only writes people.xml for signed-in authors): unverified.
|
|
13288
|
+
{ 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 },
|
|
13289
|
+
{ 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 },
|
|
13290
|
+
{ 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 }
|
|
13291
|
+
].map((spec) => Object.freeze(spec)));
|
|
13292
|
+
var BY_ID = new Map(PART_SPECS.map((spec) => [spec.id, spec]));
|
|
13293
|
+
function getPartSpec(id) {
|
|
13294
|
+
const spec = BY_ID.get(id);
|
|
13295
|
+
if (!spec) throw new Error(`Unknown package part: ${id}`);
|
|
13296
|
+
return spec;
|
|
13297
|
+
}
|
|
13298
|
+
function toEntryName(partName) {
|
|
13299
|
+
return String(partName || "").replace(/^\/+/, "");
|
|
13300
|
+
}
|
|
13301
|
+
function findPartSpecByPartName(partName) {
|
|
13302
|
+
const name = toEntryName(partName);
|
|
13303
|
+
const lower = name.toLowerCase();
|
|
13304
|
+
return PART_SPECS.find((spec) => spec.path ? spec.path.toLowerCase() === lower : spec.pathPattern?.test(name)) || null;
|
|
13305
|
+
}
|
|
13306
|
+
function correctedContentTypeFor(partName, actualContentType) {
|
|
13307
|
+
const spec = findPartSpecByPartName(partName);
|
|
13308
|
+
if (!spec || !spec.legacyContentTypes.includes(actualContentType)) return null;
|
|
13309
|
+
return spec.contentType;
|
|
13310
|
+
}
|
|
13311
|
+
|
|
13039
13312
|
// services/standalone-docx-plumbing.js
|
|
13040
13313
|
var NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
13041
13314
|
var NS_CT = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
13042
13315
|
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
|
|
13316
|
+
var NUMBERING = getPartSpec("numbering");
|
|
13317
|
+
var COMMENTS = getPartSpec("comments");
|
|
13318
|
+
var COMMENTS_EXTENDED = getPartSpec("commentsExtended");
|
|
13319
|
+
var NUMBERING_REL_TYPE = NUMBERING.relType;
|
|
13320
|
+
var NUMBERING_CONTENT_TYPE = NUMBERING.contentType;
|
|
13321
|
+
var COMMENTS_REL_TYPE = COMMENTS.relType;
|
|
13322
|
+
var COMMENTS_CONTENT_TYPE = COMMENTS.contentType;
|
|
13323
|
+
var COMMENTS_EXTENDED_REL_TYPE = COMMENTS_EXTENDED.relType;
|
|
13324
|
+
var COMMENTS_EXTENDED_CONTENT_TYPE = COMMENTS_EXTENDED.contentType;
|
|
13325
|
+
var DOCUMENT_PATH = getPartSpec("document").path;
|
|
13326
|
+
var NUMBERING_PATH = NUMBERING.path;
|
|
13327
|
+
var COMMENTS_PATH = COMMENTS.path;
|
|
13328
|
+
var COMMENTS_EXTENDED_PATH = COMMENTS_EXTENDED.path;
|
|
13053
13329
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
13054
13330
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
13055
13331
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
@@ -13337,6 +13613,22 @@ async function ensureCommentsExtendedArtifactsInZip(zip, commentsExtendedXml, op
|
|
|
13337
13613
|
}
|
|
13338
13614
|
}
|
|
13339
13615
|
}
|
|
13616
|
+
async function repairKnownContentTypes(zip) {
|
|
13617
|
+
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
13618
|
+
if (!ctText) return [];
|
|
13619
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
13620
|
+
const repairs = [];
|
|
13621
|
+
for (const override of Array.from(ctDoc.getElementsByTagNameNS("*", "Override"))) {
|
|
13622
|
+
const partName = override.getAttribute("PartName") || "";
|
|
13623
|
+
const actual = override.getAttribute("ContentType") || "";
|
|
13624
|
+
const expected = correctedContentTypeFor(partName, actual);
|
|
13625
|
+
if (!expected) continue;
|
|
13626
|
+
override.setAttribute("ContentType", expected);
|
|
13627
|
+
repairs.push({ partName, from: actual, to: expected });
|
|
13628
|
+
}
|
|
13629
|
+
if (repairs.length > 0) zip.file(CONTENT_TYPES_PATH, createSerializer().serializeToString(ctDoc));
|
|
13630
|
+
return repairs;
|
|
13631
|
+
}
|
|
13340
13632
|
async function validateDocxPackage(zip) {
|
|
13341
13633
|
const documentXml = await readZipText(zip, DOCUMENT_PATH);
|
|
13342
13634
|
if (!documentXml) {
|
|
@@ -13418,9 +13710,8 @@ async function validateDocxPackage(zip) {
|
|
|
13418
13710
|
const paraIdToCommentId = /* @__PURE__ */ new Map();
|
|
13419
13711
|
for (const comment of Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
13420
13712
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
13421
|
-
const
|
|
13422
|
-
|
|
13423
|
-
if (paraId) paraIdToCommentId.set(paraId.toUpperCase(), id);
|
|
13713
|
+
const paraId = commentParaId(comment);
|
|
13714
|
+
if (paraId) paraIdToCommentId.set(paraId, id);
|
|
13424
13715
|
}
|
|
13425
13716
|
const knownParaIds = new Set(paraIdToCommentId.keys());
|
|
13426
13717
|
const extendedParaIds = /* @__PURE__ */ new Set();
|
|
@@ -13481,10 +13772,39 @@ async function validateDocxPackage(zip) {
|
|
|
13481
13772
|
throw new Error("Validation failed: comments rel missing");
|
|
13482
13773
|
}
|
|
13483
13774
|
}
|
|
13775
|
+
{
|
|
13776
|
+
const relationshipById = /* @__PURE__ */ new Map();
|
|
13777
|
+
for (const rel of Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
13778
|
+
relationshipById.set(rel.getAttribute("Id") || "", { type: rel.getAttribute("Type") || "", target: rel.getAttribute("Target") || "" });
|
|
13779
|
+
}
|
|
13780
|
+
const headerSpec = getPartSpec("header");
|
|
13781
|
+
const footerSpec = getPartSpec("footer");
|
|
13782
|
+
const overrideTypeByPart = new Map(Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).map((override) => [(override.getAttribute("PartName") || "").toLowerCase(), override.getAttribute("ContentType") || ""]));
|
|
13783
|
+
for (const reference of [
|
|
13784
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "headerReference")),
|
|
13785
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "footerReference"))
|
|
13786
|
+
]) {
|
|
13787
|
+
const relId = reference.getAttributeNS?.("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") || reference.getAttribute("r:id") || "";
|
|
13788
|
+
const spec = reference.localName === "headerReference" ? headerSpec : footerSpec;
|
|
13789
|
+
const relationship = relationshipById.get(relId);
|
|
13790
|
+
if (!relationship) throw new Error(`Validation failed: ${reference.localName} ${relId || "(no r:id)"} has no document relationship`);
|
|
13791
|
+
if (relationship.type !== spec.relType) throw new Error(`Validation failed: ${reference.localName} ${relId} points at a relationship of the wrong type (${relationship.type})`);
|
|
13792
|
+
const partName = relationship.target.startsWith("/") ? relationship.target : `/word/${relationship.target}`;
|
|
13793
|
+
const partXml = await readZipText(zip, partName.slice(1));
|
|
13794
|
+
if (!partXml) throw new Error(`Validation failed: ${reference.localName} ${relId} targets a missing part: ${partName}`);
|
|
13795
|
+
parseXmlStrictStandalone(partXml, partName.slice(1));
|
|
13796
|
+
const contentType = overrideTypeByPart.get(partName.toLowerCase());
|
|
13797
|
+
if (contentType !== spec.contentType) throw new Error(`Validation failed: ${partName} content type is ${contentType ? `'${contentType}'` : "missing"}; Word expects '${spec.contentType}'`);
|
|
13798
|
+
}
|
|
13799
|
+
}
|
|
13484
13800
|
if (commentsExtendedXml) {
|
|
13485
|
-
const
|
|
13486
|
-
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
13801
|
+
const extendedOverride = Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).find(
|
|
13802
|
+
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
13487
13803
|
);
|
|
13804
|
+
if (extendedOverride && extendedOverride.getAttribute("ContentType") !== COMMENTS_EXTENDED_CONTENT_TYPE) {
|
|
13805
|
+
throw new Error(`Validation failed: commentsExtended CT override has wrong content type '${extendedOverride.getAttribute("ContentType")}' (Word expects '${COMMENTS_EXTENDED_CONTENT_TYPE}')`);
|
|
13806
|
+
}
|
|
13807
|
+
const hasContentType = !!extendedOverride;
|
|
13488
13808
|
const hasRelationship = Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship")).some(
|
|
13489
13809
|
(rel) => (rel.getAttribute("Type") || "") === COMMENTS_EXTENDED_REL_TYPE
|
|
13490
13810
|
);
|
|
@@ -13848,6 +14168,156 @@ function reconcileReceiptsAgainstOutput(parts, receipts) {
|
|
|
13848
14168
|
return { valid: true };
|
|
13849
14169
|
}
|
|
13850
14170
|
|
|
14171
|
+
// services/headers-footers.js
|
|
14172
|
+
var NS_W8 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
14173
|
+
var NS_R2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
14174
|
+
var REVISION_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo", "rPrChange", "pPrChange", "sectPrChange", "tblPrChange", "tblGridChange", "tcPrChange", "trPrChange", "numberingChange"]);
|
|
14175
|
+
var REVISION_CONTENT_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
14176
|
+
var attrOf2 = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
14177
|
+
function resolveTarget(target) {
|
|
14178
|
+
const clean = String(target || "");
|
|
14179
|
+
if (clean.startsWith("/")) return clean.replace(/^\/+/, "");
|
|
14180
|
+
const parts = ["word"];
|
|
14181
|
+
for (const segment of clean.split("/")) {
|
|
14182
|
+
if (segment === "..") parts.pop();
|
|
14183
|
+
else if (segment && segment !== ".") parts.push(segment);
|
|
14184
|
+
}
|
|
14185
|
+
return parts.join("/");
|
|
14186
|
+
}
|
|
14187
|
+
function discoverHeaderFooterParts({ documentXml, relsXml = null, settingsXml = null }) {
|
|
14188
|
+
if (!documentXml || !relsXml) return [];
|
|
14189
|
+
const documentParsed = parseOoxmlSafe(documentXml, "application/xml");
|
|
14190
|
+
const relsParsed = parseOoxmlSafe(relsXml, "application/xml");
|
|
14191
|
+
if (!documentParsed.doc || documentParsed.error || !relsParsed.doc || relsParsed.error) return [];
|
|
14192
|
+
const targetByRelId = /* @__PURE__ */ new Map();
|
|
14193
|
+
for (const rel of Array.from(relsParsed.doc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
14194
|
+
targetByRelId.set(rel.getAttribute("Id") || "", rel.getAttribute("Target") || "");
|
|
14195
|
+
}
|
|
14196
|
+
const evenAndOdd = !!settingsXml && /<w:evenAndOddHeaders\b(?![^>]*w:val="(?:0|false|off)")/.test(settingsXml);
|
|
14197
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
14198
|
+
const inForce = /* @__PURE__ */ new Map();
|
|
14199
|
+
const sectPrs = Array.from(documentParsed.doc.getElementsByTagNameNS(NS_W8, "sectPr"));
|
|
14200
|
+
sectPrs.forEach((sectPr, sectionIndex) => {
|
|
14201
|
+
const titlePg = Array.from(sectPr.childNodes || []).some((child) => child.localName === "titlePg" && !/^(0|false|off)$/i.test(attrOf2(child, "w:val", "val")));
|
|
14202
|
+
for (const ref of Array.from(sectPr.childNodes || [])) {
|
|
14203
|
+
if (ref.nodeType !== 1 || ref.localName !== "headerReference" && ref.localName !== "footerReference") continue;
|
|
14204
|
+
const relId = ref.getAttributeNS?.(NS_R2, "id") || ref.getAttribute("r:id") || "";
|
|
14205
|
+
const target = targetByRelId.get(relId);
|
|
14206
|
+
if (!target) continue;
|
|
14207
|
+
const path = resolveTarget(target);
|
|
14208
|
+
const kind = ref.localName === "headerReference" ? "header" : "footer";
|
|
14209
|
+
const type = attrOf2(ref, "w:type", "type") || "default";
|
|
14210
|
+
const entry = byPath.get(path) || { path, kind, type, sections: [], applies: [], titlePgSections: 0 };
|
|
14211
|
+
if (!entry.sections.includes(sectionIndex)) entry.sections.push(sectionIndex);
|
|
14212
|
+
if (titlePg) entry.titlePgSections += 1;
|
|
14213
|
+
byPath.set(path, entry);
|
|
14214
|
+
inForce.set(`${kind}:${type}`, path);
|
|
14215
|
+
}
|
|
14216
|
+
for (const path of inForce.values()) {
|
|
14217
|
+
const entry = byPath.get(path);
|
|
14218
|
+
if (entry && !entry.applies.includes(sectionIndex)) entry.applies.push(sectionIndex);
|
|
14219
|
+
}
|
|
14220
|
+
});
|
|
14221
|
+
return Array.from(byPath.values()).map((entry) => ({
|
|
14222
|
+
path: entry.path,
|
|
14223
|
+
kind: entry.kind,
|
|
14224
|
+
type: entry.type,
|
|
14225
|
+
sections: entry.sections,
|
|
14226
|
+
appliesToSections: entry.applies,
|
|
14227
|
+
sharedBySections: entry.applies.length > 1,
|
|
14228
|
+
// first-page parts only show with w:titlePg; even parts only with w:evenAndOddHeaders in settings.xml.
|
|
14229
|
+
active: entry.type === "first" ? entry.titlePgSections > 0 : entry.type === "even" ? evenAndOdd : true
|
|
14230
|
+
}));
|
|
14231
|
+
}
|
|
14232
|
+
function resolvePartSelector(parts, selector) {
|
|
14233
|
+
const describe = (part) => ({ path: part.path, kind: part.kind, type: part.type, sections: part.sections, appliesToSections: part.appliesToSections });
|
|
14234
|
+
if (typeof selector === "string") {
|
|
14235
|
+
const wanted = selector.replace(/^\/+/, "").toLowerCase();
|
|
14236
|
+
const match = parts.find((part) => part.path.toLowerCase() === wanted || part.path.toLowerCase() === `word/${wanted}`);
|
|
14237
|
+
if (match) return { part: match };
|
|
14238
|
+
return { error: { code: "PART_NOT_FOUND", message: `No header/footer part '${selector}' is referenced by the document.`, candidates: parts.map(describe) } };
|
|
14239
|
+
}
|
|
14240
|
+
if (!selector || typeof selector !== "object" || selector.kind !== "header" && selector.kind !== "footer") {
|
|
14241
|
+
return { error: { code: "INVALID_OPERATION", message: 'part must be a part path or { kind: "header"|"footer", type?: "default"|"first"|"even", section?: number }.' } };
|
|
14242
|
+
}
|
|
14243
|
+
const type = selector.type || "default";
|
|
14244
|
+
if (!["default", "first", "even"].includes(type)) {
|
|
14245
|
+
return { error: { code: "INVALID_OPERATION", message: `part.type must be default, first or even (got '${type}').` } };
|
|
14246
|
+
}
|
|
14247
|
+
let matches = parts.filter((part) => part.kind === selector.kind && part.type === type);
|
|
14248
|
+
if (Number.isInteger(selector.section)) matches = matches.filter((part) => part.appliesToSections.includes(selector.section));
|
|
14249
|
+
if (matches.length === 1) return { part: matches[0] };
|
|
14250
|
+
if (matches.length === 0) {
|
|
14251
|
+
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) } };
|
|
14252
|
+
}
|
|
14253
|
+
return { error: { code: "PART_AMBIGUOUS", message: `Several ${type} ${selector.kind} parts exist; add "section" or use the part path.`, candidates: matches.map(describe) } };
|
|
14254
|
+
}
|
|
14255
|
+
function isHeaderFooterPath(path) {
|
|
14256
|
+
const spec = findPartSpecByPartName(path);
|
|
14257
|
+
return spec?.id === "header" || spec?.id === "footer";
|
|
14258
|
+
}
|
|
14259
|
+
function countRevisionsInsideFields(xml) {
|
|
14260
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
14261
|
+
if (!parsed.doc || parsed.error) return 0;
|
|
14262
|
+
let depth = 0;
|
|
14263
|
+
let count = 0;
|
|
14264
|
+
const visit = (node) => {
|
|
14265
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
14266
|
+
if (child.nodeType !== 1) continue;
|
|
14267
|
+
const name = child.localName;
|
|
14268
|
+
if (name === "fldChar") {
|
|
14269
|
+
const type = attrOf2(child, "w:fldCharType", "fldCharType");
|
|
14270
|
+
if (type === "begin") depth += 1;
|
|
14271
|
+
else if (type === "end") depth = Math.max(0, depth - 1);
|
|
14272
|
+
continue;
|
|
14273
|
+
}
|
|
14274
|
+
if (REVISION_CONTENT_ELEMENTS.has(name) && depth > 0) count += 1;
|
|
14275
|
+
if (name === "fldSimple") {
|
|
14276
|
+
depth += 1;
|
|
14277
|
+
visit(child);
|
|
14278
|
+
depth -= 1;
|
|
14279
|
+
continue;
|
|
14280
|
+
}
|
|
14281
|
+
visit(child);
|
|
14282
|
+
}
|
|
14283
|
+
};
|
|
14284
|
+
visit(parsed.doc.documentElement);
|
|
14285
|
+
return count;
|
|
14286
|
+
}
|
|
14287
|
+
function collectRevisionIds(xml) {
|
|
14288
|
+
const ids = /* @__PURE__ */ new Set();
|
|
14289
|
+
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]);
|
|
14290
|
+
return ids;
|
|
14291
|
+
}
|
|
14292
|
+
function renumberCollidingRevisionIds(xml, taken) {
|
|
14293
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
14294
|
+
if (!parsed.doc || parsed.error) return { xml, renumbered: 0, idMap: /* @__PURE__ */ new Map() };
|
|
14295
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
14296
|
+
let next = 1 + Math.max(0, ...Array.from(taken, (id) => Number(id)).filter(Number.isFinite));
|
|
14297
|
+
const used = new Set(taken);
|
|
14298
|
+
let renumbered = 0;
|
|
14299
|
+
const visit = (node) => {
|
|
14300
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
14301
|
+
if (child.nodeType !== 1) continue;
|
|
14302
|
+
if (REVISION_ELEMENTS.has(child.localName)) {
|
|
14303
|
+
const id = attrOf2(child, "w:id", "id");
|
|
14304
|
+
if (id && used.has(id)) {
|
|
14305
|
+
while (used.has(String(next))) next += 1;
|
|
14306
|
+
child.setAttributeNS(NS_W8, "w:id", String(next));
|
|
14307
|
+
idMap.set(id, String(next));
|
|
14308
|
+
used.add(String(next));
|
|
14309
|
+
renumbered += 1;
|
|
14310
|
+
} else if (id) {
|
|
14311
|
+
used.add(id);
|
|
14312
|
+
}
|
|
14313
|
+
}
|
|
14314
|
+
visit(child);
|
|
14315
|
+
}
|
|
14316
|
+
};
|
|
14317
|
+
visit(parsed.doc.documentElement);
|
|
14318
|
+
return renumbered > 0 ? { xml: createSerializer().serializeToString(parsed.doc), renumbered, idMap } : { xml, renumbered: 0, idMap };
|
|
14319
|
+
}
|
|
14320
|
+
|
|
13851
14321
|
// services/error-recovery.js
|
|
13852
14322
|
var ERROR_RECOVERY_VERSION = 1;
|
|
13853
14323
|
var RULES = Object.freeze({
|
|
@@ -13885,6 +14355,12 @@ var RULES = Object.freeze({
|
|
|
13885
14355
|
STRUCTURED_CONTENT_INVALID: ["request", "request_fixable", "change_request"],
|
|
13886
14356
|
UNSUPPORTED_INSERTION_AFFINITY: ["request", "request_fixable", "change_request"],
|
|
13887
14357
|
EXISTING_REVISIONS: ["target-safety", "policy_choice_required", "set_option"],
|
|
14358
|
+
PART_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
14359
|
+
PART_AMBIGUOUS: ["target-resolution", "candidate_selection_required", "choose_candidate"],
|
|
14360
|
+
COMMENT_IN_HEADER_FOOTER: ["request", "request_fixable", "change_request"],
|
|
14361
|
+
FIELD_EDIT_REFUSED: ["target-safety", "request_fixable", "change_request"],
|
|
14362
|
+
COMMENT_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
14363
|
+
PARENT_ANCHOR_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
13888
14364
|
COMMENTED_CONTENT_MERGE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
13889
14365
|
COMMENTED_CONTENT_DELETE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
13890
14366
|
OVERLAPPING_SOURCE_TARGETS: ["batch-compilation", "source_conflict", "consolidate_operations"],
|
|
@@ -14337,6 +14813,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
14337
14813
|
"delete",
|
|
14338
14814
|
"comment",
|
|
14339
14815
|
"comment_reply",
|
|
14816
|
+
"comment_resolve",
|
|
14340
14817
|
"highlight"
|
|
14341
14818
|
]);
|
|
14342
14819
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
@@ -14355,11 +14832,14 @@ function isRecord(value) {
|
|
|
14355
14832
|
function nonEmptyString(value) {
|
|
14356
14833
|
return typeof value === "string" && value.trim().length > 0;
|
|
14357
14834
|
}
|
|
14835
|
+
function isCommentThreadOperationKind(kind) {
|
|
14836
|
+
return kind === "comment_reply" || kind === "comment_resolve";
|
|
14837
|
+
}
|
|
14358
14838
|
function getCanonicalOperationType(operation) {
|
|
14359
14839
|
const type = operation?.type;
|
|
14360
14840
|
if (type === "insert" && operation?.target?.revisionView === "rejected") return "rejected-insert";
|
|
14361
14841
|
if (type === "restore") return "restore";
|
|
14362
|
-
if (type === "comment" || type === "comment_reply" || type === "highlight") return type;
|
|
14842
|
+
if (type === "comment" || type === "comment_reply" || type === "comment_resolve" || type === "highlight") return type;
|
|
14363
14843
|
if (type === "paragraph-format") return "paragraph-format";
|
|
14364
14844
|
if (type === "character-format" || type === "format" && (operation?.textToFormat != null || operation?.properties != null)) return "format";
|
|
14365
14845
|
return "redline";
|
|
@@ -14479,7 +14959,7 @@ function validateDocumentOperation(operation) {
|
|
|
14479
14959
|
};
|
|
14480
14960
|
}
|
|
14481
14961
|
const target = normalized.targetDescriptor;
|
|
14482
|
-
if (normalized.operationKind
|
|
14962
|
+
if (!isCommentThreadOperationKind(normalized.operationKind) && !nonEmptyString(target.text) && target.index == null && !target.paragraphId && !target.fingerprint && !target.captureRef) {
|
|
14483
14963
|
return {
|
|
14484
14964
|
valid: false,
|
|
14485
14965
|
error: {
|
|
@@ -14706,6 +15186,14 @@ function validateDocumentOperation(operation) {
|
|
|
14706
15186
|
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment reply operations require a non-empty "commentContent" field.' } };
|
|
14707
15187
|
}
|
|
14708
15188
|
}
|
|
15189
|
+
if (normalized.operationKind === "comment_resolve") {
|
|
15190
|
+
if (!nonEmptyString(String(normalized.commentId ?? ""))) {
|
|
15191
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve operations require a "commentId".' } };
|
|
15192
|
+
}
|
|
15193
|
+
if (normalized.resolved != null && typeof normalized.resolved !== "boolean") {
|
|
15194
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve "resolved" must be a boolean when provided.' } };
|
|
15195
|
+
}
|
|
15196
|
+
}
|
|
14709
15197
|
if (normalized.operationKind === "highlight") {
|
|
14710
15198
|
if (!nonEmptyString(normalized.textToHighlight)) {
|
|
14711
15199
|
return {
|
|
@@ -15185,7 +15673,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
15185
15673
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
15186
15674
|
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
15187
15675
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
15188
|
-
if (validation.valid && operation.operationKind !== "comment_reply" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
15676
|
+
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
15189
15677
|
try {
|
|
15190
15678
|
const start = resolveDescriptor(
|
|
15191
15679
|
xmlDoc,
|
|
@@ -15686,12 +16174,12 @@ function resolveTargetFromCapture(xmlDoc, session, targetDescriptor, _opType = "
|
|
|
15686
16174
|
}
|
|
15687
16175
|
|
|
15688
16176
|
// services/document-operation-mutations.js
|
|
15689
|
-
var
|
|
16177
|
+
var NS_W9 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
15690
16178
|
var NS_W142 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
15691
16179
|
function getCommentIdsInElement(element) {
|
|
15692
16180
|
const ids = /* @__PURE__ */ new Set();
|
|
15693
16181
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
15694
|
-
for (const node of Array.from(element?.getElementsByTagNameNS?.(
|
|
16182
|
+
for (const node of Array.from(element?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
15695
16183
|
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
15696
16184
|
if (id !== "") ids.add(id);
|
|
15697
16185
|
}
|
|
@@ -15905,7 +16393,7 @@ function normalizeBodySectionOrder(xmlDoc) {
|
|
|
15905
16393
|
normalizeBodySectionOrderStandalone(xmlDoc);
|
|
15906
16394
|
}
|
|
15907
16395
|
function removeProofErrNodes(paragraph) {
|
|
15908
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
16396
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "proofErr") || [])) {
|
|
15909
16397
|
node.parentNode?.removeChild(node);
|
|
15910
16398
|
}
|
|
15911
16399
|
}
|
|
@@ -15916,12 +16404,12 @@ function preprocessRedlineTargetParagraph(targetParagraph) {
|
|
|
15916
16404
|
function getDirectWordChild4(element, localName2) {
|
|
15917
16405
|
if (!element) return null;
|
|
15918
16406
|
return Array.from(element.childNodes || []).find(
|
|
15919
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
16407
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === localName2
|
|
15920
16408
|
) || null;
|
|
15921
16409
|
}
|
|
15922
16410
|
function computeTableIndexInDocument(xmlDoc, tableElement) {
|
|
15923
16411
|
if (!xmlDoc || !tableElement) return null;
|
|
15924
|
-
const tables = Array.from(xmlDoc.getElementsByTagNameNS(
|
|
16412
|
+
const tables = Array.from(xmlDoc.getElementsByTagNameNS(NS_W9, "tbl"));
|
|
15925
16413
|
const idx = tables.indexOf(tableElement);
|
|
15926
16414
|
return idx >= 0 ? idx + 1 : null;
|
|
15927
16415
|
}
|
|
@@ -15971,7 +16459,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
15971
16459
|
markParagraphMarkInserted(xmlDoc, paragraph, author);
|
|
15972
16460
|
}
|
|
15973
16461
|
const run = createWordElement(xmlDoc, "w:r");
|
|
15974
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
16462
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
15975
16463
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
15976
16464
|
if (anchorRunPr) {
|
|
15977
16465
|
run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
@@ -15996,7 +16484,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
15996
16484
|
}
|
|
15997
16485
|
function serializeParagraphRangeAsDocument(paragraphs, serializer) {
|
|
15998
16486
|
const paragraphXml = (paragraphs || []).map((paragraph) => serializer.serializeToString(paragraph)).join("");
|
|
15999
|
-
return `<w:document xmlns:w="${
|
|
16487
|
+
return `<w:document xmlns:w="${NS_W9}"><w:body>${paragraphXml}</w:body></w:document>`;
|
|
16000
16488
|
}
|
|
16001
16489
|
function applyExplicitRangeListInsertions({
|
|
16002
16490
|
xmlDoc,
|
|
@@ -16065,7 +16553,7 @@ function buildEmptyParagraphTemplateFromAnchor(xmlDoc, anchorParagraph) {
|
|
|
16065
16553
|
const anchorPPr = getDirectWordChild4(anchorParagraph, "pPr");
|
|
16066
16554
|
if (anchorPPr) paragraph.appendChild(clonePropertiesWithoutRevisionHistory(anchorPPr));
|
|
16067
16555
|
const run = createWordElement(xmlDoc, "w:r");
|
|
16068
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
16556
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
16069
16557
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
16070
16558
|
if (anchorRunPr) run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
16071
16559
|
const textNode = createWordElement(xmlDoc, "w:t");
|
|
@@ -16088,7 +16576,7 @@ function wrapParagraphContentInInsertion(xmlDoc, paragraph, revisionMetadata, au
|
|
|
16088
16576
|
ins.setAttribute("w:author", metadata.author);
|
|
16089
16577
|
ins.setAttribute("w:date", metadata.date);
|
|
16090
16578
|
for (const child of Array.from(paragraph.childNodes || [])) {
|
|
16091
|
-
if (child?.nodeType === 1 && child.namespaceURI ===
|
|
16579
|
+
if (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
16092
16580
|
ins.appendChild(child.cloneNode(true));
|
|
16093
16581
|
}
|
|
16094
16582
|
wrappedParagraph.appendChild(ins);
|
|
@@ -16112,7 +16600,7 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
16112
16600
|
if (typeof markdownResult?.oxml === "string") {
|
|
16113
16601
|
const extracted = extractReplacementNodes(markdownResult.oxml);
|
|
16114
16602
|
sourceParagraph = (extracted.replacementNodes || []).find(
|
|
16115
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
16603
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p"
|
|
16116
16604
|
) || null;
|
|
16117
16605
|
}
|
|
16118
16606
|
if (!sourceParagraph) {
|
|
@@ -16144,7 +16632,7 @@ function collectNumberingIdsFromNodes(nodes) {
|
|
|
16144
16632
|
}
|
|
16145
16633
|
function directDeletionCarrierText(carrier) {
|
|
16146
16634
|
return Array.from(carrier?.childNodes || []).reduce((text2, child) => {
|
|
16147
|
-
return text2 + (child?.nodeType === 1 && child.namespaceURI ===
|
|
16635
|
+
return text2 + (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r" ? getRunContentPieces(child).map((piece) => piece.text).join("") : "");
|
|
16148
16636
|
}, "");
|
|
16149
16637
|
}
|
|
16150
16638
|
function findOccurrenceOffset(text2, needle, occurrence) {
|
|
@@ -16186,9 +16674,9 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
16186
16674
|
}
|
|
16187
16675
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
16188
16676
|
if (child.nodeType !== 1) continue;
|
|
16189
|
-
if (!(child.namespaceURI ===
|
|
16677
|
+
if (!(child.namespaceURI === NS_W9 && child.localName === "r")) return true;
|
|
16190
16678
|
for (const runChild of Array.from(child.childNodes || [])) {
|
|
16191
|
-
if (runChild.nodeType !== 1 || runChild.namespaceURI !==
|
|
16679
|
+
if (runChild.nodeType !== 1 || runChild.namespaceURI !== NS_W9) continue;
|
|
16192
16680
|
if (!["rPr", "delText", "t", "tab", "br", "cr", "noBreakHyphen", "softHyphen"].includes(runChild.localName)) return true;
|
|
16193
16681
|
}
|
|
16194
16682
|
}
|
|
@@ -16197,7 +16685,7 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
16197
16685
|
function insertedRunPropertiesAtDeletionOffset(carrier, localOffset) {
|
|
16198
16686
|
let offset = 0;
|
|
16199
16687
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
16200
|
-
if (!(child?.nodeType === 1 && child.namespaceURI ===
|
|
16688
|
+
if (!(child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r")) continue;
|
|
16201
16689
|
const length = getRunTextLength(getRunContentPieces(child));
|
|
16202
16690
|
if (localOffset <= offset + length) {
|
|
16203
16691
|
return clonePropertiesWithoutRevisionHistory(getDirectWordChild4(child, "rPr"));
|
|
@@ -16227,7 +16715,7 @@ function normalizedAuthor2(author) {
|
|
|
16227
16715
|
function directWordParagraphSibling(paragraph, direction = "nextSibling") {
|
|
16228
16716
|
let cursor = paragraph?.[direction] || null;
|
|
16229
16717
|
while (cursor) {
|
|
16230
|
-
if (cursor.nodeType === 1 && cursor.namespaceURI ===
|
|
16718
|
+
if (cursor.nodeType === 1 && cursor.namespaceURI === NS_W9 && cursor.localName === "p") return cursor;
|
|
16231
16719
|
cursor = cursor[direction] || null;
|
|
16232
16720
|
}
|
|
16233
16721
|
return null;
|
|
@@ -16237,7 +16725,7 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
16237
16725
|
const pPr = createWordElement(xmlDoc, "w:pPr");
|
|
16238
16726
|
if (!sourcePPr) return pPr;
|
|
16239
16727
|
for (const child of Array.from(sourcePPr.childNodes || [])) {
|
|
16240
|
-
if (child.nodeType !== 1 || child.namespaceURI !==
|
|
16728
|
+
if (child.nodeType !== 1 || child.namespaceURI !== NS_W9) continue;
|
|
16241
16729
|
if (!RESTORATION_PPR_ALLOWLIST.has(child.localName)) continue;
|
|
16242
16730
|
pPr.appendChild(xmlDoc.importNode(child, true));
|
|
16243
16731
|
}
|
|
@@ -16246,14 +16734,14 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
16246
16734
|
function collectRestorationAnchorWarnings(sourceParagraph) {
|
|
16247
16735
|
const warnings = [];
|
|
16248
16736
|
const bookmarkNames = /* @__PURE__ */ new Set();
|
|
16249
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
16737
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, "bookmarkStart") || [])) {
|
|
16250
16738
|
const name = wordAttribute4(node, "name") || "(unnamed)";
|
|
16251
16739
|
bookmarkNames.add(name);
|
|
16252
16740
|
}
|
|
16253
16741
|
for (const name of bookmarkNames) warnings.push(`RESTORATION_DROPPED_BOOKMARK:${name}`);
|
|
16254
16742
|
const commentIds = /* @__PURE__ */ new Set();
|
|
16255
16743
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
16256
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
16744
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
16257
16745
|
const id = wordAttribute4(node, "id");
|
|
16258
16746
|
if (id !== "") commentIds.add(id);
|
|
16259
16747
|
}
|
|
@@ -16270,24 +16758,24 @@ function removeRestorationAnchors(paragraph) {
|
|
|
16270
16758
|
"commentReference"
|
|
16271
16759
|
];
|
|
16272
16760
|
for (const localName2 of anchorNames) {
|
|
16273
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
16761
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
16274
16762
|
node.parentNode?.removeChild(node);
|
|
16275
16763
|
}
|
|
16276
16764
|
}
|
|
16277
|
-
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
16278
|
-
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI ===
|
|
16765
|
+
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "r") || [])) {
|
|
16766
|
+
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName !== "rPr");
|
|
16279
16767
|
if (!meaningful) run.parentNode?.removeChild(run);
|
|
16280
16768
|
}
|
|
16281
16769
|
}
|
|
16282
16770
|
function buildRejectedRestorationTemplate(xmlDoc, sourceParagraph, serializer) {
|
|
16283
16771
|
const rejected = rejectTrackedChangesInOoxml(serializer.serializeToString(sourceParagraph), { allAuthors: true });
|
|
16284
16772
|
const parsed = parseOoxmlSafe(rejected.oxml, "application/xml");
|
|
16285
|
-
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(
|
|
16773
|
+
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(NS_W9, "p")?.[0] || null;
|
|
16286
16774
|
if (!rejectedParagraph) return null;
|
|
16287
16775
|
const paragraph = createWordElement(xmlDoc, "w:p");
|
|
16288
16776
|
paragraph.appendChild(createSanitizedRestorationPPr(xmlDoc, sourceParagraph));
|
|
16289
16777
|
for (const child of Array.from(rejectedParagraph.childNodes || [])) {
|
|
16290
|
-
if (child.nodeType === 1 && child.namespaceURI ===
|
|
16778
|
+
if (child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
16291
16779
|
paragraph.appendChild(xmlDoc.importNode(child, true));
|
|
16292
16780
|
}
|
|
16293
16781
|
removeRestorationAnchors(paragraph);
|
|
@@ -16308,7 +16796,7 @@ async function editRestorationTemplate(xmlDoc, template, modifiedText, author, s
|
|
|
16308
16796
|
);
|
|
16309
16797
|
if (result?.status === "error" || typeof result?.oxml !== "string") return null;
|
|
16310
16798
|
const extracted = extractReplacementNodes(result.oxml);
|
|
16311
|
-
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
16799
|
+
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p");
|
|
16312
16800
|
return paragraph ? xmlDoc.importNode(paragraph, true) : null;
|
|
16313
16801
|
}
|
|
16314
16802
|
function allocateFreshParagraphId(xmlDoc, operationSession) {
|
|
@@ -16479,7 +16967,7 @@ async function insertIntoRejectedDeletedText(documentXml, targetText, anchor, mo
|
|
|
16479
16967
|
}
|
|
16480
16968
|
const insertionOffset = anchorStart + anchor.offset;
|
|
16481
16969
|
const carriers = Array.from(paragraph.childNodes || []).filter(
|
|
16482
|
-
(node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
16970
|
+
(node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "del"
|
|
16483
16971
|
);
|
|
16484
16972
|
if (carriers.some((carrier) => hasUnsupportedDeletionSplitMarkup(paragraph, carrier))) {
|
|
16485
16973
|
return {
|
|
@@ -17136,7 +17624,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17136
17624
|
};
|
|
17137
17625
|
}
|
|
17138
17626
|
if (!allSame) {
|
|
17139
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
17627
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
17140
17628
|
if (hasMove) {
|
|
17141
17629
|
return {
|
|
17142
17630
|
documentXml,
|
|
@@ -17151,8 +17639,8 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17151
17639
|
}
|
|
17152
17640
|
}
|
|
17153
17641
|
} else {
|
|
17154
|
-
const hasDel = targetParagraph.getElementsByTagNameNS(
|
|
17155
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
17642
|
+
const hasDel = targetParagraph.getElementsByTagNameNS(NS_W9, "del").length > 0;
|
|
17643
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
17156
17644
|
if (hasDel) {
|
|
17157
17645
|
return {
|
|
17158
17646
|
documentXml,
|
|
@@ -17671,7 +18159,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17671
18159
|
const targetParagraph = resolved.paragraph;
|
|
17672
18160
|
const collectVisibleRuns = () => {
|
|
17673
18161
|
const runEntries2 = [];
|
|
17674
|
-
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(
|
|
18162
|
+
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(NS_W9, "r"));
|
|
17675
18163
|
for (const run of allRuns) {
|
|
17676
18164
|
let parent = run.parentNode;
|
|
17677
18165
|
let isDel = false;
|
|
@@ -17763,7 +18251,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17763
18251
|
const targetRuns = finalRuns.filter((span) => span.start >= matchStart && span.end <= matchEnd);
|
|
17764
18252
|
let anyChanged = false;
|
|
17765
18253
|
for (const entry of targetRuns) {
|
|
17766
|
-
const rPr = entry.run.getElementsByTagNameNS(
|
|
18254
|
+
const rPr = entry.run.getElementsByTagNameNS(NS_W9, "rPr")[0] || null;
|
|
17767
18255
|
if (checkRunPropertiesChanged(rPr, properties)) {
|
|
17768
18256
|
anyChanged = true;
|
|
17769
18257
|
break;
|
|
@@ -17774,7 +18262,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17774
18262
|
}
|
|
17775
18263
|
for (const entry of targetRuns) {
|
|
17776
18264
|
const run = entry.run;
|
|
17777
|
-
let rPr = run.getElementsByTagNameNS(
|
|
18265
|
+
let rPr = run.getElementsByTagNameNS(NS_W9, "rPr")[0];
|
|
17778
18266
|
if (!rPr) {
|
|
17779
18267
|
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
17780
18268
|
run.insertBefore(rPr, run.firstChild);
|
|
@@ -17783,7 +18271,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
17783
18271
|
let insideSameAuthorIns = false;
|
|
17784
18272
|
while (insAncestor && insAncestor !== targetParagraph) {
|
|
17785
18273
|
if (insAncestor.nodeType === 1 && (insAncestor.localName === "ins" || insAncestor.nodeName === "w:ins")) {
|
|
17786
|
-
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(
|
|
18274
|
+
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(NS_W9, "author");
|
|
17787
18275
|
if (insAuthor === (author || getDefaultAuthor())) {
|
|
17788
18276
|
insideSameAuthorIns = true;
|
|
17789
18277
|
}
|
|
@@ -17831,7 +18319,7 @@ async function applyParagraphFormatToParagraphByExactText(documentXml, targetTex
|
|
|
17831
18319
|
return { documentXml, hasChanges: false, status: "error", error: resolved?.error || { code: "TARGET_NOT_FOUND", message: "Target paragraph not found." } };
|
|
17832
18320
|
}
|
|
17833
18321
|
const targetParagraph = resolved.paragraph;
|
|
17834
|
-
let pPr = targetParagraph.getElementsByTagNameNS(
|
|
18322
|
+
let pPr = targetParagraph.getElementsByTagNameNS(NS_W9, "pPr")[0];
|
|
17835
18323
|
if (!pPr) {
|
|
17836
18324
|
pPr = createWordElement(xmlDoc, "w:pPr");
|
|
17837
18325
|
targetParagraph.insertBefore(pPr, targetParagraph.firstChild);
|
|
@@ -17879,7 +18367,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
17879
18367
|
}
|
|
17880
18368
|
let operation = validation.operation || normalizeDocumentOperation(op);
|
|
17881
18369
|
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")) {
|
|
18370
|
+
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
18371
|
return {
|
|
17884
18372
|
documentXml,
|
|
17885
18373
|
hasChanges: false,
|
|
@@ -18046,6 +18534,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18046
18534
|
result = applyCommentReplyToParts({
|
|
18047
18535
|
commentsXml: existingCommentsXml,
|
|
18048
18536
|
commentsExtendedXml: existingExtendedXml,
|
|
18537
|
+
documentXml,
|
|
18049
18538
|
parentCommentId: operation.parentCommentId,
|
|
18050
18539
|
commentId,
|
|
18051
18540
|
commentContent: operation.commentContent,
|
|
@@ -18053,8 +18542,30 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18053
18542
|
date: operation.date || (/* @__PURE__ */ new Date()).toISOString()
|
|
18054
18543
|
});
|
|
18055
18544
|
result.documentXml = documentXml;
|
|
18056
|
-
if (result.
|
|
18545
|
+
if (result.status !== "error" && result.hasChanges) {
|
|
18546
|
+
if (!session.document || !anchorReplyInDocument(session.document, result.threadCommentIds, commentId)) {
|
|
18547
|
+
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.` } };
|
|
18548
|
+
} else if (session.receiptCollector) {
|
|
18549
|
+
session.receiptCollector.recordComment(commentId);
|
|
18550
|
+
}
|
|
18551
|
+
}
|
|
18057
18552
|
}
|
|
18553
|
+
} else if (operation.operationKind === "comment_resolve") {
|
|
18554
|
+
const resolution = applyThreadResolutionToParts({
|
|
18555
|
+
commentsXml: session.commentsXml || runtimeContext?.commentsXml || options.commentsXml,
|
|
18556
|
+
commentsExtendedXml: session.commentsExtendedXml || runtimeContext?.commentsExtendedXml || options.commentsExtendedXml,
|
|
18557
|
+
commentId: operation.commentId,
|
|
18558
|
+
resolved: operation.resolved !== false
|
|
18559
|
+
});
|
|
18560
|
+
result = resolution.status === "error" ? { documentXml, hasChanges: false, status: "error", error: resolution.error } : {
|
|
18561
|
+
documentXml,
|
|
18562
|
+
hasChanges: resolution.hasChanges,
|
|
18563
|
+
...resolution.commentsXml ? { commentsXml: resolution.commentsXml, commentsXmlMode: "replace" } : {},
|
|
18564
|
+
...resolution.commentsExtendedXml ? { commentsExtendedXml: resolution.commentsExtendedXml, commentsExtendedXmlMode: "replace" } : {},
|
|
18565
|
+
resolved: resolution.resolved,
|
|
18566
|
+
threadRootId: resolution.threadRootId,
|
|
18567
|
+
commentIds: resolution.commentIds
|
|
18568
|
+
};
|
|
18058
18569
|
} else if (operation.operationKind === "highlight") {
|
|
18059
18570
|
result = await applyHighlightToParagraphByExactText(
|
|
18060
18571
|
documentXml,
|
|
@@ -18195,7 +18706,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18195
18706
|
...resolutionCapture
|
|
18196
18707
|
};
|
|
18197
18708
|
}
|
|
18198
|
-
session.markMutationCommitted(
|
|
18709
|
+
session.markMutationCommitted(true);
|
|
18199
18710
|
if (operation._compiledSourceId && session.sourceTargetRegistry) {
|
|
18200
18711
|
session.sourceTargetRegistry.commitMutation(
|
|
18201
18712
|
operation._compiledSourceId,
|
|
@@ -18308,9 +18819,9 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18308
18819
|
}
|
|
18309
18820
|
|
|
18310
18821
|
// services/batch-operation-orchestrator.js
|
|
18311
|
-
var
|
|
18822
|
+
var NS_W10 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
18312
18823
|
function operationTargetPriority(op) {
|
|
18313
|
-
return op?.type === "comment" || op?.type === "comment_reply" ? 0 : 1;
|
|
18824
|
+
return op?.type === "comment" || op?.type === "comment_reply" || op?.type === "comment_resolve" ? 0 : 1;
|
|
18314
18825
|
}
|
|
18315
18826
|
function buildOperationDependencyPlan(operations = []) {
|
|
18316
18827
|
const list = Array.isArray(operations) ? operations : [];
|
|
@@ -18385,7 +18896,7 @@ function buildOperationDependencyPlan(operations = []) {
|
|
|
18385
18896
|
inDegrees[i2]++;
|
|
18386
18897
|
}
|
|
18387
18898
|
const kind = normalizeDocumentOperation(op).operationKind;
|
|
18388
|
-
if (kind !== "comment" && kind !== "comment_reply") {
|
|
18899
|
+
if (kind !== "comment" && kind !== "comment_reply" && kind !== "comment_resolve") {
|
|
18389
18900
|
if (!mutatingCaptureConsumers.has(ref)) mutatingCaptureConsumers.set(ref, []);
|
|
18390
18901
|
mutatingCaptureConsumers.get(ref).push({
|
|
18391
18902
|
index: i2,
|
|
@@ -18453,9 +18964,9 @@ function mergeCommentsXml(existingXml, incomingXml) {
|
|
|
18453
18964
|
const serializer = createSerializer();
|
|
18454
18965
|
const existingRoot = existingDoc.documentElement;
|
|
18455
18966
|
const existingIds = new Set(
|
|
18456
|
-
Array.from(existingDoc.getElementsByTagNameNS(
|
|
18967
|
+
Array.from(existingDoc.getElementsByTagNameNS(NS_W10, "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
18457
18968
|
);
|
|
18458
|
-
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(
|
|
18969
|
+
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(NS_W10, "comment"))) {
|
|
18459
18970
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
18460
18971
|
if (id && existingIds.has(id)) continue;
|
|
18461
18972
|
existingRoot.appendChild(existingDoc.importNode(comment, true));
|
|
@@ -18616,7 +19127,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
18616
19127
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
18617
19128
|
}
|
|
18618
19129
|
session.runtimeContext = context;
|
|
18619
|
-
if (sourceOperations.some((op) => op?.type === "comment_reply")) {
|
|
19130
|
+
if (sourceOperations.some((op) => op?.type === "comment_reply" || op?.type === "comment_resolve")) {
|
|
18620
19131
|
session.commentsXml = context.commentsXml || options.commentsXml || null;
|
|
18621
19132
|
session.commentsExtendedXml = context.commentsExtendedXml || options.commentsExtendedXml || null;
|
|
18622
19133
|
session.commentsXmlMode = "replace";
|
|
@@ -18689,6 +19200,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
18689
19200
|
...result.resolvedTarget ? { resolvedTarget: result.resolvedTarget } : {},
|
|
18690
19201
|
...result.resolvedAnchor ? { resolvedAnchor: result.resolvedAnchor } : {},
|
|
18691
19202
|
...result.change ? { change: result.change } : {},
|
|
19203
|
+
...result.operationType === "comment_resolve" && !isError ? { resolved: result.resolved, threadRootId: result.threadRootId, commentIds: result.commentIds } : {},
|
|
18692
19204
|
...Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {},
|
|
18693
19205
|
...result.error ? { error: result.error } : {},
|
|
18694
19206
|
...result.receipt ? { receipt: result.receipt } : {}
|
|
@@ -18965,6 +19477,26 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
18965
19477
|
}
|
|
18966
19478
|
commentsRequired = commentsRequired || operation.operationKind === "comment" || operation.operationKind === "comment_reply";
|
|
18967
19479
|
numberingRequired = numberingRequired || operationNeedsNumbering(operation);
|
|
19480
|
+
if (operation.operationKind === "comment_resolve") {
|
|
19481
|
+
const commentId = String(operation.commentId);
|
|
19482
|
+
results.push(options._existingCommentDetails?.[commentId] ? {
|
|
19483
|
+
index: index + 1,
|
|
19484
|
+
type: sourceOperation.type,
|
|
19485
|
+
operationType: operation.operationKind,
|
|
19486
|
+
status: "ready",
|
|
19487
|
+
authorUsed,
|
|
19488
|
+
resolvedBy: "comment_id",
|
|
19489
|
+
commentId
|
|
19490
|
+
} : {
|
|
19491
|
+
index: index + 1,
|
|
19492
|
+
type: sourceOperation.type,
|
|
19493
|
+
operationType: operation.operationKind,
|
|
19494
|
+
status: "error",
|
|
19495
|
+
authorUsed,
|
|
19496
|
+
error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` }
|
|
19497
|
+
});
|
|
19498
|
+
continue;
|
|
19499
|
+
}
|
|
18968
19500
|
if (operation.operationKind === "comment_reply") {
|
|
18969
19501
|
const parentId = String(operation.parentCommentId);
|
|
18970
19502
|
const parent = options._existingCommentDetails?.[parentId];
|
|
@@ -20116,6 +20648,15 @@ var text = (entries, path) => {
|
|
|
20116
20648
|
if (typeof entry === "string") return entry;
|
|
20117
20649
|
return textDecoder2.decode(entry);
|
|
20118
20650
|
};
|
|
20651
|
+
function reconcileCommentSiblings(working) {
|
|
20652
|
+
const updated = reconcileCommentSiblingParts({
|
|
20653
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
20654
|
+
commentsIdsXml: text(working, "word/commentsIds.xml"),
|
|
20655
|
+
commentsExtensibleXml: text(working, "word/commentsExtensible.xml")
|
|
20656
|
+
});
|
|
20657
|
+
if (updated.commentsIdsXml) working.set("word/commentsIds.xml", textEncoder4.encode(updated.commentsIdsXml));
|
|
20658
|
+
if (updated.commentsExtensibleXml) working.set("word/commentsExtensible.xml", textEncoder4.encode(updated.commentsExtensibleXml));
|
|
20659
|
+
}
|
|
20119
20660
|
var cloneEntries = (entries) => new Map([...entries].map(([name, data]) => [name, new Uint8Array(data)]));
|
|
20120
20661
|
function toBufferCompatible(uint8Arr) {
|
|
20121
20662
|
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
@@ -20183,6 +20724,7 @@ function computePackageRevisionToken(input) {
|
|
|
20183
20724
|
entries
|
|
20184
20725
|
});
|
|
20185
20726
|
}
|
|
20727
|
+
var COMMENT_OPERATION_TYPES = /* @__PURE__ */ new Set(["comment", "comment_reply", "comment_resolve"]);
|
|
20186
20728
|
function nextCommentId(entries) {
|
|
20187
20729
|
const ids = `${text(entries, "word/document.xml") || ""} ${text(entries, "word/comments.xml") || ""}`.match(/(?:w:)?id=["'](\d+)["']/g) || [];
|
|
20188
20730
|
let next = ids.reduce((max2, token) => Math.max(max2, Number(token.match(/\d+/)?.[0] || 0)), 0) + 1;
|
|
@@ -20201,7 +20743,7 @@ function existingCommentDetails(entries) {
|
|
|
20201
20743
|
id,
|
|
20202
20744
|
author: comment.getAttribute("w:author") || comment.getAttribute("author") || "",
|
|
20203
20745
|
text: String(comment.textContent || "").trim(),
|
|
20204
|
-
paraId:
|
|
20746
|
+
paraId: commentParaId(comment) || null
|
|
20205
20747
|
};
|
|
20206
20748
|
}
|
|
20207
20749
|
return details;
|
|
@@ -20232,13 +20774,34 @@ var DocxDocument = class {
|
|
|
20232
20774
|
}
|
|
20233
20775
|
inspect(options = {}) {
|
|
20234
20776
|
const digestFn = options.digestFn || ((bytes) => sha256(bytes));
|
|
20235
|
-
|
|
20777
|
+
const inspection = inspectDocumentParts({
|
|
20236
20778
|
documentXml: text(this.entries, "word/document.xml"),
|
|
20237
20779
|
commentsXml: text(this.entries, "word/comments.xml"),
|
|
20238
20780
|
commentsExtendedXml: text(this.entries, "word/commentsExtended.xml"),
|
|
20239
20781
|
numberingXml: text(this.entries, "word/numbering.xml"),
|
|
20240
20782
|
stylesXml: text(this.entries, "word/styles.xml")
|
|
20241
20783
|
}, { ...options, digestFn });
|
|
20784
|
+
if (inspection.status === "error") return inspection;
|
|
20785
|
+
return { ...inspection, headersFooters: this.inspectHeadersFooters() };
|
|
20786
|
+
}
|
|
20787
|
+
/**
|
|
20788
|
+
* Header and footer parts the document uses, with their paragraph text, so operations can target them with
|
|
20789
|
+
* `part`. `hasFields` marks parts containing PAGE/NUMPAGES-style fields (edit around them, not inside).
|
|
20790
|
+
*/
|
|
20791
|
+
inspectHeadersFooters() {
|
|
20792
|
+
return discoverHeaderFooterParts({
|
|
20793
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
20794
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
20795
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
20796
|
+
}).map((part) => {
|
|
20797
|
+
const xml = text(this.entries, part.path) || "";
|
|
20798
|
+
const partInspection = xml ? inspectDocumentParts({ documentXml: xml }) : { paragraphs: [] };
|
|
20799
|
+
return {
|
|
20800
|
+
...part,
|
|
20801
|
+
hasFields: /<w:fldChar\b|<w:fldSimple\b/.test(xml),
|
|
20802
|
+
paragraphs: (partInspection.paragraphs || []).map((paragraph) => ({ index: paragraph.index, text: paragraph.text }))
|
|
20803
|
+
};
|
|
20804
|
+
});
|
|
20242
20805
|
}
|
|
20243
20806
|
getRevisionToken() {
|
|
20244
20807
|
return computePackageRevisionToken(this.entries);
|
|
@@ -20247,12 +20810,51 @@ var DocxDocument = class {
|
|
|
20247
20810
|
return this.getRevisionToken();
|
|
20248
20811
|
}
|
|
20249
20812
|
preflight(operations, author = getDefaultAuthor(), options = {}) {
|
|
20250
|
-
|
|
20813
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
20814
|
+
const runBody = (ops) => preflightOperations(
|
|
20251
20815
|
text(this.entries, "word/document.xml"),
|
|
20252
|
-
|
|
20816
|
+
ops,
|
|
20253
20817
|
author || getDefaultAuthor(),
|
|
20254
20818
|
{ ...options, _existingCommentDetails: existingCommentDetails(this.entries) }
|
|
20255
20819
|
);
|
|
20820
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) return runBody(operations);
|
|
20821
|
+
const parts = this.inspectHeadersFooters();
|
|
20822
|
+
const bodyOps = [];
|
|
20823
|
+
const results = [];
|
|
20824
|
+
const conflicts = [];
|
|
20825
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
20826
|
+
let valid = true;
|
|
20827
|
+
list.forEach((operation, i2) => {
|
|
20828
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
20829
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
20830
|
+
return;
|
|
20831
|
+
}
|
|
20832
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
20833
|
+
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;
|
|
20834
|
+
if (refusal) {
|
|
20835
|
+
valid = false;
|
|
20836
|
+
results.push({ index: i2 + 1, type: operation.type || "redline", status: "error", error: normalizeErrorWithRecovery({ ...refusal, operationIndex: i2 + 1 }) });
|
|
20837
|
+
return;
|
|
20838
|
+
}
|
|
20839
|
+
const { part: _selector, ...rest } = operation;
|
|
20840
|
+
const partResult = preflightOperations(text(this.entries, resolved.part.path), [rest], author || getDefaultAuthor(), { ...options, _existingCommentDetails: {} });
|
|
20841
|
+
valid = valid && partResult.valid;
|
|
20842
|
+
for (const result of partResult.results || []) results.push({ ...result, index: i2 + 1, part: resolved.part.path });
|
|
20843
|
+
conflicts.push(...partResult.conflicts || []);
|
|
20844
|
+
for (const name of partResult.authorsUsed || []) authorsUsed.add(name);
|
|
20845
|
+
});
|
|
20846
|
+
let requiredArtifacts = { comments: false, numbering: false };
|
|
20847
|
+
if (bodyOps.length) {
|
|
20848
|
+
const body = runBody(bodyOps.map((item) => item.operation));
|
|
20849
|
+
valid = valid && body.valid;
|
|
20850
|
+
for (const result of body.results || []) results.push({ ...result, index: bodyOps[result.index - 1].origIndex + 1 });
|
|
20851
|
+
conflicts.push(...body.conflicts || []);
|
|
20852
|
+
for (const name of body.authorsUsed || []) authorsUsed.add(name);
|
|
20853
|
+
requiredArtifacts = body.requiredArtifacts || requiredArtifacts;
|
|
20854
|
+
}
|
|
20855
|
+
results.sort((a, b) => a.index - b.index);
|
|
20856
|
+
const ok = valid && conflicts.length === 0;
|
|
20857
|
+
return { valid: ok, status: ok ? "ok" : "error", results, conflicts, authorsUsed: [...authorsUsed], requiredArtifacts };
|
|
20256
20858
|
}
|
|
20257
20859
|
toUint8Array() {
|
|
20258
20860
|
return zipDocx(this.entries);
|
|
@@ -20260,7 +20862,240 @@ var DocxDocument = class {
|
|
|
20260
20862
|
toBuffer() {
|
|
20261
20863
|
return toBufferCompatible(this.toUint8Array());
|
|
20262
20864
|
}
|
|
20865
|
+
/**
|
|
20866
|
+
* Applies operations to the document. Operations without `part` target the body (word/document.xml).
|
|
20867
|
+
* An operation with `part` targets one header or footer part:
|
|
20868
|
+
* `{ part: { kind: 'footer', type: 'default', section: 0 } | 'word/footer2.xml', target: { exactText }, ... }`.
|
|
20869
|
+
* Discover parts with `inspect().headersFooters`.
|
|
20870
|
+
*/
|
|
20263
20871
|
async applyOperations(operations, options = {}) {
|
|
20872
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
20873
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) {
|
|
20874
|
+
return this.applyBodyOperations(operations, options);
|
|
20875
|
+
}
|
|
20876
|
+
return this.applyOperationsWithParts(list, options);
|
|
20877
|
+
}
|
|
20878
|
+
async applyOperationsWithParts(operations, options = {}) {
|
|
20879
|
+
const author = options.author || getDefaultAuthor();
|
|
20880
|
+
const atomic = options.atomic === true;
|
|
20881
|
+
const snapshotEntries = this.entries;
|
|
20882
|
+
const snapshotBytes = this.originalBytes;
|
|
20883
|
+
const total = operations.length;
|
|
20884
|
+
const restore = () => {
|
|
20885
|
+
this.entries = snapshotEntries;
|
|
20886
|
+
this.originalBytes = snapshotBytes;
|
|
20887
|
+
};
|
|
20888
|
+
const original = () => ({
|
|
20889
|
+
uint8Array: snapshotBytes,
|
|
20890
|
+
toUint8Array: () => new Uint8Array(snapshotBytes),
|
|
20891
|
+
buffer: toBufferCompatible(snapshotBytes),
|
|
20892
|
+
toBuffer: () => toBufferCompatible(snapshotBytes)
|
|
20893
|
+
});
|
|
20894
|
+
const refuse = (error2, results2 = [], receipts2 = [], extra = {}) => ({
|
|
20895
|
+
status: "error",
|
|
20896
|
+
hasChanges: false,
|
|
20897
|
+
written: false,
|
|
20898
|
+
rolledBack: true,
|
|
20899
|
+
results: results2,
|
|
20900
|
+
receipts: receipts2,
|
|
20901
|
+
executionOrder: [],
|
|
20902
|
+
authorsUsed: [],
|
|
20903
|
+
artifactsChanged: [],
|
|
20904
|
+
error: normalizeErrorWithRecovery(error2),
|
|
20905
|
+
retryPlan: createRetryPlan({ atomic: true, rolledBack: true, results: results2, receipts: receipts2, operationCount: total }),
|
|
20906
|
+
validation: { originalIssues: [], generatedIssues: [] },
|
|
20907
|
+
...extra,
|
|
20908
|
+
...original()
|
|
20909
|
+
});
|
|
20910
|
+
if (options.expectedRevision) {
|
|
20911
|
+
const precondition = await this.applyBodyOperations([], { expectedRevision: options.expectedRevision });
|
|
20912
|
+
if (precondition.status === "error") return precondition;
|
|
20913
|
+
}
|
|
20914
|
+
const parts = discoverHeaderFooterParts({
|
|
20915
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
20916
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
20917
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
20918
|
+
});
|
|
20919
|
+
const bodyOps = [];
|
|
20920
|
+
const partOps = [];
|
|
20921
|
+
for (let i2 = 0; i2 < operations.length; i2++) {
|
|
20922
|
+
const operation = operations[i2];
|
|
20923
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
20924
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
20925
|
+
continue;
|
|
20926
|
+
}
|
|
20927
|
+
if (COMMENT_OPERATION_TYPES.has(operation.type)) {
|
|
20928
|
+
return refuse({ code: "COMMENT_IN_HEADER_FOOTER", message: `Operation ${i2 + 1}: Word does not support comments in headers or footers.`, operationIndex: i2 + 1 });
|
|
20929
|
+
}
|
|
20930
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
20931
|
+
if (resolved.error) return refuse({ ...resolved.error, message: `Operation ${i2 + 1}: ${resolved.error.message}`, operationIndex: i2 + 1 });
|
|
20932
|
+
const { part: _selector, ...rest } = operation;
|
|
20933
|
+
partOps.push({ operation: rest, origIndex: i2, part: resolved.part });
|
|
20934
|
+
}
|
|
20935
|
+
const results = [];
|
|
20936
|
+
const receipts = [];
|
|
20937
|
+
const executionOrder = [];
|
|
20938
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
20939
|
+
const originalIssues = [];
|
|
20940
|
+
let anyChange = false;
|
|
20941
|
+
let anyFailure = false;
|
|
20942
|
+
let bodyResult = null;
|
|
20943
|
+
if (bodyOps.length) {
|
|
20944
|
+
bodyResult = await this.applyBodyOperations(bodyOps.map((item) => item.operation), { ...options, expectedRevision: void 0 });
|
|
20945
|
+
const toOriginal = (index) => bodyOps[index - 1]?.origIndex + 1;
|
|
20946
|
+
for (const result of bodyResult.results || []) {
|
|
20947
|
+
results.push({
|
|
20948
|
+
...result,
|
|
20949
|
+
index: toOriginal(result.index),
|
|
20950
|
+
...result.receipt ? { receipt: { ...result.receipt, operationIndex: toOriginal(result.receipt.operationIndex) } } : {}
|
|
20951
|
+
});
|
|
20952
|
+
}
|
|
20953
|
+
for (const receipt of bodyResult.receipts || []) receipts.push({ ...receipt, operationIndex: toOriginal(receipt.operationIndex) });
|
|
20954
|
+
for (const index of bodyResult.executionOrder || []) executionOrder.push(toOriginal(index));
|
|
20955
|
+
for (const name of bodyResult.authorsUsed || []) authorsUsed.add(name);
|
|
20956
|
+
originalIssues.push(...bodyResult.validation?.originalIssues || []);
|
|
20957
|
+
if (bodyResult.status === "error" || bodyResult.rolledBack) {
|
|
20958
|
+
anyFailure = true;
|
|
20959
|
+
if (atomic) {
|
|
20960
|
+
restore();
|
|
20961
|
+
return { ...bodyResult, results, receipts, executionOrder, written: false, artifactsChanged: [], rolledBack: true, ...original() };
|
|
20962
|
+
}
|
|
20963
|
+
}
|
|
20964
|
+
anyChange = bodyResult.hasChanges === true;
|
|
20965
|
+
}
|
|
20966
|
+
const working = cloneEntries(this.entries);
|
|
20967
|
+
const zip = new MemoryZip(working);
|
|
20968
|
+
const changedParts = /* @__PURE__ */ new Set();
|
|
20969
|
+
const { expectedRevision: _ignored, ...runnerOptions } = options;
|
|
20970
|
+
const failPart = (item, error2) => {
|
|
20971
|
+
anyFailure = true;
|
|
20972
|
+
const failure = normalizeErrorWithRecovery({ ...error2, operationIndex: item.origIndex + 1 });
|
|
20973
|
+
results.push({ index: item.origIndex + 1, type: item.operation.type || "redline", status: "error", authorUsed: author, part: item.part.path, error: failure });
|
|
20974
|
+
};
|
|
20975
|
+
for (const item of partOps) {
|
|
20976
|
+
const path = item.part.path;
|
|
20977
|
+
const xml = text(working, path);
|
|
20978
|
+
if (!xml) {
|
|
20979
|
+
failPart(item, { code: "PART_NOT_FOUND", message: `Part '${path}' is referenced but missing from the package.` });
|
|
20980
|
+
if (atomic) break;
|
|
20981
|
+
continue;
|
|
20982
|
+
}
|
|
20983
|
+
const numberingText = text(working, "word/numbering.xml");
|
|
20984
|
+
const outcome = await applyOperationsToDocumentXml(xml, [item.operation], author, { numberingIdState: createDynamicNumberingIdState(numberingText || void 0) }, {
|
|
20985
|
+
...runnerOptions,
|
|
20986
|
+
atomic: true,
|
|
20987
|
+
strictTargets: options.strictTargets !== false,
|
|
20988
|
+
_existingCommentDetails: {}
|
|
20989
|
+
});
|
|
20990
|
+
const single = outcome.results?.[0];
|
|
20991
|
+
if (outcome.rolledBack || outcome.status === "error") {
|
|
20992
|
+
failPart(item, single?.error || outcome.error || { code: "OPERATION_FAILED", message: "Operation failed." });
|
|
20993
|
+
if (atomic) break;
|
|
20994
|
+
continue;
|
|
20995
|
+
}
|
|
20996
|
+
if (!outcome.hasChanges) {
|
|
20997
|
+
results.push({ ...single, index: item.origIndex + 1, part: path });
|
|
20998
|
+
continue;
|
|
20999
|
+
}
|
|
21000
|
+
if (countRevisionsInsideFields(outcome.documentXml) > countRevisionsInsideFields(xml)) {
|
|
21001
|
+
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.` });
|
|
21002
|
+
if (atomic) break;
|
|
21003
|
+
continue;
|
|
21004
|
+
}
|
|
21005
|
+
const taken = /* @__PURE__ */ new Set();
|
|
21006
|
+
for (const [name, data] of working) {
|
|
21007
|
+
if (name === path || !(name === "word/document.xml" || isHeaderFooterPath(name))) continue;
|
|
21008
|
+
for (const id of collectRevisionIds(textDecoder2.decode(data))) taken.add(id);
|
|
21009
|
+
}
|
|
21010
|
+
const renumbered = renumberCollidingRevisionIds(outcome.documentXml, taken);
|
|
21011
|
+
working.set(path, textEncoder4.encode(renumbered.xml));
|
|
21012
|
+
const describeReceipt = (receipt) => receipt?.revisionItems ? { ...receipt, revisionItems: receipt.revisionItems.map((revision) => ({ ...revision, id: renumbered.idMap.get(String(revision.id)) ?? revision.id, partName: path })) } : receipt;
|
|
21013
|
+
if (outcome.numberingXmlParts?.length) {
|
|
21014
|
+
await ensureNumberingArtifactsInZip(zip, outcome.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
21015
|
+
}
|
|
21016
|
+
changedParts.add(path);
|
|
21017
|
+
anyChange = true;
|
|
21018
|
+
results.push({
|
|
21019
|
+
...single,
|
|
21020
|
+
index: item.origIndex + 1,
|
|
21021
|
+
part: path,
|
|
21022
|
+
partSections: item.part.appliesToSections,
|
|
21023
|
+
...single?.receipt ? { receipt: { ...describeReceipt(single.receipt), operationIndex: item.origIndex + 1 } } : {}
|
|
21024
|
+
});
|
|
21025
|
+
if (outcome.receipts?.[0]) receipts.push({ ...describeReceipt(outcome.receipts[0]), operationIndex: item.origIndex + 1 });
|
|
21026
|
+
executionOrder.push(item.origIndex + 1);
|
|
21027
|
+
for (const name of outcome.authorsUsed || []) authorsUsed.add(name);
|
|
21028
|
+
}
|
|
21029
|
+
const generatedIssues = [];
|
|
21030
|
+
if (!(atomic && anyFailure)) {
|
|
21031
|
+
for (const path of changedParts) {
|
|
21032
|
+
const before = validateRedlineOoxml(text(this.entries, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
21033
|
+
const after = validateRedlineOoxml(text(working, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
21034
|
+
generatedIssues.push(...validationErrors(subtractValidationIssueMultiset(after, before)));
|
|
21035
|
+
}
|
|
21036
|
+
if (options.validate !== false && changedParts.size) {
|
|
21037
|
+
try {
|
|
21038
|
+
await validateDocxPackage(zip);
|
|
21039
|
+
} catch (error2) {
|
|
21040
|
+
generatedIssues.push({ source: "package", code: "PACKAGE_VALIDATION", severity: "error", message: error2.message });
|
|
21041
|
+
}
|
|
21042
|
+
}
|
|
21043
|
+
}
|
|
21044
|
+
if (atomic && anyFailure || generatedIssues.length) {
|
|
21045
|
+
restore();
|
|
21046
|
+
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." };
|
|
21047
|
+
const rolledBackResults = results.map((result) => result.receipt ? { ...result, receipt: { ...result.receipt, committed: false, finalDisposition: "rolled_back" } } : result).sort((a, b) => a.index - b.index);
|
|
21048
|
+
const rolledBackReceipts = receipts.map((receipt) => ({ ...receipt, committed: false, finalDisposition: "rolled_back" }));
|
|
21049
|
+
return refuse(error2, rolledBackResults, rolledBackReceipts, { validation: { originalIssues, generatedIssues }, issues: generatedIssues });
|
|
21050
|
+
}
|
|
21051
|
+
results.sort((a, b) => a.index - b.index);
|
|
21052
|
+
receipts.sort((a, b) => a.operationIndex - b.operationIndex);
|
|
21053
|
+
const status = !anyFailure ? "ok" : anyChange ? "partial" : "error";
|
|
21054
|
+
const failureError = anyFailure ? { error: normalizeErrorWithRecovery({ code: "BATCH_OPERATION_FAILED", message: "One or more operations failed; the others were applied." }) } : {};
|
|
21055
|
+
if (!changedParts.size) {
|
|
21056
|
+
const buf = toBufferCompatible(this.originalBytes);
|
|
21057
|
+
return {
|
|
21058
|
+
...bodyResult || {},
|
|
21059
|
+
results,
|
|
21060
|
+
receipts,
|
|
21061
|
+
executionOrder,
|
|
21062
|
+
authorsUsed: [...authorsUsed],
|
|
21063
|
+
status,
|
|
21064
|
+
hasChanges: anyChange,
|
|
21065
|
+
written: bodyResult?.written === true,
|
|
21066
|
+
artifactsChanged: bodyResult?.artifactsChanged || [],
|
|
21067
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
21068
|
+
...failureError,
|
|
21069
|
+
uint8Array: this.originalBytes,
|
|
21070
|
+
toUint8Array: () => new Uint8Array(this.originalBytes),
|
|
21071
|
+
buffer: buf,
|
|
21072
|
+
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
21073
|
+
};
|
|
21074
|
+
}
|
|
21075
|
+
this.entries = working;
|
|
21076
|
+
const outputBytes = this.toUint8Array();
|
|
21077
|
+
this.originalBytes = outputBytes;
|
|
21078
|
+
const artifactsChanged = [...working].filter(([name, data]) => !snapshotEntries.has(name) || !areByteArraysEqual(data, snapshotEntries.get(name))).map(([name]) => name);
|
|
21079
|
+
return {
|
|
21080
|
+
...bodyResult || {},
|
|
21081
|
+
status,
|
|
21082
|
+
hasChanges: true,
|
|
21083
|
+
written: true,
|
|
21084
|
+
results,
|
|
21085
|
+
receipts,
|
|
21086
|
+
executionOrder,
|
|
21087
|
+
authorsUsed: [...authorsUsed],
|
|
21088
|
+
artifactsChanged,
|
|
21089
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
21090
|
+
...failureError,
|
|
21091
|
+
uint8Array: outputBytes,
|
|
21092
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
21093
|
+
buffer: toBufferCompatible(outputBytes),
|
|
21094
|
+
inspection: this.inspect(),
|
|
21095
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
21096
|
+
};
|
|
21097
|
+
}
|
|
21098
|
+
async applyBodyOperations(operations, options = {}) {
|
|
20264
21099
|
const failedApply = (error2) => {
|
|
20265
21100
|
const results = [];
|
|
20266
21101
|
const receipts = [];
|
|
@@ -20371,18 +21206,21 @@ var DocxDocument = class {
|
|
|
20371
21206
|
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
20372
21207
|
};
|
|
20373
21208
|
}
|
|
20374
|
-
|
|
21209
|
+
const bodyChanged = (result.results || []).some((item) => item?.status !== "error" && item?.operationType !== "comment_resolve");
|
|
21210
|
+
if (bodyChanged) working.set("word/document.xml", textEncoder4.encode(result.documentXml));
|
|
20375
21211
|
await ensureNumberingArtifactsInZip(zip, result.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
20376
21212
|
const existingCommentsXml = text(working, "word/comments.xml");
|
|
20377
21213
|
const commentsXmlForPackaging = result.commentsXml || existingCommentsXml;
|
|
20378
21214
|
await ensureCommentsArtifactsInZip(zip, commentsXmlForPackaging, {
|
|
20379
21215
|
replaceExisting: result.commentsXmlMode === "replace" || !result.commentsXml && !!existingCommentsXml
|
|
20380
21216
|
});
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20384
|
-
|
|
20385
|
-
}
|
|
21217
|
+
if (result.commentsExtendedXml) {
|
|
21218
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, {
|
|
21219
|
+
replaceExisting: result.commentsExtendedXmlMode === "replace"
|
|
21220
|
+
});
|
|
21221
|
+
}
|
|
21222
|
+
await repairKnownContentTypes(zip);
|
|
21223
|
+
reconcileCommentSiblings(working);
|
|
20386
21224
|
if (options.validate !== false) {
|
|
20387
21225
|
const generated = validateRedlineOoxml(result.documentXml);
|
|
20388
21226
|
const outputIssues = generated.issues.map((issue) => ({ source: "word/document.xml", ...issue }));
|
|
@@ -20451,13 +21289,25 @@ var DocxDocument = class {
|
|
|
20451
21289
|
const transform = action === "accept" ? acceptTrackedChangesInOoxml : action === "reject" ? rejectTrackedChangesInOoxml : null;
|
|
20452
21290
|
if (!transform) return packageFailure(this.originalBytes, "INVALID_ACTION", `Unknown revision action: ${action}`);
|
|
20453
21291
|
const sourceBytes = this.originalBytes;
|
|
21292
|
+
const originalEntries = this.entries;
|
|
20454
21293
|
const working = cloneEntries(this.entries);
|
|
20455
21294
|
const zip = new MemoryZip(working);
|
|
20456
21295
|
const result = transform(text(working, "word/document.xml"), { author: options.author, allAuthors: options.allAuthors === true });
|
|
20457
21296
|
if (result.status === "error" || result.error) {
|
|
20458
21297
|
return packageFailure(sourceBytes, result.error?.code || "REVISION_OPERATION_FAILED", result.error?.message || "Revision operation failed.");
|
|
20459
21298
|
}
|
|
20460
|
-
|
|
21299
|
+
const partUpdates = /* @__PURE__ */ new Map();
|
|
21300
|
+
for (const part of this.inspectHeadersFooters()) {
|
|
21301
|
+
const partXml = text(working, part.path);
|
|
21302
|
+
if (!partXml) continue;
|
|
21303
|
+
const partResult = transform(partXml, { author: options.author, allAuthors: options.allAuthors === true });
|
|
21304
|
+
if (partResult.status === "error" || partResult.error) {
|
|
21305
|
+
return packageFailure(sourceBytes, partResult.error?.code || "REVISION_OPERATION_FAILED", `${part.path}: ${partResult.error?.message || "Revision operation failed."}`);
|
|
21306
|
+
}
|
|
21307
|
+
if (partResult.hasChanges) partUpdates.set(part.path, partResult.oxml);
|
|
21308
|
+
}
|
|
21309
|
+
const hasChanges = result.hasChanges || partUpdates.size > 0;
|
|
21310
|
+
if (!hasChanges) {
|
|
20461
21311
|
const buf = toBufferCompatible(sourceBytes);
|
|
20462
21312
|
return {
|
|
20463
21313
|
...result,
|
|
@@ -20470,8 +21320,10 @@ var DocxDocument = class {
|
|
|
20470
21320
|
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
20471
21321
|
};
|
|
20472
21322
|
}
|
|
20473
|
-
working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
21323
|
+
if (result.hasChanges) working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
21324
|
+
for (const [path, xml] of partUpdates) working.set(path, textEncoder4.encode(xml));
|
|
20474
21325
|
try {
|
|
21326
|
+
await repairKnownContentTypes(zip);
|
|
20475
21327
|
if (options.validate !== false) await validateDocxPackage(zip);
|
|
20476
21328
|
} catch (error2) {
|
|
20477
21329
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
@@ -20484,13 +21336,71 @@ var DocxDocument = class {
|
|
|
20484
21336
|
...result,
|
|
20485
21337
|
status: "ok",
|
|
20486
21338
|
written: true,
|
|
20487
|
-
|
|
21339
|
+
hasChanges: true,
|
|
21340
|
+
artifactsChanged: [...working].filter(([name, data]) => !originalEntries.has(name) || !areByteArraysEqual(data, originalEntries.get(name))).map(([name]) => name),
|
|
20488
21341
|
uint8Array: outputBytes,
|
|
20489
21342
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
20490
21343
|
buffer: outputBuf,
|
|
20491
21344
|
toBuffer: () => toBufferCompatible(outputBytes)
|
|
20492
21345
|
};
|
|
20493
21346
|
}
|
|
21347
|
+
/**
|
|
21348
|
+
* Resolves (or reopens) the comment thread containing `commentId`.
|
|
21349
|
+
* Resolved is a thread-level state in Word, so the root and every reply are updated together.
|
|
21350
|
+
*/
|
|
21351
|
+
async resolveComment(commentId, options = {}) {
|
|
21352
|
+
const sourceBytes = this.originalBytes;
|
|
21353
|
+
const working = cloneEntries(this.entries);
|
|
21354
|
+
const resolved = options.resolved !== false;
|
|
21355
|
+
const noChange = (extra) => {
|
|
21356
|
+
const buf = toBufferCompatible(sourceBytes);
|
|
21357
|
+
return {
|
|
21358
|
+
status: "ok",
|
|
21359
|
+
hasChanges: false,
|
|
21360
|
+
written: false,
|
|
21361
|
+
artifactsChanged: [],
|
|
21362
|
+
...extra,
|
|
21363
|
+
uint8Array: sourceBytes,
|
|
21364
|
+
toUint8Array: () => new Uint8Array(sourceBytes),
|
|
21365
|
+
buffer: buf,
|
|
21366
|
+
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
21367
|
+
};
|
|
21368
|
+
};
|
|
21369
|
+
const result = applyThreadResolutionToParts({
|
|
21370
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
21371
|
+
commentsExtendedXml: text(working, "word/commentsExtended.xml"),
|
|
21372
|
+
commentId,
|
|
21373
|
+
resolved
|
|
21374
|
+
});
|
|
21375
|
+
if (result.status === "error") return packageFailure(sourceBytes, result.error.code, result.error.message);
|
|
21376
|
+
if (!result.hasChanges) return noChange({ resolved, threadRootId: result.threadRootId, commentIds: result.commentIds });
|
|
21377
|
+
const zip = new MemoryZip(working);
|
|
21378
|
+
try {
|
|
21379
|
+
if (result.commentsXml) working.set("word/comments.xml", textEncoder4.encode(result.commentsXml));
|
|
21380
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, { replaceExisting: true });
|
|
21381
|
+
await repairKnownContentTypes(zip);
|
|
21382
|
+
if (options.validate !== false) await validateDocxPackage(zip);
|
|
21383
|
+
} catch (error2) {
|
|
21384
|
+
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
21385
|
+
}
|
|
21386
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
21387
|
+
this.entries = working;
|
|
21388
|
+
const outputBytes = this.toUint8Array();
|
|
21389
|
+
this.originalBytes = outputBytes;
|
|
21390
|
+
return {
|
|
21391
|
+
status: "ok",
|
|
21392
|
+
hasChanges: true,
|
|
21393
|
+
written: true,
|
|
21394
|
+
resolved,
|
|
21395
|
+
threadRootId: result.threadRootId,
|
|
21396
|
+
commentIds: result.commentIds,
|
|
21397
|
+
artifactsChanged,
|
|
21398
|
+
uint8Array: outputBytes,
|
|
21399
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
21400
|
+
buffer: toBufferCompatible(outputBytes),
|
|
21401
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
21402
|
+
};
|
|
21403
|
+
}
|
|
20494
21404
|
async deleteComments(options = {}) {
|
|
20495
21405
|
const sourceBytes = this.originalBytes;
|
|
20496
21406
|
const working = cloneEntries(this.entries);
|
|
@@ -20514,7 +21424,13 @@ var DocxDocument = class {
|
|
|
20514
21424
|
if (!parsed.doc || parsed.error) {
|
|
20515
21425
|
return packageFailure(sourceBytes, "PARSE_ERROR", parsed.error?.message || "Could not parse comments.xml.");
|
|
20516
21426
|
}
|
|
20517
|
-
const
|
|
21427
|
+
const requestedIds = Array.isArray(options.ids) ? new Set(options.ids.map(String)) : null;
|
|
21428
|
+
if (requestedIds) {
|
|
21429
|
+
const known = new Set(Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")));
|
|
21430
|
+
const missing = [...requestedIds].filter((id) => !known.has(id));
|
|
21431
|
+
if (missing.length) return packageFailure(sourceBytes, "COMMENT_NOT_FOUND", `Comment id(s) not found: ${missing.join(", ")}.`);
|
|
21432
|
+
}
|
|
21433
|
+
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
21434
|
const ids = new Set(
|
|
20519
21435
|
Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).filter(matches).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
20520
21436
|
);
|
|
@@ -20543,9 +21459,8 @@ var DocxDocument = class {
|
|
|
20543
21459
|
const idByParaId = /* @__PURE__ */ new Map();
|
|
20544
21460
|
for (const comment of Array.from(parsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
20545
21461
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
20546
|
-
const
|
|
20547
|
-
|
|
20548
|
-
if (paraId) idByParaId.set(paraId.toUpperCase(), id);
|
|
21462
|
+
const paraId = commentParaId(comment);
|
|
21463
|
+
if (paraId) idByParaId.set(paraId, id);
|
|
20549
21464
|
}
|
|
20550
21465
|
for (const [paraId, id] of idByParaId) {
|
|
20551
21466
|
if (ids.has(id)) removedParaIds.add(paraId);
|
|
@@ -20564,8 +21479,8 @@ var DocxDocument = class {
|
|
|
20564
21479
|
}
|
|
20565
21480
|
}
|
|
20566
21481
|
}
|
|
20567
|
-
const
|
|
20568
|
-
const remainingParsed = parseOoxmlSafe(
|
|
21482
|
+
const remainingXml = requestedIds ? commentsXml : deleteCommentsByAuthorInOoxml(commentsXml, { author: options.author, allAuthors: options.allAuthors === true }).oxml;
|
|
21483
|
+
const remainingParsed = parseOoxmlSafe(remainingXml, "application/xml");
|
|
20569
21484
|
if (!remainingParsed.doc || remainingParsed.error) {
|
|
20570
21485
|
return packageFailure(sourceBytes, "PARSE_ERROR", remainingParsed.error?.message || "Could not parse updated comments.xml.");
|
|
20571
21486
|
}
|
|
@@ -20601,10 +21516,14 @@ var DocxDocument = class {
|
|
|
20601
21516
|
}
|
|
20602
21517
|
working.set("word/document.xml", textEncoder4.encode(serializer.serializeToString(documentParsed.doc)));
|
|
20603
21518
|
try {
|
|
20604
|
-
|
|
21519
|
+
reconcileCommentSiblings(working);
|
|
21520
|
+
const finalZip = new MemoryZip(working);
|
|
21521
|
+
await repairKnownContentTypes(finalZip);
|
|
21522
|
+
if (options.validate !== false) await validateDocxPackage(finalZip);
|
|
20605
21523
|
} catch (error2) {
|
|
20606
21524
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
20607
21525
|
}
|
|
21526
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
20608
21527
|
this.entries = working;
|
|
20609
21528
|
const outputBytes = this.toUint8Array();
|
|
20610
21529
|
this.originalBytes = outputBytes;
|
|
@@ -20615,7 +21534,7 @@ var DocxDocument = class {
|
|
|
20615
21534
|
written: true,
|
|
20616
21535
|
commentsRemoved: ids.size,
|
|
20617
21536
|
referencesRemoved,
|
|
20618
|
-
artifactsChanged
|
|
21537
|
+
artifactsChanged,
|
|
20619
21538
|
uint8Array: outputBytes,
|
|
20620
21539
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
20621
21540
|
buffer: outputBuf,
|
|
@@ -20871,6 +21790,7 @@ export {
|
|
|
20871
21790
|
rejectTrackedChangesInOoxml,
|
|
20872
21791
|
remapNumberingPayloadForDocument,
|
|
20873
21792
|
removeFormattingFromRPr,
|
|
21793
|
+
repairKnownContentTypes,
|
|
20874
21794
|
reserveNextNumberingId,
|
|
20875
21795
|
reserveNextNumberingIdPair,
|
|
20876
21796
|
resolveParagraphRangeByRefs,
|