@shbernal/ts-xlsx 1.0.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/LICENSE +23 -0
- package/README.md +230 -0
- package/dist/core/address.d.ts +49 -0
- package/dist/core/address.js +125 -0
- package/dist/core/autofilter.d.ts +55 -0
- package/dist/core/autofilter.js +48 -0
- package/dist/core/cell.d.ts +132 -0
- package/dist/core/cell.js +249 -0
- package/dist/core/color-resolution.d.ts +58 -0
- package/dist/core/color-resolution.js +170 -0
- package/dist/core/column.d.ts +87 -0
- package/dist/core/column.js +167 -0
- package/dist/core/comment-thread.d.ts +116 -0
- package/dist/core/comment-thread.js +62 -0
- package/dist/core/conditional-formatting-overlay.d.ts +15 -0
- package/dist/core/conditional-formatting-overlay.js +24 -0
- package/dist/core/conditional-formatting.d.ts +70 -0
- package/dist/core/conditional-formatting.js +35 -0
- package/dist/core/containers.d.ts +2 -0
- package/dist/core/containers.js +25 -0
- package/dist/core/data-validation-overlay.d.ts +28 -0
- package/dist/core/data-validation-overlay.js +56 -0
- package/dist/core/data-validation.d.ts +38 -0
- package/dist/core/data-validation.js +13 -0
- package/dist/core/date.d.ts +25 -0
- package/dist/core/date.js +58 -0
- package/dist/core/formula.d.ts +49 -0
- package/dist/core/formula.js +319 -0
- package/dist/core/grid-edits.d.ts +22 -0
- package/dist/core/grid-edits.js +206 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +65 -0
- package/dist/core/internal.d.ts +11 -0
- package/dist/core/internal.js +29 -0
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +26 -0
- package/dist/core/modern-functions.d.ts +1 -0
- package/dist/core/modern-functions.js +165 -0
- package/dist/core/page-setup.d.ts +94 -0
- package/dist/core/page-setup.js +6 -0
- package/dist/core/pivot-table.d.ts +113 -0
- package/dist/core/pivot-table.js +249 -0
- package/dist/core/preserved.d.ts +58 -0
- package/dist/core/preserved.js +5 -0
- package/dist/core/protection.d.ts +67 -0
- package/dist/core/protection.js +68 -0
- package/dist/core/range.d.ts +112 -0
- package/dist/core/range.js +283 -0
- package/dist/core/row.d.ts +73 -0
- package/dist/core/row.js +135 -0
- package/dist/core/style.d.ts +246 -0
- package/dist/core/style.js +125 -0
- package/dist/core/table-style.d.ts +67 -0
- package/dist/core/table-style.js +90 -0
- package/dist/core/table.d.ts +194 -0
- package/dist/core/table.js +291 -0
- package/dist/core/theme.d.ts +90 -0
- package/dist/core/theme.js +272 -0
- package/dist/core/value.d.ts +115 -0
- package/dist/core/value.js +144 -0
- package/dist/core/workbook-protection.d.ts +27 -0
- package/dist/core/workbook-protection.js +25 -0
- package/dist/core/workbook.d.ts +528 -0
- package/dist/core/workbook.js +732 -0
- package/dist/core/worksheet-model.d.ts +34 -0
- package/dist/core/worksheet-model.js +100 -0
- package/dist/core/worksheet.d.ts +647 -0
- package/dist/core/worksheet.js +1025 -0
- package/dist/customui/errors.d.ts +15 -0
- package/dist/customui/errors.js +15 -0
- package/dist/customui/index.d.ts +2 -0
- package/dist/customui/index.js +7 -0
- package/dist/customui/ribbon.d.ts +86 -0
- package/dist/customui/ribbon.js +180 -0
- package/dist/entries/core.d.ts +23 -0
- package/dist/entries/core.js +20 -0
- package/dist/entries/csv.d.ts +2 -0
- package/dist/entries/csv.js +6 -0
- package/dist/entries/customui.d.ts +1 -0
- package/dist/entries/customui.js +3 -0
- package/dist/entries/errors.d.ts +7 -0
- package/dist/entries/errors.js +20 -0
- package/dist/entries/vba.d.ts +2 -0
- package/dist/entries/vba.js +7 -0
- package/dist/entries/xlsb.d.ts +1 -0
- package/dist/entries/xlsb.js +7 -0
- package/dist/entries/xlsx.d.ts +5 -0
- package/dist/entries/xlsx.js +18 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -0
- package/dist/io/csv/read.d.ts +15 -0
- package/dist/io/csv/read.js +113 -0
- package/dist/io/csv/write.d.ts +28 -0
- package/dist/io/csv/write.js +124 -0
- package/dist/io/opc/errors.d.ts +46 -0
- package/dist/io/opc/errors.js +49 -0
- package/dist/io/opc/inflate.d.ts +15 -0
- package/dist/io/opc/inflate.js +91 -0
- package/dist/io/opc/namespaces.d.ts +18 -0
- package/dist/io/opc/namespaces.js +18 -0
- package/dist/io/opc/part-paths.d.ts +4 -0
- package/dist/io/opc/part-paths.js +35 -0
- package/dist/io/opc/read-opc.d.ts +24 -0
- package/dist/io/opc/read-opc.js +153 -0
- package/dist/io/opc/read-options.d.ts +10 -0
- package/dist/io/opc/read-options.js +7 -0
- package/dist/io/opc/rels.d.ts +15 -0
- package/dist/io/opc/rels.js +28 -0
- package/dist/io/opc/sniff-format.d.ts +27 -0
- package/dist/io/opc/sniff-format.js +92 -0
- package/dist/io/style/xf-style.d.ts +47 -0
- package/dist/io/style/xf-style.js +95 -0
- package/dist/io/xlsb/errors.d.ts +15 -0
- package/dist/io/xlsb/errors.js +15 -0
- package/dist/io/xlsb/formula.d.ts +48 -0
- package/dist/io/xlsb/formula.js +398 -0
- package/dist/io/xlsb/primitives.d.ts +86 -0
- package/dist/io/xlsb/primitives.js +230 -0
- package/dist/io/xlsb/ptg-functions.d.ts +16 -0
- package/dist/io/xlsb/ptg-functions.js +659 -0
- package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
- package/dist/io/xlsb/read-shared-strings.js +22 -0
- package/dist/io/xlsb/read-styles.d.ts +4 -0
- package/dist/io/xlsb/read-styles.js +386 -0
- package/dist/io/xlsb/read-worksheet.d.ts +9 -0
- package/dist/io/xlsb/read-worksheet.js +301 -0
- package/dist/io/xlsb/read.d.ts +20 -0
- package/dist/io/xlsb/read.js +199 -0
- package/dist/io/xlsb/record-stream.d.ts +14 -0
- package/dist/io/xlsb/record-stream.js +56 -0
- package/dist/io/xlsb/record-types.d.ts +55 -0
- package/dist/io/xlsb/record-types.js +73 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
- package/dist/io/xlsx/cell-accumulator.js +172 -0
- package/dist/io/xlsx/cell-value.d.ts +32 -0
- package/dist/io/xlsx/cell-value.js +85 -0
- package/dist/io/xlsx/comments.d.ts +74 -0
- package/dist/io/xlsx/comments.js +243 -0
- package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
- package/dist/io/xlsx/conditional-formatting.js +464 -0
- package/dist/io/xlsx/data-validation.d.ts +20 -0
- package/dist/io/xlsx/data-validation.js +258 -0
- package/dist/io/xlsx/edit-vba.d.ts +23 -0
- package/dist/io/xlsx/edit-vba.js +139 -0
- package/dist/io/xlsx/errors.d.ts +17 -0
- package/dist/io/xlsx/errors.js +17 -0
- package/dist/io/xlsx/hyperlinks.d.ts +42 -0
- package/dist/io/xlsx/hyperlinks.js +139 -0
- package/dist/io/xlsx/images.d.ts +34 -0
- package/dist/io/xlsx/images.js +218 -0
- package/dist/io/xlsx/namespaces.d.ts +55 -0
- package/dist/io/xlsx/namespaces.js +55 -0
- package/dist/io/xlsx/package-plan.d.ts +103 -0
- package/dist/io/xlsx/package-plan.js +168 -0
- package/dist/io/xlsx/pivot-read.d.ts +5 -0
- package/dist/io/xlsx/pivot-read.js +134 -0
- package/dist/io/xlsx/pivot.d.ts +12 -0
- package/dist/io/xlsx/pivot.js +141 -0
- package/dist/io/xlsx/read-rows.d.ts +85 -0
- package/dist/io/xlsx/read-rows.js +286 -0
- package/dist/io/xlsx/read-styles.d.ts +8 -0
- package/dist/io/xlsx/read-styles.js +551 -0
- package/dist/io/xlsx/read-worksheet.d.ts +4 -0
- package/dist/io/xlsx/read-worksheet.js +528 -0
- package/dist/io/xlsx/read.d.ts +32 -0
- package/dist/io/xlsx/read.js +680 -0
- package/dist/io/xlsx/relationships.d.ts +33 -0
- package/dist/io/xlsx/relationships.js +47 -0
- package/dist/io/xlsx/rich-runs.d.ts +13 -0
- package/dist/io/xlsx/rich-runs.js +59 -0
- package/dist/io/xlsx/rich-text.d.ts +7 -0
- package/dist/io/xlsx/rich-text.js +23 -0
- package/dist/io/xlsx/shared-formulas.d.ts +6 -0
- package/dist/io/xlsx/shared-formulas.js +52 -0
- package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
- package/dist/io/xlsx/shared-strings-read.js +70 -0
- package/dist/io/xlsx/shared-strings.d.ts +17 -0
- package/dist/io/xlsx/shared-strings.js +50 -0
- package/dist/io/xlsx/sheet-properties.d.ts +13 -0
- package/dist/io/xlsx/sheet-properties.js +211 -0
- package/dist/io/xlsx/styles.d.ts +138 -0
- package/dist/io/xlsx/styles.js +821 -0
- package/dist/io/xlsx/tables.d.ts +9 -0
- package/dist/io/xlsx/tables.js +208 -0
- package/dist/io/xlsx/threaded-comments.d.ts +102 -0
- package/dist/io/xlsx/threaded-comments.js +308 -0
- package/dist/io/xlsx/workbook-xml.d.ts +12 -0
- package/dist/io/xlsx/workbook-xml.js +353 -0
- package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
- package/dist/io/xlsx/worksheet-xml.js +643 -0
- package/dist/io/xlsx/write-stream.d.ts +171 -0
- package/dist/io/xlsx/write-stream.js +399 -0
- package/dist/io/xlsx/write.d.ts +81 -0
- package/dist/io/xlsx/write.js +396 -0
- package/dist/io/xlsx/x14-ext.d.ts +9 -0
- package/dist/io/xlsx/x14-ext.js +12 -0
- package/dist/vba/cfb-writer.d.ts +19 -0
- package/dist/vba/cfb-writer.js +277 -0
- package/dist/vba/cfb.d.ts +16 -0
- package/dist/vba/cfb.js +324 -0
- package/dist/vba/codepage.d.ts +18 -0
- package/dist/vba/codepage.js +87 -0
- package/dist/vba/errors.d.ts +24 -0
- package/dist/vba/errors.js +24 -0
- package/dist/vba/index.d.ts +5 -0
- package/dist/vba/index.js +15 -0
- package/dist/vba/ms-ovba.d.ts +12 -0
- package/dist/vba/ms-ovba.js +176 -0
- package/dist/vba/project-editor.d.ts +62 -0
- package/dist/vba/project-editor.js +377 -0
- package/dist/vba/project.d.ts +45 -0
- package/dist/vba/project.js +160 -0
- package/dist/vba/vba-encoding.d.ts +14 -0
- package/dist/vba/vba-encoding.js +39 -0
- package/dist/xml/errors.d.ts +15 -0
- package/dist/xml/errors.js +15 -0
- package/dist/xml/xml-read.d.ts +107 -0
- package/dist/xml/xml-read.js +283 -0
- package/dist/xml/xml.d.ts +41 -0
- package/dist/xml/xml.js +86 -0
- package/package.json +117 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { CellStyle } from './style.ts';
|
|
2
|
+
import type { CellValue } from './value.ts';
|
|
3
|
+
/** A per-column cell format applied to a table's body cells — the facets Excel's table-column style
|
|
4
|
+
* bakes into the cells rather than storing as table metadata. Every facet ({@link CellStyle}) is
|
|
5
|
+
* optional; only the ones set are applied, leaving the rest of each cell's style untouched. */
|
|
6
|
+
export type TableColumnStyle = Readonly<CellStyle>;
|
|
7
|
+
/** Writes a value into the owning worksheet's grid at a 1-based row/column, applying the column's
|
|
8
|
+
* style (if any) to the cell — the hook a {@link Table} uses to materialise the cells of a row
|
|
9
|
+
* appended through {@link Table.addRow}. A worksheet supplies it when it registers the table; a table
|
|
10
|
+
* built standalone has none and cannot write cell values. */
|
|
11
|
+
export type TableCellWriter = (row: number, col: number, value: CellValue, style?: TableColumnStyle) => void;
|
|
12
|
+
/** Inserts one empty row into the owning worksheet's grid at a 1-based `row`, shifting that row and
|
|
13
|
+
* everything below it down by one — the hook a {@link Table} with a totals row uses to open a slot
|
|
14
|
+
* for an appended data row above the totals. Relocating the totals row lives in the grid, so a
|
|
15
|
+
* standalone table has no inserter and cannot append past a totals row. */
|
|
16
|
+
export type TableRowInserter = (row: number) => void;
|
|
17
|
+
/**
|
|
18
|
+
* A table's visual style (`<tableStyleInfo>`): the named style to apply plus the banding/highlight
|
|
19
|
+
* toggles. Every field is a tri-state so a round-trip stays faithful — a value present in the source
|
|
20
|
+
* re-emits, one the source omitted stays omitted rather than being defaulted to `"0"`. A workbook
|
|
21
|
+
* whose part carries no `<tableStyleInfo>` at all leaves {@link TableOptions.style} undefined.
|
|
22
|
+
*/
|
|
23
|
+
export interface TableStyleInfo {
|
|
24
|
+
/**
|
|
25
|
+
* Named table style to apply — one of the built-in gallery (`"TableStyleMedium2"`, …) or a custom
|
|
26
|
+
* one the workbook defines with {@link Workbook.addTableStyle}.
|
|
27
|
+
*
|
|
28
|
+
* **Not validated.** A name that matches nothing renders the table unstyled, silently — but this
|
|
29
|
+
* library must not be the thing that rejects it. A reader has to accept a name from a newer Excel
|
|
30
|
+
* than the gallery list it was built with, and a writer that threw would make round-tripping such a
|
|
31
|
+
* file impossible; there is also no diagnostics channel to warn through, so the only options were
|
|
32
|
+
* "throw" and "accept". Accepting is the one that never makes a readable file unreadable. If a
|
|
33
|
+
* warning channel is ever added, this is the first thing that should use it.
|
|
34
|
+
*/
|
|
35
|
+
readonly name?: string;
|
|
36
|
+
/** Emphasise the first column. */
|
|
37
|
+
readonly showFirstColumn?: boolean;
|
|
38
|
+
/** Emphasise the last column. */
|
|
39
|
+
readonly showLastColumn?: boolean;
|
|
40
|
+
/** Band the rows (alternating fill). */
|
|
41
|
+
readonly showRowStripes?: boolean;
|
|
42
|
+
/** Band the columns (alternating fill). */
|
|
43
|
+
readonly showColumnStripes?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* OOXML's totals-row function names (`ST_TotalsRowFunction`) to the `SUBTOTAL` first-argument code
|
|
47
|
+
* Excel writes into a materialised totals cell. The `10x` band ignores manually hidden rows — the
|
|
48
|
+
* behaviour Excel's totals row uses. The one inversion trap: `count` is COUNTA (103, non-empty) while
|
|
49
|
+
* `countNums` is COUNT (102, numbers only). `none` (no aggregate) has no built-in code, so a column
|
|
50
|
+
* carrying it is absent here and its totals cell is left unmaterialised — Excel accepts the blank
|
|
51
|
+
* cell. `custom` is likewise absent: its aggregate is not a `SUBTOTAL` but the arbitrary formula
|
|
52
|
+
* stored in {@link TableColumn.totalsRowFormula}, which the reader/writer round-trip and the
|
|
53
|
+
* materialiser writes into the cell verbatim.
|
|
54
|
+
*/
|
|
55
|
+
export declare const TOTALS_ROW_SUBTOTAL_CODE: Readonly<Partial<Record<TotalsRowFunction, number>>>;
|
|
56
|
+
/**
|
|
57
|
+
* The values `ST_TotalsRowFunction` (ECMA-376 §18.18.86) can take — a closed OOXML enumeration Excel
|
|
58
|
+
* does not extend over time (unlike, say, a conditional-formatting rule type), so an author-side typo
|
|
59
|
+
* such as `"avg"` is a compile error here rather than a silently no-op attribute at write time.
|
|
60
|
+
*/
|
|
61
|
+
export type TotalsRowFunction = 'average' | 'countNums' | 'count' | 'max' | 'min' | 'stdDev' | 'sum' | 'var' | 'custom' | 'none';
|
|
62
|
+
/** Narrow a raw `totalsRowFunction` attribute to a known {@link TotalsRowFunction}. */
|
|
63
|
+
export declare function isTotalsRowFunction(value: string): value is TotalsRowFunction;
|
|
64
|
+
/** One column of a table: a header name and its optional totals-row behaviour. */
|
|
65
|
+
export interface TableColumn {
|
|
66
|
+
/** The column's header/display name. Must be unique within the table (case-insensitively) —
|
|
67
|
+
* Excel writes a table with colliding column names as corrupt. A collision supplied at construction
|
|
68
|
+
* is disambiguated deterministically (the first keeps its name, later clashes gain a numeric
|
|
69
|
+
* suffix), the same repair the reader applies to a loaded file, rather than being rejected. */
|
|
70
|
+
readonly name: string;
|
|
71
|
+
/** Literal label shown in the totals row (e.g. `"Total"`), mutually exclusive with a function. */
|
|
72
|
+
readonly totalsRowLabel?: string;
|
|
73
|
+
/** Built-in totals-row aggregate (`"sum"`, `"average"`, `"count"`, …), or `"custom"` when the
|
|
74
|
+
* column's total is the arbitrary formula in {@link totalsRowFormula} rather than a `SUBTOTAL`. */
|
|
75
|
+
readonly totalsRowFunction?: TotalsRowFunction;
|
|
76
|
+
/** The formula (no leading `=`) backing a `totalsRowFunction: "custom"` column — OOXML's
|
|
77
|
+
* `<totalsRowFormula>` child. Round-tripped verbatim and written into the totals cell as the
|
|
78
|
+
* cell's formula. Meaningful only alongside `totalsRowFunction: "custom"`; ignored otherwise. */
|
|
79
|
+
readonly totalsRowFormula?: string;
|
|
80
|
+
/** A format applied to this column's body cells as they are written (see {@link TableColumnStyle}).
|
|
81
|
+
* Excel bakes a table-column style into the cells rather than storing it as table metadata, so this
|
|
82
|
+
* is an authoring convenience: it round-trips as the affected cells' own styles, not as the table. */
|
|
83
|
+
readonly style?: TableColumnStyle;
|
|
84
|
+
}
|
|
85
|
+
export interface TableOptions {
|
|
86
|
+
/** Table name — a valid Excel identifier, unique across the workbook. This is the name used in
|
|
87
|
+
* structured formula references (`Table1[Column]`). */
|
|
88
|
+
name: string;
|
|
89
|
+
/** Human-facing display name shown in the UI. A free-form label (spaces allowed) that need not
|
|
90
|
+
* be a valid identifier. Defaults to {@link name} when omitted. */
|
|
91
|
+
displayName?: string;
|
|
92
|
+
/** A1 reference of the table's top-left cell (an anchor, e.g. `"A1"` — not the full range). */
|
|
93
|
+
ref: string;
|
|
94
|
+
/** The table's columns, left to right. At least one is required. */
|
|
95
|
+
columns: readonly TableColumn[];
|
|
96
|
+
/** Number of data rows (excludes the header and totals rows). May be zero. */
|
|
97
|
+
rowCount: number;
|
|
98
|
+
/** Whether the table has a header row. Defaults to `true`. */
|
|
99
|
+
headerRow?: boolean;
|
|
100
|
+
/** Whether the table has a totals row. Defaults to `false`. */
|
|
101
|
+
totalsRow?: boolean;
|
|
102
|
+
/** The `totalsRowShown` flag on a table *without* a totals row — Excel's record of whether a
|
|
103
|
+
* totals row has ever been toggled on. Tri-state so a round-trip is faithful: `false` re-emits
|
|
104
|
+
* `totalsRowShown="0"`, `true` re-emits `totalsRowShown="1"`, and `undefined` (the authoring
|
|
105
|
+
* default) emits nothing — a file read without the attribute must not have one injected. Ignored
|
|
106
|
+
* when {@link totalsRow} is set, since a present totals row already implies it is shown. */
|
|
107
|
+
totalsRowShown?: boolean;
|
|
108
|
+
/** Whether the header row carries an autoFilter. Defaults to {@link headerRow}: a header table
|
|
109
|
+
* gains an autoFilter, a headerless one never can. Set `false` to keep a header table's rows
|
|
110
|
+
* unfiltered — a file read without an autoFilter must round-trip without one being injected. */
|
|
111
|
+
autoFilter?: boolean;
|
|
112
|
+
/** The table's visual style. Preserved verbatim across a round-trip; when omitted, a freshly
|
|
113
|
+
* authored table is written with Excel's default (`TableStyleMedium2`, banded rows). A part read
|
|
114
|
+
* with no `<tableStyleInfo>` sets this to `undefined`. See {@link TableStyleInfo}. */
|
|
115
|
+
style?: TableStyleInfo;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Return copies of `columns` with every name made unique (case-insensitively): the first occurrence
|
|
119
|
+
* keeps its name; a later clash gains the smallest numeric suffix that resolves it (`foo`, `foo2`,
|
|
120
|
+
* `foo3`, …). OOXML requires unique column names within a table — Excel treats a collision as
|
|
121
|
+
* corruption — so this is applied both when a table is authored and when one is read from a file,
|
|
122
|
+
* keeping the two paths identical rather than rejecting a name list the reader would accept.
|
|
123
|
+
*/
|
|
124
|
+
export declare function disambiguateColumnNames(columns: readonly TableColumn[]): TableColumn[];
|
|
125
|
+
/** The rectangle a table occupies, in 1-based coordinates. */
|
|
126
|
+
export interface TableRegion {
|
|
127
|
+
readonly top: number;
|
|
128
|
+
readonly left: number;
|
|
129
|
+
readonly bottom: number;
|
|
130
|
+
readonly right: number;
|
|
131
|
+
}
|
|
132
|
+
export declare class Table {
|
|
133
|
+
#private;
|
|
134
|
+
readonly name: string;
|
|
135
|
+
readonly displayName: string;
|
|
136
|
+
readonly columns: readonly TableColumn[];
|
|
137
|
+
readonly headerRow: boolean;
|
|
138
|
+
readonly totalsRow: boolean;
|
|
139
|
+
readonly totalsRowShown: boolean | undefined;
|
|
140
|
+
readonly autoFilter: boolean;
|
|
141
|
+
readonly style: TableStyleInfo | undefined;
|
|
142
|
+
constructor(options: TableOptions, writeCell?: TableCellWriter, insertRow?: TableRowInserter);
|
|
143
|
+
get columnCount(): number;
|
|
144
|
+
/** The number of data rows (excludes the header and totals rows). Always defined — a table loaded
|
|
145
|
+
* from a file derives it from the stored range, so reading the height never throws. */
|
|
146
|
+
get rowCount(): number;
|
|
147
|
+
/**
|
|
148
|
+
* Append a data row to the bottom of the table, growing its range by one row and writing `values`
|
|
149
|
+
* left-to-right across its columns. A loaded table exposes its rows the same as a freshly-authored
|
|
150
|
+
* one, so this works identically whether the table was built in memory or read from a file.
|
|
151
|
+
*
|
|
152
|
+
* A table carrying a totals row appends above it: the new data row lands where the totals row sat,
|
|
153
|
+
* and the totals row (with any sheet content below) shifts down by one — exactly what inserting a
|
|
154
|
+
* worksheet row does. That relocation lives in the grid, so a totals-row table not attached to a
|
|
155
|
+
* worksheet throws, as does passing `values` on any detached table — there is nowhere to put them.
|
|
156
|
+
*/
|
|
157
|
+
addRow(values?: readonly CellValue[]): void;
|
|
158
|
+
/**
|
|
159
|
+
* Re-pin the table through a row splice: `count` rows removed at the 1-based `start`, then rows
|
|
160
|
+
* inserted so surviving rows below shift by `delta`. A splice entirely above the table moves its
|
|
161
|
+
* whole range by `delta`; one landing inside grows or shrinks the data rows to absorb the change;
|
|
162
|
+
* one that deletes the table's every row removes it. Returns `false` when the table no longer has
|
|
163
|
+
* a row to occupy (the caller drops it), `true` when it survives.
|
|
164
|
+
*/
|
|
165
|
+
shiftRows(start: number, count: number, delta: number): boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Re-pin the table through a column splice. A splice entirely to the table's left moves its anchor
|
|
168
|
+
* by `delta`; one to its right leaves it untouched. A splice landing inside the table's columns is
|
|
169
|
+
* structural surgery on named columns with no unambiguous answer, so the table's columns are left
|
|
170
|
+
* as-is (anchor unchanged) rather than fabricated or dropped. Always returns `true`.
|
|
171
|
+
*/
|
|
172
|
+
shiftColumns(start: number, count: number, delta: number): boolean;
|
|
173
|
+
/**
|
|
174
|
+
* The options that reconstruct this table — the anchor as a single-cell ref (not the derived
|
|
175
|
+
* full range), the columns, and the data-row count with the header/totals flags. Feeding this
|
|
176
|
+
* back to the constructor yields an equivalent table, so a worksheet model can carry a table
|
|
177
|
+
* losslessly across an export/import round-trip.
|
|
178
|
+
*/
|
|
179
|
+
get options(): TableOptions;
|
|
180
|
+
/** The full A1 range the table occupies: header (if any) + data rows + totals (if any). Distinct
|
|
181
|
+
* from {@link TableOptions.ref} (and {@link options}'s own `ref`), which is only the single-cell
|
|
182
|
+
* anchor a table is constructed from — this is the anchor plus the columns/rows it has grown to
|
|
183
|
+
* cover. */
|
|
184
|
+
get range(): string;
|
|
185
|
+
/**
|
|
186
|
+
* The autoFilter range — the header row plus the data rows, never the totals row — or
|
|
187
|
+
* `undefined` when the table has no autoFilter: either it is headerless (an autoFilter has
|
|
188
|
+
* nothing to anchor to and Excel treats its presence as corruption) or its {@link autoFilter}
|
|
189
|
+
* flag is off (a table read without one must not gain one on round-trip).
|
|
190
|
+
*/
|
|
191
|
+
get autoFilterRef(): string | undefined;
|
|
192
|
+
/** The occupied rectangle, for conflict checks such as overlapping merges. */
|
|
193
|
+
get region(): TableRegion;
|
|
194
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// A worksheet table (OOXML `<table>`): a named, structured range with typed columns,
|
|
2
|
+
// an optional header row, and an optional totals row.
|
|
3
|
+
//
|
|
4
|
+
// The model stores the anchor and the column/row counts, not a pre-baked range string —
|
|
5
|
+
// the occupied geometry is derived, so it stays correct as an empty table (header row
|
|
6
|
+
// only), a headerless table (data rows only), or a totals-bearing table. The writer is
|
|
7
|
+
// the OOXML gatekeeper for serialization; this model owns the invariants Excel enforces
|
|
8
|
+
// on the *shape* itself: a legal name, at least one column, and at least one row.
|
|
9
|
+
import { AuthoringError } from "../errors.js";
|
|
10
|
+
import { decodeAddress, encodeAddress } from "./address.js";
|
|
11
|
+
/** Copy a style, keeping only its defined fields off the literal so `exactOptionalPropertyTypes`
|
|
12
|
+
* never sees a fabricated `key: undefined` — an absent attribute must stay absent across a copy.
|
|
13
|
+
*
|
|
14
|
+
* The sentinel name `"None"` (Excel's table-style gallery entry for *no* style) is normalised to an
|
|
15
|
+
* absent name: OOXML expresses "unstyled" as a `<tableStyleInfo>` with no `name` attribute, so a
|
|
16
|
+
* literal `name="None"` would reference a style that does not exist and make the file suspect. The
|
|
17
|
+
* banding flags set alongside it are untouched. */
|
|
18
|
+
function cloneStyleInfo(style) {
|
|
19
|
+
const clone = {};
|
|
20
|
+
if (style.name !== undefined && style.name !== 'None')
|
|
21
|
+
clone.name = style.name;
|
|
22
|
+
if (style.showFirstColumn !== undefined)
|
|
23
|
+
clone.showFirstColumn = style.showFirstColumn;
|
|
24
|
+
if (style.showLastColumn !== undefined)
|
|
25
|
+
clone.showLastColumn = style.showLastColumn;
|
|
26
|
+
if (style.showRowStripes !== undefined)
|
|
27
|
+
clone.showRowStripes = style.showRowStripes;
|
|
28
|
+
if (style.showColumnStripes !== undefined)
|
|
29
|
+
clone.showColumnStripes = style.showColumnStripes;
|
|
30
|
+
return clone;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* OOXML's totals-row function names (`ST_TotalsRowFunction`) to the `SUBTOTAL` first-argument code
|
|
34
|
+
* Excel writes into a materialised totals cell. The `10x` band ignores manually hidden rows — the
|
|
35
|
+
* behaviour Excel's totals row uses. The one inversion trap: `count` is COUNTA (103, non-empty) while
|
|
36
|
+
* `countNums` is COUNT (102, numbers only). `none` (no aggregate) has no built-in code, so a column
|
|
37
|
+
* carrying it is absent here and its totals cell is left unmaterialised — Excel accepts the blank
|
|
38
|
+
* cell. `custom` is likewise absent: its aggregate is not a `SUBTOTAL` but the arbitrary formula
|
|
39
|
+
* stored in {@link TableColumn.totalsRowFormula}, which the reader/writer round-trip and the
|
|
40
|
+
* materialiser writes into the cell verbatim.
|
|
41
|
+
*/
|
|
42
|
+
export const TOTALS_ROW_SUBTOTAL_CODE = {
|
|
43
|
+
average: 101,
|
|
44
|
+
countNums: 102,
|
|
45
|
+
count: 103,
|
|
46
|
+
max: 104,
|
|
47
|
+
min: 105,
|
|
48
|
+
stdDev: 107,
|
|
49
|
+
sum: 109,
|
|
50
|
+
var: 110,
|
|
51
|
+
};
|
|
52
|
+
const TOTALS_ROW_FUNCTIONS = new Set([
|
|
53
|
+
'average',
|
|
54
|
+
'countNums',
|
|
55
|
+
'count',
|
|
56
|
+
'max',
|
|
57
|
+
'min',
|
|
58
|
+
'stdDev',
|
|
59
|
+
'sum',
|
|
60
|
+
'var',
|
|
61
|
+
'custom',
|
|
62
|
+
'none',
|
|
63
|
+
]);
|
|
64
|
+
/** Narrow a raw `totalsRowFunction` attribute to a known {@link TotalsRowFunction}. */
|
|
65
|
+
export function isTotalsRowFunction(value) {
|
|
66
|
+
return TOTALS_ROW_FUNCTIONS.has(value);
|
|
67
|
+
}
|
|
68
|
+
// Excel's table-name grammar: start with a letter, underscore, or backslash; every later
|
|
69
|
+
// character a letter, digit, period, or underscore. Unicode letters/digits are allowed.
|
|
70
|
+
// Excel additionally forbids a name that *is* a cell reference (`A1`, `R1C1`); we defer
|
|
71
|
+
// that rule deliberately — the regression corpus treats cell-reference-shaped names like
|
|
72
|
+
// `T1` as valid table names, so enforcing the collision rule here would reject a fixture
|
|
73
|
+
// the contract accepts.
|
|
74
|
+
const IDENTIFIER = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
|
|
75
|
+
/**
|
|
76
|
+
* Return copies of `columns` with every name made unique (case-insensitively): the first occurrence
|
|
77
|
+
* keeps its name; a later clash gains the smallest numeric suffix that resolves it (`foo`, `foo2`,
|
|
78
|
+
* `foo3`, …). OOXML requires unique column names within a table — Excel treats a collision as
|
|
79
|
+
* corruption — so this is applied both when a table is authored and when one is read from a file,
|
|
80
|
+
* keeping the two paths identical rather than rejecting a name list the reader would accept.
|
|
81
|
+
*/
|
|
82
|
+
export function disambiguateColumnNames(columns) {
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
return columns.map((column) => {
|
|
85
|
+
let candidate = column.name;
|
|
86
|
+
for (let n = 2; seen.has(candidate.toLowerCase()); n++)
|
|
87
|
+
candidate = `${column.name}${n}`;
|
|
88
|
+
seen.add(candidate.toLowerCase());
|
|
89
|
+
return candidate === column.name ? { ...column } : { ...column, name: candidate };
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function validateTableName(name) {
|
|
93
|
+
if (name.length === 0 || name.length > 255) {
|
|
94
|
+
throw new AuthoringError(`table name ${JSON.stringify(name)} must be between 1 and 255 characters`);
|
|
95
|
+
}
|
|
96
|
+
if (!IDENTIFIER.test(name)) {
|
|
97
|
+
throw new AuthoringError(`table name ${JSON.stringify(name)} is not a valid Excel identifier — it must start with a letter, ` +
|
|
98
|
+
'underscore, or backslash and contain only letters, digits, periods, and underscores');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export class Table {
|
|
102
|
+
name;
|
|
103
|
+
displayName;
|
|
104
|
+
columns;
|
|
105
|
+
headerRow;
|
|
106
|
+
totalsRow;
|
|
107
|
+
totalsRowShown;
|
|
108
|
+
autoFilter;
|
|
109
|
+
style;
|
|
110
|
+
// The anchor and data-row count move when a row/column splice shifts or resizes the table, so
|
|
111
|
+
// they are mutable behind the class's controlled `shiftRows`/`shiftColumns` methods.
|
|
112
|
+
#anchorCol;
|
|
113
|
+
#anchorRow;
|
|
114
|
+
#dataRowCount;
|
|
115
|
+
// Set by the worksheet that registers this table so an appended row can be written into the grid.
|
|
116
|
+
// A table constructed standalone (a unit test, a bare model) has none — appending values then
|
|
117
|
+
// throws rather than silently dropping them.
|
|
118
|
+
#writeCell;
|
|
119
|
+
// Supplied alongside #writeCell by the registering worksheet. A totals-row table appends by
|
|
120
|
+
// inserting a grid row above the totals; a standalone table has neither hook.
|
|
121
|
+
#insertRow;
|
|
122
|
+
constructor(options, writeCell, insertRow) {
|
|
123
|
+
validateTableName(options.name);
|
|
124
|
+
if (options.columns.length === 0) {
|
|
125
|
+
throw new AuthoringError(`table "${options.name}" must declare at least one column`);
|
|
126
|
+
}
|
|
127
|
+
if (!Number.isInteger(options.rowCount) || options.rowCount < 0) {
|
|
128
|
+
throw new RangeError(`table "${options.name}" has an invalid data-row count (${options.rowCount})`);
|
|
129
|
+
}
|
|
130
|
+
const { col, row } = decodeAddress(options.ref);
|
|
131
|
+
if (col === undefined || row === undefined) {
|
|
132
|
+
throw new SyntaxError(`table ref "${options.ref}" must anchor at a single cell (e.g. "A1")`);
|
|
133
|
+
}
|
|
134
|
+
this.name = options.name;
|
|
135
|
+
this.displayName = options.displayName ?? options.name;
|
|
136
|
+
this.columns = disambiguateColumnNames(options.columns);
|
|
137
|
+
this.headerRow = options.headerRow ?? true;
|
|
138
|
+
this.totalsRow = options.totalsRow ?? false;
|
|
139
|
+
this.totalsRowShown = options.totalsRowShown;
|
|
140
|
+
this.style = options.style === undefined ? undefined : cloneStyleInfo(options.style);
|
|
141
|
+
// A header table gains an autoFilter by default (Excel's behaviour when a table is inserted);
|
|
142
|
+
// a headerless table can never carry one — an autoFilter has no header row to anchor to.
|
|
143
|
+
this.autoFilter = this.headerRow && (options.autoFilter ?? true);
|
|
144
|
+
this.#anchorCol = col;
|
|
145
|
+
this.#anchorRow = row;
|
|
146
|
+
this.#dataRowCount = options.rowCount;
|
|
147
|
+
this.#writeCell = writeCell;
|
|
148
|
+
this.#insertRow = insertRow;
|
|
149
|
+
if (this.#rowSpan < 1) {
|
|
150
|
+
throw new AuthoringError(`table "${this.name}" has no rows — it needs a header row or at least one data row`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
get columnCount() {
|
|
154
|
+
return this.columns.length;
|
|
155
|
+
}
|
|
156
|
+
/** The number of data rows (excludes the header and totals rows). Always defined — a table loaded
|
|
157
|
+
* from a file derives it from the stored range, so reading the height never throws. */
|
|
158
|
+
get rowCount() {
|
|
159
|
+
return this.#dataRowCount;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Append a data row to the bottom of the table, growing its range by one row and writing `values`
|
|
163
|
+
* left-to-right across its columns. A loaded table exposes its rows the same as a freshly-authored
|
|
164
|
+
* one, so this works identically whether the table was built in memory or read from a file.
|
|
165
|
+
*
|
|
166
|
+
* A table carrying a totals row appends above it: the new data row lands where the totals row sat,
|
|
167
|
+
* and the totals row (with any sheet content below) shifts down by one — exactly what inserting a
|
|
168
|
+
* worksheet row does. That relocation lives in the grid, so a totals-row table not attached to a
|
|
169
|
+
* worksheet throws, as does passing `values` on any detached table — there is nowhere to put them.
|
|
170
|
+
*/
|
|
171
|
+
addRow(values = []) {
|
|
172
|
+
if (values.length > this.columnCount) {
|
|
173
|
+
throw new RangeError(`row has ${values.length} values but table "${this.name}" has ${this.columnCount} columns`);
|
|
174
|
+
}
|
|
175
|
+
// The append point is the row directly below the last data row: the totals row when one exists,
|
|
176
|
+
// otherwise the first free row under the table.
|
|
177
|
+
const target = this.#anchorRow + (this.headerRow ? 1 : 0) + this.#dataRowCount;
|
|
178
|
+
if (this.totalsRow) {
|
|
179
|
+
if (this.#insertRow === undefined) {
|
|
180
|
+
throw new AuthoringError(`table "${this.name}" is not attached to a worksheet — cannot relocate its totals row to append a data row`);
|
|
181
|
+
}
|
|
182
|
+
// Opening a grid slot at the totals row shifts the totals down and grows this table by one
|
|
183
|
+
// through the sheet's own table re-pinning, so #dataRowCount is not bumped again here.
|
|
184
|
+
this.#insertRow(target);
|
|
185
|
+
values.forEach((value, index) => {
|
|
186
|
+
this.#writeCell?.(target, this.#anchorCol + index, value, this.columns[index]?.style);
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (values.length > 0) {
|
|
191
|
+
if (this.#writeCell === undefined) {
|
|
192
|
+
throw new AuthoringError(`table "${this.name}" is not attached to a worksheet — cannot write appended row values`);
|
|
193
|
+
}
|
|
194
|
+
values.forEach((value, index) => {
|
|
195
|
+
this.#writeCell?.(target, this.#anchorCol + index, value, this.columns[index]?.style);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
this.#dataRowCount += 1;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Re-pin the table through a row splice: `count` rows removed at the 1-based `start`, then rows
|
|
202
|
+
* inserted so surviving rows below shift by `delta`. A splice entirely above the table moves its
|
|
203
|
+
* whole range by `delta`; one landing inside grows or shrinks the data rows to absorb the change;
|
|
204
|
+
* one that deletes the table's every row removes it. Returns `false` when the table no longer has
|
|
205
|
+
* a row to occupy (the caller drops it), `true` when it survives.
|
|
206
|
+
*/
|
|
207
|
+
shiftRows(start, count, delta) {
|
|
208
|
+
// A table whose every row lies within the deleted span has nothing left to occupy.
|
|
209
|
+
if (this.#anchorRow >= start && this.#bottom < start + count)
|
|
210
|
+
return false;
|
|
211
|
+
const shift = (v) => (v < start ? v : v >= start + count ? v + delta : start);
|
|
212
|
+
const top = shift(this.#anchorRow);
|
|
213
|
+
const bottom = shift(this.#bottom);
|
|
214
|
+
const span = bottom - top + 1;
|
|
215
|
+
const fixedRows = (this.headerRow ? 1 : 0) + (this.totalsRow ? 1 : 0);
|
|
216
|
+
const dataRows = span - fixedRows;
|
|
217
|
+
if (span < 1 || dataRows < 0)
|
|
218
|
+
return false;
|
|
219
|
+
this.#anchorRow = top;
|
|
220
|
+
this.#dataRowCount = dataRows;
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Re-pin the table through a column splice. A splice entirely to the table's left moves its anchor
|
|
225
|
+
* by `delta`; one to its right leaves it untouched. A splice landing inside the table's columns is
|
|
226
|
+
* structural surgery on named columns with no unambiguous answer, so the table's columns are left
|
|
227
|
+
* as-is (anchor unchanged) rather than fabricated or dropped. Always returns `true`.
|
|
228
|
+
*/
|
|
229
|
+
shiftColumns(start, count, delta) {
|
|
230
|
+
if (this.#anchorCol >= start + count)
|
|
231
|
+
this.#anchorCol += delta;
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The options that reconstruct this table — the anchor as a single-cell ref (not the derived
|
|
236
|
+
* full range), the columns, and the data-row count with the header/totals flags. Feeding this
|
|
237
|
+
* back to the constructor yields an equivalent table, so a worksheet model can carry a table
|
|
238
|
+
* losslessly across an export/import round-trip.
|
|
239
|
+
*/
|
|
240
|
+
get options() {
|
|
241
|
+
const options = {
|
|
242
|
+
name: this.name,
|
|
243
|
+
displayName: this.displayName,
|
|
244
|
+
ref: encodeAddress(this.#anchorCol, this.#anchorRow),
|
|
245
|
+
columns: this.columns.map((column) => ({ ...column })),
|
|
246
|
+
rowCount: this.#dataRowCount,
|
|
247
|
+
headerRow: this.headerRow,
|
|
248
|
+
totalsRow: this.totalsRow,
|
|
249
|
+
autoFilter: this.autoFilter,
|
|
250
|
+
};
|
|
251
|
+
// Kept off the literal so `undefined` (attribute absent) stays absent, not an explicit
|
|
252
|
+
// `totalsRowShown: undefined` — the round-trip must not fabricate the flag.
|
|
253
|
+
if (this.totalsRowShown !== undefined)
|
|
254
|
+
options.totalsRowShown = this.totalsRowShown;
|
|
255
|
+
if (this.style !== undefined)
|
|
256
|
+
options.style = cloneStyleInfo(this.style);
|
|
257
|
+
return options;
|
|
258
|
+
}
|
|
259
|
+
/** The full A1 range the table occupies: header (if any) + data rows + totals (if any). Distinct
|
|
260
|
+
* from {@link TableOptions.ref} (and {@link options}'s own `ref`), which is only the single-cell
|
|
261
|
+
* anchor a table is constructed from — this is the anchor plus the columns/rows it has grown to
|
|
262
|
+
* cover. */
|
|
263
|
+
get range() {
|
|
264
|
+
return `${encodeAddress(this.#anchorCol, this.#anchorRow)}:${encodeAddress(this.#right, this.#bottom)}`;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The autoFilter range — the header row plus the data rows, never the totals row — or
|
|
268
|
+
* `undefined` when the table has no autoFilter: either it is headerless (an autoFilter has
|
|
269
|
+
* nothing to anchor to and Excel treats its presence as corruption) or its {@link autoFilter}
|
|
270
|
+
* flag is off (a table read without one must not gain one on round-trip).
|
|
271
|
+
*/
|
|
272
|
+
get autoFilterRef() {
|
|
273
|
+
if (!this.autoFilter)
|
|
274
|
+
return undefined;
|
|
275
|
+
const bottom = this.#anchorRow + this.#dataRowCount;
|
|
276
|
+
return `${encodeAddress(this.#anchorCol, this.#anchorRow)}:${encodeAddress(this.#right, bottom)}`;
|
|
277
|
+
}
|
|
278
|
+
/** The occupied rectangle, for conflict checks such as overlapping merges. */
|
|
279
|
+
get region() {
|
|
280
|
+
return { top: this.#anchorRow, left: this.#anchorCol, bottom: this.#bottom, right: this.#right };
|
|
281
|
+
}
|
|
282
|
+
get #right() {
|
|
283
|
+
return this.#anchorCol + this.columns.length - 1;
|
|
284
|
+
}
|
|
285
|
+
get #rowSpan() {
|
|
286
|
+
return (this.headerRow ? 1 : 0) + this.#dataRowCount + (this.totalsRow ? 1 : 0);
|
|
287
|
+
}
|
|
288
|
+
get #bottom() {
|
|
289
|
+
return this.#anchorRow + this.#rowSpan - 1;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The twelve colour-scheme slots **in the order a `theme="n"` attribute indexes them**.
|
|
3
|
+
*
|
|
4
|
+
* This order is not the order the slots appear in the theme part. ISO/IEC 29500 §20.1.6.2 documents
|
|
5
|
+
* the `<a:clrScheme>` child sequence as `dk1, lt1, dk2, lt2, accent1…6, hlink, folHlink`, and that is
|
|
6
|
+
* how the XML is written — but SpreadsheetML's `theme="n"` does **not** index that sequence. Excel
|
|
7
|
+
* swaps each dark/light pair: index 0 is `lt1`, 1 is `dk1`, 2 is `lt2`, 3 is `dk2`.
|
|
8
|
+
*
|
|
9
|
+
* Verified against Excel Desktop rather than inferred, because the two orders differ only in the
|
|
10
|
+
* first four entries and reading either one into the other silently inverts text against background
|
|
11
|
+
* — see `docs/knowledge/specs/theme-color-index-order.md` and the recorded observation in
|
|
12
|
+
* `test/corpus/fixtures/excel-oracle/theme-color-index-order.json`. The stylesheet's own default font
|
|
13
|
+
* is the everyday witness: it carries `<color theme="1"/>` and renders black, which is `dk1`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const THEME_COLOR_SLOTS: readonly ["lt1", "dk1", "lt2", "dk2", "accent1", "accent2", "accent3", "accent4", "accent5", "accent6", "hlink", "folHlink"];
|
|
16
|
+
/** One slot of a theme's colour scheme. */
|
|
17
|
+
export type ThemeColorSlot = (typeof THEME_COLOR_SLOTS)[number];
|
|
18
|
+
/**
|
|
19
|
+
* A theme's colour scheme: each slot's colour as a 6-hex `RRGGBB` string. Partial because a foreign
|
|
20
|
+
* theme is free to omit a slot (or express one in a colour model this reader does not decode), and an
|
|
21
|
+
* absent slot is honestly absent rather than silently substituted.
|
|
22
|
+
*/
|
|
23
|
+
export type ThemeColorScheme = Readonly<Partial<Record<ThemeColorSlot, string>>>;
|
|
24
|
+
/** The Office default colour scheme, matching the theme part the writer emits for a workbook with none. */
|
|
25
|
+
export declare const DEFAULT_THEME_COLOR_SCHEME: ThemeColorScheme;
|
|
26
|
+
/**
|
|
27
|
+
* Extract the colour scheme from a theme part. Returns only the slots the part actually declares in a
|
|
28
|
+
* colour model this reader understands; an unrecognised one is dropped rather than guessed at, so a
|
|
29
|
+
* caller can tell "the theme says nothing here" from "the theme says black".
|
|
30
|
+
*
|
|
31
|
+
* Reads the `<a:clrScheme>` block alone. A theme carries a font scheme and a format scheme too, but
|
|
32
|
+
* neither participates in resolving a colour, and scanning the whole part would let a `<a:srgbClr>`
|
|
33
|
+
* buried in a gradient stop masquerade as a scheme slot.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseThemeColorScheme(themeXml: string): ThemeColorScheme;
|
|
36
|
+
/**
|
|
37
|
+
* The two typefaces a theme nominates: the `major` face headings use and the `minor` face body text
|
|
38
|
+
* uses. A cell's font reaches them by `scheme="major"`/`scheme="minor"` instead of naming a typeface,
|
|
39
|
+
* so changing these restyles every such cell at once.
|
|
40
|
+
*/
|
|
41
|
+
export interface ThemeFontScheme {
|
|
42
|
+
readonly major?: string | undefined;
|
|
43
|
+
readonly minor?: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The body typeface a workbook falls back to when neither its theme nor its styles part names one —
|
|
47
|
+
* the face the default theme nominates, and so the face every `scheme="minor"` font resolves to.
|
|
48
|
+
* Named rather than inlined because it is also the last resort of the default-font chain
|
|
49
|
+
* ({@link Workbook.defaultFont}), and the two must not drift.
|
|
50
|
+
*/
|
|
51
|
+
export declare const OFFICE_BODY_FACE = "Calibri";
|
|
52
|
+
/** The Office default typefaces, matching the theme part the writer emits for a workbook with none. */
|
|
53
|
+
export declare const DEFAULT_THEME_FONTS: ThemeFontScheme;
|
|
54
|
+
/** Extract the major/minor latin typefaces from a theme part's `<a:fontScheme>`. */
|
|
55
|
+
export declare function parseThemeFontScheme(themeXml: string): ThemeFontScheme;
|
|
56
|
+
/** What a caller can author on a workbook's theme: any subset of the colour slots and typefaces. */
|
|
57
|
+
export interface ThemeOverrides {
|
|
58
|
+
readonly colors?: Readonly<Partial<Record<ThemeColorSlot, string>>> | undefined;
|
|
59
|
+
readonly fonts?: ThemeFontScheme | undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Apply authored colour/font overrides to a theme part, returning the new part text.
|
|
63
|
+
*
|
|
64
|
+
* Surgical by design: the base part rides through untouched except for the `<a:clrScheme>` and
|
|
65
|
+
* `<a:fontScheme>` blocks, and within those, only what the caller actually named. The format scheme —
|
|
66
|
+
* the gradients, line styles and effect styles that make a theme look like a theme — is left exactly
|
|
67
|
+
* as it was, because nobody hand-authors `fillStyleLst` gradient stops from a spreadsheet API and
|
|
68
|
+
* regenerating it would replace a designer's work with the Office default.
|
|
69
|
+
*
|
|
70
|
+
* A slot the caller did not override keeps its **verbatim source element**, not a re-serialisation of
|
|
71
|
+
* its value. That matters for `dk1`/`lt1`, which Excel writes as `<a:sysClr val="windowText"
|
|
72
|
+
* lastClr="000000"/>`: rewriting those as `<a:srgbClr>` would pin them to one machine's resolved
|
|
73
|
+
* window colours and break dark-mode following.
|
|
74
|
+
*/
|
|
75
|
+
export declare function applyThemeOverrides(baseXml: string, overrides: ThemeOverrides): string;
|
|
76
|
+
/**
|
|
77
|
+
* Each colour slot's verbatim inner element from a theme part — `<a:srgbClr val="…"/>` or
|
|
78
|
+
* `<a:sysClr val="…" lastClr="…"/>`. The value-level counterpart is {@link parseThemeColorScheme};
|
|
79
|
+
* this keeps the *encoding* so an untouched slot can be re-emitted exactly as the source wrote it.
|
|
80
|
+
*/
|
|
81
|
+
export declare function parseThemeColorElements(themeXml: string): Readonly<Partial<Record<ThemeColorSlot, string>>>;
|
|
82
|
+
/**
|
|
83
|
+
* The theme part a workbook with no theme of its own ships — the standard Office theme.
|
|
84
|
+
*
|
|
85
|
+
* A spreadsheet must carry one even when nobody configured it: the stylesheet's own default font
|
|
86
|
+
* references `theme="1"`, which a consumer can only resolve against this part, so the two travel
|
|
87
|
+
* together. It is also the base {@link applyThemeOverrides} authors on top of when a workbook was
|
|
88
|
+
* built from scratch rather than read from a file.
|
|
89
|
+
*/
|
|
90
|
+
export declare const DEFAULT_THEME_XML: string;
|