@ansonlai/docx-redline-js 0.2.1 → 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 +1124 -545
- 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 +84 -99
- 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 +3 -3
- 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 +22 -4
- 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;
|
|
@@ -32,7 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
32
|
// node_modules/diff-match-patch/index.js
|
|
33
33
|
var require_diff_match_patch = __commonJS({
|
|
34
34
|
"node_modules/diff-match-patch/index.js"(exports, module) {
|
|
35
|
-
var
|
|
35
|
+
var diff_match_patch3 = function() {
|
|
36
36
|
this.Diff_Timeout = 1;
|
|
37
37
|
this.Diff_EditCost = 4;
|
|
38
38
|
this.Match_Threshold = 0.5;
|
|
@@ -44,10 +44,10 @@ var require_diff_match_patch = __commonJS({
|
|
|
44
44
|
var DIFF_DELETE = -1;
|
|
45
45
|
var DIFF_INSERT = 1;
|
|
46
46
|
var DIFF_EQUAL = 0;
|
|
47
|
-
|
|
47
|
+
diff_match_patch3.Diff = function(op, text) {
|
|
48
48
|
return [op, text];
|
|
49
49
|
};
|
|
50
|
-
|
|
50
|
+
diff_match_patch3.prototype.diff_main = function(text1, text2, opt_checklines, opt_deadline) {
|
|
51
51
|
if (typeof opt_deadline == "undefined") {
|
|
52
52
|
if (this.Diff_Timeout <= 0) {
|
|
53
53
|
opt_deadline = Number.MAX_VALUE;
|
|
@@ -61,7 +61,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
61
61
|
}
|
|
62
62
|
if (text1 == text2) {
|
|
63
63
|
if (text1) {
|
|
64
|
-
return [new
|
|
64
|
+
return [new diff_match_patch3.Diff(DIFF_EQUAL, text1)];
|
|
65
65
|
}
|
|
66
66
|
return [];
|
|
67
67
|
}
|
|
@@ -79,30 +79,30 @@ var require_diff_match_patch = __commonJS({
|
|
|
79
79
|
text2 = text2.substring(0, text2.length - commonlength);
|
|
80
80
|
var diffs = this.diff_compute_(text1, text2, checklines, deadline);
|
|
81
81
|
if (commonprefix) {
|
|
82
|
-
diffs.unshift(new
|
|
82
|
+
diffs.unshift(new diff_match_patch3.Diff(DIFF_EQUAL, commonprefix));
|
|
83
83
|
}
|
|
84
84
|
if (commonsuffix) {
|
|
85
|
-
diffs.push(new
|
|
85
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, commonsuffix));
|
|
86
86
|
}
|
|
87
87
|
this.diff_cleanupMerge(diffs);
|
|
88
88
|
return diffs;
|
|
89
89
|
};
|
|
90
|
-
|
|
90
|
+
diff_match_patch3.prototype.diff_compute_ = function(text1, text2, checklines, deadline) {
|
|
91
91
|
var diffs;
|
|
92
92
|
if (!text1) {
|
|
93
|
-
return [new
|
|
93
|
+
return [new diff_match_patch3.Diff(DIFF_INSERT, text2)];
|
|
94
94
|
}
|
|
95
95
|
if (!text2) {
|
|
96
|
-
return [new
|
|
96
|
+
return [new diff_match_patch3.Diff(DIFF_DELETE, text1)];
|
|
97
97
|
}
|
|
98
98
|
var longtext = text1.length > text2.length ? text1 : text2;
|
|
99
99
|
var shorttext = text1.length > text2.length ? text2 : text1;
|
|
100
100
|
var i = longtext.indexOf(shorttext);
|
|
101
101
|
if (i != -1) {
|
|
102
102
|
diffs = [
|
|
103
|
-
new
|
|
104
|
-
new
|
|
105
|
-
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(
|
|
106
106
|
DIFF_INSERT,
|
|
107
107
|
longtext.substring(i + shorttext.length)
|
|
108
108
|
)
|
|
@@ -114,8 +114,8 @@ var require_diff_match_patch = __commonJS({
|
|
|
114
114
|
}
|
|
115
115
|
if (shorttext.length == 1) {
|
|
116
116
|
return [
|
|
117
|
-
new
|
|
118
|
-
new
|
|
117
|
+
new diff_match_patch3.Diff(DIFF_DELETE, text1),
|
|
118
|
+
new diff_match_patch3.Diff(DIFF_INSERT, text2)
|
|
119
119
|
];
|
|
120
120
|
}
|
|
121
121
|
var hm = this.diff_halfMatch_(text1, text2);
|
|
@@ -128,7 +128,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
128
128
|
var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline);
|
|
129
129
|
var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline);
|
|
130
130
|
return diffs_a.concat(
|
|
131
|
-
[new
|
|
131
|
+
[new diff_match_patch3.Diff(DIFF_EQUAL, mid_common)],
|
|
132
132
|
diffs_b
|
|
133
133
|
);
|
|
134
134
|
}
|
|
@@ -137,7 +137,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
137
137
|
}
|
|
138
138
|
return this.diff_bisect_(text1, text2, deadline);
|
|
139
139
|
};
|
|
140
|
-
|
|
140
|
+
diff_match_patch3.prototype.diff_lineMode_ = function(text1, text2, deadline) {
|
|
141
141
|
var a = this.diff_linesToChars_(text1, text2);
|
|
142
142
|
text1 = a.chars1;
|
|
143
143
|
text2 = a.chars2;
|
|
@@ -145,7 +145,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
145
145
|
var diffs = this.diff_main(text1, text2, false, deadline);
|
|
146
146
|
this.diff_charsToLines_(diffs, linearray);
|
|
147
147
|
this.diff_cleanupSemantic(diffs);
|
|
148
|
-
diffs.push(new
|
|
148
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, ""));
|
|
149
149
|
var pointer = 0;
|
|
150
150
|
var count_delete = 0;
|
|
151
151
|
var count_insert = 0;
|
|
@@ -185,7 +185,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
185
185
|
diffs.pop();
|
|
186
186
|
return diffs;
|
|
187
187
|
};
|
|
188
|
-
|
|
188
|
+
diff_match_patch3.prototype.diff_bisect_ = function(text1, text2, deadline) {
|
|
189
189
|
var text1_length = text1.length;
|
|
190
190
|
var text2_length = text2.length;
|
|
191
191
|
var max_d = Math.ceil((text1_length + text2_length) / 2);
|
|
@@ -269,11 +269,11 @@ var require_diff_match_patch = __commonJS({
|
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
return [
|
|
272
|
-
new
|
|
273
|
-
new
|
|
272
|
+
new diff_match_patch3.Diff(DIFF_DELETE, text1),
|
|
273
|
+
new diff_match_patch3.Diff(DIFF_INSERT, text2)
|
|
274
274
|
];
|
|
275
275
|
};
|
|
276
|
-
|
|
276
|
+
diff_match_patch3.prototype.diff_bisectSplit_ = function(text1, text2, x, y, deadline) {
|
|
277
277
|
var text1a = text1.substring(0, x);
|
|
278
278
|
var text2a = text2.substring(0, y);
|
|
279
279
|
var text1b = text1.substring(x);
|
|
@@ -282,7 +282,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
282
282
|
var diffsb = this.diff_main(text1b, text2b, false, deadline);
|
|
283
283
|
return diffs.concat(diffsb);
|
|
284
284
|
};
|
|
285
|
-
|
|
285
|
+
diff_match_patch3.prototype.diff_linesToChars_ = function(text1, text2) {
|
|
286
286
|
var lineArray = [];
|
|
287
287
|
var lineHash = {};
|
|
288
288
|
lineArray[0] = "";
|
|
@@ -318,7 +318,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
318
318
|
var chars2 = diff_linesToCharsMunge_(text2);
|
|
319
319
|
return { chars1, chars2, lineArray };
|
|
320
320
|
};
|
|
321
|
-
|
|
321
|
+
diff_match_patch3.prototype.diff_charsToLines_ = function(diffs, lineArray) {
|
|
322
322
|
for (var i = 0; i < diffs.length; i++) {
|
|
323
323
|
var chars = diffs[i][1];
|
|
324
324
|
var text = [];
|
|
@@ -328,7 +328,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
328
328
|
diffs[i][1] = text.join("");
|
|
329
329
|
}
|
|
330
330
|
};
|
|
331
|
-
|
|
331
|
+
diff_match_patch3.prototype.diff_commonPrefix = function(text1, text2) {
|
|
332
332
|
if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {
|
|
333
333
|
return 0;
|
|
334
334
|
}
|
|
@@ -347,7 +347,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
347
347
|
}
|
|
348
348
|
return pointermid;
|
|
349
349
|
};
|
|
350
|
-
|
|
350
|
+
diff_match_patch3.prototype.diff_commonSuffix = function(text1, text2) {
|
|
351
351
|
if (!text1 || !text2 || text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) {
|
|
352
352
|
return 0;
|
|
353
353
|
}
|
|
@@ -366,7 +366,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
366
366
|
}
|
|
367
367
|
return pointermid;
|
|
368
368
|
};
|
|
369
|
-
|
|
369
|
+
diff_match_patch3.prototype.diff_commonOverlap_ = function(text1, text2) {
|
|
370
370
|
var text1_length = text1.length;
|
|
371
371
|
var text2_length = text2.length;
|
|
372
372
|
if (text1_length == 0 || text2_length == 0) {
|
|
@@ -396,7 +396,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
};
|
|
399
|
-
|
|
399
|
+
diff_match_patch3.prototype.diff_halfMatch_ = function(text1, text2) {
|
|
400
400
|
if (this.Diff_Timeout <= 0) {
|
|
401
401
|
return null;
|
|
402
402
|
}
|
|
@@ -475,7 +475,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
475
475
|
var mid_common = hm[4];
|
|
476
476
|
return [text1_a, text1_b, text2_a, text2_b, mid_common];
|
|
477
477
|
};
|
|
478
|
-
|
|
478
|
+
diff_match_patch3.prototype.diff_cleanupSemantic = function(diffs) {
|
|
479
479
|
var changes = false;
|
|
480
480
|
var equalities = [];
|
|
481
481
|
var equalitiesLength = 0;
|
|
@@ -506,7 +506,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
506
506
|
diffs.splice(
|
|
507
507
|
equalities[equalitiesLength - 1],
|
|
508
508
|
0,
|
|
509
|
-
new
|
|
509
|
+
new diff_match_patch3.Diff(DIFF_DELETE, lastEquality)
|
|
510
510
|
);
|
|
511
511
|
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
|
|
512
512
|
equalitiesLength--;
|
|
@@ -535,7 +535,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
535
535
|
var overlap_length2 = this.diff_commonOverlap_(insertion, deletion);
|
|
536
536
|
if (overlap_length1 >= overlap_length2) {
|
|
537
537
|
if (overlap_length1 >= deletion.length / 2 || overlap_length1 >= insertion.length / 2) {
|
|
538
|
-
diffs.splice(pointer, 0, new
|
|
538
|
+
diffs.splice(pointer, 0, new diff_match_patch3.Diff(
|
|
539
539
|
DIFF_EQUAL,
|
|
540
540
|
insertion.substring(0, overlap_length1)
|
|
541
541
|
));
|
|
@@ -545,7 +545,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
545
545
|
}
|
|
546
546
|
} else {
|
|
547
547
|
if (overlap_length2 >= deletion.length / 2 || overlap_length2 >= insertion.length / 2) {
|
|
548
|
-
diffs.splice(pointer, 0, new
|
|
548
|
+
diffs.splice(pointer, 0, new diff_match_patch3.Diff(
|
|
549
549
|
DIFF_EQUAL,
|
|
550
550
|
deletion.substring(0, overlap_length2)
|
|
551
551
|
));
|
|
@@ -561,21 +561,21 @@ var require_diff_match_patch = __commonJS({
|
|
|
561
561
|
pointer++;
|
|
562
562
|
}
|
|
563
563
|
};
|
|
564
|
-
|
|
564
|
+
diff_match_patch3.prototype.diff_cleanupSemanticLossless = function(diffs) {
|
|
565
565
|
function diff_cleanupSemanticScore_(one, two) {
|
|
566
566
|
if (!one || !two) {
|
|
567
567
|
return 6;
|
|
568
568
|
}
|
|
569
569
|
var char1 = one.charAt(one.length - 1);
|
|
570
570
|
var char2 = two.charAt(0);
|
|
571
|
-
var nonAlphaNumeric1 = char1.match(
|
|
572
|
-
var nonAlphaNumeric2 = char2.match(
|
|
573
|
-
var whitespace1 = nonAlphaNumeric1 && char1.match(
|
|
574
|
-
var whitespace2 = nonAlphaNumeric2 && char2.match(
|
|
575
|
-
var lineBreak1 = whitespace1 && char1.match(
|
|
576
|
-
var lineBreak2 = whitespace2 && char2.match(
|
|
577
|
-
var blankLine1 = lineBreak1 && one.match(
|
|
578
|
-
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_);
|
|
579
579
|
if (blankLine1 || blankLine2) {
|
|
580
580
|
return 5;
|
|
581
581
|
} else if (lineBreak1 || lineBreak2) {
|
|
@@ -637,12 +637,12 @@ var require_diff_match_patch = __commonJS({
|
|
|
637
637
|
pointer++;
|
|
638
638
|
}
|
|
639
639
|
};
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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) {
|
|
646
646
|
var changes = false;
|
|
647
647
|
var equalities = [];
|
|
648
648
|
var equalitiesLength = 0;
|
|
@@ -674,7 +674,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
674
674
|
diffs.splice(
|
|
675
675
|
equalities[equalitiesLength - 1],
|
|
676
676
|
0,
|
|
677
|
-
new
|
|
677
|
+
new diff_match_patch3.Diff(DIFF_DELETE, lastEquality)
|
|
678
678
|
);
|
|
679
679
|
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
|
|
680
680
|
equalitiesLength--;
|
|
@@ -696,8 +696,8 @@ var require_diff_match_patch = __commonJS({
|
|
|
696
696
|
this.diff_cleanupMerge(diffs);
|
|
697
697
|
}
|
|
698
698
|
};
|
|
699
|
-
|
|
700
|
-
diffs.push(new
|
|
699
|
+
diff_match_patch3.prototype.diff_cleanupMerge = function(diffs) {
|
|
700
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, ""));
|
|
701
701
|
var pointer = 0;
|
|
702
702
|
var count_delete = 0;
|
|
703
703
|
var count_insert = 0;
|
|
@@ -724,7 +724,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
724
724
|
if (pointer - count_delete - count_insert > 0 && diffs[pointer - count_delete - count_insert - 1][0] == DIFF_EQUAL) {
|
|
725
725
|
diffs[pointer - count_delete - count_insert - 1][1] += text_insert.substring(0, commonlength);
|
|
726
726
|
} else {
|
|
727
|
-
diffs.splice(0, 0, new
|
|
727
|
+
diffs.splice(0, 0, new diff_match_patch3.Diff(
|
|
728
728
|
DIFF_EQUAL,
|
|
729
729
|
text_insert.substring(0, commonlength)
|
|
730
730
|
));
|
|
@@ -746,7 +746,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
746
746
|
diffs.splice(
|
|
747
747
|
pointer,
|
|
748
748
|
0,
|
|
749
|
-
new
|
|
749
|
+
new diff_match_patch3.Diff(DIFF_DELETE, text_delete)
|
|
750
750
|
);
|
|
751
751
|
pointer++;
|
|
752
752
|
}
|
|
@@ -754,7 +754,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
754
754
|
diffs.splice(
|
|
755
755
|
pointer,
|
|
756
756
|
0,
|
|
757
|
-
new
|
|
757
|
+
new diff_match_patch3.Diff(DIFF_INSERT, text_insert)
|
|
758
758
|
);
|
|
759
759
|
pointer++;
|
|
760
760
|
}
|
|
@@ -797,7 +797,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
797
797
|
this.diff_cleanupMerge(diffs);
|
|
798
798
|
}
|
|
799
799
|
};
|
|
800
|
-
|
|
800
|
+
diff_match_patch3.prototype.diff_xIndex = function(diffs, loc) {
|
|
801
801
|
var chars1 = 0;
|
|
802
802
|
var chars2 = 0;
|
|
803
803
|
var last_chars1 = 0;
|
|
@@ -821,7 +821,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
821
821
|
}
|
|
822
822
|
return last_chars2 + (loc - last_chars1);
|
|
823
823
|
};
|
|
824
|
-
|
|
824
|
+
diff_match_patch3.prototype.diff_prettyHtml = function(diffs) {
|
|
825
825
|
var html = [];
|
|
826
826
|
var pattern_amp = /&/g;
|
|
827
827
|
var pattern_lt = /</g;
|
|
@@ -845,7 +845,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
845
845
|
}
|
|
846
846
|
return html.join("");
|
|
847
847
|
};
|
|
848
|
-
|
|
848
|
+
diff_match_patch3.prototype.diff_text1 = function(diffs) {
|
|
849
849
|
var text = [];
|
|
850
850
|
for (var x = 0; x < diffs.length; x++) {
|
|
851
851
|
if (diffs[x][0] !== DIFF_INSERT) {
|
|
@@ -854,7 +854,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
854
854
|
}
|
|
855
855
|
return text.join("");
|
|
856
856
|
};
|
|
857
|
-
|
|
857
|
+
diff_match_patch3.prototype.diff_text2 = function(diffs) {
|
|
858
858
|
var text = [];
|
|
859
859
|
for (var x = 0; x < diffs.length; x++) {
|
|
860
860
|
if (diffs[x][0] !== DIFF_DELETE) {
|
|
@@ -863,7 +863,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
863
863
|
}
|
|
864
864
|
return text.join("");
|
|
865
865
|
};
|
|
866
|
-
|
|
866
|
+
diff_match_patch3.prototype.diff_levenshtein = function(diffs) {
|
|
867
867
|
var levenshtein = 0;
|
|
868
868
|
var insertions = 0;
|
|
869
869
|
var deletions = 0;
|
|
@@ -887,7 +887,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
887
887
|
levenshtein += Math.max(insertions, deletions);
|
|
888
888
|
return levenshtein;
|
|
889
889
|
};
|
|
890
|
-
|
|
890
|
+
diff_match_patch3.prototype.diff_toDelta = function(diffs) {
|
|
891
891
|
var text = [];
|
|
892
892
|
for (var x = 0; x < diffs.length; x++) {
|
|
893
893
|
switch (diffs[x][0]) {
|
|
@@ -904,7 +904,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
904
904
|
}
|
|
905
905
|
return text.join(" ").replace(/%20/g, " ");
|
|
906
906
|
};
|
|
907
|
-
|
|
907
|
+
diff_match_patch3.prototype.diff_fromDelta = function(text1, delta) {
|
|
908
908
|
var diffs = [];
|
|
909
909
|
var diffsLength = 0;
|
|
910
910
|
var pointer = 0;
|
|
@@ -914,7 +914,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
914
914
|
switch (tokens[x].charAt(0)) {
|
|
915
915
|
case "+":
|
|
916
916
|
try {
|
|
917
|
-
diffs[diffsLength++] = new
|
|
917
|
+
diffs[diffsLength++] = new diff_match_patch3.Diff(DIFF_INSERT, decodeURI(param));
|
|
918
918
|
} catch (ex) {
|
|
919
919
|
throw new Error("Illegal escape in diff_fromDelta: " + param);
|
|
920
920
|
}
|
|
@@ -928,9 +928,9 @@ var require_diff_match_patch = __commonJS({
|
|
|
928
928
|
}
|
|
929
929
|
var text = text1.substring(pointer, pointer += n);
|
|
930
930
|
if (tokens[x].charAt(0) == "=") {
|
|
931
|
-
diffs[diffsLength++] = new
|
|
931
|
+
diffs[diffsLength++] = new diff_match_patch3.Diff(DIFF_EQUAL, text);
|
|
932
932
|
} else {
|
|
933
|
-
diffs[diffsLength++] = new
|
|
933
|
+
diffs[diffsLength++] = new diff_match_patch3.Diff(DIFF_DELETE, text);
|
|
934
934
|
}
|
|
935
935
|
break;
|
|
936
936
|
default:
|
|
@@ -944,7 +944,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
944
944
|
}
|
|
945
945
|
return diffs;
|
|
946
946
|
};
|
|
947
|
-
|
|
947
|
+
diff_match_patch3.prototype.match_main = function(text, pattern, loc) {
|
|
948
948
|
if (text == null || pattern == null || loc == null) {
|
|
949
949
|
throw new Error("Null input. (match_main)");
|
|
950
950
|
}
|
|
@@ -959,7 +959,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
959
959
|
return this.match_bitap_(text, pattern, loc);
|
|
960
960
|
}
|
|
961
961
|
};
|
|
962
|
-
|
|
962
|
+
diff_match_patch3.prototype.match_bitap_ = function(text, pattern, loc) {
|
|
963
963
|
if (pattern.length > this.Match_MaxBits) {
|
|
964
964
|
throw new Error("Pattern too long for this browser.");
|
|
965
965
|
}
|
|
@@ -1030,7 +1030,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1030
1030
|
}
|
|
1031
1031
|
return best_loc;
|
|
1032
1032
|
};
|
|
1033
|
-
|
|
1033
|
+
diff_match_patch3.prototype.match_alphabet_ = function(pattern) {
|
|
1034
1034
|
var s = {};
|
|
1035
1035
|
for (var i = 0; i < pattern.length; i++) {
|
|
1036
1036
|
s[pattern.charAt(i)] = 0;
|
|
@@ -1040,7 +1040,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1040
1040
|
}
|
|
1041
1041
|
return s;
|
|
1042
1042
|
};
|
|
1043
|
-
|
|
1043
|
+
diff_match_patch3.prototype.patch_addContext_ = function(patch, text) {
|
|
1044
1044
|
if (text.length == 0) {
|
|
1045
1045
|
return;
|
|
1046
1046
|
}
|
|
@@ -1059,21 +1059,21 @@ var require_diff_match_patch = __commonJS({
|
|
|
1059
1059
|
padding += this.Patch_Margin;
|
|
1060
1060
|
var prefix = text.substring(patch.start2 - padding, patch.start2);
|
|
1061
1061
|
if (prefix) {
|
|
1062
|
-
patch.diffs.unshift(new
|
|
1062
|
+
patch.diffs.unshift(new diff_match_patch3.Diff(DIFF_EQUAL, prefix));
|
|
1063
1063
|
}
|
|
1064
1064
|
var suffix = text.substring(
|
|
1065
1065
|
patch.start2 + patch.length1,
|
|
1066
1066
|
patch.start2 + patch.length1 + padding
|
|
1067
1067
|
);
|
|
1068
1068
|
if (suffix) {
|
|
1069
|
-
patch.diffs.push(new
|
|
1069
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, suffix));
|
|
1070
1070
|
}
|
|
1071
1071
|
patch.start1 -= prefix.length;
|
|
1072
1072
|
patch.start2 -= prefix.length;
|
|
1073
1073
|
patch.length1 += prefix.length + suffix.length;
|
|
1074
1074
|
patch.length2 += prefix.length + suffix.length;
|
|
1075
1075
|
};
|
|
1076
|
-
|
|
1076
|
+
diff_match_patch3.prototype.patch_make = function(a, opt_b, opt_c) {
|
|
1077
1077
|
var text1, diffs;
|
|
1078
1078
|
if (typeof a == "string" && typeof opt_b == "string" && typeof opt_c == "undefined") {
|
|
1079
1079
|
text1 = /** @type {string} */
|
|
@@ -1109,7 +1109,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1109
1109
|
return [];
|
|
1110
1110
|
}
|
|
1111
1111
|
var patches = [];
|
|
1112
|
-
var patch = new
|
|
1112
|
+
var patch = new diff_match_patch3.patch_obj();
|
|
1113
1113
|
var patchDiffLength = 0;
|
|
1114
1114
|
var char_count1 = 0;
|
|
1115
1115
|
var char_count2 = 0;
|
|
@@ -1142,7 +1142,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1142
1142
|
if (patchDiffLength) {
|
|
1143
1143
|
this.patch_addContext_(patch, prepatch_text);
|
|
1144
1144
|
patches.push(patch);
|
|
1145
|
-
patch = new
|
|
1145
|
+
patch = new diff_match_patch3.patch_obj();
|
|
1146
1146
|
patchDiffLength = 0;
|
|
1147
1147
|
prepatch_text = postpatch_text;
|
|
1148
1148
|
char_count1 = char_count2;
|
|
@@ -1163,14 +1163,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1163
1163
|
}
|
|
1164
1164
|
return patches;
|
|
1165
1165
|
};
|
|
1166
|
-
|
|
1166
|
+
diff_match_patch3.prototype.patch_deepCopy = function(patches) {
|
|
1167
1167
|
var patchesCopy = [];
|
|
1168
1168
|
for (var x = 0; x < patches.length; x++) {
|
|
1169
1169
|
var patch = patches[x];
|
|
1170
|
-
var patchCopy = new
|
|
1170
|
+
var patchCopy = new diff_match_patch3.patch_obj();
|
|
1171
1171
|
patchCopy.diffs = [];
|
|
1172
1172
|
for (var y = 0; y < patch.diffs.length; y++) {
|
|
1173
|
-
patchCopy.diffs[y] = new
|
|
1173
|
+
patchCopy.diffs[y] = new diff_match_patch3.Diff(patch.diffs[y][0], patch.diffs[y][1]);
|
|
1174
1174
|
}
|
|
1175
1175
|
patchCopy.start1 = patch.start1;
|
|
1176
1176
|
patchCopy.start2 = patch.start2;
|
|
@@ -1180,7 +1180,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1180
1180
|
}
|
|
1181
1181
|
return patchesCopy;
|
|
1182
1182
|
};
|
|
1183
|
-
|
|
1183
|
+
diff_match_patch3.prototype.patch_apply = function(patches, text) {
|
|
1184
1184
|
if (patches.length == 0) {
|
|
1185
1185
|
return [text, []];
|
|
1186
1186
|
}
|
|
@@ -1260,7 +1260,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1260
1260
|
text = text.substring(nullPadding.length, text.length - nullPadding.length);
|
|
1261
1261
|
return [text, results];
|
|
1262
1262
|
};
|
|
1263
|
-
|
|
1263
|
+
diff_match_patch3.prototype.patch_addPadding = function(patches) {
|
|
1264
1264
|
var paddingLength = this.Patch_Margin;
|
|
1265
1265
|
var nullPadding = "";
|
|
1266
1266
|
for (var x = 1; x <= paddingLength; x++) {
|
|
@@ -1273,7 +1273,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1273
1273
|
var patch = patches[0];
|
|
1274
1274
|
var diffs = patch.diffs;
|
|
1275
1275
|
if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) {
|
|
1276
|
-
diffs.unshift(new
|
|
1276
|
+
diffs.unshift(new diff_match_patch3.Diff(DIFF_EQUAL, nullPadding));
|
|
1277
1277
|
patch.start1 -= paddingLength;
|
|
1278
1278
|
patch.start2 -= paddingLength;
|
|
1279
1279
|
patch.length1 += paddingLength;
|
|
@@ -1289,7 +1289,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1289
1289
|
patch = patches[patches.length - 1];
|
|
1290
1290
|
diffs = patch.diffs;
|
|
1291
1291
|
if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) {
|
|
1292
|
-
diffs.push(new
|
|
1292
|
+
diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, nullPadding));
|
|
1293
1293
|
patch.length1 += paddingLength;
|
|
1294
1294
|
patch.length2 += paddingLength;
|
|
1295
1295
|
} else if (paddingLength > diffs[diffs.length - 1][1].length) {
|
|
@@ -1300,7 +1300,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1300
1300
|
}
|
|
1301
1301
|
return nullPadding;
|
|
1302
1302
|
};
|
|
1303
|
-
|
|
1303
|
+
diff_match_patch3.prototype.patch_splitMax = function(patches) {
|
|
1304
1304
|
var patch_size = this.Match_MaxBits;
|
|
1305
1305
|
for (var x = 0; x < patches.length; x++) {
|
|
1306
1306
|
if (patches[x].length1 <= patch_size) {
|
|
@@ -1312,13 +1312,13 @@ var require_diff_match_patch = __commonJS({
|
|
|
1312
1312
|
var start2 = bigpatch.start2;
|
|
1313
1313
|
var precontext = "";
|
|
1314
1314
|
while (bigpatch.diffs.length !== 0) {
|
|
1315
|
-
var patch = new
|
|
1315
|
+
var patch = new diff_match_patch3.patch_obj();
|
|
1316
1316
|
var empty = true;
|
|
1317
1317
|
patch.start1 = start1 - precontext.length;
|
|
1318
1318
|
patch.start2 = start2 - precontext.length;
|
|
1319
1319
|
if (precontext !== "") {
|
|
1320
1320
|
patch.length1 = patch.length2 = precontext.length;
|
|
1321
|
-
patch.diffs.push(new
|
|
1321
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, precontext));
|
|
1322
1322
|
}
|
|
1323
1323
|
while (bigpatch.diffs.length !== 0 && patch.length1 < patch_size - this.Patch_Margin) {
|
|
1324
1324
|
var diff_type = bigpatch.diffs[0][0];
|
|
@@ -1332,7 +1332,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1332
1332
|
patch.length1 += diff_text.length;
|
|
1333
1333
|
start1 += diff_text.length;
|
|
1334
1334
|
empty = false;
|
|
1335
|
-
patch.diffs.push(new
|
|
1335
|
+
patch.diffs.push(new diff_match_patch3.Diff(diff_type, diff_text));
|
|
1336
1336
|
bigpatch.diffs.shift();
|
|
1337
1337
|
} else {
|
|
1338
1338
|
diff_text = diff_text.substring(
|
|
@@ -1347,7 +1347,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1347
1347
|
} else {
|
|
1348
1348
|
empty = false;
|
|
1349
1349
|
}
|
|
1350
|
-
patch.diffs.push(new
|
|
1350
|
+
patch.diffs.push(new diff_match_patch3.Diff(diff_type, diff_text));
|
|
1351
1351
|
if (diff_text == bigpatch.diffs[0][1]) {
|
|
1352
1352
|
bigpatch.diffs.shift();
|
|
1353
1353
|
} else {
|
|
@@ -1364,7 +1364,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1364
1364
|
if (patch.diffs.length !== 0 && patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) {
|
|
1365
1365
|
patch.diffs[patch.diffs.length - 1][1] += postcontext;
|
|
1366
1366
|
} else {
|
|
1367
|
-
patch.diffs.push(new
|
|
1367
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, postcontext));
|
|
1368
1368
|
}
|
|
1369
1369
|
}
|
|
1370
1370
|
if (!empty) {
|
|
@@ -1373,14 +1373,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1373
1373
|
}
|
|
1374
1374
|
}
|
|
1375
1375
|
};
|
|
1376
|
-
|
|
1376
|
+
diff_match_patch3.prototype.patch_toText = function(patches) {
|
|
1377
1377
|
var text = [];
|
|
1378
1378
|
for (var x = 0; x < patches.length; x++) {
|
|
1379
1379
|
text[x] = patches[x];
|
|
1380
1380
|
}
|
|
1381
1381
|
return text.join("");
|
|
1382
1382
|
};
|
|
1383
|
-
|
|
1383
|
+
diff_match_patch3.prototype.patch_fromText = function(textline) {
|
|
1384
1384
|
var patches = [];
|
|
1385
1385
|
if (!textline) {
|
|
1386
1386
|
return patches;
|
|
@@ -1393,7 +1393,7 @@ var require_diff_match_patch = __commonJS({
|
|
|
1393
1393
|
if (!m) {
|
|
1394
1394
|
throw new Error("Invalid patch string: " + text[textPointer]);
|
|
1395
1395
|
}
|
|
1396
|
-
var patch = new
|
|
1396
|
+
var patch = new diff_match_patch3.patch_obj();
|
|
1397
1397
|
patches.push(patch);
|
|
1398
1398
|
patch.start1 = parseInt(m[1], 10);
|
|
1399
1399
|
if (m[2] === "") {
|
|
@@ -1424,11 +1424,11 @@ var require_diff_match_patch = __commonJS({
|
|
|
1424
1424
|
throw new Error("Illegal escape in patch_fromText: " + line);
|
|
1425
1425
|
}
|
|
1426
1426
|
if (sign == "-") {
|
|
1427
|
-
patch.diffs.push(new
|
|
1427
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_DELETE, line));
|
|
1428
1428
|
} else if (sign == "+") {
|
|
1429
|
-
patch.diffs.push(new
|
|
1429
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_INSERT, line));
|
|
1430
1430
|
} else if (sign == " ") {
|
|
1431
|
-
patch.diffs.push(new
|
|
1431
|
+
patch.diffs.push(new diff_match_patch3.Diff(DIFF_EQUAL, line));
|
|
1432
1432
|
} else if (sign == "@") {
|
|
1433
1433
|
break;
|
|
1434
1434
|
} else if (sign === "") {
|
|
@@ -1440,14 +1440,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1440
1440
|
}
|
|
1441
1441
|
return patches;
|
|
1442
1442
|
};
|
|
1443
|
-
|
|
1443
|
+
diff_match_patch3.patch_obj = function() {
|
|
1444
1444
|
this.diffs = [];
|
|
1445
1445
|
this.start1 = null;
|
|
1446
1446
|
this.start2 = null;
|
|
1447
1447
|
this.length1 = 0;
|
|
1448
1448
|
this.length2 = 0;
|
|
1449
1449
|
};
|
|
1450
|
-
|
|
1450
|
+
diff_match_patch3.patch_obj.prototype.toString = function() {
|
|
1451
1451
|
var coords1, coords2;
|
|
1452
1452
|
if (this.length1 === 0) {
|
|
1453
1453
|
coords1 = this.start1 + ",0";
|
|
@@ -1481,48 +1481,14 @@ var require_diff_match_patch = __commonJS({
|
|
|
1481
1481
|
}
|
|
1482
1482
|
return text.join("").replace(/%20/g, " ");
|
|
1483
1483
|
};
|
|
1484
|
-
module.exports =
|
|
1485
|
-
module.exports["diff_match_patch"] =
|
|
1484
|
+
module.exports = diff_match_patch3;
|
|
1485
|
+
module.exports["diff_match_patch"] = diff_match_patch3;
|
|
1486
1486
|
module.exports["DIFF_DELETE"] = DIFF_DELETE;
|
|
1487
1487
|
module.exports["DIFF_INSERT"] = DIFF_INSERT;
|
|
1488
1488
|
module.exports["DIFF_EQUAL"] = DIFF_EQUAL;
|
|
1489
1489
|
}
|
|
1490
1490
|
});
|
|
1491
1491
|
|
|
1492
|
-
// adapters/xml-adapter.js
|
|
1493
|
-
var _DOMParser = globalThis.DOMParser;
|
|
1494
|
-
var _XMLSerializer = globalThis.XMLSerializer;
|
|
1495
|
-
function configureXmlProvider(options = {}) {
|
|
1496
|
-
if (options.DOMParser) _DOMParser = options.DOMParser;
|
|
1497
|
-
if (options.XMLSerializer) _XMLSerializer = options.XMLSerializer;
|
|
1498
|
-
}
|
|
1499
|
-
function createParser() {
|
|
1500
|
-
if (!_DOMParser && globalThis.DOMParser) {
|
|
1501
|
-
_DOMParser = globalThis.DOMParser;
|
|
1502
|
-
}
|
|
1503
|
-
if (!_DOMParser) {
|
|
1504
|
-
throw new Error("DOMParser is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.");
|
|
1505
|
-
}
|
|
1506
|
-
return new _DOMParser();
|
|
1507
|
-
}
|
|
1508
|
-
function createSerializer() {
|
|
1509
|
-
if (!_XMLSerializer && globalThis.XMLSerializer) {
|
|
1510
|
-
_XMLSerializer = globalThis.XMLSerializer;
|
|
1511
|
-
}
|
|
1512
|
-
if (!_XMLSerializer) {
|
|
1513
|
-
throw new Error("XMLSerializer is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.");
|
|
1514
|
-
}
|
|
1515
|
-
return new _XMLSerializer();
|
|
1516
|
-
}
|
|
1517
|
-
function parseXml(xmlString, contentType = "text/xml") {
|
|
1518
|
-
const parser = createParser();
|
|
1519
|
-
return parser.parseFromString(xmlString, contentType);
|
|
1520
|
-
}
|
|
1521
|
-
function serializeXml(node) {
|
|
1522
|
-
const serializer = createSerializer();
|
|
1523
|
-
return serializer.serializeToString(node);
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
1492
|
// adapters/logger.js
|
|
1527
1493
|
var _logger = console;
|
|
1528
1494
|
var LEVELS = Object.freeze({
|
|
@@ -1565,6 +1531,86 @@ function error(...args) {
|
|
|
1565
1531
|
}))(...args);
|
|
1566
1532
|
}
|
|
1567
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
|
+
|
|
1568
1614
|
// adapters/config.js
|
|
1569
1615
|
var _defaultAuthor = "Author";
|
|
1570
1616
|
var _platform = "Unknown";
|
|
@@ -1786,37 +1832,108 @@ function escapeXml(str) {
|
|
|
1786
1832
|
if (!str) return "";
|
|
1787
1833
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1788
1834
|
}
|
|
1789
|
-
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
|
+
}
|
|
1790
1917
|
function getNextRevisionId() {
|
|
1791
|
-
return
|
|
1918
|
+
return defaultRevisionIdAllocator.next();
|
|
1792
1919
|
}
|
|
1793
1920
|
function getRevisionTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
1794
1921
|
return date.toISOString();
|
|
1795
1922
|
}
|
|
1796
|
-
function createRevisionMetadata(author) {
|
|
1923
|
+
function createRevisionMetadata(author, allocatorOrNode = null) {
|
|
1797
1924
|
const resolvedAuthor = typeof author === "string" && author.trim() ? author.trim() : getDefaultAuthor();
|
|
1925
|
+
const allocator = allocatorOrNode instanceof RevisionIdAllocator ? allocatorOrNode : getRevisionIdAllocatorForDocument(allocatorOrNode) || defaultRevisionIdAllocator;
|
|
1798
1926
|
return {
|
|
1799
|
-
id:
|
|
1927
|
+
id: allocator.next(),
|
|
1800
1928
|
author: resolvedAuthor,
|
|
1801
1929
|
date: getRevisionTimestamp()
|
|
1802
1930
|
};
|
|
1803
1931
|
}
|
|
1804
|
-
function seedRevisionIdsFromDocument(xmlDoc) {
|
|
1805
|
-
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
if ((attr.localName || "").toLowerCase() !== "id") continue;
|
|
1810
|
-
const parsed = Number.parseInt(attr.value, 10);
|
|
1811
|
-
if (Number.isFinite(parsed)) {
|
|
1812
|
-
maxFound = Math.max(maxFound, parsed);
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
if (maxFound >= revisionIdCounter) {
|
|
1817
|
-
revisionIdCounter = maxFound + 1;
|
|
1818
|
-
}
|
|
1819
|
-
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;
|
|
1820
1937
|
}
|
|
1821
1938
|
|
|
1822
1939
|
// core/paragraph-offset-policy.js
|
|
@@ -1894,10 +2011,12 @@ function ingestOoxml(ooxmlString, options = {}) {
|
|
|
1894
2011
|
return { runModel, acceptedText, pPr: null };
|
|
1895
2012
|
}
|
|
1896
2013
|
try {
|
|
1897
|
-
const
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
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
|
+
}
|
|
1901
2020
|
const parseError = getXmlParseError(doc);
|
|
1902
2021
|
if (parseError) {
|
|
1903
2022
|
error("OOXML parse error:", parseError.textContent);
|
|
@@ -2302,55 +2421,126 @@ function extractTrPr(trNode) {
|
|
|
2302
2421
|
|
|
2303
2422
|
// pipeline/diff-engine.js
|
|
2304
2423
|
var import_diff_match_patch = __toESM(require_diff_match_patch(), 1);
|
|
2305
|
-
var
|
|
2306
|
-
|
|
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 = {}) {
|
|
2307
2464
|
const wordArray = [];
|
|
2308
2465
|
const wordHash = /* @__PURE__ */ new Map();
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
let match;
|
|
2313
|
-
while ((match = regex.exec(text)) !== null) {
|
|
2314
|
-
if (match[1]) tokens.push(match[1]);
|
|
2315
|
-
if (match[2]) tokens.push(match[2]);
|
|
2316
|
-
}
|
|
2317
|
-
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}.`);
|
|
2318
2469
|
}
|
|
2319
2470
|
function mapTokensToChars(tokens) {
|
|
2320
2471
|
let chars = "";
|
|
2472
|
+
const tokenIds = [];
|
|
2321
2473
|
for (const token of tokens) {
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2474
|
+
let tokenId = wordHash.get(token);
|
|
2475
|
+
if (tokenId === void 0) {
|
|
2476
|
+
if (wordArray.length >= maxTokens) throw new DiffTokenLimitError(maxTokens);
|
|
2477
|
+
tokenId = wordArray.length;
|
|
2326
2478
|
wordArray.push(token);
|
|
2327
|
-
wordHash.set(token,
|
|
2328
|
-
chars += String.fromCharCode(charCode);
|
|
2479
|
+
wordHash.set(token, tokenId);
|
|
2329
2480
|
}
|
|
2481
|
+
tokenIds.push(tokenId);
|
|
2482
|
+
chars += String.fromCodePoint(TOKEN_CODE_POINT_BASE + tokenId);
|
|
2330
2483
|
}
|
|
2331
|
-
return chars;
|
|
2484
|
+
return { chars, tokenIds };
|
|
2332
2485
|
}
|
|
2333
2486
|
const tokens1 = tokenize(text1);
|
|
2334
2487
|
const tokens2 = tokenize(text2);
|
|
2488
|
+
const encoded1 = mapTokensToChars(tokens1);
|
|
2489
|
+
const encoded2 = mapTokensToChars(tokens2);
|
|
2335
2490
|
return {
|
|
2336
|
-
chars1:
|
|
2337
|
-
chars2:
|
|
2338
|
-
wordArray
|
|
2491
|
+
chars1: encoded1.chars,
|
|
2492
|
+
chars2: encoded2.chars,
|
|
2493
|
+
wordArray,
|
|
2494
|
+
tokenIds1: encoded1.tokenIds,
|
|
2495
|
+
tokenIds2: encoded2.tokenIds
|
|
2339
2496
|
};
|
|
2340
2497
|
}
|
|
2341
|
-
function
|
|
2342
|
-
const
|
|
2343
|
-
|
|
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]) => {
|
|
2344
2514
|
const parts = [];
|
|
2345
|
-
for (let
|
|
2346
|
-
const
|
|
2347
|
-
if (
|
|
2348
|
-
|
|
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.`);
|
|
2349
2519
|
}
|
|
2520
|
+
parts.push(wordArray[tokenId]);
|
|
2350
2521
|
}
|
|
2351
|
-
|
|
2352
|
-
}
|
|
2353
|
-
|
|
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;
|
|
2354
2544
|
}
|
|
2355
2545
|
function computeWordDiffs(originalText, newText, options = {}) {
|
|
2356
2546
|
if (originalText === newText) {
|
|
@@ -2363,12 +2553,16 @@ function computeWordDiffs(originalText, newText, options = {}) {
|
|
|
2363
2553
|
return [[-1, originalText]];
|
|
2364
2554
|
}
|
|
2365
2555
|
const { cleanupSemantic = true } = options;
|
|
2366
|
-
const {
|
|
2367
|
-
|
|
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));
|
|
2368
2562
|
if (cleanupSemantic) {
|
|
2369
|
-
|
|
2563
|
+
dmp.diff_cleanupSemantic(charDiffs);
|
|
2370
2564
|
}
|
|
2371
|
-
return
|
|
2565
|
+
return decodeBmpDiffs(charDiffs, wordArray);
|
|
2372
2566
|
}
|
|
2373
2567
|
function computeWordLevelDiffOps(originalText, newText, options = {}) {
|
|
2374
2568
|
if (originalText === newText) {
|
|
@@ -2559,7 +2753,6 @@ function applyPatches(splitModel, diffOps, options) {
|
|
|
2559
2753
|
function processInsertionOperation(context) {
|
|
2560
2754
|
const {
|
|
2561
2755
|
insertOp,
|
|
2562
|
-
splitModel,
|
|
2563
2756
|
styleLookup,
|
|
2564
2757
|
patchedModel,
|
|
2565
2758
|
state,
|
|
@@ -2824,7 +3017,7 @@ function buildParagraphOnlyPackage(paragraphXml) {
|
|
|
2824
3017
|
var XMLNS_ATTR_REGEX2 = /\s+xmlns:[^=]+="[^"]*"/g;
|
|
2825
3018
|
function serializeToOoxml(patchedModel, pPr, formatHints = [], options = {}) {
|
|
2826
3019
|
const serializationOptions = normalizeSerializationOptions(options);
|
|
2827
|
-
const {
|
|
3020
|
+
const { generateRedlines } = serializationOptions;
|
|
2828
3021
|
const paragraphs = [];
|
|
2829
3022
|
let currentPPrXml = "";
|
|
2830
3023
|
let currentPPrElement = null;
|
|
@@ -2912,7 +3105,8 @@ function normalizeSerializationOptions(options) {
|
|
|
2912
3105
|
return {
|
|
2913
3106
|
author: getDefaultAuthor(),
|
|
2914
3107
|
generateRedlines: true,
|
|
2915
|
-
font: options
|
|
3108
|
+
font: options,
|
|
3109
|
+
revisionIdAllocator: null
|
|
2916
3110
|
};
|
|
2917
3111
|
}
|
|
2918
3112
|
const normalized = options && typeof options === "object" ? options : {};
|
|
@@ -2920,7 +3114,8 @@ function normalizeSerializationOptions(options) {
|
|
|
2920
3114
|
return {
|
|
2921
3115
|
author: resolvedAuthor,
|
|
2922
3116
|
generateRedlines: normalized.generateRedlines ?? true,
|
|
2923
|
-
font: normalized.font ?? null
|
|
3117
|
+
font: normalized.font ?? null,
|
|
3118
|
+
revisionIdAllocator: normalized.revisionIdAllocator ?? null
|
|
2924
3119
|
};
|
|
2925
3120
|
}
|
|
2926
3121
|
function buildRunXmlWithHints(item, formatHints, options = {}) {
|
|
@@ -2958,7 +3153,10 @@ function buildSimpleRun(text, rPrXml) {
|
|
|
2958
3153
|
return `<w:r>${rPr}<w:t xml:space="preserve">${escapeXml(text)}</w:t></w:r>`;
|
|
2959
3154
|
}
|
|
2960
3155
|
function buildDeletionXml(item, options = {}) {
|
|
2961
|
-
const metadata = createRevisionMetadata(
|
|
3156
|
+
const metadata = createRevisionMetadata(
|
|
3157
|
+
options.author ?? getDefaultAuthor(),
|
|
3158
|
+
options.revisionIdAllocator
|
|
3159
|
+
);
|
|
2962
3160
|
const font = options.font ?? null;
|
|
2963
3161
|
let rPr = item.rPrXml ? stripNamespaceDeclarations(item.rPrXml) : "";
|
|
2964
3162
|
if (font) {
|
|
@@ -2967,7 +3165,10 @@ function buildDeletionXml(item, options = {}) {
|
|
|
2967
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>`;
|
|
2968
3166
|
}
|
|
2969
3167
|
function buildInsertionXml(item, formatHints, options = {}) {
|
|
2970
|
-
const metadata = createRevisionMetadata(
|
|
3168
|
+
const metadata = createRevisionMetadata(
|
|
3169
|
+
options.author ?? getDefaultAuthor(),
|
|
3170
|
+
options.revisionIdAllocator
|
|
3171
|
+
);
|
|
2971
3172
|
const font = options.font ?? null;
|
|
2972
3173
|
const applicableHints = getApplicableFormatHints(formatHints, item.startOffset, item.endOffset);
|
|
2973
3174
|
let innerContent = "";
|
|
@@ -3287,8 +3488,8 @@ var NumberingService = class {
|
|
|
3287
3488
|
|
|
3288
3489
|
// services/table-reconciliation.js
|
|
3289
3490
|
function generateTableOoxml(tableData, options = {}) {
|
|
3290
|
-
const { generateRedlines = false, author = "AI" } = options;
|
|
3291
|
-
const tableInsertMeta = generateRedlines ? createRevisionMetadata(author) : null;
|
|
3491
|
+
const { generateRedlines = false, author = "AI", revisionIdAllocator = null } = options;
|
|
3492
|
+
const tableInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
3292
3493
|
const numCols = tableData.headers?.length || (tableData.rows?.[0]?.length || 1);
|
|
3293
3494
|
const tblPr = `
|
|
3294
3495
|
<w:tblPr>
|
|
@@ -3323,7 +3524,11 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
3323
3524
|
startOffset: 0,
|
|
3324
3525
|
endOffset: cleanText.length
|
|
3325
3526
|
}];
|
|
3326
|
-
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3527
|
+
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3528
|
+
author,
|
|
3529
|
+
generateRedlines,
|
|
3530
|
+
revisionIdAllocator
|
|
3531
|
+
});
|
|
3327
3532
|
const tcPr = '<w:tcPr><w:tcW w:w="0" w:type="auto"/></w:tcPr>';
|
|
3328
3533
|
cellsXml += `<w:tc>${tcPr}${runsOoxml}</w:tc>`;
|
|
3329
3534
|
}
|
|
@@ -3381,7 +3586,7 @@ function diffTablesWithVirtualGrid(oldGrid, newTableData) {
|
|
|
3381
3586
|
return operations;
|
|
3382
3587
|
}
|
|
3383
3588
|
function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
3384
|
-
const { generateRedlines, author } = options;
|
|
3589
|
+
const { generateRedlines, author, revisionIdAllocator = null } = options;
|
|
3385
3590
|
const opIndex = buildTableOperationIndex(operations);
|
|
3386
3591
|
let rowsXml = "";
|
|
3387
3592
|
for (let row = 0; row < grid.rowCount; row++) {
|
|
@@ -3411,7 +3616,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
3411
3616
|
}
|
|
3412
3617
|
let trPr = grid.trPrList[row] || "<w:trPr/>";
|
|
3413
3618
|
if (rowDeleteOp && generateRedlines) {
|
|
3414
|
-
const metadata = createRevisionMetadata(author);
|
|
3619
|
+
const metadata = createRevisionMetadata(author, revisionIdAllocator);
|
|
3415
3620
|
const delMark = `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}"/>`;
|
|
3416
3621
|
if (trPr.includes("</w:trPr>")) {
|
|
3417
3622
|
trPr = trPr.replace("</w:trPr>", `${delMark}</w:trPr>`);
|
|
@@ -3424,7 +3629,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
3424
3629
|
const insertOps = opIndex.rowInsertOperations;
|
|
3425
3630
|
for (const op of insertOps) {
|
|
3426
3631
|
let cellsXml = "";
|
|
3427
|
-
const rowInsertMeta = generateRedlines ? createRevisionMetadata(author) : null;
|
|
3632
|
+
const rowInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
3428
3633
|
for (const cellText of op.cells) {
|
|
3429
3634
|
const { cleanText, formatHints } = preprocessMarkdown(cellText);
|
|
3430
3635
|
const runModel = [{
|
|
@@ -3435,7 +3640,11 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
3435
3640
|
startOffset: 0,
|
|
3436
3641
|
endOffset: cleanText.length
|
|
3437
3642
|
}];
|
|
3438
|
-
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3643
|
+
const runsOoxml = serializeToOoxml(runModel, null, formatHints, {
|
|
3644
|
+
author,
|
|
3645
|
+
generateRedlines,
|
|
3646
|
+
revisionIdAllocator
|
|
3647
|
+
});
|
|
3439
3648
|
cellsXml += `<w:tc><w:tcPr><w:tcW w:w="0" w:type="auto"/></w:tcPr>${runsOoxml}</w:tc>`;
|
|
3440
3649
|
}
|
|
3441
3650
|
let trPr = "<w:trPr/>";
|
|
@@ -3473,7 +3682,7 @@ function buildTableOperationIndex(operations) {
|
|
|
3473
3682
|
};
|
|
3474
3683
|
}
|
|
3475
3684
|
function reconcileCellContent(cell, newText, options) {
|
|
3476
|
-
const { generateRedlines, author } = options;
|
|
3685
|
+
const { generateRedlines, author, revisionIdAllocator = null } = options;
|
|
3477
3686
|
const { cleanText, formatHints } = preprocessMarkdown(newText);
|
|
3478
3687
|
const oldText = cell.getText();
|
|
3479
3688
|
const diffOps = computeWordLevelDiffOps(oldText, cleanText);
|
|
@@ -3484,7 +3693,11 @@ function reconcileCellContent(cell, newText, options) {
|
|
|
3484
3693
|
author,
|
|
3485
3694
|
formatHints
|
|
3486
3695
|
});
|
|
3487
|
-
const runsOoxml = serializeToOoxml(patchedModel, baseBlock.pPr, formatHints, {
|
|
3696
|
+
const runsOoxml = serializeToOoxml(patchedModel, baseBlock.pPr, formatHints, {
|
|
3697
|
+
author,
|
|
3698
|
+
generateRedlines,
|
|
3699
|
+
revisionIdAllocator
|
|
3700
|
+
});
|
|
3488
3701
|
return runsOoxml;
|
|
3489
3702
|
}
|
|
3490
3703
|
function serializeCellBlocks(blocks) {
|
|
@@ -3493,7 +3706,7 @@ function serializeCellBlocks(blocks) {
|
|
|
3493
3706
|
return runsOoxml;
|
|
3494
3707
|
}).join("");
|
|
3495
3708
|
}
|
|
3496
|
-
function buildTcXml(cell, content,
|
|
3709
|
+
function buildTcXml(cell, content, _options) {
|
|
3497
3710
|
let tcPr = cell.tcPrXml;
|
|
3498
3711
|
if (cell.colSpan > 1 && !tcPr.includes("gridSpan")) {
|
|
3499
3712
|
tcPr = tcPr.replace("</w:tcPr>", `<w:gridSpan w:val="${cell.colSpan}"/></w:tcPr>`);
|
|
@@ -3547,6 +3760,7 @@ async function executeListGeneration(options) {
|
|
|
3547
3760
|
generateRedlines = true,
|
|
3548
3761
|
author = "AI",
|
|
3549
3762
|
font = null,
|
|
3763
|
+
revisionIdAllocator = null,
|
|
3550
3764
|
numberingService
|
|
3551
3765
|
} = options;
|
|
3552
3766
|
const normalizedListText = normalizeCompositeListMarkers(cleanText);
|
|
@@ -3582,10 +3796,15 @@ async function executeListGeneration(options) {
|
|
|
3582
3796
|
results.push(serializeToOoxml(deletionRuns, null, [], {
|
|
3583
3797
|
author,
|
|
3584
3798
|
generateRedlines,
|
|
3585
|
-
font
|
|
3799
|
+
font,
|
|
3800
|
+
revisionIdAllocator
|
|
3586
3801
|
}));
|
|
3587
3802
|
}
|
|
3588
|
-
results.push(generateTableOoxml(tableData, {
|
|
3803
|
+
results.push(generateTableOoxml(tableData, {
|
|
3804
|
+
generateRedlines,
|
|
3805
|
+
author,
|
|
3806
|
+
revisionIdAllocator
|
|
3807
|
+
}));
|
|
3589
3808
|
i = tableBlock.endIndex;
|
|
3590
3809
|
continue;
|
|
3591
3810
|
}
|
|
@@ -3600,6 +3819,7 @@ async function executeListGeneration(options) {
|
|
|
3600
3819
|
generateRedlines,
|
|
3601
3820
|
author,
|
|
3602
3821
|
font,
|
|
3822
|
+
revisionIdAllocator,
|
|
3603
3823
|
deletionRuns
|
|
3604
3824
|
);
|
|
3605
3825
|
results.push(entry.ooxml);
|
|
@@ -3690,7 +3910,7 @@ function collectMarkdownTableBlock(lineMetadata, index) {
|
|
|
3690
3910
|
endIndex: cursor - 1
|
|
3691
3911
|
};
|
|
3692
3912
|
}
|
|
3693
|
-
function buildListEntry(line, lineIndex, indentStep, numberingContext, numberingService, generateRedlines, author, font, deletionRuns) {
|
|
3913
|
+
function buildListEntry(line, lineIndex, indentStep, numberingContext, numberingService, generateRedlines, author, font, revisionIdAllocator, deletionRuns) {
|
|
3694
3914
|
let pPrXml = "";
|
|
3695
3915
|
let segmentText = "";
|
|
3696
3916
|
if (line.headerMatch) {
|
|
@@ -3727,7 +3947,8 @@ function buildListEntry(line, lineIndex, indentStep, numberingContext, numbering
|
|
|
3727
3947
|
ooxml: serializeToOoxml(runModel, pPrXml, formatHints, {
|
|
3728
3948
|
author,
|
|
3729
3949
|
generateRedlines,
|
|
3730
|
-
font
|
|
3950
|
+
font,
|
|
3951
|
+
revisionIdAllocator
|
|
3731
3952
|
})
|
|
3732
3953
|
};
|
|
3733
3954
|
}
|
|
@@ -3758,6 +3979,7 @@ var ReconciliationPipeline = class {
|
|
|
3758
3979
|
this.validationMode = options.validationMode ?? "auto";
|
|
3759
3980
|
this.numberingService = options.numberingService || new NumberingService();
|
|
3760
3981
|
this.font = options.font || null;
|
|
3982
|
+
this.revisionIdAllocator = options.revisionIdAllocator || null;
|
|
3761
3983
|
this.platform = options.platform ?? getPlatform();
|
|
3762
3984
|
this.isWebPlatform = options.isWebPlatform ?? isWebPlatform(this.platform);
|
|
3763
3985
|
this.enableEventLoopYielding = options.enableEventLoopYielding ?? this.isWebPlatform;
|
|
@@ -3776,10 +3998,18 @@ var ReconciliationPipeline = class {
|
|
|
3776
3998
|
async execute(originalOoxml, newText, options = {}) {
|
|
3777
3999
|
const warnings = [];
|
|
3778
4000
|
try {
|
|
3779
|
-
const
|
|
3780
|
-
|
|
3781
|
-
return
|
|
3782
|
-
|
|
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;
|
|
3783
4013
|
const pElement = getFirstElementByTagNS(doc, "*", "p");
|
|
3784
4014
|
const { runModel, acceptedText, pPr } = ingestOoxml(originalOoxml, { xmlDoc: doc });
|
|
3785
4015
|
const numberingContext = pElement ? detectNumberingContext(pElement) : null;
|
|
@@ -3811,8 +4041,12 @@ var ReconciliationPipeline = class {
|
|
|
3811
4041
|
}
|
|
3812
4042
|
await this.maybeYield(runModel.length, Math.max(acceptedText.length, cleanText.length));
|
|
3813
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;
|
|
3814
4046
|
log(`[Reconcile] isTargetList: ${isTargetList}, paragraphCount: ${paragraphCount}`);
|
|
3815
|
-
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) {
|
|
3816
4050
|
log("[Reconcile] \u{1F3AF} ENTERING LIST GENERATION PATH");
|
|
3817
4051
|
log(`[Reconcile] cleanText preview: ${cleanText.substring(0, 100)}...`);
|
|
3818
4052
|
log(`[Reconcile] acceptedText preview: ${acceptedText.substring(0, 100)}...`);
|
|
@@ -3831,7 +4065,8 @@ var ReconciliationPipeline = class {
|
|
|
3831
4065
|
await this.maybeYield(patchedModel.length, Math.max(acceptedText.length, cleanText.length));
|
|
3832
4066
|
const resultOoxml = serializeToOoxml(patchedModel, pPr, formatHints, {
|
|
3833
4067
|
author: this.author,
|
|
3834
|
-
generateRedlines: this.generateRedlines
|
|
4068
|
+
generateRedlines: this.generateRedlines,
|
|
4069
|
+
revisionIdAllocator: this.revisionIdAllocator
|
|
3835
4070
|
});
|
|
3836
4071
|
if (this.shouldRunValidation()) {
|
|
3837
4072
|
const validation = this.validateBasic(resultOoxml);
|
|
@@ -3849,7 +4084,8 @@ var ReconciliationPipeline = class {
|
|
|
3849
4084
|
return {
|
|
3850
4085
|
ooxml: originalOoxml,
|
|
3851
4086
|
isValid: false,
|
|
3852
|
-
warnings: [`Pipeline error: ${error2.message}`]
|
|
4087
|
+
warnings: [`Pipeline error: ${error2.message}`],
|
|
4088
|
+
error: error2?.code ? { code: error2.code, message: error2.message } : void 0
|
|
3853
4089
|
};
|
|
3854
4090
|
}
|
|
3855
4091
|
}
|
|
@@ -3863,8 +4099,12 @@ var ReconciliationPipeline = class {
|
|
|
3863
4099
|
const errors = [];
|
|
3864
4100
|
try {
|
|
3865
4101
|
const wrappedXml = `<root xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${ooxml}</root>`;
|
|
3866
|
-
const
|
|
3867
|
-
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
|
+
}
|
|
3868
4108
|
const parseError = getXmlParseError(doc);
|
|
3869
4109
|
if (parseError) {
|
|
3870
4110
|
errors.push("Generated OOXML is not well-formed XML: " + parseError.textContent.substring(0, 100));
|
|
@@ -3935,6 +4175,7 @@ var ReconciliationPipeline = class {
|
|
|
3935
4175
|
generateRedlines: this.generateRedlines,
|
|
3936
4176
|
author: this.author,
|
|
3937
4177
|
font: this.font,
|
|
4178
|
+
revisionIdAllocator: this.revisionIdAllocator,
|
|
3938
4179
|
numberingService: this.numberingService
|
|
3939
4180
|
});
|
|
3940
4181
|
}
|
|
@@ -3964,7 +4205,8 @@ var ReconciliationPipeline = class {
|
|
|
3964
4205
|
}
|
|
3965
4206
|
const tableOoxml = generateTableOoxml(tableData, {
|
|
3966
4207
|
generateRedlines: this.generateRedlines,
|
|
3967
|
-
author: this.author
|
|
4208
|
+
author: this.author,
|
|
4209
|
+
revisionIdAllocator: this.revisionIdAllocator
|
|
3968
4210
|
});
|
|
3969
4211
|
return {
|
|
3970
4212
|
ooxml: tableOoxml,
|
|
@@ -4340,7 +4582,7 @@ function extractFormattingFromOoxml(xmlDoc) {
|
|
|
4340
4582
|
// engine/run-builders.js
|
|
4341
4583
|
function createTrackChange(xmlDoc, type, run, author) {
|
|
4342
4584
|
const wrapper = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
4343
|
-
const metadata = createRevisionMetadata(author);
|
|
4585
|
+
const metadata = createRevisionMetadata(author, xmlDoc);
|
|
4344
4586
|
wrapper.setAttribute("w:id", String(metadata.id));
|
|
4345
4587
|
wrapper.setAttribute("w:author", metadata.author);
|
|
4346
4588
|
wrapper.setAttribute("w:date", metadata.date);
|
|
@@ -4382,7 +4624,7 @@ function markParagraphMark(xmlDoc, paragraph, author, type) {
|
|
|
4382
4624
|
}
|
|
4383
4625
|
}
|
|
4384
4626
|
const marker = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
4385
|
-
const metadata = createRevisionMetadata(author);
|
|
4627
|
+
const metadata = createRevisionMetadata(author, xmlDoc);
|
|
4386
4628
|
marker.setAttribute("w:id", String(metadata.id));
|
|
4387
4629
|
marker.setAttribute("w:author", metadata.author);
|
|
4388
4630
|
marker.setAttribute("w:date", metadata.date);
|
|
@@ -4521,7 +4763,7 @@ function injectFormattingToRPr(xmlDoc, baseRPr, format, author, generateRedlines
|
|
|
4521
4763
|
}
|
|
4522
4764
|
function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceNode) {
|
|
4523
4765
|
const rPrChange = createWordElement(xmlDoc, "w:rPrChange");
|
|
4524
|
-
const metadata = createRevisionMetadata(author);
|
|
4766
|
+
const metadata = createRevisionMetadata(author, xmlDoc);
|
|
4525
4767
|
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
4526
4768
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
4527
4769
|
rPrChange.setAttribute("w:date", dateStr || metadata.date);
|
|
@@ -5351,11 +5593,11 @@ function insertTextRuns(xmlDoc, parent, referenceNode, text, baseRPr, author, fo
|
|
|
5351
5593
|
}
|
|
5352
5594
|
|
|
5353
5595
|
// engine/surgical-mode.js
|
|
5354
|
-
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 = {}) {
|
|
5355
5597
|
void originalText;
|
|
5356
5598
|
const allParagraphs = targetParagraph ? [targetParagraph] : getDocumentParagraphs(xmlDoc);
|
|
5357
5599
|
const { fullText, textSpans } = buildSurgicalTextSpans(allParagraphs);
|
|
5358
|
-
const diffs = computeWordDiffs(fullText, modifiedText);
|
|
5600
|
+
const diffs = computeWordDiffs(fullText, modifiedText, diffOptions);
|
|
5359
5601
|
const spanIndex = buildSpanIndex(textSpans);
|
|
5360
5602
|
let originalPos = 0;
|
|
5361
5603
|
let newPos = 0;
|
|
@@ -5398,10 +5640,28 @@ function applySurgicalMode(xmlDoc, originalText, modifiedText, serializer, autho
|
|
|
5398
5640
|
}
|
|
5399
5641
|
|
|
5400
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
|
+
}
|
|
5401
5651
|
function createRangeCursorLookup2(ranges) {
|
|
5402
5652
|
let cursor = 0;
|
|
5403
5653
|
return {
|
|
5404
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
|
+
}
|
|
5405
5665
|
while (cursor < ranges.length && ranges[cursor].end <= index) {
|
|
5406
5666
|
cursor++;
|
|
5407
5667
|
}
|
|
@@ -5422,11 +5682,11 @@ function indexSentinelsByStart(sentinelMap) {
|
|
|
5422
5682
|
});
|
|
5423
5683
|
return sentinelMapByStart;
|
|
5424
5684
|
}
|
|
5425
|
-
function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
5685
|
+
function buildReconstructionMapping(xmlDoc, modifiedText, selectedParagraphs = null) {
|
|
5426
5686
|
const rootElement = xmlDoc.documentElement;
|
|
5427
|
-
const isBodyRoot = rootElement
|
|
5428
|
-
const paragraphs = getDocumentParagraphs(xmlDoc);
|
|
5429
|
-
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");
|
|
5430
5690
|
if (!body && isBodyRoot) body = rootElement;
|
|
5431
5691
|
let originalFullText = "";
|
|
5432
5692
|
const propertyMap = [];
|
|
@@ -5434,7 +5694,8 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5434
5694
|
const sentinelMap = [];
|
|
5435
5695
|
const referenceMap = /* @__PURE__ */ new Map();
|
|
5436
5696
|
const tokenToCharMap = /* @__PURE__ */ new Map();
|
|
5437
|
-
|
|
5697
|
+
const breakChars = /* @__PURE__ */ new Set();
|
|
5698
|
+
const characterState = { nextCharCode: 57344 };
|
|
5438
5699
|
const uniqueContainers = /* @__PURE__ */ new Set();
|
|
5439
5700
|
paragraphs.forEach((paragraph, paragraphIndex) => {
|
|
5440
5701
|
const paragraphStart = originalFullText.length;
|
|
@@ -5446,15 +5707,13 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5446
5707
|
sentinelMap,
|
|
5447
5708
|
referenceMap,
|
|
5448
5709
|
tokenToCharMap,
|
|
5449
|
-
|
|
5710
|
+
characterState,
|
|
5711
|
+
breakChars
|
|
5450
5712
|
);
|
|
5451
|
-
if (referenceMap.size > tokenToCharMap.size) {
|
|
5452
|
-
nextCharCode++;
|
|
5453
|
-
}
|
|
5454
5713
|
});
|
|
5455
5714
|
originalFullText = appendParagraphBoundary(originalFullText, paragraphIndex, paragraphs.length);
|
|
5456
5715
|
const paragraphEnd = originalFullText.length;
|
|
5457
|
-
const pPr =
|
|
5716
|
+
const pPr = getFirstElementByTagNSOrTag(paragraph, NS_W, "pPr");
|
|
5458
5717
|
const container = paragraph.parentNode;
|
|
5459
5718
|
if (container) uniqueContainers.add(container);
|
|
5460
5719
|
paragraphMap.push({
|
|
@@ -5464,7 +5723,13 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5464
5723
|
container: container || body
|
|
5465
5724
|
});
|
|
5466
5725
|
});
|
|
5467
|
-
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);
|
|
5468
5733
|
tokenToCharMap.forEach((char, tokenString) => {
|
|
5469
5734
|
const escapedToken = tokenString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
5470
5735
|
processedModifiedText = processedModifiedText.replace(new RegExp(escapedToken, "g"), char);
|
|
@@ -5520,6 +5785,124 @@ function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
5520
5785
|
isParagraphStart: (index) => paragraphStarts.has(index)
|
|
5521
5786
|
};
|
|
5522
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
|
+
}
|
|
5523
5906
|
function preserveReferencePlaceholders(originalFullText, modifiedText, referenceMap) {
|
|
5524
5907
|
let result = modifiedText;
|
|
5525
5908
|
for (const referenceChar of referenceMap.keys()) {
|
|
@@ -5539,28 +5922,28 @@ function preserveReferencePlaceholders(originalFullText, modifiedText, reference
|
|
|
5539
5922
|
}
|
|
5540
5923
|
return result;
|
|
5541
5924
|
}
|
|
5542
|
-
function processChildNode(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap,
|
|
5543
|
-
if (child
|
|
5544
|
-
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);
|
|
5545
5928
|
}
|
|
5546
|
-
if (child
|
|
5929
|
+
if (isWordElement(child, "hyperlink")) {
|
|
5547
5930
|
return processHyperlinkForReconstruction(child, originalFullText, propertyMap);
|
|
5548
5931
|
}
|
|
5549
|
-
if (
|
|
5932
|
+
if (isWordElement(child, "sdt") || isWordElement(child, "oMath") || localNameOf(child) === "oMath" || isWordElement(child, "bookmarkStart") || isWordElement(child, "bookmarkEnd")) {
|
|
5550
5933
|
sentinelMap.push({ start: originalFullText.length, node: child });
|
|
5551
5934
|
return originalFullText + "\uFFFC";
|
|
5552
5935
|
}
|
|
5553
|
-
if (
|
|
5936
|
+
if (isWordElement(child, "commentRangeStart") || isWordElement(child, "commentRangeEnd")) {
|
|
5554
5937
|
sentinelMap.push({ start: originalFullText.length, node: child, isCommentMarker: true });
|
|
5555
5938
|
return originalFullText;
|
|
5556
5939
|
}
|
|
5557
5940
|
return originalFullText;
|
|
5558
5941
|
}
|
|
5559
|
-
function processRunForReconstruction(runElement, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap,
|
|
5942
|
+
function processRunForReconstruction(runElement, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, characterState, breakChars) {
|
|
5560
5943
|
let fullText = originalFullText;
|
|
5561
|
-
const rPr =
|
|
5944
|
+
const rPr = getFirstElementByTagNSOrTag(runElement, NS_W, "rPr");
|
|
5562
5945
|
Array.from(runElement.childNodes).forEach((runChild) => {
|
|
5563
|
-
if (runChild
|
|
5946
|
+
if (isWordElement(runChild, "t")) {
|
|
5564
5947
|
const textContent = runChild.textContent || "";
|
|
5565
5948
|
if (textContent.length > 0) {
|
|
5566
5949
|
propertyMap.push({
|
|
@@ -5570,38 +5953,47 @@ function processRunForReconstruction(runElement, originalFullText, propertyMap,
|
|
|
5570
5953
|
});
|
|
5571
5954
|
fullText += textContent;
|
|
5572
5955
|
}
|
|
5573
|
-
} else if (runChild
|
|
5574
|
-
|
|
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;
|
|
5575
5961
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5576
|
-
} else if (runChild
|
|
5962
|
+
} else if (isWordElement(runChild, "tab")) {
|
|
5577
5963
|
fullText += " ";
|
|
5578
5964
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5579
|
-
} else if (runChild
|
|
5965
|
+
} else if (isWordElement(runChild, "noBreakHyphen")) {
|
|
5580
5966
|
fullText += "\u2011";
|
|
5581
5967
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5582
|
-
} else if (["
|
|
5583
|
-
const textBoxContent =
|
|
5584
|
-
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;
|
|
5585
5973
|
sentinelMap.push({
|
|
5586
5974
|
start: fullText.length,
|
|
5587
5975
|
node: runChild,
|
|
5976
|
+
wrapInRun: true,
|
|
5977
|
+
rPr,
|
|
5978
|
+
zeroWidth: isFieldStructure,
|
|
5979
|
+
affinity: fieldCharType === "end" ? "left" : "right",
|
|
5588
5980
|
isTextBox: hasTextBox,
|
|
5589
5981
|
originalContainer: hasTextBox ? textBoxContent : void 0
|
|
5590
5982
|
});
|
|
5591
5983
|
fullText += "\uFFFC";
|
|
5592
5984
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5593
|
-
} else if (runChild
|
|
5594
|
-
const id = runChild
|
|
5985
|
+
} else if (isWordElement(runChild, "footnoteReference") || isWordElement(runChild, "endnoteReference")) {
|
|
5986
|
+
const id = wordAttribute(runChild, "id");
|
|
5595
5987
|
if (id) {
|
|
5596
|
-
const type = runChild
|
|
5988
|
+
const type = isWordElement(runChild, "footnoteReference") ? "FN" : "EN";
|
|
5597
5989
|
const tokenString = `{{__${type}_${id}__}}`;
|
|
5598
|
-
const char = String.fromCharCode(nextCharCode);
|
|
5990
|
+
const char = String.fromCharCode(characterState.nextCharCode++);
|
|
5599
5991
|
referenceMap.set(char, runChild);
|
|
5600
5992
|
tokenToCharMap.set(tokenString, char);
|
|
5601
5993
|
fullText += char;
|
|
5602
5994
|
propertyMap.push({ start: fullText.length - 1, end: fullText.length, rPr });
|
|
5603
5995
|
}
|
|
5604
|
-
} else if (runChild
|
|
5996
|
+
} else if (isWordElement(runChild, "commentReference")) {
|
|
5605
5997
|
sentinelMap.push({ start: fullText.length, node: runChild, isCommentMarker: true });
|
|
5606
5998
|
}
|
|
5607
5999
|
});
|
|
@@ -5610,9 +6002,9 @@ function processRunForReconstruction(runElement, originalFullText, propertyMap,
|
|
|
5610
6002
|
function processHyperlinkForReconstruction(hyperlinkElement, originalFullText, propertyMap) {
|
|
5611
6003
|
let fullText = originalFullText;
|
|
5612
6004
|
Array.from(hyperlinkElement.childNodes).forEach((hyperlinkChild) => {
|
|
5613
|
-
if (hyperlinkChild
|
|
5614
|
-
const rPr =
|
|
5615
|
-
const texts =
|
|
6005
|
+
if (!isWordElement(hyperlinkChild, "r")) return;
|
|
6006
|
+
const rPr = getFirstElementByTagNSOrTag(hyperlinkChild, NS_W, "rPr");
|
|
6007
|
+
const texts = getElementsByTagNSOrTag(hyperlinkChild, NS_W, "t");
|
|
5616
6008
|
texts.forEach((textNode) => {
|
|
5617
6009
|
const textContent = textNode.textContent || "";
|
|
5618
6010
|
if (textContent.length === 0) return;
|
|
@@ -5632,7 +6024,6 @@ function processHyperlinkForReconstruction(hyperlinkElement, originalFullText, p
|
|
|
5632
6024
|
function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, formatHints, generateRedlines = true) {
|
|
5633
6025
|
const {
|
|
5634
6026
|
paragraphs,
|
|
5635
|
-
paragraphMap,
|
|
5636
6027
|
containerFragments,
|
|
5637
6028
|
sentinelMapByStart,
|
|
5638
6029
|
referenceMap,
|
|
@@ -5655,10 +6046,16 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5655
6046
|
}
|
|
5656
6047
|
let currentOriginalIndex = 0;
|
|
5657
6048
|
let currentInsertOffset = 0;
|
|
6049
|
+
let pendingReplacementStart = null;
|
|
5658
6050
|
const emittedCommentMarkers = /* @__PURE__ */ new WeakSet();
|
|
5659
6051
|
for (const [op, text] of diffs) {
|
|
5660
6052
|
if (op === 0 || op === -1) {
|
|
5661
6053
|
const type = op === 0 ? "equal" : "delete";
|
|
6054
|
+
if (op === 0) {
|
|
6055
|
+
pendingReplacementStart = null;
|
|
6056
|
+
} else if (pendingReplacementStart === null) {
|
|
6057
|
+
pendingReplacementStart = currentOriginalIndex;
|
|
6058
|
+
}
|
|
5662
6059
|
let offset = 0;
|
|
5663
6060
|
while (offset < text.length) {
|
|
5664
6061
|
const chunkStart = currentOriginalIndex + offset;
|
|
@@ -5695,7 +6092,8 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5695
6092
|
continue;
|
|
5696
6093
|
}
|
|
5697
6094
|
if (op === 1) {
|
|
5698
|
-
const
|
|
6095
|
+
const propertyIndex = pendingReplacementStart !== null ? pendingReplacementStart : currentOriginalIndex > 0 && !isParagraphStart(currentOriginalIndex) ? currentOriginalIndex - 1 : currentOriginalIndex;
|
|
6096
|
+
const properties = getRunProperties(propertyIndex);
|
|
5699
6097
|
const appendResult = appendTextToCurrent(
|
|
5700
6098
|
xmlDoc,
|
|
5701
6099
|
text,
|
|
@@ -5718,29 +6116,46 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
5718
6116
|
);
|
|
5719
6117
|
currentParagraph = appendResult.currentParagraph;
|
|
5720
6118
|
currentInsertOffset += text.length;
|
|
6119
|
+
pendingReplacementStart = null;
|
|
5721
6120
|
}
|
|
5722
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
|
+
});
|
|
5723
6131
|
paragraphs.forEach((paragraph) => {
|
|
5724
6132
|
if (paragraph.parentNode) {
|
|
5725
6133
|
paragraph.parentNode.removeChild(paragraph);
|
|
5726
6134
|
}
|
|
5727
6135
|
});
|
|
6136
|
+
let hasDocumentTarget = false;
|
|
6137
|
+
let serializedDocumentOutput = "";
|
|
5728
6138
|
containerFragments.forEach((fragment, container) => {
|
|
5729
6139
|
const replacement = replacementContainers.get(container);
|
|
5730
6140
|
const target = replacement || container;
|
|
5731
6141
|
if (target.nodeType === 9) {
|
|
5732
|
-
|
|
5733
|
-
if (
|
|
5734
|
-
target.appendChild(firstChild);
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
}
|
|
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("");
|
|
5738
6147
|
}
|
|
5739
6148
|
return;
|
|
5740
6149
|
}
|
|
5741
|
-
|
|
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
|
+
}
|
|
5742
6156
|
});
|
|
5743
|
-
|
|
6157
|
+
const oxml = hasDocumentTarget && serializedDocumentOutput ? serializedDocumentOutput : serializer.serializeToString(xmlDoc);
|
|
6158
|
+
return { oxml, hasChanges: true };
|
|
5744
6159
|
}
|
|
5745
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()) {
|
|
5746
6161
|
let localBaseIndex = baseIndex;
|
|
@@ -5752,7 +6167,7 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5752
6167
|
const commentMarkers = sentinelsAtOffset.filter((sentinel) => sentinel.isCommentMarker && !emittedCommentMarkers.has(sentinel.node));
|
|
5753
6168
|
commentMarkers.forEach((marker) => {
|
|
5754
6169
|
emittedCommentMarkers.add(marker.node);
|
|
5755
|
-
if (marker.node
|
|
6170
|
+
if (isWordElement(marker.node, "commentReference")) {
|
|
5756
6171
|
const run = createWordElement(xmlDoc, "w:r");
|
|
5757
6172
|
run.appendChild(marker.node.cloneNode(true));
|
|
5758
6173
|
localParagraph.appendChild(run);
|
|
@@ -5764,9 +6179,9 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5764
6179
|
const info = getParagraphInfo(localBaseIndex + 1);
|
|
5765
6180
|
const nextParagraph = createNewParagraph(info.pPr);
|
|
5766
6181
|
if (generateRedlines && type === "insert") {
|
|
5767
|
-
markParagraphMarkInserted(xmlDoc,
|
|
6182
|
+
markParagraphMarkInserted(xmlDoc, localParagraph, author);
|
|
5768
6183
|
} else if (generateRedlines && type === "delete") {
|
|
5769
|
-
markParagraphMarkDeleted(xmlDoc,
|
|
6184
|
+
markParagraphMarkDeleted(xmlDoc, localParagraph, author);
|
|
5770
6185
|
}
|
|
5771
6186
|
const fragment = containerFragments.get(info.container);
|
|
5772
6187
|
if (fragment) {
|
|
@@ -5782,13 +6197,20 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5782
6197
|
if (sentinel) {
|
|
5783
6198
|
const clone = sentinel.node.cloneNode(true);
|
|
5784
6199
|
if (sentinel.isTextBox && sentinel.originalContainer) {
|
|
5785
|
-
const newContainer =
|
|
6200
|
+
const newContainer = getFirstElementByTagNSOrTag(clone, NS_W, "txbxContent");
|
|
5786
6201
|
if (newContainer) {
|
|
5787
6202
|
while (newContainer.firstChild) newContainer.removeChild(newContainer.firstChild);
|
|
5788
6203
|
replacementContainers.set(sentinel.originalContainer, newContainer);
|
|
5789
6204
|
}
|
|
5790
6205
|
}
|
|
5791
|
-
|
|
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
|
+
}
|
|
5792
6214
|
}
|
|
5793
6215
|
localBaseIndex++;
|
|
5794
6216
|
if (type !== "delete") localInsertOffset++;
|
|
@@ -5847,12 +6269,24 @@ function appendTextToCurrent(xmlDoc, text, type, rPr, wrapper, baseIndex, curren
|
|
|
5847
6269
|
}
|
|
5848
6270
|
|
|
5849
6271
|
// engine/reconstruction-mode.js
|
|
5850
|
-
function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true) {
|
|
5851
|
-
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);
|
|
5852
6286
|
if (mapping.paragraphs.length === 0) {
|
|
5853
6287
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
5854
6288
|
}
|
|
5855
|
-
const diffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText);
|
|
6289
|
+
const diffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText, diffOptions);
|
|
5856
6290
|
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
5857
6291
|
xmlDoc,
|
|
5858
6292
|
diffs,
|
|
@@ -5881,11 +6315,15 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
5881
6315
|
if (operations.length === 0) {
|
|
5882
6316
|
return noChanges(serializer, xmlDoc);
|
|
5883
6317
|
}
|
|
5884
|
-
const options = {
|
|
6318
|
+
const options = {
|
|
6319
|
+
generateRedlines,
|
|
6320
|
+
author,
|
|
6321
|
+
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
|
|
6322
|
+
};
|
|
5885
6323
|
const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
|
|
5886
6324
|
const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
|
|
5887
|
-
const
|
|
5888
|
-
|
|
6325
|
+
const reconciledDoc = parseOoxmlSafe(wrappedOxml, "application/xml").doc;
|
|
6326
|
+
if (!reconciledDoc) return noChanges(serializer, xmlDoc);
|
|
5889
6327
|
const parseError = getXmlParseError(reconciledDoc);
|
|
5890
6328
|
if (parseError) {
|
|
5891
6329
|
error("[OxmlEngine] Failed to parse reconciled table OOXML:", parseError.textContent);
|
|
@@ -5901,14 +6339,15 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
5901
6339
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
5902
6340
|
}
|
|
5903
6341
|
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
6342
|
+
const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
|
|
5904
6343
|
const tableData = parseTable(modifiedText);
|
|
5905
6344
|
if (!tableData || tableData.rows.length === 0 && tableData.headers.length === 0) {
|
|
5906
6345
|
log("[OxmlEngine] Failed to parse table data from Markdown");
|
|
5907
6346
|
return noChanges(serializer, xmlDoc);
|
|
5908
6347
|
}
|
|
5909
|
-
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author });
|
|
5910
|
-
const
|
|
5911
|
-
|
|
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);
|
|
5912
6351
|
const tableParseError = getXmlParseError(tableDoc);
|
|
5913
6352
|
if (tableParseError) {
|
|
5914
6353
|
error("[OxmlEngine] Failed to parse generated table OOXML:", tableParseError.textContent);
|
|
@@ -5931,13 +6370,15 @@ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser
|
|
|
5931
6370
|
let firstParagraph = paragraphs[0];
|
|
5932
6371
|
let parent = firstParagraph.parentNode;
|
|
5933
6372
|
if (parent && parent.nodeType === 9) {
|
|
5934
|
-
const wrappedDoc =
|
|
6373
|
+
const wrappedDoc = parseOoxmlSafe(
|
|
5935
6374
|
`<w:document xmlns:w="${NS_W}"><w:body/></w:document>`,
|
|
5936
6375
|
"application/xml"
|
|
5937
|
-
);
|
|
6376
|
+
).doc;
|
|
6377
|
+
if (!wrappedDoc) return noChanges(serializer, workingDoc);
|
|
5938
6378
|
const wrappedBody = getFirstElementByTagNS(wrappedDoc, NS_W, "body");
|
|
5939
6379
|
paragraphs.forEach((p) => wrappedBody.appendChild(wrappedDoc.importNode(p, true)));
|
|
5940
6380
|
workingDoc = wrappedDoc;
|
|
6381
|
+
setRevisionIdAllocatorForDocument(workingDoc, revisionIdAllocator);
|
|
5941
6382
|
paragraphs = getElementsByTagNS(workingDoc, NS_W, "p");
|
|
5942
6383
|
firstParagraph = paragraphs[0];
|
|
5943
6384
|
parent = firstParagraph.parentNode;
|
|
@@ -5958,7 +6399,7 @@ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser
|
|
|
5958
6399
|
}
|
|
5959
6400
|
});
|
|
5960
6401
|
const del = createWordElement(workingDoc, "w:del");
|
|
5961
|
-
const metadata = createRevisionMetadata(author);
|
|
6402
|
+
const metadata = createRevisionMetadata(author, workingDoc);
|
|
5962
6403
|
del.setAttribute("w:id", String(metadata.id));
|
|
5963
6404
|
del.setAttribute("w:author", metadata.author);
|
|
5964
6405
|
del.setAttribute("w:date", metadata.date);
|
|
@@ -6022,17 +6463,19 @@ function authorMatchesNode(node, filter) {
|
|
|
6022
6463
|
return !!nodeAuthor && nodeAuthor === filter.normalizedAuthor;
|
|
6023
6464
|
}
|
|
6024
6465
|
function parseXmlWithWarnings(oxml, parseFailurePrefix) {
|
|
6025
|
-
const
|
|
6026
|
-
const
|
|
6027
|
-
|
|
6028
|
-
|
|
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";
|
|
6029
6470
|
return {
|
|
6030
6471
|
xmlDoc: null,
|
|
6031
6472
|
serializer: null,
|
|
6032
|
-
warning: `${parseFailurePrefix}: ${
|
|
6473
|
+
warning: `${parseFailurePrefix}: ${message}`,
|
|
6474
|
+
warnings: parsed.warnings,
|
|
6475
|
+
error: { code: "PARSE_ERROR", message }
|
|
6033
6476
|
};
|
|
6034
6477
|
}
|
|
6035
|
-
return { xmlDoc, serializer: createSerializer(), warning: null };
|
|
6478
|
+
return { xmlDoc: parsed.doc, serializer: createSerializer(), warning: null, warnings: parsed.warnings, error: null };
|
|
6036
6479
|
}
|
|
6037
6480
|
function removeNode(node) {
|
|
6038
6481
|
if (node?.parentNode) {
|
|
@@ -6092,9 +6535,17 @@ function acceptTrackedChangesInOoxml(oxml, options = {}) {
|
|
|
6092
6535
|
}
|
|
6093
6536
|
const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
|
|
6094
6537
|
if (!parseResult.xmlDoc) {
|
|
6095
|
-
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
|
+
};
|
|
6096
6546
|
}
|
|
6097
6547
|
const { xmlDoc, serializer } = parseResult;
|
|
6548
|
+
warnings.push(...parseResult.warnings || []);
|
|
6098
6549
|
let acceptedCount = 0;
|
|
6099
6550
|
for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
|
|
6100
6551
|
if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
|
|
@@ -6226,9 +6677,17 @@ function rejectTrackedChangesInOoxml(oxml, options = {}) {
|
|
|
6226
6677
|
}
|
|
6227
6678
|
const parseResult = parseXmlWithWarnings(oxml, "Failed to parse OOXML");
|
|
6228
6679
|
if (!parseResult.xmlDoc) {
|
|
6229
|
-
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
|
+
};
|
|
6230
6688
|
}
|
|
6231
6689
|
const { xmlDoc, serializer } = parseResult;
|
|
6690
|
+
warnings.push(...parseResult.warnings || []);
|
|
6232
6691
|
let rejectedCount = 0;
|
|
6233
6692
|
for (const insNode of getWordElementsByLocalName(xmlDoc, "ins")) {
|
|
6234
6693
|
if (!insNode.parentNode || !authorMatchesNode(insNode, filter)) continue;
|
|
@@ -6351,10 +6810,13 @@ function deleteCommentsByAuthorInOoxml(oxml, options = {}) {
|
|
|
6351
6810
|
hasChanges: false,
|
|
6352
6811
|
commentsRemoved: 0,
|
|
6353
6812
|
referencesRemoved: 0,
|
|
6354
|
-
|
|
6813
|
+
status: "error",
|
|
6814
|
+
error: parseResult.error,
|
|
6815
|
+
warnings: [...parseResult.warnings || [], parseResult.warning]
|
|
6355
6816
|
};
|
|
6356
6817
|
}
|
|
6357
6818
|
const { xmlDoc, serializer } = parseResult;
|
|
6819
|
+
warnings.push(...parseResult.warnings || []);
|
|
6358
6820
|
const { targetIds, commentNodes } = collectCommentTargetIds(xmlDoc, filter);
|
|
6359
6821
|
if (filter.allAuthors) {
|
|
6360
6822
|
for (const localName of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
@@ -6377,66 +6839,96 @@ function deleteCommentsByAuthorInOoxml(oxml, options = {}) {
|
|
|
6377
6839
|
|
|
6378
6840
|
// engine/oxml-engine.js
|
|
6379
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 ?? "");
|
|
6380
6846
|
const generateRedlines = options.generateRedlines ?? true;
|
|
6381
6847
|
const author = options.author || getDefaultAuthor();
|
|
6382
|
-
const parser = createParser();
|
|
6383
6848
|
const serializer = createSerializer();
|
|
6849
|
+
let parseWarnings = [];
|
|
6850
|
+
const operationWarnings = [];
|
|
6851
|
+
let normalizedExistingRevisions = false;
|
|
6852
|
+
const keepNormalizedNoOp = options.existingRevisions === "accept-all-first-keep-normalized";
|
|
6384
6853
|
const finalize = (result) => {
|
|
6385
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
|
+
}
|
|
6386
6871
|
if (!withStatus.status) {
|
|
6387
6872
|
withStatus.status = withStatus.hasChanges ? "ok" : "no-op";
|
|
6388
6873
|
}
|
|
6389
6874
|
return withOoxmlSourceType(withStatus);
|
|
6390
6875
|
};
|
|
6391
|
-
const
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
const parseError = getXmlParseError(xmlDoc);
|
|
6405
|
-
if (parseError) {
|
|
6406
|
-
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);
|
|
6407
6893
|
return finalize({
|
|
6408
|
-
oxml,
|
|
6894
|
+
oxml: inputOoxml,
|
|
6409
6895
|
hasChanges: false,
|
|
6410
6896
|
status: "error",
|
|
6411
|
-
error: { code: "PARSE_ERROR", message
|
|
6897
|
+
error: { code: "PARSE_ERROR", message }
|
|
6412
6898
|
});
|
|
6413
6899
|
}
|
|
6414
|
-
|
|
6900
|
+
const revisionIdAllocator = options?._revisionIdAllocator instanceof RevisionIdAllocator ? options._revisionIdAllocator : new RevisionIdAllocator();
|
|
6901
|
+
seedRevisionIdsFromDocument(xmlDoc, revisionIdAllocator);
|
|
6415
6902
|
if (containsTrackedChanges(xmlDoc)) {
|
|
6416
6903
|
const existingRevisionsPolicy = options.existingRevisions || "reject-input";
|
|
6417
|
-
if (existingRevisionsPolicy === "accept-all-first") {
|
|
6904
|
+
if (existingRevisionsPolicy === "accept-all-first" || existingRevisionsPolicy === "accept-all-first-keep-normalized") {
|
|
6418
6905
|
log("[OxmlEngine] Existing revisions detected; accepting all input revisions before redlining");
|
|
6419
|
-
const accepted = acceptTrackedChangesInOoxml(
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
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);
|
|
6425
6917
|
return finalize({
|
|
6426
|
-
oxml,
|
|
6918
|
+
oxml: inputOoxml,
|
|
6427
6919
|
hasChanges: false,
|
|
6428
6920
|
status: "error",
|
|
6429
6921
|
error: {
|
|
6430
6922
|
code: "PARSE_ERROR",
|
|
6431
|
-
message
|
|
6923
|
+
message
|
|
6432
6924
|
}
|
|
6433
6925
|
});
|
|
6434
6926
|
}
|
|
6435
|
-
seedRevisionIdsFromDocument(xmlDoc);
|
|
6927
|
+
seedRevisionIdsFromDocument(xmlDoc, revisionIdAllocator);
|
|
6436
6928
|
} else {
|
|
6437
6929
|
log("[OxmlEngine] Existing revisions detected; rejecting input per existingRevisions policy");
|
|
6438
6930
|
return finalize({
|
|
6439
|
-
oxml,
|
|
6931
|
+
oxml: inputOoxml,
|
|
6440
6932
|
hasChanges: false,
|
|
6441
6933
|
status: "error",
|
|
6442
6934
|
error: {
|
|
@@ -6450,22 +6942,33 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
6450
6942
|
if (initialTableCellContext.hasTableWrapper && initialTableCellContext.targetParagraph && !options._isolatedTableCell) {
|
|
6451
6943
|
log("[OxmlEngine] Isolating table-cell paragraph before diff");
|
|
6452
6944
|
const isolatedOxml = serializeParagraphOnly(xmlDoc, initialTableCellContext.targetParagraph, serializer);
|
|
6453
|
-
|
|
6945
|
+
const isolatedResult = await applyRedlineToOxml(isolatedOxml, originalText, modifiedText, {
|
|
6454
6946
|
...options,
|
|
6455
6947
|
_isolatedTableCell: true
|
|
6456
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.");
|
|
6457
6957
|
}
|
|
6458
|
-
const sanitizedText = sanitizeAiResponse(modifiedText);
|
|
6459
6958
|
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
6460
6959
|
const hasTextChanges = cleanModifiedText.trim() !== originalText.trim();
|
|
6461
6960
|
const hasFormatHints = formatHints.length > 0;
|
|
6462
6961
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
6463
6962
|
const hasExistingFormatting = existingFormatHints.length > 0;
|
|
6464
6963
|
const visibleText = textSpans.map((span) => textSpanVisibleText(span)).join("");
|
|
6465
|
-
|
|
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) {
|
|
6466
6969
|
log("[OxmlEngine] Target text not found in OOXML");
|
|
6467
6970
|
return finalize({
|
|
6468
|
-
oxml,
|
|
6971
|
+
oxml: inputOoxml,
|
|
6469
6972
|
hasChanges: false,
|
|
6470
6973
|
status: "error",
|
|
6471
6974
|
error: {
|
|
@@ -6506,10 +7009,14 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
6506
7009
|
);
|
|
6507
7010
|
};
|
|
6508
7011
|
log(`[OxmlEngine] Text changes: ${hasTextChanges}, New format hints: ${formatHints.length}, Existing format hints: ${existingFormatHints.length}`);
|
|
6509
|
-
const needsFormatRemoval = !hasTextChanges && !hasFormatHints && hasExistingFormatting;
|
|
7012
|
+
const needsFormatRemoval = options.removeFormatting === true && !hasTextChanges && !hasFormatHints && hasExistingFormatting;
|
|
6510
7013
|
if (!hasTextChanges && !hasFormatHints && !hasExistingFormatting) {
|
|
6511
7014
|
log("[OxmlEngine] No text changes, no format hints, and no existing formatting detected");
|
|
6512
|
-
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();
|
|
6513
7020
|
}
|
|
6514
7021
|
if (needsFormatRemoval) {
|
|
6515
7022
|
log("[OxmlEngine] Format REMOVAL detected: applying surgical replacement in OOXML");
|
|
@@ -6569,50 +7076,78 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
6569
7076
|
const isTargetList = isListTargetLoose(cleanModifiedText);
|
|
6570
7077
|
const tableCellContext = initialTableCellContext;
|
|
6571
7078
|
log(`[OxmlEngine] Mode: ${hasTables ? "SURGICAL" : "RECONSTRUCTION"}, formatHints: ${formatHints.length}, isMarkdownTable: ${isMarkdownTable}, isTargetList: ${isTargetList}, isTableCellParagraph: ${tableCellContext.isTableCellParagraph}`);
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
if (hasTables && isMarkdownTable) {
|
|
6577
|
-
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, parser, author, generateRedlines));
|
|
6578
|
-
}
|
|
6579
|
-
if (hasTables) {
|
|
6580
|
-
const surgicalTarget = tableCellContext.hasTableWrapper && tableCellContext.targetParagraph ? tableCellContext.targetParagraph : null;
|
|
6581
|
-
if (surgicalTarget) {
|
|
6582
|
-
log("[OxmlEngine] Table cell edit: scoping surgical mode to target paragraph");
|
|
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));
|
|
6583
7083
|
}
|
|
6584
|
-
|
|
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();
|
|
7130
|
+
}
|
|
7131
|
+
return finalize(applyReconstructionMode(
|
|
6585
7132
|
xmlDoc,
|
|
6586
7133
|
originalText,
|
|
6587
7134
|
cleanModifiedText,
|
|
6588
7135
|
serializer,
|
|
6589
7136
|
author,
|
|
6590
7137
|
formatHints,
|
|
6591
|
-
generateRedlines
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
if (
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
if (isTargetList) {
|
|
6601
|
-
log("[OxmlEngine] \u{1F3AF} Using reconciliation pipeline for list generation");
|
|
6602
|
-
const pipeline = new ReconciliationPipeline({ author, generateRedlines });
|
|
6603
|
-
const result = await pipeline.execute(oxml, modifiedText, { xmlDoc });
|
|
6604
|
-
if (result.isValid && result.ooxml && result.ooxml !== oxml) {
|
|
6605
|
-
log(`[OxmlEngine] Wrapping list OOXML with numbering definitions, includeNumbering=${result.includeNumbering}`);
|
|
6606
|
-
const wrapped = wrapInDocumentFragment(result.ooxml, {
|
|
6607
|
-
includeNumbering: result.includeNumbering ?? true,
|
|
6608
|
-
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 }
|
|
6609
7147
|
});
|
|
6610
|
-
log(`[OxmlEngine] \u2705 Wrapped OOXML length: ${wrapped.length}`);
|
|
6611
|
-
return finalize({ oxml: wrapped, hasChanges: true });
|
|
6612
7148
|
}
|
|
6613
|
-
|
|
7149
|
+
throw caught;
|
|
6614
7150
|
}
|
|
6615
|
-
return finalize(applyReconstructionMode(xmlDoc, originalText, cleanModifiedText, serializer, author, formatHints, generateRedlines));
|
|
6616
7151
|
}
|
|
6617
7152
|
function normalizeTargetText(text) {
|
|
6618
7153
|
return String(text || "").replace(/[\t\n\u2011]/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -6626,15 +7161,13 @@ function textSpanVisibleText(span) {
|
|
|
6626
7161
|
return node?.textContent || "";
|
|
6627
7162
|
}
|
|
6628
7163
|
function sanitizeAiResponse(text) {
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
cleaned = cleaned.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n");
|
|
6634
|
-
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
|
+
);
|
|
6635
7168
|
}
|
|
6636
7169
|
function parseOoxml(ooxmlString) {
|
|
6637
|
-
return
|
|
7170
|
+
return parseOoxmlSafe(ooxmlString, "application/xml").doc;
|
|
6638
7171
|
}
|
|
6639
7172
|
function serializeOoxml(doc) {
|
|
6640
7173
|
return serializeXml(doc);
|
|
@@ -6644,6 +7177,11 @@ function serializeOoxml(doc) {
|
|
|
6644
7177
|
function toArray(nodeList) {
|
|
6645
7178
|
return Array.from(nodeList || []);
|
|
6646
7179
|
}
|
|
7180
|
+
function createTargetNotFoundError(message) {
|
|
7181
|
+
const error2 = new Error(message);
|
|
7182
|
+
error2.code = "TARGET_NOT_FOUND";
|
|
7183
|
+
return error2;
|
|
7184
|
+
}
|
|
6647
7185
|
var WORD_MAIN_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
6648
7186
|
function getElementsByLocalName(node, localName) {
|
|
6649
7187
|
if (!node) return [];
|
|
@@ -6657,8 +7195,22 @@ function getElementsByLocalName(node, localName) {
|
|
|
6657
7195
|
return toArray(node.getElementsByTagName(localName));
|
|
6658
7196
|
}
|
|
6659
7197
|
function toParagraphText(paragraph) {
|
|
6660
|
-
|
|
6661
|
-
|
|
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;
|
|
6662
7214
|
}
|
|
6663
7215
|
function getParagraphText(paragraph) {
|
|
6664
7216
|
if (!paragraph) return "";
|
|
@@ -6814,9 +7366,9 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
6814
7366
|
const fuzzyMatch = findParagraphByBestTextMatch(xmlDoc, cleanTargetText, { onInfo });
|
|
6815
7367
|
if (fuzzyMatch) return { paragraph: fuzzyMatch, resolvedBy: "fuzzy_text" };
|
|
6816
7368
|
}
|
|
6817
|
-
if (cleanTargetText) throw
|
|
6818
|
-
if (parsedRef) throw
|
|
6819
|
-
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#]).');
|
|
6820
7372
|
}
|
|
6821
7373
|
function isParagraphInTable(paragraph) {
|
|
6822
7374
|
return !!findContainingWordElement(paragraph, "tbl");
|
|
@@ -6903,7 +7455,9 @@ function resolveTargetParagraphWithSnapshot(xmlDoc, options = {}) {
|
|
|
6903
7455
|
onInfo(`[Target] [P${parsedRef}] appears stale after prior edits; using strict text rematch for ${opType}.`);
|
|
6904
7456
|
return { paragraph: bestCandidate.paragraph, resolvedBy: "strict_text_after_ref_drift" };
|
|
6905
7457
|
}
|
|
6906
|
-
|
|
7458
|
+
throw createTargetNotFoundError(
|
|
7459
|
+
`Target paragraph [P${parsedRef}] no longer matches its batch-start anchor.`
|
|
7460
|
+
);
|
|
6907
7461
|
}
|
|
6908
7462
|
function resolveParagraphRangeByRefs(xmlDoc, startRef, endRef, options = {}) {
|
|
6909
7463
|
if (!xmlDoc || !startRef || !endRef) return null;
|
|
@@ -7029,6 +7583,9 @@ function shouldPromoteBulletInsertionsToChildDepth(parsedItems, normalizedTarget
|
|
|
7029
7583
|
return isNormalizedTextEqual(firstItem.text, normalizedTargetText);
|
|
7030
7584
|
}
|
|
7031
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;
|
|
7032
7589
|
return isNormalizedTextEqual(firstItem.text, normalizedTargetText);
|
|
7033
7590
|
}
|
|
7034
7591
|
return false;
|
|
@@ -7471,10 +8028,7 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
7471
8028
|
if (!ownerDoc) continue;
|
|
7472
8029
|
let pPr = getDirectWordChild2(paragraph, "pPr");
|
|
7473
8030
|
if (!pPr) {
|
|
7474
|
-
pPr = ownerDoc
|
|
7475
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7476
|
-
"w:pPr"
|
|
7477
|
-
);
|
|
8031
|
+
pPr = createWordElement(ownerDoc, "w:pPr");
|
|
7478
8032
|
paragraph.insertBefore(pPr, paragraph.firstChild);
|
|
7479
8033
|
}
|
|
7480
8034
|
if (clearParagraphPropertyChanges) {
|
|
@@ -7487,27 +8041,18 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
7487
8041
|
}
|
|
7488
8042
|
let numPr = getDirectWordChild2(pPr, "numPr");
|
|
7489
8043
|
if (!numPr) {
|
|
7490
|
-
numPr = ownerDoc
|
|
7491
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7492
|
-
"w:numPr"
|
|
7493
|
-
);
|
|
8044
|
+
numPr = createWordElement(ownerDoc, "w:numPr");
|
|
7494
8045
|
pPr.appendChild(numPr);
|
|
7495
8046
|
}
|
|
7496
8047
|
let ilvlEl = getDirectWordChild2(numPr, "ilvl");
|
|
7497
8048
|
if (!ilvlEl) {
|
|
7498
|
-
ilvlEl = ownerDoc
|
|
7499
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7500
|
-
"w:ilvl"
|
|
7501
|
-
);
|
|
8049
|
+
ilvlEl = createWordElement(ownerDoc, "w:ilvl");
|
|
7502
8050
|
numPr.appendChild(ilvlEl);
|
|
7503
8051
|
}
|
|
7504
8052
|
ilvlEl.setAttribute("w:val", String(ilvl));
|
|
7505
8053
|
let numIdEl = getDirectWordChild2(numPr, "numId");
|
|
7506
8054
|
if (!numIdEl) {
|
|
7507
|
-
numIdEl = ownerDoc
|
|
7508
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7509
|
-
"w:numId"
|
|
7510
|
-
);
|
|
8055
|
+
numIdEl = createWordElement(ownerDoc, "w:numId");
|
|
7511
8056
|
numPr.appendChild(numIdEl);
|
|
7512
8057
|
}
|
|
7513
8058
|
numIdEl.setAttribute("w:val", String(numId));
|
|
@@ -7516,8 +8061,8 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
7516
8061
|
return updated;
|
|
7517
8062
|
}
|
|
7518
8063
|
function getFirstParagraphFromOxml(oxml) {
|
|
7519
|
-
const
|
|
7520
|
-
|
|
8064
|
+
const doc = parseOoxmlSafe(oxml, "application/xml").doc;
|
|
8065
|
+
if (!doc) return null;
|
|
7521
8066
|
const parseError = getXmlParseError(doc);
|
|
7522
8067
|
if (parseError) return null;
|
|
7523
8068
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
@@ -7543,8 +8088,8 @@ function setElementVal(element, value) {
|
|
|
7543
8088
|
element.setAttribute("w:val", String(value));
|
|
7544
8089
|
}
|
|
7545
8090
|
function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
7546
|
-
const
|
|
7547
|
-
|
|
8091
|
+
const doc = parseOoxmlSafe(oxml, "application/xml").doc;
|
|
8092
|
+
if (!doc) return null;
|
|
7548
8093
|
const parseError = getXmlParseError(doc);
|
|
7549
8094
|
if (parseError) return null;
|
|
7550
8095
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
@@ -7560,9 +8105,9 @@ function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
|
7560
8105
|
function applyStartOverrideToNumberingXml(numberingXml, targetNumId, startAt, options = {}) {
|
|
7561
8106
|
if (!numberingXml || !targetNumId || !Number.isInteger(startAt) || startAt < 1) return numberingXml;
|
|
7562
8107
|
const setAbstractStartOverride = options.setAbstractStartOverride !== false;
|
|
7563
|
-
const parser = createParser();
|
|
7564
8108
|
const serializer = createSerializer();
|
|
7565
|
-
const numberingDoc =
|
|
8109
|
+
const numberingDoc = parseOoxmlSafe(numberingXml, "application/xml").doc;
|
|
8110
|
+
if (!numberingDoc) return numberingXml;
|
|
7566
8111
|
const parseError = getXmlParseError(numberingDoc);
|
|
7567
8112
|
if (parseError) return numberingXml;
|
|
7568
8113
|
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
@@ -7578,19 +8123,13 @@ function applyStartOverrideToNumberingXml(numberingXml, targetNumId, startAt, op
|
|
|
7578
8123
|
return ilvl === 0;
|
|
7579
8124
|
}) || null;
|
|
7580
8125
|
if (!lvlOverride) {
|
|
7581
|
-
lvlOverride = numberingDoc
|
|
7582
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7583
|
-
"w:lvlOverride"
|
|
7584
|
-
);
|
|
8126
|
+
lvlOverride = createWordElement(numberingDoc, "w:lvlOverride");
|
|
7585
8127
|
lvlOverride.setAttribute("w:ilvl", "0");
|
|
7586
8128
|
target.appendChild(lvlOverride);
|
|
7587
8129
|
}
|
|
7588
8130
|
let startOverride = Array.from(lvlOverride.getElementsByTagNameNS("*", "startOverride"))[0] || null;
|
|
7589
8131
|
if (!startOverride) {
|
|
7590
|
-
startOverride = numberingDoc
|
|
7591
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7592
|
-
"w:startOverride"
|
|
7593
|
-
);
|
|
8132
|
+
startOverride = createWordElement(numberingDoc, "w:startOverride");
|
|
7594
8133
|
lvlOverride.appendChild(startOverride);
|
|
7595
8134
|
}
|
|
7596
8135
|
setElementVal(startOverride, startAt);
|
|
@@ -7606,19 +8145,13 @@ function applyStartOverrideToNumberingXml(numberingXml, targetNumId, startAt, op
|
|
|
7606
8145
|
return ilvl === 0;
|
|
7607
8146
|
}) || null;
|
|
7608
8147
|
if (!lvl) {
|
|
7609
|
-
lvl = numberingDoc
|
|
7610
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7611
|
-
"w:lvl"
|
|
7612
|
-
);
|
|
8148
|
+
lvl = createWordElement(numberingDoc, "w:lvl");
|
|
7613
8149
|
lvl.setAttribute("w:ilvl", "0");
|
|
7614
8150
|
abstractNum.appendChild(lvl);
|
|
7615
8151
|
}
|
|
7616
8152
|
let startNode = Array.from(lvl.getElementsByTagNameNS("*", "start"))[0] || null;
|
|
7617
8153
|
if (!startNode) {
|
|
7618
|
-
startNode = numberingDoc
|
|
7619
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
7620
|
-
"w:start"
|
|
7621
|
-
);
|
|
8154
|
+
startNode = createWordElement(numberingDoc, "w:start");
|
|
7622
8155
|
lvl.insertBefore(startNode, lvl.firstChild);
|
|
7623
8156
|
}
|
|
7624
8157
|
setElementVal(startNode, startAt);
|
|
@@ -7710,20 +8243,20 @@ async function executeSingleLineListStructuralFallback(plan, options = {}) {
|
|
|
7710
8243
|
// core/redline-validation.js
|
|
7711
8244
|
var REVISION_ID_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "rPrChange", "pPrChange"]);
|
|
7712
8245
|
var REVISION_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
|
|
7713
|
-
function
|
|
8246
|
+
function localNameOf2(node) {
|
|
7714
8247
|
return String(node?.localName || node?.nodeName || "").replace(/^.*:/, "");
|
|
7715
8248
|
}
|
|
7716
8249
|
function elementsByLocalName(root, name) {
|
|
7717
|
-
return Array.from(root.getElementsByTagName("*")).filter((el) =>
|
|
8250
|
+
return Array.from(root.getElementsByTagName("*")).filter((el) => localNameOf2(el) === name);
|
|
7718
8251
|
}
|
|
7719
|
-
function
|
|
8252
|
+
function wordAttribute2(node, name) {
|
|
7720
8253
|
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
7721
8254
|
}
|
|
7722
8255
|
function xmlSpaceAttribute(node) {
|
|
7723
8256
|
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
7724
8257
|
}
|
|
7725
8258
|
function isParagraphMarkRevision(node) {
|
|
7726
|
-
return
|
|
8259
|
+
return localNameOf2(node.parentNode) === "rPr";
|
|
7727
8260
|
}
|
|
7728
8261
|
function parseOoxmlForValidation(oxml) {
|
|
7729
8262
|
const attempt = (xml) => {
|
|
@@ -7759,13 +8292,28 @@ function validateRedlineOoxml(oxml) {
|
|
|
7759
8292
|
const insElements = elementsByLocalName(doc, "ins");
|
|
7760
8293
|
const delElements = elementsByLocalName(doc, "del");
|
|
7761
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
|
+
}
|
|
7762
8310
|
for (const revision of revisions) {
|
|
7763
|
-
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)));
|
|
7764
8312
|
if (nested.length > 0) {
|
|
7765
8313
|
addIssue(
|
|
7766
8314
|
"NESTED_REVISION",
|
|
7767
8315
|
"error",
|
|
7768
|
-
`<${revision.nodeName}> (w:id="${
|
|
8316
|
+
`<${revision.nodeName}> (w:id="${wordAttribute2(revision, "id")}") contains nested <${nested[0].nodeName}>.`
|
|
7769
8317
|
);
|
|
7770
8318
|
}
|
|
7771
8319
|
}
|
|
@@ -7775,15 +8323,15 @@ function validateRedlineOoxml(oxml) {
|
|
|
7775
8323
|
addIssue(
|
|
7776
8324
|
"DEL_CONTAINS_T",
|
|
7777
8325
|
"error",
|
|
7778
|
-
`<w:del> (w:id="${
|
|
8326
|
+
`<w:del> (w:id="${wordAttribute2(del, "id")}") contains <w:t>; deleted text must use <w:delText>.`
|
|
7779
8327
|
);
|
|
7780
8328
|
}
|
|
7781
8329
|
}
|
|
7782
8330
|
for (const revision of revisions) {
|
|
7783
8331
|
const missing = [];
|
|
7784
|
-
if (!
|
|
7785
|
-
if (!
|
|
7786
|
-
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");
|
|
7787
8335
|
if (missing.length > 0) {
|
|
7788
8336
|
addIssue(
|
|
7789
8337
|
"MISSING_REVISION_METADATA",
|
|
@@ -7794,8 +8342,8 @@ function validateRedlineOoxml(oxml) {
|
|
|
7794
8342
|
}
|
|
7795
8343
|
const seenIds = /* @__PURE__ */ new Set();
|
|
7796
8344
|
for (const node of Array.from(doc.getElementsByTagName("*"))) {
|
|
7797
|
-
if (!REVISION_ID_ELEMENTS.has(
|
|
7798
|
-
const id =
|
|
8345
|
+
if (!REVISION_ID_ELEMENTS.has(localNameOf2(node))) continue;
|
|
8346
|
+
const id = wordAttribute2(node, "id");
|
|
7799
8347
|
if (!id) continue;
|
|
7800
8348
|
if (seenIds.has(id)) {
|
|
7801
8349
|
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
@@ -7823,7 +8371,7 @@ function validateRedlineOoxml(oxml) {
|
|
|
7823
8371
|
addIssue(
|
|
7824
8372
|
"EMPTY_REVISION_WRAPPER",
|
|
7825
8373
|
"warning",
|
|
7826
|
-
`<${revision.nodeName}> (w:id="${
|
|
8374
|
+
`<${revision.nodeName}> (w:id="${wordAttribute2(revision, "id")}") wraps no content.`
|
|
7827
8375
|
);
|
|
7828
8376
|
}
|
|
7829
8377
|
}
|
|
@@ -8262,20 +8810,9 @@ function hasParserError(doc) {
|
|
|
8262
8810
|
return !!getXmlParseError(doc);
|
|
8263
8811
|
}
|
|
8264
8812
|
function parseWordOoxml(ooxml) {
|
|
8265
|
-
const
|
|
8266
|
-
if (
|
|
8267
|
-
|
|
8268
|
-
}
|
|
8269
|
-
try {
|
|
8270
|
-
const parser = createParser();
|
|
8271
|
-
const doc = parser.parseFromString(source, "application/xml");
|
|
8272
|
-
if (hasParserError(doc)) {
|
|
8273
|
-
return null;
|
|
8274
|
-
}
|
|
8275
|
-
return doc;
|
|
8276
|
-
} catch {
|
|
8277
|
-
return null;
|
|
8278
|
-
}
|
|
8813
|
+
const parsed = parseOoxmlSafe(ooxml, "application/xml");
|
|
8814
|
+
if (parsed.error || hasParserError(parsed.doc)) return { ...parsed, doc: null };
|
|
8815
|
+
return parsed;
|
|
8279
8816
|
}
|
|
8280
8817
|
function getWordParagraphs(doc) {
|
|
8281
8818
|
if (!doc) return [];
|
|
@@ -8430,25 +8967,37 @@ function paragraphToMarkdown(paragraph) {
|
|
|
8430
8967
|
return normalizedInline;
|
|
8431
8968
|
}
|
|
8432
8969
|
function ingestWordOoxmlToPlainText(ooxml) {
|
|
8433
|
-
|
|
8434
|
-
|
|
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;
|
|
8435
8978
|
const paragraphs = getWordParagraphs(doc);
|
|
8436
8979
|
if (paragraphs.length === 0) {
|
|
8437
8980
|
const fallback = normalizeInlineWhitespace(doc.documentElement?.textContent || "");
|
|
8438
|
-
return fallback;
|
|
8981
|
+
return { text: fallback, status: "ok", warnings: parsed.warnings };
|
|
8439
8982
|
}
|
|
8440
8983
|
const lines = paragraphs.map(paragraphToPlainText);
|
|
8441
|
-
return lines.join("\n\n").trim();
|
|
8984
|
+
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
8442
8985
|
}
|
|
8443
8986
|
function ingestWordOoxmlToMarkdown(ooxml) {
|
|
8444
|
-
|
|
8445
|
-
|
|
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;
|
|
8446
8995
|
const paragraphs = getWordParagraphs(doc);
|
|
8447
8996
|
if (paragraphs.length === 0) {
|
|
8448
|
-
return "";
|
|
8997
|
+
return { text: "", status: "ok", warnings: parsed.warnings };
|
|
8449
8998
|
}
|
|
8450
8999
|
const lines = paragraphs.map(paragraphToMarkdown);
|
|
8451
|
-
return lines.join("\n\n").trim();
|
|
9000
|
+
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
8452
9001
|
}
|
|
8453
9002
|
|
|
8454
9003
|
// services/comment-builders.js
|
|
@@ -8620,12 +9169,12 @@ function injectMarkersIntoParagraph(xmlDoc, paragraph, textToFind, commentId, pa
|
|
|
8620
9169
|
var PKG_NS = "http://schemas.microsoft.com/office/2006/xmlPackage";
|
|
8621
9170
|
var RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
8622
9171
|
function injectCommentsIntoPackage(packageOxml, commentsXml) {
|
|
8623
|
-
const parser = createParser();
|
|
8624
9172
|
const serializer = createSerializer();
|
|
8625
|
-
const
|
|
8626
|
-
const
|
|
8627
|
-
|
|
8628
|
-
|
|
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);
|
|
8629
9178
|
return packageOxml;
|
|
8630
9179
|
}
|
|
8631
9180
|
const pkgPackage = pkgDoc.documentElement;
|
|
@@ -8633,7 +9182,8 @@ function injectCommentsIntoPackage(packageOxml, commentsXml) {
|
|
|
8633
9182
|
commentsPart.setAttribute("pkg:name", "/word/comments.xml");
|
|
8634
9183
|
commentsPart.setAttribute("pkg:contentType", "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml");
|
|
8635
9184
|
const commentsXmlData = pkgDoc.createElementNS(PKG_NS, "pkg:xmlData");
|
|
8636
|
-
const commentsDoc =
|
|
9185
|
+
const commentsDoc = parseOoxmlSafe(commentsXml, "text/xml").doc;
|
|
9186
|
+
if (!commentsDoc) return packageOxml;
|
|
8637
9187
|
commentsXmlData.appendChild(pkgDoc.importNode(commentsDoc.documentElement, true));
|
|
8638
9188
|
commentsPart.appendChild(commentsXmlData);
|
|
8639
9189
|
pkgPackage.appendChild(commentsPart);
|
|
@@ -8683,17 +9233,19 @@ function injectCommentsIntoPackage(packageOxml, commentsXml) {
|
|
|
8683
9233
|
}
|
|
8684
9234
|
|
|
8685
9235
|
// services/comment-engine.js
|
|
8686
|
-
function parseDocumentOxml(oxml,
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
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
|
+
};
|
|
8696
9247
|
}
|
|
9248
|
+
return { xmlDoc: parsed.doc, warning: null, warnings: parsed.warnings, error: null };
|
|
8697
9249
|
}
|
|
8698
9250
|
function injectCommentsIntoOoxml(oxml, comments, options = {}) {
|
|
8699
9251
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -8703,23 +9255,26 @@ function injectCommentsIntoOoxml(oxml, comments, options = {}) {
|
|
|
8703
9255
|
if (!comments || comments.length === 0) {
|
|
8704
9256
|
return {
|
|
8705
9257
|
oxml,
|
|
9258
|
+
hasChanges: false,
|
|
8706
9259
|
commentsApplied: 0,
|
|
8707
9260
|
warnings: ["No comments to inject"]
|
|
8708
9261
|
};
|
|
8709
9262
|
}
|
|
8710
|
-
const parser = createParser();
|
|
8711
9263
|
const serializer = createSerializer();
|
|
8712
9264
|
const parseResult = parseDocumentOxml(
|
|
8713
9265
|
oxml,
|
|
8714
|
-
parser,
|
|
8715
9266
|
(warning) => `Failed to parse OXML: ${warning}`
|
|
8716
9267
|
);
|
|
9268
|
+
warnings.push(...parseResult.warnings || []);
|
|
8717
9269
|
if (!parseResult.xmlDoc) {
|
|
8718
9270
|
error("[CommentEngine] Parse failure:", parseResult.warning);
|
|
8719
9271
|
return {
|
|
8720
9272
|
oxml,
|
|
9273
|
+
hasChanges: false,
|
|
8721
9274
|
commentsApplied: 0,
|
|
8722
|
-
|
|
9275
|
+
status: "error",
|
|
9276
|
+
error: parseResult.error,
|
|
9277
|
+
warnings: [...warnings, parseResult.warning]
|
|
8723
9278
|
};
|
|
8724
9279
|
}
|
|
8725
9280
|
const xmlDoc = parseResult.xmlDoc;
|
|
@@ -8778,12 +9333,14 @@ function injectCommentsIntoOoxml(oxml, comments, options = {}) {
|
|
|
8778
9333
|
if (placedComments.length === 0) {
|
|
8779
9334
|
return {
|
|
8780
9335
|
oxml,
|
|
9336
|
+
hasChanges: false,
|
|
8781
9337
|
commentsApplied: 0,
|
|
8782
9338
|
warnings
|
|
8783
9339
|
};
|
|
8784
9340
|
}
|
|
8785
9341
|
return {
|
|
8786
9342
|
oxml: serializer.serializeToString(xmlDoc),
|
|
9343
|
+
hasChanges: true,
|
|
8787
9344
|
commentsXml: buildCommentsPartXml(placedComments),
|
|
8788
9345
|
commentsApplied: placedComments.length,
|
|
8789
9346
|
warnings
|
|
@@ -8853,6 +9410,7 @@ function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
|
8853
9410
|
function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
8854
9411
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
8855
9412
|
const doc = parseOoxml(ooxmlString);
|
|
9413
|
+
if (!doc) return ooxmlString;
|
|
8856
9414
|
const NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
8857
9415
|
const runs = doc.getElementsByTagNameNS(NS_W7, "r");
|
|
8858
9416
|
const insertions = doc.getElementsByTagNameNS(NS_W7, "ins");
|
|
@@ -8926,7 +9484,7 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
8926
9484
|
rPrElement.appendChild(highlightEl);
|
|
8927
9485
|
if (generateRedlines && previousRPrState) {
|
|
8928
9486
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
8929
|
-
const metadata = createRevisionMetadata(author);
|
|
9487
|
+
const metadata = createRevisionMetadata(author, doc);
|
|
8930
9488
|
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
8931
9489
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
8932
9490
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
@@ -8940,64 +9498,70 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
8940
9498
|
function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
|
|
8941
9499
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
8942
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
|
+
}
|
|
8943
9507
|
const NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
8944
9508
|
const getRunText = (run) => {
|
|
8945
9509
|
const textNodes = run.getElementsByTagNameNS(NS_W7, "t");
|
|
8946
9510
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
8947
9511
|
};
|
|
8948
9512
|
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W7, "r"));
|
|
8949
|
-
|
|
8950
|
-
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) {
|
|
8951
9534
|
const runText = getRunText(run);
|
|
8952
9535
|
if (!runText) continue;
|
|
8953
|
-
const
|
|
8954
|
-
|
|
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;
|
|
8955
9545
|
const parent = run.parentNode;
|
|
8956
9546
|
if (!parent) {
|
|
8957
9547
|
console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
|
|
8958
9548
|
continue;
|
|
8959
9549
|
}
|
|
8960
|
-
const prefixText = runText.substring(0, matchIndex);
|
|
8961
|
-
const matchText = runText.substring(matchIndex, matchIndex + targetText.length);
|
|
8962
|
-
const suffixText = runText.substring(matchIndex + targetText.length);
|
|
8963
9550
|
const fragment = doc.createDocumentFragment();
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
const newT = createWordElement(doc, "w:t");
|
|
8969
|
-
newT.setAttribute("xml:space", "preserve");
|
|
8970
|
-
newT.textContent = prefixText;
|
|
8971
|
-
prefixRun.appendChild(newT);
|
|
8972
|
-
fragment.appendChild(prefixRun);
|
|
8973
|
-
}
|
|
8974
|
-
if (matchText.length > 0) {
|
|
8975
|
-
const matchRun = run.cloneNode(true);
|
|
8976
|
-
const tNodes = matchRun.getElementsByTagNameNS(NS_W7, "t");
|
|
8977
|
-
Array.from(tNodes).forEach(removeNode2);
|
|
8978
|
-
const newT = createWordElement(doc, "w:t");
|
|
8979
|
-
newT.setAttribute("xml:space", "preserve");
|
|
8980
|
-
newT.textContent = matchText;
|
|
8981
|
-
matchRun.appendChild(newT);
|
|
8982
|
-
const rPrElements = matchRun.getElementsByTagNameNS(NS_W7, "rPr");
|
|
8983
|
-
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
8984
|
-
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
8985
|
-
if (existingRPr) {
|
|
8986
|
-
matchRun.replaceChild(newRPr, existingRPr);
|
|
8987
|
-
} else {
|
|
8988
|
-
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));
|
|
8989
9555
|
}
|
|
8990
|
-
fragment.appendChild(
|
|
9556
|
+
fragment.appendChild(cloneRunWithText2(
|
|
9557
|
+
run,
|
|
9558
|
+
runText.slice(matchIndex, matchIndex + targetText.length),
|
|
9559
|
+
true
|
|
9560
|
+
));
|
|
9561
|
+
cursor = matchIndex + targetText.length;
|
|
8991
9562
|
}
|
|
8992
|
-
if (
|
|
8993
|
-
|
|
8994
|
-
const tNodes = suffixRun.getElementsByTagNameNS(NS_W7, "t");
|
|
8995
|
-
Array.from(tNodes).forEach(removeNode2);
|
|
8996
|
-
const newT = createWordElement(doc, "w:t");
|
|
8997
|
-
newT.setAttribute("xml:space", "preserve");
|
|
8998
|
-
newT.textContent = suffixText;
|
|
8999
|
-
suffixRun.appendChild(newT);
|
|
9000
|
-
fragment.appendChild(suffixRun);
|
|
9563
|
+
if (cursor < runText.length) {
|
|
9564
|
+
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
|
|
9001
9565
|
}
|
|
9002
9566
|
parent.replaceChild(fragment, run);
|
|
9003
9567
|
}
|
|
@@ -9018,13 +9582,13 @@ var COMMENTS_PATH = "word/comments.xml";
|
|
|
9018
9582
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
9019
9583
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
9020
9584
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
9021
|
-
const
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
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;
|
|
9026
9590
|
}
|
|
9027
|
-
return
|
|
9591
|
+
return parsed.doc;
|
|
9028
9592
|
}
|
|
9029
9593
|
function isSectionPropertiesElement(node) {
|
|
9030
9594
|
return !!node && node.nodeType === 1 && node.namespaceURI === NS_W6 && node.localName === "sectPr";
|
|
@@ -9088,9 +9652,8 @@ function getPackagePartName(partElement) {
|
|
|
9088
9652
|
return partElement.getAttribute("pkg:name") || partElement.getAttribute("name") || "";
|
|
9089
9653
|
}
|
|
9090
9654
|
function extractFromPackageXml(packageXml) {
|
|
9091
|
-
const parser = createParser();
|
|
9092
9655
|
const serializer = createSerializer();
|
|
9093
|
-
const pkgDoc =
|
|
9656
|
+
const pkgDoc = parseXmlStrictStandalone(packageXml, "package OOXML");
|
|
9094
9657
|
const parts = Array.from(pkgDoc.getElementsByTagNameNS("*", "part"));
|
|
9095
9658
|
const documentPart = parts.find((part) => getPackagePartName(part) === "/word/document.xml");
|
|
9096
9659
|
if (!documentPart) {
|
|
@@ -9123,23 +9686,37 @@ function extractFromPackageXml(packageXml) {
|
|
|
9123
9686
|
}
|
|
9124
9687
|
function extractReplacementNodesFromOoxml(outputOxml) {
|
|
9125
9688
|
if (typeof outputOxml !== "string" || !outputOxml.trim()) {
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
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
|
+
};
|
|
9130
9696
|
}
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
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
|
+
};
|
|
9137
9719
|
}
|
|
9138
|
-
const wrapped = `<root xmlns:w="${NS_W6}">${outputOxml}</root>`;
|
|
9139
|
-
const parser = createParser();
|
|
9140
|
-
const fragmentDoc = parser.parseFromString(wrapped, "application/xml");
|
|
9141
|
-
const replacementNodes = Array.from(fragmentDoc.documentElement.childNodes || []).filter((node) => node.nodeType === 1);
|
|
9142
|
-
return { replacementNodes, numberingXml: null, sourceType: "fragment" };
|
|
9143
9720
|
}
|
|
9144
9721
|
function upsertContentTypeOverride(ctDoc, partName, contentType) {
|
|
9145
9722
|
const overrides = Array.from(ctDoc.getElementsByTagNameNS("*", "Override"));
|
|
@@ -9199,18 +9776,17 @@ async function ensureNumberingArtifactsInZip(zip, numberingXmlList, options = {}
|
|
|
9199
9776
|
onInfo("[Demo] Merging numbering.xml payload(s) into existing numbering definitions");
|
|
9200
9777
|
}
|
|
9201
9778
|
zip.file(NUMBERING_PATH, mergedNumberingXml);
|
|
9202
|
-
const parser = createParser();
|
|
9203
9779
|
const serializer = createSerializer();
|
|
9204
9780
|
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
9205
9781
|
if (ctText) {
|
|
9206
|
-
const ctDoc =
|
|
9782
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
9207
9783
|
if (upsertContentTypeOverride(ctDoc, "/word/numbering.xml", NUMBERING_CONTENT_TYPE)) {
|
|
9208
9784
|
zip.file(CONTENT_TYPES_PATH, serializer.serializeToString(ctDoc));
|
|
9209
9785
|
}
|
|
9210
9786
|
}
|
|
9211
9787
|
const relsText = await readZipText(zip, DOCUMENT_RELS_PATH);
|
|
9212
9788
|
if (relsText) {
|
|
9213
|
-
const relsDoc =
|
|
9789
|
+
const relsDoc = parseXmlStrictStandalone(relsText, DOCUMENT_RELS_PATH);
|
|
9214
9790
|
if (upsertDocumentRelationship(relsDoc, NUMBERING_REL_TYPE, "numbering.xml")) {
|
|
9215
9791
|
zip.file(DOCUMENT_RELS_PATH, serializer.serializeToString(relsDoc));
|
|
9216
9792
|
}
|
|
@@ -9220,7 +9796,6 @@ async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
|
|
|
9220
9796
|
const onInfo = typeof options?.onInfo === "function" ? options.onInfo : () => {
|
|
9221
9797
|
};
|
|
9222
9798
|
if (!commentsXml) return;
|
|
9223
|
-
const parser = createParser();
|
|
9224
9799
|
const serializer = createSerializer();
|
|
9225
9800
|
const existingText = await readZipText(zip, COMMENTS_PATH);
|
|
9226
9801
|
if (!existingText) {
|
|
@@ -9244,14 +9819,14 @@ async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
|
|
|
9244
9819
|
}
|
|
9245
9820
|
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
9246
9821
|
if (ctText) {
|
|
9247
|
-
const ctDoc =
|
|
9822
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
9248
9823
|
if (upsertContentTypeOverride(ctDoc, "/word/comments.xml", COMMENTS_CONTENT_TYPE)) {
|
|
9249
9824
|
zip.file(CONTENT_TYPES_PATH, serializer.serializeToString(ctDoc));
|
|
9250
9825
|
}
|
|
9251
9826
|
}
|
|
9252
9827
|
const relsText = await readZipText(zip, DOCUMENT_RELS_PATH);
|
|
9253
9828
|
if (relsText) {
|
|
9254
|
-
const relsDoc =
|
|
9829
|
+
const relsDoc = parseXmlStrictStandalone(relsText, DOCUMENT_RELS_PATH);
|
|
9255
9830
|
if (upsertDocumentRelationship(relsDoc, COMMENTS_REL_TYPE, "comments.xml")) {
|
|
9256
9831
|
zip.file(DOCUMENT_RELS_PATH, serializer.serializeToString(relsDoc));
|
|
9257
9832
|
}
|
|
@@ -9514,10 +10089,12 @@ async function reconcileMarkdownTableOoxml(oxml, originalText, markdownTable, op
|
|
|
9514
10089
|
}
|
|
9515
10090
|
async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedText, options = {}) {
|
|
9516
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."] : [];
|
|
9517
10094
|
const plan = buildSingleLineListStructuralFallbackPlan({
|
|
9518
10095
|
oxml,
|
|
9519
10096
|
originalText,
|
|
9520
|
-
modifiedText,
|
|
10097
|
+
modifiedText: fallbackModifiedText,
|
|
9521
10098
|
allowExistingList: allowExistingListForFallback
|
|
9522
10099
|
});
|
|
9523
10100
|
const preferListFallback = options.preferListStructuralFallback !== false;
|
|
@@ -9537,7 +10114,7 @@ async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedTe
|
|
|
9537
10114
|
return withOoxmlSourceType({
|
|
9538
10115
|
oxml: wrappedOxml2,
|
|
9539
10116
|
hasChanges: true,
|
|
9540
|
-
warnings: fallbackWarnings2,
|
|
10117
|
+
warnings: [...sanitizationWarnings, ...fallbackWarnings2],
|
|
9541
10118
|
listStructuralFallbackApplied: true,
|
|
9542
10119
|
listStructuralFallbackKey: fallbackResult2.listStructuralFallbackKey || null,
|
|
9543
10120
|
listStructuralFallbackNumberingXml: fallbackResult2.numberingXml || null
|
|
@@ -9656,7 +10233,9 @@ export {
|
|
|
9656
10233
|
inferTableReplacementParagraphBlock,
|
|
9657
10234
|
ingestOoxml,
|
|
9658
10235
|
ingestWordOoxmlToMarkdown,
|
|
10236
|
+
ingestWordOoxmlToMarkdownResult,
|
|
9659
10237
|
ingestWordOoxmlToPlainText,
|
|
10238
|
+
ingestWordOoxmlToPlainTextResult,
|
|
9660
10239
|
injectCommentsIntoOoxml,
|
|
9661
10240
|
injectCommentsIntoPackage2 as injectCommentsIntoPackage,
|
|
9662
10241
|
insertBodyElementBeforeSectPr,
|
|
@@ -9670,8 +10249,8 @@ export {
|
|
|
9670
10249
|
overwriteParagraphNumIds,
|
|
9671
10250
|
parseMarkdownListContent,
|
|
9672
10251
|
parseOoxml,
|
|
10252
|
+
parseOoxmlSafe,
|
|
9673
10253
|
parseParagraphReference,
|
|
9674
|
-
parseXmlStrictStandalone,
|
|
9675
10254
|
planListInsertionOnlyEdit,
|
|
9676
10255
|
preprocessMarkdown,
|
|
9677
10256
|
reconcileMarkdownTableOoxml,
|