@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.cjs
CHANGED
|
@@ -76,6 +76,9 @@ function findAllDescendants(element, tagName) {
|
|
|
76
76
|
return Array.from(element.getElementsByTagName(tagName));
|
|
77
77
|
}
|
|
78
78
|
function parseXml(xmlString) {
|
|
79
|
+
if (xmlString.startsWith("\uFEFF")) {
|
|
80
|
+
xmlString = xmlString.slice(1);
|
|
81
|
+
}
|
|
79
82
|
return new import_xmldom.DOMParser().parseFromString(xmlString, "text/xml");
|
|
80
83
|
}
|
|
81
84
|
function serializeXml(node) {
|
|
@@ -499,6 +502,21 @@ var CommentsManager = class {
|
|
|
499
502
|
return part;
|
|
500
503
|
}
|
|
501
504
|
_ensureNamespaces() {
|
|
505
|
+
const root = this._commentsPart?._element;
|
|
506
|
+
if (!root) return;
|
|
507
|
+
const required = [
|
|
508
|
+
["xmlns:w", NS.w],
|
|
509
|
+
["xmlns:w14", NS.w14],
|
|
510
|
+
["xmlns:w15", NS.w15],
|
|
511
|
+
["xmlns:w16cid", NS.w16cid],
|
|
512
|
+
["xmlns:w16cex", NS.w16cex],
|
|
513
|
+
["xmlns:mc", NS.mc]
|
|
514
|
+
];
|
|
515
|
+
for (const [attr, uri] of required) {
|
|
516
|
+
if (!root.getAttribute(attr)) {
|
|
517
|
+
root.setAttribute(attr, uri);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
502
520
|
}
|
|
503
521
|
_getNextCommentId() {
|
|
504
522
|
const ids = [0];
|
|
@@ -624,9 +642,9 @@ var CommentsManager = class {
|
|
|
624
642
|
return commentId;
|
|
625
643
|
}
|
|
626
644
|
deleteComment(commentId) {
|
|
627
|
-
if (!this.
|
|
645
|
+
if (!this.commentsPart) return;
|
|
628
646
|
let commentEl = null;
|
|
629
|
-
for (const c of findAllDescendants(this.
|
|
647
|
+
for (const c of findAllDescendants(this.commentsPart._element, "w:comment")) {
|
|
630
648
|
if (c.getAttribute("w:id") === commentId) {
|
|
631
649
|
commentEl = c;
|
|
632
650
|
break;
|
|
@@ -650,7 +668,7 @@ var CommentsManager = class {
|
|
|
650
668
|
if (child.getAttribute("w15:paraIdParent") === paraId) {
|
|
651
669
|
const childParaId = child.getAttribute("w15:paraId");
|
|
652
670
|
if (childParaId) {
|
|
653
|
-
for (const c of findAllDescendants(this.
|
|
671
|
+
for (const c of findAllDescendants(this.commentsPart._element, "w:comment")) {
|
|
654
672
|
for (const p of findAllDescendants(c, "w:p")) {
|
|
655
673
|
if (p.getAttribute("w14:paraId") === childParaId) {
|
|
656
674
|
const cid = c.getAttribute("w:id");
|
|
@@ -1884,6 +1902,29 @@ ${header}`;
|
|
|
1884
1902
|
|
|
1885
1903
|
// src/diff.ts
|
|
1886
1904
|
var import_diff_match_patch = __toESM(require("diff-match-patch"), 1);
|
|
1905
|
+
function _count_standalone_underscores(s) {
|
|
1906
|
+
let count = 0;
|
|
1907
|
+
let i = 0;
|
|
1908
|
+
const n = s.length;
|
|
1909
|
+
const isAlnum = (char) => /[a-zA-Z0-9]/.test(char);
|
|
1910
|
+
while (i < n) {
|
|
1911
|
+
if (s[i] === "_") {
|
|
1912
|
+
let is_double = false;
|
|
1913
|
+
if (i > 0 && s[i - 1] === "_" || i < n - 1 && s[i + 1] === "_") {
|
|
1914
|
+
is_double = true;
|
|
1915
|
+
}
|
|
1916
|
+
let is_intra = false;
|
|
1917
|
+
if (i > 0 && isAlnum(s[i - 1]) && i < n - 1 && isAlnum(s[i + 1])) {
|
|
1918
|
+
is_intra = true;
|
|
1919
|
+
}
|
|
1920
|
+
if (!is_double && !is_intra) {
|
|
1921
|
+
count++;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
i++;
|
|
1925
|
+
}
|
|
1926
|
+
return count;
|
|
1927
|
+
}
|
|
1887
1928
|
function trim_common_context(target, new_val) {
|
|
1888
1929
|
if (!target || !new_val) return [0, 0];
|
|
1889
1930
|
const isSpace = (char) => /\s/.test(char);
|
|
@@ -1914,7 +1955,7 @@ function trim_common_context(target, new_val) {
|
|
|
1914
1955
|
const left = target.substring(0, prefix_len);
|
|
1915
1956
|
const b_count = (left.match(/\*\*/g) || []).length;
|
|
1916
1957
|
const u2_count = (left.match(/__/g) || []).length;
|
|
1917
|
-
const u1_count = (left
|
|
1958
|
+
const u1_count = _count_standalone_underscores(left);
|
|
1918
1959
|
if (b_count % 2 !== 0) {
|
|
1919
1960
|
prefix_len = left.lastIndexOf("**");
|
|
1920
1961
|
continue;
|
|
@@ -1925,10 +1966,14 @@ function trim_common_context(target, new_val) {
|
|
|
1925
1966
|
}
|
|
1926
1967
|
if (u1_count % 2 !== 0) {
|
|
1927
1968
|
let idx = left.length - 1;
|
|
1969
|
+
const isAlnum = (char) => /[a-zA-Z0-9]/.test(char);
|
|
1928
1970
|
while (idx >= 0) {
|
|
1929
1971
|
if (left[idx] === "_" && (idx === 0 || left[idx - 1] !== "_") && (idx === left.length - 1 || left[idx + 1] !== "_")) {
|
|
1930
|
-
|
|
1931
|
-
|
|
1972
|
+
const is_intra = idx > 0 && isAlnum(left[idx - 1]) && idx < left.length - 1 && isAlnum(left[idx + 1]);
|
|
1973
|
+
if (!is_intra) {
|
|
1974
|
+
prefix_len = idx;
|
|
1975
|
+
break;
|
|
1976
|
+
}
|
|
1932
1977
|
}
|
|
1933
1978
|
idx--;
|
|
1934
1979
|
}
|
|
@@ -1988,7 +2033,7 @@ function trim_common_context(target, new_val) {
|
|
|
1988
2033
|
const right = target.substring(target.length - suffix_len);
|
|
1989
2034
|
const b_count = (right.match(/\*\*/g) || []).length;
|
|
1990
2035
|
const u2_count = (right.match(/__/g) || []).length;
|
|
1991
|
-
const u1_count = (right
|
|
2036
|
+
const u1_count = _count_standalone_underscores(right);
|
|
1992
2037
|
if (b_count % 2 !== 0) {
|
|
1993
2038
|
suffix_len -= right.indexOf("**") + 2;
|
|
1994
2039
|
continue;
|
|
@@ -1999,10 +2044,14 @@ function trim_common_context(target, new_val) {
|
|
|
1999
2044
|
}
|
|
2000
2045
|
if (u1_count % 2 !== 0) {
|
|
2001
2046
|
let idx_in_right = 0;
|
|
2047
|
+
const isAlnum = (char) => /[a-zA-Z0-9]/.test(char);
|
|
2002
2048
|
while (idx_in_right < right.length) {
|
|
2003
2049
|
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] !== "_")) {
|
|
2004
|
-
|
|
2005
|
-
|
|
2050
|
+
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]);
|
|
2051
|
+
if (!is_intra) {
|
|
2052
|
+
suffix_len -= idx_in_right + 1;
|
|
2053
|
+
break;
|
|
2054
|
+
}
|
|
2006
2055
|
}
|
|
2007
2056
|
idx_in_right++;
|
|
2008
2057
|
}
|
|
@@ -2649,6 +2698,36 @@ var RedlineEngine = class {
|
|
|
2649
2698
|
this.mapper = new DocumentMapper(this.doc);
|
|
2650
2699
|
this.comments_manager = new CommentsManager(this.doc);
|
|
2651
2700
|
}
|
|
2701
|
+
_check_punctuation_warning(target_text) {
|
|
2702
|
+
if (!target_text) return null;
|
|
2703
|
+
if (target_text.includes("_") || target_text.includes("-")) {
|
|
2704
|
+
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.`;
|
|
2705
|
+
}
|
|
2706
|
+
return null;
|
|
2707
|
+
}
|
|
2708
|
+
_build_edit_context_previews(edit) {
|
|
2709
|
+
if (edit.type !== "modify") return [null, null];
|
|
2710
|
+
if (edit._resolved_proxy_edit) {
|
|
2711
|
+
edit = edit._resolved_proxy_edit;
|
|
2712
|
+
}
|
|
2713
|
+
const start_idx = edit._resolved_start_idx;
|
|
2714
|
+
if (start_idx === void 0 || start_idx === null) return [null, null];
|
|
2715
|
+
const target_text = edit.target_text || "";
|
|
2716
|
+
const new_text = edit.new_text || "";
|
|
2717
|
+
const length = target_text.length;
|
|
2718
|
+
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
2719
|
+
const full_text = active_mapper.full_text;
|
|
2720
|
+
if (!full_text) return [null, null];
|
|
2721
|
+
const before_start = Math.max(0, start_idx - 30);
|
|
2722
|
+
const context_before = full_text.substring(before_start, start_idx);
|
|
2723
|
+
const context_after = full_text.substring(start_idx + length, start_idx + length + 30);
|
|
2724
|
+
const critic_markup = `${context_before}{--${target_text}--}{++${new_text}++}${context_after}`;
|
|
2725
|
+
let clean_text = critic_markup;
|
|
2726
|
+
clean_text = clean_text.replace(/\{>>.*?<<\}/gs, "");
|
|
2727
|
+
clean_text = clean_text.replace(/\{--.*?--\}/gs, "");
|
|
2728
|
+
clean_text = clean_text.replace(/\{\+\+(.*?)\+\+\}/gs, "$1");
|
|
2729
|
+
return [critic_markup, clean_text];
|
|
2730
|
+
}
|
|
2652
2731
|
_scan_existing_ids() {
|
|
2653
2732
|
let maxId = 0;
|
|
2654
2733
|
for (const tag of ["w:ins", "w:del"]) {
|
|
@@ -2741,28 +2820,84 @@ var RedlineEngine = class {
|
|
|
2741
2820
|
}
|
|
2742
2821
|
}
|
|
2743
2822
|
}
|
|
2744
|
-
const
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
]) {
|
|
2750
|
-
for (const node of findAllDescendants(this.doc.element, tag)) {
|
|
2751
|
-
const cid = node.getAttribute("w:id");
|
|
2752
|
-
if (cid) comment_ids.add(cid);
|
|
2823
|
+
for (const root_element of parts_to_process) {
|
|
2824
|
+
for (const tag of ["w:commentRangeStart", "w:commentRangeEnd"]) {
|
|
2825
|
+
for (const el of findAllDescendants(root_element, tag)) {
|
|
2826
|
+
el.parentNode?.removeChild(el);
|
|
2827
|
+
}
|
|
2753
2828
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2829
|
+
const refs = findAllDescendants(root_element, "w:commentReference");
|
|
2830
|
+
for (const ref of refs) {
|
|
2831
|
+
const parent = ref.parentNode;
|
|
2832
|
+
if (parent) {
|
|
2833
|
+
if (parent.tagName === "w:r" || parent.tagName.endsWith(":r")) {
|
|
2834
|
+
const nonRprChildren = Array.from(parent.childNodes).filter(
|
|
2835
|
+
(c) => c.nodeType === 1 && c.tagName !== "w:rPr" && c.tagName !== "rPr"
|
|
2836
|
+
);
|
|
2837
|
+
if (nonRprChildren.length <= 1) {
|
|
2838
|
+
parent.parentNode?.removeChild(parent);
|
|
2839
|
+
} else {
|
|
2840
|
+
parent.removeChild(ref);
|
|
2841
|
+
}
|
|
2842
|
+
} else {
|
|
2843
|
+
parent.removeChild(ref);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2762
2846
|
}
|
|
2763
2847
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2848
|
+
const pkg = this.doc.pkg;
|
|
2849
|
+
const comment_partnames = /* @__PURE__ */ new Set();
|
|
2850
|
+
for (const part of pkg.parts) {
|
|
2851
|
+
if (part.partname.toLowerCase().includes("comments")) {
|
|
2852
|
+
comment_partnames.add(part.partname);
|
|
2853
|
+
const withSlash = part.partname.startsWith("/") ? part.partname : "/" + part.partname;
|
|
2854
|
+
const withoutSlash = part.partname.startsWith("/") ? part.partname.substring(1) : part.partname;
|
|
2855
|
+
comment_partnames.add(withSlash);
|
|
2856
|
+
comment_partnames.add(withoutSlash);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
if (comment_partnames.size > 0) {
|
|
2860
|
+
for (const part of pkg.parts) {
|
|
2861
|
+
if (part.partname.endsWith(".rels")) {
|
|
2862
|
+
const rels = findAllDescendants(part._element, "Relationship");
|
|
2863
|
+
const toRemove = [];
|
|
2864
|
+
for (const rel of rels) {
|
|
2865
|
+
const target = rel.getAttribute("Target") || "";
|
|
2866
|
+
if (target.toLowerCase().includes("comments")) {
|
|
2867
|
+
toRemove.push(rel);
|
|
2868
|
+
const sourcePath = part.partname.replace("/_rels/", "/").replace(".rels", "");
|
|
2869
|
+
const sourcePart = pkg.getPartByPath(sourcePath);
|
|
2870
|
+
if (sourcePart) {
|
|
2871
|
+
const relId = rel.getAttribute("Id");
|
|
2872
|
+
if (relId) sourcePart.rels.delete(relId);
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
for (const relEl of toRemove) {
|
|
2877
|
+
relEl.parentNode?.removeChild(relEl);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
const ctPart = pkg.getPartByPath("[Content_Types].xml");
|
|
2882
|
+
if (ctPart) {
|
|
2883
|
+
const overrides = findAllDescendants(ctPart._element, "Override");
|
|
2884
|
+
const toRemove = [];
|
|
2885
|
+
for (const override of overrides) {
|
|
2886
|
+
const partName = override.getAttribute("PartName") || "";
|
|
2887
|
+
if (comment_partnames.has(partName) || partName.toLowerCase().includes("comments")) {
|
|
2888
|
+
toRemove.push(override);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
for (const overrideEl of toRemove) {
|
|
2892
|
+
overrideEl.parentNode?.removeChild(overrideEl);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
pkg.parts = pkg.parts.filter((p) => !p.partname.toLowerCase().includes("comments"));
|
|
2896
|
+
for (const key of Object.keys(pkg.unzipped)) {
|
|
2897
|
+
if (key.toLowerCase().includes("comments")) {
|
|
2898
|
+
delete pkg.unzipped[key];
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2766
2901
|
}
|
|
2767
2902
|
}
|
|
2768
2903
|
_getNextId() {
|
|
@@ -2853,40 +2988,40 @@ var RedlineEngine = class {
|
|
|
2853
2988
|
}
|
|
2854
2989
|
}
|
|
2855
2990
|
/**
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2991
|
+
* Inserts `text` as one or more tracked paragraphs anchored relative to
|
|
2992
|
+
* either an existing run or a paragraph. Returns:
|
|
2993
|
+
* { first_node, last_p, last_ins, used_block_mode }
|
|
2994
|
+
* where:
|
|
2995
|
+
* - first_node: the first <w:ins> (for inline mode) OR the first new <w:p>
|
|
2996
|
+
* (for block mode). The caller uses this for splicing into the DOM and
|
|
2997
|
+
* for anchoring comments.
|
|
2998
|
+
* - last_p: the last new <w:p> created, if any. null when entirely inline.
|
|
2999
|
+
* - last_ins: the last <w:ins> created (inside the last new <w:p>, or the
|
|
3000
|
+
* sole inline ins). Used as the comment's end anchor.
|
|
3001
|
+
* - used_block_mode: true when the first line carried a heading/list style
|
|
3002
|
+
* marker and we created a new paragraph for it (rather than inlining it).
|
|
3003
|
+
*
|
|
3004
|
+
* Multi-paragraph rules (only when text contains '\n'):
|
|
3005
|
+
* - Each additional line becomes a new <w:p>, inserted after the anchor
|
|
3006
|
+
* paragraph in document order.
|
|
3007
|
+
* - Each new <w:p> gets a copy of the anchor paragraph's <w:pPr> (so list
|
|
3008
|
+
* numbering / indentation are preserved) unless the line itself starts
|
|
3009
|
+
* with a markdown heading or list marker, which overrides the style.
|
|
3010
|
+
* - Each new <w:p> carries a tracked paragraph-break marker
|
|
3011
|
+
* (<w:pPr><w:rPr><w:ins/></w:rPr></w:pPr>) so Word natively tracks the
|
|
3012
|
+
* paragraph break.
|
|
3013
|
+
* - Each new <w:p>'s content is wrapped in a <w:ins>, with inline bold/
|
|
3014
|
+
* italic markdown parsed via _parse_inline_markdown.
|
|
3015
|
+
*
|
|
3016
|
+
* The first line:
|
|
3017
|
+
* - If it carries a heading / list marker AND we have a paragraph anchor,
|
|
3018
|
+
* we drop into "block mode": no inline <w:ins>; the first line itself
|
|
3019
|
+
* becomes the first new <w:p>.
|
|
3020
|
+
* - Otherwise we emit a single inline <w:ins> for the first line (current
|
|
3021
|
+
* behaviour) and treat the remaining lines as block extensions.
|
|
3022
|
+
*
|
|
3023
|
+
* Does NOT attach comments; callers handle that.
|
|
3024
|
+
*/
|
|
2890
3025
|
_track_insert_multiline(text, anchor_run, anchor_paragraph, reuse_id) {
|
|
2891
3026
|
if (!text) {
|
|
2892
3027
|
return {
|
|
@@ -3026,7 +3161,15 @@ var RedlineEngine = class {
|
|
|
3026
3161
|
const anchor_rPr = findChild(anchor_run._element, "w:rPr");
|
|
3027
3162
|
if (anchor_rPr) {
|
|
3028
3163
|
const clone = anchor_rPr.cloneNode(true);
|
|
3029
|
-
for (const tag of [
|
|
3164
|
+
for (const tag of [
|
|
3165
|
+
"w:vanish",
|
|
3166
|
+
"w:strike",
|
|
3167
|
+
"w:dstrike",
|
|
3168
|
+
"w:i",
|
|
3169
|
+
"w:iCs",
|
|
3170
|
+
"w:b",
|
|
3171
|
+
"w:bCs"
|
|
3172
|
+
]) {
|
|
3030
3173
|
const found = findChild(clone, tag);
|
|
3031
3174
|
if (found) clone.removeChild(found);
|
|
3032
3175
|
}
|
|
@@ -3300,6 +3443,16 @@ var RedlineEngine = class {
|
|
|
3300
3443
|
matches = this.clean_mapper.find_all_match_indices(edit.target_text);
|
|
3301
3444
|
if (matches.length > 0) activeText = this.clean_mapper.full_text;
|
|
3302
3445
|
}
|
|
3446
|
+
if (activeText === this.mapper.full_text && matches.length > 1) {
|
|
3447
|
+
const liveMatches = matches.filter(([start, length]) => {
|
|
3448
|
+
const realSpans = this.mapper.spans.filter(
|
|
3449
|
+
(s) => s.run !== null && s.end > start && s.start < start + length
|
|
3450
|
+
);
|
|
3451
|
+
if (realSpans.length === 0) return true;
|
|
3452
|
+
return realSpans.some((s) => !s.del_id);
|
|
3453
|
+
});
|
|
3454
|
+
if (liveMatches.length > 0) matches = liveMatches;
|
|
3455
|
+
}
|
|
3303
3456
|
if (matches.length === 0) {
|
|
3304
3457
|
errors.push(
|
|
3305
3458
|
`- Edit ${i + 1} Failed: Target text not found in document:
|
|
@@ -3319,6 +3472,31 @@ var RedlineEngine = class {
|
|
|
3319
3472
|
)
|
|
3320
3473
|
);
|
|
3321
3474
|
}
|
|
3475
|
+
if (matches.length === 1) {
|
|
3476
|
+
const [m_start, m_len] = matches[0];
|
|
3477
|
+
const matched = activeText.substring(m_start, m_start + m_len);
|
|
3478
|
+
const [pfx, sfx] = trim_common_context(matched, edit.new_text || "");
|
|
3479
|
+
const t_end = matched.length - sfx;
|
|
3480
|
+
const final_target = matched.substring(pfx, t_end);
|
|
3481
|
+
const final_new = (edit.new_text || "").substring(pfx, (edit.new_text || "").length - sfx);
|
|
3482
|
+
if (final_target.includes("\n\n")) {
|
|
3483
|
+
if (final_new.includes("\n\n")) {
|
|
3484
|
+
const parts = matched.split("\n\n");
|
|
3485
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3486
|
+
errors.push(
|
|
3487
|
+
`- 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.`
|
|
3488
|
+
);
|
|
3489
|
+
}
|
|
3490
|
+
} else {
|
|
3491
|
+
const parts = final_target.split("\n\n");
|
|
3492
|
+
if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
|
|
3493
|
+
errors.push(
|
|
3494
|
+
`- 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.`
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3322
3500
|
for (const [start, length] of matches) {
|
|
3323
3501
|
const spans = this.mapper.spans.filter(
|
|
3324
3502
|
(s) => s.end > start && s.start < start + length
|
|
@@ -3382,7 +3560,33 @@ var RedlineEngine = class {
|
|
|
3382
3560
|
}
|
|
3383
3561
|
return errors;
|
|
3384
3562
|
}
|
|
3385
|
-
process_batch(changes) {
|
|
3563
|
+
process_batch(changes, dry_run = false) {
|
|
3564
|
+
if (dry_run) {
|
|
3565
|
+
const baselines = /* @__PURE__ */ new Map();
|
|
3566
|
+
for (const part of this.doc.pkg.parts) {
|
|
3567
|
+
if (part._element) {
|
|
3568
|
+
baselines.set(part, part._element.cloneNode(true));
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
try {
|
|
3572
|
+
return this._process_batch_internal(changes, true);
|
|
3573
|
+
} finally {
|
|
3574
|
+
for (const [part, originalEl] of baselines.entries()) {
|
|
3575
|
+
const doc = part._element.ownerDocument;
|
|
3576
|
+
if (doc && doc.documentElement) {
|
|
3577
|
+
doc.replaceChild(originalEl, doc.documentElement);
|
|
3578
|
+
}
|
|
3579
|
+
part._element = originalEl;
|
|
3580
|
+
}
|
|
3581
|
+
this.mapper = new DocumentMapper(this.doc);
|
|
3582
|
+
this.comments_manager = new CommentsManager(this.doc);
|
|
3583
|
+
this.clean_mapper = null;
|
|
3584
|
+
}
|
|
3585
|
+
} else {
|
|
3586
|
+
return this._process_batch_internal(changes, false);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
_process_batch_internal(changes, dry_run_mode = false) {
|
|
3386
3590
|
this.skipped_details = [];
|
|
3387
3591
|
const actions = changes.filter(
|
|
3388
3592
|
(c) => ["accept", "reject", "reply"].includes(c.type)
|
|
@@ -3390,38 +3594,129 @@ var RedlineEngine = class {
|
|
|
3390
3594
|
const edits = changes.filter(
|
|
3391
3595
|
(c) => !["accept", "reject", "reply"].includes(c.type)
|
|
3392
3596
|
);
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3597
|
+
if (!dry_run_mode) {
|
|
3598
|
+
const all_errors = [];
|
|
3599
|
+
if (actions.length > 0) {
|
|
3600
|
+
all_errors.push(...this.validate_review_actions(actions));
|
|
3601
|
+
}
|
|
3602
|
+
if (edits.length > 0) {
|
|
3603
|
+
all_errors.push(...this.validate_edits(edits));
|
|
3604
|
+
}
|
|
3605
|
+
if (all_errors.length > 0) {
|
|
3606
|
+
throw new BatchValidationError(all_errors);
|
|
3607
|
+
}
|
|
3608
|
+
} else {
|
|
3609
|
+
if (actions.length > 0) {
|
|
3610
|
+
const action_errors = this.validate_review_actions(actions);
|
|
3611
|
+
if (action_errors.length > 0) {
|
|
3612
|
+
throw new BatchValidationError(action_errors);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3402
3615
|
}
|
|
3403
|
-
let applied_actions = 0
|
|
3616
|
+
let applied_actions = 0;
|
|
3617
|
+
let skipped_actions = 0;
|
|
3404
3618
|
if (actions.length > 0) {
|
|
3405
3619
|
const res = this.apply_review_actions(actions);
|
|
3406
3620
|
applied_actions = res[0];
|
|
3407
3621
|
skipped_actions = res[1];
|
|
3622
|
+
if (skipped_actions > 0) {
|
|
3623
|
+
throw new BatchValidationError(this.skipped_details);
|
|
3624
|
+
}
|
|
3408
3625
|
if (applied_actions > 0) {
|
|
3409
3626
|
this.mapper["_build_map"]();
|
|
3410
3627
|
if (this.clean_mapper) this.clean_mapper["_build_map"]();
|
|
3411
3628
|
}
|
|
3412
3629
|
}
|
|
3413
|
-
|
|
3630
|
+
const edits_reports = [];
|
|
3631
|
+
let applied_edits = 0;
|
|
3632
|
+
let skipped_edits = 0;
|
|
3414
3633
|
if (edits.length > 0) {
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3634
|
+
if (dry_run_mode) {
|
|
3635
|
+
for (const edit of edits) {
|
|
3636
|
+
const single_errors = this.validate_edits([edit]);
|
|
3637
|
+
const warning = this._check_punctuation_warning(edit.target_text || "");
|
|
3638
|
+
if (single_errors.length > 0) {
|
|
3639
|
+
skipped_edits++;
|
|
3640
|
+
edits_reports.push({
|
|
3641
|
+
status: "failed",
|
|
3642
|
+
target_text: edit.target_text || "",
|
|
3643
|
+
new_text: edit.new_text || "",
|
|
3644
|
+
warning,
|
|
3645
|
+
error: single_errors[0],
|
|
3646
|
+
critic_markup: null,
|
|
3647
|
+
clean_text: null
|
|
3648
|
+
});
|
|
3649
|
+
continue;
|
|
3650
|
+
}
|
|
3651
|
+
const res = this.apply_edits([edit]);
|
|
3652
|
+
const applied = res[0];
|
|
3653
|
+
if (applied > 0) {
|
|
3654
|
+
applied_edits++;
|
|
3655
|
+
const previews = this._build_edit_context_previews(edit);
|
|
3656
|
+
edits_reports.push({
|
|
3657
|
+
status: "applied",
|
|
3658
|
+
target_text: edit.target_text || "",
|
|
3659
|
+
new_text: edit.new_text || "",
|
|
3660
|
+
warning,
|
|
3661
|
+
error: null,
|
|
3662
|
+
critic_markup: previews[0],
|
|
3663
|
+
clean_text: previews[1]
|
|
3664
|
+
});
|
|
3665
|
+
} else {
|
|
3666
|
+
skipped_edits++;
|
|
3667
|
+
const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
|
|
3668
|
+
edits_reports.push({
|
|
3669
|
+
status: "failed",
|
|
3670
|
+
target_text: edit.target_text || "",
|
|
3671
|
+
new_text: edit.new_text || "",
|
|
3672
|
+
warning,
|
|
3673
|
+
error: error_msg,
|
|
3674
|
+
critic_markup: null,
|
|
3675
|
+
clean_text: null
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
} else {
|
|
3680
|
+
const errors = this.validate_edits(edits);
|
|
3681
|
+
if (errors.length > 0) {
|
|
3682
|
+
throw new BatchValidationError(errors);
|
|
3683
|
+
}
|
|
3684
|
+
const cloned_edits = edits.map((e) => JSON.parse(JSON.stringify(e)));
|
|
3685
|
+
const res = this.apply_edits(cloned_edits);
|
|
3686
|
+
applied_edits = res[0];
|
|
3687
|
+
skipped_edits = res[1];
|
|
3688
|
+
for (const edit of cloned_edits) {
|
|
3689
|
+
const success = edit._applied_status || false;
|
|
3690
|
+
const error_msg = edit._error_msg || null;
|
|
3691
|
+
const warning = this._check_punctuation_warning(edit.target_text || "");
|
|
3692
|
+
let critic_markup = null;
|
|
3693
|
+
let clean_text = null;
|
|
3694
|
+
if (success) {
|
|
3695
|
+
const previews = this._build_edit_context_previews(edit);
|
|
3696
|
+
critic_markup = previews[0];
|
|
3697
|
+
clean_text = previews[1];
|
|
3698
|
+
}
|
|
3699
|
+
edits_reports.push({
|
|
3700
|
+
status: success ? "applied" : "failed",
|
|
3701
|
+
target_text: edit.target_text || "",
|
|
3702
|
+
new_text: edit.new_text || "",
|
|
3703
|
+
warning,
|
|
3704
|
+
error: error_msg,
|
|
3705
|
+
critic_markup,
|
|
3706
|
+
clean_text
|
|
3707
|
+
});
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3418
3710
|
}
|
|
3419
3711
|
return {
|
|
3420
3712
|
actions_applied: applied_actions,
|
|
3421
3713
|
actions_skipped: skipped_actions,
|
|
3422
3714
|
edits_applied: applied_edits,
|
|
3423
3715
|
edits_skipped: skipped_edits,
|
|
3424
|
-
skipped_details: this.skipped_details
|
|
3716
|
+
skipped_details: this.skipped_details,
|
|
3717
|
+
edits: edits_reports,
|
|
3718
|
+
engine: "node",
|
|
3719
|
+
version: "1.9.0"
|
|
3425
3720
|
};
|
|
3426
3721
|
}
|
|
3427
3722
|
apply_edits(edits) {
|
|
@@ -3429,50 +3724,90 @@ var RedlineEngine = class {
|
|
|
3429
3724
|
let skipped = 0;
|
|
3430
3725
|
const resolved_edits = [];
|
|
3431
3726
|
for (const edit of edits) {
|
|
3432
|
-
|
|
3727
|
+
edit._applied_status = false;
|
|
3728
|
+
edit._error_msg = null;
|
|
3729
|
+
}
|
|
3730
|
+
for (const edit of edits) {
|
|
3731
|
+
if (edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null) {
|
|
3732
|
+
resolved_edits.push([edit, edit.new_text || null]);
|
|
3733
|
+
} else if (edit._match_start_index !== void 0 && edit._match_start_index !== null) {
|
|
3734
|
+
edit._resolved_start_idx = edit._match_start_index;
|
|
3433
3735
|
resolved_edits.push([edit, edit.new_text || null]);
|
|
3434
3736
|
} else if (edit.type === "insert_row" || edit.type === "delete_row") {
|
|
3435
|
-
|
|
3436
|
-
if (
|
|
3437
|
-
|
|
3737
|
+
let matches = this.mapper.find_all_match_indices(edit.target_text);
|
|
3738
|
+
if (matches.length === 0) {
|
|
3739
|
+
if (!this.clean_mapper) {
|
|
3740
|
+
this.clean_mapper = new DocumentMapper(this.doc, true);
|
|
3741
|
+
}
|
|
3742
|
+
matches = this.clean_mapper.find_all_match_indices(edit.target_text);
|
|
3743
|
+
}
|
|
3744
|
+
if (matches.length > 0) {
|
|
3745
|
+
edit._resolved_start_idx = matches[0][0];
|
|
3438
3746
|
resolved_edits.push([edit, null]);
|
|
3439
3747
|
} else {
|
|
3440
3748
|
skipped++;
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3749
|
+
edit._applied_status = false;
|
|
3750
|
+
const target_snippet = (edit.target_text || "").trim().substring(0, 40);
|
|
3751
|
+
const msg = `- Failed to locate row target: '${target_snippet}...'`;
|
|
3752
|
+
this.skipped_details.push(msg);
|
|
3753
|
+
edit._error_msg = msg;
|
|
3444
3754
|
}
|
|
3445
3755
|
} else {
|
|
3446
3756
|
const resolved = this._pre_resolve_heuristic_edit(edit);
|
|
3447
3757
|
if (resolved) {
|
|
3448
3758
|
if (Array.isArray(resolved)) {
|
|
3449
|
-
for (const r of resolved)
|
|
3759
|
+
for (const r of resolved) {
|
|
3760
|
+
r._resolved_start_idx = r._match_start_index;
|
|
3761
|
+
r._parent_edit_ref = edit;
|
|
3762
|
+
if (edit._resolved_start_idx === void 0 || edit._resolved_start_idx === null) {
|
|
3763
|
+
edit._resolved_start_idx = r._resolved_start_idx;
|
|
3764
|
+
}
|
|
3765
|
+
if (!edit._resolved_proxy_edit) {
|
|
3766
|
+
edit._resolved_proxy_edit = r;
|
|
3767
|
+
}
|
|
3768
|
+
resolved_edits.push([r, r.new_text]);
|
|
3769
|
+
}
|
|
3450
3770
|
} else {
|
|
3771
|
+
resolved._resolved_start_idx = resolved._match_start_index;
|
|
3772
|
+
resolved._parent_edit_ref = edit;
|
|
3773
|
+
edit._resolved_start_idx = resolved._resolved_start_idx;
|
|
3774
|
+
edit._resolved_proxy_edit = resolved;
|
|
3451
3775
|
resolved_edits.push([resolved, resolved.new_text]);
|
|
3452
3776
|
}
|
|
3453
3777
|
} else {
|
|
3454
3778
|
skipped++;
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
);
|
|
3779
|
+
edit._applied_status = false;
|
|
3780
|
+
const display_text = edit.target_text || "insertion";
|
|
3781
|
+
const target_snippet = display_text.trim().substring(0, 40);
|
|
3782
|
+
const msg = `- Failed to apply edit targeting: '${target_snippet}...'`;
|
|
3783
|
+
this.skipped_details.push(msg);
|
|
3784
|
+
edit._error_msg = msg;
|
|
3458
3785
|
}
|
|
3459
3786
|
}
|
|
3460
3787
|
}
|
|
3461
3788
|
resolved_edits.sort(
|
|
3462
|
-
(a, b) => (b[0].
|
|
3789
|
+
(a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
|
|
3463
3790
|
);
|
|
3464
3791
|
const occupied_ranges = [];
|
|
3465
3792
|
for (const [edit, orig_new] of resolved_edits) {
|
|
3466
|
-
const start = edit.
|
|
3793
|
+
const start = edit._resolved_start_idx || 0;
|
|
3467
3794
|
const end = start + (edit.target_text ? edit.target_text.length : 0);
|
|
3468
3795
|
const overlaps = occupied_ranges.some(
|
|
3469
3796
|
([occ_start, occ_end]) => start < occ_end && end > occ_start
|
|
3470
3797
|
);
|
|
3471
3798
|
if (overlaps) {
|
|
3472
3799
|
skipped++;
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3800
|
+
const display_text = edit.target_text || "insertion";
|
|
3801
|
+
const target_snippet = display_text.trim().substring(0, 40);
|
|
3802
|
+
const msg = `- Skipped overlapping edit targeting: '${target_snippet}...'`;
|
|
3803
|
+
this.skipped_details.push(msg);
|
|
3804
|
+
edit._applied_status = false;
|
|
3805
|
+
edit._error_msg = msg;
|
|
3806
|
+
const parent = edit._parent_edit_ref;
|
|
3807
|
+
if (parent) {
|
|
3808
|
+
parent._applied_status = false;
|
|
3809
|
+
parent._error_msg = msg;
|
|
3810
|
+
}
|
|
3476
3811
|
continue;
|
|
3477
3812
|
}
|
|
3478
3813
|
let success = false;
|
|
@@ -3484,11 +3819,26 @@ var RedlineEngine = class {
|
|
|
3484
3819
|
if (success) {
|
|
3485
3820
|
applied++;
|
|
3486
3821
|
occupied_ranges.push([start, end]);
|
|
3822
|
+
edit._applied_status = true;
|
|
3823
|
+
const parent = edit._parent_edit_ref;
|
|
3824
|
+
if (parent) {
|
|
3825
|
+
parent._applied_status = true;
|
|
3826
|
+
}
|
|
3487
3827
|
} else {
|
|
3488
3828
|
skipped++;
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3829
|
+
const display_text = edit.target_text || "insertion";
|
|
3830
|
+
const target_snippet = display_text.trim().substring(0, 40);
|
|
3831
|
+
const msg = `- Failed to apply edit targeting: '${target_snippet}...'`;
|
|
3832
|
+
this.skipped_details.push(msg);
|
|
3833
|
+
edit._applied_status = false;
|
|
3834
|
+
edit._error_msg = msg;
|
|
3835
|
+
const parent = edit._parent_edit_ref;
|
|
3836
|
+
if (parent) {
|
|
3837
|
+
if (!parent._applied_status) {
|
|
3838
|
+
parent._applied_status = false;
|
|
3839
|
+
parent._error_msg = msg;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3492
3842
|
}
|
|
3493
3843
|
}
|
|
3494
3844
|
return [applied, skipped];
|
|
@@ -3581,7 +3931,7 @@ var RedlineEngine = class {
|
|
|
3581
3931
|
return [applied, skipped];
|
|
3582
3932
|
}
|
|
3583
3933
|
_apply_table_edit(edit, rebuild_map) {
|
|
3584
|
-
const start_idx = edit._match_start_index || 0;
|
|
3934
|
+
const start_idx = edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null ? edit._resolved_start_idx : edit._match_start_index || 0;
|
|
3585
3935
|
const [anchor_run, anchor_para] = this.mapper.get_insertion_anchor(
|
|
3586
3936
|
start_idx,
|
|
3587
3937
|
rebuild_map
|
|
@@ -3625,9 +3975,31 @@ var RedlineEngine = class {
|
|
|
3625
3975
|
}
|
|
3626
3976
|
return false;
|
|
3627
3977
|
}
|
|
3978
|
+
/**
|
|
3979
|
+
* Returns the first match of `target_text` in the raw mapper that is NOT
|
|
3980
|
+
* entirely contained within a tracked deletion (<w:del>). Tracked-deleted
|
|
3981
|
+
* copies are not live, editable text, so an edit must resolve to a live
|
|
3982
|
+
* occurrence even when a dead copy appears earlier in the document
|
|
3983
|
+
* (BUG-23-5). Falls back to the plain first match when no live copy is
|
|
3984
|
+
* found (e.g. fuzzy/normalized matches the span filter cannot align).
|
|
3985
|
+
*/
|
|
3986
|
+
_first_live_match(target_text) {
|
|
3987
|
+
const all = this.mapper.find_all_match_indices(target_text);
|
|
3988
|
+
if (all.length <= 1) {
|
|
3989
|
+
return this.mapper.find_match_index(target_text);
|
|
3990
|
+
}
|
|
3991
|
+
for (const [start, length] of all) {
|
|
3992
|
+
const realSpans = this.mapper.spans.filter(
|
|
3993
|
+
(s) => s.run !== null && s.end > start && s.start < start + length
|
|
3994
|
+
);
|
|
3995
|
+
if (realSpans.length === 0) return [start, length];
|
|
3996
|
+
if (realSpans.some((s) => !s.del_id)) return [start, length];
|
|
3997
|
+
}
|
|
3998
|
+
return this.mapper.find_match_index(target_text);
|
|
3999
|
+
}
|
|
3628
4000
|
_pre_resolve_heuristic_edit(edit) {
|
|
3629
4001
|
if (!edit.target_text) return null;
|
|
3630
|
-
let [start_idx, match_len] = this.
|
|
4002
|
+
let [start_idx, match_len] = this._first_live_match(edit.target_text);
|
|
3631
4003
|
let use_clean_map = false;
|
|
3632
4004
|
if (start_idx === -1) {
|
|
3633
4005
|
if (!this.clean_mapper)
|
|
@@ -3691,7 +4063,7 @@ var RedlineEngine = class {
|
|
|
3691
4063
|
_apply_single_edit_indexed(edit, orig_new, rebuild_map) {
|
|
3692
4064
|
let op = edit._internal_op;
|
|
3693
4065
|
const active_mapper = edit._active_mapper_ref || this.mapper;
|
|
3694
|
-
const start_idx = edit._match_start_index || 0;
|
|
4066
|
+
const start_idx = edit._resolved_start_idx !== void 0 && edit._resolved_start_idx !== null ? edit._resolved_start_idx : edit._match_start_index || 0;
|
|
3695
4067
|
const length = edit.target_text ? edit.target_text.length : 0;
|
|
3696
4068
|
const del_id = ["DELETION", "MODIFICATION"].includes(op) ? this._getNextId() : null;
|
|
3697
4069
|
const ins_id = ["INSERTION", "MODIFICATION"].includes(op) ? this._getNextId() : null;
|
|
@@ -3740,6 +4112,76 @@ var RedlineEngine = class {
|
|
|
3740
4112
|
rebuild_map
|
|
3741
4113
|
);
|
|
3742
4114
|
if (!anchor_run && !anchor_para) return false;
|
|
4115
|
+
const _bug233_new = edit.new_text || "";
|
|
4116
|
+
const _bug233_trailing_break = /\n\s*$/.test(_bug233_new);
|
|
4117
|
+
let _bug233_target_para = null;
|
|
4118
|
+
{
|
|
4119
|
+
const startingSpans = active_mapper.spans.filter(
|
|
4120
|
+
(s) => s.paragraph !== null && s.start === start_idx
|
|
4121
|
+
);
|
|
4122
|
+
if (startingSpans.length > 0 && startingSpans[0].paragraph) {
|
|
4123
|
+
_bug233_target_para = startingSpans[0].paragraph._element;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
if (_bug233_trailing_break && _bug233_target_para && _bug233_target_para.parentNode) {
|
|
4127
|
+
const body = _bug233_target_para.parentNode;
|
|
4128
|
+
const xmlDoc = this.doc.part._element.ownerDocument;
|
|
4129
|
+
const lines = _bug233_new.split(/[\r\n]+/).filter((l) => l !== "");
|
|
4130
|
+
let firstNew = null;
|
|
4131
|
+
let lastNew = null;
|
|
4132
|
+
let lastIns = null;
|
|
4133
|
+
for (const raw_line of lines) {
|
|
4134
|
+
const [clean_text, style_name] = this._parse_markdown_style(raw_line);
|
|
4135
|
+
const new_p = xmlDoc.createElement("w:p");
|
|
4136
|
+
if (style_name) {
|
|
4137
|
+
this._set_paragraph_style(new_p, style_name);
|
|
4138
|
+
} else {
|
|
4139
|
+
const existing_pPr = findChild(_bug233_target_para, "w:pPr");
|
|
4140
|
+
if (existing_pPr) new_p.appendChild(existing_pPr.cloneNode(true));
|
|
4141
|
+
}
|
|
4142
|
+
let pPr = findChild(new_p, "w:pPr");
|
|
4143
|
+
if (!pPr) {
|
|
4144
|
+
pPr = xmlDoc.createElement("w:pPr");
|
|
4145
|
+
new_p.insertBefore(pPr, new_p.firstChild);
|
|
4146
|
+
}
|
|
4147
|
+
let rPr = findChild(pPr, "w:rPr");
|
|
4148
|
+
if (!rPr) {
|
|
4149
|
+
rPr = xmlDoc.createElement("w:rPr");
|
|
4150
|
+
pPr.appendChild(rPr);
|
|
4151
|
+
}
|
|
4152
|
+
rPr.appendChild(this._create_track_change_tag("w:ins", "", ins_id));
|
|
4153
|
+
const content_ins = this._build_tracked_ins_for_line(
|
|
4154
|
+
clean_text,
|
|
4155
|
+
anchor_run,
|
|
4156
|
+
ins_id,
|
|
4157
|
+
xmlDoc
|
|
4158
|
+
);
|
|
4159
|
+
if (content_ins) new_p.appendChild(content_ins);
|
|
4160
|
+
body.insertBefore(new_p, _bug233_target_para);
|
|
4161
|
+
if (!firstNew) firstNew = new_p;
|
|
4162
|
+
lastNew = new_p;
|
|
4163
|
+
lastIns = content_ins;
|
|
4164
|
+
}
|
|
4165
|
+
if (firstNew) {
|
|
4166
|
+
if (edit.comment && lastNew && lastIns) {
|
|
4167
|
+
const ascend = (el, p) => {
|
|
4168
|
+
let cur = el;
|
|
4169
|
+
while (cur.parentNode && cur.parentNode !== p)
|
|
4170
|
+
cur = cur.parentNode;
|
|
4171
|
+
return cur;
|
|
4172
|
+
};
|
|
4173
|
+
const startIns = findAllDescendants(firstNew, "w:ins")[0] || firstNew;
|
|
4174
|
+
this._attach_comment_spanning(
|
|
4175
|
+
firstNew,
|
|
4176
|
+
ascend(startIns, firstNew),
|
|
4177
|
+
lastNew,
|
|
4178
|
+
ascend(lastIns, lastNew),
|
|
4179
|
+
edit.comment
|
|
4180
|
+
);
|
|
4181
|
+
}
|
|
4182
|
+
return true;
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
3743
4185
|
const result = this._track_insert_multiline(
|
|
3744
4186
|
edit.new_text || "",
|
|
3745
4187
|
anchor_run,
|
|
@@ -5449,15 +5891,88 @@ function remove_all_comments(doc) {
|
|
|
5449
5891
|
lines.push(` ${status} "${_truncate(info.text || "", 60)}" (${info.author || "Unknown"})`);
|
|
5450
5892
|
cm.deleteComment(cId);
|
|
5451
5893
|
}
|
|
5452
|
-
for (const tag of ["w:commentRangeStart", "w:commentRangeEnd"
|
|
5894
|
+
for (const tag of ["w:commentRangeStart", "w:commentRangeEnd"]) {
|
|
5453
5895
|
for (const el of findAllDescendants(doc.element, tag)) {
|
|
5454
5896
|
el.parentNode?.removeChild(el);
|
|
5455
5897
|
}
|
|
5456
5898
|
}
|
|
5899
|
+
const refs = findAllDescendants(doc.element, "w:commentReference");
|
|
5900
|
+
for (const ref of refs) {
|
|
5901
|
+
const parent = ref.parentNode;
|
|
5902
|
+
if (parent) {
|
|
5903
|
+
if (parent.tagName === "w:r" || parent.tagName.endsWith(":r")) {
|
|
5904
|
+
const nonRprChildren = Array.from(parent.childNodes).filter(
|
|
5905
|
+
(c) => c.nodeType === 1 && c.tagName !== "w:rPr" && c.tagName !== "rPr"
|
|
5906
|
+
);
|
|
5907
|
+
if (nonRprChildren.length <= 1) {
|
|
5908
|
+
parent.parentNode?.removeChild(parent);
|
|
5909
|
+
} else {
|
|
5910
|
+
parent.removeChild(ref);
|
|
5911
|
+
}
|
|
5912
|
+
} else {
|
|
5913
|
+
parent.removeChild(ref);
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5457
5917
|
const resolvedCount = Object.values(data).filter((c) => c.resolved).length;
|
|
5458
5918
|
const openCount = Object.values(data).filter((c) => !c.resolved).length;
|
|
5459
5919
|
return [`Comments removed: ${keys.length} (${resolvedCount} resolved, ${openCount} open)`].concat(lines);
|
|
5460
5920
|
}
|
|
5921
|
+
function eject_comment_parts(doc) {
|
|
5922
|
+
const pkg = doc.pkg;
|
|
5923
|
+
const comment_partnames = /* @__PURE__ */ new Set();
|
|
5924
|
+
for (const part of pkg.parts) {
|
|
5925
|
+
if (part.partname.toLowerCase().includes("comments")) {
|
|
5926
|
+
comment_partnames.add(part.partname);
|
|
5927
|
+
const withSlash = part.partname.startsWith("/") ? part.partname : "/" + part.partname;
|
|
5928
|
+
const withoutSlash = part.partname.startsWith("/") ? part.partname.substring(1) : part.partname;
|
|
5929
|
+
comment_partnames.add(withSlash);
|
|
5930
|
+
comment_partnames.add(withoutSlash);
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
if (comment_partnames.size === 0) return;
|
|
5934
|
+
for (const part of pkg.parts) {
|
|
5935
|
+
if (part.partname.endsWith(".rels")) {
|
|
5936
|
+
const rels = findAllDescendants(part._element, "Relationship");
|
|
5937
|
+
const toRemove = [];
|
|
5938
|
+
for (const rel of rels) {
|
|
5939
|
+
const target = rel.getAttribute("Target") || "";
|
|
5940
|
+
if (target.toLowerCase().includes("comments")) {
|
|
5941
|
+
toRemove.push(rel);
|
|
5942
|
+
const sourcePath = part.partname.replace("/_rels/", "/").replace(".rels", "");
|
|
5943
|
+
const sourcePart = pkg.getPartByPath(sourcePath);
|
|
5944
|
+
if (sourcePart) {
|
|
5945
|
+
const relId = rel.getAttribute("Id");
|
|
5946
|
+
if (relId) sourcePart.rels.delete(relId);
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
for (const relEl of toRemove) {
|
|
5951
|
+
relEl.parentNode?.removeChild(relEl);
|
|
5952
|
+
}
|
|
5953
|
+
}
|
|
5954
|
+
}
|
|
5955
|
+
const ctPart = pkg.getPartByPath("[Content_Types].xml");
|
|
5956
|
+
if (ctPart) {
|
|
5957
|
+
const overrides = findAllDescendants(ctPart._element, "Override");
|
|
5958
|
+
const toRemove = [];
|
|
5959
|
+
for (const override of overrides) {
|
|
5960
|
+
const partName = override.getAttribute("PartName") || "";
|
|
5961
|
+
if (comment_partnames.has(partName) || partName.toLowerCase().includes("comments")) {
|
|
5962
|
+
toRemove.push(override);
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
for (const overrideEl of toRemove) {
|
|
5966
|
+
overrideEl.parentNode?.removeChild(overrideEl);
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
pkg.parts = pkg.parts.filter((p) => !p.partname.toLowerCase().includes("comments"));
|
|
5970
|
+
for (const key of Object.keys(pkg.unzipped)) {
|
|
5971
|
+
if (key.toLowerCase().includes("comments")) {
|
|
5972
|
+
delete pkg.unzipped[key];
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5461
5976
|
function replace_comment_authors(doc, newAuthor) {
|
|
5462
5977
|
const cm = new CommentsManager(doc);
|
|
5463
5978
|
if (!cm.commentsPart) return [];
|
|
@@ -5646,6 +6161,7 @@ async function finalize_document(doc, options) {
|
|
|
5646
6161
|
const commentsSummary = get_comments_summary(doc);
|
|
5647
6162
|
report.comments_removed = commentsSummary.total;
|
|
5648
6163
|
report.add_transform_lines(remove_all_comments(doc));
|
|
6164
|
+
eject_comment_parts(doc);
|
|
5649
6165
|
} else if (options.sanitize_mode === "keep-markup") {
|
|
5650
6166
|
const counts = count_tracked_changes(doc);
|
|
5651
6167
|
report.tracked_changes_found = counts[0] + counts[1] + counts[2];
|