@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 +38 -1
- package/ARCHITECTURE.md +7 -2
- package/README.md +50 -3
- package/dist/docx-redline-js.esm.js +304 -1
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +65 -65
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/index.js +11 -6
- package/package.json +1 -1
- package/services/revision-comment-management.js +387 -0
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.
|
|
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
|
|
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
|
-
|
|
168
|
-
|
|
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.
|
|
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,
|