@adeu/core 1.21.0 → 1.22.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
@@ -34,10 +34,13 @@ __export(index_exports, {
34
34
  DocumentMapper: () => DocumentMapper,
35
35
  DocumentObject: () => DocumentObject,
36
36
  RedlineEngine: () => RedlineEngine,
37
+ RegexTimeoutError: () => RegexTimeoutError,
38
+ USER_PATTERN_TIMEOUT_MS: () => USER_PATTERN_TIMEOUT_MS,
37
39
  _extractTextFromDoc: () => _extractTextFromDoc,
38
40
  apply_edits_to_markdown: () => apply_edits_to_markdown,
39
41
  create_unified_diff: () => create_unified_diff,
40
42
  create_word_patch_diff: () => create_word_patch_diff,
43
+ describe_illegal_control_chars: () => describe_illegal_control_chars,
41
44
  extractTextFromBuffer: () => extractTextFromBuffer,
42
45
  extract_outline: () => extract_outline,
43
46
  finalize_document: () => finalize_document,
@@ -45,7 +48,10 @@ __export(index_exports, {
45
48
  identifyEngine: () => identifyEngine,
46
49
  paginate: () => paginate,
47
50
  split_structural_appendix: () => split_structural_appendix,
48
- trim_common_context: () => trim_common_context
51
+ trim_common_context: () => trim_common_context,
52
+ userFindAllMatches: () => userFindAllMatches,
53
+ userSearch: () => userSearch,
54
+ validate_edit_strings: () => validate_edit_strings
49
55
  });
50
56
  module.exports = __toCommonJS(index_exports);
51
57
 
@@ -794,6 +800,62 @@ function extract_comments_data(pkg) {
794
800
  return data;
795
801
  }
796
802
 
803
+ // src/utils/safe-regex.ts
804
+ var vm = __toESM(require("vm"), 1);
805
+ var USER_PATTERN_TIMEOUT_MS = 2e3;
806
+ var RegexTimeoutError = class extends Error {
807
+ pattern;
808
+ constructor(pattern) {
809
+ super(
810
+ `Regular expression exceeded the ${USER_PATTERN_TIMEOUT_MS / 1e3}s matching time budget (catastrophic backtracking). Simplify the pattern \u2014 nested quantifiers like (a+)+ are the usual cause \u2014 or use a literal target instead.`
811
+ );
812
+ this.name = "RegexTimeoutError";
813
+ this.pattern = pattern;
814
+ }
815
+ };
816
+ function runBudgeted(pattern, script, sandbox) {
817
+ try {
818
+ return vm.runInNewContext(script, sandbox, {
819
+ timeout: USER_PATTERN_TIMEOUT_MS
820
+ });
821
+ } catch (e) {
822
+ if (e && e.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
823
+ throw new RegexTimeoutError(pattern);
824
+ }
825
+ throw e;
826
+ }
827
+ }
828
+ function userFindAllMatches(pattern, text, flags = "") {
829
+ const normalized = flags.includes("g") ? flags : flags + "g";
830
+ const re = new RegExp(pattern, normalized);
831
+ const raw = runBudgeted(
832
+ pattern,
833
+ `{
834
+ const out = [];
835
+ let m;
836
+ while ((m = re.exec(text)) !== null) {
837
+ out.push({ start: m.index, end: m.index + m[0].length });
838
+ if (m.index === re.lastIndex) re.lastIndex++;
839
+ }
840
+ out;
841
+ }`,
842
+ { re, text }
843
+ );
844
+ return raw.map((r) => ({ start: r.start, end: r.end }));
845
+ }
846
+ function userSearch(pattern, text, flags = "") {
847
+ const re = new RegExp(pattern, flags.replace("g", ""));
848
+ const raw = runBudgeted(
849
+ pattern,
850
+ `{
851
+ const m = re.exec(text);
852
+ m ? { start: m.index, end: m.index + m[0].length } : null;
853
+ }`,
854
+ { re, text }
855
+ );
856
+ return raw ? { start: raw.start, end: raw.end } : null;
857
+ }
858
+
797
859
  // src/utils/docx.ts
798
860
  var QN_W_P = "w:p";
799
861
  var QN_W_R = "w:r";
@@ -1868,10 +1930,10 @@ ${header}`;
1868
1930
  find_match_index(target_text, is_regex = false) {
1869
1931
  if (is_regex) {
1870
1932
  try {
1871
- const pattern = new RegExp(target_text);
1872
- const match = pattern.exec(this.full_text);
1873
- if (match) return [match.index, match[0].length];
1933
+ const match = userSearch(target_text, this.full_text);
1934
+ if (match) return [match.start, match.end - match.start];
1874
1935
  } catch (e) {
1936
+ if (e instanceof RegexTimeoutError) throw e;
1875
1937
  }
1876
1938
  return [-1, 0];
1877
1939
  }
@@ -1900,11 +1962,10 @@ ${header}`;
1900
1962
  if (!target_text) return [];
1901
1963
  if (is_regex) {
1902
1964
  try {
1903
- const pattern = new RegExp(target_text, "g");
1904
- const matches2 = [...this.full_text.matchAll(pattern)];
1905
- if (matches2.length > 0)
1906
- return matches2.map((m) => [m.index, m[0].length]);
1965
+ const matches2 = userFindAllMatches(target_text, this.full_text);
1966
+ if (matches2.length > 0) return matches2.map((m) => [m.start, m.end - m.start]);
1907
1967
  } catch (e) {
1968
+ if (e instanceof RegexTimeoutError) throw e;
1908
1969
  }
1909
1970
  return [];
1910
1971
  }
@@ -2882,7 +2943,9 @@ function apply_edits_to_markdown(markdown_text, edits, include_index = false, hi
2882
2943
  isolated_target,
2883
2944
  isolated_new,
2884
2945
  edit.comment,
2885
- orig_idx,
2946
+ // 1-based, matching apply's "Edit N" reports and batch validation
2947
+ // errors (QA 2026-07-17 F10; mirrors Python).
2948
+ orig_idx + 1,
2886
2949
  include_index,
2887
2950
  highlight_only
2888
2951
  );
@@ -3027,12 +3090,36 @@ function sequential_context_hint(applied_so_far) {
3027
3090
  Note: ${applied_so_far} earlier edit(s) in this batch were already applied. Batches apply sequentially \u2014 each edit must target the document text as it reads AFTER the preceding edits (e.g. target the replacement text an earlier edit introduced, not the original wording).`;
3028
3091
  }
3029
3092
  var TRANSACTIONAL_NOT_APPLIED_ERROR = "Not applied: the batch is transactional and other edits failed validation (see their errors). Fix or remove those edits and re-run.";
3093
+ var XML_ILLEGAL_CHARS_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
3094
+ function describe_illegal_control_chars(text) {
3095
+ if (!text) return null;
3096
+ const found = text.match(XML_ILLEGAL_CHARS_RE);
3097
+ if (!found) return null;
3098
+ const codes = Array.from(new Set(found.map((c) => `0x${c.charCodeAt(0).toString(16).padStart(2, "0")}`))).sort();
3099
+ return codes.join(", ");
3100
+ }
3030
3101
  function validate_edit_strings(edits, index_offset = 0) {
3031
3102
  const errors = [];
3032
3103
  for (let i = 0; i < edits.length; i++) {
3033
3104
  const edit = edits[i];
3034
3105
  const t_text = edit.target_text || "";
3035
3106
  const n_text = edit.new_text || "";
3107
+ const checked_fields = [
3108
+ ["target_text", t_text],
3109
+ ["new_text", n_text]
3110
+ ];
3111
+ if (edit.comment) checked_fields.push(["comment", edit.comment]);
3112
+ (edit.cells || []).forEach((cell, cell_idx) => {
3113
+ checked_fields.push([`cells[${cell_idx}]`, cell || ""]);
3114
+ });
3115
+ for (const [field_name, field_value] of checked_fields) {
3116
+ const described = describe_illegal_control_chars(field_value);
3117
+ if (described) {
3118
+ errors.push(
3119
+ `- Edit ${i + 1 + index_offset} Failed: \`${field_name}\` contains control character(s) (${described}) that cannot be stored in a DOCX. Remove them and re-submit.`
3120
+ );
3121
+ }
3122
+ }
3036
3123
  if (n_text.includes("{++") || n_text.includes("{--") || n_text.includes("{>>") || n_text.includes("{==")) {
3037
3124
  errors.push(
3038
3125
  `- Edit ${i + 1 + index_offset} Failed: Do not manually write CriticMarkup tags ({++, {--, {>>, {==) in \`new_text\`. The engine handles redlining automatically. To add a comment, use the \`comment\` parameter.`
@@ -3339,8 +3426,8 @@ var RedlineEngine = class _RedlineEngine {
3339
3426
  * Snapshots the document text around a resolved edit BEFORE anything is
3340
3427
  * applied. Previews rendered after the batch mutates the DOM cannot slice
3341
3428
  * full_text at the stored offsets: applied edits shift offsets and inject
3342
- * tracked-change markup, which produced garbled previews mixing unrelated
3343
- * edits and internal scaffolding (QA H1).
3429
+ * tracked-change markup, garbling previews with unrelated edits and
3430
+ * internal scaffolding (QA H1).
3344
3431
  */
3345
3432
  _capture_preview_context(edit) {
3346
3433
  if (edit.type !== "modify") return;
@@ -3441,8 +3528,8 @@ var RedlineEngine = class _RedlineEngine {
3441
3528
  }
3442
3529
  /**
3443
3530
  * The "new text" a batch report should show for an edit. InsertTableRow has
3444
- * no new_text field — surface its cell contents instead of the misleading
3445
- * empty string the report used to print (QA M4).
3531
+ * no new_text field — surface its cell contents rather than a misleading
3532
+ * empty string (QA M4).
3446
3533
  */
3447
3534
  static _report_new_text(edit) {
3448
3535
  if (edit && edit.type === "insert_row" && Array.isArray(edit.cells)) {
@@ -4727,7 +4814,13 @@ var RedlineEngine = class _RedlineEngine {
4727
4814
  const reports_by_input = new Array(edits.length);
4728
4815
  const validation_failed_idx = /* @__PURE__ */ new Set();
4729
4816
  for (const { edit, i } of ordered_edits) {
4730
- let single_errors = this.validate_edits([edit], i);
4817
+ let single_errors;
4818
+ try {
4819
+ single_errors = this.validate_edits([edit], i);
4820
+ } catch (e) {
4821
+ if (!(e instanceof RegexTimeoutError)) throw e;
4822
+ single_errors = [`- Edit ${i + 1} Failed: ${e.message}`];
4823
+ }
4731
4824
  if (single_errors.length > 0) {
4732
4825
  if (applied_edits > 0) {
4733
4826
  const hint = sequential_context_hint(applied_edits);
@@ -4812,7 +4905,13 @@ var RedlineEngine = class _RedlineEngine {
4812
4905
  const sequential_errors = [];
4813
4906
  let applied_so_far = 0;
4814
4907
  for (const { edit, i } of ordered_edits) {
4815
- let single_errors = this.validate_edits([edit], i);
4908
+ let single_errors;
4909
+ try {
4910
+ single_errors = this.validate_edits([edit], i);
4911
+ } catch (e) {
4912
+ if (!(e instanceof RegexTimeoutError)) throw e;
4913
+ single_errors = [`- Edit ${i + 1} Failed: ${e.message}`];
4914
+ }
4816
4915
  if (single_errors.length > 0) {
4817
4916
  if (applied_so_far > 0) {
4818
4917
  const hint = sequential_context_hint(applied_so_far);
@@ -4926,7 +5025,18 @@ var RedlineEngine = class _RedlineEngine {
4926
5025
  edit._error_msg = msg;
4927
5026
  }
4928
5027
  } else {
4929
- const resolved = this._pre_resolve_heuristic_edit(edit);
5028
+ let resolved;
5029
+ try {
5030
+ resolved = this._pre_resolve_heuristic_edit(edit);
5031
+ } catch (e) {
5032
+ if (!(e instanceof RegexTimeoutError)) throw e;
5033
+ skipped++;
5034
+ edit._applied_status = false;
5035
+ const msg = `- Failed to apply edit targeting: '${(edit.target_text || "").substring(0, 40)}...' (${e.message})`;
5036
+ this.skipped_details.push(msg);
5037
+ edit._error_msg = msg;
5038
+ continue;
5039
+ }
4930
5040
  if (resolved) {
4931
5041
  if (Array.isArray(resolved)) {
4932
5042
  for (const r of resolved) {
@@ -5085,6 +5195,16 @@ var RedlineEngine = class _RedlineEngine {
5085
5195
  );
5086
5196
  continue;
5087
5197
  }
5198
+ const dup_authors = Array.from(
5199
+ new Set(all_nodes.map((n) => n.getAttribute("w:author") || "Unknown"))
5200
+ ).sort();
5201
+ if (dup_authors.length > 1) {
5202
+ skipped++;
5203
+ this.skipped_details.push(
5204
+ `- Failed to apply action: ${type} on Chg:${target_id} is ambiguous. The document contains ${all_nodes.length} tracked-change elements sharing w:id=${target_id} from different authors (${dup_authors.join(", ")}) \u2014 duplicate revision IDs produced outside this engine (e.g. by a document merge or copy-paste). Acting on this ID would resolve all of them at once. Resolve these changes individually in Word, or apply the intended outcome as an explicit text edit instead.`
5205
+ );
5206
+ continue;
5207
+ }
5088
5208
  for (const node of all_nodes) {
5089
5209
  const is_ins = node.tagName === "w:ins";
5090
5210
  const parent_tag = node.parentNode ? node.parentNode.tagName : "";
@@ -6400,6 +6520,27 @@ function scrub_doc_properties(doc) {
6400
6520
  c.textContent = "";
6401
6521
  }
6402
6522
  });
6523
+ const titles = findDescendantsByLocalName(corePart._element, "title");
6524
+ titles.forEach((c) => {
6525
+ if (c.textContent) {
6526
+ lines.push(`Title kept (review manually): "${c.textContent}"`);
6527
+ }
6528
+ });
6529
+ const leakFields = [
6530
+ ["category", "Category"],
6531
+ ["keywords", "Keywords"],
6532
+ ["subject", "Subject"],
6533
+ ["contentStatus", "Content status"],
6534
+ ["description", "Description/comments"]
6535
+ ];
6536
+ for (const [local, label] of leakFields) {
6537
+ findDescendantsByLocalName(corePart._element, local).forEach((c) => {
6538
+ if (c.textContent) {
6539
+ lines.push(`${label}: ${c.textContent}`);
6540
+ c.textContent = "";
6541
+ }
6542
+ });
6543
+ }
6403
6544
  const revisions = findDescendantsByLocalName(corePart._element, "revision");
6404
6545
  revisions.forEach((c) => {
6405
6546
  if (c.textContent && parseInt(c.textContent) > 1) {
@@ -6605,7 +6746,11 @@ function extract_all_domain_metadata(doc, base_text) {
6605
6746
  for (const term of def_keys) {
6606
6747
  if (definitions[term].count === 0) {
6607
6748
  delete definitions[term];
6608
- duplicates.delete(term);
6749
+ if (!duplicates.has(term)) {
6750
+ diagnostics.push(
6751
+ `[Warning] Unused Definition: '${term}' is defined but never used.`
6752
+ );
6753
+ }
6609
6754
  }
6610
6755
  }
6611
6756
  }
@@ -7941,10 +8086,13 @@ function identifyEngine() {
7941
8086
  DocumentMapper,
7942
8087
  DocumentObject,
7943
8088
  RedlineEngine,
8089
+ RegexTimeoutError,
8090
+ USER_PATTERN_TIMEOUT_MS,
7944
8091
  _extractTextFromDoc,
7945
8092
  apply_edits_to_markdown,
7946
8093
  create_unified_diff,
7947
8094
  create_word_patch_diff,
8095
+ describe_illegal_control_chars,
7948
8096
  extractTextFromBuffer,
7949
8097
  extract_outline,
7950
8098
  finalize_document,
@@ -7952,6 +8100,9 @@ function identifyEngine() {
7952
8100
  identifyEngine,
7953
8101
  paginate,
7954
8102
  split_structural_appendix,
7955
- trim_common_context
8103
+ trim_common_context,
8104
+ userFindAllMatches,
8105
+ userSearch,
8106
+ validate_edit_strings
7956
8107
  });
7957
8108
  //# sourceMappingURL=index.cjs.map