@shbernal/ts-xlsx 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +60 -2
- package/dist/core/address.js +40 -24
- package/dist/core/autofilter.d.ts +4 -3
- package/dist/core/autofilter.js +31 -30
- package/dist/core/axis-handle.d.ts +2 -0
- package/dist/core/axis-handle.js +5 -2
- package/dist/core/cell.d.ts +4 -3
- package/dist/core/cell.js +4 -6
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +2 -4
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +3 -0
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +2 -1
- package/dist/core/conditional-formatting-overlay.js +2 -2
- package/dist/core/conditional-formatting.d.ts +9 -1
- package/dist/core/conditional-formatting.js +34 -18
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +2 -1
- package/dist/core/data-validation-overlay.js +4 -4
- package/dist/core/data-validation.d.ts +6 -0
- package/dist/core/data-validation.js +15 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +160 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +18 -2
- package/dist/core/formula.d.ts +26 -0
- package/dist/core/formula.js +20 -4
- package/dist/core/grid-edits.d.ts +2 -3
- package/dist/core/grid-edits.js +69 -70
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/image.d.ts +13 -9
- package/dist/core/image.js +7 -5
- package/dist/core/internal.d.ts +8 -2
- package/dist/core/limits.d.ts +23 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +5 -4
- package/dist/core/merge.js +18 -19
- package/dist/core/pivot-table.js +63 -45
- package/dist/core/protection.js +2 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +1 -0
- package/dist/core/row.js +3 -0
- package/dist/core/style.d.ts +85 -70
- package/dist/core/style.js +92 -28
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +27 -9
- package/dist/core/table.js +51 -34
- package/dist/core/theme.js +6 -5
- package/dist/core/value.d.ts +9 -0
- package/dist/core/value.js +36 -34
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +67 -1
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +23 -2
- package/dist/core/workbook.js +24 -40
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +44 -0
- package/dist/core/worksheet-merges.js +59 -0
- package/dist/core/worksheet-model.d.ts +9 -1
- package/dist/core/worksheet-model.js +24 -9
- package/dist/core/worksheet.d.ts +55 -6
- package/dist/core/worksheet.js +36 -44
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +18 -13
- package/dist/entries/core.js +12 -6
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/csv/delimiter.d.ts +9 -0
- package/dist/io/csv/delimiter.js +6 -0
- package/dist/io/csv/read.js +11 -4
- package/dist/io/csv/write.d.ts +9 -2
- package/dist/io/csv/write.js +9 -38
- package/dist/io/opc/inflate.js +14 -0
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +9 -0
- package/dist/io/opc/part-paths.js +1 -1
- package/dist/io/opc/read-opc.d.ts +54 -14
- package/dist/io/opc/read-opc.js +42 -20
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/style/xf-style.d.ts +23 -1
- package/dist/io/style/xf-style.js +8 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +49 -34
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +385 -596
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +20 -1
- package/dist/io/xlsb/read-worksheet.js +73 -89
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +44 -23
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +1 -5
- package/dist/io/xlsb/record-types.js +1 -5
- package/dist/io/xlsx/cell-accumulator.d.ts +8 -1
- package/dist/io/xlsx/cell-accumulator.js +32 -9
- package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
- package/dist/io/xlsx/cell-style-resolution.js +29 -0
- package/dist/io/xlsx/cell-value.d.ts +5 -3
- package/dist/io/xlsx/cell-value.js +13 -15
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-budget.d.ts +26 -0
- package/dist/io/xlsx/column-budget.js +22 -0
- package/dist/io/xlsx/comments.d.ts +6 -2
- package/dist/io/xlsx/comments.js +36 -32
- package/dist/io/xlsx/conditional-formatting.d.ts +6 -3
- package/dist/io/xlsx/conditional-formatting.js +30 -26
- package/dist/io/xlsx/data-validation.js +29 -27
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +14 -21
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +37 -5
- package/dist/io/xlsx/hyperlinks.js +22 -13
- package/dist/io/xlsx/images.js +11 -13
- package/dist/io/xlsx/namespaces.d.ts +16 -0
- package/dist/io/xlsx/namespaces.js +7 -0
- package/dist/io/xlsx/package-plan.d.ts +14 -2
- package/dist/io/xlsx/package-plan.js +39 -11
- package/dist/io/xlsx/pivot.js +8 -7
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +252 -0
- package/dist/io/xlsx/read-pivot.js +2 -1
- package/dist/io/xlsx/read-repair.d.ts +26 -0
- package/dist/io/xlsx/read-repair.js +39 -0
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +6 -3
- package/dist/io/xlsx/read-rows.d.ts +18 -5
- package/dist/io/xlsx/read-rows.js +45 -31
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +1 -6
- package/dist/io/xlsx/read-styles.js +6 -71
- package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
- package/dist/io/xlsx/read-workbook-xml.js +170 -0
- package/dist/io/xlsx/read-worksheet.d.ts +2 -1
- package/dist/io/xlsx/read-worksheet.js +35 -164
- package/dist/io/xlsx/read.d.ts +5 -12
- package/dist/io/xlsx/read.js +73 -406
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +18 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +128 -0
- package/dist/io/xlsx/row-xml.js +203 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +145 -9
- package/dist/io/xlsx/style-elements.d.ts +43 -0
- package/dist/io/xlsx/style-elements.js +156 -0
- package/dist/io/xlsx/styles.d.ts +12 -3
- package/dist/io/xlsx/styles.js +5 -207
- package/dist/io/xlsx/tables.js +10 -14
- package/dist/io/xlsx/theme-xml.d.ts +26 -4
- package/dist/io/xlsx/theme-xml.js +107 -69
- package/dist/io/xlsx/threaded-comments.d.ts +8 -10
- package/dist/io/xlsx/threaded-comments.js +2 -1
- package/dist/io/xlsx/workbook-xml.d.ts +54 -4
- package/dist/io/xlsx/workbook-xml.js +60 -34
- package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
- package/dist/io/xlsx/worksheet-xml.js +48 -229
- package/dist/io/xlsx/write-stream.d.ts +65 -16
- package/dist/io/xlsx/write-stream.js +50 -20
- package/dist/io/xlsx/write.d.ts +1 -2
- package/dist/io/xlsx/write.js +114 -74
- package/dist/rel-type.d.ts +16 -0
- package/dist/rel-type.js +6 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +14 -5
- package/dist/vba/bytes.js +16 -6
- package/dist/vba/cfb-format.d.ts +8 -0
- package/dist/vba/cfb-format.js +1 -0
- package/dist/vba/cfb-writer.d.ts +9 -0
- package/dist/vba/cfb-writer.js +12 -9
- package/dist/vba/cfb.d.ts +13 -1
- package/dist/vba/cfb.js +73 -51
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/errors.d.ts +7 -0
- package/dist/vba/ms-ovba.js +51 -16
- package/dist/vba/project-editor.js +35 -40
- package/dist/vba/project.d.ts +11 -1
- package/dist/vba/project.js +27 -8
- package/dist/vba/vba-encoding.js +4 -5
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +72 -10
- package/dist/xml/xml-read.js +94 -18
- package/dist/xml/xml-scan.d.ts +15 -37
- package/dist/xml/xml-scan.js +12 -40
- package/dist/xml/xml.d.ts +28 -27
- package/dist/xml/xml.js +24 -24
- package/package.json +12 -7
- /package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +0 -0
|
@@ -1,5 +1,71 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CellStyle } from './style.ts';
|
|
2
2
|
import type { TableStyle } from './table-style.ts';
|
|
3
|
+
/**
|
|
4
|
+
* A named cell style: the OOXML `cellStyleXfs`/`cellStyles` layer. A spreadsheet applies a built-in
|
|
5
|
+
* or custom style (e.g. "Normal", "Accent1") whose visual facets live in this shared, named layer
|
|
6
|
+
* rather than on each cell's direct format; a cell links to it and inherits any facet the direct
|
|
7
|
+
* format leaves unset. The facets are a cell's own (see {@link CellStyle}); `name` is the style's
|
|
8
|
+
* display name and `builtinId` its Excel gallery index when it is a built-in style.
|
|
9
|
+
*/
|
|
10
|
+
export type NamedCellStyle = Readonly<CellStyle> & {
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
readonly builtinId?: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A differential style (OOXML CT_Dxf): formatting laid *over* whatever a cell already carries. Only
|
|
16
|
+
* the facets present override; the rest of the cell's own style shows through. It carries the subset
|
|
17
|
+
* of the cell-style facets (see {@link CellStyle}) a `<dxf>` can express: font, number format, fill,
|
|
18
|
+
* and border.
|
|
19
|
+
*
|
|
20
|
+
* Differential styles live in one workbook-level table (`<dxfs>`) that several features index into:
|
|
21
|
+
* a conditional-formatting rule's highlight format, and a table style's per-element formatting
|
|
22
|
+
* (`<tableStyleElement dxfId="…">`). They are interned and shared, so two features asking for the
|
|
23
|
+
* same formatting land on one entry.
|
|
24
|
+
*
|
|
25
|
+
* Not every facet reaches every consumer. As a **table style element**, Excel applies only the font,
|
|
26
|
+
* fill, and border: its own object model exposes `Font`, `Interior`, and `Borders` on a table style
|
|
27
|
+
* element and nothing for a number format, so a `numFmt` set here is carried faithfully through a
|
|
28
|
+
* round-trip but has no visible effect. The type is left whole rather than split, because the same
|
|
29
|
+
* value is legitimately reused across both consumers and narrowing it would only move the surprise.
|
|
30
|
+
*/
|
|
31
|
+
export type DifferentialStyle = Pick<CellStyle, 'font' | 'numFmt' | 'fill' | 'border'>;
|
|
32
|
+
/**
|
|
33
|
+
* The `<tableStyles>` block of a styles part: the custom table/pivot style definitions a file
|
|
34
|
+
* declares, and the two gallery names it nominates as the default for a new table and a new pivot.
|
|
35
|
+
*
|
|
36
|
+
* Each entry of {@link styles} is one `<tableStyle>…</tableStyle>` fragment kept verbatim, for the
|
|
37
|
+
* same reason a `<dxf>` is: a `tableStyleElement`'s `dxfId` indexes the differential-style table,
|
|
38
|
+
* which the writer re-emits **at its original indices**, so the references stay valid without
|
|
39
|
+
* reparsing anything. That index-stability is load-bearing: renumbering the dxf table would
|
|
40
|
+
* silently re-point every preserved table style at a different format.
|
|
41
|
+
*
|
|
42
|
+
* The two default names are ordinary strings, not fragments: they are re-escaped on write, so they
|
|
43
|
+
* are held decoded.
|
|
44
|
+
*/
|
|
45
|
+
export interface TableStyleTable {
|
|
46
|
+
readonly styles: readonly string[];
|
|
47
|
+
readonly defaultTableStyle?: string | undefined;
|
|
48
|
+
readonly defaultPivotStyle?: string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* The namespace prefixes the verbatim {@link styles} fragments use, mapped to their URI and to
|
|
51
|
+
* whether the source marked the prefix ignorable (`mc:Ignorable`).
|
|
52
|
+
*
|
|
53
|
+
* Carrying a fragment verbatim carries its *prefixes* too. Excel stamps a revision id
|
|
54
|
+
* (`xr9:uid="{…}"`) on every `<tableStyle>` it writes, so a fragment re-emitted under a
|
|
55
|
+
* `<styleSheet>` that declares only the default namespace is not namespace-well-formed, and no
|
|
56
|
+
* consumer can parse the part at all, which is a far louder failure than the dropped table style
|
|
57
|
+
* this preservation exists to prevent. The writer re-declares each prefix on `<styleSheet>` and
|
|
58
|
+
* re-states the ignorable ones, exactly as the source did.
|
|
59
|
+
*/
|
|
60
|
+
readonly namespaces?: readonly TableStyleNamespace[];
|
|
61
|
+
}
|
|
62
|
+
/** One namespace declaration a preserved `<tableStyle>` fragment depends on. */
|
|
63
|
+
export interface TableStyleNamespace {
|
|
64
|
+
readonly prefix: string;
|
|
65
|
+
readonly uri: string;
|
|
66
|
+
/** Whether the source listed this prefix in the stylesheet's `mc:Ignorable`. */
|
|
67
|
+
readonly ignorable: boolean;
|
|
68
|
+
}
|
|
3
69
|
/**
|
|
4
70
|
* The preserved style tables of a workbook: the `<dxfs>` fragments, the named cell styles, the two
|
|
5
71
|
* colour lists, and the table-style definitions a file declared plus the ones a caller authored.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRelType } from '../rel-type.js';
|
|
1
2
|
import { addVbaReference, parseVbaProject, removeVbaModule, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, VbaAuthorError, vbaProjectSignatureKind, } from '../vba/index.js';
|
|
2
3
|
import { replaceContents } from './containers.js';
|
|
3
4
|
export class WorkbookVbaProject {
|
|
@@ -21,7 +22,7 @@ export class WorkbookVbaProject {
|
|
|
21
22
|
set bytes(bytes) {
|
|
22
23
|
if (bytes !== undefined)
|
|
23
24
|
parseVbaProject(bytes);
|
|
24
|
-
replaceContents(this.#references, this.#references.filter((r) => !r.relType
|
|
25
|
+
replaceContents(this.#references, this.#references.filter((r) => !isRelType(r.relType, 'vbaProject')));
|
|
25
26
|
if (bytes !== undefined) {
|
|
26
27
|
this.#references.push({
|
|
27
28
|
relType: VBA_PROJECT_REL_TYPE,
|
|
@@ -70,7 +71,7 @@ export class WorkbookVbaProject {
|
|
|
70
71
|
this.bytes = addVbaReference(bytes, ref);
|
|
71
72
|
}
|
|
72
73
|
#ref() {
|
|
73
|
-
return this.#references.find((r) => r.relType
|
|
74
|
+
return this.#references.find((r) => isRelType(r.relType, 'vbaProject'));
|
|
74
75
|
}
|
|
75
76
|
#entry() {
|
|
76
77
|
const ref = this.#ref();
|
package/dist/core/workbook.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { type CustomUiDocument } from '../customui/index.ts';
|
|
2
2
|
import { type VbaLibraryReference, type VbaProject, type VbaProjectSignature } from '../vba/index.ts';
|
|
3
3
|
import { type Person } from './comment-thread.ts';
|
|
4
|
-
import
|
|
4
|
+
import type { DateEpoch } from './date.ts';
|
|
5
|
+
import type { WorkbookImage, WorksheetImages } from './image.ts';
|
|
5
6
|
import { INTERNAL } from './internal.ts';
|
|
6
7
|
import type { PreservedPart, PreservedRootReference } from './preserved.ts';
|
|
7
|
-
import type { Color, Font
|
|
8
|
+
import type { Color, Font } from './style.ts';
|
|
8
9
|
import { type TableStyle } from './table-style.ts';
|
|
9
10
|
import { type ThemeColorScheme, type ThemeFontScheme, type ThemeOverrides } from './theme.ts';
|
|
10
11
|
import type { WorkbookProtection } from './workbook-protection.ts';
|
|
12
|
+
import { type NamedCellStyle, type TableStyleTable } from './workbook-styles.ts';
|
|
11
13
|
import { type DeclaredThemeSchemes } from './workbook-theme.ts';
|
|
12
14
|
import { type Visibility, Worksheet, type WorksheetState } from './worksheet.ts';
|
|
13
15
|
/**
|
|
@@ -155,6 +157,25 @@ export declare class Workbook {
|
|
|
155
157
|
* workbook whose cached results are authoritative stays unmarked.
|
|
156
158
|
*/
|
|
157
159
|
fullCalcOnLoad: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Which date system this workbook's serials count in: `1900` (the Windows default) or `1904` (the
|
|
162
|
+
* `date1904` flag of `<workbookPr>`, Excel for Macintosh's original). It governs every conversion
|
|
163
|
+
* between a `Date` and the number a cell actually stores, in both directions, so setting it after
|
|
164
|
+
* cells hold dates changes what those cells mean rather than converting them.
|
|
165
|
+
*
|
|
166
|
+
* Read from the file and written back, because dropping it is not a cosmetic loss: the serials stay
|
|
167
|
+
* as they were and the consumer re-reads them under the other system, so the workbook silently
|
|
168
|
+
* changes meaning by four years and a day.
|
|
169
|
+
*/
|
|
170
|
+
dateEpoch: DateEpoch;
|
|
171
|
+
/**
|
|
172
|
+
* The workbook's VBA identity (`<workbookPr codeName>`), the name a macro means by `ThisWorkbook`.
|
|
173
|
+
* Undefined for a workbook with no VBA project, which is what Excel writes for one.
|
|
174
|
+
*
|
|
175
|
+
* Preserved rather than modeled: nothing here reads it, but a `.xlsm` whose code name is dropped on
|
|
176
|
+
* a round trip has had the binding between its macros and its document cut.
|
|
177
|
+
*/
|
|
178
|
+
codeName?: string;
|
|
158
179
|
/**
|
|
159
180
|
* Workbook-level structure/window protection: the OOXML `<workbookProtection>` element. Absent by
|
|
160
181
|
* default (an unprotected workbook). Set it to lock the workbook shell, or leave it as read from a
|
package/dist/core/workbook.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { isCustomUiRelType, parseCustomUi } from '../customui/index.js';
|
|
2
|
-
import { AuthoringError } from '../errors.js';
|
|
2
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
3
3
|
import { addVbaReference, removeVbaModule, } from '../vba/index.js';
|
|
4
4
|
import { commentThreadGuid } from './comment-thread.js';
|
|
5
|
-
import { findRegisteredImage, normalizeImageExtension, } from './image.js';
|
|
6
5
|
import { INTERNAL } from './internal.js';
|
|
6
|
+
import { INVALID_SHEET_NAME_CHARS, MAX_SHEET_NAME_LENGTH } from './limits.js';
|
|
7
7
|
import { checkTableStyle } from './table-style.js';
|
|
8
8
|
import { OFFICE_BODY_FACE, THEME_COLOR_SLOTS, } from './theme.js';
|
|
9
|
+
import { WorkbookMedia } from './workbook-media.js';
|
|
9
10
|
import { WorkbookStyleTables } from './workbook-styles.js';
|
|
10
11
|
import { WorkbookTheme } from './workbook-theme.js';
|
|
11
12
|
import { WorkbookVbaProject } from './workbook-vba.js';
|
|
@@ -17,16 +18,16 @@ export const DEFAULT_WORKBOOK_VIEW = {
|
|
|
17
18
|
height: 12220,
|
|
18
19
|
activeTab: 0,
|
|
19
20
|
};
|
|
20
|
-
const MAX_SHEET_NAME_LENGTH = 31;
|
|
21
|
-
const INVALID_SHEET_NAME_CHARS = /[*?:\\/[\]]/;
|
|
22
21
|
export class Workbook {
|
|
23
22
|
properties = {};
|
|
24
23
|
view = { ...DEFAULT_WORKBOOK_VIEW };
|
|
25
24
|
fullCalcOnLoad = false;
|
|
25
|
+
dateEpoch = 1900;
|
|
26
|
+
codeName;
|
|
26
27
|
protection = undefined;
|
|
27
28
|
#worksheets = [];
|
|
28
29
|
#nextSheetId = 1;
|
|
29
|
-
#media =
|
|
30
|
+
#media = new WorkbookMedia();
|
|
30
31
|
#definedNames = [];
|
|
31
32
|
#styles = new WorkbookStyleTables();
|
|
32
33
|
#theme = new WorkbookTheme(() => this.#styles.indexedPalette());
|
|
@@ -37,7 +38,7 @@ export class Workbook {
|
|
|
37
38
|
}
|
|
38
39
|
get activeTabIndex() {
|
|
39
40
|
const { activeTab } = this.view;
|
|
40
|
-
return Number.isInteger(activeTab) && activeTab
|
|
41
|
+
return Number.isInteger(activeTab) && activeTab >= 0 && activeTab < this.#worksheets.length
|
|
41
42
|
? activeTab
|
|
42
43
|
: 0;
|
|
43
44
|
}
|
|
@@ -180,31 +181,22 @@ export class Workbook {
|
|
|
180
181
|
return this.#persons.get(id);
|
|
181
182
|
}
|
|
182
183
|
addImage(options) {
|
|
183
|
-
this.#media.
|
|
184
|
-
extension: normalizeImageExtension(options.extension, options.buffer),
|
|
185
|
-
data: options.buffer,
|
|
186
|
-
});
|
|
187
|
-
return this.#media.length - 1;
|
|
184
|
+
return this.#media.register(options.extension, options.buffer);
|
|
188
185
|
}
|
|
189
186
|
get media() {
|
|
190
|
-
return this.#media;
|
|
187
|
+
return this.#media.all;
|
|
191
188
|
}
|
|
192
189
|
getImage(id) {
|
|
193
|
-
return this.#media
|
|
190
|
+
return this.#media.get(id);
|
|
194
191
|
}
|
|
195
192
|
exportImages(sheet) {
|
|
196
|
-
const resolve = (id) => {
|
|
197
|
-
const image = this.#media[id];
|
|
198
|
-
if (image === undefined) {
|
|
199
|
-
throw new AuthoringError(`worksheet "${sheet.name}" shows image id ${id}, which is not registered on this ` +
|
|
200
|
-
"workbook: a sheet's images can only be exported by the workbook that holds them");
|
|
201
|
-
}
|
|
202
|
-
return image;
|
|
203
|
-
};
|
|
204
193
|
const background = sheet.backgroundImageId;
|
|
205
194
|
return {
|
|
206
|
-
anchored: sheet.images.map(({ imageId, anchor }) => ({
|
|
207
|
-
|
|
195
|
+
anchored: sheet.images.map(({ imageId, anchor }) => ({
|
|
196
|
+
image: this.#media.require(imageId, sheet.name),
|
|
197
|
+
anchor,
|
|
198
|
+
})),
|
|
199
|
+
background: background === undefined ? undefined : this.#media.require(background, sheet.name),
|
|
208
200
|
};
|
|
209
201
|
}
|
|
210
202
|
importImages(sheet, images) {
|
|
@@ -212,20 +204,12 @@ export class Workbook {
|
|
|
212
204
|
sheet.removeImage(id);
|
|
213
205
|
sheet.removeBackgroundImage();
|
|
214
206
|
for (const { image, anchor } of images.anchored) {
|
|
215
|
-
sheet.addImageAnchor(this.#
|
|
207
|
+
sheet.addImageAnchor(this.#media.registerExisting(image), anchor);
|
|
216
208
|
}
|
|
217
209
|
if (images.background !== undefined) {
|
|
218
|
-
sheet.addBackgroundImage(this.#
|
|
210
|
+
sheet.addBackgroundImage(this.#media.registerExisting(images.background));
|
|
219
211
|
}
|
|
220
212
|
}
|
|
221
|
-
#registerImage(image) {
|
|
222
|
-
const candidate = {
|
|
223
|
-
extension: normalizeImageExtension(image.extension, image.data),
|
|
224
|
-
data: image.data,
|
|
225
|
-
};
|
|
226
|
-
return (findRegisteredImage(this.#media, candidate) ??
|
|
227
|
-
this.addImage({ buffer: candidate.data, extension: candidate.extension }));
|
|
228
|
-
}
|
|
229
213
|
get definedNames() {
|
|
230
214
|
return this.#definedNames;
|
|
231
215
|
}
|
|
@@ -234,7 +218,7 @@ export class Workbook {
|
|
|
234
218
|
throw new AuthoringError('a defined name cannot be empty');
|
|
235
219
|
}
|
|
236
220
|
if (definedName.scope !== undefined && this.getWorksheet(definedName.scope) === undefined) {
|
|
237
|
-
throw new AuthoringError(`defined name
|
|
221
|
+
throw new AuthoringError(`defined name ${quoted(definedName.name)} is scoped to unknown worksheet ${quoted(definedName.scope)}`);
|
|
238
222
|
}
|
|
239
223
|
this.#definedNames.push(definedName);
|
|
240
224
|
}
|
|
@@ -255,11 +239,11 @@ export class Workbook {
|
|
|
255
239
|
const sheet = this.getWorksheet(nameOrId);
|
|
256
240
|
if (sheet !== undefined)
|
|
257
241
|
return sheet;
|
|
258
|
-
const wanted = typeof nameOrId === 'number' ? `id ${nameOrId}` :
|
|
242
|
+
const wanted = typeof nameOrId === 'number' ? `id ${nameOrId}` : quoted(nameOrId);
|
|
259
243
|
if (this.#worksheets.length === 0) {
|
|
260
244
|
throw new AuthoringError(`no worksheet ${wanted}: this workbook has no worksheets`);
|
|
261
245
|
}
|
|
262
|
-
const have = this.#worksheets.map((sheet) =>
|
|
246
|
+
const have = this.#worksheets.map((sheet) => quoted(sheet.name)).join(', ');
|
|
263
247
|
throw new AuthoringError(`no worksheet ${wanted}; this workbook has ${have}`);
|
|
264
248
|
}
|
|
265
249
|
#assertValidSheetName(name) {
|
|
@@ -267,16 +251,16 @@ export class Workbook {
|
|
|
267
251
|
throw new AuthoringError('worksheet name cannot be empty');
|
|
268
252
|
}
|
|
269
253
|
if (name.length > MAX_SHEET_NAME_LENGTH) {
|
|
270
|
-
throw new AuthoringError(`worksheet name
|
|
254
|
+
throw new AuthoringError(`worksheet name ${quoted(name)} exceeds the ${MAX_SHEET_NAME_LENGTH}-character limit`);
|
|
271
255
|
}
|
|
272
256
|
if (INVALID_SHEET_NAME_CHARS.test(name)) {
|
|
273
|
-
throw new AuthoringError(`worksheet name
|
|
257
|
+
throw new AuthoringError(`worksheet name ${quoted(name)} contains a character Excel forbids (* ? : \\ / [ ])`);
|
|
274
258
|
}
|
|
275
259
|
if (name.startsWith("'") || name.endsWith("'")) {
|
|
276
|
-
throw new AuthoringError(`worksheet name
|
|
260
|
+
throw new AuthoringError(`worksheet name ${quoted(name)} cannot start or end with an apostrophe`);
|
|
277
261
|
}
|
|
278
262
|
if (this.getWorksheet(name) !== undefined) {
|
|
279
|
-
throw new AuthoringError(`a worksheet named
|
|
263
|
+
throw new AuthoringError(`a worksheet named ${quoted(name)} already exists (names are case-insensitive)`);
|
|
280
264
|
}
|
|
281
265
|
}
|
|
282
266
|
[INTERNAL] = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CommentThread } from './comment-thread.ts';
|
|
2
|
+
import { type AxisSplice } from './grid-shift.ts';
|
|
2
3
|
export declare class WorksheetComments {
|
|
3
4
|
#private;
|
|
4
5
|
constructor(sheetName: () => string);
|
|
@@ -12,6 +13,6 @@ export declare class WorksheetComments {
|
|
|
12
13
|
* the cell, so a thread left behind would put a note on one cell and its conversation on another,
|
|
13
14
|
* a pairing the writer emits and Excel refuses.
|
|
14
15
|
*/
|
|
15
|
-
shift(
|
|
16
|
+
shift(splice: AxisSplice): void;
|
|
16
17
|
restore(threads: readonly CommentThread[]): void;
|
|
17
18
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
1
2
|
import { decodeCellRef, encodeAddress } from './address.js';
|
|
2
3
|
import { commentThreadGuid, commentThreadOffset } from './comment-thread.js';
|
|
3
4
|
import { replaceContents } from './containers.js';
|
|
4
|
-
import {
|
|
5
|
+
import { shiftPoint } from './grid-shift.js';
|
|
5
6
|
export class WorksheetComments {
|
|
6
7
|
#sheetName;
|
|
7
8
|
#threads = [];
|
|
9
|
+
#takenIds = new Set();
|
|
8
10
|
constructor(sheetName) {
|
|
9
11
|
this.#sheetName = sheetName;
|
|
10
12
|
}
|
|
@@ -12,14 +14,14 @@ export class WorksheetComments {
|
|
|
12
14
|
return this.#threads;
|
|
13
15
|
}
|
|
14
16
|
add(thread) {
|
|
15
|
-
const
|
|
17
|
+
const claimed = new Set();
|
|
16
18
|
const comments = thread.comments.map((comment) => {
|
|
17
19
|
const id = commentThreadGuid(comment.id, 'a comment id');
|
|
18
|
-
if (
|
|
19
|
-
throw new SyntaxError(`a comment id must be unique within a sheet, but
|
|
20
|
+
if (this.#takenIds.has(id) || claimed.has(id)) {
|
|
21
|
+
throw new SyntaxError(`a comment id must be unique within a sheet, but ${quoted(id)} is already used on ${quoted(this.#sheetName())}: ` +
|
|
20
22
|
'a reply and the legacy fallback comment both find their thread by it');
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
claimed.add(id);
|
|
23
25
|
return {
|
|
24
26
|
...comment,
|
|
25
27
|
id,
|
|
@@ -38,30 +40,35 @@ export class WorksheetComments {
|
|
|
38
40
|
};
|
|
39
41
|
});
|
|
40
42
|
this.#threads.push({ ...thread, ref: anchorRef(thread.ref), comments });
|
|
43
|
+
for (const id of claimed)
|
|
44
|
+
this.#takenIds.add(id);
|
|
41
45
|
}
|
|
42
46
|
at(reference) {
|
|
43
47
|
const anchor = anchorRef(reference);
|
|
44
48
|
return this.#threads.find((thread) => thread.ref === anchor);
|
|
45
49
|
}
|
|
46
|
-
shift(
|
|
50
|
+
shift(splice) {
|
|
47
51
|
const survivors = [];
|
|
48
52
|
for (const thread of this.#threads) {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
53
|
+
const anchor = decodeCellRef(thread.ref);
|
|
54
|
+
const moved = shiftPoint(anchor, splice);
|
|
55
|
+
if (moved === undefined)
|
|
52
56
|
continue;
|
|
53
|
-
|
|
54
|
-
if (moved === line) {
|
|
57
|
+
if (moved.col === anchor.col && moved.row === anchor.row) {
|
|
55
58
|
survivors.push(thread);
|
|
56
59
|
continue;
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
survivors.push({ ...thread, ref });
|
|
61
|
+
survivors.push({ ...thread, ref: encodeAddress(moved.col, moved.row) });
|
|
60
62
|
}
|
|
61
63
|
replaceContents(this.#threads, survivors);
|
|
62
64
|
}
|
|
63
65
|
restore(threads) {
|
|
64
66
|
replaceContents(this.#threads, threads);
|
|
67
|
+
this.#takenIds.clear();
|
|
68
|
+
for (const thread of threads) {
|
|
69
|
+
for (const comment of thread.comments)
|
|
70
|
+
this.#takenIds.add(comment.id);
|
|
71
|
+
}
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
function anchorRef(reference) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MergeRect } from './merge.ts';
|
|
2
|
+
/** What removing a declared range did, which is two answers because a range and a rectangle are not
|
|
3
|
+
* the same thing: an unbounded whole-row/column merge is declared but has no rectangle, so dropping
|
|
4
|
+
* it leaves every geometry derived from the rectangles (the used extent, the index) still valid. */
|
|
5
|
+
export interface MergeRemoval {
|
|
6
|
+
/** Whether a merge with this exact range string was declared. */
|
|
7
|
+
readonly existed: boolean;
|
|
8
|
+
/** Whether a rectangle went with it, so anything derived from the rectangles is now stale. */
|
|
9
|
+
readonly rectsChanged: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class WorksheetMerges {
|
|
12
|
+
#private;
|
|
13
|
+
/** The declared ranges, in the order they were added. Live, not a copy. */
|
|
14
|
+
get ranges(): readonly string[];
|
|
15
|
+
/**
|
|
16
|
+
* The bounded regions as rectangles. Live, and its *identity* is stable for the sheet's lifetime:
|
|
17
|
+
* {@link UsedExtent} holds this array by reference, so every rewrite here goes through
|
|
18
|
+
* `replaceContents` rather than reassignment.
|
|
19
|
+
*/
|
|
20
|
+
get rects(): readonly MergeRect[];
|
|
21
|
+
/**
|
|
22
|
+
* Declare a merged range, returning the rectangle it covers, or `undefined` for an unbounded
|
|
23
|
+
* whole-row/column range, which is declared and overlap-checks against nothing.
|
|
24
|
+
*
|
|
25
|
+
* @throws {AuthoringError} if the range overlaps an already-merged region. Excel forbids
|
|
26
|
+
* overlapping merges and writes such geometry as a file it then offers to repair.
|
|
27
|
+
*/
|
|
28
|
+
add(range: string): MergeRect | undefined;
|
|
29
|
+
/** Drop a declared range and, with it, the rectangle it covers. The inverse of {@link add}. */
|
|
30
|
+
remove(range: string): MergeRemoval;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a position to the top-left of the region covering it, or to itself when none does. This
|
|
33
|
+
* is what makes a write through a covered address land on the region's master.
|
|
34
|
+
*/
|
|
35
|
+
masterOf(row: number, col: number): {
|
|
36
|
+
row: number;
|
|
37
|
+
col: number;
|
|
38
|
+
};
|
|
39
|
+
/** Replace every declared range and rectangle at once: what a structural splice re-anchors to. */
|
|
40
|
+
replaceAll(ranges: readonly string[], rects: readonly MergeRect[]): void;
|
|
41
|
+
/** Report that something outside has rewritten the grid under these regions. */
|
|
42
|
+
invalidate(): void;
|
|
43
|
+
clear(): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
2
|
+
import { boundedRect, decodeRange } from './address.js';
|
|
3
|
+
import { replaceContents } from './containers.js';
|
|
4
|
+
import { MergeIndex } from './merge-index.js';
|
|
5
|
+
export class WorksheetMerges {
|
|
6
|
+
#ranges = [];
|
|
7
|
+
#rects = [];
|
|
8
|
+
#index = new MergeIndex(this.#rects);
|
|
9
|
+
get ranges() {
|
|
10
|
+
return this.#ranges;
|
|
11
|
+
}
|
|
12
|
+
get rects() {
|
|
13
|
+
return this.#rects;
|
|
14
|
+
}
|
|
15
|
+
add(range) {
|
|
16
|
+
const rect = boundedRect(decodeRange(range));
|
|
17
|
+
if (rect !== undefined) {
|
|
18
|
+
if (this.#index.overlapping(rect) !== undefined) {
|
|
19
|
+
throw new AuthoringError(`merged range ${quoted(range)} overlaps an existing merged region`);
|
|
20
|
+
}
|
|
21
|
+
this.#rects.push(rect);
|
|
22
|
+
this.#index.note(rect);
|
|
23
|
+
}
|
|
24
|
+
this.#ranges.push(range);
|
|
25
|
+
return rect;
|
|
26
|
+
}
|
|
27
|
+
remove(range) {
|
|
28
|
+
const index = this.#ranges.indexOf(range);
|
|
29
|
+
if (index === -1)
|
|
30
|
+
return { existed: false, rectsChanged: false };
|
|
31
|
+
this.#ranges.splice(index, 1);
|
|
32
|
+
const rect = boundedRect(decodeRange(range));
|
|
33
|
+
if (rect === undefined)
|
|
34
|
+
return { existed: true, rectsChanged: false };
|
|
35
|
+
const { top, left, bottom, right } = rect;
|
|
36
|
+
const at = this.#rects.findIndex((r) => r.top === top && r.left === left && r.bottom === bottom && r.right === right);
|
|
37
|
+
if (at === -1)
|
|
38
|
+
return { existed: true, rectsChanged: false };
|
|
39
|
+
this.#rects.splice(at, 1);
|
|
40
|
+
this.#index.invalidate();
|
|
41
|
+
return { existed: true, rectsChanged: true };
|
|
42
|
+
}
|
|
43
|
+
masterOf(row, col) {
|
|
44
|
+
return this.#index.masterOf(row, col);
|
|
45
|
+
}
|
|
46
|
+
replaceAll(ranges, rects) {
|
|
47
|
+
replaceContents(this.#ranges, ranges);
|
|
48
|
+
replaceContents(this.#rects, rects);
|
|
49
|
+
this.#index.invalidate();
|
|
50
|
+
}
|
|
51
|
+
invalidate() {
|
|
52
|
+
this.#index.invalidate();
|
|
53
|
+
}
|
|
54
|
+
clear() {
|
|
55
|
+
this.#ranges.length = 0;
|
|
56
|
+
this.#rects.length = 0;
|
|
57
|
+
this.#index.invalidate();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -3,7 +3,15 @@ import type { Worksheet, WorksheetModel } from './worksheet.ts';
|
|
|
3
3
|
/** One field of a {@link WorksheetModel}, with both directions of its round-trip declared together. */
|
|
4
4
|
interface ModelFacet<K extends keyof WorksheetModel = keyof WorksheetModel> {
|
|
5
5
|
readonly key: K;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Produce the field's value, in a form a caller cannot mutate the sheet through.
|
|
8
|
+
*
|
|
9
|
+
* Copying is one way to get there and the type is the other. `tabColor`, `autoFilter` and
|
|
10
|
+
* `protection` are handed back by reference precisely because their types are readonly all the way
|
|
11
|
+
* down, so there is nothing to defend against and a clone would only be one more shape to keep in
|
|
12
|
+
* step with its declaration. Every other field is a mutable record or array and is copied, which is
|
|
13
|
+
* where the spreads below come from.
|
|
14
|
+
*/
|
|
7
15
|
readonly read: (sheet: Worksheet) => WorksheetModel[K];
|
|
8
16
|
/**
|
|
9
17
|
* Apply the field to a sheet whose content has already been reset. Takes the whole model rather
|
|
@@ -6,6 +6,21 @@ import { INTERNAL } from './internal.js';
|
|
|
6
6
|
function facet(key, read, write) {
|
|
7
7
|
return { key, read, write: (sheet, model) => write(sheet, model[key]) };
|
|
8
8
|
}
|
|
9
|
+
function recordFacet(key) {
|
|
10
|
+
return {
|
|
11
|
+
key,
|
|
12
|
+
read: (sheet) => ({ ...sheet[key] }),
|
|
13
|
+
write: (sheet, model) => overwrite(sheet[key], model[key]),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function recordsFacet(key) {
|
|
17
|
+
const copy = (breaks) => breaks.map((brk) => ({ ...brk }));
|
|
18
|
+
return {
|
|
19
|
+
key,
|
|
20
|
+
read: (sheet) => copy(sheet[key]),
|
|
21
|
+
write: (sheet, model) => replaceContents(sheet[key], copy(model[key])),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
9
24
|
export const WORKSHEET_MODEL_FACETS = [
|
|
10
25
|
facet('state', (sheet) => sheet.state, (sheet, value) => {
|
|
11
26
|
sheet.state = value;
|
|
@@ -13,15 +28,15 @@ export const WORKSHEET_MODEL_FACETS = [
|
|
|
13
28
|
facet('tabColor', (sheet) => sheet.tabColor, (sheet, value) => {
|
|
14
29
|
sheet.tabColor = value;
|
|
15
30
|
}),
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
recordFacet('properties'),
|
|
32
|
+
recordFacet('outline'),
|
|
33
|
+
recordFacet('view'),
|
|
34
|
+
recordFacet('pageSetup'),
|
|
35
|
+
recordFacet('printOptions'),
|
|
36
|
+
recordFacet('pageMargins'),
|
|
37
|
+
recordFacet('headerFooter'),
|
|
38
|
+
recordsFacet('rowBreaks'),
|
|
39
|
+
recordsFacet('columnBreaks'),
|
|
25
40
|
facet('columns', (sheet) => [...sheet.columns()].map(({ index, properties }) => ({ index, properties: { ...properties } })), (sheet, value) => {
|
|
26
41
|
for (const { index, properties } of value)
|
|
27
42
|
Object.assign(sheet.getColumn(index), properties);
|