@docx4j/core-ts 0.3.1 → 0.4.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/README.md +3 -1
- package/dist/html/convert.d.mts +116 -0
- package/dist/html/convert.mjs +541 -0
- package/dist/html/elements.d.mts +19 -0
- package/dist/html/elements.mjs +182 -0
- package/dist/html/index.d.mts +2 -0
- package/dist/html/index.mjs +4 -0
- package/dist/model/content/Body.mjs +9 -1
- package/dist/model/content/ContentControl.d.mts +5 -1
- package/dist/model/content/ContentControl.mjs +18 -5
- package/dist/model/content/InlinePicture.d.mts +18 -3
- package/dist/model/content/InlinePicture.mjs +44 -7
- package/dist/model/content/Paragraph.d.mts +11 -11
- package/dist/model/content/Paragraph.mjs +30 -43
- package/dist/model/content/Range.mjs +3 -2
- package/dist/model/content/Table.d.mts +38 -0
- package/dist/model/content/Table.mjs +80 -2
- package/dist/model/content/index.d.mts +2 -1
- package/dist/model/content/index.mjs +2 -1
- package/dist/model/content/stylesFacade.d.mts +4 -2
- package/dist/model/content/stylesFacade.mjs +23 -6
- package/dist/model/content/tableColumns.d.mts +48 -0
- package/dist/model/content/tableColumns.mjs +484 -0
- package/dist/model/content/tracking.d.mts +14 -0
- package/dist/model/content/tracking.mjs +19 -10
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
- package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
- package/dist/model/customxml/XmlMapping.d.mts +20 -4
- package/dist/model/customxml/XmlMapping.mjs +44 -6
- package/dist/model/customxml/bindings.d.mts +13 -3
- package/dist/model/customxml/bindings.mjs +38 -7
- package/dist/model/customxml/index.d.mts +4 -1
- package/dist/model/customxml/index.mjs +4 -1
- package/dist/model/customxml/insert.d.mts +10 -0
- package/dist/model/customxml/insert.mjs +13 -0
- package/dist/model/customxml/opendope.d.mts +32 -0
- package/dist/model/customxml/opendope.mjs +56 -0
- package/dist/model/customxml/pictures.d.mts +54 -0
- package/dist/model/customxml/pictures.mjs +186 -0
- package/dist/model/customxml/xhtml.d.mts +42 -0
- package/dist/model/customxml/xhtml.mjs +162 -0
- package/dist/model/fonts/RunFontSelector.d.mts +8 -1
- package/dist/model/fonts/RunFontSelector.mjs +6 -5
- package/dist/model/properties/PropertyResolver.d.mts +110 -10
- package/dist/model/properties/PropertyResolver.mjs +300 -31
- package/dist/model/properties/index.d.mts +3 -0
- package/dist/model/properties/index.mjs +2 -0
- package/dist/model/properties/table.d.mts +155 -0
- package/dist/model/properties/table.mjs +315 -0
- package/dist/model/properties/tableStyleConditions.d.mts +101 -0
- package/dist/model/properties/tableStyleConditions.mjs +296 -0
- package/dist/packages/WordprocessingMLPackage.mjs +1 -0
- package/dist/parts/wml/knownStyles.d.mts +1 -0
- package/dist/parts/wml/knownStyles.mjs +2233 -0
- package/dist/parts/wml/placeholderStyle.d.mts +3 -0
- package/dist/parts/wml/placeholderStyle.mjs +13 -0
- package/package.json +7 -2
|
@@ -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
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
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
|
|
403
|
-
const styleId = (
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
(
|
|
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
|
|
422
|
-
* {@link getEffectiveTableStyle} decides
|
|
423
|
-
* style
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
2
|
+
import { type Condition, type Look } from './tableStyleConditions.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* The table level of a paragraph's formatting, as a value: two paragraphs whose keys are equal
|
|
5
|
+
* get the same contribution from their table. A structured key, never a joined string (docx4j
|
|
6
|
+
* CR-030 D8: ids may contain the separator); `mask` is the conditions as a number, so a cache
|
|
7
|
+
* keys on the table style id and then on it.
|
|
8
|
+
*/
|
|
9
|
+
export declare class CellContextKey {
|
|
10
|
+
readonly tableStyleId: string | undefined;
|
|
11
|
+
/** The conditional formats which give the paragraph text formatting, `wholeTable` included where the style has one. */
|
|
12
|
+
readonly conditions: ReadonlySet<Condition>;
|
|
13
|
+
/** The conditions as bits over {@link PRECEDENCE}. */
|
|
14
|
+
readonly mask: number;
|
|
15
|
+
constructor(tableStyleId: string | undefined,
|
|
16
|
+
/** The conditional formats which give the paragraph text formatting, `wholeTable` included where the style has one. */
|
|
17
|
+
conditions: ReadonlySet<Condition>);
|
|
18
|
+
equals(other: CellContextKey | undefined): boolean;
|
|
19
|
+
toString(): string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Where a paragraph sits in a table, as far as its formatting is concerned: the table style
|
|
23
|
+
* which applies to the table, and the conditional formats the paragraph is under. Handed to the
|
|
24
|
+
* `PropertyResolver` overloads which take one. Obtained from a {@link TableContext}
|
|
25
|
+
* (`forParagraph`), from a {@link TableContextTracker} during a walk, or from
|
|
26
|
+
* `PropertyResolver.cellContextOf(p)`. Undefined stands for "not in a table". Immutable.
|
|
27
|
+
*/
|
|
28
|
+
export declare class CellContext {
|
|
29
|
+
/** The id of the table style the table resolves to, or undefined where none applies. */
|
|
30
|
+
readonly tableStyleId: string | undefined;
|
|
31
|
+
/** That style's `w:basedOn` chain merged (`getTableStyleChain`): shared, read it only. */
|
|
32
|
+
readonly tableStyle: wml.Style;
|
|
33
|
+
/** The conditions the paragraph is under, gated by the table's `w:tblLook`. */
|
|
34
|
+
readonly conditions: ReadonlySet<Condition>;
|
|
35
|
+
/**
|
|
36
|
+
* The table style's conditional formats which apply here and give text a `w:pPr` or `w:rPr`,
|
|
37
|
+
* in the order they are applied (ECMA-376-1 17.7.6).
|
|
38
|
+
*/
|
|
39
|
+
readonly textConditions: readonly wml.CTTblStylePr[];
|
|
40
|
+
/**
|
|
41
|
+
* Whether the table style gives this paragraph anything at all: its own `w:pPr` or `w:rPr`, or
|
|
42
|
+
* a conditional format with one. Where it does not, resolving with this context gives the same
|
|
43
|
+
* answer as resolving without one.
|
|
44
|
+
*/
|
|
45
|
+
readonly formatsText: boolean;
|
|
46
|
+
/** What the table level's composition depends on. A value, for keying caches. */
|
|
47
|
+
readonly key: CellContextKey;
|
|
48
|
+
constructor(
|
|
49
|
+
/** The id of the table style the table resolves to, or undefined where none applies. */
|
|
50
|
+
tableStyleId: string | undefined,
|
|
51
|
+
/** That style's `w:basedOn` chain merged (`getTableStyleChain`): shared, read it only. */
|
|
52
|
+
tableStyle: wml.Style, conditions: ReadonlySet<Condition> | undefined);
|
|
53
|
+
toString(): string;
|
|
54
|
+
}
|
|
55
|
+
/** What a {@link TableContext} needs of the resolver; `PropertyResolver` satisfies it. */
|
|
56
|
+
export interface TableStyleSource {
|
|
57
|
+
getTableStyleIdOf(tblPr: wml.CTTblPrBase | undefined): string | undefined;
|
|
58
|
+
getTableStyleChain(styleId: string | undefined): wml.Style;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What a table's paragraphs need to know about it to be formatted: the table style it resolves
|
|
62
|
+
* to (its `w:basedOn` chain merged), the conditional formats its `w:tblLook` asks for, its band
|
|
63
|
+
* sizes, and where each row and cell sits. {@link forParagraph} then gives the
|
|
64
|
+
* {@link CellContext} of a paragraph in one of its cells.
|
|
65
|
+
*
|
|
66
|
+
* A reading of the table's *content* (rows, spans, look), so it is built fresh for each table by
|
|
67
|
+
* `PropertyResolver.tableContext(tbl)` and held by the caller while it is in the table; the
|
|
68
|
+
* resolver keeps none (a cached one would go stale when a row is added). The rows and cells are
|
|
69
|
+
* counted with nested tables excluded, a cell's column its row's `w:gridBefore` plus the spans
|
|
70
|
+
* before it.
|
|
71
|
+
*/
|
|
72
|
+
export declare class TableContext {
|
|
73
|
+
/** The id of the table style the table resolves to, or undefined. */
|
|
74
|
+
readonly tableStyleId: string | undefined;
|
|
75
|
+
/** Whether the table states its own `w:tblStyle` (rather than taking the default table style). */
|
|
76
|
+
readonly namesStyle: boolean;
|
|
77
|
+
/** The table style's chain merged; shared, read it only. */
|
|
78
|
+
readonly tableStyle: wml.Style;
|
|
79
|
+
readonly look: Look;
|
|
80
|
+
readonly rowBandSize: number;
|
|
81
|
+
readonly colBandSize: number;
|
|
82
|
+
readonly rowCount: number;
|
|
83
|
+
readonly colCount: number;
|
|
84
|
+
private readonly rowIndex;
|
|
85
|
+
/** [first grid column, span] per cell */
|
|
86
|
+
private readonly cellColumns;
|
|
87
|
+
/** Use `PropertyResolver.tableContext(tbl)`. */
|
|
88
|
+
constructor(tbl: wml.Tbl, resolver: TableStyleSource);
|
|
89
|
+
/**
|
|
90
|
+
* The context of a paragraph in this table: the conditions its row, its cell and its own
|
|
91
|
+
* `w:cnfStyle` caches say it is under, or its position where they say nothing. A row or cell
|
|
92
|
+
* this table does not hold (a nested table's, or one added since this context was built) is
|
|
93
|
+
* under no condition.
|
|
94
|
+
*/
|
|
95
|
+
forParagraph(tr: wml.Tr | undefined, tc: wml.Tc | undefined, pPr?: wml.PPr | undefined): CellContext;
|
|
96
|
+
/** The context of a cell, for its row and cell properties: {@link forParagraph} without a paragraph's own `w:cnfStyle`. */
|
|
97
|
+
forCell(tr: wml.Tr | undefined, tc: wml.Tc | undefined): CellContext;
|
|
98
|
+
/** The row-axis conditions a row is under, for its own properties; none for a row this table does not hold. */
|
|
99
|
+
rowConditions(tr: wml.Tr | undefined): Set<Condition>;
|
|
100
|
+
/** The table style's `w:tblStylePr` entries which apply under these conditions, in order: all of them. */
|
|
101
|
+
applicable(conditions: ReadonlySet<Condition>): wml.CTTblStylePr[];
|
|
102
|
+
/** The row's index in the table, nested tables excluded, or -1 for a row it does not hold. */
|
|
103
|
+
rowIndexOf(tr: wml.Tr | undefined): number;
|
|
104
|
+
private conditions;
|
|
105
|
+
}
|
|
106
|
+
/** What a tracker needs of the resolver. */
|
|
107
|
+
export interface TableContextSource {
|
|
108
|
+
tableContext(tbl: wml.Tbl): TableContext;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Knows, for each paragraph a walk reaches, the table context the paragraph is in: the way to
|
|
112
|
+
* resolve every paragraph of a document in context (`PropertyResolver.cellContextOf(p)` builds a
|
|
113
|
+
* {@link TableContext} per call and is for one paragraph at a time).
|
|
114
|
+
*
|
|
115
|
+
* docx4j's is a `TraversalUtil` callback to extend; here it is the stack on its own, driven by
|
|
116
|
+
* any walker: call {@link enter} with each object before descending into it and the function it
|
|
117
|
+
* returns afterwards, and {@link cellContext} for each paragraph. {@link walk} is a walker that
|
|
118
|
+
* does so over a whole tree.
|
|
119
|
+
*
|
|
120
|
+
* The table on top of the stack is the paragraph's own (a nested table pushes its own context).
|
|
121
|
+
* A text box is a story of its own, so the walk starts an empty stack inside every text box and
|
|
122
|
+
* restores the cell's on the way out.
|
|
123
|
+
*/
|
|
124
|
+
export declare class TableContextTracker {
|
|
125
|
+
private readonly resolver;
|
|
126
|
+
/** One entry per table being walked, innermost last; a barrier (no table) inside a text box. */
|
|
127
|
+
private readonly stack;
|
|
128
|
+
constructor(resolver: TableContextSource | undefined);
|
|
129
|
+
/**
|
|
130
|
+
* Call before descending into an object (a `w:tbl`, `w:tr`, `w:tc`, or anything else: other
|
|
131
|
+
* types change nothing unless they begin a story); call what it returns after its children.
|
|
132
|
+
*/
|
|
133
|
+
enter(o: unknown): () => void;
|
|
134
|
+
/** The context of a paragraph reached by this walk, or undefined where it is in no table (or in a text box). */
|
|
135
|
+
cellContext(p: wml.P): CellContext | undefined;
|
|
136
|
+
/** The table the walk is in, or undefined (outside any table, or in a text box). */
|
|
137
|
+
tableContext(): TableContext | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Walks everything under `root` (an object, an element pair or an array of them), in document
|
|
140
|
+
* 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.
|
|
142
|
+
*/
|
|
143
|
+
walk(root: unknown, visit: (p: wml.P, cellContext: CellContext | undefined) => void): void;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The nearest enclosing cell, row and table of a paragraph, through its `PARENT` pointers and
|
|
147
|
+
* whatever lies between (`w:sdt`, `w:customXml`, `w:smartTag`); undefined where the paragraph is
|
|
148
|
+
* in no table, where a story begins before a cell is reached, or where the pointers are not
|
|
149
|
+
* there (content built by hand and never linked: `linkParents`).
|
|
150
|
+
*/
|
|
151
|
+
export declare function enclosingCellOf(p: wml.P): {
|
|
152
|
+
tbl: wml.Tbl;
|
|
153
|
+
tr: wml.Tr;
|
|
154
|
+
tc: wml.Tc;
|
|
155
|
+
} | undefined;
|