docxmlater 10.0.4 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/Document.d.ts +22 -0
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +170 -26
- package/dist/core/Document.js.map +1 -1
- package/dist/core/DocumentParser.d.ts.map +1 -1
- package/dist/core/DocumentParser.js +64 -1
- package/dist/core/DocumentParser.js.map +1 -1
- package/dist/elements/Hyperlink.d.ts +6 -0
- package/dist/elements/Hyperlink.d.ts.map +1 -1
- package/dist/elements/Hyperlink.js +23 -0
- package/dist/elements/Hyperlink.js.map +1 -1
- package/dist/elements/StructuredDocumentTag.d.ts +23 -1
- package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
- package/dist/elements/StructuredDocumentTag.js +97 -0
- package/dist/elements/StructuredDocumentTag.js.map +1 -1
- package/dist/elements/TableCell.d.ts +5 -0
- package/dist/elements/TableCell.d.ts.map +1 -1
- package/dist/elements/TableCell.js +13 -0
- package/dist/elements/TableCell.js.map +1 -1
- package/dist/elements/TableRow.d.ts +3 -0
- package/dist/elements/TableRow.d.ts.map +1 -1
- package/dist/elements/TableRow.js +10 -0
- package/dist/elements/TableRow.js.map +1 -1
- package/dist/formatting/AbstractNumbering.d.ts +4 -0
- package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
- package/dist/formatting/AbstractNumbering.js +15 -0
- package/dist/formatting/AbstractNumbering.js.map +1 -1
- package/dist/formatting/NumberingInstance.d.ts +6 -0
- package/dist/formatting/NumberingInstance.d.ts.map +1 -1
- package/dist/formatting/NumberingInstance.js +55 -1
- package/dist/formatting/NumberingInstance.js.map +1 -1
- package/dist/formatting/NumberingLevel.d.ts +4 -1
- package/dist/formatting/NumberingLevel.d.ts.map +1 -1
- package/dist/formatting/NumberingLevel.js +17 -0
- package/dist/formatting/NumberingLevel.js.map +1 -1
- package/dist/formatting/Style.d.ts +6 -0
- package/dist/formatting/Style.d.ts.map +1 -1
- package/dist/formatting/Style.js +20 -0
- package/dist/formatting/Style.js.map +1 -1
- package/dist/formatting/StylesManager.d.ts +23 -0
- package/dist/formatting/StylesManager.d.ts.map +1 -1
- package/dist/formatting/StylesManager.js +65 -0
- package/dist/formatting/StylesManager.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
- package/dist/tracking/DocumentTrackingContext.js +30 -7
- package/dist/tracking/DocumentTrackingContext.js.map +1 -1
- package/package.json +1 -1
- package/src/core/Document.ts +287 -31
- package/src/core/DocumentParser.ts +76 -1
- package/src/elements/Hyperlink.ts +47 -0
- package/src/elements/StructuredDocumentTag.ts +230 -1
- package/src/elements/TableCell.ts +36 -1
- package/src/elements/TableRow.ts +24 -1
- package/src/formatting/AbstractNumbering.ts +31 -0
- package/src/formatting/NumberingInstance.ts +88 -1
- package/src/formatting/NumberingLevel.ts +37 -3
- package/src/formatting/Style.ts +46 -0
- package/src/formatting/StylesManager.ts +125 -0
- package/src/index.ts +2 -2
- package/src/tracking/DocumentTrackingContext.ts +38 -7
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
|
|
10
|
+
import { NumberingLevel } from './NumberingLevel';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Properties for creating a numbering instance
|
|
@@ -29,6 +30,7 @@ export class NumberingInstance {
|
|
|
29
30
|
private numId: number;
|
|
30
31
|
private abstractNumId: number;
|
|
31
32
|
private levelOverrides = new Map<number, number>();
|
|
33
|
+
private fullLevelOverrides = new Map<number, NumberingLevel>();
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Creates a new numbering instance
|
|
@@ -144,6 +146,44 @@ export class NumberingInstance {
|
|
|
144
146
|
return this.levelOverrides.get(level);
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Sets a full level definition override for a specific level
|
|
151
|
+
* This replaces the entire level definition from the abstract numbering
|
|
152
|
+
* (ECMA-376 §17.9.8 - w:lvlOverride with full w:lvl child)
|
|
153
|
+
*
|
|
154
|
+
* @param level The level index (0-based)
|
|
155
|
+
* @param levelDef The full NumberingLevel definition to use as override
|
|
156
|
+
*/
|
|
157
|
+
setFullLevelOverride(level: number, levelDef: NumberingLevel): this {
|
|
158
|
+
if (level < 0) {
|
|
159
|
+
throw new Error('Level index must be non-negative');
|
|
160
|
+
}
|
|
161
|
+
this.fullLevelOverrides.set(level, levelDef);
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Gets a full level definition override for a specific level
|
|
167
|
+
*/
|
|
168
|
+
getFullLevelOverride(level: number): NumberingLevel | undefined {
|
|
169
|
+
return this.fullLevelOverrides.get(level);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Gets all full level definition overrides
|
|
174
|
+
*/
|
|
175
|
+
getFullLevelOverrides(): Map<number, NumberingLevel> {
|
|
176
|
+
return new Map(this.fullLevelOverrides);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Clears a full level override
|
|
181
|
+
*/
|
|
182
|
+
clearFullLevelOverride(level: number): this {
|
|
183
|
+
this.fullLevelOverrides.delete(level);
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
|
|
147
187
|
/**
|
|
148
188
|
* Generates the WordprocessingML XML for this numbering instance
|
|
149
189
|
*/
|
|
@@ -157,6 +197,8 @@ export class NumberingInstance {
|
|
|
157
197
|
|
|
158
198
|
// Add level overrides if any are set
|
|
159
199
|
for (const [level, startValue] of this.levelOverrides) {
|
|
200
|
+
// Skip levels that have a full level override (they take precedence)
|
|
201
|
+
if (this.fullLevelOverrides.has(level)) continue;
|
|
160
202
|
children.push({
|
|
161
203
|
name: 'w:lvlOverride',
|
|
162
204
|
attributes: { 'w:ilvl': level.toString() },
|
|
@@ -166,6 +208,23 @@ export class NumberingInstance {
|
|
|
166
208
|
});
|
|
167
209
|
}
|
|
168
210
|
|
|
211
|
+
// Add full level overrides
|
|
212
|
+
for (const [level, levelDef] of this.fullLevelOverrides) {
|
|
213
|
+
const overrideChildren: XMLElement[] = [];
|
|
214
|
+
// Include startOverride if also set for this level
|
|
215
|
+
if (this.levelOverrides.has(level)) {
|
|
216
|
+
overrideChildren.push(
|
|
217
|
+
XMLBuilder.wSelf('startOverride', { 'w:val': this.levelOverrides.get(level)!.toString() })
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
overrideChildren.push(levelDef.toXML());
|
|
221
|
+
children.push({
|
|
222
|
+
name: 'w:lvlOverride',
|
|
223
|
+
attributes: { 'w:ilvl': level.toString() },
|
|
224
|
+
children: overrideChildren,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
169
228
|
return XMLBuilder.w('num', { 'w:numId': this.numId.toString() }, children);
|
|
170
229
|
}
|
|
171
230
|
|
|
@@ -204,9 +263,37 @@ export class NumberingInstance {
|
|
|
204
263
|
}
|
|
205
264
|
const abstractNumId = parseInt(abstractNumIdMatch[1], 10);
|
|
206
265
|
|
|
207
|
-
|
|
266
|
+
const instance = new NumberingInstance({
|
|
208
267
|
numId,
|
|
209
268
|
abstractNumId,
|
|
210
269
|
});
|
|
270
|
+
|
|
271
|
+
// Parse level overrides (w:lvlOverride)
|
|
272
|
+
const lvlOverrideRegex = /<w:lvlOverride[^>]*w:ilvl="(\d+)"[^>]*>([\s\S]*?)<\/w:lvlOverride>/g;
|
|
273
|
+
let match: RegExpExecArray | null;
|
|
274
|
+
while ((match = lvlOverrideRegex.exec(xml)) !== null) {
|
|
275
|
+
const levelStr = match[1]!;
|
|
276
|
+
const content = match[2]!;
|
|
277
|
+
const level = parseInt(levelStr, 10);
|
|
278
|
+
|
|
279
|
+
// Check for startOverride
|
|
280
|
+
const startOverrideMatch = /<w:startOverride[^>]*w:val="([^"]+)"/.exec(content);
|
|
281
|
+
if (startOverrideMatch?.[1]) {
|
|
282
|
+
instance.setLevelOverride(level, parseInt(startOverrideMatch[1], 10));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check for full w:lvl element
|
|
286
|
+
const lvlMatch = /<w:lvl[^>]*>[\s\S]*?<\/w:lvl>/.exec(content);
|
|
287
|
+
if (lvlMatch) {
|
|
288
|
+
try {
|
|
289
|
+
const levelDef = NumberingLevel.fromXML(lvlMatch[0]);
|
|
290
|
+
instance.setFullLevelOverride(level, levelDef);
|
|
291
|
+
} catch {
|
|
292
|
+
// Skip invalid level definitions
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return instance;
|
|
211
298
|
}
|
|
212
299
|
}
|
|
@@ -111,13 +111,16 @@ export interface NumberingLevelProperties {
|
|
|
111
111
|
* - undefined (default): Restart when level-1 changes (standard behavior)
|
|
112
112
|
*/
|
|
113
113
|
lvlRestart?: number;
|
|
114
|
+
|
|
115
|
+
/** Paragraph style ID linked to this numbering level (w:pStyle per ECMA-376 §17.9.23) */
|
|
116
|
+
pStyle?: string;
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
/**
|
|
117
120
|
* Represents a single level in a numbering definition
|
|
118
121
|
*/
|
|
119
122
|
export class NumberingLevel {
|
|
120
|
-
private properties: Required<Omit<NumberingLevelProperties, 'lvlRestart' | 'underline'>> & Pick<NumberingLevelProperties, 'lvlRestart' | 'underline'>;
|
|
123
|
+
private properties: Required<Omit<NumberingLevelProperties, 'lvlRestart' | 'underline' | 'pStyle'>> & Pick<NumberingLevelProperties, 'lvlRestart' | 'underline' | 'pStyle'>;
|
|
121
124
|
|
|
122
125
|
/**
|
|
123
126
|
* Creates a new numbering level
|
|
@@ -149,6 +152,7 @@ export class NumberingLevel {
|
|
|
149
152
|
italic: properties.italic !== undefined ? properties.italic : false,
|
|
150
153
|
underline: properties.underline,
|
|
151
154
|
lvlRestart: properties.lvlRestart, // undefined means default behavior (restart on level-1 change)
|
|
155
|
+
pStyle: properties.pStyle,
|
|
152
156
|
};
|
|
153
157
|
|
|
154
158
|
this.validate();
|
|
@@ -247,7 +251,7 @@ export class NumberingLevel {
|
|
|
247
251
|
/**
|
|
248
252
|
* Gets the level properties
|
|
249
253
|
*/
|
|
250
|
-
getProperties(): Required<Omit<NumberingLevelProperties, 'lvlRestart' | 'underline'>> & Pick<NumberingLevelProperties, 'lvlRestart' | 'underline'> {
|
|
254
|
+
getProperties(): Required<Omit<NumberingLevelProperties, 'lvlRestart' | 'underline' | 'pStyle'>> & Pick<NumberingLevelProperties, 'lvlRestart' | 'underline' | 'pStyle'> {
|
|
251
255
|
return { ...this.properties };
|
|
252
256
|
}
|
|
253
257
|
|
|
@@ -399,6 +403,23 @@ export class NumberingLevel {
|
|
|
399
403
|
return this.properties.lvlRestart;
|
|
400
404
|
}
|
|
401
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Sets the paragraph style ID linked to this numbering level
|
|
408
|
+
* Links this level to a paragraph style definition (w:pStyle per ECMA-376 §17.9.23)
|
|
409
|
+
* @param styleId The paragraph style ID
|
|
410
|
+
*/
|
|
411
|
+
setParagraphStyle(styleId: string): this {
|
|
412
|
+
this.properties.pStyle = styleId;
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Gets the paragraph style ID linked to this numbering level
|
|
418
|
+
*/
|
|
419
|
+
getParagraphStyle(): string | undefined {
|
|
420
|
+
return this.properties.pStyle;
|
|
421
|
+
}
|
|
422
|
+
|
|
402
423
|
/**
|
|
403
424
|
* Sets the numbering format (decimal, lowerLetter, bullet, etc.)
|
|
404
425
|
* @param format The numbering format
|
|
@@ -447,7 +468,12 @@ export class NumberingLevel {
|
|
|
447
468
|
);
|
|
448
469
|
}
|
|
449
470
|
|
|
450
|
-
// 4. pStyle
|
|
471
|
+
// 4. pStyle (paragraph style link)
|
|
472
|
+
if (this.properties.pStyle) {
|
|
473
|
+
children.push(
|
|
474
|
+
XMLBuilder.wSelf("pStyle", { "w:val": this.properties.pStyle })
|
|
475
|
+
);
|
|
476
|
+
}
|
|
451
477
|
|
|
452
478
|
// 5. Legal numbering style
|
|
453
479
|
if (this.properties.isLegalNumberingStyle) {
|
|
@@ -945,6 +971,13 @@ export class NumberingLevel {
|
|
|
945
971
|
lvlRestart = parseInt(lvlRestartMatch[1], 10);
|
|
946
972
|
}
|
|
947
973
|
|
|
974
|
+
// Extract pStyle (paragraph style link)
|
|
975
|
+
let pStyle: string | undefined;
|
|
976
|
+
const pStyleMatch = /<w:pStyle[^>]*w:val="([^"]+)"/.exec(xml);
|
|
977
|
+
if (pStyleMatch?.[1]) {
|
|
978
|
+
pStyle = pStyleMatch[1];
|
|
979
|
+
}
|
|
980
|
+
|
|
948
981
|
// Extract indentation from <w:pPr><w:ind>
|
|
949
982
|
let leftIndent = 720 + level * 360; // default
|
|
950
983
|
let hangingIndent = 360; // default
|
|
@@ -1001,6 +1034,7 @@ export class NumberingLevel {
|
|
|
1001
1034
|
underline,
|
|
1002
1035
|
color,
|
|
1003
1036
|
lvlRestart,
|
|
1037
|
+
pStyle,
|
|
1004
1038
|
});
|
|
1005
1039
|
}
|
|
1006
1040
|
}
|
package/src/formatting/Style.ts
CHANGED
|
@@ -222,6 +222,10 @@ export interface StyleProperties {
|
|
|
222
222
|
locked?: boolean;
|
|
223
223
|
/** Personal - user-specific style */
|
|
224
224
|
personal?: boolean;
|
|
225
|
+
/** Personal compose - style for composing new messages */
|
|
226
|
+
personalCompose?: boolean;
|
|
227
|
+
/** Personal reply - style for replying to messages */
|
|
228
|
+
personalReply?: boolean;
|
|
225
229
|
/** Link - linked character/paragraph style ID */
|
|
226
230
|
link?: string;
|
|
227
231
|
/** Auto-redefine - update style from manual formatting */
|
|
@@ -440,6 +444,38 @@ export class Style {
|
|
|
440
444
|
return this;
|
|
441
445
|
}
|
|
442
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Sets whether this is a personal compose style (for composing new messages)
|
|
449
|
+
* @param enabled - True to mark as personal compose
|
|
450
|
+
*/
|
|
451
|
+
setPersonalCompose(enabled: boolean): this {
|
|
452
|
+
this.properties.personalCompose = enabled;
|
|
453
|
+
return this;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Gets whether this is a personal compose style
|
|
458
|
+
*/
|
|
459
|
+
getPersonalCompose(): boolean {
|
|
460
|
+
return this.properties.personalCompose === true;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Sets whether this is a personal reply style (for replying to messages)
|
|
465
|
+
* @param enabled - True to mark as personal reply
|
|
466
|
+
*/
|
|
467
|
+
setPersonalReply(enabled: boolean): this {
|
|
468
|
+
this.properties.personalReply = enabled;
|
|
469
|
+
return this;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Gets whether this is a personal reply style
|
|
474
|
+
*/
|
|
475
|
+
getPersonalReply(): boolean {
|
|
476
|
+
return this.properties.personalReply === true;
|
|
477
|
+
}
|
|
478
|
+
|
|
443
479
|
/**
|
|
444
480
|
* Sets the linked style ID (for character/paragraph style linking)
|
|
445
481
|
* @param styleId - ID of the linked style
|
|
@@ -794,6 +830,16 @@ export class Style {
|
|
|
794
830
|
styleChildren.push(XMLBuilder.wSelf("personal"));
|
|
795
831
|
}
|
|
796
832
|
|
|
833
|
+
// personalCompose - Style for composing new messages
|
|
834
|
+
if (this.properties.personalCompose) {
|
|
835
|
+
styleChildren.push(XMLBuilder.wSelf("personalCompose"));
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// personalReply - Style for replying to messages
|
|
839
|
+
if (this.properties.personalReply) {
|
|
840
|
+
styleChildren.push(XMLBuilder.wSelf("personalReply"));
|
|
841
|
+
}
|
|
842
|
+
|
|
797
843
|
// Add paragraph properties
|
|
798
844
|
if (this.properties.paragraphFormatting || this.properties.numPr) {
|
|
799
845
|
const pPr = this.generateParagraphProperties(
|
|
@@ -8,6 +8,43 @@ import { XMLBuilder } from "../xml/XMLBuilder";
|
|
|
8
8
|
import { XMLParser } from "../xml/XMLParser";
|
|
9
9
|
import { Style, StyleType } from "./Style";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for latent styles (w:latentStyles per ECMA-376 §17.7.4.6)
|
|
13
|
+
* Controls which built-in styles are shown in the Word UI gallery
|
|
14
|
+
*/
|
|
15
|
+
export interface LatentStylesConfig {
|
|
16
|
+
/** Default locked state for built-in styles */
|
|
17
|
+
defaultLockedState?: boolean;
|
|
18
|
+
/** Default UI priority for built-in styles */
|
|
19
|
+
defaultUiPriority?: number;
|
|
20
|
+
/** Default semi-hidden state */
|
|
21
|
+
defaultSemiHidden?: boolean;
|
|
22
|
+
/** Default unhide-when-used state */
|
|
23
|
+
defaultUnhideWhenUsed?: boolean;
|
|
24
|
+
/** Default quick format flag */
|
|
25
|
+
defaultQFormat?: boolean;
|
|
26
|
+
/** Total count of style definitions */
|
|
27
|
+
count?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Exception to latent style defaults (w:lsdException per ECMA-376 §17.7.4.7)
|
|
32
|
+
*/
|
|
33
|
+
export interface LatentStyleException {
|
|
34
|
+
/** Style name */
|
|
35
|
+
name: string;
|
|
36
|
+
/** Override: locked state */
|
|
37
|
+
locked?: boolean;
|
|
38
|
+
/** Override: UI priority */
|
|
39
|
+
uiPriority?: number;
|
|
40
|
+
/** Override: semi-hidden */
|
|
41
|
+
semiHidden?: boolean;
|
|
42
|
+
/** Override: unhide when used */
|
|
43
|
+
unhideWhenUsed?: boolean;
|
|
44
|
+
/** Override: quick format */
|
|
45
|
+
qFormat?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
11
48
|
/**
|
|
12
49
|
* Result of XML validation
|
|
13
50
|
*/
|
|
@@ -37,6 +74,10 @@ export class StylesManager {
|
|
|
37
74
|
// Track which specific styles have been modified (for selective merging)
|
|
38
75
|
private _modifiedStyleIds = new Set<string>();
|
|
39
76
|
|
|
77
|
+
// Latent styles configuration
|
|
78
|
+
private latentStyles?: LatentStylesConfig;
|
|
79
|
+
private latentStyleExceptions: LatentStyleException[] = [];
|
|
80
|
+
|
|
40
81
|
/**
|
|
41
82
|
* Registry of built-in style factory functions
|
|
42
83
|
* Maps style ID to factory function for lazy loading
|
|
@@ -411,6 +452,46 @@ export class StylesManager {
|
|
|
411
452
|
return style;
|
|
412
453
|
}
|
|
413
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Sets the latent styles configuration
|
|
457
|
+
* @param config - Latent styles configuration
|
|
458
|
+
*/
|
|
459
|
+
setLatentStyles(config: LatentStylesConfig): this {
|
|
460
|
+
this.latentStyles = config;
|
|
461
|
+
this._modified = true;
|
|
462
|
+
return this;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Gets the latent styles configuration
|
|
467
|
+
*/
|
|
468
|
+
getLatentStyles(): LatentStylesConfig | undefined {
|
|
469
|
+
return this.latentStyles;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Adds a latent style exception
|
|
474
|
+
* @param exception - The exception to add
|
|
475
|
+
*/
|
|
476
|
+
addLatentStyleException(exception: LatentStyleException): this {
|
|
477
|
+
// Replace existing exception for same name
|
|
478
|
+
const idx = this.latentStyleExceptions.findIndex(e => e.name === exception.name);
|
|
479
|
+
if (idx >= 0) {
|
|
480
|
+
this.latentStyleExceptions[idx] = exception;
|
|
481
|
+
} else {
|
|
482
|
+
this.latentStyleExceptions.push(exception);
|
|
483
|
+
}
|
|
484
|
+
this._modified = true;
|
|
485
|
+
return this;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Gets all latent style exceptions
|
|
490
|
+
*/
|
|
491
|
+
getLatentStyleExceptions(): LatentStyleException[] {
|
|
492
|
+
return [...this.latentStyleExceptions];
|
|
493
|
+
}
|
|
494
|
+
|
|
414
495
|
/**
|
|
415
496
|
* Generates the complete styles.xml file
|
|
416
497
|
* @returns XML string for word/styles.xml
|
|
@@ -424,6 +505,11 @@ export class StylesManager {
|
|
|
424
505
|
// Add document defaults
|
|
425
506
|
stylesChildren.push(this.generateDocDefaults());
|
|
426
507
|
|
|
508
|
+
// Add latent styles if configured (per ECMA-376 CT_Styles order: docDefaults, latentStyles, style*)
|
|
509
|
+
if (this.latentStyles) {
|
|
510
|
+
stylesChildren.push(this.generateLatentStyles());
|
|
511
|
+
}
|
|
512
|
+
|
|
427
513
|
// Add all styles
|
|
428
514
|
for (const style of this.getAllStyles()) {
|
|
429
515
|
stylesChildren.push(style.toXML());
|
|
@@ -481,6 +567,45 @@ export class StylesManager {
|
|
|
481
567
|
]);
|
|
482
568
|
}
|
|
483
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Generates the latent styles XML element
|
|
572
|
+
*/
|
|
573
|
+
private generateLatentStyles() {
|
|
574
|
+
if (!this.latentStyles) return XMLBuilder.w("latentStyles", {}, []);
|
|
575
|
+
|
|
576
|
+
const attrs: Record<string, string> = {};
|
|
577
|
+
if (this.latentStyles.defaultLockedState !== undefined) {
|
|
578
|
+
attrs["w:defLockedState"] = this.latentStyles.defaultLockedState ? "1" : "0";
|
|
579
|
+
}
|
|
580
|
+
if (this.latentStyles.defaultUiPriority !== undefined) {
|
|
581
|
+
attrs["w:defUIPriority"] = this.latentStyles.defaultUiPriority.toString();
|
|
582
|
+
}
|
|
583
|
+
if (this.latentStyles.defaultSemiHidden !== undefined) {
|
|
584
|
+
attrs["w:defSemiHidden"] = this.latentStyles.defaultSemiHidden ? "1" : "0";
|
|
585
|
+
}
|
|
586
|
+
if (this.latentStyles.defaultUnhideWhenUsed !== undefined) {
|
|
587
|
+
attrs["w:defUnhideWhenUsed"] = this.latentStyles.defaultUnhideWhenUsed ? "1" : "0";
|
|
588
|
+
}
|
|
589
|
+
if (this.latentStyles.defaultQFormat !== undefined) {
|
|
590
|
+
attrs["w:defQFormat"] = this.latentStyles.defaultQFormat ? "1" : "0";
|
|
591
|
+
}
|
|
592
|
+
if (this.latentStyles.count !== undefined) {
|
|
593
|
+
attrs["w:count"] = this.latentStyles.count.toString();
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const children = this.latentStyleExceptions.map(exc => {
|
|
597
|
+
const excAttrs: Record<string, string> = { "w:name": exc.name };
|
|
598
|
+
if (exc.locked !== undefined) excAttrs["w:locked"] = exc.locked ? "1" : "0";
|
|
599
|
+
if (exc.uiPriority !== undefined) excAttrs["w:uiPriority"] = exc.uiPriority.toString();
|
|
600
|
+
if (exc.semiHidden !== undefined) excAttrs["w:semiHidden"] = exc.semiHidden ? "1" : "0";
|
|
601
|
+
if (exc.unhideWhenUsed !== undefined) excAttrs["w:unhideWhenUsed"] = exc.unhideWhenUsed ? "1" : "0";
|
|
602
|
+
if (exc.qFormat !== undefined) excAttrs["w:qFormat"] = exc.qFormat ? "1" : "0";
|
|
603
|
+
return XMLBuilder.wSelf("lsdException", excAttrs);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
return XMLBuilder.w("latentStyles", attrs, children);
|
|
607
|
+
}
|
|
608
|
+
|
|
484
609
|
/**
|
|
485
610
|
* Creates a new StylesManager with built-in styles
|
|
486
611
|
* @returns New StylesManager instance
|
package/src/index.ts
CHANGED
|
@@ -103,7 +103,7 @@ export {
|
|
|
103
103
|
isHyperlinkInstruction,
|
|
104
104
|
ParsedHyperlinkInstruction,
|
|
105
105
|
} from './elements/FieldHelpers';
|
|
106
|
-
export { StructuredDocumentTag, SDTProperties, SDTLockType, SDTContent } from './elements/StructuredDocumentTag';
|
|
106
|
+
export { StructuredDocumentTag, SDTProperties, SDTLockType, SDTContent, SDTPlaceholder, SDTDataBinding, ContentControlType } from './elements/StructuredDocumentTag';
|
|
107
107
|
export { TableOfContents, TOCProperties } from './elements/TableOfContents';
|
|
108
108
|
export { TableOfContentsElement } from './elements/TableOfContentsElement';
|
|
109
109
|
export { AlternateContent } from './elements/AlternateContent';
|
|
@@ -181,7 +181,7 @@ export {
|
|
|
181
181
|
// =============================================================================
|
|
182
182
|
|
|
183
183
|
export { Style, StyleType, StyleProperties } from './formatting/Style';
|
|
184
|
-
export { StylesManager, ValidationResult } from './formatting/StylesManager';
|
|
184
|
+
export { StylesManager, ValidationResult, LatentStylesConfig, LatentStyleException } from './formatting/StylesManager';
|
|
185
185
|
export {
|
|
186
186
|
NumberingLevel,
|
|
187
187
|
NumberFormat,
|
|
@@ -343,16 +343,47 @@ export class DocumentTrackingContext implements TrackingContext {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
// Apply tblPrChange to each Table
|
|
346
|
+
// Per ECMA-376 §17.13.5.36, tblPrChange must contain FULL previous tblPr,
|
|
347
|
+
// not just the delta of changed properties.
|
|
346
348
|
for (const [table, changes] of tableChanges) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
349
|
+
// Build full snapshot: start from current formatting, roll back changed properties
|
|
350
|
+
const currentFormatting = table.getFormatting();
|
|
351
|
+
const fullPrevProps: Record<string, unknown> = {};
|
|
352
|
+
|
|
353
|
+
for (const [key, value] of Object.entries(currentFormatting)) {
|
|
354
|
+
if (value !== undefined) {
|
|
355
|
+
fullPrevProps[key] = value;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Roll back changed properties to their previous values
|
|
360
|
+
let latestTimestamp = 0;
|
|
361
|
+
for (const change of changes) {
|
|
362
|
+
if (change.previousValue !== undefined) {
|
|
363
|
+
fullPrevProps[change.property] = change.previousValue;
|
|
352
364
|
} else {
|
|
353
|
-
|
|
365
|
+
delete fullPrevProps[change.property];
|
|
354
366
|
}
|
|
355
|
-
|
|
367
|
+
if (change.timestamp > latestTimestamp) {
|
|
368
|
+
latestTimestamp = change.timestamp;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const date = formatDateForXml(new Date(latestTimestamp));
|
|
373
|
+
|
|
374
|
+
const existing = table.getTblPrChange();
|
|
375
|
+
if (existing) {
|
|
376
|
+
// Merge: existing previous state takes precedence (it's the ORIGINAL baseline)
|
|
377
|
+
const merged = { ...fullPrevProps, ...(existing.previousProperties || {}) };
|
|
378
|
+
table.setTblPrChange({ ...existing, previousProperties: merged });
|
|
379
|
+
} else {
|
|
380
|
+
table.setTblPrChange({
|
|
381
|
+
author: this.author,
|
|
382
|
+
date,
|
|
383
|
+
id: String(this.revisionManager.consumeNextId()),
|
|
384
|
+
previousProperties: fullPrevProps,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
356
387
|
}
|
|
357
388
|
|
|
358
389
|
// Apply trPrChange to each TableRow
|