@docx4j/core-ts 0.3.0 → 0.3.2

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.
Files changed (42) hide show
  1. package/dist/model/content/Body.mjs +40 -19
  2. package/dist/model/content/Comment.d.mts +6 -0
  3. package/dist/model/content/Comment.mjs +76 -22
  4. package/dist/model/content/ContentControl.d.mts +11 -1
  5. package/dist/model/content/ContentControl.mjs +69 -10
  6. package/dist/model/content/Paragraph.d.mts +37 -2
  7. package/dist/model/content/Paragraph.mjs +140 -38
  8. package/dist/model/content/Range.mjs +49 -5
  9. package/dist/model/content/Table.mjs +6 -8
  10. package/dist/model/content/TrackedChange.d.mts +32 -4
  11. package/dist/model/content/TrackedChange.mjs +271 -34
  12. package/dist/model/content/index.d.mts +1 -1
  13. package/dist/model/content/index.mjs +1 -1
  14. package/dist/model/content/stylesFacade.mjs +9 -3
  15. package/dist/model/content/tracking.d.mts +65 -5
  16. package/dist/model/content/tracking.mjs +170 -43
  17. package/dist/model/customxml/XmlMapping.d.mts +20 -4
  18. package/dist/model/customxml/XmlMapping.mjs +44 -6
  19. package/dist/model/customxml/bindings.d.mts +9 -2
  20. package/dist/model/customxml/bindings.mjs +18 -2
  21. package/dist/model/customxml/index.d.mts +1 -1
  22. package/dist/model/customxml/index.mjs +1 -1
  23. package/dist/model/customxml/insert.d.mts +10 -0
  24. package/dist/model/customxml/insert.mjs +13 -0
  25. package/dist/model/fonts/RunFontSelector.d.mts +8 -1
  26. package/dist/model/fonts/RunFontSelector.mjs +6 -5
  27. package/dist/model/properties/PropertyResolver.d.mts +110 -10
  28. package/dist/model/properties/PropertyResolver.mjs +300 -31
  29. package/dist/model/properties/index.d.mts +3 -0
  30. package/dist/model/properties/index.mjs +2 -0
  31. package/dist/model/properties/table.d.mts +155 -0
  32. package/dist/model/properties/table.mjs +315 -0
  33. package/dist/model/properties/tableStyleConditions.d.mts +101 -0
  34. package/dist/model/properties/tableStyleConditions.mjs +296 -0
  35. package/dist/packages/WordprocessingMLPackage.d.mts +6 -0
  36. package/dist/packages/WordprocessingMLPackage.mjs +11 -0
  37. package/dist/parts/wml/commentStyles.d.mts +3 -0
  38. package/dist/parts/wml/commentStyles.mjs +25 -0
  39. package/dist/parts/wml/comments.mjs +4 -22
  40. package/dist/parts/wml/placeholderStyle.d.mts +3 -0
  41. package/dist/parts/wml/placeholderStyle.mjs +13 -0
  42. package/package.json +2 -2
@@ -1,11 +1,17 @@
1
1
  import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
2
  import { NumberingDefinitions } from '../listnumbering/definitions.mjs';
3
+ import { CellContext, TableContext } from './table.mjs';
3
4
  /** The parts the resolver reads. Structural, so this module imports no part class. */
4
5
  export interface ResolverSource {
5
6
  /** The styles part's `w:styles`, or undefined when the package has none. */
6
7
  styles?: wml.Styles;
7
8
  /** The numbering part's `w:numbering`, or undefined. */
8
9
  numbering?: wml.Numbering;
10
+ /**
11
+ * The settings part's `w:settings`, or undefined: read for the compatibility mode and
12
+ * `overrideTableStyleFontSizeAndJustification` (CR-007). A document with none is in mode 12.
13
+ */
14
+ settings?: wml.CTSettings;
9
15
  }
10
16
  /** As much of an `XmlPart<T>` as the resolver uses. */
11
17
  interface ReadablePart<T> {
@@ -18,6 +24,7 @@ interface PackageLike {
18
24
  getMainDocumentPart(): {
19
25
  styleDefinitionsPart?: ReadablePart<wml.Styles>;
20
26
  numberingDefinitionsPart?: ReadablePart<wml.Numbering>;
27
+ documentSettingsPart?: ReadablePart<wml.CTSettings>;
21
28
  };
22
29
  }
23
30
  /** 108 twips (0.08in): the left and right cell margin of Word's built-in Normal Table. */
@@ -44,6 +51,17 @@ export declare class PropertyResolver {
44
51
  /** The document defaults with the chain applied over them: what the style overloads return. */
45
52
  private readonly effectivePPrByStyle;
46
53
  private readonly effectiveRPrByStyle;
54
+ /** Each table style's `w:basedOn` chain merged, by id; styles only, so `refresh()` clears it. */
55
+ private readonly tableStyleChains;
56
+ /**
57
+ * A paragraph style composed over a table level, document defaults included; by (paragraph
58
+ * style, table level). What the overloads with a context start from, as
59
+ * `getEffectivePPr(styleId)` is what they start from without one.
60
+ */
61
+ private readonly composedPPrCache;
62
+ private readonly composedRPrCache;
63
+ /** See {@link appliesTableStyleSizeJcException}; read once per `refresh()`. */
64
+ private tableStyleSizeJcException;
47
65
  /** Missing styles are logged once each per resolver. */
48
66
  private readonly missingLogged;
49
67
  private numberingDefinitions;
@@ -142,6 +160,15 @@ export declare class PropertyResolver {
142
160
  * naming no style resolves as the default paragraph style does.
143
161
  */
144
162
  getEffectivePPr(styleId: string | undefined): wml.PPr;
163
+ /**
164
+ * The paragraph properties which apply to a paragraph in a table cell: as
165
+ * `getEffectivePPr(pPr)`, with the table style's contribution (its own `w:pPr`, then the
166
+ * conditional formats the paragraph is under) between the document defaults and the
167
+ * paragraph's style, and [MS-DOCX]'s justification exception where it applies. An undefined
168
+ * context - a paragraph in no table - resolves exactly as `getEffectivePPr(pPr)`. A live
169
+ * object where the paragraph has no direct formatting: copy it before changing it.
170
+ */
171
+ getEffectivePPr(pPr: wml.PPr | undefined, cellContext: CellContext | undefined): wml.PPr;
145
172
  private effectivePPrOfStyle;
146
173
  /**
147
174
  * The run properties which apply to a run, given its own `w:rPr` and the `w:pPr` of the
@@ -158,6 +185,14 @@ export declare class PropertyResolver {
158
185
  * what it contributes with nothing under it. Undefined when no such style exists.
159
186
  */
160
187
  getEffectiveRPr(styleId: string): wml.RPr | undefined;
188
+ /**
189
+ * The run properties which apply to a run in a table cell: as `getEffectiveRPr(rPr, pPr)`,
190
+ * with the table style's run properties (its own, then its conditional formats') as a level
191
+ * of the style hierarchy beneath the paragraph style's - the toggle properties combine across
192
+ * the two (ECMA-376-1 17.7.3) - and [MS-DOCX]'s size exception where it applies. An undefined
193
+ * context resolves exactly as `getEffectiveRPr(rPr, pPr)`. A new object each call.
194
+ */
195
+ getEffectiveRPr(rPr: wml.RPr | undefined, pPr: wml.PPr | undefined, cellContext: CellContext | undefined): wml.RPr;
161
196
  private effectiveRPrOfStyle;
162
197
  /**
163
198
  * The character style's chain, then the direct formatting.
@@ -173,29 +208,94 @@ export declare class PropertyResolver {
173
208
  * properties, then the `w:pPr`'s own `w:rPr`. What sizes an empty paragraph, and what a list
174
209
  * label starts from.
175
210
  */
176
- getEffectiveParagraphMarkRPr(pPr: wml.PPr | undefined): wml.RPr;
211
+ getEffectiveParagraphMarkRPr(pPr: wml.PPr | undefined, cellContext?: CellContext | undefined): wml.RPr;
177
212
  /** Whether the run states any formatting of its own (docx4j's deprecated `hasDirectRPrFormatting`). */
178
213
  hasDirectRPrFormatting(rPr: wml.RPr | undefined): boolean;
179
214
  /**
180
215
  * The table style which applies, merged root-first down its `w:basedOn` chain, then the
181
216
  * table's own `w:tblPr` over it.
182
217
  *
183
- * Word's built-in "Normal Table" (`w:tblInd` 0; cell margins 108 twips left and right, 0 top
184
- * and bottom) underlies a table naming no style and a table whose chain reaches the
185
- * document's default table style - and it is the built-in that applies, not the document's
186
- * definition of that style (measured, CR-015 probe styles-table-default). So the default
187
- * style's own layer is skipped in favour of the built-in, and a chain that does not reach it
188
- * starts from nothing.
218
+ * Word goes by the style's **name** (measured, docx4j CR-030 probes T1, T2 and T7). A style
219
+ * named "Normal Table" is Word's built-in Normal Table (`w:tblInd` 0; cell margins 108 twips
220
+ * left and right, 0 top and bottom), whatever its own definition says and whether or not it is
221
+ * the document's default: the built-in stands in for it, and the walk up the chain ends there.
222
+ * Every other style applies as written, with nothing beneath it: a chain which reaches no style
223
+ * so named starts from nothing, and the document's default table style, where it is named
224
+ * otherwise, applies as written to a table naming no style. The built-in also stands in for a
225
+ * style which is missing, and where the document has no default table style. (Until CR-007 the
226
+ * built-in was decided by the default table style's *id*, as docx4j did until 17.3.1.)
189
227
  *
190
228
  * A new `Style` each call, with a non-null `w:tblPr`.
191
229
  */
192
230
  getEffectiveTableStyle(tblPr: wml.CTTblPrBase | undefined): wml.Style;
193
231
  /**
194
- * Whether a table's style chain reaches the document's default table style - the flag
195
- * {@link getEffectiveTableStyle} decides Word's built-in Normal Table by (a table naming no
196
- * style counts as reaching it).
232
+ * Whether Word's built-in Normal Table underlies a table - the flag
233
+ * {@link getEffectiveTableStyle} decides by: the table's style chain (its own `w:tblStyle`,
234
+ * else the default table style) reaches a style *named* "Normal Table", or the style is
235
+ * missing, or there is none. Decided by name since CR-007; the method keeps docx4j's name
236
+ * from when it was decided by the default style's id.
197
237
  */
198
238
  reachesDefaultTableStyle(tblPr: wml.CTTblPrBase | undefined): boolean;
239
+ private builtInTableNormalUnderlies;
240
+ /**
241
+ * Whether [MS-DOCX]'s `overrideTableStyleFontSizeAndJustification` exception applies to this
242
+ * document: a default paragraph style's 12pt does not override the table style's size, nor its
243
+ * left justification the table style's, for paragraphs in tables. Measured with Word 365
244
+ * (docx4j CR-030 probes T5 and T6): it applies below compatibility mode 15 (a document stating
245
+ * no `compatibilityMode` is mode 12) where the setting is not on, and never in mode 15.
246
+ */
247
+ appliesTableStyleSizeJcException(): boolean;
248
+ /**
249
+ * The id of the table style a table resolves to: its own `w:tblStyle`, else the document's
250
+ * `w:default` table style, whatever its name; undefined where the table names none and the
251
+ * document has no default. (Whether that style contributes anything is
252
+ * {@link getTableStyleChain}'s question.)
253
+ */
254
+ getTableStyleIdOf(tblPr: wml.CTTblPrBase | undefined): string | undefined;
255
+ /**
256
+ * A table style's `w:basedOn` chain merged root-first, conditional formats merged per
257
+ * condition, as it gives the paragraphs of its tables their text formatting. Word goes by the
258
+ * style's *name*: a style named "Normal Table" is Word's built-in, which gives text nothing
259
+ * whatever its own definition says, so the walk up the chain ends below it; every other style
260
+ * applies as written, the default table style included.
261
+ *
262
+ * Cached per id, and shared: read it, do not change it. An empty style for undefined, for a
263
+ * missing style, and for a chain which is "Normal Table" all the way down.
264
+ */
265
+ getTableStyleChain(styleId: string | undefined): wml.Style;
266
+ /**
267
+ * The context of a table, for the paragraphs in it: build one per table and hold it while
268
+ * walking the table (the resolver keeps none, since it reads the table's content).
269
+ */
270
+ tableContext(tbl: wml.Tbl): TableContext;
271
+ /**
272
+ * The table context of one paragraph, found through its `PARENT` pointers: the nearest
273
+ * enclosing cell, its row and its table, through whatever lies between (`w:sdt`,
274
+ * `w:customXml`, `w:smartTag`). Undefined where the paragraph is in no table, where a story
275
+ * begins before a cell is reached (a text box, a footnote, endnote or comment, a header, a
276
+ * footer, the body), or where the pointers are not there (unmarshalled content, and content
277
+ * the content API inserted, has them: `linkParents`).
278
+ *
279
+ * Each call builds a {@link TableContext}, a walk of the table's rows. That is right for "what
280
+ * formatting does this paragraph have"; code resolving every paragraph of a document walks it
281
+ * with a `TableContextTracker` instead, since calling this for each paragraph of a large table
282
+ * is quadratic.
283
+ */
284
+ cellContextOf(p: wml.P): CellContext | undefined;
285
+ private clearTableCaches;
286
+ /** The table level's `w:pPr`: the style's own, then its conditional formats'; undefined if none. */
287
+ private tableLevelPPr;
288
+ /** The table level's `w:rPr`: the style's own, then its conditional formats'; undefined if none. */
289
+ private tableLevelRPr;
290
+ private composedPPr;
291
+ private composedRPr;
292
+ /**
293
+ * [MS-DOCX]'s exception, for a paragraph in a table whose style states a size (or a
294
+ * justification): the paragraph's style gives way to it if the style is the default paragraph
295
+ * style, or states no size (justification) of its own, and resolves to 12pt (left). A style of
296
+ * the paragraph's own which states one keeps it.
297
+ */
298
+ private paragraphStyleGivesWay;
199
299
  /** Word's built-in Normal Table, as a style: what it applies whatever the document's own definition says. */
200
300
  private builtInTableNormal;
201
301
  /**
@@ -2,9 +2,62 @@ import { deepCopy } from '@docx4j/generated-objects-ts';
2
2
  import { Docx4JException } from '../../opc/exceptions.mjs';
3
3
  import { log } from './log.mjs';
4
4
  import { NumberingDefinitions } from '../listnumbering/definitions.mjs';
5
- import { applyPPrBase, applyRPr, applyStyle, applyStyleLevel, applyTblPr, hasDirectFormattingPPr, hasDirectFormattingRPr, isCyclic, } from './styleUtil.mjs';
5
+ import { applyPPr, applyPPrBase, applyRPr, applyStyle, applyStyleLevel, applyTblPr, applyToggles, hasDirectFormattingPPr, hasDirectFormattingRPr, isCyclic, isEmptyStyle, } from './styleUtil.mjs';
6
+ import { TableContext, enclosingCellOf } from './table.mjs';
6
7
  /** The cache key for "no style" (a styles part with no default paragraph style). */
7
8
  const NO_STYLE = '';
9
+ /** An empty table style: what a table with no style to apply contributes. Shared, never changed. */
10
+ const NO_TABLE_STYLE = Object.freeze({ TYPE_NAME: 'org_docx4j_wml.Style' });
11
+ /** Word's compatibility mode where the document states none (docx4j `CompatibilityOptions.DEFAULT_MODE`). */
12
+ const DEFAULT_COMPATIBILITY_MODE = 12;
13
+ const WORD_COMPAT_URI = 'http://schemas.microsoft.com/office/word';
14
+ /** A `w:compatSetting` of Word's own (`w:uri` its namespace), by name. */
15
+ function wordCompatSetting(settings, name) {
16
+ var _a, _b;
17
+ return (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.compat) === null || _a === void 0 ? void 0 : _a.compatSetting) === null || _b === void 0 ? void 0 : _b.find((cs) => cs.uri === WORD_COMPAT_URI && cs.name === name);
18
+ }
19
+ /** `compatibilityMode` (11, 12, 14 or 15); 12 where none is stated or it is unreadable. */
20
+ function compatibilityModeOf(settings) {
21
+ var _a;
22
+ const val = (_a = wordCompatSetting(settings, 'compatibilityMode')) === null || _a === void 0 ? void 0 : _a.val;
23
+ if (val === undefined || val === null)
24
+ return DEFAULT_COMPATIBILITY_MODE;
25
+ const text = String(val).trim();
26
+ // Integer.parseInt: an optional sign and decimal digits, nothing else
27
+ return /^[+-]?\d+$/.test(text) ? parseInt(text, 10) : DEFAULT_COMPATIBILITY_MODE;
28
+ }
29
+ /** Whether a Word compat setting is stated on (docx4j `CompatibilityOptions.setting(name, false)`). */
30
+ function compatSettingOn(settings, name) {
31
+ var _a;
32
+ const val = (_a = wordCompatSetting(settings, name)) === null || _a === void 0 ? void 0 : _a.val;
33
+ if (val === undefined || val === null)
34
+ return false;
35
+ const v = String(val).trim().toLowerCase();
36
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
37
+ }
38
+ /** A two-level cache: (paragraph style, table style) and then the conditions' mask - a structured key, never a joined string. */
39
+ class CompositionCache {
40
+ constructor() {
41
+ this.byStyle = new Map();
42
+ }
43
+ get(styleId, key) {
44
+ var _a, _b, _c;
45
+ return (_c = (_a = this.byStyle.get(styleId !== null && styleId !== void 0 ? styleId : NO_STYLE)) === null || _a === void 0 ? void 0 : _a.get((_b = key.tableStyleId) !== null && _b !== void 0 ? _b : NO_STYLE)) === null || _c === void 0 ? void 0 : _c.get(key.mask);
46
+ }
47
+ set(styleId, key, value) {
48
+ var _a, _b;
49
+ let byTable = this.byStyle.get(styleId !== null && styleId !== void 0 ? styleId : NO_STYLE);
50
+ if (byTable === undefined)
51
+ this.byStyle.set(styleId !== null && styleId !== void 0 ? styleId : NO_STYLE, byTable = new Map());
52
+ let byMask = byTable.get((_a = key.tableStyleId) !== null && _a !== void 0 ? _a : NO_STYLE);
53
+ if (byMask === undefined)
54
+ byTable.set((_b = key.tableStyleId) !== null && _b !== void 0 ? _b : NO_STYLE, byMask = new Map());
55
+ byMask.set(key.mask, value);
56
+ }
57
+ clear() {
58
+ this.byStyle.clear();
59
+ }
60
+ }
8
61
  /** 108 twips (0.08in): the left and right cell margin of Word's built-in Normal Table. */
9
62
  export const WORD_DEFAULT_CELL_MARGIN_TWIPS = 108;
10
63
  const HEADING_NAME = /^heading ([1-9])$/i;
@@ -40,6 +93,17 @@ export class PropertyResolver {
40
93
  /** The document defaults with the chain applied over them: what the style overloads return. */
41
94
  this.effectivePPrByStyle = new Map();
42
95
  this.effectiveRPrByStyle = new Map();
96
+ /** Each table style's `w:basedOn` chain merged, by id; styles only, so `refresh()` clears it. */
97
+ this.tableStyleChains = new Map();
98
+ /**
99
+ * A paragraph style composed over a table level, document defaults included; by (paragraph
100
+ * style, table level). What the overloads with a context start from, as
101
+ * `getEffectivePPr(styleId)` is what they start from without one.
102
+ */
103
+ this.composedPPrCache = new CompositionCache();
104
+ this.composedRPrCache = new CompositionCache();
105
+ /** See {@link appliesTableStyleSizeJcException}; read once per `refresh()`. */
106
+ this.tableStyleSizeJcException = true;
43
107
  /** Missing styles are logged once each per resolver. */
44
108
  this.missingLogged = new Set();
45
109
  this.source = provider();
@@ -70,9 +134,19 @@ export class PropertyResolver {
70
134
  const numberingPart = main.numberingDefinitionsPart;
71
135
  const privateStyles = stylesPart === undefined ? undefined : await stylesPart.readContents();
72
136
  const privateNumbering = numberingPart === undefined ? undefined : await numberingPart.readContents();
137
+ // the settings are read for two compatibility values (CR-007), privately, as the others are
138
+ const settingsPart = main.documentSettingsPart;
139
+ let privateSettings;
140
+ try {
141
+ privateSettings = settingsPart === undefined ? undefined : await settingsPart.readContents();
142
+ }
143
+ catch (e) {
144
+ log.warn(`The settings part could not be read; taking compatibility mode ${DEFAULT_COMPATIBILITY_MODE}: ${String(e)}`);
145
+ }
73
146
  return new PropertyResolver(() => ({
74
147
  styles: (stylesPart === null || stylesPart === void 0 ? void 0 : stylesPart.isUnmarshalled) === true ? stylesPart.contents : privateStyles,
75
148
  numbering: (numberingPart === null || numberingPart === void 0 ? void 0 : numberingPart.isUnmarshalled) === true ? numberingPart.contents : privateNumbering,
149
+ settings: (settingsPart === null || settingsPart === void 0 ? void 0 : settingsPart.isUnmarshalled) === true ? settingsPart.contents : privateSettings,
76
150
  }), () => stylesPart !== undefined && !stylesPart.isUnmarshalled);
77
151
  }
78
152
  /** Builds a resolver over object trees directly (a test, or parts already in hand). */
@@ -103,6 +177,10 @@ export class PropertyResolver {
103
177
  }
104
178
  if (this.defaultParagraphStyleId === undefined)
105
179
  log.warn('No default paragraph style!!');
180
+ // [MS-DOCX] overrideTableStyleFontSizeAndJustification: see appliesTableStyleSizeJcException
181
+ const settings = this.source.settings;
182
+ this.tableStyleSizeJcException = compatibilityModeOf(settings) < 15
183
+ && !compatSettingOn(settings, 'overrideTableStyleFontSizeAndJustification');
106
184
  // private copies: the resolver's defaults are its own, so nothing below writes into the
107
185
  // styles part (until CR-015 phase 3 the w:sz 20 default went into the part, and a docx
108
186
  // saved after an export carried it)
@@ -163,6 +241,7 @@ export class PropertyResolver {
163
241
  this.chainRPrCache.clear();
164
242
  this.effectivePPrByStyle.clear();
165
243
  this.effectiveRPrByStyle.clear();
244
+ this.clearTableCaches();
166
245
  this.missingLogged.clear();
167
246
  this.source = this.provider();
168
247
  this.init();
@@ -304,7 +383,15 @@ export class PropertyResolver {
304
383
  }
305
384
  return styleId;
306
385
  }
307
- getEffectivePPr(arg) {
386
+ getEffectivePPr(arg, cellContext) {
387
+ if (cellContext !== undefined && cellContext.formatsText && typeof arg !== 'string') {
388
+ const composed = this.composedPPr(this.paragraphStyleOf(arg), cellContext);
389
+ if (!hasDirectFormattingPPr(arg))
390
+ return composed;
391
+ const effective = deepCopy(composed);
392
+ this.applyPPrLayer(arg, effective);
393
+ return effective;
394
+ }
308
395
  if (arg === undefined || typeof arg === 'string')
309
396
  return this.effectivePPrOfStyle(arg);
310
397
  const resolved = this.effectivePPrOfStyle(this.paragraphStyleOf(arg));
@@ -325,9 +412,14 @@ export class PropertyResolver {
325
412
  this.effectivePPrByStyle.set(key, resolved);
326
413
  return resolved;
327
414
  }
328
- getEffectiveRPr(arg, pPr) {
415
+ getEffectiveRPr(arg, pPr, cellContext) {
329
416
  if (typeof arg === 'string')
330
417
  return this.effectiveRPrOfStyle(arg);
418
+ if (cellContext !== undefined && cellContext.formatsText) {
419
+ const inTable = deepCopy(this.composedRPr(this.paragraphStyleOf(pPr), cellContext));
420
+ this.applyCharacterStyleAndDirect(arg, inTable);
421
+ return inTable;
422
+ }
331
423
  const effective = deepCopy(this.documentDefaultRPr);
332
424
  applyRPr(this.getChainRPr(this.paragraphStyleOf(pPr)), effective);
333
425
  this.applyCharacterStyleAndDirect(arg, effective);
@@ -373,7 +465,14 @@ export class PropertyResolver {
373
465
  * properties, then the `w:pPr`'s own `w:rPr`. What sizes an empty paragraph, and what a list
374
466
  * label starts from.
375
467
  */
376
- getEffectiveParagraphMarkRPr(pPr) {
468
+ getEffectiveParagraphMarkRPr(pPr, cellContext) {
469
+ if (cellContext !== undefined && cellContext.formatsText) {
470
+ // in a table cell: the table level beneath the paragraph style's (CR-007)
471
+ const inTable = deepCopy(this.composedRPr(this.paragraphStyleOf(pPr), cellContext));
472
+ if ((pPr === null || pPr === void 0 ? void 0 : pPr.rPr) !== undefined)
473
+ applyRPr(pPr.rPr, inTable);
474
+ return inTable;
475
+ }
377
476
  const effective = deepCopy(this.documentDefaultRPr);
378
477
  applyRPr(this.getChainRPr(this.paragraphStyleOf(pPr)), effective);
379
478
  if ((pPr === null || pPr === void 0 ? void 0 : pPr.rPr) !== undefined)
@@ -389,45 +488,209 @@ export class PropertyResolver {
389
488
  * The table style which applies, merged root-first down its `w:basedOn` chain, then the
390
489
  * table's own `w:tblPr` over it.
391
490
  *
392
- * Word's built-in "Normal Table" (`w:tblInd` 0; cell margins 108 twips left and right, 0 top
393
- * and bottom) underlies a table naming no style and a table whose chain reaches the
394
- * document's default table style - and it is the built-in that applies, not the document's
395
- * definition of that style (measured, CR-015 probe styles-table-default). So the default
396
- * style's own layer is skipped in favour of the built-in, and a chain that does not reach it
397
- * starts from nothing.
491
+ * Word goes by the style's **name** (measured, docx4j CR-030 probes T1, T2 and T7). A style
492
+ * named "Normal Table" is Word's built-in Normal Table (`w:tblInd` 0; cell margins 108 twips
493
+ * left and right, 0 top and bottom), whatever its own definition says and whether or not it is
494
+ * the document's default: the built-in stands in for it, and the walk up the chain ends there.
495
+ * Every other style applies as written, with nothing beneath it: a chain which reaches no style
496
+ * so named starts from nothing, and the document's default table style, where it is named
497
+ * otherwise, applies as written to a table naming no style. The built-in also stands in for a
498
+ * style which is missing, and where the document has no default table style. (Until CR-007 the
499
+ * built-in was decided by the default table style's *id*, as docx4j did until 17.3.1.)
398
500
  *
399
501
  * A new `Style` each call, with a non-null `w:tblPr`.
400
502
  */
401
503
  getEffectiveTableStyle(tblPr) {
402
- var _a, _b;
403
- const styleId = (_a = tblPr === null || tblPr === void 0 ? void 0 : tblPr.tblStyle) === null || _a === void 0 ? void 0 : _a.val;
404
- const chain = styleId === undefined ? [] : this.ancestry(styleId);
405
- const builtIn = chain.length === 0
406
- || (this.defaultTableStyleId !== undefined && chain.some((s) => s.styleId === this.defaultTableStyleId));
407
- const result = builtIn ? this.builtInTableNormal() : emptyTableStyle();
408
- for (const layer of chain) {
409
- // the built-in stands in for the document's definition of the default table style
410
- if (this.defaultTableStyleId !== undefined && layer.styleId === this.defaultTableStyleId)
411
- continue;
412
- applyStyle(layer, result);
413
- }
504
+ var _a;
505
+ const styleId = this.getTableStyleIdOf(tblPr);
506
+ const result = this.builtInTableNormalUnderlies(styleId) ? this.builtInTableNormal() : emptyTableStyle();
507
+ const chain = this.getTableStyleChain(styleId);
508
+ if (chain !== NO_TABLE_STYLE)
509
+ applyStyle(deepCopy(chain), result);
414
510
  if (tblPr !== undefined) {
415
511
  result.tblPr = applyTblPr(tblPr, result.tblPr);
416
512
  }
417
- (_b = result.tblPr) !== null && _b !== void 0 ? _b : (result.tblPr = { TYPE_NAME: 'org_docx4j_wml.CTTblPrBase' });
513
+ (_a = result.tblPr) !== null && _a !== void 0 ? _a : (result.tblPr = { TYPE_NAME: 'org_docx4j_wml.CTTblPrBase' });
418
514
  return result;
419
515
  }
420
516
  /**
421
- * Whether a table's style chain reaches the document's default table style - the flag
422
- * {@link getEffectiveTableStyle} decides Word's built-in Normal Table by (a table naming no
423
- * style counts as reaching it).
517
+ * Whether Word's built-in Normal Table underlies a table - the flag
518
+ * {@link getEffectiveTableStyle} decides by: the table's style chain (its own `w:tblStyle`,
519
+ * else the default table style) reaches a style *named* "Normal Table", or the style is
520
+ * missing, or there is none. Decided by name since CR-007; the method keeps docx4j's name
521
+ * from when it was decided by the default style's id.
424
522
  */
425
523
  reachesDefaultTableStyle(tblPr) {
426
- var _a;
427
- const styleId = (_a = tblPr === null || tblPr === void 0 ? void 0 : tblPr.tblStyle) === null || _a === void 0 ? void 0 : _a.val;
428
- const chain = styleId === undefined ? [] : this.ancestry(styleId);
429
- return chain.length === 0
430
- || (this.defaultTableStyleId !== undefined && chain.some((s) => s.styleId === this.defaultTableStyleId));
524
+ return this.builtInTableNormalUnderlies(this.getTableStyleIdOf(tblPr));
525
+ }
526
+ builtInTableNormalUnderlies(styleId) {
527
+ if (styleId === undefined)
528
+ return true;
529
+ const chain = this.ancestry(styleId);
530
+ return chain.length === 0 || chain.some(isNamedNormalTable);
531
+ }
532
+ // ------------------------------------------------------------------ the table context (CR-007)
533
+ /**
534
+ * Whether [MS-DOCX]'s `overrideTableStyleFontSizeAndJustification` exception applies to this
535
+ * document: a default paragraph style's 12pt does not override the table style's size, nor its
536
+ * left justification the table style's, for paragraphs in tables. Measured with Word 365
537
+ * (docx4j CR-030 probes T5 and T6): it applies below compatibility mode 15 (a document stating
538
+ * no `compatibilityMode` is mode 12) where the setting is not on, and never in mode 15.
539
+ */
540
+ appliesTableStyleSizeJcException() {
541
+ return this.tableStyleSizeJcException;
542
+ }
543
+ /**
544
+ * The id of the table style a table resolves to: its own `w:tblStyle`, else the document's
545
+ * `w:default` table style, whatever its name; undefined where the table names none and the
546
+ * document has no default. (Whether that style contributes anything is
547
+ * {@link getTableStyleChain}'s question.)
548
+ */
549
+ getTableStyleIdOf(tblPr) {
550
+ return (tblPr === null || tblPr === void 0 ? void 0 : tblPr.tblStyle) !== undefined ? tblPr.tblStyle.val : this.defaultTableStyleId;
551
+ }
552
+ /**
553
+ * A table style's `w:basedOn` chain merged root-first, conditional formats merged per
554
+ * condition, as it gives the paragraphs of its tables their text formatting. Word goes by the
555
+ * style's *name*: a style named "Normal Table" is Word's built-in, which gives text nothing
556
+ * whatever its own definition says, so the walk up the chain ends below it; every other style
557
+ * applies as written, the default table style included.
558
+ *
559
+ * Cached per id, and shared: read it, do not change it. An empty style for undefined, for a
560
+ * missing style, and for a chain which is "Normal Table" all the way down.
561
+ */
562
+ getTableStyleChain(styleId) {
563
+ if (styleId === undefined)
564
+ return NO_TABLE_STYLE;
565
+ const cached = this.tableStyleChains.get(styleId);
566
+ if (cached !== undefined)
567
+ return cached;
568
+ const chain = this.ancestry(styleId);
569
+ let start = 0;
570
+ for (let i = chain.length - 1; i >= 0; i--) {
571
+ if (isNamedNormalTable(chain[i])) {
572
+ start = i + 1;
573
+ break;
574
+ }
575
+ }
576
+ let merged;
577
+ for (let i = start; i < chain.length; i++) {
578
+ const layer = chain[i];
579
+ // docx4j apply(Style, null): an empty layer leaves the destination as it is, null included
580
+ if (isEmptyStyle(layer))
581
+ continue;
582
+ merged = applyStyle(layer, merged !== null && merged !== void 0 ? merged : { TYPE_NAME: 'org_docx4j_wml.Style' });
583
+ }
584
+ const result = merged !== null && merged !== void 0 ? merged : NO_TABLE_STYLE;
585
+ this.tableStyleChains.set(styleId, result);
586
+ return result;
587
+ }
588
+ /**
589
+ * The context of a table, for the paragraphs in it: build one per table and hold it while
590
+ * walking the table (the resolver keeps none, since it reads the table's content).
591
+ */
592
+ tableContext(tbl) {
593
+ return new TableContext(tbl, this);
594
+ }
595
+ /**
596
+ * The table context of one paragraph, found through its `PARENT` pointers: the nearest
597
+ * enclosing cell, its row and its table, through whatever lies between (`w:sdt`,
598
+ * `w:customXml`, `w:smartTag`). Undefined where the paragraph is in no table, where a story
599
+ * begins before a cell is reached (a text box, a footnote, endnote or comment, a header, a
600
+ * footer, the body), or where the pointers are not there (unmarshalled content, and content
601
+ * the content API inserted, has them: `linkParents`).
602
+ *
603
+ * Each call builds a {@link TableContext}, a walk of the table's rows. That is right for "what
604
+ * formatting does this paragraph have"; code resolving every paragraph of a document walks it
605
+ * with a `TableContextTracker` instead, since calling this for each paragraph of a large table
606
+ * is quadratic.
607
+ */
608
+ cellContextOf(p) {
609
+ const found = enclosingCellOf(p);
610
+ return found === undefined ? undefined : this.tableContext(found.tbl).forParagraph(found.tr, found.tc, p.pPr);
611
+ }
612
+ clearTableCaches() {
613
+ this.tableStyleChains.clear();
614
+ this.composedPPrCache.clear();
615
+ this.composedRPrCache.clear();
616
+ }
617
+ /** The table level's `w:pPr`: the style's own, then its conditional formats'; undefined if none. */
618
+ tableLevelPPr(ctx) {
619
+ let out = applyPPr(ctx.tableStyle.pPr, undefined);
620
+ for (const pr of ctx.textConditions)
621
+ out = applyPPr(pr.pPr, out);
622
+ return out;
623
+ }
624
+ /** The table level's `w:rPr`: the style's own, then its conditional formats'; undefined if none. */
625
+ tableLevelRPr(ctx) {
626
+ let out = applyRPr(ctx.tableStyle.rPr, undefined);
627
+ for (const pr of ctx.textConditions)
628
+ out = applyRPr(pr.rPr, out);
629
+ return out;
630
+ }
631
+ /*
632
+ * The composition: document defaults, the table level over them, the paragraph style's chain
633
+ * over that (for the run properties a level of its own, the toggles combined), then the
634
+ * [MS-DOCX] exception. Cached per (paragraph style, table level).
635
+ */
636
+ composedPPr(styleId, ctx) {
637
+ const cached = this.composedPPrCache.get(styleId, ctx.key);
638
+ if (cached !== undefined)
639
+ return cached;
640
+ const tableLevel = this.tableLevelPPr(ctx);
641
+ const composed = deepCopy(this.documentDefaultPPr);
642
+ if (tableLevel !== undefined)
643
+ applyPPr(tableLevel, composed);
644
+ this.applyPPrLayer(this.getChainPPr(styleId), composed);
645
+ if (this.tableStyleSizeJcException && (tableLevel === null || tableLevel === void 0 ? void 0 : tableLevel.jc) !== undefined
646
+ && this.paragraphStyleGivesWay(styleId, false)) {
647
+ composed.jc = deepCopy(tableLevel.jc);
648
+ }
649
+ this.composedPPrCache.set(styleId, ctx.key, composed);
650
+ return composed;
651
+ }
652
+ composedRPr(styleId, ctx) {
653
+ const cached = this.composedRPrCache.get(styleId, ctx.key);
654
+ if (cached !== undefined)
655
+ return cached;
656
+ const tableOnly = this.tableLevelRPr(ctx);
657
+ const tableLevel = deepCopy(this.documentDefaultRPr);
658
+ if (tableOnly !== undefined)
659
+ applyRPr(tableOnly, tableLevel);
660
+ const paragraphLevel = this.getChainRPr(styleId);
661
+ const composed = deepCopy(tableLevel);
662
+ applyRPr(paragraphLevel, composed);
663
+ // the table and the paragraph style are two levels of the hierarchy: the twelve toggles
664
+ // combine across them rather than the paragraph's overriding (17.7.3)
665
+ applyToggles(paragraphLevel, tableLevel, this.documentDefaultRPr, composed);
666
+ if (this.tableStyleSizeJcException && (tableOnly === null || tableOnly === void 0 ? void 0 : tableOnly.sz) !== undefined
667
+ && this.paragraphStyleGivesWay(styleId, true)) {
668
+ composed.sz = deepCopy(tableOnly.sz);
669
+ }
670
+ this.composedRPrCache.set(styleId, ctx.key, composed);
671
+ return composed;
672
+ }
673
+ /**
674
+ * [MS-DOCX]'s exception, for a paragraph in a table whose style states a size (or a
675
+ * justification): the paragraph's style gives way to it if the style is the default paragraph
676
+ * style, or states no size (justification) of its own, and resolves to 12pt (left). A style of
677
+ * the paragraph's own which states one keeps it.
678
+ */
679
+ paragraphStyleGivesWay(styleId, size) {
680
+ var _a, _b, _c, _d;
681
+ const isDefault = styleId === undefined || styleId === this.defaultParagraphStyleId;
682
+ const express = this.getLiveStyle(styleId);
683
+ if (!isDefault && express !== undefined) {
684
+ if (size && ((_a = express.rPr) === null || _a === void 0 ? void 0 : _a.sz) !== undefined)
685
+ return false;
686
+ if (!size && ((_b = express.pPr) === null || _b === void 0 ? void 0 : _b.jc) !== undefined)
687
+ return false;
688
+ }
689
+ if (size) {
690
+ const effective = styleId === undefined ? undefined : this.effectiveRPrOfStyle(styleId);
691
+ return ((_c = effective === null || effective === void 0 ? void 0 : effective.sz) === null || _c === void 0 ? void 0 : _c.val) === 24;
692
+ }
693
+ return ((_d = this.effectivePPrOfStyle(styleId).jc) === null || _d === void 0 ? void 0 : _d.val) === 'left';
431
694
  }
432
695
  /** Word's built-in Normal Table, as a style: what it applies whatever the document's own definition says. */
433
696
  builtInTableNormal() {
@@ -502,6 +765,7 @@ export class PropertyResolver {
502
765
  this.chainRPrCache.clear();
503
766
  this.effectivePPrByStyle.clear();
504
767
  this.effectiveRPrByStyle.clear();
768
+ this.clearTableCaches();
505
769
  let ok = true;
506
770
  const basedOn = (_b = style.basedOn) === null || _b === void 0 ? void 0 : _b.val;
507
771
  if (basedOn !== undefined)
@@ -515,6 +779,11 @@ export class PropertyResolver {
515
779
  function twips(w) {
516
780
  return { TYPE_NAME: 'org_docx4j_wml.TblWidth', type: 'dxa', w };
517
781
  }
782
+ /** Google Docs (Nov 2014) writes table styles without a `w:name`. */
783
+ function isNamedNormalTable(style) {
784
+ var _a;
785
+ return ((_a = style.name) === null || _a === void 0 ? void 0 : _a.val) === 'Normal Table';
786
+ }
518
787
  function emptyTableStyle() {
519
788
  return {
520
789
  TYPE_NAME: 'org_docx4j_wml.Style',
@@ -1,4 +1,7 @@
1
1
  export { type Property, type RunProps, type Merge, RUN, PARAGRAPH, TABLE, CELL, TOGGLES, TOGGLE_NAMES, IND, RUN_EXCLUDED, PARAGRAPH_EXCLUDED, TABLE_EXCLUDED, CELL_EXCLUDED, namedProperty, isEmptyOf, hasDirectFormattingOf, applyCatalogue, applyProperty, unsetCatalogue, copyLeaf, } from './catalogue.mjs';
2
2
  export * from './styleUtil.mjs';
3
3
  export { PropertyResolver, headingLevelByName, WORD_DEFAULT_CELL_MARGIN_TWIPS, type ResolverSource, } from './PropertyResolver.mjs';
4
+ export * as TableStyleConditions from './tableStyleConditions.mjs';
5
+ export { type Condition, type Look } from './tableStyleConditions.mjs';
6
+ export { type TableStyleSource, type TableContextSource, CellContextKey, CellContext, TableContext, TableContextTracker, enclosingCellOf, } from './table.mjs';
4
7
  export { setLogger, type Logger } from './log.mjs';
@@ -6,4 +6,6 @@
6
6
  export { RUN, PARAGRAPH, TABLE, CELL, TOGGLES, TOGGLE_NAMES, IND, RUN_EXCLUDED, PARAGRAPH_EXCLUDED, TABLE_EXCLUDED, CELL_EXCLUDED, namedProperty, isEmptyOf, hasDirectFormattingOf, applyCatalogue, applyProperty, unsetCatalogue, copyLeaf, } from './catalogue.mjs';
7
7
  export * from './styleUtil.mjs';
8
8
  export { PropertyResolver, headingLevelByName, WORD_DEFAULT_CELL_MARGIN_TWIPS, } from './PropertyResolver.mjs';
9
+ export * as TableStyleConditions from './tableStyleConditions.mjs';
10
+ export { CellContextKey, CellContext, TableContext, TableContextTracker, enclosingCellOf, } from './table.mjs';
9
11
  export { setLogger } from './log.mjs';