@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.js
CHANGED
|
@@ -1333,7 +1333,19 @@ ${header}`;
|
|
|
1333
1333
|
this._add_virtual_text(" | ", current, null);
|
|
1334
1334
|
current += 3;
|
|
1335
1335
|
}
|
|
1336
|
+
const cell_start = current;
|
|
1336
1337
|
current = this._map_blocks(cell, current);
|
|
1338
|
+
if (!this.clean_view && !this.original_view) {
|
|
1339
|
+
const firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
1340
|
+
const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
1341
|
+
if (paraId && firstP) {
|
|
1342
|
+
const cellPara = new Paragraph(firstP, cell);
|
|
1343
|
+
this._add_virtual_text("", current, cellPara);
|
|
1344
|
+
const anchor = `{#cell:${paraId}}`;
|
|
1345
|
+
this._add_virtual_text(anchor, current, cellPara);
|
|
1346
|
+
current += anchor.length;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1337
1349
|
cells_processed += 1;
|
|
1338
1350
|
}
|
|
1339
1351
|
if (ins && !this.clean_view && !this.original_view) {
|
|
@@ -1710,7 +1722,7 @@ ${header}`;
|
|
|
1710
1722
|
target_text = this._strip_markdown_formatting(target_text);
|
|
1711
1723
|
target_text = this._replace_smart_quotes(target_text);
|
|
1712
1724
|
const parts = [];
|
|
1713
|
-
const token_pattern = /(\[_+\])|(\s+)|(['"])|([
|
|
1725
|
+
const token_pattern = /(\[_+\])|(\s+)|(['"])|([.,;:\/\-\[\](){}+=$?*!|#^<>\\%&@~`_])/g;
|
|
1714
1726
|
let last_idx = 0;
|
|
1715
1727
|
let match;
|
|
1716
1728
|
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -2596,7 +2608,7 @@ function _refine_match_boundaries(text, start, end) {
|
|
|
2596
2608
|
function _make_fuzzy_regex(target_text) {
|
|
2597
2609
|
target_text = _replace_smart_quotes(target_text);
|
|
2598
2610
|
const parts = [];
|
|
2599
|
-
const token_pattern = /(_+)|(\s+)|(['"])|([
|
|
2611
|
+
const token_pattern = /(_+)|(\s+)|(['"])|([.,;:\/\-\[\](){}+=$?*!|#^<>\\%&@~`])/g;
|
|
2600
2612
|
const md_noise = "[*_]*";
|
|
2601
2613
|
const structural_noise = "(?:\\s*(?:[*+\\->]|\\d+\\.)\\s+|\\s*\\n\\s*)";
|
|
2602
2614
|
const start_list_marker = "(?:[ \\t]*(?:[*+\\->]|\\d+\\.)\\s+)?";
|
|
@@ -2818,6 +2830,51 @@ function insertAfter(newNode, refNode) {
|
|
|
2818
2830
|
refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
|
|
2819
2831
|
}
|
|
2820
2832
|
}
|
|
2833
|
+
function takeSnapshot(doc) {
|
|
2834
|
+
const parts = [...doc.pkg.parts];
|
|
2835
|
+
const unzipped = { ...doc.pkg.unzipped };
|
|
2836
|
+
const rels = /* @__PURE__ */ new Map();
|
|
2837
|
+
const elements = /* @__PURE__ */ new Map();
|
|
2838
|
+
for (const part of parts) {
|
|
2839
|
+
rels.set(part, new Map(part.rels));
|
|
2840
|
+
if (part._element) {
|
|
2841
|
+
elements.set(part, part._element.cloneNode(true));
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
return { parts, unzipped, rels, elements };
|
|
2845
|
+
}
|
|
2846
|
+
function restoreSnapshot(doc, snapshot) {
|
|
2847
|
+
doc.pkg.parts = [...snapshot.parts];
|
|
2848
|
+
for (const key of Object.keys(doc.pkg.unzipped)) {
|
|
2849
|
+
delete doc.pkg.unzipped[key];
|
|
2850
|
+
}
|
|
2851
|
+
for (const [key, val] of Object.entries(snapshot.unzipped)) {
|
|
2852
|
+
doc.pkg.unzipped[key] = val;
|
|
2853
|
+
}
|
|
2854
|
+
for (const part of snapshot.parts) {
|
|
2855
|
+
part.rels = new Map(snapshot.rels.get(part));
|
|
2856
|
+
const originalEl = snapshot.elements.get(part);
|
|
2857
|
+
if (originalEl && part._element) {
|
|
2858
|
+
const xmlDoc = part._element.ownerDocument;
|
|
2859
|
+
if (xmlDoc && xmlDoc.documentElement) {
|
|
2860
|
+
xmlDoc.replaceChild(originalEl, xmlDoc.documentElement);
|
|
2861
|
+
}
|
|
2862
|
+
part._element = originalEl;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
function stripMatchingHeadingHashes(target, newText) {
|
|
2867
|
+
if (!target || !newText) return [target, newText];
|
|
2868
|
+
const targetMatch = target.match(/^(#+)\s+/);
|
|
2869
|
+
const newMatch = newText.match(/^(#+)\s+/);
|
|
2870
|
+
if (targetMatch && newMatch && targetMatch[1] === newMatch[1]) {
|
|
2871
|
+
const hashes = targetMatch[1];
|
|
2872
|
+
const targetClean = target.substring(hashes.length).trimStart();
|
|
2873
|
+
const newClean = newText.substring(hashes.length).trimStart();
|
|
2874
|
+
return [targetClean, newClean];
|
|
2875
|
+
}
|
|
2876
|
+
return [target, newText];
|
|
2877
|
+
}
|
|
2821
2878
|
var BatchValidationError = class extends Error {
|
|
2822
2879
|
errors;
|
|
2823
2880
|
constructor(errors) {
|
|
@@ -2826,7 +2883,7 @@ var BatchValidationError = class extends Error {
|
|
|
2826
2883
|
this.errors = errors;
|
|
2827
2884
|
}
|
|
2828
2885
|
};
|
|
2829
|
-
function validate_edit_strings(edits) {
|
|
2886
|
+
function validate_edit_strings(edits, index_offset = 0) {
|
|
2830
2887
|
const errors = [];
|
|
2831
2888
|
for (let i = 0; i < edits.length; i++) {
|
|
2832
2889
|
const edit = edits[i];
|
|
@@ -2834,7 +2891,7 @@ function validate_edit_strings(edits) {
|
|
|
2834
2891
|
const n_text = edit.new_text || "";
|
|
2835
2892
|
if (n_text.includes("{++") || n_text.includes("{--") || n_text.includes("{>>") || n_text.includes("{==")) {
|
|
2836
2893
|
errors.push(
|
|
2837
|
-
`- 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.`
|
|
2894
|
+
`- 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.`
|
|
2838
2895
|
);
|
|
2839
2896
|
}
|
|
2840
2897
|
if (t_text.includes("[^") || n_text.includes("[^")) {
|
|
@@ -2845,11 +2902,11 @@ function validate_edit_strings(edits) {
|
|
|
2845
2902
|
(f) => n_fns.filter((x) => x === f).length > t_fns.filter((x) => x === f).length
|
|
2846
2903
|
)) {
|
|
2847
2904
|
errors.push(
|
|
2848
|
-
`- 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.`
|
|
2905
|
+
`- 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.`
|
|
2849
2906
|
);
|
|
2850
2907
|
} else {
|
|
2851
2908
|
errors.push(
|
|
2852
|
-
`- Edit ${i + 1} Failed: Cannot delete footnote/endnote references via text replace. The marker corresponds to a structural XML element.`
|
|
2909
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot delete footnote/endnote references via text replace. The marker corresponds to a structural XML element.`
|
|
2853
2910
|
);
|
|
2854
2911
|
}
|
|
2855
2912
|
}
|
|
@@ -2860,16 +2917,16 @@ function validate_edit_strings(edits) {
|
|
|
2860
2917
|
if (t_links.length !== n_links.length) {
|
|
2861
2918
|
if (n_links.length > t_links.length) {
|
|
2862
2919
|
errors.push(
|
|
2863
|
-
`- Edit ${i + 1} Failed: Cannot insert hyperlinks via text replace. Use a dedicated structural operation.`
|
|
2920
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot insert hyperlinks via text replace. Use a dedicated structural operation.`
|
|
2864
2921
|
);
|
|
2865
2922
|
} else {
|
|
2866
2923
|
errors.push(
|
|
2867
|
-
`- Edit ${i + 1} Failed: Cannot delete hyperlinks via text replace. The marker corresponds to a structural XML element.`
|
|
2924
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot delete hyperlinks via text replace. The marker corresponds to a structural XML element.`
|
|
2868
2925
|
);
|
|
2869
2926
|
}
|
|
2870
2927
|
} else if (t_links.length > 1 && JSON.stringify(t_links) !== JSON.stringify(n_links)) {
|
|
2871
2928
|
errors.push(
|
|
2872
|
-
`- Edit ${i + 1} Failed: Can only edit or retarget one hyperlink per text replacement. Please split into multiple edits.`
|
|
2929
|
+
`- Edit ${i + 1 + index_offset} Failed: Can only edit or retarget one hyperlink per text replacement. Please split into multiple edits.`
|
|
2873
2930
|
);
|
|
2874
2931
|
}
|
|
2875
2932
|
}
|
|
@@ -2879,17 +2936,17 @@ function validate_edit_strings(edits) {
|
|
|
2879
2936
|
if (t_xrefs.length !== n_xrefs.length) {
|
|
2880
2937
|
if (n_xrefs.length > t_xrefs.length) {
|
|
2881
2938
|
errors.push(
|
|
2882
|
-
`- Edit ${i + 1} Failed: Cannot insert cross-references via text replace. Markers are read-only projections.`
|
|
2939
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot insert cross-references via text replace. Markers are read-only projections.`
|
|
2883
2940
|
);
|
|
2884
2941
|
} else {
|
|
2885
2942
|
errors.push(
|
|
2886
|
-
`- Edit ${i + 1} Failed: Cannot delete cross-references via text replace. The marker corresponds to a structural XML element.`
|
|
2943
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot delete cross-references via text replace. The marker corresponds to a structural XML element.`
|
|
2887
2944
|
);
|
|
2888
2945
|
}
|
|
2889
2946
|
} else {
|
|
2890
2947
|
if (JSON.stringify(t_xrefs) !== JSON.stringify(n_xrefs)) {
|
|
2891
2948
|
errors.push(
|
|
2892
|
-
`- Edit ${i + 1} Failed: Modifying or retargeting cross-reference markers is disallowed to prevent dependency corruption.`
|
|
2949
|
+
`- Edit ${i + 1 + index_offset} Failed: Modifying or retargeting cross-reference markers is disallowed to prevent dependency corruption.`
|
|
2893
2950
|
);
|
|
2894
2951
|
}
|
|
2895
2952
|
}
|
|
@@ -2900,7 +2957,7 @@ function validate_edit_strings(edits) {
|
|
|
2900
2957
|
for (const a of n_anchors) {
|
|
2901
2958
|
if (n_anchors.filter((x) => x === a).length > t_anchors.filter((x) => x === a).length) {
|
|
2902
2959
|
errors.push(
|
|
2903
|
-
`- Edit ${i + 1} Failed: Cannot modify or insert internal anchor markers (\`{#...}\`). These represent structural XML bookmarks.`
|
|
2960
|
+
`- Edit ${i + 1 + index_offset} Failed: Cannot modify or insert internal anchor markers (\`{#...}\`). These represent structural XML bookmarks.`
|
|
2904
2961
|
);
|
|
2905
2962
|
break;
|
|
2906
2963
|
}
|
|
@@ -2914,16 +2971,16 @@ function validate_edit_strings(edits) {
|
|
|
2914
2971
|
const level = stripped.length - stripped.replace(/^#+/, "").length;
|
|
2915
2972
|
if (stripped.substring(level).startsWith(" ") || stripped.substring(level) === "") {
|
|
2916
2973
|
errors.push(
|
|
2917
|
-
`- Edit ${i + 1} Failed: Heading level ${level} is not supported (maximum is 6).`
|
|
2974
|
+
`- Edit ${i + 1 + index_offset} Failed: Heading level ${level} is not supported (maximum is 6).`
|
|
2918
2975
|
);
|
|
2919
2976
|
break;
|
|
2920
2977
|
}
|
|
2921
2978
|
}
|
|
2922
2979
|
}
|
|
2923
2980
|
}
|
|
2924
|
-
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)")) {
|
|
2981
|
+
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)")) {
|
|
2925
2982
|
errors.push(
|
|
2926
|
-
`- Edit ${i + 1} Failed: Modification targets the read-only boundary (Structural Appendix). This section cannot be edited.`
|
|
2983
|
+
`- Edit ${i + 1 + index_offset} Failed: Modification targets the read-only boundary (Structural Appendix). This section cannot be edited.`
|
|
2927
2984
|
);
|
|
2928
2985
|
}
|
|
2929
2986
|
}
|
|
@@ -2945,7 +3002,7 @@ var RedlineEngine = class {
|
|
|
2945
3002
|
this.timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
2946
3003
|
const w16du_ns = "http://schemas.microsoft.com/office/word/2023/wordml/word16du";
|
|
2947
3004
|
for (const part of this.doc.pkg.parts) {
|
|
2948
|
-
if (part === this.doc.part
|
|
3005
|
+
if (part === this.doc.part) {
|
|
2949
3006
|
if (!part._element.hasAttribute("xmlns:w16du")) {
|
|
2950
3007
|
part._element.setAttribute("xmlns:w16du", w16du_ns);
|
|
2951
3008
|
}
|
|
@@ -2962,15 +3019,59 @@ var RedlineEngine = class {
|
|
|
2962
3019
|
}
|
|
2963
3020
|
return null;
|
|
2964
3021
|
}
|
|
3022
|
+
/**
|
|
3023
|
+
* Best-effort "did you mean" hint for a failed target. The common loop trap
|
|
3024
|
+
* (observed in the field) is an anchored regex like `^\( x \)$` against a
|
|
3025
|
+
* mid-document string: ^/$ bind to the whole full_text, so it never matches
|
|
3026
|
+
* even though the literal `( x )` is present. We strip regex anchoring/escapes
|
|
3027
|
+
* and probe full_text for a literal occurrence; if found, we tell the model
|
|
3028
|
+
* the exact literal that WOULD match so it drops the anchors instead of
|
|
3029
|
+
* escalating the regex further.
|
|
3030
|
+
*/
|
|
3031
|
+
_nearest_match_hint(target_text, is_regex) {
|
|
3032
|
+
if (!target_text) return "";
|
|
3033
|
+
let probe = target_text;
|
|
3034
|
+
if (is_regex) {
|
|
3035
|
+
probe = probe.replace(/^\^/, "").replace(/\$$/, "");
|
|
3036
|
+
probe = probe.replace(/^\\s\*/, "").replace(/\\s\*$/, "");
|
|
3037
|
+
probe = probe.replace(/\\([.^$*+?()[\]{}|\\/])/g, "$1");
|
|
3038
|
+
}
|
|
3039
|
+
probe = probe.trim();
|
|
3040
|
+
if (!probe || probe === target_text) {
|
|
3041
|
+
if (!is_regex) return "";
|
|
3042
|
+
}
|
|
3043
|
+
const idx = this.mapper.full_text.indexOf(probe);
|
|
3044
|
+
if (idx !== -1) {
|
|
3045
|
+
const ctx_start = Math.max(0, idx - 15);
|
|
3046
|
+
const ctx_end = Math.min(
|
|
3047
|
+
this.mapper.full_text.length,
|
|
3048
|
+
idx + probe.length + 15
|
|
3049
|
+
);
|
|
3050
|
+
const ctx = this.mapper.full_text.substring(ctx_start, ctx_end).replace(/\n/g, " ");
|
|
3051
|
+
return `
|
|
3052
|
+
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.`;
|
|
3053
|
+
}
|
|
3054
|
+
return "";
|
|
3055
|
+
}
|
|
2965
3056
|
_build_edit_context_previews(edit) {
|
|
2966
3057
|
if (edit.type !== "modify") return [null, null];
|
|
2967
3058
|
if (edit._resolved_proxy_edit) {
|
|
2968
3059
|
edit = edit._resolved_proxy_edit;
|
|
2969
3060
|
}
|
|
2970
|
-
|
|
3061
|
+
let start_idx = edit._resolved_start_idx;
|
|
2971
3062
|
if (start_idx === void 0 || start_idx === null) return [null, null];
|
|
2972
|
-
|
|
2973
|
-
|
|
3063
|
+
let target_text = edit.target_text || "";
|
|
3064
|
+
let new_text = edit.new_text || "";
|
|
3065
|
+
const [clean_target, target_style] = this._parse_markdown_style(target_text);
|
|
3066
|
+
if (target_style && target_style.startsWith("Heading")) {
|
|
3067
|
+
const prefix_len = target_text.length - clean_target.length;
|
|
3068
|
+
start_idx += prefix_len;
|
|
3069
|
+
target_text = clean_target;
|
|
3070
|
+
}
|
|
3071
|
+
const [clean_new, new_style] = this._parse_markdown_style(new_text);
|
|
3072
|
+
if (new_style && new_style.startsWith("Heading")) {
|
|
3073
|
+
new_text = clean_new;
|
|
3074
|
+
}
|
|
2974
3075
|
const length = target_text.length;
|
|
2975
3076
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
2976
3077
|
const full_text = active_mapper.full_text;
|
|
@@ -3722,28 +3823,34 @@ var RedlineEngine = class {
|
|
|
3722
3823
|
}
|
|
3723
3824
|
}
|
|
3724
3825
|
}
|
|
3725
|
-
validate_edits(edits) {
|
|
3826
|
+
validate_edits(edits, index_offset = 0) {
|
|
3726
3827
|
const errors = [];
|
|
3727
3828
|
if (!this.mapper.full_text) this.mapper["_build_map"]();
|
|
3728
|
-
errors.push(...validate_edit_strings(edits));
|
|
3829
|
+
errors.push(...validate_edit_strings(edits, index_offset));
|
|
3729
3830
|
for (let i = 0; i < edits.length; i++) {
|
|
3730
3831
|
const edit = edits[i];
|
|
3731
3832
|
if (typeof edit !== "object" || edit === null) {
|
|
3732
3833
|
errors.push(
|
|
3733
|
-
`- Edit ${i + 1} Failed: Invalid change format. Expected a JSON object, but received a primitive ${typeof edit}. Do not pass raw strings.`
|
|
3834
|
+
`- Edit ${i + 1 + index_offset} Failed: Invalid change format. Expected a JSON object, but received a primitive ${typeof edit}. Do not pass raw strings.`
|
|
3734
3835
|
);
|
|
3735
3836
|
continue;
|
|
3736
3837
|
}
|
|
3737
3838
|
if (!edit.target_text) continue;
|
|
3738
3839
|
const is_regex = edit.regex || false;
|
|
3739
3840
|
const match_mode = edit.match_mode || "strict";
|
|
3740
|
-
let matches = this.mapper.find_all_match_indices(
|
|
3841
|
+
let matches = this.mapper.find_all_match_indices(
|
|
3842
|
+
edit.target_text,
|
|
3843
|
+
is_regex
|
|
3844
|
+
);
|
|
3741
3845
|
let activeText = this.mapper.full_text;
|
|
3742
3846
|
let target_mapper = this.mapper;
|
|
3743
3847
|
if (matches.length === 0) {
|
|
3744
3848
|
if (!this.clean_mapper)
|
|
3745
3849
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
3746
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
3850
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
3851
|
+
edit.target_text,
|
|
3852
|
+
is_regex
|
|
3853
|
+
);
|
|
3747
3854
|
if (matches.length > 0) {
|
|
3748
3855
|
activeText = this.clean_mapper.full_text;
|
|
3749
3856
|
target_mapper = this.clean_mapper;
|
|
@@ -3765,7 +3872,10 @@ var RedlineEngine = class {
|
|
|
3765
3872
|
if (!this.original_mapper) {
|
|
3766
3873
|
this.original_mapper = new DocumentMapper(this.doc, false, true);
|
|
3767
3874
|
}
|
|
3768
|
-
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
3875
|
+
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
3876
|
+
edit.target_text,
|
|
3877
|
+
is_regex
|
|
3878
|
+
);
|
|
3769
3879
|
if (orig_matches.length > 0) {
|
|
3770
3880
|
is_deleted_text = true;
|
|
3771
3881
|
for (const [start, length] of orig_matches) {
|
|
@@ -3794,27 +3904,32 @@ var RedlineEngine = class {
|
|
|
3794
3904
|
if (is_deleted_text) {
|
|
3795
3905
|
const author_phrase = deleted_authors.size > 0 ? `by ${Array.from(deleted_authors).sort().join(", ")}` : "by an existing revision";
|
|
3796
3906
|
errors.push(
|
|
3797
|
-
`- 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.`
|
|
3907
|
+
`- 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.`
|
|
3798
3908
|
);
|
|
3799
3909
|
} else {
|
|
3910
|
+
const hint = this._nearest_match_hint(edit.target_text, is_regex);
|
|
3800
3911
|
errors.push(
|
|
3801
|
-
`- Edit ${i + 1} Failed: Target text not found in document:
|
|
3802
|
-
"${edit.target_text}"`
|
|
3912
|
+
`- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
|
|
3913
|
+
"${edit.target_text}"${hint}`
|
|
3803
3914
|
);
|
|
3804
3915
|
}
|
|
3805
3916
|
} else if (matches.length > 1 && match_mode === "strict") {
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3917
|
+
if (edit.target_text.includes("|")) {
|
|
3918
|
+
matches = matches.slice(0, 1);
|
|
3919
|
+
} else {
|
|
3920
|
+
const positions = matches.map(([start, length]) => [
|
|
3921
|
+
start,
|
|
3922
|
+
start + length
|
|
3923
|
+
]);
|
|
3924
|
+
errors.push(
|
|
3925
|
+
format_ambiguity_error(
|
|
3926
|
+
i + 1 + index_offset,
|
|
3927
|
+
edit.target_text,
|
|
3928
|
+
activeText,
|
|
3929
|
+
positions
|
|
3930
|
+
)
|
|
3931
|
+
);
|
|
3932
|
+
}
|
|
3818
3933
|
}
|
|
3819
3934
|
if (matches.length === 1) {
|
|
3820
3935
|
const [m_start, m_len] = matches[0];
|
|
@@ -3831,14 +3946,14 @@ var RedlineEngine = class {
|
|
|
3831
3946
|
const parts = matched.split("\n\n");
|
|
3832
3947
|
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3833
3948
|
errors.push(
|
|
3834
|
-
`- 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.`
|
|
3949
|
+
`- 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.`
|
|
3835
3950
|
);
|
|
3836
3951
|
}
|
|
3837
3952
|
} else {
|
|
3838
3953
|
const parts = final_target.split("\n\n");
|
|
3839
3954
|
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3840
3955
|
errors.push(
|
|
3841
|
-
`- 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.`
|
|
3956
|
+
`- 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.`
|
|
3842
3957
|
);
|
|
3843
3958
|
}
|
|
3844
3959
|
}
|
|
@@ -3857,7 +3972,13 @@ var RedlineEngine = class {
|
|
|
3857
3972
|
).filter((n) => n.getAttribute("w:id") === s.ins_id);
|
|
3858
3973
|
if (insNodes.length > 0) {
|
|
3859
3974
|
const auth = insNodes[0].getAttribute("w:author");
|
|
3860
|
-
if (auth && auth !== this.author)
|
|
3975
|
+
if (auth && auth !== this.author) {
|
|
3976
|
+
const is_fully_contained_in_ins = start >= s.start && start + length <= s.end;
|
|
3977
|
+
const is_lockout = is_fully_contained_in_ins || match_mode === "all";
|
|
3978
|
+
if (is_lockout) {
|
|
3979
|
+
nestedAuthors.add(auth);
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3861
3982
|
}
|
|
3862
3983
|
}
|
|
3863
3984
|
if (s.comment_ids) {
|
|
@@ -3871,7 +3992,7 @@ var RedlineEngine = class {
|
|
|
3871
3992
|
}
|
|
3872
3993
|
if (nestedAuthors.size > 0) {
|
|
3873
3994
|
errors.push(
|
|
3874
|
-
`- 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.`
|
|
3995
|
+
`- 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.`
|
|
3875
3996
|
);
|
|
3876
3997
|
}
|
|
3877
3998
|
}
|
|
@@ -3933,22 +4054,13 @@ var RedlineEngine = class {
|
|
|
3933
4054
|
});
|
|
3934
4055
|
}
|
|
3935
4056
|
if (dry_run) {
|
|
3936
|
-
const
|
|
3937
|
-
|
|
3938
|
-
if (part._element) {
|
|
3939
|
-
baselines.set(part, part._element.cloneNode(true));
|
|
3940
|
-
}
|
|
3941
|
-
}
|
|
4057
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4058
|
+
const originalCurrentId = this.current_id;
|
|
3942
4059
|
try {
|
|
3943
4060
|
return this._process_batch_internal(changes, true);
|
|
3944
4061
|
} finally {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
if (doc && doc.documentElement) {
|
|
3948
|
-
doc.replaceChild(originalEl, doc.documentElement);
|
|
3949
|
-
}
|
|
3950
|
-
part._element = originalEl;
|
|
3951
|
-
}
|
|
4062
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4063
|
+
this.current_id = originalCurrentId;
|
|
3952
4064
|
this.mapper = new DocumentMapper(this.doc);
|
|
3953
4065
|
this.comments_manager = new CommentsManager(this.doc);
|
|
3954
4066
|
this.clean_mapper = null;
|
|
@@ -3958,6 +4070,13 @@ var RedlineEngine = class {
|
|
|
3958
4070
|
}
|
|
3959
4071
|
}
|
|
3960
4072
|
_process_batch_internal(changes, dry_run_mode = false) {
|
|
4073
|
+
for (const c of changes) {
|
|
4074
|
+
if (c && typeof c === "object" && c.type === "modify" && c.target_text && c.new_text) {
|
|
4075
|
+
const [strippedTarget, strippedNew] = stripMatchingHeadingHashes(c.target_text, c.new_text);
|
|
4076
|
+
c.target_text = strippedTarget;
|
|
4077
|
+
c.new_text = strippedNew;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
3961
4080
|
this.skipped_details = [];
|
|
3962
4081
|
const actions = changes.filter(
|
|
3963
4082
|
(c) => c !== null && typeof c === "object" && ["accept", "reject", "reply"].includes(c.type)
|
|
@@ -3965,14 +4084,67 @@ var RedlineEngine = class {
|
|
|
3965
4084
|
const edits = changes.filter(
|
|
3966
4085
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
3967
4086
|
);
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
if (
|
|
3971
|
-
|
|
4087
|
+
let mapper_dirty = false;
|
|
4088
|
+
for (const edit of edits) {
|
|
4089
|
+
if (typeof edit !== "object" || edit === null || !edit.target_text) continue;
|
|
4090
|
+
const is_regex = edit.regex || false;
|
|
4091
|
+
let matches = this.mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4092
|
+
let target_mapper = this.mapper;
|
|
4093
|
+
if (matches.length === 0) {
|
|
4094
|
+
if (!this.clean_mapper) {
|
|
4095
|
+
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4096
|
+
}
|
|
4097
|
+
matches = this.clean_mapper.find_all_match_indices(edit.target_text, is_regex);
|
|
4098
|
+
target_mapper = this.clean_mapper;
|
|
3972
4099
|
}
|
|
3973
|
-
|
|
3974
|
-
|
|
4100
|
+
for (const [start, length] of matches) {
|
|
4101
|
+
const spans = target_mapper.spans.filter(
|
|
4102
|
+
(s) => s.end > start && s.start < start + length
|
|
4103
|
+
);
|
|
4104
|
+
for (const s of spans) {
|
|
4105
|
+
if (s.ins_id) {
|
|
4106
|
+
const insNodes = findAllDescendants(this.doc.element, "w:ins").filter(
|
|
4107
|
+
(n) => n.getAttribute("w:id") === s.ins_id
|
|
4108
|
+
);
|
|
4109
|
+
if (insNodes.length > 0) {
|
|
4110
|
+
const auth = insNodes[0].getAttribute("w:author");
|
|
4111
|
+
if (auth && auth !== this.author) {
|
|
4112
|
+
const is_fully_contained_in_ins = start >= s.start && start + length <= s.end;
|
|
4113
|
+
const match_mode = edit.match_mode || "strict";
|
|
4114
|
+
if (!is_fully_contained_in_ins && match_mode !== "all") {
|
|
4115
|
+
const node = insNodes[0];
|
|
4116
|
+
this._clean_wrapping_comments(node);
|
|
4117
|
+
const parent = node.parentNode;
|
|
4118
|
+
if (parent) {
|
|
4119
|
+
if (parent.tagName === "w:trPr") {
|
|
4120
|
+
parent.removeChild(node);
|
|
4121
|
+
} else {
|
|
4122
|
+
while (node.firstChild) {
|
|
4123
|
+
parent.insertBefore(node.firstChild, node);
|
|
4124
|
+
}
|
|
4125
|
+
parent.removeChild(node);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
mapper_dirty = true;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
3975
4134
|
}
|
|
4135
|
+
}
|
|
4136
|
+
if (mapper_dirty) {
|
|
4137
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4138
|
+
this.clean_mapper = null;
|
|
4139
|
+
}
|
|
4140
|
+
if (!dry_run_mode) {
|
|
4141
|
+
const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
4142
|
+
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
4143
|
+
const edit_errors = validate_edits_now ? this.validate_edits(edits) : [];
|
|
4144
|
+
const all_errors = [
|
|
4145
|
+
...action_errors,
|
|
4146
|
+
...edit_errors
|
|
4147
|
+
];
|
|
3976
4148
|
if (all_errors.length > 0) {
|
|
3977
4149
|
throw new BatchValidationError(all_errors);
|
|
3978
4150
|
}
|
|
@@ -4006,8 +4178,9 @@ var RedlineEngine = class {
|
|
|
4006
4178
|
let skipped_edits = 0;
|
|
4007
4179
|
if (edits.length > 0) {
|
|
4008
4180
|
if (dry_run_mode) {
|
|
4009
|
-
for (
|
|
4010
|
-
const
|
|
4181
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4182
|
+
const edit = edits[i];
|
|
4183
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4011
4184
|
const warning = this._check_punctuation_warning(
|
|
4012
4185
|
edit.target_text || ""
|
|
4013
4186
|
);
|
|
@@ -4041,6 +4214,8 @@ var RedlineEngine = class {
|
|
|
4041
4214
|
occurrences_modified: edit._occurrences_modified || 0,
|
|
4042
4215
|
match_mode: edit.match_mode || "strict"
|
|
4043
4216
|
});
|
|
4217
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4218
|
+
this.clean_mapper = null;
|
|
4044
4219
|
} else {
|
|
4045
4220
|
skipped_edits++;
|
|
4046
4221
|
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
@@ -4056,15 +4231,37 @@ var RedlineEngine = class {
|
|
|
4056
4231
|
}
|
|
4057
4232
|
}
|
|
4058
4233
|
} else {
|
|
4059
|
-
const
|
|
4060
|
-
|
|
4061
|
-
|
|
4234
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4235
|
+
const originalCurrentId = this.current_id;
|
|
4236
|
+
try {
|
|
4237
|
+
const sequential_errors = [];
|
|
4238
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4239
|
+
const edit = edits[i];
|
|
4240
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4241
|
+
if (single_errors.length > 0) {
|
|
4242
|
+
sequential_errors.push(...single_errors);
|
|
4243
|
+
} else {
|
|
4244
|
+
this.apply_edits([edit], page_offsets);
|
|
4245
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4246
|
+
this.clean_mapper = null;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
if (sequential_errors.length > 0) {
|
|
4250
|
+
throw new BatchValidationError(sequential_errors);
|
|
4251
|
+
}
|
|
4252
|
+
} catch (err) {
|
|
4253
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4254
|
+
this.current_id = originalCurrentId;
|
|
4255
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4256
|
+
this.comments_manager = new CommentsManager(this.doc);
|
|
4257
|
+
this.clean_mapper = null;
|
|
4258
|
+
throw err;
|
|
4062
4259
|
}
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
skipped_edits =
|
|
4067
|
-
for (const edit of
|
|
4260
|
+
applied_edits = edits.filter(
|
|
4261
|
+
(e) => e._applied_status
|
|
4262
|
+
).length;
|
|
4263
|
+
skipped_edits = edits.length - applied_edits;
|
|
4264
|
+
for (const edit of edits) {
|
|
4068
4265
|
const success = edit._applied_status || false;
|
|
4069
4266
|
const error_msg = edit._error_msg || null;
|
|
4070
4267
|
const warning = this._check_punctuation_warning(
|
|
@@ -4218,14 +4415,22 @@ var RedlineEngine = class {
|
|
|
4218
4415
|
if (parent) {
|
|
4219
4416
|
parent._applied_status = true;
|
|
4220
4417
|
parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
|
|
4221
|
-
const [path, page] = this._get_heading_path_and_page(
|
|
4418
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4419
|
+
start,
|
|
4420
|
+
this.mapper.full_text,
|
|
4421
|
+
page_offsets
|
|
4422
|
+
);
|
|
4222
4423
|
const pages = parent._pages || [];
|
|
4223
4424
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4224
4425
|
parent._pages = pages;
|
|
4225
4426
|
parent._heading_path = path;
|
|
4226
4427
|
} else {
|
|
4227
4428
|
edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
|
|
4228
|
-
const [path, page] = this._get_heading_path_and_page(
|
|
4429
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4430
|
+
start,
|
|
4431
|
+
this.mapper.full_text,
|
|
4432
|
+
page_offsets
|
|
4433
|
+
);
|
|
4229
4434
|
const pages = edit._pages || [];
|
|
4230
4435
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4231
4436
|
edit._pages = pages;
|
|
@@ -4386,12 +4591,18 @@ var RedlineEngine = class {
|
|
|
4386
4591
|
if (!edit.target_text) return null;
|
|
4387
4592
|
const is_regex = edit.regex || false;
|
|
4388
4593
|
const match_mode = edit.match_mode || "strict";
|
|
4389
|
-
let matches = this.mapper.find_all_match_indices(
|
|
4594
|
+
let matches = this.mapper.find_all_match_indices(
|
|
4595
|
+
edit.target_text,
|
|
4596
|
+
is_regex
|
|
4597
|
+
);
|
|
4390
4598
|
let use_clean_map = false;
|
|
4391
4599
|
if (matches.length === 0) {
|
|
4392
4600
|
if (!this.clean_mapper)
|
|
4393
4601
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4394
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
4602
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
4603
|
+
edit.target_text,
|
|
4604
|
+
is_regex
|
|
4605
|
+
);
|
|
4395
4606
|
if (matches.length > 0) use_clean_map = true;
|
|
4396
4607
|
else return null;
|
|
4397
4608
|
}
|
|
@@ -4416,14 +4627,49 @@ var RedlineEngine = class {
|
|
|
4416
4627
|
start_idx + match_len
|
|
4417
4628
|
);
|
|
4418
4629
|
let current_effective_new_text = edit.new_text || "";
|
|
4630
|
+
if (/^\{#cell:[^}]+\}$/.test(actual_doc_text.trim())) {
|
|
4631
|
+
let ins_text = current_effective_new_text;
|
|
4632
|
+
ins_text = ins_text.split(actual_doc_text.trim()).join("");
|
|
4633
|
+
if (ins_text) {
|
|
4634
|
+
all_sub_edits.push({
|
|
4635
|
+
type: "modify",
|
|
4636
|
+
target_text: "",
|
|
4637
|
+
new_text: ins_text,
|
|
4638
|
+
comment: edit.comment,
|
|
4639
|
+
// Insert at the anchor token's start so the new run lands inside
|
|
4640
|
+
// the cell paragraph that get_insertion_anchor resolves there.
|
|
4641
|
+
_match_start_index: start_idx,
|
|
4642
|
+
_internal_op: "INSERTION",
|
|
4643
|
+
_active_mapper_ref: active_mapper
|
|
4644
|
+
});
|
|
4645
|
+
} else if (edit.comment) {
|
|
4646
|
+
all_sub_edits.push({
|
|
4647
|
+
type: "modify",
|
|
4648
|
+
target_text: "",
|
|
4649
|
+
new_text: "",
|
|
4650
|
+
comment: edit.comment,
|
|
4651
|
+
_match_start_index: start_idx,
|
|
4652
|
+
_internal_op: "COMMENT_ONLY",
|
|
4653
|
+
_active_mapper_ref: active_mapper
|
|
4654
|
+
});
|
|
4655
|
+
}
|
|
4656
|
+
continue;
|
|
4657
|
+
}
|
|
4419
4658
|
if (is_regex && current_effective_new_text) {
|
|
4420
4659
|
try {
|
|
4421
|
-
current_effective_new_text = actual_doc_text.replace(
|
|
4660
|
+
current_effective_new_text = actual_doc_text.replace(
|
|
4661
|
+
new RegExp(edit.target_text),
|
|
4662
|
+
current_effective_new_text
|
|
4663
|
+
);
|
|
4422
4664
|
} catch (e) {
|
|
4423
4665
|
}
|
|
4424
4666
|
}
|
|
4425
|
-
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4426
|
-
|
|
4667
|
+
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4668
|
+
edit.target_text
|
|
4669
|
+
);
|
|
4670
|
+
const [edit_new_clean, edit_new_style] = this._parse_markdown_style(
|
|
4671
|
+
current_effective_new_text
|
|
4672
|
+
);
|
|
4427
4673
|
if (edit_target_style !== edit_new_style) {
|
|
4428
4674
|
const [actual_clean] = this._parse_markdown_style(actual_doc_text);
|
|
4429
4675
|
const final_target2 = actual_clean;
|
|
@@ -4462,7 +4708,9 @@ var RedlineEngine = class {
|
|
|
4462
4708
|
let effective_start_idx = start_idx;
|
|
4463
4709
|
if (current_effective_new_text.startsWith(actual_doc_text)) {
|
|
4464
4710
|
effective_op = "INSERTION";
|
|
4465
|
-
final_new = current_effective_new_text.substring(
|
|
4711
|
+
final_new = current_effective_new_text.substring(
|
|
4712
|
+
actual_doc_text.length
|
|
4713
|
+
);
|
|
4466
4714
|
effective_start_idx = start_idx + match_len;
|
|
4467
4715
|
} else {
|
|
4468
4716
|
const [prefix_len, suffix_len] = trim_common_context(
|
|
@@ -4543,7 +4791,12 @@ var RedlineEngine = class {
|
|
|
4543
4791
|
const first_anchor = ascend_to_paragraph_child(first_el, start_p);
|
|
4544
4792
|
const last_anchor = ascend_to_paragraph_child(last_el, end_p);
|
|
4545
4793
|
if (start_p === end_p) {
|
|
4546
|
-
this._attach_comment(
|
|
4794
|
+
this._attach_comment(
|
|
4795
|
+
start_p,
|
|
4796
|
+
first_anchor,
|
|
4797
|
+
last_anchor,
|
|
4798
|
+
edit.comment
|
|
4799
|
+
);
|
|
4547
4800
|
} else {
|
|
4548
4801
|
this._attach_comment_spanning(
|
|
4549
4802
|
start_p,
|
|
@@ -5832,13 +6085,21 @@ function extract_table(table, comments_map, cleanView, cursor, paragraph_offsets
|
|
|
5832
6085
|
if (seen_cells.has(cell)) continue;
|
|
5833
6086
|
seen_cells.add(cell);
|
|
5834
6087
|
if (!first_cell) cell_cursor += 3;
|
|
5835
|
-
|
|
6088
|
+
let cell_content = _extract_blocks(
|
|
5836
6089
|
cell,
|
|
5837
6090
|
comments_map,
|
|
5838
6091
|
cleanView,
|
|
5839
6092
|
cell_cursor,
|
|
5840
6093
|
paragraph_offsets
|
|
5841
6094
|
);
|
|
6095
|
+
if (!cleanView) {
|
|
6096
|
+
const firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
6097
|
+
const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
6098
|
+
if (paraId) {
|
|
6099
|
+
const anchor = `{#cell:${paraId}}`;
|
|
6100
|
+
cell_content = cell_content + anchor;
|
|
6101
|
+
}
|
|
6102
|
+
}
|
|
5842
6103
|
cell_texts.push(cell_content);
|
|
5843
6104
|
cell_cursor += cell_content.length;
|
|
5844
6105
|
first_cell = false;
|