@ansonlai/docx-redline-js 0.1.3 → 0.1.6
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 +91 -5
- package/ARCHITECTURE.md +62 -9
- package/README.md +94 -3
- package/core/types.js +35 -8
- package/core/word-xml.js +90 -0
- package/dist/docx-redline-js.esm.js +1149 -367
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +78 -74
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/VALIDATION.md +48 -0
- package/docs/plans/2026-03-01-release-0.1.4-design.md +31 -0
- package/docs/plans/2026-03-01-release-0.1.4.md +108 -0
- package/engine/format-application.js +13 -14
- package/engine/format-span-application.js +7 -6
- package/engine/formatting-removal.js +15 -12
- package/engine/oxml-engine.js +146 -55
- package/engine/reconstruction-mapper.js +35 -8
- package/engine/reconstruction-mode.js +14 -13
- package/engine/reconstruction-writer.js +97 -78
- package/engine/rpr-helpers.js +34 -32
- package/engine/run-builders.js +150 -39
- package/engine/surgical-diff-application.js +216 -0
- package/engine/surgical-mode.js +84 -519
- package/engine/surgical-run-splitting.js +96 -0
- package/engine/surgical-spans.js +169 -0
- package/engine/table-cell-context.js +15 -13
- package/engine/table-mode.js +39 -35
- package/index.d.ts +148 -0
- package/index.js +26 -19
- package/package.json +8 -1
- package/pipeline/ingestion-export.js +1 -0
- package/pipeline/ingestion-paragraph.js +37 -12
- package/pipeline/ingestion-table.js +11 -8
- package/scripts/build.mjs +35 -0
- package/scripts/check-types.mjs +28 -0
- package/scripts/export-validation-fixtures.mjs +68 -0
- package/scripts/run-tests.mjs +43 -0
- package/scripts/word-com-smoke.ps1 +48 -0
- package/services/comment-locator.js +10 -9
- package/services/revision-comment-management.js +501 -0
- package/services/standalone-operation-runner.js +119 -69
- package/services/table-reconciliation.js +7 -8
|
@@ -109,11 +109,12 @@ export function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
109
109
|
});
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
let processedModifiedText = modifiedText;
|
|
113
|
-
tokenToCharMap.forEach((char, tokenString) => {
|
|
114
|
-
const escapedToken = tokenString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
115
|
-
processedModifiedText = processedModifiedText.replace(new RegExp(escapedToken, 'g'), char);
|
|
116
|
-
});
|
|
112
|
+
let processedModifiedText = modifiedText;
|
|
113
|
+
tokenToCharMap.forEach((char, tokenString) => {
|
|
114
|
+
const escapedToken = tokenString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
115
|
+
processedModifiedText = processedModifiedText.replace(new RegExp(escapedToken, 'g'), char);
|
|
116
|
+
});
|
|
117
|
+
processedModifiedText = preserveReferencePlaceholders(originalFullText, processedModifiedText, referenceMap);
|
|
117
118
|
|
|
118
119
|
const containerFragments = new Map();
|
|
119
120
|
uniqueContainers.forEach(container => {
|
|
@@ -169,9 +170,35 @@ export function buildReconstructionMapping(xmlDoc, modifiedText) {
|
|
|
169
170
|
getPropertySpanLength,
|
|
170
171
|
isParagraphStart: index => paragraphStarts.has(index)
|
|
171
172
|
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function preserveReferencePlaceholders(originalFullText, modifiedText, referenceMap) {
|
|
176
|
+
let result = modifiedText;
|
|
177
|
+
|
|
178
|
+
for (const referenceChar of referenceMap.keys()) {
|
|
179
|
+
if (result.includes(referenceChar)) continue;
|
|
180
|
+
|
|
181
|
+
const originalIndex = originalFullText.indexOf(referenceChar);
|
|
182
|
+
if (originalIndex < 0) continue;
|
|
183
|
+
|
|
184
|
+
const prefix = originalFullText.slice(0, originalIndex);
|
|
185
|
+
const suffix = originalFullText.slice(originalIndex + referenceChar.length);
|
|
186
|
+
|
|
187
|
+
if (prefix && result.startsWith(prefix)) {
|
|
188
|
+
result = `${result.slice(0, prefix.length)}${referenceChar}${result.slice(prefix.length)}`;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (suffix && result.endsWith(suffix)) {
|
|
193
|
+
const insertAt = result.length - suffix.length;
|
|
194
|
+
result = `${result.slice(0, insertAt)}${referenceChar}${result.slice(insertAt)}`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function processChildNode(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, nextCharCode) {
|
|
175
202
|
if (child.nodeName === 'w:r') {
|
|
176
203
|
return processRunForReconstruction(child, originalFullText, propertyMap, sentinelMap, referenceMap, tokenToCharMap, nextCharCode);
|
|
177
204
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { computeWordDiffs } from '../pipeline/diff-engine.js';
|
|
6
6
|
import { buildReconstructionMapping } from './reconstruction-mapper.js';
|
|
7
7
|
import { applyReconstructionDiffs } from './reconstruction-writer.js';
|
|
8
|
+
import { withOoxmlSourceType } from '../core/word-xml.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Applies reconstruction mode reconciliation.
|
|
@@ -19,20 +20,20 @@ import { applyReconstructionDiffs } from './reconstruction-writer.js';
|
|
|
19
20
|
* @returns {{ oxml: string, hasChanges: boolean }}
|
|
20
21
|
*/
|
|
21
22
|
export function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer, author, formatHints, generateRedlines = true) {
|
|
22
|
-
const mapping = buildReconstructionMapping(xmlDoc, modifiedText);
|
|
23
|
-
if (mapping.paragraphs.length === 0) {
|
|
24
|
-
return { oxml: serializer.serializeToString(xmlDoc), hasChanges: false };
|
|
25
|
-
}
|
|
23
|
+
const mapping = buildReconstructionMapping(xmlDoc, modifiedText);
|
|
24
|
+
if (mapping.paragraphs.length === 0) {
|
|
25
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
26
|
+
}
|
|
26
27
|
|
|
27
28
|
const diffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText);
|
|
28
29
|
|
|
29
|
-
return applyReconstructionDiffs(
|
|
30
|
+
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
30
31
|
xmlDoc,
|
|
31
|
-
diffs,
|
|
32
|
-
mapping,
|
|
33
|
-
serializer,
|
|
34
|
-
author,
|
|
35
|
-
formatHints,
|
|
36
|
-
generateRedlines
|
|
37
|
-
);
|
|
38
|
-
}
|
|
32
|
+
diffs,
|
|
33
|
+
mapping,
|
|
34
|
+
serializer,
|
|
35
|
+
author,
|
|
36
|
+
formatHints,
|
|
37
|
+
generateRedlines
|
|
38
|
+
));
|
|
39
|
+
}
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
* Applies diff segments to mapped reconstruction context and writes updated DOM content.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { getApplicableFormatHints } from '../pipeline/markdown-processor.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import { getApplicableFormatHints } from '../pipeline/markdown-processor.js';
|
|
8
|
+
import {
|
|
9
|
+
createTrackChange,
|
|
10
|
+
createFormattedRuns,
|
|
11
|
+
markParagraphMarkDeleted,
|
|
12
|
+
markParagraphMarkInserted
|
|
13
|
+
} from './run-builders.js';
|
|
14
|
+
import { getFirstElementByTag } from '../core/xml-query.js';
|
|
15
|
+
import { createWordElement } from '../core/word-xml.js';
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* Applies diffs to reconstruction context and writes updated XML.
|
|
@@ -32,23 +38,24 @@ export function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, aut
|
|
|
32
38
|
getRunProperties,
|
|
33
39
|
getPropertySpanLength,
|
|
34
40
|
isParagraphStart
|
|
35
|
-
} = context;
|
|
36
|
-
|
|
37
|
-
const createNewParagraph = (pPr) => {
|
|
38
|
-
const newParagraph = xmlDoc
|
|
39
|
-
if (pPr) newParagraph.appendChild(pPr.cloneNode(true));
|
|
40
|
-
return newParagraph;
|
|
41
|
-
};
|
|
41
|
+
} = context;
|
|
42
|
+
|
|
43
|
+
const createNewParagraph = (pPr) => {
|
|
44
|
+
const newParagraph = createWordElement(xmlDoc, 'w:p');
|
|
45
|
+
if (pPr) newParagraph.appendChild(pPr.cloneNode(true));
|
|
46
|
+
return newParagraph;
|
|
47
|
+
};
|
|
42
48
|
|
|
43
49
|
const startInfo = getParagraphInfo(0);
|
|
44
|
-
let currentParagraph = createNewParagraph(startInfo.pPr);
|
|
45
|
-
const initialFragment = containerFragments.get(startInfo.container);
|
|
46
|
-
if (initialFragment) {
|
|
47
|
-
initialFragment.appendChild(currentParagraph);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let currentOriginalIndex = 0;
|
|
51
|
-
let currentInsertOffset = 0;
|
|
50
|
+
let currentParagraph = createNewParagraph(startInfo.pPr);
|
|
51
|
+
const initialFragment = containerFragments.get(startInfo.container);
|
|
52
|
+
if (initialFragment) {
|
|
53
|
+
initialFragment.appendChild(currentParagraph);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let currentOriginalIndex = 0;
|
|
57
|
+
let currentInsertOffset = 0;
|
|
58
|
+
const emittedCommentMarkers = new WeakSet();
|
|
52
59
|
|
|
53
60
|
for (const [op, text] of diffs) {
|
|
54
61
|
if (op === 0 || op === -1) {
|
|
@@ -61,9 +68,9 @@ export function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, aut
|
|
|
61
68
|
const chunkLength = getPropertySpanLength(chunkStart, text.length - offset);
|
|
62
69
|
const chunk = text.substring(offset, offset + chunkLength);
|
|
63
70
|
|
|
64
|
-
appendTextToCurrent(
|
|
65
|
-
xmlDoc,
|
|
66
|
-
chunk,
|
|
71
|
+
const appendResult = appendTextToCurrent(
|
|
72
|
+
xmlDoc,
|
|
73
|
+
chunk,
|
|
67
74
|
type,
|
|
68
75
|
properties.rPr,
|
|
69
76
|
properties.wrapper,
|
|
@@ -76,13 +83,15 @@ export function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, aut
|
|
|
76
83
|
getParagraphInfo,
|
|
77
84
|
createNewParagraph,
|
|
78
85
|
author,
|
|
79
|
-
formatHints,
|
|
80
|
-
currentInsertOffset,
|
|
81
|
-
generateRedlines
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
formatHints,
|
|
87
|
+
currentInsertOffset,
|
|
88
|
+
generateRedlines,
|
|
89
|
+
emittedCommentMarkers
|
|
90
|
+
);
|
|
91
|
+
currentParagraph = appendResult.currentParagraph;
|
|
92
|
+
|
|
93
|
+
if (op === 0) {
|
|
94
|
+
currentInsertOffset += chunkLength;
|
|
86
95
|
}
|
|
87
96
|
offset += chunkLength;
|
|
88
97
|
}
|
|
@@ -96,9 +105,9 @@ export function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, aut
|
|
|
96
105
|
? getRunProperties(currentOriginalIndex - 1)
|
|
97
106
|
: getRunProperties(currentOriginalIndex);
|
|
98
107
|
|
|
99
|
-
appendTextToCurrent(
|
|
100
|
-
xmlDoc,
|
|
101
|
-
text,
|
|
108
|
+
const appendResult = appendTextToCurrent(
|
|
109
|
+
xmlDoc,
|
|
110
|
+
text,
|
|
102
111
|
'insert',
|
|
103
112
|
properties.rPr,
|
|
104
113
|
properties.wrapper,
|
|
@@ -111,13 +120,15 @@ export function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, aut
|
|
|
111
120
|
getParagraphInfo,
|
|
112
121
|
createNewParagraph,
|
|
113
122
|
author,
|
|
114
|
-
formatHints,
|
|
115
|
-
currentInsertOffset,
|
|
116
|
-
generateRedlines
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
formatHints,
|
|
124
|
+
currentInsertOffset,
|
|
125
|
+
generateRedlines,
|
|
126
|
+
emittedCommentMarkers
|
|
127
|
+
);
|
|
128
|
+
currentParagraph = appendResult.currentParagraph;
|
|
129
|
+
currentInsertOffset += text.length;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
121
132
|
|
|
122
133
|
paragraphs.forEach(paragraph => {
|
|
123
134
|
if (paragraph.parentNode) {
|
|
@@ -163,8 +174,9 @@ function appendTextToCurrent(
|
|
|
163
174
|
author,
|
|
164
175
|
formatHints = [],
|
|
165
176
|
insertOffset = 0,
|
|
166
|
-
generateRedlines = true
|
|
167
|
-
)
|
|
177
|
+
generateRedlines = true,
|
|
178
|
+
emittedCommentMarkers = new WeakSet()
|
|
179
|
+
) {
|
|
168
180
|
let localBaseIndex = baseIndex;
|
|
169
181
|
let localInsertOffset = insertOffset;
|
|
170
182
|
let localParagraph = currentParagraphRef;
|
|
@@ -173,30 +185,35 @@ function appendTextToCurrent(
|
|
|
173
185
|
|
|
174
186
|
parts.forEach(part => {
|
|
175
187
|
const sentinelsAtOffset = sentinelMapByStart.get(localBaseIndex) || [];
|
|
176
|
-
const commentMarkers = sentinelsAtOffset.filter(sentinel => sentinel.isCommentMarker);
|
|
177
|
-
|
|
178
|
-
commentMarkers.forEach(marker => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
run
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
const commentMarkers = sentinelsAtOffset.filter(sentinel => sentinel.isCommentMarker && !emittedCommentMarkers.has(sentinel.node));
|
|
189
|
+
|
|
190
|
+
commentMarkers.forEach(marker => {
|
|
191
|
+
emittedCommentMarkers.add(marker.node);
|
|
192
|
+
if (marker.node.nodeName === 'w:commentReference') {
|
|
193
|
+
const run = createWordElement(xmlDoc, 'w:r');
|
|
194
|
+
run.appendChild(marker.node.cloneNode(true));
|
|
195
|
+
localParagraph.appendChild(run);
|
|
196
|
+
} else {
|
|
184
197
|
localParagraph.appendChild(marker.node.cloneNode(true));
|
|
185
198
|
}
|
|
186
199
|
});
|
|
187
200
|
|
|
188
|
-
if (part === '\n') {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
201
|
+
if (part === '\n') {
|
|
202
|
+
const info = getParagraphInfo(localBaseIndex + 1);
|
|
203
|
+
const nextParagraph = createNewParagraph(info.pPr);
|
|
204
|
+
if (generateRedlines && type === 'insert') {
|
|
205
|
+
markParagraphMarkInserted(xmlDoc, nextParagraph, author);
|
|
206
|
+
} else if (generateRedlines && type === 'delete') {
|
|
207
|
+
markParagraphMarkDeleted(xmlDoc, nextParagraph, author);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const fragment = containerFragments.get(info.container);
|
|
211
|
+
if (fragment) {
|
|
212
|
+
fragment.appendChild(nextParagraph);
|
|
213
|
+
localParagraph = nextParagraph;
|
|
214
|
+
}
|
|
215
|
+
localBaseIndex++;
|
|
216
|
+
if (type !== 'delete') localInsertOffset++;
|
|
200
217
|
return;
|
|
201
218
|
}
|
|
202
219
|
|
|
@@ -220,13 +237,13 @@ function appendTextToCurrent(
|
|
|
220
237
|
|
|
221
238
|
if (referenceMap.has(part)) {
|
|
222
239
|
if (type !== 'delete') {
|
|
223
|
-
const refNode = referenceMap.get(part);
|
|
224
|
-
if (refNode) {
|
|
225
|
-
const clone = refNode.cloneNode(true);
|
|
226
|
-
const run = xmlDoc
|
|
227
|
-
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
228
|
-
run.appendChild(clone);
|
|
229
|
-
localParagraph.appendChild(run);
|
|
240
|
+
const refNode = referenceMap.get(part);
|
|
241
|
+
if (refNode) {
|
|
242
|
+
const clone = refNode.cloneNode(true);
|
|
243
|
+
const run = createWordElement(xmlDoc, 'w:r');
|
|
244
|
+
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
245
|
+
run.appendChild(clone);
|
|
246
|
+
localParagraph.appendChild(run);
|
|
230
247
|
}
|
|
231
248
|
}
|
|
232
249
|
localBaseIndex++;
|
|
@@ -242,14 +259,14 @@ function appendTextToCurrent(
|
|
|
242
259
|
parent = wrapperClone;
|
|
243
260
|
localParagraph.appendChild(wrapperClone);
|
|
244
261
|
}
|
|
245
|
-
|
|
246
|
-
if (type === 'delete') {
|
|
247
|
-
const run = xmlDoc
|
|
248
|
-
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
249
|
-
const delText = xmlDoc
|
|
250
|
-
delText.setAttribute('xml:space', 'preserve');
|
|
251
|
-
delText.textContent = part;
|
|
252
|
-
run.appendChild(delText);
|
|
262
|
+
|
|
263
|
+
if (type === 'delete') {
|
|
264
|
+
const run = createWordElement(xmlDoc, 'w:r');
|
|
265
|
+
if (rPr) run.appendChild(rPr.cloneNode(true));
|
|
266
|
+
const delText = createWordElement(xmlDoc, 'w:delText');
|
|
267
|
+
delText.setAttribute('xml:space', 'preserve');
|
|
268
|
+
delText.textContent = part;
|
|
269
|
+
run.appendChild(delText);
|
|
253
270
|
|
|
254
271
|
if (generateRedlines) {
|
|
255
272
|
const del = createTrackChange(xmlDoc, 'del', run, author);
|
|
@@ -271,6 +288,8 @@ function appendTextToCurrent(
|
|
|
271
288
|
if (type !== 'delete') {
|
|
272
289
|
localInsertOffset += part.length;
|
|
273
290
|
}
|
|
274
|
-
localBaseIndex += part.length;
|
|
275
|
-
});
|
|
276
|
-
|
|
291
|
+
localBaseIndex += part.length;
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return { currentParagraph: localParagraph };
|
|
295
|
+
}
|
package/engine/rpr-helpers.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Run property (w:rPr) helper utilities.
|
|
3
3
|
*
|
|
4
4
|
* This module owns low-level formatting element operations, including
|
|
5
|
-
* schema-order insertion, format extraction, and format add/remove transforms.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
* schema-order insertion, format extraction, and format add/remove transforms.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createWordElement } from '../core/word-xml.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
9
11
|
* Canonical OOXML run-property schema ordering.
|
|
10
12
|
* Shared by all rPr synchronizers.
|
|
11
13
|
*/
|
|
@@ -82,33 +84,33 @@ function _applyOverrides(xmlDoc, rPr, formatFlags, mode) {
|
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
if (applyBold) {
|
|
86
|
-
const b = xmlDoc
|
|
87
|
-
b.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
88
|
-
insertRPrChildInOrder(rPr, b);
|
|
89
|
-
|
|
90
|
-
const bCs = xmlDoc
|
|
91
|
-
bCs.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
92
|
-
insertRPrChildInOrder(rPr, bCs);
|
|
93
|
-
}
|
|
94
|
-
if (applyItalic) {
|
|
95
|
-
const i = xmlDoc
|
|
96
|
-
i.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
97
|
-
insertRPrChildInOrder(rPr, i);
|
|
98
|
-
|
|
99
|
-
const iCs = xmlDoc
|
|
100
|
-
iCs.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
101
|
-
insertRPrChildInOrder(rPr, iCs);
|
|
102
|
-
}
|
|
103
|
-
if (applyUnderline) {
|
|
104
|
-
const u = xmlDoc
|
|
105
|
-
u.setAttribute('w:val', mode === 'add' ? 'single' : 'none');
|
|
106
|
-
insertRPrChildInOrder(rPr, u);
|
|
107
|
-
}
|
|
108
|
-
if (applyStrike) {
|
|
109
|
-
const strike = xmlDoc
|
|
110
|
-
strike.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
111
|
-
insertRPrChildInOrder(rPr, strike);
|
|
87
|
+
if (applyBold) {
|
|
88
|
+
const b = createWordElement(xmlDoc, 'w:b');
|
|
89
|
+
b.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
90
|
+
insertRPrChildInOrder(rPr, b);
|
|
91
|
+
|
|
92
|
+
const bCs = createWordElement(xmlDoc, 'w:bCs');
|
|
93
|
+
bCs.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
94
|
+
insertRPrChildInOrder(rPr, bCs);
|
|
95
|
+
}
|
|
96
|
+
if (applyItalic) {
|
|
97
|
+
const i = createWordElement(xmlDoc, 'w:i');
|
|
98
|
+
i.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
99
|
+
insertRPrChildInOrder(rPr, i);
|
|
100
|
+
|
|
101
|
+
const iCs = createWordElement(xmlDoc, 'w:iCs');
|
|
102
|
+
iCs.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
103
|
+
insertRPrChildInOrder(rPr, iCs);
|
|
104
|
+
}
|
|
105
|
+
if (applyUnderline) {
|
|
106
|
+
const u = createWordElement(xmlDoc, 'w:u');
|
|
107
|
+
u.setAttribute('w:val', mode === 'add' ? 'single' : 'none');
|
|
108
|
+
insertRPrChildInOrder(rPr, u);
|
|
109
|
+
}
|
|
110
|
+
if (applyStrike) {
|
|
111
|
+
const strike = createWordElement(xmlDoc, 'w:strike');
|
|
112
|
+
strike.setAttribute('w:val', mode === 'add' ? '1' : '0');
|
|
113
|
+
insertRPrChildInOrder(rPr, strike);
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -123,7 +125,7 @@ function _applyOverrides(xmlDoc, rPr, formatFlags, mode) {
|
|
|
123
125
|
*/
|
|
124
126
|
export function buildOverrideRPrXml(xmlDoc, originalRun, formatToRemove, serializer) {
|
|
125
127
|
const baseRPr = originalRun.getElementsByTagName('w:rPr')[0] || null;
|
|
126
|
-
const rPr = baseRPr ? baseRPr.cloneNode(true) : xmlDoc
|
|
128
|
+
const rPr = baseRPr ? baseRPr.cloneNode(true) : createWordElement(xmlDoc, 'w:rPr');
|
|
127
129
|
_applyOverrides(xmlDoc, rPr, formatToRemove, 'remove');
|
|
128
130
|
|
|
129
131
|
let rPrXml = serializer.serializeToString(rPr);
|