@shbernal/ts-xlsx 3.1.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 +30 -23
- package/dist/bytes.d.ts +3 -2
- package/dist/core/address.d.ts +36 -0
- package/dist/core/address.js +47 -9
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- package/dist/core/color-resolution.js +2 -2
- package/dist/core/column.js +3 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/conditional-formatting-overlay.d.ts +9 -0
- package/dist/core/conditional-formatting-overlay.js +28 -1
- package/dist/core/conditional-formatting.d.ts +98 -10
- package/dist/core/conditional-formatting.js +42 -1
- package/dist/core/data-validation-overlay.d.ts +5 -0
- package/dist/core/data-validation-overlay.js +15 -0
- package/dist/core/data-validation.d.ts +16 -0
- package/dist/core/data-validation.js +15 -0
- package/dist/core/date-format.js +14 -4
- package/dist/core/date.js +1 -1
- 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 +51 -18
- package/dist/core/formula.js +77 -86
- 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 +28 -0
- package/dist/core/grid-edits.js +193 -25
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +36 -5
- package/dist/core/image.js +9 -0
- package/dist/core/internal.d.ts +3 -1
- package/dist/core/limits.d.ts +5 -3
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +15 -1
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +55 -21
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +4 -0
- package/dist/core/row.js +3 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -0
- package/dist/core/table.d.ts +38 -8
- package/dist/core/table.js +37 -7
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +4 -1
- package/dist/core/value.d.ts +51 -28
- package/dist/core/value.js +15 -12
- package/dist/core/workbook-styles.d.ts +17 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook.d.ts +20 -3
- package/dist/core/workbook.js +26 -5
- package/dist/core/worksheet-merges.d.ts +9 -4
- package/dist/core/worksheet-merges.js +17 -11
- package/dist/core/worksheet-model.d.ts +1 -1
- package/dist/core/worksheet-model.js +4 -0
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +110 -21
- package/dist/core/worksheet.js +174 -49
- package/dist/entries/core.d.ts +7 -5
- package/dist/entries/core.js +4 -3
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- package/dist/errors.js +1 -1
- 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 +14 -2
- package/dist/io/csv/delimiter.js +12 -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 +46 -24
- package/dist/io/csv/write.d.ts +10 -3
- package/dist/io/csv/write.js +16 -7
- package/dist/io/opc/part-paths.js +4 -4
- package/dist/io/opc/read-opc.d.ts +4 -0
- package/dist/io/opc/read-opc.js +10 -4
- 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/{xlsx → read-policy}/column-budget.js +4 -8
- 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/xlsb/formula.js +4 -2
- package/dist/io/xlsb/ptg-functions.js +110 -0
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-worksheet.d.ts +6 -0
- package/dist/io/xlsb/read-worksheet.js +95 -37
- package/dist/io/xlsb/read.js +26 -8
- package/dist/io/xlsb/record-types.d.ts +13 -0
- package/dist/io/xlsb/record-types.js +13 -0
- 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 +20 -12
- package/dist/io/xlsx/cell-accumulator.js +49 -19
- 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 +16 -4
- package/dist/io/xlsx/cell-value.js +38 -21
- package/dist/io/xlsx/color-xml.js +1 -1
- 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 +3 -28
- package/dist/io/xlsx/comments.js +3 -65
- package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
- package/dist/io/xlsx/conditional-formatting.js +424 -129
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +41 -29
- package/dist/io/xlsx/edit-vba.js +43 -36
- package/dist/io/xlsx/hyperlinks.d.ts +16 -50
- package/dist/io/xlsx/hyperlinks.js +8 -37
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +145 -44
- package/dist/io/xlsx/namespaces.d.ts +8 -0
- package/dist/io/xlsx/namespaces.js +4 -0
- package/dist/io/xlsx/package-plan.d.ts +48 -15
- package/dist/io/xlsx/package-plan.js +46 -9
- 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 +23 -22
- 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 +2 -2
- package/dist/io/xlsx/read-parts.js +44 -37
- package/dist/io/xlsx/read-pivot.js +9 -3
- package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
- package/dist/io/xlsx/read-rich-runs.js +18 -1
- package/dist/io/xlsx/read-rows.d.ts +18 -7
- package/dist/io/xlsx/read-rows.js +47 -37
- package/dist/io/xlsx/read-styles.d.ts +0 -25
- package/dist/io/xlsx/read-styles.js +39 -34
- package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
- package/dist/io/xlsx/read-workbook-xml.js +9 -4
- package/dist/io/xlsx/read-worksheet.d.ts +8 -3
- package/dist/io/xlsx/read-worksheet.js +45 -23
- package/dist/io/xlsx/read.d.ts +0 -4
- package/dist/io/xlsx/read.js +17 -21
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/row-position.d.ts +3 -1
- package/dist/io/xlsx/row-xml.d.ts +18 -7
- package/dist/io/xlsx/row-xml.js +54 -35
- package/dist/io/xlsx/sheet-properties.js +68 -42
- 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 +0 -2
- package/dist/io/xlsx/style-elements.js +9 -29
- package/dist/io/xlsx/styles.d.ts +5 -0
- package/dist/io/xlsx/styles.js +11 -5
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +49 -42
- package/dist/io/xlsx/theme-xml.d.ts +6 -0
- package/dist/io/xlsx/theme-xml.js +16 -19
- package/dist/io/xlsx/threaded-comments.d.ts +6 -1
- package/dist/io/xlsx/threaded-comments.js +14 -7
- package/dist/io/xlsx/workbook-xml.d.ts +13 -24
- package/dist/io/xlsx/workbook-xml.js +15 -33
- package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
- package/dist/io/xlsx/worksheet-xml.js +37 -69
- package/dist/io/xlsx/write-stream.d.ts +28 -6
- package/dist/io/xlsx/write-stream.js +42 -17
- package/dist/io/xlsx/write.d.ts +11 -4
- package/dist/io/xlsx/write.js +153 -90
- package/dist/rel-type.d.ts +9 -0
- package/dist/rel-type.js +3 -0
- package/dist/vba/bytes.d.ts +2 -0
- package/dist/vba/bytes.js +11 -0
- package/dist/vba/cfb-format.d.ts +47 -0
- package/dist/vba/cfb-format.js +34 -0
- package/dist/vba/cfb-writer.d.ts +1 -2
- package/dist/vba/cfb-writer.js +33 -36
- package/dist/vba/cfb.d.ts +9 -4
- package/dist/vba/cfb.js +55 -43
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +4 -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 +15 -6
- package/dist/vba/project-editor.js +102 -87
- package/dist/vba/project.d.ts +4 -0
- package/dist/vba/project.js +12 -6
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +2 -15
- package/dist/xml/xml-read.d.ts +29 -1
- package/dist/xml/xml-read.js +56 -0
- package/dist/xml/xml-scan.d.ts +13 -0
- package/dist/xml/xml-scan.js +58 -7
- package/dist/xml/xml.d.ts +33 -1
- package/dist/xml/xml.js +22 -0
- package/package.json +5 -4
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
- package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
- package/dist/io/xlsx/cell-style-resolution.js +0 -29
- package/dist/io/xlsx/column-budget.d.ts +0 -26
- package/dist/io/xlsx/read-repair.d.ts +0 -26
- package/dist/io/xlsx/read-repair.js +0 -39
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ A TypeScript-first library for reading and writing spreadsheet documents
|
|
|
16
16
|
> and has been rebuilt from the ground up into a modern, strict-TypeScript library.
|
|
17
17
|
> **It carries no backwards-compatibility guarantee with ExcelJS** — the API below is
|
|
18
18
|
> its own, not a drop-in. From 1.0.0 onward it follows
|
|
19
|
-
> [SemVer](https://semver.org/) against
|
|
19
|
+
> [SemVer](https://semver.org/) against _itself_; see the [changelog](CHANGELOG.md).
|
|
20
20
|
> See [`CLAUDE.md`](CLAUDE.md) for the goals and [`docs/architecture.md`](docs/architecture.md)
|
|
21
21
|
> for the design, and [migrating from ExcelJS](docs/migrating-from-exceljs.md) if you are coming across.
|
|
22
22
|
|
|
@@ -32,7 +32,7 @@ shim. It is a different, better library that happens to share ancestry.
|
|
|
32
32
|
What that buys you today:
|
|
33
33
|
|
|
34
34
|
- **Strict, precise types are the contract.** `strict` + `noUncheckedIndexedAccess` +
|
|
35
|
-
`exactOptionalPropertyTypes`. The published `.d.ts`
|
|
35
|
+
`exactOptionalPropertyTypes`. The published `.d.ts` _is_ the documentation, and the
|
|
36
36
|
[API reference](docs/api/README.md) is generated straight from it.
|
|
37
37
|
- **Synchronous, buffer-native I/O.** `readXlsx`/`writeXlsx` take and return a
|
|
38
38
|
`Uint8Array` — no streams to await for the buffered path, no Node `Buffer` assumption,
|
|
@@ -98,8 +98,10 @@ const wb2 = readXlsx(readFileSync('people.xlsx')); // a Buffer is a Uint8Array
|
|
|
98
98
|
`column.width = 12`, `column.key = 'name'`), and cells are reachable from it:
|
|
99
99
|
`row.getCell('B')`, `row.cells`, `row.values`.
|
|
100
100
|
- **`Cell`** — one cell. `cell.value` is the whole story: a `number`, `string`,
|
|
101
|
-
`boolean`, `Date`, `null` (empty), a formula (`{formula, result}`), rich text,
|
|
102
|
-
|
|
101
|
+
`boolean`, `Date`, `null` (empty), a formula (`{formula, result}`), rich text, or an
|
|
102
|
+
error — all precisely typed as [`CellValue`](docs/api/cell-values.md). A hyperlink is not
|
|
103
|
+
a value: it sits on the sheet, over any cell, through
|
|
104
|
+
`sheet.addHyperlink({ref: 'B2', target: 'https://example.com'})`.
|
|
103
105
|
|
|
104
106
|
```ts
|
|
105
107
|
sheet.getRow(1).height = 20;
|
|
@@ -112,7 +114,7 @@ for (const row of sheet.rows()) {
|
|
|
112
114
|
}
|
|
113
115
|
```
|
|
114
116
|
|
|
115
|
-
`Row` and `Column` are
|
|
117
|
+
`Row` and `Column` are _handles_, not snapshots: they read and write straight through to the
|
|
116
118
|
sheet, so two handles on the same line always agree, and reading one creates nothing — asking
|
|
117
119
|
about row 500 costs nothing and does not extend the used range. Position is fixed, exactly as a
|
|
118
120
|
`Cell`'s is: after a splice, `getRow(3)` still means row 3, now holding whatever moved there.
|
|
@@ -129,11 +131,13 @@ no API to author a new one — see [`docs/api/preserved.md`](docs/api/preserved.
|
|
|
129
131
|
|
|
130
132
|
```ts
|
|
131
133
|
import {
|
|
132
|
-
readXlsx,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
readXlsx,
|
|
135
|
+
writeXlsx, // buffered .xlsx (Uint8Array ⇄ Workbook)
|
|
136
|
+
readSheetRows, // stream one sheet's rows, bounded memory
|
|
137
|
+
readWorkbookStream, // stream every sheet, rows one at a time
|
|
138
|
+
readCsv,
|
|
139
|
+
writeCsv, // CSV as Uint8Array
|
|
140
|
+
writeCsvText, // CSV as a string
|
|
137
141
|
} from '@shbernal/ts-xlsx';
|
|
138
142
|
// The streaming writer opens files and pipes Node streams, so it is the one Node-only face
|
|
139
143
|
// and lives behind its own subpath — which is what keeps everything above browser-safe:
|
|
@@ -141,7 +145,10 @@ import {WorkbookStreamWriter} from '@shbernal/ts-xlsx/node';
|
|
|
141
145
|
|
|
142
146
|
// Bounded-memory extraction — the whole workbook is never materialized:
|
|
143
147
|
for (const row of readSheetRows(bytes, {sheet: 'People'})) {
|
|
144
|
-
console.log(
|
|
148
|
+
console.log(
|
|
149
|
+
row.number,
|
|
150
|
+
row.cells.map((c) => c.value),
|
|
151
|
+
);
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
// Bounded-memory generation — commit each row to serialize and free it as you go:
|
|
@@ -194,17 +201,17 @@ The bare package name gives you everything, and with a bundler that is the right
|
|
|
194
201
|
are for when you'd rather the module graph itself said which half of the library you depend
|
|
195
202
|
on — a Lambda with no bundler, a service that only classifies failures:
|
|
196
203
|
|
|
197
|
-
| Import from
|
|
198
|
-
|
|
|
199
|
-
| `@shbernal/ts-xlsx`
|
|
200
|
-
| `@shbernal/ts-xlsx/core`
|
|
201
|
-
| `@shbernal/ts-xlsx/xlsx`
|
|
202
|
-
| `@shbernal/ts-xlsx/xlsb`
|
|
203
|
-
| `@shbernal/ts-xlsx/csv`
|
|
204
|
-
| `@shbernal/ts-xlsx/node`
|
|
205
|
-
| `@shbernal/ts-xlsx/vba`
|
|
206
|
-
| `@shbernal/ts-xlsx/customui` | `parseCustomUi` and the ribbon types
|
|
207
|
-
| `@shbernal/ts-xlsx/errors`
|
|
204
|
+
| Import from | You get | It loads |
|
|
205
|
+
| ---------------------------- | ------------------------------------------------------------------------------ | -------- |
|
|
206
|
+
| `@shbernal/ts-xlsx` | everything except the streaming writer | 501 KB |
|
|
207
|
+
| `@shbernal/ts-xlsx/core` | `Workbook`, `Worksheet`, `Cell`, styles, values, addresses | 185 KB |
|
|
208
|
+
| `@shbernal/ts-xlsx/xlsx` | `readXlsx`, `writeXlsx`/`writeXlsxAsync`, the streaming reader, VBA part edits | 490 KB |
|
|
209
|
+
| `@shbernal/ts-xlsx/xlsb` | `readXlsb` | 256 KB |
|
|
210
|
+
| `@shbernal/ts-xlsx/csv` | `readCsv`, `writeCsv`, `writeCsvText` | 191 KB |
|
|
211
|
+
| `@shbernal/ts-xlsx/node` | `WorkbookStreamWriter` and the rest of the streaming writer | 358 KB |
|
|
212
|
+
| `@shbernal/ts-xlsx/vba` | `parseVbaProject`, `addVbaReference`, `removeVbaModule` | 46 KB |
|
|
213
|
+
| `@shbernal/ts-xlsx/customui` | `parseCustomUi` and the ribbon types | 15 KB |
|
|
214
|
+
| `@shbernal/ts-xlsx/errors` | every error class the library throws | 3 KB |
|
|
208
215
|
|
|
209
216
|
`/node` is the only one the root specifier does not carry, and the reason is its imports
|
|
210
217
|
rather than its size: it reaches `node:fs` and `node:stream`, and every other entry reaches
|
|
@@ -234,7 +241,7 @@ pnpm run docs
|
|
|
234
241
|
Read **[migrating from ExcelJS](docs/migrating-from-exceljs.md)**. The short version: the
|
|
235
242
|
shapes are deliberately different (synchronous `Uint8Array` I/O instead of async
|
|
236
243
|
`Buffer`/stream methods, `readXlsx`/`writeXlsx` free functions instead of
|
|
237
|
-
`workbook.xlsx.*`), because the goal was the
|
|
244
|
+
`workbook.xlsx.*`), because the goal was the _right_ API, not the familiar one.
|
|
238
245
|
|
|
239
246
|
## Hit a bug? There is a skill for that
|
|
240
247
|
|
package/dist/bytes.d.ts
CHANGED
|
@@ -18,8 +18,9 @@ export declare function concat(chunks: readonly Uint8Array[], size?: number): Ui
|
|
|
18
18
|
* Spelled out rather than delegated because both platform routes are unavailable here: `Buffer` is
|
|
19
19
|
* a Node global, which is exactly what a browser-safe module may not reach for, and `btoa` is
|
|
20
20
|
* declared deprecated in Node's types (so the `no-deprecated` lint rule rejects it) and takes a
|
|
21
|
-
* binary string rather than bytes anyway. The
|
|
22
|
-
*
|
|
21
|
+
* binary string rather than bytes anyway. The callers encode a 16-byte salt and a 64-byte hash, a
|
|
22
|
+
* sheet password's when one is set and a binary sheet's credential when one is read, so the loop below
|
|
23
|
+
* is not on any path where its cost is measurable.
|
|
23
24
|
*/
|
|
24
25
|
export declare function toBase64(bytes: Uint8Array): string;
|
|
25
26
|
/** A string's UTF-16LE bytes, code unit by code unit, with lone surrogates carried through. */
|
package/dist/core/address.d.ts
CHANGED
|
@@ -106,11 +106,18 @@ export declare function encodeCornerRef(col: number | undefined, row: number | u
|
|
|
106
106
|
* one of those sites.
|
|
107
107
|
*/
|
|
108
108
|
export declare function encodeRect(rect: GridRect): string;
|
|
109
|
+
/**
|
|
110
|
+
* A bounded rectangle as a formula's `ref` spells it: {@link encodeRect}'s `"B1:B3"`, except that one
|
|
111
|
+
* cell is its bare address, `"B1"`. Excel writes an array formula over a single cell that way, and a
|
|
112
|
+
* range that moves keeps the spelling it would be saved with.
|
|
113
|
+
*/
|
|
114
|
+
export declare function encodeRange(rect: GridRect): string;
|
|
109
115
|
/**
|
|
110
116
|
* Decode a single cell/row/column reference into `{address, col, row}`. Anchoring
|
|
111
117
|
* `$` signs are accepted and dropped; an absent axis is `undefined`.
|
|
112
118
|
*
|
|
113
119
|
* @throws {SyntaxError} if the reference mentions neither a column nor a row.
|
|
120
|
+
* @throws {RangeError} if it names a column past `XFD` or a row outside `1..1048576`.
|
|
114
121
|
*/
|
|
115
122
|
export declare function decodeAddress(reference: string): CellAddress;
|
|
116
123
|
/**
|
|
@@ -127,6 +134,7 @@ export interface CellPosition {
|
|
|
127
134
|
* Decode a reference that must name a single cell. Anchoring `$` signs are accepted and dropped.
|
|
128
135
|
*
|
|
129
136
|
* @throws {SyntaxError} if the reference is unparseable, or parses but omits an axis (`"A"`, `"1"`).
|
|
137
|
+
* @throws {RangeError} if it names a position off the grid (`"A0"`, `"XFE1"`).
|
|
130
138
|
*/
|
|
131
139
|
export declare function decodeCellRef(reference: string): CellPosition;
|
|
132
140
|
/**
|
|
@@ -160,10 +168,20 @@ export declare function boundedRect(range: RangeAddress): GridRect | undefined;
|
|
|
160
168
|
* range is unbounded, not unreadable, and a caller that needs a bounded rectangle says so itself.
|
|
161
169
|
*/
|
|
162
170
|
export declare function tryDecodeRange(reference: string): RangeAddress | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* The rectangle `reference` names when its top-left cell is (`col`, `row`), or `undefined` for a
|
|
173
|
+
* reference that does not parse, is unbounded, names a sheet, or starts at another cell. That is the
|
|
174
|
+
* shape of the range an array formula fills: Excel states the formula on the cell the range starts at,
|
|
175
|
+
* so a `ref` read from a file and one handed to the writer are both held to it.
|
|
176
|
+
*/
|
|
177
|
+
export declare function tryDecodeAnchoredRange(reference: string, col: number, row: number): GridRect | undefined;
|
|
163
178
|
/**
|
|
164
179
|
* Decode a range reference (`A1:B2`, `$1:$1`, `Sheet1!$A:$A`) into its corners and
|
|
165
180
|
* canonical dimensions. A single reference collapses to a degenerate range whose
|
|
166
181
|
* corners coincide.
|
|
182
|
+
*
|
|
183
|
+
* @throws {SyntaxError} if an endpoint is unparseable.
|
|
184
|
+
* @throws {RangeError} if an endpoint names a column past `XFD` or a row outside `1..1048576`.
|
|
167
185
|
*/
|
|
168
186
|
export declare function decodeRange(reference: string): RangeAddress;
|
|
169
187
|
/**
|
|
@@ -174,3 +192,21 @@ export declare function decodeRange(reference: string): RangeAddress;
|
|
|
174
192
|
* address naming a row Excel has no reference for while `encodeAddress(16385, 1)` refused.
|
|
175
193
|
*/
|
|
176
194
|
export declare function encodeAddress(col: number, row: number): string;
|
|
195
|
+
/**
|
|
196
|
+
* Does Excel read this name as a reference, where it expects a name? Such a name must be quoted as a
|
|
197
|
+
* sheet prefix (`'R1C1'!A1`), and cannot name a table at all.
|
|
198
|
+
*
|
|
199
|
+
* Four shapes, case-insensitive, all found by asking Excel (16.0 build 20326) rather than read off a
|
|
200
|
+
* specification, which states the rule as "not the same as a cell reference, and not `C` or `R`" and
|
|
201
|
+
* leaves the edges to the implementation:
|
|
202
|
+
*
|
|
203
|
+
* - an A1 cell **on the grid**: `T1`, `a01`, `XFD1048576`, but not `XFE1` or `A0`;
|
|
204
|
+
* - a whole R1C1 reference, each number optional and on the grid: `R`, `C`, `RC`, `R1C`, `R1C1`;
|
|
205
|
+
* - an R1C1 row or column number followed by a letter or underscore: `R1X`, `C1X`, `R1C1_`, but
|
|
206
|
+
* not `R1.5`, nor `RCX`, which carries no number;
|
|
207
|
+
* - `TRUE` or `FALSE`.
|
|
208
|
+
*
|
|
209
|
+
* Every name tried both ways was quoted in a formula exactly when a table by that name made Excel
|
|
210
|
+
* offer to repair the package, so the two rules are one.
|
|
211
|
+
*/
|
|
212
|
+
export declare function nameReadsAsReference(name: string): boolean;
|
package/dist/core/address.js
CHANGED
|
@@ -57,6 +57,11 @@ export function encodeCornerRef(col, row) {
|
|
|
57
57
|
export function encodeRect(rect) {
|
|
58
58
|
return `${encodeAddress(rect.left, rect.top)}:${encodeAddress(rect.right, rect.bottom)}`;
|
|
59
59
|
}
|
|
60
|
+
export function encodeRange(rect) {
|
|
61
|
+
return rect.top === rect.bottom && rect.left === rect.right
|
|
62
|
+
? encodeAddress(rect.left, rect.top)
|
|
63
|
+
: encodeRect(rect);
|
|
64
|
+
}
|
|
60
65
|
function makeCellAddress(col, row) {
|
|
61
66
|
return { address: encodeCornerRef(col, row), col, row };
|
|
62
67
|
}
|
|
@@ -72,6 +77,8 @@ export function decodeAddress(reference) {
|
|
|
72
77
|
}
|
|
73
78
|
const col = letters.length > 0 ? columnToNumber(letters) : undefined;
|
|
74
79
|
const row = digits.length > 0 ? Number.parseInt(digits, 10) : undefined;
|
|
80
|
+
if (row !== undefined)
|
|
81
|
+
assertRowInBounds(row);
|
|
75
82
|
return { address: `${letters}${digits}`, col, row };
|
|
76
83
|
}
|
|
77
84
|
export function decodeCellRef(reference) {
|
|
@@ -81,18 +88,13 @@ export function decodeCellRef(reference) {
|
|
|
81
88
|
}
|
|
82
89
|
return { col, row };
|
|
83
90
|
}
|
|
84
|
-
function rowCanExist(row) {
|
|
85
|
-
return row === undefined || (Number.isInteger(row) && row >= 1 && row <= MAX_ROW);
|
|
86
|
-
}
|
|
87
91
|
export function tryDecodeCellRef(reference) {
|
|
88
|
-
let position;
|
|
89
92
|
try {
|
|
90
|
-
|
|
93
|
+
return decodeCellRef(reference);
|
|
91
94
|
}
|
|
92
95
|
catch {
|
|
93
96
|
return undefined;
|
|
94
97
|
}
|
|
95
|
-
return rowCanExist(position.row) ? position : undefined;
|
|
96
98
|
}
|
|
97
99
|
export function boundedRect(range) {
|
|
98
100
|
const { top, left, bottom, right } = range;
|
|
@@ -102,14 +104,19 @@ export function boundedRect(range) {
|
|
|
102
104
|
return { top, left, bottom, right };
|
|
103
105
|
}
|
|
104
106
|
export function tryDecodeRange(reference) {
|
|
105
|
-
let range;
|
|
106
107
|
try {
|
|
107
|
-
|
|
108
|
+
return decodeRange(reference);
|
|
108
109
|
}
|
|
109
110
|
catch {
|
|
110
111
|
return undefined;
|
|
111
112
|
}
|
|
112
|
-
|
|
113
|
+
}
|
|
114
|
+
export function tryDecodeAnchoredRange(reference, col, row) {
|
|
115
|
+
const range = tryDecodeRange(reference);
|
|
116
|
+
if (range === undefined || range.sheetName !== undefined)
|
|
117
|
+
return undefined;
|
|
118
|
+
const rect = boundedRect(range);
|
|
119
|
+
return rect !== undefined && rect.left === col && rect.top === row ? rect : undefined;
|
|
113
120
|
}
|
|
114
121
|
export function decodeRange(reference) {
|
|
115
122
|
const prefix = SHEET_PREFIX.exec(reference);
|
|
@@ -146,3 +153,34 @@ export function encodeAddress(col, row) {
|
|
|
146
153
|
assertRowInBounds(row);
|
|
147
154
|
return `${numberToColumn(col)}${row}`;
|
|
148
155
|
}
|
|
156
|
+
function isLineNumber(digits, bound) {
|
|
157
|
+
const n = Number(digits);
|
|
158
|
+
return n >= 1 && n <= bound;
|
|
159
|
+
}
|
|
160
|
+
const A1_NAME = /^([A-Za-z]{1,3})(\d+)$/;
|
|
161
|
+
const R1C1_NAME = /^(?:R(\d*)C(\d*)|R(\d*)|C(\d*))$/i;
|
|
162
|
+
const R1C1_PREFIX = /^(?:R(\d+)|C(\d+))[A-Za-z_]/i;
|
|
163
|
+
export function nameReadsAsReference(name) {
|
|
164
|
+
const a1 = A1_NAME.exec(name);
|
|
165
|
+
if (a1 !== null) {
|
|
166
|
+
const [, letters = '', digits = ''] = a1;
|
|
167
|
+
if (tryColumnToNumber(letters.toUpperCase()) !== undefined && isLineNumber(digits, MAX_ROW)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const r1c1 = R1C1_NAME.exec(name);
|
|
172
|
+
if (r1c1 !== null) {
|
|
173
|
+
const [, row, column, rowOnly, columnOnly] = r1c1;
|
|
174
|
+
const on = (digits, bound) => digits === undefined || digits === '' || isLineNumber(digits, bound);
|
|
175
|
+
if (on(row ?? rowOnly, MAX_ROW) && on(column ?? columnOnly, MAX_COLUMN))
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
const prefix = R1C1_PREFIX.exec(name);
|
|
179
|
+
if (prefix !== null) {
|
|
180
|
+
const [, row, column] = prefix;
|
|
181
|
+
if (row !== undefined ? isLineNumber(row, MAX_ROW) : isLineNumber(column ?? '', MAX_COLUMN)) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return /^(?:TRUE|FALSE)$/i.test(name);
|
|
186
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CellValue } from './value.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Where a cell holding `value` places a formula, or `undefined` when the value is no formula. A shared
|
|
4
|
+
* formula's clone and a data table's cell count: each is a formula Excel will not find inside another
|
|
5
|
+
* array formula's range.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formulaPlacement(address: string, col: number, row: number, value: CellValue): FormulaPlacement | undefined;
|
|
8
|
+
/** A cell holding a formula of any kind, with the range it fills when it is an array formula. */
|
|
9
|
+
export interface FormulaPlacement {
|
|
10
|
+
readonly address: string;
|
|
11
|
+
readonly col: number;
|
|
12
|
+
readonly row: number;
|
|
13
|
+
/** The array formula's `ref`, as its value states it; absent for any other formula. */
|
|
14
|
+
readonly arrayRef?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/** An array formula whose range holds another formula's cell, or shares a cell with a range kept. */
|
|
17
|
+
export interface ArrayRangeConflict {
|
|
18
|
+
readonly array: FormulaPlacement;
|
|
19
|
+
/** The address of the other formula in the range, or of the array formula whose range it overlaps. */
|
|
20
|
+
readonly other: string;
|
|
21
|
+
readonly kind: 'formula-inside' | 'ranges-overlap';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The array formulas that cannot stand, deciding in reading order: an array formula conflicts when
|
|
25
|
+
* another formula's cell lies in its range, or when its range shares a cell with the range of an
|
|
26
|
+
* array formula decided before it that did not conflict. The array formulas left over hold ranges
|
|
27
|
+
* that share no cell with each other and hold no formula but their own, so reading each conflict as
|
|
28
|
+
* its plain formula leaves a sheet the writer accepts.
|
|
29
|
+
*
|
|
30
|
+
* A `ref` that names no bounded range starting at its own cell fills no cells here; the reader never
|
|
31
|
+
* produces one and the writer refuses it on its own terms.
|
|
32
|
+
*/
|
|
33
|
+
export declare function arrayRangeConflicts(placements: Iterable<FormulaPlacement>): ArrayRangeConflict[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { tryDecodeAnchoredRange } from './address.js';
|
|
2
|
+
import { MergeIndex } from './merge-index.js';
|
|
3
|
+
import { isArrayFormulaValue, isDataTableFormulaValue, isFormulaValue, isSharedFormulaValue, } from './value.js';
|
|
4
|
+
export function formulaPlacement(address, col, row, value) {
|
|
5
|
+
if (isArrayFormulaValue(value))
|
|
6
|
+
return { address, col, row, arrayRef: value.ref };
|
|
7
|
+
if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
|
|
8
|
+
return { address, col, row };
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
export function arrayRangeConflicts(placements) {
|
|
13
|
+
const all = [...placements];
|
|
14
|
+
const cellRects = [];
|
|
15
|
+
const placementOfCell = new Map();
|
|
16
|
+
const arrays = [];
|
|
17
|
+
for (const placement of all) {
|
|
18
|
+
const cell = {
|
|
19
|
+
top: placement.row,
|
|
20
|
+
left: placement.col,
|
|
21
|
+
bottom: placement.row,
|
|
22
|
+
right: placement.col,
|
|
23
|
+
};
|
|
24
|
+
cellRects.push(cell);
|
|
25
|
+
placementOfCell.set(cell, placement);
|
|
26
|
+
const range = placement.arrayRef === undefined
|
|
27
|
+
? undefined
|
|
28
|
+
: tryDecodeAnchoredRange(placement.arrayRef, placement.col, placement.row);
|
|
29
|
+
if (range !== undefined)
|
|
30
|
+
arrays.push({ placement, range });
|
|
31
|
+
}
|
|
32
|
+
arrays.sort((a, b) => a.placement.row - b.placement.row || a.placement.col - b.placement.col);
|
|
33
|
+
const cells = new MergeIndex(cellRects);
|
|
34
|
+
const keptRanges = [];
|
|
35
|
+
const kept = new MergeIndex(keptRanges);
|
|
36
|
+
const placementOfRange = new Map();
|
|
37
|
+
const conflicts = [];
|
|
38
|
+
for (const { placement, range } of arrays) {
|
|
39
|
+
const inside = formulaInside(range, cells);
|
|
40
|
+
if (inside !== undefined) {
|
|
41
|
+
const other = placementOfCell.get(inside)?.address ?? '';
|
|
42
|
+
conflicts.push({ array: placement, other, kind: 'formula-inside' });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const overlapped = kept.overlapping(range);
|
|
46
|
+
if (overlapped !== undefined) {
|
|
47
|
+
const other = placementOfRange.get(overlapped)?.address ?? '';
|
|
48
|
+
conflicts.push({ array: placement, other, kind: 'ranges-overlap' });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
keptRanges.push(range);
|
|
52
|
+
placementOfRange.set(range, placement);
|
|
53
|
+
kept.note(range);
|
|
54
|
+
}
|
|
55
|
+
return conflicts;
|
|
56
|
+
}
|
|
57
|
+
function formulaInside(range, cells) {
|
|
58
|
+
const below = { ...range, top: range.top + 1 };
|
|
59
|
+
const beside = { ...range, bottom: range.top, left: range.left + 1 };
|
|
60
|
+
return ((below.top <= below.bottom ? cells.overlapping(below) : undefined) ??
|
|
61
|
+
(beside.left <= beside.right ? cells.overlapping(beside) : undefined));
|
|
62
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hex } from '../hex.js';
|
|
2
2
|
import { parseArgb } from './style.js';
|
|
3
|
-
import { DEFAULT_THEME_COLOR_SCHEME, THEME_COLOR_SLOTS } from './theme.js';
|
|
3
|
+
import { DEFAULT_THEME_COLOR_SCHEME, isThemeRgb, THEME_COLOR_SLOTS, } from './theme.js';
|
|
4
4
|
export const DEFAULT_INDEXED_COLORS = [
|
|
5
5
|
'00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', '0000FFFF',
|
|
6
6
|
'00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', '0000FFFF',
|
|
@@ -32,7 +32,7 @@ function resolveBase(color, context) {
|
|
|
32
32
|
if (slot === undefined)
|
|
33
33
|
return undefined;
|
|
34
34
|
const value = (context.theme ?? DEFAULT_THEME_COLOR_SCHEME)[slot];
|
|
35
|
-
return value
|
|
35
|
+
return value !== undefined && isThemeRgb(value) ? `FF${value.toUpperCase()}` : undefined;
|
|
36
36
|
}
|
|
37
37
|
if (color.indexed !== undefined) {
|
|
38
38
|
if (SYSTEM_INDEXED_COLORS.has(color.indexed))
|
package/dist/core/column.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertColumnInBounds,
|
|
1
|
+
import { assertColumnInBounds, assertRowInBounds, numberToColumn } from './address.js';
|
|
2
2
|
import { AxisHandle } from './axis-handle.js';
|
|
3
3
|
import { INTERNAL } from './internal.js';
|
|
4
4
|
export class Column extends AxisHandle {
|
|
@@ -92,7 +92,8 @@ export class Column extends AxisHandle {
|
|
|
92
92
|
this.write('protection', protection);
|
|
93
93
|
}
|
|
94
94
|
getCell(row) {
|
|
95
|
-
|
|
95
|
+
assertRowInBounds(row);
|
|
96
|
+
return this.#sheet[INTERNAL].masterAt(row, this.index);
|
|
96
97
|
}
|
|
97
98
|
get cells() {
|
|
98
99
|
return this.#sheet[INTERNAL].columnCells(this.index);
|
|
@@ -55,11 +55,15 @@ export interface Comment {
|
|
|
55
55
|
/** Brace-wrapped GUID identifying this message, preserved verbatim from the file. */
|
|
56
56
|
readonly id: string;
|
|
57
57
|
/**
|
|
58
|
-
* Who wrote it, resolved through the workbook registry. Absent when the file recorded no author or
|
|
59
|
-
* named an id the registry does not hold
|
|
58
|
+
* Who wrote it, resolved through the workbook registry. Absent when the file recorded no author, or
|
|
59
|
+
* named an id the registry does not hold, which Excel treats the same way.
|
|
60
60
|
*/
|
|
61
61
|
readonly author?: Person;
|
|
62
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* The author's {@link Person.id}, which must be registered ({@link Workbook.addPerson}) by the time
|
|
64
|
+
* the workbook is written. Absent for a message with no recorded author, which is written under the
|
|
65
|
+
* null GUID Excel uses for one and shown by Excel as "Author".
|
|
66
|
+
*/
|
|
63
67
|
readonly personId?: string;
|
|
64
68
|
/**
|
|
65
69
|
* When it was written, verbatim. Excel writes local wall-clock with fractional seconds and no
|
|
@@ -7,6 +7,10 @@ export declare class ConditionalFormattingOverlay {
|
|
|
7
7
|
* range (`"A1:A10"`), a whole column, or several space-separated areas (`"A1:C1 A3:C3"`) sharing one
|
|
8
8
|
* rule set. The block is stored once against the range, defensively copied so the getter never hands
|
|
9
9
|
* back a reference into the caller's object.
|
|
10
|
+
*
|
|
11
|
+
* @throws {AuthoringError} when `formatting.ref` names no area at all. A rule set attached to
|
|
12
|
+
* nothing formats no cell and is written back as the same unreadable text, so it is a mistake worth
|
|
13
|
+
* surfacing at the call. The reader does not reach this: it drops such a block at its own boundary.
|
|
10
14
|
*/
|
|
11
15
|
add(formatting: ConditionalFormatting): void;
|
|
12
16
|
/** The conditional formattings on this sheet, each bound to its target range, in insertion order. */
|
|
@@ -16,6 +20,11 @@ export declare class ConditionalFormattingOverlay {
|
|
|
16
20
|
* it was written for. A rule set whose every target area fell inside a deleted span goes with them.
|
|
17
21
|
*/
|
|
18
22
|
shift(splice: AxisSplice): void;
|
|
23
|
+
/**
|
|
24
|
+
* Rewrite every rule's formulae, and the value of each scale anchor whose type is `formula`, through
|
|
25
|
+
* `rewrite`, keeping a rule set nothing in which changed as the same object.
|
|
26
|
+
*/
|
|
27
|
+
mapFormulas(rewrite: (formula: string) => string): void;
|
|
19
28
|
/** Drop every conditional formatting, leaving the overlay empty. */
|
|
20
29
|
clear(): void;
|
|
21
30
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
1
2
|
import { cloneConditionalFormatting } from './conditional-formatting.js';
|
|
2
3
|
import { replaceContents } from './containers.js';
|
|
3
|
-
import { shiftSqref } from './merge.js';
|
|
4
|
+
import { decodeSqrefRects, shiftSqref } from './merge.js';
|
|
4
5
|
export class ConditionalFormattingOverlay {
|
|
5
6
|
#entries = [];
|
|
6
7
|
add(formatting) {
|
|
8
|
+
if (decodeSqrefRects(formatting.ref).length === 0) {
|
|
9
|
+
throw new AuthoringError(`conditional formatting range ${quoted(formatting.ref)} names no cells`);
|
|
10
|
+
}
|
|
7
11
|
this.#entries.push(cloneConditionalFormatting(formatting));
|
|
8
12
|
}
|
|
9
13
|
get entries() {
|
|
@@ -18,6 +22,29 @@ export class ConditionalFormattingOverlay {
|
|
|
18
22
|
}
|
|
19
23
|
replaceContents(this.#entries, entries);
|
|
20
24
|
}
|
|
25
|
+
mapFormulas(rewrite) {
|
|
26
|
+
for (const [index, entry] of this.#entries.entries()) {
|
|
27
|
+
let changed = false;
|
|
28
|
+
const rules = entry.rules.map((rule) => {
|
|
29
|
+
const formulae = rule.formulae?.map((operand) => typeof operand === 'string' ? rewrite(operand) : operand);
|
|
30
|
+
const cfvo = rule.cfvo?.map((anchor) => anchor.type === 'formula' && typeof anchor.value === 'string'
|
|
31
|
+
? { ...anchor, value: rewrite(anchor.value) }
|
|
32
|
+
: anchor);
|
|
33
|
+
const formulaeMoved = formulae?.some((operand, i) => operand !== rule.formulae?.[i]) ?? false;
|
|
34
|
+
const cfvoMoved = cfvo?.some((anchor, i) => anchor.value !== rule.cfvo?.[i]?.value) ?? false;
|
|
35
|
+
if (!formulaeMoved && !cfvoMoved)
|
|
36
|
+
return rule;
|
|
37
|
+
changed = true;
|
|
38
|
+
return {
|
|
39
|
+
...rule,
|
|
40
|
+
...(formulae === undefined ? {} : { formulae }),
|
|
41
|
+
...(cfvo === undefined ? {} : { cfvo }),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
if (changed)
|
|
45
|
+
this.#entries[index] = { ...entry, rules };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
21
48
|
clear() {
|
|
22
49
|
this.#entries.length = 0;
|
|
23
50
|
}
|