@ansonlai/docx-redline-js 0.2.0 → 0.4.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/AGENTS.md +36 -10
- package/README.md +83 -6
- package/adapters/xml-adapter.js +73 -10
- package/core/list-targeting.js +3 -0
- package/core/paragraph-targeting.js +33 -7
- package/core/redline-validation.js +22 -0
- package/core/types.js +122 -27
- package/core/xml-query.js +3 -1
- package/dist/docx-redline-js.esm.js +1148 -572
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +79 -78
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/TESTING.md +687 -0
- package/docs/VALIDATION.md +81 -2
- package/docs/WORD-MANUAL-REVIEW.md +138 -0
- package/docs/plans/2026-08-30-reliability-testing-improvements.md +488 -0
- package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +210 -0
- package/docs/plans/{2026-03-01-release-0.1.4-design.md → completed/2026-03-01-release-0.1.4-design.md} +2 -0
- package/docs/plans/{2026-03-01-release-0.1.4.md → completed/2026-03-01-release-0.1.4.md} +5 -3
- package/docs/plans/{2026-05-31-architectural changes.md → completed/2026-05-31-architectural changes.md } +2 -0
- package/docs/plans/completed/2026-08-02-reliability-improvements.md +1155 -0
- package/docs/test-comparison-dashboard.html +95 -0
- package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +22 -0
- package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +24 -0
- package/docs/validation-reports/2026-08-30-phase-3-coverage.md +73 -0
- package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +82 -0
- package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +114 -0
- package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +79 -0
- package/engine/format-extraction.js +1 -1
- package/engine/formatting-removal.js +95 -104
- package/engine/oxml-engine.js +176 -83
- package/engine/reconstruction-mapper.js +276 -79
- package/engine/reconstruction-mode.js +20 -6
- package/engine/reconstruction-writer.js +117 -72
- package/engine/run-builders.js +17 -13
- package/engine/surgical-diff-application.js +7 -21
- package/engine/surgical-mode.js +3 -2
- package/engine/table-mode.js +27 -16
- package/index.d.ts +95 -3
- package/index.js +14 -13
- package/orchestration/list-structural-fallback.js +16 -39
- package/package.json +23 -5
- package/pipeline/diff-engine.js +174 -55
- package/pipeline/ingestion-export.js +39 -24
- package/pipeline/ingestion-paragraph.js +7 -5
- package/pipeline/list-generation.js +27 -18
- package/pipeline/patching.js +2 -3
- package/pipeline/pipeline.js +65 -36
- package/pipeline/serialization.js +13 -5
- package/scripts/build-test-dashboard.mjs +43 -0
- package/scripts/check-types.mjs +16 -24
- package/scripts/export-validation-fixtures.mjs +191 -45
- package/scripts/fetch-superdoc-corpus.mjs +61 -0
- package/scripts/generate-test-dashboard.mjs +199 -0
- package/scripts/inspect-visual-evidence.mjs +271 -0
- package/scripts/lib/minimal-zip.mjs +199 -18
- package/scripts/lib/word-coverage-catalogue.mjs +207 -0
- package/scripts/lib/word-coverage-metadata.mjs +93 -0
- package/scripts/lib/zip-reader.mjs +64 -0
- package/scripts/package-superdoc-word-fixtures.ps1 +64 -0
- package/scripts/prepare-corpus-word-visual-review.mjs +84 -0
- package/scripts/prepare-superdoc-word-corpus.mjs +284 -0
- package/scripts/prepare-word-review.mjs +77 -0
- package/scripts/prepare-word-visual-review.mjs +90 -0
- package/scripts/render-agenda-multilevel.mjs +70 -0
- package/scripts/render-case22.mjs +73 -0
- package/scripts/render-case40.ps1 +35 -0
- package/scripts/render-multilevel-bullet-images.py +58 -0
- package/scripts/render-multilevel-bullet-visual.ps1 +32 -0
- package/scripts/render-multilevel-cases.mjs +80 -0
- package/scripts/report-coverage-gaps.mjs +103 -0
- package/scripts/report-word-coverage.mjs +71 -0
- package/scripts/sample-multimodal-visual-check.mjs +221 -0
- package/scripts/test-multilevel-bullet-visual.mjs +187 -0
- package/scripts/word-com-corpus-suite.ps1 +43 -0
- package/scripts/word-com-corpus-visual-suite.ps1 +116 -0
- package/scripts/word-com-differential.ps1 +158 -16
- package/scripts/word-com-suite.ps1 +19 -0
- package/scripts/word-com-visual-suite.ps1 +132 -0
- package/services/comment-engine.js +51 -46
- package/services/comment-locator.js +0 -1
- package/services/comment-package.js +11 -10
- package/services/numbering-service.js +1 -1
- package/services/revision-comment-management.js +31 -10
- package/services/standalone-docx-plumbing.js +45 -34
- package/services/standalone-operation-runner.js +315 -75
- package/services/table-reconciliation.js +23 -11
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.
|
|
1
|
+
// @ansonlai/docx-redline-js v0.4.0 — https://github.com/AnsonLai/docx-redline-js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
-
|
|
9
|
+
try {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
} catch (e) {
|
|
12
|
+
throw mod = 0, e;
|
|
13
|
+
}
|
|
10
14
|
};
|
|
11
15
|
var __copyProps = (to, from, except, desc) => {
|
|
12
16
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -28,7 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
32
|
// node_modules/diff-match-patch/index.js
|
|
29
33
|
var require_diff_match_patch = __commonJS({
|
|
30
34
|
"node_modules/diff-match-patch/index.js"(exports, module) {
|
|
31
|
-
var
|
|
35
|
+
var diff_match_patch3 = function() {
|
|
32
36
|
this.Diff_Timeout = 1;
|
|
33
37
|
this.Diff_EditCost = 4;
|
|
34
38
|
this.Match_Threshold = 0.5;
|
|
@@ -40,10 +44,10 @@ var require_diff_match_patch = __commonJS({
|
|
|
40
44
|
var DIFF_DELETE = -1;
|
|
41
45
|
var DIFF_INSERT = 1;
|
|
42
46
|
var DIFF_EQUAL = 0;
|
|
43
|
-
|
|
47
|
+
diff_match_patch3.Diff = function(op, text) {
|
|
44
48
|
return [op, text];
|
|
45
49
|
};
|
|
46
|
-
|
|
50
|
+
diff_match_patch3.prototype.diff_main = function(text1, text2, opt_checklines, opt_deadline) {
|
|
47
51
|
if (typeof opt_deadline == "undefined") {
|
|
48
52
|
if (this.Diff_Timeout <= 0) {
|
|
49
53
|
opt_deadline = Number.MAX_VALUE;
|
|
@@ -57,7 +61,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
57
61
|
}
|
|
58
62
|
if (text1 == text2) {
|
|
59
63
|
if (text1) {
|
|
60
|
-
return [new
|
|
64
|
+
return [new diff_match_patch3.Diff(DIFF_EQUAL, text1)];
|
|
61
65
|
}
|
|
62
66
|
return [];
|
|
63
67
|
}
|
|
@@ -75,30 +79,30 @@ var require_diff_match_patch = __commonJS({
|
|
|
75
79
|
text2 = text2.substring(0, text2.length - commonlength);
|
|
76
80
|
var diffs = this.diff_compute_(text1, text2, checklines, deadline);
|
|
77
81
|
if (commonprefix) {
|
|
78
|
-
diffs.unshift(new
|
|
82
|
+
diffs.unshift(new diff_match_patch3.Diff(DIFF_EQUAL, commonprefix));
|
|
79
83
|
}
|
|
80
84
|
if (commonsuffix) {
|
|
81
|
-
diffs.push(new
|
|
85
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, commonsuffix));
|
|
82
86
|
}
|
|
83
87
|
this.diff_cleanupMerge(diffs);
|
|
84
88
|
return diffs;
|
|
85
89
|
};
|
|
86
|
-
|
|
90
|
+
diff_match_patch3.prototype.diff_compute_ = function(text1, text2, checklines, deadline) {
|
|
87
91
|
var diffs;
|
|
88
92
|
if (!text1) {
|
|
89
|
-
return [new
|
|
93
|
+
return [new diff_match_patch3.Diff(DIFF_INSERT, text2)];
|
|
90
94
|
}
|
|
91
95
|
if (!text2) {
|
|
92
|
-
return [new
|
|
96
|
+
return [new diff_match_patch3.Diff(DIFF_DELETE, text1)];
|
|
93
97
|
}
|
|
94
98
|
var longtext = text1.length > text2.length ? text1 : text2;
|
|
95
99
|
var shorttext = text1.length > text2.length ? text2 : text1;
|
|
96
100
|
var i = longtext.indexOf(shorttext);
|
|
97
101
|
if (i != -1) {
|
|
98
102
|
diffs = [
|
|
99
|
-
new
|
|
100
|
-
new
|
|
101
|
-
new
|
|
103
|
+
new diff_match_patch3.Diff(DIFF_INSERT, longtext.substring(0, i)),
|
|
104
|
+
new diff_match_patch3.Diff(DIFF_EQUAL, shorttext),
|
|
105
|
+
new diff_match_patch3.Diff(
|
|
102
106
|
DIFF_INSERT,
|
|
103
107
|
longtext.substring(i + shorttext.length)
|
|
104
108
|
)
|
|
@@ -110,8 +114,8 @@ var require_diff_match_patch = __commonJS({
|
|
|
110
114
|
}
|
|
111
115
|
if (shorttext.length == 1) {
|
|
112
116
|
return [
|
|
113
|
-
new
|
|
114
|
-
new
|
|
117
|
+
new diff_match_patch3.Diff(DIFF_DELETE, text1),
|
|
118
|
+
new diff_match_patch3.Diff(DIFF_INSERT, text2)
|
|
115
119
|
];
|
|
116
120
|
}
|
|
117
121
|
var hm = this.diff_halfMatch_(text1, text2);
|
|
@@ -124,7 +128,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
124
128
|
var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline);
|
|
125
129
|
var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline);
|
|
126
130
|
return diffs_a.concat(
|
|
127
|
-
[new
|
|
131
|
+
[new diff_match_patch3.Diff(DIFF_EQUAL, mid_common)],
|
|
128
132
|
diffs_b
|
|
129
133
|
);
|
|
130
134
|
}
|
|
@@ -133,7 +137,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
133
137
|
}
|
|
134
138
|
return this.diff_bisect_(text1, text2, deadline);
|
|
135
139
|
};
|
|
136
|
-
|
|
140
|
+
diff_match_patch3.prototype.diff_lineMode_ = function(text1, text2, deadline) {
|
|
137
141
|
var a = this.diff_linesToChars_(text1, text2);
|
|
138
142
|
text1 = a.chars1;
|
|
139
143
|
text2 = a.chars2;
|
|
@@ -141,7 +145,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
141
145
|
var diffs = this.diff_main(text1, text2, false, deadline);
|
|
142
146
|
this.diff_charsToLines_(diffs, linearray);
|
|
143
147
|
this.diff_cleanupSemantic(diffs);
|
|
144
|
-
diffs.push(new
|
|
148
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, ""));
|
|
145
149
|
var pointer = 0;
|
|
146
150
|
var count_delete = 0;
|
|
147
151
|
var count_insert = 0;
|
|
@@ -181,7 +185,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
181
185
|
diffs.pop();
|
|
182
186
|
return diffs;
|
|
183
187
|
};
|
|
184
|
-
|
|
188
|
+
diff_match_patch3.prototype.diff_bisect_ = function(text1, text2, deadline) {
|
|
185
189
|
var text1_length = text1.length;
|
|
186
190
|
var text2_length = text2.length;
|
|
187
191
|
var max_d = Math.ceil((text1_length + text2_length) / 2);
|
|
@@ -265,11 +269,11 @@ var require_diff_match_patch = __commonJS({
|
|
|
265
269
|
}
|
|
266
270
|
}
|
|
267
271
|
return [
|
|
268
|
-
new
|
|
269
|
-
new
|
|
272
|
+
new diff_match_patch3.Diff(DIFF_DELETE, text1),
|
|
273
|
+
new diff_match_patch3.Diff(DIFF_INSERT, text2)
|
|
270
274
|
];
|
|
271
275
|
};
|
|
272
|
-
|
|
276
|
+
diff_match_patch3.prototype.diff_bisectSplit_ = function(text1, text2, x, y, deadline) {
|
|
273
277
|
var text1a = text1.substring(0, x);
|
|
274
278
|
var text2a = text2.substring(0, y);
|
|
275
279
|
var text1b = text1.substring(x);
|
|
@@ -278,7 +282,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
278
282
|
var diffsb = this.diff_main(text1b, text2b, false, deadline);
|
|
279
283
|
return diffs.concat(diffsb);
|
|
280
284
|
};
|
|
281
|
-
|
|
285
|
+
diff_match_patch3.prototype.diff_linesToChars_ = function(text1, text2) {
|
|
282
286
|
var lineArray = [];
|
|
283
287
|
var lineHash = {};
|
|
284
288
|
lineArray[0] = "";
|
|
@@ -314,7 +318,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
314
318
|
var chars2 = diff_linesToCharsMunge_(text2);
|
|
315
319
|
return { chars1, chars2, lineArray };
|
|
316
320
|
};
|
|
317
|
-
|
|
321
|
+
diff_match_patch3.prototype.diff_charsToLines_ = function(diffs, lineArray) {
|
|
318
322
|
for (var i = 0; i < diffs.length; i++) {
|
|
319
323
|
var chars = diffs[i][1];
|
|
320
324
|
var text = [];
|
|
@@ -324,7 +328,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
324
328
|
diffs[i][1] = text.join("");
|
|
325
329
|
}
|
|
326
330
|
};
|
|
327
|
-
|
|
331
|
+
diff_match_patch3.prototype.diff_commonPrefix = function(text1, text2) {
|
|
328
332
|
if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {
|
|
329
333
|
return 0;
|
|
330
334
|
}
|
|
@@ -343,7 +347,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
343
347
|
}
|
|
344
348
|
return pointermid;
|
|
345
349
|
};
|
|
346
|
-
|
|
350
|
+
diff_match_patch3.prototype.diff_commonSuffix = function(text1, text2) {
|
|
347
351
|
if (!text1 || !text2 || text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) {
|
|
348
352
|
return 0;
|
|
349
353
|
}
|
|
@@ -362,7 +366,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
362
366
|
}
|
|
363
367
|
return pointermid;
|
|
364
368
|
};
|
|
365
|
-
|
|
369
|
+
diff_match_patch3.prototype.diff_commonOverlap_ = function(text1, text2) {
|
|
366
370
|
var text1_length = text1.length;
|
|
367
371
|
var text2_length = text2.length;
|
|
368
372
|
if (text1_length == 0 || text2_length == 0) {
|
|
@@ -392,7 +396,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
392
396
|
}
|
|
393
397
|
}
|
|
394
398
|
};
|
|
395
|
-
|
|
399
|
+
diff_match_patch3.prototype.diff_halfMatch_ = function(text1, text2) {
|
|
396
400
|
if (this.Diff_Timeout <= 0) {
|
|
397
401
|
return null;
|
|
398
402
|
}
|
|
@@ -471,7 +475,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
471
475
|
var mid_common = hm[4];
|
|
472
476
|
return [text1_a, text1_b, text2_a, text2_b, mid_common];
|
|
473
477
|
};
|
|
474
|
-
|
|
478
|
+
diff_match_patch3.prototype.diff_cleanupSemantic = function(diffs) {
|
|
475
479
|
var changes = false;
|
|
476
480
|
var equalities = [];
|
|
477
481
|
var equalitiesLength = 0;
|
|
@@ -502,7 +506,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
502
506
|
diffs.splice(
|
|
503
507
|
equalities[equalitiesLength - 1],
|
|
504
508
|
0,
|
|
505
|
-
new
|
|
509
|
+
new diff_match_patch3.Diff(DIFF_DELETE, lastEquality)
|
|
506
510
|
);
|
|
507
511
|
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
|
|
508
512
|
equalitiesLength--;
|
|
@@ -531,7 +535,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
531
535
|
var overlap_length2 = this.diff_commonOverlap_(insertion, deletion);
|
|
532
536
|
if (overlap_length1 >= overlap_length2) {
|
|
533
537
|
if (overlap_length1 >= deletion.length / 2 || overlap_length1 >= insertion.length / 2) {
|
|
534
|
-
diffs.splice(pointer, 0, new
|
|
538
|
+
diffs.splice(pointer, 0, new diff_match_patch3.Diff(
|
|
535
539
|
DIFF_EQUAL,
|
|
536
540
|
insertion.substring(0, overlap_length1)
|
|
537
541
|
));
|
|
@@ -541,7 +545,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
541
545
|
}
|
|
542
546
|
} else {
|
|
543
547
|
if (overlap_length2 >= deletion.length / 2 || overlap_length2 >= insertion.length / 2) {
|
|
544
|
-
diffs.splice(pointer, 0, new
|
|
548
|
+
diffs.splice(pointer, 0, new diff_match_patch3.Diff(
|
|
545
549
|
DIFF_EQUAL,
|
|
546
550
|
deletion.substring(0, overlap_length2)
|
|
547
551
|
));
|
|
@@ -557,21 +561,21 @@ var require_diff_match_patch = __commonJS({
|
|
|
557
561
|
pointer++;
|
|
558
562
|
}
|
|
559
563
|
};
|
|
560
|
-
|
|
564
|
+
diff_match_patch3.prototype.diff_cleanupSemanticLossless = function(diffs) {
|
|
561
565
|
function diff_cleanupSemanticScore_(one, two) {
|
|
562
566
|
if (!one || !two) {
|
|
563
567
|
return 6;
|
|
564
568
|
}
|
|
565
569
|
var char1 = one.charAt(one.length - 1);
|
|
566
570
|
var char2 = two.charAt(0);
|
|
567
|
-
var nonAlphaNumeric1 = char1.match(
|
|
568
|
-
var nonAlphaNumeric2 = char2.match(
|
|
569
|
-
var whitespace1 = nonAlphaNumeric1 && char1.match(
|
|
570
|
-
var whitespace2 = nonAlphaNumeric2 && char2.match(
|
|
571
|
-
var lineBreak1 = whitespace1 && char1.match(
|
|
572
|
-
var lineBreak2 = whitespace2 && char2.match(
|
|
573
|
-
var blankLine1 = lineBreak1 && one.match(
|
|
574
|
-
var blankLine2 = lineBreak2 && two.match(
|
|
571
|
+
var nonAlphaNumeric1 = char1.match(diff_match_patch3.nonAlphaNumericRegex_);
|
|
572
|
+
var nonAlphaNumeric2 = char2.match(diff_match_patch3.nonAlphaNumericRegex_);
|
|
573
|
+
var whitespace1 = nonAlphaNumeric1 && char1.match(diff_match_patch3.whitespaceRegex_);
|
|
574
|
+
var whitespace2 = nonAlphaNumeric2 && char2.match(diff_match_patch3.whitespaceRegex_);
|
|
575
|
+
var lineBreak1 = whitespace1 && char1.match(diff_match_patch3.linebreakRegex_);
|
|
576
|
+
var lineBreak2 = whitespace2 && char2.match(diff_match_patch3.linebreakRegex_);
|
|
577
|
+
var blankLine1 = lineBreak1 && one.match(diff_match_patch3.blanklineEndRegex_);
|
|
578
|
+
var blankLine2 = lineBreak2 && two.match(diff_match_patch3.blanklineStartRegex_);
|
|
575
579
|
if (blankLine1 || blankLine2) {
|
|
576
580
|
return 5;
|
|
577
581
|
} else if (lineBreak1 || lineBreak2) {
|
|
@@ -633,12 +637,12 @@ var require_diff_match_patch = __commonJS({
|
|
|
633
637
|
pointer++;
|
|
634
638
|
}
|
|
635
639
|
};
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
640
|
+
diff_match_patch3.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/;
|
|
641
|
+
diff_match_patch3.whitespaceRegex_ = /\s/;
|
|
642
|
+
diff_match_patch3.linebreakRegex_ = /[\r\n]/;
|
|
643
|
+
diff_match_patch3.blanklineEndRegex_ = /\n\r?\n$/;
|
|
644
|
+
diff_match_patch3.blanklineStartRegex_ = /^\r?\n\r?\n/;
|
|
645
|
+
diff_match_patch3.prototype.diff_cleanupEfficiency = function(diffs) {
|
|
642
646
|
var changes = false;
|
|
643
647
|
var equalities = [];
|
|
644
648
|
var equalitiesLength = 0;
|
|
@@ -670,7 +674,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
670
674
|
diffs.splice(
|
|
671
675
|
equalities[equalitiesLength - 1],
|
|
672
676
|
0,
|
|
673
|
-
new
|
|
677
|
+
new diff_match_patch3.Diff(DIFF_DELETE, lastEquality)
|
|
674
678
|
);
|
|
675
679
|
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
|
|
676
680
|
equalitiesLength--;
|
|
@@ -692,8 +696,8 @@ var require_diff_match_patch = __commonJS({
|
|
|
692
696
|
this.diff_cleanupMerge(diffs);
|
|
693
697
|
}
|
|
694
698
|
};
|
|
695
|
-
|
|
696
|
-
diffs.push(new
|
|
699
|
+
diff_match_patch3.prototype.diff_cleanupMerge = function(diffs) {
|
|
700
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, ""));
|
|
697
701
|
var pointer = 0;
|
|
698
702
|
var count_delete = 0;
|
|
699
703
|
var count_insert = 0;
|
|
@@ -720,7 +724,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
720
724
|
if (pointer - count_delete - count_insert > 0 && diffs[pointer - count_delete - count_insert - 1][0] == DIFF_EQUAL) {
|
|
721
725
|
diffs[pointer - count_delete - count_insert - 1][1] += text_insert.substring(0, commonlength);
|
|
722
726
|
} else {
|
|
723
|
-
diffs.splice(0, 0, new
|
|
727
|
+
diffs.splice(0, 0, new diff_match_patch3.Diff(
|
|
724
728
|
DIFF_EQUAL,
|
|
725
729
|
text_insert.substring(0, commonlength)
|
|
726
730
|
));
|
|
@@ -742,7 +746,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
742
746
|
diffs.splice(
|
|
743
747
|
pointer,
|
|
744
748
|
0,
|
|
745
|
-
new
|
|
749
|
+
new diff_match_patch3.Diff(DIFF_DELETE, text_delete)
|
|
746
750
|
);
|
|
747
751
|
pointer++;
|
|
748
752
|
}
|
|
@@ -750,7 +754,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
750
754
|
diffs.splice(
|
|
751
755
|
pointer,
|
|
752
756
|
0,
|
|
753
|
-
new
|
|
757
|
+
new diff_match_patch3.Diff(DIFF_INSERT, text_insert)
|
|
754
758
|
);
|
|
755
759
|
pointer++;
|
|
756
760
|
}
|
|
@@ -793,7 +797,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
793
797
|
this.diff_cleanupMerge(diffs);
|
|
794
798
|
}
|
|
795
799
|
};
|
|
796
|
-
|
|
800
|
+
diff_match_patch3.prototype.diff_xIndex = function(diffs, loc) {
|
|
797
801
|
var chars1 = 0;
|
|
798
802
|
var chars2 = 0;
|
|
799
803
|
var last_chars1 = 0;
|
|
@@ -817,7 +821,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
817
821
|
}
|
|
818
822
|
return last_chars2 + (loc - last_chars1);
|
|
819
823
|
};
|
|
820
|
-
|
|
824
|
+
diff_match_patch3.prototype.diff_prettyHtml = function(diffs) {
|
|
821
825
|
var html = [];
|
|
822
826
|
var pattern_amp = /&/g;
|
|
823
827
|
var pattern_lt = /</g;
|
|
@@ -841,7 +845,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
841
845
|
}
|
|
842
846
|
return html.join("");
|
|
843
847
|
};
|
|
844
|
-
|
|
848
|
+
diff_match_patch3.prototype.diff_text1 = function(diffs) {
|
|
845
849
|
var text = [];
|
|
846
850
|
for (var x = 0; x < diffs.length; x++) {
|
|
847
851
|
if (diffs[x][0] !== DIFF_INSERT) {
|
|
@@ -850,7 +854,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
850
854
|
}
|
|
851
855
|
return text.join("");
|
|
852
856
|
};
|
|
853
|
-
|
|
857
|
+
diff_match_patch3.prototype.diff_text2 = function(diffs) {
|
|
854
858
|
var text = [];
|
|
855
859
|
for (var x = 0; x < diffs.length; x++) {
|
|
856
860
|
if (diffs[x][0] !== DIFF_DELETE) {
|
|
@@ -859,7 +863,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
859
863
|
}
|
|
860
864
|
return text.join("");
|
|
861
865
|
};
|
|
862
|
-
|
|
866
|
+
diff_match_patch3.prototype.diff_levenshtein = function(diffs) {
|
|
863
867
|
var levenshtein = 0;
|
|
864
868
|
var insertions = 0;
|
|
865
869
|
var deletions = 0;
|
|
@@ -883,7 +887,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
883
887
|
levenshtein += Math.max(insertions, deletions);
|
|
884
888
|
return levenshtein;
|
|
885
889
|
};
|
|
886
|
-
|
|
890
|
+
diff_match_patch3.prototype.diff_toDelta = function(diffs) {
|
|
887
891
|
var text = [];
|
|
888
892
|
for (var x = 0; x < diffs.length; x++) {
|
|
889
893
|
switch (diffs[x][0]) {
|
|
@@ -900,7 +904,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
900
904
|
}
|
|
901
905
|
return text.join(" ").replace(/%20/g, " ");
|
|
902
906
|
};
|
|
903
|
-
|
|
907
|
+
diff_match_patch3.prototype.diff_fromDelta = function(text1, delta) {
|
|
904
908
|
var diffs = [];
|
|
905
909
|
var diffsLength = 0;
|
|
906
910
|
var pointer = 0;
|
|
@@ -910,7 +914,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
910
914
|
switch (tokens[x].charAt(0)) {
|
|
911
915
|
case "+":
|
|
912
916
|
try {
|
|
913
|
-
diffs[diffsLength++] = new
|
|
917
|
+
diffs[diffsLength++] = new diff_match_patch3.Diff(DIFF_INSERT, decodeURI(param));
|
|
914
918
|
} catch (ex) {
|
|
915
919
|
throw new Error("Illegal escape in diff_fromDelta: " + param);
|
|
916
920
|
}
|
|
@@ -924,9 +928,9 @@ var require_diff_match_patch = __commonJS({
|
|
|
924
928
|
}
|
|
925
929
|
var text = text1.substring(pointer, pointer += n);
|
|
926
930
|
if (tokens[x].charAt(0) == "=") {
|
|
927
|
-
diffs[diffsLength++] = new
|
|
931
|
+
diffs[diffsLength++] = new diff_match_patch3.Diff(DIFF_EQUAL, text);
|
|
928
932
|
} else {
|
|
929
|
-
diffs[diffsLength++] = new
|
|
933
|
+
diffs[diffsLength++] = new diff_match_patch3.Diff(DIFF_DELETE, text);
|
|
930
934
|
}
|
|
931
935
|
break;
|
|
932
936
|
default:
|
|
@@ -940,7 +944,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
940
944
|
}
|
|
941
945
|
return diffs;
|
|
942
946
|
};
|
|
943
|
-
|
|
947
|
+
diff_match_patch3.prototype.match_main = function(text, pattern, loc) {
|
|
944
948
|
if (text == null || pattern == null || loc == null) {
|
|
945
949
|
throw new Error("Null input. (match_main)");
|
|
946
950
|
}
|
|
@@ -955,7 +959,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
955
959
|
return this.match_bitap_(text, pattern, loc);
|
|
956
960
|
}
|
|
957
961
|
};
|
|
958
|
-
|
|
962
|
+
diff_match_patch3.prototype.match_bitap_ = function(text, pattern, loc) {
|
|
959
963
|
if (pattern.length > this.Match_MaxBits) {
|
|
960
964
|
throw new Error("Pattern too long for this browser.");
|
|
961
965
|
}
|
|
@@ -1026,7 +1030,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1026
1030
|
}
|
|
1027
1031
|
return best_loc;
|
|
1028
1032
|
};
|
|
1029
|
-
|
|
1033
|
+
diff_match_patch3.prototype.match_alphabet_ = function(pattern) {
|
|
1030
1034
|
var s = {};
|
|
1031
1035
|
for (var i = 0; i < pattern.length; i++) {
|
|
1032
1036
|
s[pattern.charAt(i)] = 0;
|
|
@@ -1036,7 +1040,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1036
1040
|
}
|
|
1037
1041
|
return s;
|
|
1038
1042
|
};
|
|
1039
|
-
|
|
1043
|
+
diff_match_patch3.prototype.patch_addContext_ = function(patch, text) {
|
|
1040
1044
|
if (text.length == 0) {
|
|
1041
1045
|
return;
|
|
1042
1046
|
}
|
|
@@ -1055,21 +1059,21 @@ var require_diff_match_patch = __commonJS({
|
|
|
1055
1059
|
padding += this.Patch_Margin;
|
|
1056
1060
|
var prefix = text.substring(patch.start2 - padding, patch.start2);
|
|
1057
1061
|
if (prefix) {
|
|
1058
|
-
patch.diffs.unshift(new
|
|
1062
|
+
patch.diffs.unshift(new diff_match_patch3.Diff(DIFF_EQUAL, prefix));
|
|
1059
1063
|
}
|
|
1060
1064
|
var suffix = text.substring(
|
|
1061
1065
|
patch.start2 + patch.length1,
|
|
1062
1066
|
patch.start2 + patch.length1 + padding
|
|
1063
1067
|
);
|
|
1064
1068
|
if (suffix) {
|
|
1065
|
-
patch.diffs.push(new
|
|
1069
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, suffix));
|
|
1066
1070
|
}
|
|
1067
1071
|
patch.start1 -= prefix.length;
|
|
1068
1072
|
patch.start2 -= prefix.length;
|
|
1069
1073
|
patch.length1 += prefix.length + suffix.length;
|
|
1070
1074
|
patch.length2 += prefix.length + suffix.length;
|
|
1071
1075
|
};
|
|
1072
|
-
|
|
1076
|
+
diff_match_patch3.prototype.patch_make = function(a, opt_b, opt_c) {
|
|
1073
1077
|
var text1, diffs;
|
|
1074
1078
|
if (typeof a == "string" && typeof opt_b == "string" && typeof opt_c == "undefined") {
|
|
1075
1079
|
text1 = /** @type {string} */
|
|
@@ -1105,7 +1109,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1105
1109
|
return [];
|
|
1106
1110
|
}
|
|
1107
1111
|
var patches = [];
|
|
1108
|
-
var patch = new
|
|
1112
|
+
var patch = new diff_match_patch3.patch_obj();
|
|
1109
1113
|
var patchDiffLength = 0;
|
|
1110
1114
|
var char_count1 = 0;
|
|
1111
1115
|
var char_count2 = 0;
|
|
@@ -1138,7 +1142,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1138
1142
|
if (patchDiffLength) {
|
|
1139
1143
|
this.patch_addContext_(patch, prepatch_text);
|
|
1140
1144
|
patches.push(patch);
|
|
1141
|
-
patch = new
|
|
1145
|
+
patch = new diff_match_patch3.patch_obj();
|
|
1142
1146
|
patchDiffLength = 0;
|
|
1143
1147
|
prepatch_text = postpatch_text;
|
|
1144
1148
|
char_count1 = char_count2;
|
|
@@ -1159,14 +1163,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1159
1163
|
}
|
|
1160
1164
|
return patches;
|
|
1161
1165
|
};
|
|
1162
|
-
|
|
1166
|
+
diff_match_patch3.prototype.patch_deepCopy = function(patches) {
|
|
1163
1167
|
var patchesCopy = [];
|
|
1164
1168
|
for (var x = 0; x < patches.length; x++) {
|
|
1165
1169
|
var patch = patches[x];
|
|
1166
|
-
var patchCopy = new
|
|
1170
|
+
var patchCopy = new diff_match_patch3.patch_obj();
|
|
1167
1171
|
patchCopy.diffs = [];
|
|
1168
1172
|
for (var y = 0; y < patch.diffs.length; y++) {
|
|
1169
|
-
patchCopy.diffs[y] = new
|
|
1173
|
+
patchCopy.diffs[y] = new diff_match_patch3.Diff(patch.diffs[y][0], patch.diffs[y][1]);
|
|
1170
1174
|
}
|
|
1171
1175
|
patchCopy.start1 = patch.start1;
|
|
1172
1176
|
patchCopy.start2 = patch.start2;
|
|
@@ -1176,7 +1180,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1176
1180
|
}
|
|
1177
1181
|
return patchesCopy;
|
|
1178
1182
|
};
|
|
1179
|
-
|
|
1183
|
+
diff_match_patch3.prototype.patch_apply = function(patches, text) {
|
|
1180
1184
|
if (patches.length == 0) {
|
|
1181
1185
|
return [text, []];
|
|
1182
1186
|
}
|
|
@@ -1256,7 +1260,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1256
1260
|
text = text.substring(nullPadding.length, text.length - nullPadding.length);
|
|
1257
1261
|
return [text, results];
|
|
1258
1262
|
};
|
|
1259
|
-
|
|
1263
|
+
diff_match_patch3.prototype.patch_addPadding = function(patches) {
|
|
1260
1264
|
var paddingLength = this.Patch_Margin;
|
|
1261
1265
|
var nullPadding = "";
|
|
1262
1266
|
for (var x = 1; x <= paddingLength; x++) {
|
|
@@ -1269,7 +1273,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1269
1273
|
var patch = patches[0];
|
|
1270
1274
|
var diffs = patch.diffs;
|
|
1271
1275
|
if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) {
|
|
1272
|
-
diffs.unshift(new
|
|
1276
|
+
diffs.unshift(new diff_match_patch3.Diff(DIFF_EQUAL, nullPadding));
|
|
1273
1277
|
patch.start1 -= paddingLength;
|
|
1274
1278
|
patch.start2 -= paddingLength;
|
|
1275
1279
|
patch.length1 += paddingLength;
|
|
@@ -1285,7 +1289,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1285
1289
|
patch = patches[patches.length - 1];
|
|
1286
1290
|
diffs = patch.diffs;
|
|
1287
1291
|
if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) {
|
|
1288
|
-
diffs.push(new
|
|
1292
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, nullPadding));
|
|
1289
1293
|
patch.length1 += paddingLength;
|
|
1290
1294
|
patch.length2 += paddingLength;
|
|
1291
1295
|
} else if (paddingLength > diffs[diffs.length - 1][1].length) {
|
|
@@ -1296,7 +1300,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1296
1300
|
}
|
|
1297
1301
|
return nullPadding;
|
|
1298
1302
|
};
|
|
1299
|
-
|
|
1303
|
+
diff_match_patch3.prototype.patch_splitMax = function(patches) {
|
|
1300
1304
|
var patch_size = this.Match_MaxBits;
|
|
1301
1305
|
for (var x = 0; x < patches.length; x++) {
|
|
1302
1306
|
if (patches[x].length1 <= patch_size) {
|
|
@@ -1308,13 +1312,13 @@ var require_diff_match_patch = __commonJS({
|
|
|
1308
1312
|
var start2 = bigpatch.start2;
|
|
1309
1313
|
var precontext = "";
|
|
1310
1314
|
while (bigpatch.diffs.length !== 0) {
|
|
1311
|
-
var patch = new
|
|
1315
|
+
var patch = new diff_match_patch3.patch_obj();
|
|
1312
1316
|
var empty = true;
|
|
1313
1317
|
patch.start1 = start1 - precontext.length;
|
|
1314
1318
|
patch.start2 = start2 - precontext.length;
|
|
1315
1319
|
if (precontext !== "") {
|
|
1316
1320
|
patch.length1 = patch.length2 = precontext.length;
|
|
1317
|
-
patch.diffs.push(new
|
|
1321
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, precontext));
|
|
1318
1322
|
}
|
|
1319
1323
|
while (bigpatch.diffs.length !== 0 && patch.length1 < patch_size - this.Patch_Margin) {
|
|
1320
1324
|
var diff_type = bigpatch.diffs[0][0];
|
|
@@ -1328,7 +1332,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1328
1332
|
patch.length1 += diff_text.length;
|
|
1329
1333
|
start1 += diff_text.length;
|
|
1330
1334
|
empty = false;
|
|
1331
|
-
patch.diffs.push(new
|
|
1335
|
+
patch.diffs.push(new diff_match_patch3.Diff(diff_type, diff_text));
|
|
1332
1336
|
bigpatch.diffs.shift();
|
|
1333
1337
|
} else {
|
|
1334
1338
|
diff_text = diff_text.substring(
|
|
@@ -1343,7 +1347,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1343
1347
|
} else {
|
|
1344
1348
|
empty = false;
|
|
1345
1349
|
}
|
|
1346
|
-
patch.diffs.push(new
|
|
1350
|
+
patch.diffs.push(new diff_match_patch3.Diff(diff_type, diff_text));
|
|
1347
1351
|
if (diff_text == bigpatch.diffs[0][1]) {
|
|
1348
1352
|
bigpatch.diffs.shift();
|
|
1349
1353
|
} else {
|
|
@@ -1360,7 +1364,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1360
1364
|
if (patch.diffs.length !== 0 && patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) {
|
|
1361
1365
|
patch.diffs[patch.diffs.length - 1][1] += postcontext;
|
|
1362
1366
|
} else {
|
|
1363
|
-
patch.diffs.push(new
|
|
1367
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, postcontext));
|
|
1364
1368
|
}
|
|
1365
1369
|
}
|
|
1366
1370
|
if (!empty) {
|
|
@@ -1369,14 +1373,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1369
1373
|
}
|
|
1370
1374
|
}
|
|
1371
1375
|
};
|
|
1372
|
-
|
|
1376
|
+
diff_match_patch3.prototype.patch_toText = function(patches) {
|
|
1373
1377
|
var text = [];
|
|
1374
1378
|
for (var x = 0; x < patches.length; x++) {
|
|
1375
1379
|
text[x] = patches[x];
|
|
1376
1380
|
}
|
|
1377
1381
|
return text.join("");
|
|
1378
1382
|
};
|
|
1379
|
-
|
|
1383
|
+
diff_match_patch3.prototype.patch_fromText = function(textline) {
|
|
1380
1384
|
var patches = [];
|
|
1381
1385
|
if (!textline) {
|
|
1382
1386
|
return patches;
|
|
@@ -1389,7 +1393,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1389
1393
|
if (!m) {
|
|
1390
1394
|
throw new Error("Invalid patch string: " + text[textPointer]);
|
|
1391
1395
|
}
|
|
1392
|
-
var patch = new
|
|
1396
|
+
var patch = new diff_match_patch3.patch_obj();
|
|
1393
1397
|
patches.push(patch);
|
|
1394
1398
|
patch.start1 = parseInt(m[1], 10);
|
|
1395
1399
|
if (m[2] === "") {
|
|
@@ -1420,11 +1424,11 @@ var require_diff_match_patch = __commonJS({
|
|
|
1420
1424
|
throw new Error("Illegal escape in patch_fromText: " + line);
|
|
1421
1425
|
}
|
|
1422
1426
|
if (sign == "-") {
|
|
1423
|
-
patch.diffs.push(new
|
|
1427
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_DELETE, line));
|
|
1424
1428
|
} else if (sign == "+") {
|
|
1425
|
-
patch.diffs.push(new
|
|
1429
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_INSERT, line));
|
|
1426
1430
|
} else if (sign == " ") {
|
|
1427
|
-
patch.diffs.push(new
|
|
1431
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, line));
|
|
1428
1432
|
} else if (sign == "@") {
|
|
1429
1433
|
break;
|
|
1430
1434
|
} else if (sign === "") {
|
|
@@ -1436,14 +1440,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1436
1440
|
}
|
|
1437
1441
|
return patches;
|
|
1438
1442
|
};
|
|
1439
|
-
|
|
1443
|
+
diff_match_patch3.patch_obj = function() {
|
|
1440
1444
|
this.diffs = [];
|
|
1441
1445
|
this.start1 = null;
|
|
1442
1446
|
this.start2 = null;
|
|
1443
1447
|
this.length1 = 0;
|
|
1444
1448
|
this.length2 = 0;
|
|
1445
1449
|
};
|
|
1446
|
-
|
|
1450
|
+
diff_match_patch3.patch_obj.prototype.toString = function() {
|
|
1447
1451
|
var coords1, coords2;
|
|
1448
1452
|
if (this.length1 === 0) {
|
|
1449
1453
|
coords1 = this.start1 + ",0";
|
|
@@ -1477,48 +1481,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1477
1481
|
}
|
|
1478
1482
|
return text.join("").replace(/%20/g, " ");
|
|
1479
1483
|
};
|
|
1480
|
-
module.exports =
|
|
1481
|
-
module.exports["diff_match_patch"] =
|
|
1484
|
+
module.exports = diff_match_patch3;
|
|
1485
|
+
module.exports["diff_match_patch"] = diff_match_patch3;
|
|
1482
1486
|
module.exports["DIFF_DELETE"] = DIFF_DELETE;
|
|
1483
1487
|
module.exports["DIFF_INSERT"] = DIFF_INSERT;
|
|
1484
1488
|
module.exports["DIFF_EQUAL"] = DIFF_EQUAL;
|
|
1485
1489
|
}
|
|
1486
1490
|
});
|
|
1487
1491
|
|
|
1488
|
-
// adapters/xml-adapter.js
|
|
1489
|
-
var _DOMParser = globalThis.DOMParser;
|
|
1490
|
-
var _XMLSerializer = globalThis.XMLSerializer;
|
|
1491
|
-
function configureXmlProvider(options = {}) {
|
|
1492
|
-
if (options.DOMParser) _DOMParser = options.DOMParser;
|
|
1493
|
-
if (options.XMLSerializer) _XMLSerializer = options.XMLSerializer;
|
|
1494
|
-
}
|
|
1495
|
-
function createParser() {
|
|
1496
|
-
if (!_DOMParser && globalThis.DOMParser) {
|
|
1497
|
-
_DOMParser = globalThis.DOMParser;
|
|
1498
|
-
}
|
|
1499
|
-
if (!_DOMParser) {
|
|
1500
|
-
throw new Error("DOMParser is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.");
|
|
1501
|
-
}
|
|
1502
|
-
return new _DOMParser();
|
|
1503
|
-
}
|
|
1504
|
-
function createSerializer() {
|
|
1505
|
-
if (!_XMLSerializer && globalThis.XMLSerializer) {
|
|
1506
|
-
_XMLSerializer = globalThis.XMLSerializer;
|
|
1507
|
-
}
|
|
1508
|
-
if (!_XMLSerializer) {
|
|
1509
|
-
throw new Error("XMLSerializer is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.");
|
|
1510
|
-
}
|
|
1511
|
-
return new _XMLSerializer();
|
|
1512
|
-
}
|
|
1513
|
-
function parseXml(xmlString, contentType = "text/xml") {
|
|
1514
|
-
const parser = createParser();
|
|
1515
|
-
return parser.parseFromString(xmlString, contentType);
|
|
1516
|
-
}
|
|
1517
|
-
function serializeXml(node) {
|
|
1518
|
-
const serializer = createSerializer();
|
|
1519
|
-
return serializer.serializeToString(node);
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
1492
|
// adapters/logger.js
|
|
1523
1493
|
var _logger = console;
|
|
1524
1494
|
var LEVELS = Object.freeze({
|
|
@@ -1561,6 +1531,86 @@ function error(...args) {
|
|
|
1561
1531
|
}))(...args);
|
|
1562
1532
|
}
|
|
1563
1533
|
|
|
1534
|
+
// adapters/xml-adapter.js
|
|
1535
|
+
var _DOMParser = globalThis.DOMParser;
|
|
1536
|
+
var _XMLSerializer = globalThis.XMLSerializer;
|
|
1537
|
+
function configureXmlProvider(options = {}) {
|
|
1538
|
+
if (options.DOMParser) _DOMParser = options.DOMParser;
|
|
1539
|
+
if (options.XMLSerializer) _XMLSerializer = options.XMLSerializer;
|
|
1540
|
+
}
|
|
1541
|
+
function createParser(options = {}) {
|
|
1542
|
+
if (!_DOMParser && globalThis.DOMParser) {
|
|
1543
|
+
_DOMParser = globalThis.DOMParser;
|
|
1544
|
+
}
|
|
1545
|
+
if (!_DOMParser) {
|
|
1546
|
+
throw new Error("DOMParser is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.");
|
|
1547
|
+
}
|
|
1548
|
+
return new _DOMParser(options);
|
|
1549
|
+
}
|
|
1550
|
+
function createSerializer() {
|
|
1551
|
+
if (!_XMLSerializer && globalThis.XMLSerializer) {
|
|
1552
|
+
_XMLSerializer = globalThis.XMLSerializer;
|
|
1553
|
+
}
|
|
1554
|
+
if (!_XMLSerializer) {
|
|
1555
|
+
throw new Error("XMLSerializer is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.");
|
|
1556
|
+
}
|
|
1557
|
+
return new _XMLSerializer();
|
|
1558
|
+
}
|
|
1559
|
+
function parseXml(xmlString, contentType = "text/xml") {
|
|
1560
|
+
const result = parseOoxmlSafe(xmlString, contentType);
|
|
1561
|
+
if (result.error) {
|
|
1562
|
+
const parseError = new Error(result.error.message);
|
|
1563
|
+
parseError.code = result.error.code;
|
|
1564
|
+
throw parseError;
|
|
1565
|
+
}
|
|
1566
|
+
return result.doc;
|
|
1567
|
+
}
|
|
1568
|
+
function browserParseError(doc) {
|
|
1569
|
+
if (!doc?.documentElement) return null;
|
|
1570
|
+
if (String(doc.documentElement.localName || doc.documentElement.nodeName).toLowerCase() === "parsererror") {
|
|
1571
|
+
return doc.documentElement;
|
|
1572
|
+
}
|
|
1573
|
+
return doc.getElementsByTagName?.("parsererror")?.[0] || null;
|
|
1574
|
+
}
|
|
1575
|
+
function parseOoxmlSafe(xmlString, contentType = "application/xml") {
|
|
1576
|
+
const warnings = [];
|
|
1577
|
+
if (typeof xmlString !== "string" || xmlString.trim() === "") {
|
|
1578
|
+
return {
|
|
1579
|
+
doc: null,
|
|
1580
|
+
error: { code: "PARSE_ERROR", message: "Input is not a non-empty XML string." },
|
|
1581
|
+
warnings
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
const onError = (level, message) => {
|
|
1585
|
+
const diagnostic = String(message || "XML parser diagnostic.");
|
|
1586
|
+
if (level === "fatalError") {
|
|
1587
|
+
error("[XmlAdapter] XML fatal parse error:", diagnostic);
|
|
1588
|
+
} else {
|
|
1589
|
+
warnings.push(diagnostic);
|
|
1590
|
+
warn(`[XmlAdapter] XML ${level || "warning"}:`, diagnostic);
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
try {
|
|
1594
|
+
const parser = createParser({ onError });
|
|
1595
|
+
const doc = parser.parseFromString(xmlString, contentType);
|
|
1596
|
+
const parseError = browserParseError(doc);
|
|
1597
|
+
if (!doc?.documentElement || parseError) {
|
|
1598
|
+
const message = parseError?.textContent || "Could not parse XML input.";
|
|
1599
|
+
error("[XmlAdapter] XML parse error:", message);
|
|
1600
|
+
return { doc: null, error: { code: "PARSE_ERROR", message }, warnings };
|
|
1601
|
+
}
|
|
1602
|
+
return { doc, error: null, warnings };
|
|
1603
|
+
} catch (caught) {
|
|
1604
|
+
const message = caught?.message || String(caught || "Could not parse XML input.");
|
|
1605
|
+
error("[XmlAdapter] XML parse error:", message);
|
|
1606
|
+
return { doc: null, error: { code: "PARSE_ERROR", message }, warnings };
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
function serializeXml(node) {
|
|
1610
|
+
const serializer = createSerializer();
|
|
1611
|
+
return serializer.serializeToString(node);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1564
1614
|
// adapters/config.js
|
|
1565
1615
|
var _defaultAuthor = "Author";
|
|
1566
1616
|
var _platform = "Unknown";
|
|
@@ -1782,37 +1832,108 @@ function escapeXml(str) {
|
|
|
1782
1832
|
if (!str) return "";
|
|
1783
1833
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1784
1834
|
}
|
|
1785
|
-
var
|
|
1835
|
+
var DEFAULT_REVISION_ID_START = 1e3;
|
|
1836
|
+
var MAX_PRACTICAL_REVISION_ID = 2147483647;
|
|
1837
|
+
var REVISION_ID_SAFETY_MARGIN = 1e4;
|
|
1838
|
+
var REVISION_ELEMENT_NAMES = /* @__PURE__ */ new Set([
|
|
1839
|
+
"ins",
|
|
1840
|
+
"del",
|
|
1841
|
+
"moveFrom",
|
|
1842
|
+
"moveTo",
|
|
1843
|
+
"rPrChange",
|
|
1844
|
+
"pPrChange",
|
|
1845
|
+
"cellIns",
|
|
1846
|
+
"cellDel",
|
|
1847
|
+
"comment"
|
|
1848
|
+
]);
|
|
1849
|
+
var revisionAllocatorByDocument = /* @__PURE__ */ new WeakMap();
|
|
1850
|
+
function isRevisionIdElement(element) {
|
|
1851
|
+
if (!element || element.nodeType !== 1) return false;
|
|
1852
|
+
const localName = String(element.localName || element.nodeName || "").replace(/^.*:/, "");
|
|
1853
|
+
if (!REVISION_ELEMENT_NAMES.has(localName)) return false;
|
|
1854
|
+
return !element.namespaceURI || element.namespaceURI === NS_W || String(element.nodeName || "").startsWith("w:");
|
|
1855
|
+
}
|
|
1856
|
+
function readWordId(element) {
|
|
1857
|
+
const raw = element?.getAttributeNS?.(NS_W, "id") || element?.getAttribute?.("w:id") || element?.getAttribute?.("id");
|
|
1858
|
+
const parsed = Number.parseInt(String(raw ?? ""), 10);
|
|
1859
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : null;
|
|
1860
|
+
}
|
|
1861
|
+
var RevisionIdAllocator = class {
|
|
1862
|
+
constructor(startValue = DEFAULT_REVISION_ID_START) {
|
|
1863
|
+
this.startValue = Number.isInteger(startValue) && startValue >= 0 ? startValue : DEFAULT_REVISION_ID_START;
|
|
1864
|
+
this.nextId = this.startValue;
|
|
1865
|
+
this.occupiedIds = /* @__PURE__ */ new Set();
|
|
1866
|
+
}
|
|
1867
|
+
seed(xmlDoc) {
|
|
1868
|
+
let maxFound = -1;
|
|
1869
|
+
const elements = Array.from(xmlDoc?.getElementsByTagName?.("*") || []);
|
|
1870
|
+
if (xmlDoc?.nodeType === 1) elements.unshift(xmlDoc);
|
|
1871
|
+
for (const element of elements) {
|
|
1872
|
+
if (!isRevisionIdElement(element)) continue;
|
|
1873
|
+
const id = readWordId(element);
|
|
1874
|
+
if (id == null) continue;
|
|
1875
|
+
this.occupiedIds.add(id);
|
|
1876
|
+
maxFound = Math.max(maxFound, id);
|
|
1877
|
+
}
|
|
1878
|
+
const highRiskBoundary = MAX_PRACTICAL_REVISION_ID - REVISION_ID_SAFETY_MARGIN;
|
|
1879
|
+
this.nextId = maxFound >= highRiskBoundary ? this.startValue : Math.max(this.nextId, maxFound + 1);
|
|
1880
|
+
this.advanceToAvailableId();
|
|
1881
|
+
return this.nextId;
|
|
1882
|
+
}
|
|
1883
|
+
advanceToAvailableId() {
|
|
1884
|
+
const highRiskBoundary = MAX_PRACTICAL_REVISION_ID - REVISION_ID_SAFETY_MARGIN;
|
|
1885
|
+
if (this.nextId >= highRiskBoundary) this.nextId = this.startValue;
|
|
1886
|
+
while (this.occupiedIds.has(this.nextId)) {
|
|
1887
|
+
this.nextId += 1;
|
|
1888
|
+
if (this.nextId >= highRiskBoundary) this.nextId = this.startValue;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
next() {
|
|
1892
|
+
this.advanceToAvailableId();
|
|
1893
|
+
const id = this.nextId;
|
|
1894
|
+
this.occupiedIds.add(id);
|
|
1895
|
+
this.nextId += 1;
|
|
1896
|
+
return id;
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
var defaultRevisionIdAllocator = new RevisionIdAllocator();
|
|
1900
|
+
function setRevisionIdAllocatorForDocument(xmlNode, allocator) {
|
|
1901
|
+
const xmlDoc = xmlNode?.nodeType === 9 ? xmlNode : xmlNode?.ownerDocument;
|
|
1902
|
+
if (xmlDoc && allocator instanceof RevisionIdAllocator) {
|
|
1903
|
+
revisionAllocatorByDocument.set(xmlDoc, allocator);
|
|
1904
|
+
}
|
|
1905
|
+
return allocator;
|
|
1906
|
+
}
|
|
1907
|
+
function getRevisionIdAllocatorForDocument(xmlNode) {
|
|
1908
|
+
const xmlDoc = xmlNode?.nodeType === 9 ? xmlNode : xmlNode?.ownerDocument;
|
|
1909
|
+
return xmlDoc ? revisionAllocatorByDocument.get(xmlDoc) || null : null;
|
|
1910
|
+
}
|
|
1911
|
+
function createRevisionIdAllocator(xmlDoc, startValue = DEFAULT_REVISION_ID_START) {
|
|
1912
|
+
const allocator = new RevisionIdAllocator(startValue);
|
|
1913
|
+
allocator.seed(xmlDoc);
|
|
1914
|
+
setRevisionIdAllocatorForDocument(xmlDoc, allocator);
|
|
1915
|
+
return allocator;
|
|
1916
|
+
}
|
|
1786
1917
|
function getNextRevisionId() {
|
|
1787
|
-
return
|
|
1918
|
+
return defaultRevisionIdAllocator.next();
|
|
1788
1919
|
}
|
|
1789
1920
|
function getRevisionTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
1790
1921
|
return date.toISOString();
|
|
1791
1922
|
}
|
|
1792
|
-
function createRevisionMetadata(author) {
|
|
1923
|
+
function createRevisionMetadata(author, allocatorOrNode = null) {
|
|
1793
1924
|
const resolvedAuthor = typeof author === "string" && author.trim() ? author.trim() : getDefaultAuthor();
|
|
1925
|
+
const allocator = allocatorOrNode instanceof RevisionIdAllocator ? allocatorOrNode : getRevisionIdAllocatorForDocument(allocatorOrNode) || defaultRevisionIdAllocator;
|
|
1794
1926
|
return {
|
|
1795
|
-
id:
|
|
1927
|
+
id: allocator.next(),
|
|
1796
1928
|
author: resolvedAuthor,
|
|
1797
1929
|
date: getRevisionTimestamp()
|
|
1798
1930
|
};
|
|
1799
1931
|
}
|
|
1800
|
-
function seedRevisionIdsFromDocument(xmlDoc) {
|
|
1801
|
-
|
|
1802
|
-
const
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
if ((attr.localName || "").toLowerCase() !== "id") continue;
|
|
1806
|
-
const parsed = Number.parseInt(attr.value, 10);
|
|
1807
|
-
if (Number.isFinite(parsed)) {
|
|
1808
|
-
maxFound = Math.max(maxFound, parsed);
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
if (maxFound >= revisionIdCounter) {
|
|
1813
|
-
revisionIdCounter = maxFound + 1;
|
|
1814
|
-
}
|
|
1815
|
-
return revisionIdCounter;
|
|
1932
|
+
function seedRevisionIdsFromDocument(xmlDoc, allocator = defaultRevisionIdAllocator) {
|
|
1933
|
+
const resolvedAllocator = allocator instanceof RevisionIdAllocator ? allocator : defaultRevisionIdAllocator;
|
|
1934
|
+
const nextId = resolvedAllocator.seed(xmlDoc);
|
|
1935
|
+
setRevisionIdAllocatorForDocument(xmlDoc, resolvedAllocator);
|
|
1936
|
+
return nextId;
|
|
1816
1937
|
}
|
|
1817
1938
|
|
|
1818
1939
|
// core/paragraph-offset-policy.js
|
|
@@ -1890,10 +2011,12 @@ function ingestOoxml(ooxmlString, options = {}) {
|
|
|
1890
2011
|
return { runModel, acceptedText, pPr: null };
|
|
1891
2012
|
}
|
|
1892
2013
|
try {
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2014
|
+
const parsed = preParsedDoc ? { doc: preParsedDoc, error: null } : parseOoxmlSafe(ooxmlString, "application/xml");
|
|
2015
|
+
const doc = parsed.doc;
|
|
2016
|
+
if (parsed.error || !doc) {
|
|
2017
|
+
error("OOXML parse error:", parsed.error?.message);
|
|
2018
|
+
return { runModel, acceptedText, pPr: null, error: parsed.error };
|
|
2019
|
+
}
|
|
1897
2020
|
const parseError = getXmlParseError(doc);
|
|
1898
2021
|
if (parseError) {
|
|
1899
2022
|
error("OOXML parse error:", parseError.textContent);
|
|
@@ -2298,55 +2421,126 @@ function extractTrPr(trNode) {
|
|
|
2298
2421
|
|
|
2299
2422
|
// pipeline/diff-engine.js
|
|
2300
2423
|
var import_diff_match_patch = __toESM(require_diff_match_patch(), 1);
|
|
2301
|
-
var
|
|
2302
|
-
|
|
2424
|
+
var TOKEN_CODE_POINT_BASE = 65536;
|
|
2425
|
+
var MAX_DIFF_TOKENS = 262144;
|
|
2426
|
+
var BMP_FIRST_CODE = 1;
|
|
2427
|
+
var BMP_BEFORE_SURROGATES = 55296 - BMP_FIRST_CODE;
|
|
2428
|
+
var BMP_AFTER_SURROGATES = 65536 - 57344;
|
|
2429
|
+
var DMP_SAFE_TOKEN_LIMIT = BMP_BEFORE_SURROGATES + BMP_AFTER_SURROGATES;
|
|
2430
|
+
var DiffTokenLimitError = class extends Error {
|
|
2431
|
+
constructor(limit = MAX_DIFF_TOKENS) {
|
|
2432
|
+
super(`Word diff exceeds the safe limit of ${limit} unique tokens.`);
|
|
2433
|
+
this.name = "DiffTokenLimitError";
|
|
2434
|
+
this.code = "DIFF_TOKEN_LIMIT";
|
|
2435
|
+
this.limit = limit;
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
function isDiffTokenLimitError(error2) {
|
|
2439
|
+
return error2?.code === "DIFF_TOKEN_LIMIT";
|
|
2440
|
+
}
|
|
2441
|
+
function createDiffEngine(options = {}) {
|
|
2442
|
+
const timeout = options.diffTimeoutSeconds ?? 0;
|
|
2443
|
+
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
2444
|
+
throw new TypeError("diffTimeoutSeconds must be a finite non-negative number.");
|
|
2445
|
+
}
|
|
2446
|
+
const engine = new import_diff_match_patch.diff_match_patch();
|
|
2447
|
+
engine.Diff_Timeout = timeout;
|
|
2448
|
+
return engine;
|
|
2449
|
+
}
|
|
2450
|
+
function tokenize(text) {
|
|
2451
|
+
const tokens = [];
|
|
2452
|
+
const leading = text.match(/^\s+/);
|
|
2453
|
+
if (leading) tokens.push(leading[0]);
|
|
2454
|
+
const regex = /(\S+)(\s*)/g;
|
|
2455
|
+
regex.lastIndex = leading?.[0].length || 0;
|
|
2456
|
+
let match;
|
|
2457
|
+
while ((match = regex.exec(text)) !== null) {
|
|
2458
|
+
if (match[1]) tokens.push(match[1]);
|
|
2459
|
+
if (match[2]) tokens.push(match[2]);
|
|
2460
|
+
}
|
|
2461
|
+
return tokens;
|
|
2462
|
+
}
|
|
2463
|
+
function wordsToChars(text1, text2, options = {}) {
|
|
2303
2464
|
const wordArray = [];
|
|
2304
2465
|
const wordHash = /* @__PURE__ */ new Map();
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
let match;
|
|
2309
|
-
while ((match = regex.exec(text)) !== null) {
|
|
2310
|
-
if (match[1]) tokens.push(match[1]);
|
|
2311
|
-
if (match[2]) tokens.push(match[2]);
|
|
2312
|
-
}
|
|
2313
|
-
return tokens;
|
|
2466
|
+
const maxTokens = options.maxTokens ?? MAX_DIFF_TOKENS;
|
|
2467
|
+
if (!Number.isInteger(maxTokens) || maxTokens < 1 || maxTokens > MAX_DIFF_TOKENS) {
|
|
2468
|
+
throw new RangeError(`maxTokens must be an integer from 1 to ${MAX_DIFF_TOKENS}.`);
|
|
2314
2469
|
}
|
|
2315
2470
|
function mapTokensToChars(tokens) {
|
|
2316
2471
|
let chars = "";
|
|
2472
|
+
const tokenIds = [];
|
|
2317
2473
|
for (const token of tokens) {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2474
|
+
let tokenId = wordHash.get(token);
|
|
2475
|
+
if (tokenId === void 0) {
|
|
2476
|
+
if (wordArray.length >= maxTokens) throw new DiffTokenLimitError(maxTokens);
|
|
2477
|
+
tokenId = wordArray.length;
|
|
2322
2478
|
wordArray.push(token);
|
|
2323
|
-
wordHash.set(token,
|
|
2324
|
-
chars += String.fromCharCode(charCode);
|
|
2479
|
+
wordHash.set(token, tokenId);
|
|
2325
2480
|
}
|
|
2481
|
+
tokenIds.push(tokenId);
|
|
2482
|
+
chars += String.fromCodePoint(TOKEN_CODE_POINT_BASE + tokenId);
|
|
2326
2483
|
}
|
|
2327
|
-
return chars;
|
|
2484
|
+
return { chars, tokenIds };
|
|
2328
2485
|
}
|
|
2329
2486
|
const tokens1 = tokenize(text1);
|
|
2330
2487
|
const tokens2 = tokenize(text2);
|
|
2488
|
+
const encoded1 = mapTokensToChars(tokens1);
|
|
2489
|
+
const encoded2 = mapTokensToChars(tokens2);
|
|
2331
2490
|
return {
|
|
2332
|
-
chars1:
|
|
2333
|
-
chars2:
|
|
2334
|
-
wordArray
|
|
2491
|
+
chars1: encoded1.chars,
|
|
2492
|
+
chars2: encoded2.chars,
|
|
2493
|
+
wordArray,
|
|
2494
|
+
tokenIds1: encoded1.tokenIds,
|
|
2495
|
+
tokenIds2: encoded2.tokenIds
|
|
2335
2496
|
};
|
|
2336
2497
|
}
|
|
2337
|
-
function
|
|
2338
|
-
const
|
|
2339
|
-
|
|
2498
|
+
function tokenIdToBmpChar(tokenId) {
|
|
2499
|
+
const code = tokenId < BMP_BEFORE_SURROGATES ? BMP_FIRST_CODE + tokenId : 57344 + (tokenId - BMP_BEFORE_SURROGATES);
|
|
2500
|
+
return String.fromCharCode(code);
|
|
2501
|
+
}
|
|
2502
|
+
function bmpCharToTokenId(charCode) {
|
|
2503
|
+
if (charCode >= BMP_FIRST_CODE && charCode < 55296) return charCode - BMP_FIRST_CODE;
|
|
2504
|
+
if (charCode >= 57344 && charCode <= 65535) return BMP_BEFORE_SURROGATES + charCode - 57344;
|
|
2505
|
+
throw new RangeError(`BMP diff token U+${charCode.toString(16).toUpperCase()} has no mapping.`);
|
|
2506
|
+
}
|
|
2507
|
+
function encodeTokenIdsForDmp(tokenIds) {
|
|
2508
|
+
let chars = "";
|
|
2509
|
+
for (const tokenId of tokenIds) chars += tokenIdToBmpChar(tokenId);
|
|
2510
|
+
return chars;
|
|
2511
|
+
}
|
|
2512
|
+
function decodeBmpDiffs(diffs, wordArray) {
|
|
2513
|
+
return diffs.map(([op, chars]) => {
|
|
2340
2514
|
const parts = [];
|
|
2341
|
-
for (let
|
|
2342
|
-
const
|
|
2343
|
-
if (
|
|
2344
|
-
|
|
2515
|
+
for (let index = 0; index < chars.length; index++) {
|
|
2516
|
+
const tokenId = bmpCharToTokenId(chars.charCodeAt(index));
|
|
2517
|
+
if (tokenId >= wordArray.length) {
|
|
2518
|
+
throw new RangeError(`BMP diff token ${tokenId} has no mapping.`);
|
|
2345
2519
|
}
|
|
2520
|
+
parts.push(wordArray[tokenId]);
|
|
2346
2521
|
}
|
|
2347
|
-
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2522
|
+
return [op, parts.join("")];
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
function deterministicLargeTokenDiff(tokenIds1, tokenIds2, wordArray) {
|
|
2526
|
+
let prefixLength = 0;
|
|
2527
|
+
const sharedLength = Math.min(tokenIds1.length, tokenIds2.length);
|
|
2528
|
+
while (prefixLength < sharedLength && tokenIds1[prefixLength] === tokenIds2[prefixLength]) {
|
|
2529
|
+
prefixLength++;
|
|
2530
|
+
}
|
|
2531
|
+
let suffixLength = 0;
|
|
2532
|
+
while (suffixLength < sharedLength - prefixLength && tokenIds1[tokenIds1.length - 1 - suffixLength] === tokenIds2[tokenIds2.length - 1 - suffixLength]) {
|
|
2533
|
+
suffixLength++;
|
|
2534
|
+
}
|
|
2535
|
+
const joinTokens = (ids) => ids.map((id) => wordArray[id]).join("");
|
|
2536
|
+
const diffs = [];
|
|
2537
|
+
if (prefixLength) diffs.push([0, joinTokens(tokenIds1.slice(0, prefixLength))]);
|
|
2538
|
+
const deleted = tokenIds1.slice(prefixLength, tokenIds1.length - suffixLength);
|
|
2539
|
+
const inserted = tokenIds2.slice(prefixLength, tokenIds2.length - suffixLength);
|
|
2540
|
+
if (deleted.length) diffs.push([-1, joinTokens(deleted)]);
|
|
2541
|
+
if (inserted.length) diffs.push([1, joinTokens(inserted)]);
|
|
2542
|
+
if (suffixLength) diffs.push([0, joinTokens(tokenIds1.slice(tokenIds1.length - suffixLength))]);
|
|
2543
|
+
return diffs;
|
|
2350
2544
|
}
|
|
2351
2545
|
function computeWordDiffs(originalText, newText, options = {}) {
|
|
2352
2546
|
if (originalText === newText) {
|
|
@@ -2359,12 +2553,16 @@ function computeWordDiffs(originalText, newText, options = {}) {
|
|
|
2359
2553
|
return [[-1, originalText]];
|
|
2360
2554
|
}
|
|
2361
2555
|
const { cleanupSemantic = true } = options;
|
|
2362
|
-
const {
|
|
2363
|
-
|
|
2556
|
+
const { wordArray, tokenIds1, tokenIds2 } = wordsToChars(originalText, newText, options);
|
|
2557
|
+
if (wordArray.length > DMP_SAFE_TOKEN_LIMIT) {
|
|
2558
|
+
return deterministicLargeTokenDiff(tokenIds1, tokenIds2, wordArray);
|
|
2559
|
+
}
|
|
2560
|
+
const dmp = createDiffEngine(options);
|
|
2561
|
+
const charDiffs = dmp.diff_main(encodeTokenIdsForDmp(tokenIds1), encodeTokenIdsForDmp(tokenIds2));
|
|
2364
2562
|
if (cleanupSemantic) {
|
|
2365
|
-
|
|
2563
|
+
dmp.diff_cleanupSemantic(charDiffs);
|
|
2366
2564
|
}
|
|
2367
|
-
return
|
|
2565
|
+
return decodeBmpDiffs(charDiffs, wordArray);
|
|
2368
2566
|
}
|
|
2369
2567
|
function computeWordLevelDiffOps(originalText, newText, options = {}) {
|
|
2370
2568
|
if (originalText === newText) {
|
|
@@ -2555,7 +2753,6 @@ function applyPatches(splitModel, diffOps, options) {
|
|
|
2555
2753
|
function processInsertionOperation(context) {
|
|
2556
2754
|
const {
|
|
2557
2755
|
insertOp,
|
|
2558
|
-
splitModel,
|
|
2559
2756
|
styleLookup,
|
|
2560
2757
|
patchedModel,
|
|
2561
2758
|
state,
|
|
@@ -2820,7 +3017,7 @@ function buildParagraphOnlyPackage(paragraphXml) {
|
|
|
2820
3017
|
var XMLNS_ATTR_REGEX2 = /\s+xmlns:[^=]+="[^"]*"/g;
|
|
2821
3018
|
function serializeToOoxml(patchedModel, pPr, formatHints = [], options = {}) {
|
|
2822
3019
|
const serializationOptions = normalizeSerializationOptions(options);
|
|
2823
|
-
const {
|
|
3020
|
+
const { generateRedlines } = serializationOptions;
|
|
2824
3021
|
const paragraphs = [];
|
|
2825
3022
|
let currentPPrXml = "";
|
|
2826
3023
|
let currentPPrElement = null;
|
|
@@ -2908,7 +3105,8 @@ function normalizeSerializationOptions(options) {
|
|
|
2908
3105
|
return {
|
|
2909
3106
|
author: getDefaultAuthor(),
|
|
2910
3107
|
generateRedlines: true,
|
|
2911
|
-
font: options
|
|
3108
|
+
font: options,
|
|
3109
|
+
revisionIdAllocator: null
|
|
2912
3110
|
};
|
|
2913
3111
|
}
|
|
2914
3112
|
const normalized = options && typeof options === "object" ? options : {};
|
|
@@ -2916,7 +3114,8 @@ function normalizeSerializationOptions(options) {
|
|
|
2916
3114
|
return {
|
|
2917
3115
|
author: resolvedAuthor,
|
|
2918
3116
|
generateRedlines: normalized.generateRedlines ?? true,
|
|
2919
|
-
font: normalized.font ?? null
|
|
3117
|
+
font: normalized.font ?? null,
|
|
3118
|
+
revisionIdAllocator: normalized.revisionIdAllocator ?? null
|
|
2920
3119
|
};
|
|
2921
3120
|
}
|
|
2922
3121
|
function buildRunXmlWithHints(item, formatHints, options = {}) {
|
|
@@ -2954,7 +3153,10 @@ function buildSimpleRun(text, rPrXml) {
|
|
|
2954
3153
|
return `<w:r>${rPr}<w:t xml:space="preserve">${escapeXml(text)}</w:t></w:r>`;
|
|
2955
3154
|
}
|
|
2956
3155
|
function buildDeletionXml(item, options = {}) {
|
|
2957
|
-
const metadata = createRevisionMetadata(
|
|
3156
|
+
const metadata = createRevisionMetadata(
|
|
3157
|
+
options.author ?? getDefaultAuthor(),
|
|
3158
|
+
options.revisionIdAllocator
|
|
3159
|
+
);
|
|
2958
3160
|
const font = options.font ?? null;
|
|
2959
3161
|
let rPr = item.rPrXml ? stripNamespaceDeclarations(item.rPrXml) : "";
|
|
2960
3162
|
if (font) {
|
|
@@ -2963,7 +3165,10 @@ function buildDeletionXml(item, options = {}) {
|
|
|
2963
3165
|
return `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}"><w:r>${rPr}<w:delText xml:space="preserve">${escapeXml(item.text)}</w:delText></w:r></w:del>`;
|
|
2964
3166
|
}
|
|
2965
3167
|
function buildInsertionXml(item, formatHints, options = {}) {
|
|
2966
|
-
const metadata = createRevisionMetadata(
|
|
3168
|
+
const metadata = createRevisionMetadata(
|
|
3169
|
+
options.author ?? getDefaultAuthor(),
|
|
3170
|
+
options.revisionIdAllocator
|
|
3171
|
+
);
|
|
2967
3172
|
const font = options.font ?? null;
|
|
2968
3173
|
const applicableHints = getApplicableFormatHints(formatHints, item.startOffset, item.endOffset);
|
|
2969
3174
|
let innerContent = "";
|
|
@@ -3283,8 +3488,8 @@ var NumberingService = class {
|
|
|
3283
3488
|
|
|
3284
3489
|
// services/table-reconciliation.js
|
|
3285
3490
|
function generateTableOoxml(tableData, options = {}) {
|
|
3286
|
-
const { generateRedlines = false, author = "AI" } = options;
|
|
3287
|
-
const tableInsertMeta = generateRedlines ? createRevisionMetadata(author) : null;
|
|
3491
|
+
const { generateRedlines = false, author = "AI", revisionIdAllocator = null } = options;
|
|
3492
|
+
const tableInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
3288
3493
|
const numCols = tableData.headers?.length || (tableData.rows?.[0]?.length || 1);
|
|
3289
3494
|
const tblPr = `
|
|
3290
3495
|
<w:tblPr>
|
|
@@ -3319,7 +3524,11 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
3319
3524
|
startOffset: 0,
|
|
3320
3525
|
endOffset: cleanText.length
|
|
3321
3526
|
}];
|
|
3322
|
-
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3527
|
+
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3528
|
+
author,
|
|
3529
|
+
generateRedlines,
|
|
3530
|
+
revisionIdAllocator
|
|
3531
|
+
});
|
|
3323
3532
|
const tcPr = '<w:tcPr><w:tcW w:w="0" w:type="auto"/></w:tcPr>';
|
|
3324
3533
|
cellsXml += `<w:tc>${tcPr}${runsOoxml}</w:tc>`;
|
|
3325
3534
|
}
|
|
@@ -3377,7 +3586,7 @@ function diffTablesWithVirtualGrid(oldGrid, newTableData) {
|
|
|
3377
3586
|
return operations;
|
|
3378
3587
|
}
|
|
3379
3588
|
function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
3380
|
-
const { generateRedlines, author } = options;
|
|
3589
|
+
const { generateRedlines, author, revisionIdAllocator = null } = options;
|
|
3381
3590
|
const opIndex = buildTableOperationIndex(operations);
|
|
3382
3591
|
let rowsXml = "";
|
|
3383
3592
|
for (let row = 0; row < grid.rowCount; row++) {
|
|
@@ -3407,7 +3616,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
3407
3616
|
}
|
|
3408
3617
|
let trPr = grid.trPrList[row] || "<w:trPr/>";
|
|
3409
3618
|
if (rowDeleteOp && generateRedlines) {
|
|
3410
|
-
const metadata = createRevisionMetadata(author);
|
|
3619
|
+
const metadata = createRevisionMetadata(author, revisionIdAllocator);
|
|
3411
3620
|
const delMark = `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}"/>`;
|
|
3412
3621
|
if (trPr.includes("</w:trPr>")) {
|
|
3413
3622
|
trPr = trPr.replace("</w:trPr>", `${delMark}</w:trPr>`);
|
|
@@ -3420,7 +3629,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
3420
3629
|
const insertOps = opIndex.rowInsertOperations;
|
|
3421
3630
|
for (const op of insertOps) {
|
|
3422
3631
|
let cellsXml = "";
|
|
3423
|
-
const rowInsertMeta = generateRedlines ? createRevisionMetadata(author) : null;
|
|
3632
|
+
const rowInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
3424
3633
|
for (const cellText of op.cells) {
|
|
3425
3634
|
const { cleanText, formatHints } = preprocessMarkdown(cellText);
|
|
3426
3635
|
const runModel = [{
|
|
@@ -3431,7 +3640,11 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
3431
3640
|
startOffset: 0,
|
|
3432
3641
|
endOffset: cleanText.length
|
|
3433
3642
|
}];
|
|
3434
|
-
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3643
|
+
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3644
|
+
author,
|
|
3645
|
+
generateRedlines,
|
|
3646
|
+
revisionIdAllocator
|
|
3647
|
+
});
|
|
3435
3648
|
cellsXml += `<w:tc><w:tcPr><w:tcW w:w="0" w:type="auto"/></w:tcPr>${runsOoxml}</w:tc>`;
|
|
3436
3649
|
}
|
|
3437
3650
|
let trPr = "<w:trPr/>";
|
|
@@ -3469,7 +3682,7 @@ function buildTableOperationIndex(operations) {
|
|
|
3469
3682
|
};
|
|
3470
3683
|
}
|
|
3471
3684
|
function reconcileCellContent(cell, newText, options) {
|
|
3472
|
-
const { generateRedlines, author } = options;
|
|
3685
|
+
const { generateRedlines, author, revisionIdAllocator = null } = options;
|
|
3473
3686
|
const { cleanText, formatHints } = preprocessMarkdown(newText);
|
|
3474
3687
|
const oldText = cell.getText();
|
|
3475
3688
|
const diffOps = computeWordLevelDiffOps(oldText, cleanText);
|
|
@@ -3480,7 +3693,11 @@ function reconcileCellContent(cell, newText, options) {
|
|
|
3480
3693
|
author,
|
|
3481
3694
|
formatHints
|
|
3482
3695
|
});
|
|
3483
|
-
const runsOoxml = serializeToOoxml(patchedModel, baseBlock.pPr, formatHints, {
|
|
3696
|
+
const runsOoxml = serializeToOoxml(patchedModel, baseBlock.pPr, formatHints, {
|
|
3697
|
+
author,
|
|
3698
|
+
generateRedlines,
|
|
3699
|
+
revisionIdAllocator
|
|
3700
|
+
});
|
|
3484
3701
|
return runsOoxml;
|
|
3485
3702
|
}
|
|
3486
3703
|
function serializeCellBlocks(blocks) {
|
|
@@ -3489,7 +3706,7 @@ function serializeCellBlocks(blocks) {
|
|
|
3489
3706
|
return runsOoxml;
|
|
3490
3707
|
}).join("");
|
|
3491
3708
|
}
|
|
3492
|
-
function buildTcXml(cell, content,
|
|
3709
|
+
function buildTcXml(cell, content, _options) {
|
|
3493
3710
|
let tcPr = cell.tcPrXml;
|
|
3494
3711
|
if (cell.colSpan > 1 && !tcPr.includes("gridSpan")) {
|
|
3495
3712
|
tcPr = tcPr.replace("</w:tcPr>", `<w:gridSpan w:val="${cell.colSpan}"/></w:tcPr>`);
|
|
@@ -3543,6 +3760,7 @@ async function executeListGeneration(options) {
|
|
|
3543
3760
|
generateRedlines = true,
|
|
3544
3761
|
author = "AI",
|
|
3545
3762
|
font = null,
|
|
3763
|
+
revisionIdAllocator = null,
|
|
3546
3764
|
numberingService
|
|
3547
3765
|
} = options;
|
|
3548
3766
|
const normalizedListText = normalizeCompositeListMarkers(cleanText);
|
|
@@ -3578,10 +3796,15 @@ async function executeListGeneration(options) {
|
|
|
3578
3796
|
results.push(serializeToOoxml(deletionRuns, null, [], {
|
|
3579
3797
|
author,
|
|
3580
3798
|
generateRedlines,
|
|
3581
|
-
font
|
|
3799
|
+
font,
|
|
3800
|
+
revisionIdAllocator
|
|
3582
3801
|
}));
|
|
3583
3802
|
}
|
|
3584
|
-
results.push(generateTableOoxml(tableData, {
|
|
3803
|
+
results.push(generateTableOoxml(tableData, {
|
|
3804
|
+
generateRedlines,
|
|
3805
|
+
author,
|
|
3806
|
+
revisionIdAllocator
|
|
3807
|
+
}));
|
|
3585
3808
|
i = tableBlock.endIndex;
|
|
3586
3809
|
continue;
|
|
3587
3810
|
}
|
|
@@ -3596,6 +3819,7 @@ async function executeListGeneration(options) {
|
|
|
3596
3819
|
generateRedlines,
|
|
3597
3820
|
author,
|
|
3598
3821
|
font,
|
|
3822
|
+
revisionIdAllocator,
|
|
3599
3823
|
deletionRuns
|
|
3600
3824
|
);
|
|
3601
3825
|
results.push(entry.ooxml);
|
|
@@ -3686,7 +3910,7 @@ function collectMarkdownTableBlock(lineMetadata, index) {
|
|
|
3686
3910
|
endIndex: cursor - 1
|
|
3687
3911
|
};
|
|
3688
3912
|
}
|
|
3689
|
-
function buildListEntry(line, lineIndex, indentStep, numberingContext, numberingService, generateRedlines, author, font, deletionRuns) {
|
|
3913
|
+
function buildListEntry(line, lineIndex, indentStep, numberingContext, numberingService, generateRedlines, author, font, revisionIdAllocator, deletionRuns) {
|
|
3690
3914
|
let pPrXml = "";
|
|
3691
3915
|
let segmentText = "";
|
|
3692
3916
|
if (line.headerMatch) {
|
|
@@ -3723,7 +3947,8 @@ function buildListEntry(line, lineIndex, indentStep, numberingContext, numbering
|
|
|
3723
3947
|
ooxml: serializeToOoxml(runModel, pPrXml, formatHints, {
|
|
3724
3948
|
author,
|
|
3725
3949
|
generateRedlines,
|
|
3726
|
-
font
|
|
3950
|
+
font,
|
|
3951
|
+
revisionIdAllocator
|
|
3727
3952
|
})
|
|
3728
3953
|
};
|
|
3729
3954
|
}
|
|
@@ -3754,6 +3979,7 @@ var ReconciliationPipeline = class {
|
|
|
3754
3979
|
this.validationMode = options.validationMode ?? "auto";
|
|
3755
3980
|
this.numberingService = options.numberingService || new NumberingService();
|
|
3756
3981
|
this.font = options.font || null;
|
|
3982
|
+
this.revisionIdAllocator = options.revisionIdAllocator || null;
|
|
3757
3983
|
this.platform = options.platform ?? getPlatform();
|
|
3758
3984
|
this.isWebPlatform = options.isWebPlatform ?? isWebPlatform(this.platform);
|
|
3759
3985
|
this.enableEventLoopYielding = options.enableEventLoopYielding ?? this.isWebPlatform;
|
|
@@ -3772,10 +3998,18 @@ var ReconciliationPipeline = class {
|
|
|
3772
3998
|
async execute(originalOoxml, newText, options = {}) {
|
|
3773
3999
|
const warnings = [];
|
|
3774
4000
|
try {
|
|
3775
|
-
const
|
|
3776
|
-
|
|
3777
|
-
return
|
|
3778
|
-
|
|
4001
|
+
const parsed = options.xmlDoc ? { doc: options.xmlDoc, error: null, warnings: [] } : parseOoxmlSafe(originalOoxml, "application/xml");
|
|
4002
|
+
if (parsed.error || !parsed.doc) {
|
|
4003
|
+
return {
|
|
4004
|
+
ooxml: originalOoxml,
|
|
4005
|
+
isValid: false,
|
|
4006
|
+
status: "error",
|
|
4007
|
+
error: parsed.error,
|
|
4008
|
+
warnings: parsed.warnings || []
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
warnings.push(...parsed.warnings || []);
|
|
4012
|
+
const doc = parsed.doc;
|
|
3779
4013
|
const pElement = getFirstElementByTagNS(doc, "*", "p");
|
|
3780
4014
|
const { runModel, acceptedText, pPr } = ingestOoxml(originalOoxml, { xmlDoc: doc });
|
|
3781
4015
|
const numberingContext = pElement ? detectNumberingContext(pElement) : null;
|
|
@@ -3807,8 +4041,12 @@ var ReconciliationPipeline = class {
|
|
|
3807
4041
|
}
|
|
3808
4042
|
await this.maybeYield(runModel.length, Math.max(acceptedText.length, cleanText.length));
|
|
3809
4043
|
const paragraphCount = runModel.filter((r) => r.kind === RunKind.PARAGRAPH_START).length;
|
|
4044
|
+
const sourceIsMarkedMultilineList = isListTargetStrict(acceptedText) || isListTargetLoose(acceptedText);
|
|
4045
|
+
const canPatchExistingMarkedList = isTargetList && sourceIsMarkedMultilineList && paragraphCount > 1 && acceptedText !== cleanText;
|
|
3810
4046
|
log(`[Reconcile] isTargetList: ${isTargetList}, paragraphCount: ${paragraphCount}`);
|
|
3811
|
-
if (
|
|
4047
|
+
if (canPatchExistingMarkedList) {
|
|
4048
|
+
log("[Reconcile] Existing marked list edit detected; using run-aware patching to preserve formatting and paragraph boundaries.");
|
|
4049
|
+
} else if (isTargetList) {
|
|
3812
4050
|
log("[Reconcile] \u{1F3AF} ENTERING LIST GENERATION PATH");
|
|
3813
4051
|
log(`[Reconcile] cleanText preview: ${cleanText.substring(0, 100)}...`);
|
|
3814
4052
|
log(`[Reconcile] acceptedText preview: ${acceptedText.substring(0, 100)}...`);
|
|
@@ -3827,7 +4065,8 @@ var ReconciliationPipeline = class {
|
|
|
3827
4065
|
await this.maybeYield(patchedModel.length, Math.max(acceptedText.length, cleanText.length));
|
|
3828
4066
|
const resultOoxml = serializeToOoxml(patchedModel, pPr, formatHints, {
|
|
3829
4067
|
author: this.author,
|
|
3830
|
-
generateRedlines: this.generateRedlines
|
|
4068
|
+
generateRedlines: this.generateRedlines,
|
|
4069
|
+
revisionIdAllocator: this.revisionIdAllocator
|
|
3831
4070
|
});
|
|
3832
4071
|
if (this.shouldRunValidation()) {
|
|
3833
4072
|
const validation = this.validateBasic(resultOoxml);
|
|
@@ -3845,7 +4084,8 @@ var ReconciliationPipeline = class {
|
|
|
3845
4084
|
return {
|
|
3846
4085
|
ooxml: originalOoxml,
|
|
3847
4086
|
isValid: false,
|
|
3848
|
-
warnings: [`Pipeline error: ${error2.message}`]
|
|
4087
|
+
warnings: [`Pipeline error: ${error2.message}`],
|
|
4088
|
+
error: error2?.code ? { code: error2.code, message: error2.message } : void 0
|
|
3849
4089
|
};
|
|
3850
4090
|
}
|
|
3851
4091
|
}
|
|
@@ -3859,8 +4099,12 @@ var ReconciliationPipeline = class {
|
|
|
3859
4099
|
const errors = [];
|
|
3860
4100
|
try {
|
|
3861
4101
|
const wrappedXml = `<root xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${ooxml}</root>`;
|
|
3862
|
-
const
|
|
3863
|
-
const doc =
|
|
4102
|
+
const parsed = parseOoxmlSafe(wrappedXml, "application/xml");
|
|
4103
|
+
const doc = parsed.doc;
|
|
4104
|
+
if (parsed.error || !doc) {
|
|
4105
|
+
errors.push("Generated OOXML is not well-formed XML: " + (parsed.error?.message || "parse error"));
|
|
4106
|
+
return { isValid: false, errors };
|
|
4107
|
+
}
|
|
3864
4108
|
const parseError = getXmlParseError(doc);
|
|
3865
4109
|
if (parseError) {
|
|
3866
4110
|
errors.push("Generated OOXML is not well-formed XML: " + parseError.textContent.substring(0, 100));
|
|
@@ -3931,6 +4175,7 @@ var ReconciliationPipeline = class {
|
|
|
3931
4175
|
generateRedlines: this.generateRedlines,
|
|
3932
4176
|
author: this.author,
|
|
3933
4177
|
font: this.font,
|
|
4178
|
+
revisionIdAllocator: this.revisionIdAllocator,
|
|
3934
4179
|
numberingService: this.numberingService
|
|
3935
4180
|
});
|
|
3936
4181
|
}
|
|
@@ -3960,7 +4205,8 @@ var ReconciliationPipeline = class {
|
|
|
3960
4205
|
}
|
|
3961
4206
|
const tableOoxml = generateTableOoxml(tableData, {
|
|
3962
4207
|
generateRedlines: this.generateRedlines,
|
|
3963
|
-
author: this.author
|
|
4208
|
+
author: this.author,
|
|
4209
|
+
revisionIdAllocator: this.revisionIdAllocator
|
|
3964
4210
|
});
|
|
3965
4211
|
return {
|
|
3966
4212
|
ooxml: tableOoxml,
|
|
@@ -4336,7 +4582,7 @@ function extractFormattingFromOoxml(xmlDoc) {
|
|
|
4336
4582
|
// engine/run-builders.js
|
|
4337
4583
|
function createTrackChange(xmlDoc, type, run, author) {
|
|
4338
4584
|
const wrapper = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
4339
|
-
const metadata = createRevisionMetadata(author);
|
|
4585
|
+
const metadata = createRevisionMetadata(author, xmlDoc);
|
|
4340
4586
|
wrapper.setAttribute("w:id", String(metadata.id));
|
|
4341
4587
|
wrapper.setAttribute("w:author", metadata.author);
|
|
4342
4588
|
wrapper.setAttribute("w:date", metadata.date);
|
|
@@ -4378,7 +4624,7 @@ function markParagraphMark(xmlDoc, paragraph, author, type) {
|
|
|
4378
4624
|
}
|
|
4379
4625
|
}
|
|
4380
4626
|
const marker = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
4381
|
-
const metadata = createRevisionMetadata(author);
|
|
4627
|
+
const metadata = createRevisionMetadata(author, xmlDoc);
|
|
4382
4628
|
marker.setAttribute("w:id", String(metadata.id));
|
|
4383
4629
|
marker.setAttribute("w:author", metadata.author);
|
|
4384
4630
|
marker.setAttribute("w:date", metadata.date);
|
|
@@ -4425,11 +4671,11 @@ function createFormattedRuns(xmlDoc, text, baseRPr, formatHints, baseOffset, aut
|
|
|
4425
4671
|
const applicableHints = formatHints.filter(
|
|
4426
4672
|
(h) => h.start <= segmentBaseOffset && h.end >= segmentEndOffset
|
|
4427
4673
|
);
|
|
4428
|
-
const combinedFormat = {};
|
|
4674
|
+
const combinedFormat = { ...extractFormatFromRPr(baseRPr) };
|
|
4429
4675
|
applicableHints.forEach((h) => {
|
|
4430
4676
|
if (h.format) Object.assign(combinedFormat, h.format);
|
|
4431
4677
|
});
|
|
4432
|
-
const formattedRPr = injectFormattingToRPr(xmlDoc, baseRPr, combinedFormat, author, generateRedlines);
|
|
4678
|
+
const formattedRPr = applicableHints.length > 0 ? injectFormattingToRPr(xmlDoc, baseRPr, combinedFormat, author, generateRedlines) : baseRPr?.cloneNode(true) || null;
|
|
4433
4679
|
runs.push(createTextRunWithRPrElement(xmlDoc, segment, formattedRPr, false));
|
|
4434
4680
|
}
|
|
4435
4681
|
return runs;
|
|
@@ -4517,7 +4763,7 @@ function injectFormattingToRPr(xmlDoc, baseRPr, format, author, generateRedlines
|
|
|
4517
4763
|
}
|
|
4518
4764
|
function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceNode) {
|
|
4519
4765
|
const rPrChange = createWordElement(xmlDoc, "w:rPrChange");
|
|
4520
|
-
const metadata = createRevisionMetadata(author);
|
|
4766
|
+
const metadata = createRevisionMetadata(author, xmlDoc);
|
|
4521
4767
|
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
4522
4768
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
4523
4769
|
rPrChange.setAttribute("w:date", dateStr || metadata.date);
|
|
@@ -4549,6 +4795,7 @@ function isWordElement3(node, localName) {
|
|
|
4549
4795
|
return nodeName === `w:${localName}` || nodeName === localName;
|
|
4550
4796
|
}
|
|
4551
4797
|
function buildParagraphInfos(xmlDoc, paragraphs, textSpans) {
|
|
4798
|
+
void xmlDoc;
|
|
4552
4799
|
const spansByParagraph = /* @__PURE__ */ new Map();
|
|
4553
4800
|
for (const span of textSpans) {
|
|
4554
4801
|
if (!span || !span.paragraph) continue;
|
|
@@ -4753,6 +5000,7 @@ function normalizePrecomputedFormatContext(precomputedContext) {
|
|
|
4753
5000
|
};
|
|
4754
5001
|
}
|
|
4755
5002
|
function applyFormatRemovalAsSurgicalReplacement(xmlDoc, textSpans, existingFormatHints, serializer, author, generateRedlines = true) {
|
|
5003
|
+
void textSpans;
|
|
4756
5004
|
let hasAnyChanges = false;
|
|
4757
5005
|
const processedRuns = /* @__PURE__ */ new Set();
|
|
4758
5006
|
log(`[OxmlEngine] Surgical format removal: ${existingFormatHints.length} hints to process (using w:rPrChange)`);
|
|
@@ -5208,26 +5456,11 @@ function cloneRunPiece(xmlDoc, sourceNode, text, asDeletedText) {
|
|
|
5208
5456
|
|
|
5209
5457
|
// engine/surgical-diff-application.js
|
|
5210
5458
|
function reconcileFormattingForTextSpan(xmlDoc, span, start, end, applicableHints, author, generateRedlines) {
|
|
5211
|
-
|
|
5212
|
-
if (applicableHints.length > 0) {
|
|
5213
|
-
applicableHints.forEach((h) => Object.assign(desiredFormat, h.format));
|
|
5214
|
-
}
|
|
5459
|
+
if (applicableHints.length === 0) return false;
|
|
5215
5460
|
const rPr = span.rPr;
|
|
5216
|
-
const
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
if (isWordElement(node, localName)) {
|
|
5220
|
-
return true;
|
|
5221
|
-
}
|
|
5222
|
-
}
|
|
5223
|
-
return false;
|
|
5224
|
-
};
|
|
5225
|
-
const existingFormat = {
|
|
5226
|
-
bold: hasElement("b"),
|
|
5227
|
-
italic: hasElement("i"),
|
|
5228
|
-
underline: hasElement("u"),
|
|
5229
|
-
strikethrough: hasElement("strike")
|
|
5230
|
-
};
|
|
5461
|
+
const existingFormat = extractFormatFromRPr(rPr);
|
|
5462
|
+
const desiredFormat = { ...existingFormat };
|
|
5463
|
+
applicableHints.forEach((h) => Object.assign(desiredFormat, h.format));
|
|
5231
5464
|
const formatsToCheck = ["bold", "italic", "underline", "strikethrough"];
|
|
5232
5465
|
const changesNeeded = formatsToCheck.some((f) => !!desiredFormat[f] !== existingFormat[f]);
|
|
5233
5466
|
if (!changesNeeded) return false;
|
|
@@ -5360,10 +5593,11 @@ function insertTextRuns(xmlDoc, parent, referenceNode, text, baseRPr, author, fo
|
|
|
5360
5593
|
}
|
|
5361
5594
|
|
|
5362
5595
|
// engine/surgical-mode.js
|
|
5363
|
-
function applySurgicalMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true, targetParagraph = null) {
|
|
5596
|
+
function applySurgicalMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true, targetParagraph = null, diffOptions = {}) {
|
|
5597
|
+
void originalText;
|
|
5364
5598
|
const allParagraphs = targetParagraph ? [targetParagraph] : getDocumentParagraphs(xmlDoc);
|
|
5365
5599
|
const { fullText, textSpans } = buildSurgicalTextSpans(allParagraphs);
|
|
5366
|
-
const diffs = computeWordDiffs(fullText, modifiedText);
|
|
5600
|
+
const diffs = computeWordDiffs(fullText, modifiedText, diffOptions);
|
|
5367
5601
|
const spanIndex = buildSpanIndex(textSpans);
|
|
5368
5602
|
let originalPos = 0;
|
|
5369
5603
|
let newPos = 0;
|
|
@@ -5406,10 +5640,28 @@ function applySurgicalMode(xmlDoc, originalText, modifiedText, serializer, autho
|
|
|
5406
5640
|
}
|
|
5407
5641
|
|
|
5408
5642
|
// engine/reconstruction-mapper.js
|
|
5643
|
+
var import_diff_match_patch2 = __toESM(require_diff_match_patch(), 1);
|
|
5644
|
+
var DMP = new import_diff_match_patch2.diff_match_patch();
|
|
5645
|
+
function localNameOf(node) {
|
|
5646
|
+
return String(node?.localName || node?.nodeName || "").replace(/^.*:/, "");
|
|
5647
|
+
}
|
|
5648
|
+
function wordAttribute(node, localName) {
|
|
5649
|
+
return node?.getAttributeNS?.(NS_W, localName) || node?.getAttribute?.(`w:${localName}`) || node?.getAttribute?.(localName) || "";
|
|
5650
|
+
}
|
|
5409
5651
|
function createRangeCursorLookup2(ranges) {
|
|
5410
5652
|
let cursor = 0;
|
|
5411
5653
|
return {
|
|
5412
5654
|
at(index) {
|
|
5655
|
+
if (cursor > 0 && (!ranges[cursor] || index < ranges[cursor].start)) {
|
|
5656
|
+
let low = 0;
|
|
5657
|
+
let high = cursor - 1;
|
|
5658
|
+
while (low <= high) {
|
|
5659
|
+
const middle = Math.floor((low + high) / 2);
|
|
5660
|
+
if (ranges[middle].end <= index) low = middle + 1;
|
|
5661
|
+
else high = middle - 1;
|
|
5662
|
+
}
|
|
5663
|
+
cursor = low;
|
|
5664
|
+
}
|
|
5413
5665
|
while (cursor < ranges.length && ranges[cursor].end <= index) {
|
|
5414
5666
|
cursor++;
|
|
5415
5667
|
}
|
|
@@ -5430,11 +5682,11 @@ function indexSentinelsByStart(sentinelMap) {
|
|
|
5430
5682
|
});
|
|
5431
5683
|
return sentinelMapByStart;
|
|
5432
5684
|
}
|
|
5433
|
-
function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
5685
|
+
function buildReconstructionMapping(xmlDoc, modifiedText, selectedParagraphs = null) {
|
|
5434
5686
|
const rootElement = xmlDoc.documentElement;
|
|
5435
|
-
const isBodyRoot = rootElement
|
|
5436
|
-
const paragraphs = getDocumentParagraphs(xmlDoc);
|
|
5437
|
-
let body =
|
|
5687
|
+
const isBodyRoot = isWordElement(rootElement, "body") || localNameOf(rootElement) === "package";
|
|
5688
|
+
const paragraphs = selectedParagraphs || getDocumentParagraphs(xmlDoc);
|
|
5689
|
+
let body = getFirstElementByTagNSOrTag(xmlDoc, NS_W, "body");
|
|
5438
5690
|
if (!body && isBodyRoot) body = rootElement;
|
|
5439
5691
|
let originalFullText = "";
|
|
5440
5692
|
const propertyMap = [];
|
|
@@ -5442,7 +5694,8 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5442
5694
|
const sentinelMap = [];
|
|
5443
5695
|
const referenceMap = /* @__PURE__ */ new Map();
|
|
5444
5696
|
const tokenToCharMap = /* @__PURE__ */ new Map();
|
|
5445
|
-
|
|
5697
|
+
const breakChars = /* @__PURE__ */ new Set();
|
|
5698
|
+
const characterState = { nextCharCode: 57344 };
|
|
5446
5699
|
const uniqueContainers = /* @__PURE__ */ new Set();
|
|
5447
5700
|
paragraphs.forEach((paragraph, paragraphIndex) => {
|
|
5448
5701
|
const paragraphStart = originalFullText.length;
|
|
@@ -5454,15 +5707,13 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5454
5707
|
sentinelMap,
|
|
5455
5708
|
referenceMap,
|
|
5456
5709
|
tokenToCharMap,
|
|
5457
|
-
|
|
5710
|
+
characterState,
|
|
5711
|
+
breakChars
|
|
5458
5712
|
);
|
|
5459
|
-
if (referenceMap.size > tokenToCharMap.size) {
|
|
5460
|
-
nextCharCode++;
|
|
5461
|
-
}
|
|
5462
5713
|
});
|
|
5463
5714
|
originalFullText = appendParagraphBoundary(originalFullText, paragraphIndex, paragraphs.length);
|
|
5464
5715
|
const paragraphEnd = originalFullText.length;
|
|
5465
|
-
const pPr =
|
|
5716
|
+
const pPr = getFirstElementByTagNSOrTag(paragraph, NS_W, "pPr");
|
|
5466
5717
|
const container = paragraph.parentNode;
|
|
5467
5718
|
if (container) uniqueContainers.add(container);
|
|
5468
5719
|
paragraphMap.push({
|
|
@@ -5472,7 +5723,13 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5472
5723
|
container: container || body
|
|
5473
5724
|
});
|
|
5474
5725
|
});
|
|
5475
|
-
let
|
|
5726
|
+
let displayOriginalText = "";
|
|
5727
|
+
for (let index = 0; index < originalFullText.length; index++) {
|
|
5728
|
+
const char = originalFullText[index];
|
|
5729
|
+
displayOriginalText += breakChars.has(char) ? "\n" : char;
|
|
5730
|
+
}
|
|
5731
|
+
let processedModifiedText = preserveZeroWidthSentinels(displayOriginalText, modifiedText, sentinelMap);
|
|
5732
|
+
processedModifiedText = preserveStructuralBreaks(displayOriginalText, originalFullText, processedModifiedText, breakChars);
|
|
5476
5733
|
tokenToCharMap.forEach((char, tokenString) => {
|
|
5477
5734
|
const escapedToken = tokenString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
5478
5735
|
processedModifiedText = processedModifiedText.replace(new RegExp(escapedToken, "g"), char);
|
|
@@ -5528,6 +5785,124 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5528
5785
|
isParagraphStart: (index) => paragraphStarts.has(index)
|
|
5529
5786
|
};
|
|
5530
5787
|
}
|
|
5788
|
+
function findReconstructionParagraphRange(xmlDoc, originalText) {
|
|
5789
|
+
const paragraphs = getDocumentParagraphs(xmlDoc);
|
|
5790
|
+
if (paragraphs.length === 0) return [];
|
|
5791
|
+
const wanted = normalizeComparisonText(originalText);
|
|
5792
|
+
const paragraphTexts = paragraphs.map(extractParagraphVisibleText);
|
|
5793
|
+
if (!wanted) {
|
|
5794
|
+
const emptyIndex = paragraphTexts.findIndex((text) => text === "");
|
|
5795
|
+
return emptyIndex >= 0 ? [paragraphs[emptyIndex]] : null;
|
|
5796
|
+
}
|
|
5797
|
+
if (paragraphs.length === 1 && paragraphTexts[0] === "") {
|
|
5798
|
+
return paragraphs;
|
|
5799
|
+
}
|
|
5800
|
+
const comparisons = [
|
|
5801
|
+
(text) => text,
|
|
5802
|
+
(text) => text.trim(),
|
|
5803
|
+
(text) => text.replace(/\s+/g, " ").trim()
|
|
5804
|
+
];
|
|
5805
|
+
for (const compare of comparisons) {
|
|
5806
|
+
const expected = compare(wanted);
|
|
5807
|
+
for (let start = 0; start < paragraphs.length; start++) {
|
|
5808
|
+
let combined = "";
|
|
5809
|
+
for (let end = start; end < paragraphs.length; end++) {
|
|
5810
|
+
combined += (end === start ? "" : "\n") + paragraphTexts[end];
|
|
5811
|
+
const candidate = compare(combined);
|
|
5812
|
+
if (candidate === expected) return paragraphs.slice(start, end + 1);
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
}
|
|
5816
|
+
return null;
|
|
5817
|
+
}
|
|
5818
|
+
function normalizeComparisonText(text) {
|
|
5819
|
+
return String(text ?? "").replace(/\r\n?/g, "\n").replace(/\u00a0/g, " ");
|
|
5820
|
+
}
|
|
5821
|
+
function extractParagraphVisibleText(paragraph) {
|
|
5822
|
+
let text = "";
|
|
5823
|
+
const visit = (node) => {
|
|
5824
|
+
for (const child of Array.from(node?.childNodes || [])) {
|
|
5825
|
+
if (child.nodeType !== 1) continue;
|
|
5826
|
+
if (isWordElement(child, "pPr") || isWordElement(child, "del") || isWordElement(child, "moveFrom")) continue;
|
|
5827
|
+
if (isWordElement(child, "t")) text += child.textContent || "";
|
|
5828
|
+
else if (isWordElement(child, "tab")) text += " ";
|
|
5829
|
+
else if (isWordElement(child, "br") || isWordElement(child, "cr")) text += "\n";
|
|
5830
|
+
else if (isWordElement(child, "noBreakHyphen")) text += "\u2011";
|
|
5831
|
+
else visit(child);
|
|
5832
|
+
}
|
|
5833
|
+
};
|
|
5834
|
+
visit(paragraph);
|
|
5835
|
+
return normalizeComparisonText(text);
|
|
5836
|
+
}
|
|
5837
|
+
function preserveStructuralBreaks(displayOriginalText, internalOriginalText, modifiedText, breakChars) {
|
|
5838
|
+
if (breakChars.size === 0) return modifiedText;
|
|
5839
|
+
const diffs = DMP.diff_main(displayOriginalText, modifiedText);
|
|
5840
|
+
let originalOffset = 0;
|
|
5841
|
+
let result = "";
|
|
5842
|
+
for (const [op, text] of diffs) {
|
|
5843
|
+
if (op === 0) {
|
|
5844
|
+
for (let index = 0; index < text.length; index++) {
|
|
5845
|
+
const internalChar = internalOriginalText[originalOffset + index];
|
|
5846
|
+
result += breakChars.has(internalChar) ? internalChar : text[index];
|
|
5847
|
+
}
|
|
5848
|
+
originalOffset += text.length;
|
|
5849
|
+
} else if (op === -1) {
|
|
5850
|
+
originalOffset += text.length;
|
|
5851
|
+
} else {
|
|
5852
|
+
result += text;
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
return result;
|
|
5856
|
+
}
|
|
5857
|
+
function preserveZeroWidthSentinels(displayOriginalText, modifiedText, sentinelMap) {
|
|
5858
|
+
const sentinelsByInternalOffset = /* @__PURE__ */ new Map();
|
|
5859
|
+
sentinelMap.forEach((sentinel) => {
|
|
5860
|
+
if (sentinel.zeroWidth) sentinelsByInternalOffset.set(sentinel.start, sentinel);
|
|
5861
|
+
});
|
|
5862
|
+
if (sentinelsByInternalOffset.size === 0) return modifiedText;
|
|
5863
|
+
let visibleOriginalText = "";
|
|
5864
|
+
let visibleOffset = 0;
|
|
5865
|
+
const sentinelsByVisibleBoundary = /* @__PURE__ */ new Map();
|
|
5866
|
+
for (let internalOffset = 0; internalOffset < displayOriginalText.length; internalOffset++) {
|
|
5867
|
+
const sentinel = sentinelsByInternalOffset.get(internalOffset);
|
|
5868
|
+
if (sentinel) {
|
|
5869
|
+
if (!sentinelsByVisibleBoundary.has(visibleOffset)) sentinelsByVisibleBoundary.set(visibleOffset, []);
|
|
5870
|
+
sentinelsByVisibleBoundary.get(visibleOffset).push({
|
|
5871
|
+
char: displayOriginalText[internalOffset],
|
|
5872
|
+
affinity: sentinel.affinity || "right",
|
|
5873
|
+
emitted: false
|
|
5874
|
+
});
|
|
5875
|
+
continue;
|
|
5876
|
+
}
|
|
5877
|
+
visibleOriginalText += displayOriginalText[internalOffset];
|
|
5878
|
+
visibleOffset++;
|
|
5879
|
+
}
|
|
5880
|
+
const diffs = DMP.diff_main(visibleOriginalText, modifiedText);
|
|
5881
|
+
let originalOffset = 0;
|
|
5882
|
+
let result = "";
|
|
5883
|
+
const emitSentinels = (boundary, affinity) => {
|
|
5884
|
+
const sentinels = sentinelsByVisibleBoundary.get(boundary) || [];
|
|
5885
|
+
for (const sentinel of sentinels) {
|
|
5886
|
+
if (sentinel.emitted || affinity && sentinel.affinity !== affinity) continue;
|
|
5887
|
+
result += sentinel.char;
|
|
5888
|
+
sentinel.emitted = true;
|
|
5889
|
+
}
|
|
5890
|
+
};
|
|
5891
|
+
for (const [op, text] of diffs) {
|
|
5892
|
+
if (op === 1) {
|
|
5893
|
+
emitSentinels(originalOffset, "left");
|
|
5894
|
+
result += text;
|
|
5895
|
+
continue;
|
|
5896
|
+
}
|
|
5897
|
+
for (let index = 0; index < text.length; index++) {
|
|
5898
|
+
emitSentinels(originalOffset);
|
|
5899
|
+
if (op === 0) result += text[index];
|
|
5900
|
+
originalOffset++;
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
emitSentinels(originalOffset);
|
|
5904
|
+
return result;
|
|
5905
|
+
}
|
|
5531
5906
|
function preserveReferencePlaceholders(originalFullText, modifiedText, referenceMap) {
|
|
5532
5907
|
let result = modifiedText;
|
|
5533
5908
|
for (const referenceChar of referenceMap.keys()) {
|
|
@@ -5547,28 +5922,28 @@ function preserveReferencePlaceholders(originalFullText, modifiedText, reference
|
|
|
5547
5922
|
}
|
|
5548
5923
|
return result;
|
|
5549
5924
|
}
|
|
5550
|
-
function processChildNode(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap,
|
|
5551
|
-
if (child
|
|
5552
|
-
return processRunForReconstruction(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap,
|
|
5925
|
+
function processChildNode(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, characterState, breakChars) {
|
|
5926
|
+
if (isWordElement(child, "r")) {
|
|
5927
|
+
return processRunForReconstruction(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, characterState, breakChars);
|
|
5553
5928
|
}
|
|
5554
|
-
if (child
|
|
5929
|
+
if (isWordElement(child, "hyperlink")) {
|
|
5555
5930
|
return processHyperlinkForReconstruction(child, originalFullText, propertyMap);
|
|
5556
5931
|
}
|
|
5557
|
-
if (
|
|
5932
|
+
if (isWordElement(child, "sdt") || isWordElement(child, "oMath") || localNameOf(child) === "oMath" || isWordElement(child, "bookmarkStart") || isWordElement(child, "bookmarkEnd")) {
|
|
5558
5933
|
sentinelMap.push({ start: originalFullText.length, node: child });
|
|
5559
5934
|
return originalFullText + "\uFFFC";
|
|
5560
5935
|
}
|
|
5561
|
-
if (
|
|
5936
|
+
if (isWordElement(child, "commentRangeStart") || isWordElement(child, "commentRangeEnd")) {
|
|
5562
5937
|
sentinelMap.push({ start: originalFullText.length, node: child, isCommentMarker: true });
|
|
5563
5938
|
return originalFullText;
|
|
5564
5939
|
}
|
|
5565
5940
|
return originalFullText;
|
|
5566
5941
|
}
|
|
5567
|
-
function processRunForReconstruction(runElement, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap,
|
|
5942
|
+
function processRunForReconstruction(runElement, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, characterState, breakChars) {
|
|
5568
5943
|
let fullText = originalFullText;
|
|
5569
|
-
const rPr =
|
|
5944
|
+
const rPr = getFirstElementByTagNSOrTag(runElement, NS_W, "rPr");
|
|
5570
5945
|
Array.from(runElement.childNodes).forEach((runChild) => {
|
|
5571
|
-
if (runChild
|
|
5946
|
+
if (isWordElement(runChild, "t")) {
|
|
5572
5947
|
const textContent = runChild.textContent || "";
|
|
5573
5948
|
if (textContent.length > 0) {
|
|
5574
5949
|
propertyMap.push({
|
|
@@ -5578,38 +5953,47 @@ function processRunForReconstruction(runElement, originalFullText, propertyMap,
|
|
|
5578
5953
|
});
|
|
5579
5954
|
fullText += textContent;
|
|
5580
5955
|
}
|
|
5581
|
-
} else if (runChild
|
|
5582
|
-
|
|
5956
|
+
} else if (isWordElement(runChild, "br") || isWordElement(runChild, "cr")) {
|
|
5957
|
+
const char = String.fromCharCode(characterState.nextCharCode++);
|
|
5958
|
+
referenceMap.set(char, runChild);
|
|
5959
|
+
breakChars.add(char);
|
|
5960
|
+
fullText += char;
|
|
5583
5961
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5584
|
-
} else if (runChild
|
|
5962
|
+
} else if (isWordElement(runChild, "tab")) {
|
|
5585
5963
|
fullText += " ";
|
|
5586
5964
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5587
|
-
} else if (runChild
|
|
5965
|
+
} else if (isWordElement(runChild, "noBreakHyphen")) {
|
|
5588
5966
|
fullText += "\u2011";
|
|
5589
5967
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5590
|
-
} else if (["
|
|
5591
|
-
const textBoxContent =
|
|
5592
|
-
const hasTextBox = runChild
|
|
5968
|
+
} else if (["drawing", "pict", "object", "fldChar", "instrText", "sym"].some((name) => isWordElement(runChild, name))) {
|
|
5969
|
+
const textBoxContent = getFirstElementByTagNSOrTag(runChild, NS_W, "txbxContent");
|
|
5970
|
+
const hasTextBox = isWordElement(runChild, "pict") && !!textBoxContent;
|
|
5971
|
+
const isFieldStructure = isWordElement(runChild, "fldChar") || isWordElement(runChild, "instrText");
|
|
5972
|
+
const fieldCharType = isWordElement(runChild, "fldChar") ? runChild.getAttributeNS?.(NS_W, "fldCharType") || runChild.getAttribute("w:fldCharType") || runChild.getAttribute("fldCharType") : null;
|
|
5593
5973
|
sentinelMap.push({
|
|
5594
5974
|
start: fullText.length,
|
|
5595
5975
|
node: runChild,
|
|
5976
|
+
wrapInRun: true,
|
|
5977
|
+
rPr,
|
|
5978
|
+
zeroWidth: isFieldStructure,
|
|
5979
|
+
affinity: fieldCharType === "end" ? "left" : "right",
|
|
5596
5980
|
isTextBox: hasTextBox,
|
|
5597
5981
|
originalContainer: hasTextBox ? textBoxContent : void 0
|
|
5598
5982
|
});
|
|
5599
5983
|
fullText += "\uFFFC";
|
|
5600
5984
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5601
|
-
} else if (runChild
|
|
5602
|
-
const id = runChild
|
|
5985
|
+
} else if (isWordElement(runChild, "footnoteReference") || isWordElement(runChild, "endnoteReference")) {
|
|
5986
|
+
const id = wordAttribute(runChild, "id");
|
|
5603
5987
|
if (id) {
|
|
5604
|
-
const type = runChild
|
|
5988
|
+
const type = isWordElement(runChild, "footnoteReference") ? "FN" : "EN";
|
|
5605
5989
|
const tokenString = `{{__${type}_${id}__}}`;
|
|
5606
|
-
const char = String.fromCharCode(nextCharCode);
|
|
5990
|
+
const char = String.fromCharCode(characterState.nextCharCode++);
|
|
5607
5991
|
referenceMap.set(char, runChild);
|
|
5608
5992
|
tokenToCharMap.set(tokenString, char);
|
|
5609
5993
|
fullText += char;
|
|
5610
5994
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5611
5995
|
}
|
|
5612
|
-
} else if (runChild
|
|
5996
|
+
} else if (isWordElement(runChild, "commentReference")) {
|
|
5613
5997
|
sentinelMap.push({ start: fullText.length, node: runChild, isCommentMarker: true });
|
|
5614
5998
|
}
|
|
5615
5999
|
});
|
|
@@ -5618,9 +6002,9 @@ function processRunForReconstruction(runElement, originalFullText, propertyMap,
|
|
|
5618
6002
|
function processHyperlinkForReconstruction(hyperlinkElement, originalFullText, propertyMap) {
|
|
5619
6003
|
let fullText = originalFullText;
|
|
5620
6004
|
Array.from(hyperlinkElement.childNodes).forEach((hyperlinkChild) => {
|
|
5621
|
-
if (hyperlinkChild
|
|
5622
|
-
const rPr =
|
|
5623
|
-
const texts =
|
|
6005
|
+
if (!isWordElement(hyperlinkChild, "r")) return;
|
|
6006
|
+
const rPr = getFirstElementByTagNSOrTag(hyperlinkChild, NS_W, "rPr");
|
|
6007
|
+
const texts = getElementsByTagNSOrTag(hyperlinkChild, NS_W, "t");
|
|
5624
6008
|
texts.forEach((textNode) => {
|
|
5625
6009
|
const textContent = textNode.textContent || "";
|
|
5626
6010
|
if (textContent.length === 0) return;
|
|
@@ -5640,7 +6024,6 @@ function processHyperlinkForReconstruction(hyperlinkElement, originalFullText, p
|
|
|
5640
6024
|
function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, formatHints, generateRedlines = true) {
|
|
5641
6025
|
const {
|
|
5642
6026
|
paragraphs,
|
|
5643
|
-
paragraphMap,
|
|
5644
6027
|
containerFragments,
|
|
5645
6028
|
sentinelMapByStart,
|
|
5646
6029
|
referenceMap,
|
|
@@ -5663,10 +6046,16 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5663
6046
|
}
|
|
5664
6047
|
let currentOriginalIndex = 0;
|
|
5665
6048
|
let currentInsertOffset = 0;
|
|
6049
|
+
let pendingReplacementStart = null;
|
|
5666
6050
|
const emittedCommentMarkers = /* @__PURE__ */ new WeakSet();
|
|
5667
6051
|
for (const [op, text] of diffs) {
|
|
5668
6052
|
if (op === 0 || op === -1) {
|
|
5669
6053
|
const type = op === 0 ? "equal" : "delete";
|
|
6054
|
+
if (op === 0) {
|
|
6055
|
+
pendingReplacementStart = null;
|
|
6056
|
+
} else if (pendingReplacementStart === null) {
|
|
6057
|
+
pendingReplacementStart = currentOriginalIndex;
|
|
6058
|
+
}
|
|
5670
6059
|
let offset = 0;
|
|
5671
6060
|
while (offset < text.length) {
|
|
5672
6061
|
const chunkStart = currentOriginalIndex + offset;
|
|
@@ -5703,7 +6092,8 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5703
6092
|
continue;
|
|
5704
6093
|
}
|
|
5705
6094
|
if (op === 1) {
|
|
5706
|
-
const
|
|
6095
|
+
const propertyIndex = pendingReplacementStart !== null ? pendingReplacementStart : currentOriginalIndex > 0 && !isParagraphStart(currentOriginalIndex) ? currentOriginalIndex - 1 : currentOriginalIndex;
|
|
6096
|
+
const properties = getRunProperties(propertyIndex);
|
|
5707
6097
|
const appendResult = appendTextToCurrent(
|
|
5708
6098
|
xmlDoc,
|
|
5709
6099
|
text,
|
|
@@ -5726,29 +6116,46 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5726
6116
|
);
|
|
5727
6117
|
currentParagraph = appendResult.currentParagraph;
|
|
5728
6118
|
currentInsertOffset += text.length;
|
|
6119
|
+
pendingReplacementStart = null;
|
|
5729
6120
|
}
|
|
5730
6121
|
}
|
|
6122
|
+
const paragraphSet = new Set(paragraphs);
|
|
6123
|
+
const insertionAnchors = /* @__PURE__ */ new Map();
|
|
6124
|
+
paragraphs.forEach((paragraph) => {
|
|
6125
|
+
const container = paragraph.parentNode;
|
|
6126
|
+
if (!container || insertionAnchors.has(container)) return;
|
|
6127
|
+
let anchor = paragraph.nextSibling;
|
|
6128
|
+
while (anchor && paragraphSet.has(anchor)) anchor = anchor.nextSibling;
|
|
6129
|
+
insertionAnchors.set(container, anchor);
|
|
6130
|
+
});
|
|
5731
6131
|
paragraphs.forEach((paragraph) => {
|
|
5732
6132
|
if (paragraph.parentNode) {
|
|
5733
6133
|
paragraph.parentNode.removeChild(paragraph);
|
|
5734
6134
|
}
|
|
5735
6135
|
});
|
|
6136
|
+
let hasDocumentTarget = false;
|
|
6137
|
+
let serializedDocumentOutput = "";
|
|
5736
6138
|
containerFragments.forEach((fragment, container) => {
|
|
5737
6139
|
const replacement = replacementContainers.get(container);
|
|
5738
6140
|
const target = replacement || container;
|
|
5739
6141
|
if (target.nodeType === 9) {
|
|
5740
|
-
|
|
5741
|
-
if (
|
|
5742
|
-
target.appendChild(firstChild);
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
}
|
|
6142
|
+
hasDocumentTarget = true;
|
|
6143
|
+
if (fragment.childNodes.length === 1) {
|
|
6144
|
+
target.appendChild(fragment.firstChild);
|
|
6145
|
+
} else {
|
|
6146
|
+
serializedDocumentOutput = Array.from(fragment.childNodes).map((node) => serializer.serializeToString(node)).join("");
|
|
5746
6147
|
}
|
|
5747
6148
|
return;
|
|
5748
6149
|
}
|
|
5749
|
-
|
|
6150
|
+
const anchor = replacement ? null : insertionAnchors.get(container);
|
|
6151
|
+
if (anchor && anchor.parentNode === target) {
|
|
6152
|
+
target.insertBefore(fragment, anchor);
|
|
6153
|
+
} else {
|
|
6154
|
+
target.appendChild(fragment);
|
|
6155
|
+
}
|
|
5750
6156
|
});
|
|
5751
|
-
|
|
6157
|
+
const oxml = hasDocumentTarget && serializedDocumentOutput ? serializedDocumentOutput : serializer.serializeToString(xmlDoc);
|
|
6158
|
+
return { oxml, hasChanges: true };
|
|
5752
6159
|
}
|
|
5753
6160
|
function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, currentParagraphRef, containerFragments, sentinelMapByStart, referenceMap, replacementContainers, getParagraphInfo, createNewParagraph, author, formatHints = [], insertOffset = 0, generateRedlines = true, emittedCommentMarkers = /* @__PURE__ */ new WeakSet()) {
|
|
5754
6161
|
let localBaseIndex = baseIndex;
|
|
@@ -5760,7 +6167,7 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5760
6167
|
const commentMarkers = sentinelsAtOffset.filter((sentinel) => sentinel.isCommentMarker && !emittedCommentMarkers.has(sentinel.node));
|
|
5761
6168
|
commentMarkers.forEach((marker) => {
|
|
5762
6169
|
emittedCommentMarkers.add(marker.node);
|
|
5763
|
-
if (marker.node
|
|
6170
|
+
if (isWordElement(marker.node, "commentReference")) {
|
|
5764
6171
|
const run = createWordElement(xmlDoc, "w:r");
|
|
5765
6172
|
run.appendChild(marker.node.cloneNode(true));
|
|
5766
6173
|
localParagraph.appendChild(run);
|
|
@@ -5772,9 +6179,9 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5772
6179
|
const info = getParagraphInfo(localBaseIndex + 1);
|
|
5773
6180
|
const nextParagraph = createNewParagraph(info.pPr);
|
|
5774
6181
|
if (generateRedlines && type === "insert") {
|
|
5775
|
-
markParagraphMarkInserted(xmlDoc,
|
|
6182
|
+
markParagraphMarkInserted(xmlDoc, localParagraph, author);
|
|
5776
6183
|
} else if (generateRedlines && type === "delete") {
|
|
5777
|
-
markParagraphMarkDeleted(xmlDoc,
|
|
6184
|
+
markParagraphMarkDeleted(xmlDoc, localParagraph, author);
|
|
5778
6185
|
}
|
|
5779
6186
|
const fragment = containerFragments.get(info.container);
|
|
5780
6187
|
if (fragment) {
|
|
@@ -5790,13 +6197,20 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5790
6197
|
if (sentinel) {
|
|
5791
6198
|
const clone = sentinel.node.cloneNode(true);
|
|
5792
6199
|
if (sentinel.isTextBox && sentinel.originalContainer) {
|
|
5793
|
-
const newContainer =
|
|
6200
|
+
const newContainer = getFirstElementByTagNSOrTag(clone, NS_W, "txbxContent");
|
|
5794
6201
|
if (newContainer) {
|
|
5795
6202
|
while (newContainer.firstChild) newContainer.removeChild(newContainer.firstChild);
|
|
5796
6203
|
replacementContainers.set(sentinel.originalContainer, newContainer);
|
|
5797
6204
|
}
|
|
5798
6205
|
}
|
|
5799
|
-
|
|
6206
|
+
if (sentinel.wrapInRun) {
|
|
6207
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
6208
|
+
if (sentinel.rPr) run.appendChild(sentinel.rPr.cloneNode(true));
|
|
6209
|
+
run.appendChild(clone);
|
|
6210
|
+
localParagraph.appendChild(run);
|
|
6211
|
+
} else {
|
|
6212
|
+
localParagraph.appendChild(clone);
|
|
6213
|
+
}
|
|
5800
6214
|
}
|
|
5801
6215
|
localBaseIndex++;
|
|
5802
6216
|
if (type !== "delete") localInsertOffset++;
|
|
@@ -5855,12 +6269,24 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5855
6269
|
}
|
|
5856
6270
|
|
|
5857
6271
|
// engine/reconstruction-mode.js
|
|
5858
|
-
function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true) {
|
|
5859
|
-
const
|
|
6272
|
+
function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true, diffOptions = {}) {
|
|
6273
|
+
const selectedParagraphs = findReconstructionParagraphRange(xmlDoc, originalText);
|
|
6274
|
+
if (selectedParagraphs === null) {
|
|
6275
|
+
return withOoxmlSourceType({
|
|
6276
|
+
oxml: serializer.serializeToString(xmlDoc),
|
|
6277
|
+
hasChanges: false,
|
|
6278
|
+
status: "error",
|
|
6279
|
+
error: {
|
|
6280
|
+
code: "PARTIAL_TARGET",
|
|
6281
|
+
message: "Original text did not identify a complete contiguous paragraph range for reconstruction."
|
|
6282
|
+
}
|
|
6283
|
+
});
|
|
6284
|
+
}
|
|
6285
|
+
const mapping = buildReconstructionMapping(xmlDoc, modifiedText, selectedParagraphs);
|
|
5860
6286
|
if (mapping.paragraphs.length === 0) {
|
|
5861
6287
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
5862
6288
|
}
|
|
5863
|
-
const diffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText);
|
|
6289
|
+
const diffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText, diffOptions);
|
|
5864
6290
|
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
5865
6291
|
xmlDoc,
|
|
5866
6292
|
diffs,
|
|
@@ -5889,11 +6315,15 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
5889
6315
|
if (operations.length === 0) {
|
|
5890
6316
|
return noChanges(serializer, xmlDoc);
|
|
5891
6317
|
}
|
|
5892
|
-
const options = {
|
|
6318
|
+
const options = {
|
|
6319
|
+
generateRedlines,
|
|
6320
|
+
author,
|
|
6321
|
+
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
|
|
6322
|
+
};
|
|
5893
6323
|
const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
|
|
5894
6324
|
const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
|
|
5895
|
-
const
|
|
5896
|
-
|
|
6325
|
+
const reconciledDoc = parseOoxmlSafe(wrappedOxml, "application/xml").doc;
|
|
6326
|
+
if (!reconciledDoc) return noChanges(serializer, xmlDoc);
|
|
5897
6327
|
const parseError = getXmlParseError(reconciledDoc);
|
|
5898
6328
|
if (parseError) {
|
|
5899
6329
|
error("[OxmlEngine] Failed to parse reconciled table OOXML:", parseError.textContent);
|
|
@@ -5909,14 +6339,15 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
5909
6339
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
5910
6340
|
}
|
|
5911
6341
|
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
6342
|
+
const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
|
|
5912
6343
|
const tableData = parseTable(modifiedText);
|
|
5913
6344
|
if (!tableData || tableData.rows.length === 0 && tableData.headers.length === 0) {
|
|
5914
6345
|
log("[OxmlEngine] Failed to parse table data from Markdown");
|
|
5915
6346
|
return noChanges(serializer, xmlDoc);
|
|
5916
6347
|
}
|
|
5917
|
-
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author });
|
|
5918
|
-
const
|
|
5919
|
-
|
|
6348
|
+
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator });
|
|
6349
|
+
const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, "application/xml").doc;
|
|
6350
|
+
if (!tableDoc) return noChanges(serializer, xmlDoc);
|
|
5920
6351
|
const tableParseError = getXmlParseError(tableDoc);
|
|
5921
6352
|
if (tableParseError) {
|
|
5922
6353
|
error("[OxmlEngine] Failed to parse generated table OOXML:", tableParseError.textContent);
|
|
@@ -5939,13 +6370,15 @@ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser
|
|
|
5939
6370
|
let firstParagraph = paragraphs[0];
|
|
5940
6371
|
let parent = firstParagraph.parentNode;
|
|
5941
6372
|
if (parent && parent.nodeType === 9) {
|
|
5942
|
-
const wrappedDoc =
|
|
6373
|
+
const wrappedDoc = parseOoxmlSafe(
|
|
5943
6374
|
`<w:document xmlns:w="${NS_W}"><w:body/></w:document>`,
|
|
5944
6375
|
"application/xml"
|
|
5945
|
-
);
|
|
6376
|
+
).doc;
|
|
6377
|
+
if (!wrappedDoc) return noChanges(serializer, workingDoc);
|
|
5946
6378
|
const wrappedBody = getFirstElementByTagNS(wrappedDoc, NS_W, "body");
|
|
5947
6379
|
paragraphs.forEach((p) => wrappedBody.appendChild(wrappedDoc.importNode(p, true)));
|
|
5948
6380
|
workingDoc = wrappedDoc;
|
|
6381
|
+
setRevisionIdAllocatorForDocument(workingDoc, revisionIdAllocator);
|
|
5949
6382
|
paragraphs = getElementsByTagNS(workingDoc, NS_W, "p");
|
|
5950
6383
|
firstParagraph = paragraphs[0];
|
|
5951
6384
|
parent = firstParagraph.parentNode;
|
|
@@ -5966,7 +6399,7 @@ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser
|
|
|
5966
6399
|
}
|
|
5967
6400
|
});
|
|
5968
6401
|
const del = createWordElement(workingDoc, "w:del");
|
|
5969
|
-
const metadata = createRevisionMetadata(author);
|
|
6402
|
+
const metadata = createRevisionMetadata(author, workingDoc);
|
|
5970
6403
|
del.setAttribute("w:id", String(metadata.id));
|
|
5971
6404
|
del.setAttribute("w:author", metadata.author);
|
|
5972
6405
|
del.setAttribute("w:date", metadata.date);
|
|
@@ -6030,17 +6463,19 @@ function authorMatchesNode(node, filter) {
|
|
|
6030
6463
|
return !!nodeAuthor && nodeAuthor === filter.normalizedAuthor;
|
|
6031
6464
|
}
|
|
6032
6465
|
function parseXmlWithWarnings(oxml, parseFailurePrefix) {
|
|
6033
|
-
const
|
|
6034
|
-
const
|
|
6035
|
-
|
|
6036
|
-
|
|
6466
|
+
const parsed = parseOoxmlSafe(oxml, "application/xml");
|
|
6467
|
+
const parseError = parsed.doc ? getXmlParseError(parsed.doc) : null;
|
|
6468
|
+
if (parsed.error || parseError) {
|
|
6469
|
+
const message = parsed.error?.message || parseError?.textContent || "parse error";
|
|
6037
6470
|
return {
|
|
6038
6471
|
xmlDoc: null,
|
|
6039
6472
|
serializer: null,
|
|
6040
|
-
warning: `${parseFailurePrefix}: ${
|
|
6473
|
+
warning: `${parseFailurePrefix}: ${message}`,
|
|
6474
|
+
warnings: parsed.warnings,
|
|
6475
|
+
error: { code: "PARSE_ERROR", message }
|
|
6041
6476
|
};
|
|
6042
6477
|
}
|
|
6043
|
-
return { xmlDoc, serializer: createSerializer(), warning: null };
|
|
6478
|
+
return { xmlDoc: parsed.doc, serializer: createSerializer(), warning: null, warnings: parsed.warnings, error: null };
|
|
6044
6479
|
}
|
|
6045
6480
|
function removeNode(node) {
|
|
6046
6481
|
if (node?.parentNode) {
|
|
@@ -6100,9 +6535,17 @@ function acceptTrackedChangesInOoxml(oxml, options = {}) {
|
|
|
6100
6535
|
}
|
|
6101
6536
|
const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
|
|
6102
6537
|
if (!parseResult.xmlDoc) {
|
|
6103
|
-
return {
|
|
6538
|
+
return {
|
|
6539
|
+
oxml,
|
|
6540
|
+
hasChanges: false,
|
|
6541
|
+
acceptedCount: 0,
|
|
6542
|
+
status: "error",
|
|
6543
|
+
error: parseResult.error,
|
|
6544
|
+
warnings: [...parseResult.warnings || [], parseResult.warning]
|
|
6545
|
+
};
|
|
6104
6546
|
}
|
|
6105
6547
|
const { xmlDoc, serializer } = parseResult;
|
|
6548
|
+
warnings.push(...parseResult.warnings || []);
|
|
6106
6549
|
let acceptedCount = 0;
|
|
6107
6550
|
for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
|
|
6108
6551
|
if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
|
|
@@ -6234,9 +6677,17 @@ function rejectTrackedChangesInOoxml(oxml, options = {}) {
|
|
|
6234
6677
|
}
|
|
6235
6678
|
const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
|
|
6236
6679
|
if (!parseResult.xmlDoc) {
|
|
6237
|
-
return {
|
|
6680
|
+
return {
|
|
6681
|
+
oxml,
|
|
6682
|
+
hasChanges: false,
|
|
6683
|
+
rejectedCount: 0,
|
|
6684
|
+
status: "error",
|
|
6685
|
+
error: parseResult.error,
|
|
6686
|
+
warnings: [...parseResult.warnings || [], parseResult.warning]
|
|
6687
|
+
};
|
|
6238
6688
|
}
|
|
6239
6689
|
const { xmlDoc, serializer } = parseResult;
|
|
6690
|
+
warnings.push(...parseResult.warnings || []);
|
|
6240
6691
|
let rejectedCount = 0;
|
|
6241
6692
|
for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
|
|
6242
6693
|
if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
|
|
@@ -6359,10 +6810,13 @@ function deleteCommentsByAuthorInOoxml(oxml, options = {}) {
|
|
|
6359
6810
|
hasChanges: false,
|
|
6360
6811
|
commentsRemoved: 0,
|
|
6361
6812
|
referencesRemoved: 0,
|
|
6362
|
-
|
|
6813
|
+
status: "error",
|
|
6814
|
+
error: parseResult.error,
|
|
6815
|
+
warnings: [...parseResult.warnings || [], parseResult.warning]
|
|
6363
6816
|
};
|
|
6364
6817
|
}
|
|
6365
6818
|
const { xmlDoc, serializer } = parseResult;
|
|
6819
|
+
warnings.push(...parseResult.warnings || []);
|
|
6366
6820
|
const { targetIds, commentNodes } = collectCommentTargetIds(xmlDoc, filter);
|
|
6367
6821
|
if (filter.allAuthors) {
|
|
6368
6822
|
for (const localName of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
@@ -6385,66 +6839,96 @@ function deleteCommentsByAuthorInOoxml(oxml, options = {}) {
|
|
|
6385
6839
|
|
|
6386
6840
|
// engine/oxml-engine.js
|
|
6387
6841
|
async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
|
|
6842
|
+
const inputOoxml = oxml;
|
|
6843
|
+
let workingOoxml = oxml;
|
|
6844
|
+
originalText = typeof originalText === "string" ? originalText : String(originalText ?? "");
|
|
6845
|
+
modifiedText = typeof modifiedText === "string" ? modifiedText : String(modifiedText ?? "");
|
|
6388
6846
|
const generateRedlines = options.generateRedlines ?? true;
|
|
6389
6847
|
const author = options.author || getDefaultAuthor();
|
|
6390
|
-
const parser = createParser();
|
|
6391
6848
|
const serializer = createSerializer();
|
|
6849
|
+
let parseWarnings = [];
|
|
6850
|
+
const operationWarnings = [];
|
|
6851
|
+
let normalizedExistingRevisions = false;
|
|
6852
|
+
const keepNormalizedNoOp = options.existingRevisions === "accept-all-first-keep-normalized";
|
|
6392
6853
|
const finalize = (result) => {
|
|
6393
6854
|
const withStatus = { ...result };
|
|
6855
|
+
if (normalizedExistingRevisions && withStatus.hasChanges === false && withStatus.status !== "error") {
|
|
6856
|
+
if (keepNormalizedNoOp) {
|
|
6857
|
+
withStatus.oxml = workingOoxml;
|
|
6858
|
+
withStatus.hasChanges = true;
|
|
6859
|
+
withStatus.warnings = [
|
|
6860
|
+
...Array.isArray(withStatus.warnings) ? withStatus.warnings : [],
|
|
6861
|
+
"Existing revisions were accepted before redlining."
|
|
6862
|
+
];
|
|
6863
|
+
} else {
|
|
6864
|
+
withStatus.oxml = inputOoxml;
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
const warnings = [...parseWarnings, ...operationWarnings, ...Array.isArray(withStatus.warnings) ? withStatus.warnings : []];
|
|
6868
|
+
if (warnings.length > 0) {
|
|
6869
|
+
withStatus.warnings = [...new Set(warnings)];
|
|
6870
|
+
}
|
|
6394
6871
|
if (!withStatus.status) {
|
|
6395
6872
|
withStatus.status = withStatus.hasChanges ? "ok" : "no-op";
|
|
6396
6873
|
}
|
|
6397
6874
|
return withOoxmlSourceType(withStatus);
|
|
6398
6875
|
};
|
|
6399
|
-
const
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
const parseError = getXmlParseError(xmlDoc);
|
|
6413
|
-
if (parseError) {
|
|
6414
|
-
error
|
|
6876
|
+
const finalizeUnchanged = () => {
|
|
6877
|
+
if (normalizedExistingRevisions && keepNormalizedNoOp) {
|
|
6878
|
+
return finalize({
|
|
6879
|
+
oxml: workingOoxml,
|
|
6880
|
+
hasChanges: true,
|
|
6881
|
+
warnings: ["Existing revisions were accepted before redlining."]
|
|
6882
|
+
});
|
|
6883
|
+
}
|
|
6884
|
+
return finalize({ oxml: inputOoxml, hasChanges: false });
|
|
6885
|
+
};
|
|
6886
|
+
const parsed = parseOoxmlSafe(inputOoxml, "text/xml");
|
|
6887
|
+
parseWarnings = parsed.warnings;
|
|
6888
|
+
let xmlDoc = parsed.doc;
|
|
6889
|
+
const parseError = xmlDoc ? getXmlParseError(xmlDoc) : null;
|
|
6890
|
+
if (parsed.error || parseError) {
|
|
6891
|
+
const message = parsed.error?.message || parseError?.textContent || "Could not parse OOXML input.";
|
|
6892
|
+
error("[OxmlEngine] XML parse error:", message);
|
|
6415
6893
|
return finalize({
|
|
6416
|
-
oxml,
|
|
6894
|
+
oxml: inputOoxml,
|
|
6417
6895
|
hasChanges: false,
|
|
6418
6896
|
status: "error",
|
|
6419
|
-
error: { code: "PARSE_ERROR", message
|
|
6897
|
+
error: { code: "PARSE_ERROR", message }
|
|
6420
6898
|
});
|
|
6421
6899
|
}
|
|
6422
|
-
|
|
6900
|
+
const revisionIdAllocator = options?._revisionIdAllocator instanceof RevisionIdAllocator ? options._revisionIdAllocator : new RevisionIdAllocator();
|
|
6901
|
+
seedRevisionIdsFromDocument(xmlDoc, revisionIdAllocator);
|
|
6423
6902
|
if (containsTrackedChanges(xmlDoc)) {
|
|
6424
6903
|
const existingRevisionsPolicy = options.existingRevisions || "reject-input";
|
|
6425
|
-
if (existingRevisionsPolicy === "accept-all-first") {
|
|
6904
|
+
if (existingRevisionsPolicy === "accept-all-first" || existingRevisionsPolicy === "accept-all-first-keep-normalized") {
|
|
6426
6905
|
log("[OxmlEngine] Existing revisions detected; accepting all input revisions before redlining");
|
|
6427
|
-
const accepted = acceptTrackedChangesInOoxml(
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6906
|
+
const accepted = acceptTrackedChangesInOoxml(inputOoxml, { allAuthors: true });
|
|
6907
|
+
if (accepted.status === "error") return finalize(accepted);
|
|
6908
|
+
workingOoxml = accepted.oxml;
|
|
6909
|
+
normalizedExistingRevisions = true;
|
|
6910
|
+
const acceptedParsed = parseOoxmlSafe(workingOoxml, "text/xml");
|
|
6911
|
+
parseWarnings.push(...acceptedParsed.warnings);
|
|
6912
|
+
xmlDoc = acceptedParsed.doc;
|
|
6913
|
+
const acceptedParseError = xmlDoc ? getXmlParseError(xmlDoc) : null;
|
|
6914
|
+
if (acceptedParsed.error || acceptedParseError) {
|
|
6915
|
+
const message = acceptedParsed.error?.message || acceptedParseError?.textContent || "Could not parse OOXML after accepting existing revisions.";
|
|
6916
|
+
error("[OxmlEngine] XML parse error after accepting existing revisions:", message);
|
|
6433
6917
|
return finalize({
|
|
6434
|
-
oxml,
|
|
6918
|
+
oxml: inputOoxml,
|
|
6435
6919
|
hasChanges: false,
|
|
6436
6920
|
status: "error",
|
|
6437
6921
|
error: {
|
|
6438
6922
|
code: "PARSE_ERROR",
|
|
6439
|
-
message
|
|
6923
|
+
message
|
|
6440
6924
|
}
|
|
6441
6925
|
});
|
|
6442
6926
|
}
|
|
6443
|
-
seedRevisionIdsFromDocument(xmlDoc);
|
|
6927
|
+
seedRevisionIdsFromDocument(xmlDoc, revisionIdAllocator);
|
|
6444
6928
|
} else {
|
|
6445
6929
|
log("[OxmlEngine] Existing revisions detected; rejecting input per existingRevisions policy");
|
|
6446
6930
|
return finalize({
|
|
6447
|
-
oxml,
|
|
6931
|
+
oxml: inputOoxml,
|
|
6448
6932
|
hasChanges: false,
|
|
6449
6933
|
status: "error",
|
|
6450
6934
|
error: {
|
|
@@ -6458,22 +6942,33 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
6458
6942
|
if (initialTableCellContext.hasTableWrapper && initialTableCellContext.targetParagraph && !options._isolatedTableCell) {
|
|
6459
6943
|
log("[OxmlEngine] Isolating table-cell paragraph before diff");
|
|
6460
6944
|
const isolatedOxml = serializeParagraphOnly(xmlDoc, initialTableCellContext.targetParagraph, serializer);
|
|
6461
|
-
|
|
6945
|
+
const isolatedResult = await applyRedlineToOxml(isolatedOxml, originalText, modifiedText, {
|
|
6462
6946
|
...options,
|
|
6463
6947
|
_isolatedTableCell: true
|
|
6464
6948
|
});
|
|
6949
|
+
if (!isolatedResult.hasChanges && isolatedResult.status === "no-op") {
|
|
6950
|
+
return finalizeUnchanged();
|
|
6951
|
+
}
|
|
6952
|
+
return isolatedResult;
|
|
6953
|
+
}
|
|
6954
|
+
const sanitizedText = options.sanitizeInput === true ? sanitizeAiResponse(modifiedText) : modifiedText;
|
|
6955
|
+
if (sanitizedText !== modifiedText) {
|
|
6956
|
+
operationWarnings.push("Input was sanitized; pass sanitizeInput: false to disable.");
|
|
6465
6957
|
}
|
|
6466
|
-
const sanitizedText = sanitizeAiResponse(modifiedText);
|
|
6467
6958
|
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
6468
6959
|
const hasTextChanges = cleanModifiedText.trim() !== originalText.trim();
|
|
6469
6960
|
const hasFormatHints = formatHints.length > 0;
|
|
6470
6961
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
6471
6962
|
const hasExistingFormatting = existingFormatHints.length > 0;
|
|
6472
6963
|
const visibleText = textSpans.map((span) => textSpanVisibleText(span)).join("");
|
|
6473
|
-
|
|
6964
|
+
const targetFound = originalText.includes("\n") || originalText.includes("\r") ? originalText.split(/\r?\n/).map(normalizeTargetText).filter(Boolean).every((line) => paragraphs.some((paragraph) => {
|
|
6965
|
+
const paragraphText = textSpans.filter((span) => span.paragraph === paragraph).map(textSpanVisibleText).join("");
|
|
6966
|
+
return normalizeTargetText(paragraphText).includes(line);
|
|
6967
|
+
})) : visibleText.includes(originalText.trim()) || visibleText.replace(/[\t\n\u2011]/g, "").includes(originalText.trim().replace(/[\t\n\u2011]/g, "")) || normalizeTargetText(visibleText).includes(normalizeTargetText(originalText));
|
|
6968
|
+
if (hasTextChanges && typeof originalText === "string" && originalText.trim() && !targetFound) {
|
|
6474
6969
|
log("[OxmlEngine] Target text not found in OOXML");
|
|
6475
6970
|
return finalize({
|
|
6476
|
-
oxml,
|
|
6971
|
+
oxml: inputOoxml,
|
|
6477
6972
|
hasChanges: false,
|
|
6478
6973
|
status: "error",
|
|
6479
6974
|
error: {
|
|
@@ -6514,10 +7009,14 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
6514
7009
|
);
|
|
6515
7010
|
};
|
|
6516
7011
|
log(`[OxmlEngine] Text changes: ${hasTextChanges}, New format hints: ${formatHints.length}, Existing format hints: ${existingFormatHints.length}`);
|
|
6517
|
-
const needsFormatRemoval = !hasTextChanges && !hasFormatHints && hasExistingFormatting;
|
|
7012
|
+
const needsFormatRemoval = options.removeFormatting === true && !hasTextChanges && !hasFormatHints && hasExistingFormatting;
|
|
6518
7013
|
if (!hasTextChanges && !hasFormatHints && !hasExistingFormatting) {
|
|
6519
7014
|
log("[OxmlEngine] No text changes, no format hints, and no existing formatting detected");
|
|
6520
|
-
return
|
|
7015
|
+
return finalizeUnchanged();
|
|
7016
|
+
}
|
|
7017
|
+
if (!hasTextChanges && !hasFormatHints && hasExistingFormatting && !needsFormatRemoval) {
|
|
7018
|
+
log("[OxmlEngine] No text or explicit formatting changes; preserving existing formatting");
|
|
7019
|
+
return finalizeUnchanged();
|
|
6521
7020
|
}
|
|
6522
7021
|
if (needsFormatRemoval) {
|
|
6523
7022
|
log("[OxmlEngine] Format REMOVAL detected: applying surgical replacement in OOXML");
|
|
@@ -6577,50 +7076,78 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
6577
7076
|
const isTargetList = isListTargetLoose(cleanModifiedText);
|
|
6578
7077
|
const tableCellContext = initialTableCellContext;
|
|
6579
7078
|
log(`[OxmlEngine] Mode: ${hasTables ? "SURGICAL" : "RECONSTRUCTION"}, formatHints: ${formatHints.length}, isMarkdownTable: ${isMarkdownTable}, isTargetList: ${isTargetList}, isTableCellParagraph: ${tableCellContext.isTableCellParagraph}`);
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
7079
|
+
try {
|
|
7080
|
+
if (isMarkdownTable && !hasTables) {
|
|
7081
|
+
log("[OxmlEngine] Text-to-table transformation: generating new table from Markdown");
|
|
7082
|
+
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
7083
|
+
}
|
|
7084
|
+
if (hasTables && isMarkdownTable) {
|
|
7085
|
+
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
7086
|
+
}
|
|
7087
|
+
if (hasTables) {
|
|
7088
|
+
const surgicalTarget = tableCellContext.hasTableWrapper && tableCellContext.targetParagraph ? tableCellContext.targetParagraph : null;
|
|
7089
|
+
if (surgicalTarget) {
|
|
7090
|
+
log("[OxmlEngine] Table cell edit: scoping surgical mode to target paragraph");
|
|
7091
|
+
}
|
|
7092
|
+
const result = applySurgicalMode(
|
|
7093
|
+
xmlDoc,
|
|
7094
|
+
originalText,
|
|
7095
|
+
cleanModifiedText,
|
|
7096
|
+
serializer,
|
|
7097
|
+
author,
|
|
7098
|
+
formatHints,
|
|
7099
|
+
generateRedlines,
|
|
7100
|
+
surgicalTarget
|
|
7101
|
+
);
|
|
7102
|
+
if (tableCellContext.hasTableWrapper && result.hasChanges && tableCellContext.targetParagraph) {
|
|
7103
|
+
log("[OxmlEngine] Stripping table wrapper for table cell paragraph (surgical mode)");
|
|
7104
|
+
return finalize({ oxml: serializeParagraphOnly(xmlDoc, tableCellContext.targetParagraph, serializer), hasChanges: true });
|
|
7105
|
+
}
|
|
7106
|
+
return finalize(result);
|
|
7107
|
+
}
|
|
7108
|
+
if (isTargetList) {
|
|
7109
|
+
log("[OxmlEngine] \u{1F3AF} Using reconciliation pipeline for list generation");
|
|
7110
|
+
const pipeline = new ReconciliationPipeline({
|
|
7111
|
+
author,
|
|
7112
|
+
generateRedlines,
|
|
7113
|
+
revisionIdAllocator
|
|
7114
|
+
});
|
|
7115
|
+
const result = await pipeline.execute(workingOoxml, sanitizedText, { xmlDoc });
|
|
7116
|
+
if (result.error?.code === "DIFF_TOKEN_LIMIT") {
|
|
7117
|
+
return finalize({ oxml: inputOoxml, hasChanges: false, status: "error", error: result.error });
|
|
7118
|
+
}
|
|
7119
|
+
if (result.isValid && result.ooxml && result.ooxml !== workingOoxml) {
|
|
7120
|
+
const includeNumbering = result.includeNumbering === true;
|
|
7121
|
+
log(`[OxmlEngine] Wrapping list OOXML with numbering definitions, includeNumbering=${includeNumbering}`);
|
|
7122
|
+
const wrapped = wrapInDocumentFragment(result.ooxml, {
|
|
7123
|
+
includeNumbering,
|
|
7124
|
+
numberingXml: result.numberingXml
|
|
7125
|
+
});
|
|
7126
|
+
log(`[OxmlEngine] \u2705 Wrapped OOXML length: ${wrapped.length}`);
|
|
7127
|
+
return finalize({ oxml: wrapped, hasChanges: true });
|
|
7128
|
+
}
|
|
7129
|
+
return finalizeUnchanged();
|
|
6591
7130
|
}
|
|
6592
|
-
|
|
7131
|
+
return finalize(applyReconstructionMode(
|
|
6593
7132
|
xmlDoc,
|
|
6594
7133
|
originalText,
|
|
6595
7134
|
cleanModifiedText,
|
|
6596
7135
|
serializer,
|
|
6597
7136
|
author,
|
|
6598
7137
|
formatHints,
|
|
6599
|
-
generateRedlines
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
if (
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
if (isTargetList) {
|
|
6609
|
-
log("[OxmlEngine] \u{1F3AF} Using reconciliation pipeline for list generation");
|
|
6610
|
-
const pipeline = new ReconciliationPipeline({ author, generateRedlines });
|
|
6611
|
-
const result = await pipeline.execute(oxml, modifiedText, { xmlDoc });
|
|
6612
|
-
if (result.isValid && result.ooxml && result.ooxml !== oxml) {
|
|
6613
|
-
log(`[OxmlEngine] Wrapping list OOXML with numbering definitions, includeNumbering=${result.includeNumbering}`);
|
|
6614
|
-
const wrapped = wrapInDocumentFragment(result.ooxml, {
|
|
6615
|
-
includeNumbering: result.includeNumbering ?? true,
|
|
6616
|
-
numberingXml: result.numberingXml
|
|
7138
|
+
generateRedlines
|
|
7139
|
+
));
|
|
7140
|
+
} catch (caught) {
|
|
7141
|
+
if (isDiffTokenLimitError(caught)) {
|
|
7142
|
+
return finalize({
|
|
7143
|
+
oxml: inputOoxml,
|
|
7144
|
+
hasChanges: false,
|
|
7145
|
+
status: "error",
|
|
7146
|
+
error: { code: caught.code, message: caught.message }
|
|
6617
7147
|
});
|
|
6618
|
-
log(`[OxmlEngine] \u2705 Wrapped OOXML length: ${wrapped.length}`);
|
|
6619
|
-
return finalize({ oxml: wrapped, hasChanges: true });
|
|
6620
7148
|
}
|
|
6621
|
-
|
|
7149
|
+
throw caught;
|
|
6622
7150
|
}
|
|
6623
|
-
return finalize(applyReconstructionMode(xmlDoc, originalText, cleanModifiedText, serializer, author, formatHints, generateRedlines));
|
|
6624
7151
|
}
|
|
6625
7152
|
function normalizeTargetText(text) {
|
|
6626
7153
|
return String(text || "").replace(/[\t\n\u2011]/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -6634,15 +7161,13 @@ function textSpanVisibleText(span) {
|
|
|
6634
7161
|
return node?.textContent || "";
|
|
6635
7162
|
}
|
|
6636
7163
|
function sanitizeAiResponse(text) {
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
cleaned = cleaned.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n");
|
|
6642
|
-
return cleaned;
|
|
7164
|
+
return String(text ?? "").replace(
|
|
7165
|
+
/^(?:Here is the redline:|Here is the text:|Sure, I can help:|Here's the updated text:)[ \t]*\r?\n/i,
|
|
7166
|
+
""
|
|
7167
|
+
);
|
|
6643
7168
|
}
|
|
6644
7169
|
function parseOoxml(ooxmlString) {
|
|
6645
|
-
return
|
|
7170
|
+
return parseOoxmlSafe(ooxmlString, "application/xml").doc;
|
|
6646
7171
|
}
|
|
6647
7172
|
function serializeOoxml(doc) {
|
|
6648
7173
|
return serializeXml(doc);
|
|
@@ -6652,6 +7177,11 @@ function serializeOoxml(doc) {
|
|
|
6652
7177
|
function toArray(nodeList) {
|
|
6653
7178
|
return Array.from(nodeList || []);
|
|
6654
7179
|
}
|
|
7180
|
+
function createTargetNotFoundError(message) {
|
|
7181
|
+
const error2 = new Error(message);
|
|
7182
|
+
error2.code = "TARGET_NOT_FOUND";
|
|
7183
|
+
return error2;
|
|
7184
|
+
}
|
|
6655
7185
|
var WORD_MAIN_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
6656
7186
|
function getElementsByLocalName(node, localName) {
|
|
6657
7187
|
if (!node) return [];
|
|
@@ -6665,8 +7195,22 @@ function getElementsByLocalName(node, localName) {
|
|
|
6665
7195
|
return toArray(node.getElementsByTagName(localName));
|
|
6666
7196
|
}
|
|
6667
7197
|
function toParagraphText(paragraph) {
|
|
6668
|
-
|
|
6669
|
-
|
|
7198
|
+
let text = "";
|
|
7199
|
+
const visit = (node) => {
|
|
7200
|
+
for (const child of toArray(node?.childNodes)) {
|
|
7201
|
+
if (child?.nodeType !== 1) continue;
|
|
7202
|
+
const localName = String(child.localName || child.nodeName || "").replace(/^.*:/, "");
|
|
7203
|
+
if (localName === "t") {
|
|
7204
|
+
text += child.textContent || "";
|
|
7205
|
+
} else if (localName === "tab") {
|
|
7206
|
+
text += " ";
|
|
7207
|
+
} else {
|
|
7208
|
+
visit(child);
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
};
|
|
7212
|
+
visit(paragraph);
|
|
7213
|
+
return text;
|
|
6670
7214
|
}
|
|
6671
7215
|
function getParagraphText(paragraph) {
|
|
6672
7216
|
if (!paragraph) return "";
|
|
@@ -6822,9 +7366,9 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
6822
7366
|
const fuzzyMatch = findParagraphByBestTextMatch(xmlDoc, cleanTargetText, { onInfo });
|
|
6823
7367
|
if (fuzzyMatch) return { paragraph: fuzzyMatch, resolvedBy: "fuzzy_text" };
|
|
6824
7368
|
}
|
|
6825
|
-
if (cleanTargetText) throw
|
|
6826
|
-
if (parsedRef) throw
|
|
6827
|
-
throw
|
|
7369
|
+
if (cleanTargetText) throw createTargetNotFoundError(`Target paragraph not found: "${cleanTargetText}"`);
|
|
7370
|
+
if (parsedRef) throw createTargetNotFoundError(`Target paragraph reference not found: [P${parsedRef}]`);
|
|
7371
|
+
throw createTargetNotFoundError('Operation target missing: provide "target" text or "targetRef" ([P#]).');
|
|
6828
7372
|
}
|
|
6829
7373
|
function isParagraphInTable(paragraph) {
|
|
6830
7374
|
return !!findContainingWordElement(paragraph, "tbl");
|
|
@@ -6911,7 +7455,9 @@ function resolveTargetParagraphWithSnapshot(xmlDoc, options = {}) {
|
|
|
6911
7455
|
onInfo(`[Target] [P${parsedRef}] appears stale after prior edits; using strict text rematch for ${opType}.`);
|
|
6912
7456
|
return { paragraph: bestCandidate.paragraph, resolvedBy: "strict_text_after_ref_drift" };
|
|
6913
7457
|
}
|
|
6914
|
-
|
|
7458
|
+
throw createTargetNotFoundError(
|
|
7459
|
+
`Target paragraph [P${parsedRef}] no longer matches its batch-start anchor.`
|
|
7460
|
+
);
|
|
6915
7461
|
}
|
|
6916
7462
|
function resolveParagraphRangeByRefs(xmlDoc, startRef, endRef, options = {}) {
|
|
6917
7463
|
if (!xmlDoc || !startRef || !endRef) return null;
|
|
@@ -7037,6 +7583,9 @@ function shouldPromoteBulletInsertionsToChildDepth(parsedItems, normalizedTarget
|
|
|
7037
7583
|
return isNormalizedTextEqual(firstItem.text, normalizedTargetText);
|
|
7038
7584
|
}
|
|
7039
7585
|
if (firstItem?.kind === "list" && firstItem.markerType === "numbered") {
|
|
7586
|
+
const firstLevel = firstItem.level || 0;
|
|
7587
|
+
const alreadyIndented = trailingListItems.some((item) => (item.level || 0) > firstLevel);
|
|
7588
|
+
if (alreadyIndented) return false;
|
|
7040
7589
|
return isNormalizedTextEqual(firstItem.text, normalizedTargetText);
|
|
7041
7590
|
}
|
|
7042
7591
|
return false;
|
|
@@ -7479,10 +8028,7 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
7479
8028
|
if (!ownerDoc) continue;
|
|
7480
8029
|
let pPr = getDirectWordChild2(paragraph, "pPr");
|
|
7481
8030
|
if (!pPr) {
|
|
7482
|
-
pPr = ownerDoc
|
|
7483
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7484
|
-
"w:pPr"
|
|
7485
|
-
);
|
|
8031
|
+
pPr = createWordElement(ownerDoc, "w:pPr");
|
|
7486
8032
|
paragraph.insertBefore(pPr, paragraph.firstChild);
|
|
7487
8033
|
}
|
|
7488
8034
|
if (clearParagraphPropertyChanges) {
|
|
@@ -7495,27 +8041,18 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
7495
8041
|
}
|
|
7496
8042
|
let numPr = getDirectWordChild2(pPr, "numPr");
|
|
7497
8043
|
if (!numPr) {
|
|
7498
|
-
numPr = ownerDoc
|
|
7499
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7500
|
-
"w:numPr"
|
|
7501
|
-
);
|
|
8044
|
+
numPr = createWordElement(ownerDoc, "w:numPr");
|
|
7502
8045
|
pPr.appendChild(numPr);
|
|
7503
8046
|
}
|
|
7504
8047
|
let ilvlEl = getDirectWordChild2(numPr, "ilvl");
|
|
7505
8048
|
if (!ilvlEl) {
|
|
7506
|
-
ilvlEl = ownerDoc
|
|
7507
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7508
|
-
"w:ilvl"
|
|
7509
|
-
);
|
|
8049
|
+
ilvlEl = createWordElement(ownerDoc, "w:ilvl");
|
|
7510
8050
|
numPr.appendChild(ilvlEl);
|
|
7511
8051
|
}
|
|
7512
8052
|
ilvlEl.setAttribute("w:val", String(ilvl));
|
|
7513
8053
|
let numIdEl = getDirectWordChild2(numPr, "numId");
|
|
7514
8054
|
if (!numIdEl) {
|
|
7515
|
-
numIdEl = ownerDoc
|
|
7516
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7517
|
-
"w:numId"
|
|
7518
|
-
);
|
|
8055
|
+
numIdEl = createWordElement(ownerDoc, "w:numId");
|
|
7519
8056
|
numPr.appendChild(numIdEl);
|
|
7520
8057
|
}
|
|
7521
8058
|
numIdEl.setAttribute("w:val", String(numId));
|
|
@@ -7524,8 +8061,8 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
7524
8061
|
return updated;
|
|
7525
8062
|
}
|
|
7526
8063
|
function getFirstParagraphFromOxml(oxml) {
|
|
7527
|
-
const
|
|
7528
|
-
|
|
8064
|
+
const doc = parseOoxmlSafe(oxml, "application/xml").doc;
|
|
8065
|
+
if (!doc) return null;
|
|
7529
8066
|
const parseError = getXmlParseError(doc);
|
|
7530
8067
|
if (parseError) return null;
|
|
7531
8068
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
@@ -7551,8 +8088,8 @@ function setElementVal(element, value) {
|
|
|
7551
8088
|
element.setAttribute("w:val", String(value));
|
|
7552
8089
|
}
|
|
7553
8090
|
function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
7554
|
-
const
|
|
7555
|
-
|
|
8091
|
+
const doc = parseOoxmlSafe(oxml, "application/xml").doc;
|
|
8092
|
+
if (!doc) return null;
|
|
7556
8093
|
const parseError = getXmlParseError(doc);
|
|
7557
8094
|
if (parseError) return null;
|
|
7558
8095
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
@@ -7568,9 +8105,9 @@ function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
|
7568
8105
|
function applyStartOverrideToNumberingXml(numberingXml, targetNumId, startAt, options = {}) {
|
|
7569
8106
|
if (!numberingXml || !targetNumId || !Number.isInteger(startAt) || startAt < 1) return numberingXml;
|
|
7570
8107
|
const setAbstractStartOverride = options.setAbstractStartOverride !== false;
|
|
7571
|
-
const parser = createParser();
|
|
7572
8108
|
const serializer = createSerializer();
|
|
7573
|
-
const numberingDoc =
|
|
8109
|
+
const numberingDoc = parseOoxmlSafe(numberingXml, "application/xml").doc;
|
|
8110
|
+
if (!numberingDoc) return numberingXml;
|
|
7574
8111
|
const parseError = getXmlParseError(numberingDoc);
|
|
7575
8112
|
if (parseError) return numberingXml;
|
|
7576
8113
|
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
@@ -7586,19 +8123,13 @@ function applyStartOverrideToNumberingXml(numberingXml, targetNumId, startAt, op
|
|
|
7586
8123
|
return ilvl === 0;
|
|
7587
8124
|
}) || null;
|
|
7588
8125
|
if (!lvlOverride) {
|
|
7589
|
-
lvlOverride = numberingDoc
|
|
7590
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7591
|
-
"w:lvlOverride"
|
|
7592
|
-
);
|
|
8126
|
+
lvlOverride = createWordElement(numberingDoc, "w:lvlOverride");
|
|
7593
8127
|
lvlOverride.setAttribute("w:ilvl", "0");
|
|
7594
8128
|
target.appendChild(lvlOverride);
|
|
7595
8129
|
}
|
|
7596
8130
|
let startOverride = Array.from(lvlOverride.getElementsByTagNameNS("*", "startOverride"))[0] || null;
|
|
7597
8131
|
if (!startOverride) {
|
|
7598
|
-
startOverride = numberingDoc
|
|
7599
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7600
|
-
"w:startOverride"
|
|
7601
|
-
);
|
|
8132
|
+
startOverride = createWordElement(numberingDoc, "w:startOverride");
|
|
7602
8133
|
lvlOverride.appendChild(startOverride);
|
|
7603
8134
|
}
|
|
7604
8135
|
setElementVal(startOverride, startAt);
|
|
@@ -7614,19 +8145,13 @@ function applyStartOverrideToNumberingXml(numberingXml, targetNumId, startAt, op
|
|
|
7614
8145
|
return ilvl === 0;
|
|
7615
8146
|
}) || null;
|
|
7616
8147
|
if (!lvl) {
|
|
7617
|
-
lvl = numberingDoc
|
|
7618
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7619
|
-
"w:lvl"
|
|
7620
|
-
);
|
|
8148
|
+
lvl = createWordElement(numberingDoc, "w:lvl");
|
|
7621
8149
|
lvl.setAttribute("w:ilvl", "0");
|
|
7622
8150
|
abstractNum.appendChild(lvl);
|
|
7623
8151
|
}
|
|
7624
8152
|
let startNode = Array.from(lvl.getElementsByTagNameNS("*", "start"))[0] || null;
|
|
7625
8153
|
if (!startNode) {
|
|
7626
|
-
startNode = numberingDoc
|
|
7627
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7628
|
-
"w:start"
|
|
7629
|
-
);
|
|
8154
|
+
startNode = createWordElement(numberingDoc, "w:start");
|
|
7630
8155
|
lvl.insertBefore(startNode, lvl.firstChild);
|
|
7631
8156
|
}
|
|
7632
8157
|
setElementVal(startNode, startAt);
|
|
@@ -7718,20 +8243,20 @@ async function executeSingleLineListStructuralFallback(plan, options = {}) {
|
|
|
7718
8243
|
// core/redline-validation.js
|
|
7719
8244
|
var REVISION_ID_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "rPrChange", "pPrChange"]);
|
|
7720
8245
|
var REVISION_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
|
|
7721
|
-
function
|
|
8246
|
+
function localNameOf2(node) {
|
|
7722
8247
|
return String(node?.localName || node?.nodeName || "").replace(/^.*:/, "");
|
|
7723
8248
|
}
|
|
7724
8249
|
function elementsByLocalName(root, name) {
|
|
7725
|
-
return Array.from(root.getElementsByTagName("*")).filter((el) =>
|
|
8250
|
+
return Array.from(root.getElementsByTagName("*")).filter((el) => localNameOf2(el) === name);
|
|
7726
8251
|
}
|
|
7727
|
-
function
|
|
8252
|
+
function wordAttribute2(node, name) {
|
|
7728
8253
|
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
7729
8254
|
}
|
|
7730
8255
|
function xmlSpaceAttribute(node) {
|
|
7731
8256
|
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
7732
8257
|
}
|
|
7733
8258
|
function isParagraphMarkRevision(node) {
|
|
7734
|
-
return
|
|
8259
|
+
return localNameOf2(node.parentNode) === "rPr";
|
|
7735
8260
|
}
|
|
7736
8261
|
function parseOoxmlForValidation(oxml) {
|
|
7737
8262
|
const attempt = (xml) => {
|
|
@@ -7767,13 +8292,28 @@ function validateRedlineOoxml(oxml) {
|
|
|
7767
8292
|
const insElements = elementsByLocalName(doc, "ins");
|
|
7768
8293
|
const delElements = elementsByLocalName(doc, "del");
|
|
7769
8294
|
const revisions = insElements.concat(delElements);
|
|
8295
|
+
for (const paragraph of elementsByLocalName(doc, "p")) {
|
|
8296
|
+
const nested = Array.from(paragraph.getElementsByTagName("*")).find((el) => el !== paragraph && localNameOf2(el) === "p");
|
|
8297
|
+
if (nested) {
|
|
8298
|
+
addIssue("NESTED_PARAGRAPH", "error", `<${paragraph.nodeName}> contains nested <${nested.nodeName}>.`);
|
|
8299
|
+
}
|
|
8300
|
+
}
|
|
8301
|
+
for (const body of elementsByLocalName(doc, "body")) {
|
|
8302
|
+
const children = Array.from(body.childNodes || []).filter((child) => child.nodeType === 1);
|
|
8303
|
+
const sectPrIndexes = children.map((child, index) => localNameOf2(child) === "sectPr" ? index : -1).filter((index) => index >= 0);
|
|
8304
|
+
if (sectPrIndexes.length > 1) {
|
|
8305
|
+
addIssue("MULTIPLE_BODY_SECTPR", "error", "<w:body> contains multiple direct <w:sectPr> elements.");
|
|
8306
|
+
} else if (sectPrIndexes.length === 1 && sectPrIndexes[0] !== children.length - 1) {
|
|
8307
|
+
addIssue("SECTPR_NOT_LAST", "error", "<w:sectPr> is not the last element child of <w:body>.");
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
7770
8310
|
for (const revision of revisions) {
|
|
7771
|
-
const nested = Array.from(revision.getElementsByTagName("*")).filter((el) => el !== revision && ["ins", "del"].includes(
|
|
8311
|
+
const nested = Array.from(revision.getElementsByTagName("*")).filter((el) => el !== revision && ["ins", "del"].includes(localNameOf2(el)));
|
|
7772
8312
|
if (nested.length > 0) {
|
|
7773
8313
|
addIssue(
|
|
7774
8314
|
"NESTED_REVISION",
|
|
7775
8315
|
"error",
|
|
7776
|
-
`<${revision.nodeName}> (w:id="${
|
|
8316
|
+
`<${revision.nodeName}> (w:id="${wordAttribute2(revision, "id")}") contains nested <${nested[0].nodeName}>.`
|
|
7777
8317
|
);
|
|
7778
8318
|
}
|
|
7779
8319
|
}
|
|
@@ -7783,15 +8323,15 @@ function validateRedlineOoxml(oxml) {
|
|
|
7783
8323
|
addIssue(
|
|
7784
8324
|
"DEL_CONTAINS_T",
|
|
7785
8325
|
"error",
|
|
7786
|
-
`<w:del> (w:id="${
|
|
8326
|
+
`<w:del> (w:id="${wordAttribute2(del, "id")}") contains <w:t>; deleted text must use <w:delText>.`
|
|
7787
8327
|
);
|
|
7788
8328
|
}
|
|
7789
8329
|
}
|
|
7790
8330
|
for (const revision of revisions) {
|
|
7791
8331
|
const missing = [];
|
|
7792
|
-
if (!
|
|
7793
|
-
if (!
|
|
7794
|
-
if (!REVISION_DATE_PATTERN.test(
|
|
8332
|
+
if (!wordAttribute2(revision, "id")) missing.push("w:id");
|
|
8333
|
+
if (!wordAttribute2(revision, "author")) missing.push("w:author");
|
|
8334
|
+
if (!REVISION_DATE_PATTERN.test(wordAttribute2(revision, "date"))) missing.push("w:date");
|
|
7795
8335
|
if (missing.length > 0) {
|
|
7796
8336
|
addIssue(
|
|
7797
8337
|
"MISSING_REVISION_METADATA",
|
|
@@ -7802,8 +8342,8 @@ function validateRedlineOoxml(oxml) {
|
|
|
7802
8342
|
}
|
|
7803
8343
|
const seenIds = /* @__PURE__ */ new Set();
|
|
7804
8344
|
for (const node of Array.from(doc.getElementsByTagName("*"))) {
|
|
7805
|
-
if (!REVISION_ID_ELEMENTS.has(
|
|
7806
|
-
const id =
|
|
8345
|
+
if (!REVISION_ID_ELEMENTS.has(localNameOf2(node))) continue;
|
|
8346
|
+
const id = wordAttribute2(node, "id");
|
|
7807
8347
|
if (!id) continue;
|
|
7808
8348
|
if (seenIds.has(id)) {
|
|
7809
8349
|
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
@@ -7831,7 +8371,7 @@ function validateRedlineOoxml(oxml) {
|
|
|
7831
8371
|
addIssue(
|
|
7832
8372
|
"EMPTY_REVISION_WRAPPER",
|
|
7833
8373
|
"warning",
|
|
7834
|
-
`<${revision.nodeName}> (w:id="${
|
|
8374
|
+
`<${revision.nodeName}> (w:id="${wordAttribute2(revision, "id")}") wraps no content.`
|
|
7835
8375
|
);
|
|
7836
8376
|
}
|
|
7837
8377
|
}
|
|
@@ -8270,20 +8810,9 @@ function hasParserError(doc) {
|
|
|
8270
8810
|
return !!getXmlParseError(doc);
|
|
8271
8811
|
}
|
|
8272
8812
|
function parseWordOoxml(ooxml) {
|
|
8273
|
-
const
|
|
8274
|
-
if (
|
|
8275
|
-
|
|
8276
|
-
}
|
|
8277
|
-
try {
|
|
8278
|
-
const parser = createParser();
|
|
8279
|
-
const doc = parser.parseFromString(source, "application/xml");
|
|
8280
|
-
if (hasParserError(doc)) {
|
|
8281
|
-
return null;
|
|
8282
|
-
}
|
|
8283
|
-
return doc;
|
|
8284
|
-
} catch {
|
|
8285
|
-
return null;
|
|
8286
|
-
}
|
|
8813
|
+
const parsed = parseOoxmlSafe(ooxml, "application/xml");
|
|
8814
|
+
if (parsed.error || hasParserError(parsed.doc)) return { ...parsed, doc: null };
|
|
8815
|
+
return parsed;
|
|
8287
8816
|
}
|
|
8288
8817
|
function getWordParagraphs(doc) {
|
|
8289
8818
|
if (!doc) return [];
|
|
@@ -8438,25 +8967,37 @@ function paragraphToMarkdown(paragraph) {
|
|
|
8438
8967
|
return normalizedInline;
|
|
8439
8968
|
}
|
|
8440
8969
|
function ingestWordOoxmlToPlainText(ooxml) {
|
|
8441
|
-
|
|
8442
|
-
|
|
8970
|
+
return ingestWordOoxmlToPlainTextResult(ooxml).text;
|
|
8971
|
+
}
|
|
8972
|
+
function ingestWordOoxmlToPlainTextResult(ooxml) {
|
|
8973
|
+
const parsed = parseWordOoxml(ooxml);
|
|
8974
|
+
if (!parsed.doc) {
|
|
8975
|
+
return { text: "", status: "error", error: parsed.error, warnings: parsed.warnings };
|
|
8976
|
+
}
|
|
8977
|
+
const doc = parsed.doc;
|
|
8443
8978
|
const paragraphs = getWordParagraphs(doc);
|
|
8444
8979
|
if (paragraphs.length === 0) {
|
|
8445
8980
|
const fallback = normalizeInlineWhitespace(doc.documentElement?.textContent || "");
|
|
8446
|
-
return fallback;
|
|
8981
|
+
return { text: fallback, status: "ok", warnings: parsed.warnings };
|
|
8447
8982
|
}
|
|
8448
8983
|
const lines = paragraphs.map(paragraphToPlainText);
|
|
8449
|
-
return lines.join("\n\n").trim();
|
|
8984
|
+
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
8450
8985
|
}
|
|
8451
8986
|
function ingestWordOoxmlToMarkdown(ooxml) {
|
|
8452
|
-
|
|
8453
|
-
|
|
8987
|
+
return ingestWordOoxmlToMarkdownResult(ooxml).text;
|
|
8988
|
+
}
|
|
8989
|
+
function ingestWordOoxmlToMarkdownResult(ooxml) {
|
|
8990
|
+
const parsed = parseWordOoxml(ooxml);
|
|
8991
|
+
if (!parsed.doc) {
|
|
8992
|
+
return { text: "", status: "error", error: parsed.error, warnings: parsed.warnings };
|
|
8993
|
+
}
|
|
8994
|
+
const doc = parsed.doc;
|
|
8454
8995
|
const paragraphs = getWordParagraphs(doc);
|
|
8455
8996
|
if (paragraphs.length === 0) {
|
|
8456
|
-
return "";
|
|
8997
|
+
return { text: "", status: "ok", warnings: parsed.warnings };
|
|
8457
8998
|
}
|
|
8458
8999
|
const lines = paragraphs.map(paragraphToMarkdown);
|
|
8459
|
-
return lines.join("\n\n").trim();
|
|
9000
|
+
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
8460
9001
|
}
|
|
8461
9002
|
|
|
8462
9003
|
// services/comment-builders.js
|
|
@@ -8628,12 +9169,12 @@ function injectMarkersIntoParagraph(xmlDoc, paragraph, textToFind, commentId, pa
|
|
|
8628
9169
|
var PKG_NS = "http://schemas.microsoft.com/office/2006/xmlPackage";
|
|
8629
9170
|
var RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
8630
9171
|
function injectCommentsIntoPackage(packageOxml, commentsXml) {
|
|
8631
|
-
const parser = createParser();
|
|
8632
9172
|
const serializer = createSerializer();
|
|
8633
|
-
const
|
|
8634
|
-
const
|
|
8635
|
-
|
|
8636
|
-
|
|
9173
|
+
const packageParsed = parseOoxmlSafe(packageOxml, "text/xml");
|
|
9174
|
+
const pkgDoc = packageParsed.doc;
|
|
9175
|
+
const parseError = pkgDoc ? getXmlParseError(pkgDoc) : null;
|
|
9176
|
+
if (packageParsed.error || parseError) {
|
|
9177
|
+
error("[CommentEngine] Failed to parse package:", packageParsed.error?.message || parseError?.textContent);
|
|
8637
9178
|
return packageOxml;
|
|
8638
9179
|
}
|
|
8639
9180
|
const pkgPackage = pkgDoc.documentElement;
|
|
@@ -8641,7 +9182,8 @@ function injectCommentsIntoPackage(packageOxml, commentsXml) {
|
|
|
8641
9182
|
commentsPart.setAttribute("pkg:name", "/word/comments.xml");
|
|
8642
9183
|
commentsPart.setAttribute("pkg:contentType", "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml");
|
|
8643
9184
|
const commentsXmlData = pkgDoc.createElementNS(PKG_NS, "pkg:xmlData");
|
|
8644
|
-
const commentsDoc =
|
|
9185
|
+
const commentsDoc = parseOoxmlSafe(commentsXml, "text/xml").doc;
|
|
9186
|
+
if (!commentsDoc) return packageOxml;
|
|
8645
9187
|
commentsXmlData.appendChild(pkgDoc.importNode(commentsDoc.documentElement, true));
|
|
8646
9188
|
commentsPart.appendChild(commentsXmlData);
|
|
8647
9189
|
pkgPackage.appendChild(commentsPart);
|
|
@@ -8691,17 +9233,19 @@ function injectCommentsIntoPackage(packageOxml, commentsXml) {
|
|
|
8691
9233
|
}
|
|
8692
9234
|
|
|
8693
9235
|
// services/comment-engine.js
|
|
8694
|
-
function parseDocumentOxml(oxml,
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
9236
|
+
function parseDocumentOxml(oxml, parseFailureWarning) {
|
|
9237
|
+
const parsed = parseOoxmlSafe(oxml, "text/xml");
|
|
9238
|
+
const parseError = parsed.doc ? getXmlParseError(parsed.doc) : null;
|
|
9239
|
+
if (parsed.error || parseError) {
|
|
9240
|
+
const message = parsed.error?.message || parseError?.textContent || "parse error";
|
|
9241
|
+
return {
|
|
9242
|
+
xmlDoc: null,
|
|
9243
|
+
warning: parseFailureWarning(message),
|
|
9244
|
+
warnings: parsed.warnings,
|
|
9245
|
+
error: { code: "PARSE_ERROR", message }
|
|
9246
|
+
};
|
|
8704
9247
|
}
|
|
9248
|
+
return { xmlDoc: parsed.doc, warning: null, warnings: parsed.warnings, error: null };
|
|
8705
9249
|
}
|
|
8706
9250
|
function injectCommentsIntoOoxml(oxml, comments, options = {}) {
|
|
8707
9251
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -8711,23 +9255,26 @@ function injectCommentsIntoOoxml(oxml, comments, options = {}) {
|
|
|
8711
9255
|
if (!comments || comments.length === 0) {
|
|
8712
9256
|
return {
|
|
8713
9257
|
oxml,
|
|
9258
|
+
hasChanges: false,
|
|
8714
9259
|
commentsApplied: 0,
|
|
8715
9260
|
warnings: ["No comments to inject"]
|
|
8716
9261
|
};
|
|
8717
9262
|
}
|
|
8718
|
-
const parser = createParser();
|
|
8719
9263
|
const serializer = createSerializer();
|
|
8720
9264
|
const parseResult = parseDocumentOxml(
|
|
8721
9265
|
oxml,
|
|
8722
|
-
parser,
|
|
8723
9266
|
(warning) => `Failed to parse OXML: ${warning}`
|
|
8724
9267
|
);
|
|
9268
|
+
warnings.push(...parseResult.warnings || []);
|
|
8725
9269
|
if (!parseResult.xmlDoc) {
|
|
8726
9270
|
error("[CommentEngine] Parse failure:", parseResult.warning);
|
|
8727
9271
|
return {
|
|
8728
9272
|
oxml,
|
|
9273
|
+
hasChanges: false,
|
|
8729
9274
|
commentsApplied: 0,
|
|
8730
|
-
|
|
9275
|
+
status: "error",
|
|
9276
|
+
error: parseResult.error,
|
|
9277
|
+
warnings: [...warnings, parseResult.warning]
|
|
8731
9278
|
};
|
|
8732
9279
|
}
|
|
8733
9280
|
const xmlDoc = parseResult.xmlDoc;
|
|
@@ -8786,12 +9333,14 @@ function injectCommentsIntoOoxml(oxml, comments, options = {}) {
|
|
|
8786
9333
|
if (placedComments.length === 0) {
|
|
8787
9334
|
return {
|
|
8788
9335
|
oxml,
|
|
9336
|
+
hasChanges: false,
|
|
8789
9337
|
commentsApplied: 0,
|
|
8790
9338
|
warnings
|
|
8791
9339
|
};
|
|
8792
9340
|
}
|
|
8793
9341
|
return {
|
|
8794
9342
|
oxml: serializer.serializeToString(xmlDoc),
|
|
9343
|
+
hasChanges: true,
|
|
8795
9344
|
commentsXml: buildCommentsPartXml(placedComments),
|
|
8796
9345
|
commentsApplied: placedComments.length,
|
|
8797
9346
|
warnings
|
|
@@ -8802,6 +9351,11 @@ function injectCommentsIntoPackage2(packageOxml, commentsXml) {
|
|
|
8802
9351
|
}
|
|
8803
9352
|
|
|
8804
9353
|
// engine/formatting-removal.js
|
|
9354
|
+
function removeNode2(node) {
|
|
9355
|
+
if (node?.parentNode) {
|
|
9356
|
+
node.parentNode.removeChild(node);
|
|
9357
|
+
}
|
|
9358
|
+
}
|
|
8805
9359
|
function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
8806
9360
|
if (!rPr) return null;
|
|
8807
9361
|
const rPrClone = rPr.cloneNode(true);
|
|
@@ -8825,7 +9379,7 @@ function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
|
8825
9379
|
];
|
|
8826
9380
|
toRemove.forEach((tag) => {
|
|
8827
9381
|
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
8828
|
-
elements.forEach(
|
|
9382
|
+
elements.forEach(removeNode2);
|
|
8829
9383
|
});
|
|
8830
9384
|
} else {
|
|
8831
9385
|
const tagMap = {
|
|
@@ -8847,7 +9401,7 @@ function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
|
8847
9401
|
const tag = tagMap[type];
|
|
8848
9402
|
if (tag) {
|
|
8849
9403
|
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
8850
|
-
elements.forEach(
|
|
9404
|
+
elements.forEach(removeNode2);
|
|
8851
9405
|
}
|
|
8852
9406
|
});
|
|
8853
9407
|
}
|
|
@@ -8856,6 +9410,7 @@ function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
|
8856
9410
|
function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
8857
9411
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
8858
9412
|
const doc = parseOoxml(ooxmlString);
|
|
9413
|
+
if (!doc) return ooxmlString;
|
|
8859
9414
|
const NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
8860
9415
|
const runs = doc.getElementsByTagNameNS(NS_W7, "r");
|
|
8861
9416
|
const insertions = doc.getElementsByTagNameNS(NS_W7, "ins");
|
|
@@ -8877,7 +9432,7 @@ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
|
8877
9432
|
rPr.parentNode.replaceChild(newRPr, rPr);
|
|
8878
9433
|
}
|
|
8879
9434
|
} else {
|
|
8880
|
-
rPr
|
|
9435
|
+
removeNode2(rPr);
|
|
8881
9436
|
}
|
|
8882
9437
|
}
|
|
8883
9438
|
}
|
|
@@ -8923,19 +9478,19 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
8923
9478
|
});
|
|
8924
9479
|
}
|
|
8925
9480
|
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W7, "highlight");
|
|
8926
|
-
Array.from(existingHighlight).forEach(
|
|
9481
|
+
Array.from(existingHighlight).forEach(removeNode2);
|
|
8927
9482
|
const highlightEl = createWordElement(doc, "w:highlight");
|
|
8928
9483
|
highlightEl.setAttributeNS(NS_W7, "w:val", ooxmlColor);
|
|
8929
9484
|
rPrElement.appendChild(highlightEl);
|
|
8930
9485
|
if (generateRedlines && previousRPrState) {
|
|
8931
9486
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
8932
|
-
const metadata = createRevisionMetadata(author);
|
|
9487
|
+
const metadata = createRevisionMetadata(author, doc);
|
|
8933
9488
|
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
8934
9489
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
8935
9490
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
8936
9491
|
rPrChange.appendChild(previousRPrState);
|
|
8937
9492
|
const existingChange = rPrElement.getElementsByTagNameNS(NS_W7, "rPrChange");
|
|
8938
|
-
Array.from(existingChange).forEach(
|
|
9493
|
+
Array.from(existingChange).forEach(removeNode2);
|
|
8939
9494
|
rPrElement.appendChild(rPrChange);
|
|
8940
9495
|
}
|
|
8941
9496
|
return rPrElement;
|
|
@@ -8943,64 +9498,70 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
8943
9498
|
function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
|
|
8944
9499
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
8945
9500
|
const doc = parseOoxml(ooxmlString);
|
|
9501
|
+
if (!doc) return ooxmlString;
|
|
9502
|
+
if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
|
|
9503
|
+
seedRevisionIdsFromDocument(doc, options._revisionIdAllocator);
|
|
9504
|
+
} else {
|
|
9505
|
+
createRevisionIdAllocator(doc);
|
|
9506
|
+
}
|
|
8946
9507
|
const NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
8947
9508
|
const getRunText = (run) => {
|
|
8948
9509
|
const textNodes = run.getElementsByTagNameNS(NS_W7, "t");
|
|
8949
9510
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
8950
9511
|
};
|
|
8951
9512
|
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W7, "r"));
|
|
8952
|
-
|
|
8953
|
-
const
|
|
9513
|
+
const cloneRunWithText2 = (sourceRun, text, shouldHighlight) => {
|
|
9514
|
+
const clonedRun = sourceRun.cloneNode(true);
|
|
9515
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W7, "t");
|
|
9516
|
+
Array.from(textNodes).forEach(removeNode2);
|
|
9517
|
+
const newText = createWordElement(doc, "w:t");
|
|
9518
|
+
newText.setAttribute("xml:space", "preserve");
|
|
9519
|
+
newText.textContent = text;
|
|
9520
|
+
clonedRun.appendChild(newText);
|
|
9521
|
+
if (shouldHighlight) {
|
|
9522
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W7, "rPr");
|
|
9523
|
+
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
9524
|
+
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
9525
|
+
if (existingRPr) {
|
|
9526
|
+
clonedRun.replaceChild(newRPr, existingRPr);
|
|
9527
|
+
} else {
|
|
9528
|
+
clonedRun.insertBefore(newRPr, clonedRun.firstChild);
|
|
9529
|
+
}
|
|
9530
|
+
}
|
|
9531
|
+
return clonedRun;
|
|
9532
|
+
};
|
|
9533
|
+
for (const run of allRuns) {
|
|
8954
9534
|
const runText = getRunText(run);
|
|
8955
9535
|
if (!runText) continue;
|
|
8956
|
-
const
|
|
8957
|
-
|
|
9536
|
+
const matchIndexes = [];
|
|
9537
|
+
let searchOffset = 0;
|
|
9538
|
+
while (searchOffset <= runText.length - targetText.length) {
|
|
9539
|
+
const matchIndex = runText.indexOf(targetText, searchOffset);
|
|
9540
|
+
if (matchIndex === -1) break;
|
|
9541
|
+
matchIndexes.push(matchIndex);
|
|
9542
|
+
searchOffset = matchIndex + targetText.length;
|
|
9543
|
+
}
|
|
9544
|
+
if (matchIndexes.length === 0) continue;
|
|
8958
9545
|
const parent = run.parentNode;
|
|
8959
9546
|
if (!parent) {
|
|
8960
9547
|
console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
|
|
8961
9548
|
continue;
|
|
8962
9549
|
}
|
|
8963
|
-
const prefixText = runText.substring(0, matchIndex);
|
|
8964
|
-
const matchText = runText.substring(matchIndex, matchIndex + targetText.length);
|
|
8965
|
-
const suffixText = runText.substring(matchIndex + targetText.length);
|
|
8966
9550
|
const fragment = doc.createDocumentFragment();
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
const newT = createWordElement(doc, "w:t");
|
|
8972
|
-
newT.setAttribute("xml:space", "preserve");
|
|
8973
|
-
newT.textContent = prefixText;
|
|
8974
|
-
prefixRun.appendChild(newT);
|
|
8975
|
-
fragment.appendChild(prefixRun);
|
|
8976
|
-
}
|
|
8977
|
-
if (matchText.length > 0) {
|
|
8978
|
-
const matchRun = run.cloneNode(true);
|
|
8979
|
-
const tNodes = matchRun.getElementsByTagNameNS(NS_W7, "t");
|
|
8980
|
-
Array.from(tNodes).forEach((t) => t.remove());
|
|
8981
|
-
const newT = createWordElement(doc, "w:t");
|
|
8982
|
-
newT.setAttribute("xml:space", "preserve");
|
|
8983
|
-
newT.textContent = matchText;
|
|
8984
|
-
matchRun.appendChild(newT);
|
|
8985
|
-
const rPrElements = matchRun.getElementsByTagNameNS(NS_W7, "rPr");
|
|
8986
|
-
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
8987
|
-
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
8988
|
-
if (existingRPr) {
|
|
8989
|
-
matchRun.replaceChild(newRPr, existingRPr);
|
|
8990
|
-
} else {
|
|
8991
|
-
matchRun.insertBefore(newRPr, matchRun.firstChild);
|
|
9551
|
+
let cursor = 0;
|
|
9552
|
+
for (const matchIndex of matchIndexes) {
|
|
9553
|
+
if (matchIndex > cursor) {
|
|
9554
|
+
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
|
|
8992
9555
|
}
|
|
8993
|
-
fragment.appendChild(
|
|
9556
|
+
fragment.appendChild(cloneRunWithText2(
|
|
9557
|
+
run,
|
|
9558
|
+
runText.slice(matchIndex, matchIndex + targetText.length),
|
|
9559
|
+
true
|
|
9560
|
+
));
|
|
9561
|
+
cursor = matchIndex + targetText.length;
|
|
8994
9562
|
}
|
|
8995
|
-
if (
|
|
8996
|
-
|
|
8997
|
-
const tNodes = suffixRun.getElementsByTagNameNS(NS_W7, "t");
|
|
8998
|
-
Array.from(tNodes).forEach((t) => t.remove());
|
|
8999
|
-
const newT = createWordElement(doc, "w:t");
|
|
9000
|
-
newT.setAttribute("xml:space", "preserve");
|
|
9001
|
-
newT.textContent = suffixText;
|
|
9002
|
-
suffixRun.appendChild(newT);
|
|
9003
|
-
fragment.appendChild(suffixRun);
|
|
9563
|
+
if (cursor < runText.length) {
|
|
9564
|
+
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
|
|
9004
9565
|
}
|
|
9005
9566
|
parent.replaceChild(fragment, run);
|
|
9006
9567
|
}
|
|
@@ -9021,13 +9582,13 @@ var COMMENTS_PATH = "word/comments.xml";
|
|
|
9021
9582
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
9022
9583
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
9023
9584
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
9024
|
-
const
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
throw
|
|
9585
|
+
const parsed = parseOoxmlSafe(xmlText, "application/xml");
|
|
9586
|
+
if (parsed.error || !parsed.doc) {
|
|
9587
|
+
const error2 = new Error(`[XML parse error] ${label}: ${parsed.error?.message || "Unknown"}`);
|
|
9588
|
+
error2.code = "PARSE_ERROR";
|
|
9589
|
+
throw error2;
|
|
9029
9590
|
}
|
|
9030
|
-
return
|
|
9591
|
+
return parsed.doc;
|
|
9031
9592
|
}
|
|
9032
9593
|
function isSectionPropertiesElement(node) {
|
|
9033
9594
|
return !!node && node.nodeType === 1 && node.namespaceURI === NS_W6 && node.localName === "sectPr";
|
|
@@ -9091,9 +9652,8 @@ function getPackagePartName(partElement) {
|
|
|
9091
9652
|
return partElement.getAttribute("pkg:name") || partElement.getAttribute("name") || "";
|
|
9092
9653
|
}
|
|
9093
9654
|
function extractFromPackageXml(packageXml) {
|
|
9094
|
-
const parser = createParser();
|
|
9095
9655
|
const serializer = createSerializer();
|
|
9096
|
-
const pkgDoc =
|
|
9656
|
+
const pkgDoc = parseXmlStrictStandalone(packageXml, "package OOXML");
|
|
9097
9657
|
const parts = Array.from(pkgDoc.getElementsByTagNameNS("*", "part"));
|
|
9098
9658
|
const documentPart = parts.find((part) => getPackagePartName(part) === "/word/document.xml");
|
|
9099
9659
|
if (!documentPart) {
|
|
@@ -9126,23 +9686,37 @@ function extractFromPackageXml(packageXml) {
|
|
|
9126
9686
|
}
|
|
9127
9687
|
function extractReplacementNodesFromOoxml(outputOxml) {
|
|
9128
9688
|
if (typeof outputOxml !== "string" || !outputOxml.trim()) {
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9689
|
+
return {
|
|
9690
|
+
replacementNodes: [],
|
|
9691
|
+
numberingXml: null,
|
|
9692
|
+
sourceType: "fragment",
|
|
9693
|
+
status: "error",
|
|
9694
|
+
error: { code: "PARSE_ERROR", message: "Reconciliation engine returned no OOXML payload for this operation" }
|
|
9695
|
+
};
|
|
9133
9696
|
}
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9697
|
+
try {
|
|
9698
|
+
if (outputOxml.includes("<pkg:package")) {
|
|
9699
|
+
return extractFromPackageXml(outputOxml);
|
|
9700
|
+
}
|
|
9701
|
+
if (outputOxml.includes("<w:document")) {
|
|
9702
|
+
const doc = parseXmlStrictStandalone(outputOxml, "document OOXML");
|
|
9703
|
+
const body = doc.getElementsByTagNameNS("*", "body")[0];
|
|
9704
|
+
const replacementNodes2 = body ? Array.from(body.childNodes || []).filter((node) => node.nodeType === 1 && !isSectionPropertiesElement(node)) : Array.from(doc.childNodes || []).filter((node) => node.nodeType === 1);
|
|
9705
|
+
return { replacementNodes: replacementNodes2, numberingXml: null, sourceType: "document" };
|
|
9706
|
+
}
|
|
9707
|
+
const wrapped = `<root xmlns:w="${NS_W6}">${outputOxml}</root>`;
|
|
9708
|
+
const fragmentDoc = parseXmlStrictStandalone(wrapped, "OOXML fragment");
|
|
9709
|
+
const replacementNodes = Array.from(fragmentDoc.documentElement.childNodes || []).filter((node) => node.nodeType === 1);
|
|
9710
|
+
return { replacementNodes, numberingXml: null, sourceType: "fragment" };
|
|
9711
|
+
} catch (caught) {
|
|
9712
|
+
return {
|
|
9713
|
+
replacementNodes: [],
|
|
9714
|
+
numberingXml: null,
|
|
9715
|
+
sourceType: "fragment",
|
|
9716
|
+
status: "error",
|
|
9717
|
+
error: { code: "PARSE_ERROR", message: caught?.message || "Could not parse OOXML payload." }
|
|
9718
|
+
};
|
|
9140
9719
|
}
|
|
9141
|
-
const wrapped = `<root xmlns:w="${NS_W6}">${outputOxml}</root>`;
|
|
9142
|
-
const parser = createParser();
|
|
9143
|
-
const fragmentDoc = parser.parseFromString(wrapped, "application/xml");
|
|
9144
|
-
const replacementNodes = Array.from(fragmentDoc.documentElement.childNodes || []).filter((node) => node.nodeType === 1);
|
|
9145
|
-
return { replacementNodes, numberingXml: null, sourceType: "fragment" };
|
|
9146
9720
|
}
|
|
9147
9721
|
function upsertContentTypeOverride(ctDoc, partName, contentType) {
|
|
9148
9722
|
const overrides = Array.from(ctDoc.getElementsByTagNameNS("*", "Override"));
|
|
@@ -9202,18 +9776,17 @@ async function ensureNumberingArtifactsInZip(zip, numberingXmlList, options = {}
|
|
|
9202
9776
|
onInfo("[Demo] Merging numbering.xml payload(s) into existing numbering definitions");
|
|
9203
9777
|
}
|
|
9204
9778
|
zip.file(NUMBERING_PATH, mergedNumberingXml);
|
|
9205
|
-
const parser = createParser();
|
|
9206
9779
|
const serializer = createSerializer();
|
|
9207
9780
|
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
9208
9781
|
if (ctText) {
|
|
9209
|
-
const ctDoc =
|
|
9782
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
9210
9783
|
if (upsertContentTypeOverride(ctDoc, "/word/numbering.xml", NUMBERING_CONTENT_TYPE)) {
|
|
9211
9784
|
zip.file(CONTENT_TYPES_PATH, serializer.serializeToString(ctDoc));
|
|
9212
9785
|
}
|
|
9213
9786
|
}
|
|
9214
9787
|
const relsText = await readZipText(zip, DOCUMENT_RELS_PATH);
|
|
9215
9788
|
if (relsText) {
|
|
9216
|
-
const relsDoc =
|
|
9789
|
+
const relsDoc = parseXmlStrictStandalone(relsText, DOCUMENT_RELS_PATH);
|
|
9217
9790
|
if (upsertDocumentRelationship(relsDoc, NUMBERING_REL_TYPE, "numbering.xml")) {
|
|
9218
9791
|
zip.file(DOCUMENT_RELS_PATH, serializer.serializeToString(relsDoc));
|
|
9219
9792
|
}
|
|
@@ -9223,7 +9796,6 @@ async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
|
|
|
9223
9796
|
const onInfo = typeof options?.onInfo === "function" ? options.onInfo : () => {
|
|
9224
9797
|
};
|
|
9225
9798
|
if (!commentsXml) return;
|
|
9226
|
-
const parser = createParser();
|
|
9227
9799
|
const serializer = createSerializer();
|
|
9228
9800
|
const existingText = await readZipText(zip, COMMENTS_PATH);
|
|
9229
9801
|
if (!existingText) {
|
|
@@ -9247,14 +9819,14 @@ async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
|
|
|
9247
9819
|
}
|
|
9248
9820
|
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
9249
9821
|
if (ctText) {
|
|
9250
|
-
const ctDoc =
|
|
9822
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
9251
9823
|
if (upsertContentTypeOverride(ctDoc, "/word/comments.xml", COMMENTS_CONTENT_TYPE)) {
|
|
9252
9824
|
zip.file(CONTENT_TYPES_PATH, serializer.serializeToString(ctDoc));
|
|
9253
9825
|
}
|
|
9254
9826
|
}
|
|
9255
9827
|
const relsText = await readZipText(zip, DOCUMENT_RELS_PATH);
|
|
9256
9828
|
if (relsText) {
|
|
9257
|
-
const relsDoc =
|
|
9829
|
+
const relsDoc = parseXmlStrictStandalone(relsText, DOCUMENT_RELS_PATH);
|
|
9258
9830
|
if (upsertDocumentRelationship(relsDoc, COMMENTS_REL_TYPE, "comments.xml")) {
|
|
9259
9831
|
zip.file(DOCUMENT_RELS_PATH, serializer.serializeToString(relsDoc));
|
|
9260
9832
|
}
|
|
@@ -9517,10 +10089,12 @@ async function reconcileMarkdownTableOoxml(oxml, originalText, markdownTable, op
|
|
|
9517
10089
|
}
|
|
9518
10090
|
async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedText, options = {}) {
|
|
9519
10091
|
const allowExistingListForFallback = options.listFallbackAllowExistingList !== false;
|
|
10092
|
+
const fallbackModifiedText = options.sanitizeInput === true ? sanitizeAiResponse(modifiedText) : modifiedText;
|
|
10093
|
+
const sanitizationWarnings = fallbackModifiedText !== modifiedText ? ["Input was sanitized; pass sanitizeInput: false to disable."] : [];
|
|
9520
10094
|
const plan = buildSingleLineListStructuralFallbackPlan({
|
|
9521
10095
|
oxml,
|
|
9522
10096
|
originalText,
|
|
9523
|
-
modifiedText,
|
|
10097
|
+
modifiedText: fallbackModifiedText,
|
|
9524
10098
|
allowExistingList: allowExistingListForFallback
|
|
9525
10099
|
});
|
|
9526
10100
|
const preferListFallback = options.preferListStructuralFallback !== false;
|
|
@@ -9540,7 +10114,7 @@ async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedTe
|
|
|
9540
10114
|
return withOoxmlSourceType({
|
|
9541
10115
|
oxml: wrappedOxml2,
|
|
9542
10116
|
hasChanges: true,
|
|
9543
|
-
warnings: fallbackWarnings2,
|
|
10117
|
+
warnings: [...sanitizationWarnings, ...fallbackWarnings2],
|
|
9544
10118
|
listStructuralFallbackApplied: true,
|
|
9545
10119
|
listStructuralFallbackKey: fallbackResult2.listStructuralFallbackKey || null,
|
|
9546
10120
|
listStructuralFallbackNumberingXml: fallbackResult2.numberingXml || null
|
|
@@ -9659,7 +10233,9 @@ export {
|
|
|
9659
10233
|
inferTableReplacementParagraphBlock,
|
|
9660
10234
|
ingestOoxml,
|
|
9661
10235
|
ingestWordOoxmlToMarkdown,
|
|
10236
|
+
ingestWordOoxmlToMarkdownResult,
|
|
9662
10237
|
ingestWordOoxmlToPlainText,
|
|
10238
|
+
ingestWordOoxmlToPlainTextResult,
|
|
9663
10239
|
injectCommentsIntoOoxml,
|
|
9664
10240
|
injectCommentsIntoPackage2 as injectCommentsIntoPackage,
|
|
9665
10241
|
insertBodyElementBeforeSectPr,
|
|
@@ -9673,8 +10249,8 @@ export {
|
|
|
9673
10249
|
overwriteParagraphNumIds,
|
|
9674
10250
|
parseMarkdownListContent,
|
|
9675
10251
|
parseOoxml,
|
|
10252
|
+
parseOoxmlSafe,
|
|
9676
10253
|
parseParagraphReference,
|
|
9677
|
-
parseXmlStrictStandalone,
|
|
9678
10254
|
planListInsertionOnlyEdit,
|
|
9679
10255
|
preprocessMarkdown,
|
|
9680
10256
|
reconcileMarkdownTableOoxml,
|