@adeu/core 1.17.1 → 1.18.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 +514 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +514 -106
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine.bugs.test.ts +12 -14
- package/src/engine.issue23.test.ts +23 -18
- package/src/engine.qa.test.ts +4 -4
- package/src/engine.tables.test.ts +138 -36
- package/src/engine.ts +700 -134
- package/src/ingest.ts +16 -1
- package/src/mapper.ts +23 -1
- package/src/markup.ts +1 -1
- package/src/reject_and_nested_redline.test.ts +137 -0
- package/src/repro.feedback.test.ts +160 -0
- package/src/repro.report.test.ts +132 -0
- package/src/repro_qa_report_v3.test.ts +186 -0
- package/src/repro_report_boundary_failures.test.ts +108 -0
package/dist/index.cjs
CHANGED
|
@@ -1384,7 +1384,19 @@ ${header}`;
|
|
|
1384
1384
|
this._add_virtual_text(" | ", current, null);
|
|
1385
1385
|
current += 3;
|
|
1386
1386
|
}
|
|
1387
|
+
const cell_start = current;
|
|
1387
1388
|
current = this._map_blocks(cell, current);
|
|
1389
|
+
if (!this.clean_view && !this.original_view) {
|
|
1390
|
+
const firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
1391
|
+
const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
1392
|
+
if (paraId && firstP) {
|
|
1393
|
+
const cellPara = new Paragraph(firstP, cell);
|
|
1394
|
+
this._add_virtual_text("", current, cellPara);
|
|
1395
|
+
const anchor = `{#cell:${paraId}}`;
|
|
1396
|
+
this._add_virtual_text(anchor, current, cellPara);
|
|
1397
|
+
current += anchor.length;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1388
1400
|
cells_processed += 1;
|
|
1389
1401
|
}
|
|
1390
1402
|
if (ins && !this.clean_view && !this.original_view) {
|
|
@@ -1761,7 +1773,7 @@ ${header}`;
|
|
|
1761
1773
|
target_text = this._strip_markdown_formatting(target_text);
|
|
1762
1774
|
target_text = this._replace_smart_quotes(target_text);
|
|
1763
1775
|
const parts = [];
|
|
1764
|
-
const token_pattern = /(\[_+\])|(\s+)|(['"])|([
|
|
1776
|
+
const token_pattern = /(\[_+\])|(\s+)|(['"])|([.,;:\/\-\[\](){}+=$?*!|#^<>\\%&@~`_])/g;
|
|
1765
1777
|
let last_idx = 0;
|
|
1766
1778
|
let match;
|
|
1767
1779
|
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -2647,7 +2659,7 @@ function _refine_match_boundaries(text, start, end) {
|
|
|
2647
2659
|
function _make_fuzzy_regex(target_text) {
|
|
2648
2660
|
target_text = _replace_smart_quotes(target_text);
|
|
2649
2661
|
const parts = [];
|
|
2650
|
-
const token_pattern = /(_+)|(\s+)|(['"])|([
|
|
2662
|
+
const token_pattern = /(_+)|(\s+)|(['"])|([.,;:\/\-\[\](){}+=$?*!|#^<>\\%&@~`])/g;
|
|
2651
2663
|
const md_noise = "[*_]*";
|
|
2652
2664
|
const structural_noise = "(?:\\s*(?:[*+\\->]|\\d+\\.)\\s+|\\s*\\n\\s*)";
|
|
2653
2665
|
const start_list_marker = "(?:[ \\t]*(?:[*+\\->]|\\d+\\.)\\s+)?";
|
|
@@ -2869,6 +2881,51 @@ function insertAfter(newNode, refNode) {
|
|
|
2869
2881
|
refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
|
|
2870
2882
|
}
|
|
2871
2883
|
}
|
|
2884
|
+
function takeSnapshot(doc) {
|
|
2885
|
+
const parts = [...doc.pkg.parts];
|
|
2886
|
+
const unzipped = { ...doc.pkg.unzipped };
|
|
2887
|
+
const rels = /* @__PURE__ */ new Map();
|
|
2888
|
+
const elements = /* @__PURE__ */ new Map();
|
|
2889
|
+
for (const part of parts) {
|
|
2890
|
+
rels.set(part, new Map(part.rels));
|
|
2891
|
+
if (part._element) {
|
|
2892
|
+
elements.set(part, part._element.cloneNode(true));
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
return { parts, unzipped, rels, elements };
|
|
2896
|
+
}
|
|
2897
|
+
function restoreSnapshot(doc, snapshot) {
|
|
2898
|
+
doc.pkg.parts = [...snapshot.parts];
|
|
2899
|
+
for (const key of Object.keys(doc.pkg.unzipped)) {
|
|
2900
|
+
delete doc.pkg.unzipped[key];
|
|
2901
|
+
}
|
|
2902
|
+
for (const [key, val] of Object.entries(snapshot.unzipped)) {
|
|
2903
|
+
doc.pkg.unzipped[key] = val;
|
|
2904
|
+
}
|
|
2905
|
+
for (const part of snapshot.parts) {
|
|
2906
|
+
part.rels = new Map(snapshot.rels.get(part));
|
|
2907
|
+
const originalEl = snapshot.elements.get(part);
|
|
2908
|
+
if (originalEl && part._element) {
|
|
2909
|
+
const xmlDoc = part._element.ownerDocument;
|
|
2910
|
+
if (xmlDoc && xmlDoc.documentElement) {
|
|
2911
|
+
xmlDoc.replaceChild(originalEl, xmlDoc.documentElement);
|
|
2912
|
+
}
|
|
2913
|
+
part._element = originalEl;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
function stripMatchingHeadingHashes(target, newText) {
|
|
2918
|
+
if (!target || !newText) return [target, newText];
|
|
2919
|
+
const targetMatch = target.match(/^(#+)\s+/);
|
|
2920
|
+
const newMatch = newText.match(/^(#+)\s+/);
|
|
2921
|
+
if (targetMatch && newMatch && targetMatch[1] === newMatch[1]) {
|
|
2922
|
+
const hashes = targetMatch[1];
|
|
2923
|
+
const targetClean = target.substring(hashes.length).trimStart();
|
|
2924
|
+
const newClean = newText.substring(hashes.length).trimStart();
|
|
2925
|
+
return [targetClean, newClean];
|
|
2926
|
+
}
|
|
2927
|
+
return [target, newText];
|
|
2928
|
+
}
|
|
2872
2929
|
var BatchValidationError = class extends Error {
|
|
2873
2930
|
errors;
|
|
2874
2931
|
constructor(errors) {
|
|
@@ -2877,7 +2934,7 @@ var BatchValidationError = class extends Error {
|
|
|
2877
2934
|
this.errors = errors;
|
|
2878
2935
|
}
|
|
2879
2936
|
};
|
|
2880
|
-
function validate_edit_strings(edits) {
|
|
2937
|
+
function validate_edit_strings(edits, index_offset = 0) {
|
|
2881
2938
|
const errors = [];
|
|
2882
2939
|
for (let i = 0; i < edits.length; i++) {
|
|
2883
2940
|
const edit = edits[i];
|
|
@@ -2885,7 +2942,7 @@ function validate_edit_strings(edits) {
|
|
|
2885
2942
|
const n_text = edit.new_text || "";
|
|
2886
2943
|
if (n_text.includes("{++") || n_text.includes("{--") || n_text.includes("{>>") || n_text.includes("{==")) {
|
|
2887
2944
|
errors.push(
|
|
2888
|
-
`- Edit ${i + 1} Failed: Do not manually write CriticMarkup tags ({++, {--, {>>, {==) in \`new_text\`. The engine handles redlining automatically. To add a comment, use the \`comment\` parameter.`
|
|
2945
|
+
`- Edit ${i + 1 + index_offset} Failed: Do not manually write CriticMarkup tags ({++, {--, {>>, {==) in \`new_text\`. The engine handles redlining automatically. To add a comment, use the \`comment\` parameter.`
|
|
2889
2946
|
);
|
|
2890
2947
|
}
|
|
2891
2948
|
if (t_text.includes("[^") || n_text.includes("[^")) {
|
|
@@ -2896,11 +2953,11 @@ function validate_edit_strings(edits) {
|
|
|
2896
2953
|
(f) => n_fns.filter((x) => x === f).length > t_fns.filter((x) => x === f).length
|
|
2897
2954
|
)) {
|
|
2898
2955
|
errors.push(
|
|
2899
|
-
`- Edit ${i + 1} Failed: Cannot insert footnote/endnote markers via text replace. Markers like \`[^fn-N]\` are read-only projections. Use Word's References menu.`
|
|
2956
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot insert footnote/endnote markers via text replace. Markers like \`[^fn-N]\` are read-only projections. Use Word's References menu.`
|
|
2900
2957
|
);
|
|
2901
2958
|
} else {
|
|
2902
2959
|
errors.push(
|
|
2903
|
-
`- Edit ${i + 1} Failed: Cannot delete footnote/endnote references via text replace. The marker corresponds to a structural XML element.`
|
|
2960
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot delete footnote/endnote references via text replace. The marker corresponds to a structural XML element.`
|
|
2904
2961
|
);
|
|
2905
2962
|
}
|
|
2906
2963
|
}
|
|
@@ -2911,16 +2968,16 @@ function validate_edit_strings(edits) {
|
|
|
2911
2968
|
if (t_links.length !== n_links.length) {
|
|
2912
2969
|
if (n_links.length > t_links.length) {
|
|
2913
2970
|
errors.push(
|
|
2914
|
-
`- Edit ${i + 1} Failed: Cannot insert hyperlinks via text replace. Use a dedicated structural operation.`
|
|
2971
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot insert hyperlinks via text replace. Use a dedicated structural operation.`
|
|
2915
2972
|
);
|
|
2916
2973
|
} else {
|
|
2917
2974
|
errors.push(
|
|
2918
|
-
`- Edit ${i + 1} Failed: Cannot delete hyperlinks via text replace. The marker corresponds to a structural XML element.`
|
|
2975
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot delete hyperlinks via text replace. The marker corresponds to a structural XML element.`
|
|
2919
2976
|
);
|
|
2920
2977
|
}
|
|
2921
2978
|
} else if (t_links.length > 1 && JSON.stringify(t_links) !== JSON.stringify(n_links)) {
|
|
2922
2979
|
errors.push(
|
|
2923
|
-
`- Edit ${i + 1} Failed: Can only edit or retarget one hyperlink per text replacement. Please split into multiple edits.`
|
|
2980
|
+
`- Edit ${i + 1 + index_offset} Failed: Can only edit or retarget one hyperlink per text replacement. Please split into multiple edits.`
|
|
2924
2981
|
);
|
|
2925
2982
|
}
|
|
2926
2983
|
}
|
|
@@ -2930,17 +2987,17 @@ function validate_edit_strings(edits) {
|
|
|
2930
2987
|
if (t_xrefs.length !== n_xrefs.length) {
|
|
2931
2988
|
if (n_xrefs.length > t_xrefs.length) {
|
|
2932
2989
|
errors.push(
|
|
2933
|
-
`- Edit ${i + 1} Failed: Cannot insert cross-references via text replace. Markers are read-only projections.`
|
|
2990
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot insert cross-references via text replace. Markers are read-only projections.`
|
|
2934
2991
|
);
|
|
2935
2992
|
} else {
|
|
2936
2993
|
errors.push(
|
|
2937
|
-
`- Edit ${i + 1} Failed: Cannot delete cross-references via text replace. The marker corresponds to a structural XML element.`
|
|
2994
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot delete cross-references via text replace. The marker corresponds to a structural XML element.`
|
|
2938
2995
|
);
|
|
2939
2996
|
}
|
|
2940
2997
|
} else {
|
|
2941
2998
|
if (JSON.stringify(t_xrefs) !== JSON.stringify(n_xrefs)) {
|
|
2942
2999
|
errors.push(
|
|
2943
|
-
`- Edit ${i + 1} Failed: Modifying or retargeting cross-reference markers is disallowed to prevent dependency corruption.`
|
|
3000
|
+
`- Edit ${i + 1 + index_offset} Failed: Modifying or retargeting cross-reference markers is disallowed to prevent dependency corruption.`
|
|
2944
3001
|
);
|
|
2945
3002
|
}
|
|
2946
3003
|
}
|
|
@@ -2951,7 +3008,7 @@ function validate_edit_strings(edits) {
|
|
|
2951
3008
|
for (const a of n_anchors) {
|
|
2952
3009
|
if (n_anchors.filter((x) => x === a).length > t_anchors.filter((x) => x === a).length) {
|
|
2953
3010
|
errors.push(
|
|
2954
|
-
`- Edit ${i + 1} Failed: Cannot modify or insert internal anchor markers (\`{#...}\`). These represent structural XML bookmarks.`
|
|
3011
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot modify or insert internal anchor markers (\`{#...}\`). These represent structural XML bookmarks.`
|
|
2955
3012
|
);
|
|
2956
3013
|
break;
|
|
2957
3014
|
}
|
|
@@ -2965,16 +3022,16 @@ function validate_edit_strings(edits) {
|
|
|
2965
3022
|
const level = stripped.length - stripped.replace(/^#+/, "").length;
|
|
2966
3023
|
if (stripped.substring(level).startsWith(" ") || stripped.substring(level) === "") {
|
|
2967
3024
|
errors.push(
|
|
2968
|
-
`- Edit ${i + 1} Failed: Heading level ${level} is not supported (maximum is 6).`
|
|
3025
|
+
`- Edit ${i + 1 + index_offset} Failed: Heading level ${level} is not supported (maximum is 6).`
|
|
2969
3026
|
);
|
|
2970
3027
|
break;
|
|
2971
3028
|
}
|
|
2972
3029
|
}
|
|
2973
3030
|
}
|
|
2974
3031
|
}
|
|
2975
|
-
if (t_text.includes("READONLY_BOUNDARY_START") || n_text.includes("READONLY_BOUNDARY_START") || t_text.includes("# Document Structure (Read-Only)") || n_text.includes("# Document Structure (Read-Only)")) {
|
|
3032
|
+
if (t_text.includes("READONLY_BOUNDARY_START") || n_text.includes("READONLY_BOUNDARY_START") || t_text.includes("# Document Structure (Read-Only)") || n_text.includes("# Document Structure (Read-Only)") || t_text.includes("Document Structure (Read-Only)") || n_text.includes("Document Structure (Read-Only)")) {
|
|
2976
3033
|
errors.push(
|
|
2977
|
-
`- Edit ${i + 1} Failed: Modification targets the read-only boundary (Structural Appendix). This section cannot be edited.`
|
|
3034
|
+
`- Edit ${i + 1 + index_offset} Failed: Modification targets the read-only boundary (Structural Appendix). This section cannot be edited.`
|
|
2978
3035
|
);
|
|
2979
3036
|
}
|
|
2980
3037
|
}
|
|
@@ -2996,7 +3053,7 @@ var RedlineEngine = class {
|
|
|
2996
3053
|
this.timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
2997
3054
|
const w16du_ns = "http://schemas.microsoft.com/office/word/2023/wordml/word16du";
|
|
2998
3055
|
for (const part of this.doc.pkg.parts) {
|
|
2999
|
-
if (part === this.doc.part
|
|
3056
|
+
if (part === this.doc.part) {
|
|
3000
3057
|
if (!part._element.hasAttribute("xmlns:w16du")) {
|
|
3001
3058
|
part._element.setAttribute("xmlns:w16du", w16du_ns);
|
|
3002
3059
|
}
|
|
@@ -3013,15 +3070,59 @@ var RedlineEngine = class {
|
|
|
3013
3070
|
}
|
|
3014
3071
|
return null;
|
|
3015
3072
|
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Best-effort "did you mean" hint for a failed target. The common loop trap
|
|
3075
|
+
* (observed in the field) is an anchored regex like `^\( x \)$` against a
|
|
3076
|
+
* mid-document string: ^/$ bind to the whole full_text, so it never matches
|
|
3077
|
+
* even though the literal `( x )` is present. We strip regex anchoring/escapes
|
|
3078
|
+
* and probe full_text for a literal occurrence; if found, we tell the model
|
|
3079
|
+
* the exact literal that WOULD match so it drops the anchors instead of
|
|
3080
|
+
* escalating the regex further.
|
|
3081
|
+
*/
|
|
3082
|
+
_nearest_match_hint(target_text, is_regex) {
|
|
3083
|
+
if (!target_text) return "";
|
|
3084
|
+
let probe = target_text;
|
|
3085
|
+
if (is_regex) {
|
|
3086
|
+
probe = probe.replace(/^\^/, "").replace(/\$$/, "");
|
|
3087
|
+
probe = probe.replace(/^\\s\*/, "").replace(/\\s\*$/, "");
|
|
3088
|
+
probe = probe.replace(/\\([.^$*+?()[\]{}|\\/])/g, "$1");
|
|
3089
|
+
}
|
|
3090
|
+
probe = probe.trim();
|
|
3091
|
+
if (!probe || probe === target_text) {
|
|
3092
|
+
if (!is_regex) return "";
|
|
3093
|
+
}
|
|
3094
|
+
const idx = this.mapper.full_text.indexOf(probe);
|
|
3095
|
+
if (idx !== -1) {
|
|
3096
|
+
const ctx_start = Math.max(0, idx - 15);
|
|
3097
|
+
const ctx_end = Math.min(
|
|
3098
|
+
this.mapper.full_text.length,
|
|
3099
|
+
idx + probe.length + 15
|
|
3100
|
+
);
|
|
3101
|
+
const ctx = this.mapper.full_text.substring(ctx_start, ctx_end).replace(/\n/g, " ");
|
|
3102
|
+
return `
|
|
3103
|
+
Did you mean the literal "${probe}"? It appears in the document (\u2026${ctx}\u2026). If you used a regex, drop the ^/$ anchors \u2014 they match the start/end of the entire document, not a line.`;
|
|
3104
|
+
}
|
|
3105
|
+
return "";
|
|
3106
|
+
}
|
|
3016
3107
|
_build_edit_context_previews(edit) {
|
|
3017
3108
|
if (edit.type !== "modify") return [null, null];
|
|
3018
3109
|
if (edit._resolved_proxy_edit) {
|
|
3019
3110
|
edit = edit._resolved_proxy_edit;
|
|
3020
3111
|
}
|
|
3021
|
-
|
|
3112
|
+
let start_idx = edit._resolved_start_idx;
|
|
3022
3113
|
if (start_idx === void 0 || start_idx === null) return [null, null];
|
|
3023
|
-
|
|
3024
|
-
|
|
3114
|
+
let target_text = edit.target_text || "";
|
|
3115
|
+
let new_text = edit.new_text || "";
|
|
3116
|
+
const [clean_target, target_style] = this._parse_markdown_style(target_text);
|
|
3117
|
+
if (target_style && target_style.startsWith("Heading")) {
|
|
3118
|
+
const prefix_len = target_text.length - clean_target.length;
|
|
3119
|
+
start_idx += prefix_len;
|
|
3120
|
+
target_text = clean_target;
|
|
3121
|
+
}
|
|
3122
|
+
const [clean_new, new_style] = this._parse_markdown_style(new_text);
|
|
3123
|
+
if (new_style && new_style.startsWith("Heading")) {
|
|
3124
|
+
new_text = clean_new;
|
|
3125
|
+
}
|
|
3025
3126
|
const length = target_text.length;
|
|
3026
3127
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3027
3128
|
const full_text = active_mapper.full_text;
|
|
@@ -3245,6 +3346,93 @@ var RedlineEngine = class {
|
|
|
3245
3346
|
}
|
|
3246
3347
|
}
|
|
3247
3348
|
}
|
|
3349
|
+
/**
|
|
3350
|
+
* Revert every tracked change, returning the document to the state it had
|
|
3351
|
+
* before any revision was proposed. The exact inverse of
|
|
3352
|
+
* accept_all_revisions:
|
|
3353
|
+
*
|
|
3354
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
3355
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
3356
|
+
* <w:trPr>) drops the whole row.
|
|
3357
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
3358
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
3359
|
+
* the row survives.
|
|
3360
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
3361
|
+
*
|
|
3362
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
3363
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
3364
|
+
*
|
|
3365
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
3366
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
3367
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
3368
|
+
* body text.
|
|
3369
|
+
*
|
|
3370
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
3371
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
3372
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
3373
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
3374
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
3375
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
3376
|
+
*/
|
|
3377
|
+
reject_all_revisions() {
|
|
3378
|
+
const parts_to_process = [this.doc.element];
|
|
3379
|
+
for (const part of this.doc.pkg.parts) {
|
|
3380
|
+
if (part === this.doc.part) continue;
|
|
3381
|
+
if (part.contentType.includes("wordprocessingml") && part.contentType.endsWith("+xml")) {
|
|
3382
|
+
parts_to_process.push(part._element);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
for (const root_element of parts_to_process) {
|
|
3386
|
+
const insNodes = findAllDescendants(root_element, "w:ins");
|
|
3387
|
+
for (const ins of insNodes) {
|
|
3388
|
+
const parent = ins.parentNode;
|
|
3389
|
+
if (!parent) continue;
|
|
3390
|
+
this._clean_wrapping_comments(ins);
|
|
3391
|
+
this._delete_comments_in_element(ins);
|
|
3392
|
+
if (parent.tagName === "w:trPr") {
|
|
3393
|
+
const row = parent.parentNode;
|
|
3394
|
+
if (row && row.parentNode) {
|
|
3395
|
+
row.parentNode.removeChild(row);
|
|
3396
|
+
}
|
|
3397
|
+
} else {
|
|
3398
|
+
parent.removeChild(ins);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
const pNodes = findAllDescendants(root_element, "w:p");
|
|
3402
|
+
for (const p of pNodes) {
|
|
3403
|
+
const pPr = findChild(p, "w:pPr");
|
|
3404
|
+
if (pPr) {
|
|
3405
|
+
const rPr = findChild(pPr, "w:rPr");
|
|
3406
|
+
const delMark = rPr ? findChild(rPr, "w:del") : null;
|
|
3407
|
+
if (rPr && delMark) {
|
|
3408
|
+
rPr.removeChild(delMark);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
const delNodes = findAllDescendants(root_element, "w:del");
|
|
3413
|
+
for (const d of delNodes) {
|
|
3414
|
+
const parent = d.parentNode;
|
|
3415
|
+
if (!parent) continue;
|
|
3416
|
+
this._clean_wrapping_comments(d);
|
|
3417
|
+
if (parent.tagName === "w:trPr") {
|
|
3418
|
+
parent.removeChild(d);
|
|
3419
|
+
continue;
|
|
3420
|
+
}
|
|
3421
|
+
const delTexts = Array.from(d.getElementsByTagName("w:delText"));
|
|
3422
|
+
for (const dt of delTexts) {
|
|
3423
|
+
const t = d.ownerDocument.createElement("w:t");
|
|
3424
|
+
t.textContent = dt.textContent;
|
|
3425
|
+
if (dt.hasAttribute("xml:space"))
|
|
3426
|
+
t.setAttribute("xml:space", "preserve");
|
|
3427
|
+
dt.parentNode?.replaceChild(t, dt);
|
|
3428
|
+
}
|
|
3429
|
+
while (d.firstChild) {
|
|
3430
|
+
parent.insertBefore(d.firstChild, d);
|
|
3431
|
+
}
|
|
3432
|
+
parent.removeChild(d);
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3248
3436
|
_getNextId() {
|
|
3249
3437
|
this.current_id++;
|
|
3250
3438
|
return this.current_id.toString();
|
|
@@ -3773,28 +3961,34 @@ var RedlineEngine = class {
|
|
|
3773
3961
|
}
|
|
3774
3962
|
}
|
|
3775
3963
|
}
|
|
3776
|
-
validate_edits(edits) {
|
|
3964
|
+
validate_edits(edits, index_offset = 0) {
|
|
3777
3965
|
const errors = [];
|
|
3778
3966
|
if (!this.mapper.full_text) this.mapper["_build_map"]();
|
|
3779
|
-
errors.push(...validate_edit_strings(edits));
|
|
3967
|
+
errors.push(...validate_edit_strings(edits, index_offset));
|
|
3780
3968
|
for (let i = 0; i < edits.length; i++) {
|
|
3781
3969
|
const edit = edits[i];
|
|
3782
3970
|
if (typeof edit !== "object" || edit === null) {
|
|
3783
3971
|
errors.push(
|
|
3784
|
-
`- Edit ${i + 1} Failed: Invalid change format. Expected a JSON object, but received a primitive ${typeof edit}. Do not pass raw strings.`
|
|
3972
|
+
`- Edit ${i + 1 + index_offset} Failed: Invalid change format. Expected a JSON object, but received a primitive ${typeof edit}. Do not pass raw strings.`
|
|
3785
3973
|
);
|
|
3786
3974
|
continue;
|
|
3787
3975
|
}
|
|
3788
3976
|
if (!edit.target_text) continue;
|
|
3789
3977
|
const is_regex = edit.regex || false;
|
|
3790
3978
|
const match_mode = edit.match_mode || "strict";
|
|
3791
|
-
let matches = this.mapper.find_all_match_indices(
|
|
3979
|
+
let matches = this.mapper.find_all_match_indices(
|
|
3980
|
+
edit.target_text,
|
|
3981
|
+
is_regex
|
|
3982
|
+
);
|
|
3792
3983
|
let activeText = this.mapper.full_text;
|
|
3793
3984
|
let target_mapper = this.mapper;
|
|
3794
3985
|
if (matches.length === 0) {
|
|
3795
3986
|
if (!this.clean_mapper)
|
|
3796
3987
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
3797
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
3988
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
3989
|
+
edit.target_text,
|
|
3990
|
+
is_regex
|
|
3991
|
+
);
|
|
3798
3992
|
if (matches.length > 0) {
|
|
3799
3993
|
activeText = this.clean_mapper.full_text;
|
|
3800
3994
|
target_mapper = this.clean_mapper;
|
|
@@ -3816,7 +4010,10 @@ var RedlineEngine = class {
|
|
|
3816
4010
|
if (!this.original_mapper) {
|
|
3817
4011
|
this.original_mapper = new DocumentMapper(this.doc, false, true);
|
|
3818
4012
|
}
|
|
3819
|
-
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
4013
|
+
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
4014
|
+
edit.target_text,
|
|
4015
|
+
is_regex
|
|
4016
|
+
);
|
|
3820
4017
|
if (orig_matches.length > 0) {
|
|
3821
4018
|
is_deleted_text = true;
|
|
3822
4019
|
for (const [start, length] of orig_matches) {
|
|
@@ -3845,27 +4042,32 @@ var RedlineEngine = class {
|
|
|
3845
4042
|
if (is_deleted_text) {
|
|
3846
4043
|
const author_phrase = deleted_authors.size > 0 ? `by ${Array.from(deleted_authors).sort().join(", ")}` : "by an existing revision";
|
|
3847
4044
|
errors.push(
|
|
3848
|
-
`- Edit ${i + 1} Failed: Target text matches text inside a tracked deletion ${author_phrase}. Reject/accept that change first or target the active replacement text instead.`
|
|
4045
|
+
`- Edit ${i + 1 + index_offset} Failed: Target text matches text inside a tracked deletion ${author_phrase}. Reject/accept that change first or target the active replacement text instead.`
|
|
3849
4046
|
);
|
|
3850
4047
|
} else {
|
|
4048
|
+
const hint = this._nearest_match_hint(edit.target_text, is_regex);
|
|
3851
4049
|
errors.push(
|
|
3852
|
-
`- Edit ${i + 1} Failed: Target text not found in document:
|
|
3853
|
-
"${edit.target_text}"`
|
|
4050
|
+
`- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
|
|
4051
|
+
"${edit.target_text}"${hint}`
|
|
3854
4052
|
);
|
|
3855
4053
|
}
|
|
3856
4054
|
} else if (matches.length > 1 && match_mode === "strict") {
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
4055
|
+
if (edit.target_text.includes("|")) {
|
|
4056
|
+
matches = matches.slice(0, 1);
|
|
4057
|
+
} else {
|
|
4058
|
+
const positions = matches.map(([start, length]) => [
|
|
4059
|
+
start,
|
|
4060
|
+
start + length
|
|
4061
|
+
]);
|
|
4062
|
+
errors.push(
|
|
4063
|
+
format_ambiguity_error(
|
|
4064
|
+
i + 1 + index_offset,
|
|
4065
|
+
edit.target_text,
|
|
4066
|
+
activeText,
|
|
4067
|
+
positions
|
|
4068
|
+
)
|
|
4069
|
+
);
|
|
4070
|
+
}
|
|
3869
4071
|
}
|
|
3870
4072
|
if (matches.length === 1) {
|
|
3871
4073
|
const [m_start, m_len] = matches[0];
|
|
@@ -3878,29 +4080,36 @@ var RedlineEngine = class {
|
|
|
3878
4080
|
(edit.new_text || "").length - sfx
|
|
3879
4081
|
);
|
|
3880
4082
|
if (final_target.includes("\n\n")) {
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
if (
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4083
|
+
const balanced = matched.split("\n\n").length === (edit.new_text || "").split("\n\n").length;
|
|
4084
|
+
if (!balanced) {
|
|
4085
|
+
if (final_new.includes("\n\n")) {
|
|
4086
|
+
const parts = matched.split("\n\n");
|
|
4087
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4088
|
+
errors.push(
|
|
4089
|
+
`- Edit ${i + 1 + index_offset} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
|
|
4090
|
+
);
|
|
4091
|
+
}
|
|
4092
|
+
} else {
|
|
4093
|
+
const parts = final_target.split("\n\n");
|
|
4094
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4095
|
+
errors.push(
|
|
4096
|
+
`- Edit ${i + 1 + index_offset} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
|
|
4097
|
+
);
|
|
4098
|
+
}
|
|
3894
4099
|
}
|
|
3895
4100
|
}
|
|
3896
4101
|
}
|
|
3897
4102
|
}
|
|
3898
4103
|
for (const [start, length] of matches) {
|
|
3899
|
-
const spans =
|
|
4104
|
+
const spans = target_mapper.spans.filter(
|
|
3900
4105
|
(s) => s.end > start && s.start < start + length
|
|
3901
4106
|
);
|
|
3902
|
-
const
|
|
4107
|
+
const insAuthors = /* @__PURE__ */ new Set();
|
|
4108
|
+
const commentAuthors = /* @__PURE__ */ new Set();
|
|
4109
|
+
let hasNonForeignRealText = false;
|
|
3903
4110
|
for (const s of spans) {
|
|
4111
|
+
if (s.run === null) continue;
|
|
4112
|
+
let isForeignIns = false;
|
|
3904
4113
|
if (s.ins_id) {
|
|
3905
4114
|
const insNodes = findAllDescendants(
|
|
3906
4115
|
this.doc.element,
|
|
@@ -3908,21 +4117,35 @@ var RedlineEngine = class {
|
|
|
3908
4117
|
).filter((n) => n.getAttribute("w:id") === s.ins_id);
|
|
3909
4118
|
if (insNodes.length > 0) {
|
|
3910
4119
|
const auth = insNodes[0].getAttribute("w:author");
|
|
3911
|
-
if (auth && auth !== this.author)
|
|
4120
|
+
if (auth && auth !== this.author) {
|
|
4121
|
+
insAuthors.add(auth);
|
|
4122
|
+
isForeignIns = true;
|
|
4123
|
+
}
|
|
3912
4124
|
}
|
|
3913
4125
|
}
|
|
4126
|
+
if (!isForeignIns) hasNonForeignRealText = true;
|
|
4127
|
+
}
|
|
4128
|
+
for (const s of spans) {
|
|
3914
4129
|
if (s.comment_ids) {
|
|
3915
4130
|
for (const cid of s.comment_ids) {
|
|
3916
4131
|
const c_data = this.mapper.comments_map[cid];
|
|
3917
4132
|
if (c_data && c_data.author && c_data.author !== this.author) {
|
|
3918
|
-
|
|
4133
|
+
commentAuthors.add(c_data.author);
|
|
3919
4134
|
}
|
|
3920
4135
|
}
|
|
3921
4136
|
}
|
|
3922
4137
|
}
|
|
3923
|
-
if (
|
|
4138
|
+
if (insAuthors.size > 0 || commentAuthors.size > 0) {
|
|
4139
|
+
const fullyWithinForeignIns = insAuthors.size > 0 && !hasNonForeignRealText && commentAuthors.size === 0;
|
|
4140
|
+
if ((match_mode === "strict" || match_mode === "first") && fullyWithinForeignIns) {
|
|
4141
|
+
continue;
|
|
4142
|
+
}
|
|
4143
|
+
const nestedAuthors = /* @__PURE__ */ new Set([
|
|
4144
|
+
...insAuthors,
|
|
4145
|
+
...commentAuthors
|
|
4146
|
+
]);
|
|
3924
4147
|
errors.push(
|
|
3925
|
-
`- Edit ${i + 1} Failed: Modification targets an active insertion from another author (${Array.from(nestedAuthors).join(", ")}). Accept that change first or scope your edit outside of it.`
|
|
4148
|
+
`- Edit ${i + 1 + index_offset} Failed: Modification targets an active insertion from another author (${Array.from(nestedAuthors).join(", ")}). Accept that change first or scope your edit outside of it.`
|
|
3926
4149
|
);
|
|
3927
4150
|
}
|
|
3928
4151
|
}
|
|
@@ -3984,22 +4207,13 @@ var RedlineEngine = class {
|
|
|
3984
4207
|
});
|
|
3985
4208
|
}
|
|
3986
4209
|
if (dry_run) {
|
|
3987
|
-
const
|
|
3988
|
-
|
|
3989
|
-
if (part._element) {
|
|
3990
|
-
baselines.set(part, part._element.cloneNode(true));
|
|
3991
|
-
}
|
|
3992
|
-
}
|
|
4210
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4211
|
+
const originalCurrentId = this.current_id;
|
|
3993
4212
|
try {
|
|
3994
4213
|
return this._process_batch_internal(changes, true);
|
|
3995
4214
|
} finally {
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
if (doc && doc.documentElement) {
|
|
3999
|
-
doc.replaceChild(originalEl, doc.documentElement);
|
|
4000
|
-
}
|
|
4001
|
-
part._element = originalEl;
|
|
4002
|
-
}
|
|
4215
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4216
|
+
this.current_id = originalCurrentId;
|
|
4003
4217
|
this.mapper = new DocumentMapper(this.doc);
|
|
4004
4218
|
this.comments_manager = new CommentsManager(this.doc);
|
|
4005
4219
|
this.clean_mapper = null;
|
|
@@ -4009,6 +4223,13 @@ var RedlineEngine = class {
|
|
|
4009
4223
|
}
|
|
4010
4224
|
}
|
|
4011
4225
|
_process_batch_internal(changes, dry_run_mode = false) {
|
|
4226
|
+
for (const c of changes) {
|
|
4227
|
+
if (c && typeof c === "object" && c.type === "modify" && c.target_text && c.new_text) {
|
|
4228
|
+
const [strippedTarget, strippedNew] = stripMatchingHeadingHashes(c.target_text, c.new_text);
|
|
4229
|
+
c.target_text = strippedTarget;
|
|
4230
|
+
c.new_text = strippedNew;
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4012
4233
|
this.skipped_details = [];
|
|
4013
4234
|
const actions = changes.filter(
|
|
4014
4235
|
(c) => c !== null && typeof c === "object" && ["accept", "reject", "reply"].includes(c.type)
|
|
@@ -4017,13 +4238,13 @@ var RedlineEngine = class {
|
|
|
4017
4238
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
4018
4239
|
);
|
|
4019
4240
|
if (!dry_run_mode) {
|
|
4020
|
-
const
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4241
|
+
const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
4242
|
+
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
4243
|
+
const edit_errors = validate_edits_now ? this.validate_edits(edits) : [];
|
|
4244
|
+
const all_errors = [
|
|
4245
|
+
...action_errors,
|
|
4246
|
+
...edit_errors
|
|
4247
|
+
];
|
|
4027
4248
|
if (all_errors.length > 0) {
|
|
4028
4249
|
throw new BatchValidationError(all_errors);
|
|
4029
4250
|
}
|
|
@@ -4057,8 +4278,9 @@ var RedlineEngine = class {
|
|
|
4057
4278
|
let skipped_edits = 0;
|
|
4058
4279
|
if (edits.length > 0) {
|
|
4059
4280
|
if (dry_run_mode) {
|
|
4060
|
-
for (
|
|
4061
|
-
const
|
|
4281
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4282
|
+
const edit = edits[i];
|
|
4283
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4062
4284
|
const warning = this._check_punctuation_warning(
|
|
4063
4285
|
edit.target_text || ""
|
|
4064
4286
|
);
|
|
@@ -4092,6 +4314,8 @@ var RedlineEngine = class {
|
|
|
4092
4314
|
occurrences_modified: edit._occurrences_modified || 0,
|
|
4093
4315
|
match_mode: edit.match_mode || "strict"
|
|
4094
4316
|
});
|
|
4317
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4318
|
+
this.clean_mapper = null;
|
|
4095
4319
|
} else {
|
|
4096
4320
|
skipped_edits++;
|
|
4097
4321
|
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
@@ -4107,15 +4331,37 @@ var RedlineEngine = class {
|
|
|
4107
4331
|
}
|
|
4108
4332
|
}
|
|
4109
4333
|
} else {
|
|
4110
|
-
const
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4334
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4335
|
+
const originalCurrentId = this.current_id;
|
|
4336
|
+
try {
|
|
4337
|
+
const sequential_errors = [];
|
|
4338
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4339
|
+
const edit = edits[i];
|
|
4340
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4341
|
+
if (single_errors.length > 0) {
|
|
4342
|
+
sequential_errors.push(...single_errors);
|
|
4343
|
+
} else {
|
|
4344
|
+
this.apply_edits([edit], page_offsets);
|
|
4345
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4346
|
+
this.clean_mapper = null;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
if (sequential_errors.length > 0) {
|
|
4350
|
+
throw new BatchValidationError(sequential_errors);
|
|
4351
|
+
}
|
|
4352
|
+
} catch (err) {
|
|
4353
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4354
|
+
this.current_id = originalCurrentId;
|
|
4355
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4356
|
+
this.comments_manager = new CommentsManager(this.doc);
|
|
4357
|
+
this.clean_mapper = null;
|
|
4358
|
+
throw err;
|
|
4359
|
+
}
|
|
4360
|
+
applied_edits = edits.filter(
|
|
4361
|
+
(e) => e._applied_status
|
|
4362
|
+
).length;
|
|
4363
|
+
skipped_edits = edits.length - applied_edits;
|
|
4364
|
+
for (const edit of edits) {
|
|
4119
4365
|
const success = edit._applied_status || false;
|
|
4120
4366
|
const error_msg = edit._error_msg || null;
|
|
4121
4367
|
const warning = this._check_punctuation_warning(
|
|
@@ -4234,6 +4480,7 @@ var RedlineEngine = class {
|
|
|
4234
4480
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4235
4481
|
);
|
|
4236
4482
|
const occupied_ranges = [];
|
|
4483
|
+
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4237
4484
|
for (const [edit, orig_new] of resolved_edits) {
|
|
4238
4485
|
const start = edit._resolved_start_idx || 0;
|
|
4239
4486
|
const end = start + (edit.target_text ? edit.target_text.length : 0);
|
|
@@ -4262,21 +4509,38 @@ var RedlineEngine = class {
|
|
|
4262
4509
|
success = this._apply_table_edit(edit, false);
|
|
4263
4510
|
}
|
|
4264
4511
|
if (success) {
|
|
4265
|
-
|
|
4512
|
+
const group_id = edit._split_group_id;
|
|
4513
|
+
const first_in_group = group_id === void 0 || group_id === null || !counted_split_groups.has(group_id);
|
|
4514
|
+
if (first_in_group && group_id !== void 0 && group_id !== null) {
|
|
4515
|
+
counted_split_groups.add(group_id);
|
|
4516
|
+
}
|
|
4517
|
+
if (first_in_group) applied++;
|
|
4266
4518
|
occupied_ranges.push([start, end]);
|
|
4267
4519
|
edit._applied_status = true;
|
|
4268
4520
|
const parent = edit._parent_edit_ref;
|
|
4269
4521
|
if (parent) {
|
|
4270
4522
|
parent._applied_status = true;
|
|
4271
|
-
|
|
4272
|
-
|
|
4523
|
+
if (first_in_group) {
|
|
4524
|
+
parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
|
|
4525
|
+
}
|
|
4526
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4527
|
+
start,
|
|
4528
|
+
this.mapper.full_text,
|
|
4529
|
+
page_offsets
|
|
4530
|
+
);
|
|
4273
4531
|
const pages = parent._pages || [];
|
|
4274
4532
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4275
4533
|
parent._pages = pages;
|
|
4276
4534
|
parent._heading_path = path;
|
|
4277
4535
|
} else {
|
|
4278
|
-
|
|
4279
|
-
|
|
4536
|
+
if (first_in_group) {
|
|
4537
|
+
edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
|
|
4538
|
+
}
|
|
4539
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4540
|
+
start,
|
|
4541
|
+
this.mapper.full_text,
|
|
4542
|
+
page_offsets
|
|
4543
|
+
);
|
|
4280
4544
|
const pages = edit._pages || [];
|
|
4281
4545
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4282
4546
|
edit._pages = pages;
|
|
@@ -4437,12 +4701,18 @@ var RedlineEngine = class {
|
|
|
4437
4701
|
if (!edit.target_text) return null;
|
|
4438
4702
|
const is_regex = edit.regex || false;
|
|
4439
4703
|
const match_mode = edit.match_mode || "strict";
|
|
4440
|
-
let matches = this.mapper.find_all_match_indices(
|
|
4704
|
+
let matches = this.mapper.find_all_match_indices(
|
|
4705
|
+
edit.target_text,
|
|
4706
|
+
is_regex
|
|
4707
|
+
);
|
|
4441
4708
|
let use_clean_map = false;
|
|
4442
4709
|
if (matches.length === 0) {
|
|
4443
4710
|
if (!this.clean_mapper)
|
|
4444
4711
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4445
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
4712
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
4713
|
+
edit.target_text,
|
|
4714
|
+
is_regex
|
|
4715
|
+
);
|
|
4446
4716
|
if (matches.length > 0) use_clean_map = true;
|
|
4447
4717
|
else return null;
|
|
4448
4718
|
}
|
|
@@ -4467,14 +4737,49 @@ var RedlineEngine = class {
|
|
|
4467
4737
|
start_idx + match_len
|
|
4468
4738
|
);
|
|
4469
4739
|
let current_effective_new_text = edit.new_text || "";
|
|
4740
|
+
if (/^\{#cell:[^}]+\}$/.test(actual_doc_text.trim())) {
|
|
4741
|
+
let ins_text = current_effective_new_text;
|
|
4742
|
+
ins_text = ins_text.split(actual_doc_text.trim()).join("");
|
|
4743
|
+
if (ins_text) {
|
|
4744
|
+
all_sub_edits.push({
|
|
4745
|
+
type: "modify",
|
|
4746
|
+
target_text: "",
|
|
4747
|
+
new_text: ins_text,
|
|
4748
|
+
comment: edit.comment,
|
|
4749
|
+
// Insert at the anchor token's start so the new run lands inside
|
|
4750
|
+
// the cell paragraph that get_insertion_anchor resolves there.
|
|
4751
|
+
_match_start_index: start_idx,
|
|
4752
|
+
_internal_op: "INSERTION",
|
|
4753
|
+
_active_mapper_ref: active_mapper
|
|
4754
|
+
});
|
|
4755
|
+
} else if (edit.comment) {
|
|
4756
|
+
all_sub_edits.push({
|
|
4757
|
+
type: "modify",
|
|
4758
|
+
target_text: "",
|
|
4759
|
+
new_text: "",
|
|
4760
|
+
comment: edit.comment,
|
|
4761
|
+
_match_start_index: start_idx,
|
|
4762
|
+
_internal_op: "COMMENT_ONLY",
|
|
4763
|
+
_active_mapper_ref: active_mapper
|
|
4764
|
+
});
|
|
4765
|
+
}
|
|
4766
|
+
continue;
|
|
4767
|
+
}
|
|
4470
4768
|
if (is_regex && current_effective_new_text) {
|
|
4471
4769
|
try {
|
|
4472
|
-
current_effective_new_text = actual_doc_text.replace(
|
|
4770
|
+
current_effective_new_text = actual_doc_text.replace(
|
|
4771
|
+
new RegExp(edit.target_text),
|
|
4772
|
+
current_effective_new_text
|
|
4773
|
+
);
|
|
4473
4774
|
} catch (e) {
|
|
4474
4775
|
}
|
|
4475
4776
|
}
|
|
4476
|
-
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4477
|
-
|
|
4777
|
+
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4778
|
+
edit.target_text
|
|
4779
|
+
);
|
|
4780
|
+
const [edit_new_clean, edit_new_style] = this._parse_markdown_style(
|
|
4781
|
+
current_effective_new_text
|
|
4782
|
+
);
|
|
4478
4783
|
if (edit_target_style !== edit_new_style) {
|
|
4479
4784
|
const [actual_clean] = this._parse_markdown_style(actual_doc_text);
|
|
4480
4785
|
const final_target2 = actual_clean;
|
|
@@ -4513,7 +4818,9 @@ var RedlineEngine = class {
|
|
|
4513
4818
|
let effective_start_idx = start_idx;
|
|
4514
4819
|
if (current_effective_new_text.startsWith(actual_doc_text)) {
|
|
4515
4820
|
effective_op = "INSERTION";
|
|
4516
|
-
final_new = current_effective_new_text.substring(
|
|
4821
|
+
final_new = current_effective_new_text.substring(
|
|
4822
|
+
actual_doc_text.length
|
|
4823
|
+
);
|
|
4517
4824
|
effective_start_idx = start_idx + match_len;
|
|
4518
4825
|
} else {
|
|
4519
4826
|
const [prefix_len, suffix_len] = trim_common_context(
|
|
@@ -4525,6 +4832,51 @@ var RedlineEngine = class {
|
|
|
4525
4832
|
final_target = actual_doc_text.substring(prefix_len, t_end);
|
|
4526
4833
|
final_new = current_effective_new_text.substring(prefix_len, n_end);
|
|
4527
4834
|
effective_start_idx = start_idx + prefix_len;
|
|
4835
|
+
const target_segs = actual_doc_text.split("\n\n");
|
|
4836
|
+
const new_segs = current_effective_new_text.split("\n\n");
|
|
4837
|
+
if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
|
|
4838
|
+
const split_sub_edits = [];
|
|
4839
|
+
let seg_offset = start_idx;
|
|
4840
|
+
let comment_assigned = false;
|
|
4841
|
+
for (let k = 0; k < target_segs.length; k++) {
|
|
4842
|
+
const t_seg = target_segs[k];
|
|
4843
|
+
const n_seg = new_segs[k];
|
|
4844
|
+
if (t_seg !== n_seg) {
|
|
4845
|
+
const [seg_prefix, seg_suffix] = trim_common_context(t_seg, n_seg);
|
|
4846
|
+
const seg_target = t_seg.substring(
|
|
4847
|
+
seg_prefix,
|
|
4848
|
+
t_seg.length - seg_suffix
|
|
4849
|
+
);
|
|
4850
|
+
const seg_new = n_seg.substring(
|
|
4851
|
+
seg_prefix,
|
|
4852
|
+
n_seg.length - seg_suffix
|
|
4853
|
+
);
|
|
4854
|
+
const seg_start = seg_offset + seg_prefix;
|
|
4855
|
+
let seg_op;
|
|
4856
|
+
if (!seg_target && seg_new) seg_op = "INSERTION";
|
|
4857
|
+
else if (seg_target && !seg_new) seg_op = "DELETION";
|
|
4858
|
+
else if (seg_target && seg_new) seg_op = "MODIFICATION";
|
|
4859
|
+
else seg_op = "COMMENT_ONLY";
|
|
4860
|
+
const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
|
|
4861
|
+
if (seg_comment) comment_assigned = true;
|
|
4862
|
+
split_sub_edits.push({
|
|
4863
|
+
type: "modify",
|
|
4864
|
+
target_text: seg_target,
|
|
4865
|
+
new_text: seg_new,
|
|
4866
|
+
comment: seg_comment,
|
|
4867
|
+
_match_start_index: seg_start,
|
|
4868
|
+
_internal_op: seg_op,
|
|
4869
|
+
_active_mapper_ref: active_mapper,
|
|
4870
|
+
_split_group_id: start_idx
|
|
4871
|
+
});
|
|
4872
|
+
}
|
|
4873
|
+
seg_offset += t_seg.length + 2;
|
|
4874
|
+
}
|
|
4875
|
+
if (split_sub_edits.length > 0) {
|
|
4876
|
+
for (const sub of split_sub_edits) all_sub_edits.push(sub);
|
|
4877
|
+
continue;
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4528
4880
|
if (!final_target && final_new) effective_op = "INSERTION";
|
|
4529
4881
|
else if (final_target && !final_new) effective_op = "DELETION";
|
|
4530
4882
|
else if (final_target && final_new) effective_op = "MODIFICATION";
|
|
@@ -4544,6 +4896,35 @@ var RedlineEngine = class {
|
|
|
4544
4896
|
if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
|
|
4545
4897
|
return all_sub_edits[0];
|
|
4546
4898
|
}
|
|
4899
|
+
/**
|
|
4900
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
4901
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
4902
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
4903
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
4904
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
4905
|
+
* <w:ins>.
|
|
4906
|
+
*/
|
|
4907
|
+
_insert_and_split_ins(parent_ins, split_after, new_elem) {
|
|
4908
|
+
const grandparent = parent_ins.parentNode;
|
|
4909
|
+
if (!grandparent) return;
|
|
4910
|
+
const left = parent_ins.cloneNode(false);
|
|
4911
|
+
const right = parent_ins.cloneNode(false);
|
|
4912
|
+
let toRight = false;
|
|
4913
|
+
for (const kid of Array.from(parent_ins.childNodes)) {
|
|
4914
|
+
parent_ins.removeChild(kid);
|
|
4915
|
+
if (!toRight) {
|
|
4916
|
+
left.appendChild(kid);
|
|
4917
|
+
if (kid === split_after) toRight = true;
|
|
4918
|
+
} else {
|
|
4919
|
+
right.appendChild(kid);
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
if (left.childNodes.length > 0) grandparent.insertBefore(left, parent_ins);
|
|
4923
|
+
grandparent.insertBefore(new_elem, parent_ins);
|
|
4924
|
+
if (right.childNodes.length > 0)
|
|
4925
|
+
grandparent.insertBefore(right, parent_ins);
|
|
4926
|
+
grandparent.removeChild(parent_ins);
|
|
4927
|
+
}
|
|
4547
4928
|
_apply_single_edit_indexed(edit, orig_new, rebuild_map) {
|
|
4548
4929
|
let op = edit._internal_op;
|
|
4549
4930
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
@@ -4594,7 +4975,12 @@ var RedlineEngine = class {
|
|
|
4594
4975
|
const first_anchor = ascend_to_paragraph_child(first_el, start_p);
|
|
4595
4976
|
const last_anchor = ascend_to_paragraph_child(last_el, end_p);
|
|
4596
4977
|
if (start_p === end_p) {
|
|
4597
|
-
this._attach_comment(
|
|
4978
|
+
this._attach_comment(
|
|
4979
|
+
start_p,
|
|
4980
|
+
first_anchor,
|
|
4981
|
+
last_anchor,
|
|
4982
|
+
edit.comment
|
|
4983
|
+
);
|
|
4598
4984
|
} else {
|
|
4599
4985
|
this._attach_comment_spanning(
|
|
4600
4986
|
start_p,
|
|
@@ -4746,7 +5132,16 @@ var RedlineEngine = class {
|
|
|
4746
5132
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
4747
5133
|
if (is_inline_first) {
|
|
4748
5134
|
if (anchor_run) {
|
|
4749
|
-
|
|
5135
|
+
const anchor_parent = anchor_run._element.parentNode;
|
|
5136
|
+
if (anchor_parent && anchor_parent.tagName === "w:ins") {
|
|
5137
|
+
this._insert_and_split_ins(
|
|
5138
|
+
anchor_parent,
|
|
5139
|
+
anchor_run._element,
|
|
5140
|
+
result.first_node
|
|
5141
|
+
);
|
|
5142
|
+
} else {
|
|
5143
|
+
insertAfter(result.first_node, anchor_run._element);
|
|
5144
|
+
}
|
|
4750
5145
|
} else if (anchor_para) {
|
|
4751
5146
|
anchor_para._element.appendChild(result.first_node);
|
|
4752
5147
|
}
|
|
@@ -4856,7 +5251,12 @@ var RedlineEngine = class {
|
|
|
4856
5251
|
if (result.first_node) {
|
|
4857
5252
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
4858
5253
|
if (is_inline_first) {
|
|
4859
|
-
|
|
5254
|
+
const del_parent = last_del.parentNode;
|
|
5255
|
+
if (del_parent && del_parent.tagName === "w:ins") {
|
|
5256
|
+
this._insert_and_split_ins(del_parent, last_del, result.first_node);
|
|
5257
|
+
} else {
|
|
5258
|
+
insertAfter(result.first_node, last_del);
|
|
5259
|
+
}
|
|
4860
5260
|
ins_elem = result.first_node;
|
|
4861
5261
|
} else {
|
|
4862
5262
|
ins_elem = result.last_ins;
|
|
@@ -5883,13 +6283,21 @@ function extract_table(table, comments_map, cleanView, cursor, paragraph_offsets
|
|
|
5883
6283
|
if (seen_cells.has(cell)) continue;
|
|
5884
6284
|
seen_cells.add(cell);
|
|
5885
6285
|
if (!first_cell) cell_cursor += 3;
|
|
5886
|
-
|
|
6286
|
+
let cell_content = _extract_blocks(
|
|
5887
6287
|
cell,
|
|
5888
6288
|
comments_map,
|
|
5889
6289
|
cleanView,
|
|
5890
6290
|
cell_cursor,
|
|
5891
6291
|
paragraph_offsets
|
|
5892
6292
|
);
|
|
6293
|
+
if (!cleanView) {
|
|
6294
|
+
const firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
6295
|
+
const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
6296
|
+
if (paraId) {
|
|
6297
|
+
const anchor = `{#cell:${paraId}}`;
|
|
6298
|
+
cell_content = cell_content + anchor;
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
5893
6301
|
cell_texts.push(cell_content);
|
|
5894
6302
|
cell_cursor += cell_content.length;
|
|
5895
6303
|
first_cell = false;
|