@adeu/core 1.17.1 → 1.17.2
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 +345 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +345 -84
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine.qa.test.ts +4 -4
- package/src/engine.tables.test.ts +138 -36
- package/src/engine.ts +433 -105
- package/src/ingest.ts +16 -1
- package/src/mapper.ts +23 -1
- package/src/markup.ts +1 -1
- package/src/repro.feedback.test.ts +160 -0
- package/src/repro.report.test.ts +130 -0
- package/src/repro_qa_report_v3.test.ts +194 -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;
|
|
@@ -3773,28 +3874,34 @@ var RedlineEngine = class {
|
|
|
3773
3874
|
}
|
|
3774
3875
|
}
|
|
3775
3876
|
}
|
|
3776
|
-
validate_edits(edits) {
|
|
3877
|
+
validate_edits(edits, index_offset = 0) {
|
|
3777
3878
|
const errors = [];
|
|
3778
3879
|
if (!this.mapper.full_text) this.mapper["_build_map"]();
|
|
3779
|
-
errors.push(...validate_edit_strings(edits));
|
|
3880
|
+
errors.push(...validate_edit_strings(edits, index_offset));
|
|
3780
3881
|
for (let i = 0; i < edits.length; i++) {
|
|
3781
3882
|
const edit = edits[i];
|
|
3782
3883
|
if (typeof edit !== "object" || edit === null) {
|
|
3783
3884
|
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.`
|
|
3885
|
+
`- 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
3886
|
);
|
|
3786
3887
|
continue;
|
|
3787
3888
|
}
|
|
3788
3889
|
if (!edit.target_text) continue;
|
|
3789
3890
|
const is_regex = edit.regex || false;
|
|
3790
3891
|
const match_mode = edit.match_mode || "strict";
|
|
3791
|
-
let matches = this.mapper.find_all_match_indices(
|
|
3892
|
+
let matches = this.mapper.find_all_match_indices(
|
|
3893
|
+
edit.target_text,
|
|
3894
|
+
is_regex
|
|
3895
|
+
);
|
|
3792
3896
|
let activeText = this.mapper.full_text;
|
|
3793
3897
|
let target_mapper = this.mapper;
|
|
3794
3898
|
if (matches.length === 0) {
|
|
3795
3899
|
if (!this.clean_mapper)
|
|
3796
3900
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
3797
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
3901
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
3902
|
+
edit.target_text,
|
|
3903
|
+
is_regex
|
|
3904
|
+
);
|
|
3798
3905
|
if (matches.length > 0) {
|
|
3799
3906
|
activeText = this.clean_mapper.full_text;
|
|
3800
3907
|
target_mapper = this.clean_mapper;
|
|
@@ -3816,7 +3923,10 @@ var RedlineEngine = class {
|
|
|
3816
3923
|
if (!this.original_mapper) {
|
|
3817
3924
|
this.original_mapper = new DocumentMapper(this.doc, false, true);
|
|
3818
3925
|
}
|
|
3819
|
-
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
3926
|
+
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
3927
|
+
edit.target_text,
|
|
3928
|
+
is_regex
|
|
3929
|
+
);
|
|
3820
3930
|
if (orig_matches.length > 0) {
|
|
3821
3931
|
is_deleted_text = true;
|
|
3822
3932
|
for (const [start, length] of orig_matches) {
|
|
@@ -3845,27 +3955,32 @@ var RedlineEngine = class {
|
|
|
3845
3955
|
if (is_deleted_text) {
|
|
3846
3956
|
const author_phrase = deleted_authors.size > 0 ? `by ${Array.from(deleted_authors).sort().join(", ")}` : "by an existing revision";
|
|
3847
3957
|
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.`
|
|
3958
|
+
`- 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
3959
|
);
|
|
3850
3960
|
} else {
|
|
3961
|
+
const hint = this._nearest_match_hint(edit.target_text, is_regex);
|
|
3851
3962
|
errors.push(
|
|
3852
|
-
`- Edit ${i + 1} Failed: Target text not found in document:
|
|
3853
|
-
"${edit.target_text}"`
|
|
3963
|
+
`- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
|
|
3964
|
+
"${edit.target_text}"${hint}`
|
|
3854
3965
|
);
|
|
3855
3966
|
}
|
|
3856
3967
|
} else if (matches.length > 1 && match_mode === "strict") {
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3968
|
+
if (edit.target_text.includes("|")) {
|
|
3969
|
+
matches = matches.slice(0, 1);
|
|
3970
|
+
} else {
|
|
3971
|
+
const positions = matches.map(([start, length]) => [
|
|
3972
|
+
start,
|
|
3973
|
+
start + length
|
|
3974
|
+
]);
|
|
3975
|
+
errors.push(
|
|
3976
|
+
format_ambiguity_error(
|
|
3977
|
+
i + 1 + index_offset,
|
|
3978
|
+
edit.target_text,
|
|
3979
|
+
activeText,
|
|
3980
|
+
positions
|
|
3981
|
+
)
|
|
3982
|
+
);
|
|
3983
|
+
}
|
|
3869
3984
|
}
|
|
3870
3985
|
if (matches.length === 1) {
|
|
3871
3986
|
const [m_start, m_len] = matches[0];
|
|
@@ -3882,14 +3997,14 @@ var RedlineEngine = class {
|
|
|
3882
3997
|
const parts = matched.split("\n\n");
|
|
3883
3998
|
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3884
3999
|
errors.push(
|
|
3885
|
-
`- Edit ${i + 1} 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.`
|
|
4000
|
+
`- 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.`
|
|
3886
4001
|
);
|
|
3887
4002
|
}
|
|
3888
4003
|
} else {
|
|
3889
4004
|
const parts = final_target.split("\n\n");
|
|
3890
4005
|
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3891
4006
|
errors.push(
|
|
3892
|
-
`- Edit ${i + 1} 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.`
|
|
4007
|
+
`- 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.`
|
|
3893
4008
|
);
|
|
3894
4009
|
}
|
|
3895
4010
|
}
|
|
@@ -3908,7 +4023,13 @@ var RedlineEngine = class {
|
|
|
3908
4023
|
).filter((n) => n.getAttribute("w:id") === s.ins_id);
|
|
3909
4024
|
if (insNodes.length > 0) {
|
|
3910
4025
|
const auth = insNodes[0].getAttribute("w:author");
|
|
3911
|
-
if (auth && auth !== this.author)
|
|
4026
|
+
if (auth && auth !== this.author) {
|
|
4027
|
+
const is_fully_contained_in_ins = start >= s.start && start + length <= s.end;
|
|
4028
|
+
const is_lockout = is_fully_contained_in_ins || match_mode === "all";
|
|
4029
|
+
if (is_lockout) {
|
|
4030
|
+
nestedAuthors.add(auth);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
3912
4033
|
}
|
|
3913
4034
|
}
|
|
3914
4035
|
if (s.comment_ids) {
|
|
@@ -3922,7 +4043,7 @@ var RedlineEngine = class {
|
|
|
3922
4043
|
}
|
|
3923
4044
|
if (nestedAuthors.size > 0) {
|
|
3924
4045
|
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.`
|
|
4046
|
+
`- 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
4047
|
);
|
|
3927
4048
|
}
|
|
3928
4049
|
}
|
|
@@ -3984,22 +4105,13 @@ var RedlineEngine = class {
|
|
|
3984
4105
|
});
|
|
3985
4106
|
}
|
|
3986
4107
|
if (dry_run) {
|
|
3987
|
-
const
|
|
3988
|
-
|
|
3989
|
-
if (part._element) {
|
|
3990
|
-
baselines.set(part, part._element.cloneNode(true));
|
|
3991
|
-
}
|
|
3992
|
-
}
|
|
4108
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4109
|
+
const originalCurrentId = this.current_id;
|
|
3993
4110
|
try {
|
|
3994
4111
|
return this._process_batch_internal(changes, true);
|
|
3995
4112
|
} finally {
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
if (doc && doc.documentElement) {
|
|
3999
|
-
doc.replaceChild(originalEl, doc.documentElement);
|
|
4000
|
-
}
|
|
4001
|
-
part._element = originalEl;
|
|
4002
|
-
}
|
|
4113
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4114
|
+
this.current_id = originalCurrentId;
|
|
4003
4115
|
this.mapper = new DocumentMapper(this.doc);
|
|
4004
4116
|
this.comments_manager = new CommentsManager(this.doc);
|
|
4005
4117
|
this.clean_mapper = null;
|
|
@@ -4009,6 +4121,13 @@ var RedlineEngine = class {
|
|
|
4009
4121
|
}
|
|
4010
4122
|
}
|
|
4011
4123
|
_process_batch_internal(changes, dry_run_mode = false) {
|
|
4124
|
+
for (const c of changes) {
|
|
4125
|
+
if (c && typeof c === "object" && c.type === "modify" && c.target_text && c.new_text) {
|
|
4126
|
+
const [strippedTarget, strippedNew] = stripMatchingHeadingHashes(c.target_text, c.new_text);
|
|
4127
|
+
c.target_text = strippedTarget;
|
|
4128
|
+
c.new_text = strippedNew;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4012
4131
|
this.skipped_details = [];
|
|
4013
4132
|
const actions = changes.filter(
|
|
4014
4133
|
(c) => c !== null && typeof c === "object" && ["accept", "reject", "reply"].includes(c.type)
|
|
@@ -4016,14 +4135,67 @@ var RedlineEngine = class {
|
|
|
4016
4135
|
const edits = changes.filter(
|
|
4017
4136
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
4018
4137
|
);
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
if (
|
|
4022
|
-
|
|
4138
|
+
let mapper_dirty = false;
|
|
4139
|
+
for (const edit of edits) {
|
|
4140
|
+
if (typeof edit !== "object" || edit === null || !edit.target_text) continue;
|
|
4141
|
+
const is_regex = edit.regex || false;
|
|
4142
|
+
let matches = this.mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4143
|
+
let target_mapper = this.mapper;
|
|
4144
|
+
if (matches.length === 0) {
|
|
4145
|
+
if (!this.clean_mapper) {
|
|
4146
|
+
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4147
|
+
}
|
|
4148
|
+
matches = this.clean_mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4149
|
+
target_mapper = this.clean_mapper;
|
|
4023
4150
|
}
|
|
4024
|
-
|
|
4025
|
-
|
|
4151
|
+
for (const [start, length] of matches) {
|
|
4152
|
+
const spans = target_mapper.spans.filter(
|
|
4153
|
+
(s) => s.end > start && s.start < start + length
|
|
4154
|
+
);
|
|
4155
|
+
for (const s of spans) {
|
|
4156
|
+
if (s.ins_id) {
|
|
4157
|
+
const insNodes = findAllDescendants(this.doc.element, "w:ins").filter(
|
|
4158
|
+
(n) => n.getAttribute("w:id") === s.ins_id
|
|
4159
|
+
);
|
|
4160
|
+
if (insNodes.length > 0) {
|
|
4161
|
+
const auth = insNodes[0].getAttribute("w:author");
|
|
4162
|
+
if (auth && auth !== this.author) {
|
|
4163
|
+
const is_fully_contained_in_ins = start >= s.start && start + length <= s.end;
|
|
4164
|
+
const match_mode = edit.match_mode || "strict";
|
|
4165
|
+
if (!is_fully_contained_in_ins && match_mode !== "all") {
|
|
4166
|
+
const node = insNodes[0];
|
|
4167
|
+
this._clean_wrapping_comments(node);
|
|
4168
|
+
const parent = node.parentNode;
|
|
4169
|
+
if (parent) {
|
|
4170
|
+
if (parent.tagName === "w:trPr") {
|
|
4171
|
+
parent.removeChild(node);
|
|
4172
|
+
} else {
|
|
4173
|
+
while (node.firstChild) {
|
|
4174
|
+
parent.insertBefore(node.firstChild, node);
|
|
4175
|
+
}
|
|
4176
|
+
parent.removeChild(node);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
mapper_dirty = true;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4026
4185
|
}
|
|
4186
|
+
}
|
|
4187
|
+
if (mapper_dirty) {
|
|
4188
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4189
|
+
this.clean_mapper = null;
|
|
4190
|
+
}
|
|
4191
|
+
if (!dry_run_mode) {
|
|
4192
|
+
const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
4193
|
+
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
4194
|
+
const edit_errors = validate_edits_now ? this.validate_edits(edits) : [];
|
|
4195
|
+
const all_errors = [
|
|
4196
|
+
...action_errors,
|
|
4197
|
+
...edit_errors
|
|
4198
|
+
];
|
|
4027
4199
|
if (all_errors.length > 0) {
|
|
4028
4200
|
throw new BatchValidationError(all_errors);
|
|
4029
4201
|
}
|
|
@@ -4057,8 +4229,9 @@ var RedlineEngine = class {
|
|
|
4057
4229
|
let skipped_edits = 0;
|
|
4058
4230
|
if (edits.length > 0) {
|
|
4059
4231
|
if (dry_run_mode) {
|
|
4060
|
-
for (
|
|
4061
|
-
const
|
|
4232
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4233
|
+
const edit = edits[i];
|
|
4234
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4062
4235
|
const warning = this._check_punctuation_warning(
|
|
4063
4236
|
edit.target_text || ""
|
|
4064
4237
|
);
|
|
@@ -4092,6 +4265,8 @@ var RedlineEngine = class {
|
|
|
4092
4265
|
occurrences_modified: edit._occurrences_modified || 0,
|
|
4093
4266
|
match_mode: edit.match_mode || "strict"
|
|
4094
4267
|
});
|
|
4268
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4269
|
+
this.clean_mapper = null;
|
|
4095
4270
|
} else {
|
|
4096
4271
|
skipped_edits++;
|
|
4097
4272
|
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
@@ -4107,15 +4282,37 @@ var RedlineEngine = class {
|
|
|
4107
4282
|
}
|
|
4108
4283
|
}
|
|
4109
4284
|
} else {
|
|
4110
|
-
const
|
|
4111
|
-
|
|
4112
|
-
|
|
4285
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4286
|
+
const originalCurrentId = this.current_id;
|
|
4287
|
+
try {
|
|
4288
|
+
const sequential_errors = [];
|
|
4289
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4290
|
+
const edit = edits[i];
|
|
4291
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4292
|
+
if (single_errors.length > 0) {
|
|
4293
|
+
sequential_errors.push(...single_errors);
|
|
4294
|
+
} else {
|
|
4295
|
+
this.apply_edits([edit], page_offsets);
|
|
4296
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4297
|
+
this.clean_mapper = null;
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
if (sequential_errors.length > 0) {
|
|
4301
|
+
throw new BatchValidationError(sequential_errors);
|
|
4302
|
+
}
|
|
4303
|
+
} catch (err) {
|
|
4304
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4305
|
+
this.current_id = originalCurrentId;
|
|
4306
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4307
|
+
this.comments_manager = new CommentsManager(this.doc);
|
|
4308
|
+
this.clean_mapper = null;
|
|
4309
|
+
throw err;
|
|
4113
4310
|
}
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
skipped_edits =
|
|
4118
|
-
for (const edit of
|
|
4311
|
+
applied_edits = edits.filter(
|
|
4312
|
+
(e) => e._applied_status
|
|
4313
|
+
).length;
|
|
4314
|
+
skipped_edits = edits.length - applied_edits;
|
|
4315
|
+
for (const edit of edits) {
|
|
4119
4316
|
const success = edit._applied_status || false;
|
|
4120
4317
|
const error_msg = edit._error_msg || null;
|
|
4121
4318
|
const warning = this._check_punctuation_warning(
|
|
@@ -4269,14 +4466,22 @@ var RedlineEngine = class {
|
|
|
4269
4466
|
if (parent) {
|
|
4270
4467
|
parent._applied_status = true;
|
|
4271
4468
|
parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
|
|
4272
|
-
const [path, page] = this._get_heading_path_and_page(
|
|
4469
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4470
|
+
start,
|
|
4471
|
+
this.mapper.full_text,
|
|
4472
|
+
page_offsets
|
|
4473
|
+
);
|
|
4273
4474
|
const pages = parent._pages || [];
|
|
4274
4475
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4275
4476
|
parent._pages = pages;
|
|
4276
4477
|
parent._heading_path = path;
|
|
4277
4478
|
} else {
|
|
4278
4479
|
edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
|
|
4279
|
-
const [path, page] = this._get_heading_path_and_page(
|
|
4480
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4481
|
+
start,
|
|
4482
|
+
this.mapper.full_text,
|
|
4483
|
+
page_offsets
|
|
4484
|
+
);
|
|
4280
4485
|
const pages = edit._pages || [];
|
|
4281
4486
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4282
4487
|
edit._pages = pages;
|
|
@@ -4437,12 +4642,18 @@ var RedlineEngine = class {
|
|
|
4437
4642
|
if (!edit.target_text) return null;
|
|
4438
4643
|
const is_regex = edit.regex || false;
|
|
4439
4644
|
const match_mode = edit.match_mode || "strict";
|
|
4440
|
-
let matches = this.mapper.find_all_match_indices(
|
|
4645
|
+
let matches = this.mapper.find_all_match_indices(
|
|
4646
|
+
edit.target_text,
|
|
4647
|
+
is_regex
|
|
4648
|
+
);
|
|
4441
4649
|
let use_clean_map = false;
|
|
4442
4650
|
if (matches.length === 0) {
|
|
4443
4651
|
if (!this.clean_mapper)
|
|
4444
4652
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4445
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
4653
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
4654
|
+
edit.target_text,
|
|
4655
|
+
is_regex
|
|
4656
|
+
);
|
|
4446
4657
|
if (matches.length > 0) use_clean_map = true;
|
|
4447
4658
|
else return null;
|
|
4448
4659
|
}
|
|
@@ -4467,14 +4678,49 @@ var RedlineEngine = class {
|
|
|
4467
4678
|
start_idx + match_len
|
|
4468
4679
|
);
|
|
4469
4680
|
let current_effective_new_text = edit.new_text || "";
|
|
4681
|
+
if (/^\{#cell:[^}]+\}$/.test(actual_doc_text.trim())) {
|
|
4682
|
+
let ins_text = current_effective_new_text;
|
|
4683
|
+
ins_text = ins_text.split(actual_doc_text.trim()).join("");
|
|
4684
|
+
if (ins_text) {
|
|
4685
|
+
all_sub_edits.push({
|
|
4686
|
+
type: "modify",
|
|
4687
|
+
target_text: "",
|
|
4688
|
+
new_text: ins_text,
|
|
4689
|
+
comment: edit.comment,
|
|
4690
|
+
// Insert at the anchor token's start so the new run lands inside
|
|
4691
|
+
// the cell paragraph that get_insertion_anchor resolves there.
|
|
4692
|
+
_match_start_index: start_idx,
|
|
4693
|
+
_internal_op: "INSERTION",
|
|
4694
|
+
_active_mapper_ref: active_mapper
|
|
4695
|
+
});
|
|
4696
|
+
} else if (edit.comment) {
|
|
4697
|
+
all_sub_edits.push({
|
|
4698
|
+
type: "modify",
|
|
4699
|
+
target_text: "",
|
|
4700
|
+
new_text: "",
|
|
4701
|
+
comment: edit.comment,
|
|
4702
|
+
_match_start_index: start_idx,
|
|
4703
|
+
_internal_op: "COMMENT_ONLY",
|
|
4704
|
+
_active_mapper_ref: active_mapper
|
|
4705
|
+
});
|
|
4706
|
+
}
|
|
4707
|
+
continue;
|
|
4708
|
+
}
|
|
4470
4709
|
if (is_regex && current_effective_new_text) {
|
|
4471
4710
|
try {
|
|
4472
|
-
current_effective_new_text = actual_doc_text.replace(
|
|
4711
|
+
current_effective_new_text = actual_doc_text.replace(
|
|
4712
|
+
new RegExp(edit.target_text),
|
|
4713
|
+
current_effective_new_text
|
|
4714
|
+
);
|
|
4473
4715
|
} catch (e) {
|
|
4474
4716
|
}
|
|
4475
4717
|
}
|
|
4476
|
-
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4477
|
-
|
|
4718
|
+
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4719
|
+
edit.target_text
|
|
4720
|
+
);
|
|
4721
|
+
const [edit_new_clean, edit_new_style] = this._parse_markdown_style(
|
|
4722
|
+
current_effective_new_text
|
|
4723
|
+
);
|
|
4478
4724
|
if (edit_target_style !== edit_new_style) {
|
|
4479
4725
|
const [actual_clean] = this._parse_markdown_style(actual_doc_text);
|
|
4480
4726
|
const final_target2 = actual_clean;
|
|
@@ -4513,7 +4759,9 @@ var RedlineEngine = class {
|
|
|
4513
4759
|
let effective_start_idx = start_idx;
|
|
4514
4760
|
if (current_effective_new_text.startsWith(actual_doc_text)) {
|
|
4515
4761
|
effective_op = "INSERTION";
|
|
4516
|
-
final_new = current_effective_new_text.substring(
|
|
4762
|
+
final_new = current_effective_new_text.substring(
|
|
4763
|
+
actual_doc_text.length
|
|
4764
|
+
);
|
|
4517
4765
|
effective_start_idx = start_idx + match_len;
|
|
4518
4766
|
} else {
|
|
4519
4767
|
const [prefix_len, suffix_len] = trim_common_context(
|
|
@@ -4594,7 +4842,12 @@ var RedlineEngine = class {
|
|
|
4594
4842
|
const first_anchor = ascend_to_paragraph_child(first_el, start_p);
|
|
4595
4843
|
const last_anchor = ascend_to_paragraph_child(last_el, end_p);
|
|
4596
4844
|
if (start_p === end_p) {
|
|
4597
|
-
this._attach_comment(
|
|
4845
|
+
this._attach_comment(
|
|
4846
|
+
start_p,
|
|
4847
|
+
first_anchor,
|
|
4848
|
+
last_anchor,
|
|
4849
|
+
edit.comment
|
|
4850
|
+
);
|
|
4598
4851
|
} else {
|
|
4599
4852
|
this._attach_comment_spanning(
|
|
4600
4853
|
start_p,
|
|
@@ -5883,13 +6136,21 @@ function extract_table(table, comments_map, cleanView, cursor, paragraph_offsets
|
|
|
5883
6136
|
if (seen_cells.has(cell)) continue;
|
|
5884
6137
|
seen_cells.add(cell);
|
|
5885
6138
|
if (!first_cell) cell_cursor += 3;
|
|
5886
|
-
|
|
6139
|
+
let cell_content = _extract_blocks(
|
|
5887
6140
|
cell,
|
|
5888
6141
|
comments_map,
|
|
5889
6142
|
cleanView,
|
|
5890
6143
|
cell_cursor,
|
|
5891
6144
|
paragraph_offsets
|
|
5892
6145
|
);
|
|
6146
|
+
if (!cleanView) {
|
|
6147
|
+
const firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
6148
|
+
const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
6149
|
+
if (paraId) {
|
|
6150
|
+
const anchor = `{#cell:${paraId}}`;
|
|
6151
|
+
cell_content = cell_content + anchor;
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
5893
6154
|
cell_texts.push(cell_content);
|
|
5894
6155
|
cell_cursor += cell_content.length;
|
|
5895
6156
|
first_cell = false;
|