@adeu/core 1.8.0 → 1.10.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 +618 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +618 -102
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/comments.ts +33 -14
- package/src/consistency.test.ts +62 -4
- package/src/diff.ts +42 -6
- package/src/docx/dom.ts +6 -2
- package/src/engine.bugs.test.ts +63 -10
- package/src/engine.feedback.test.ts +144 -0
- package/src/engine.issue23.test.ts +511 -0
- package/src/engine.ts +513 -64
- package/src/sanitize/core.ts +1 -0
- package/src/sanitize/sanitize.test.ts +48 -6
- package/src/sanitize/transforms.ts +88 -1
package/dist/index.js
CHANGED
|
@@ -26,6 +26,9 @@ function findAllDescendants(element, tagName) {
|
|
|
26
26
|
return Array.from(element.getElementsByTagName(tagName));
|
|
27
27
|
}
|
|
28
28
|
function parseXml(xmlString) {
|
|
29
|
+
if (xmlString.startsWith("\uFEFF")) {
|
|
30
|
+
xmlString = xmlString.slice(1);
|
|
31
|
+
}
|
|
29
32
|
return new DOMParser().parseFromString(xmlString, "text/xml");
|
|
30
33
|
}
|
|
31
34
|
function serializeXml(node) {
|
|
@@ -449,6 +452,21 @@ var CommentsManager = class {
|
|
|
449
452
|
return part;
|
|
450
453
|
}
|
|
451
454
|
_ensureNamespaces() {
|
|
455
|
+
const root = this._commentsPart?._element;
|
|
456
|
+
if (!root) return;
|
|
457
|
+
const required = [
|
|
458
|
+
["xmlns:w", NS.w],
|
|
459
|
+
["xmlns:w14", NS.w14],
|
|
460
|
+
["xmlns:w15", NS.w15],
|
|
461
|
+
["xmlns:w16cid", NS.w16cid],
|
|
462
|
+
["xmlns:w16cex", NS.w16cex],
|
|
463
|
+
["xmlns:mc", NS.mc]
|
|
464
|
+
];
|
|
465
|
+
for (const [attr, uri] of required) {
|
|
466
|
+
if (!root.getAttribute(attr)) {
|
|
467
|
+
root.setAttribute(attr, uri);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
452
470
|
}
|
|
453
471
|
_getNextCommentId() {
|
|
454
472
|
const ids = [0];
|
|
@@ -574,9 +592,9 @@ var CommentsManager = class {
|
|
|
574
592
|
return commentId;
|
|
575
593
|
}
|
|
576
594
|
deleteComment(commentId) {
|
|
577
|
-
if (!this.
|
|
595
|
+
if (!this.commentsPart) return;
|
|
578
596
|
let commentEl = null;
|
|
579
|
-
for (const c of findAllDescendants(this.
|
|
597
|
+
for (const c of findAllDescendants(this.commentsPart._element, "w:comment")) {
|
|
580
598
|
if (c.getAttribute("w:id") === commentId) {
|
|
581
599
|
commentEl = c;
|
|
582
600
|
break;
|
|
@@ -600,7 +618,7 @@ var CommentsManager = class {
|
|
|
600
618
|
if (child.getAttribute("w15:paraIdParent") === paraId) {
|
|
601
619
|
const childParaId = child.getAttribute("w15:paraId");
|
|
602
620
|
if (childParaId) {
|
|
603
|
-
for (const c of findAllDescendants(this.
|
|
621
|
+
for (const c of findAllDescendants(this.commentsPart._element, "w:comment")) {
|
|
604
622
|
for (const p of findAllDescendants(c, "w:p")) {
|
|
605
623
|
if (p.getAttribute("w14:paraId") === childParaId) {
|
|
606
624
|
const cid = c.getAttribute("w:id");
|
|
@@ -1834,6 +1852,29 @@ ${header}`;
|
|
|
1834
1852
|
|
|
1835
1853
|
// src/diff.ts
|
|
1836
1854
|
import diff_match_patch from "diff-match-patch";
|
|
1855
|
+
function _count_standalone_underscores(s) {
|
|
1856
|
+
let count = 0;
|
|
1857
|
+
let i = 0;
|
|
1858
|
+
const n = s.length;
|
|
1859
|
+
const isAlnum = (char) => /[a-zA-Z0-9]/.test(char);
|
|
1860
|
+
while (i < n) {
|
|
1861
|
+
if (s[i] === "_") {
|
|
1862
|
+
let is_double = false;
|
|
1863
|
+
if (i > 0 && s[i - 1] === "_" || i < n - 1 && s[i + 1] === "_") {
|
|
1864
|
+
is_double = true;
|
|
1865
|
+
}
|
|
1866
|
+
let is_intra = false;
|
|
1867
|
+
if (i > 0 && isAlnum(s[i - 1]) && i < n - 1 && isAlnum(s[i + 1])) {
|
|
1868
|
+
is_intra = true;
|
|
1869
|
+
}
|
|
1870
|
+
if (!is_double && !is_intra) {
|
|
1871
|
+
count++;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
i++;
|
|
1875
|
+
}
|
|
1876
|
+
return count;
|
|
1877
|
+
}
|
|
1837
1878
|
function trim_common_context(target, new_val) {
|
|
1838
1879
|
if (!target || !new_val) return [0, 0];
|
|
1839
1880
|
const isSpace = (char) => /\s/.test(char);
|
|
@@ -1864,7 +1905,7 @@ function trim_common_context(target, new_val) {
|
|
|
1864
1905
|
const left = target.substring(0, prefix_len);
|
|
1865
1906
|
const b_count = (left.match(/\*\*/g) || []).length;
|
|
1866
1907
|
const u2_count = (left.match(/__/g) || []).length;
|
|
1867
|
-
const u1_count = (left
|
|
1908
|
+
const u1_count = _count_standalone_underscores(left);
|
|
1868
1909
|
if (b_count % 2 !== 0) {
|
|
1869
1910
|
prefix_len = left.lastIndexOf("**");
|
|
1870
1911
|
continue;
|
|
@@ -1875,10 +1916,14 @@ function trim_common_context(target, new_val) {
|
|
|
1875
1916
|
}
|
|
1876
1917
|
if (u1_count % 2 !== 0) {
|
|
1877
1918
|
let idx = left.length - 1;
|
|
1919
|
+
const isAlnum = (char) => /[a-zA-Z0-9]/.test(char);
|
|
1878
1920
|
while (idx >= 0) {
|
|
1879
1921
|
if (left[idx] === "_" && (idx === 0 || left[idx - 1] !== "_") && (idx === left.length - 1 || left[idx + 1] !== "_")) {
|
|
1880
|
-
|
|
1881
|
-
|
|
1922
|
+
const is_intra = idx > 0 && isAlnum(left[idx - 1]) && idx < left.length - 1 && isAlnum(left[idx + 1]);
|
|
1923
|
+
if (!is_intra) {
|
|
1924
|
+
prefix_len = idx;
|
|
1925
|
+
break;
|
|
1926
|
+
}
|
|
1882
1927
|
}
|
|
1883
1928
|
idx--;
|
|
1884
1929
|
}
|
|
@@ -1938,7 +1983,7 @@ function trim_common_context(target, new_val) {
|
|
|
1938
1983
|
const right = target.substring(target.length - suffix_len);
|
|
1939
1984
|
const b_count = (right.match(/\*\*/g) || []).length;
|
|
1940
1985
|
const u2_count = (right.match(/__/g) || []).length;
|
|
1941
|
-
const u1_count = (right
|
|
1986
|
+
const u1_count = _count_standalone_underscores(right);
|
|
1942
1987
|
if (b_count % 2 !== 0) {
|
|
1943
1988
|
suffix_len -= right.indexOf("**") + 2;
|
|
1944
1989
|
continue;
|
|
@@ -1949,10 +1994,14 @@ function trim_common_context(target, new_val) {
|
|
|
1949
1994
|
}
|
|
1950
1995
|
if (u1_count % 2 !== 0) {
|
|
1951
1996
|
let idx_in_right = 0;
|
|
1997
|
+
const isAlnum = (char) => /[a-zA-Z0-9]/.test(char);
|
|
1952
1998
|
while (idx_in_right < right.length) {
|
|
1953
1999
|
if (right[idx_in_right] === "_" && (idx_in_right === 0 || right[idx_in_right - 1] !== "_") && (idx_in_right === right.length - 1 || right[idx_in_right + 1] !== "_")) {
|
|
1954
|
-
|
|
1955
|
-
|
|
2000
|
+
const is_intra = idx_in_right > 0 && isAlnum(right[idx_in_right - 1]) && idx_in_right < right.length - 1 && isAlnum(right[idx_in_right + 1]);
|
|
2001
|
+
if (!is_intra) {
|
|
2002
|
+
suffix_len -= idx_in_right + 1;
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
1956
2005
|
}
|
|
1957
2006
|
idx_in_right++;
|
|
1958
2007
|
}
|
|
@@ -2599,6 +2648,36 @@ var RedlineEngine = class {
|
|
|
2599
2648
|
this.mapper = new DocumentMapper(this.doc);
|
|
2600
2649
|
this.comments_manager = new CommentsManager(this.doc);
|
|
2601
2650
|
}
|
|
2651
|
+
_check_punctuation_warning(target_text) {
|
|
2652
|
+
if (!target_text) return null;
|
|
2653
|
+
if (target_text.includes("_") || target_text.includes("-")) {
|
|
2654
|
+
return `Warning: target_text '${target_text}' contains tokenization-splitting punctuation ('_' or '-'). This can trigger mid-word splits in the diff engine. Consider using a longer plain-prose anchor.`;
|
|
2655
|
+
}
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
_build_edit_context_previews(edit) {
|
|
2659
|
+
if (edit.type !== "modify") return [null, null];
|
|
2660
|
+
if (edit._resolved_proxy_edit) {
|
|
2661
|
+
edit = edit._resolved_proxy_edit;
|
|
2662
|
+
}
|
|
2663
|
+
const start_idx = edit._resolved_start_idx;
|
|
2664
|
+
if (start_idx === void 0 || start_idx === null) return [null, null];
|
|
2665
|
+
const target_text = edit.target_text || "";
|
|
2666
|
+
const new_text = edit.new_text || "";
|
|
2667
|
+
const length = target_text.length;
|
|
2668
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
2669
|
+
const full_text = active_mapper.full_text;
|
|
2670
|
+
if (!full_text) return [null, null];
|
|
2671
|
+
const before_start = Math.max(0, start_idx - 30);
|
|
2672
|
+
const context_before = full_text.substring(before_start, start_idx);
|
|
2673
|
+
const context_after = full_text.substring(start_idx + length, start_idx + length + 30);
|
|
2674
|
+
const critic_markup = `${context_before}{--${target_text}--}{++${new_text}++}${context_after}`;
|
|
2675
|
+
let clean_text = critic_markup;
|
|
2676
|
+
clean_text = clean_text.replace(/\{>>.*?<<\}/gs, "");
|
|
2677
|
+
clean_text = clean_text.replace(/\{--.*?--\}/gs, "");
|
|
2678
|
+
clean_text = clean_text.replace(/\{\+\+(.*?)\+\+\}/gs, "$1");
|
|
2679
|
+
return [critic_markup, clean_text];
|
|
2680
|
+
}
|
|
2602
2681
|
_scan_existing_ids() {
|
|
2603
2682
|
let maxId = 0;
|
|
2604
2683
|
for (const tag of ["w:ins", "w:del"]) {
|
|
@@ -2691,28 +2770,84 @@ var RedlineEngine = class {
|
|
|
2691
2770
|
}
|
|
2692
2771
|
}
|
|
2693
2772
|
}
|
|
2694
|
-
const
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
]) {
|
|
2700
|
-
for (const node of findAllDescendants(this.doc.element, tag)) {
|
|
2701
|
-
const cid = node.getAttribute("w:id");
|
|
2702
|
-
if (cid) comment_ids.add(cid);
|
|
2773
|
+
for (const root_element of parts_to_process) {
|
|
2774
|
+
for (const tag of ["w:commentRangeStart", "w:commentRangeEnd"]) {
|
|
2775
|
+
for (const el of findAllDescendants(root_element, tag)) {
|
|
2776
|
+
el.parentNode?.removeChild(el);
|
|
2777
|
+
}
|
|
2703
2778
|
}
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2779
|
+
const refs = findAllDescendants(root_element, "w:commentReference");
|
|
2780
|
+
for (const ref of refs) {
|
|
2781
|
+
const parent = ref.parentNode;
|
|
2782
|
+
if (parent) {
|
|
2783
|
+
if (parent.tagName === "w:r" || parent.tagName.endsWith(":r")) {
|
|
2784
|
+
const nonRprChildren = Array.from(parent.childNodes).filter(
|
|
2785
|
+
(c) => c.nodeType === 1 && c.tagName !== "w:rPr" && c.tagName !== "rPr"
|
|
2786
|
+
);
|
|
2787
|
+
if (nonRprChildren.length <= 1) {
|
|
2788
|
+
parent.parentNode?.removeChild(parent);
|
|
2789
|
+
} else {
|
|
2790
|
+
parent.removeChild(ref);
|
|
2791
|
+
}
|
|
2792
|
+
} else {
|
|
2793
|
+
parent.removeChild(ref);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2712
2796
|
}
|
|
2713
2797
|
}
|
|
2714
|
-
|
|
2715
|
-
|
|
2798
|
+
const pkg = this.doc.pkg;
|
|
2799
|
+
const comment_partnames = /* @__PURE__ */ new Set();
|
|
2800
|
+
for (const part of pkg.parts) {
|
|
2801
|
+
if (part.partname.toLowerCase().includes("comments")) {
|
|
2802
|
+
comment_partnames.add(part.partname);
|
|
2803
|
+
const withSlash = part.partname.startsWith("/") ? part.partname : "/" + part.partname;
|
|
2804
|
+
const withoutSlash = part.partname.startsWith("/") ? part.partname.substring(1) : part.partname;
|
|
2805
|
+
comment_partnames.add(withSlash);
|
|
2806
|
+
comment_partnames.add(withoutSlash);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
if (comment_partnames.size > 0) {
|
|
2810
|
+
for (const part of pkg.parts) {
|
|
2811
|
+
if (part.partname.endsWith(".rels")) {
|
|
2812
|
+
const rels = findAllDescendants(part._element, "Relationship");
|
|
2813
|
+
const toRemove = [];
|
|
2814
|
+
for (const rel of rels) {
|
|
2815
|
+
const target = rel.getAttribute("Target") || "";
|
|
2816
|
+
if (target.toLowerCase().includes("comments")) {
|
|
2817
|
+
toRemove.push(rel);
|
|
2818
|
+
const sourcePath = part.partname.replace("/_rels/", "/").replace(".rels", "");
|
|
2819
|
+
const sourcePart = pkg.getPartByPath(sourcePath);
|
|
2820
|
+
if (sourcePart) {
|
|
2821
|
+
const relId = rel.getAttribute("Id");
|
|
2822
|
+
if (relId) sourcePart.rels.delete(relId);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
for (const relEl of toRemove) {
|
|
2827
|
+
relEl.parentNode?.removeChild(relEl);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
const ctPart = pkg.getPartByPath("[Content_Types].xml");
|
|
2832
|
+
if (ctPart) {
|
|
2833
|
+
const overrides = findAllDescendants(ctPart._element, "Override");
|
|
2834
|
+
const toRemove = [];
|
|
2835
|
+
for (const override of overrides) {
|
|
2836
|
+
const partName = override.getAttribute("PartName") || "";
|
|
2837
|
+
if (comment_partnames.has(partName) || partName.toLowerCase().includes("comments")) {
|
|
2838
|
+
toRemove.push(override);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
for (const overrideEl of toRemove) {
|
|
2842
|
+
overrideEl.parentNode?.removeChild(overrideEl);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
pkg.parts = pkg.parts.filter((p) => !p.partname.toLowerCase().includes("comments"));
|
|
2846
|
+
for (const key of Object.keys(pkg.unzipped)) {
|
|
2847
|
+
if (key.toLowerCase().includes("comments")) {
|
|
2848
|
+
delete pkg.unzipped[key];
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2716
2851
|
}
|
|
2717
2852
|
}
|
|
2718
2853
|
_getNextId() {
|
|
@@ -2803,40 +2938,40 @@ var RedlineEngine = class {
|
|
|
2803
2938
|
}
|
|
2804
2939
|
}
|
|
2805
2940
|
/**
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2941
|
+
* Inserts `text` as one or more tracked paragraphs anchored relative to
|
|
2942
|
+
* either an existing run or a paragraph. Returns:
|
|
2943
|
+
* { first_node, last_p, last_ins, used_block_mode }
|
|
2944
|
+
* where:
|
|
2945
|
+
* - first_node: the first <w:ins> (for inline mode) OR the first new <w:p>
|
|
2946
|
+
* (for block mode). The caller uses this for splicing into the DOM and
|
|
2947
|
+
* for anchoring comments.
|
|
2948
|
+
* - last_p: the last new <w:p> created, if any. null when entirely inline.
|
|
2949
|
+
* - last_ins: the last <w:ins> created (inside the last new <w:p>, or the
|
|
2950
|
+
* sole inline ins). Used as the comment's end anchor.
|
|
2951
|
+
* - used_block_mode: true when the first line carried a heading/list style
|
|
2952
|
+
* marker and we created a new paragraph for it (rather than inlining it).
|
|
2953
|
+
*
|
|
2954
|
+
* Multi-paragraph rules (only when text contains '\n'):
|
|
2955
|
+
* - Each additional line becomes a new <w:p>, inserted after the anchor
|
|
2956
|
+
* paragraph in document order.
|
|
2957
|
+
* - Each new <w:p> gets a copy of the anchor paragraph's <w:pPr> (so list
|
|
2958
|
+
* numbering / indentation are preserved) unless the line itself starts
|
|
2959
|
+
* with a markdown heading or list marker, which overrides the style.
|
|
2960
|
+
* - Each new <w:p> carries a tracked paragraph-break marker
|
|
2961
|
+
* (<w:pPr><w:rPr><w:ins/></w:rPr></w:pPr>) so Word natively tracks the
|
|
2962
|
+
* paragraph break.
|
|
2963
|
+
* - Each new <w:p>'s content is wrapped in a <w:ins>, with inline bold/
|
|
2964
|
+
* italic markdown parsed via _parse_inline_markdown.
|
|
2965
|
+
*
|
|
2966
|
+
* The first line:
|
|
2967
|
+
* - If it carries a heading / list marker AND we have a paragraph anchor,
|
|
2968
|
+
* we drop into "block mode": no inline <w:ins>; the first line itself
|
|
2969
|
+
* becomes the first new <w:p>.
|
|
2970
|
+
* - Otherwise we emit a single inline <w:ins> for the first line (current
|
|
2971
|
+
* behaviour) and treat the remaining lines as block extensions.
|
|
2972
|
+
*
|
|
2973
|
+
* Does NOT attach comments; callers handle that.
|
|
2974
|
+
*/
|
|
2840
2975
|
_track_insert_multiline(text, anchor_run, anchor_paragraph, reuse_id) {
|
|
2841
2976
|
if (!text) {
|
|
2842
2977
|
return {
|
|
@@ -2976,7 +3111,15 @@ var RedlineEngine = class {
|
|
|
2976
3111
|
const anchor_rPr = findChild(anchor_run._element, "w:rPr");
|
|
2977
3112
|
if (anchor_rPr) {
|
|
2978
3113
|
const clone = anchor_rPr.cloneNode(true);
|
|
2979
|
-
for (const tag of [
|
|
3114
|
+
for (const tag of [
|
|
3115
|
+
"w:vanish",
|
|
3116
|
+
"w:strike",
|
|
3117
|
+
"w:dstrike",
|
|
3118
|
+
"w:i",
|
|
3119
|
+
"w:iCs",
|
|
3120
|
+
"w:b",
|
|
3121
|
+
"w:bCs"
|
|
3122
|
+
]) {
|
|
2980
3123
|
const found = findChild(clone, tag);
|
|
2981
3124
|
if (found) clone.removeChild(found);
|
|
2982
3125
|
}
|
|
@@ -3250,6 +3393,16 @@ var RedlineEngine = class {
|
|
|
3250
3393
|
matches = this.clean_mapper.find_all_match_indices(edit.target_text);
|
|
3251
3394
|
if (matches.length > 0) activeText = this.clean_mapper.full_text;
|
|
3252
3395
|
}
|
|
3396
|
+
if (activeText === this.mapper.full_text && matches.length > 1) {
|
|
3397
|
+
const liveMatches = matches.filter(([start, length]) => {
|
|
3398
|
+
const realSpans = this.mapper.spans.filter(
|
|
3399
|
+
(s) => s.run !== null && s.end > start && s.start < start + length
|
|
3400
|
+
);
|
|
3401
|
+
if (realSpans.length === 0) return true;
|
|
3402
|
+
return realSpans.some((s) => !s.del_id);
|
|
3403
|
+
});
|
|
3404
|
+
if (liveMatches.length > 0) matches = liveMatches;
|
|
3405
|
+
}
|
|
3253
3406
|
if (matches.length === 0) {
|
|
3254
3407
|
errors.push(
|
|
3255
3408
|
`- Edit ${i + 1} Failed: Target text not found in document:
|
|
@@ -3269,6 +3422,31 @@ var RedlineEngine = class {
|
|
|
3269
3422
|
)
|
|
3270
3423
|
);
|
|
3271
3424
|
}
|
|
3425
|
+
if (matches.length === 1) {
|
|
3426
|
+
const [m_start, m_len] = matches[0];
|
|
3427
|
+
const matched = activeText.substring(m_start, m_start + m_len);
|
|
3428
|
+
const [pfx, sfx] = trim_common_context(matched, edit.new_text || "");
|
|
3429
|
+
const t_end = matched.length - sfx;
|
|
3430
|
+
const final_target = matched.substring(pfx, t_end);
|
|
3431
|
+
const final_new = (edit.new_text || "").substring(pfx, (edit.new_text || "").length - sfx);
|
|
3432
|
+
if (final_target.includes("\n\n")) {
|
|
3433
|
+
if (final_new.includes("\n\n")) {
|
|
3434
|
+
const parts = matched.split("\n\n");
|
|
3435
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3436
|
+
errors.push(
|
|
3437
|
+
`- Edit ${i + 1} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
} else {
|
|
3441
|
+
const parts = final_target.split("\n\n");
|
|
3442
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3443
|
+
errors.push(
|
|
3444
|
+
`- Edit ${i + 1} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3272
3450
|
for (const [start, length] of matches) {
|
|
3273
3451
|
const spans = this.mapper.spans.filter(
|
|
3274
3452
|
(s) => s.end > start && s.start < start + length
|
|
@@ -3332,7 +3510,33 @@ var RedlineEngine = class {
|
|
|
3332
3510
|
}
|
|
3333
3511
|
return errors;
|
|
3334
3512
|
}
|
|
3335
|
-
process_batch(changes) {
|
|
3513
|
+
process_batch(changes, dry_run = false) {
|
|
3514
|
+
if (dry_run) {
|
|
3515
|
+
const baselines = /* @__PURE__ */ new Map();
|
|
3516
|
+
for (const part of this.doc.pkg.parts) {
|
|
3517
|
+
if (part._element) {
|
|
3518
|
+
baselines.set(part, part._element.cloneNode(true));
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
try {
|
|
3522
|
+
return this._process_batch_internal(changes, true);
|
|
3523
|
+
} finally {
|
|
3524
|
+
for (const [part, originalEl] of baselines.entries()) {
|
|
3525
|
+
const doc = part._element.ownerDocument;
|
|
3526
|
+
if (doc && doc.documentElement) {
|
|
3527
|
+
doc.replaceChild(originalEl, doc.documentElement);
|
|
3528
|
+
}
|
|
3529
|
+
part._element = originalEl;
|
|
3530
|
+
}
|
|
3531
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
3532
|
+
this.comments_manager = new CommentsManager(this.doc);
|
|
3533
|
+
this.clean_mapper = null;
|
|
3534
|
+
}
|
|
3535
|
+
} else {
|
|
3536
|
+
return this._process_batch_internal(changes, false);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
_process_batch_internal(changes, dry_run_mode = false) {
|
|
3336
3540
|
this.skipped_details = [];
|
|
3337
3541
|
const actions = changes.filter(
|
|
3338
3542
|
(c) => ["accept", "reject", "reply"].includes(c.type)
|
|
@@ -3340,38 +3544,129 @@ var RedlineEngine = class {
|
|
|
3340
3544
|
const edits = changes.filter(
|
|
3341
3545
|
(c) => !["accept", "reject", "reply"].includes(c.type)
|
|
3342
3546
|
);
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3547
|
+
if (!dry_run_mode) {
|
|
3548
|
+
const all_errors = [];
|
|
3549
|
+
if (actions.length > 0) {
|
|
3550
|
+
all_errors.push(...this.validate_review_actions(actions));
|
|
3551
|
+
}
|
|
3552
|
+
if (edits.length > 0) {
|
|
3553
|
+
all_errors.push(...this.validate_edits(edits));
|
|
3554
|
+
}
|
|
3555
|
+
if (all_errors.length > 0) {
|
|
3556
|
+
throw new BatchValidationError(all_errors);
|
|
3557
|
+
}
|
|
3558
|
+
} else {
|
|
3559
|
+
if (actions.length > 0) {
|
|
3560
|
+
const action_errors = this.validate_review_actions(actions);
|
|
3561
|
+
if (action_errors.length > 0) {
|
|
3562
|
+
throw new BatchValidationError(action_errors);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3352
3565
|
}
|
|
3353
|
-
let applied_actions = 0
|
|
3566
|
+
let applied_actions = 0;
|
|
3567
|
+
let skipped_actions = 0;
|
|
3354
3568
|
if (actions.length > 0) {
|
|
3355
3569
|
const res = this.apply_review_actions(actions);
|
|
3356
3570
|
applied_actions = res[0];
|
|
3357
3571
|
skipped_actions = res[1];
|
|
3572
|
+
if (skipped_actions > 0) {
|
|
3573
|
+
throw new BatchValidationError(this.skipped_details);
|
|
3574
|
+
}
|
|
3358
3575
|
if (applied_actions > 0) {
|
|
3359
3576
|
this.mapper["_build_map"]();
|
|
3360
3577
|
if (this.clean_mapper) this.clean_mapper["_build_map"]();
|
|
3361
3578
|
}
|
|
3362
3579
|
}
|
|
3363
|
-
|
|
3580
|
+
const edits_reports = [];
|
|
3581
|
+
let applied_edits = 0;
|
|
3582
|
+
let skipped_edits = 0;
|
|
3364
3583
|
if (edits.length > 0) {
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3584
|
+
if (dry_run_mode) {
|
|
3585
|
+
for (const edit of edits) {
|
|
3586
|
+
const single_errors = this.validate_edits([edit]);
|
|
3587
|
+
const warning = this._check_punctuation_warning(edit.target_text || "");
|
|
3588
|
+
if (single_errors.length > 0) {
|
|
3589
|
+
skipped_edits++;
|
|
3590
|
+
edits_reports.push({
|
|
3591
|
+
status: "failed",
|
|
3592
|
+
target_text: edit.target_text || "",
|
|
3593
|
+
new_text: edit.new_text || "",
|
|
3594
|
+
warning,
|
|
3595
|
+
error: single_errors[0],
|
|
3596
|
+
critic_markup: null,
|
|
3597
|
+
clean_text: null
|
|
3598
|
+
});
|
|
3599
|
+
continue;
|
|
3600
|
+
}
|
|
3601
|
+
const res = this.apply_edits([edit]);
|
|
3602
|
+
const applied = res[0];
|
|
3603
|
+
if (applied > 0) {
|
|
3604
|
+
applied_edits++;
|
|
3605
|
+
const previews = this._build_edit_context_previews(edit);
|
|
3606
|
+
edits_reports.push({
|
|
3607
|
+
status: "applied",
|
|
3608
|
+
target_text: edit.target_text || "",
|
|
3609
|
+
new_text: edit.new_text || "",
|
|
3610
|
+
warning,
|
|
3611
|
+
error: null,
|
|
3612
|
+
critic_markup: previews[0],
|
|
3613
|
+
clean_text: previews[1]
|
|
3614
|
+
});
|
|
3615
|
+
} else {
|
|
3616
|
+
skipped_edits++;
|
|
3617
|
+
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
3618
|
+
edits_reports.push({
|
|
3619
|
+
status: "failed",
|
|
3620
|
+
target_text: edit.target_text || "",
|
|
3621
|
+
new_text: edit.new_text || "",
|
|
3622
|
+
warning,
|
|
3623
|
+
error: error_msg,
|
|
3624
|
+
critic_markup: null,
|
|
3625
|
+
clean_text: null
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
} else {
|
|
3630
|
+
const errors = this.validate_edits(edits);
|
|
3631
|
+
if (errors.length > 0) {
|
|
3632
|
+
throw new BatchValidationError(errors);
|
|
3633
|
+
}
|
|
3634
|
+
const cloned_edits = edits.map((e) => JSON.parse(JSON.stringify(e)));
|
|
3635
|
+
const res = this.apply_edits(cloned_edits);
|
|
3636
|
+
applied_edits = res[0];
|
|
3637
|
+
skipped_edits = res[1];
|
|
3638
|
+
for (const edit of cloned_edits) {
|
|
3639
|
+
const success = edit._applied_status || false;
|
|
3640
|
+
const error_msg = edit._error_msg || null;
|
|
3641
|
+
const warning = this._check_punctuation_warning(edit.target_text || "");
|
|
3642
|
+
let critic_markup = null;
|
|
3643
|
+
let clean_text = null;
|
|
3644
|
+
if (success) {
|
|
3645
|
+
const previews = this._build_edit_context_previews(edit);
|
|
3646
|
+
critic_markup = previews[0];
|
|
3647
|
+
clean_text = previews[1];
|
|
3648
|
+
}
|
|
3649
|
+
edits_reports.push({
|
|
3650
|
+
status: success ? "applied" : "failed",
|
|
3651
|
+
target_text: edit.target_text || "",
|
|
3652
|
+
new_text: edit.new_text || "",
|
|
3653
|
+
warning,
|
|
3654
|
+
error: error_msg,
|
|
3655
|
+
critic_markup,
|
|
3656
|
+
clean_text
|
|
3657
|
+
});
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3368
3660
|
}
|
|
3369
3661
|
return {
|
|
3370
3662
|
actions_applied: applied_actions,
|
|
3371
3663
|
actions_skipped: skipped_actions,
|
|
3372
3664
|
edits_applied: applied_edits,
|
|
3373
3665
|
edits_skipped: skipped_edits,
|
|
3374
|
-
skipped_details: this.skipped_details
|
|
3666
|
+
skipped_details: this.skipped_details,
|
|
3667
|
+
edits: edits_reports,
|
|
3668
|
+
engine: "node",
|
|
3669
|
+
version: "1.9.0"
|
|
3375
3670
|
};
|
|
3376
3671
|
}
|
|
3377
3672
|
apply_edits(edits) {
|
|
@@ -3379,50 +3674,90 @@ var RedlineEngine = class {
|
|
|
3379
3674
|
let skipped = 0;
|
|
3380
3675
|
const resolved_edits = [];
|
|
3381
3676
|
for (const edit of edits) {
|
|
3382
|
-
|
|
3677
|
+
edit._applied_status = false;
|
|
3678
|
+
edit._error_msg = null;
|
|
3679
|
+
}
|
|
3680
|
+
for (const edit of edits) {
|
|
3681
|
+
if (edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null) {
|
|
3682
|
+
resolved_edits.push([edit, edit.new_text || null]);
|
|
3683
|
+
} else if (edit._match_start_index !== void 0 && edit._match_start_index !== null) {
|
|
3684
|
+
edit._resolved_start_idx = edit._match_start_index;
|
|
3383
3685
|
resolved_edits.push([edit, edit.new_text || null]);
|
|
3384
3686
|
} else if (edit.type === "insert_row" || edit.type === "delete_row") {
|
|
3385
|
-
|
|
3386
|
-
if (
|
|
3387
|
-
|
|
3687
|
+
let matches = this.mapper.find_all_match_indices(edit.target_text);
|
|
3688
|
+
if (matches.length === 0) {
|
|
3689
|
+
if (!this.clean_mapper) {
|
|
3690
|
+
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
3691
|
+
}
|
|
3692
|
+
matches = this.clean_mapper.find_all_match_indices(edit.target_text);
|
|
3693
|
+
}
|
|
3694
|
+
if (matches.length > 0) {
|
|
3695
|
+
edit._resolved_start_idx = matches[0][0];
|
|
3388
3696
|
resolved_edits.push([edit, null]);
|
|
3389
3697
|
} else {
|
|
3390
3698
|
skipped++;
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3699
|
+
edit._applied_status = false;
|
|
3700
|
+
const target_snippet = (edit.target_text || "").trim().substring(0, 40);
|
|
3701
|
+
const msg = `- Failed to locate row target: '${target_snippet}...'`;
|
|
3702
|
+
this.skipped_details.push(msg);
|
|
3703
|
+
edit._error_msg = msg;
|
|
3394
3704
|
}
|
|
3395
3705
|
} else {
|
|
3396
3706
|
const resolved = this._pre_resolve_heuristic_edit(edit);
|
|
3397
3707
|
if (resolved) {
|
|
3398
3708
|
if (Array.isArray(resolved)) {
|
|
3399
|
-
for (const r of resolved)
|
|
3709
|
+
for (const r of resolved) {
|
|
3710
|
+
r._resolved_start_idx = r._match_start_index;
|
|
3711
|
+
r._parent_edit_ref = edit;
|
|
3712
|
+
if (edit._resolved_start_idx === void 0 || edit._resolved_start_idx === null) {
|
|
3713
|
+
edit._resolved_start_idx = r._resolved_start_idx;
|
|
3714
|
+
}
|
|
3715
|
+
if (!edit._resolved_proxy_edit) {
|
|
3716
|
+
edit._resolved_proxy_edit = r;
|
|
3717
|
+
}
|
|
3718
|
+
resolved_edits.push([r, r.new_text]);
|
|
3719
|
+
}
|
|
3400
3720
|
} else {
|
|
3721
|
+
resolved._resolved_start_idx = resolved._match_start_index;
|
|
3722
|
+
resolved._parent_edit_ref = edit;
|
|
3723
|
+
edit._resolved_start_idx = resolved._resolved_start_idx;
|
|
3724
|
+
edit._resolved_proxy_edit = resolved;
|
|
3401
3725
|
resolved_edits.push([resolved, resolved.new_text]);
|
|
3402
3726
|
}
|
|
3403
3727
|
} else {
|
|
3404
3728
|
skipped++;
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
);
|
|
3729
|
+
edit._applied_status = false;
|
|
3730
|
+
const display_text = edit.target_text || "insertion";
|
|
3731
|
+
const target_snippet = display_text.trim().substring(0, 40);
|
|
3732
|
+
const msg = `- Failed to apply edit targeting: '${target_snippet}...'`;
|
|
3733
|
+
this.skipped_details.push(msg);
|
|
3734
|
+
edit._error_msg = msg;
|
|
3408
3735
|
}
|
|
3409
3736
|
}
|
|
3410
3737
|
}
|
|
3411
3738
|
resolved_edits.sort(
|
|
3412
|
-
(a, b) => (b[0].
|
|
3739
|
+
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
3413
3740
|
);
|
|
3414
3741
|
const occupied_ranges = [];
|
|
3415
3742
|
for (const [edit, orig_new] of resolved_edits) {
|
|
3416
|
-
const start = edit.
|
|
3743
|
+
const start = edit._resolved_start_idx || 0;
|
|
3417
3744
|
const end = start + (edit.target_text ? edit.target_text.length : 0);
|
|
3418
3745
|
const overlaps = occupied_ranges.some(
|
|
3419
3746
|
([occ_start, occ_end]) => start < occ_end && end > occ_start
|
|
3420
3747
|
);
|
|
3421
3748
|
if (overlaps) {
|
|
3422
3749
|
skipped++;
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3750
|
+
const display_text = edit.target_text || "insertion";
|
|
3751
|
+
const target_snippet = display_text.trim().substring(0, 40);
|
|
3752
|
+
const msg = `- Skipped overlapping edit targeting: '${target_snippet}...'`;
|
|
3753
|
+
this.skipped_details.push(msg);
|
|
3754
|
+
edit._applied_status = false;
|
|
3755
|
+
edit._error_msg = msg;
|
|
3756
|
+
const parent = edit._parent_edit_ref;
|
|
3757
|
+
if (parent) {
|
|
3758
|
+
parent._applied_status = false;
|
|
3759
|
+
parent._error_msg = msg;
|
|
3760
|
+
}
|
|
3426
3761
|
continue;
|
|
3427
3762
|
}
|
|
3428
3763
|
let success = false;
|
|
@@ -3434,11 +3769,26 @@ var RedlineEngine = class {
|
|
|
3434
3769
|
if (success) {
|
|
3435
3770
|
applied++;
|
|
3436
3771
|
occupied_ranges.push([start, end]);
|
|
3772
|
+
edit._applied_status = true;
|
|
3773
|
+
const parent = edit._parent_edit_ref;
|
|
3774
|
+
if (parent) {
|
|
3775
|
+
parent._applied_status = true;
|
|
3776
|
+
}
|
|
3437
3777
|
} else {
|
|
3438
3778
|
skipped++;
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3779
|
+
const display_text = edit.target_text || "insertion";
|
|
3780
|
+
const target_snippet = display_text.trim().substring(0, 40);
|
|
3781
|
+
const msg = `- Failed to apply edit targeting: '${target_snippet}...'`;
|
|
3782
|
+
this.skipped_details.push(msg);
|
|
3783
|
+
edit._applied_status = false;
|
|
3784
|
+
edit._error_msg = msg;
|
|
3785
|
+
const parent = edit._parent_edit_ref;
|
|
3786
|
+
if (parent) {
|
|
3787
|
+
if (!parent._applied_status) {
|
|
3788
|
+
parent._applied_status = false;
|
|
3789
|
+
parent._error_msg = msg;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3442
3792
|
}
|
|
3443
3793
|
}
|
|
3444
3794
|
return [applied, skipped];
|
|
@@ -3531,7 +3881,7 @@ var RedlineEngine = class {
|
|
|
3531
3881
|
return [applied, skipped];
|
|
3532
3882
|
}
|
|
3533
3883
|
_apply_table_edit(edit, rebuild_map) {
|
|
3534
|
-
const start_idx = edit._match_start_index || 0;
|
|
3884
|
+
const start_idx = edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null ? edit._resolved_start_idx : edit._match_start_index || 0;
|
|
3535
3885
|
const [anchor_run, anchor_para] = this.mapper.get_insertion_anchor(
|
|
3536
3886
|
start_idx,
|
|
3537
3887
|
rebuild_map
|
|
@@ -3575,9 +3925,31 @@ var RedlineEngine = class {
|
|
|
3575
3925
|
}
|
|
3576
3926
|
return false;
|
|
3577
3927
|
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Returns the first match of `target_text` in the raw mapper that is NOT
|
|
3930
|
+
* entirely contained within a tracked deletion (<w:del>). Tracked-deleted
|
|
3931
|
+
* copies are not live, editable text, so an edit must resolve to a live
|
|
3932
|
+
* occurrence even when a dead copy appears earlier in the document
|
|
3933
|
+
* (BUG-23-5). Falls back to the plain first match when no live copy is
|
|
3934
|
+
* found (e.g. fuzzy/normalized matches the span filter cannot align).
|
|
3935
|
+
*/
|
|
3936
|
+
_first_live_match(target_text) {
|
|
3937
|
+
const all = this.mapper.find_all_match_indices(target_text);
|
|
3938
|
+
if (all.length <= 1) {
|
|
3939
|
+
return this.mapper.find_match_index(target_text);
|
|
3940
|
+
}
|
|
3941
|
+
for (const [start, length] of all) {
|
|
3942
|
+
const realSpans = this.mapper.spans.filter(
|
|
3943
|
+
(s) => s.run !== null && s.end > start && s.start < start + length
|
|
3944
|
+
);
|
|
3945
|
+
if (realSpans.length === 0) return [start, length];
|
|
3946
|
+
if (realSpans.some((s) => !s.del_id)) return [start, length];
|
|
3947
|
+
}
|
|
3948
|
+
return this.mapper.find_match_index(target_text);
|
|
3949
|
+
}
|
|
3578
3950
|
_pre_resolve_heuristic_edit(edit) {
|
|
3579
3951
|
if (!edit.target_text) return null;
|
|
3580
|
-
let [start_idx, match_len] = this.
|
|
3952
|
+
let [start_idx, match_len] = this._first_live_match(edit.target_text);
|
|
3581
3953
|
let use_clean_map = false;
|
|
3582
3954
|
if (start_idx === -1) {
|
|
3583
3955
|
if (!this.clean_mapper)
|
|
@@ -3641,7 +4013,7 @@ var RedlineEngine = class {
|
|
|
3641
4013
|
_apply_single_edit_indexed(edit, orig_new, rebuild_map) {
|
|
3642
4014
|
let op = edit._internal_op;
|
|
3643
4015
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3644
|
-
const start_idx = edit._match_start_index || 0;
|
|
4016
|
+
const start_idx = edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null ? edit._resolved_start_idx : edit._match_start_index || 0;
|
|
3645
4017
|
const length = edit.target_text ? edit.target_text.length : 0;
|
|
3646
4018
|
const del_id = ["DELETION", "MODIFICATION"].includes(op) ? this._getNextId() : null;
|
|
3647
4019
|
const ins_id = ["INSERTION", "MODIFICATION"].includes(op) ? this._getNextId() : null;
|
|
@@ -3690,6 +4062,76 @@ var RedlineEngine = class {
|
|
|
3690
4062
|
rebuild_map
|
|
3691
4063
|
);
|
|
3692
4064
|
if (!anchor_run && !anchor_para) return false;
|
|
4065
|
+
const _bug233_new = edit.new_text || "";
|
|
4066
|
+
const _bug233_trailing_break = /\n\s*$/.test(_bug233_new);
|
|
4067
|
+
let _bug233_target_para = null;
|
|
4068
|
+
{
|
|
4069
|
+
const startingSpans = active_mapper.spans.filter(
|
|
4070
|
+
(s) => s.paragraph !== null && s.start === start_idx
|
|
4071
|
+
);
|
|
4072
|
+
if (startingSpans.length > 0 && startingSpans[0].paragraph) {
|
|
4073
|
+
_bug233_target_para = startingSpans[0].paragraph._element;
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
if (_bug233_trailing_break && _bug233_target_para && _bug233_target_para.parentNode) {
|
|
4077
|
+
const body = _bug233_target_para.parentNode;
|
|
4078
|
+
const xmlDoc = this.doc.part._element.ownerDocument;
|
|
4079
|
+
const lines = _bug233_new.split(/[\r\n]+/).filter((l) => l !== "");
|
|
4080
|
+
let firstNew = null;
|
|
4081
|
+
let lastNew = null;
|
|
4082
|
+
let lastIns = null;
|
|
4083
|
+
for (const raw_line of lines) {
|
|
4084
|
+
const [clean_text, style_name] = this._parse_markdown_style(raw_line);
|
|
4085
|
+
const new_p = xmlDoc.createElement("w:p");
|
|
4086
|
+
if (style_name) {
|
|
4087
|
+
this._set_paragraph_style(new_p, style_name);
|
|
4088
|
+
} else {
|
|
4089
|
+
const existing_pPr = findChild(_bug233_target_para, "w:pPr");
|
|
4090
|
+
if (existing_pPr) new_p.appendChild(existing_pPr.cloneNode(true));
|
|
4091
|
+
}
|
|
4092
|
+
let pPr = findChild(new_p, "w:pPr");
|
|
4093
|
+
if (!pPr) {
|
|
4094
|
+
pPr = xmlDoc.createElement("w:pPr");
|
|
4095
|
+
new_p.insertBefore(pPr, new_p.firstChild);
|
|
4096
|
+
}
|
|
4097
|
+
let rPr = findChild(pPr, "w:rPr");
|
|
4098
|
+
if (!rPr) {
|
|
4099
|
+
rPr = xmlDoc.createElement("w:rPr");
|
|
4100
|
+
pPr.appendChild(rPr);
|
|
4101
|
+
}
|
|
4102
|
+
rPr.appendChild(this._create_track_change_tag("w:ins", "", ins_id));
|
|
4103
|
+
const content_ins = this._build_tracked_ins_for_line(
|
|
4104
|
+
clean_text,
|
|
4105
|
+
anchor_run,
|
|
4106
|
+
ins_id,
|
|
4107
|
+
xmlDoc
|
|
4108
|
+
);
|
|
4109
|
+
if (content_ins) new_p.appendChild(content_ins);
|
|
4110
|
+
body.insertBefore(new_p, _bug233_target_para);
|
|
4111
|
+
if (!firstNew) firstNew = new_p;
|
|
4112
|
+
lastNew = new_p;
|
|
4113
|
+
lastIns = content_ins;
|
|
4114
|
+
}
|
|
4115
|
+
if (firstNew) {
|
|
4116
|
+
if (edit.comment && lastNew && lastIns) {
|
|
4117
|
+
const ascend = (el, p) => {
|
|
4118
|
+
let cur = el;
|
|
4119
|
+
while (cur.parentNode && cur.parentNode !== p)
|
|
4120
|
+
cur = cur.parentNode;
|
|
4121
|
+
return cur;
|
|
4122
|
+
};
|
|
4123
|
+
const startIns = findAllDescendants(firstNew, "w:ins")[0] || firstNew;
|
|
4124
|
+
this._attach_comment_spanning(
|
|
4125
|
+
firstNew,
|
|
4126
|
+
ascend(startIns, firstNew),
|
|
4127
|
+
lastNew,
|
|
4128
|
+
ascend(lastIns, lastNew),
|
|
4129
|
+
edit.comment
|
|
4130
|
+
);
|
|
4131
|
+
}
|
|
4132
|
+
return true;
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
3693
4135
|
const result = this._track_insert_multiline(
|
|
3694
4136
|
edit.new_text || "",
|
|
3695
4137
|
anchor_run,
|
|
@@ -5399,15 +5841,88 @@ function remove_all_comments(doc) {
|
|
|
5399
5841
|
lines.push(` ${status} "${_truncate(info.text || "", 60)}" (${info.author || "Unknown"})`);
|
|
5400
5842
|
cm.deleteComment(cId);
|
|
5401
5843
|
}
|
|
5402
|
-
for (const tag of ["w:commentRangeStart", "w:commentRangeEnd"
|
|
5844
|
+
for (const tag of ["w:commentRangeStart", "w:commentRangeEnd"]) {
|
|
5403
5845
|
for (const el of findAllDescendants(doc.element, tag)) {
|
|
5404
5846
|
el.parentNode?.removeChild(el);
|
|
5405
5847
|
}
|
|
5406
5848
|
}
|
|
5849
|
+
const refs = findAllDescendants(doc.element, "w:commentReference");
|
|
5850
|
+
for (const ref of refs) {
|
|
5851
|
+
const parent = ref.parentNode;
|
|
5852
|
+
if (parent) {
|
|
5853
|
+
if (parent.tagName === "w:r" || parent.tagName.endsWith(":r")) {
|
|
5854
|
+
const nonRprChildren = Array.from(parent.childNodes).filter(
|
|
5855
|
+
(c) => c.nodeType === 1 && c.tagName !== "w:rPr" && c.tagName !== "rPr"
|
|
5856
|
+
);
|
|
5857
|
+
if (nonRprChildren.length <= 1) {
|
|
5858
|
+
parent.parentNode?.removeChild(parent);
|
|
5859
|
+
} else {
|
|
5860
|
+
parent.removeChild(ref);
|
|
5861
|
+
}
|
|
5862
|
+
} else {
|
|
5863
|
+
parent.removeChild(ref);
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5407
5867
|
const resolvedCount = Object.values(data).filter((c) => c.resolved).length;
|
|
5408
5868
|
const openCount = Object.values(data).filter((c) => !c.resolved).length;
|
|
5409
5869
|
return [`Comments removed: ${keys.length} (${resolvedCount} resolved, ${openCount} open)`].concat(lines);
|
|
5410
5870
|
}
|
|
5871
|
+
function eject_comment_parts(doc) {
|
|
5872
|
+
const pkg = doc.pkg;
|
|
5873
|
+
const comment_partnames = /* @__PURE__ */ new Set();
|
|
5874
|
+
for (const part of pkg.parts) {
|
|
5875
|
+
if (part.partname.toLowerCase().includes("comments")) {
|
|
5876
|
+
comment_partnames.add(part.partname);
|
|
5877
|
+
const withSlash = part.partname.startsWith("/") ? part.partname : "/" + part.partname;
|
|
5878
|
+
const withoutSlash = part.partname.startsWith("/") ? part.partname.substring(1) : part.partname;
|
|
5879
|
+
comment_partnames.add(withSlash);
|
|
5880
|
+
comment_partnames.add(withoutSlash);
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
if (comment_partnames.size === 0) return;
|
|
5884
|
+
for (const part of pkg.parts) {
|
|
5885
|
+
if (part.partname.endsWith(".rels")) {
|
|
5886
|
+
const rels = findAllDescendants(part._element, "Relationship");
|
|
5887
|
+
const toRemove = [];
|
|
5888
|
+
for (const rel of rels) {
|
|
5889
|
+
const target = rel.getAttribute("Target") || "";
|
|
5890
|
+
if (target.toLowerCase().includes("comments")) {
|
|
5891
|
+
toRemove.push(rel);
|
|
5892
|
+
const sourcePath = part.partname.replace("/_rels/", "/").replace(".rels", "");
|
|
5893
|
+
const sourcePart = pkg.getPartByPath(sourcePath);
|
|
5894
|
+
if (sourcePart) {
|
|
5895
|
+
const relId = rel.getAttribute("Id");
|
|
5896
|
+
if (relId) sourcePart.rels.delete(relId);
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
for (const relEl of toRemove) {
|
|
5901
|
+
relEl.parentNode?.removeChild(relEl);
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
const ctPart = pkg.getPartByPath("[Content_Types].xml");
|
|
5906
|
+
if (ctPart) {
|
|
5907
|
+
const overrides = findAllDescendants(ctPart._element, "Override");
|
|
5908
|
+
const toRemove = [];
|
|
5909
|
+
for (const override of overrides) {
|
|
5910
|
+
const partName = override.getAttribute("PartName") || "";
|
|
5911
|
+
if (comment_partnames.has(partName) || partName.toLowerCase().includes("comments")) {
|
|
5912
|
+
toRemove.push(override);
|
|
5913
|
+
}
|
|
5914
|
+
}
|
|
5915
|
+
for (const overrideEl of toRemove) {
|
|
5916
|
+
overrideEl.parentNode?.removeChild(overrideEl);
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
pkg.parts = pkg.parts.filter((p) => !p.partname.toLowerCase().includes("comments"));
|
|
5920
|
+
for (const key of Object.keys(pkg.unzipped)) {
|
|
5921
|
+
if (key.toLowerCase().includes("comments")) {
|
|
5922
|
+
delete pkg.unzipped[key];
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5411
5926
|
function replace_comment_authors(doc, newAuthor) {
|
|
5412
5927
|
const cm = new CommentsManager(doc);
|
|
5413
5928
|
if (!cm.commentsPart) return [];
|
|
@@ -5596,6 +6111,7 @@ async function finalize_document(doc, options) {
|
|
|
5596
6111
|
const commentsSummary = get_comments_summary(doc);
|
|
5597
6112
|
report.comments_removed = commentsSummary.total;
|
|
5598
6113
|
report.add_transform_lines(remove_all_comments(doc));
|
|
6114
|
+
eject_comment_parts(doc);
|
|
5599
6115
|
} else if (options.sanitize_mode === "keep-markup") {
|
|
5600
6116
|
const counts = count_tracked_changes(doc);
|
|
5601
6117
|
report.tracked_changes_found = counts[0] + counts[1] + counts[2];
|