@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.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;
|
|
@@ -3194,6 +3295,93 @@ var RedlineEngine = class {
|
|
|
3194
3295
|
}
|
|
3195
3296
|
}
|
|
3196
3297
|
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Revert every tracked change, returning the document to the state it had
|
|
3300
|
+
* before any revision was proposed. The exact inverse of
|
|
3301
|
+
* accept_all_revisions:
|
|
3302
|
+
*
|
|
3303
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
3304
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
3305
|
+
* <w:trPr>) drops the whole row.
|
|
3306
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
3307
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
3308
|
+
* the row survives.
|
|
3309
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
3310
|
+
*
|
|
3311
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
3312
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
3313
|
+
*
|
|
3314
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
3315
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
3316
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
3317
|
+
* body text.
|
|
3318
|
+
*
|
|
3319
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
3320
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
3321
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
3322
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
3323
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
3324
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
3325
|
+
*/
|
|
3326
|
+
reject_all_revisions() {
|
|
3327
|
+
const parts_to_process = [this.doc.element];
|
|
3328
|
+
for (const part of this.doc.pkg.parts) {
|
|
3329
|
+
if (part === this.doc.part) continue;
|
|
3330
|
+
if (part.contentType.includes("wordprocessingml") && part.contentType.endsWith("+xml")) {
|
|
3331
|
+
parts_to_process.push(part._element);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
for (const root_element of parts_to_process) {
|
|
3335
|
+
const insNodes = findAllDescendants(root_element, "w:ins");
|
|
3336
|
+
for (const ins of insNodes) {
|
|
3337
|
+
const parent = ins.parentNode;
|
|
3338
|
+
if (!parent) continue;
|
|
3339
|
+
this._clean_wrapping_comments(ins);
|
|
3340
|
+
this._delete_comments_in_element(ins);
|
|
3341
|
+
if (parent.tagName === "w:trPr") {
|
|
3342
|
+
const row = parent.parentNode;
|
|
3343
|
+
if (row && row.parentNode) {
|
|
3344
|
+
row.parentNode.removeChild(row);
|
|
3345
|
+
}
|
|
3346
|
+
} else {
|
|
3347
|
+
parent.removeChild(ins);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
const pNodes = findAllDescendants(root_element, "w:p");
|
|
3351
|
+
for (const p of pNodes) {
|
|
3352
|
+
const pPr = findChild(p, "w:pPr");
|
|
3353
|
+
if (pPr) {
|
|
3354
|
+
const rPr = findChild(pPr, "w:rPr");
|
|
3355
|
+
const delMark = rPr ? findChild(rPr, "w:del") : null;
|
|
3356
|
+
if (rPr && delMark) {
|
|
3357
|
+
rPr.removeChild(delMark);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
const delNodes = findAllDescendants(root_element, "w:del");
|
|
3362
|
+
for (const d of delNodes) {
|
|
3363
|
+
const parent = d.parentNode;
|
|
3364
|
+
if (!parent) continue;
|
|
3365
|
+
this._clean_wrapping_comments(d);
|
|
3366
|
+
if (parent.tagName === "w:trPr") {
|
|
3367
|
+
parent.removeChild(d);
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
const delTexts = Array.from(d.getElementsByTagName("w:delText"));
|
|
3371
|
+
for (const dt of delTexts) {
|
|
3372
|
+
const t = d.ownerDocument.createElement("w:t");
|
|
3373
|
+
t.textContent = dt.textContent;
|
|
3374
|
+
if (dt.hasAttribute("xml:space"))
|
|
3375
|
+
t.setAttribute("xml:space", "preserve");
|
|
3376
|
+
dt.parentNode?.replaceChild(t, dt);
|
|
3377
|
+
}
|
|
3378
|
+
while (d.firstChild) {
|
|
3379
|
+
parent.insertBefore(d.firstChild, d);
|
|
3380
|
+
}
|
|
3381
|
+
parent.removeChild(d);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3197
3385
|
_getNextId() {
|
|
3198
3386
|
this.current_id++;
|
|
3199
3387
|
return this.current_id.toString();
|
|
@@ -3722,28 +3910,34 @@ var RedlineEngine = class {
|
|
|
3722
3910
|
}
|
|
3723
3911
|
}
|
|
3724
3912
|
}
|
|
3725
|
-
validate_edits(edits) {
|
|
3913
|
+
validate_edits(edits, index_offset = 0) {
|
|
3726
3914
|
const errors = [];
|
|
3727
3915
|
if (!this.mapper.full_text) this.mapper["_build_map"]();
|
|
3728
|
-
errors.push(...validate_edit_strings(edits));
|
|
3916
|
+
errors.push(...validate_edit_strings(edits, index_offset));
|
|
3729
3917
|
for (let i = 0; i < edits.length; i++) {
|
|
3730
3918
|
const edit = edits[i];
|
|
3731
3919
|
if (typeof edit !== "object" || edit === null) {
|
|
3732
3920
|
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.`
|
|
3921
|
+
`- 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
3922
|
);
|
|
3735
3923
|
continue;
|
|
3736
3924
|
}
|
|
3737
3925
|
if (!edit.target_text) continue;
|
|
3738
3926
|
const is_regex = edit.regex || false;
|
|
3739
3927
|
const match_mode = edit.match_mode || "strict";
|
|
3740
|
-
let matches = this.mapper.find_all_match_indices(
|
|
3928
|
+
let matches = this.mapper.find_all_match_indices(
|
|
3929
|
+
edit.target_text,
|
|
3930
|
+
is_regex
|
|
3931
|
+
);
|
|
3741
3932
|
let activeText = this.mapper.full_text;
|
|
3742
3933
|
let target_mapper = this.mapper;
|
|
3743
3934
|
if (matches.length === 0) {
|
|
3744
3935
|
if (!this.clean_mapper)
|
|
3745
3936
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
3746
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
3937
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
3938
|
+
edit.target_text,
|
|
3939
|
+
is_regex
|
|
3940
|
+
);
|
|
3747
3941
|
if (matches.length > 0) {
|
|
3748
3942
|
activeText = this.clean_mapper.full_text;
|
|
3749
3943
|
target_mapper = this.clean_mapper;
|
|
@@ -3765,7 +3959,10 @@ var RedlineEngine = class {
|
|
|
3765
3959
|
if (!this.original_mapper) {
|
|
3766
3960
|
this.original_mapper = new DocumentMapper(this.doc, false, true);
|
|
3767
3961
|
}
|
|
3768
|
-
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
3962
|
+
const orig_matches = this.original_mapper.find_all_match_indices(
|
|
3963
|
+
edit.target_text,
|
|
3964
|
+
is_regex
|
|
3965
|
+
);
|
|
3769
3966
|
if (orig_matches.length > 0) {
|
|
3770
3967
|
is_deleted_text = true;
|
|
3771
3968
|
for (const [start, length] of orig_matches) {
|
|
@@ -3794,27 +3991,32 @@ var RedlineEngine = class {
|
|
|
3794
3991
|
if (is_deleted_text) {
|
|
3795
3992
|
const author_phrase = deleted_authors.size > 0 ? `by ${Array.from(deleted_authors).sort().join(", ")}` : "by an existing revision";
|
|
3796
3993
|
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.`
|
|
3994
|
+
`- 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
3995
|
);
|
|
3799
3996
|
} else {
|
|
3997
|
+
const hint = this._nearest_match_hint(edit.target_text, is_regex);
|
|
3800
3998
|
errors.push(
|
|
3801
|
-
`- Edit ${i + 1} Failed: Target text not found in document:
|
|
3802
|
-
"${edit.target_text}"`
|
|
3999
|
+
`- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
|
|
4000
|
+
"${edit.target_text}"${hint}`
|
|
3803
4001
|
);
|
|
3804
4002
|
}
|
|
3805
4003
|
} else if (matches.length > 1 && match_mode === "strict") {
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
4004
|
+
if (edit.target_text.includes("|")) {
|
|
4005
|
+
matches = matches.slice(0, 1);
|
|
4006
|
+
} else {
|
|
4007
|
+
const positions = matches.map(([start, length]) => [
|
|
4008
|
+
start,
|
|
4009
|
+
start + length
|
|
4010
|
+
]);
|
|
4011
|
+
errors.push(
|
|
4012
|
+
format_ambiguity_error(
|
|
4013
|
+
i + 1 + index_offset,
|
|
4014
|
+
edit.target_text,
|
|
4015
|
+
activeText,
|
|
4016
|
+
positions
|
|
4017
|
+
)
|
|
4018
|
+
);
|
|
4019
|
+
}
|
|
3818
4020
|
}
|
|
3819
4021
|
if (matches.length === 1) {
|
|
3820
4022
|
const [m_start, m_len] = matches[0];
|
|
@@ -3827,29 +4029,36 @@ var RedlineEngine = class {
|
|
|
3827
4029
|
(edit.new_text || "").length - sfx
|
|
3828
4030
|
);
|
|
3829
4031
|
if (final_target.includes("\n\n")) {
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
if (
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
4032
|
+
const balanced = matched.split("\n\n").length === (edit.new_text || "").split("\n\n").length;
|
|
4033
|
+
if (!balanced) {
|
|
4034
|
+
if (final_new.includes("\n\n")) {
|
|
4035
|
+
const parts = matched.split("\n\n");
|
|
4036
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4037
|
+
errors.push(
|
|
4038
|
+
`- 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.`
|
|
4039
|
+
);
|
|
4040
|
+
}
|
|
4041
|
+
} else {
|
|
4042
|
+
const parts = final_target.split("\n\n");
|
|
4043
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4044
|
+
errors.push(
|
|
4045
|
+
`- 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.`
|
|
4046
|
+
);
|
|
4047
|
+
}
|
|
3843
4048
|
}
|
|
3844
4049
|
}
|
|
3845
4050
|
}
|
|
3846
4051
|
}
|
|
3847
4052
|
for (const [start, length] of matches) {
|
|
3848
|
-
const spans =
|
|
4053
|
+
const spans = target_mapper.spans.filter(
|
|
3849
4054
|
(s) => s.end > start && s.start < start + length
|
|
3850
4055
|
);
|
|
3851
|
-
const
|
|
4056
|
+
const insAuthors = /* @__PURE__ */ new Set();
|
|
4057
|
+
const commentAuthors = /* @__PURE__ */ new Set();
|
|
4058
|
+
let hasNonForeignRealText = false;
|
|
3852
4059
|
for (const s of spans) {
|
|
4060
|
+
if (s.run === null) continue;
|
|
4061
|
+
let isForeignIns = false;
|
|
3853
4062
|
if (s.ins_id) {
|
|
3854
4063
|
const insNodes = findAllDescendants(
|
|
3855
4064
|
this.doc.element,
|
|
@@ -3857,21 +4066,35 @@ var RedlineEngine = class {
|
|
|
3857
4066
|
).filter((n) => n.getAttribute("w:id") === s.ins_id);
|
|
3858
4067
|
if (insNodes.length > 0) {
|
|
3859
4068
|
const auth = insNodes[0].getAttribute("w:author");
|
|
3860
|
-
if (auth && auth !== this.author)
|
|
4069
|
+
if (auth && auth !== this.author) {
|
|
4070
|
+
insAuthors.add(auth);
|
|
4071
|
+
isForeignIns = true;
|
|
4072
|
+
}
|
|
3861
4073
|
}
|
|
3862
4074
|
}
|
|
4075
|
+
if (!isForeignIns) hasNonForeignRealText = true;
|
|
4076
|
+
}
|
|
4077
|
+
for (const s of spans) {
|
|
3863
4078
|
if (s.comment_ids) {
|
|
3864
4079
|
for (const cid of s.comment_ids) {
|
|
3865
4080
|
const c_data = this.mapper.comments_map[cid];
|
|
3866
4081
|
if (c_data && c_data.author && c_data.author !== this.author) {
|
|
3867
|
-
|
|
4082
|
+
commentAuthors.add(c_data.author);
|
|
3868
4083
|
}
|
|
3869
4084
|
}
|
|
3870
4085
|
}
|
|
3871
4086
|
}
|
|
3872
|
-
if (
|
|
4087
|
+
if (insAuthors.size > 0 || commentAuthors.size > 0) {
|
|
4088
|
+
const fullyWithinForeignIns = insAuthors.size > 0 && !hasNonForeignRealText && commentAuthors.size === 0;
|
|
4089
|
+
if ((match_mode === "strict" || match_mode === "first") && fullyWithinForeignIns) {
|
|
4090
|
+
continue;
|
|
4091
|
+
}
|
|
4092
|
+
const nestedAuthors = /* @__PURE__ */ new Set([
|
|
4093
|
+
...insAuthors,
|
|
4094
|
+
...commentAuthors
|
|
4095
|
+
]);
|
|
3873
4096
|
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.`
|
|
4097
|
+
`- 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
4098
|
);
|
|
3876
4099
|
}
|
|
3877
4100
|
}
|
|
@@ -3933,22 +4156,13 @@ var RedlineEngine = class {
|
|
|
3933
4156
|
});
|
|
3934
4157
|
}
|
|
3935
4158
|
if (dry_run) {
|
|
3936
|
-
const
|
|
3937
|
-
|
|
3938
|
-
if (part._element) {
|
|
3939
|
-
baselines.set(part, part._element.cloneNode(true));
|
|
3940
|
-
}
|
|
3941
|
-
}
|
|
4159
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4160
|
+
const originalCurrentId = this.current_id;
|
|
3942
4161
|
try {
|
|
3943
4162
|
return this._process_batch_internal(changes, true);
|
|
3944
4163
|
} finally {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
if (doc && doc.documentElement) {
|
|
3948
|
-
doc.replaceChild(originalEl, doc.documentElement);
|
|
3949
|
-
}
|
|
3950
|
-
part._element = originalEl;
|
|
3951
|
-
}
|
|
4164
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4165
|
+
this.current_id = originalCurrentId;
|
|
3952
4166
|
this.mapper = new DocumentMapper(this.doc);
|
|
3953
4167
|
this.comments_manager = new CommentsManager(this.doc);
|
|
3954
4168
|
this.clean_mapper = null;
|
|
@@ -3958,6 +4172,13 @@ var RedlineEngine = class {
|
|
|
3958
4172
|
}
|
|
3959
4173
|
}
|
|
3960
4174
|
_process_batch_internal(changes, dry_run_mode = false) {
|
|
4175
|
+
for (const c of changes) {
|
|
4176
|
+
if (c && typeof c === "object" && c.type === "modify" && c.target_text && c.new_text) {
|
|
4177
|
+
const [strippedTarget, strippedNew] = stripMatchingHeadingHashes(c.target_text, c.new_text);
|
|
4178
|
+
c.target_text = strippedTarget;
|
|
4179
|
+
c.new_text = strippedNew;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
3961
4182
|
this.skipped_details = [];
|
|
3962
4183
|
const actions = changes.filter(
|
|
3963
4184
|
(c) => c !== null && typeof c === "object" && ["accept", "reject", "reply"].includes(c.type)
|
|
@@ -3966,13 +4187,13 @@ var RedlineEngine = class {
|
|
|
3966
4187
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
3967
4188
|
);
|
|
3968
4189
|
if (!dry_run_mode) {
|
|
3969
|
-
const
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
4190
|
+
const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
4191
|
+
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
4192
|
+
const edit_errors = validate_edits_now ? this.validate_edits(edits) : [];
|
|
4193
|
+
const all_errors = [
|
|
4194
|
+
...action_errors,
|
|
4195
|
+
...edit_errors
|
|
4196
|
+
];
|
|
3976
4197
|
if (all_errors.length > 0) {
|
|
3977
4198
|
throw new BatchValidationError(all_errors);
|
|
3978
4199
|
}
|
|
@@ -4006,8 +4227,9 @@ var RedlineEngine = class {
|
|
|
4006
4227
|
let skipped_edits = 0;
|
|
4007
4228
|
if (edits.length > 0) {
|
|
4008
4229
|
if (dry_run_mode) {
|
|
4009
|
-
for (
|
|
4010
|
-
const
|
|
4230
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4231
|
+
const edit = edits[i];
|
|
4232
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4011
4233
|
const warning = this._check_punctuation_warning(
|
|
4012
4234
|
edit.target_text || ""
|
|
4013
4235
|
);
|
|
@@ -4041,6 +4263,8 @@ var RedlineEngine = class {
|
|
|
4041
4263
|
occurrences_modified: edit._occurrences_modified || 0,
|
|
4042
4264
|
match_mode: edit.match_mode || "strict"
|
|
4043
4265
|
});
|
|
4266
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4267
|
+
this.clean_mapper = null;
|
|
4044
4268
|
} else {
|
|
4045
4269
|
skipped_edits++;
|
|
4046
4270
|
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
@@ -4056,15 +4280,37 @@ var RedlineEngine = class {
|
|
|
4056
4280
|
}
|
|
4057
4281
|
}
|
|
4058
4282
|
} else {
|
|
4059
|
-
const
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4283
|
+
const snapshot = takeSnapshot(this.doc);
|
|
4284
|
+
const originalCurrentId = this.current_id;
|
|
4285
|
+
try {
|
|
4286
|
+
const sequential_errors = [];
|
|
4287
|
+
for (let i = 0; i < edits.length; i++) {
|
|
4288
|
+
const edit = edits[i];
|
|
4289
|
+
const single_errors = this.validate_edits([edit], i);
|
|
4290
|
+
if (single_errors.length > 0) {
|
|
4291
|
+
sequential_errors.push(...single_errors);
|
|
4292
|
+
} else {
|
|
4293
|
+
this.apply_edits([edit], page_offsets);
|
|
4294
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4295
|
+
this.clean_mapper = null;
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
if (sequential_errors.length > 0) {
|
|
4299
|
+
throw new BatchValidationError(sequential_errors);
|
|
4300
|
+
}
|
|
4301
|
+
} catch (err) {
|
|
4302
|
+
restoreSnapshot(this.doc, snapshot);
|
|
4303
|
+
this.current_id = originalCurrentId;
|
|
4304
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
4305
|
+
this.comments_manager = new CommentsManager(this.doc);
|
|
4306
|
+
this.clean_mapper = null;
|
|
4307
|
+
throw err;
|
|
4308
|
+
}
|
|
4309
|
+
applied_edits = edits.filter(
|
|
4310
|
+
(e) => e._applied_status
|
|
4311
|
+
).length;
|
|
4312
|
+
skipped_edits = edits.length - applied_edits;
|
|
4313
|
+
for (const edit of edits) {
|
|
4068
4314
|
const success = edit._applied_status || false;
|
|
4069
4315
|
const error_msg = edit._error_msg || null;
|
|
4070
4316
|
const warning = this._check_punctuation_warning(
|
|
@@ -4183,6 +4429,7 @@ var RedlineEngine = class {
|
|
|
4183
4429
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4184
4430
|
);
|
|
4185
4431
|
const occupied_ranges = [];
|
|
4432
|
+
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4186
4433
|
for (const [edit, orig_new] of resolved_edits) {
|
|
4187
4434
|
const start = edit._resolved_start_idx || 0;
|
|
4188
4435
|
const end = start + (edit.target_text ? edit.target_text.length : 0);
|
|
@@ -4211,21 +4458,38 @@ var RedlineEngine = class {
|
|
|
4211
4458
|
success = this._apply_table_edit(edit, false);
|
|
4212
4459
|
}
|
|
4213
4460
|
if (success) {
|
|
4214
|
-
|
|
4461
|
+
const group_id = edit._split_group_id;
|
|
4462
|
+
const first_in_group = group_id === void 0 || group_id === null || !counted_split_groups.has(group_id);
|
|
4463
|
+
if (first_in_group && group_id !== void 0 && group_id !== null) {
|
|
4464
|
+
counted_split_groups.add(group_id);
|
|
4465
|
+
}
|
|
4466
|
+
if (first_in_group) applied++;
|
|
4215
4467
|
occupied_ranges.push([start, end]);
|
|
4216
4468
|
edit._applied_status = true;
|
|
4217
4469
|
const parent = edit._parent_edit_ref;
|
|
4218
4470
|
if (parent) {
|
|
4219
4471
|
parent._applied_status = true;
|
|
4220
|
-
|
|
4221
|
-
|
|
4472
|
+
if (first_in_group) {
|
|
4473
|
+
parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
|
|
4474
|
+
}
|
|
4475
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4476
|
+
start,
|
|
4477
|
+
this.mapper.full_text,
|
|
4478
|
+
page_offsets
|
|
4479
|
+
);
|
|
4222
4480
|
const pages = parent._pages || [];
|
|
4223
4481
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4224
4482
|
parent._pages = pages;
|
|
4225
4483
|
parent._heading_path = path;
|
|
4226
4484
|
} else {
|
|
4227
|
-
|
|
4228
|
-
|
|
4485
|
+
if (first_in_group) {
|
|
4486
|
+
edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
|
|
4487
|
+
}
|
|
4488
|
+
const [path, page] = this._get_heading_path_and_page(
|
|
4489
|
+
start,
|
|
4490
|
+
this.mapper.full_text,
|
|
4491
|
+
page_offsets
|
|
4492
|
+
);
|
|
4229
4493
|
const pages = edit._pages || [];
|
|
4230
4494
|
if (!pages.includes(page)) pages.unshift(page);
|
|
4231
4495
|
edit._pages = pages;
|
|
@@ -4386,12 +4650,18 @@ var RedlineEngine = class {
|
|
|
4386
4650
|
if (!edit.target_text) return null;
|
|
4387
4651
|
const is_regex = edit.regex || false;
|
|
4388
4652
|
const match_mode = edit.match_mode || "strict";
|
|
4389
|
-
let matches = this.mapper.find_all_match_indices(
|
|
4653
|
+
let matches = this.mapper.find_all_match_indices(
|
|
4654
|
+
edit.target_text,
|
|
4655
|
+
is_regex
|
|
4656
|
+
);
|
|
4390
4657
|
let use_clean_map = false;
|
|
4391
4658
|
if (matches.length === 0) {
|
|
4392
4659
|
if (!this.clean_mapper)
|
|
4393
4660
|
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
4394
|
-
matches = this.clean_mapper.find_all_match_indices(
|
|
4661
|
+
matches = this.clean_mapper.find_all_match_indices(
|
|
4662
|
+
edit.target_text,
|
|
4663
|
+
is_regex
|
|
4664
|
+
);
|
|
4395
4665
|
if (matches.length > 0) use_clean_map = true;
|
|
4396
4666
|
else return null;
|
|
4397
4667
|
}
|
|
@@ -4416,14 +4686,49 @@ var RedlineEngine = class {
|
|
|
4416
4686
|
start_idx + match_len
|
|
4417
4687
|
);
|
|
4418
4688
|
let current_effective_new_text = edit.new_text || "";
|
|
4689
|
+
if (/^\{#cell:[^}]+\}$/.test(actual_doc_text.trim())) {
|
|
4690
|
+
let ins_text = current_effective_new_text;
|
|
4691
|
+
ins_text = ins_text.split(actual_doc_text.trim()).join("");
|
|
4692
|
+
if (ins_text) {
|
|
4693
|
+
all_sub_edits.push({
|
|
4694
|
+
type: "modify",
|
|
4695
|
+
target_text: "",
|
|
4696
|
+
new_text: ins_text,
|
|
4697
|
+
comment: edit.comment,
|
|
4698
|
+
// Insert at the anchor token's start so the new run lands inside
|
|
4699
|
+
// the cell paragraph that get_insertion_anchor resolves there.
|
|
4700
|
+
_match_start_index: start_idx,
|
|
4701
|
+
_internal_op: "INSERTION",
|
|
4702
|
+
_active_mapper_ref: active_mapper
|
|
4703
|
+
});
|
|
4704
|
+
} else if (edit.comment) {
|
|
4705
|
+
all_sub_edits.push({
|
|
4706
|
+
type: "modify",
|
|
4707
|
+
target_text: "",
|
|
4708
|
+
new_text: "",
|
|
4709
|
+
comment: edit.comment,
|
|
4710
|
+
_match_start_index: start_idx,
|
|
4711
|
+
_internal_op: "COMMENT_ONLY",
|
|
4712
|
+
_active_mapper_ref: active_mapper
|
|
4713
|
+
});
|
|
4714
|
+
}
|
|
4715
|
+
continue;
|
|
4716
|
+
}
|
|
4419
4717
|
if (is_regex && current_effective_new_text) {
|
|
4420
4718
|
try {
|
|
4421
|
-
current_effective_new_text = actual_doc_text.replace(
|
|
4719
|
+
current_effective_new_text = actual_doc_text.replace(
|
|
4720
|
+
new RegExp(edit.target_text),
|
|
4721
|
+
current_effective_new_text
|
|
4722
|
+
);
|
|
4422
4723
|
} catch (e) {
|
|
4423
4724
|
}
|
|
4424
4725
|
}
|
|
4425
|
-
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4426
|
-
|
|
4726
|
+
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4727
|
+
edit.target_text
|
|
4728
|
+
);
|
|
4729
|
+
const [edit_new_clean, edit_new_style] = this._parse_markdown_style(
|
|
4730
|
+
current_effective_new_text
|
|
4731
|
+
);
|
|
4427
4732
|
if (edit_target_style !== edit_new_style) {
|
|
4428
4733
|
const [actual_clean] = this._parse_markdown_style(actual_doc_text);
|
|
4429
4734
|
const final_target2 = actual_clean;
|
|
@@ -4462,7 +4767,9 @@ var RedlineEngine = class {
|
|
|
4462
4767
|
let effective_start_idx = start_idx;
|
|
4463
4768
|
if (current_effective_new_text.startsWith(actual_doc_text)) {
|
|
4464
4769
|
effective_op = "INSERTION";
|
|
4465
|
-
final_new = current_effective_new_text.substring(
|
|
4770
|
+
final_new = current_effective_new_text.substring(
|
|
4771
|
+
actual_doc_text.length
|
|
4772
|
+
);
|
|
4466
4773
|
effective_start_idx = start_idx + match_len;
|
|
4467
4774
|
} else {
|
|
4468
4775
|
const [prefix_len, suffix_len] = trim_common_context(
|
|
@@ -4474,6 +4781,51 @@ var RedlineEngine = class {
|
|
|
4474
4781
|
final_target = actual_doc_text.substring(prefix_len, t_end);
|
|
4475
4782
|
final_new = current_effective_new_text.substring(prefix_len, n_end);
|
|
4476
4783
|
effective_start_idx = start_idx + prefix_len;
|
|
4784
|
+
const target_segs = actual_doc_text.split("\n\n");
|
|
4785
|
+
const new_segs = current_effective_new_text.split("\n\n");
|
|
4786
|
+
if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
|
|
4787
|
+
const split_sub_edits = [];
|
|
4788
|
+
let seg_offset = start_idx;
|
|
4789
|
+
let comment_assigned = false;
|
|
4790
|
+
for (let k = 0; k < target_segs.length; k++) {
|
|
4791
|
+
const t_seg = target_segs[k];
|
|
4792
|
+
const n_seg = new_segs[k];
|
|
4793
|
+
if (t_seg !== n_seg) {
|
|
4794
|
+
const [seg_prefix, seg_suffix] = trim_common_context(t_seg, n_seg);
|
|
4795
|
+
const seg_target = t_seg.substring(
|
|
4796
|
+
seg_prefix,
|
|
4797
|
+
t_seg.length - seg_suffix
|
|
4798
|
+
);
|
|
4799
|
+
const seg_new = n_seg.substring(
|
|
4800
|
+
seg_prefix,
|
|
4801
|
+
n_seg.length - seg_suffix
|
|
4802
|
+
);
|
|
4803
|
+
const seg_start = seg_offset + seg_prefix;
|
|
4804
|
+
let seg_op;
|
|
4805
|
+
if (!seg_target && seg_new) seg_op = "INSERTION";
|
|
4806
|
+
else if (seg_target && !seg_new) seg_op = "DELETION";
|
|
4807
|
+
else if (seg_target && seg_new) seg_op = "MODIFICATION";
|
|
4808
|
+
else seg_op = "COMMENT_ONLY";
|
|
4809
|
+
const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
|
|
4810
|
+
if (seg_comment) comment_assigned = true;
|
|
4811
|
+
split_sub_edits.push({
|
|
4812
|
+
type: "modify",
|
|
4813
|
+
target_text: seg_target,
|
|
4814
|
+
new_text: seg_new,
|
|
4815
|
+
comment: seg_comment,
|
|
4816
|
+
_match_start_index: seg_start,
|
|
4817
|
+
_internal_op: seg_op,
|
|
4818
|
+
_active_mapper_ref: active_mapper,
|
|
4819
|
+
_split_group_id: start_idx
|
|
4820
|
+
});
|
|
4821
|
+
}
|
|
4822
|
+
seg_offset += t_seg.length + 2;
|
|
4823
|
+
}
|
|
4824
|
+
if (split_sub_edits.length > 0) {
|
|
4825
|
+
for (const sub of split_sub_edits) all_sub_edits.push(sub);
|
|
4826
|
+
continue;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4477
4829
|
if (!final_target && final_new) effective_op = "INSERTION";
|
|
4478
4830
|
else if (final_target && !final_new) effective_op = "DELETION";
|
|
4479
4831
|
else if (final_target && final_new) effective_op = "MODIFICATION";
|
|
@@ -4493,6 +4845,35 @@ var RedlineEngine = class {
|
|
|
4493
4845
|
if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
|
|
4494
4846
|
return all_sub_edits[0];
|
|
4495
4847
|
}
|
|
4848
|
+
/**
|
|
4849
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
4850
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
4851
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
4852
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
4853
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
4854
|
+
* <w:ins>.
|
|
4855
|
+
*/
|
|
4856
|
+
_insert_and_split_ins(parent_ins, split_after, new_elem) {
|
|
4857
|
+
const grandparent = parent_ins.parentNode;
|
|
4858
|
+
if (!grandparent) return;
|
|
4859
|
+
const left = parent_ins.cloneNode(false);
|
|
4860
|
+
const right = parent_ins.cloneNode(false);
|
|
4861
|
+
let toRight = false;
|
|
4862
|
+
for (const kid of Array.from(parent_ins.childNodes)) {
|
|
4863
|
+
parent_ins.removeChild(kid);
|
|
4864
|
+
if (!toRight) {
|
|
4865
|
+
left.appendChild(kid);
|
|
4866
|
+
if (kid === split_after) toRight = true;
|
|
4867
|
+
} else {
|
|
4868
|
+
right.appendChild(kid);
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
if (left.childNodes.length > 0) grandparent.insertBefore(left, parent_ins);
|
|
4872
|
+
grandparent.insertBefore(new_elem, parent_ins);
|
|
4873
|
+
if (right.childNodes.length > 0)
|
|
4874
|
+
grandparent.insertBefore(right, parent_ins);
|
|
4875
|
+
grandparent.removeChild(parent_ins);
|
|
4876
|
+
}
|
|
4496
4877
|
_apply_single_edit_indexed(edit, orig_new, rebuild_map) {
|
|
4497
4878
|
let op = edit._internal_op;
|
|
4498
4879
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
@@ -4543,7 +4924,12 @@ var RedlineEngine = class {
|
|
|
4543
4924
|
const first_anchor = ascend_to_paragraph_child(first_el, start_p);
|
|
4544
4925
|
const last_anchor = ascend_to_paragraph_child(last_el, end_p);
|
|
4545
4926
|
if (start_p === end_p) {
|
|
4546
|
-
this._attach_comment(
|
|
4927
|
+
this._attach_comment(
|
|
4928
|
+
start_p,
|
|
4929
|
+
first_anchor,
|
|
4930
|
+
last_anchor,
|
|
4931
|
+
edit.comment
|
|
4932
|
+
);
|
|
4547
4933
|
} else {
|
|
4548
4934
|
this._attach_comment_spanning(
|
|
4549
4935
|
start_p,
|
|
@@ -4695,7 +5081,16 @@ var RedlineEngine = class {
|
|
|
4695
5081
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
4696
5082
|
if (is_inline_first) {
|
|
4697
5083
|
if (anchor_run) {
|
|
4698
|
-
|
|
5084
|
+
const anchor_parent = anchor_run._element.parentNode;
|
|
5085
|
+
if (anchor_parent && anchor_parent.tagName === "w:ins") {
|
|
5086
|
+
this._insert_and_split_ins(
|
|
5087
|
+
anchor_parent,
|
|
5088
|
+
anchor_run._element,
|
|
5089
|
+
result.first_node
|
|
5090
|
+
);
|
|
5091
|
+
} else {
|
|
5092
|
+
insertAfter(result.first_node, anchor_run._element);
|
|
5093
|
+
}
|
|
4699
5094
|
} else if (anchor_para) {
|
|
4700
5095
|
anchor_para._element.appendChild(result.first_node);
|
|
4701
5096
|
}
|
|
@@ -4805,7 +5200,12 @@ var RedlineEngine = class {
|
|
|
4805
5200
|
if (result.first_node) {
|
|
4806
5201
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
4807
5202
|
if (is_inline_first) {
|
|
4808
|
-
|
|
5203
|
+
const del_parent = last_del.parentNode;
|
|
5204
|
+
if (del_parent && del_parent.tagName === "w:ins") {
|
|
5205
|
+
this._insert_and_split_ins(del_parent, last_del, result.first_node);
|
|
5206
|
+
} else {
|
|
5207
|
+
insertAfter(result.first_node, last_del);
|
|
5208
|
+
}
|
|
4809
5209
|
ins_elem = result.first_node;
|
|
4810
5210
|
} else {
|
|
4811
5211
|
ins_elem = result.last_ins;
|
|
@@ -5832,13 +6232,21 @@ function extract_table(table, comments_map, cleanView, cursor, paragraph_offsets
|
|
|
5832
6232
|
if (seen_cells.has(cell)) continue;
|
|
5833
6233
|
seen_cells.add(cell);
|
|
5834
6234
|
if (!first_cell) cell_cursor += 3;
|
|
5835
|
-
|
|
6235
|
+
let cell_content = _extract_blocks(
|
|
5836
6236
|
cell,
|
|
5837
6237
|
comments_map,
|
|
5838
6238
|
cleanView,
|
|
5839
6239
|
cell_cursor,
|
|
5840
6240
|
paragraph_offsets
|
|
5841
6241
|
);
|
|
6242
|
+
if (!cleanView) {
|
|
6243
|
+
const firstP = cell._element.getElementsByTagName("w:p")[0];
|
|
6244
|
+
const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
|
|
6245
|
+
if (paraId) {
|
|
6246
|
+
const anchor = `{#cell:${paraId}}`;
|
|
6247
|
+
cell_content = cell_content + anchor;
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
5842
6250
|
cell_texts.push(cell_content);
|
|
5843
6251
|
cell_cursor += cell_content.length;
|
|
5844
6252
|
first_cell = false;
|