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