@adeu/core 1.27.0 → 1.29.0
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/dist/index.cjs +696 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -2
- package/dist/index.d.ts +63 -2
- package/dist/index.js +696 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/diff.test.ts +8 -1
- package/src/diff.ts +242 -3
- package/src/engine.atomic.test.ts +5 -1
- package/src/engine.batch.test.ts +16 -6
- package/src/engine.bugs.test.ts +49 -0
- package/src/engine.tables.test.ts +31 -0
- package/src/engine.ts +443 -66
- package/src/ingest.test.ts +3 -1
- package/src/ingest.ts +35 -5
- package/src/mapper.ts +94 -11
- package/src/repro_agy_bug.test.ts +108 -0
- package/src/repro_qa_mcp_issues_2026_07_20.test.ts +224 -0
- package/src/repro_qa_report_v9.test.ts +392 -0
- package/src/sanitize/core.ts +12 -1
- package/src/sanitize/report.ts +9 -7
- package/src/sanitize/sanitize.test.ts +1 -0
- package/src/sanitize/transforms.ts +39 -0
- package/src/utils/docx.ts +87 -0
package/dist/index.cjs
CHANGED
|
@@ -1226,6 +1226,18 @@ function get_paragraph_prefix(paragraph, style_cache, default_pstyle) {
|
|
|
1226
1226
|
if (custom_level !== null) return "#".repeat(custom_level) + " ";
|
|
1227
1227
|
}
|
|
1228
1228
|
if (!style_name || style_name === "Normal") {
|
|
1229
|
+
let is_inside_tc = false;
|
|
1230
|
+
let curr = paragraph._element;
|
|
1231
|
+
while (curr) {
|
|
1232
|
+
if (curr.tagName === "w:tc") {
|
|
1233
|
+
is_inside_tc = true;
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
curr = curr.parentNode;
|
|
1237
|
+
}
|
|
1238
|
+
if (is_inside_tc) {
|
|
1239
|
+
return "";
|
|
1240
|
+
}
|
|
1229
1241
|
const text = paragraph.text.trim();
|
|
1230
1242
|
if (text && text.length < 100 && text === text.toUpperCase()) {
|
|
1231
1243
|
let is_bold = false;
|
|
@@ -1297,6 +1309,50 @@ function split_boundary_whitespace(text) {
|
|
|
1297
1309
|
text.substring(lead_len + core.length)
|
|
1298
1310
|
];
|
|
1299
1311
|
}
|
|
1312
|
+
function compute_change_pair_map(states_list) {
|
|
1313
|
+
const groups = [];
|
|
1314
|
+
let current = [];
|
|
1315
|
+
const seen_ids = /* @__PURE__ */ new Set();
|
|
1316
|
+
for (const [ins_map, del_map] of states_list) {
|
|
1317
|
+
const ins_entries = Object.entries(ins_map);
|
|
1318
|
+
const del_entries = Object.entries(del_map);
|
|
1319
|
+
if (ins_entries.length === 0 && del_entries.length === 0) {
|
|
1320
|
+
if (current.length > 0) {
|
|
1321
|
+
groups.push(current);
|
|
1322
|
+
current = [];
|
|
1323
|
+
}
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const state_new = [];
|
|
1327
|
+
for (const [uid, meta] of ins_entries) {
|
|
1328
|
+
if (!seen_ids.has(uid)) {
|
|
1329
|
+
state_new.push([uid, meta && meta.author || "Unknown"]);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
for (const [uid, meta] of del_entries) {
|
|
1333
|
+
if (!seen_ids.has(uid)) {
|
|
1334
|
+
state_new.push([uid, meta && meta.author || "Unknown"]);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
for (const [uid, author] of state_new) {
|
|
1338
|
+
seen_ids.add(uid);
|
|
1339
|
+
if (current.length > 0 && current[current.length - 1][1] !== author) {
|
|
1340
|
+
groups.push(current);
|
|
1341
|
+
current = [];
|
|
1342
|
+
}
|
|
1343
|
+
current.push([uid, author]);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (current.length > 0) groups.push(current);
|
|
1347
|
+
const pair_map = {};
|
|
1348
|
+
for (const group of groups) {
|
|
1349
|
+
if (group.length < 2) continue;
|
|
1350
|
+
for (const [uid] of group) {
|
|
1351
|
+
pair_map[uid] = group.filter(([u]) => u !== uid).map(([u]) => `Chg:${u}`).join(", ");
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
return pair_map;
|
|
1355
|
+
}
|
|
1300
1356
|
function apply_formatting_to_segments(text, prefix, suffix) {
|
|
1301
1357
|
if (!prefix && !suffix) return text;
|
|
1302
1358
|
if (!text) return "";
|
|
@@ -1699,11 +1755,34 @@ ${header}`;
|
|
|
1699
1755
|
const cell_start = current;
|
|
1700
1756
|
current = this._map_blocks(cell, current);
|
|
1701
1757
|
if (!this.clean_view && !this.original_view) {
|
|
1702
|
-
|
|
1703
|
-
|
|
1758
|
+
let firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
1759
|
+
let paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
1760
|
+
const is_empty = current === cell_start;
|
|
1761
|
+
if (!paraId && is_empty) {
|
|
1762
|
+
if (!firstP) {
|
|
1763
|
+
const xmlDoc = cell._element.ownerDocument;
|
|
1764
|
+
firstP = xmlDoc.createElement("w:p");
|
|
1765
|
+
cell._element.appendChild(firstP);
|
|
1766
|
+
}
|
|
1767
|
+
const allPs = Array.from(cell._element.ownerDocument.getElementsByTagName("w:p"));
|
|
1768
|
+
const index = allPs.indexOf(firstP);
|
|
1769
|
+
let hash = 2166136261;
|
|
1770
|
+
const str = `fallback-paraId-${index}`;
|
|
1771
|
+
for (let i = 0; i < str.length; i++) {
|
|
1772
|
+
hash ^= str.charCodeAt(i);
|
|
1773
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
1774
|
+
}
|
|
1775
|
+
paraId = (hash >>> 0).toString(16).toUpperCase().padStart(8, "0");
|
|
1776
|
+
firstP.setAttribute("w14:paraId", paraId);
|
|
1777
|
+
}
|
|
1704
1778
|
if (paraId && firstP) {
|
|
1705
1779
|
const cellPara = new Paragraph(firstP, cell);
|
|
1706
1780
|
this._add_virtual_text("", current, cellPara);
|
|
1781
|
+
const has_content = current > cell_start;
|
|
1782
|
+
if (has_content) {
|
|
1783
|
+
this._add_virtual_text(" ", current, cellPara);
|
|
1784
|
+
current += 1;
|
|
1785
|
+
}
|
|
1707
1786
|
const anchor = `{#cell:${paraId}}`;
|
|
1708
1787
|
this._add_virtual_text(anchor, current, cellPara);
|
|
1709
1788
|
current += anchor.length;
|
|
@@ -2054,6 +2133,8 @@ ${header}`;
|
|
|
2054
2133
|
const change_lines = [];
|
|
2055
2134
|
const comment_lines = [];
|
|
2056
2135
|
const seen_sigs = /* @__PURE__ */ new Set();
|
|
2136
|
+
const pair_map = compute_change_pair_map(states_list);
|
|
2137
|
+
const pairSuffix = (uid) => pair_map[uid] ? ` (pairs with ${pair_map[uid]})` : "";
|
|
2057
2138
|
for (const [ins_map, del_map, comments_set, fmt_map] of states_list) {
|
|
2058
2139
|
for (const [uid, meta] of Object.entries(
|
|
2059
2140
|
ins_map
|
|
@@ -2061,7 +2142,7 @@ ${header}`;
|
|
|
2061
2142
|
const sig = `Chg:${uid}`;
|
|
2062
2143
|
if (!seen_sigs.has(sig)) {
|
|
2063
2144
|
const auth = meta.author || "Unknown";
|
|
2064
|
-
change_lines.push(`[${sig} insert] ${auth}`);
|
|
2145
|
+
change_lines.push(`[${sig} insert] ${auth}${pairSuffix(uid)}`);
|
|
2065
2146
|
seen_sigs.add(sig);
|
|
2066
2147
|
}
|
|
2067
2148
|
}
|
|
@@ -2071,7 +2152,7 @@ ${header}`;
|
|
|
2071
2152
|
const sig = `Chg:${uid}`;
|
|
2072
2153
|
if (!seen_sigs.has(sig)) {
|
|
2073
2154
|
const auth = meta.author || "Unknown";
|
|
2074
|
-
change_lines.push(`[${sig} delete] ${auth}`);
|
|
2155
|
+
change_lines.push(`[${sig} delete] ${auth}${pairSuffix(uid)}`);
|
|
2075
2156
|
seen_sigs.add(sig);
|
|
2076
2157
|
}
|
|
2077
2158
|
}
|
|
@@ -2211,21 +2292,60 @@ ${header}`;
|
|
|
2211
2292
|
}
|
|
2212
2293
|
return results;
|
|
2213
2294
|
}
|
|
2295
|
+
/**
|
|
2296
|
+
* True when no run-backed span overlaps [start, start+length): the range
|
|
2297
|
+
* covers only virtual projection text — meta bubbles (change/comment
|
|
2298
|
+
* headers, timestamps), style markers, list prefixes. Such text does not
|
|
2299
|
+
* exist in the document, so it can neither satisfy a match nor count
|
|
2300
|
+
* toward ambiguity (QA 2026-07-19 ADEU-QA-002 C): an edit targeting "4"
|
|
2301
|
+
* used to be rejected as "appears 8 times" because a comment bubble's
|
|
2302
|
+
* timestamp matched.
|
|
2303
|
+
*
|
|
2304
|
+
* Anchor tokens ({#Bookmark}, {#cell:paraId}) are the exception: they are
|
|
2305
|
+
* deliberate virtual TARGETING surfaces (empty-cell writes, bookmark
|
|
2306
|
+
* anchors) and must stay matchable.
|
|
2307
|
+
*/
|
|
2308
|
+
range_is_virtual_only(start, length) {
|
|
2309
|
+
const end = start + length;
|
|
2310
|
+
const overlapping = this.spans.filter(
|
|
2311
|
+
(s) => s.end > start && s.start < end
|
|
2312
|
+
);
|
|
2313
|
+
if (overlapping.some((s) => s.run !== null)) return false;
|
|
2314
|
+
return !overlapping.some(
|
|
2315
|
+
(s) => s.run === null && s.text.startsWith("{#")
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
/** Filters find_all_match_indices output down to matches that touch at
|
|
2319
|
+
* least one run-backed span. See range_is_virtual_only. */
|
|
2320
|
+
drop_virtual_only_matches(matches) {
|
|
2321
|
+
return matches.filter(
|
|
2322
|
+
([start, length]) => !this.range_is_virtual_only(start, length)
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2214
2325
|
find_match_index(target_text, is_regex = false) {
|
|
2215
2326
|
if (is_regex) {
|
|
2216
2327
|
try {
|
|
2217
2328
|
const match = userSearch(target_text, this.full_text);
|
|
2218
|
-
if (match
|
|
2329
|
+
if (match && !this.range_is_virtual_only(match.start, match.end - match.start)) {
|
|
2330
|
+
return [match.start, match.end - match.start];
|
|
2331
|
+
}
|
|
2219
2332
|
} catch (e) {
|
|
2220
2333
|
if (e instanceof RegexTimeoutError) throw e;
|
|
2221
2334
|
}
|
|
2222
2335
|
return [-1, 0];
|
|
2223
2336
|
}
|
|
2224
|
-
let
|
|
2225
|
-
|
|
2337
|
+
let from = 0;
|
|
2338
|
+
while (true) {
|
|
2339
|
+
const idx = this.full_text.indexOf(target_text, from);
|
|
2340
|
+
if (idx === -1) break;
|
|
2341
|
+
if (!this.range_is_virtual_only(idx, target_text.length)) {
|
|
2342
|
+
return [idx, target_text.length];
|
|
2343
|
+
}
|
|
2344
|
+
from = idx + 1;
|
|
2345
|
+
}
|
|
2226
2346
|
const norm_full = this._replace_smart_quotes(this.full_text);
|
|
2227
2347
|
const norm_target = this._replace_smart_quotes(target_text);
|
|
2228
|
-
start_idx = norm_full.indexOf(norm_target);
|
|
2348
|
+
let start_idx = norm_full.indexOf(norm_target);
|
|
2229
2349
|
if (start_idx !== -1) return [start_idx, target_text.length];
|
|
2230
2350
|
const stripped_target = this._strip_markdown_formatting(target_text);
|
|
2231
2351
|
if (this.full_text.includes(stripped_target)) {
|
|
@@ -2235,9 +2355,12 @@ ${header}`;
|
|
|
2235
2355
|
const plain_first = this._find_plain_projection_matches(target_text);
|
|
2236
2356
|
if (plain_first.length > 0) return plain_first[0];
|
|
2237
2357
|
try {
|
|
2238
|
-
const pattern = new RegExp(this._make_fuzzy_regex(target_text));
|
|
2239
|
-
const match
|
|
2240
|
-
|
|
2358
|
+
const pattern = new RegExp(this._make_fuzzy_regex(target_text), "g");
|
|
2359
|
+
for (const match of this.full_text.matchAll(pattern)) {
|
|
2360
|
+
if (!this.range_is_virtual_only(match.index, match[0].length)) {
|
|
2361
|
+
return [match.index, match[0].length];
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2241
2364
|
} catch (e) {
|
|
2242
2365
|
}
|
|
2243
2366
|
return [-1, 0];
|
|
@@ -2766,6 +2889,157 @@ function _sequence_opcodes(a, b) {
|
|
|
2766
2889
|
flushPending();
|
|
2767
2890
|
return ops;
|
|
2768
2891
|
}
|
|
2892
|
+
function _is_table_blob(block) {
|
|
2893
|
+
const lines = block.split("\n");
|
|
2894
|
+
return lines.length > 0 && lines.every((line) => line.includes(" | "));
|
|
2895
|
+
}
|
|
2896
|
+
function _table_blob_row_edits(orig_blob, mod_blob) {
|
|
2897
|
+
const rows_o = orig_blob.split("\n");
|
|
2898
|
+
const rows_m = mod_blob.split("\n");
|
|
2899
|
+
if (rows_o.length !== rows_m.length) return null;
|
|
2900
|
+
const row_edits = [];
|
|
2901
|
+
for (let k = 0; k < rows_o.length; k++) {
|
|
2902
|
+
if (rows_o[k] === rows_m[k]) continue;
|
|
2903
|
+
row_edits.push({
|
|
2904
|
+
type: "modify",
|
|
2905
|
+
target_text: rows_o[k],
|
|
2906
|
+
new_text: rows_m[k],
|
|
2907
|
+
comment: "Diff: Table row modified",
|
|
2908
|
+
_is_table_edit: true
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
return row_edits;
|
|
2912
|
+
}
|
|
2913
|
+
function _split_cross_paragraph_hunks(edits) {
|
|
2914
|
+
const out = [];
|
|
2915
|
+
for (const e of edits) {
|
|
2916
|
+
const target = e.target_text || "";
|
|
2917
|
+
const newText = e.new_text || "";
|
|
2918
|
+
const idx = e._match_start_index;
|
|
2919
|
+
if (idx === void 0 || idx === null || !target.includes("\n\n") || target.split("\n\n").length - 1 === newText.split("\n\n").length - 1) {
|
|
2920
|
+
out.push(e);
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
const parts = target.split("\n\n");
|
|
2924
|
+
if (parts.length < 2 || !parts[0].trim() || !parts[parts.length - 1].trim()) {
|
|
2925
|
+
out.push(e);
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2928
|
+
let offset = idx;
|
|
2929
|
+
for (const piece of parts.slice(0, -1)) {
|
|
2930
|
+
out.push({
|
|
2931
|
+
type: "modify",
|
|
2932
|
+
target_text: piece + "\n\n",
|
|
2933
|
+
new_text: "",
|
|
2934
|
+
comment: e.comment || "Diff: Text deleted",
|
|
2935
|
+
_match_start_index: offset
|
|
2936
|
+
});
|
|
2937
|
+
offset += piece.length + 2;
|
|
2938
|
+
}
|
|
2939
|
+
out.push({
|
|
2940
|
+
type: "modify",
|
|
2941
|
+
target_text: parts[parts.length - 1],
|
|
2942
|
+
new_text: newText,
|
|
2943
|
+
comment: e.comment,
|
|
2944
|
+
_match_start_index: offset
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
return out;
|
|
2948
|
+
}
|
|
2949
|
+
function generate_edits_via_paragraph_alignment(original_text, modified_text) {
|
|
2950
|
+
const orig_stripped = original_text.trimEnd();
|
|
2951
|
+
const orig_ws = original_text.slice(orig_stripped.length);
|
|
2952
|
+
const mod_stripped = modified_text.trimEnd();
|
|
2953
|
+
modified_text = mod_stripped + orig_ws;
|
|
2954
|
+
const orig_paragraphs = original_text.split("\n\n");
|
|
2955
|
+
const mod_paragraphs = modified_text.split("\n\n");
|
|
2956
|
+
const orig_offsets = [];
|
|
2957
|
+
let current_offset = 0;
|
|
2958
|
+
for (const p of orig_paragraphs) {
|
|
2959
|
+
orig_offsets.push(current_offset);
|
|
2960
|
+
current_offset += p.length + 2;
|
|
2961
|
+
}
|
|
2962
|
+
const opcodes = _sequence_opcodes(orig_paragraphs, mod_paragraphs);
|
|
2963
|
+
const edits = [];
|
|
2964
|
+
for (const [tag, i1, i2, j1, j2] of opcodes) {
|
|
2965
|
+
if (tag === "equal") continue;
|
|
2966
|
+
const offset = i1 < orig_offsets.length ? orig_offsets[i1] : original_text.length;
|
|
2967
|
+
if (tag === "delete") {
|
|
2968
|
+
if (i2 < orig_paragraphs.length) {
|
|
2969
|
+
let piece_offset = offset;
|
|
2970
|
+
for (let k = i1; k < i2; k++) {
|
|
2971
|
+
const piece = orig_paragraphs[k] + "\n\n";
|
|
2972
|
+
edits.push({
|
|
2973
|
+
type: "modify",
|
|
2974
|
+
target_text: piece,
|
|
2975
|
+
new_text: "",
|
|
2976
|
+
comment: "Diff: Text deleted",
|
|
2977
|
+
_match_start_index: piece_offset
|
|
2978
|
+
});
|
|
2979
|
+
piece_offset += piece.length;
|
|
2980
|
+
}
|
|
2981
|
+
} else {
|
|
2982
|
+
let deleted_text = orig_paragraphs.slice(i1, i2).join("\n\n");
|
|
2983
|
+
let del_offset = offset;
|
|
2984
|
+
if (i1 > 0) {
|
|
2985
|
+
deleted_text = "\n\n" + deleted_text;
|
|
2986
|
+
del_offset -= 2;
|
|
2987
|
+
}
|
|
2988
|
+
edits.push({
|
|
2989
|
+
type: "modify",
|
|
2990
|
+
target_text: deleted_text,
|
|
2991
|
+
new_text: "",
|
|
2992
|
+
comment: "Diff: Text deleted",
|
|
2993
|
+
_match_start_index: del_offset
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
} else if (tag === "insert") {
|
|
2997
|
+
let inserted_text = mod_paragraphs.slice(j1, j2).join("\n\n");
|
|
2998
|
+
if (i1 < orig_paragraphs.length) {
|
|
2999
|
+
inserted_text = inserted_text + "\n\n";
|
|
3000
|
+
} else {
|
|
3001
|
+
inserted_text = "\n\n" + inserted_text;
|
|
3002
|
+
}
|
|
3003
|
+
edits.push({
|
|
3004
|
+
type: "modify",
|
|
3005
|
+
target_text: "",
|
|
3006
|
+
new_text: inserted_text,
|
|
3007
|
+
comment: "Diff: Text inserted",
|
|
3008
|
+
_match_start_index: offset
|
|
3009
|
+
});
|
|
3010
|
+
} else if (tag === "replace") {
|
|
3011
|
+
if (i2 - i1 === j2 - j1 && Array.from({ length: i2 - i1 }).some(
|
|
3012
|
+
(_, k) => _is_table_blob(orig_paragraphs[i1 + k]) || _is_table_blob(mod_paragraphs[j1 + k])
|
|
3013
|
+
)) {
|
|
3014
|
+
for (let k = 0; k < i2 - i1; k++) {
|
|
3015
|
+
const orig_p = orig_paragraphs[i1 + k];
|
|
3016
|
+
const mod_p = mod_paragraphs[j1 + k];
|
|
3017
|
+
if (orig_p === mod_p) continue;
|
|
3018
|
+
const pair_offset = orig_offsets[i1 + k];
|
|
3019
|
+
const row_edits = _is_table_blob(orig_p) && _is_table_blob(mod_p) ? _table_blob_row_edits(orig_p, mod_p) : null;
|
|
3020
|
+
if (row_edits !== null) {
|
|
3021
|
+
edits.push(...row_edits);
|
|
3022
|
+
continue;
|
|
3023
|
+
}
|
|
3024
|
+
const pair_edits = generate_edits_from_text(orig_p, mod_p);
|
|
3025
|
+
for (const ce of pair_edits) {
|
|
3026
|
+
ce._match_start_index = (ce._match_start_index || 0) + pair_offset;
|
|
3027
|
+
edits.push(ce);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
continue;
|
|
3031
|
+
}
|
|
3032
|
+
const orig_chunk = orig_paragraphs.slice(i1, i2).join("\n\n");
|
|
3033
|
+
const mod_chunk = mod_paragraphs.slice(j1, j2).join("\n\n");
|
|
3034
|
+
const chunk_edits = generate_edits_from_text(orig_chunk, mod_chunk);
|
|
3035
|
+
for (const ce of chunk_edits) {
|
|
3036
|
+
ce._match_start_index = (ce._match_start_index || 0) + offset;
|
|
3037
|
+
edits.push(ce);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
return _split_cross_paragraph_hunks(edits);
|
|
3042
|
+
}
|
|
2769
3043
|
var _IMAGE_MARKER_RE = /!\[[^\]]*\]\(docx-image:[^)]*\)/g;
|
|
2770
3044
|
function _drop_image_marker_hunks(edits, warnings) {
|
|
2771
3045
|
const _normalized = (s) => s.replace(_IMAGE_MARKER_RE, "").replace(/\s+/g, " ").trim();
|
|
@@ -2963,7 +3237,7 @@ function generate_structured_edits(text_orig, struct_orig, text_mod, struct_mod)
|
|
|
2963
3237
|
);
|
|
2964
3238
|
const flat = _drop_marker_interior_hunks(
|
|
2965
3239
|
_drop_image_marker_hunks(
|
|
2966
|
-
|
|
3240
|
+
generate_edits_via_paragraph_alignment(text_orig, text_mod),
|
|
2967
3241
|
warnings
|
|
2968
3242
|
),
|
|
2969
3243
|
text_orig,
|
|
@@ -2989,7 +3263,7 @@ function generate_structured_edits(text_orig, struct_orig, text_mod, struct_mod)
|
|
|
2989
3263
|
);
|
|
2990
3264
|
}
|
|
2991
3265
|
if (!tables_alignable) {
|
|
2992
|
-
const part_edits =
|
|
3266
|
+
const part_edits = generate_edits_via_paragraph_alignment(
|
|
2993
3267
|
text_orig.substring(po_start, po_end),
|
|
2994
3268
|
text_mod.substring(pm_start, pm_end)
|
|
2995
3269
|
);
|
|
@@ -3014,7 +3288,7 @@ function generate_structured_edits(text_orig, struct_orig, text_mod, struct_mod)
|
|
|
3014
3288
|
const seg_o_end = boundaries_o[seg_idx + 1][0];
|
|
3015
3289
|
const seg_m_start = boundaries_m[seg_idx][1];
|
|
3016
3290
|
const seg_m_end = boundaries_m[seg_idx + 1][0];
|
|
3017
|
-
const seg_edits =
|
|
3291
|
+
const seg_edits = generate_edits_via_paragraph_alignment(
|
|
3018
3292
|
text_orig.substring(seg_o_start, seg_o_end),
|
|
3019
3293
|
text_mod.substring(seg_m_start, seg_m_end)
|
|
3020
3294
|
);
|
|
@@ -4462,10 +4736,21 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4462
4736
|
parts_to_process.push(part._element);
|
|
4463
4737
|
}
|
|
4464
4738
|
}
|
|
4739
|
+
let accepted_insertions = 0;
|
|
4740
|
+
let accepted_deletions = 0;
|
|
4741
|
+
let accepted_formatting = 0;
|
|
4742
|
+
for (const root_element of parts_to_process) {
|
|
4743
|
+
accepted_insertions += findAllDescendants(root_element, "w:ins").length;
|
|
4744
|
+
accepted_deletions += findAllDescendants(root_element, "w:del").length;
|
|
4745
|
+
for (const tag of ["w:rPrChange", "w:pPrChange", "w:sectPrChange"]) {
|
|
4746
|
+
accepted_formatting += findAllDescendants(root_element, tag).length;
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
let removed_comments = 0;
|
|
4465
4750
|
for (const root_element of parts_to_process) {
|
|
4466
4751
|
const insNodes = findAllDescendants(root_element, "w:ins");
|
|
4467
4752
|
for (const ins of insNodes) {
|
|
4468
|
-
this._clean_wrapping_comments(ins);
|
|
4753
|
+
removed_comments += this._clean_wrapping_comments(ins);
|
|
4469
4754
|
const parent = ins.parentNode;
|
|
4470
4755
|
if (!parent) continue;
|
|
4471
4756
|
if (parent.tagName === "w:trPr") {
|
|
@@ -4509,8 +4794,8 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4509
4794
|
if (has_content) {
|
|
4510
4795
|
rPr.removeChild(delMark);
|
|
4511
4796
|
} else {
|
|
4512
|
-
this._clean_wrapping_comments(p);
|
|
4513
|
-
this._delete_comments_in_element(p);
|
|
4797
|
+
removed_comments += this._clean_wrapping_comments(p);
|
|
4798
|
+
removed_comments += this._delete_comments_in_element(p);
|
|
4514
4799
|
if (p.parentNode) {
|
|
4515
4800
|
p.parentNode.removeChild(p);
|
|
4516
4801
|
}
|
|
@@ -4520,8 +4805,8 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4520
4805
|
}
|
|
4521
4806
|
const delNodes = findAllDescendants(root_element, "w:del");
|
|
4522
4807
|
for (const d of delNodes) {
|
|
4523
|
-
this._clean_wrapping_comments(d);
|
|
4524
|
-
this._delete_comments_in_element(d);
|
|
4808
|
+
removed_comments += this._clean_wrapping_comments(d);
|
|
4809
|
+
removed_comments += this._delete_comments_in_element(d);
|
|
4525
4810
|
const parent = d.parentNode;
|
|
4526
4811
|
if (parent) {
|
|
4527
4812
|
if (parent.tagName === "w:trPr") {
|
|
@@ -4616,6 +4901,12 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4616
4901
|
}
|
|
4617
4902
|
}
|
|
4618
4903
|
}
|
|
4904
|
+
return {
|
|
4905
|
+
accepted_insertions,
|
|
4906
|
+
accepted_deletions,
|
|
4907
|
+
accepted_formatting,
|
|
4908
|
+
removed_comments
|
|
4909
|
+
};
|
|
4619
4910
|
}
|
|
4620
4911
|
/**
|
|
4621
4912
|
* Revert every tracked change, returning the document to the state it had
|
|
@@ -4868,6 +5159,24 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4868
5159
|
*
|
|
4869
5160
|
* Does NOT attach comments; callers handle that.
|
|
4870
5161
|
*/
|
|
5162
|
+
_clone_pPr_scrubbing_headings(existing_pPr) {
|
|
5163
|
+
const pPr_clone = existing_pPr.cloneNode(true);
|
|
5164
|
+
const pStyle_el = findChild(pPr_clone, "w:pStyle");
|
|
5165
|
+
if (pStyle_el) {
|
|
5166
|
+
const style_val = pStyle_el.getAttribute("w:val");
|
|
5167
|
+
if (style_val) {
|
|
5168
|
+
const is_heading = style_val.startsWith("Heading") || style_val === "Title" || style_val.replace(/\s+/g, "").startsWith("Heading");
|
|
5169
|
+
if (is_heading) {
|
|
5170
|
+
pPr_clone.removeChild(pStyle_el);
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
const outlineLvl_el = findChild(pPr_clone, "w:outlineLvl");
|
|
5175
|
+
if (outlineLvl_el) {
|
|
5176
|
+
pPr_clone.removeChild(outlineLvl_el);
|
|
5177
|
+
}
|
|
5178
|
+
return pPr_clone;
|
|
5179
|
+
}
|
|
4871
5180
|
_track_insert_multiline(text, anchor_run, anchor_paragraph, reuse_id, positional_anchor_el = null, suppress_emphasis = false, insert_before = false) {
|
|
4872
5181
|
if (!text) {
|
|
4873
5182
|
return {
|
|
@@ -4989,7 +5298,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
4989
5298
|
} else {
|
|
4990
5299
|
const existing_pPr = findChild(current_p, "w:pPr");
|
|
4991
5300
|
if (existing_pPr) {
|
|
4992
|
-
new_p.appendChild(
|
|
5301
|
+
new_p.appendChild(this._clone_pPr_scrubbing_headings(existing_pPr));
|
|
4993
5302
|
}
|
|
4994
5303
|
}
|
|
4995
5304
|
let pPr = findChild(new_p, "w:pPr");
|
|
@@ -5254,7 +5563,10 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5254
5563
|
}
|
|
5255
5564
|
return null;
|
|
5256
5565
|
}
|
|
5566
|
+
/** Returns how many comment BODIES were actually deleted (see below: only
|
|
5567
|
+
* our own are; foreign ones keep their body and lose only the anchor). */
|
|
5257
5568
|
_clean_wrapping_comments(element) {
|
|
5569
|
+
let deleted = 0;
|
|
5258
5570
|
let first_node = element;
|
|
5259
5571
|
while (true) {
|
|
5260
5572
|
const prev2 = getPreviousElement(first_node);
|
|
@@ -5322,6 +5634,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5322
5634
|
const is_own = author !== null && author === this.author;
|
|
5323
5635
|
if (is_own) {
|
|
5324
5636
|
this.comments_manager.deleteComment(c_id);
|
|
5637
|
+
deleted++;
|
|
5325
5638
|
}
|
|
5326
5639
|
if (s.parentNode) s.parentNode.removeChild(s);
|
|
5327
5640
|
for (const e of ends_to_remove) {
|
|
@@ -5339,13 +5652,17 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5339
5652
|
}
|
|
5340
5653
|
}
|
|
5341
5654
|
}
|
|
5655
|
+
return deleted;
|
|
5342
5656
|
}
|
|
5657
|
+
/** Returns how many comment bodies were deleted. */
|
|
5343
5658
|
_delete_comments_in_element(element) {
|
|
5659
|
+
let deleted = 0;
|
|
5344
5660
|
const refs = findAllDescendants(element, "w:commentReference");
|
|
5345
5661
|
for (const ref of refs) {
|
|
5346
5662
|
const c_id = ref.getAttribute("w:id");
|
|
5347
5663
|
if (c_id) {
|
|
5348
5664
|
this.comments_manager.deleteComment(c_id);
|
|
5665
|
+
deleted++;
|
|
5349
5666
|
for (const tag of ["w:commentRangeStart", "w:commentRangeEnd"]) {
|
|
5350
5667
|
const nodes = findAllDescendants(this.doc.element, tag);
|
|
5351
5668
|
for (const node of nodes) {
|
|
@@ -5356,6 +5673,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5356
5673
|
}
|
|
5357
5674
|
}
|
|
5358
5675
|
}
|
|
5676
|
+
return deleted;
|
|
5359
5677
|
}
|
|
5360
5678
|
validate_edits(edits, index_offset = 0) {
|
|
5361
5679
|
const errors = [];
|
|
@@ -5384,18 +5702,16 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5384
5702
|
continue;
|
|
5385
5703
|
}
|
|
5386
5704
|
}
|
|
5387
|
-
let matches = this.mapper.
|
|
5388
|
-
edit.target_text,
|
|
5389
|
-
is_regex
|
|
5705
|
+
let matches = this.mapper.drop_virtual_only_matches(
|
|
5706
|
+
this.mapper.find_all_match_indices(edit.target_text, is_regex)
|
|
5390
5707
|
);
|
|
5391
5708
|
let activeText = this.mapper.full_text;
|
|
5392
5709
|
let target_mapper = this.mapper;
|
|
5393
5710
|
if (matches.length === 0) {
|
|
5394
5711
|
if (!this.clean_mapper)
|
|
5395
5712
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
5396
|
-
matches = this.clean_mapper.
|
|
5397
|
-
edit.target_text,
|
|
5398
|
-
is_regex
|
|
5713
|
+
matches = this.clean_mapper.drop_virtual_only_matches(
|
|
5714
|
+
this.clean_mapper.find_all_match_indices(edit.target_text, is_regex)
|
|
5399
5715
|
);
|
|
5400
5716
|
if (matches.length > 0) {
|
|
5401
5717
|
activeText = this.clean_mapper.full_text;
|
|
@@ -5418,9 +5734,11 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5418
5734
|
if (!this.original_mapper) {
|
|
5419
5735
|
this.original_mapper = new DocumentMapper(this.doc, false, true);
|
|
5420
5736
|
}
|
|
5421
|
-
const orig_matches = this.original_mapper.
|
|
5422
|
-
|
|
5423
|
-
|
|
5737
|
+
const orig_matches = this.original_mapper.drop_virtual_only_matches(
|
|
5738
|
+
this.original_mapper.find_all_match_indices(
|
|
5739
|
+
edit.target_text,
|
|
5740
|
+
is_regex
|
|
5741
|
+
)
|
|
5424
5742
|
);
|
|
5425
5743
|
if (orig_matches.length > 0) {
|
|
5426
5744
|
is_deleted_text = true;
|
|
@@ -5642,10 +5960,15 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5642
5960
|
}
|
|
5643
5961
|
static _column_count_at(mapper, start, length) {
|
|
5644
5962
|
for (const s of mapper.spans) {
|
|
5645
|
-
if (s.
|
|
5963
|
+
if (s.end <= start || s.start >= start + length) {
|
|
5646
5964
|
continue;
|
|
5647
5965
|
}
|
|
5648
|
-
let curr =
|
|
5966
|
+
let curr = null;
|
|
5967
|
+
if (s.run !== null) {
|
|
5968
|
+
curr = s.run._element;
|
|
5969
|
+
} else if (s.paragraph !== null) {
|
|
5970
|
+
curr = s.paragraph._element;
|
|
5971
|
+
}
|
|
5649
5972
|
while (curr) {
|
|
5650
5973
|
if (curr.nodeType === 1 && curr.tagName === "w:tr") {
|
|
5651
5974
|
return findAllDescendants(curr, "w:tc").filter(
|
|
@@ -5785,7 +6108,10 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5785
6108
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
5786
6109
|
);
|
|
5787
6110
|
if (!dry_run_mode) {
|
|
5788
|
-
|
|
6111
|
+
let action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
6112
|
+
if (actions.length > 0 && action_errors.length === 0) {
|
|
6113
|
+
action_errors = this.validate_action_pairing(actions);
|
|
6114
|
+
}
|
|
5789
6115
|
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
5790
6116
|
const edit_errors = validate_edits_now ? this.validate_edits(edits) : [];
|
|
5791
6117
|
const all_errors = [...action_errors, ...edit_errors];
|
|
@@ -5794,7 +6120,10 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5794
6120
|
}
|
|
5795
6121
|
} else {
|
|
5796
6122
|
if (actions.length > 0) {
|
|
5797
|
-
|
|
6123
|
+
let action_errors = this.validate_review_actions(actions);
|
|
6124
|
+
if (action_errors.length === 0) {
|
|
6125
|
+
action_errors = this.validate_action_pairing(actions);
|
|
6126
|
+
}
|
|
5798
6127
|
if (action_errors.length > 0) {
|
|
5799
6128
|
throw new BatchValidationError(action_errors);
|
|
5800
6129
|
}
|
|
@@ -5802,10 +6131,12 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5802
6131
|
}
|
|
5803
6132
|
let applied_actions = 0;
|
|
5804
6133
|
let skipped_actions = 0;
|
|
6134
|
+
let already_resolved_actions = 0;
|
|
5805
6135
|
if (actions.length > 0) {
|
|
5806
6136
|
const res = this.apply_review_actions(actions);
|
|
5807
6137
|
applied_actions = res[0];
|
|
5808
6138
|
skipped_actions = res[1];
|
|
6139
|
+
already_resolved_actions = res[2];
|
|
5809
6140
|
if (skipped_actions > 0) {
|
|
5810
6141
|
throw new BatchValidationError(this.skipped_details);
|
|
5811
6142
|
}
|
|
@@ -5859,6 +6190,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5859
6190
|
);
|
|
5860
6191
|
reports_by_input[i] = {
|
|
5861
6192
|
status: "failed",
|
|
6193
|
+
type: edit.type || "modify",
|
|
5862
6194
|
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
5863
6195
|
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
5864
6196
|
warning,
|
|
@@ -5874,6 +6206,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5874
6206
|
const previews = this._build_edit_context_previews(edit);
|
|
5875
6207
|
reports_by_input[i] = {
|
|
5876
6208
|
status: "applied",
|
|
6209
|
+
type: edit.type || "modify",
|
|
5877
6210
|
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
5878
6211
|
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
5879
6212
|
warning: null,
|
|
@@ -5895,6 +6228,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5895
6228
|
);
|
|
5896
6229
|
reports_by_input[i] = {
|
|
5897
6230
|
status: "failed",
|
|
6231
|
+
type: edit.type || "modify",
|
|
5898
6232
|
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
5899
6233
|
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
5900
6234
|
warning,
|
|
@@ -5913,6 +6247,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5913
6247
|
if (report.status === "applied") {
|
|
5914
6248
|
reports_by_input[i] = {
|
|
5915
6249
|
status: "failed",
|
|
6250
|
+
type: report.type || "modify",
|
|
5916
6251
|
target_text: report.target_text,
|
|
5917
6252
|
new_text: report.new_text,
|
|
5918
6253
|
warning: null,
|
|
@@ -5983,6 +6318,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
5983
6318
|
}
|
|
5984
6319
|
edits_reports.push({
|
|
5985
6320
|
status: success ? "applied" : "failed",
|
|
6321
|
+
type: edit.type || "modify",
|
|
5986
6322
|
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
5987
6323
|
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
5988
6324
|
warning,
|
|
@@ -6000,6 +6336,11 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6000
6336
|
return {
|
|
6001
6337
|
actions_applied: applied_actions,
|
|
6002
6338
|
actions_skipped: skipped_actions,
|
|
6339
|
+
// Actions whose target was already resolved by an earlier action of
|
|
6340
|
+
// this batch (via its replacement pair): consistent no-ops, never
|
|
6341
|
+
// counted as applied — every reported "applied" action causes an
|
|
6342
|
+
// observable state transition (ADEU-QA-004).
|
|
6343
|
+
actions_already_resolved: already_resolved_actions,
|
|
6003
6344
|
edits_applied: applied_edits,
|
|
6004
6345
|
edits_skipped: skipped_edits,
|
|
6005
6346
|
// edits_applied counts change OBJECTS; this is the total number of
|
|
@@ -6041,19 +6382,66 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6041
6382
|
edit._resolved_start_idx = edit._match_start_index;
|
|
6042
6383
|
resolved_edits.push([edit, edit.new_text || null]);
|
|
6043
6384
|
} else if (edit.type === "insert_row" || edit.type === "delete_row") {
|
|
6044
|
-
let matches = this.mapper.
|
|
6385
|
+
let matches = this.mapper.drop_virtual_only_matches(
|
|
6386
|
+
this.mapper.find_all_match_indices(edit.target_text)
|
|
6387
|
+
);
|
|
6045
6388
|
let resolved_mapper = this.mapper;
|
|
6046
6389
|
if (matches.length === 0) {
|
|
6047
6390
|
if (!this.clean_mapper) {
|
|
6048
6391
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
6049
6392
|
}
|
|
6050
|
-
matches = this.clean_mapper.
|
|
6393
|
+
matches = this.clean_mapper.drop_virtual_only_matches(
|
|
6394
|
+
this.clean_mapper.find_all_match_indices(edit.target_text)
|
|
6395
|
+
);
|
|
6051
6396
|
resolved_mapper = this.clean_mapper;
|
|
6052
6397
|
}
|
|
6053
6398
|
if (matches.length > 0) {
|
|
6054
|
-
edit.
|
|
6055
|
-
|
|
6056
|
-
|
|
6399
|
+
const match_mode = edit.match_mode || "strict";
|
|
6400
|
+
const unique_matches = [];
|
|
6401
|
+
const seen_trs = /* @__PURE__ */ new Set();
|
|
6402
|
+
for (const match of matches) {
|
|
6403
|
+
const start_idx = match[0];
|
|
6404
|
+
const [anchor_run, anchor_para] = resolved_mapper.get_insertion_anchor(start_idx, false);
|
|
6405
|
+
let target_element = null;
|
|
6406
|
+
if (anchor_run) target_element = anchor_run._element;
|
|
6407
|
+
else if (anchor_para) target_element = anchor_para._element;
|
|
6408
|
+
let tr = target_element;
|
|
6409
|
+
while (tr && tr.tagName !== "w:tr") tr = tr.parentNode;
|
|
6410
|
+
if (tr) {
|
|
6411
|
+
if (!seen_trs.has(tr)) {
|
|
6412
|
+
seen_trs.add(tr);
|
|
6413
|
+
unique_matches.push(match);
|
|
6414
|
+
}
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6417
|
+
if (unique_matches.length > 0) {
|
|
6418
|
+
let matches_to_apply = unique_matches;
|
|
6419
|
+
if (match_mode === "strict" || match_mode === "first") {
|
|
6420
|
+
matches_to_apply = unique_matches.slice(0, 1);
|
|
6421
|
+
}
|
|
6422
|
+
if (match_mode === "all" || matches_to_apply.length > 1) {
|
|
6423
|
+
for (const m of matches_to_apply) {
|
|
6424
|
+
const sub_edit = {
|
|
6425
|
+
...edit,
|
|
6426
|
+
_resolved_start_idx: m[0],
|
|
6427
|
+
_active_mapper_ref: resolved_mapper,
|
|
6428
|
+
_parent_edit_ref: edit
|
|
6429
|
+
};
|
|
6430
|
+
resolved_edits.push([sub_edit, null]);
|
|
6431
|
+
}
|
|
6432
|
+
} else {
|
|
6433
|
+
edit._resolved_start_idx = matches_to_apply[0][0];
|
|
6434
|
+
edit._active_mapper_ref = resolved_mapper;
|
|
6435
|
+
resolved_edits.push([edit, null]);
|
|
6436
|
+
}
|
|
6437
|
+
} else {
|
|
6438
|
+
skipped++;
|
|
6439
|
+
edit._applied_status = false;
|
|
6440
|
+
const target_snippet = (edit.target_text || "").trim().substring(0, 40);
|
|
6441
|
+
const msg = `- Failed to locate row target: '${target_snippet}...'`;
|
|
6442
|
+
this.skipped_details.push(msg);
|
|
6443
|
+
edit._error_msg = msg;
|
|
6444
|
+
}
|
|
6057
6445
|
} else {
|
|
6058
6446
|
skipped++;
|
|
6059
6447
|
edit._applied_status = false;
|
|
@@ -6218,10 +6606,156 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6218
6606
|
}
|
|
6219
6607
|
return [applied_logical, skipped_logical];
|
|
6220
6608
|
}
|
|
6609
|
+
/**
|
|
6610
|
+
* True when the paragraph still carries visible content (w:t text, w:tab,
|
|
6611
|
+
* w:br) that is NOT wrapped in a tracked deletion — i.e. the paragraph
|
|
6612
|
+
* would render non-empty in the accepted document.
|
|
6613
|
+
*/
|
|
6614
|
+
_paragraph_has_visible_content(p_elem) {
|
|
6615
|
+
for (const tag of ["w:t", "w:tab", "w:br"]) {
|
|
6616
|
+
const nodes = findAllDescendants(p_elem, tag);
|
|
6617
|
+
for (const node of nodes) {
|
|
6618
|
+
let is_deleted = false;
|
|
6619
|
+
let curr = node.parentNode;
|
|
6620
|
+
while (curr && curr !== p_elem.parentNode) {
|
|
6621
|
+
if (curr.tagName === "w:del") {
|
|
6622
|
+
is_deleted = true;
|
|
6623
|
+
break;
|
|
6624
|
+
}
|
|
6625
|
+
curr = curr.parentNode;
|
|
6626
|
+
}
|
|
6627
|
+
if (!is_deleted) {
|
|
6628
|
+
if (tag === "w:t" && !node.textContent) continue;
|
|
6629
|
+
return true;
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
return false;
|
|
6634
|
+
}
|
|
6635
|
+
/**
|
|
6636
|
+
* All contiguous same-author w:ins/w:del siblings that form one logical
|
|
6637
|
+
* modification block with `node` (a replacement's del+ins pair). Mirrors
|
|
6638
|
+
* the Python engine's _get_paired_nodes: comment range markers and
|
|
6639
|
+
* rPr/pPr are transparent; a different author or any other element breaks
|
|
6640
|
+
* the group.
|
|
6641
|
+
*/
|
|
6642
|
+
_get_paired_nodes(node) {
|
|
6643
|
+
const pairs = [];
|
|
6644
|
+
const author = node.getAttribute("w:author");
|
|
6645
|
+
const transparent = /* @__PURE__ */ new Set([
|
|
6646
|
+
"w:commentRangeStart",
|
|
6647
|
+
"w:commentRangeEnd",
|
|
6648
|
+
"w:commentReference",
|
|
6649
|
+
"w:rPr",
|
|
6650
|
+
"w:pPr"
|
|
6651
|
+
]);
|
|
6652
|
+
const walk = (start, dir) => {
|
|
6653
|
+
let cur = dir === "next" ? start.nextSibling : start.previousSibling;
|
|
6654
|
+
while (cur) {
|
|
6655
|
+
if (cur.nodeType !== 1) {
|
|
6656
|
+
cur = dir === "next" ? cur.nextSibling : cur.previousSibling;
|
|
6657
|
+
continue;
|
|
6658
|
+
}
|
|
6659
|
+
const el = cur;
|
|
6660
|
+
if (transparent.has(el.tagName)) {
|
|
6661
|
+
cur = dir === "next" ? cur.nextSibling : cur.previousSibling;
|
|
6662
|
+
continue;
|
|
6663
|
+
}
|
|
6664
|
+
if ((el.tagName === "w:ins" || el.tagName === "w:del") && el.getAttribute("w:author") === author) {
|
|
6665
|
+
pairs.push(el);
|
|
6666
|
+
cur = dir === "next" ? cur.nextSibling : cur.previousSibling;
|
|
6667
|
+
continue;
|
|
6668
|
+
}
|
|
6669
|
+
break;
|
|
6670
|
+
}
|
|
6671
|
+
};
|
|
6672
|
+
walk(node, "next");
|
|
6673
|
+
walk(node, "prev");
|
|
6674
|
+
return pairs;
|
|
6675
|
+
}
|
|
6676
|
+
/**
|
|
6677
|
+
* All revision ids that resolve as ONE unit with `target_id`: the ids of
|
|
6678
|
+
* every contiguous same-author w:ins/w:del sibling of its elements (a
|
|
6679
|
+
* replacement's del+ins pair), plus the id itself.
|
|
6680
|
+
*/
|
|
6681
|
+
_resolution_group_ids(target_id) {
|
|
6682
|
+
const nodes = [
|
|
6683
|
+
...findAllDescendants(this.doc.element, "w:ins"),
|
|
6684
|
+
...findAllDescendants(this.doc.element, "w:del")
|
|
6685
|
+
].filter((n) => n.getAttribute("w:id") === target_id);
|
|
6686
|
+
const group = /* @__PURE__ */ new Set();
|
|
6687
|
+
if (nodes.length === 0) return group;
|
|
6688
|
+
group.add(target_id);
|
|
6689
|
+
for (const node of nodes) {
|
|
6690
|
+
for (const paired of this._get_paired_nodes(node)) {
|
|
6691
|
+
const pid = paired.getAttribute("w:id");
|
|
6692
|
+
if (pid) group.add(pid);
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
return group;
|
|
6696
|
+
}
|
|
6697
|
+
/**
|
|
6698
|
+
* Document-aware validation (QA 2026-07-19 ADEU-QA-004): a replacement's
|
|
6699
|
+
* del+ins pair carries two distinct ids but resolves as one unit, so a
|
|
6700
|
+
* batch that accepts one side and rejects the other is contradictory.
|
|
6701
|
+
* Rejecting it up front — before any action mutates the document — keeps
|
|
6702
|
+
* the batch transactional.
|
|
6703
|
+
*/
|
|
6704
|
+
validate_action_pairing(actions) {
|
|
6705
|
+
const errors = [];
|
|
6706
|
+
const group_first = /* @__PURE__ */ new Map();
|
|
6707
|
+
for (let pos = 0; pos < actions.length; pos++) {
|
|
6708
|
+
const act = actions[pos];
|
|
6709
|
+
if (act.type !== "accept" && act.type !== "reject") continue;
|
|
6710
|
+
const raw_id = String(act.target_id ?? "");
|
|
6711
|
+
if (raw_id.startsWith("Com:")) continue;
|
|
6712
|
+
const target_id = raw_id.startsWith("Chg:") ? raw_id.slice(4) : raw_id;
|
|
6713
|
+
const group = this._resolution_group_ids(target_id);
|
|
6714
|
+
if (group.size === 0) continue;
|
|
6715
|
+
let conflict = null;
|
|
6716
|
+
for (const gid of group) {
|
|
6717
|
+
const prior = group_first.get(gid);
|
|
6718
|
+
if (prior !== void 0 && prior[1] !== act.type) {
|
|
6719
|
+
conflict = prior;
|
|
6720
|
+
break;
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
if (conflict !== null) {
|
|
6724
|
+
const [first_pos, first_type, first_id] = conflict;
|
|
6725
|
+
errors.push(
|
|
6726
|
+
`- Action ${pos + 1} Failed: conflicting actions on one replacement \u2014 Action ${first_pos + 1} applies '${first_type}' to Chg:${first_id}, and Chg:${target_id} is part of the same change (a replacement's contiguous del+ins pair resolves as one unit, so '${first_type}' already decides both sides). Accepting one side and rejecting the other is contradictory \u2014 decide the outcome and submit exactly one action for the pair.`
|
|
6727
|
+
);
|
|
6728
|
+
continue;
|
|
6729
|
+
}
|
|
6730
|
+
for (const gid of group) {
|
|
6731
|
+
if (!group_first.has(gid)) {
|
|
6732
|
+
group_first.set(gid, [pos, act.type, target_id]);
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
return errors;
|
|
6737
|
+
}
|
|
6738
|
+
/**
|
|
6739
|
+
* Returns [applied, skipped, already_resolved]. `applied` counts actions
|
|
6740
|
+
* that caused an observable state transition; an action naming an id an
|
|
6741
|
+
* earlier action of this batch already resolved (via its replacement pair)
|
|
6742
|
+
* is counted in `already_resolved` instead — never as applied
|
|
6743
|
+
* (QA 2026-07-19 ADEU-QA-004).
|
|
6744
|
+
*/
|
|
6221
6745
|
apply_review_actions(actions) {
|
|
6222
6746
|
let applied = 0;
|
|
6223
6747
|
let skipped = 0;
|
|
6224
|
-
|
|
6748
|
+
let already_resolved = 0;
|
|
6749
|
+
const resolved_history = /* @__PURE__ */ new Map();
|
|
6750
|
+
const sortedActions = actions.map((action, pos) => ({ action, pos })).sort((a, b) => {
|
|
6751
|
+
const aPri = a.action.type === "reply" ? 0 : 1;
|
|
6752
|
+
const bPri = b.action.type === "reply" ? 0 : 1;
|
|
6753
|
+
if (aPri !== bPri) {
|
|
6754
|
+
return aPri - bPri;
|
|
6755
|
+
}
|
|
6756
|
+
return a.pos - b.pos;
|
|
6757
|
+
});
|
|
6758
|
+
for (const { action, pos } of sortedActions) {
|
|
6225
6759
|
const type = action.type;
|
|
6226
6760
|
if (type === "reply") {
|
|
6227
6761
|
const cid = action.target_id.replace("Com:", "");
|
|
@@ -6235,6 +6769,21 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6235
6769
|
continue;
|
|
6236
6770
|
}
|
|
6237
6771
|
const target_id = action.target_id.replace("Chg:", "");
|
|
6772
|
+
const prior_type = resolved_history.get(target_id);
|
|
6773
|
+
if (prior_type !== void 0) {
|
|
6774
|
+
if (prior_type === type) {
|
|
6775
|
+
already_resolved++;
|
|
6776
|
+
this.skipped_details.push(
|
|
6777
|
+
`- Note: Action ${pos + 1} ('${type}' on ${action.target_id}) had no additional effect \u2014 the change was already resolved together with its replacement pair by an earlier action in this batch. Counted as already_resolved, not applied.`
|
|
6778
|
+
);
|
|
6779
|
+
continue;
|
|
6780
|
+
}
|
|
6781
|
+
this.skipped_details.push(
|
|
6782
|
+
`- Action ${pos + 1} Failed: contradictory action \u2014 '${type}' on ${action.target_id}, but the change was already resolved as '${prior_type}' together with its replacement pair by an earlier action in this batch.`
|
|
6783
|
+
);
|
|
6784
|
+
skipped++;
|
|
6785
|
+
continue;
|
|
6786
|
+
}
|
|
6238
6787
|
const all_ins = findAllDescendants(this.doc.element, "w:ins").filter(
|
|
6239
6788
|
(n) => n.getAttribute("w:id") === target_id
|
|
6240
6789
|
);
|
|
@@ -6259,7 +6808,18 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6259
6808
|
);
|
|
6260
6809
|
continue;
|
|
6261
6810
|
}
|
|
6811
|
+
const group_nodes = new Set(all_nodes);
|
|
6262
6812
|
for (const node of all_nodes) {
|
|
6813
|
+
for (const paired of this._get_paired_nodes(node)) {
|
|
6814
|
+
group_nodes.add(paired);
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
const resolved_now = /* @__PURE__ */ new Set();
|
|
6818
|
+
for (const node of group_nodes) {
|
|
6819
|
+
const rid = node.getAttribute("w:id");
|
|
6820
|
+
if (rid) resolved_now.add(rid);
|
|
6821
|
+
}
|
|
6822
|
+
for (const node of group_nodes) {
|
|
6263
6823
|
const is_ins = node.tagName === "w:ins";
|
|
6264
6824
|
const parent_tag = node.parentNode ? node.parentNode.tagName : "";
|
|
6265
6825
|
const is_trPr = parent_tag === "w:trPr";
|
|
@@ -6311,9 +6871,12 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6311
6871
|
}
|
|
6312
6872
|
}
|
|
6313
6873
|
}
|
|
6874
|
+
for (const rid of resolved_now) {
|
|
6875
|
+
resolved_history.set(rid, type);
|
|
6876
|
+
}
|
|
6314
6877
|
applied++;
|
|
6315
6878
|
}
|
|
6316
|
-
return [applied, skipped];
|
|
6879
|
+
return [applied, skipped, already_resolved];
|
|
6317
6880
|
}
|
|
6318
6881
|
_apply_table_edit(edit, rebuild_map) {
|
|
6319
6882
|
const start_idx = edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null ? edit._resolved_start_idx : edit._match_start_index || 0;
|
|
@@ -6373,17 +6936,15 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6373
6936
|
if (!edit.target_text) return null;
|
|
6374
6937
|
const is_regex = edit.regex || false;
|
|
6375
6938
|
const match_mode = edit.match_mode || "strict";
|
|
6376
|
-
let matches = this.mapper.
|
|
6377
|
-
edit.target_text,
|
|
6378
|
-
is_regex
|
|
6939
|
+
let matches = this.mapper.drop_virtual_only_matches(
|
|
6940
|
+
this.mapper.find_all_match_indices(edit.target_text, is_regex)
|
|
6379
6941
|
);
|
|
6380
6942
|
let use_clean_map = false;
|
|
6381
6943
|
if (matches.length === 0) {
|
|
6382
6944
|
if (!this.clean_mapper)
|
|
6383
6945
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
6384
|
-
matches = this.clean_mapper.
|
|
6385
|
-
edit.target_text,
|
|
6386
|
-
is_regex
|
|
6946
|
+
matches = this.clean_mapper.drop_virtual_only_matches(
|
|
6947
|
+
this.clean_mapper.find_all_match_indices(edit.target_text, is_regex)
|
|
6387
6948
|
);
|
|
6388
6949
|
if (matches.length > 0) use_clean_map = true;
|
|
6389
6950
|
else return null;
|
|
@@ -6893,7 +7454,9 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
6893
7454
|
this._set_paragraph_style(new_p, style_name);
|
|
6894
7455
|
} else {
|
|
6895
7456
|
const existing_pPr = findChild(_bug233_target_para, "w:pPr");
|
|
6896
|
-
if (existing_pPr)
|
|
7457
|
+
if (existing_pPr) {
|
|
7458
|
+
new_p.appendChild(this._clone_pPr_scrubbing_headings(existing_pPr));
|
|
7459
|
+
}
|
|
6897
7460
|
}
|
|
6898
7461
|
let pPr = findChild(new_p, "w:pPr");
|
|
6899
7462
|
if (!pPr) {
|
|
@@ -7151,7 +7714,24 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
7151
7714
|
p2_element = getNextElement(p2_element);
|
|
7152
7715
|
}
|
|
7153
7716
|
if (p2_element && p2_element.tagName === "w:p") {
|
|
7717
|
+
const p1_fully_deleted = !this._paragraph_has_visible_content(p1_element);
|
|
7154
7718
|
let pPr = findChild(p1_element, "w:pPr");
|
|
7719
|
+
if (p1_fully_deleted) {
|
|
7720
|
+
const p2_pPr = findChild(p2_element, "w:pPr");
|
|
7721
|
+
const adopted = p2_pPr ? p2_pPr.cloneNode(true) : p1_element.ownerDocument.createElement("w:pPr");
|
|
7722
|
+
if (pPr) {
|
|
7723
|
+
const sect = findChild(pPr, "w:sectPr");
|
|
7724
|
+
if (sect && !findChild(adopted, "w:sectPr")) {
|
|
7725
|
+
adopted.appendChild(sect.cloneNode(true));
|
|
7726
|
+
}
|
|
7727
|
+
p1_element.removeChild(pPr);
|
|
7728
|
+
}
|
|
7729
|
+
p1_element.insertBefore(
|
|
7730
|
+
adopted,
|
|
7731
|
+
p1_element.firstChild
|
|
7732
|
+
);
|
|
7733
|
+
pPr = adopted;
|
|
7734
|
+
}
|
|
7155
7735
|
if (!pPr) {
|
|
7156
7736
|
pPr = p1_element.ownerDocument.createElement("w:pPr");
|
|
7157
7737
|
p1_element.insertBefore(
|
|
@@ -7164,8 +7744,10 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
7164
7744
|
rPr = p1_element.ownerDocument.createElement("w:rPr");
|
|
7165
7745
|
pPr.appendChild(rPr);
|
|
7166
7746
|
}
|
|
7167
|
-
|
|
7168
|
-
|
|
7747
|
+
if (!findChild(rPr, "w:del")) {
|
|
7748
|
+
const del_mark = this._create_track_change_tag("w:del");
|
|
7749
|
+
rPr.appendChild(del_mark);
|
|
7750
|
+
}
|
|
7169
7751
|
const children = Array.from(p2_element.childNodes);
|
|
7170
7752
|
for (const child of children) {
|
|
7171
7753
|
if (child.nodeType === 1 && child.tagName === "w:pPr") {
|
|
@@ -7219,27 +7801,7 @@ var RedlineEngine = class _RedlineEngine {
|
|
|
7219
7801
|
}
|
|
7220
7802
|
}
|
|
7221
7803
|
for (const p_elem of affected_ps) {
|
|
7222
|
-
|
|
7223
|
-
for (const tag of ["w:t", "w:tab", "w:br"]) {
|
|
7224
|
-
const nodes = findAllDescendants(p_elem, tag);
|
|
7225
|
-
for (const node of nodes) {
|
|
7226
|
-
let is_deleted = false;
|
|
7227
|
-
let curr = node.parentNode;
|
|
7228
|
-
while (curr && curr !== p_elem.parentNode) {
|
|
7229
|
-
if (curr.tagName === "w:del") {
|
|
7230
|
-
is_deleted = true;
|
|
7231
|
-
break;
|
|
7232
|
-
}
|
|
7233
|
-
curr = curr.parentNode;
|
|
7234
|
-
}
|
|
7235
|
-
if (!is_deleted) {
|
|
7236
|
-
if (tag === "w:t" && !node.textContent) continue;
|
|
7237
|
-
has_visible = true;
|
|
7238
|
-
break;
|
|
7239
|
-
}
|
|
7240
|
-
}
|
|
7241
|
-
if (has_visible) break;
|
|
7242
|
-
}
|
|
7804
|
+
const has_visible = this._paragraph_has_visible_content(p_elem);
|
|
7243
7805
|
if (!has_visible) {
|
|
7244
7806
|
let pPr = findChild(p_elem, "w:pPr");
|
|
7245
7807
|
if (!pPr) {
|
|
@@ -7798,6 +8360,31 @@ function strip_custom_properties(doc) {
|
|
|
7798
8360
|
const count = Math.max(lines.length, 1);
|
|
7799
8361
|
return [`Custom document properties: ${count} removed (docProps/custom.xml)`].concat(lines);
|
|
7800
8362
|
}
|
|
8363
|
+
function strip_document_variables(doc) {
|
|
8364
|
+
const settingsPart = doc.pkg.getPartByPath("word/settings.xml");
|
|
8365
|
+
if (!settingsPart) return [];
|
|
8366
|
+
const names = [];
|
|
8367
|
+
let removedAny = false;
|
|
8368
|
+
for (const container of findDescendantsByLocalName(settingsPart._element, "docVars")) {
|
|
8369
|
+
for (const child of Array.from(container.childNodes)) {
|
|
8370
|
+
const el = child;
|
|
8371
|
+
const tag = el.tagName || "";
|
|
8372
|
+
if (tag === "docVar" || tag.endsWith(":docVar")) {
|
|
8373
|
+
names.push(el.getAttribute("w:name") || el.getAttribute("name") || "(unnamed)");
|
|
8374
|
+
}
|
|
8375
|
+
}
|
|
8376
|
+
container.parentNode?.removeChild(container);
|
|
8377
|
+
removedAny = true;
|
|
8378
|
+
}
|
|
8379
|
+
for (const stray of findDescendantsByLocalName(settingsPart._element, "docVar")) {
|
|
8380
|
+
names.push(stray.getAttribute("w:name") || stray.getAttribute("name") || "(unnamed)");
|
|
8381
|
+
stray.parentNode?.removeChild(stray);
|
|
8382
|
+
removedAny = true;
|
|
8383
|
+
}
|
|
8384
|
+
if (!removedAny) return [];
|
|
8385
|
+
const shown = Array.from(new Set(names)).sort().join(", ") || "(unnamed)";
|
|
8386
|
+
return [`Document variables: ${names.length} removed (${shown})`];
|
|
8387
|
+
}
|
|
7801
8388
|
function strip_image_alt_text(doc) {
|
|
7802
8389
|
let count = 0;
|
|
7803
8390
|
for (const docPr of findDescendantsByLocalName(doc.element, "docPr")) {
|
|
@@ -8316,10 +8903,28 @@ function extract_table(table, comments_map, cleanView, cursor, paragraph_offsets
|
|
|
8316
8903
|
paragraph_offsets
|
|
8317
8904
|
);
|
|
8318
8905
|
if (!cleanView) {
|
|
8319
|
-
|
|
8320
|
-
|
|
8906
|
+
let firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
8907
|
+
let paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
8908
|
+
if (!paraId && (!cell_content || cell_content.trim() === "")) {
|
|
8909
|
+
if (!firstP) {
|
|
8910
|
+
const xmlDoc = cell._element.ownerDocument;
|
|
8911
|
+
firstP = xmlDoc.createElement("w:p");
|
|
8912
|
+
cell._element.appendChild(firstP);
|
|
8913
|
+
}
|
|
8914
|
+
const allPs = Array.from(cell._element.ownerDocument.getElementsByTagName("w:p"));
|
|
8915
|
+
const index = allPs.indexOf(firstP);
|
|
8916
|
+
let hash = 2166136261;
|
|
8917
|
+
const str = `fallback-paraId-${index}`;
|
|
8918
|
+
for (let i = 0; i < str.length; i++) {
|
|
8919
|
+
hash ^= str.charCodeAt(i);
|
|
8920
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
8921
|
+
}
|
|
8922
|
+
paraId = (hash >>> 0).toString(16).toUpperCase().padStart(8, "0");
|
|
8923
|
+
firstP.setAttribute("w14:paraId", paraId);
|
|
8924
|
+
}
|
|
8321
8925
|
if (paraId) {
|
|
8322
|
-
const
|
|
8926
|
+
const space_pad = cell_content ? " " : "";
|
|
8927
|
+
const anchor = `${space_pad}{#cell:${paraId}}`;
|
|
8323
8928
|
cell_content = cell_content + anchor;
|
|
8324
8929
|
}
|
|
8325
8930
|
}
|
|
@@ -8584,6 +9189,8 @@ function _build_merged_meta_block(states_list, comments_map) {
|
|
|
8584
9189
|
const change_lines = [];
|
|
8585
9190
|
const comment_lines = [];
|
|
8586
9191
|
const seen_sigs = /* @__PURE__ */ new Set();
|
|
9192
|
+
const pair_map = compute_change_pair_map(states_list);
|
|
9193
|
+
const pairSuffix = (uid) => pair_map[uid] ? ` (pairs with ${pair_map[uid]})` : "";
|
|
8587
9194
|
for (const [ins_map, del_map, comments_set, fmt_map] of states_list) {
|
|
8588
9195
|
let render_comment2 = function(cid) {
|
|
8589
9196
|
if (!comments_map[cid]) return;
|
|
@@ -8612,7 +9219,9 @@ function _build_merged_meta_block(states_list, comments_map) {
|
|
|
8612
9219
|
)) {
|
|
8613
9220
|
const sig = `Chg:${uid}`;
|
|
8614
9221
|
if (!seen_sigs.has(sig)) {
|
|
8615
|
-
change_lines.push(
|
|
9222
|
+
change_lines.push(
|
|
9223
|
+
`[${sig} insert] ${meta.author || "Unknown"}${pairSuffix(uid)}`
|
|
9224
|
+
);
|
|
8616
9225
|
seen_sigs.add(sig);
|
|
8617
9226
|
}
|
|
8618
9227
|
}
|
|
@@ -8621,7 +9230,9 @@ function _build_merged_meta_block(states_list, comments_map) {
|
|
|
8621
9230
|
)) {
|
|
8622
9231
|
const sig = `Chg:${uid}`;
|
|
8623
9232
|
if (!seen_sigs.has(sig)) {
|
|
8624
|
-
change_lines.push(
|
|
9233
|
+
change_lines.push(
|
|
9234
|
+
`[${sig} delete] ${meta.author || "Unknown"}${pairSuffix(uid)}`
|
|
9235
|
+
);
|
|
8625
9236
|
seen_sigs.add(sig);
|
|
8626
9237
|
}
|
|
8627
9238
|
}
|
|
@@ -9183,14 +9794,14 @@ var SanitizeReport = class {
|
|
|
9183
9794
|
const lower = line.toLowerCase();
|
|
9184
9795
|
if (lower.includes("tracked change") || lower.includes("insertion") || lower.includes("deletion") || lower.includes("accepted")) {
|
|
9185
9796
|
this.change_lines.push(line);
|
|
9797
|
+
} else if (lower.includes("author") || lower.includes("template") || lower.includes("company") || lower.includes("manager") || lower.includes("metadata") || lower.includes("timestamp") || lower.includes("custom xml") || lower.includes("custom propert") || lower.includes("identifier") || lower.includes("document variable") || lower.includes("language") || lower.includes("version") || lower.includes("last modified by") || lower.includes("revision count") || lower.includes("last printed") || lower.includes("description/comments")) {
|
|
9798
|
+
this.metadata_lines.push(line);
|
|
9186
9799
|
} else if (lower.includes("comment") || lower.includes("[open]") || lower.includes("[resolved]")) {
|
|
9187
9800
|
if (lower.includes("kept") || lower.includes("visible")) {
|
|
9188
9801
|
this.kept_comment_lines.push(line);
|
|
9189
9802
|
} else {
|
|
9190
9803
|
this.removed_comment_lines.push(line);
|
|
9191
9804
|
}
|
|
9192
|
-
} else if (lower.includes("author") || lower.includes("template") || lower.includes("company") || lower.includes("manager") || lower.includes("metadata") || lower.includes("timestamp") || lower.includes("custom xml") || lower.includes("custom propert") || lower.includes("identifier") || lower.includes("language") || lower.includes("version") || lower.includes("last modified by") || lower.includes("revision count") || lower.includes("last printed")) {
|
|
9193
|
-
this.metadata_lines.push(line);
|
|
9194
9805
|
} else if (lower.includes("hyperlink") || lower.includes("warning")) {
|
|
9195
9806
|
this.warnings.push(line);
|
|
9196
9807
|
} else {
|
|
@@ -9269,7 +9880,7 @@ async function finalize_document(doc, options) {
|
|
|
9269
9880
|
report.tracked_changes_found = total;
|
|
9270
9881
|
if (total > 0 && !options.accept_all) {
|
|
9271
9882
|
report.status = "blocked";
|
|
9272
|
-
report.blocked_reason = `Document contains ${total} unresolved tracked changes (${counts[0]} insertions, ${counts[1]} deletions, ${counts[2]} formatting). Review in Word first, or set
|
|
9883
|
+
report.blocked_reason = `Document contains ${total} unresolved tracked changes (${counts[0]} insertions, ${counts[1]} deletions, ${counts[2]} formatting). Review in Word first, set accept_all=true, or set sanitize_mode='keep-markup'.`;
|
|
9273
9884
|
return { reportText: report.render() };
|
|
9274
9885
|
}
|
|
9275
9886
|
if (total > 0) {
|
|
@@ -9303,6 +9914,7 @@ async function finalize_document(doc, options) {
|
|
|
9303
9914
|
report.add_transform_lines(scrub_timestamps(doc));
|
|
9304
9915
|
report.add_transform_lines(strip_custom_xml(doc));
|
|
9305
9916
|
report.add_transform_lines(strip_custom_properties(doc));
|
|
9917
|
+
report.add_transform_lines(strip_document_variables(doc));
|
|
9306
9918
|
report.add_transform_lines(strip_image_alt_text(doc));
|
|
9307
9919
|
const warnings = audit_hyperlinks(doc);
|
|
9308
9920
|
for (const w of warnings) report.warnings.push(w);
|
|
@@ -9386,6 +9998,12 @@ function verifySanitizedPackage(outBuffer) {
|
|
|
9386
9998
|
}
|
|
9387
9999
|
}
|
|
9388
10000
|
}
|
|
10001
|
+
if (names.includes("word/settings.xml")) {
|
|
10002
|
+
const settings = parseXml((0, import_fflate3.strFromU8)(unzipped["word/settings.xml"]));
|
|
10003
|
+
if (findDescendantsByLocalName(settings.documentElement, "docVar").length > 0) {
|
|
10004
|
+
problems.push("word/settings.xml still contains document variables (w:docVar)");
|
|
10005
|
+
}
|
|
10006
|
+
}
|
|
9389
10007
|
if (problems.length > 0) {
|
|
9390
10008
|
throw new Error(
|
|
9391
10009
|
"Sanitize integrity check failed \u2014 the saved package still contains metadata this run claims to remove:\n - " + problems.join("\n - ") + "\nRefusing to report a clean document."
|