@adeu/core 1.20.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 +577 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -1
- package/dist/index.d.ts +105 -1
- package/dist/index.js +570 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/domain.ts +10 -1
- package/src/engine.bugs.test.ts +1 -1
- package/src/engine.qa_v4_port.test.ts +304 -0
- package/src/engine.sequential.test.ts +140 -0
- package/src/engine.ts +501 -36
- package/src/index.ts +3 -2
- package/src/mapper.ts +128 -28
- package/src/markup.test.ts +3 -2
- package/src/markup.ts +14 -6
- package/src/repro_qa_report_v3.test.ts +9 -3
- package/src/repro_qa_report_v5.test.ts +399 -0
- package/src/sanitize/transforms.ts +26 -2
- package/src/utils/safe-regex.ts +90 -0
- package/src/utils/text.ts +26 -0
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";
|
|
@@ -1264,6 +1326,7 @@ function* iter_paragraph_content(paragraph) {
|
|
|
1264
1326
|
}
|
|
1265
1327
|
|
|
1266
1328
|
// src/mapper.ts
|
|
1329
|
+
var STYLE_MARKER_TEXTS = /* @__PURE__ */ new Set(["**", "__", "*", "_"]);
|
|
1267
1330
|
var DocumentMapper = class {
|
|
1268
1331
|
doc;
|
|
1269
1332
|
clean_view;
|
|
@@ -1273,6 +1336,7 @@ var DocumentMapper = class {
|
|
|
1273
1336
|
spans = [];
|
|
1274
1337
|
appendix_start_index = -1;
|
|
1275
1338
|
_text_chunks = [];
|
|
1339
|
+
_plain_projection = null;
|
|
1276
1340
|
constructor(doc, clean_view = false, original_view = false) {
|
|
1277
1341
|
this.doc = doc;
|
|
1278
1342
|
this.clean_view = clean_view;
|
|
@@ -1285,6 +1349,7 @@ var DocumentMapper = class {
|
|
|
1285
1349
|
this.spans = [];
|
|
1286
1350
|
this._text_chunks = [];
|
|
1287
1351
|
this.full_text = "";
|
|
1352
|
+
this._plain_projection = null;
|
|
1288
1353
|
for (const part of iter_document_parts(this.doc)) {
|
|
1289
1354
|
current_offset = this._map_blocks(part, current_offset);
|
|
1290
1355
|
if (this.spans.length > 0 && this.spans[this.spans.length - 1].text !== "\n\n") {
|
|
@@ -1804,13 +1869,71 @@ ${header}`;
|
|
|
1804
1869
|
if (remaining) parts.push(escapeRegExp(remaining));
|
|
1805
1870
|
return parts.join("");
|
|
1806
1871
|
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Returns [plain_text, offset_map] where plain_text is full_text with the
|
|
1874
|
+
* VIRTUAL markdown style delimiters (bold/italic markers emitted around
|
|
1875
|
+
* formatted runs) removed, and offset_map[i] is the full_text index of
|
|
1876
|
+
* plain_text[i].
|
|
1877
|
+
*
|
|
1878
|
+
* Formatting run boundaries can fall mid-word (e.g. a paragraph projected
|
|
1879
|
+
* as "**Al**pha"), where neither exact matching nor the whitespace-anchored
|
|
1880
|
+
* fuzzy regex can find the plain target "Alpha". Matching against this
|
|
1881
|
+
* projection and mapping the span back to full_text closes that gap (QA H2).
|
|
1882
|
+
*
|
|
1883
|
+
* Built lazily and invalidated by _build_map(): most batches never need it.
|
|
1884
|
+
*/
|
|
1885
|
+
_get_plain_projection() {
|
|
1886
|
+
if (this._plain_projection === null) {
|
|
1887
|
+
const chunks = [];
|
|
1888
|
+
const offsets = [];
|
|
1889
|
+
for (const s of this.spans) {
|
|
1890
|
+
if (s.run === null && s.paragraph !== null && STYLE_MARKER_TEXTS.has(s.text)) {
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
chunks.push(s.text);
|
|
1894
|
+
for (let k = s.start; k < s.end; k++) offsets.push(k);
|
|
1895
|
+
}
|
|
1896
|
+
this._plain_projection = [chunks.join(""), offsets];
|
|
1897
|
+
}
|
|
1898
|
+
return this._plain_projection;
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Matches a markdown-stripped target against the plain projection and maps
|
|
1902
|
+
* each hit back to a [start, length] span in full_text. Interior style
|
|
1903
|
+
* markers end up inside the returned span (so "Alpha" over "**Al**pha"
|
|
1904
|
+
* resolves to the "Al**pha" range); markers just outside the matched
|
|
1905
|
+
* characters are excluded.
|
|
1906
|
+
*/
|
|
1907
|
+
_find_plain_projection_matches(target_text) {
|
|
1908
|
+
const [plain_text, offsets] = this._get_plain_projection();
|
|
1909
|
+
if (plain_text.length === this.full_text.length) {
|
|
1910
|
+
return [];
|
|
1911
|
+
}
|
|
1912
|
+
const norm_target = this._replace_smart_quotes(
|
|
1913
|
+
this._strip_markdown_formatting(target_text)
|
|
1914
|
+
);
|
|
1915
|
+
if (!norm_target) return [];
|
|
1916
|
+
const norm_plain = this._replace_smart_quotes(plain_text);
|
|
1917
|
+
const results = [];
|
|
1918
|
+
let from = 0;
|
|
1919
|
+
while (true) {
|
|
1920
|
+
const p_start = norm_plain.indexOf(norm_target, from);
|
|
1921
|
+
if (p_start === -1) break;
|
|
1922
|
+
const p_end = p_start + norm_target.length;
|
|
1923
|
+
const raw_start = offsets[p_start];
|
|
1924
|
+
const raw_end = offsets[p_end - 1] + 1;
|
|
1925
|
+
results.push([raw_start, raw_end - raw_start]);
|
|
1926
|
+
from = p_end;
|
|
1927
|
+
}
|
|
1928
|
+
return results;
|
|
1929
|
+
}
|
|
1807
1930
|
find_match_index(target_text, is_regex = false) {
|
|
1808
1931
|
if (is_regex) {
|
|
1809
1932
|
try {
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
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];
|
|
1813
1935
|
} catch (e) {
|
|
1936
|
+
if (e instanceof RegexTimeoutError) throw e;
|
|
1814
1937
|
}
|
|
1815
1938
|
return [-1, 0];
|
|
1816
1939
|
}
|
|
@@ -1825,6 +1948,8 @@ ${header}`;
|
|
|
1825
1948
|
start_idx = this.full_text.indexOf(stripped_target);
|
|
1826
1949
|
return [start_idx, stripped_target.length];
|
|
1827
1950
|
}
|
|
1951
|
+
const plain_first = this._find_plain_projection_matches(target_text);
|
|
1952
|
+
if (plain_first.length > 0) return plain_first[0];
|
|
1828
1953
|
try {
|
|
1829
1954
|
const pattern = new RegExp(this._make_fuzzy_regex(target_text));
|
|
1830
1955
|
const match = pattern.exec(this.full_text);
|
|
@@ -1837,37 +1962,40 @@ ${header}`;
|
|
|
1837
1962
|
if (!target_text) return [];
|
|
1838
1963
|
if (is_regex) {
|
|
1839
1964
|
try {
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
if (matches2.length > 0)
|
|
1843
|
-
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]);
|
|
1844
1967
|
} catch (e) {
|
|
1968
|
+
if (e instanceof RegexTimeoutError) throw e;
|
|
1845
1969
|
}
|
|
1846
1970
|
return [];
|
|
1847
1971
|
}
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1972
|
+
const findAllLiteral = (haystack, needle) => {
|
|
1973
|
+
const out = [];
|
|
1974
|
+
if (!needle) return out;
|
|
1975
|
+
let from = 0;
|
|
1976
|
+
while (true) {
|
|
1977
|
+
const idx = haystack.indexOf(needle, from);
|
|
1978
|
+
if (idx === -1) break;
|
|
1979
|
+
out.push([idx, needle.length]);
|
|
1980
|
+
from = idx + needle.length;
|
|
1981
|
+
}
|
|
1982
|
+
return out;
|
|
1983
|
+
};
|
|
1984
|
+
let matches = findAllLiteral(this.full_text, target_text);
|
|
1985
|
+
if (matches.length > 0) return matches;
|
|
1853
1986
|
const norm_full = this._replace_smart_quotes(this.full_text);
|
|
1854
1987
|
const norm_target = this._replace_smart_quotes(target_text);
|
|
1855
|
-
matches =
|
|
1856
|
-
|
|
1857
|
-
];
|
|
1858
|
-
if (matches.length > 0) return matches.map((m) => [m.index, m[0].length]);
|
|
1988
|
+
matches = findAllLiteral(norm_full, norm_target);
|
|
1989
|
+
if (matches.length > 0) return matches;
|
|
1859
1990
|
const stripped_target = this._strip_markdown_formatting(target_text);
|
|
1860
|
-
matches =
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
];
|
|
1865
|
-
if (matches.length > 0) return matches.map((m) => [m.index, m[0].length]);
|
|
1991
|
+
matches = findAllLiteral(this.full_text, stripped_target);
|
|
1992
|
+
if (matches.length > 0) return matches;
|
|
1993
|
+
const plain_matches = this._find_plain_projection_matches(target_text);
|
|
1994
|
+
if (plain_matches.length > 0) return plain_matches;
|
|
1866
1995
|
try {
|
|
1867
1996
|
const pattern = new RegExp(this._make_fuzzy_regex(target_text), "g");
|
|
1868
|
-
|
|
1869
|
-
if (
|
|
1870
|
-
return matches.map((m) => [m.index, m[0].length]);
|
|
1997
|
+
const fuzzy = [...this.full_text.matchAll(pattern)];
|
|
1998
|
+
if (fuzzy.length > 0) return fuzzy.map((m) => [m.index, m[0].length]);
|
|
1871
1999
|
} catch (e) {
|
|
1872
2000
|
}
|
|
1873
2001
|
return [];
|
|
@@ -2815,7 +2943,9 @@ function apply_edits_to_markdown(markdown_text, edits, include_index = false, hi
|
|
|
2815
2943
|
isolated_target,
|
|
2816
2944
|
isolated_new,
|
|
2817
2945
|
edit.comment,
|
|
2818
|
-
|
|
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,
|
|
2819
2949
|
include_index,
|
|
2820
2950
|
highlight_only
|
|
2821
2951
|
);
|
|
@@ -2857,18 +2987,30 @@ function format_ambiguity_error(edit_index, target_text, haystack, match_positio
|
|
|
2857
2987
|
}
|
|
2858
2988
|
lines.push(" To resolve, re-send this edit using ONE of these strategies:");
|
|
2859
2989
|
lines.push(
|
|
2860
|
-
|
|
2990
|
+
' 1. RECOMMENDED: Provide more surrounding context in your target_text to uniquely identify a single location (keep the default "match_mode": "strict").'
|
|
2861
2991
|
);
|
|
2862
2992
|
lines.push(
|
|
2863
|
-
|
|
2993
|
+
` 2. Set "match_mode": "all" to modify ALL ${total} occurrences \u2014 only after verifying from the occurrence list above that EVERY occurrence should change.`
|
|
2864
2994
|
);
|
|
2865
2995
|
lines.push(
|
|
2866
|
-
' 3.
|
|
2996
|
+
' 3. Set "match_mode": "first" to modify only the FIRST occurrence \u2014 only after verifying the first occurrence above is the one you intend to change.'
|
|
2867
2997
|
);
|
|
2868
2998
|
return lines.join("\n");
|
|
2869
2999
|
}
|
|
2870
3000
|
|
|
3001
|
+
// src/utils/text.ts
|
|
3002
|
+
var REPORT_ECHO_CAP = 500;
|
|
3003
|
+
var PREVIEW_TEXT_CAP = 200;
|
|
3004
|
+
function truncate_middle(text, cap) {
|
|
3005
|
+
if (text === null || text === void 0 || text.length <= cap) return text;
|
|
3006
|
+
const head = Math.max(1, Math.floor(cap * 2 / 3));
|
|
3007
|
+
const tail = Math.max(1, cap - head);
|
|
3008
|
+
const omitted = text.length - head - tail;
|
|
3009
|
+
return `${text.slice(0, head)}\u2026 [${omitted.toLocaleString("en-US")} chars omitted] \u2026${text.slice(-tail)}`;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
2871
3012
|
// src/engine.ts
|
|
3013
|
+
var PREVIEW_CONTEXT_CHARS = 30;
|
|
2872
3014
|
function getNextElement(el) {
|
|
2873
3015
|
let next = el.nextSibling;
|
|
2874
3016
|
while (next) {
|
|
@@ -2943,12 +3085,41 @@ var BatchValidationError = class extends Error {
|
|
|
2943
3085
|
this.errors = errors;
|
|
2944
3086
|
}
|
|
2945
3087
|
};
|
|
3088
|
+
function sequential_context_hint(applied_so_far) {
|
|
3089
|
+
return `
|
|
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).`;
|
|
3091
|
+
}
|
|
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
|
+
}
|
|
2946
3101
|
function validate_edit_strings(edits, index_offset = 0) {
|
|
2947
3102
|
const errors = [];
|
|
2948
3103
|
for (let i = 0; i < edits.length; i++) {
|
|
2949
3104
|
const edit = edits[i];
|
|
2950
3105
|
const t_text = edit.target_text || "";
|
|
2951
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
|
+
}
|
|
2952
3123
|
if (n_text.includes("{++") || n_text.includes("{--") || n_text.includes("{>>") || n_text.includes("{==")) {
|
|
2953
3124
|
errors.push(
|
|
2954
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.`
|
|
@@ -3046,7 +3217,7 @@ function validate_edit_strings(edits, index_offset = 0) {
|
|
|
3046
3217
|
}
|
|
3047
3218
|
return errors;
|
|
3048
3219
|
}
|
|
3049
|
-
var RedlineEngine = class {
|
|
3220
|
+
var RedlineEngine = class _RedlineEngine {
|
|
3050
3221
|
doc;
|
|
3051
3222
|
author;
|
|
3052
3223
|
timestamp;
|
|
@@ -3193,8 +3364,184 @@ var RedlineEngine = class {
|
|
|
3193
3364
|
}
|
|
3194
3365
|
return "";
|
|
3195
3366
|
}
|
|
3367
|
+
// CriticMarkup wrapper pairs used when tidying preview context windows.
|
|
3368
|
+
static _PREVIEW_WRAPPER_PAIRS = [
|
|
3369
|
+
["{--", "--}"],
|
|
3370
|
+
["{++", "++}"],
|
|
3371
|
+
["{==", "==}"],
|
|
3372
|
+
["{>>", "<<}"]
|
|
3373
|
+
];
|
|
3374
|
+
/**
|
|
3375
|
+
* Makes a fixed-width slice of the raw-view projection presentable: drops
|
|
3376
|
+
* complete {>>...<<} meta blocks (annotations of pre-existing changes, not
|
|
3377
|
+
* part of this edit) and any wrapper fragments the window boundary chopped
|
|
3378
|
+
* in half. Without this, previews leak internal scaffolding like
|
|
3379
|
+
* "[Chg:5 delete]" (QA H1). Mirrors the Python engine.
|
|
3380
|
+
*/
|
|
3381
|
+
static _tidy_preview_context(snippet, side) {
|
|
3382
|
+
snippet = snippet.replace(/\{>>[\s\S]*?<<\}/g, "");
|
|
3383
|
+
for (const [open_tok, close_tok] of _RedlineEngine._PREVIEW_WRAPPER_PAIRS) {
|
|
3384
|
+
if (side === "before") {
|
|
3385
|
+
let depth = 0;
|
|
3386
|
+
let cut = 0;
|
|
3387
|
+
let i = 0;
|
|
3388
|
+
while (i < snippet.length) {
|
|
3389
|
+
if (snippet.startsWith(open_tok, i)) {
|
|
3390
|
+
depth += 1;
|
|
3391
|
+
i += open_tok.length;
|
|
3392
|
+
} else if (snippet.startsWith(close_tok, i)) {
|
|
3393
|
+
if (depth === 0) cut = i + close_tok.length;
|
|
3394
|
+
else depth -= 1;
|
|
3395
|
+
i += close_tok.length;
|
|
3396
|
+
} else {
|
|
3397
|
+
i += 1;
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
snippet = snippet.substring(cut);
|
|
3401
|
+
} else {
|
|
3402
|
+
const opens = [];
|
|
3403
|
+
let i = 0;
|
|
3404
|
+
while (i < snippet.length) {
|
|
3405
|
+
if (snippet.startsWith(open_tok, i)) {
|
|
3406
|
+
opens.push(i);
|
|
3407
|
+
i += open_tok.length;
|
|
3408
|
+
} else if (snippet.startsWith(close_tok, i)) {
|
|
3409
|
+
if (opens.length > 0) opens.pop();
|
|
3410
|
+
i += close_tok.length;
|
|
3411
|
+
} else {
|
|
3412
|
+
i += 1;
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (opens.length > 0) snippet = snippet.substring(0, opens[0]);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
if (side === "before") {
|
|
3419
|
+
snippet = snippet.replace(/^[-+=<>]{0,2}\}/, "");
|
|
3420
|
+
} else {
|
|
3421
|
+
snippet = snippet.replace(/\{[-+=<>]{0,2}$/, "");
|
|
3422
|
+
}
|
|
3423
|
+
return snippet;
|
|
3424
|
+
}
|
|
3425
|
+
/**
|
|
3426
|
+
* Snapshots the document text around a resolved edit BEFORE anything is
|
|
3427
|
+
* applied. Previews rendered after the batch mutates the DOM cannot slice
|
|
3428
|
+
* full_text at the stored offsets: applied edits shift offsets and inject
|
|
3429
|
+
* tracked-change markup, garbling previews with unrelated edits and
|
|
3430
|
+
* internal scaffolding (QA H1).
|
|
3431
|
+
*/
|
|
3432
|
+
_capture_preview_context(edit) {
|
|
3433
|
+
if (edit.type !== "modify") return;
|
|
3434
|
+
const start_idx = edit._resolved_start_idx;
|
|
3435
|
+
if (start_idx === void 0 || start_idx === null) return;
|
|
3436
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3437
|
+
const full_text = active_mapper.full_text;
|
|
3438
|
+
if (!full_text) return;
|
|
3439
|
+
const length = (edit.target_text || "").length;
|
|
3440
|
+
const before = full_text.substring(
|
|
3441
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3442
|
+
start_idx
|
|
3443
|
+
);
|
|
3444
|
+
const after = full_text.substring(
|
|
3445
|
+
start_idx + length,
|
|
3446
|
+
start_idx + length + PREVIEW_CONTEXT_CHARS
|
|
3447
|
+
);
|
|
3448
|
+
edit._preview_context = [
|
|
3449
|
+
_RedlineEngine._tidy_preview_context(before, "before"),
|
|
3450
|
+
_RedlineEngine._tidy_preview_context(after, "after")
|
|
3451
|
+
];
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Like _capture_preview_context, but snapshots the context around the
|
|
3455
|
+
* ORIGINAL edit's full matched span (stashed by _pre_resolve_heuristic_edit),
|
|
3456
|
+
* so the report preview can present the complete logical change of a
|
|
3457
|
+
* compound modification instead of its first sub-edit.
|
|
3458
|
+
*/
|
|
3459
|
+
_capture_parent_preview_context(parent) {
|
|
3460
|
+
if (!parent || parent.type !== "modify") return;
|
|
3461
|
+
if (parent._preview_context || !parent._preview_span) return;
|
|
3462
|
+
const [start_idx, match_len] = parent._preview_span;
|
|
3463
|
+
const active_mapper = parent._preview_mapper_ref || this.mapper;
|
|
3464
|
+
const full_text = active_mapper.full_text;
|
|
3465
|
+
if (!full_text) return;
|
|
3466
|
+
const before = full_text.substring(
|
|
3467
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3468
|
+
start_idx
|
|
3469
|
+
);
|
|
3470
|
+
const after = full_text.substring(
|
|
3471
|
+
start_idx + match_len,
|
|
3472
|
+
start_idx + match_len + PREVIEW_CONTEXT_CHARS
|
|
3473
|
+
);
|
|
3474
|
+
parent._preview_context = [
|
|
3475
|
+
_RedlineEngine._tidy_preview_context(before, "before"),
|
|
3476
|
+
_RedlineEngine._tidy_preview_context(after, "after")
|
|
3477
|
+
];
|
|
3478
|
+
}
|
|
3479
|
+
/**
|
|
3480
|
+
* Renders the preview from the edit's full matched span. The common
|
|
3481
|
+
* prefix/suffix between matched and replacement text is moved into the
|
|
3482
|
+
* surrounding context so the {--...--}{++...++} block shows the minimal
|
|
3483
|
+
* complete change.
|
|
3484
|
+
*/
|
|
3485
|
+
_build_full_match_preview(edit) {
|
|
3486
|
+
let [context_before, context_after] = edit._preview_context;
|
|
3487
|
+
let matched = edit._preview_matched_text || "";
|
|
3488
|
+
let new_text = edit._preview_new_text !== void 0 && edit._preview_new_text !== null ? edit._preview_new_text : edit.new_text || "";
|
|
3489
|
+
const [matched_clean, matched_style] = this._parse_markdown_style(matched);
|
|
3490
|
+
const [new_clean, new_style] = this._parse_markdown_style(new_text);
|
|
3491
|
+
if (matched_style && matched_style.startsWith("Heading")) {
|
|
3492
|
+
context_before = context_before + matched.substring(0, matched.length - matched_clean.length);
|
|
3493
|
+
matched = matched_clean;
|
|
3494
|
+
}
|
|
3495
|
+
if (new_style && new_style.startsWith("Heading")) {
|
|
3496
|
+
new_text = new_clean;
|
|
3497
|
+
}
|
|
3498
|
+
const [prefix_len, suffix_len] = trim_common_context(matched, new_text);
|
|
3499
|
+
let display_target = matched.substring(
|
|
3500
|
+
prefix_len,
|
|
3501
|
+
matched.length - suffix_len
|
|
3502
|
+
);
|
|
3503
|
+
let display_new = new_text.substring(
|
|
3504
|
+
prefix_len,
|
|
3505
|
+
new_text.length - suffix_len
|
|
3506
|
+
);
|
|
3507
|
+
context_before = context_before + matched.substring(0, prefix_len);
|
|
3508
|
+
if (suffix_len) {
|
|
3509
|
+
context_after = matched.substring(matched.length - suffix_len) + context_after;
|
|
3510
|
+
}
|
|
3511
|
+
display_target = truncate_middle(display_target, PREVIEW_TEXT_CAP);
|
|
3512
|
+
display_new = truncate_middle(display_new, PREVIEW_TEXT_CAP);
|
|
3513
|
+
let critic_markup;
|
|
3514
|
+
if (!display_target && !display_new) {
|
|
3515
|
+
const anchor = truncate_middle(matched, PREVIEW_TEXT_CAP);
|
|
3516
|
+
const body = anchor ? `{==${anchor}==}` : "";
|
|
3517
|
+
critic_markup = `${context_before.substring(0, context_before.length - matched.length)}${body}${context_after}`;
|
|
3518
|
+
} else {
|
|
3519
|
+
const deletion = display_target ? `{--${display_target}--}` : "";
|
|
3520
|
+
const insertion = display_new ? `{++${display_new}++}` : "";
|
|
3521
|
+
critic_markup = `${context_before}${deletion}${insertion}${context_after}`;
|
|
3522
|
+
}
|
|
3523
|
+
let clean_text = critic_markup;
|
|
3524
|
+
clean_text = clean_text.replace(/\{>>[\s\S]*?<<\}/g, "");
|
|
3525
|
+
clean_text = clean_text.replace(/\{--[\s\S]*?--\}/g, "");
|
|
3526
|
+
clean_text = clean_text.replace(/\{\+\+([\s\S]*?)\+\+\}/g, "$1");
|
|
3527
|
+
return [critic_markup, clean_text];
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* The "new text" a batch report should show for an edit. InsertTableRow has
|
|
3531
|
+
* no new_text field — surface its cell contents rather than a misleading
|
|
3532
|
+
* empty string (QA M4).
|
|
3533
|
+
*/
|
|
3534
|
+
static _report_new_text(edit) {
|
|
3535
|
+
if (edit && edit.type === "insert_row" && Array.isArray(edit.cells)) {
|
|
3536
|
+
return edit.cells.join(" | ");
|
|
3537
|
+
}
|
|
3538
|
+
return edit && edit.new_text || "";
|
|
3539
|
+
}
|
|
3196
3540
|
_build_edit_context_previews(edit) {
|
|
3197
3541
|
if (edit.type !== "modify") return [null, null];
|
|
3542
|
+
if (edit._preview_span && edit._preview_context) {
|
|
3543
|
+
return this._build_full_match_preview(edit);
|
|
3544
|
+
}
|
|
3198
3545
|
if (edit._resolved_proxy_edit) {
|
|
3199
3546
|
edit = edit._resolved_proxy_edit;
|
|
3200
3547
|
}
|
|
@@ -3213,17 +3560,33 @@ var RedlineEngine = class {
|
|
|
3213
3560
|
new_text = clean_new;
|
|
3214
3561
|
}
|
|
3215
3562
|
const length = target_text.length;
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
if (
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3563
|
+
let context_before;
|
|
3564
|
+
let context_after;
|
|
3565
|
+
if (edit._preview_context) {
|
|
3566
|
+
[context_before, context_after] = edit._preview_context;
|
|
3567
|
+
} else {
|
|
3568
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3569
|
+
const full_text = active_mapper.full_text;
|
|
3570
|
+
if (!full_text) return [null, null];
|
|
3571
|
+
context_before = _RedlineEngine._tidy_preview_context(
|
|
3572
|
+
full_text.substring(
|
|
3573
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3574
|
+
start_idx
|
|
3575
|
+
),
|
|
3576
|
+
"before"
|
|
3577
|
+
);
|
|
3578
|
+
context_after = _RedlineEngine._tidy_preview_context(
|
|
3579
|
+
full_text.substring(
|
|
3580
|
+
start_idx + length,
|
|
3581
|
+
start_idx + length + PREVIEW_CONTEXT_CHARS
|
|
3582
|
+
),
|
|
3583
|
+
"after"
|
|
3584
|
+
);
|
|
3585
|
+
}
|
|
3586
|
+
const display_target = truncate_middle(target_text, PREVIEW_TEXT_CAP);
|
|
3587
|
+
const display_new = truncate_middle(new_text, PREVIEW_TEXT_CAP);
|
|
3588
|
+
const insertion = display_new ? `{++${display_new}++}` : "";
|
|
3589
|
+
const critic_markup = `${context_before}{--${display_target}--}${insertion}${context_after}`;
|
|
3227
3590
|
let clean_text = critic_markup;
|
|
3228
3591
|
clean_text = clean_text.replace(/\{>>.*?<<\}/gs, "");
|
|
3229
3592
|
clean_text = clean_text.replace(/\{--.*?--\}/gs, "");
|
|
@@ -4164,7 +4527,7 @@ var RedlineEngine = class {
|
|
|
4164
4527
|
const hint = this._nearest_match_hint(edit.target_text, is_regex);
|
|
4165
4528
|
errors.push(
|
|
4166
4529
|
`- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
|
|
4167
|
-
"${edit.target_text}"${hint}`
|
|
4530
|
+
"${truncate_middle(edit.target_text, REPORT_ECHO_CAP)}"${hint}`
|
|
4168
4531
|
);
|
|
4169
4532
|
}
|
|
4170
4533
|
} else if (matches.length > 1 && match_mode === "strict") {
|
|
@@ -4265,9 +4628,48 @@ var RedlineEngine = class {
|
|
|
4265
4628
|
);
|
|
4266
4629
|
}
|
|
4267
4630
|
}
|
|
4631
|
+
if ((edit.type === "insert_row" || edit.type === "delete_row") && matches.length > 0) {
|
|
4632
|
+
const [start, length] = matches[0];
|
|
4633
|
+
const n_cols = _RedlineEngine._column_count_at(
|
|
4634
|
+
target_mapper,
|
|
4635
|
+
start,
|
|
4636
|
+
length
|
|
4637
|
+
);
|
|
4638
|
+
if (n_cols === null) {
|
|
4639
|
+
errors.push(
|
|
4640
|
+
`- Edit ${i + 1 + index_offset} Failed: ${edit.type} target text was found, but it is not inside a table row. Anchor the operation on text that appears within the table.`
|
|
4641
|
+
);
|
|
4642
|
+
} else if (edit.type === "insert_row" && Array.isArray(edit.cells) && edit.cells.length > n_cols) {
|
|
4643
|
+
errors.push(
|
|
4644
|
+
`- Edit ${i + 1 + index_offset} Failed: insert_row provides ${edit.cells.length} cells but the target table has ${n_cols} column(s). The extra cell(s) would be dropped. Provide at most ${n_cols} cells \u2014 rows given fewer cells are padded with empty ones.`
|
|
4645
|
+
);
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4268
4648
|
}
|
|
4269
4649
|
return errors;
|
|
4270
4650
|
}
|
|
4651
|
+
/**
|
|
4652
|
+
* Number of columns (w:tc elements) in the table row containing the text at
|
|
4653
|
+
* [start, start+length) in `mapper`, or null if that text is not inside a
|
|
4654
|
+
* table row.
|
|
4655
|
+
*/
|
|
4656
|
+
static _column_count_at(mapper, start, length) {
|
|
4657
|
+
for (const s of mapper.spans) {
|
|
4658
|
+
if (s.run === null || s.end <= start || s.start >= start + length) {
|
|
4659
|
+
continue;
|
|
4660
|
+
}
|
|
4661
|
+
let curr = s.run._element;
|
|
4662
|
+
while (curr) {
|
|
4663
|
+
if (curr.nodeType === 1 && curr.tagName === "w:tr") {
|
|
4664
|
+
return findAllDescendants(curr, "w:tc").filter(
|
|
4665
|
+
(tc) => tc.parentNode === curr
|
|
4666
|
+
).length;
|
|
4667
|
+
}
|
|
4668
|
+
curr = curr.parentNode;
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
return null;
|
|
4672
|
+
}
|
|
4271
4673
|
validate_review_actions(actions) {
|
|
4272
4674
|
const errors = [];
|
|
4273
4675
|
for (let i = 0; i < actions.length; i++) {
|
|
@@ -4410,19 +4812,31 @@ var RedlineEngine = class {
|
|
|
4410
4812
|
});
|
|
4411
4813
|
if (dry_run_mode) {
|
|
4412
4814
|
const reports_by_input = new Array(edits.length);
|
|
4815
|
+
const validation_failed_idx = /* @__PURE__ */ new Set();
|
|
4413
4816
|
for (const { edit, i } of ordered_edits) {
|
|
4414
|
-
|
|
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
|
+
}
|
|
4415
4824
|
if (single_errors.length > 0) {
|
|
4825
|
+
if (applied_edits > 0) {
|
|
4826
|
+
const hint = sequential_context_hint(applied_edits);
|
|
4827
|
+
single_errors = single_errors.map((err) => err + hint);
|
|
4828
|
+
}
|
|
4829
|
+
validation_failed_idx.add(i);
|
|
4416
4830
|
skipped_edits++;
|
|
4417
4831
|
const warning = this._check_punctuation_warning(
|
|
4418
4832
|
edit.target_text || ""
|
|
4419
4833
|
);
|
|
4420
4834
|
reports_by_input[i] = {
|
|
4421
4835
|
status: "failed",
|
|
4422
|
-
target_text: edit.target_text || "",
|
|
4423
|
-
new_text: edit
|
|
4836
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4837
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4424
4838
|
warning,
|
|
4425
|
-
error: single_errors
|
|
4839
|
+
error: single_errors.join("\n"),
|
|
4426
4840
|
critic_markup: null,
|
|
4427
4841
|
clean_text: null
|
|
4428
4842
|
};
|
|
@@ -4434,8 +4848,8 @@ var RedlineEngine = class {
|
|
|
4434
4848
|
const previews = this._build_edit_context_previews(edit);
|
|
4435
4849
|
reports_by_input[i] = {
|
|
4436
4850
|
status: "applied",
|
|
4437
|
-
target_text: edit.target_text || "",
|
|
4438
|
-
new_text: edit
|
|
4851
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4852
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4439
4853
|
warning: null,
|
|
4440
4854
|
error: null,
|
|
4441
4855
|
critic_markup: previews[0],
|
|
@@ -4455,8 +4869,8 @@ var RedlineEngine = class {
|
|
|
4455
4869
|
);
|
|
4456
4870
|
reports_by_input[i] = {
|
|
4457
4871
|
status: "failed",
|
|
4458
|
-
target_text: edit.target_text || "",
|
|
4459
|
-
new_text: edit
|
|
4872
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4873
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4460
4874
|
warning,
|
|
4461
4875
|
error: error_msg,
|
|
4462
4876
|
critic_markup: null,
|
|
@@ -4464,18 +4878,51 @@ var RedlineEngine = class {
|
|
|
4464
4878
|
};
|
|
4465
4879
|
}
|
|
4466
4880
|
}
|
|
4881
|
+
if (validation_failed_idx.size > 0) {
|
|
4882
|
+
applied_edits = 0;
|
|
4883
|
+
skipped_edits = edits.length;
|
|
4884
|
+
for (let i = 0; i < reports_by_input.length; i++) {
|
|
4885
|
+
const report = reports_by_input[i];
|
|
4886
|
+
if (!report || validation_failed_idx.has(i)) continue;
|
|
4887
|
+
if (report.status === "applied") {
|
|
4888
|
+
reports_by_input[i] = {
|
|
4889
|
+
status: "failed",
|
|
4890
|
+
target_text: report.target_text,
|
|
4891
|
+
new_text: report.new_text,
|
|
4892
|
+
warning: null,
|
|
4893
|
+
error: TRANSACTIONAL_NOT_APPLIED_ERROR,
|
|
4894
|
+
critic_markup: null,
|
|
4895
|
+
clean_text: null
|
|
4896
|
+
};
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4467
4900
|
edits_reports.push(...reports_by_input);
|
|
4468
4901
|
} else {
|
|
4469
4902
|
const snapshot = takeSnapshot(this.doc);
|
|
4470
4903
|
const originalCurrentId = this.current_id;
|
|
4471
4904
|
try {
|
|
4472
4905
|
const sequential_errors = [];
|
|
4906
|
+
let applied_so_far = 0;
|
|
4473
4907
|
for (const { edit, i } of ordered_edits) {
|
|
4474
|
-
|
|
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
|
+
}
|
|
4475
4915
|
if (single_errors.length > 0) {
|
|
4916
|
+
if (applied_so_far > 0) {
|
|
4917
|
+
const hint = sequential_context_hint(applied_so_far);
|
|
4918
|
+
single_errors = single_errors.map((err) => err + hint);
|
|
4919
|
+
}
|
|
4476
4920
|
sequential_errors.push(...single_errors);
|
|
4477
4921
|
} else {
|
|
4478
4922
|
this.apply_edits([edit], page_offsets);
|
|
4923
|
+
if (edit._applied_status) {
|
|
4924
|
+
applied_so_far++;
|
|
4925
|
+
}
|
|
4479
4926
|
this.mapper = new DocumentMapper(this.doc);
|
|
4480
4927
|
this.clean_mapper = null;
|
|
4481
4928
|
}
|
|
@@ -4510,8 +4957,8 @@ var RedlineEngine = class {
|
|
|
4510
4957
|
}
|
|
4511
4958
|
edits_reports.push({
|
|
4512
4959
|
status: success ? "applied" : "failed",
|
|
4513
|
-
target_text: edit.target_text || "",
|
|
4514
|
-
new_text: edit
|
|
4960
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4961
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4515
4962
|
warning,
|
|
4516
4963
|
error: error_msg,
|
|
4517
4964
|
critic_markup,
|
|
@@ -4578,7 +5025,18 @@ var RedlineEngine = class {
|
|
|
4578
5025
|
edit._error_msg = msg;
|
|
4579
5026
|
}
|
|
4580
5027
|
} else {
|
|
4581
|
-
|
|
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
|
+
}
|
|
4582
5040
|
if (resolved) {
|
|
4583
5041
|
if (Array.isArray(resolved)) {
|
|
4584
5042
|
for (const r of resolved) {
|
|
@@ -4613,6 +5071,12 @@ var RedlineEngine = class {
|
|
|
4613
5071
|
resolved_edits.sort(
|
|
4614
5072
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4615
5073
|
);
|
|
5074
|
+
for (const [res_edit] of resolved_edits) {
|
|
5075
|
+
this._capture_preview_context(res_edit);
|
|
5076
|
+
if (res_edit._parent_edit_ref) {
|
|
5077
|
+
this._capture_parent_preview_context(res_edit._parent_edit_ref);
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
4616
5080
|
const occupied_ranges = [];
|
|
4617
5081
|
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4618
5082
|
for (const [edit, orig_new] of resolved_edits) {
|
|
@@ -4731,6 +5195,16 @@ var RedlineEngine = class {
|
|
|
4731
5195
|
);
|
|
4732
5196
|
continue;
|
|
4733
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
|
+
}
|
|
4734
5208
|
for (const node of all_nodes) {
|
|
4735
5209
|
const is_ins = node.tagName === "w:ins";
|
|
4736
5210
|
const parent_tag = node.parentNode ? node.parentNode.tagName : "";
|
|
@@ -4813,7 +5287,15 @@ var RedlineEngine = class {
|
|
|
4813
5287
|
const trPr = tr.ownerDocument.createElement("w:trPr");
|
|
4814
5288
|
new_tr.appendChild(trPr);
|
|
4815
5289
|
trPr.appendChild(this._create_track_change_tag("w:ins"));
|
|
4816
|
-
|
|
5290
|
+
const anchor_cols = findAllDescendants(tr, "w:tc").filter(
|
|
5291
|
+
(tc) => tc.parentNode === tr
|
|
5292
|
+
).length;
|
|
5293
|
+
let cell_values = Array.isArray(edit.cells) ? [...edit.cells] : [];
|
|
5294
|
+
if (anchor_cols > 0) {
|
|
5295
|
+
while (cell_values.length < anchor_cols) cell_values.push("");
|
|
5296
|
+
cell_values = cell_values.slice(0, anchor_cols);
|
|
5297
|
+
}
|
|
5298
|
+
for (const cellText of cell_values) {
|
|
4817
5299
|
const tc = tr.ownerDocument.createElement("w:tc");
|
|
4818
5300
|
const p = tr.ownerDocument.createElement("w:p");
|
|
4819
5301
|
const r = tr.ownerDocument.createElement("w:r");
|
|
@@ -4909,6 +5391,12 @@ var RedlineEngine = class {
|
|
|
4909
5391
|
} catch (e) {
|
|
4910
5392
|
}
|
|
4911
5393
|
}
|
|
5394
|
+
if (!edit._preview_span) {
|
|
5395
|
+
edit._preview_span = [start_idx, match_len];
|
|
5396
|
+
edit._preview_matched_text = actual_doc_text;
|
|
5397
|
+
edit._preview_new_text = current_effective_new_text;
|
|
5398
|
+
edit._preview_mapper_ref = active_mapper;
|
|
5399
|
+
}
|
|
4912
5400
|
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4913
5401
|
edit.target_text
|
|
4914
5402
|
);
|
|
@@ -6032,6 +6520,27 @@ function scrub_doc_properties(doc) {
|
|
|
6032
6520
|
c.textContent = "";
|
|
6033
6521
|
}
|
|
6034
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
|
+
}
|
|
6035
6544
|
const revisions = findDescendantsByLocalName(corePart._element, "revision");
|
|
6036
6545
|
revisions.forEach((c) => {
|
|
6037
6546
|
if (c.textContent && parseInt(c.textContent) > 1) {
|
|
@@ -6237,7 +6746,11 @@ function extract_all_domain_metadata(doc, base_text) {
|
|
|
6237
6746
|
for (const term of def_keys) {
|
|
6238
6747
|
if (definitions[term].count === 0) {
|
|
6239
6748
|
delete definitions[term];
|
|
6240
|
-
duplicates.
|
|
6749
|
+
if (!duplicates.has(term)) {
|
|
6750
|
+
diagnostics.push(
|
|
6751
|
+
`[Warning] Unused Definition: '${term}' is defined but never used.`
|
|
6752
|
+
);
|
|
6753
|
+
}
|
|
6241
6754
|
}
|
|
6242
6755
|
}
|
|
6243
6756
|
}
|
|
@@ -7573,10 +8086,13 @@ function identifyEngine() {
|
|
|
7573
8086
|
DocumentMapper,
|
|
7574
8087
|
DocumentObject,
|
|
7575
8088
|
RedlineEngine,
|
|
8089
|
+
RegexTimeoutError,
|
|
8090
|
+
USER_PATTERN_TIMEOUT_MS,
|
|
7576
8091
|
_extractTextFromDoc,
|
|
7577
8092
|
apply_edits_to_markdown,
|
|
7578
8093
|
create_unified_diff,
|
|
7579
8094
|
create_word_patch_diff,
|
|
8095
|
+
describe_illegal_control_chars,
|
|
7580
8096
|
extractTextFromBuffer,
|
|
7581
8097
|
extract_outline,
|
|
7582
8098
|
finalize_document,
|
|
@@ -7584,6 +8100,9 @@ function identifyEngine() {
|
|
|
7584
8100
|
identifyEngine,
|
|
7585
8101
|
paginate,
|
|
7586
8102
|
split_structural_appendix,
|
|
7587
|
-
trim_common_context
|
|
8103
|
+
trim_common_context,
|
|
8104
|
+
userFindAllMatches,
|
|
8105
|
+
userSearch,
|
|
8106
|
+
validate_edit_strings
|
|
7588
8107
|
});
|
|
7589
8108
|
//# sourceMappingURL=index.cjs.map
|