@adeu/core 1.18.5 → 1.19.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 CHANGED
@@ -3081,6 +3081,75 @@ var RedlineEngine = class {
3081
3081
  }
3082
3082
  return null;
3083
3083
  }
3084
+ _word_diff_sub_edits(target_str, new_str, base_offset, parent_comment = null, is_table = false, active_mapper = null) {
3085
+ let raw_sub_edits = [];
3086
+ try {
3087
+ raw_sub_edits = generate_edits_from_text(target_str, new_str);
3088
+ } catch (e) {
3089
+ console.error("generate_edits_from_text failed, falling back to wholesale edit", e);
3090
+ raw_sub_edits = [];
3091
+ }
3092
+ if (!raw_sub_edits || raw_sub_edits.length === 0) {
3093
+ const fallback_edit = {
3094
+ type: "modify",
3095
+ target_text: target_str,
3096
+ new_text: new_str,
3097
+ comment: parent_comment
3098
+ };
3099
+ fallback_edit._resolved_start_idx = base_offset;
3100
+ fallback_edit._match_start_index = base_offset;
3101
+ fallback_edit._active_mapper_ref = active_mapper;
3102
+ if (is_table) {
3103
+ fallback_edit._is_table_edit = true;
3104
+ }
3105
+ if (target_str === new_str) {
3106
+ fallback_edit._internal_op = "COMMENT_ONLY";
3107
+ } else if (!target_str && new_str) {
3108
+ fallback_edit._internal_op = "INSERTION";
3109
+ } else if (target_str && !new_str) {
3110
+ fallback_edit._internal_op = "DELETION";
3111
+ } else if (target_str && new_str) {
3112
+ fallback_edit._internal_op = "MODIFICATION";
3113
+ } else {
3114
+ fallback_edit._internal_op = "COMMENT_ONLY";
3115
+ }
3116
+ return [fallback_edit];
3117
+ }
3118
+ const sub_edits = [];
3119
+ let comment_assigned = false;
3120
+ for (const raw_edit of raw_sub_edits) {
3121
+ const sub_start = base_offset + (raw_edit._match_start_index || 0);
3122
+ const should_attach_comment = parent_comment !== null && !comment_assigned;
3123
+ if (should_attach_comment) {
3124
+ comment_assigned = true;
3125
+ }
3126
+ const sub_edit = {
3127
+ type: "modify",
3128
+ target_text: raw_edit.target_text,
3129
+ new_text: raw_edit.new_text,
3130
+ comment: should_attach_comment ? parent_comment : null
3131
+ };
3132
+ sub_edit._resolved_start_idx = sub_start;
3133
+ sub_edit._match_start_index = sub_start;
3134
+ sub_edit._active_mapper_ref = active_mapper;
3135
+ if (is_table) {
3136
+ sub_edit._is_table_edit = true;
3137
+ }
3138
+ const t_val = raw_edit.target_text;
3139
+ const n_val = raw_edit.new_text;
3140
+ if (!t_val && n_val) {
3141
+ sub_edit._internal_op = "INSERTION";
3142
+ } else if (t_val && !n_val) {
3143
+ sub_edit._internal_op = "DELETION";
3144
+ } else if (t_val && n_val) {
3145
+ sub_edit._internal_op = "MODIFICATION";
3146
+ } else {
3147
+ sub_edit._internal_op = "COMMENT_ONLY";
3148
+ }
3149
+ sub_edits.push(sub_edit);
3150
+ }
3151
+ return sub_edits;
3152
+ }
3084
3153
  /**
3085
3154
  * Best-effort "did you mean" hint for a failed target. The common loop trap
3086
3155
  * (observed in the field) is an anchored regex like `^\( x \)$` against a
@@ -4010,6 +4079,8 @@ var RedlineEngine = class {
4010
4079
  continue;
4011
4080
  }
4012
4081
  if (!edit.target_text) continue;
4082
+ if (edit._match_start_index !== void 0 && edit._match_start_index !== null || edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null)
4083
+ continue;
4013
4084
  const is_regex = edit.regex || false;
4014
4085
  const match_mode = edit.match_mode || "strict";
4015
4086
  let matches = this.mapper.find_all_match_indices(
@@ -4313,16 +4384,31 @@ var RedlineEngine = class {
4313
4384
  let applied_edits = 0;
4314
4385
  let skipped_edits = 0;
4315
4386
  if (edits.length > 0) {
4387
+ const pinned_idx = (e) => {
4388
+ if (e._resolved_start_idx !== void 0 && e._resolved_start_idx !== null)
4389
+ return e._resolved_start_idx;
4390
+ if (e._match_start_index !== void 0 && e._match_start_index !== null)
4391
+ return e._match_start_index;
4392
+ return null;
4393
+ };
4394
+ const ordered_edits = edits.map((edit, i) => ({ edit, i })).sort((a, b) => {
4395
+ const ka = pinned_idx(a.edit);
4396
+ const kb = pinned_idx(b.edit);
4397
+ if (ka === null && kb === null) return a.i - b.i;
4398
+ if (ka === null) return 1;
4399
+ if (kb === null) return -1;
4400
+ return kb - ka || a.i - b.i;
4401
+ });
4316
4402
  if (dry_run_mode) {
4317
- for (let i = 0; i < edits.length; i++) {
4318
- const edit = edits[i];
4403
+ const reports_by_input = new Array(edits.length);
4404
+ for (const { edit, i } of ordered_edits) {
4319
4405
  const single_errors = this.validate_edits([edit], i);
4320
4406
  if (single_errors.length > 0) {
4321
4407
  skipped_edits++;
4322
4408
  const warning = this._check_punctuation_warning(
4323
4409
  edit.target_text || ""
4324
4410
  );
4325
- edits_reports.push({
4411
+ reports_by_input[i] = {
4326
4412
  status: "failed",
4327
4413
  target_text: edit.target_text || "",
4328
4414
  new_text: edit.new_text || "",
@@ -4330,14 +4416,14 @@ var RedlineEngine = class {
4330
4416
  error: single_errors[0],
4331
4417
  critic_markup: null,
4332
4418
  clean_text: null
4333
- });
4419
+ };
4334
4420
  continue;
4335
4421
  }
4336
4422
  const res = this.apply_edits([edit], page_offsets);
4337
4423
  if (edit._applied_status) {
4338
4424
  applied_edits++;
4339
4425
  const previews = this._build_edit_context_previews(edit);
4340
- edits_reports.push({
4426
+ reports_by_input[i] = {
4341
4427
  status: "applied",
4342
4428
  target_text: edit.target_text || "",
4343
4429
  new_text: edit.new_text || "",
@@ -4349,7 +4435,7 @@ var RedlineEngine = class {
4349
4435
  heading_path: edit._heading_path || "",
4350
4436
  occurrences_modified: edit._occurrences_modified || 0,
4351
4437
  match_mode: edit.match_mode || "strict"
4352
- });
4438
+ };
4353
4439
  this.mapper = new DocumentMapper(this.doc);
4354
4440
  this.clean_mapper = null;
4355
4441
  } else {
@@ -4358,7 +4444,7 @@ var RedlineEngine = class {
4358
4444
  const warning = this._check_punctuation_warning(
4359
4445
  edit.target_text || ""
4360
4446
  );
4361
- edits_reports.push({
4447
+ reports_by_input[i] = {
4362
4448
  status: "failed",
4363
4449
  target_text: edit.target_text || "",
4364
4450
  new_text: edit.new_text || "",
@@ -4366,16 +4452,16 @@ var RedlineEngine = class {
4366
4452
  error: error_msg,
4367
4453
  critic_markup: null,
4368
4454
  clean_text: null
4369
- });
4455
+ };
4370
4456
  }
4371
4457
  }
4458
+ edits_reports.push(...reports_by_input);
4372
4459
  } else {
4373
4460
  const snapshot = takeSnapshot(this.doc);
4374
4461
  const originalCurrentId = this.current_id;
4375
4462
  try {
4376
4463
  const sequential_errors = [];
4377
- for (let i = 0; i < edits.length; i++) {
4378
- const edit = edits[i];
4464
+ for (const { edit, i } of ordered_edits) {
4379
4465
  const single_errors = this.validate_edits([edit], i);
4380
4466
  if (single_errors.length > 0) {
4381
4467
  sequential_errors.push(...single_errors);
@@ -4543,7 +4629,8 @@ var RedlineEngine = class {
4543
4629
  }
4544
4630
  let success = false;
4545
4631
  if (edit.type === "modify") {
4546
- success = this._apply_single_edit_indexed(edit, orig_new, false);
4632
+ const rebuild = edit._split_group_id !== void 0 && edit._split_group_id !== null;
4633
+ success = this._apply_single_edit_indexed(edit, orig_new, rebuild);
4547
4634
  } else if (edit.type === "insert_row" || edit.type === "delete_row") {
4548
4635
  success = this._apply_table_edit(edit, false);
4549
4636
  }
@@ -4851,15 +4938,87 @@ var RedlineEngine = class {
4851
4938
  });
4852
4939
  continue;
4853
4940
  }
4941
+ let overlaps_virtual_pipe = false;
4942
+ if (active_mapper) {
4943
+ overlaps_virtual_pipe = active_mapper.spans.some(
4944
+ (s) => s.text === " | " && (s.run === null || s.run === void 0) && s.start < start_idx + match_len && s.end > start_idx
4945
+ );
4946
+ }
4947
+ if (overlaps_virtual_pipe) {
4948
+ const actual_cells = actual_doc_text.split("|");
4949
+ const new_cells = current_effective_new_text.split("|");
4950
+ if (actual_cells.length === new_cells.length && actual_cells.length > 1) {
4951
+ const sub_edits2 = [];
4952
+ let search_offset = start_idx;
4953
+ let target_comment_idx = 0;
4954
+ for (let idx = 0; idx < actual_cells.length; idx++) {
4955
+ if (actual_cells[idx].trim() !== new_cells[idx].trim()) {
4956
+ target_comment_idx = idx;
4957
+ break;
4958
+ }
4959
+ }
4960
+ for (let cell_idx = 0; cell_idx < actual_cells.length; cell_idx++) {
4961
+ const a_cell = actual_cells[cell_idx];
4962
+ const n_cell = new_cells[cell_idx];
4963
+ const a_clean = a_cell.trim();
4964
+ const n_clean = n_cell.trim();
4965
+ let actual_start = search_offset;
4966
+ if (a_clean) {
4967
+ actual_start = this.mapper.full_text.indexOf(a_clean, search_offset);
4968
+ if (actual_start === -1 || actual_start > search_offset + 10) {
4969
+ actual_start = search_offset;
4970
+ }
4971
+ }
4972
+ const should_attach_comment = edit.comment !== null && edit.comment !== void 0 && cell_idx === target_comment_idx;
4973
+ if (a_clean !== n_clean || should_attach_comment) {
4974
+ const cell_sub_edits = this._word_diff_sub_edits(
4975
+ a_clean,
4976
+ n_clean,
4977
+ actual_start,
4978
+ should_attach_comment ? edit.comment : null,
4979
+ true,
4980
+ active_mapper
4981
+ );
4982
+ for (const se of cell_sub_edits) {
4983
+ se._original_target_text = edit.target_text;
4984
+ se._split_group_id = start_idx;
4985
+ sub_edits2.push(se);
4986
+ }
4987
+ }
4988
+ if (a_clean) {
4989
+ search_offset = actual_start + a_clean.length;
4990
+ }
4991
+ const next_pipe = this.mapper.full_text.indexOf(" | ", search_offset);
4992
+ if (next_pipe !== -1 && next_pipe <= search_offset + 10) {
4993
+ search_offset = next_pipe + 3;
4994
+ } else {
4995
+ search_offset += a_cell.length + 1;
4996
+ }
4997
+ }
4998
+ for (const sub of sub_edits2) {
4999
+ all_sub_edits.push(sub);
5000
+ }
5001
+ continue;
5002
+ } else {
5003
+ throw new BatchValidationError([
5004
+ `Target text spans ${actual_cells.length} table cells, but replacement provides ${new_cells.length}. To modify text without altering table structure (rows or columns), ensure the replacement contains the exact same number of '|' separators (e.g., replace with 'CellC | ' to empty the second cell).`
5005
+ ]);
5006
+ }
5007
+ }
5008
+ let has_markdown = false;
5009
+ if (edit.target_text && (edit.target_text.includes("**") || edit.target_text.includes("_"))) {
5010
+ has_markdown = true;
5011
+ }
5012
+ if (current_effective_new_text && (current_effective_new_text.includes("**") || current_effective_new_text.includes("_"))) {
5013
+ has_markdown = true;
5014
+ }
4854
5015
  let effective_op = "";
4855
5016
  let final_target = "";
4856
5017
  let final_new = "";
4857
5018
  let effective_start_idx = start_idx;
4858
5019
  if (current_effective_new_text.startsWith(actual_doc_text)) {
4859
5020
  effective_op = "INSERTION";
4860
- final_new = current_effective_new_text.substring(
4861
- actual_doc_text.length
4862
- );
5021
+ final_new = current_effective_new_text.substring(actual_doc_text.length);
4863
5022
  effective_start_idx = start_idx + match_len;
4864
5023
  } else {
4865
5024
  const [prefix_len, suffix_len] = trim_common_context(
@@ -4871,68 +5030,84 @@ var RedlineEngine = class {
4871
5030
  final_target = actual_doc_text.substring(prefix_len, t_end);
4872
5031
  final_new = current_effective_new_text.substring(prefix_len, n_end);
4873
5032
  effective_start_idx = start_idx + prefix_len;
4874
- const target_segs = actual_doc_text.split("\n\n");
4875
- const new_segs = current_effective_new_text.split("\n\n");
4876
- if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
4877
- const split_sub_edits = [];
4878
- let seg_offset = start_idx;
4879
- let comment_assigned = false;
4880
- for (let k = 0; k < target_segs.length; k++) {
4881
- const t_seg = target_segs[k];
4882
- const n_seg = new_segs[k];
4883
- if (t_seg !== n_seg) {
4884
- const [seg_prefix, seg_suffix] = trim_common_context(
4885
- t_seg,
4886
- n_seg
4887
- );
4888
- const seg_target = t_seg.substring(
4889
- seg_prefix,
4890
- t_seg.length - seg_suffix
4891
- );
4892
- const seg_new = n_seg.substring(
4893
- seg_prefix,
4894
- n_seg.length - seg_suffix
4895
- );
4896
- const seg_start = seg_offset + seg_prefix;
4897
- let seg_op;
4898
- if (!seg_target && seg_new) seg_op = "INSERTION";
4899
- else if (seg_target && !seg_new) seg_op = "DELETION";
4900
- else if (seg_target && seg_new) seg_op = "MODIFICATION";
4901
- else seg_op = "COMMENT_ONLY";
4902
- const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
4903
- if (seg_comment) comment_assigned = true;
4904
- split_sub_edits.push({
4905
- type: "modify",
4906
- target_text: seg_target,
4907
- new_text: seg_new,
4908
- comment: seg_comment,
4909
- _match_start_index: seg_start,
4910
- _internal_op: seg_op,
4911
- _active_mapper_ref: active_mapper,
4912
- _split_group_id: start_idx
4913
- });
5033
+ }
5034
+ if (has_markdown) {
5035
+ if (!final_target && final_new) {
5036
+ effective_op = "INSERTION";
5037
+ } else if (final_target && !final_new) {
5038
+ effective_op = "DELETION";
5039
+ } else if (final_target && final_new) {
5040
+ effective_op = "MODIFICATION";
5041
+ } else {
5042
+ all_sub_edits.push({
5043
+ type: "modify",
5044
+ target_text: final_target,
5045
+ new_text: final_new,
5046
+ comment: edit.comment,
5047
+ _match_start_index: effective_start_idx,
5048
+ _internal_op: "COMMENT_ONLY",
5049
+ _active_mapper_ref: active_mapper
5050
+ });
5051
+ continue;
5052
+ }
5053
+ all_sub_edits.push({
5054
+ type: "modify",
5055
+ target_text: final_target,
5056
+ new_text: final_new,
5057
+ comment: edit.comment,
5058
+ _resolved_start_idx: effective_start_idx,
5059
+ _match_start_index: effective_start_idx,
5060
+ _internal_op: effective_op,
5061
+ _active_mapper_ref: active_mapper
5062
+ });
5063
+ continue;
5064
+ }
5065
+ const target_segs = actual_doc_text.split("\n\n");
5066
+ const new_segs = current_effective_new_text.split("\n\n");
5067
+ if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
5068
+ const split_sub_edits = [];
5069
+ let seg_offset = start_idx;
5070
+ let comment_assigned = false;
5071
+ for (let k = 0; k < target_segs.length; k++) {
5072
+ const t_seg = target_segs[k];
5073
+ const n_seg = new_segs[k];
5074
+ if (t_seg !== n_seg) {
5075
+ const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
5076
+ const seg_sub_edits = this._word_diff_sub_edits(
5077
+ t_seg,
5078
+ n_seg,
5079
+ seg_offset,
5080
+ seg_comment,
5081
+ false,
5082
+ active_mapper
5083
+ );
5084
+ if (seg_sub_edits.some((se) => se.comment !== null && se.comment !== void 0)) {
5085
+ comment_assigned = true;
5086
+ }
5087
+ for (const se of seg_sub_edits) {
5088
+ se._split_group_id = start_idx;
5089
+ split_sub_edits.push(se);
4914
5090
  }
4915
- seg_offset += t_seg.length + 2;
4916
- }
4917
- if (split_sub_edits.length > 0) {
4918
- for (const sub of split_sub_edits) all_sub_edits.push(sub);
4919
- continue;
4920
5091
  }
5092
+ seg_offset += t_seg.length + 2;
5093
+ }
5094
+ if (split_sub_edits.length > 0) {
5095
+ for (const sub of split_sub_edits) all_sub_edits.push(sub);
5096
+ continue;
4921
5097
  }
4922
- if (!final_target && final_new) effective_op = "INSERTION";
4923
- else if (final_target && !final_new) effective_op = "DELETION";
4924
- else if (final_target && final_new) effective_op = "MODIFICATION";
4925
- else effective_op = "COMMENT_ONLY";
4926
5098
  }
4927
- all_sub_edits.push({
4928
- type: "modify",
4929
- target_text: final_target,
4930
- new_text: final_new,
4931
- comment: edit.comment,
4932
- _match_start_index: effective_start_idx,
4933
- _internal_op: effective_op,
4934
- _active_mapper_ref: active_mapper
4935
- });
5099
+ const sub_edits = this._word_diff_sub_edits(
5100
+ actual_doc_text,
5101
+ current_effective_new_text,
5102
+ start_idx,
5103
+ edit.comment,
5104
+ false,
5105
+ active_mapper
5106
+ );
5107
+ for (const se of sub_edits) {
5108
+ se._split_group_id = start_idx;
5109
+ all_sub_edits.push(se);
5110
+ }
4936
5111
  }
4937
5112
  if (all_sub_edits.length === 0) return null;
4938
5113
  if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
@@ -4946,7 +5121,7 @@ var RedlineEngine = class {
4946
5121
  * our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
4947
5122
  * <w:ins>.
4948
5123
  */
4949
- _insert_and_split_ins(parent_ins, split_after, new_elem) {
5124
+ _insert_and_split_ins(parent_ins, anchor, new_elem, split_before = false) {
4950
5125
  const grandparent = parent_ins.parentNode;
4951
5126
  if (!grandparent) return;
4952
5127
  const left = parent_ins.cloneNode(false);
@@ -4954,9 +5129,10 @@ var RedlineEngine = class {
4954
5129
  let toRight = false;
4955
5130
  for (const kid of Array.from(parent_ins.childNodes)) {
4956
5131
  parent_ins.removeChild(kid);
5132
+ if (split_before && kid === anchor) toRight = true;
4957
5133
  if (!toRight) {
4958
5134
  left.appendChild(kid);
4959
- if (kid === split_after) toRight = true;
5135
+ if (kid === anchor) toRight = true;
4960
5136
  } else {
4961
5137
  right.appendChild(kid);
4962
5138
  }
@@ -4972,6 +5148,15 @@ var RedlineEngine = class {
4972
5148
  const active_mapper = edit._active_mapper_ref || this.mapper;
4973
5149
  const start_idx = edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null ? edit._resolved_start_idx : edit._match_start_index || 0;
4974
5150
  const length = edit.target_text ? edit.target_text.length : 0;
5151
+ if (op === void 0 || op === null) {
5152
+ if (!edit.target_text && edit.new_text) {
5153
+ op = "INSERTION";
5154
+ } else if (edit.target_text && !edit.new_text) {
5155
+ op = "DELETION";
5156
+ } else {
5157
+ op = "MODIFICATION";
5158
+ }
5159
+ }
4975
5160
  if (op === "STYLE_ONLY" || op === "STYLE_AND_TEXT") {
4976
5161
  const [anchor_run, anchor_para] = active_mapper.get_insertion_anchor(
4977
5162
  start_idx,
@@ -5175,17 +5360,26 @@ var RedlineEngine = class {
5175
5360
  if (is_inline_first) {
5176
5361
  if (anchor_run) {
5177
5362
  const anchor_parent = anchor_run._element.parentNode;
5363
+ const before_anchor = start_idx === 0;
5178
5364
  if (anchor_parent && anchor_parent.tagName === "w:ins") {
5179
5365
  this._insert_and_split_ins(
5180
5366
  anchor_parent,
5181
5367
  anchor_run._element,
5182
- result.first_node
5368
+ result.first_node,
5369
+ before_anchor
5183
5370
  );
5371
+ } else if (before_anchor && anchor_parent) {
5372
+ anchor_parent.insertBefore(result.first_node, anchor_run._element);
5184
5373
  } else {
5185
5374
  insertAfter(result.first_node, anchor_run._element);
5186
5375
  }
5187
5376
  } else if (anchor_para) {
5188
- anchor_para._element.appendChild(result.first_node);
5377
+ const para_el = anchor_para._element;
5378
+ let ref = para_el.firstChild;
5379
+ while (ref && ref.tagName === "w:pPr") {
5380
+ ref = ref.nextSibling;
5381
+ }
5382
+ para_el.insertBefore(result.first_node, ref);
5189
5383
  }
5190
5384
  }
5191
5385
  if (edit.comment) {