@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.js
CHANGED
|
@@ -743,6 +743,62 @@ function extract_comments_data(pkg) {
|
|
|
743
743
|
return data;
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
+
// src/utils/safe-regex.ts
|
|
747
|
+
import * as vm from "vm";
|
|
748
|
+
var USER_PATTERN_TIMEOUT_MS = 2e3;
|
|
749
|
+
var RegexTimeoutError = class extends Error {
|
|
750
|
+
pattern;
|
|
751
|
+
constructor(pattern) {
|
|
752
|
+
super(
|
|
753
|
+
`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.`
|
|
754
|
+
);
|
|
755
|
+
this.name = "RegexTimeoutError";
|
|
756
|
+
this.pattern = pattern;
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
function runBudgeted(pattern, script, sandbox) {
|
|
760
|
+
try {
|
|
761
|
+
return vm.runInNewContext(script, sandbox, {
|
|
762
|
+
timeout: USER_PATTERN_TIMEOUT_MS
|
|
763
|
+
});
|
|
764
|
+
} catch (e) {
|
|
765
|
+
if (e && e.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
|
|
766
|
+
throw new RegexTimeoutError(pattern);
|
|
767
|
+
}
|
|
768
|
+
throw e;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function userFindAllMatches(pattern, text, flags = "") {
|
|
772
|
+
const normalized = flags.includes("g") ? flags : flags + "g";
|
|
773
|
+
const re = new RegExp(pattern, normalized);
|
|
774
|
+
const raw = runBudgeted(
|
|
775
|
+
pattern,
|
|
776
|
+
`{
|
|
777
|
+
const out = [];
|
|
778
|
+
let m;
|
|
779
|
+
while ((m = re.exec(text)) !== null) {
|
|
780
|
+
out.push({ start: m.index, end: m.index + m[0].length });
|
|
781
|
+
if (m.index === re.lastIndex) re.lastIndex++;
|
|
782
|
+
}
|
|
783
|
+
out;
|
|
784
|
+
}`,
|
|
785
|
+
{ re, text }
|
|
786
|
+
);
|
|
787
|
+
return raw.map((r) => ({ start: r.start, end: r.end }));
|
|
788
|
+
}
|
|
789
|
+
function userSearch(pattern, text, flags = "") {
|
|
790
|
+
const re = new RegExp(pattern, flags.replace("g", ""));
|
|
791
|
+
const raw = runBudgeted(
|
|
792
|
+
pattern,
|
|
793
|
+
`{
|
|
794
|
+
const m = re.exec(text);
|
|
795
|
+
m ? { start: m.index, end: m.index + m[0].length } : null;
|
|
796
|
+
}`,
|
|
797
|
+
{ re, text }
|
|
798
|
+
);
|
|
799
|
+
return raw ? { start: raw.start, end: raw.end } : null;
|
|
800
|
+
}
|
|
801
|
+
|
|
746
802
|
// src/utils/docx.ts
|
|
747
803
|
var QN_W_P = "w:p";
|
|
748
804
|
var QN_W_R = "w:r";
|
|
@@ -1213,6 +1269,7 @@ function* iter_paragraph_content(paragraph) {
|
|
|
1213
1269
|
}
|
|
1214
1270
|
|
|
1215
1271
|
// src/mapper.ts
|
|
1272
|
+
var STYLE_MARKER_TEXTS = /* @__PURE__ */ new Set(["**", "__", "*", "_"]);
|
|
1216
1273
|
var DocumentMapper = class {
|
|
1217
1274
|
doc;
|
|
1218
1275
|
clean_view;
|
|
@@ -1222,6 +1279,7 @@ var DocumentMapper = class {
|
|
|
1222
1279
|
spans = [];
|
|
1223
1280
|
appendix_start_index = -1;
|
|
1224
1281
|
_text_chunks = [];
|
|
1282
|
+
_plain_projection = null;
|
|
1225
1283
|
constructor(doc, clean_view = false, original_view = false) {
|
|
1226
1284
|
this.doc = doc;
|
|
1227
1285
|
this.clean_view = clean_view;
|
|
@@ -1234,6 +1292,7 @@ var DocumentMapper = class {
|
|
|
1234
1292
|
this.spans = [];
|
|
1235
1293
|
this._text_chunks = [];
|
|
1236
1294
|
this.full_text = "";
|
|
1295
|
+
this._plain_projection = null;
|
|
1237
1296
|
for (const part of iter_document_parts(this.doc)) {
|
|
1238
1297
|
current_offset = this._map_blocks(part, current_offset);
|
|
1239
1298
|
if (this.spans.length > 0 && this.spans[this.spans.length - 1].text !== "\n\n") {
|
|
@@ -1753,13 +1812,71 @@ ${header}`;
|
|
|
1753
1812
|
if (remaining) parts.push(escapeRegExp(remaining));
|
|
1754
1813
|
return parts.join("");
|
|
1755
1814
|
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Returns [plain_text, offset_map] where plain_text is full_text with the
|
|
1817
|
+
* VIRTUAL markdown style delimiters (bold/italic markers emitted around
|
|
1818
|
+
* formatted runs) removed, and offset_map[i] is the full_text index of
|
|
1819
|
+
* plain_text[i].
|
|
1820
|
+
*
|
|
1821
|
+
* Formatting run boundaries can fall mid-word (e.g. a paragraph projected
|
|
1822
|
+
* as "**Al**pha"), where neither exact matching nor the whitespace-anchored
|
|
1823
|
+
* fuzzy regex can find the plain target "Alpha". Matching against this
|
|
1824
|
+
* projection and mapping the span back to full_text closes that gap (QA H2).
|
|
1825
|
+
*
|
|
1826
|
+
* Built lazily and invalidated by _build_map(): most batches never need it.
|
|
1827
|
+
*/
|
|
1828
|
+
_get_plain_projection() {
|
|
1829
|
+
if (this._plain_projection === null) {
|
|
1830
|
+
const chunks = [];
|
|
1831
|
+
const offsets = [];
|
|
1832
|
+
for (const s of this.spans) {
|
|
1833
|
+
if (s.run === null && s.paragraph !== null && STYLE_MARKER_TEXTS.has(s.text)) {
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
chunks.push(s.text);
|
|
1837
|
+
for (let k = s.start; k < s.end; k++) offsets.push(k);
|
|
1838
|
+
}
|
|
1839
|
+
this._plain_projection = [chunks.join(""), offsets];
|
|
1840
|
+
}
|
|
1841
|
+
return this._plain_projection;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Matches a markdown-stripped target against the plain projection and maps
|
|
1845
|
+
* each hit back to a [start, length] span in full_text. Interior style
|
|
1846
|
+
* markers end up inside the returned span (so "Alpha" over "**Al**pha"
|
|
1847
|
+
* resolves to the "Al**pha" range); markers just outside the matched
|
|
1848
|
+
* characters are excluded.
|
|
1849
|
+
*/
|
|
1850
|
+
_find_plain_projection_matches(target_text) {
|
|
1851
|
+
const [plain_text, offsets] = this._get_plain_projection();
|
|
1852
|
+
if (plain_text.length === this.full_text.length) {
|
|
1853
|
+
return [];
|
|
1854
|
+
}
|
|
1855
|
+
const norm_target = this._replace_smart_quotes(
|
|
1856
|
+
this._strip_markdown_formatting(target_text)
|
|
1857
|
+
);
|
|
1858
|
+
if (!norm_target) return [];
|
|
1859
|
+
const norm_plain = this._replace_smart_quotes(plain_text);
|
|
1860
|
+
const results = [];
|
|
1861
|
+
let from = 0;
|
|
1862
|
+
while (true) {
|
|
1863
|
+
const p_start = norm_plain.indexOf(norm_target, from);
|
|
1864
|
+
if (p_start === -1) break;
|
|
1865
|
+
const p_end = p_start + norm_target.length;
|
|
1866
|
+
const raw_start = offsets[p_start];
|
|
1867
|
+
const raw_end = offsets[p_end - 1] + 1;
|
|
1868
|
+
results.push([raw_start, raw_end - raw_start]);
|
|
1869
|
+
from = p_end;
|
|
1870
|
+
}
|
|
1871
|
+
return results;
|
|
1872
|
+
}
|
|
1756
1873
|
find_match_index(target_text, is_regex = false) {
|
|
1757
1874
|
if (is_regex) {
|
|
1758
1875
|
try {
|
|
1759
|
-
const
|
|
1760
|
-
|
|
1761
|
-
if (match) return [match.index, match[0].length];
|
|
1876
|
+
const match = userSearch(target_text, this.full_text);
|
|
1877
|
+
if (match) return [match.start, match.end - match.start];
|
|
1762
1878
|
} catch (e) {
|
|
1879
|
+
if (e instanceof RegexTimeoutError) throw e;
|
|
1763
1880
|
}
|
|
1764
1881
|
return [-1, 0];
|
|
1765
1882
|
}
|
|
@@ -1774,6 +1891,8 @@ ${header}`;
|
|
|
1774
1891
|
start_idx = this.full_text.indexOf(stripped_target);
|
|
1775
1892
|
return [start_idx, stripped_target.length];
|
|
1776
1893
|
}
|
|
1894
|
+
const plain_first = this._find_plain_projection_matches(target_text);
|
|
1895
|
+
if (plain_first.length > 0) return plain_first[0];
|
|
1777
1896
|
try {
|
|
1778
1897
|
const pattern = new RegExp(this._make_fuzzy_regex(target_text));
|
|
1779
1898
|
const match = pattern.exec(this.full_text);
|
|
@@ -1786,37 +1905,40 @@ ${header}`;
|
|
|
1786
1905
|
if (!target_text) return [];
|
|
1787
1906
|
if (is_regex) {
|
|
1788
1907
|
try {
|
|
1789
|
-
const
|
|
1790
|
-
|
|
1791
|
-
if (matches2.length > 0)
|
|
1792
|
-
return matches2.map((m) => [m.index, m[0].length]);
|
|
1908
|
+
const matches2 = userFindAllMatches(target_text, this.full_text);
|
|
1909
|
+
if (matches2.length > 0) return matches2.map((m) => [m.start, m.end - m.start]);
|
|
1793
1910
|
} catch (e) {
|
|
1911
|
+
if (e instanceof RegexTimeoutError) throw e;
|
|
1794
1912
|
}
|
|
1795
1913
|
return [];
|
|
1796
1914
|
}
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1915
|
+
const findAllLiteral = (haystack, needle) => {
|
|
1916
|
+
const out = [];
|
|
1917
|
+
if (!needle) return out;
|
|
1918
|
+
let from = 0;
|
|
1919
|
+
while (true) {
|
|
1920
|
+
const idx = haystack.indexOf(needle, from);
|
|
1921
|
+
if (idx === -1) break;
|
|
1922
|
+
out.push([idx, needle.length]);
|
|
1923
|
+
from = idx + needle.length;
|
|
1924
|
+
}
|
|
1925
|
+
return out;
|
|
1926
|
+
};
|
|
1927
|
+
let matches = findAllLiteral(this.full_text, target_text);
|
|
1928
|
+
if (matches.length > 0) return matches;
|
|
1802
1929
|
const norm_full = this._replace_smart_quotes(this.full_text);
|
|
1803
1930
|
const norm_target = this._replace_smart_quotes(target_text);
|
|
1804
|
-
matches =
|
|
1805
|
-
|
|
1806
|
-
];
|
|
1807
|
-
if (matches.length > 0) return matches.map((m) => [m.index, m[0].length]);
|
|
1931
|
+
matches = findAllLiteral(norm_full, norm_target);
|
|
1932
|
+
if (matches.length > 0) return matches;
|
|
1808
1933
|
const stripped_target = this._strip_markdown_formatting(target_text);
|
|
1809
|
-
matches =
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
];
|
|
1814
|
-
if (matches.length > 0) return matches.map((m) => [m.index, m[0].length]);
|
|
1934
|
+
matches = findAllLiteral(this.full_text, stripped_target);
|
|
1935
|
+
if (matches.length > 0) return matches;
|
|
1936
|
+
const plain_matches = this._find_plain_projection_matches(target_text);
|
|
1937
|
+
if (plain_matches.length > 0) return plain_matches;
|
|
1815
1938
|
try {
|
|
1816
1939
|
const pattern = new RegExp(this._make_fuzzy_regex(target_text), "g");
|
|
1817
|
-
|
|
1818
|
-
if (
|
|
1819
|
-
return matches.map((m) => [m.index, m[0].length]);
|
|
1940
|
+
const fuzzy = [...this.full_text.matchAll(pattern)];
|
|
1941
|
+
if (fuzzy.length > 0) return fuzzy.map((m) => [m.index, m[0].length]);
|
|
1820
1942
|
} catch (e) {
|
|
1821
1943
|
}
|
|
1822
1944
|
return [];
|
|
@@ -2764,7 +2886,9 @@ function apply_edits_to_markdown(markdown_text, edits, include_index = false, hi
|
|
|
2764
2886
|
isolated_target,
|
|
2765
2887
|
isolated_new,
|
|
2766
2888
|
edit.comment,
|
|
2767
|
-
|
|
2889
|
+
// 1-based, matching apply's "Edit N" reports and batch validation
|
|
2890
|
+
// errors (QA 2026-07-17 F10; mirrors Python).
|
|
2891
|
+
orig_idx + 1,
|
|
2768
2892
|
include_index,
|
|
2769
2893
|
highlight_only
|
|
2770
2894
|
);
|
|
@@ -2806,18 +2930,30 @@ function format_ambiguity_error(edit_index, target_text, haystack, match_positio
|
|
|
2806
2930
|
}
|
|
2807
2931
|
lines.push(" To resolve, re-send this edit using ONE of these strategies:");
|
|
2808
2932
|
lines.push(
|
|
2809
|
-
|
|
2933
|
+
' 1. RECOMMENDED: Provide more surrounding context in your target_text to uniquely identify a single location (keep the default "match_mode": "strict").'
|
|
2810
2934
|
);
|
|
2811
2935
|
lines.push(
|
|
2812
|
-
|
|
2936
|
+
` 2. Set "match_mode": "all" to modify ALL ${total} occurrences \u2014 only after verifying from the occurrence list above that EVERY occurrence should change.`
|
|
2813
2937
|
);
|
|
2814
2938
|
lines.push(
|
|
2815
|
-
' 3.
|
|
2939
|
+
' 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.'
|
|
2816
2940
|
);
|
|
2817
2941
|
return lines.join("\n");
|
|
2818
2942
|
}
|
|
2819
2943
|
|
|
2944
|
+
// src/utils/text.ts
|
|
2945
|
+
var REPORT_ECHO_CAP = 500;
|
|
2946
|
+
var PREVIEW_TEXT_CAP = 200;
|
|
2947
|
+
function truncate_middle(text, cap) {
|
|
2948
|
+
if (text === null || text === void 0 || text.length <= cap) return text;
|
|
2949
|
+
const head = Math.max(1, Math.floor(cap * 2 / 3));
|
|
2950
|
+
const tail = Math.max(1, cap - head);
|
|
2951
|
+
const omitted = text.length - head - tail;
|
|
2952
|
+
return `${text.slice(0, head)}\u2026 [${omitted.toLocaleString("en-US")} chars omitted] \u2026${text.slice(-tail)}`;
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2820
2955
|
// src/engine.ts
|
|
2956
|
+
var PREVIEW_CONTEXT_CHARS = 30;
|
|
2821
2957
|
function getNextElement(el) {
|
|
2822
2958
|
let next = el.nextSibling;
|
|
2823
2959
|
while (next) {
|
|
@@ -2892,12 +3028,41 @@ var BatchValidationError = class extends Error {
|
|
|
2892
3028
|
this.errors = errors;
|
|
2893
3029
|
}
|
|
2894
3030
|
};
|
|
3031
|
+
function sequential_context_hint(applied_so_far) {
|
|
3032
|
+
return `
|
|
3033
|
+
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).`;
|
|
3034
|
+
}
|
|
3035
|
+
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.";
|
|
3036
|
+
var XML_ILLEGAL_CHARS_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
|
|
3037
|
+
function describe_illegal_control_chars(text) {
|
|
3038
|
+
if (!text) return null;
|
|
3039
|
+
const found = text.match(XML_ILLEGAL_CHARS_RE);
|
|
3040
|
+
if (!found) return null;
|
|
3041
|
+
const codes = Array.from(new Set(found.map((c) => `0x${c.charCodeAt(0).toString(16).padStart(2, "0")}`))).sort();
|
|
3042
|
+
return codes.join(", ");
|
|
3043
|
+
}
|
|
2895
3044
|
function validate_edit_strings(edits, index_offset = 0) {
|
|
2896
3045
|
const errors = [];
|
|
2897
3046
|
for (let i = 0; i < edits.length; i++) {
|
|
2898
3047
|
const edit = edits[i];
|
|
2899
3048
|
const t_text = edit.target_text || "";
|
|
2900
3049
|
const n_text = edit.new_text || "";
|
|
3050
|
+
const checked_fields = [
|
|
3051
|
+
["target_text", t_text],
|
|
3052
|
+
["new_text", n_text]
|
|
3053
|
+
];
|
|
3054
|
+
if (edit.comment) checked_fields.push(["comment", edit.comment]);
|
|
3055
|
+
(edit.cells || []).forEach((cell, cell_idx) => {
|
|
3056
|
+
checked_fields.push([`cells[${cell_idx}]`, cell || ""]);
|
|
3057
|
+
});
|
|
3058
|
+
for (const [field_name, field_value] of checked_fields) {
|
|
3059
|
+
const described = describe_illegal_control_chars(field_value);
|
|
3060
|
+
if (described) {
|
|
3061
|
+
errors.push(
|
|
3062
|
+
`- 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.`
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2901
3066
|
if (n_text.includes("{++") || n_text.includes("{--") || n_text.includes("{>>") || n_text.includes("{==")) {
|
|
2902
3067
|
errors.push(
|
|
2903
3068
|
`- 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.`
|
|
@@ -2995,7 +3160,7 @@ function validate_edit_strings(edits, index_offset = 0) {
|
|
|
2995
3160
|
}
|
|
2996
3161
|
return errors;
|
|
2997
3162
|
}
|
|
2998
|
-
var RedlineEngine = class {
|
|
3163
|
+
var RedlineEngine = class _RedlineEngine {
|
|
2999
3164
|
doc;
|
|
3000
3165
|
author;
|
|
3001
3166
|
timestamp;
|
|
@@ -3142,8 +3307,184 @@ var RedlineEngine = class {
|
|
|
3142
3307
|
}
|
|
3143
3308
|
return "";
|
|
3144
3309
|
}
|
|
3310
|
+
// CriticMarkup wrapper pairs used when tidying preview context windows.
|
|
3311
|
+
static _PREVIEW_WRAPPER_PAIRS = [
|
|
3312
|
+
["{--", "--}"],
|
|
3313
|
+
["{++", "++}"],
|
|
3314
|
+
["{==", "==}"],
|
|
3315
|
+
["{>>", "<<}"]
|
|
3316
|
+
];
|
|
3317
|
+
/**
|
|
3318
|
+
* Makes a fixed-width slice of the raw-view projection presentable: drops
|
|
3319
|
+
* complete {>>...<<} meta blocks (annotations of pre-existing changes, not
|
|
3320
|
+
* part of this edit) and any wrapper fragments the window boundary chopped
|
|
3321
|
+
* in half. Without this, previews leak internal scaffolding like
|
|
3322
|
+
* "[Chg:5 delete]" (QA H1). Mirrors the Python engine.
|
|
3323
|
+
*/
|
|
3324
|
+
static _tidy_preview_context(snippet, side) {
|
|
3325
|
+
snippet = snippet.replace(/\{>>[\s\S]*?<<\}/g, "");
|
|
3326
|
+
for (const [open_tok, close_tok] of _RedlineEngine._PREVIEW_WRAPPER_PAIRS) {
|
|
3327
|
+
if (side === "before") {
|
|
3328
|
+
let depth = 0;
|
|
3329
|
+
let cut = 0;
|
|
3330
|
+
let i = 0;
|
|
3331
|
+
while (i < snippet.length) {
|
|
3332
|
+
if (snippet.startsWith(open_tok, i)) {
|
|
3333
|
+
depth += 1;
|
|
3334
|
+
i += open_tok.length;
|
|
3335
|
+
} else if (snippet.startsWith(close_tok, i)) {
|
|
3336
|
+
if (depth === 0) cut = i + close_tok.length;
|
|
3337
|
+
else depth -= 1;
|
|
3338
|
+
i += close_tok.length;
|
|
3339
|
+
} else {
|
|
3340
|
+
i += 1;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
snippet = snippet.substring(cut);
|
|
3344
|
+
} else {
|
|
3345
|
+
const opens = [];
|
|
3346
|
+
let i = 0;
|
|
3347
|
+
while (i < snippet.length) {
|
|
3348
|
+
if (snippet.startsWith(open_tok, i)) {
|
|
3349
|
+
opens.push(i);
|
|
3350
|
+
i += open_tok.length;
|
|
3351
|
+
} else if (snippet.startsWith(close_tok, i)) {
|
|
3352
|
+
if (opens.length > 0) opens.pop();
|
|
3353
|
+
i += close_tok.length;
|
|
3354
|
+
} else {
|
|
3355
|
+
i += 1;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
if (opens.length > 0) snippet = snippet.substring(0, opens[0]);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
if (side === "before") {
|
|
3362
|
+
snippet = snippet.replace(/^[-+=<>]{0,2}\}/, "");
|
|
3363
|
+
} else {
|
|
3364
|
+
snippet = snippet.replace(/\{[-+=<>]{0,2}$/, "");
|
|
3365
|
+
}
|
|
3366
|
+
return snippet;
|
|
3367
|
+
}
|
|
3368
|
+
/**
|
|
3369
|
+
* Snapshots the document text around a resolved edit BEFORE anything is
|
|
3370
|
+
* applied. Previews rendered after the batch mutates the DOM cannot slice
|
|
3371
|
+
* full_text at the stored offsets: applied edits shift offsets and inject
|
|
3372
|
+
* tracked-change markup, garbling previews with unrelated edits and
|
|
3373
|
+
* internal scaffolding (QA H1).
|
|
3374
|
+
*/
|
|
3375
|
+
_capture_preview_context(edit) {
|
|
3376
|
+
if (edit.type !== "modify") return;
|
|
3377
|
+
const start_idx = edit._resolved_start_idx;
|
|
3378
|
+
if (start_idx === void 0 || start_idx === null) return;
|
|
3379
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3380
|
+
const full_text = active_mapper.full_text;
|
|
3381
|
+
if (!full_text) return;
|
|
3382
|
+
const length = (edit.target_text || "").length;
|
|
3383
|
+
const before = full_text.substring(
|
|
3384
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3385
|
+
start_idx
|
|
3386
|
+
);
|
|
3387
|
+
const after = full_text.substring(
|
|
3388
|
+
start_idx + length,
|
|
3389
|
+
start_idx + length + PREVIEW_CONTEXT_CHARS
|
|
3390
|
+
);
|
|
3391
|
+
edit._preview_context = [
|
|
3392
|
+
_RedlineEngine._tidy_preview_context(before, "before"),
|
|
3393
|
+
_RedlineEngine._tidy_preview_context(after, "after")
|
|
3394
|
+
];
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Like _capture_preview_context, but snapshots the context around the
|
|
3398
|
+
* ORIGINAL edit's full matched span (stashed by _pre_resolve_heuristic_edit),
|
|
3399
|
+
* so the report preview can present the complete logical change of a
|
|
3400
|
+
* compound modification instead of its first sub-edit.
|
|
3401
|
+
*/
|
|
3402
|
+
_capture_parent_preview_context(parent) {
|
|
3403
|
+
if (!parent || parent.type !== "modify") return;
|
|
3404
|
+
if (parent._preview_context || !parent._preview_span) return;
|
|
3405
|
+
const [start_idx, match_len] = parent._preview_span;
|
|
3406
|
+
const active_mapper = parent._preview_mapper_ref || this.mapper;
|
|
3407
|
+
const full_text = active_mapper.full_text;
|
|
3408
|
+
if (!full_text) return;
|
|
3409
|
+
const before = full_text.substring(
|
|
3410
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3411
|
+
start_idx
|
|
3412
|
+
);
|
|
3413
|
+
const after = full_text.substring(
|
|
3414
|
+
start_idx + match_len,
|
|
3415
|
+
start_idx + match_len + PREVIEW_CONTEXT_CHARS
|
|
3416
|
+
);
|
|
3417
|
+
parent._preview_context = [
|
|
3418
|
+
_RedlineEngine._tidy_preview_context(before, "before"),
|
|
3419
|
+
_RedlineEngine._tidy_preview_context(after, "after")
|
|
3420
|
+
];
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Renders the preview from the edit's full matched span. The common
|
|
3424
|
+
* prefix/suffix between matched and replacement text is moved into the
|
|
3425
|
+
* surrounding context so the {--...--}{++...++} block shows the minimal
|
|
3426
|
+
* complete change.
|
|
3427
|
+
*/
|
|
3428
|
+
_build_full_match_preview(edit) {
|
|
3429
|
+
let [context_before, context_after] = edit._preview_context;
|
|
3430
|
+
let matched = edit._preview_matched_text || "";
|
|
3431
|
+
let new_text = edit._preview_new_text !== void 0 && edit._preview_new_text !== null ? edit._preview_new_text : edit.new_text || "";
|
|
3432
|
+
const [matched_clean, matched_style] = this._parse_markdown_style(matched);
|
|
3433
|
+
const [new_clean, new_style] = this._parse_markdown_style(new_text);
|
|
3434
|
+
if (matched_style && matched_style.startsWith("Heading")) {
|
|
3435
|
+
context_before = context_before + matched.substring(0, matched.length - matched_clean.length);
|
|
3436
|
+
matched = matched_clean;
|
|
3437
|
+
}
|
|
3438
|
+
if (new_style && new_style.startsWith("Heading")) {
|
|
3439
|
+
new_text = new_clean;
|
|
3440
|
+
}
|
|
3441
|
+
const [prefix_len, suffix_len] = trim_common_context(matched, new_text);
|
|
3442
|
+
let display_target = matched.substring(
|
|
3443
|
+
prefix_len,
|
|
3444
|
+
matched.length - suffix_len
|
|
3445
|
+
);
|
|
3446
|
+
let display_new = new_text.substring(
|
|
3447
|
+
prefix_len,
|
|
3448
|
+
new_text.length - suffix_len
|
|
3449
|
+
);
|
|
3450
|
+
context_before = context_before + matched.substring(0, prefix_len);
|
|
3451
|
+
if (suffix_len) {
|
|
3452
|
+
context_after = matched.substring(matched.length - suffix_len) + context_after;
|
|
3453
|
+
}
|
|
3454
|
+
display_target = truncate_middle(display_target, PREVIEW_TEXT_CAP);
|
|
3455
|
+
display_new = truncate_middle(display_new, PREVIEW_TEXT_CAP);
|
|
3456
|
+
let critic_markup;
|
|
3457
|
+
if (!display_target && !display_new) {
|
|
3458
|
+
const anchor = truncate_middle(matched, PREVIEW_TEXT_CAP);
|
|
3459
|
+
const body = anchor ? `{==${anchor}==}` : "";
|
|
3460
|
+
critic_markup = `${context_before.substring(0, context_before.length - matched.length)}${body}${context_after}`;
|
|
3461
|
+
} else {
|
|
3462
|
+
const deletion = display_target ? `{--${display_target}--}` : "";
|
|
3463
|
+
const insertion = display_new ? `{++${display_new}++}` : "";
|
|
3464
|
+
critic_markup = `${context_before}${deletion}${insertion}${context_after}`;
|
|
3465
|
+
}
|
|
3466
|
+
let clean_text = critic_markup;
|
|
3467
|
+
clean_text = clean_text.replace(/\{>>[\s\S]*?<<\}/g, "");
|
|
3468
|
+
clean_text = clean_text.replace(/\{--[\s\S]*?--\}/g, "");
|
|
3469
|
+
clean_text = clean_text.replace(/\{\+\+([\s\S]*?)\+\+\}/g, "$1");
|
|
3470
|
+
return [critic_markup, clean_text];
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* The "new text" a batch report should show for an edit. InsertTableRow has
|
|
3474
|
+
* no new_text field — surface its cell contents rather than a misleading
|
|
3475
|
+
* empty string (QA M4).
|
|
3476
|
+
*/
|
|
3477
|
+
static _report_new_text(edit) {
|
|
3478
|
+
if (edit && edit.type === "insert_row" && Array.isArray(edit.cells)) {
|
|
3479
|
+
return edit.cells.join(" | ");
|
|
3480
|
+
}
|
|
3481
|
+
return edit && edit.new_text || "";
|
|
3482
|
+
}
|
|
3145
3483
|
_build_edit_context_previews(edit) {
|
|
3146
3484
|
if (edit.type !== "modify") return [null, null];
|
|
3485
|
+
if (edit._preview_span && edit._preview_context) {
|
|
3486
|
+
return this._build_full_match_preview(edit);
|
|
3487
|
+
}
|
|
3147
3488
|
if (edit._resolved_proxy_edit) {
|
|
3148
3489
|
edit = edit._resolved_proxy_edit;
|
|
3149
3490
|
}
|
|
@@ -3162,17 +3503,33 @@ var RedlineEngine = class {
|
|
|
3162
3503
|
new_text = clean_new;
|
|
3163
3504
|
}
|
|
3164
3505
|
const length = target_text.length;
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
if (
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3506
|
+
let context_before;
|
|
3507
|
+
let context_after;
|
|
3508
|
+
if (edit._preview_context) {
|
|
3509
|
+
[context_before, context_after] = edit._preview_context;
|
|
3510
|
+
} else {
|
|
3511
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3512
|
+
const full_text = active_mapper.full_text;
|
|
3513
|
+
if (!full_text) return [null, null];
|
|
3514
|
+
context_before = _RedlineEngine._tidy_preview_context(
|
|
3515
|
+
full_text.substring(
|
|
3516
|
+
Math.max(0, start_idx - PREVIEW_CONTEXT_CHARS),
|
|
3517
|
+
start_idx
|
|
3518
|
+
),
|
|
3519
|
+
"before"
|
|
3520
|
+
);
|
|
3521
|
+
context_after = _RedlineEngine._tidy_preview_context(
|
|
3522
|
+
full_text.substring(
|
|
3523
|
+
start_idx + length,
|
|
3524
|
+
start_idx + length + PREVIEW_CONTEXT_CHARS
|
|
3525
|
+
),
|
|
3526
|
+
"after"
|
|
3527
|
+
);
|
|
3528
|
+
}
|
|
3529
|
+
const display_target = truncate_middle(target_text, PREVIEW_TEXT_CAP);
|
|
3530
|
+
const display_new = truncate_middle(new_text, PREVIEW_TEXT_CAP);
|
|
3531
|
+
const insertion = display_new ? `{++${display_new}++}` : "";
|
|
3532
|
+
const critic_markup = `${context_before}{--${display_target}--}${insertion}${context_after}`;
|
|
3176
3533
|
let clean_text = critic_markup;
|
|
3177
3534
|
clean_text = clean_text.replace(/\{>>.*?<<\}/gs, "");
|
|
3178
3535
|
clean_text = clean_text.replace(/\{--.*?--\}/gs, "");
|
|
@@ -4113,7 +4470,7 @@ var RedlineEngine = class {
|
|
|
4113
4470
|
const hint = this._nearest_match_hint(edit.target_text, is_regex);
|
|
4114
4471
|
errors.push(
|
|
4115
4472
|
`- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
|
|
4116
|
-
"${edit.target_text}"${hint}`
|
|
4473
|
+
"${truncate_middle(edit.target_text, REPORT_ECHO_CAP)}"${hint}`
|
|
4117
4474
|
);
|
|
4118
4475
|
}
|
|
4119
4476
|
} else if (matches.length > 1 && match_mode === "strict") {
|
|
@@ -4214,9 +4571,48 @@ var RedlineEngine = class {
|
|
|
4214
4571
|
);
|
|
4215
4572
|
}
|
|
4216
4573
|
}
|
|
4574
|
+
if ((edit.type === "insert_row" || edit.type === "delete_row") && matches.length > 0) {
|
|
4575
|
+
const [start, length] = matches[0];
|
|
4576
|
+
const n_cols = _RedlineEngine._column_count_at(
|
|
4577
|
+
target_mapper,
|
|
4578
|
+
start,
|
|
4579
|
+
length
|
|
4580
|
+
);
|
|
4581
|
+
if (n_cols === null) {
|
|
4582
|
+
errors.push(
|
|
4583
|
+
`- 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.`
|
|
4584
|
+
);
|
|
4585
|
+
} else if (edit.type === "insert_row" && Array.isArray(edit.cells) && edit.cells.length > n_cols) {
|
|
4586
|
+
errors.push(
|
|
4587
|
+
`- 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.`
|
|
4588
|
+
);
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4217
4591
|
}
|
|
4218
4592
|
return errors;
|
|
4219
4593
|
}
|
|
4594
|
+
/**
|
|
4595
|
+
* Number of columns (w:tc elements) in the table row containing the text at
|
|
4596
|
+
* [start, start+length) in `mapper`, or null if that text is not inside a
|
|
4597
|
+
* table row.
|
|
4598
|
+
*/
|
|
4599
|
+
static _column_count_at(mapper, start, length) {
|
|
4600
|
+
for (const s of mapper.spans) {
|
|
4601
|
+
if (s.run === null || s.end <= start || s.start >= start + length) {
|
|
4602
|
+
continue;
|
|
4603
|
+
}
|
|
4604
|
+
let curr = s.run._element;
|
|
4605
|
+
while (curr) {
|
|
4606
|
+
if (curr.nodeType === 1 && curr.tagName === "w:tr") {
|
|
4607
|
+
return findAllDescendants(curr, "w:tc").filter(
|
|
4608
|
+
(tc) => tc.parentNode === curr
|
|
4609
|
+
).length;
|
|
4610
|
+
}
|
|
4611
|
+
curr = curr.parentNode;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
return null;
|
|
4615
|
+
}
|
|
4220
4616
|
validate_review_actions(actions) {
|
|
4221
4617
|
const errors = [];
|
|
4222
4618
|
for (let i = 0; i < actions.length; i++) {
|
|
@@ -4359,19 +4755,31 @@ var RedlineEngine = class {
|
|
|
4359
4755
|
});
|
|
4360
4756
|
if (dry_run_mode) {
|
|
4361
4757
|
const reports_by_input = new Array(edits.length);
|
|
4758
|
+
const validation_failed_idx = /* @__PURE__ */ new Set();
|
|
4362
4759
|
for (const { edit, i } of ordered_edits) {
|
|
4363
|
-
|
|
4760
|
+
let single_errors;
|
|
4761
|
+
try {
|
|
4762
|
+
single_errors = this.validate_edits([edit], i);
|
|
4763
|
+
} catch (e) {
|
|
4764
|
+
if (!(e instanceof RegexTimeoutError)) throw e;
|
|
4765
|
+
single_errors = [`- Edit ${i + 1} Failed: ${e.message}`];
|
|
4766
|
+
}
|
|
4364
4767
|
if (single_errors.length > 0) {
|
|
4768
|
+
if (applied_edits > 0) {
|
|
4769
|
+
const hint = sequential_context_hint(applied_edits);
|
|
4770
|
+
single_errors = single_errors.map((err) => err + hint);
|
|
4771
|
+
}
|
|
4772
|
+
validation_failed_idx.add(i);
|
|
4365
4773
|
skipped_edits++;
|
|
4366
4774
|
const warning = this._check_punctuation_warning(
|
|
4367
4775
|
edit.target_text || ""
|
|
4368
4776
|
);
|
|
4369
4777
|
reports_by_input[i] = {
|
|
4370
4778
|
status: "failed",
|
|
4371
|
-
target_text: edit.target_text || "",
|
|
4372
|
-
new_text: edit
|
|
4779
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4780
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4373
4781
|
warning,
|
|
4374
|
-
error: single_errors
|
|
4782
|
+
error: single_errors.join("\n"),
|
|
4375
4783
|
critic_markup: null,
|
|
4376
4784
|
clean_text: null
|
|
4377
4785
|
};
|
|
@@ -4383,8 +4791,8 @@ var RedlineEngine = class {
|
|
|
4383
4791
|
const previews = this._build_edit_context_previews(edit);
|
|
4384
4792
|
reports_by_input[i] = {
|
|
4385
4793
|
status: "applied",
|
|
4386
|
-
target_text: edit.target_text || "",
|
|
4387
|
-
new_text: edit
|
|
4794
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4795
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4388
4796
|
warning: null,
|
|
4389
4797
|
error: null,
|
|
4390
4798
|
critic_markup: previews[0],
|
|
@@ -4404,8 +4812,8 @@ var RedlineEngine = class {
|
|
|
4404
4812
|
);
|
|
4405
4813
|
reports_by_input[i] = {
|
|
4406
4814
|
status: "failed",
|
|
4407
|
-
target_text: edit.target_text || "",
|
|
4408
|
-
new_text: edit
|
|
4815
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4816
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4409
4817
|
warning,
|
|
4410
4818
|
error: error_msg,
|
|
4411
4819
|
critic_markup: null,
|
|
@@ -4413,18 +4821,51 @@ var RedlineEngine = class {
|
|
|
4413
4821
|
};
|
|
4414
4822
|
}
|
|
4415
4823
|
}
|
|
4824
|
+
if (validation_failed_idx.size > 0) {
|
|
4825
|
+
applied_edits = 0;
|
|
4826
|
+
skipped_edits = edits.length;
|
|
4827
|
+
for (let i = 0; i < reports_by_input.length; i++) {
|
|
4828
|
+
const report = reports_by_input[i];
|
|
4829
|
+
if (!report || validation_failed_idx.has(i)) continue;
|
|
4830
|
+
if (report.status === "applied") {
|
|
4831
|
+
reports_by_input[i] = {
|
|
4832
|
+
status: "failed",
|
|
4833
|
+
target_text: report.target_text,
|
|
4834
|
+
new_text: report.new_text,
|
|
4835
|
+
warning: null,
|
|
4836
|
+
error: TRANSACTIONAL_NOT_APPLIED_ERROR,
|
|
4837
|
+
critic_markup: null,
|
|
4838
|
+
clean_text: null
|
|
4839
|
+
};
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4416
4843
|
edits_reports.push(...reports_by_input);
|
|
4417
4844
|
} else {
|
|
4418
4845
|
const snapshot = takeSnapshot(this.doc);
|
|
4419
4846
|
const originalCurrentId = this.current_id;
|
|
4420
4847
|
try {
|
|
4421
4848
|
const sequential_errors = [];
|
|
4849
|
+
let applied_so_far = 0;
|
|
4422
4850
|
for (const { edit, i } of ordered_edits) {
|
|
4423
|
-
|
|
4851
|
+
let single_errors;
|
|
4852
|
+
try {
|
|
4853
|
+
single_errors = this.validate_edits([edit], i);
|
|
4854
|
+
} catch (e) {
|
|
4855
|
+
if (!(e instanceof RegexTimeoutError)) throw e;
|
|
4856
|
+
single_errors = [`- Edit ${i + 1} Failed: ${e.message}`];
|
|
4857
|
+
}
|
|
4424
4858
|
if (single_errors.length > 0) {
|
|
4859
|
+
if (applied_so_far > 0) {
|
|
4860
|
+
const hint = sequential_context_hint(applied_so_far);
|
|
4861
|
+
single_errors = single_errors.map((err) => err + hint);
|
|
4862
|
+
}
|
|
4425
4863
|
sequential_errors.push(...single_errors);
|
|
4426
4864
|
} else {
|
|
4427
4865
|
this.apply_edits([edit], page_offsets);
|
|
4866
|
+
if (edit._applied_status) {
|
|
4867
|
+
applied_so_far++;
|
|
4868
|
+
}
|
|
4428
4869
|
this.mapper = new DocumentMapper(this.doc);
|
|
4429
4870
|
this.clean_mapper = null;
|
|
4430
4871
|
}
|
|
@@ -4459,8 +4900,8 @@ var RedlineEngine = class {
|
|
|
4459
4900
|
}
|
|
4460
4901
|
edits_reports.push({
|
|
4461
4902
|
status: success ? "applied" : "failed",
|
|
4462
|
-
target_text: edit.target_text || "",
|
|
4463
|
-
new_text: edit
|
|
4903
|
+
target_text: truncate_middle(edit.target_text || "", REPORT_ECHO_CAP),
|
|
4904
|
+
new_text: truncate_middle(_RedlineEngine._report_new_text(edit), REPORT_ECHO_CAP),
|
|
4464
4905
|
warning,
|
|
4465
4906
|
error: error_msg,
|
|
4466
4907
|
critic_markup,
|
|
@@ -4527,7 +4968,18 @@ var RedlineEngine = class {
|
|
|
4527
4968
|
edit._error_msg = msg;
|
|
4528
4969
|
}
|
|
4529
4970
|
} else {
|
|
4530
|
-
|
|
4971
|
+
let resolved;
|
|
4972
|
+
try {
|
|
4973
|
+
resolved = this._pre_resolve_heuristic_edit(edit);
|
|
4974
|
+
} catch (e) {
|
|
4975
|
+
if (!(e instanceof RegexTimeoutError)) throw e;
|
|
4976
|
+
skipped++;
|
|
4977
|
+
edit._applied_status = false;
|
|
4978
|
+
const msg = `- Failed to apply edit targeting: '${(edit.target_text || "").substring(0, 40)}...' (${e.message})`;
|
|
4979
|
+
this.skipped_details.push(msg);
|
|
4980
|
+
edit._error_msg = msg;
|
|
4981
|
+
continue;
|
|
4982
|
+
}
|
|
4531
4983
|
if (resolved) {
|
|
4532
4984
|
if (Array.isArray(resolved)) {
|
|
4533
4985
|
for (const r of resolved) {
|
|
@@ -4562,6 +5014,12 @@ var RedlineEngine = class {
|
|
|
4562
5014
|
resolved_edits.sort(
|
|
4563
5015
|
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
4564
5016
|
);
|
|
5017
|
+
for (const [res_edit] of resolved_edits) {
|
|
5018
|
+
this._capture_preview_context(res_edit);
|
|
5019
|
+
if (res_edit._parent_edit_ref) {
|
|
5020
|
+
this._capture_parent_preview_context(res_edit._parent_edit_ref);
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
4565
5023
|
const occupied_ranges = [];
|
|
4566
5024
|
const counted_split_groups = /* @__PURE__ */ new Set();
|
|
4567
5025
|
for (const [edit, orig_new] of resolved_edits) {
|
|
@@ -4680,6 +5138,16 @@ var RedlineEngine = class {
|
|
|
4680
5138
|
);
|
|
4681
5139
|
continue;
|
|
4682
5140
|
}
|
|
5141
|
+
const dup_authors = Array.from(
|
|
5142
|
+
new Set(all_nodes.map((n) => n.getAttribute("w:author") || "Unknown"))
|
|
5143
|
+
).sort();
|
|
5144
|
+
if (dup_authors.length > 1) {
|
|
5145
|
+
skipped++;
|
|
5146
|
+
this.skipped_details.push(
|
|
5147
|
+
`- 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.`
|
|
5148
|
+
);
|
|
5149
|
+
continue;
|
|
5150
|
+
}
|
|
4683
5151
|
for (const node of all_nodes) {
|
|
4684
5152
|
const is_ins = node.tagName === "w:ins";
|
|
4685
5153
|
const parent_tag = node.parentNode ? node.parentNode.tagName : "";
|
|
@@ -4762,7 +5230,15 @@ var RedlineEngine = class {
|
|
|
4762
5230
|
const trPr = tr.ownerDocument.createElement("w:trPr");
|
|
4763
5231
|
new_tr.appendChild(trPr);
|
|
4764
5232
|
trPr.appendChild(this._create_track_change_tag("w:ins"));
|
|
4765
|
-
|
|
5233
|
+
const anchor_cols = findAllDescendants(tr, "w:tc").filter(
|
|
5234
|
+
(tc) => tc.parentNode === tr
|
|
5235
|
+
).length;
|
|
5236
|
+
let cell_values = Array.isArray(edit.cells) ? [...edit.cells] : [];
|
|
5237
|
+
if (anchor_cols > 0) {
|
|
5238
|
+
while (cell_values.length < anchor_cols) cell_values.push("");
|
|
5239
|
+
cell_values = cell_values.slice(0, anchor_cols);
|
|
5240
|
+
}
|
|
5241
|
+
for (const cellText of cell_values) {
|
|
4766
5242
|
const tc = tr.ownerDocument.createElement("w:tc");
|
|
4767
5243
|
const p = tr.ownerDocument.createElement("w:p");
|
|
4768
5244
|
const r = tr.ownerDocument.createElement("w:r");
|
|
@@ -4858,6 +5334,12 @@ var RedlineEngine = class {
|
|
|
4858
5334
|
} catch (e) {
|
|
4859
5335
|
}
|
|
4860
5336
|
}
|
|
5337
|
+
if (!edit._preview_span) {
|
|
5338
|
+
edit._preview_span = [start_idx, match_len];
|
|
5339
|
+
edit._preview_matched_text = actual_doc_text;
|
|
5340
|
+
edit._preview_new_text = current_effective_new_text;
|
|
5341
|
+
edit._preview_mapper_ref = active_mapper;
|
|
5342
|
+
}
|
|
4861
5343
|
const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
|
|
4862
5344
|
edit.target_text
|
|
4863
5345
|
);
|
|
@@ -5981,6 +6463,27 @@ function scrub_doc_properties(doc) {
|
|
|
5981
6463
|
c.textContent = "";
|
|
5982
6464
|
}
|
|
5983
6465
|
});
|
|
6466
|
+
const titles = findDescendantsByLocalName(corePart._element, "title");
|
|
6467
|
+
titles.forEach((c) => {
|
|
6468
|
+
if (c.textContent) {
|
|
6469
|
+
lines.push(`Title kept (review manually): "${c.textContent}"`);
|
|
6470
|
+
}
|
|
6471
|
+
});
|
|
6472
|
+
const leakFields = [
|
|
6473
|
+
["category", "Category"],
|
|
6474
|
+
["keywords", "Keywords"],
|
|
6475
|
+
["subject", "Subject"],
|
|
6476
|
+
["contentStatus", "Content status"],
|
|
6477
|
+
["description", "Description/comments"]
|
|
6478
|
+
];
|
|
6479
|
+
for (const [local, label] of leakFields) {
|
|
6480
|
+
findDescendantsByLocalName(corePart._element, local).forEach((c) => {
|
|
6481
|
+
if (c.textContent) {
|
|
6482
|
+
lines.push(`${label}: ${c.textContent}`);
|
|
6483
|
+
c.textContent = "";
|
|
6484
|
+
}
|
|
6485
|
+
});
|
|
6486
|
+
}
|
|
5984
6487
|
const revisions = findDescendantsByLocalName(corePart._element, "revision");
|
|
5985
6488
|
revisions.forEach((c) => {
|
|
5986
6489
|
if (c.textContent && parseInt(c.textContent) > 1) {
|
|
@@ -6186,7 +6689,11 @@ function extract_all_domain_metadata(doc, base_text) {
|
|
|
6186
6689
|
for (const term of def_keys) {
|
|
6187
6690
|
if (definitions[term].count === 0) {
|
|
6188
6691
|
delete definitions[term];
|
|
6189
|
-
duplicates.
|
|
6692
|
+
if (!duplicates.has(term)) {
|
|
6693
|
+
diagnostics.push(
|
|
6694
|
+
`[Warning] Unused Definition: '${term}' is defined but never used.`
|
|
6695
|
+
);
|
|
6696
|
+
}
|
|
6190
6697
|
}
|
|
6191
6698
|
}
|
|
6192
6699
|
}
|
|
@@ -7521,10 +8028,13 @@ export {
|
|
|
7521
8028
|
DocumentMapper,
|
|
7522
8029
|
DocumentObject,
|
|
7523
8030
|
RedlineEngine,
|
|
8031
|
+
RegexTimeoutError,
|
|
8032
|
+
USER_PATTERN_TIMEOUT_MS,
|
|
7524
8033
|
_extractTextFromDoc,
|
|
7525
8034
|
apply_edits_to_markdown,
|
|
7526
8035
|
create_unified_diff,
|
|
7527
8036
|
create_word_patch_diff,
|
|
8037
|
+
describe_illegal_control_chars,
|
|
7528
8038
|
extractTextFromBuffer,
|
|
7529
8039
|
extract_outline,
|
|
7530
8040
|
finalize_document,
|
|
@@ -7532,6 +8042,9 @@ export {
|
|
|
7532
8042
|
identifyEngine,
|
|
7533
8043
|
paginate,
|
|
7534
8044
|
split_structural_appendix,
|
|
7535
|
-
trim_common_context
|
|
8045
|
+
trim_common_context,
|
|
8046
|
+
userFindAllMatches,
|
|
8047
|
+
userSearch,
|
|
8048
|
+
validate_edit_strings
|
|
7536
8049
|
};
|
|
7537
8050
|
//# sourceMappingURL=index.js.map
|