@ansonlai/docx-redline-js 0.1.3 → 0.1.6
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 +91 -5
- package/ARCHITECTURE.md +62 -9
- package/README.md +94 -3
- package/core/types.js +35 -8
- package/core/word-xml.js +90 -0
- package/dist/docx-redline-js.esm.js +1149 -367
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +78 -74
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/VALIDATION.md +48 -0
- package/docs/plans/2026-03-01-release-0.1.4-design.md +31 -0
- package/docs/plans/2026-03-01-release-0.1.4.md +108 -0
- package/engine/format-application.js +13 -14
- package/engine/format-span-application.js +7 -6
- package/engine/formatting-removal.js +15 -12
- package/engine/oxml-engine.js +146 -55
- package/engine/reconstruction-mapper.js +35 -8
- package/engine/reconstruction-mode.js +14 -13
- package/engine/reconstruction-writer.js +97 -78
- package/engine/rpr-helpers.js +34 -32
- package/engine/run-builders.js +150 -39
- package/engine/surgical-diff-application.js +216 -0
- package/engine/surgical-mode.js +84 -519
- package/engine/surgical-run-splitting.js +96 -0
- package/engine/surgical-spans.js +169 -0
- package/engine/table-cell-context.js +15 -13
- package/engine/table-mode.js +39 -35
- package/index.d.ts +148 -0
- package/index.js +26 -19
- package/package.json +8 -1
- package/pipeline/ingestion-export.js +1 -0
- package/pipeline/ingestion-paragraph.js +37 -12
- package/pipeline/ingestion-table.js +11 -8
- package/scripts/build.mjs +35 -0
- package/scripts/check-types.mjs +28 -0
- package/scripts/export-validation-fixtures.mjs +68 -0
- package/scripts/run-tests.mjs +43 -0
- package/scripts/word-com-smoke.ps1 +48 -0
- package/services/comment-locator.js +10 -9
- package/services/revision-comment-management.js +501 -0
- package/services/standalone-operation-runner.js +119 -69
- package/services/table-reconciliation.js +7 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.1.
|
|
1
|
+
// @ansonlai/docx-redline-js v0.1.6 — 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;
|
|
@@ -1797,6 +1797,23 @@ function createRevisionMetadata(author) {
|
|
|
1797
1797
|
date: getRevisionTimestamp()
|
|
1798
1798
|
};
|
|
1799
1799
|
}
|
|
1800
|
+
function seedRevisionIdsFromDocument(xmlDoc) {
|
|
1801
|
+
let maxFound = -1;
|
|
1802
|
+
const elements = Array.from(xmlDoc?.getElementsByTagName?.("*") || []);
|
|
1803
|
+
for (const element of elements) {
|
|
1804
|
+
for (const attr of Array.from(element.attributes || [])) {
|
|
1805
|
+
if ((attr.localName || "").toLowerCase() !== "id") continue;
|
|
1806
|
+
const parsed = Number.parseInt(attr.value, 10);
|
|
1807
|
+
if (Number.isFinite(parsed)) {
|
|
1808
|
+
maxFound = Math.max(maxFound, parsed);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (maxFound >= revisionIdCounter) {
|
|
1813
|
+
revisionIdCounter = maxFound + 1;
|
|
1814
|
+
}
|
|
1815
|
+
return revisionIdCounter;
|
|
1816
|
+
}
|
|
1800
1817
|
|
|
1801
1818
|
// core/paragraph-offset-policy.js
|
|
1802
1819
|
var PARAGRAPH_BOUNDARY_TEXT = "\n";
|
|
@@ -2016,6 +2033,26 @@ function getNodeHandlers(runModel) {
|
|
|
2016
2033
|
}
|
|
2017
2034
|
return { offset, text: "" };
|
|
2018
2035
|
});
|
|
2036
|
+
handlers.set("moveFrom", (child, offset) => {
|
|
2037
|
+
const deletionEntry = processDeletion(child, offset);
|
|
2038
|
+
if (deletionEntry) {
|
|
2039
|
+
runModel.push(deletionEntry);
|
|
2040
|
+
}
|
|
2041
|
+
return { offset, text: "" };
|
|
2042
|
+
});
|
|
2043
|
+
handlers.set("moveTo", (child, offset) => processNodeRecursive(child, offset, runModel));
|
|
2044
|
+
for (const markerName of ["moveFromRangeStart", "moveFromRangeEnd", "moveToRangeStart", "moveToRangeEnd"]) {
|
|
2045
|
+
handlers.set(markerName, (child, offset) => {
|
|
2046
|
+
runModel.push({
|
|
2047
|
+
kind: RunKind.BOOKMARK,
|
|
2048
|
+
nodeXml: serializeXml(child),
|
|
2049
|
+
startOffset: offset,
|
|
2050
|
+
endOffset: offset,
|
|
2051
|
+
text: ""
|
|
2052
|
+
});
|
|
2053
|
+
return { offset, text: "" };
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2019
2056
|
handlers.set("bookmarkStart", (child, offset) => {
|
|
2020
2057
|
runModel.push({
|
|
2021
2058
|
kind: RunKind.BOOKMARK,
|
|
@@ -2127,9 +2164,13 @@ function processDeletion(delElement, offset) {
|
|
|
2127
2164
|
function ingestTableToVirtualGrid(tableNode) {
|
|
2128
2165
|
const tblGrid = getFirstElementByTagNS(tableNode, NS_W, "tblGrid");
|
|
2129
2166
|
const gridCols = tblGrid ? getElementsByTagNS(tblGrid, NS_W, "gridCol") : [];
|
|
2130
|
-
const
|
|
2131
|
-
const trElements = getElementsByTagNS(tableNode, NS_W, "tr");
|
|
2167
|
+
const trElements = getElementsByTagNSOrTag(tableNode, NS_W, "tr");
|
|
2132
2168
|
const rowCount = trElements.length;
|
|
2169
|
+
const inferredColCount = trElements.reduce((max, tr) => {
|
|
2170
|
+
const tcElements = getElementsByTagNSOrTag(tr, NS_W, "tc");
|
|
2171
|
+
return Math.max(max, tcElements.length);
|
|
2172
|
+
}, 0);
|
|
2173
|
+
const colCount = gridCols.length || inferredColCount;
|
|
2133
2174
|
const grid = Array.from(
|
|
2134
2175
|
{ length: rowCount },
|
|
2135
2176
|
() => Array.from({ length: colCount }, () => null)
|
|
@@ -2138,7 +2179,7 @@ function ingestTableToVirtualGrid(tableNode) {
|
|
|
2138
2179
|
const vMergeOrigins = /* @__PURE__ */ new Map();
|
|
2139
2180
|
for (let rowIdx = 0; rowIdx < trElements.length; rowIdx++) {
|
|
2140
2181
|
const tr = trElements[rowIdx];
|
|
2141
|
-
const tcElements =
|
|
2182
|
+
const tcElements = getElementsByTagNSOrTag(tr, NS_W, "tc");
|
|
2142
2183
|
let gridCol = 0;
|
|
2143
2184
|
for (let tcIdx = 0; tcIdx < tcElements.length; tcIdx++) {
|
|
2144
2185
|
const tc = tcElements[tcIdx];
|
|
@@ -3243,8 +3284,7 @@ var NumberingService = class {
|
|
|
3243
3284
|
// services/table-reconciliation.js
|
|
3244
3285
|
function generateTableOoxml(tableData, options = {}) {
|
|
3245
3286
|
const { generateRedlines = false, author = "AI" } = options;
|
|
3246
|
-
const
|
|
3247
|
-
const revId = generateRedlines ? getNextRevisionId() : null;
|
|
3287
|
+
const tableInsertMeta = generateRedlines ? createRevisionMetadata(author) : null;
|
|
3248
3288
|
const numCols = tableData.headers?.length || (tableData.rows?.[0]?.length || 1);
|
|
3249
3289
|
const tblPr = `
|
|
3250
3290
|
<w:tblPr>
|
|
@@ -3287,8 +3327,8 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
3287
3327
|
rowsXml += `<w:tr>${trPr}${cellsXml}</w:tr>`;
|
|
3288
3328
|
}
|
|
3289
3329
|
let tableXml = `<w:tbl>${tblPr}${tblGrid}${rowsXml}</w:tbl>`;
|
|
3290
|
-
if (
|
|
3291
|
-
tableXml = `<w:ins w:id="${
|
|
3330
|
+
if (tableInsertMeta) {
|
|
3331
|
+
tableXml = `<w:ins w:id="${tableInsertMeta.id}" w:author="${escapeXml(tableInsertMeta.author)}" w:date="${tableInsertMeta.date}">${tableXml}</w:ins>`;
|
|
3292
3332
|
}
|
|
3293
3333
|
return tableXml;
|
|
3294
3334
|
}
|
|
@@ -3931,6 +3971,55 @@ var ReconciliationPipeline = class {
|
|
|
3931
3971
|
}
|
|
3932
3972
|
};
|
|
3933
3973
|
|
|
3974
|
+
// core/word-xml.js
|
|
3975
|
+
function isWordElement(node, localName) {
|
|
3976
|
+
if (!node || node.nodeType !== 1) return false;
|
|
3977
|
+
if (node.namespaceURI === NS_W && node.localName === localName) return true;
|
|
3978
|
+
const nodeName = String(node.nodeName || "");
|
|
3979
|
+
return nodeName === `w:${localName}` || nodeName === localName;
|
|
3980
|
+
}
|
|
3981
|
+
function createWordElement(xmlDoc, qualifiedName) {
|
|
3982
|
+
return typeof xmlDoc.createElementNS === "function" ? xmlDoc.createElementNS(NS_W, qualifiedName) : xmlDoc.createElement(qualifiedName);
|
|
3983
|
+
}
|
|
3984
|
+
function wordElementsByLocalName(xmlDoc, localName) {
|
|
3985
|
+
const namespaced = Array.from(xmlDoc?.getElementsByTagNameNS?.(NS_W, localName) || []);
|
|
3986
|
+
if (namespaced.length > 0) return namespaced;
|
|
3987
|
+
return Array.from(xmlDoc?.getElementsByTagName?.("*") || []).filter((node) => isWordElement(node, localName));
|
|
3988
|
+
}
|
|
3989
|
+
function containsTrackedChanges(xmlDoc) {
|
|
3990
|
+
const trackedChangeNames = [
|
|
3991
|
+
"ins",
|
|
3992
|
+
"del",
|
|
3993
|
+
"moveFrom",
|
|
3994
|
+
"moveTo",
|
|
3995
|
+
"moveFromRangeStart",
|
|
3996
|
+
"moveFromRangeEnd",
|
|
3997
|
+
"moveToRangeStart",
|
|
3998
|
+
"moveToRangeEnd",
|
|
3999
|
+
"rPrChange",
|
|
4000
|
+
"pPrChange",
|
|
4001
|
+
"cellIns",
|
|
4002
|
+
"cellDel"
|
|
4003
|
+
];
|
|
4004
|
+
return trackedChangeNames.some((localName) => wordElementsByLocalName(xmlDoc, localName).length > 0);
|
|
4005
|
+
}
|
|
4006
|
+
function classifyOoxmlSourceType(oxml) {
|
|
4007
|
+
const trimmed = String(oxml || "").trim();
|
|
4008
|
+
if (/^<\?xml\b[^>]*>\s*<pkg:package\b/i.test(trimmed) || /^<pkg:package\b/i.test(trimmed)) {
|
|
4009
|
+
return "package";
|
|
4010
|
+
}
|
|
4011
|
+
if (/^<\?xml\b[^>]*>\s*<(?:w:)?document\b/i.test(trimmed) || /^<(?:w:)?document\b/i.test(trimmed)) {
|
|
4012
|
+
return "document";
|
|
4013
|
+
}
|
|
4014
|
+
return "fragment";
|
|
4015
|
+
}
|
|
4016
|
+
function withOoxmlSourceType(result) {
|
|
4017
|
+
if (!result || typeof result !== "object" || result.sourceType || typeof result.oxml !== "string") {
|
|
4018
|
+
return result;
|
|
4019
|
+
}
|
|
4020
|
+
return { ...result, sourceType: classifyOoxmlSourceType(result.oxml) };
|
|
4021
|
+
}
|
|
4022
|
+
|
|
3934
4023
|
// engine/rpr-helpers.js
|
|
3935
4024
|
var RPR_SCHEMA_ORDER = [
|
|
3936
4025
|
"w:rStyle",
|
|
@@ -4018,28 +4107,28 @@ function _applyOverrides(xmlDoc, rPr, formatFlags, mode) {
|
|
|
4018
4107
|
}
|
|
4019
4108
|
}
|
|
4020
4109
|
if (applyBold) {
|
|
4021
|
-
const b = xmlDoc
|
|
4110
|
+
const b = createWordElement(xmlDoc, "w:b");
|
|
4022
4111
|
b.setAttribute("w:val", mode === "add" ? "1" : "0");
|
|
4023
4112
|
insertRPrChildInOrder(rPr, b);
|
|
4024
|
-
const bCs = xmlDoc
|
|
4113
|
+
const bCs = createWordElement(xmlDoc, "w:bCs");
|
|
4025
4114
|
bCs.setAttribute("w:val", mode === "add" ? "1" : "0");
|
|
4026
4115
|
insertRPrChildInOrder(rPr, bCs);
|
|
4027
4116
|
}
|
|
4028
4117
|
if (applyItalic) {
|
|
4029
|
-
const i = xmlDoc
|
|
4118
|
+
const i = createWordElement(xmlDoc, "w:i");
|
|
4030
4119
|
i.setAttribute("w:val", mode === "add" ? "1" : "0");
|
|
4031
4120
|
insertRPrChildInOrder(rPr, i);
|
|
4032
|
-
const iCs = xmlDoc
|
|
4121
|
+
const iCs = createWordElement(xmlDoc, "w:iCs");
|
|
4033
4122
|
iCs.setAttribute("w:val", mode === "add" ? "1" : "0");
|
|
4034
4123
|
insertRPrChildInOrder(rPr, iCs);
|
|
4035
4124
|
}
|
|
4036
4125
|
if (applyUnderline) {
|
|
4037
|
-
const u = xmlDoc
|
|
4126
|
+
const u = createWordElement(xmlDoc, "w:u");
|
|
4038
4127
|
u.setAttribute("w:val", mode === "add" ? "single" : "none");
|
|
4039
4128
|
insertRPrChildInOrder(rPr, u);
|
|
4040
4129
|
}
|
|
4041
4130
|
if (applyStrike) {
|
|
4042
|
-
const strike = xmlDoc
|
|
4131
|
+
const strike = createWordElement(xmlDoc, "w:strike");
|
|
4043
4132
|
strike.setAttribute("w:val", mode === "add" ? "1" : "0");
|
|
4044
4133
|
insertRPrChildInOrder(rPr, strike);
|
|
4045
4134
|
}
|
|
@@ -4080,7 +4169,7 @@ function isFormattingElementEnabled(element, isUnderline) {
|
|
|
4080
4169
|
|
|
4081
4170
|
// engine/format-extraction.js
|
|
4082
4171
|
var NS_W3 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
4083
|
-
function
|
|
4172
|
+
function isWordElement2(node, localName) {
|
|
4084
4173
|
if (!node || node.nodeType !== 1) return false;
|
|
4085
4174
|
if (node.namespaceURI === NS_W3 && node.localName === localName) return true;
|
|
4086
4175
|
const nodeName = String(node.nodeName || "");
|
|
@@ -4097,7 +4186,7 @@ function collectParagraphRuns(paragraph) {
|
|
|
4097
4186
|
const node = stack.pop();
|
|
4098
4187
|
if (!node || node.nodeType !== 1) continue;
|
|
4099
4188
|
if (isExcludedRevisionContainer(node)) continue;
|
|
4100
|
-
if (
|
|
4189
|
+
if (isWordElement2(node, "r")) {
|
|
4101
4190
|
runs.push(node);
|
|
4102
4191
|
continue;
|
|
4103
4192
|
}
|
|
@@ -4132,7 +4221,7 @@ function buildTextSpansFromParagraphs(paragraphs) {
|
|
|
4132
4221
|
for (const run of runs) {
|
|
4133
4222
|
const rPr = getFirstElementByTag(run, "w:rPr");
|
|
4134
4223
|
Array.from(run.childNodes || []).forEach((rc) => {
|
|
4135
|
-
if (
|
|
4224
|
+
if (isWordElement2(rc, "t")) {
|
|
4136
4225
|
const text = rc.textContent || "";
|
|
4137
4226
|
if (text.length > 0) {
|
|
4138
4227
|
textSpans.push({
|
|
@@ -4146,7 +4235,7 @@ function buildTextSpansFromParagraphs(paragraphs) {
|
|
|
4146
4235
|
});
|
|
4147
4236
|
charOffset += text.length;
|
|
4148
4237
|
}
|
|
4149
|
-
} else if (
|
|
4238
|
+
} else if (isWordElement2(rc, "br") || isWordElement2(rc, "cr") || isWordElement2(rc, "tab") || isWordElement2(rc, "noBreakHyphen")) {
|
|
4150
4239
|
textSpans.push({
|
|
4151
4240
|
charStart: charOffset,
|
|
4152
4241
|
charEnd: charOffset + 1,
|
|
@@ -4167,7 +4256,7 @@ function buildTextSpansFromParagraphs(paragraphs) {
|
|
|
4167
4256
|
function processRunForFormatting(run, paragraph, charOffset, textSpans, formatHints, pFormat = null) {
|
|
4168
4257
|
let rPr = null;
|
|
4169
4258
|
for (const child of Array.from(run.childNodes)) {
|
|
4170
|
-
if (
|
|
4259
|
+
if (isWordElement2(child, "rPr")) {
|
|
4171
4260
|
rPr = child;
|
|
4172
4261
|
break;
|
|
4173
4262
|
}
|
|
@@ -4182,7 +4271,7 @@ function processRunForFormatting(run, paragraph, charOffset, textSpans, formatHi
|
|
|
4182
4271
|
format.hasFormatting = format.bold || format.italic || format.underline || format.strikethrough;
|
|
4183
4272
|
let currentOffset = charOffset;
|
|
4184
4273
|
for (const child of Array.from(run.childNodes)) {
|
|
4185
|
-
if (
|
|
4274
|
+
if (isWordElement2(child, "t")) {
|
|
4186
4275
|
const text = child.textContent || "";
|
|
4187
4276
|
if (text.length > 0) {
|
|
4188
4277
|
const start = currentOffset;
|
|
@@ -4220,9 +4309,9 @@ function extractFormattingFromOoxml(xmlDoc) {
|
|
|
4220
4309
|
const p = paragraphs[pIndex];
|
|
4221
4310
|
let pRPr = null;
|
|
4222
4311
|
for (const child of Array.from(p.childNodes)) {
|
|
4223
|
-
if (
|
|
4312
|
+
if (isWordElement2(child, "pPr")) {
|
|
4224
4313
|
for (const pChild of Array.from(child.childNodes)) {
|
|
4225
|
-
if (
|
|
4314
|
+
if (isWordElement2(pChild, "rPr")) {
|
|
4226
4315
|
pRPr = pChild;
|
|
4227
4316
|
break;
|
|
4228
4317
|
}
|
|
@@ -4246,7 +4335,7 @@ function extractFormattingFromOoxml(xmlDoc) {
|
|
|
4246
4335
|
|
|
4247
4336
|
// engine/run-builders.js
|
|
4248
4337
|
function createTrackChange(xmlDoc, type, run, author) {
|
|
4249
|
-
const wrapper = xmlDoc
|
|
4338
|
+
const wrapper = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
4250
4339
|
const metadata = createRevisionMetadata(author);
|
|
4251
4340
|
wrapper.setAttribute("w:id", String(metadata.id));
|
|
4252
4341
|
wrapper.setAttribute("w:author", metadata.author);
|
|
@@ -4256,10 +4345,60 @@ function createTrackChange(xmlDoc, type, run, author) {
|
|
|
4256
4345
|
}
|
|
4257
4346
|
return wrapper;
|
|
4258
4347
|
}
|
|
4348
|
+
function getDirectWordChild(node, localName) {
|
|
4349
|
+
return Array.from(node?.childNodes || []).find((child) => child.nodeType === 1 && (child.localName === localName || child.nodeName === `w:${localName}`)) || null;
|
|
4350
|
+
}
|
|
4351
|
+
function ensureParagraphProperties(xmlDoc, paragraph) {
|
|
4352
|
+
let pPr = getDirectWordChild(paragraph, "pPr");
|
|
4353
|
+
if (!pPr) {
|
|
4354
|
+
pPr = createWordElement(xmlDoc, "w:pPr");
|
|
4355
|
+
paragraph.insertBefore(pPr, paragraph.firstChild || null);
|
|
4356
|
+
} else if (paragraph.firstChild !== pPr) {
|
|
4357
|
+
paragraph.insertBefore(pPr, paragraph.firstChild || null);
|
|
4358
|
+
}
|
|
4359
|
+
return pPr;
|
|
4360
|
+
}
|
|
4361
|
+
function ensureParagraphMarkRunProperties(xmlDoc, pPr) {
|
|
4362
|
+
let rPr = getDirectWordChild(pPr, "rPr");
|
|
4363
|
+
if (!rPr) {
|
|
4364
|
+
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
4365
|
+
pPr.appendChild(rPr);
|
|
4366
|
+
} else if (pPr.lastChild !== rPr) {
|
|
4367
|
+
pPr.appendChild(rPr);
|
|
4368
|
+
}
|
|
4369
|
+
return rPr;
|
|
4370
|
+
}
|
|
4371
|
+
function markParagraphMark(xmlDoc, paragraph, author, type) {
|
|
4372
|
+
const pPr = ensureParagraphProperties(xmlDoc, paragraph);
|
|
4373
|
+
const rPr = ensureParagraphMarkRunProperties(xmlDoc, pPr);
|
|
4374
|
+
for (const child of Array.from(rPr.childNodes || [])) {
|
|
4375
|
+
if (child.nodeType !== 1) continue;
|
|
4376
|
+
if (child.localName === "ins" || child.localName === "del" || child.nodeName === "w:ins" || child.nodeName === "w:del") {
|
|
4377
|
+
rPr.removeChild(child);
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
const marker = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
4381
|
+
const metadata = createRevisionMetadata(author);
|
|
4382
|
+
marker.setAttribute("w:id", String(metadata.id));
|
|
4383
|
+
marker.setAttribute("w:author", metadata.author);
|
|
4384
|
+
marker.setAttribute("w:date", metadata.date);
|
|
4385
|
+
rPr.appendChild(marker);
|
|
4386
|
+
return marker;
|
|
4387
|
+
}
|
|
4388
|
+
function markParagraphMarkInserted(xmlDoc, paragraph, author) {
|
|
4389
|
+
return markParagraphMark(xmlDoc, paragraph, author, "ins");
|
|
4390
|
+
}
|
|
4391
|
+
function markParagraphMarkDeleted(xmlDoc, paragraph, author) {
|
|
4392
|
+
return markParagraphMark(xmlDoc, paragraph, author, "del");
|
|
4393
|
+
}
|
|
4259
4394
|
function createTextRun(xmlDoc, text, rPr, isDelete) {
|
|
4260
|
-
const run = xmlDoc
|
|
4395
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
4261
4396
|
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
4262
|
-
|
|
4397
|
+
if (!isDelete) {
|
|
4398
|
+
appendVisibleTextPieces(xmlDoc, run, text);
|
|
4399
|
+
return run;
|
|
4400
|
+
}
|
|
4401
|
+
const textEl = createWordElement(xmlDoc, isDelete ? "w:delText" : "w:t");
|
|
4263
4402
|
textEl.setAttribute("xml:space", "preserve");
|
|
4264
4403
|
textEl.textContent = text;
|
|
4265
4404
|
run.appendChild(textEl);
|
|
@@ -4296,16 +4435,45 @@ function createFormattedRuns(xmlDoc, text, baseRPr, formatHints, baseOffset, aut
|
|
|
4296
4435
|
return runs;
|
|
4297
4436
|
}
|
|
4298
4437
|
function createTextRunWithRPrElement(xmlDoc, text, rPrElement, isDelete) {
|
|
4299
|
-
const run = xmlDoc
|
|
4438
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
4300
4439
|
if (rPrElement) run.appendChild(rPrElement);
|
|
4301
|
-
|
|
4440
|
+
if (!isDelete) {
|
|
4441
|
+
appendVisibleTextPieces(xmlDoc, run, text);
|
|
4442
|
+
return run;
|
|
4443
|
+
}
|
|
4444
|
+
const textEl = createWordElement(xmlDoc, isDelete ? "w:delText" : "w:t");
|
|
4302
4445
|
textEl.setAttribute("xml:space", "preserve");
|
|
4303
4446
|
textEl.textContent = text;
|
|
4304
4447
|
run.appendChild(textEl);
|
|
4305
4448
|
return run;
|
|
4306
4449
|
}
|
|
4450
|
+
function appendVisibleTextPieces(xmlDoc, run, text) {
|
|
4451
|
+
const source = String(text || "");
|
|
4452
|
+
const parts = source.split(/(\t|\n|\u2011)/);
|
|
4453
|
+
for (const part of parts) {
|
|
4454
|
+
if (!part) continue;
|
|
4455
|
+
if (part === " ") {
|
|
4456
|
+
run.appendChild(createWordElement(xmlDoc, "w:tab"));
|
|
4457
|
+
continue;
|
|
4458
|
+
}
|
|
4459
|
+
if (part === "\n") {
|
|
4460
|
+
run.appendChild(createWordElement(xmlDoc, "w:br"));
|
|
4461
|
+
continue;
|
|
4462
|
+
}
|
|
4463
|
+
if (part === "\u2011") {
|
|
4464
|
+
run.appendChild(createWordElement(xmlDoc, "w:noBreakHyphen"));
|
|
4465
|
+
continue;
|
|
4466
|
+
}
|
|
4467
|
+
const textEl = createWordElement(xmlDoc, "w:t");
|
|
4468
|
+
if (/^\s|\s$/.test(part)) {
|
|
4469
|
+
textEl.setAttribute("xml:space", "preserve");
|
|
4470
|
+
}
|
|
4471
|
+
textEl.textContent = part;
|
|
4472
|
+
run.appendChild(textEl);
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4307
4475
|
function injectFormattingToRPr(xmlDoc, baseRPr, format, author, generateRedlines) {
|
|
4308
|
-
const rPr = xmlDoc
|
|
4476
|
+
const rPr = createWordElement(xmlDoc, "w:rPr");
|
|
4309
4477
|
if (baseRPr) {
|
|
4310
4478
|
Array.from(baseRPr.childNodes).forEach((child) => {
|
|
4311
4479
|
if (!["w:b", "w:bCs", "w:i", "w:iCs", "w:u", "w:strike", "w:rPrChange"].includes(child.nodeName)) {
|
|
@@ -4318,7 +4486,7 @@ function injectFormattingToRPr(xmlDoc, baseRPr, format, author, generateRedlines
|
|
|
4318
4486
|
createRPrChange(xmlDoc, rPr, author, baseRPr);
|
|
4319
4487
|
}
|
|
4320
4488
|
const syncElement = (tagName, isOn, valOn = null, valOff = "0") => {
|
|
4321
|
-
const el = xmlDoc
|
|
4489
|
+
const el = createWordElement(xmlDoc, tagName);
|
|
4322
4490
|
if (isOn) {
|
|
4323
4491
|
if (valOn) el.setAttribute("w:val", valOn);
|
|
4324
4492
|
} else if (valOff) {
|
|
@@ -4348,11 +4516,12 @@ function injectFormattingToRPr(xmlDoc, baseRPr, format, author, generateRedlines
|
|
|
4348
4516
|
return rPr;
|
|
4349
4517
|
}
|
|
4350
4518
|
function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceNode) {
|
|
4351
|
-
const rPrChange = xmlDoc
|
|
4352
|
-
|
|
4353
|
-
rPrChange.setAttribute("w:
|
|
4354
|
-
rPrChange.setAttribute("w:
|
|
4355
|
-
|
|
4519
|
+
const rPrChange = createWordElement(xmlDoc, "w:rPrChange");
|
|
4520
|
+
const metadata = createRevisionMetadata(author);
|
|
4521
|
+
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
4522
|
+
rPrChange.setAttribute("w:author", metadata.author);
|
|
4523
|
+
rPrChange.setAttribute("w:date", dateStr || metadata.date);
|
|
4524
|
+
const previousRPr = createWordElement(xmlDoc, "w:rPr");
|
|
4356
4525
|
const source = sourceNode || rPr;
|
|
4357
4526
|
Array.from(source.childNodes).forEach((child) => {
|
|
4358
4527
|
if (child.nodeName !== "w:rPrChange") {
|
|
@@ -4368,12 +4537,12 @@ function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceNode) {
|
|
|
4368
4537
|
return rPrChange;
|
|
4369
4538
|
}
|
|
4370
4539
|
function createRPrChange(xmlDoc, rPr, author, previousRPrArg) {
|
|
4371
|
-
snapshotAndAttachRPrChange(xmlDoc, rPr, author,
|
|
4540
|
+
snapshotAndAttachRPrChange(xmlDoc, rPr, author, null, previousRPrArg || rPr);
|
|
4372
4541
|
}
|
|
4373
4542
|
|
|
4374
4543
|
// engine/format-paragraph-targeting.js
|
|
4375
4544
|
var NS_W4 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
4376
|
-
function
|
|
4545
|
+
function isWordElement3(node, localName) {
|
|
4377
4546
|
if (!node || node.nodeType !== 1) return false;
|
|
4378
4547
|
if (node.namespaceURI === NS_W4 && node.localName === localName) return true;
|
|
4379
4548
|
const nodeName = String(node.nodeName || "");
|
|
@@ -4475,7 +4644,7 @@ function findTargetParagraphInfo(paragraphInfos, originalText) {
|
|
|
4475
4644
|
function getContainingParagraph(node) {
|
|
4476
4645
|
let current = node;
|
|
4477
4646
|
while (current) {
|
|
4478
|
-
if (
|
|
4647
|
+
if (isWordElement3(current, "p")) return current;
|
|
4479
4648
|
current = current.parentNode;
|
|
4480
4649
|
}
|
|
4481
4650
|
return null;
|
|
@@ -4486,13 +4655,13 @@ function buildParagraphTextFromSpans(spans) {
|
|
|
4486
4655
|
for (const span of spans) {
|
|
4487
4656
|
if (!span || !span.textElement) continue;
|
|
4488
4657
|
const textElement = span.textElement;
|
|
4489
|
-
if (
|
|
4658
|
+
if (isWordElement3(textElement, "t")) {
|
|
4490
4659
|
text += span.textElement.textContent || "";
|
|
4491
|
-
} else if (
|
|
4660
|
+
} else if (isWordElement3(textElement, "tab")) {
|
|
4492
4661
|
text += " ";
|
|
4493
|
-
} else if (
|
|
4662
|
+
} else if (isWordElement3(textElement, "br") || isWordElement3(textElement, "cr")) {
|
|
4494
4663
|
text += "\n";
|
|
4495
|
-
} else if (
|
|
4664
|
+
} else if (isWordElement3(textElement, "noBreakHyphen")) {
|
|
4496
4665
|
text += "\u2011";
|
|
4497
4666
|
}
|
|
4498
4667
|
}
|
|
@@ -4556,7 +4725,7 @@ function splitSpanAtOffset(xmlDoc, span, absoluteOffset) {
|
|
|
4556
4725
|
|
|
4557
4726
|
// engine/format-application.js
|
|
4558
4727
|
var NS_W5 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
4559
|
-
function
|
|
4728
|
+
function isWordElement4(node, localName) {
|
|
4560
4729
|
if (!node || node.nodeType !== 1) return false;
|
|
4561
4730
|
if (node.namespaceURI === NS_W5 && node.localName === localName) return true;
|
|
4562
4731
|
const nodeName = String(node.nodeName || "");
|
|
@@ -4586,7 +4755,6 @@ function normalizePrecomputedFormatContext(precomputedContext) {
|
|
|
4586
4755
|
function applyFormatRemovalAsSurgicalReplacement(xmlDoc, textSpans, existingFormatHints, serializer, author, generateRedlines = true) {
|
|
4587
4756
|
let hasAnyChanges = false;
|
|
4588
4757
|
const processedRuns = /* @__PURE__ */ new Set();
|
|
4589
|
-
const dateStr = getRevisionTimestamp();
|
|
4590
4758
|
log(`[OxmlEngine] Surgical format removal: ${existingFormatHints.length} hints to process (using w:rPrChange)`);
|
|
4591
4759
|
for (const hint of existingFormatHints) {
|
|
4592
4760
|
const run = hint.run;
|
|
@@ -4596,11 +4764,11 @@ function applyFormatRemovalAsSurgicalReplacement(xmlDoc, textSpans, existingForm
|
|
|
4596
4764
|
log("[OxmlEngine] Processing run for surgical format removal, format:", hint.format);
|
|
4597
4765
|
let rPr = getFirstElementByTag(run, "w:rPr");
|
|
4598
4766
|
if (!rPr) {
|
|
4599
|
-
rPr = xmlDoc
|
|
4767
|
+
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
4600
4768
|
run.insertBefore(rPr, run.firstChild);
|
|
4601
4769
|
}
|
|
4602
4770
|
if (generateRedlines) {
|
|
4603
|
-
snapshotAndAttachRPrChange(xmlDoc, rPr, author || getDefaultAuthor()
|
|
4771
|
+
snapshotAndAttachRPrChange(xmlDoc, rPr, author || getDefaultAuthor());
|
|
4604
4772
|
}
|
|
4605
4773
|
applyFormatOverridesToRPr(xmlDoc, rPr, hint.format);
|
|
4606
4774
|
hasAnyChanges = true;
|
|
@@ -4626,7 +4794,7 @@ function applyFormatAdditionsAsSurgicalReplacement(xmlDoc, textSpans, formatHint
|
|
|
4626
4794
|
const orderedSpans = currentSpans.slice().sort((a, b) => a.charStart - b.charStart || a.charEnd - b.charEnd);
|
|
4627
4795
|
const getOverlappingHints = createFormatHintOverlapLookup(formatHints);
|
|
4628
4796
|
for (const span of orderedSpans) {
|
|
4629
|
-
if (!span || !span.textElement || !
|
|
4797
|
+
if (!span || !span.textElement || !isWordElement4(span.textElement, "t")) continue;
|
|
4630
4798
|
const applicableHints = getOverlappingHints(span.charStart, span.charEnd);
|
|
4631
4799
|
if (applicableHints.length === 0) continue;
|
|
4632
4800
|
const mergedDesiredFormat = mergeFormats(...applicableHints.map((h) => h.format));
|
|
@@ -4746,7 +4914,7 @@ function createFormatHintOverlapLookup(formatHints) {
|
|
|
4746
4914
|
var W14_NS = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
4747
4915
|
function detectTableCellContext(xmlDoc, originalText, options = {}) {
|
|
4748
4916
|
const { targetParagraphId = null } = options;
|
|
4749
|
-
const tables =
|
|
4917
|
+
const tables = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
4750
4918
|
if (tables.length === 0) {
|
|
4751
4919
|
return { hasTableWrapper: false, isTableCellParagraph: false, paragraphs: [], paragraph: null, tableElement: null };
|
|
4752
4920
|
}
|
|
@@ -4754,7 +4922,7 @@ function detectTableCellContext(xmlDoc, originalText, options = {}) {
|
|
|
4754
4922
|
const paragraphsInCells = allParagraphs.filter((p) => {
|
|
4755
4923
|
let parent = p.parentNode;
|
|
4756
4924
|
while (parent) {
|
|
4757
|
-
if (parent
|
|
4925
|
+
if (isWordElement(parent, "tc")) return true;
|
|
4758
4926
|
parent = parent.parentNode;
|
|
4759
4927
|
}
|
|
4760
4928
|
return false;
|
|
@@ -4777,7 +4945,7 @@ function detectTableCellContext(xmlDoc, originalText, options = {}) {
|
|
|
4777
4945
|
const normalizedTarget = originalText.trim();
|
|
4778
4946
|
if (!targetParagraph) {
|
|
4779
4947
|
for (const p of paragraphsInCells) {
|
|
4780
|
-
const textNodes =
|
|
4948
|
+
const textNodes = getElementsByTagNSOrTag(p, NS_W, "t");
|
|
4781
4949
|
let paragraphText = "";
|
|
4782
4950
|
for (const t of textNodes) {
|
|
4783
4951
|
paragraphText += t.textContent || "";
|
|
@@ -4824,89 +4992,247 @@ function getParagraphId(paragraph) {
|
|
|
4824
4992
|
return paragraph.getAttribute("w14:paraId") || paragraph.getAttribute("w:paraId") || paragraph.getAttribute("paraId") || null;
|
|
4825
4993
|
}
|
|
4826
4994
|
|
|
4827
|
-
// engine/surgical-
|
|
4828
|
-
function
|
|
4995
|
+
// engine/surgical-spans.js
|
|
4996
|
+
function getRunChildText(child) {
|
|
4997
|
+
if (isWordElement(child, "t")) return child.textContent || "";
|
|
4998
|
+
if (isWordElement(child, "br") || isWordElement(child, "cr")) return "\n";
|
|
4999
|
+
if (isWordElement(child, "tab")) return " ";
|
|
5000
|
+
if (isWordElement(child, "noBreakHyphen")) return "\u2011";
|
|
5001
|
+
return "";
|
|
5002
|
+
}
|
|
5003
|
+
function isTextLikeRunChild(child) {
|
|
5004
|
+
return isWordElement(child, "t") || isWordElement(child, "br") || isWordElement(child, "cr") || isWordElement(child, "tab") || isWordElement(child, "noBreakHyphen");
|
|
5005
|
+
}
|
|
5006
|
+
function buildSurgicalTextSpans(paragraphs) {
|
|
4829
5007
|
let fullText = "";
|
|
4830
5008
|
const textSpans = [];
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
fullText += runResult.text;
|
|
4838
|
-
} else if (child.nodeName === "w:hyperlink") {
|
|
5009
|
+
paragraphs.forEach((paragraph, paragraphIndex) => {
|
|
5010
|
+
const container = paragraph.parentNode;
|
|
5011
|
+
for (let child = paragraph.firstChild; child; child = child.nextSibling) {
|
|
5012
|
+
if (isWordElement(child, "r")) {
|
|
5013
|
+
fullText += processRunElement(child, paragraph, container, fullText.length, textSpans).text;
|
|
5014
|
+
} else if (isWordElement(child, "hyperlink")) {
|
|
4839
5015
|
for (let hc = child.firstChild; hc; hc = hc.nextSibling) {
|
|
4840
|
-
if (hc
|
|
4841
|
-
|
|
4842
|
-
fullText += runResult.text;
|
|
5016
|
+
if (isWordElement(hc, "r")) {
|
|
5017
|
+
fullText += processRunElement(hc, paragraph, container, fullText.length, textSpans).text;
|
|
4843
5018
|
}
|
|
4844
5019
|
}
|
|
4845
5020
|
}
|
|
4846
5021
|
}
|
|
4847
|
-
fullText = appendParagraphBoundary(fullText,
|
|
5022
|
+
fullText = appendParagraphBoundary(fullText, paragraphIndex, paragraphs.length);
|
|
4848
5023
|
});
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
const
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
reconcileFormattingForTextSpan(xmlDoc, span, overlapStartOriginal, overlapEndOriginal, applicableHints, author, generateRedlines);
|
|
5024
|
+
return { fullText, textSpans };
|
|
5025
|
+
}
|
|
5026
|
+
function processRunElement(run, paragraph, container, currentOffset, textSpans) {
|
|
5027
|
+
const rPr = getFirstElementByTag(run, "w:rPr");
|
|
5028
|
+
let localOffset = currentOffset;
|
|
5029
|
+
const textParts = [];
|
|
5030
|
+
for (let child = run.firstChild; child; child = child.nextSibling) {
|
|
5031
|
+
if (isWordElement(child, "t")) {
|
|
5032
|
+
const text = child.textContent || "";
|
|
5033
|
+
if (text.length === 0) continue;
|
|
5034
|
+
textSpans.push({
|
|
5035
|
+
charStart: localOffset,
|
|
5036
|
+
charEnd: localOffset + text.length,
|
|
5037
|
+
textElement: child,
|
|
5038
|
+
runElement: run,
|
|
5039
|
+
paragraph,
|
|
5040
|
+
container,
|
|
5041
|
+
rPr
|
|
4868
5042
|
});
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
} else if (
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
5043
|
+
localOffset += text.length;
|
|
5044
|
+
textParts.push(text);
|
|
5045
|
+
} else if (isTextLikeRunChild(child)) {
|
|
5046
|
+
const text = getRunChildText(child);
|
|
5047
|
+
textSpans.push({
|
|
5048
|
+
charStart: localOffset,
|
|
5049
|
+
charEnd: localOffset + 1,
|
|
5050
|
+
textElement: child,
|
|
5051
|
+
runElement: run,
|
|
5052
|
+
paragraph,
|
|
5053
|
+
container,
|
|
5054
|
+
rPr
|
|
5055
|
+
});
|
|
5056
|
+
localOffset += 1;
|
|
5057
|
+
textParts.push(text);
|
|
4880
5058
|
}
|
|
4881
5059
|
}
|
|
4882
|
-
return {
|
|
5060
|
+
return { text: textParts.join("") };
|
|
5061
|
+
}
|
|
5062
|
+
function buildSpanIndex(textSpans) {
|
|
5063
|
+
const spans = textSpans.slice().sort((a, b) => a.charStart - b.charStart || a.charEnd - b.charEnd);
|
|
5064
|
+
const starts = spans.map((span) => span.charStart);
|
|
5065
|
+
const ends = spans.map((span) => span.charEnd);
|
|
5066
|
+
return { spans, starts, ends };
|
|
5067
|
+
}
|
|
5068
|
+
function forEachOverlappingSpan(spanIndex, startPos, endPos, callback) {
|
|
5069
|
+
if (endPos <= startPos || spanIndex.spans.length === 0) {
|
|
5070
|
+
return;
|
|
5071
|
+
}
|
|
5072
|
+
let index = upperBound(spanIndex.ends, startPos);
|
|
5073
|
+
while (index < spanIndex.spans.length) {
|
|
5074
|
+
const span = spanIndex.spans[index];
|
|
5075
|
+
if (span.charStart >= endPos) {
|
|
5076
|
+
break;
|
|
5077
|
+
}
|
|
5078
|
+
callback(span);
|
|
5079
|
+
index++;
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
function findContainingSpan(spanIndex, pos) {
|
|
5083
|
+
if (spanIndex.spans.length === 0) return null;
|
|
5084
|
+
const index = upperBound(spanIndex.starts, pos) - 1;
|
|
5085
|
+
if (index < 0) return null;
|
|
5086
|
+
const span = spanIndex.spans[index];
|
|
5087
|
+
return pos >= span.charStart && pos < span.charEnd ? span : null;
|
|
5088
|
+
}
|
|
5089
|
+
function findFirstSpanEndingAt(spanIndex, pos) {
|
|
5090
|
+
const index = lowerBound(spanIndex.ends, pos);
|
|
5091
|
+
if (index < spanIndex.spans.length && spanIndex.ends[index] === pos) {
|
|
5092
|
+
return spanIndex.spans[index];
|
|
5093
|
+
}
|
|
5094
|
+
return null;
|
|
5095
|
+
}
|
|
5096
|
+
function findLastSpanEndingBeforeOrAt(spanIndex, pos) {
|
|
5097
|
+
const index = upperBound(spanIndex.ends, pos) - 1;
|
|
5098
|
+
if (index >= 0) {
|
|
5099
|
+
return spanIndex.spans[index];
|
|
5100
|
+
}
|
|
5101
|
+
return null;
|
|
5102
|
+
}
|
|
5103
|
+
function upperBound(values, target) {
|
|
5104
|
+
let left = 0;
|
|
5105
|
+
let right = values.length;
|
|
5106
|
+
while (left < right) {
|
|
5107
|
+
const middle = left + right >> 1;
|
|
5108
|
+
if (values[middle] <= target) {
|
|
5109
|
+
left = middle + 1;
|
|
5110
|
+
} else {
|
|
5111
|
+
right = middle;
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
return left;
|
|
4883
5115
|
}
|
|
5116
|
+
function lowerBound(values, target) {
|
|
5117
|
+
let left = 0;
|
|
5118
|
+
let right = values.length;
|
|
5119
|
+
while (left < right) {
|
|
5120
|
+
const middle = left + right >> 1;
|
|
5121
|
+
if (values[middle] < target) {
|
|
5122
|
+
left = middle + 1;
|
|
5123
|
+
} else {
|
|
5124
|
+
right = middle;
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
5127
|
+
return left;
|
|
5128
|
+
}
|
|
5129
|
+
|
|
5130
|
+
// engine/surgical-run-splitting.js
|
|
5131
|
+
function getRunContentPieces(runElement) {
|
|
5132
|
+
const pieces = [];
|
|
5133
|
+
let offset = 0;
|
|
5134
|
+
for (const child of Array.from(runElement.childNodes || [])) {
|
|
5135
|
+
if (isWordElement(child, "rPr")) continue;
|
|
5136
|
+
if (!isTextLikeRunChild(child)) continue;
|
|
5137
|
+
const text = getRunChildText(child);
|
|
5138
|
+
if (text.length === 0) continue;
|
|
5139
|
+
pieces.push({
|
|
5140
|
+
node: child,
|
|
5141
|
+
start: offset,
|
|
5142
|
+
end: offset + text.length,
|
|
5143
|
+
text
|
|
5144
|
+
});
|
|
5145
|
+
offset += text.length;
|
|
5146
|
+
}
|
|
5147
|
+
return pieces;
|
|
5148
|
+
}
|
|
5149
|
+
function getRunTextLength(pieces) {
|
|
5150
|
+
if (pieces.length === 0) return 0;
|
|
5151
|
+
return pieces[pieces.length - 1].end;
|
|
5152
|
+
}
|
|
5153
|
+
function sliceRunPieces(xmlDoc, pieces, start, end, asDeletedText) {
|
|
5154
|
+
const sliced = [];
|
|
5155
|
+
if (end <= start) return sliced;
|
|
5156
|
+
pieces.forEach((piece) => {
|
|
5157
|
+
const overlapStart = Math.max(start, piece.start);
|
|
5158
|
+
const overlapEnd = Math.min(end, piece.end);
|
|
5159
|
+
if (overlapEnd <= overlapStart) return;
|
|
5160
|
+
const localStart = overlapStart - piece.start;
|
|
5161
|
+
const localEnd = overlapEnd - piece.start;
|
|
5162
|
+
const text = piece.text.slice(localStart, localEnd);
|
|
5163
|
+
sliced.push(cloneRunPiece(xmlDoc, piece.node, text, asDeletedText));
|
|
5164
|
+
});
|
|
5165
|
+
return sliced;
|
|
5166
|
+
}
|
|
5167
|
+
function createRunFromPieces(xmlDoc, pieces, rPr) {
|
|
5168
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
5169
|
+
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
5170
|
+
pieces.forEach((piece) => run.appendChild(piece));
|
|
5171
|
+
return run;
|
|
5172
|
+
}
|
|
5173
|
+
function insertRunPiecesBefore(xmlDoc, parent, referenceNode, pieces, rPr) {
|
|
5174
|
+
if (pieces.length === 0) return null;
|
|
5175
|
+
const run = createRunFromPieces(xmlDoc, pieces, rPr);
|
|
5176
|
+
parent.insertBefore(run, referenceNode);
|
|
5177
|
+
return run;
|
|
5178
|
+
}
|
|
5179
|
+
function cloneRunPiece(xmlDoc, sourceNode, text, asDeletedText) {
|
|
5180
|
+
if (asDeletedText) {
|
|
5181
|
+
const delText = createWordElement(xmlDoc, "w:delText");
|
|
5182
|
+
delText.setAttribute("xml:space", "preserve");
|
|
5183
|
+
delText.textContent = text;
|
|
5184
|
+
return delText;
|
|
5185
|
+
}
|
|
5186
|
+
if (isWordElement(sourceNode, "t")) {
|
|
5187
|
+
const textNode2 = sourceNode.cloneNode(false);
|
|
5188
|
+
textNode2.textContent = text;
|
|
5189
|
+
if (/^\s|\s$/.test(text)) {
|
|
5190
|
+
textNode2.setAttribute("xml:space", "preserve");
|
|
5191
|
+
}
|
|
5192
|
+
return textNode2;
|
|
5193
|
+
}
|
|
5194
|
+
if (text === "\n" && (isWordElement(sourceNode, "br") || isWordElement(sourceNode, "cr"))) {
|
|
5195
|
+
return sourceNode.cloneNode(true);
|
|
5196
|
+
}
|
|
5197
|
+
if (text === " " && isWordElement(sourceNode, "tab")) {
|
|
5198
|
+
return sourceNode.cloneNode(true);
|
|
5199
|
+
}
|
|
5200
|
+
if (text === "\u2011" && isWordElement(sourceNode, "noBreakHyphen")) {
|
|
5201
|
+
return sourceNode.cloneNode(true);
|
|
5202
|
+
}
|
|
5203
|
+
const textNode = createWordElement(xmlDoc, "w:t");
|
|
5204
|
+
textNode.setAttribute("xml:space", "preserve");
|
|
5205
|
+
textNode.textContent = text;
|
|
5206
|
+
return textNode;
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5209
|
+
// engine/surgical-diff-application.js
|
|
4884
5210
|
function reconcileFormattingForTextSpan(xmlDoc, span, start, end, applicableHints, author, generateRedlines) {
|
|
4885
5211
|
const desiredFormat = {};
|
|
4886
5212
|
if (applicableHints.length > 0) {
|
|
4887
5213
|
applicableHints.forEach((h) => Object.assign(desiredFormat, h.format));
|
|
4888
5214
|
}
|
|
4889
5215
|
const rPr = span.rPr;
|
|
4890
|
-
const hasElement = (
|
|
5216
|
+
const hasElement = (localName) => {
|
|
4891
5217
|
if (!rPr) return false;
|
|
4892
5218
|
for (let node = rPr.firstChild; node; node = node.nextSibling) {
|
|
4893
|
-
if (node
|
|
5219
|
+
if (isWordElement(node, localName)) {
|
|
4894
5220
|
return true;
|
|
4895
5221
|
}
|
|
4896
5222
|
}
|
|
4897
5223
|
return false;
|
|
4898
5224
|
};
|
|
4899
5225
|
const existingFormat = {
|
|
4900
|
-
bold: hasElement("
|
|
4901
|
-
italic: hasElement("
|
|
4902
|
-
underline: hasElement("
|
|
4903
|
-
strikethrough: hasElement("
|
|
5226
|
+
bold: hasElement("b"),
|
|
5227
|
+
italic: hasElement("i"),
|
|
5228
|
+
underline: hasElement("u"),
|
|
5229
|
+
strikethrough: hasElement("strike")
|
|
4904
5230
|
};
|
|
4905
5231
|
const formatsToCheck = ["bold", "italic", "underline", "strikethrough"];
|
|
4906
5232
|
const changesNeeded = formatsToCheck.some((f) => !!desiredFormat[f] !== existingFormat[f]);
|
|
4907
|
-
if (!changesNeeded) return;
|
|
5233
|
+
if (!changesNeeded) return false;
|
|
4908
5234
|
const parent = span.runElement.parentNode;
|
|
4909
|
-
if (!parent) return;
|
|
5235
|
+
if (!parent) return false;
|
|
4910
5236
|
const fullText = span.textElement.textContent || "";
|
|
4911
5237
|
const runStart = span.charStart;
|
|
4912
5238
|
const localStart = start - runStart;
|
|
@@ -4926,109 +5252,54 @@ function reconcileFormattingForTextSpan(xmlDoc, span, start, end, applicableHint
|
|
|
4926
5252
|
parent.insertBefore(afterRun, span.runElement);
|
|
4927
5253
|
}
|
|
4928
5254
|
parent.removeChild(span.runElement);
|
|
5255
|
+
return true;
|
|
4929
5256
|
}
|
|
4930
|
-
function
|
|
4931
|
-
const
|
|
4932
|
-
let localOffset = currentOffset;
|
|
4933
|
-
const textParts = [];
|
|
4934
|
-
for (let rc = r.firstChild; rc; rc = rc.nextSibling) {
|
|
4935
|
-
if (rc.nodeName === "w:t") {
|
|
4936
|
-
const text = rc.textContent || "";
|
|
4937
|
-
if (text.length > 0) {
|
|
4938
|
-
textSpans.push({
|
|
4939
|
-
charStart: localOffset,
|
|
4940
|
-
charEnd: localOffset + text.length,
|
|
4941
|
-
textElement: rc,
|
|
4942
|
-
runElement: r,
|
|
4943
|
-
paragraph: p,
|
|
4944
|
-
container,
|
|
4945
|
-
rPr
|
|
4946
|
-
});
|
|
4947
|
-
localOffset += text.length;
|
|
4948
|
-
textParts.push(text);
|
|
4949
|
-
}
|
|
4950
|
-
} else if (rc.nodeName === "w:br" || rc.nodeName === "w:cr") {
|
|
4951
|
-
textSpans.push({
|
|
4952
|
-
charStart: localOffset,
|
|
4953
|
-
charEnd: localOffset + 1,
|
|
4954
|
-
textElement: rc,
|
|
4955
|
-
runElement: r,
|
|
4956
|
-
paragraph: p,
|
|
4957
|
-
container,
|
|
4958
|
-
rPr
|
|
4959
|
-
});
|
|
4960
|
-
localOffset += 1;
|
|
4961
|
-
textParts.push("\n");
|
|
4962
|
-
} else if (rc.nodeName === "w:tab") {
|
|
4963
|
-
textSpans.push({
|
|
4964
|
-
charStart: localOffset,
|
|
4965
|
-
charEnd: localOffset + 1,
|
|
4966
|
-
textElement: rc,
|
|
4967
|
-
runElement: r,
|
|
4968
|
-
paragraph: p,
|
|
4969
|
-
container,
|
|
4970
|
-
rPr
|
|
4971
|
-
});
|
|
4972
|
-
localOffset += 1;
|
|
4973
|
-
textParts.push(" ");
|
|
4974
|
-
} else if (rc.nodeName === "w:noBreakHyphen") {
|
|
4975
|
-
textSpans.push({
|
|
4976
|
-
charStart: localOffset,
|
|
4977
|
-
charEnd: localOffset + 1,
|
|
4978
|
-
textElement: rc,
|
|
4979
|
-
runElement: r,
|
|
4980
|
-
paragraph: p,
|
|
4981
|
-
container,
|
|
4982
|
-
rPr
|
|
4983
|
-
});
|
|
4984
|
-
localOffset += 1;
|
|
4985
|
-
textParts.push("\u2011");
|
|
4986
|
-
}
|
|
4987
|
-
}
|
|
4988
|
-
return { nextOffset: localOffset, text: textParts.join("") };
|
|
4989
|
-
}
|
|
4990
|
-
function processDelete(xmlDoc, spanIndex, startPos, endPos, processedSpans, author, generateRedlines) {
|
|
5257
|
+
function processDelete(xmlDoc, spanIndex, startPos, endPos, author, generateRedlines) {
|
|
5258
|
+
const spans = [];
|
|
4991
5259
|
forEachOverlappingSpan(spanIndex, startPos, endPos, (span) => {
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5260
|
+
spans.push(span);
|
|
5261
|
+
});
|
|
5262
|
+
if (spans.length === 0) return false;
|
|
5263
|
+
const spansByRun = /* @__PURE__ */ new Map();
|
|
5264
|
+
spans.forEach((span) => {
|
|
5265
|
+
if (!span.runElement?.parentNode) return;
|
|
5266
|
+
if (!spansByRun.has(span.runElement)) spansByRun.set(span.runElement, []);
|
|
5267
|
+
spansByRun.get(span.runElement).push(span);
|
|
5268
|
+
});
|
|
5269
|
+
let changed = false;
|
|
5270
|
+
spansByRun.forEach((runSpans, runElement) => {
|
|
5271
|
+
const parent = runElement.parentNode;
|
|
5001
5272
|
if (!parent) return;
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
const
|
|
5011
|
-
if (
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
processedSpans.add(span.textElement);
|
|
5273
|
+
const pieces = getRunContentPieces(runElement);
|
|
5274
|
+
if (pieces.length === 0) return;
|
|
5275
|
+
let deleteStart = Infinity;
|
|
5276
|
+
let deleteEnd = -Infinity;
|
|
5277
|
+
runSpans.forEach((span) => {
|
|
5278
|
+
const piece = pieces.find((candidate) => candidate.node === span.textElement);
|
|
5279
|
+
if (!piece) return;
|
|
5280
|
+
const spanDeleteStart = Math.max(0, startPos - span.charStart);
|
|
5281
|
+
const spanDeleteEnd = Math.min(span.charEnd - span.charStart, endPos - span.charStart);
|
|
5282
|
+
if (spanDeleteEnd <= spanDeleteStart) return;
|
|
5283
|
+
deleteStart = Math.min(deleteStart, piece.start + spanDeleteStart);
|
|
5284
|
+
deleteEnd = Math.max(deleteEnd, piece.start + spanDeleteEnd);
|
|
5285
|
+
});
|
|
5286
|
+
if (!Number.isFinite(deleteStart) || deleteEnd <= deleteStart) return;
|
|
5287
|
+
const beforePieces = sliceRunPieces(xmlDoc, pieces, 0, deleteStart, false);
|
|
5288
|
+
const deletedPieces = sliceRunPieces(xmlDoc, pieces, deleteStart, deleteEnd, true);
|
|
5289
|
+
const afterPieces = sliceRunPieces(xmlDoc, pieces, deleteEnd, getRunTextLength(pieces), false);
|
|
5290
|
+
insertRunPiecesBefore(xmlDoc, parent, runElement, beforePieces, runSpans[0].rPr);
|
|
5291
|
+
if (generateRedlines && deletedPieces.length > 0) {
|
|
5292
|
+
const delRun = createRunFromPieces(xmlDoc, deletedPieces, runSpans[0].rPr);
|
|
5293
|
+
const delWrapper = createTrackChange(xmlDoc, "del", delRun, author);
|
|
5294
|
+
parent.insertBefore(delWrapper, runElement);
|
|
5295
|
+
}
|
|
5296
|
+
insertRunPiecesBefore(xmlDoc, parent, runElement, afterPieces, runSpans[0].rPr);
|
|
5297
|
+
parent.removeChild(runElement);
|
|
5298
|
+
changed = true;
|
|
5029
5299
|
});
|
|
5300
|
+
return changed;
|
|
5030
5301
|
}
|
|
5031
|
-
function processInsert(xmlDoc, spanIndex, pos, text,
|
|
5302
|
+
function processInsert(xmlDoc, spanIndex, pos, text, author, formatHints = [], insertOffset = 0, generateRedlines = true, fallbackParagraph = null) {
|
|
5032
5303
|
let targetSpan = findContainingSpan(spanIndex, pos);
|
|
5033
5304
|
if (!targetSpan && pos > 0) {
|
|
5034
5305
|
targetSpan = findFirstSpanEndingAt(spanIndex, pos);
|
|
@@ -5039,99 +5310,99 @@ function processInsert(xmlDoc, spanIndex, pos, text, processedSpans, author, for
|
|
|
5039
5310
|
if (!targetSpan && spanIndex.spans.length > 0) {
|
|
5040
5311
|
targetSpan = spanIndex.spans[spanIndex.spans.length - 1];
|
|
5041
5312
|
}
|
|
5042
|
-
if (targetSpan) {
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
if (parent) {
|
|
5047
|
-
const referenceNode = pos === targetSpan.charStart ? targetSpan.runElement : targetSpan.runElement.nextSibling;
|
|
5048
|
-
if (applicableHints.length === 0) {
|
|
5049
|
-
const insRun = createTextRun(xmlDoc, text, baseRPr, false);
|
|
5050
|
-
if (generateRedlines) {
|
|
5051
|
-
const insWrapper = createTrackChange(xmlDoc, "ins", insRun, author);
|
|
5052
|
-
parent.insertBefore(insWrapper, referenceNode);
|
|
5053
|
-
} else {
|
|
5054
|
-
parent.insertBefore(insRun, referenceNode);
|
|
5055
|
-
}
|
|
5056
|
-
} else {
|
|
5057
|
-
const runs = createFormattedRuns(xmlDoc, text, baseRPr, applicableHints, insertOffset, author, generateRedlines);
|
|
5058
|
-
if (generateRedlines) {
|
|
5059
|
-
const insWrapper = createTrackChange(xmlDoc, "ins", null, author);
|
|
5060
|
-
runs.forEach((run) => insWrapper.appendChild(run));
|
|
5061
|
-
parent.insertBefore(insWrapper, referenceNode);
|
|
5062
|
-
} else {
|
|
5063
|
-
runs.forEach((run) => parent.insertBefore(run, referenceNode));
|
|
5064
|
-
}
|
|
5065
|
-
}
|
|
5066
|
-
}
|
|
5067
|
-
}
|
|
5068
|
-
}
|
|
5069
|
-
function buildSpanIndex(textSpans) {
|
|
5070
|
-
const spans = textSpans.slice().sort((a, b) => a.charStart - b.charStart || a.charEnd - b.charEnd);
|
|
5071
|
-
const starts = spans.map((span) => span.charStart);
|
|
5072
|
-
const ends = spans.map((span) => span.charEnd);
|
|
5073
|
-
return { spans, starts, ends };
|
|
5074
|
-
}
|
|
5075
|
-
function upperBound(values, target) {
|
|
5076
|
-
let left = 0;
|
|
5077
|
-
let right = values.length;
|
|
5078
|
-
while (left < right) {
|
|
5079
|
-
const middle = left + right >> 1;
|
|
5080
|
-
if (values[middle] <= target) {
|
|
5081
|
-
left = middle + 1;
|
|
5082
|
-
} else {
|
|
5083
|
-
right = middle;
|
|
5084
|
-
}
|
|
5313
|
+
if (!targetSpan) {
|
|
5314
|
+
if (!fallbackParagraph) return false;
|
|
5315
|
+
insertTextRuns(xmlDoc, fallbackParagraph, null, text, null, author, formatHints, insertOffset, generateRedlines);
|
|
5316
|
+
return true;
|
|
5085
5317
|
}
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
while (left < right) {
|
|
5092
|
-
const middle = left + right >> 1;
|
|
5093
|
-
if (values[middle] < target) {
|
|
5094
|
-
left = middle + 1;
|
|
5095
|
-
} else {
|
|
5096
|
-
right = middle;
|
|
5097
|
-
}
|
|
5318
|
+
const parent = targetSpan.runElement.parentNode;
|
|
5319
|
+
if (!parent) {
|
|
5320
|
+
if (!fallbackParagraph) return false;
|
|
5321
|
+
insertTextRuns(xmlDoc, fallbackParagraph, null, text, targetSpan.rPr, author, formatHints, insertOffset, generateRedlines);
|
|
5322
|
+
return true;
|
|
5098
5323
|
}
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
if (
|
|
5103
|
-
|
|
5324
|
+
const pieces = getRunContentPieces(targetSpan.runElement);
|
|
5325
|
+
const targetPiece = pieces.find((piece) => piece.node === targetSpan.textElement);
|
|
5326
|
+
const localInsertPos = targetPiece ? targetPiece.start + Math.max(0, Math.min(pos - targetSpan.charStart, targetSpan.charEnd - targetSpan.charStart)) : pos <= targetSpan.charStart ? 0 : getRunTextLength(pieces);
|
|
5327
|
+
if (localInsertPos > 0 && localInsertPos < getRunTextLength(pieces)) {
|
|
5328
|
+
const beforePieces = sliceRunPieces(xmlDoc, pieces, 0, localInsertPos, false);
|
|
5329
|
+
const afterPieces = sliceRunPieces(xmlDoc, pieces, localInsertPos, getRunTextLength(pieces), false);
|
|
5330
|
+
insertRunPiecesBefore(xmlDoc, parent, targetSpan.runElement, beforePieces, targetSpan.rPr);
|
|
5331
|
+
insertTextRuns(xmlDoc, parent, targetSpan.runElement, text, targetSpan.rPr, author, formatHints, insertOffset, generateRedlines);
|
|
5332
|
+
insertRunPiecesBefore(xmlDoc, parent, targetSpan.runElement, afterPieces, targetSpan.rPr);
|
|
5333
|
+
parent.removeChild(targetSpan.runElement);
|
|
5334
|
+
return true;
|
|
5104
5335
|
}
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5336
|
+
const referenceNode = pos <= targetSpan.charStart ? targetSpan.runElement : targetSpan.runElement.nextSibling;
|
|
5337
|
+
insertTextRuns(xmlDoc, parent, referenceNode, text, targetSpan.rPr, author, formatHints, insertOffset, generateRedlines);
|
|
5338
|
+
return true;
|
|
5339
|
+
}
|
|
5340
|
+
function insertTextRuns(xmlDoc, parent, referenceNode, text, baseRPr, author, formatHints, insertOffset, generateRedlines) {
|
|
5341
|
+
const applicableHints = getApplicableFormatHints(formatHints, insertOffset, insertOffset + text.length);
|
|
5342
|
+
if (applicableHints.length === 0) {
|
|
5343
|
+
const insRun = createTextRun(xmlDoc, text, baseRPr, false);
|
|
5344
|
+
if (generateRedlines) {
|
|
5345
|
+
const insWrapper = createTrackChange(xmlDoc, "ins", insRun, author);
|
|
5346
|
+
parent.insertBefore(insWrapper, referenceNode);
|
|
5347
|
+
} else {
|
|
5348
|
+
parent.insertBefore(insRun, referenceNode);
|
|
5110
5349
|
}
|
|
5111
|
-
|
|
5112
|
-
index++;
|
|
5350
|
+
return;
|
|
5113
5351
|
}
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
}
|
|
5122
|
-
function findFirstSpanEndingAt(spanIndex, pos) {
|
|
5123
|
-
const index = lowerBound(spanIndex.ends, pos);
|
|
5124
|
-
if (index < spanIndex.spans.length && spanIndex.ends[index] === pos) {
|
|
5125
|
-
return spanIndex.spans[index];
|
|
5352
|
+
const runs = createFormattedRuns(xmlDoc, text, baseRPr, applicableHints, insertOffset, author, generateRedlines);
|
|
5353
|
+
if (generateRedlines) {
|
|
5354
|
+
const insWrapper = createTrackChange(xmlDoc, "ins", null, author);
|
|
5355
|
+
runs.forEach((run) => insWrapper.appendChild(run));
|
|
5356
|
+
parent.insertBefore(insWrapper, referenceNode);
|
|
5357
|
+
} else {
|
|
5358
|
+
runs.forEach((run) => parent.insertBefore(run, referenceNode));
|
|
5126
5359
|
}
|
|
5127
|
-
return null;
|
|
5128
5360
|
}
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5361
|
+
|
|
5362
|
+
// engine/surgical-mode.js
|
|
5363
|
+
function applySurgicalMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true, targetParagraph = null) {
|
|
5364
|
+
const allParagraphs = targetParagraph ? [targetParagraph] : getDocumentParagraphs(xmlDoc);
|
|
5365
|
+
const { fullText, textSpans } = buildSurgicalTextSpans(allParagraphs);
|
|
5366
|
+
const diffs = computeWordDiffs(fullText, modifiedText);
|
|
5367
|
+
const spanIndex = buildSpanIndex(textSpans);
|
|
5368
|
+
let originalPos = 0;
|
|
5369
|
+
let newPos = 0;
|
|
5370
|
+
let hasChanges = false;
|
|
5371
|
+
for (const [op, text] of diffs) {
|
|
5372
|
+
if (op === 0) {
|
|
5373
|
+
const len = text.length;
|
|
5374
|
+
const startPos = originalPos;
|
|
5375
|
+
const endPos = originalPos + len;
|
|
5376
|
+
forEachOverlappingSpan(spanIndex, startPos, endPos, (span) => {
|
|
5377
|
+
const overlapStartOriginal = Math.max(span.charStart, startPos);
|
|
5378
|
+
const overlapEndOriginal = Math.min(span.charEnd, endPos);
|
|
5379
|
+
const segmentLen = overlapEndOriginal - overlapStartOriginal;
|
|
5380
|
+
const relativeOffset = overlapStartOriginal - startPos;
|
|
5381
|
+
const overlapStartNew = newPos + relativeOffset;
|
|
5382
|
+
const overlapEndNew = overlapStartNew + segmentLen;
|
|
5383
|
+
const applicableHints = getApplicableFormatHints(formatHints, overlapStartNew, overlapEndNew);
|
|
5384
|
+
if (reconcileFormattingForTextSpan(xmlDoc, span, overlapStartOriginal, overlapEndOriginal, applicableHints, author, generateRedlines)) {
|
|
5385
|
+
hasChanges = true;
|
|
5386
|
+
}
|
|
5387
|
+
});
|
|
5388
|
+
originalPos += len;
|
|
5389
|
+
newPos += len;
|
|
5390
|
+
} else if (op === -1) {
|
|
5391
|
+
if (processDelete(xmlDoc, spanIndex, originalPos, originalPos + text.length, author, generateRedlines)) {
|
|
5392
|
+
hasChanges = true;
|
|
5393
|
+
}
|
|
5394
|
+
originalPos += text.length;
|
|
5395
|
+
} else if (op === 1) {
|
|
5396
|
+
const textWithoutNewlines = text.replace(/\n/g, " ");
|
|
5397
|
+
if (textWithoutNewlines.trim().length > 0) {
|
|
5398
|
+
if (processInsert(xmlDoc, spanIndex, originalPos, textWithoutNewlines, author, formatHints, newPos, generateRedlines, allParagraphs[0] || null)) {
|
|
5399
|
+
hasChanges = true;
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
newPos += text.length;
|
|
5403
|
+
}
|
|
5133
5404
|
}
|
|
5134
|
-
return
|
|
5405
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges });
|
|
5135
5406
|
}
|
|
5136
5407
|
|
|
5137
5408
|
// engine/reconstruction-mapper.js
|
|
@@ -5206,6 +5477,7 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5206
5477
|
const escapedToken = tokenString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
5207
5478
|
processedModifiedText = processedModifiedText.replace(new RegExp(escapedToken, "g"), char);
|
|
5208
5479
|
});
|
|
5480
|
+
processedModifiedText = preserveReferencePlaceholders(originalFullText, processedModifiedText, referenceMap);
|
|
5209
5481
|
const containerFragments = /* @__PURE__ */ new Map();
|
|
5210
5482
|
uniqueContainers.forEach((container) => {
|
|
5211
5483
|
containerFragments.set(container, xmlDoc.createDocumentFragment());
|
|
@@ -5256,6 +5528,25 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5256
5528
|
isParagraphStart: (index) => paragraphStarts.has(index)
|
|
5257
5529
|
};
|
|
5258
5530
|
}
|
|
5531
|
+
function preserveReferencePlaceholders(originalFullText, modifiedText, referenceMap) {
|
|
5532
|
+
let result = modifiedText;
|
|
5533
|
+
for (const referenceChar of referenceMap.keys()) {
|
|
5534
|
+
if (result.includes(referenceChar)) continue;
|
|
5535
|
+
const originalIndex = originalFullText.indexOf(referenceChar);
|
|
5536
|
+
if (originalIndex < 0) continue;
|
|
5537
|
+
const prefix = originalFullText.slice(0, originalIndex);
|
|
5538
|
+
const suffix = originalFullText.slice(originalIndex + referenceChar.length);
|
|
5539
|
+
if (prefix && result.startsWith(prefix)) {
|
|
5540
|
+
result = `${result.slice(0, prefix.length)}${referenceChar}${result.slice(prefix.length)}`;
|
|
5541
|
+
continue;
|
|
5542
|
+
}
|
|
5543
|
+
if (suffix && result.endsWith(suffix)) {
|
|
5544
|
+
const insertAt = result.length - suffix.length;
|
|
5545
|
+
result = `${result.slice(0, insertAt)}${referenceChar}${result.slice(insertAt)}`;
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
return result;
|
|
5549
|
+
}
|
|
5259
5550
|
function processChildNode(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, nextCharCode) {
|
|
5260
5551
|
if (child.nodeName === "w:r") {
|
|
5261
5552
|
return processRunForReconstruction(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, nextCharCode);
|
|
@@ -5360,7 +5651,7 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5360
5651
|
isParagraphStart
|
|
5361
5652
|
} = context;
|
|
5362
5653
|
const createNewParagraph = (pPr) => {
|
|
5363
|
-
const newParagraph = xmlDoc
|
|
5654
|
+
const newParagraph = createWordElement(xmlDoc, "w:p");
|
|
5364
5655
|
if (pPr) newParagraph.appendChild(pPr.cloneNode(true));
|
|
5365
5656
|
return newParagraph;
|
|
5366
5657
|
};
|
|
@@ -5372,6 +5663,7 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5372
5663
|
}
|
|
5373
5664
|
let currentOriginalIndex = 0;
|
|
5374
5665
|
let currentInsertOffset = 0;
|
|
5666
|
+
const emittedCommentMarkers = /* @__PURE__ */ new WeakSet();
|
|
5375
5667
|
for (const [op, text] of diffs) {
|
|
5376
5668
|
if (op === 0 || op === -1) {
|
|
5377
5669
|
const type = op === 0 ? "equal" : "delete";
|
|
@@ -5381,7 +5673,7 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5381
5673
|
const properties = getRunProperties(chunkStart);
|
|
5382
5674
|
const chunkLength = getPropertySpanLength(chunkStart, text.length - offset);
|
|
5383
5675
|
const chunk = text.substring(offset, offset + chunkLength);
|
|
5384
|
-
appendTextToCurrent(
|
|
5676
|
+
const appendResult = appendTextToCurrent(
|
|
5385
5677
|
xmlDoc,
|
|
5386
5678
|
chunk,
|
|
5387
5679
|
type,
|
|
@@ -5398,8 +5690,10 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5398
5690
|
author,
|
|
5399
5691
|
formatHints,
|
|
5400
5692
|
currentInsertOffset,
|
|
5401
|
-
generateRedlines
|
|
5693
|
+
generateRedlines,
|
|
5694
|
+
emittedCommentMarkers
|
|
5402
5695
|
);
|
|
5696
|
+
currentParagraph = appendResult.currentParagraph;
|
|
5403
5697
|
if (op === 0) {
|
|
5404
5698
|
currentInsertOffset += chunkLength;
|
|
5405
5699
|
}
|
|
@@ -5410,7 +5704,7 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5410
5704
|
}
|
|
5411
5705
|
if (op === 1) {
|
|
5412
5706
|
const properties = currentOriginalIndex > 0 && !isParagraphStart(currentOriginalIndex) ? getRunProperties(currentOriginalIndex - 1) : getRunProperties(currentOriginalIndex);
|
|
5413
|
-
appendTextToCurrent(
|
|
5707
|
+
const appendResult = appendTextToCurrent(
|
|
5414
5708
|
xmlDoc,
|
|
5415
5709
|
text,
|
|
5416
5710
|
"insert",
|
|
@@ -5427,8 +5721,10 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5427
5721
|
author,
|
|
5428
5722
|
formatHints,
|
|
5429
5723
|
currentInsertOffset,
|
|
5430
|
-
generateRedlines
|
|
5724
|
+
generateRedlines,
|
|
5725
|
+
emittedCommentMarkers
|
|
5431
5726
|
);
|
|
5727
|
+
currentParagraph = appendResult.currentParagraph;
|
|
5432
5728
|
currentInsertOffset += text.length;
|
|
5433
5729
|
}
|
|
5434
5730
|
}
|
|
@@ -5454,17 +5750,18 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5454
5750
|
});
|
|
5455
5751
|
return { oxml: serializer.serializeToString(xmlDoc), hasChanges: true };
|
|
5456
5752
|
}
|
|
5457
|
-
function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, currentParagraphRef, containerFragments, sentinelMapByStart, referenceMap, replacementContainers, getParagraphInfo, createNewParagraph, author, formatHints = [], insertOffset = 0, generateRedlines = true) {
|
|
5753
|
+
function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, currentParagraphRef, containerFragments, sentinelMapByStart, referenceMap, replacementContainers, getParagraphInfo, createNewParagraph, author, formatHints = [], insertOffset = 0, generateRedlines = true, emittedCommentMarkers = /* @__PURE__ */ new WeakSet()) {
|
|
5458
5754
|
let localBaseIndex = baseIndex;
|
|
5459
5755
|
let localInsertOffset = insertOffset;
|
|
5460
5756
|
let localParagraph = currentParagraphRef;
|
|
5461
5757
|
const parts = text.split(/([\n\uFFFC]|[\uE000-\uF8FF])/);
|
|
5462
5758
|
parts.forEach((part) => {
|
|
5463
5759
|
const sentinelsAtOffset = sentinelMapByStart.get(localBaseIndex) || [];
|
|
5464
|
-
const commentMarkers = sentinelsAtOffset.filter((sentinel) => sentinel.isCommentMarker);
|
|
5760
|
+
const commentMarkers = sentinelsAtOffset.filter((sentinel) => sentinel.isCommentMarker && !emittedCommentMarkers.has(sentinel.node));
|
|
5465
5761
|
commentMarkers.forEach((marker) => {
|
|
5762
|
+
emittedCommentMarkers.add(marker.node);
|
|
5466
5763
|
if (marker.node.nodeName === "w:commentReference") {
|
|
5467
|
-
const run = xmlDoc
|
|
5764
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
5468
5765
|
run.appendChild(marker.node.cloneNode(true));
|
|
5469
5766
|
localParagraph.appendChild(run);
|
|
5470
5767
|
} else {
|
|
@@ -5472,14 +5769,17 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5472
5769
|
}
|
|
5473
5770
|
});
|
|
5474
5771
|
if (part === "\n") {
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5772
|
+
const info = getParagraphInfo(localBaseIndex + 1);
|
|
5773
|
+
const nextParagraph = createNewParagraph(info.pPr);
|
|
5774
|
+
if (generateRedlines && type === "insert") {
|
|
5775
|
+
markParagraphMarkInserted(xmlDoc, nextParagraph, author);
|
|
5776
|
+
} else if (generateRedlines && type === "delete") {
|
|
5777
|
+
markParagraphMarkDeleted(xmlDoc, nextParagraph, author);
|
|
5778
|
+
}
|
|
5779
|
+
const fragment = containerFragments.get(info.container);
|
|
5780
|
+
if (fragment) {
|
|
5781
|
+
fragment.appendChild(nextParagraph);
|
|
5782
|
+
localParagraph = nextParagraph;
|
|
5483
5783
|
}
|
|
5484
5784
|
localBaseIndex++;
|
|
5485
5785
|
if (type !== "delete") localInsertOffset++;
|
|
@@ -5507,7 +5807,7 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5507
5807
|
const refNode = referenceMap.get(part);
|
|
5508
5808
|
if (refNode) {
|
|
5509
5809
|
const clone = refNode.cloneNode(true);
|
|
5510
|
-
const run = xmlDoc
|
|
5810
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
5511
5811
|
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
5512
5812
|
run.appendChild(clone);
|
|
5513
5813
|
localParagraph.appendChild(run);
|
|
@@ -5525,9 +5825,9 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5525
5825
|
localParagraph.appendChild(wrapperClone);
|
|
5526
5826
|
}
|
|
5527
5827
|
if (type === "delete") {
|
|
5528
|
-
const run = xmlDoc
|
|
5828
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
5529
5829
|
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
5530
|
-
const delText = xmlDoc
|
|
5830
|
+
const delText = createWordElement(xmlDoc, "w:delText");
|
|
5531
5831
|
delText.setAttribute("xml:space", "preserve");
|
|
5532
5832
|
delText.textContent = part;
|
|
5533
5833
|
run.appendChild(delText);
|
|
@@ -5551,16 +5851,17 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5551
5851
|
}
|
|
5552
5852
|
localBaseIndex += part.length;
|
|
5553
5853
|
});
|
|
5854
|
+
return { currentParagraph: localParagraph };
|
|
5554
5855
|
}
|
|
5555
5856
|
|
|
5556
5857
|
// engine/reconstruction-mode.js
|
|
5557
5858
|
function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true) {
|
|
5558
5859
|
const mapping = buildReconstructionMapping(xmlDoc, modifiedText);
|
|
5559
5860
|
if (mapping.paragraphs.length === 0) {
|
|
5560
|
-
return { oxml: serializer.serializeToString(xmlDoc), hasChanges: false };
|
|
5861
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
5561
5862
|
}
|
|
5562
5863
|
const diffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText);
|
|
5563
|
-
return applyReconstructionDiffs(
|
|
5864
|
+
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
5564
5865
|
xmlDoc,
|
|
5565
5866
|
diffs,
|
|
5566
5867
|
mapping,
|
|
@@ -5568,15 +5869,15 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
5568
5869
|
author,
|
|
5569
5870
|
formatHints,
|
|
5570
5871
|
generateRedlines
|
|
5571
|
-
);
|
|
5872
|
+
));
|
|
5572
5873
|
}
|
|
5573
5874
|
|
|
5574
5875
|
// engine/table-mode.js
|
|
5575
5876
|
function noChanges(serializer, xmlDoc) {
|
|
5576
|
-
return { oxml: serializer.serializeToString(xmlDoc), hasChanges: false };
|
|
5877
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
5577
5878
|
}
|
|
5578
5879
|
function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
|
|
5579
|
-
const tableNodes =
|
|
5880
|
+
const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
5580
5881
|
const newTableData = parseTable(modifiedText);
|
|
5581
5882
|
const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
|
|
5582
5883
|
if (tableNodes.length === 0 || !hasNewContent) {
|
|
@@ -5598,14 +5899,14 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
5598
5899
|
error("[OxmlEngine] Failed to parse reconciled table OOXML:", parseError.textContent);
|
|
5599
5900
|
return noChanges(serializer, xmlDoc);
|
|
5600
5901
|
}
|
|
5601
|
-
const newTableNode =
|
|
5902
|
+
const newTableNode = getFirstElementByTagNSOrTag(reconciledDoc, NS_W, "tbl");
|
|
5602
5903
|
if (!newTableNode) {
|
|
5603
5904
|
error("[OxmlEngine] No table found in reconciled OOXML");
|
|
5604
5905
|
return noChanges(serializer, xmlDoc);
|
|
5605
5906
|
}
|
|
5606
5907
|
const importedTable = xmlDoc.importNode(newTableNode, true);
|
|
5607
5908
|
targetTable.parentNode.replaceChild(importedTable, targetTable);
|
|
5608
|
-
return { oxml: serializer.serializeToString(xmlDoc), hasChanges: true };
|
|
5909
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
5609
5910
|
}
|
|
5610
5911
|
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
5611
5912
|
const tableData = parseTable(modifiedText);
|
|
@@ -5651,23 +5952,24 @@ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser
|
|
|
5651
5952
|
}
|
|
5652
5953
|
const importedTable = workingDoc.importNode(newTableElement, true);
|
|
5653
5954
|
if (generateRedlines) {
|
|
5654
|
-
const date = getRevisionTimestamp();
|
|
5655
5955
|
paragraphs.forEach((p) => {
|
|
5956
|
+
markParagraphMarkDeleted(workingDoc, p, author);
|
|
5656
5957
|
const runs = getElementsByTagNS(p, NS_W, "r");
|
|
5657
5958
|
runs.forEach((run) => {
|
|
5658
5959
|
const textNodes = getElementsByTagNS(run, NS_W, "t");
|
|
5659
5960
|
textNodes.forEach((t) => {
|
|
5660
5961
|
const text = t.textContent || "";
|
|
5661
5962
|
if (text.trim()) {
|
|
5662
|
-
const delText = workingDoc
|
|
5963
|
+
const delText = createWordElement(workingDoc, "w:delText");
|
|
5663
5964
|
delText.textContent = text;
|
|
5664
5965
|
t.parentNode.replaceChild(delText, t);
|
|
5665
5966
|
}
|
|
5666
5967
|
});
|
|
5667
|
-
const del = workingDoc
|
|
5668
|
-
|
|
5669
|
-
del.setAttribute("w:
|
|
5670
|
-
del.setAttribute("w:
|
|
5968
|
+
const del = createWordElement(workingDoc, "w:del");
|
|
5969
|
+
const metadata = createRevisionMetadata(author);
|
|
5970
|
+
del.setAttribute("w:id", String(metadata.id));
|
|
5971
|
+
del.setAttribute("w:author", metadata.author);
|
|
5972
|
+
del.setAttribute("w:date", metadata.date);
|
|
5671
5973
|
run.parentNode.insertBefore(del, run);
|
|
5672
5974
|
del.appendChild(run);
|
|
5673
5975
|
});
|
|
@@ -5680,7 +5982,405 @@ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser
|
|
|
5680
5982
|
parent.removeChild(firstParagraph);
|
|
5681
5983
|
}
|
|
5682
5984
|
log("[OxmlEngine] Text-to-table transformation complete");
|
|
5683
|
-
return { oxml: serializer.serializeToString(workingDoc), hasChanges: true };
|
|
5985
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(workingDoc), hasChanges: true });
|
|
5986
|
+
}
|
|
5987
|
+
|
|
5988
|
+
// services/revision-comment-management.js
|
|
5989
|
+
function getAttributeByLocalName(node, localName) {
|
|
5990
|
+
if (!node || !node.attributes) return "";
|
|
5991
|
+
for (const attr of Array.from(node.attributes)) {
|
|
5992
|
+
if ((attr.localName || "").toLowerCase() === localName.toLowerCase()) {
|
|
5993
|
+
return String(attr.value || "");
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
return String(
|
|
5997
|
+
node.getAttribute?.(`w:${localName}`) || node.getAttribute?.(localName) || ""
|
|
5998
|
+
);
|
|
5999
|
+
}
|
|
6000
|
+
function normalizeAuthor(author) {
|
|
6001
|
+
return typeof author === "string" ? author.trim().toLowerCase() : "";
|
|
6002
|
+
}
|
|
6003
|
+
function isElement(node) {
|
|
6004
|
+
return !!node && node.nodeType === 1;
|
|
6005
|
+
}
|
|
6006
|
+
function isWordElement5(node, localName) {
|
|
6007
|
+
return isElement(node) && node.namespaceURI === NS_W && String(node.localName || "").toLowerCase() === localName.toLowerCase();
|
|
6008
|
+
}
|
|
6009
|
+
function getWordElementsByLocalName(xmlDoc, localName) {
|
|
6010
|
+
return Array.from(xmlDoc.getElementsByTagNameNS(NS_W, localName));
|
|
6011
|
+
}
|
|
6012
|
+
function resolveAuthorFilter(options = {}) {
|
|
6013
|
+
if (options?.allAuthors === true) {
|
|
6014
|
+
return { valid: true, allAuthors: true, normalizedAuthor: "" };
|
|
6015
|
+
}
|
|
6016
|
+
const normalizedAuthor = normalizeAuthor(options?.author);
|
|
6017
|
+
if (!normalizedAuthor) {
|
|
6018
|
+
return {
|
|
6019
|
+
valid: false,
|
|
6020
|
+
allAuthors: false,
|
|
6021
|
+
normalizedAuthor: "",
|
|
6022
|
+
warning: "No author provided. Pass { author } or set { allAuthors: true }."
|
|
6023
|
+
};
|
|
6024
|
+
}
|
|
6025
|
+
return { valid: true, allAuthors: false, normalizedAuthor };
|
|
6026
|
+
}
|
|
6027
|
+
function authorMatchesNode(node, filter) {
|
|
6028
|
+
if (filter.allAuthors) return true;
|
|
6029
|
+
const nodeAuthor = normalizeAuthor(getAttributeByLocalName(node, "author"));
|
|
6030
|
+
return !!nodeAuthor && nodeAuthor === filter.normalizedAuthor;
|
|
6031
|
+
}
|
|
6032
|
+
function parseXmlWithWarnings(oxml, parseFailurePrefix) {
|
|
6033
|
+
const parser = createParser();
|
|
6034
|
+
const xmlDoc = parser.parseFromString(oxml, "application/xml");
|
|
6035
|
+
const parseError = getXmlParseError(xmlDoc);
|
|
6036
|
+
if (parseError) {
|
|
6037
|
+
return {
|
|
6038
|
+
xmlDoc: null,
|
|
6039
|
+
serializer: null,
|
|
6040
|
+
warning: `${parseFailurePrefix}: ${parseError.textContent || "parse error"}`
|
|
6041
|
+
};
|
|
6042
|
+
}
|
|
6043
|
+
return { xmlDoc, serializer: createSerializer(), warning: null };
|
|
6044
|
+
}
|
|
6045
|
+
function removeNode(node) {
|
|
6046
|
+
if (node?.parentNode) {
|
|
6047
|
+
node.parentNode.removeChild(node);
|
|
6048
|
+
return true;
|
|
6049
|
+
}
|
|
6050
|
+
return false;
|
|
6051
|
+
}
|
|
6052
|
+
function unwrapNode(node) {
|
|
6053
|
+
const parent = node?.parentNode;
|
|
6054
|
+
if (!parent) return false;
|
|
6055
|
+
while (node.firstChild) {
|
|
6056
|
+
parent.insertBefore(node.firstChild, node);
|
|
6057
|
+
}
|
|
6058
|
+
parent.removeChild(node);
|
|
6059
|
+
return true;
|
|
6060
|
+
}
|
|
6061
|
+
function isTableRowRevisionMarker(node) {
|
|
6062
|
+
const parent = node?.parentNode;
|
|
6063
|
+
return isWordElement5(parent, "trPr") && isWordElement5(parent?.parentNode, "tr");
|
|
6064
|
+
}
|
|
6065
|
+
function isParagraphMarkRevisionMarker(node) {
|
|
6066
|
+
const rPr = node?.parentNode;
|
|
6067
|
+
const pPr = rPr?.parentNode;
|
|
6068
|
+
const paragraph = pPr?.parentNode;
|
|
6069
|
+
return isWordElement5(rPr, "rPr") && isWordElement5(pPr, "pPr") && isWordElement5(paragraph, "p");
|
|
6070
|
+
}
|
|
6071
|
+
function getContainingParagraphMarkRevision(node) {
|
|
6072
|
+
return isParagraphMarkRevisionMarker(node) ? node.parentNode.parentNode.parentNode : null;
|
|
6073
|
+
}
|
|
6074
|
+
function getNextWordParagraph(paragraph) {
|
|
6075
|
+
let cursor = paragraph?.nextSibling || null;
|
|
6076
|
+
while (cursor) {
|
|
6077
|
+
if (isWordElement5(cursor, "p")) return cursor;
|
|
6078
|
+
cursor = cursor.nextSibling;
|
|
6079
|
+
}
|
|
6080
|
+
return null;
|
|
6081
|
+
}
|
|
6082
|
+
function mergeParagraphIntoNextAndRemove(paragraph) {
|
|
6083
|
+
if (!paragraph?.parentNode) return false;
|
|
6084
|
+
const nextParagraph = getNextWordParagraph(paragraph);
|
|
6085
|
+
if (!nextParagraph) {
|
|
6086
|
+
return removeNode(paragraph);
|
|
6087
|
+
}
|
|
6088
|
+
const childrenToMove = Array.from(paragraph.childNodes || []).filter((child) => !isWordElement5(child, "pPr"));
|
|
6089
|
+
const insertionPoint = nextParagraph.firstChild || null;
|
|
6090
|
+
for (const child of childrenToMove) {
|
|
6091
|
+
nextParagraph.insertBefore(child, insertionPoint);
|
|
6092
|
+
}
|
|
6093
|
+
return removeNode(paragraph);
|
|
6094
|
+
}
|
|
6095
|
+
function acceptTrackedChangesInOoxml(oxml, options = {}) {
|
|
6096
|
+
const warnings = [];
|
|
6097
|
+
const filter = resolveAuthorFilter(options);
|
|
6098
|
+
if (!filter.valid) {
|
|
6099
|
+
return { oxml, hasChanges: false, acceptedCount: 0, warnings: [filter.warning] };
|
|
6100
|
+
}
|
|
6101
|
+
const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
|
|
6102
|
+
if (!parseResult.xmlDoc) {
|
|
6103
|
+
return { oxml, hasChanges: false, acceptedCount: 0, warnings: [parseResult.warning] };
|
|
6104
|
+
}
|
|
6105
|
+
const { xmlDoc, serializer } = parseResult;
|
|
6106
|
+
let acceptedCount = 0;
|
|
6107
|
+
for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
|
|
6108
|
+
if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
|
|
6109
|
+
if (isParagraphMarkRevisionMarker(insNode)) {
|
|
6110
|
+
if (removeNode(insNode)) acceptedCount += 1;
|
|
6111
|
+
continue;
|
|
6112
|
+
}
|
|
6113
|
+
if (isTableRowRevisionMarker(insNode)) {
|
|
6114
|
+
if (removeNode(insNode)) acceptedCount += 1;
|
|
6115
|
+
continue;
|
|
6116
|
+
}
|
|
6117
|
+
if (unwrapNode(insNode)) acceptedCount += 1;
|
|
6118
|
+
}
|
|
6119
|
+
for (const delNode of getWordElementsByLocalName(xmlDoc, "del")) {
|
|
6120
|
+
if (!delNode.parentNode || !authorMatchesNode(delNode, filter)) continue;
|
|
6121
|
+
const paragraphMark = getContainingParagraphMarkRevision(delNode);
|
|
6122
|
+
if (paragraphMark) {
|
|
6123
|
+
if (mergeParagraphIntoNextAndRemove(paragraphMark)) acceptedCount += 1;
|
|
6124
|
+
continue;
|
|
6125
|
+
}
|
|
6126
|
+
if (isTableRowRevisionMarker(delNode)) {
|
|
6127
|
+
const rowNode = delNode.parentNode?.parentNode;
|
|
6128
|
+
if (removeNode(rowNode)) acceptedCount += 1;
|
|
6129
|
+
continue;
|
|
6130
|
+
}
|
|
6131
|
+
if (removeNode(delNode)) acceptedCount += 1;
|
|
6132
|
+
}
|
|
6133
|
+
for (const moveFromNode of getWordElementsByLocalName(xmlDoc, "moveFrom")) {
|
|
6134
|
+
if (!moveFromNode.parentNode || !authorMatchesNode(moveFromNode, filter)) continue;
|
|
6135
|
+
if (removeNode(moveFromNode)) acceptedCount += 1;
|
|
6136
|
+
}
|
|
6137
|
+
for (const moveToNode of getWordElementsByLocalName(xmlDoc, "moveTo")) {
|
|
6138
|
+
if (!moveToNode.parentNode || !authorMatchesNode(moveToNode, filter)) continue;
|
|
6139
|
+
if (unwrapNode(moveToNode)) acceptedCount += 1;
|
|
6140
|
+
}
|
|
6141
|
+
acceptedCount += removeMoveRangeMarkers(xmlDoc, filter);
|
|
6142
|
+
const changeTags = ["rPrChange", "pPrChange", "tblPrChange", "trPrChange", "tcPrChange"];
|
|
6143
|
+
for (const localName of changeTags) {
|
|
6144
|
+
for (const changeNode of getWordElementsByLocalName(xmlDoc, localName)) {
|
|
6145
|
+
if (!changeNode.parentNode || !authorMatchesNode(changeNode, filter)) continue;
|
|
6146
|
+
if (removeNode(changeNode)) acceptedCount += 1;
|
|
6147
|
+
}
|
|
6148
|
+
}
|
|
6149
|
+
return {
|
|
6150
|
+
oxml: serializer.serializeToString(xmlDoc),
|
|
6151
|
+
hasChanges: acceptedCount > 0,
|
|
6152
|
+
acceptedCount,
|
|
6153
|
+
warnings
|
|
6154
|
+
};
|
|
6155
|
+
}
|
|
6156
|
+
function convertDeletionTextNodes(xmlDoc, delNode) {
|
|
6157
|
+
for (const delTextNode of Array.from(delNode.getElementsByTagNameNS(NS_W, "delText"))) {
|
|
6158
|
+
const normalText = createWordElement(xmlDoc, "w:t");
|
|
6159
|
+
const spaceValue = delTextNode.getAttribute("xml:space");
|
|
6160
|
+
if (spaceValue) {
|
|
6161
|
+
normalText.setAttribute("xml:space", spaceValue);
|
|
6162
|
+
}
|
|
6163
|
+
while (delTextNode.firstChild) {
|
|
6164
|
+
normalText.appendChild(delTextNode.firstChild);
|
|
6165
|
+
}
|
|
6166
|
+
delTextNode.parentNode?.replaceChild(normalText, delTextNode);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
function rejectPropertyChangeNode(changeNode, localName) {
|
|
6170
|
+
const parent = changeNode?.parentNode;
|
|
6171
|
+
if (!parent) return false;
|
|
6172
|
+
const baseLocalName = localName.endsWith("Change") ? localName.slice(0, -"Change".length) : "";
|
|
6173
|
+
if (!baseLocalName || String(parent.localName || "").toLowerCase() !== baseLocalName.toLowerCase() || parent.namespaceURI !== NS_W) {
|
|
6174
|
+
return removeNode(changeNode);
|
|
6175
|
+
}
|
|
6176
|
+
const historicalNode = Array.from(changeNode.childNodes || []).find(
|
|
6177
|
+
(child) => child.nodeType === 1 && child.namespaceURI === NS_W && String(child.localName || "").toLowerCase() === baseLocalName.toLowerCase()
|
|
6178
|
+
);
|
|
6179
|
+
if (!historicalNode) {
|
|
6180
|
+
return removeNode(changeNode);
|
|
6181
|
+
}
|
|
6182
|
+
const toAppend = Array.from(historicalNode.childNodes || []);
|
|
6183
|
+
while (parent.firstChild) {
|
|
6184
|
+
parent.removeChild(parent.firstChild);
|
|
6185
|
+
}
|
|
6186
|
+
for (const node of toAppend) {
|
|
6187
|
+
const clone = xmlDocImportNode(parent.ownerDocument, node);
|
|
6188
|
+
parent.appendChild(clone);
|
|
6189
|
+
}
|
|
6190
|
+
return true;
|
|
6191
|
+
}
|
|
6192
|
+
function xmlDocImportNode(xmlDoc, node) {
|
|
6193
|
+
if (xmlDoc && typeof xmlDoc.importNode === "function") {
|
|
6194
|
+
return xmlDoc.importNode(node, true);
|
|
6195
|
+
}
|
|
6196
|
+
return node.cloneNode(true);
|
|
6197
|
+
}
|
|
6198
|
+
function collectMoveRangeStartIds(xmlDoc, localName, filter) {
|
|
6199
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6200
|
+
for (const node of getWordElementsByLocalName(xmlDoc, localName)) {
|
|
6201
|
+
if (!authorMatchesNode(node, filter)) continue;
|
|
6202
|
+
const id = getAttributeByLocalName(node, "id");
|
|
6203
|
+
if (id) ids.add(id);
|
|
6204
|
+
}
|
|
6205
|
+
return ids;
|
|
6206
|
+
}
|
|
6207
|
+
function removeMoveRangeMarkers(xmlDoc, filter) {
|
|
6208
|
+
let removed = 0;
|
|
6209
|
+
const moveFromIds = collectMoveRangeStartIds(xmlDoc, "moveFromRangeStart", filter);
|
|
6210
|
+
const moveToIds = collectMoveRangeStartIds(xmlDoc, "moveToRangeStart", filter);
|
|
6211
|
+
const markerSpecs = [
|
|
6212
|
+
["moveFromRangeStart", moveFromIds, true],
|
|
6213
|
+
["moveFromRangeEnd", moveFromIds, false],
|
|
6214
|
+
["moveToRangeStart", moveToIds, true],
|
|
6215
|
+
["moveToRangeEnd", moveToIds, false]
|
|
6216
|
+
];
|
|
6217
|
+
for (const [localName, ids, isStart] of markerSpecs) {
|
|
6218
|
+
for (const node of getWordElementsByLocalName(xmlDoc, localName)) {
|
|
6219
|
+
if (!node.parentNode) continue;
|
|
6220
|
+
const id = getAttributeByLocalName(node, "id");
|
|
6221
|
+
if (!id) continue;
|
|
6222
|
+
if (filter.allAuthors || ids.has(id) || isStart && authorMatchesNode(node, filter)) {
|
|
6223
|
+
if (removeNode(node)) removed += 1;
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
return removed;
|
|
6228
|
+
}
|
|
6229
|
+
function rejectTrackedChangesInOoxml(oxml, options = {}) {
|
|
6230
|
+
const warnings = [];
|
|
6231
|
+
const filter = resolveAuthorFilter(options);
|
|
6232
|
+
if (!filter.valid) {
|
|
6233
|
+
return { oxml, hasChanges: false, rejectedCount: 0, warnings: [filter.warning] };
|
|
6234
|
+
}
|
|
6235
|
+
const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
|
|
6236
|
+
if (!parseResult.xmlDoc) {
|
|
6237
|
+
return { oxml, hasChanges: false, rejectedCount: 0, warnings: [parseResult.warning] };
|
|
6238
|
+
}
|
|
6239
|
+
const { xmlDoc, serializer } = parseResult;
|
|
6240
|
+
let rejectedCount = 0;
|
|
6241
|
+
for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
|
|
6242
|
+
if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
|
|
6243
|
+
const paragraphMark = getContainingParagraphMarkRevision(insNode);
|
|
6244
|
+
if (paragraphMark) {
|
|
6245
|
+
if (mergeParagraphIntoNextAndRemove(paragraphMark)) rejectedCount += 1;
|
|
6246
|
+
continue;
|
|
6247
|
+
}
|
|
6248
|
+
if (isTableRowRevisionMarker(insNode)) {
|
|
6249
|
+
const rowNode = insNode.parentNode?.parentNode;
|
|
6250
|
+
if (removeNode(rowNode)) rejectedCount += 1;
|
|
6251
|
+
continue;
|
|
6252
|
+
}
|
|
6253
|
+
if (removeNode(insNode)) rejectedCount += 1;
|
|
6254
|
+
}
|
|
6255
|
+
for (const delNode of getWordElementsByLocalName(xmlDoc, "del")) {
|
|
6256
|
+
if (!delNode.parentNode || !authorMatchesNode(delNode, filter)) continue;
|
|
6257
|
+
if (isParagraphMarkRevisionMarker(delNode)) {
|
|
6258
|
+
if (removeNode(delNode)) rejectedCount += 1;
|
|
6259
|
+
continue;
|
|
6260
|
+
}
|
|
6261
|
+
if (isTableRowRevisionMarker(delNode)) {
|
|
6262
|
+
if (removeNode(delNode)) rejectedCount += 1;
|
|
6263
|
+
continue;
|
|
6264
|
+
}
|
|
6265
|
+
convertDeletionTextNodes(xmlDoc, delNode);
|
|
6266
|
+
if (unwrapNode(delNode)) rejectedCount += 1;
|
|
6267
|
+
}
|
|
6268
|
+
for (const moveFromNode of getWordElementsByLocalName(xmlDoc, "moveFrom")) {
|
|
6269
|
+
if (!moveFromNode.parentNode || !authorMatchesNode(moveFromNode, filter)) continue;
|
|
6270
|
+
convertDeletionTextNodes(xmlDoc, moveFromNode);
|
|
6271
|
+
if (unwrapNode(moveFromNode)) rejectedCount += 1;
|
|
6272
|
+
}
|
|
6273
|
+
for (const moveToNode of getWordElementsByLocalName(xmlDoc, "moveTo")) {
|
|
6274
|
+
if (!moveToNode.parentNode || !authorMatchesNode(moveToNode, filter)) continue;
|
|
6275
|
+
if (removeNode(moveToNode)) rejectedCount += 1;
|
|
6276
|
+
}
|
|
6277
|
+
rejectedCount += removeMoveRangeMarkers(xmlDoc, filter);
|
|
6278
|
+
const changeTags = ["rPrChange", "pPrChange", "tblPrChange", "trPrChange", "tcPrChange"];
|
|
6279
|
+
for (const localName of changeTags) {
|
|
6280
|
+
for (const changeNode of getWordElementsByLocalName(xmlDoc, localName)) {
|
|
6281
|
+
if (!changeNode.parentNode || !authorMatchesNode(changeNode, filter)) continue;
|
|
6282
|
+
if (rejectPropertyChangeNode(changeNode, localName)) rejectedCount += 1;
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
6285
|
+
return {
|
|
6286
|
+
oxml: serializer.serializeToString(xmlDoc),
|
|
6287
|
+
hasChanges: rejectedCount > 0,
|
|
6288
|
+
rejectedCount,
|
|
6289
|
+
warnings
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
6292
|
+
function collectCommentTargetIds(xmlDoc, filter) {
|
|
6293
|
+
const targetIds = /* @__PURE__ */ new Set();
|
|
6294
|
+
const commentNodes = getWordElementsByLocalName(xmlDoc, "comment");
|
|
6295
|
+
for (const commentNode of commentNodes) {
|
|
6296
|
+
if (!authorMatchesNode(commentNode, filter)) continue;
|
|
6297
|
+
const id = getAttributeByLocalName(commentNode, "id");
|
|
6298
|
+
if (id) targetIds.add(id);
|
|
6299
|
+
}
|
|
6300
|
+
return { targetIds, commentNodes };
|
|
6301
|
+
}
|
|
6302
|
+
function removeCommentNodesById(commentNodes, targetIds) {
|
|
6303
|
+
let removed = 0;
|
|
6304
|
+
for (const commentNode of commentNodes) {
|
|
6305
|
+
const id = getAttributeByLocalName(commentNode, "id");
|
|
6306
|
+
if (!id || !targetIds.has(id)) continue;
|
|
6307
|
+
if (removeNode(commentNode)) removed += 1;
|
|
6308
|
+
}
|
|
6309
|
+
return removed;
|
|
6310
|
+
}
|
|
6311
|
+
function runIsOnlyCommentReference(runNode) {
|
|
6312
|
+
if (!isWordElement5(runNode, "r")) return false;
|
|
6313
|
+
const meaningfulChildren = Array.from(runNode.childNodes || []).filter((child) => {
|
|
6314
|
+
if (child.nodeType === 3) return String(child.nodeValue || "").trim().length > 0;
|
|
6315
|
+
if (child.nodeType !== 1) return false;
|
|
6316
|
+
if (child.namespaceURI !== NS_W) return true;
|
|
6317
|
+
const local = String(child.localName || "").toLowerCase();
|
|
6318
|
+
return local !== "rpr" && local !== "commentreference";
|
|
6319
|
+
});
|
|
6320
|
+
return meaningfulChildren.length === 0;
|
|
6321
|
+
}
|
|
6322
|
+
function removeCommentAnchors(xmlDoc, targetIds) {
|
|
6323
|
+
let removed = 0;
|
|
6324
|
+
const anchorTags = ["commentRangeStart", "commentRangeEnd", "commentReference"];
|
|
6325
|
+
for (const localName of anchorTags) {
|
|
6326
|
+
for (const node of getWordElementsByLocalName(xmlDoc, localName)) {
|
|
6327
|
+
if (!node.parentNode) continue;
|
|
6328
|
+
const id = getAttributeByLocalName(node, "id");
|
|
6329
|
+
if (!id || !targetIds.has(id)) continue;
|
|
6330
|
+
if (localName === "commentReference" && runIsOnlyCommentReference(node.parentNode)) {
|
|
6331
|
+
if (removeNode(node.parentNode)) {
|
|
6332
|
+
removed += 1;
|
|
6333
|
+
}
|
|
6334
|
+
continue;
|
|
6335
|
+
}
|
|
6336
|
+
if (removeNode(node)) {
|
|
6337
|
+
removed += 1;
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
}
|
|
6341
|
+
return removed;
|
|
6342
|
+
}
|
|
6343
|
+
function deleteCommentsByAuthorInOoxml(oxml, options = {}) {
|
|
6344
|
+
const warnings = [];
|
|
6345
|
+
const filter = resolveAuthorFilter(options);
|
|
6346
|
+
if (!filter.valid) {
|
|
6347
|
+
return {
|
|
6348
|
+
oxml,
|
|
6349
|
+
hasChanges: false,
|
|
6350
|
+
commentsRemoved: 0,
|
|
6351
|
+
referencesRemoved: 0,
|
|
6352
|
+
warnings: [filter.warning]
|
|
6353
|
+
};
|
|
6354
|
+
}
|
|
6355
|
+
const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
|
|
6356
|
+
if (!parseResult.xmlDoc) {
|
|
6357
|
+
return {
|
|
6358
|
+
oxml,
|
|
6359
|
+
hasChanges: false,
|
|
6360
|
+
commentsRemoved: 0,
|
|
6361
|
+
referencesRemoved: 0,
|
|
6362
|
+
warnings: [parseResult.warning]
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
const { xmlDoc, serializer } = parseResult;
|
|
6366
|
+
const { targetIds, commentNodes } = collectCommentTargetIds(xmlDoc, filter);
|
|
6367
|
+
if (filter.allAuthors) {
|
|
6368
|
+
for (const localName of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
6369
|
+
for (const node of getWordElementsByLocalName(xmlDoc, localName)) {
|
|
6370
|
+
const id = getAttributeByLocalName(node, "id");
|
|
6371
|
+
if (id) targetIds.add(id);
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
const commentsRemoved = removeCommentNodesById(commentNodes, targetIds);
|
|
6376
|
+
const referencesRemoved = removeCommentAnchors(xmlDoc, targetIds);
|
|
6377
|
+
return {
|
|
6378
|
+
oxml: serializer.serializeToString(xmlDoc),
|
|
6379
|
+
hasChanges: commentsRemoved > 0 || referencesRemoved > 0,
|
|
6380
|
+
commentsRemoved,
|
|
6381
|
+
referencesRemoved,
|
|
6382
|
+
warnings
|
|
6383
|
+
};
|
|
5684
6384
|
}
|
|
5685
6385
|
|
|
5686
6386
|
// engine/oxml-engine.js
|
|
@@ -5689,18 +6389,70 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
5689
6389
|
const author = options.author || getDefaultAuthor();
|
|
5690
6390
|
const parser = createParser();
|
|
5691
6391
|
const serializer = createSerializer();
|
|
5692
|
-
const
|
|
6392
|
+
const finalize = (result) => {
|
|
6393
|
+
const withStatus = { ...result };
|
|
6394
|
+
if (!withStatus.status) {
|
|
6395
|
+
withStatus.status = withStatus.hasChanges ? "ok" : "no-op";
|
|
6396
|
+
}
|
|
6397
|
+
return withOoxmlSourceType(withStatus);
|
|
6398
|
+
};
|
|
6399
|
+
const noChanges2 = () => finalize({ oxml, hasChanges: false });
|
|
5693
6400
|
let xmlDoc;
|
|
5694
6401
|
try {
|
|
5695
6402
|
xmlDoc = parser.parseFromString(oxml, "text/xml");
|
|
5696
6403
|
} catch (e) {
|
|
5697
6404
|
error("[OxmlEngine] Failed to parse OXML:", e);
|
|
5698
|
-
return
|
|
6405
|
+
return finalize({
|
|
6406
|
+
oxml,
|
|
6407
|
+
hasChanges: false,
|
|
6408
|
+
status: "error",
|
|
6409
|
+
error: { code: "PARSE_ERROR", message: "Could not parse OOXML input." }
|
|
6410
|
+
});
|
|
5699
6411
|
}
|
|
5700
6412
|
const parseError = getXmlParseError(xmlDoc);
|
|
5701
6413
|
if (parseError) {
|
|
5702
6414
|
error("[OxmlEngine] XML parse error:", parseError.textContent);
|
|
5703
|
-
return
|
|
6415
|
+
return finalize({
|
|
6416
|
+
oxml,
|
|
6417
|
+
hasChanges: false,
|
|
6418
|
+
status: "error",
|
|
6419
|
+
error: { code: "PARSE_ERROR", message: parseError.textContent || "Could not parse OOXML input." }
|
|
6420
|
+
});
|
|
6421
|
+
}
|
|
6422
|
+
seedRevisionIdsFromDocument(xmlDoc);
|
|
6423
|
+
if (containsTrackedChanges(xmlDoc)) {
|
|
6424
|
+
const existingRevisionsPolicy = options.existingRevisions || "reject-input";
|
|
6425
|
+
if (existingRevisionsPolicy === "accept-all-first") {
|
|
6426
|
+
log("[OxmlEngine] Existing revisions detected; accepting all input revisions before redlining");
|
|
6427
|
+
const accepted = acceptTrackedChangesInOoxml(oxml, { allAuthors: true });
|
|
6428
|
+
oxml = accepted.oxml;
|
|
6429
|
+
xmlDoc = parser.parseFromString(oxml, "text/xml");
|
|
6430
|
+
const acceptedParseError = getXmlParseError(xmlDoc);
|
|
6431
|
+
if (acceptedParseError) {
|
|
6432
|
+
error("[OxmlEngine] XML parse error after accepting existing revisions:", acceptedParseError.textContent);
|
|
6433
|
+
return finalize({
|
|
6434
|
+
oxml,
|
|
6435
|
+
hasChanges: false,
|
|
6436
|
+
status: "error",
|
|
6437
|
+
error: {
|
|
6438
|
+
code: "PARSE_ERROR",
|
|
6439
|
+
message: "Could not parse OOXML after accepting existing revisions."
|
|
6440
|
+
}
|
|
6441
|
+
});
|
|
6442
|
+
}
|
|
6443
|
+
seedRevisionIdsFromDocument(xmlDoc);
|
|
6444
|
+
} else {
|
|
6445
|
+
log("[OxmlEngine] Existing revisions detected; rejecting input per existingRevisions policy");
|
|
6446
|
+
return finalize({
|
|
6447
|
+
oxml,
|
|
6448
|
+
hasChanges: false,
|
|
6449
|
+
status: "error",
|
|
6450
|
+
error: {
|
|
6451
|
+
code: "EXISTING_REVISIONS",
|
|
6452
|
+
message: 'Input OOXML contains existing tracked changes. Pass existingRevisions: "accept-all-first" to normalize before redlining.'
|
|
6453
|
+
}
|
|
6454
|
+
});
|
|
6455
|
+
}
|
|
5704
6456
|
}
|
|
5705
6457
|
const initialTableCellContext = detectTableCellContext(xmlDoc, originalText, options);
|
|
5706
6458
|
if (initialTableCellContext.hasTableWrapper && initialTableCellContext.targetParagraph && !options._isolatedTableCell) {
|
|
@@ -5717,6 +6469,19 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
5717
6469
|
const hasFormatHints = formatHints.length > 0;
|
|
5718
6470
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
5719
6471
|
const hasExistingFormatting = existingFormatHints.length > 0;
|
|
6472
|
+
const visibleText = textSpans.map((span) => textSpanVisibleText(span)).join("");
|
|
6473
|
+
if (hasTextChanges && typeof originalText === "string" && originalText.trim() && !originalText.includes("\n") && !visibleText.includes(originalText.trim()) && !visibleText.replace(/[\t\n\u2011]/g, "").includes(originalText.trim().replace(/[\t\n\u2011]/g, "")) && !normalizeTargetText(visibleText).includes(normalizeTargetText(originalText))) {
|
|
6474
|
+
log("[OxmlEngine] Target text not found in OOXML");
|
|
6475
|
+
return finalize({
|
|
6476
|
+
oxml,
|
|
6477
|
+
hasChanges: false,
|
|
6478
|
+
status: "error",
|
|
6479
|
+
error: {
|
|
6480
|
+
code: "TARGET_NOT_FOUND",
|
|
6481
|
+
message: "Original text was not found in the supplied OOXML."
|
|
6482
|
+
}
|
|
6483
|
+
});
|
|
6484
|
+
}
|
|
5720
6485
|
let paragraphInfos = null;
|
|
5721
6486
|
const getParagraphInfos = () => {
|
|
5722
6487
|
if (!paragraphInfos) {
|
|
@@ -5780,12 +6545,12 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
5780
6545
|
generateRedlines
|
|
5781
6546
|
);
|
|
5782
6547
|
if (tableCellCtx.hasTableWrapper && targetParagraph) {
|
|
5783
|
-
return {
|
|
6548
|
+
return finalize({
|
|
5784
6549
|
oxml: serializeParagraphOnly(xmlDoc, targetParagraph, serializer),
|
|
5785
6550
|
hasChanges: removalResult.hasChanges
|
|
5786
|
-
};
|
|
6551
|
+
});
|
|
5787
6552
|
}
|
|
5788
|
-
return removalResult;
|
|
6553
|
+
return finalize(removalResult);
|
|
5789
6554
|
}
|
|
5790
6555
|
if (!hasTextChanges && hasFormatHints) {
|
|
5791
6556
|
log(`[OxmlEngine] Format-only change detected: ${formatHints.length} format hints`);
|
|
@@ -5799,14 +6564,14 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
5799
6564
|
log("[OxmlEngine] Table cell context: applying formatting to target paragraph only");
|
|
5800
6565
|
const formatResult = applyFormatOnlyWithOoxmlFallback(precomputedFormatContext);
|
|
5801
6566
|
log("[OxmlEngine] Stripping table wrapper for table cell paragraph (format-only)");
|
|
5802
|
-
return {
|
|
6567
|
+
return finalize({
|
|
5803
6568
|
oxml: serializeParagraphOnly(xmlDoc, tableCellCtx.targetParagraph, serializer),
|
|
5804
6569
|
hasChanges: formatResult.hasChanges
|
|
5805
|
-
};
|
|
6570
|
+
});
|
|
5806
6571
|
}
|
|
5807
|
-
return applyFormatOnlyWithOoxmlFallback(precomputedFormatContext);
|
|
6572
|
+
return finalize(applyFormatOnlyWithOoxmlFallback(precomputedFormatContext));
|
|
5808
6573
|
}
|
|
5809
|
-
const tables =
|
|
6574
|
+
const tables = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
5810
6575
|
const hasTables = tables.length > 0;
|
|
5811
6576
|
const isMarkdownTable = /^\|.+\|/.test(cleanModifiedText.trim()) && cleanModifiedText.includes("\n");
|
|
5812
6577
|
const isTargetList = isListTargetLoose(cleanModifiedText);
|
|
@@ -5814,10 +6579,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
5814
6579
|
log(`[OxmlEngine] Mode: ${hasTables ? "SURGICAL" : "RECONSTRUCTION"}, formatHints: ${formatHints.length}, isMarkdownTable: ${isMarkdownTable}, isTargetList: ${isTargetList}, isTableCellParagraph: ${tableCellContext.isTableCellParagraph}`);
|
|
5815
6580
|
if (isMarkdownTable && !hasTables) {
|
|
5816
6581
|
log("[OxmlEngine] Text-to-table transformation: generating new table from Markdown");
|
|
5817
|
-
return applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, parser, author, generateRedlines);
|
|
6582
|
+
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, parser, author, generateRedlines));
|
|
5818
6583
|
}
|
|
5819
6584
|
if (hasTables && isMarkdownTable) {
|
|
5820
|
-
return applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, parser, author, generateRedlines);
|
|
6585
|
+
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, parser, author, generateRedlines));
|
|
5821
6586
|
}
|
|
5822
6587
|
if (hasTables) {
|
|
5823
6588
|
const surgicalTarget = tableCellContext.hasTableWrapper && tableCellContext.targetParagraph ? tableCellContext.targetParagraph : null;
|
|
@@ -5836,9 +6601,9 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
5836
6601
|
);
|
|
5837
6602
|
if (tableCellContext.hasTableWrapper && result.hasChanges && tableCellContext.targetParagraph) {
|
|
5838
6603
|
log("[OxmlEngine] Stripping table wrapper for table cell paragraph (surgical mode)");
|
|
5839
|
-
return { oxml: serializeParagraphOnly(xmlDoc, tableCellContext.targetParagraph, serializer), hasChanges: true };
|
|
6604
|
+
return finalize({ oxml: serializeParagraphOnly(xmlDoc, tableCellContext.targetParagraph, serializer), hasChanges: true });
|
|
5840
6605
|
}
|
|
5841
|
-
return result;
|
|
6606
|
+
return finalize(result);
|
|
5842
6607
|
}
|
|
5843
6608
|
if (isTargetList) {
|
|
5844
6609
|
log("[OxmlEngine] \u{1F3AF} Using reconciliation pipeline for list generation");
|
|
@@ -5851,11 +6616,22 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
5851
6616
|
numberingXml: result.numberingXml
|
|
5852
6617
|
});
|
|
5853
6618
|
log(`[OxmlEngine] \u2705 Wrapped OOXML length: ${wrapped.length}`);
|
|
5854
|
-
return { oxml: wrapped, hasChanges: true };
|
|
6619
|
+
return finalize({ oxml: wrapped, hasChanges: true });
|
|
5855
6620
|
}
|
|
5856
6621
|
return noChanges2();
|
|
5857
6622
|
}
|
|
5858
|
-
return applyReconstructionMode(xmlDoc, originalText, cleanModifiedText, serializer, author, formatHints, generateRedlines);
|
|
6623
|
+
return finalize(applyReconstructionMode(xmlDoc, originalText, cleanModifiedText, serializer, author, formatHints, generateRedlines));
|
|
6624
|
+
}
|
|
6625
|
+
function normalizeTargetText(text) {
|
|
6626
|
+
return String(text || "").replace(/[\t\n\u2011]/g, " ").replace(/\s+/g, " ").trim();
|
|
6627
|
+
}
|
|
6628
|
+
function textSpanVisibleText(span) {
|
|
6629
|
+
const node = span?.textElement;
|
|
6630
|
+
const localName = String(node?.localName || node?.nodeName || "").replace(/^.*:/, "");
|
|
6631
|
+
if (localName === "tab") return " ";
|
|
6632
|
+
if (localName === "br" || localName === "cr") return "\n";
|
|
6633
|
+
if (localName === "noBreakHyphen") return "\u2011";
|
|
6634
|
+
return node?.textContent || "";
|
|
5859
6635
|
}
|
|
5860
6636
|
function sanitizeAiResponse(text) {
|
|
5861
6637
|
let cleaned = text;
|
|
@@ -6684,7 +7460,7 @@ function clearSingleLineListFallbackExplicitSequence(sequenceState, numberingKey
|
|
|
6684
7460
|
if (!numberingKey) return;
|
|
6685
7461
|
sequenceState.explicitByNumberingKey.delete(String(numberingKey));
|
|
6686
7462
|
}
|
|
6687
|
-
function
|
|
7463
|
+
function getDirectWordChild2(element, localName) {
|
|
6688
7464
|
if (!element) return null;
|
|
6689
7465
|
return Array.from(element.childNodes || []).find(
|
|
6690
7466
|
(node) => node && node.nodeType === 1 && node.namespaceURI === "http://schemas.openxmlformats.org/wordprocessingml/2006/main" && node.localName === localName
|
|
@@ -6701,7 +7477,7 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
6701
7477
|
for (const paragraph of paragraphs) {
|
|
6702
7478
|
const ownerDoc = paragraph.ownerDocument;
|
|
6703
7479
|
if (!ownerDoc) continue;
|
|
6704
|
-
let pPr =
|
|
7480
|
+
let pPr = getDirectWordChild2(paragraph, "pPr");
|
|
6705
7481
|
if (!pPr) {
|
|
6706
7482
|
pPr = ownerDoc.createElementNS(
|
|
6707
7483
|
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
@@ -6710,14 +7486,14 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
6710
7486
|
paragraph.insertBefore(pPr, paragraph.firstChild);
|
|
6711
7487
|
}
|
|
6712
7488
|
if (clearParagraphPropertyChanges) {
|
|
6713
|
-
const pPrChange =
|
|
7489
|
+
const pPrChange = getDirectWordChild2(pPr, "pPrChange");
|
|
6714
7490
|
if (pPrChange) pPr.removeChild(pPrChange);
|
|
6715
7491
|
}
|
|
6716
7492
|
if (removeListPropertyNode) {
|
|
6717
|
-
const listPr =
|
|
7493
|
+
const listPr = getDirectWordChild2(pPr, "listPr");
|
|
6718
7494
|
if (listPr) pPr.removeChild(listPr);
|
|
6719
7495
|
}
|
|
6720
|
-
let numPr =
|
|
7496
|
+
let numPr = getDirectWordChild2(pPr, "numPr");
|
|
6721
7497
|
if (!numPr) {
|
|
6722
7498
|
numPr = ownerDoc.createElementNS(
|
|
6723
7499
|
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
@@ -6725,7 +7501,7 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
6725
7501
|
);
|
|
6726
7502
|
pPr.appendChild(numPr);
|
|
6727
7503
|
}
|
|
6728
|
-
let ilvlEl =
|
|
7504
|
+
let ilvlEl = getDirectWordChild2(numPr, "ilvl");
|
|
6729
7505
|
if (!ilvlEl) {
|
|
6730
7506
|
ilvlEl = ownerDoc.createElementNS(
|
|
6731
7507
|
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
@@ -6734,7 +7510,7 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
6734
7510
|
numPr.appendChild(ilvlEl);
|
|
6735
7511
|
}
|
|
6736
7512
|
ilvlEl.setAttribute("w:val", String(ilvl));
|
|
6737
|
-
let numIdEl =
|
|
7513
|
+
let numIdEl = getDirectWordChild2(numPr, "numId");
|
|
6738
7514
|
if (!numIdEl) {
|
|
6739
7515
|
numIdEl = ownerDoc.createElementNS(
|
|
6740
7516
|
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
@@ -7392,7 +8168,7 @@ function getWordParagraphs(doc) {
|
|
|
7392
8168
|
if (namespaced.length > 0) return namespaced;
|
|
7393
8169
|
return Array.from(doc.getElementsByTagNameNS("*", "p")).filter((node) => node?.localName === "p");
|
|
7394
8170
|
}
|
|
7395
|
-
function
|
|
8171
|
+
function getDirectWordChild3(node, localName) {
|
|
7396
8172
|
const children = Array.from(node?.childNodes || []);
|
|
7397
8173
|
for (const child of children) {
|
|
7398
8174
|
if (child?.nodeType !== 1) continue;
|
|
@@ -7439,7 +8215,7 @@ function readRunText(run) {
|
|
|
7439
8215
|
return text;
|
|
7440
8216
|
}
|
|
7441
8217
|
function getRunFormatting(run) {
|
|
7442
|
-
const rPr =
|
|
8218
|
+
const rPr = getDirectWordChild3(run, "rPr");
|
|
7443
8219
|
if (!rPr) return { bold: false, italic: false };
|
|
7444
8220
|
const rStyle = getWordDescendants(rPr, "rStyle")[0] || null;
|
|
7445
8221
|
const rStyleValue = getWordAttribute(rStyle, ["w:val", "val"]).toLowerCase();
|
|
@@ -7455,6 +8231,7 @@ function collectParagraphSegments(paragraph) {
|
|
|
7455
8231
|
const runs = Array.from(paragraph?.getElementsByTagNameNS?.(NS_W, "r") || []);
|
|
7456
8232
|
for (const run of runs) {
|
|
7457
8233
|
if (hasWordAncestorWithin(run, "del", paragraph)) continue;
|
|
8234
|
+
if (hasWordAncestorWithin(run, "moveFrom", paragraph)) continue;
|
|
7458
8235
|
const text = readRunText(run);
|
|
7459
8236
|
if (!text) continue;
|
|
7460
8237
|
segments.push({
|
|
@@ -7485,7 +8262,7 @@ function wrapRunMarkdown(text, format) {
|
|
|
7485
8262
|
return `${leading}${wrapped}${trailing}`;
|
|
7486
8263
|
}
|
|
7487
8264
|
function parseHeadingLevel(paragraph) {
|
|
7488
|
-
const pPr =
|
|
8265
|
+
const pPr = getDirectWordChild3(paragraph, "pPr");
|
|
7489
8266
|
if (!pPr) return null;
|
|
7490
8267
|
const pStyle = getWordDescendants(pPr, "pStyle")[0] || null;
|
|
7491
8268
|
const styleVal = getWordAttribute(pStyle, ["w:val", "val"]);
|
|
@@ -7505,7 +8282,7 @@ function parseHeadingLevel(paragraph) {
|
|
|
7505
8282
|
return null;
|
|
7506
8283
|
}
|
|
7507
8284
|
function parseListInfo(paragraph) {
|
|
7508
|
-
const pPr =
|
|
8285
|
+
const pPr = getDirectWordChild3(paragraph, "pPr");
|
|
7509
8286
|
if (!pPr) return null;
|
|
7510
8287
|
const numPr = getWordDescendants(pPr, "numPr")[0] || null;
|
|
7511
8288
|
if (!numPr) return null;
|
|
@@ -7626,11 +8403,11 @@ function findTextInParagraphIndex(paragraphIndex, searchText) {
|
|
|
7626
8403
|
};
|
|
7627
8404
|
}
|
|
7628
8405
|
function cloneRunWithText(xmlDoc, rPr, newText) {
|
|
7629
|
-
const newRun = xmlDoc
|
|
8406
|
+
const newRun = createWordElement(xmlDoc, "w:r");
|
|
7630
8407
|
if (rPr) {
|
|
7631
8408
|
newRun.appendChild(rPr.cloneNode(true));
|
|
7632
8409
|
}
|
|
7633
|
-
const newTextNode = xmlDoc
|
|
8410
|
+
const newTextNode = createWordElement(xmlDoc, "w:t");
|
|
7634
8411
|
newTextNode.setAttribute("xml:space", "preserve");
|
|
7635
8412
|
newTextNode.textContent = newText;
|
|
7636
8413
|
newRun.appendChild(newTextNode);
|
|
@@ -7642,12 +8419,12 @@ function injectMarkersIntoParagraph(xmlDoc, paragraph, textToFind, commentId, pa
|
|
|
7642
8419
|
if (!location.found || !location.startRun) {
|
|
7643
8420
|
return false;
|
|
7644
8421
|
}
|
|
7645
|
-
const startMarker = xmlDoc
|
|
8422
|
+
const startMarker = createWordElement(xmlDoc, "w:commentRangeStart");
|
|
7646
8423
|
startMarker.setAttribute("w:id", String(commentId));
|
|
7647
|
-
const endMarker = xmlDoc
|
|
8424
|
+
const endMarker = createWordElement(xmlDoc, "w:commentRangeEnd");
|
|
7648
8425
|
endMarker.setAttribute("w:id", String(commentId));
|
|
7649
|
-
const referenceRun = xmlDoc
|
|
7650
|
-
const reference = xmlDoc
|
|
8426
|
+
const referenceRun = createWordElement(xmlDoc, "w:r");
|
|
8427
|
+
const reference = createWordElement(xmlDoc, "w:commentReference");
|
|
7651
8428
|
reference.setAttribute("w:id", String(commentId));
|
|
7652
8429
|
referenceRun.appendChild(reference);
|
|
7653
8430
|
if (location.startRun === location.endRun) {
|
|
@@ -8009,13 +8786,13 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
8009
8786
|
const author = options?.author || getDefaultAuthor();
|
|
8010
8787
|
let rPrElement = rPr;
|
|
8011
8788
|
if (!rPrElement) {
|
|
8012
|
-
rPrElement = doc
|
|
8789
|
+
rPrElement = createWordElement(doc, "w:rPr");
|
|
8013
8790
|
} else {
|
|
8014
8791
|
rPrElement = rPr.cloneNode(true);
|
|
8015
8792
|
}
|
|
8016
8793
|
let previousRPrState = null;
|
|
8017
8794
|
if (generateRedlines) {
|
|
8018
|
-
previousRPrState = doc
|
|
8795
|
+
previousRPrState = createWordElement(doc, "w:rPr");
|
|
8019
8796
|
Array.from(rPrElement.childNodes).forEach((child) => {
|
|
8020
8797
|
if (child.nodeName !== "w:rPrChange") {
|
|
8021
8798
|
previousRPrState.appendChild(child.cloneNode(true));
|
|
@@ -8024,14 +8801,15 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
8024
8801
|
}
|
|
8025
8802
|
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W7, "highlight");
|
|
8026
8803
|
Array.from(existingHighlight).forEach((el) => el.remove());
|
|
8027
|
-
const highlightEl = doc
|
|
8804
|
+
const highlightEl = createWordElement(doc, "w:highlight");
|
|
8028
8805
|
highlightEl.setAttributeNS(NS_W7, "w:val", ooxmlColor);
|
|
8029
8806
|
rPrElement.appendChild(highlightEl);
|
|
8030
8807
|
if (generateRedlines && previousRPrState) {
|
|
8031
|
-
const rPrChange = doc
|
|
8032
|
-
|
|
8033
|
-
rPrChange.
|
|
8034
|
-
rPrChange.
|
|
8808
|
+
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
8809
|
+
const metadata = createRevisionMetadata(author);
|
|
8810
|
+
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
8811
|
+
rPrChange.setAttribute("w:author", metadata.author);
|
|
8812
|
+
rPrChange.setAttribute("w:date", metadata.date);
|
|
8035
8813
|
rPrChange.appendChild(previousRPrState);
|
|
8036
8814
|
const existingChange = rPrElement.getElementsByTagNameNS(NS_W7, "rPrChange");
|
|
8037
8815
|
Array.from(existingChange).forEach((el) => el.remove());
|
|
@@ -8067,7 +8845,7 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
8067
8845
|
const prefixRun = run.cloneNode(true);
|
|
8068
8846
|
const tNodes = prefixRun.getElementsByTagNameNS(NS_W7, "t");
|
|
8069
8847
|
Array.from(tNodes).forEach((t) => t.remove());
|
|
8070
|
-
const newT = doc
|
|
8848
|
+
const newT = createWordElement(doc, "w:t");
|
|
8071
8849
|
newT.setAttribute("xml:space", "preserve");
|
|
8072
8850
|
newT.textContent = prefixText;
|
|
8073
8851
|
prefixRun.appendChild(newT);
|
|
@@ -8077,7 +8855,7 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
8077
8855
|
const matchRun = run.cloneNode(true);
|
|
8078
8856
|
const tNodes = matchRun.getElementsByTagNameNS(NS_W7, "t");
|
|
8079
8857
|
Array.from(tNodes).forEach((t) => t.remove());
|
|
8080
|
-
const newT = doc
|
|
8858
|
+
const newT = createWordElement(doc, "w:t");
|
|
8081
8859
|
newT.setAttribute("xml:space", "preserve");
|
|
8082
8860
|
newT.textContent = matchText;
|
|
8083
8861
|
matchRun.appendChild(newT);
|
|
@@ -8095,7 +8873,7 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
8095
8873
|
const suffixRun = run.cloneNode(true);
|
|
8096
8874
|
const tNodes = suffixRun.getElementsByTagNameNS(NS_W7, "t");
|
|
8097
8875
|
Array.from(tNodes).forEach((t) => t.remove());
|
|
8098
|
-
const newT = doc
|
|
8876
|
+
const newT = createWordElement(doc, "w:t");
|
|
8099
8877
|
newT.setAttribute("xml:space", "preserve");
|
|
8100
8878
|
newT.textContent = suffixText;
|
|
8101
8879
|
suffixRun.appendChild(newT);
|
|
@@ -8572,7 +9350,7 @@ async function applyRedlineToOxml2(oxml, originalText, modifiedText, options = {
|
|
|
8572
9350
|
const result = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
|
|
8573
9351
|
if (result?.useNativeApi && typeof result?.oxml !== "string") {
|
|
8574
9352
|
const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
8575
|
-
return {
|
|
9353
|
+
return withOoxmlSourceType({
|
|
8576
9354
|
...result,
|
|
8577
9355
|
oxml,
|
|
8578
9356
|
hasChanges: false,
|
|
@@ -8580,7 +9358,7 @@ async function applyRedlineToOxml2(oxml, originalText, modifiedText, options = {
|
|
|
8580
9358
|
...existingWarnings,
|
|
8581
9359
|
"Standalone mode cannot execute native Word API fallback for this operation."
|
|
8582
9360
|
]
|
|
8583
|
-
};
|
|
9361
|
+
});
|
|
8584
9362
|
}
|
|
8585
9363
|
return result;
|
|
8586
9364
|
}
|
|
@@ -8636,14 +9414,14 @@ async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedTe
|
|
|
8636
9414
|
numberingXml: fallbackResult2.numberingXml
|
|
8637
9415
|
});
|
|
8638
9416
|
const fallbackWarnings2 = Array.isArray(fallbackResult2?.warnings) ? fallbackResult2.warnings : [];
|
|
8639
|
-
return {
|
|
9417
|
+
return withOoxmlSourceType({
|
|
8640
9418
|
oxml: wrappedOxml2,
|
|
8641
9419
|
hasChanges: true,
|
|
8642
9420
|
warnings: fallbackWarnings2,
|
|
8643
9421
|
listStructuralFallbackApplied: true,
|
|
8644
9422
|
listStructuralFallbackKey: fallbackResult2.listStructuralFallbackKey || null,
|
|
8645
9423
|
listStructuralFallbackNumberingXml: fallbackResult2.numberingXml || null
|
|
8646
|
-
};
|
|
9424
|
+
});
|
|
8647
9425
|
}
|
|
8648
9426
|
preflightFallbackWarnings = Array.isArray(fallbackResult2?.warnings) ? fallbackResult2.warnings : [];
|
|
8649
9427
|
}
|
|
@@ -8694,7 +9472,7 @@ async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedTe
|
|
|
8694
9472
|
});
|
|
8695
9473
|
const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
|
|
8696
9474
|
const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
8697
|
-
return {
|
|
9475
|
+
return withOoxmlSourceType({
|
|
8698
9476
|
...baseResult,
|
|
8699
9477
|
oxml: wrappedOxml,
|
|
8700
9478
|
hasChanges: true,
|
|
@@ -8702,7 +9480,7 @@ async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedTe
|
|
|
8702
9480
|
listStructuralFallbackApplied: true,
|
|
8703
9481
|
listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
|
|
8704
9482
|
listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
|
|
8705
|
-
};
|
|
9483
|
+
});
|
|
8706
9484
|
}
|
|
8707
9485
|
export {
|
|
8708
9486
|
ContainerKind,
|
|
@@ -8714,6 +9492,7 @@ export {
|
|
|
8714
9492
|
RoutePlanKind,
|
|
8715
9493
|
RunKind,
|
|
8716
9494
|
WORD_MAIN_NS,
|
|
9495
|
+
acceptTrackedChangesInOoxml,
|
|
8717
9496
|
applyFormattingRemovalToOoxml,
|
|
8718
9497
|
applyHighlightToOoxml,
|
|
8719
9498
|
applyRedlineToOxml2 as applyRedlineToOxml,
|
|
@@ -8729,7 +9508,9 @@ export {
|
|
|
8729
9508
|
collectContiguousListParagraphBlock,
|
|
8730
9509
|
configureLogger,
|
|
8731
9510
|
configureXmlProvider,
|
|
9511
|
+
containsTrackedChanges,
|
|
8732
9512
|
createDynamicNumberingIdState,
|
|
9513
|
+
deleteCommentsByAuthorInOoxml,
|
|
8733
9514
|
enforceListBindingOnParagraphNodes,
|
|
8734
9515
|
ensureCommentsArtifactsInZip,
|
|
8735
9516
|
ensureNumberingArtifactsInZip,
|
|
@@ -8775,6 +9556,7 @@ export {
|
|
|
8775
9556
|
preprocessMarkdown,
|
|
8776
9557
|
reconcileMarkdownTableOoxml,
|
|
8777
9558
|
recordSingleLineListFallbackExplicitSequence,
|
|
9559
|
+
rejectTrackedChangesInOoxml,
|
|
8778
9560
|
remapNumberingPayloadForDocument,
|
|
8779
9561
|
removeFormattingFromRPr,
|
|
8780
9562
|
reserveNextNumberingId,
|