@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.d.cts CHANGED
@@ -196,6 +196,7 @@ declare class RedlineEngine {
196
196
  * and the edit succeeds despite it. Mirrors the Python engine.
197
197
  */
198
198
  private _check_punctuation_warning;
199
+ private _word_diff_sub_edits;
199
200
  /**
200
201
  * Best-effort "did you mean" hint for a failed target. The common loop trap
201
202
  * (observed in the field) is an anchored regex like `^\( x \)$` against a
package/dist/index.d.ts CHANGED
@@ -196,6 +196,7 @@ declare class RedlineEngine {
196
196
  * and the edit succeeds despite it. Mirrors the Python engine.
197
197
  */
198
198
  private _check_punctuation_warning;
199
+ private _word_diff_sub_edits;
199
200
  /**
200
201
  * Best-effort "did you mean" hint for a failed target. The common loop trap
201
202
  * (observed in the field) is an anchored regex like `^\( x \)$` against a
package/dist/index.js CHANGED
@@ -3030,6 +3030,76 @@ var RedlineEngine = class {
3030
3030
  }
3031
3031
  return null;
3032
3032
  }
3033
+ _word_diff_sub_edits(target_str, new_str, base_offset, parent_comment = null, is_table = false, active_mapper = null) {
3034
+ let raw_sub_edits = [];
3035
+ try {
3036
+ raw_sub_edits = generate_edits_from_text(target_str, new_str);
3037
+ console.log("_word_diff_sub_edits RAW_SUB_EDITS:", JSON.stringify(raw_sub_edits, null, 2));
3038
+ } catch (e) {
3039
+ console.warn("generate_edits_from_text failed, falling back to wholesale edit", e);
3040
+ raw_sub_edits = [];
3041
+ }
3042
+ if (!raw_sub_edits || raw_sub_edits.length === 0) {
3043
+ const fallback_edit = {
3044
+ type: "modify",
3045
+ target_text: target_str,
3046
+ new_text: new_str,
3047
+ comment: parent_comment
3048
+ };
3049
+ fallback_edit._resolved_start_idx = base_offset;
3050
+ fallback_edit._match_start_index = base_offset;
3051
+ fallback_edit._active_mapper_ref = active_mapper;
3052
+ if (is_table) {
3053
+ fallback_edit._is_table_edit = true;
3054
+ }
3055
+ if (target_str === new_str) {
3056
+ fallback_edit._internal_op = "COMMENT_ONLY";
3057
+ } else if (!target_str && new_str) {
3058
+ fallback_edit._internal_op = "INSERTION";
3059
+ } else if (target_str && !new_str) {
3060
+ fallback_edit._internal_op = "DELETION";
3061
+ } else if (target_str && new_str) {
3062
+ fallback_edit._internal_op = "MODIFICATION";
3063
+ } else {
3064
+ fallback_edit._internal_op = "COMMENT_ONLY";
3065
+ }
3066
+ return [fallback_edit];
3067
+ }
3068
+ const sub_edits = [];
3069
+ let comment_assigned = false;
3070
+ for (const raw_edit of raw_sub_edits) {
3071
+ const sub_start = base_offset + (raw_edit._match_start_index || 0);
3072
+ const should_attach_comment = parent_comment !== null && !comment_assigned;
3073
+ if (should_attach_comment) {
3074
+ comment_assigned = true;
3075
+ }
3076
+ const sub_edit = {
3077
+ type: "modify",
3078
+ target_text: raw_edit.target_text,
3079
+ new_text: raw_edit.new_text,
3080
+ comment: should_attach_comment ? parent_comment : null
3081
+ };
3082
+ sub_edit._resolved_start_idx = sub_start;
3083
+ sub_edit._match_start_index = sub_start;
3084
+ sub_edit._active_mapper_ref = active_mapper;
3085
+ if (is_table) {
3086
+ sub_edit._is_table_edit = true;
3087
+ }
3088
+ const t_val = raw_edit.target_text;
3089
+ const n_val = raw_edit.new_text;
3090
+ if (!t_val && n_val) {
3091
+ sub_edit._internal_op = "INSERTION";
3092
+ } else if (t_val && !n_val) {
3093
+ sub_edit._internal_op = "DELETION";
3094
+ } else if (t_val && n_val) {
3095
+ sub_edit._internal_op = "MODIFICATION";
3096
+ } else {
3097
+ sub_edit._internal_op = "COMMENT_ONLY";
3098
+ }
3099
+ sub_edits.push(sub_edit);
3100
+ }
3101
+ return sub_edits;
3102
+ }
3033
3103
  /**
3034
3104
  * Best-effort "did you mean" hint for a failed target. The common loop trap
3035
3105
  * (observed in the field) is an anchored regex like `^\( x \)$` against a
@@ -3959,6 +4029,8 @@ var RedlineEngine = class {
3959
4029
  continue;
3960
4030
  }
3961
4031
  if (!edit.target_text) continue;
4032
+ if (edit._match_start_index !== void 0 && edit._match_start_index !== null || edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null)
4033
+ continue;
3962
4034
  const is_regex = edit.regex || false;
3963
4035
  const match_mode = edit.match_mode || "strict";
3964
4036
  let matches = this.mapper.find_all_match_indices(
@@ -4262,16 +4334,31 @@ var RedlineEngine = class {
4262
4334
  let applied_edits = 0;
4263
4335
  let skipped_edits = 0;
4264
4336
  if (edits.length > 0) {
4337
+ const pinned_idx = (e) => {
4338
+ if (e._resolved_start_idx !== void 0 && e._resolved_start_idx !== null)
4339
+ return e._resolved_start_idx;
4340
+ if (e._match_start_index !== void 0 && e._match_start_index !== null)
4341
+ return e._match_start_index;
4342
+ return null;
4343
+ };
4344
+ const ordered_edits = edits.map((edit, i) => ({ edit, i })).sort((a, b) => {
4345
+ const ka = pinned_idx(a.edit);
4346
+ const kb = pinned_idx(b.edit);
4347
+ if (ka === null && kb === null) return a.i - b.i;
4348
+ if (ka === null) return 1;
4349
+ if (kb === null) return -1;
4350
+ return kb - ka || a.i - b.i;
4351
+ });
4265
4352
  if (dry_run_mode) {
4266
- for (let i = 0; i < edits.length; i++) {
4267
- const edit = edits[i];
4353
+ const reports_by_input = new Array(edits.length);
4354
+ for (const { edit, i } of ordered_edits) {
4268
4355
  const single_errors = this.validate_edits([edit], i);
4269
4356
  if (single_errors.length > 0) {
4270
4357
  skipped_edits++;
4271
4358
  const warning = this._check_punctuation_warning(
4272
4359
  edit.target_text || ""
4273
4360
  );
4274
- edits_reports.push({
4361
+ reports_by_input[i] = {
4275
4362
  status: "failed",
4276
4363
  target_text: edit.target_text || "",
4277
4364
  new_text: edit.new_text || "",
@@ -4279,14 +4366,14 @@ var RedlineEngine = class {
4279
4366
  error: single_errors[0],
4280
4367
  critic_markup: null,
4281
4368
  clean_text: null
4282
- });
4369
+ };
4283
4370
  continue;
4284
4371
  }
4285
4372
  const res = this.apply_edits([edit], page_offsets);
4286
4373
  if (edit._applied_status) {
4287
4374
  applied_edits++;
4288
4375
  const previews = this._build_edit_context_previews(edit);
4289
- edits_reports.push({
4376
+ reports_by_input[i] = {
4290
4377
  status: "applied",
4291
4378
  target_text: edit.target_text || "",
4292
4379
  new_text: edit.new_text || "",
@@ -4298,7 +4385,7 @@ var RedlineEngine = class {
4298
4385
  heading_path: edit._heading_path || "",
4299
4386
  occurrences_modified: edit._occurrences_modified || 0,
4300
4387
  match_mode: edit.match_mode || "strict"
4301
- });
4388
+ };
4302
4389
  this.mapper = new DocumentMapper(this.doc);
4303
4390
  this.clean_mapper = null;
4304
4391
  } else {
@@ -4307,7 +4394,7 @@ var RedlineEngine = class {
4307
4394
  const warning = this._check_punctuation_warning(
4308
4395
  edit.target_text || ""
4309
4396
  );
4310
- edits_reports.push({
4397
+ reports_by_input[i] = {
4311
4398
  status: "failed",
4312
4399
  target_text: edit.target_text || "",
4313
4400
  new_text: edit.new_text || "",
@@ -4315,16 +4402,16 @@ var RedlineEngine = class {
4315
4402
  error: error_msg,
4316
4403
  critic_markup: null,
4317
4404
  clean_text: null
4318
- });
4405
+ };
4319
4406
  }
4320
4407
  }
4408
+ edits_reports.push(...reports_by_input);
4321
4409
  } else {
4322
4410
  const snapshot = takeSnapshot(this.doc);
4323
4411
  const originalCurrentId = this.current_id;
4324
4412
  try {
4325
4413
  const sequential_errors = [];
4326
- for (let i = 0; i < edits.length; i++) {
4327
- const edit = edits[i];
4414
+ for (const { edit, i } of ordered_edits) {
4328
4415
  const single_errors = this.validate_edits([edit], i);
4329
4416
  if (single_errors.length > 0) {
4330
4417
  sequential_errors.push(...single_errors);
@@ -4492,7 +4579,8 @@ var RedlineEngine = class {
4492
4579
  }
4493
4580
  let success = false;
4494
4581
  if (edit.type === "modify") {
4495
- success = this._apply_single_edit_indexed(edit, orig_new, false);
4582
+ const rebuild = edit._split_group_id !== void 0 && edit._split_group_id !== null;
4583
+ success = this._apply_single_edit_indexed(edit, orig_new, rebuild);
4496
4584
  } else if (edit.type === "insert_row" || edit.type === "delete_row") {
4497
4585
  success = this._apply_table_edit(edit, false);
4498
4586
  }
@@ -4800,15 +4888,87 @@ var RedlineEngine = class {
4800
4888
  });
4801
4889
  continue;
4802
4890
  }
4891
+ let overlaps_virtual_pipe = false;
4892
+ if (active_mapper) {
4893
+ overlaps_virtual_pipe = active_mapper.spans.some(
4894
+ (s) => s.text === " | " && (s.run === null || s.run === void 0) && s.start < start_idx + match_len && s.end > start_idx
4895
+ );
4896
+ }
4897
+ if (overlaps_virtual_pipe) {
4898
+ const actual_cells = actual_doc_text.split("|");
4899
+ const new_cells = current_effective_new_text.split("|");
4900
+ if (actual_cells.length === new_cells.length && actual_cells.length > 1) {
4901
+ const sub_edits2 = [];
4902
+ let search_offset = start_idx;
4903
+ let target_comment_idx = 0;
4904
+ for (let idx = 0; idx < actual_cells.length; idx++) {
4905
+ if (actual_cells[idx].trim() !== new_cells[idx].trim()) {
4906
+ target_comment_idx = idx;
4907
+ break;
4908
+ }
4909
+ }
4910
+ for (let cell_idx = 0; cell_idx < actual_cells.length; cell_idx++) {
4911
+ const a_cell = actual_cells[cell_idx];
4912
+ const n_cell = new_cells[cell_idx];
4913
+ const a_clean = a_cell.trim();
4914
+ const n_clean = n_cell.trim();
4915
+ let actual_start = search_offset;
4916
+ if (a_clean) {
4917
+ actual_start = this.mapper.full_text.indexOf(a_clean, search_offset);
4918
+ if (actual_start === -1 || actual_start > search_offset + 10) {
4919
+ actual_start = search_offset;
4920
+ }
4921
+ }
4922
+ const should_attach_comment = edit.comment !== null && edit.comment !== void 0 && cell_idx === target_comment_idx;
4923
+ if (a_clean !== n_clean || should_attach_comment) {
4924
+ const cell_sub_edits = this._word_diff_sub_edits(
4925
+ a_clean,
4926
+ n_clean,
4927
+ actual_start,
4928
+ should_attach_comment ? edit.comment : null,
4929
+ true,
4930
+ active_mapper
4931
+ );
4932
+ for (const se of cell_sub_edits) {
4933
+ se._original_target_text = edit.target_text;
4934
+ se._split_group_id = start_idx;
4935
+ sub_edits2.push(se);
4936
+ }
4937
+ }
4938
+ if (a_clean) {
4939
+ search_offset = actual_start + a_clean.length;
4940
+ }
4941
+ const next_pipe = this.mapper.full_text.indexOf(" | ", search_offset);
4942
+ if (next_pipe !== -1 && next_pipe <= search_offset + 10) {
4943
+ search_offset = next_pipe + 3;
4944
+ } else {
4945
+ search_offset += a_cell.length + 1;
4946
+ }
4947
+ }
4948
+ for (const sub of sub_edits2) {
4949
+ all_sub_edits.push(sub);
4950
+ }
4951
+ continue;
4952
+ } else {
4953
+ throw new BatchValidationError([
4954
+ `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).`
4955
+ ]);
4956
+ }
4957
+ }
4958
+ let has_markdown = false;
4959
+ if (edit.target_text && (edit.target_text.includes("**") || edit.target_text.includes("_"))) {
4960
+ has_markdown = true;
4961
+ }
4962
+ if (current_effective_new_text && (current_effective_new_text.includes("**") || current_effective_new_text.includes("_"))) {
4963
+ has_markdown = true;
4964
+ }
4803
4965
  let effective_op = "";
4804
4966
  let final_target = "";
4805
4967
  let final_new = "";
4806
4968
  let effective_start_idx = start_idx;
4807
4969
  if (current_effective_new_text.startsWith(actual_doc_text)) {
4808
4970
  effective_op = "INSERTION";
4809
- final_new = current_effective_new_text.substring(
4810
- actual_doc_text.length
4811
- );
4971
+ final_new = current_effective_new_text.substring(actual_doc_text.length);
4812
4972
  effective_start_idx = start_idx + match_len;
4813
4973
  } else {
4814
4974
  const [prefix_len, suffix_len] = trim_common_context(
@@ -4820,68 +4980,84 @@ var RedlineEngine = class {
4820
4980
  final_target = actual_doc_text.substring(prefix_len, t_end);
4821
4981
  final_new = current_effective_new_text.substring(prefix_len, n_end);
4822
4982
  effective_start_idx = start_idx + prefix_len;
4823
- const target_segs = actual_doc_text.split("\n\n");
4824
- const new_segs = current_effective_new_text.split("\n\n");
4825
- if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
4826
- const split_sub_edits = [];
4827
- let seg_offset = start_idx;
4828
- let comment_assigned = false;
4829
- for (let k = 0; k < target_segs.length; k++) {
4830
- const t_seg = target_segs[k];
4831
- const n_seg = new_segs[k];
4832
- if (t_seg !== n_seg) {
4833
- const [seg_prefix, seg_suffix] = trim_common_context(
4834
- t_seg,
4835
- n_seg
4836
- );
4837
- const seg_target = t_seg.substring(
4838
- seg_prefix,
4839
- t_seg.length - seg_suffix
4840
- );
4841
- const seg_new = n_seg.substring(
4842
- seg_prefix,
4843
- n_seg.length - seg_suffix
4844
- );
4845
- const seg_start = seg_offset + seg_prefix;
4846
- let seg_op;
4847
- if (!seg_target && seg_new) seg_op = "INSERTION";
4848
- else if (seg_target && !seg_new) seg_op = "DELETION";
4849
- else if (seg_target && seg_new) seg_op = "MODIFICATION";
4850
- else seg_op = "COMMENT_ONLY";
4851
- const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
4852
- if (seg_comment) comment_assigned = true;
4853
- split_sub_edits.push({
4854
- type: "modify",
4855
- target_text: seg_target,
4856
- new_text: seg_new,
4857
- comment: seg_comment,
4858
- _match_start_index: seg_start,
4859
- _internal_op: seg_op,
4860
- _active_mapper_ref: active_mapper,
4861
- _split_group_id: start_idx
4862
- });
4983
+ }
4984
+ if (has_markdown) {
4985
+ if (!final_target && final_new) {
4986
+ effective_op = "INSERTION";
4987
+ } else if (final_target && !final_new) {
4988
+ effective_op = "DELETION";
4989
+ } else if (final_target && final_new) {
4990
+ effective_op = "MODIFICATION";
4991
+ } else {
4992
+ all_sub_edits.push({
4993
+ type: "modify",
4994
+ target_text: final_target,
4995
+ new_text: final_new,
4996
+ comment: edit.comment,
4997
+ _match_start_index: effective_start_idx,
4998
+ _internal_op: "COMMENT_ONLY",
4999
+ _active_mapper_ref: active_mapper
5000
+ });
5001
+ continue;
5002
+ }
5003
+ all_sub_edits.push({
5004
+ type: "modify",
5005
+ target_text: final_target,
5006
+ new_text: final_new,
5007
+ comment: edit.comment,
5008
+ _resolved_start_idx: effective_start_idx,
5009
+ _match_start_index: effective_start_idx,
5010
+ _internal_op: effective_op,
5011
+ _active_mapper_ref: active_mapper
5012
+ });
5013
+ continue;
5014
+ }
5015
+ const target_segs = actual_doc_text.split("\n\n");
5016
+ const new_segs = current_effective_new_text.split("\n\n");
5017
+ if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
5018
+ const split_sub_edits = [];
5019
+ let seg_offset = start_idx;
5020
+ let comment_assigned = false;
5021
+ for (let k = 0; k < target_segs.length; k++) {
5022
+ const t_seg = target_segs[k];
5023
+ const n_seg = new_segs[k];
5024
+ if (t_seg !== n_seg) {
5025
+ const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
5026
+ const seg_sub_edits = this._word_diff_sub_edits(
5027
+ t_seg,
5028
+ n_seg,
5029
+ seg_offset,
5030
+ seg_comment,
5031
+ false,
5032
+ active_mapper
5033
+ );
5034
+ if (seg_sub_edits.some((se) => se.comment !== null && se.comment !== void 0)) {
5035
+ comment_assigned = true;
5036
+ }
5037
+ for (const se of seg_sub_edits) {
5038
+ se._split_group_id = start_idx;
5039
+ split_sub_edits.push(se);
4863
5040
  }
4864
- seg_offset += t_seg.length + 2;
4865
- }
4866
- if (split_sub_edits.length > 0) {
4867
- for (const sub of split_sub_edits) all_sub_edits.push(sub);
4868
- continue;
4869
5041
  }
5042
+ seg_offset += t_seg.length + 2;
5043
+ }
5044
+ if (split_sub_edits.length > 0) {
5045
+ for (const sub of split_sub_edits) all_sub_edits.push(sub);
5046
+ continue;
4870
5047
  }
4871
- if (!final_target && final_new) effective_op = "INSERTION";
4872
- else if (final_target && !final_new) effective_op = "DELETION";
4873
- else if (final_target && final_new) effective_op = "MODIFICATION";
4874
- else effective_op = "COMMENT_ONLY";
4875
5048
  }
4876
- all_sub_edits.push({
4877
- type: "modify",
4878
- target_text: final_target,
4879
- new_text: final_new,
4880
- comment: edit.comment,
4881
- _match_start_index: effective_start_idx,
4882
- _internal_op: effective_op,
4883
- _active_mapper_ref: active_mapper
4884
- });
5049
+ const sub_edits = this._word_diff_sub_edits(
5050
+ actual_doc_text,
5051
+ current_effective_new_text,
5052
+ start_idx,
5053
+ edit.comment,
5054
+ false,
5055
+ active_mapper
5056
+ );
5057
+ for (const se of sub_edits) {
5058
+ se._split_group_id = start_idx;
5059
+ all_sub_edits.push(se);
5060
+ }
4885
5061
  }
4886
5062
  if (all_sub_edits.length === 0) return null;
4887
5063
  if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
@@ -4895,7 +5071,7 @@ var RedlineEngine = class {
4895
5071
  * our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
4896
5072
  * <w:ins>.
4897
5073
  */
4898
- _insert_and_split_ins(parent_ins, split_after, new_elem) {
5074
+ _insert_and_split_ins(parent_ins, anchor, new_elem, split_before = false) {
4899
5075
  const grandparent = parent_ins.parentNode;
4900
5076
  if (!grandparent) return;
4901
5077
  const left = parent_ins.cloneNode(false);
@@ -4903,9 +5079,10 @@ var RedlineEngine = class {
4903
5079
  let toRight = false;
4904
5080
  for (const kid of Array.from(parent_ins.childNodes)) {
4905
5081
  parent_ins.removeChild(kid);
5082
+ if (split_before && kid === anchor) toRight = true;
4906
5083
  if (!toRight) {
4907
5084
  left.appendChild(kid);
4908
- if (kid === split_after) toRight = true;
5085
+ if (kid === anchor) toRight = true;
4909
5086
  } else {
4910
5087
  right.appendChild(kid);
4911
5088
  }
@@ -4921,6 +5098,15 @@ var RedlineEngine = class {
4921
5098
  const active_mapper = edit._active_mapper_ref || this.mapper;
4922
5099
  const start_idx = edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null ? edit._resolved_start_idx : edit._match_start_index || 0;
4923
5100
  const length = edit.target_text ? edit.target_text.length : 0;
5101
+ if (op === void 0 || op === null) {
5102
+ if (!edit.target_text && edit.new_text) {
5103
+ op = "INSERTION";
5104
+ } else if (edit.target_text && !edit.new_text) {
5105
+ op = "DELETION";
5106
+ } else {
5107
+ op = "MODIFICATION";
5108
+ }
5109
+ }
4924
5110
  if (op === "STYLE_ONLY" || op === "STYLE_AND_TEXT") {
4925
5111
  const [anchor_run, anchor_para] = active_mapper.get_insertion_anchor(
4926
5112
  start_idx,
@@ -5124,17 +5310,26 @@ var RedlineEngine = class {
5124
5310
  if (is_inline_first) {
5125
5311
  if (anchor_run) {
5126
5312
  const anchor_parent = anchor_run._element.parentNode;
5313
+ const before_anchor = start_idx === 0;
5127
5314
  if (anchor_parent && anchor_parent.tagName === "w:ins") {
5128
5315
  this._insert_and_split_ins(
5129
5316
  anchor_parent,
5130
5317
  anchor_run._element,
5131
- result.first_node
5318
+ result.first_node,
5319
+ before_anchor
5132
5320
  );
5321
+ } else if (before_anchor && anchor_parent) {
5322
+ anchor_parent.insertBefore(result.first_node, anchor_run._element);
5133
5323
  } else {
5134
5324
  insertAfter(result.first_node, anchor_run._element);
5135
5325
  }
5136
5326
  } else if (anchor_para) {
5137
- anchor_para._element.appendChild(result.first_node);
5327
+ const para_el = anchor_para._element;
5328
+ let ref = para_el.firstChild;
5329
+ while (ref && ref.tagName === "w:pPr") {
5330
+ ref = ref.nextSibling;
5331
+ }
5332
+ para_el.insertBefore(result.first_node, ref);
5138
5333
  }
5139
5334
  }
5140
5335
  if (edit.comment) {