@shbernal/ts-xlsx 3.0.0 → 3.2.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 +34 -24
- package/dist/bytes.d.ts +10 -2
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +96 -2
- package/dist/core/address.js +87 -33
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- 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 +4 -6
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +6 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +11 -1
- package/dist/core/conditional-formatting-overlay.js +30 -3
- package/dist/core/conditional-formatting.d.ts +107 -11
- package/dist/core/conditional-formatting.js +76 -19
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +7 -1
- package/dist/core/data-validation-overlay.js +19 -4
- package/dist/core/data-validation.d.ts +22 -0
- package/dist/core/data-validation.js +30 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +170 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +19 -3
- package/dist/core/formula-references.d.ts +26 -0
- package/dist/core/formula-references.js +173 -0
- package/dist/core/formula-scan.d.ts +2 -0
- package/dist/core/formula-scan.js +58 -0
- package/dist/core/formula.d.ts +76 -17
- package/dist/core/formula.js +87 -80
- package/dist/core/function-values.d.ts +4 -0
- package/dist/core/function-values.js +58 -0
- package/dist/core/future-functions.d.ts +3 -0
- package/dist/core/{modern-functions.js → future-functions.js} +119 -88
- package/dist/core/grid-edits.d.ts +30 -3
- package/dist/core/grid-edits.js +248 -81
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +49 -14
- package/dist/core/image.js +16 -5
- package/dist/core/internal.d.ts +10 -2
- package/dist/core/limits.d.ts +25 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +18 -4
- package/dist/core/merge.js +33 -20
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +112 -60
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +6 -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 +6 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -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 +64 -16
- package/dist/core/table.js +87 -40
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +9 -5
- package/dist/core/value.d.ts +60 -28
- package/dist/core/value.js +48 -43
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +83 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +41 -3
- package/dist/core/workbook.js +46 -41
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +49 -0
- package/dist/core/worksheet-merges.js +65 -0
- package/dist/core/worksheet-model.d.ts +10 -2
- package/dist/core/worksheet-model.js +28 -9
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +165 -27
- package/dist/core/worksheet.js +206 -89
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +21 -14
- package/dist/entries/core.js +14 -7
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- 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/cell-metadata/metadata.d.ts +42 -0
- package/dist/io/cell-metadata/metadata.js +35 -0
- package/dist/io/cell-metadata/rich-values.d.ts +11 -0
- package/dist/io/cell-metadata/rich-values.js +80 -0
- package/dist/io/csv/delimiter.d.ts +21 -0
- package/dist/io/csv/delimiter.js +18 -0
- package/dist/io/csv/errors.d.ts +14 -0
- package/dist/io/csv/errors.js +5 -0
- package/dist/io/csv/read.d.ts +6 -1
- package/dist/io/csv/read.js +51 -22
- package/dist/io/csv/write.d.ts +19 -5
- package/dist/io/csv/write.js +24 -44
- 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 +5 -5
- package/dist/io/opc/read-opc.d.ts +58 -14
- package/dist/io/opc/read-opc.js +49 -21
- 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/opc/strict-relationships.d.ts +2 -0
- package/dist/io/opc/strict-relationships.js +12 -0
- package/dist/io/read-policy/column-budget.d.ts +26 -0
- package/dist/io/read-policy/column-budget.js +18 -0
- package/dist/io/read-policy/read-repair.d.ts +74 -0
- package/dist/io/read-policy/read-repair.js +119 -0
- package/dist/io/style/cell-style-resolution.d.ts +25 -0
- package/dist/io/style/cell-style-resolution.js +30 -0
- 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 +52 -35
- 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 +495 -596
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +26 -1
- package/dist/io/xlsb/read-worksheet.js +148 -106
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +70 -31
- 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 +14 -5
- package/dist/io/xlsb/record-types.js +14 -5
- package/dist/io/xlsb/sheet-protection.d.ts +20 -0
- package/dist/io/xlsb/sheet-protection.js +60 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
- package/dist/io/xlsx/cell-accumulator.js +74 -21
- package/dist/io/xlsx/cell-metadata.d.ts +39 -0
- package/dist/io/xlsx/cell-metadata.js +162 -0
- package/dist/io/xlsx/cell-value.d.ts +19 -5
- package/dist/io/xlsx/cell-value.js +45 -30
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-span.d.ts +11 -0
- package/dist/io/xlsx/column-span.js +9 -0
- package/dist/io/xlsx/comments.d.ts +9 -30
- package/dist/io/xlsx/comments.js +25 -83
- package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
- package/dist/io/xlsx/conditional-formatting.js +437 -138
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +67 -53
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +52 -52
- 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 +19 -21
- package/dist/io/xlsx/hyperlinks.js +12 -32
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +149 -50
- package/dist/io/xlsx/namespaces.d.ts +24 -0
- package/dist/io/xlsx/namespaces.js +11 -0
- package/dist/io/xlsx/package-plan.d.ts +60 -15
- package/dist/io/xlsx/package-plan.js +83 -18
- package/dist/io/xlsx/part-names.d.ts +6 -8
- package/dist/io/xlsx/part-names.js +3 -7
- package/dist/io/xlsx/pivot.d.ts +5 -5
- package/dist/io/xlsx/pivot.js +26 -24
- package/dist/io/xlsx/preserved-splices.d.ts +6 -0
- package/dist/io/xlsx/preserved-splices.js +74 -0
- package/dist/io/xlsx/read-comments.d.ts +29 -0
- package/dist/io/xlsx/read-comments.js +75 -0
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +259 -0
- package/dist/io/xlsx/read-pivot.js +11 -4
- package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
- package/dist/io/xlsx/read-rows.d.ts +35 -11
- package/dist/io/xlsx/read-rows.js +73 -49
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +0 -30
- package/dist/io/xlsx/read-styles.js +45 -105
- package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
- package/dist/io/xlsx/read-workbook-xml.js +175 -0
- package/dist/io/xlsx/read-worksheet.d.ts +9 -3
- package/dist/io/xlsx/read-worksheet.js +70 -177
- package/dist/io/xlsx/read.d.ts +3 -14
- package/dist/io/xlsx/read.js +74 -411
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +20 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +139 -0
- package/dist/io/xlsx/row-xml.js +222 -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 +182 -20
- package/dist/io/xlsx/strict-parts.d.ts +7 -0
- package/dist/io/xlsx/strict-parts.js +171 -0
- package/dist/io/xlsx/style-elements.d.ts +41 -0
- package/dist/io/xlsx/style-elements.js +136 -0
- package/dist/io/xlsx/styles.d.ts +17 -3
- package/dist/io/xlsx/styles.js +13 -209
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +55 -52
- package/dist/io/xlsx/theme-xml.d.ts +32 -4
- package/dist/io/xlsx/theme-xml.js +106 -71
- package/dist/io/xlsx/threaded-comments.d.ts +14 -11
- package/dist/io/xlsx/threaded-comments.js +16 -8
- package/dist/io/xlsx/workbook-xml.d.ts +45 -6
- package/dist/io/xlsx/workbook-xml.js +62 -54
- package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
- package/dist/io/xlsx/worksheet-xml.js +69 -282
- package/dist/io/xlsx/write-stream.d.ts +90 -19
- package/dist/io/xlsx/write-stream.js +80 -25
- package/dist/io/xlsx/write.d.ts +12 -6
- package/dist/io/xlsx/write.js +226 -123
- package/dist/rel-type.d.ts +25 -0
- package/dist/rel-type.js +9 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +16 -5
- package/dist/vba/bytes.js +27 -6
- package/dist/vba/cfb-format.d.ts +55 -0
- package/dist/vba/cfb-format.js +35 -0
- package/dist/vba/cfb-writer.d.ts +8 -0
- package/dist/vba/cfb-writer.js +40 -40
- package/dist/vba/cfb.d.ts +21 -4
- package/dist/vba/cfb.js +124 -90
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +11 -4
- package/dist/vba/index.d.ts +0 -2
- package/dist/vba/index.js +0 -2
- package/dist/vba/ms-ovba.d.ts +6 -2
- package/dist/vba/ms-ovba.js +66 -22
- package/dist/vba/project-editor.js +129 -119
- package/dist/vba/project.d.ts +15 -1
- package/dist/vba/project.js +38 -13
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +3 -17
- 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 +101 -11
- package/dist/xml/xml-read.js +150 -18
- package/dist/xml/xml-scan.d.ts +25 -34
- package/dist/xml/xml-scan.js +69 -46
- package/dist/xml/xml.d.ts +59 -26
- package/dist/xml/xml.js +45 -23
- package/package.json +15 -9
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
|
@@ -1,5 +1,86 @@
|
|
|
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 TableStyleDefinition[];
|
|
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
|
+
/**
|
|
63
|
+
* One preserved `<tableStyle>` definition: its bytes, and the `name` a table's style info matches it by.
|
|
64
|
+
*
|
|
65
|
+
* The name travels beside the fragment rather than being read back out of it. An authored style
|
|
66
|
+
* replaces the preserved definition it shares a name with, and a pattern pulling `name="…"` out of
|
|
67
|
+
* the bytes did not see `name='…'`, which is the same attribute in single quotes: the writer emitted
|
|
68
|
+
* both definitions under one name, the duplicate {@link Workbook.addTableStyle} promises cannot happen.
|
|
69
|
+
* The reader takes the name from the scanner that read the element, decoded, whatever its quoting.
|
|
70
|
+
*/
|
|
71
|
+
export interface TableStyleDefinition {
|
|
72
|
+
/** The style's `name`, decoded; empty for a fragment that declared none. */
|
|
73
|
+
readonly name: string;
|
|
74
|
+
/** The `<tableStyle>…</tableStyle>` element exactly as the file spelled it. */
|
|
75
|
+
readonly xml: string;
|
|
76
|
+
}
|
|
77
|
+
/** One namespace declaration a preserved `<tableStyle>` fragment depends on. */
|
|
78
|
+
export interface TableStyleNamespace {
|
|
79
|
+
readonly prefix: string;
|
|
80
|
+
readonly uri: string;
|
|
81
|
+
/** Whether the source listed this prefix in the stylesheet's `mc:Ignorable`. */
|
|
82
|
+
readonly ignorable: boolean;
|
|
83
|
+
}
|
|
3
84
|
/**
|
|
4
85
|
* The preserved style tables of a workbook: the `<dxfs>` fragments, the named cell styles, the two
|
|
5
86
|
* colour lists, and the table-style definitions a file declared plus the ones a caller authored.
|
|
@@ -19,6 +100,7 @@ export declare class WorkbookStyleTables {
|
|
|
19
100
|
addCustomTableStyle(style: TableStyle): void;
|
|
20
101
|
indexedPalette(): readonly string[];
|
|
21
102
|
restoreDifferentialStyles(fragments: readonly string[]): void;
|
|
103
|
+
adoptDifferentialStyle(fragment: string): number;
|
|
22
104
|
restoreNamedStyles(styles: readonly NamedCellStyle[]): void;
|
|
23
105
|
restoreIndexedColors(fragments: readonly string[]): void;
|
|
24
106
|
restoreMruColors(fragments: readonly string[]): void;
|
|
@@ -33,6 +33,10 @@ export class WorkbookStyleTables {
|
|
|
33
33
|
restoreDifferentialStyles(fragments) {
|
|
34
34
|
replaceContents(this.#differentialStyles, fragments);
|
|
35
35
|
}
|
|
36
|
+
adoptDifferentialStyle(fragment) {
|
|
37
|
+
const existing = this.#differentialStyles.indexOf(fragment);
|
|
38
|
+
return existing === -1 ? this.#differentialStyles.push(fragment) - 1 : existing;
|
|
39
|
+
}
|
|
36
40
|
restoreNamedStyles(styles) {
|
|
37
41
|
replaceContents(this.#namedStyles, styles);
|
|
38
42
|
}
|
|
@@ -19,9 +19,11 @@ export class WorkbookTheme {
|
|
|
19
19
|
this.#declared = declared;
|
|
20
20
|
}
|
|
21
21
|
author(overrides) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const colors = Object.fromEntries(Object.entries(overrides.colors ?? {}).map(([slot, value]) => [
|
|
23
|
+
slot,
|
|
24
|
+
normalizeThemeColor(value),
|
|
25
|
+
]));
|
|
26
|
+
Object.assign(this.#authored.colors, colors);
|
|
25
27
|
this.#authored.fonts = { ...this.#authored.fonts, ...overrides.fonts };
|
|
26
28
|
}
|
|
27
29
|
get colors() {
|
|
@@ -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,16 @@
|
|
|
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 type { DateEpoch } from './date.ts';
|
|
5
|
+
import { type SheetSplice } from './formula-references.ts';
|
|
4
6
|
import { type WorkbookImage, type WorksheetImages } from './image.ts';
|
|
5
7
|
import { INTERNAL } from './internal.ts';
|
|
6
8
|
import type { PreservedPart, PreservedRootReference } from './preserved.ts';
|
|
7
|
-
import type { Color, Font
|
|
9
|
+
import type { Color, Font } from './style.ts';
|
|
8
10
|
import { type TableStyle } from './table-style.ts';
|
|
9
11
|
import { type ThemeColorScheme, type ThemeFontScheme, type ThemeOverrides } from './theme.ts';
|
|
10
12
|
import type { WorkbookProtection } from './workbook-protection.ts';
|
|
13
|
+
import { type NamedCellStyle, type TableStyleTable } from './workbook-styles.ts';
|
|
11
14
|
import { type DeclaredThemeSchemes } from './workbook-theme.ts';
|
|
12
15
|
import { type Visibility, Worksheet, type WorksheetState } from './worksheet.ts';
|
|
13
16
|
/**
|
|
@@ -155,6 +158,25 @@ export declare class Workbook {
|
|
|
155
158
|
* workbook whose cached results are authoritative stays unmarked.
|
|
156
159
|
*/
|
|
157
160
|
fullCalcOnLoad: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Which date system this workbook's serials count in: `1900` (the Windows default) or `1904` (the
|
|
163
|
+
* `date1904` flag of `<workbookPr>`, Excel for Macintosh's original). It governs every conversion
|
|
164
|
+
* between a `Date` and the number a cell actually stores, in both directions, so setting it after
|
|
165
|
+
* cells hold dates changes what those cells mean rather than converting them.
|
|
166
|
+
*
|
|
167
|
+
* Read from the file and written back, because dropping it is not a cosmetic loss: the serials stay
|
|
168
|
+
* as they were and the consumer re-reads them under the other system, so the workbook silently
|
|
169
|
+
* changes meaning by four years and a day.
|
|
170
|
+
*/
|
|
171
|
+
dateEpoch: DateEpoch;
|
|
172
|
+
/**
|
|
173
|
+
* The workbook's VBA identity (`<workbookPr codeName>`), the name a macro means by `ThisWorkbook`.
|
|
174
|
+
* Undefined for a workbook with no VBA project, which is what Excel writes for one.
|
|
175
|
+
*
|
|
176
|
+
* Preserved rather than modeled: nothing here reads it, but a `.xlsm` whose code name is dropped on
|
|
177
|
+
* a round trip has had the binding between its macros and its document cut.
|
|
178
|
+
*/
|
|
179
|
+
codeName?: string;
|
|
158
180
|
/**
|
|
159
181
|
* Workbook-level structure/window protection: the OOXML `<workbookProtection>` element. Absent by
|
|
160
182
|
* default (an unprotected workbook). Set it to lock the workbook shell, or leave it as read from a
|
|
@@ -324,7 +346,8 @@ export declare class Workbook {
|
|
|
324
346
|
* slot left unauthored keeps the source's own encoding, including the `<a:sysClr>` form Excel uses
|
|
325
347
|
* for `dk1`/`lt1` so they follow the viewer's window colours.
|
|
326
348
|
*
|
|
327
|
-
* @throws {
|
|
349
|
+
* @throws {SyntaxError} if a colour is not 6 or 8 hexadecimal digits, with or without a leading `#`.
|
|
350
|
+
* Every colour is read before any is stored, so a refused call changes nothing.
|
|
328
351
|
*/
|
|
329
352
|
setTheme(overrides: ThemeOverrides): void;
|
|
330
353
|
/**
|
|
@@ -333,6 +356,9 @@ export declare class Workbook {
|
|
|
333
356
|
*
|
|
334
357
|
* Note the slot *order*: `theme="0"` is `lt1` and `theme="1"` is `dk1`, which is not the order the
|
|
335
358
|
* slots appear in the theme part. See {@link THEME_COLOR_SLOTS}.
|
|
359
|
+
*
|
|
360
|
+
* Every slot reads back as six uppercase hexadecimal digits, whichever spelling {@link setTheme} was
|
|
361
|
+
* given or the theme part used.
|
|
336
362
|
*/
|
|
337
363
|
get themeColors(): ThemeColorScheme;
|
|
338
364
|
/** The theme's major (heading) and minor (body) typefaces, authored values over the source's. */
|
|
@@ -392,7 +418,8 @@ export declare class Workbook {
|
|
|
392
418
|
*
|
|
393
419
|
* A serializer composes these *over* the existing part rather than generating one from scratch, so
|
|
394
420
|
* a preserved theme keeps its format scheme, its unauthored slots' exact encoding, and the
|
|
395
|
-
* relationships it carries.
|
|
421
|
+
* relationships it carries. Its colours are already reduced to the six uppercase hexadecimal digits
|
|
422
|
+
* a slot holds.
|
|
396
423
|
*/
|
|
397
424
|
get themeOverrides(): ThemeOverrides | undefined;
|
|
398
425
|
/**
|
|
@@ -540,6 +567,12 @@ export interface WorkbookInternals {
|
|
|
540
567
|
* re-write.
|
|
541
568
|
*/
|
|
542
569
|
restoreDifferentialStyles(fragments: readonly string[]): void;
|
|
570
|
+
/**
|
|
571
|
+
* The index of a `<dxf>` fragment a sheet carried inline, found among the differential styles or
|
|
572
|
+
* appended after them, so a conditional format read from the extension form holds a `dxfId` like any
|
|
573
|
+
* other rule.
|
|
574
|
+
*/
|
|
575
|
+
adoptDifferentialStyle(fragment: string): number;
|
|
543
576
|
/**
|
|
544
577
|
* Reinstate the custom indexed-color palette (`<colors><indexedColors>`) read from a file, each
|
|
545
578
|
* entry a verbatim `<rgbColor rgb="…"/>` fragment, so a colour referenced by `indexed="…"` keeps
|
|
@@ -596,4 +629,9 @@ export interface WorkbookInternals {
|
|
|
596
629
|
* {@link Workbook.addPerson} is the authoring verb.
|
|
597
630
|
*/
|
|
598
631
|
restorePersons(persons: readonly Person[]): void;
|
|
632
|
+
/**
|
|
633
|
+
* Every row and column splice made to the workbook's sheets, in the order they were made, for the
|
|
634
|
+
* writer to replay over the references a preserved part spells.
|
|
635
|
+
*/
|
|
636
|
+
splices(): readonly SheetSplice[];
|
|
599
637
|
}
|
package/dist/core/workbook.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
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 {
|
|
5
|
+
import { spliceFormula } from './formula-references.js';
|
|
6
|
+
import { pictureProperties } from './image.js';
|
|
6
7
|
import { INTERNAL } from './internal.js';
|
|
8
|
+
import { INVALID_SHEET_NAME_CHARS, MAX_SHEET_NAME_LENGTH } from './limits.js';
|
|
7
9
|
import { checkTableStyle } from './table-style.js';
|
|
8
10
|
import { OFFICE_BODY_FACE, THEME_COLOR_SLOTS, } from './theme.js';
|
|
11
|
+
import { WorkbookMedia } from './workbook-media.js';
|
|
9
12
|
import { WorkbookStyleTables } from './workbook-styles.js';
|
|
10
13
|
import { WorkbookTheme } from './workbook-theme.js';
|
|
11
14
|
import { WorkbookVbaProject } from './workbook-vba.js';
|
|
@@ -17,27 +20,28 @@ export const DEFAULT_WORKBOOK_VIEW = {
|
|
|
17
20
|
height: 12220,
|
|
18
21
|
activeTab: 0,
|
|
19
22
|
};
|
|
20
|
-
const MAX_SHEET_NAME_LENGTH = 31;
|
|
21
|
-
const INVALID_SHEET_NAME_CHARS = /[*?:\\/[\]]/;
|
|
22
23
|
export class Workbook {
|
|
23
24
|
properties = {};
|
|
24
25
|
view = { ...DEFAULT_WORKBOOK_VIEW };
|
|
25
26
|
fullCalcOnLoad = false;
|
|
27
|
+
dateEpoch = 1900;
|
|
28
|
+
codeName;
|
|
26
29
|
protection = undefined;
|
|
27
30
|
#worksheets = [];
|
|
28
31
|
#nextSheetId = 1;
|
|
29
|
-
#media =
|
|
32
|
+
#media = new WorkbookMedia();
|
|
30
33
|
#definedNames = [];
|
|
31
34
|
#styles = new WorkbookStyleTables();
|
|
32
35
|
#theme = new WorkbookTheme(() => this.#styles.indexedPalette());
|
|
33
36
|
#preservedReferences = [];
|
|
37
|
+
#splices = [];
|
|
34
38
|
#persons = new Map();
|
|
35
39
|
get worksheets() {
|
|
36
40
|
return this.#worksheets;
|
|
37
41
|
}
|
|
38
42
|
get activeTabIndex() {
|
|
39
43
|
const { activeTab } = this.view;
|
|
40
|
-
return Number.isInteger(activeTab) && activeTab
|
|
44
|
+
return Number.isInteger(activeTab) && activeTab >= 0 && activeTab < this.#worksheets.length
|
|
41
45
|
? activeTab
|
|
42
46
|
: 0;
|
|
43
47
|
}
|
|
@@ -180,52 +184,36 @@ export class Workbook {
|
|
|
180
184
|
return this.#persons.get(id);
|
|
181
185
|
}
|
|
182
186
|
addImage(options) {
|
|
183
|
-
this.#media.
|
|
184
|
-
extension: normalizeImageExtension(options.extension, options.buffer),
|
|
185
|
-
data: options.buffer,
|
|
186
|
-
});
|
|
187
|
-
return this.#media.length - 1;
|
|
187
|
+
return this.#media.register(options.extension, options.buffer);
|
|
188
188
|
}
|
|
189
189
|
get media() {
|
|
190
|
-
return this.#media;
|
|
190
|
+
return this.#media.all;
|
|
191
191
|
}
|
|
192
192
|
getImage(id) {
|
|
193
|
-
return this.#media
|
|
193
|
+
return this.#media.get(id);
|
|
194
194
|
}
|
|
195
195
|
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
196
|
const background = sheet.backgroundImageId;
|
|
205
197
|
return {
|
|
206
|
-
anchored: sheet.images.map((
|
|
207
|
-
|
|
198
|
+
anchored: sheet.images.map((picture) => ({
|
|
199
|
+
image: this.#media.require(picture.imageId, sheet.name),
|
|
200
|
+
anchor: picture.anchor,
|
|
201
|
+
...pictureProperties(picture),
|
|
202
|
+
})),
|
|
203
|
+
background: background === undefined ? undefined : this.#media.require(background, sheet.name),
|
|
208
204
|
};
|
|
209
205
|
}
|
|
210
206
|
importImages(sheet, images) {
|
|
211
207
|
for (const id of new Set(sheet.images.map((image) => image.imageId)))
|
|
212
208
|
sheet.removeImage(id);
|
|
213
209
|
sheet.removeBackgroundImage();
|
|
214
|
-
for (const
|
|
215
|
-
sheet.addImageAnchor(this.#
|
|
210
|
+
for (const picture of images.anchored) {
|
|
211
|
+
sheet.addImageAnchor(this.#media.registerExisting(picture.image), picture.anchor, pictureProperties(picture));
|
|
216
212
|
}
|
|
217
213
|
if (images.background !== undefined) {
|
|
218
|
-
sheet.addBackgroundImage(this.#
|
|
214
|
+
sheet.addBackgroundImage(this.#media.registerExisting(images.background));
|
|
219
215
|
}
|
|
220
216
|
}
|
|
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
217
|
get definedNames() {
|
|
230
218
|
return this.#definedNames;
|
|
231
219
|
}
|
|
@@ -234,16 +222,31 @@ export class Workbook {
|
|
|
234
222
|
throw new AuthoringError('a defined name cannot be empty');
|
|
235
223
|
}
|
|
236
224
|
if (definedName.scope !== undefined && this.getWorksheet(definedName.scope) === undefined) {
|
|
237
|
-
throw new AuthoringError(`defined name
|
|
225
|
+
throw new AuthoringError(`defined name ${quoted(definedName.name)} is scoped to unknown worksheet ${quoted(definedName.scope)}`);
|
|
238
226
|
}
|
|
239
227
|
this.#definedNames.push(definedName);
|
|
240
228
|
}
|
|
241
229
|
addWorksheet(name, options = {}) {
|
|
242
230
|
this.#assertValidSheetName(name);
|
|
243
231
|
const sheet = new Worksheet(name, this.#nextSheetId++, options.state ?? 'visible');
|
|
232
|
+
sheet[INTERNAL].setFormulaHost((edit) => {
|
|
233
|
+
this.#spliceFormulasBeyond(sheet, edit);
|
|
234
|
+
});
|
|
244
235
|
this.#worksheets.push(sheet);
|
|
245
236
|
return sheet;
|
|
246
237
|
}
|
|
238
|
+
#spliceFormulasBeyond(spliced, edit) {
|
|
239
|
+
this.#splices.push(edit);
|
|
240
|
+
for (const sheet of this.#worksheets) {
|
|
241
|
+
if (sheet !== spliced)
|
|
242
|
+
sheet[INTERNAL].spliceFormulas(edit);
|
|
243
|
+
}
|
|
244
|
+
for (const [index, definedName] of this.#definedNames.entries()) {
|
|
245
|
+
const refersTo = spliceFormula(definedName.refersTo, definedName.scope, edit);
|
|
246
|
+
if (refersTo !== definedName.refersTo)
|
|
247
|
+
this.#definedNames[index] = { ...definedName, refersTo };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
247
250
|
getWorksheet(nameOrId) {
|
|
248
251
|
if (typeof nameOrId === 'number') {
|
|
249
252
|
return this.#worksheets.find((sheet) => sheet.id === nameOrId);
|
|
@@ -255,11 +258,11 @@ export class Workbook {
|
|
|
255
258
|
const sheet = this.getWorksheet(nameOrId);
|
|
256
259
|
if (sheet !== undefined)
|
|
257
260
|
return sheet;
|
|
258
|
-
const wanted = typeof nameOrId === 'number' ? `id ${nameOrId}` :
|
|
261
|
+
const wanted = typeof nameOrId === 'number' ? `id ${nameOrId}` : quoted(nameOrId);
|
|
259
262
|
if (this.#worksheets.length === 0) {
|
|
260
263
|
throw new AuthoringError(`no worksheet ${wanted}: this workbook has no worksheets`);
|
|
261
264
|
}
|
|
262
|
-
const have = this.#worksheets.map((sheet) =>
|
|
265
|
+
const have = this.#worksheets.map((sheet) => quoted(sheet.name)).join(', ');
|
|
263
266
|
throw new AuthoringError(`no worksheet ${wanted}; this workbook has ${have}`);
|
|
264
267
|
}
|
|
265
268
|
#assertValidSheetName(name) {
|
|
@@ -267,16 +270,16 @@ export class Workbook {
|
|
|
267
270
|
throw new AuthoringError('worksheet name cannot be empty');
|
|
268
271
|
}
|
|
269
272
|
if (name.length > MAX_SHEET_NAME_LENGTH) {
|
|
270
|
-
throw new AuthoringError(`worksheet name
|
|
273
|
+
throw new AuthoringError(`worksheet name ${quoted(name)} exceeds the ${MAX_SHEET_NAME_LENGTH}-character limit`);
|
|
271
274
|
}
|
|
272
275
|
if (INVALID_SHEET_NAME_CHARS.test(name)) {
|
|
273
|
-
throw new AuthoringError(`worksheet name
|
|
276
|
+
throw new AuthoringError(`worksheet name ${quoted(name)} contains a character Excel forbids (* ? : \\ / [ ])`);
|
|
274
277
|
}
|
|
275
278
|
if (name.startsWith("'") || name.endsWith("'")) {
|
|
276
|
-
throw new AuthoringError(`worksheet name
|
|
279
|
+
throw new AuthoringError(`worksheet name ${quoted(name)} cannot start or end with an apostrophe`);
|
|
277
280
|
}
|
|
278
281
|
if (this.getWorksheet(name) !== undefined) {
|
|
279
|
-
throw new AuthoringError(`a worksheet named
|
|
282
|
+
throw new AuthoringError(`a worksheet named ${quoted(name)} already exists (names are case-insensitive)`);
|
|
280
283
|
}
|
|
281
284
|
}
|
|
282
285
|
[INTERNAL] = {
|
|
@@ -289,6 +292,7 @@ export class Workbook {
|
|
|
289
292
|
restoreDifferentialStyles: (fragments) => {
|
|
290
293
|
this.#styles.restoreDifferentialStyles(fragments);
|
|
291
294
|
},
|
|
295
|
+
adoptDifferentialStyle: (fragment) => this.#styles.adoptDifferentialStyle(fragment),
|
|
292
296
|
restoreIndexedColors: (fragments) => {
|
|
293
297
|
this.#styles.restoreIndexedColors(fragments);
|
|
294
298
|
},
|
|
@@ -312,5 +316,6 @@ export class Workbook {
|
|
|
312
316
|
for (const person of persons)
|
|
313
317
|
this.#persons.set(person.id, person);
|
|
314
318
|
},
|
|
319
|
+
splices: () => this.#splices,
|
|
315
320
|
};
|
|
316
321
|
}
|
|
@@ -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,49 @@
|
|
|
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. The range is stored
|
|
24
|
+
* in canonical form (see {@link canonicalMerge}).
|
|
25
|
+
*
|
|
26
|
+
* @throws {SyntaxError} if the range is unparseable or names a worksheet.
|
|
27
|
+
* @throws {AuthoringError} if the range overlaps an already-merged region. Excel forbids
|
|
28
|
+
* overlapping merges and writes such geometry as a file it then offers to repair.
|
|
29
|
+
*/
|
|
30
|
+
add(range: string): MergeRect | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Drop a declared range and, with it, the rectangle it covers. The inverse of {@link add}, and it
|
|
33
|
+
* matches however the range is spelled, since both sides are compared in canonical form.
|
|
34
|
+
*
|
|
35
|
+
* @throws {SyntaxError} if the range is unparseable or names a worksheet.
|
|
36
|
+
*/
|
|
37
|
+
remove(range: string): MergeRemoval;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a position to the top-left of the region covering it, or to itself when none does. This
|
|
40
|
+
* is what makes a write through a covered address land on the region's master.
|
|
41
|
+
*/
|
|
42
|
+
masterOf(row: number, col: number): {
|
|
43
|
+
row: number;
|
|
44
|
+
col: number;
|
|
45
|
+
};
|
|
46
|
+
/** Replace every declared range and rectangle at once: what a structural splice re-anchors to. */
|
|
47
|
+
replaceAll(ranges: readonly string[], rects: readonly MergeRect[]): void;
|
|
48
|
+
clear(): void;
|
|
49
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AuthoringError, InternalError, quoted } from '../errors.js';
|
|
2
|
+
import { boundedRect, decodeRange, encodeRect } from './address.js';
|
|
3
|
+
import { replaceContents } from './containers.js';
|
|
4
|
+
import { MergeIndex } from './merge-index.js';
|
|
5
|
+
function canonicalMerge(range) {
|
|
6
|
+
const decoded = decodeRange(range);
|
|
7
|
+
if (decoded.sheetName !== undefined) {
|
|
8
|
+
throw new SyntaxError(`merged range ${quoted(range)} names worksheet ${quoted(decoded.sheetName)}: a merge belongs to the sheet it is made on`);
|
|
9
|
+
}
|
|
10
|
+
const rect = boundedRect(decoded);
|
|
11
|
+
return { canonical: rect === undefined ? decoded.dimensions : encodeRect(rect), rect };
|
|
12
|
+
}
|
|
13
|
+
export class WorksheetMerges {
|
|
14
|
+
#ranges = [];
|
|
15
|
+
#rects = [];
|
|
16
|
+
#index = new MergeIndex(this.#rects);
|
|
17
|
+
get ranges() {
|
|
18
|
+
return this.#ranges;
|
|
19
|
+
}
|
|
20
|
+
get rects() {
|
|
21
|
+
return this.#rects;
|
|
22
|
+
}
|
|
23
|
+
add(range) {
|
|
24
|
+
const { canonical, rect } = canonicalMerge(range);
|
|
25
|
+
if (rect !== undefined) {
|
|
26
|
+
if (this.#index.overlapping(rect) !== undefined) {
|
|
27
|
+
throw new AuthoringError(`merged range ${quoted(range)} overlaps an existing merged region`);
|
|
28
|
+
}
|
|
29
|
+
this.#rects.push(rect);
|
|
30
|
+
this.#index.note(rect);
|
|
31
|
+
}
|
|
32
|
+
this.#ranges.push(canonical);
|
|
33
|
+
return rect;
|
|
34
|
+
}
|
|
35
|
+
remove(range) {
|
|
36
|
+
const { canonical, rect } = canonicalMerge(range);
|
|
37
|
+
const index = this.#ranges.indexOf(canonical);
|
|
38
|
+
if (index === -1)
|
|
39
|
+
return { existed: false, rectsChanged: false };
|
|
40
|
+
this.#ranges.splice(index, 1);
|
|
41
|
+
if (rect === undefined)
|
|
42
|
+
return { existed: true, rectsChanged: false };
|
|
43
|
+
const { top, left, bottom, right } = rect;
|
|
44
|
+
const at = this.#rects.findIndex((r) => r.top === top && r.left === left && r.bottom === bottom && r.right === right);
|
|
45
|
+
if (at === -1) {
|
|
46
|
+
throw new InternalError(`merged range ${quoted(canonical)} was declared without its rectangle`);
|
|
47
|
+
}
|
|
48
|
+
this.#rects.splice(at, 1);
|
|
49
|
+
this.#index.invalidate();
|
|
50
|
+
return { existed: true, rectsChanged: true };
|
|
51
|
+
}
|
|
52
|
+
masterOf(row, col) {
|
|
53
|
+
return this.#index.masterOf(row, col);
|
|
54
|
+
}
|
|
55
|
+
replaceAll(ranges, rects) {
|
|
56
|
+
replaceContents(this.#ranges, ranges);
|
|
57
|
+
replaceContents(this.#rects, rects);
|
|
58
|
+
this.#index.invalidate();
|
|
59
|
+
}
|
|
60
|
+
clear() {
|
|
61
|
+
this.#ranges.length = 0;
|
|
62
|
+
this.#rects.length = 0;
|
|
63
|
+
this.#index.invalidate();
|
|
64
|
+
}
|
|
65
|
+
}
|