@ansonlai/docx-redline-js 0.4.0 → 0.5.1
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 +646 -288
- package/ARCHITECTURE.md +215 -9
- package/CHANGELOG.md +319 -0
- package/README.md +604 -360
- package/adapters/config.js +45 -43
- package/bin/docx-redline.js +3 -0
- package/core/list-targeting.js +101 -110
- package/core/paragraph-targeting.js +501 -61
- package/core/paragraph-text.js +209 -0
- package/core/redline-validation.js +11 -5
- package/core/revision-cloning.js +38 -0
- package/core/types.js +64 -10
- package/core/word-xml.js +43 -15
- package/dist/docx-redline-js.esm.js +3145 -505
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +88 -76
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/TESTING.md +342 -23
- package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +1669 -0
- package/docs/plans/2026-09-08-cross-author-revision-slicing.md +505 -0
- package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +669 -0
- package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +427 -0
- package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +519 -0
- package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +69 -0
- package/docs/plans/completed/structural-revision-capability-matrix.md +115 -0
- package/docs/schemas/document-operations.schema.json +109 -0
- package/docs/test-comparison-dashboard.html +4250 -7
- package/engine/formatting-removal.js +11 -2
- package/engine/oxml-engine.js +508 -336
- package/engine/reconstruction-mode.js +15 -14
- package/engine/reconstruction-writer.js +247 -142
- package/engine/route-selection.js +35 -0
- package/engine/rpr-helpers.js +334 -35
- package/engine/run-builders.js +239 -196
- package/engine/surgical-diff-application.js +407 -50
- package/engine/surgical-mode.js +142 -6
- package/engine/surgical-run-splitting.js +103 -0
- package/engine/surgical-spans.js +52 -1
- package/engine/table-cell-context.js +3 -6
- package/engine/table-mode.js +1 -1
- package/index.d.ts +234 -6
- package/index.js +24 -1
- package/node/cli.js +322 -0
- package/node/docx-document.js +302 -0
- package/node/index.d.ts +31 -0
- package/node/index.js +2 -0
- package/node/zip-archive.js +52 -0
- package/orchestration/list-markdown.js +10 -16
- package/orchestration/list-parsing.js +7 -12
- package/orchestration/list-structural-fallback.js +21 -10
- package/package.json +123 -102
- package/pipeline/content-analysis.js +12 -17
- package/pipeline/ingestion-export.js +3 -31
- package/pipeline/ingestion-paragraph.js +10 -5
- package/pipeline/list-generation.js +150 -55
- package/pipeline/list-markers.js +70 -3
- package/pipeline/serialization.js +4 -2
- package/pipeline/structured-content.js +160 -0
- package/scripts/apply_changes.mjs +27 -0
- package/scripts/benchmark-operation-session.mjs +137 -0
- package/scripts/benchmark-targeting-browser.html +74 -0
- package/scripts/benchmark-targeting-hot-paths.mjs +67 -0
- package/scripts/benchmark-test-runner.mjs +59 -0
- package/scripts/build-test-dashboard.mjs +23 -0
- package/scripts/export-lane1-fixtures.mjs +380 -0
- package/scripts/export-reredline-stress-fixtures.mjs +317 -0
- package/scripts/export-validation-fixtures.mjs +1 -1
- package/scripts/extract_text.mjs +7 -0
- package/scripts/generate-cross-author-slicing-fixtures.ps1 +256 -0
- package/scripts/generate-paragraph-boundary-fixtures.ps1 +215 -0
- package/scripts/generate-test-dashboard.mjs +362 -11
- package/scripts/lib/word-coverage-catalogue.mjs +6 -2
- package/scripts/profile-route-selection.mjs +19 -0
- package/scripts/render-agenda-multilevel.mjs +0 -5
- package/scripts/render-multilevel-cases.mjs +0 -1
- package/scripts/run-tests.mjs +107 -35
- package/scripts/word-com-corpus-suite.ps1 +3 -0
- package/scripts/word-com-differential.ps1 +64 -4
- package/scripts/word-com-suite.ps1 +3 -0
- package/services/batch-operation-orchestrator.js +513 -0
- package/services/capture-engine.js +226 -0
- package/services/comment-builders.js +23 -6
- package/services/comment-engine.js +108 -47
- package/services/comment-locator.js +187 -82
- package/services/comment-replies.js +95 -0
- package/services/document-inspection.js +258 -0
- package/services/document-operation-applier.js +372 -0
- package/services/document-operation-contract.js +345 -0
- package/services/document-operation-mutations.js +1749 -0
- package/services/document-operation-session.js +258 -0
- package/services/numbering-service.js +14 -5
- package/services/operation-heuristics.js +173 -0
- package/services/operation-preflight.js +390 -0
- package/services/receipt-collector.js +288 -0
- package/services/revision-comment-management.js +77 -5
- package/services/revision-token.js +290 -0
- package/services/standalone-docx-plumbing.js +123 -8
- package/services/standalone-operation-runner.d.ts +296 -0
- package/services/standalone-operation-runner.js +10 -1455
- package/services/table-reconciliation.js +15 -6
- package/docs/VALIDATION.md +0 -183
- package/docs/WORD-MANUAL-REVIEW.md +0 -138
- package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +0 -210
- /package/docs/plans/{2026-08-30-reliability-testing-improvements.md → completed/2026-08-30-reliability-testing-improvements.md} +0 -0
package/engine/rpr-helpers.js
CHANGED
|
@@ -2,12 +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
|
-
import { createWordElement } from '../core/word-xml.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
5
|
+
* schema-order insertion, format extraction, and format add/remove transforms.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createWordElement } from '../core/word-xml.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
11
|
* Canonical OOXML run-property schema ordering.
|
|
12
12
|
* Shared by all rPr synchronizers.
|
|
13
13
|
*/
|
|
@@ -17,7 +17,7 @@ export const RPR_SCHEMA_ORDER = [
|
|
|
17
17
|
'w:snapToGrid', 'w:vanish', 'w:webHidden', 'w:color', 'w:spacing', 'w:w', 'w:kern',
|
|
18
18
|
'w:position', 'w:sz', 'w:szCs', 'w:highlight', 'w:u', 'w:effect', 'w:bdr', 'w:shd',
|
|
19
19
|
'w:fitText', 'w:vertAlign', 'w:rtl', 'w:cs', 'w:em', 'w:lang', 'w:eastAsianLayout',
|
|
20
|
-
'w:specVanish', 'w:oMath'
|
|
20
|
+
'w:specVanish', 'w:oMath', 'w:rPrChange'
|
|
21
21
|
];
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -84,33 +84,33 @@ function _applyOverrides(xmlDoc, rPr, formatFlags, mode) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
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);
|
|
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);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -125,7 +125,7 @@ function _applyOverrides(xmlDoc, rPr, formatFlags, mode) {
|
|
|
125
125
|
*/
|
|
126
126
|
export function buildOverrideRPrXml(xmlDoc, originalRun, formatToRemove, serializer) {
|
|
127
127
|
const baseRPr = originalRun.getElementsByTagName('w:rPr')[0] || null;
|
|
128
|
-
const rPr = baseRPr ? baseRPr.cloneNode(true) : createWordElement(xmlDoc, 'w:rPr');
|
|
128
|
+
const rPr = baseRPr ? baseRPr.cloneNode(true) : createWordElement(xmlDoc, 'w:rPr');
|
|
129
129
|
_applyOverrides(xmlDoc, rPr, formatToRemove, 'remove');
|
|
130
130
|
|
|
131
131
|
let rPrXml = serializer.serializeToString(rPr);
|
|
@@ -194,3 +194,302 @@ function isFormattingElementEnabled(element, isUnderline) {
|
|
|
194
194
|
|
|
195
195
|
return value !== '0' && value !== 'false' && value !== 'off';
|
|
196
196
|
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Canonical OOXML paragraph-property schema ordering.
|
|
200
|
+
*/
|
|
201
|
+
export const PPR_SCHEMA_ORDER = [
|
|
202
|
+
'w:pStyle', 'w:keepNext', 'w:keepLines', 'w:pageBreakBefore', 'w:framePr',
|
|
203
|
+
'w:widowControl', 'w:numPr', 'w:suppressLineNumbers', 'w:pBdr', 'w:shd',
|
|
204
|
+
'w:tabs', 'w:suppressAutoHyphens', 'w:kinsoku', 'w:wordWrap', 'w:overflowPunct',
|
|
205
|
+
'w:topLinePunct', 'w:autoSpaceDE', 'w:autoSpaceDN', 'w:bidi', 'w:adjustRightInd',
|
|
206
|
+
'w:snapToGrid', 'w:spacing', 'w:ind', 'w:contextualSpacing', 'w:mirrorIndents',
|
|
207
|
+
'w:suppressOverlap', 'w:jc', 'w:textDirection', 'w:textAlignment', 'w:textboxTightWrap',
|
|
208
|
+
'w:outlineLvl', 'w:divId', 'w:cnfStyle', 'w:rPr', 'w:sectPr', 'w:pPrChange'
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Inserts a pPr child node in schema order.
|
|
213
|
+
*
|
|
214
|
+
* @param {Element} pPr - Paragraph properties element
|
|
215
|
+
* @param {Element} el - Child element to insert
|
|
216
|
+
*/
|
|
217
|
+
export function insertPPrChildInOrder(pPr, el) {
|
|
218
|
+
const myIndex = PPR_SCHEMA_ORDER.indexOf(el.nodeName);
|
|
219
|
+
const myPriority = myIndex === -1 ? 999 : myIndex;
|
|
220
|
+
|
|
221
|
+
let inserted = false;
|
|
222
|
+
for (const child of Array.from(pPr.childNodes)) {
|
|
223
|
+
if (child.nodeType !== 1) continue;
|
|
224
|
+
const childIndex = PPR_SCHEMA_ORDER.indexOf(child.nodeName);
|
|
225
|
+
const childPriority = childIndex === -1 ? 999 : childIndex;
|
|
226
|
+
if (childPriority > myPriority) {
|
|
227
|
+
pPr.insertBefore(el, child);
|
|
228
|
+
inserted = true;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!inserted) pPr.appendChild(el);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Checks if applying character properties to an rPr will change anything.
|
|
237
|
+
*
|
|
238
|
+
* @param {Element|null} rPr - Run properties
|
|
239
|
+
* @param {Object} properties - Desired properties
|
|
240
|
+
* @returns {boolean}
|
|
241
|
+
*/
|
|
242
|
+
export function checkRunPropertiesChanged(rPr, properties) {
|
|
243
|
+
if (!properties) return false;
|
|
244
|
+
const current = extractFormatFromRPr(rPr);
|
|
245
|
+
|
|
246
|
+
if (properties.bold !== undefined && current.bold !== !!properties.bold) return true;
|
|
247
|
+
if (properties.italic !== undefined && current.italic !== !!properties.italic) return true;
|
|
248
|
+
if (properties.underline !== undefined && current.underline !== !!properties.underline) return true;
|
|
249
|
+
|
|
250
|
+
const desiredStrike = properties.strike !== undefined ? properties.strike : properties.strikethrough;
|
|
251
|
+
if (desiredStrike !== undefined && current.strikethrough !== !!desiredStrike) return true;
|
|
252
|
+
|
|
253
|
+
if (properties.highlight !== undefined) {
|
|
254
|
+
const hlEl = Array.from(rPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:highlight' || c.localName === 'highlight'));
|
|
255
|
+
const currentHl = hlEl ? (hlEl.getAttribute('w:val') || hlEl.getAttribute('val')) : null;
|
|
256
|
+
const desiredHl = properties.highlight && properties.highlight !== 'none' ? String(properties.highlight).toLowerCase() : null;
|
|
257
|
+
if ((currentHl || null) !== desiredHl) return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (properties.color !== undefined) {
|
|
261
|
+
const colEl = Array.from(rPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:color' || c.localName === 'color'));
|
|
262
|
+
const currentCol = colEl ? (colEl.getAttribute('w:val') || colEl.getAttribute('val')) : null;
|
|
263
|
+
const desiredCol = properties.color && properties.color !== 'auto' ? String(properties.color).replace(/^#/, '').toUpperCase() : null;
|
|
264
|
+
if ((currentCol?.toUpperCase() || null) !== desiredCol) return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (properties.fontSize !== undefined) {
|
|
268
|
+
const szEl = Array.from(rPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:sz' || c.localName === 'sz'));
|
|
269
|
+
const currentSz = szEl ? (szEl.getAttribute('w:val') || szEl.getAttribute('val')) : null;
|
|
270
|
+
const desiredSz = properties.fontSize !== null && properties.fontSize !== '' ? String(properties.fontSize) : null;
|
|
271
|
+
if (currentSz !== desiredSz) return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (properties.fontFamily !== undefined) {
|
|
275
|
+
const fEl = Array.from(rPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:rFonts' || c.localName === 'rFonts'));
|
|
276
|
+
const currentFont = fEl ? (fEl.getAttribute('w:ascii') || fEl.getAttribute('ascii')) : null;
|
|
277
|
+
const desiredFont = properties.fontFamily || null;
|
|
278
|
+
if (currentFont !== desiredFont) return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Applies explicit character formatting to an rPr in schema order.
|
|
286
|
+
*
|
|
287
|
+
* @param {Document} xmlDoc - XML document
|
|
288
|
+
* @param {Element} rPr - Run properties
|
|
289
|
+
* @param {Object} properties - Desired properties
|
|
290
|
+
*/
|
|
291
|
+
export function applyCharacterFormatToRPr(xmlDoc, rPr, properties) {
|
|
292
|
+
if (!rPr || !properties) return;
|
|
293
|
+
|
|
294
|
+
const removeTags = (tags) => {
|
|
295
|
+
const toRemove = [];
|
|
296
|
+
for (const child of Array.from(rPr.childNodes)) {
|
|
297
|
+
if (child.nodeType === 1 && tags.includes(child.nodeName)) {
|
|
298
|
+
toRemove.push(child);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const el of toRemove) rPr.removeChild(el);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
if (properties.bold !== undefined) {
|
|
305
|
+
removeTags(['w:b', 'w:bCs']);
|
|
306
|
+
const b = createWordElement(xmlDoc, 'w:b');
|
|
307
|
+
b.setAttribute('w:val', properties.bold ? '1' : '0');
|
|
308
|
+
insertRPrChildInOrder(rPr, b);
|
|
309
|
+
const bCs = createWordElement(xmlDoc, 'w:bCs');
|
|
310
|
+
bCs.setAttribute('w:val', properties.bold ? '1' : '0');
|
|
311
|
+
insertRPrChildInOrder(rPr, bCs);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (properties.italic !== undefined) {
|
|
315
|
+
removeTags(['w:i', 'w:iCs']);
|
|
316
|
+
const i = createWordElement(xmlDoc, 'w:i');
|
|
317
|
+
i.setAttribute('w:val', properties.italic ? '1' : '0');
|
|
318
|
+
insertRPrChildInOrder(rPr, i);
|
|
319
|
+
const iCs = createWordElement(xmlDoc, 'w:iCs');
|
|
320
|
+
iCs.setAttribute('w:val', properties.italic ? '1' : '0');
|
|
321
|
+
insertRPrChildInOrder(rPr, iCs);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (properties.underline !== undefined) {
|
|
325
|
+
removeTags(['w:u']);
|
|
326
|
+
const u = createWordElement(xmlDoc, 'w:u');
|
|
327
|
+
u.setAttribute('w:val', properties.underline ? 'single' : 'none');
|
|
328
|
+
insertRPrChildInOrder(rPr, u);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const strikeVal = properties.strike !== undefined ? properties.strike : properties.strikethrough;
|
|
332
|
+
if (strikeVal !== undefined) {
|
|
333
|
+
removeTags(['w:strike', 'w:dstrike']);
|
|
334
|
+
const strike = createWordElement(xmlDoc, 'w:strike');
|
|
335
|
+
strike.setAttribute('w:val', strikeVal ? '1' : '0');
|
|
336
|
+
insertRPrChildInOrder(rPr, strike);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (properties.highlight !== undefined) {
|
|
340
|
+
removeTags(['w:highlight']);
|
|
341
|
+
if (properties.highlight && properties.highlight !== 'none') {
|
|
342
|
+
const hl = createWordElement(xmlDoc, 'w:highlight');
|
|
343
|
+
hl.setAttribute('w:val', String(properties.highlight).toLowerCase());
|
|
344
|
+
insertRPrChildInOrder(rPr, hl);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (properties.color !== undefined) {
|
|
349
|
+
removeTags(['w:color']);
|
|
350
|
+
if (properties.color && properties.color !== 'auto') {
|
|
351
|
+
const col = createWordElement(xmlDoc, 'w:color');
|
|
352
|
+
col.setAttribute('w:val', String(properties.color).replace(/^#/, ''));
|
|
353
|
+
insertRPrChildInOrder(rPr, col);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (properties.fontSize !== undefined) {
|
|
358
|
+
removeTags(['w:sz', 'w:szCs']);
|
|
359
|
+
if (properties.fontSize !== null && properties.fontSize !== '') {
|
|
360
|
+
const szVal = String(properties.fontSize);
|
|
361
|
+
const sz = createWordElement(xmlDoc, 'w:sz');
|
|
362
|
+
sz.setAttribute('w:val', szVal);
|
|
363
|
+
insertRPrChildInOrder(rPr, sz);
|
|
364
|
+
const szCs = createWordElement(xmlDoc, 'w:szCs');
|
|
365
|
+
szCs.setAttribute('w:val', szVal);
|
|
366
|
+
insertRPrChildInOrder(rPr, szCs);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (properties.fontFamily !== undefined) {
|
|
371
|
+
removeTags(['w:rFonts']);
|
|
372
|
+
if (properties.fontFamily) {
|
|
373
|
+
const fonts = createWordElement(xmlDoc, 'w:rFonts');
|
|
374
|
+
fonts.setAttribute('w:ascii', properties.fontFamily);
|
|
375
|
+
fonts.setAttribute('w:hAnsi', properties.fontFamily);
|
|
376
|
+
fonts.setAttribute('w:cs', properties.fontFamily);
|
|
377
|
+
insertRPrChildInOrder(rPr, fonts);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Checks if applying paragraph properties to a pPr will change anything.
|
|
384
|
+
*
|
|
385
|
+
* @param {Element|null} pPr - Paragraph properties
|
|
386
|
+
* @param {Object} properties - Desired properties
|
|
387
|
+
* @returns {boolean}
|
|
388
|
+
*/
|
|
389
|
+
export function checkParagraphPropertiesChanged(pPr, properties) {
|
|
390
|
+
if (!properties) return false;
|
|
391
|
+
|
|
392
|
+
if (properties.alignment !== undefined) {
|
|
393
|
+
const jc = Array.from(pPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:jc' || c.localName === 'jc'));
|
|
394
|
+
const currentVal = jc ? (jc.getAttribute('w:val') || jc.getAttribute('val')) : null;
|
|
395
|
+
if (currentVal !== properties.alignment) return true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (properties.keepNext !== undefined) {
|
|
399
|
+
const kn = Array.from(pPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:keepNext' || c.localName === 'keepNext'));
|
|
400
|
+
const currentVal = kn ? isFormattingElementEnabled(kn, false) : false;
|
|
401
|
+
if (currentVal !== !!properties.keepNext) return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (properties.keepLines !== undefined) {
|
|
405
|
+
const kl = Array.from(pPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:keepLines' || c.localName === 'keepLines'));
|
|
406
|
+
const currentVal = kl ? isFormattingElementEnabled(kl, false) : false;
|
|
407
|
+
if (currentVal !== !!properties.keepLines) return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (properties.pageBreakBefore !== undefined) {
|
|
411
|
+
const pbb = Array.from(pPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:pageBreakBefore' || c.localName === 'pageBreakBefore'));
|
|
412
|
+
const currentVal = pbb ? isFormattingElementEnabled(pbb, false) : false;
|
|
413
|
+
if (currentVal !== !!properties.pageBreakBefore) return true;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (properties.style !== undefined) {
|
|
417
|
+
const pStyle = Array.from(pPr?.childNodes || []).find(c => c.nodeType === 1 && (c.nodeName === 'w:pStyle' || c.localName === 'pStyle'));
|
|
418
|
+
const currentVal = pStyle ? (pStyle.getAttribute('w:val') || pStyle.getAttribute('val')) : null;
|
|
419
|
+
if (currentVal !== properties.style) return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Applies explicit paragraph formatting to a pPr in schema order.
|
|
427
|
+
*
|
|
428
|
+
* @param {Document} xmlDoc - XML document
|
|
429
|
+
* @param {Element} pPr - Paragraph properties
|
|
430
|
+
* @param {Object} properties - Desired properties
|
|
431
|
+
*/
|
|
432
|
+
export function applyParagraphPropertiesToPPr(xmlDoc, pPr, properties) {
|
|
433
|
+
if (!pPr || !properties) return;
|
|
434
|
+
|
|
435
|
+
const removeTags = (tags) => {
|
|
436
|
+
const toRemove = [];
|
|
437
|
+
for (const child of Array.from(pPr.childNodes)) {
|
|
438
|
+
if (child.nodeType === 1 && tags.includes(child.nodeName)) {
|
|
439
|
+
toRemove.push(child);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
for (const el of toRemove) pPr.removeChild(el);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
if (properties.alignment !== undefined) {
|
|
446
|
+
removeTags(['w:jc']);
|
|
447
|
+
if (properties.alignment) {
|
|
448
|
+
const jc = createWordElement(xmlDoc, 'w:jc');
|
|
449
|
+
jc.setAttribute('w:val', properties.alignment);
|
|
450
|
+
insertPPrChildInOrder(pPr, jc);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (properties.keepNext !== undefined) {
|
|
455
|
+
removeTags(['w:keepNext']);
|
|
456
|
+
const kn = createWordElement(xmlDoc, 'w:keepNext');
|
|
457
|
+
if (properties.keepNext) {
|
|
458
|
+
insertPPrChildInOrder(pPr, kn);
|
|
459
|
+
} else {
|
|
460
|
+
kn.setAttribute('w:val', '0');
|
|
461
|
+
insertPPrChildInOrder(pPr, kn);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (properties.keepLines !== undefined) {
|
|
466
|
+
removeTags(['w:keepLines']);
|
|
467
|
+
const kl = createWordElement(xmlDoc, 'w:keepLines');
|
|
468
|
+
if (properties.keepLines) {
|
|
469
|
+
insertPPrChildInOrder(pPr, kl);
|
|
470
|
+
} else {
|
|
471
|
+
kl.setAttribute('w:val', '0');
|
|
472
|
+
insertPPrChildInOrder(pPr, kl);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (properties.pageBreakBefore !== undefined) {
|
|
477
|
+
removeTags(['w:pageBreakBefore']);
|
|
478
|
+
const pbb = createWordElement(xmlDoc, 'w:pageBreakBefore');
|
|
479
|
+
if (properties.pageBreakBefore) {
|
|
480
|
+
insertPPrChildInOrder(pPr, pbb);
|
|
481
|
+
} else {
|
|
482
|
+
pbb.setAttribute('w:val', '0');
|
|
483
|
+
insertPPrChildInOrder(pPr, pbb);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (properties.style !== undefined) {
|
|
488
|
+
removeTags(['w:pStyle']);
|
|
489
|
+
if (properties.style) {
|
|
490
|
+
const pStyle = createWordElement(xmlDoc, 'w:pStyle');
|
|
491
|
+
pStyle.setAttribute('w:val', properties.style);
|
|
492
|
+
insertPPrChildInOrder(pPr, pStyle);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|