@adeu/core 1.17.2 → 1.18.1
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 +250 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +250 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine.bugs.test.ts +12 -14
- package/src/engine.feedback.test.ts +34 -2
- package/src/engine.issue23.test.ts +23 -18
- package/src/engine.ts +359 -98
- package/src/reject_and_nested_redline.test.ts +137 -0
- package/src/repro.report.test.ts +11 -9
- package/src/repro_qa_report_v3.test.ts +19 -27
- package/src/repro_report_boundary_failures.test.ts +108 -0
package/dist/index.d.cts
CHANGED
|
@@ -185,6 +185,16 @@ declare class RedlineEngine {
|
|
|
185
185
|
original_mapper: DocumentMapper | null;
|
|
186
186
|
skipped_details: string[];
|
|
187
187
|
constructor(doc: DocumentObject, author?: string);
|
|
188
|
+
/**
|
|
189
|
+
* Return a hint when a short, single-token anchor contains punctuation that
|
|
190
|
+
* can split awkwardly, else null.
|
|
191
|
+
*
|
|
192
|
+
* Surface this ONLY for edits that actually failed to match/apply. On a
|
|
193
|
+
* successful edit the batch report already carries the redline preview, so
|
|
194
|
+
* emitting this would be a false positive: the punctuation (dates,
|
|
195
|
+
* `[_name_]` placeholders, `____` blanks) is frequently the literal target
|
|
196
|
+
* and the edit succeeds despite it. Mirrors the Python engine.
|
|
197
|
+
*/
|
|
188
198
|
private _check_punctuation_warning;
|
|
189
199
|
/**
|
|
190
200
|
* Best-effort "did you mean" hint for a failed target. The common loop trap
|
|
@@ -200,6 +210,35 @@ declare class RedlineEngine {
|
|
|
200
210
|
private _scan_existing_ids;
|
|
201
211
|
private _get_heading_path_and_page;
|
|
202
212
|
accept_all_revisions(): void;
|
|
213
|
+
/**
|
|
214
|
+
* Revert every tracked change, returning the document to the state it had
|
|
215
|
+
* before any revision was proposed. The exact inverse of
|
|
216
|
+
* accept_all_revisions:
|
|
217
|
+
*
|
|
218
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
219
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
220
|
+
* <w:trPr>) drops the whole row.
|
|
221
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
222
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
223
|
+
* the row survives.
|
|
224
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
225
|
+
*
|
|
226
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
227
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
228
|
+
*
|
|
229
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
230
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
231
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
232
|
+
* body text.
|
|
233
|
+
*
|
|
234
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
235
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
236
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
237
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
238
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
239
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
240
|
+
*/
|
|
241
|
+
reject_all_revisions(): void;
|
|
203
242
|
private _getNextId;
|
|
204
243
|
private _create_track_change_tag;
|
|
205
244
|
private _set_text_content;
|
|
@@ -286,6 +325,15 @@ declare class RedlineEngine {
|
|
|
286
325
|
apply_review_actions(actions: any[]): [number, number];
|
|
287
326
|
private _apply_table_edit;
|
|
288
327
|
private _pre_resolve_heuristic_edit;
|
|
328
|
+
/**
|
|
329
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
330
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
331
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
332
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
333
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
334
|
+
* <w:ins>.
|
|
335
|
+
*/
|
|
336
|
+
private _insert_and_split_ins;
|
|
289
337
|
private _apply_single_edit_indexed;
|
|
290
338
|
}
|
|
291
339
|
|
package/dist/index.d.ts
CHANGED
|
@@ -185,6 +185,16 @@ declare class RedlineEngine {
|
|
|
185
185
|
original_mapper: DocumentMapper | null;
|
|
186
186
|
skipped_details: string[];
|
|
187
187
|
constructor(doc: DocumentObject, author?: string);
|
|
188
|
+
/**
|
|
189
|
+
* Return a hint when a short, single-token anchor contains punctuation that
|
|
190
|
+
* can split awkwardly, else null.
|
|
191
|
+
*
|
|
192
|
+
* Surface this ONLY for edits that actually failed to match/apply. On a
|
|
193
|
+
* successful edit the batch report already carries the redline preview, so
|
|
194
|
+
* emitting this would be a false positive: the punctuation (dates,
|
|
195
|
+
* `[_name_]` placeholders, `____` blanks) is frequently the literal target
|
|
196
|
+
* and the edit succeeds despite it. Mirrors the Python engine.
|
|
197
|
+
*/
|
|
188
198
|
private _check_punctuation_warning;
|
|
189
199
|
/**
|
|
190
200
|
* Best-effort "did you mean" hint for a failed target. The common loop trap
|
|
@@ -200,6 +210,35 @@ declare class RedlineEngine {
|
|
|
200
210
|
private _scan_existing_ids;
|
|
201
211
|
private _get_heading_path_and_page;
|
|
202
212
|
accept_all_revisions(): void;
|
|
213
|
+
/**
|
|
214
|
+
* Revert every tracked change, returning the document to the state it had
|
|
215
|
+
* before any revision was proposed. The exact inverse of
|
|
216
|
+
* accept_all_revisions:
|
|
217
|
+
*
|
|
218
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
219
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
220
|
+
* <w:trPr>) drops the whole row.
|
|
221
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
222
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
223
|
+
* the row survives.
|
|
224
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
225
|
+
*
|
|
226
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
227
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
228
|
+
*
|
|
229
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
230
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
231
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
232
|
+
* body text.
|
|
233
|
+
*
|
|
234
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
235
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
236
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
237
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
238
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
239
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
240
|
+
*/
|
|
241
|
+
reject_all_revisions(): void;
|
|
203
242
|
private _getNextId;
|
|
204
243
|
private _create_track_change_tag;
|
|
205
244
|
private _set_text_content;
|
|
@@ -286,6 +325,15 @@ declare class RedlineEngine {
|
|
|
286
325
|
apply_review_actions(actions: any[]): [number, number];
|
|
287
326
|
private _apply_table_edit;
|
|
288
327
|
private _pre_resolve_heuristic_edit;
|
|
328
|
+
/**
|
|
329
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
330
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
331
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
332
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
333
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
334
|
+
* <w:ins>.
|
|
335
|
+
*/
|
|
336
|
+
private _insert_and_split_ins;
|
|
289
337
|
private _apply_single_edit_indexed;
|
|
290
338
|
}
|
|
291
339
|
|
package/dist/index.js
CHANGED
|
@@ -3012,8 +3012,19 @@ var RedlineEngine = class {
|
|
|
3012
3012
|
this.mapper = new DocumentMapper(this.doc);
|
|
3013
3013
|
this.comments_manager = new CommentsManager(this.doc);
|
|
3014
3014
|
}
|
|
3015
|
+
/**
|
|
3016
|
+
* Return a hint when a short, single-token anchor contains punctuation that
|
|
3017
|
+
* can split awkwardly, else null.
|
|
3018
|
+
*
|
|
3019
|
+
* Surface this ONLY for edits that actually failed to match/apply. On a
|
|
3020
|
+
* successful edit the batch report already carries the redline preview, so
|
|
3021
|
+
* emitting this would be a false positive: the punctuation (dates,
|
|
3022
|
+
* `[_name_]` placeholders, `____` blanks) is frequently the literal target
|
|
3023
|
+
* and the edit succeeds despite it. Mirrors the Python engine.
|
|
3024
|
+
*/
|
|
3015
3025
|
_check_punctuation_warning(target_text) {
|
|
3016
3026
|
if (!target_text) return null;
|
|
3027
|
+
if (target_text.length > 20 || target_text.includes(" ")) return null;
|
|
3017
3028
|
if (target_text.includes("_") || target_text.includes("-")) {
|
|
3018
3029
|
return `Warning: target_text '${target_text}' contains tokenization-splitting punctuation ('_' or '-'). This can trigger mid-word splits in the diff engine. Consider using a longer plain-prose anchor.`;
|
|
3019
3030
|
}
|
|
@@ -3295,6 +3306,93 @@ var RedlineEngine = class {
|
|
|
3295
3306
|
}
|
|
3296
3307
|
}
|
|
3297
3308
|
}
|
|
3309
|
+
/**
|
|
3310
|
+
* Revert every tracked change, returning the document to the state it had
|
|
3311
|
+
* before any revision was proposed. The exact inverse of
|
|
3312
|
+
* accept_all_revisions:
|
|
3313
|
+
*
|
|
3314
|
+
* - <w:ins> -> removed together with all of its content (the proposed
|
|
3315
|
+
* insertion never existed); an inserted row (<w:ins> in
|
|
3316
|
+
* <w:trPr>) drops the whole row.
|
|
3317
|
+
* - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
|
|
3318
|
+
* <w:t> again); a row-deletion mark in <w:trPr> is removed so
|
|
3319
|
+
* the row survives.
|
|
3320
|
+
* - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
|
|
3321
|
+
*
|
|
3322
|
+
* Comments are annotations, not revisions, so standalone comments are left in
|
|
3323
|
+
* place; only anchors stranded inside a rejected insertion are cleaned up.
|
|
3324
|
+
*
|
|
3325
|
+
* Insertions are reverted before deletions are restored so a deletion nested
|
|
3326
|
+
* inside a foreign author's insertion is removed wholesale with the insertion
|
|
3327
|
+
* — the contingent text disappears rather than being promoted to committed
|
|
3328
|
+
* body text.
|
|
3329
|
+
*
|
|
3330
|
+
* Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
|
|
3331
|
+
* pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
|
|
3332
|
+
* to the extent of dropping/keeping the mark; the original paragraph boundary
|
|
3333
|
+
* is not reconstructed, because the merge protocol coalesces paragraphs
|
|
3334
|
+
* destructively at edit time. Reverting run-level insertions/deletions (the
|
|
3335
|
+
* common case) is exact. This limitation is shared with the Python engine.
|
|
3336
|
+
*/
|
|
3337
|
+
reject_all_revisions() {
|
|
3338
|
+
const parts_to_process = [this.doc.element];
|
|
3339
|
+
for (const part of this.doc.pkg.parts) {
|
|
3340
|
+
if (part === this.doc.part) continue;
|
|
3341
|
+
if (part.contentType.includes("wordprocessingml") && part.contentType.endsWith("+xml")) {
|
|
3342
|
+
parts_to_process.push(part._element);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
for (const root_element of parts_to_process) {
|
|
3346
|
+
const insNodes = findAllDescendants(root_element, "w:ins");
|
|
3347
|
+
for (const ins of insNodes) {
|
|
3348
|
+
const parent = ins.parentNode;
|
|
3349
|
+
if (!parent) continue;
|
|
3350
|
+
this._clean_wrapping_comments(ins);
|
|
3351
|
+
this._delete_comments_in_element(ins);
|
|
3352
|
+
if (parent.tagName === "w:trPr") {
|
|
3353
|
+
const row = parent.parentNode;
|
|
3354
|
+
if (row && row.parentNode) {
|
|
3355
|
+
row.parentNode.removeChild(row);
|
|
3356
|
+
}
|
|
3357
|
+
} else {
|
|
3358
|
+
parent.removeChild(ins);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
const pNodes = findAllDescendants(root_element, "w:p");
|
|
3362
|
+
for (const p of pNodes) {
|
|
3363
|
+
const pPr = findChild(p, "w:pPr");
|
|
3364
|
+
if (pPr) {
|
|
3365
|
+
const rPr = findChild(pPr, "w:rPr");
|
|
3366
|
+
const delMark = rPr ? findChild(rPr, "w:del") : null;
|
|
3367
|
+
if (rPr && delMark) {
|
|
3368
|
+
rPr.removeChild(delMark);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
const delNodes = findAllDescendants(root_element, "w:del");
|
|
3373
|
+
for (const d of delNodes) {
|
|
3374
|
+
const parent = d.parentNode;
|
|
3375
|
+
if (!parent) continue;
|
|
3376
|
+
this._clean_wrapping_comments(d);
|
|
3377
|
+
if (parent.tagName === "w:trPr") {
|
|
3378
|
+
parent.removeChild(d);
|
|
3379
|
+
continue;
|
|
3380
|
+
}
|
|
3381
|
+
const delTexts = Array.from(d.getElementsByTagName("w:delText"));
|
|
3382
|
+
for (const dt of delTexts) {
|
|
3383
|
+
const t = d.ownerDocument.createElement("w:t");
|
|
3384
|
+
t.textContent = dt.textContent;
|
|
3385
|
+
if (dt.hasAttribute("xml:space"))
|
|
3386
|
+
t.setAttribute("xml:space", "preserve");
|
|
3387
|
+
dt.parentNode?.replaceChild(t, dt);
|
|
3388
|
+
}
|
|
3389
|
+
while (d.firstChild) {
|
|
3390
|
+
parent.insertBefore(d.firstChild, d);
|
|
3391
|
+
}
|
|
3392
|
+
parent.removeChild(d);
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3298
3396
|
_getNextId() {
|
|
3299
3397
|
this.current_id++;
|
|
3300
3398
|
return this.current_id.toString();
|
|
@@ -3942,29 +4040,36 @@ var RedlineEngine = class {
|
|
|
3942
4040
|
(edit.new_text || "").length - sfx
|
|
3943
4041
|
);
|
|
3944
4042
|
if (final_target.includes("\n\n")) {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
if (
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
4043
|
+
const balanced = matched.split("\n\n").length === (edit.new_text || "").split("\n\n").length;
|
|
4044
|
+
if (!balanced) {
|
|
4045
|
+
if (final_new.includes("\n\n")) {
|
|
4046
|
+
const parts = matched.split("\n\n");
|
|
4047
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4048
|
+
errors.push(
|
|
4049
|
+
`- 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.`
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
4052
|
+
} else {
|
|
4053
|
+
const parts = final_target.split("\n\n");
|
|
4054
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
4055
|
+
errors.push(
|
|
4056
|
+
`- 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.`
|
|
4057
|
+
);
|
|
4058
|
+
}
|
|
3958
4059
|
}
|
|
3959
4060
|
}
|
|
3960
4061
|
}
|
|
3961
4062
|
}
|
|
3962
4063
|
for (const [start, length] of matches) {
|
|
3963
|
-
const spans =
|
|
4064
|
+
const spans = target_mapper.spans.filter(
|
|
3964
4065
|
(s) => s.end > start && s.start < start + length
|
|
3965
4066
|
);
|
|
3966
|
-
const
|
|
4067
|
+
const insAuthors = /* @__PURE__ */ new Set();
|
|
4068
|
+
const commentAuthors = /* @__PURE__ */ new Set();
|
|
4069
|
+
let hasNonForeignRealText = false;
|
|
3967
4070
|
for (const s of spans) {
|
|
4071
|
+
if (s.run === null) continue;
|
|
4072
|
+
let isForeignIns = false;
|
|
3968
4073
|
if (s.ins_id) {
|
|
3969
4074
|
const insNodes = findAllDescendants(
|
|
3970
4075
|
this.doc.element,
|
|
@@ -3973,24 +4078,32 @@ var RedlineEngine = class {
|
|
|
3973
4078
|
if (insNodes.length > 0) {
|
|
3974
4079
|
const auth = insNodes[0].getAttribute("w:author");
|
|
3975
4080
|
if (auth && auth !== this.author) {
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
if (is_lockout) {
|
|
3979
|
-
nestedAuthors.add(auth);
|
|
3980
|
-
}
|
|
4081
|
+
insAuthors.add(auth);
|
|
4082
|
+
isForeignIns = true;
|
|
3981
4083
|
}
|
|
3982
4084
|
}
|
|
3983
4085
|
}
|
|
4086
|
+
if (!isForeignIns) hasNonForeignRealText = true;
|
|
4087
|
+
}
|
|
4088
|
+
for (const s of spans) {
|
|
3984
4089
|
if (s.comment_ids) {
|
|
3985
4090
|
for (const cid of s.comment_ids) {
|
|
3986
4091
|
const c_data = this.mapper.comments_map[cid];
|
|
3987
4092
|
if (c_data && c_data.author && c_data.author !== this.author) {
|
|
3988
|
-
|
|
4093
|
+
commentAuthors.add(c_data.author);
|
|
3989
4094
|
}
|
|
3990
4095
|
}
|
|
3991
4096
|
}
|
|
3992
4097
|
}
|
|
3993
|
-
if (
|
|
4098
|
+
if (insAuthors.size > 0 || commentAuthors.size > 0) {
|
|
4099
|
+
const fullyWithinForeignIns = insAuthors.size > 0 && !hasNonForeignRealText && commentAuthors.size === 0;
|
|
4100
|
+
if ((match_mode === "strict" || match_mode === "first") && fullyWithinForeignIns) {
|
|
4101
|
+
continue;
|
|
4102
|
+
}
|
|
4103
|
+
const nestedAuthors = /* @__PURE__ */ new Set([
|
|
4104
|
+
...insAuthors,
|
|
4105
|
+
...commentAuthors
|
|
4106
|
+
]);
|
|
3994
4107
|
errors.push(
|
|
3995
4108
|
`- 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.`
|
|
3996
4109
|
);
|
|
@@ -4084,59 +4197,6 @@ var RedlineEngine = class {
|
|
|
4084
4197
|
const edits = changes.filter(
|
|
4085
4198
|
(c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
|
|
4086
4199
|
);
|
|
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;
|
|
4099
|
-
}
|
|
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
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
if (mapper_dirty) {
|
|
4137
|
-
this.mapper = new DocumentMapper(this.doc);
|
|
4138
|
-
this.clean_mapper = null;
|
|
4139
|
-
}
|
|
4140
4200
|
if (!dry_run_mode) {
|
|
4141
4201
|
const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
|
|
4142
4202
|
const validate_edits_now = edits.length > 0 && action_errors.length > 0;
|
|
@@ -4181,11 +4241,11 @@ var RedlineEngine = class {
|
|
|
4181
4241
|
for (let i = 0; i < edits.length; i++) {
|
|
4182
4242
|
const edit = edits[i];
|
|
4183
4243
|
const single_errors = this.validate_edits([edit], i);
|
|
4184
|
-
const warning = this._check_punctuation_warning(
|
|
4185
|
-
edit.target_text || ""
|
|
4186
|
-
);
|
|
4187
4244
|
if (single_errors.length > 0) {
|
|
4188
4245
|
skipped_edits++;
|
|
4246
|
+
const warning = this._check_punctuation_warning(
|
|
4247
|
+
edit.target_text || ""
|
|
4248
|
+
);
|
|
4189
4249
|
edits_reports.push({
|
|
4190
4250
|
status: "failed",
|
|
4191
4251
|
target_text: edit.target_text || "",
|
|
@@ -4205,7 +4265,7 @@ var RedlineEngine = class {
|
|
|
4205
4265
|
status: "applied",
|
|
4206
4266
|
target_text: edit.target_text || "",
|
|
4207
4267
|
new_text: edit.new_text || "",
|
|
4208
|
-
warning,
|
|
4268
|
+
warning: null,
|
|
4209
4269
|
error: null,
|
|
4210
4270
|
critic_markup: previews[0],
|
|
4211
4271
|
clean_text: previews[1],
|
|
@@ -4219,6 +4279,9 @@ var RedlineEngine = class {
|
|
|
4219
4279
|
} else {
|
|
4220
4280
|
skipped_edits++;
|
|
4221
4281
|
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
4282
|
+
const warning = this._check_punctuation_warning(
|
|
4283
|
+
edit.target_text || ""
|
|
4284
|
+
);
|
|
4222
4285
|
edits_reports.push({
|
|
4223
4286
|
status: "failed",
|
|
4224
4287
|
target_text: edit.target_text || "",
|
|
@@ -4264,15 +4327,17 @@ var RedlineEngine = class {
|
|
|
4264
4327
|
for (const edit of edits) {
|
|
4265
4328
|
const success = edit._applied_status || false;
|
|
4266
4329
|
const error_msg = edit._error_msg || null;
|
|
4267
|
-
const warning = this._check_punctuation_warning(
|
|
4268
|
-
edit.target_text || ""
|
|
4269
|
-
);
|
|
4270
4330
|
let critic_markup = null;
|
|
4271
4331
|
let clean_text = null;
|
|
4332
|
+
let warning = null;
|
|
4272
4333
|
if (success) {
|
|
4273
4334
|
const previews = this._build_edit_context_previews(edit);
|
|
4274
4335
|
critic_markup = previews[0];
|
|
4275
4336
|
clean_text = previews[1];
|
|
4337
|
+
} else {
|
|
4338
|
+
warning = this._check_punctuation_warning(
|
|
4339
|
+
edit.target_text || ""
|
|
4340
|
+
);
|
|
4276
4341
|
}
|
|
4277
4342
|
edits_reports.push({
|
|
4278
4343
|
status: success ? "applied" : "failed",
|
|
@@ -4380,6 +4445,7 @@ var RedlineEngine = class {
|
|
|
4380
4445
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4381
4446
|
);
|
|
4382
4447
|
const occupied_ranges = [];
|
|
4448
|
+
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4383
4449
|
for (const [edit, orig_new] of resolved_edits) {
|
|
4384
4450
|
const start = edit._resolved_start_idx || 0;
|
|
4385
4451
|
const end = start + (edit.target_text ? edit.target_text.length : 0);
|
|
@@ -4408,13 +4474,20 @@ var RedlineEngine = class {
|
|
|
4408
4474
|
success = this._apply_table_edit(edit, false);
|
|
4409
4475
|
}
|
|
4410
4476
|
if (success) {
|
|
4411
|
-
|
|
4477
|
+
const group_id = edit._split_group_id;
|
|
4478
|
+
const first_in_group = group_id === void 0 || group_id === null || !counted_split_groups.has(group_id);
|
|
4479
|
+
if (first_in_group && group_id !== void 0 && group_id !== null) {
|
|
4480
|
+
counted_split_groups.add(group_id);
|
|
4481
|
+
}
|
|
4482
|
+
if (first_in_group) applied++;
|
|
4412
4483
|
occupied_ranges.push([start, end]);
|
|
4413
4484
|
edit._applied_status = true;
|
|
4414
4485
|
const parent = edit._parent_edit_ref;
|
|
4415
4486
|
if (parent) {
|
|
4416
4487
|
parent._applied_status = true;
|
|
4417
|
-
|
|
4488
|
+
if (first_in_group) {
|
|
4489
|
+
parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
|
|
4490
|
+
}
|
|
4418
4491
|
const [path, page] = this._get_heading_path_and_page(
|
|
4419
4492
|
start,
|
|
4420
4493
|
this.mapper.full_text,
|
|
@@ -4425,7 +4498,9 @@ var RedlineEngine = class {
|
|
|
4425
4498
|
parent._pages = pages;
|
|
4426
4499
|
parent._heading_path = path;
|
|
4427
4500
|
} else {
|
|
4428
|
-
|
|
4501
|
+
if (first_in_group) {
|
|
4502
|
+
edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
|
|
4503
|
+
}
|
|
4429
4504
|
const [path, page] = this._get_heading_path_and_page(
|
|
4430
4505
|
start,
|
|
4431
4506
|
this.mapper.full_text,
|
|
@@ -4722,6 +4797,51 @@ var RedlineEngine = class {
|
|
|
4722
4797
|
final_target = actual_doc_text.substring(prefix_len, t_end);
|
|
4723
4798
|
final_new = current_effective_new_text.substring(prefix_len, n_end);
|
|
4724
4799
|
effective_start_idx = start_idx + prefix_len;
|
|
4800
|
+
const target_segs = actual_doc_text.split("\n\n");
|
|
4801
|
+
const new_segs = current_effective_new_text.split("\n\n");
|
|
4802
|
+
if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
|
|
4803
|
+
const split_sub_edits = [];
|
|
4804
|
+
let seg_offset = start_idx;
|
|
4805
|
+
let comment_assigned = false;
|
|
4806
|
+
for (let k = 0; k < target_segs.length; k++) {
|
|
4807
|
+
const t_seg = target_segs[k];
|
|
4808
|
+
const n_seg = new_segs[k];
|
|
4809
|
+
if (t_seg !== n_seg) {
|
|
4810
|
+
const [seg_prefix, seg_suffix] = trim_common_context(t_seg, n_seg);
|
|
4811
|
+
const seg_target = t_seg.substring(
|
|
4812
|
+
seg_prefix,
|
|
4813
|
+
t_seg.length - seg_suffix
|
|
4814
|
+
);
|
|
4815
|
+
const seg_new = n_seg.substring(
|
|
4816
|
+
seg_prefix,
|
|
4817
|
+
n_seg.length - seg_suffix
|
|
4818
|
+
);
|
|
4819
|
+
const seg_start = seg_offset + seg_prefix;
|
|
4820
|
+
let seg_op;
|
|
4821
|
+
if (!seg_target && seg_new) seg_op = "INSERTION";
|
|
4822
|
+
else if (seg_target && !seg_new) seg_op = "DELETION";
|
|
4823
|
+
else if (seg_target && seg_new) seg_op = "MODIFICATION";
|
|
4824
|
+
else seg_op = "COMMENT_ONLY";
|
|
4825
|
+
const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
|
|
4826
|
+
if (seg_comment) comment_assigned = true;
|
|
4827
|
+
split_sub_edits.push({
|
|
4828
|
+
type: "modify",
|
|
4829
|
+
target_text: seg_target,
|
|
4830
|
+
new_text: seg_new,
|
|
4831
|
+
comment: seg_comment,
|
|
4832
|
+
_match_start_index: seg_start,
|
|
4833
|
+
_internal_op: seg_op,
|
|
4834
|
+
_active_mapper_ref: active_mapper,
|
|
4835
|
+
_split_group_id: start_idx
|
|
4836
|
+
});
|
|
4837
|
+
}
|
|
4838
|
+
seg_offset += t_seg.length + 2;
|
|
4839
|
+
}
|
|
4840
|
+
if (split_sub_edits.length > 0) {
|
|
4841
|
+
for (const sub of split_sub_edits) all_sub_edits.push(sub);
|
|
4842
|
+
continue;
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4725
4845
|
if (!final_target && final_new) effective_op = "INSERTION";
|
|
4726
4846
|
else if (final_target && !final_new) effective_op = "DELETION";
|
|
4727
4847
|
else if (final_target && final_new) effective_op = "MODIFICATION";
|
|
@@ -4741,6 +4861,35 @@ var RedlineEngine = class {
|
|
|
4741
4861
|
if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
|
|
4742
4862
|
return all_sub_edits[0];
|
|
4743
4863
|
}
|
|
4864
|
+
/**
|
|
4865
|
+
* Split a <w:ins> so that everything up to and INCLUDING split_after stays in
|
|
4866
|
+
* a left <w:ins>, new_elem is placed between, and the remainder moves to a
|
|
4867
|
+
* right <w:ins> — all at the grandparent level. Used when revising another
|
|
4868
|
+
* author's pending insertion: the <w:del> stays nested in their <w:ins> while
|
|
4869
|
+
* our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
|
|
4870
|
+
* <w:ins>.
|
|
4871
|
+
*/
|
|
4872
|
+
_insert_and_split_ins(parent_ins, split_after, new_elem) {
|
|
4873
|
+
const grandparent = parent_ins.parentNode;
|
|
4874
|
+
if (!grandparent) return;
|
|
4875
|
+
const left = parent_ins.cloneNode(false);
|
|
4876
|
+
const right = parent_ins.cloneNode(false);
|
|
4877
|
+
let toRight = false;
|
|
4878
|
+
for (const kid of Array.from(parent_ins.childNodes)) {
|
|
4879
|
+
parent_ins.removeChild(kid);
|
|
4880
|
+
if (!toRight) {
|
|
4881
|
+
left.appendChild(kid);
|
|
4882
|
+
if (kid === split_after) toRight = true;
|
|
4883
|
+
} else {
|
|
4884
|
+
right.appendChild(kid);
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
if (left.childNodes.length > 0) grandparent.insertBefore(left, parent_ins);
|
|
4888
|
+
grandparent.insertBefore(new_elem, parent_ins);
|
|
4889
|
+
if (right.childNodes.length > 0)
|
|
4890
|
+
grandparent.insertBefore(right, parent_ins);
|
|
4891
|
+
grandparent.removeChild(parent_ins);
|
|
4892
|
+
}
|
|
4744
4893
|
_apply_single_edit_indexed(edit, orig_new, rebuild_map) {
|
|
4745
4894
|
let op = edit._internal_op;
|
|
4746
4895
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
@@ -4948,7 +5097,16 @@ var RedlineEngine = class {
|
|
|
4948
5097
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
4949
5098
|
if (is_inline_first) {
|
|
4950
5099
|
if (anchor_run) {
|
|
4951
|
-
|
|
5100
|
+
const anchor_parent = anchor_run._element.parentNode;
|
|
5101
|
+
if (anchor_parent && anchor_parent.tagName === "w:ins") {
|
|
5102
|
+
this._insert_and_split_ins(
|
|
5103
|
+
anchor_parent,
|
|
5104
|
+
anchor_run._element,
|
|
5105
|
+
result.first_node
|
|
5106
|
+
);
|
|
5107
|
+
} else {
|
|
5108
|
+
insertAfter(result.first_node, anchor_run._element);
|
|
5109
|
+
}
|
|
4952
5110
|
} else if (anchor_para) {
|
|
4953
5111
|
anchor_para._element.appendChild(result.first_node);
|
|
4954
5112
|
}
|
|
@@ -5058,7 +5216,12 @@ var RedlineEngine = class {
|
|
|
5058
5216
|
if (result.first_node) {
|
|
5059
5217
|
const is_inline_first = result.first_node.tagName === "w:ins";
|
|
5060
5218
|
if (is_inline_first) {
|
|
5061
|
-
|
|
5219
|
+
const del_parent = last_del.parentNode;
|
|
5220
|
+
if (del_parent && del_parent.tagName === "w:ins") {
|
|
5221
|
+
this._insert_and_split_ins(del_parent, last_del, result.first_node);
|
|
5222
|
+
} else {
|
|
5223
|
+
insertAfter(result.first_node, last_del);
|
|
5224
|
+
}
|
|
5062
5225
|
ins_elem = result.first_node;
|
|
5063
5226
|
} else {
|
|
5064
5227
|
ins_elem = result.last_ins;
|