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