@docx4j/core-ts 0.4.0 → 0.5.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.
Files changed (49) hide show
  1. package/README.md +5 -2
  2. package/dist/html/convert.mjs +38 -14
  3. package/dist/model/content/Body.d.mts +13 -1
  4. package/dist/model/content/Body.mjs +90 -19
  5. package/dist/model/content/Comment.mjs +7 -0
  6. package/dist/model/content/InlinePicture.d.mts +71 -7
  7. package/dist/model/content/InlinePicture.mjs +201 -28
  8. package/dist/model/content/List.mjs +10 -1
  9. package/dist/model/content/Paragraph.d.mts +28 -5
  10. package/dist/model/content/Paragraph.mjs +77 -37
  11. package/dist/model/content/Range.d.mts +18 -2
  12. package/dist/model/content/Range.mjs +194 -64
  13. package/dist/model/content/TrackedChange.d.mts +129 -3
  14. package/dist/model/content/TrackedChange.mjs +577 -12
  15. package/dist/model/content/binder.d.mts +2 -1
  16. package/dist/model/content/binder.mjs +48 -1
  17. package/dist/model/content/index.d.mts +2 -2
  18. package/dist/model/content/index.mjs +1 -1
  19. package/dist/model/content/relationships.d.mts +30 -0
  20. package/dist/model/content/relationships.mjs +133 -0
  21. package/dist/model/content/tree.d.mts +52 -2
  22. package/dist/model/content/tree.mjs +48 -3
  23. package/dist/model/customxml/pictures.d.mts +5 -2
  24. package/dist/model/customxml/pictures.mjs +13 -7
  25. package/dist/model/fonts/families.generated.mjs +2 -2
  26. package/dist/model/fonts/registry.mjs +7 -1
  27. package/dist/model/fonts/substitutions.generated.mjs +2 -2
  28. package/dist/model/fonts/themes.generated.mjs +1 -1
  29. package/dist/model/listnumbering/definitions.mjs +18 -0
  30. package/dist/model/properties/PropertyResolver.d.mts +27 -6
  31. package/dist/model/properties/PropertyResolver.mjs +83 -16
  32. package/dist/model/properties/catalogue.d.mts +15 -1
  33. package/dist/model/properties/catalogue.mjs +80 -27
  34. package/dist/model/properties/table.d.mts +16 -2
  35. package/dist/model/properties/table.mjs +13 -3
  36. package/dist/opc/Load.d.mts +9 -2
  37. package/dist/opc/index.d.mts +2 -2
  38. package/dist/opc/index.mjs +2 -2
  39. package/dist/opc/mce/preprocessor.d.mts +62 -6
  40. package/dist/opc/mce/preprocessor.mjs +142 -35
  41. package/dist/opc/mce/understood.d.mts +10 -0
  42. package/dist/opc/mce/understood.mjs +25 -3
  43. package/dist/packages/OpcPackage.d.mts +7 -0
  44. package/dist/packages/OpcPackage.mjs +15 -3
  45. package/dist/packages/WordprocessingMLPackage.d.mts +35 -2
  46. package/dist/packages/WordprocessingMLPackage.mjs +98 -2
  47. package/dist/parts/XmlPart.d.mts +26 -5
  48. package/dist/parts/XmlPart.mjs +67 -18
  49. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { deepCopy } from '@docx4j/generated-objects-ts';
2
- import { Docx4JException } from '../../opc/exceptions.mjs';
2
+ import { Docx4JException, PartUnmarshalException } from '../../opc/exceptions.mjs';
3
3
  import { log } from './log.mjs';
4
4
  import { NumberingDefinitions } from '../listnumbering/definitions.mjs';
5
5
  import { applyPPr, applyPPrBase, applyRPr, applyStyle, applyStyleLevel, applyTblPr, applyToggles, hasDirectFormattingPPr, hasDirectFormattingRPr, isCyclic, isEmptyStyle, } from './styleUtil.mjs';
@@ -75,6 +75,26 @@ export function headingLevelByName(style) {
75
75
  const match = HEADING_NAME.exec(name.trim());
76
76
  return match ? Number(match[1]) : -1;
77
77
  }
78
+ /**
79
+ * A styles or numbering part the resolver can't do without: when it can't be read, nothing in the
80
+ * document can be given its formatting, so the document is refused (Jason, 2026-10-17), and the
81
+ * error says so before it says what was wrong where. A `PartUnmarshalException` stays one, with its
82
+ * part, location, line and column, so a caller showing the fault reads the same fields as for any
83
+ * part.
84
+ */
85
+ async function readOrRefuse(part, role) {
86
+ try {
87
+ return await part.readContents();
88
+ }
89
+ catch (e) {
90
+ const why = role === 'styles' ? "every paragraph's formatting depends on it" : "its lists' numbers and indents depend on it";
91
+ const lead = `The document cannot be opened: its ${role} part could not be read, and ${why}.`;
92
+ if (e instanceof PartUnmarshalException) {
93
+ throw new PartUnmarshalException(`${lead} ${e.message}`, e.partName, { cause: e, location: e.location, line: e.line, column: e.column });
94
+ }
95
+ throw new Docx4JException(`${lead} ${e instanceof Error ? e.message : String(e)}`, { cause: e });
96
+ }
97
+ }
78
98
  export class PropertyResolver {
79
99
  constructor(provider,
80
100
  /** Set when the styles tree the resolver holds is not the styles part's own. */
@@ -104,6 +124,12 @@ export class PropertyResolver {
104
124
  this.composedRPrCache = new CompositionCache();
105
125
  /** See {@link appliesTableStyleSizeJcException}; read once per `refresh()`. */
106
126
  this.tableStyleSizeJcException = true;
127
+ /**
128
+ * Whether the document's own `w:docDefaults` state a size, rather than the 10pt the resolver
129
+ * makes explicit where they do not: the size exception takes the document defaults' size only
130
+ * where they state one. Read once per `refresh()`.
131
+ */
132
+ this.docDefaultsStateSize = false;
107
133
  /** Missing styles are logged once each per resolver. */
108
134
  this.missingLogged = new Set();
109
135
  this.source = provider();
@@ -132,8 +158,8 @@ export class PropertyResolver {
132
158
  }
133
159
  const stylesPart = main.styleDefinitionsPart;
134
160
  const numberingPart = main.numberingDefinitionsPart;
135
- const privateStyles = stylesPart === undefined ? undefined : await stylesPart.readContents();
136
- const privateNumbering = numberingPart === undefined ? undefined : await numberingPart.readContents();
161
+ const privateStyles = stylesPart === undefined ? undefined : await readOrRefuse(stylesPart, 'styles');
162
+ const privateNumbering = numberingPart === undefined ? undefined : await readOrRefuse(numberingPart, 'numbering');
137
163
  // the settings are read for two compatibility values (CR-007), privately, as the others are
138
164
  const settingsPart = main.documentSettingsPart;
139
165
  let privateSettings;
@@ -189,6 +215,7 @@ export class PropertyResolver {
189
215
  ? { TYPE_NAME: 'org_docx4j_wml.PPr' } : deepCopy(docDefaults.pPrDefault.pPr);
190
216
  this.documentDefaultRPr = ((_f = docDefaults === null || docDefaults === void 0 ? void 0 : docDefaults.rPrDefault) === null || _f === void 0 ? void 0 : _f.rPr) === undefined
191
217
  ? { TYPE_NAME: 'org_docx4j_wml.RPr' } : deepCopy(docDefaults.rPrDefault.rPr);
218
+ this.docDefaultsStateSize = this.documentDefaultRPr.sz !== undefined;
192
219
  if (this.documentDefaultRPr.sz === undefined) {
193
220
  // Word's default made explicit: 10pt where nothing states a size (measured, CR-015 probe
194
221
  // styles-no-size-anywhere: identical to an explicit 10pt run)
@@ -384,7 +411,7 @@ export class PropertyResolver {
384
411
  return styleId;
385
412
  }
386
413
  getEffectivePPr(arg, cellContext) {
387
- if (cellContext !== undefined && cellContext.formatsText && typeof arg !== 'string') {
414
+ if (this.composes(cellContext) && typeof arg !== 'string') {
388
415
  const composed = this.composedPPr(this.paragraphStyleOf(arg), cellContext);
389
416
  if (!hasDirectFormattingPPr(arg))
390
417
  return composed;
@@ -415,7 +442,7 @@ export class PropertyResolver {
415
442
  getEffectiveRPr(arg, pPr, cellContext) {
416
443
  if (typeof arg === 'string')
417
444
  return this.effectiveRPrOfStyle(arg);
418
- if (cellContext !== undefined && cellContext.formatsText) {
445
+ if (this.composes(cellContext)) {
419
446
  const inTable = deepCopy(this.composedRPr(this.paragraphStyleOf(pPr), cellContext));
420
447
  this.applyCharacterStyleAndDirect(arg, inTable);
421
448
  return inTable;
@@ -462,23 +489,43 @@ export class PropertyResolver {
462
489
  }
463
490
  /**
464
491
  * The run properties of the paragraph mark: document defaults, the paragraph style's run
465
- * properties, then the `w:pPr`'s own `w:rPr`. What sizes an empty paragraph, and what a list
466
- * label starts from.
492
+ * properties, the mark's character style, then the `w:pPr`'s own `w:rPr`. What sizes an empty
493
+ * paragraph, and what a list label starts from.
467
494
  */
468
495
  getEffectiveParagraphMarkRPr(pPr, cellContext) {
469
- if (cellContext !== undefined && cellContext.formatsText) {
496
+ if (this.composes(cellContext)) {
470
497
  // in a table cell: the table level beneath the paragraph style's (CR-007)
471
498
  const inTable = deepCopy(this.composedRPr(this.paragraphStyleOf(pPr), cellContext));
472
499
  if ((pPr === null || pPr === void 0 ? void 0 : pPr.rPr) !== undefined)
473
- applyRPr(pPr.rPr, inTable);
500
+ this.applyMarkRPr(pPr.rPr, inTable);
474
501
  return inTable;
475
502
  }
476
503
  const effective = deepCopy(this.documentDefaultRPr);
477
504
  applyRPr(this.getChainRPr(this.paragraphStyleOf(pPr)), effective);
478
505
  if ((pPr === null || pPr === void 0 ? void 0 : pPr.rPr) !== undefined)
479
- applyRPr(pPr.rPr, effective);
506
+ this.applyMarkRPr(pPr.rPr, effective);
480
507
  return effective;
481
508
  }
509
+ /**
510
+ * The paragraph mark's character style (`w:pPr/w:rPr/w:rStyle`) as a level over what is
511
+ * beneath it, then the mark's direct formatting: the order {@link applyCharacterStyleAndDirect}
512
+ * gives a run. docx4j `applyMarkRPr`, since 17.3.2 (fb2a764f7: until then the mark's
513
+ * `w:rStyle` was copied as an attribute and never resolved, so an empty paragraph whose mark
514
+ * names a 9pt character style was sized at its paragraph style's 12pt).
515
+ */
516
+ applyMarkRPr(mark, effective) {
517
+ var _a;
518
+ const runStyleId = (_a = mark.rStyle) === null || _a === void 0 ? void 0 : _a.val;
519
+ if (runStyleId !== undefined) {
520
+ if (this.getLiveStyle(runStyleId) === undefined) {
521
+ this.logMissing(runStyleId);
522
+ }
523
+ else {
524
+ applyStyleLevel(this.getChainRPr(runStyleId), effective, this.documentDefaultRPr);
525
+ }
526
+ }
527
+ applyRPr(mark, effective);
528
+ }
482
529
  /** Whether the run states any formatting of its own (docx4j's deprecated `hasDirectRPrFormatting`). */
483
530
  hasDirectRPrFormatting(rPr) {
484
531
  return hasDirectFormattingRPr(rPr);
@@ -609,6 +656,15 @@ export class PropertyResolver {
609
656
  const found = enclosingCellOf(p);
610
657
  return found === undefined ? undefined : this.tableContext(found.tbl).forParagraph(found.tr, found.tc, p.pPr);
611
658
  }
659
+ /**
660
+ * Whether a paragraph in this context is composed over its table level: where the table style
661
+ * gives it text formatting, or, under [MS-DOCX]'s size exception, where the style formats text
662
+ * anywhere ({@link CellContext.styleFormatsText}). docx4j `composes` (900b6a212, 17.3.2).
663
+ */
664
+ composes(cellContext) {
665
+ return cellContext !== undefined && (cellContext.formatsText
666
+ || (this.tableStyleSizeJcException && cellContext.styleFormatsText));
667
+ }
612
668
  clearTableCaches() {
613
669
  this.tableStyleChains.clear();
614
670
  this.composedPPrCache.clear();
@@ -663,18 +719,29 @@ export class PropertyResolver {
663
719
  // the table and the paragraph style are two levels of the hierarchy: the twelve toggles
664
720
  // combine across them rather than the paragraph's overriding (17.7.3)
665
721
  applyToggles(paragraphLevel, tableLevel, this.documentDefaultRPr, composed);
666
- if (this.tableStyleSizeJcException && (tableOnly === null || tableOnly === void 0 ? void 0 : tableOnly.sz) !== undefined
722
+ // The size the table level gives is its own where the table style states one, else the
723
+ // document defaults' where they state one: Word applies the exception wherever the table
724
+ // style formats text at all, not only where it states a size (docx4j 900b6a212, measured on
725
+ // its table-style-size-trigger probes: in mode 12, with docDefaults 11pt and Normal 12pt, a
726
+ // table style with only a w:pPr, only an rPr colour, or only a firstRow format sets its
727
+ // table's paragraphs at 11pt; with none of these, at Normal's 12pt). But where neither the
728
+ // table style nor the docDefaults state a size, Normal keeps its 12pt, not the 10pt Word
729
+ // falls back to (docx4j's corpus documents 10244 and 12723).
730
+ const tableStatesSize = (tableOnly === null || tableOnly === void 0 ? void 0 : tableOnly.sz) !== undefined;
731
+ if (this.tableStyleSizeJcException && ctx.styleFormatsText && tableLevel.sz !== undefined
732
+ && (tableStatesSize || this.docDefaultsStateSize)
667
733
  && this.paragraphStyleGivesWay(styleId, true)) {
668
- composed.sz = deepCopy(tableOnly.sz);
734
+ composed.sz = deepCopy(tableLevel.sz);
669
735
  }
670
736
  this.composedRPrCache.set(styleId, ctx.key, composed);
671
737
  return composed;
672
738
  }
673
739
  /**
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.
740
+ * [MS-DOCX]'s exception, for a paragraph in a table whose style formats text (for the size: the
741
+ * style need not state one, since docx4j 17.3.2) or states a justification: the paragraph's
742
+ * style gives way to it if the style is the default paragraph style, or states no size
743
+ * (justification) of its own, and resolves to 12pt (left). A style of the paragraph's own which
744
+ * states one keeps it.
678
745
  */
679
746
  paragraphStyleGivesWay(styleId, size) {
680
747
  var _a, _b, _c, _d;
@@ -39,7 +39,12 @@ export declare function isTrue(value: wml.BooleanDefaultTrue | undefined): boole
39
39
  export declare function isEmptyRStyle(value: wml.RStyle | undefined): boolean;
40
40
  /** docx4j `isEmpty(RFonts)`: insensitive to a `w:hint` (which `apply` still carries). */
41
41
  export declare function isEmptyRFonts(value: wml.RFonts | undefined): boolean;
42
+ /**
43
+ * A `w:color` stating only a theme colour is not empty: it is inherited, where until docx4j CR-034
44
+ * (2e381ef26) it was dropped.
45
+ */
42
46
  export declare function isEmptyColor(value: wml.Color | undefined): boolean;
47
+ /** The underline's colour attributes count, the theme ones too (docx4j CR-034, 2e381ef26). */
43
48
  export declare function isEmptyU(value: wml.U | undefined): boolean;
44
49
  export declare function isEmptyHighlight(value: wml.Highlight | undefined): boolean;
45
50
  /** @since CR-015 phase 1: a style stating only `w:lang` used to count as empty and was never applied. */
@@ -87,8 +92,17 @@ export declare const mergePStyle: Merge<wml.PPrBase.PStyle>;
87
92
  * formatted).
88
93
  */
89
94
  export declare const mergeRFonts: Merge<wml.RFonts>;
95
+ /**
96
+ * A colour inherits as one (docx4j CR-034, 2e381ef26): where the source states a value or a theme
97
+ * reference, its value, theme colour, tint and shade replace the destination's together. Word
98
+ * draws a theme reference over the value written beside it, so taken one by one, a run's
99
+ * `w:val="FF0000"` under a style's `w:themeColor="accent1"` would keep the style's reference and
100
+ * draw accent1, where Word draws red. A tint or shade stated alone still inherits on its own. The
101
+ * same holds for the colours of `w:u`, `w:shd` (its pattern colour and its fill) and the borders.
102
+ * (Attribute by attribute until then, CR-015 phase 1.)
103
+ */
90
104
  export declare const mergeColor: Merge<wml.Color>;
91
- /** `w:val` and the colour attributes each inherit on their own (CR-015 phase 1). */
105
+ /** `w:val` and the colour inherit on their own (CR-015 phase 1), the colour as one ({@link mergeColor}). */
92
106
  export declare const mergeU: Merge<wml.U>;
93
107
  /** `w:val`, `w:eastAsia` and `w:bidi` each name the language of one script range and each inherits. */
94
108
  export declare const mergeLang: Merge<wml.CTLanguage>;
@@ -29,9 +29,12 @@ function emptyThemeColor(value) {
29
29
  function attr(source, destination) {
30
30
  return source === undefined ? destination : source;
31
31
  }
32
- /** docx4j `StyleUtil.apply(STThemeColor, STThemeColor)`: `none` does not override. */
33
- function themeColor(source, destination) {
34
- return emptyThemeColor(source) ? destination : source;
32
+ /**
33
+ * docx4j `StyleUtil.statesColour`: a colour's value or theme reference is stated, so the colour
34
+ * inherits as one (docx4j CR-034, 2e381ef26; see {@link mergeColor}).
35
+ */
36
+ function statesColour(val, theme) {
37
+ return !emptyString(val) || !emptyThemeColor(theme);
35
38
  }
36
39
  /** A merge with no per-attribute rule: a copy of the source where it has one, else the inherited. */
37
40
  export function mergeReplace(source, destination) {
@@ -71,11 +74,20 @@ export function isEmptyRFonts(value) {
71
74
  && emptyString(value.eastAsia) && value.eastAsiaTheme === undefined
72
75
  && emptyString(value.hAnsi) && value.hAnsiTheme === undefined);
73
76
  }
77
+ /**
78
+ * A `w:color` stating only a theme colour is not empty: it is inherited, where until docx4j CR-034
79
+ * (2e381ef26) it was dropped.
80
+ */
74
81
  export function isEmptyColor(value) {
75
- return value === undefined || emptyString(value.val);
82
+ return value === undefined
83
+ || (emptyString(value.val) && value.themeColor === undefined
84
+ && emptyString(value.themeTint) && emptyString(value.themeShade));
76
85
  }
86
+ /** The underline's colour attributes count, the theme ones too (docx4j CR-034, 2e381ef26). */
77
87
  export function isEmptyU(value) {
78
- return value === undefined || (value.val === undefined && emptyString(value.color));
88
+ return value === undefined
89
+ || (value.val === undefined && emptyString(value.color) && value.themeColor === undefined
90
+ && emptyString(value.themeTint) && emptyString(value.themeShade));
79
91
  }
80
92
  export function isEmptyHighlight(value) {
81
93
  return value === undefined || emptyString(value.val);
@@ -291,28 +303,49 @@ export const mergeRFonts = (source, destination) => {
291
303
  out.hint = source.hint;
292
304
  return out;
293
305
  };
306
+ /**
307
+ * A colour inherits as one (docx4j CR-034, 2e381ef26): where the source states a value or a theme
308
+ * reference, its value, theme colour, tint and shade replace the destination's together. Word
309
+ * draws a theme reference over the value written beside it, so taken one by one, a run's
310
+ * `w:val="FF0000"` under a style's `w:themeColor="accent1"` would keep the style's reference and
311
+ * draw accent1, where Word draws red. A tint or shade stated alone still inherits on its own. The
312
+ * same holds for the colours of `w:u`, `w:shd` (its pattern colour and its fill) and the borders.
313
+ * (Attribute by attribute until then, CR-015 phase 1.)
314
+ */
294
315
  export const mergeColor = (source, destination) => {
295
316
  if (isEmptyColor(source))
296
317
  return destination;
297
318
  const s = source;
298
319
  const out = (destination !== null && destination !== void 0 ? destination : {});
299
- out.themeColor = themeColor(s.themeColor, out.themeColor);
300
- out.themeShade = attr(s.themeShade, out.themeShade);
301
- out.themeTint = attr(s.themeTint, out.themeTint);
302
- out.val = attr(s.val, out.val);
320
+ if (statesColour(s.val, s.themeColor)) {
321
+ out.val = s.val;
322
+ out.themeColor = s.themeColor;
323
+ out.themeTint = s.themeTint;
324
+ out.themeShade = s.themeShade;
325
+ }
326
+ else {
327
+ out.themeShade = attr(s.themeShade, out.themeShade);
328
+ out.themeTint = attr(s.themeTint, out.themeTint);
329
+ }
303
330
  return out;
304
331
  };
305
- /** `w:val` and the colour attributes each inherit on their own (CR-015 phase 1). */
332
+ /** `w:val` and the colour inherit on their own (CR-015 phase 1), the colour as one ({@link mergeColor}). */
306
333
  export const mergeU = (source, destination) => {
307
334
  if (isEmptyU(source))
308
335
  return destination;
309
336
  const s = source;
310
337
  const out = destination !== null && destination !== void 0 ? destination : {};
311
338
  out.val = attr(s.val, out.val);
312
- out.color = attr(s.color, out.color);
313
- out.themeColor = themeColor(s.themeColor, out.themeColor);
314
- out.themeTint = attr(s.themeTint, out.themeTint);
315
- out.themeShade = attr(s.themeShade, out.themeShade);
339
+ if (statesColour(s.color, s.themeColor)) {
340
+ out.color = s.color;
341
+ out.themeColor = s.themeColor;
342
+ out.themeTint = s.themeTint;
343
+ out.themeShade = s.themeShade;
344
+ }
345
+ else {
346
+ out.themeTint = attr(s.themeTint, out.themeTint);
347
+ out.themeShade = attr(s.themeShade, out.themeShade);
348
+ }
316
349
  return out;
317
350
  };
318
351
  /** `w:val`, `w:eastAsia` and `w:bidi` each name the language of one script range and each inherits. */
@@ -331,16 +364,29 @@ export const mergeShd = (source, destination) => {
331
364
  return destination;
332
365
  const s = source;
333
366
  const out = (destination !== null && destination !== void 0 ? destination : {});
334
- out.color = attr(s.color, out.color);
335
- out.fill = attr(s.fill, out.fill);
336
367
  out.val = attr(s.val, out.val);
337
- out.themeTint = attr(s.themeTint, out.themeTint);
338
- out.themeShade = attr(s.themeShade, out.themeShade);
339
- out.themeFillTint = attr(s.themeFillTint, out.themeFillTint);
340
- out.themeFillShade = attr(s.themeFillShade, out.themeFillShade);
341
- // the enums only when the source states them (until CR-015 a null source cleared them)
342
- out.themeColor = themeColor(s.themeColor, out.themeColor);
343
- out.themeFill = themeColor(s.themeFill, out.themeFill);
368
+ // the pattern colour and the fill each inherit as one (see mergeColor), so a direct w:shd
369
+ // stating no fill still leaves an inherited w:themeFill (CR-015)
370
+ if (statesColour(s.color, s.themeColor)) {
371
+ out.color = s.color;
372
+ out.themeColor = s.themeColor;
373
+ out.themeTint = s.themeTint;
374
+ out.themeShade = s.themeShade;
375
+ }
376
+ else {
377
+ out.themeTint = attr(s.themeTint, out.themeTint);
378
+ out.themeShade = attr(s.themeShade, out.themeShade);
379
+ }
380
+ if (statesColour(s.fill, s.themeFill)) {
381
+ out.fill = s.fill;
382
+ out.themeFill = s.themeFill;
383
+ out.themeFillTint = s.themeFillTint;
384
+ out.themeFillShade = s.themeFillShade;
385
+ }
386
+ else {
387
+ out.themeFillTint = attr(s.themeFillTint, out.themeFillTint);
388
+ out.themeFillShade = attr(s.themeFillShade, out.themeFillShade);
389
+ }
344
390
  return out;
345
391
  };
346
392
  export const mergeBorder = (source, destination) => {
@@ -348,13 +394,20 @@ export const mergeBorder = (source, destination) => {
348
394
  return destination;
349
395
  const s = source;
350
396
  const out = (destination !== null && destination !== void 0 ? destination : {});
351
- out.color = attr(s.color, out.color);
352
397
  out.space = attr(s.space, out.space);
353
398
  out.sz = attr(s.sz, out.sz);
354
- out.themeColor = themeColor(s.themeColor, out.themeColor);
355
- out.themeShade = attr(s.themeShade, out.themeShade);
356
- out.themeTint = attr(s.themeTint, out.themeTint);
357
399
  out.val = attr(s.val, out.val);
400
+ // the colour inherits as one (see mergeColor)
401
+ if (statesColour(s.color, s.themeColor)) {
402
+ out.color = s.color;
403
+ out.themeColor = s.themeColor;
404
+ out.themeTint = s.themeTint;
405
+ out.themeShade = s.themeShade;
406
+ }
407
+ else {
408
+ out.themeShade = attr(s.themeShade, out.themeShade);
409
+ out.themeTint = attr(s.themeTint, out.themeTint);
410
+ }
358
411
  return out;
359
412
  };
360
413
  export const mergePBdr = (source, destination) => {
@@ -40,9 +40,21 @@ export declare class CellContext {
40
40
  /**
41
41
  * Whether the table style gives this paragraph anything at all: its own `w:pPr` or `w:rPr`, or
42
42
  * a conditional format with one. Where it does not, resolving with this context gives the same
43
- * answer as resolving without one.
43
+ * answer as resolving without one - except under [MS-DOCX]'s size exception, for which see
44
+ * {@link styleFormatsText}.
44
45
  */
45
46
  readonly formatsText: boolean;
47
+ /**
48
+ * Whether the table style formats text anywhere: its own `w:pPr` or `w:rPr`, or a conditional
49
+ * format with one, whether or not that condition holds for this paragraph. Below compatibility
50
+ * mode 15 this, not what applies here, is what brings [MS-DOCX]'s size exception into play:
51
+ * measured on docx4j's table-style-size-trigger probes (its corpus document 1912), a table
52
+ * style with only a `w:pPr`, only an `w:rPr` colour, or only a `firstRow` format sets every row
53
+ * of its table at the document defaults' size where Normal states 12pt - the `firstRow`
54
+ * format's second row included - while a style with none of these leaves Normal's 12pt.
55
+ * docx4j `styleFormatsText()` (900b6a212, 17.3.2).
56
+ */
57
+ readonly styleFormatsText: boolean;
46
58
  /** What the table level's composition depends on. A value, for keying caches. */
47
59
  readonly key: CellContextKey;
48
60
  constructor(
@@ -138,7 +150,9 @@ export declare class TableContextTracker {
138
150
  /**
139
151
  * Walks everything under `root` (an object, an element pair or an array of them), in document
140
152
  * order, calling `visit` for each paragraph with its context. It descends through every
141
- * member but the `PARENT` pointers, so a text box's paragraphs are reached, in no table.
153
+ * member but the `PARENT` pointers, so a text box's paragraphs are reached, in no table. A kept
154
+ * `mc:AlternateContent` is descended through its selected branch alone (`selectedBranchOf`,
155
+ * docx4j's `McMode.READ`), so a text box held in both branches is visited once (CR-010 phase 3).
142
156
  */
143
157
  walk(root: unknown, visit: (p: wml.P, cellContext: CellContext | undefined) => void): void;
144
158
  }
@@ -1,5 +1,5 @@
1
1
  import { isEmptyPPr, isEmptyRPr } from './styleUtil.mjs';
2
- import { rowsOf, cellsOf } from '../content/tree.mjs';
2
+ import { rowsOf, cellsOf, selectedBranchOf, ALTERNATE_CONTENT } from '../content/tree.mjs';
3
3
  import { PRECEDENCE, DEFAULT_LOOK, lookOf, rowBandSize, colBandSize, rowCnf, resolve, rowConditions, applicable, formatsText, key as conditionsKey, } from './tableStyleConditions.mjs';
4
4
  /** The conditions as bits over {@link PRECEDENCE}: an exact value for keying caches. */
5
5
  function maskOf(conditions) {
@@ -43,6 +43,7 @@ export class CellContext {
43
43
  tableStyleId,
44
44
  /** That style's `w:basedOn` chain merged (`getTableStyleChain`): shared, read it only. */
45
45
  tableStyle, conditions) {
46
+ var _a;
46
47
  this.tableStyleId = tableStyleId;
47
48
  this.tableStyle = tableStyle;
48
49
  const c = new Set(conditions !== null && conditions !== void 0 ? conditions : []);
@@ -56,7 +57,10 @@ export class CellContext {
56
57
  }
57
58
  }
58
59
  this.textConditions = text;
59
- this.formatsText = text.length > 0 || !isEmptyPPr(tableStyle.pPr) || !isEmptyRPr(tableStyle.rPr);
60
+ const own = !isEmptyPPr(tableStyle.pPr) || !isEmptyRPr(tableStyle.rPr);
61
+ this.formatsText = text.length > 0 || own;
62
+ this.styleFormatsText = own
63
+ || ((_a = tableStyle.tblStylePr) !== null && _a !== void 0 ? _a : []).some((pr) => pr !== undefined && pr !== null && formatsText(pr));
60
64
  this.key = new CellContextKey(tableStyleId, named);
61
65
  }
62
66
  toString() {
@@ -248,7 +252,9 @@ export class TableContextTracker {
248
252
  /**
249
253
  * Walks everything under `root` (an object, an element pair or an array of them), in document
250
254
  * order, calling `visit` for each paragraph with its context. It descends through every
251
- * member but the `PARENT` pointers, so a text box's paragraphs are reached, in no table.
255
+ * member but the `PARENT` pointers, so a text box's paragraphs are reached, in no table. A kept
256
+ * `mc:AlternateContent` is descended through its selected branch alone (`selectedBranchOf`,
257
+ * docx4j's `McMode.READ`), so a text box held in both branches is visited once (CR-010 phase 3).
252
258
  */
253
259
  walk(root, visit) {
254
260
  const seen = new Set();
@@ -264,6 +270,10 @@ export class TableContextTracker {
264
270
  return;
265
271
  seen.add(o);
266
272
  const typeName = o.TYPE_NAME;
273
+ if (typeName === ALTERNATE_CONTENT) {
274
+ descend(selectedBranchOf(o), depth + 1);
275
+ return;
276
+ }
267
277
  if (typeName === P)
268
278
  visit(o, this.cellContext(o));
269
279
  const leave = this.enter(o);
@@ -5,9 +5,16 @@ import type { OpcPackage } from '../packages/OpcPackage.mjs';
5
5
  export interface LoadOptions {
6
6
  /** Part classes by content type; defaults to docx4j's table. */
7
7
  registry?: PartRegistry;
8
- /** Resolve `mc:AlternateContent` before unmarshalling (default true, as docx4j and Word). */
8
+ /**
9
+ * Resolve `mc:AlternateContent` before unmarshalling, as Word does on open. Default false since
10
+ * CR-010 phase 4: every element is kept with all its branches, as docx4j keeps it since its CR-021;
11
+ * readers take the branch resolving would (`selectedBranchOf`), mutators every branch, and a save
12
+ * writes them all. Either way a part's `warnings` report where the branches' text differs. True
13
+ * resolves, but keeps the element under a `KEPT_HOSTS` host and one nothing can be selected from
14
+ * (CR-010 phase 1).
15
+ */
9
16
  mcePreprocess?: boolean;
10
- /** Runs over every XML part's DOM before it is unmarshalled; set by the package from `mcePreprocess`. */
17
+ /** Runs over every XML part's DOM before it is unmarshalled; set by the package from `mcePreprocess` (`mceKeep` or `mcePreprocess`). */
11
18
  preprocessor?: DomPreprocessor;
12
19
  }
13
20
  /**
@@ -6,7 +6,7 @@ export { ZipPartStore, ZipPartSink } from './ZipPartStore.mjs';
6
6
  export { FlatOpcPartStore, FlatOpcPartSink, type FlatOpcSinkOptions, PKG_NS } from './FlatOpcPartStore.mjs';
7
7
  export { loadPackage, type LoadOptions } from './Load.mjs';
8
8
  export { savePackage } from './Save.mjs';
9
- export { mcePreprocess, createMcePreprocessor, resolveAlternateContent, MCE_NS, type McePreprocessOptions } from './mce/preprocessor.mjs';
10
- export { UNDERSTOOD_NAMESPACES } from './mce/understood.mjs';
9
+ export { mceKeep, mcePreprocess, createMcePreprocessor, resolveAlternateContent, preprocessAlternateContent, MCE_NS, KEPT_HOSTS, type McePreprocessOptions, type McBranchesDifferWarning, type PartWarning, type MceResult } from './mce/preprocessor.mjs';
10
+ export { UNDERSTOOD_NAMESPACES, UNDERSTOOD_PREFIXES } from './mce/understood.mjs';
11
11
  export { Docx4JException, InvalidFormatException, PartUnrecognisedException, PartUnmarshalException, CyclicStylesException, PropertyResolverNotCreatedException } from './exceptions.mjs';
12
12
  export { parseXml, serializeXml, serializeXmlPart, createDocument, encodeText, decodeXmlText, stripXmlDeclaration, base64Encode, base64Decode, childElements, XML_DECLARATION } from '../xml/dom.mjs';
@@ -7,7 +7,7 @@ export { ZipPartStore, ZipPartSink } from './ZipPartStore.mjs';
7
7
  export { FlatOpcPartStore, FlatOpcPartSink, PKG_NS } from './FlatOpcPartStore.mjs';
8
8
  export { loadPackage } from './Load.mjs';
9
9
  export { savePackage } from './Save.mjs';
10
- export { mcePreprocess, createMcePreprocessor, resolveAlternateContent, MCE_NS } from './mce/preprocessor.mjs';
11
- export { UNDERSTOOD_NAMESPACES } from './mce/understood.mjs';
10
+ export { mceKeep, mcePreprocess, createMcePreprocessor, resolveAlternateContent, preprocessAlternateContent, MCE_NS, KEPT_HOSTS } from './mce/preprocessor.mjs';
11
+ export { UNDERSTOOD_NAMESPACES, UNDERSTOOD_PREFIXES } from './mce/understood.mjs';
12
12
  export { Docx4JException, InvalidFormatException, PartUnrecognisedException, PartUnmarshalException, CyclicStylesException, PropertyResolverNotCreatedException } from './exceptions.mjs';
13
13
  export { parseXml, serializeXml, serializeXmlPart, createDocument, encodeText, decodeXmlText, stripXmlDeclaration, base64Encode, base64Decode, childElements, XML_DECLARATION } from '../xml/dom.mjs';
@@ -1,15 +1,71 @@
1
1
  import type { XmlPart } from '../../parts/XmlPart.mjs';
2
2
  export declare const MCE_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
3
+ /**
4
+ * The hosts whose mc:AlternateContent is kept whole on load, as `{namespace}localName`
5
+ * (CR-010 phase 1): a chart's `c:chartSpace` and SpreadsheetML's `workbook`.
6
+ */
7
+ export declare const KEPT_HOSTS: ReadonlySet<string>;
3
8
  export interface McePreprocessOptions {
4
9
  /** Namespaces counted as understood; defaults to the object model's modules. */
5
10
  understood?: ReadonlySet<string>;
11
+ /** Hosts, as `{namespace}localName`, whose mc:AlternateContent is left whole; defaults to `KEPT_HOSTS`. */
12
+ keptHosts?: ReadonlySet<string>;
13
+ /**
14
+ * False leaves every element whole and reports what resolving would (CR-010 phase 4): the same
15
+ * warnings, from the same elements, and no change to the DOM. Defaults to true.
16
+ */
17
+ resolve?: boolean;
18
+ }
19
+ /**
20
+ * What load reports about a part (CR-010 phase 1; the text extraction paper's warning channel).
21
+ * `mce.branches_differ`: an mc:AlternateContent whose branches' text differs, so a reader that
22
+ * takes another branch than this package's reads something else. `selected` is the branch this
23
+ * package took (`Choice(wps)`, `Fallback`); `branches` gives every branch's text, in document
24
+ * order, as the w:t, a:t and m:t text it holds, with whitespace collapsed.
25
+ */
26
+ export interface McBranchesDifferWarning {
27
+ code: 'mce.branches_differ';
28
+ partName: string | undefined;
29
+ /** Where the element starts in the part, where the DOM records it (xmldom does; a browser's DOMParser does not). */
30
+ line?: number;
31
+ column?: number;
32
+ selected: string;
33
+ branches: {
34
+ branch: string;
35
+ text: string;
36
+ }[];
37
+ message: string;
38
+ }
39
+ /** A warning load records on a part. */
40
+ export type PartWarning = McBranchesDifferWarning;
41
+ /**
42
+ * What one pass over a DOM did. `kept` counts the elements it met and left whole; it doesn't enter a
43
+ * branch the rule doesn't select, so an element nested there is not counted.
44
+ */
45
+ export interface MceResult {
46
+ resolved: number;
47
+ kept: number;
48
+ warnings: PartWarning[];
6
49
  }
7
- /** The default preprocessor, over the object model's namespaces. */
8
- export declare function mcePreprocess(doc: Document, _part?: XmlPart<unknown>): void;
9
- /** A preprocessor over a namespace set of your own. */
10
- export declare function createMcePreprocessor(options: McePreprocessOptions): (doc: Document, part?: XmlPart<unknown>) => void;
11
50
  /**
12
- * Resolves every mc:AlternateContent under a node, innermost first (a chosen branch may contain
13
- * another). Returns the number resolved.
51
+ * Load's preprocessor since CR-010 phase 4: every mc:AlternateContent is kept, and the part's
52
+ * warnings are what resolving would report. Returns them.
53
+ */
54
+ export declare function mceKeep(doc: Document, part?: XmlPart<unknown>): PartWarning[];
55
+ /** The resolving preprocessor (`mcePreprocess: true`), over the object model's namespaces. Returns the part's warnings. */
56
+ export declare function mcePreprocess(doc: Document, part?: XmlPart<unknown>): PartWarning[];
57
+ /** A preprocessor over a namespace set (or kept hosts) of your own. */
58
+ export declare function createMcePreprocessor(options: McePreprocessOptions): (doc: Document, part?: XmlPart<unknown>) => PartWarning[];
59
+ /**
60
+ * Resolves every mc:AlternateContent under a node, outermost first, but those `KEPT_HOSTS` and the
61
+ * nothing-selectable rule keep (see the file comment). Returns the number resolved.
14
62
  */
15
63
  export declare function resolveAlternateContent(root: Node, understood?: ReadonlySet<string>): number;
64
+ /**
65
+ * The pass itself: what it resolved, kept and reported. With `resolve: false` it visits the elements
66
+ * resolving would, outermost first and into the branch the rule selects, reports the same warnings,
67
+ * and leaves the DOM as it was.
68
+ */
69
+ export declare function preprocessAlternateContent(root: Node, options?: McePreprocessOptions & {
70
+ partName?: string;
71
+ }): MceResult;