@ansonlai/docx-redline-js 0.1.3 → 0.1.4

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/AGENTS.md CHANGED
@@ -74,6 +74,30 @@ const result = injectCommentsIntoOoxml(paragraphOoxml, [
74
74
  ]);
75
75
  ```
76
76
 
77
+ ### Accept tracked changes from one user (or all users)
78
+
79
+ ```js
80
+ import { acceptTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
81
+ const acceptedMine = acceptTrackedChangesInOoxml(documentXml, { author: 'Agent' });
82
+ const acceptedAll = acceptTrackedChangesInOoxml(documentXml, { allAuthors: true });
83
+ ```
84
+
85
+ ### Reject tracked changes from one user (or all users)
86
+
87
+ ```js
88
+ import { rejectTrackedChangesInOoxml } from '@ansonlai/docx-redline-js';
89
+ const rejectedMine = rejectTrackedChangesInOoxml(documentXml, { author: 'Agent' });
90
+ const rejectedAll = rejectTrackedChangesInOoxml(documentXml, { allAuthors: true });
91
+ ```
92
+
93
+ ### Delete comments from one user (or all users)
94
+
95
+ ```js
96
+ import { deleteCommentsByAuthorInOoxml } from '@ansonlai/docx-redline-js';
97
+ const removedMine = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { author: 'Agent' });
98
+ const removedAll = deleteCommentsByAuthorInOoxml(packageOrDocumentOoxml, { allAuthors: true });
99
+ ```
100
+
77
101
  ### Apply multiple operations to full document XML
78
102
 
79
103
  ```js
@@ -129,6 +153,7 @@ services/
129
153
  standalone-docx-plumbing.js
130
154
  numbering-helpers.js
131
155
  comment-engine.js
156
+ revision-comment-management.js
132
157
  table-reconciliation.js
133
158
  package-builder.js
134
159
  orchestration/
@@ -167,10 +192,22 @@ import { wrapInDocumentFragment } from '@ansonlai/docx-redline-js';
167
192
  const wrapped = wrapInDocumentFragment(rawOoxml, { includeNumbering: true, numberingXml });
168
193
  ```
169
194
 
195
+ ### Output shape guardrail (important for packaging)
196
+
197
+ When consuming `result.oxml`, do not assume the payload is always safe to write
198
+ directly into `word/document.xml`.
199
+
200
+ - Paragraph/range/table APIs can return a fragment, `<w:document>`, or package payload (`<pkg:package>`).
201
+ - `applyOperationToDocumentXml(...).documentXml` is the document-safe path when you need a full `word/document.xml` replacement.
202
+ - Use `extractReplacementNodesFromOoxml(payload)` to normalize unknown payloads.
203
+ - If `sourceType === 'package'` or the payload starts with `<pkg:package`, do not write it into `word/document.xml` as-is.
204
+
170
205
  ## Gotchas
171
206
 
172
207
  1. Call `configureXmlProvider` first in Node.js.
173
208
  2. `applyRedlineToOxml` is async.
174
209
  3. Paragraph APIs expect paragraph-level OOXML, not full `word/document.xml` in all cases.
175
210
  4. List operations may return `numberingXml` that must be merged into package parts.
176
- 5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
211
+ 5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
212
+ 6. `deleteCommentsByAuthorInOoxml` removes matching `comments.xml` entries and linked comment anchors/references in the document.
213
+ 7. If output begins with `<pkg:package`, treat it as package-level OOXML and normalize it before writing anything back to `word/document.xml`.
package/ARCHITECTURE.md CHANGED
@@ -38,6 +38,7 @@ No Word add-in entrypoints or host-specific integration layers are part of this
38
38
  ├── pipeline/
39
39
  ├── services/
40
40
  │ ├── numbering-helpers.js
41
+ │ ├── revision-comment-management.js
41
42
  │ ├── standalone-docx-plumbing.js
42
43
  │ └── standalone-operation-runner.js
43
44
  └── index.js
@@ -67,6 +68,8 @@ No Word add-in entrypoints or host-specific integration layers are part of this
67
68
  - Dynamic numbering ID allocation, numbering payload remapping, and schema-order-safe numbering merges.
68
69
  - `services/standalone-docx-plumbing.js`
69
70
  - Package-level extraction/wiring/validation for `word/document.xml`, `word/numbering.xml`, and `word/comments.xml`.
71
+ - `services/revision-comment-management.js`
72
+ - OOXML transforms for accepting/rejecting tracked changes by author/all-authors and deleting comments by author/all-authors.
70
73
  - `services/standalone-operation-runner.js`
71
74
  - Host-agnostic operation bridge for `redline`, `highlight`, and `comment` workflows.
72
75
  - `orchestration/*`
@@ -79,7 +82,8 @@ No Word add-in entrypoints or host-specific integration layers are part of this
79
82
  3. Caller invokes reconciliation APIs (`applyRedlineToOxml`, operation runner, ingestion/export helpers).
80
83
  4. `engine/oxml-engine.js` routes to format, table, list, surgical, or reconstruction flows.
81
84
  5. Pipeline/services return OOXML and optional package artifacts (`numberingXml`, comments payloads).
82
- 6. Caller writes resulting XML back to package/document boundaries.
85
+ 6. Optional revision/comment management transforms can accept/reject revisions or delete comments by author.
86
+ 7. Caller writes resulting XML back to package/document boundaries.
83
87
 
84
88
  ## Public Surfaces
85
89
 
@@ -115,4 +119,5 @@ Use this sequence to understand or modify behavior without reading everything:
115
119
  2. Follow exports into `engine/oxml-engine.js` or relevant `services/*` module.
116
120
  3. For targeting bugs, inspect `core/paragraph-targeting.js`, `core/list-targeting.js`, and `core/table-targeting.js`.
117
121
  4. For package wiring issues, inspect `services/standalone-docx-plumbing.js`.
118
- 5. For numbering/list issues, inspect `services/numbering-helpers.js` and orchestration list-fallback modules.
122
+ 5. For revision/comment cleanup behavior, inspect `services/revision-comment-management.js`.
123
+ 6. For numbering/list issues, inspect `services/numbering-helpers.js` and orchestration list-fallback modules.
package/README.md CHANGED
@@ -11,6 +11,8 @@ Converts AI-generated or programmatic text/markdown edits into valid Office Open
11
11
  - Lists: generate and edit real Word lists (`w:numPr`) from markdown
12
12
  - Tables: virtual-grid diffing for cell-level edits with merge safety
13
13
  - Comments: inject OOXML comments anchored to text ranges
14
+ - Revision management: accept/reject tracked changes by author or for all authors
15
+ - Comment management: delete comments by author or for all authors
14
16
  - Highlights: apply highlight colors to runs
15
17
  - Markdown and OOXML conversion in both directions
16
18
  - Package plumbing helpers for numbering.xml, comments.xml, content types, and relationships
@@ -125,6 +127,9 @@ const result = await applyRedlineToOxml(oxml, original, modified, {
125
127
  | Function | Purpose |
126
128
  |----------|---------|
127
129
  | `injectCommentsIntoOoxml(oxml, comments, options)` | Add comments anchored to text ranges. |
130
+ | `acceptTrackedChangesInOoxml(oxml, { author?, allAuthors? })` | Accept `w:ins` / `w:del` / `*PrChange` revisions for one author or all authors. |
131
+ | `rejectTrackedChangesInOoxml(oxml, { author?, allAuthors? })` | Reject `w:ins` / `w:del` / `*PrChange` revisions for one author or all authors. |
132
+ | `deleteCommentsByAuthorInOoxml(oxml, { author?, allAuthors? })` | Delete comments and matching anchors/references for one author or all authors. |
128
133
  | `generateTableOoxml(headers, rows, options)` | Generate a `w:tbl` from tabular data. |
129
134
  | `createDynamicNumberingIdState(numberingXml)` | Allocate numbering IDs without collisions. |
130
135
  | `ensureNumberingArtifactsInZip(zip, numberingXml)` | Merge numbering artifacts into a `.docx` package. |
@@ -140,6 +145,26 @@ import { applyOperationToDocumentXml } from '@ansonlai/docx-redline-js/services/
140
145
  import { getParagraphText } from '@ansonlai/docx-redline-js/core/paragraph-targeting.js';
141
146
  ```
142
147
 
148
+ ### Output Shape Matrix
149
+
150
+ Different APIs return different OOXML shapes. Use this as a packaging safety check.
151
+
152
+ | API | Typical input scope | Output field | Possible root/output shape | Safe to write directly into `word/document.xml` |
153
+ |-----|----------------------|--------------|----------------------------|--------------------------------------------------|
154
+ | `applyRedlineToOxml(...)` | Paragraph, range, or table-scope OOXML | `result.oxml` | Fragment, `<w:document>`, or package payload (`<pkg:package>`) | No. Inspect first. |
155
+ | `applyRedlineToOxmlWithListFallback(...)` | Paragraph or range-scope OOXML | `result.oxml` | Fragment, `<w:document>`, or package payload (`<pkg:package>`) | No. Inspect first. |
156
+ | `reconcileMarkdownTableOoxml(...)` | Table or paragraph-scope OOXML | `result.oxml` | Same shapes as `applyRedlineToOxml(...)` for the supplied scope | No. Inspect first. |
157
+ | `applyOperationToDocumentXml(...)` | Full `word/document.xml` string | `result.documentXml` | `<w:document>` | Yes. This is the document-safe helper. |
158
+ | `extractReplacementNodesFromOoxml(...)` | Any OOXML payload | `{ replacementNodes, numberingXml, sourceType }` | Normalized to `fragment`, `document`, or `package` | Yes. Use this when consuming `result.oxml`. |
159
+
160
+ ### Do / Don't for Packaging
161
+
162
+ - Do use `applyOperationToDocumentXml(...).documentXml` when your intent is to replace `word/document.xml`.
163
+ - Do use `extractReplacementNodesFromOoxml(...)` when you are consuming `result.oxml` from paragraph/range/table APIs.
164
+ - Do merge numbering/comments artifacts with `ensureNumberingArtifactsInZip(...)` and `ensureCommentsArtifactsInZip(...)` when those parts are present.
165
+ - Don't write payloads that start with `<pkg:package` directly into `word/document.xml`.
166
+ - Don't assume every `result.oxml` payload is a raw paragraph fragment.
167
+
143
168
  ## Working With `.docx` Files
144
169
 
145
170
  This package operates on OOXML strings (XML parts inside `.docx` zip archives), not raw `.docx` binaries.
@@ -155,18 +180,40 @@ Typical flow:
155
180
  ```js
156
181
  import JSZip from 'jszip';
157
182
  import {
158
- configureXmlProvider,
159
183
  applyRedlineToOxml,
184
+ extractReplacementNodesFromOoxml,
160
185
  ensureNumberingArtifactsInZip,
161
186
  validateDocxPackage
162
187
  } from '@ansonlai/docx-redline-js';
188
+ import { applyOperationToDocumentXml } from '@ansonlai/docx-redline-js/services/standalone-operation-runner.js';
163
189
 
164
190
  const zip = await JSZip.loadAsync(docxBuffer);
165
191
  const documentXml = await zip.file('word/document.xml').async('string');
166
192
 
167
- // Apply edits with applyRedlineToOxml(...)
168
- // Merge artifacts with ensureNumberingArtifactsInZip(...) as needed
193
+ const opResult = await applyOperationToDocumentXml(
194
+ documentXml,
195
+ { type: 'redline', target: 'old text', modified: 'new text' },
196
+ 'Editor'
197
+ );
198
+
199
+ // applyOperationToDocumentXml(...) returns a full w:document payload.
200
+ zip.file('word/document.xml', opResult.documentXml);
201
+
202
+ const fragmentResult = await applyRedlineToOxml(
203
+ paragraphOoxml,
204
+ 'Item text',
205
+ '1. Item text',
206
+ { generateRedlines: true, author: 'Editor' }
207
+ );
208
+ const normalized = extractReplacementNodesFromOoxml(fragmentResult.oxml);
209
+
210
+ // If sourceType === 'package', merge extracted content/artifacts instead of
211
+ // writing the raw pkg:package payload into word/document.xml.
212
+ if (normalized.numberingXml) {
213
+ await ensureNumberingArtifactsInZip(zip, normalized.numberingXml);
214
+ }
169
215
 
216
+ await validateDocxPackage(zip);
170
217
  const output = await zip.generateAsync({ type: 'nodebuffer' });
171
218
  ```
172
219
 
@@ -1,4 +1,4 @@
1
- // @ansonlai/docx-redline-js v0.1.3 — https://github.com/AnsonLai/docx-redline-js
1
+ // @ansonlai/docx-redline-js v0.1.4 — 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;
@@ -7901,6 +7901,306 @@ function injectCommentsIntoPackage2(packageOxml, commentsXml) {
7901
7901
  return injectCommentsIntoPackage(packageOxml, commentsXml);
7902
7902
  }
7903
7903
 
7904
+ // services/revision-comment-management.js
7905
+ function getAttributeByLocalName(node, localName) {
7906
+ if (!node || !node.attributes) return "";
7907
+ for (const attr of Array.from(node.attributes)) {
7908
+ if ((attr.localName || "").toLowerCase() === localName.toLowerCase()) {
7909
+ return String(attr.value || "");
7910
+ }
7911
+ }
7912
+ return String(
7913
+ node.getAttribute?.(`w:${localName}`) || node.getAttribute?.(localName) || ""
7914
+ );
7915
+ }
7916
+ function normalizeAuthor(author) {
7917
+ return typeof author === "string" ? author.trim().toLowerCase() : "";
7918
+ }
7919
+ function isElement(node) {
7920
+ return !!node && node.nodeType === 1;
7921
+ }
7922
+ function isWordElement4(node, localName) {
7923
+ return isElement(node) && node.namespaceURI === NS_W && String(node.localName || "").toLowerCase() === localName.toLowerCase();
7924
+ }
7925
+ function getWordElementsByLocalName(xmlDoc, localName) {
7926
+ return Array.from(xmlDoc.getElementsByTagNameNS(NS_W, localName));
7927
+ }
7928
+ function resolveAuthorFilter(options = {}) {
7929
+ if (options?.allAuthors === true) {
7930
+ return { valid: true, allAuthors: true, normalizedAuthor: "" };
7931
+ }
7932
+ const normalizedAuthor = normalizeAuthor(options?.author);
7933
+ if (!normalizedAuthor) {
7934
+ return {
7935
+ valid: false,
7936
+ allAuthors: false,
7937
+ normalizedAuthor: "",
7938
+ warning: "No author provided. Pass { author } or set { allAuthors: true }."
7939
+ };
7940
+ }
7941
+ return { valid: true, allAuthors: false, normalizedAuthor };
7942
+ }
7943
+ function authorMatchesNode(node, filter) {
7944
+ if (filter.allAuthors) return true;
7945
+ const nodeAuthor = normalizeAuthor(getAttributeByLocalName(node, "author"));
7946
+ return !!nodeAuthor && nodeAuthor === filter.normalizedAuthor;
7947
+ }
7948
+ function parseXmlWithWarnings(oxml, parseFailurePrefix) {
7949
+ const parser = createParser();
7950
+ const xmlDoc = parser.parseFromString(oxml, "application/xml");
7951
+ const parseError = getXmlParseError(xmlDoc);
7952
+ if (parseError) {
7953
+ return {
7954
+ xmlDoc: null,
7955
+ serializer: null,
7956
+ warning: `${parseFailurePrefix}: ${parseError.textContent || "parse error"}`
7957
+ };
7958
+ }
7959
+ return { xmlDoc, serializer: createSerializer(), warning: null };
7960
+ }
7961
+ function removeNode(node) {
7962
+ if (node?.parentNode) {
7963
+ node.parentNode.removeChild(node);
7964
+ return true;
7965
+ }
7966
+ return false;
7967
+ }
7968
+ function unwrapNode(node) {
7969
+ const parent = node?.parentNode;
7970
+ if (!parent) return false;
7971
+ while (node.firstChild) {
7972
+ parent.insertBefore(node.firstChild, node);
7973
+ }
7974
+ parent.removeChild(node);
7975
+ return true;
7976
+ }
7977
+ function isTableRowRevisionMarker(node) {
7978
+ const parent = node?.parentNode;
7979
+ return isWordElement4(parent, "trPr") && isWordElement4(parent?.parentNode, "tr");
7980
+ }
7981
+ function acceptTrackedChangesInOoxml(oxml, options = {}) {
7982
+ const warnings = [];
7983
+ const filter = resolveAuthorFilter(options);
7984
+ if (!filter.valid) {
7985
+ return { oxml, hasChanges: false, acceptedCount: 0, warnings: [filter.warning] };
7986
+ }
7987
+ const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
7988
+ if (!parseResult.xmlDoc) {
7989
+ return { oxml, hasChanges: false, acceptedCount: 0, warnings: [parseResult.warning] };
7990
+ }
7991
+ const { xmlDoc, serializer } = parseResult;
7992
+ let acceptedCount = 0;
7993
+ for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
7994
+ if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
7995
+ if (isTableRowRevisionMarker(insNode)) {
7996
+ if (removeNode(insNode)) acceptedCount += 1;
7997
+ continue;
7998
+ }
7999
+ if (unwrapNode(insNode)) acceptedCount += 1;
8000
+ }
8001
+ for (const delNode of getWordElementsByLocalName(xmlDoc, "del")) {
8002
+ if (!delNode.parentNode || !authorMatchesNode(delNode, filter)) continue;
8003
+ if (isTableRowRevisionMarker(delNode)) {
8004
+ const rowNode = delNode.parentNode?.parentNode;
8005
+ if (removeNode(rowNode)) acceptedCount += 1;
8006
+ continue;
8007
+ }
8008
+ if (removeNode(delNode)) acceptedCount += 1;
8009
+ }
8010
+ const changeTags = ["rPrChange", "pPrChange", "tblPrChange", "trPrChange", "tcPrChange"];
8011
+ for (const localName of changeTags) {
8012
+ for (const changeNode of getWordElementsByLocalName(xmlDoc, localName)) {
8013
+ if (!changeNode.parentNode || !authorMatchesNode(changeNode, filter)) continue;
8014
+ if (removeNode(changeNode)) acceptedCount += 1;
8015
+ }
8016
+ }
8017
+ return {
8018
+ oxml: serializer.serializeToString(xmlDoc),
8019
+ hasChanges: acceptedCount > 0,
8020
+ acceptedCount,
8021
+ warnings
8022
+ };
8023
+ }
8024
+ function convertDeletionTextNodes(xmlDoc, delNode) {
8025
+ for (const delTextNode of Array.from(delNode.getElementsByTagNameNS(NS_W, "delText"))) {
8026
+ const normalText = xmlDoc.createElementNS(NS_W, "w:t");
8027
+ const spaceValue = delTextNode.getAttribute("xml:space");
8028
+ if (spaceValue) {
8029
+ normalText.setAttribute("xml:space", spaceValue);
8030
+ }
8031
+ while (delTextNode.firstChild) {
8032
+ normalText.appendChild(delTextNode.firstChild);
8033
+ }
8034
+ delTextNode.parentNode?.replaceChild(normalText, delTextNode);
8035
+ }
8036
+ }
8037
+ function rejectPropertyChangeNode(changeNode, localName) {
8038
+ const parent = changeNode?.parentNode;
8039
+ if (!parent) return false;
8040
+ const baseLocalName = localName.endsWith("Change") ? localName.slice(0, -"Change".length) : "";
8041
+ if (!baseLocalName || String(parent.localName || "").toLowerCase() !== baseLocalName.toLowerCase() || parent.namespaceURI !== NS_W) {
8042
+ return removeNode(changeNode);
8043
+ }
8044
+ const historicalNode = Array.from(changeNode.childNodes || []).find(
8045
+ (child) => child.nodeType === 1 && child.namespaceURI === NS_W && String(child.localName || "").toLowerCase() === baseLocalName.toLowerCase()
8046
+ );
8047
+ if (!historicalNode) {
8048
+ return removeNode(changeNode);
8049
+ }
8050
+ const toAppend = Array.from(historicalNode.childNodes || []);
8051
+ while (parent.firstChild) {
8052
+ parent.removeChild(parent.firstChild);
8053
+ }
8054
+ for (const node of toAppend) {
8055
+ const clone = xmlDocImportNode(parent.ownerDocument, node);
8056
+ parent.appendChild(clone);
8057
+ }
8058
+ return true;
8059
+ }
8060
+ function xmlDocImportNode(xmlDoc, node) {
8061
+ if (xmlDoc && typeof xmlDoc.importNode === "function") {
8062
+ return xmlDoc.importNode(node, true);
8063
+ }
8064
+ return node.cloneNode(true);
8065
+ }
8066
+ function rejectTrackedChangesInOoxml(oxml, options = {}) {
8067
+ const warnings = [];
8068
+ const filter = resolveAuthorFilter(options);
8069
+ if (!filter.valid) {
8070
+ return { oxml, hasChanges: false, rejectedCount: 0, warnings: [filter.warning] };
8071
+ }
8072
+ const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
8073
+ if (!parseResult.xmlDoc) {
8074
+ return { oxml, hasChanges: false, rejectedCount: 0, warnings: [parseResult.warning] };
8075
+ }
8076
+ const { xmlDoc, serializer } = parseResult;
8077
+ let rejectedCount = 0;
8078
+ for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
8079
+ if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
8080
+ if (isTableRowRevisionMarker(insNode)) {
8081
+ const rowNode = insNode.parentNode?.parentNode;
8082
+ if (removeNode(rowNode)) rejectedCount += 1;
8083
+ continue;
8084
+ }
8085
+ if (removeNode(insNode)) rejectedCount += 1;
8086
+ }
8087
+ for (const delNode of getWordElementsByLocalName(xmlDoc, "del")) {
8088
+ if (!delNode.parentNode || !authorMatchesNode(delNode, filter)) continue;
8089
+ if (isTableRowRevisionMarker(delNode)) {
8090
+ if (removeNode(delNode)) rejectedCount += 1;
8091
+ continue;
8092
+ }
8093
+ convertDeletionTextNodes(xmlDoc, delNode);
8094
+ if (unwrapNode(delNode)) rejectedCount += 1;
8095
+ }
8096
+ const changeTags = ["rPrChange", "pPrChange", "tblPrChange", "trPrChange", "tcPrChange"];
8097
+ for (const localName of changeTags) {
8098
+ for (const changeNode of getWordElementsByLocalName(xmlDoc, localName)) {
8099
+ if (!changeNode.parentNode || !authorMatchesNode(changeNode, filter)) continue;
8100
+ if (rejectPropertyChangeNode(changeNode, localName)) rejectedCount += 1;
8101
+ }
8102
+ }
8103
+ return {
8104
+ oxml: serializer.serializeToString(xmlDoc),
8105
+ hasChanges: rejectedCount > 0,
8106
+ rejectedCount,
8107
+ warnings
8108
+ };
8109
+ }
8110
+ function collectCommentTargetIds(xmlDoc, filter) {
8111
+ const targetIds = /* @__PURE__ */ new Set();
8112
+ const commentNodes = getWordElementsByLocalName(xmlDoc, "comment");
8113
+ for (const commentNode of commentNodes) {
8114
+ if (!authorMatchesNode(commentNode, filter)) continue;
8115
+ const id = getAttributeByLocalName(commentNode, "id");
8116
+ if (id) targetIds.add(id);
8117
+ }
8118
+ return { targetIds, commentNodes };
8119
+ }
8120
+ function removeCommentNodesById(commentNodes, targetIds) {
8121
+ let removed = 0;
8122
+ for (const commentNode of commentNodes) {
8123
+ const id = getAttributeByLocalName(commentNode, "id");
8124
+ if (!id || !targetIds.has(id)) continue;
8125
+ if (removeNode(commentNode)) removed += 1;
8126
+ }
8127
+ return removed;
8128
+ }
8129
+ function runIsOnlyCommentReference(runNode) {
8130
+ if (!isWordElement4(runNode, "r")) return false;
8131
+ const meaningfulChildren = Array.from(runNode.childNodes || []).filter((child) => {
8132
+ if (child.nodeType === 3) return String(child.nodeValue || "").trim().length > 0;
8133
+ if (child.nodeType !== 1) return false;
8134
+ if (child.namespaceURI !== NS_W) return true;
8135
+ const local = String(child.localName || "").toLowerCase();
8136
+ return local !== "rpr" && local !== "commentreference";
8137
+ });
8138
+ return meaningfulChildren.length === 0;
8139
+ }
8140
+ function removeCommentAnchors(xmlDoc, targetIds) {
8141
+ let removed = 0;
8142
+ const anchorTags = ["commentRangeStart", "commentRangeEnd", "commentReference"];
8143
+ for (const localName of anchorTags) {
8144
+ for (const node of getWordElementsByLocalName(xmlDoc, localName)) {
8145
+ if (!node.parentNode) continue;
8146
+ const id = getAttributeByLocalName(node, "id");
8147
+ if (!id || !targetIds.has(id)) continue;
8148
+ if (localName === "commentReference" && runIsOnlyCommentReference(node.parentNode)) {
8149
+ if (removeNode(node.parentNode)) {
8150
+ removed += 1;
8151
+ }
8152
+ continue;
8153
+ }
8154
+ if (removeNode(node)) {
8155
+ removed += 1;
8156
+ }
8157
+ }
8158
+ }
8159
+ return removed;
8160
+ }
8161
+ function deleteCommentsByAuthorInOoxml(oxml, options = {}) {
8162
+ const warnings = [];
8163
+ const filter = resolveAuthorFilter(options);
8164
+ if (!filter.valid) {
8165
+ return {
8166
+ oxml,
8167
+ hasChanges: false,
8168
+ commentsRemoved: 0,
8169
+ referencesRemoved: 0,
8170
+ warnings: [filter.warning]
8171
+ };
8172
+ }
8173
+ const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
8174
+ if (!parseResult.xmlDoc) {
8175
+ return {
8176
+ oxml,
8177
+ hasChanges: false,
8178
+ commentsRemoved: 0,
8179
+ referencesRemoved: 0,
8180
+ warnings: [parseResult.warning]
8181
+ };
8182
+ }
8183
+ const { xmlDoc, serializer } = parseResult;
8184
+ const { targetIds, commentNodes } = collectCommentTargetIds(xmlDoc, filter);
8185
+ if (filter.allAuthors) {
8186
+ for (const localName of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
8187
+ for (const node of getWordElementsByLocalName(xmlDoc, localName)) {
8188
+ const id = getAttributeByLocalName(node, "id");
8189
+ if (id) targetIds.add(id);
8190
+ }
8191
+ }
8192
+ }
8193
+ const commentsRemoved = removeCommentNodesById(commentNodes, targetIds);
8194
+ const referencesRemoved = removeCommentAnchors(xmlDoc, targetIds);
8195
+ return {
8196
+ oxml: serializer.serializeToString(xmlDoc),
8197
+ hasChanges: commentsRemoved > 0 || referencesRemoved > 0,
8198
+ commentsRemoved,
8199
+ referencesRemoved,
8200
+ warnings
8201
+ };
8202
+ }
8203
+
7904
8204
  // engine/formatting-removal.js
7905
8205
  function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
7906
8206
  if (!rPr) return null;
@@ -8714,6 +9014,7 @@ export {
8714
9014
  RoutePlanKind,
8715
9015
  RunKind,
8716
9016
  WORD_MAIN_NS,
9017
+ acceptTrackedChangesInOoxml,
8717
9018
  applyFormattingRemovalToOoxml,
8718
9019
  applyHighlightToOoxml,
8719
9020
  applyRedlineToOxml2 as applyRedlineToOxml,
@@ -8730,6 +9031,7 @@ export {
8730
9031
  configureLogger,
8731
9032
  configureXmlProvider,
8732
9033
  createDynamicNumberingIdState,
9034
+ deleteCommentsByAuthorInOoxml,
8733
9035
  enforceListBindingOnParagraphNodes,
8734
9036
  ensureCommentsArtifactsInZip,
8735
9037
  ensureNumberingArtifactsInZip,
@@ -8775,6 +9077,7 @@ export {
8775
9077
  preprocessMarkdown,
8776
9078
  reconcileMarkdownTableOoxml,
8777
9079
  recordSingleLineListFallbackExplicitSequence,
9080
+ rejectTrackedChangesInOoxml,
8778
9081
  remapNumberingPayloadForDocument,
8779
9082
  removeFormattingFromRPr,
8780
9083
  reserveNextNumberingId,