@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
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:
|
|
@@ -149,12 +156,15 @@ const writer = new WorkbookStreamWriter();
|
|
|
149
156
|
const out = writer.addWorksheet('Big');
|
|
150
157
|
for (let i = 1; i <= 1_000_000; i++) out.addRow([i, i * i]).commit();
|
|
151
158
|
out.commit();
|
|
152
|
-
const packaged
|
|
159
|
+
const packaged = await writer.commit(); // Uint8Array here; also delivered via writer.stream
|
|
153
160
|
```
|
|
154
161
|
|
|
155
162
|
The streaming writer is asynchronous where the buffered path is synchronous: `commit()`
|
|
156
163
|
resolves to the package bytes and simultaneously pipes them through `writer.stream` (a Node
|
|
157
164
|
`Readable`), so `writer.stream.pipe(res)` streams a workbook straight to an HTTP response.
|
|
165
|
+
Hand the writer its own sink instead (`{stream}` or `{filename}`) and never touch
|
|
166
|
+
`writer.stream`, and `commit()` resolves with `undefined`: the archive goes to the sink and is
|
|
167
|
+
never assembled as one object, which is what passing a sink was for.
|
|
158
168
|
|
|
159
169
|
## Writing without blocking the event loop
|
|
160
170
|
|
|
@@ -191,17 +201,17 @@ The bare package name gives you everything, and with a bundler that is the right
|
|
|
191
201
|
are for when you'd rather the module graph itself said which half of the library you depend
|
|
192
202
|
on — a Lambda with no bundler, a service that only classifies failures:
|
|
193
203
|
|
|
194
|
-
| Import from
|
|
195
|
-
|
|
|
196
|
-
| `@shbernal/ts-xlsx`
|
|
197
|
-
| `@shbernal/ts-xlsx/core`
|
|
198
|
-
| `@shbernal/ts-xlsx/xlsx`
|
|
199
|
-
| `@shbernal/ts-xlsx/xlsb`
|
|
200
|
-
| `@shbernal/ts-xlsx/csv`
|
|
201
|
-
| `@shbernal/ts-xlsx/node`
|
|
202
|
-
| `@shbernal/ts-xlsx/vba`
|
|
203
|
-
| `@shbernal/ts-xlsx/customui` | `parseCustomUi` and the ribbon types
|
|
204
|
-
| `@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 |
|
|
205
215
|
|
|
206
216
|
`/node` is the only one the root specifier does not carry, and the reason is its imports
|
|
207
217
|
rather than its size: it reaches `node:fs` and `node:stream`, and every other entry reaches
|
|
@@ -231,7 +241,7 @@ pnpm run docs
|
|
|
231
241
|
Read **[migrating from ExcelJS](docs/migrating-from-exceljs.md)**. The short version: the
|
|
232
242
|
shapes are deliberately different (synchronous `Uint8Array` I/O instead of async
|
|
233
243
|
`Buffer`/stream methods, `readXlsx`/`writeXlsx` free functions instead of
|
|
234
|
-
`workbook.xlsx.*`), because the goal was the
|
|
244
|
+
`workbook.xlsx.*`), because the goal was the _right_ API, not the familiar one.
|
|
235
245
|
|
|
236
246
|
## Hit a bug? There is a skill for that
|
|
237
247
|
|
package/dist/bytes.d.ts
CHANGED
|
@@ -18,7 +18,15 @@ 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;
|
|
26
|
+
/** A string's UTF-16LE bytes, code unit by code unit, with lone surrogates carried through. */
|
|
27
|
+
export declare function utf16leBytes(text: string): Uint8Array;
|
|
28
|
+
/**
|
|
29
|
+
* Decode UTF-16LE code units. A trailing odd byte is dropped: these fields are length-prefixed by
|
|
30
|
+
* their producer, and half a code unit carries nothing to decode.
|
|
31
|
+
*/
|
|
32
|
+
export declare function decodeUtf16le(bytes: Uint8Array): string;
|
package/dist/bytes.js
CHANGED
|
@@ -32,3 +32,25 @@ export function toBase64(bytes) {
|
|
|
32
32
|
}
|
|
33
33
|
return out;
|
|
34
34
|
}
|
|
35
|
+
export function utf16leBytes(text) {
|
|
36
|
+
const bytes = new Uint8Array(text.length * 2);
|
|
37
|
+
for (let i = 0; i < text.length; i++) {
|
|
38
|
+
const unit = text.charCodeAt(i);
|
|
39
|
+
bytes[i * 2] = unit & 0xff;
|
|
40
|
+
bytes[i * 2 + 1] = unit >>> 8;
|
|
41
|
+
}
|
|
42
|
+
return bytes;
|
|
43
|
+
}
|
|
44
|
+
const CHARS_PER_BATCH = 4096;
|
|
45
|
+
export function decodeUtf16le(bytes) {
|
|
46
|
+
let text = '';
|
|
47
|
+
let batch = [];
|
|
48
|
+
for (let i = 0; i + 1 < bytes.length; i += 2) {
|
|
49
|
+
batch.push(bytes[i] | (bytes[i + 1] << 8));
|
|
50
|
+
if (batch.length === CHARS_PER_BATCH) {
|
|
51
|
+
text += String.fromCharCode(...batch);
|
|
52
|
+
batch = [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return batch.length > 0 ? text + String.fromCharCode(...batch) : text;
|
|
56
|
+
}
|
package/dist/core/address.d.ts
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
export declare const MAX_COLUMN = 16384;
|
|
3
3
|
/** Excel's row bound: 1 through 1048576. The other axis of {@link MAX_COLUMN}. */
|
|
4
4
|
export declare const MAX_ROW = 1048576;
|
|
5
|
+
/**
|
|
6
|
+
* The same two bounds, zero-based: the last valid *index* on each axis.
|
|
7
|
+
*
|
|
8
|
+
* BIFF12 counts from zero throughout, so its reader and its formula decoder both need these, and both
|
|
9
|
+
* were deriving them privately. Two derivations of one fact is one fewer than three, but it is still
|
|
10
|
+
* the shape where a change to a limit reaches some of its consequences. The subtraction belongs beside
|
|
11
|
+
* the number it subtracts from.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MAX_COLUMN_INDEX: number;
|
|
14
|
+
export declare const MAX_ROW_INDEX: number;
|
|
15
|
+
/**
|
|
16
|
+
* Refuse a coordinate that names no position on the grid.
|
|
17
|
+
*
|
|
18
|
+
* @throws {RangeError} unless `value` is an integer within the axis's bounds.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assertAxisInBounds(axis: 'row' | 'column', value: number): void;
|
|
5
21
|
/** @throws {RangeError} unless `n` is an integer in `1..MAX_COLUMN`. */
|
|
6
22
|
export declare function assertColumnInBounds(n: number): void;
|
|
7
23
|
/** @throws {RangeError} unless `n` is an integer in `1..MAX_ROW`. */
|
|
@@ -56,7 +72,19 @@ export interface RangeAddress {
|
|
|
56
72
|
*
|
|
57
73
|
* @throws {RangeError} unless `n` is an integer in `1..MAX_COLUMN`. */
|
|
58
74
|
export declare function numberToColumn(n: number): string;
|
|
59
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Convert column letters to a 1-based number (`"A" → 1`, `"AA" → 27`), or `undefined` when they name
|
|
77
|
+
* no column that can exist: not letters at all, or past `XFD` (`"ZZZ"` is well-formed and is column
|
|
78
|
+
* 18278, which the grid does not have). The tolerant half of the pair the module header describes,
|
|
79
|
+
* for a caller reading a file rather than a caller's own argument.
|
|
80
|
+
*/
|
|
81
|
+
export declare function tryColumnToNumber(letters: string): number | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Convert column letters to a 1-based number (`"A" → 1`, `"AA" → 27`).
|
|
84
|
+
*
|
|
85
|
+
* @throws {RangeError} if the letters are malformed or name a column past `XFD`. The two are
|
|
86
|
+
* separate messages because a caller fixes them differently.
|
|
87
|
+
*/
|
|
60
88
|
export declare function columnToNumber(letters: string): number;
|
|
61
89
|
/**
|
|
62
90
|
* One corner of a range, as its reference text. An axis the corner omits contributes nothing, so a
|
|
@@ -67,11 +95,29 @@ export declare function columnToNumber(letters: string): number;
|
|
|
67
95
|
* The unbounded counterpart to {@link encodeAddress}, which names one cell and so requires both.
|
|
68
96
|
*/
|
|
69
97
|
export declare function encodeCornerRef(col: number | undefined, row: number | undefined): string;
|
|
98
|
+
/**
|
|
99
|
+
* A bounded rectangle as its canonical `tl:br` A1 range, `"B2:D5"`. A one-cell rectangle still reads
|
|
100
|
+
* as `"B2:B2"`, which is the form Excel writes and the form every consumer of these refs parses.
|
|
101
|
+
*
|
|
102
|
+
* The rendering half of {@link boundedRect}, and it belongs beside it for the same reason: ten sites
|
|
103
|
+
* spelled `` `${encodeAddress(left, top)}:${encodeAddress(right, bottom)}` `` by hand, and the risk in
|
|
104
|
+
* that expression is not its length but its argument order. `encodeAddress` takes column first and a
|
|
105
|
+
* rectangle names its rows first, so the two orders are transposed with respect to each other at every
|
|
106
|
+
* one of those sites.
|
|
107
|
+
*/
|
|
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;
|
|
70
115
|
/**
|
|
71
116
|
* Decode a single cell/row/column reference into `{address, col, row}`. Anchoring
|
|
72
117
|
* `$` signs are accepted and dropped; an absent axis is `undefined`.
|
|
73
118
|
*
|
|
74
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`.
|
|
75
121
|
*/
|
|
76
122
|
export declare function decodeAddress(reference: string): CellAddress;
|
|
77
123
|
/**
|
|
@@ -88,6 +134,7 @@ export interface CellPosition {
|
|
|
88
134
|
* Decode a reference that must name a single cell. Anchoring `$` signs are accepted and dropped.
|
|
89
135
|
*
|
|
90
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"`).
|
|
91
138
|
*/
|
|
92
139
|
export declare function decodeCellRef(reference: string): CellPosition;
|
|
93
140
|
/**
|
|
@@ -98,6 +145,19 @@ export declare function decodeCellRef(reference: string): CellPosition;
|
|
|
98
145
|
* there is nothing here".
|
|
99
146
|
*/
|
|
100
147
|
export declare function tryDecodeCellRef(reference: string): CellPosition | undefined;
|
|
148
|
+
/**
|
|
149
|
+
* Narrow a decoded range to a {@link GridRect}, or `undefined` when either axis is unbounded.
|
|
150
|
+
*
|
|
151
|
+
* A whole-column reference (`A:A`) and a whole-row one (`1:1`) are legal range references whose
|
|
152
|
+
* corners are partly absent, so every caller that needs a real rectangle has to test all four. Doing
|
|
153
|
+
* that in seven places is the other half of the convention `GridRect` already exists to state once:
|
|
154
|
+
* the shape is declared here, and so is the one narrowing that produces it.
|
|
155
|
+
*
|
|
156
|
+
* A caller wanting to say something *different* about each axis (a `Range` refuses `A:A` and `1:1`
|
|
157
|
+
* with two distinct messages) still tests them separately, and one that only cares about the axis it
|
|
158
|
+
* is splicing still tests only that one. Neither is this question.
|
|
159
|
+
*/
|
|
160
|
+
export declare function boundedRect(range: RangeAddress): GridRect | undefined;
|
|
101
161
|
/**
|
|
102
162
|
* {@link decodeRange} for a reference that came out of a file: `undefined` for anything that does
|
|
103
163
|
* not name a region that can exist. The sibling of {@link tryDecodeCellRef} on the other arity: a
|
|
@@ -108,11 +168,45 @@ export declare function tryDecodeCellRef(reference: string): CellPosition | unde
|
|
|
108
168
|
* range is unbounded, not unreadable, and a caller that needs a bounded rectangle says so itself.
|
|
109
169
|
*/
|
|
110
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;
|
|
111
178
|
/**
|
|
112
179
|
* Decode a range reference (`A1:B2`, `$1:$1`, `Sheet1!$A:$A`) into its corners and
|
|
113
180
|
* canonical dimensions. A single reference collapses to a degenerate range whose
|
|
114
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`.
|
|
115
185
|
*/
|
|
116
186
|
export declare function decodeRange(reference: string): RangeAddress;
|
|
117
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* Encode a 1-based `col`/`row` pair into its canonical A1 address (`"B2"`).
|
|
189
|
+
*
|
|
190
|
+
* Both axes go through the shared guard. The column already did, through `numberToColumn`; the row
|
|
191
|
+
* checked only its lower bound in a message of its own, so `encodeAddress(1, 1048577)` produced an
|
|
192
|
+
* address naming a row Excel has no reference for while `encodeAddress(16385, 1)` refused.
|
|
193
|
+
*/
|
|
118
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
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
1
2
|
export const MAX_COLUMN = 16384;
|
|
2
3
|
export const MAX_ROW = 1048576;
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export const MAX_COLUMN_INDEX = MAX_COLUMN - 1;
|
|
5
|
+
export const MAX_ROW_INDEX = MAX_ROW - 1;
|
|
6
|
+
const AXIS_BOUND = { row: MAX_ROW, column: MAX_COLUMN };
|
|
7
|
+
export function assertAxisInBounds(axis, value) {
|
|
8
|
+
const bound = AXIS_BOUND[axis];
|
|
9
|
+
if (!Number.isInteger(value) || value < 1 || value > bound) {
|
|
10
|
+
throw new RangeError(`${axis} ${value} is out of bounds: Excel supports 1..${bound}`);
|
|
6
11
|
}
|
|
7
12
|
}
|
|
13
|
+
export function assertColumnInBounds(n) {
|
|
14
|
+
assertAxisInBounds('column', n);
|
|
15
|
+
}
|
|
8
16
|
export function assertRowInBounds(n) {
|
|
9
|
-
|
|
10
|
-
throw new RangeError(`row ${n} is out of bounds: Excel supports 1..${MAX_ROW}`);
|
|
11
|
-
}
|
|
17
|
+
assertAxisInBounds('row', n);
|
|
12
18
|
}
|
|
13
19
|
export function rectsOverlap(a, b) {
|
|
14
20
|
return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom;
|
|
@@ -26,72 +32,91 @@ export function numberToColumn(n) {
|
|
|
26
32
|
}
|
|
27
33
|
return letters;
|
|
28
34
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const COLUMN_LETTERS = /^[A-Z]{1,3}$/;
|
|
36
|
+
export function tryColumnToNumber(letters) {
|
|
37
|
+
if (!COLUMN_LETTERS.test(letters))
|
|
38
|
+
return undefined;
|
|
33
39
|
let n = 0;
|
|
34
40
|
for (let i = 0; i < letters.length; i++) {
|
|
35
|
-
|
|
36
|
-
if (code < 65 || code > 90) {
|
|
37
|
-
throw new RangeError(`invalid column letters: "${letters}"`);
|
|
38
|
-
}
|
|
39
|
-
n = n * 26 + (code - 64);
|
|
41
|
+
n = n * 26 + (letters.charCodeAt(i) - 64);
|
|
40
42
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
return n > MAX_COLUMN ? undefined : n;
|
|
44
|
+
}
|
|
45
|
+
export function columnToNumber(letters) {
|
|
46
|
+
const n = tryColumnToNumber(letters);
|
|
47
|
+
if (n !== undefined)
|
|
48
|
+
return n;
|
|
49
|
+
if (!COLUMN_LETTERS.test(letters)) {
|
|
50
|
+
throw new RangeError(`invalid column letters: ${quoted(letters)}`);
|
|
43
51
|
}
|
|
44
|
-
|
|
52
|
+
throw new RangeError(`column ${quoted(letters)} is out of bounds: Excel supports up to ${MAX_COLUMN} (XFD)`);
|
|
45
53
|
}
|
|
46
54
|
export function encodeCornerRef(col, row) {
|
|
47
55
|
return `${col !== undefined ? numberToColumn(col) : ''}${row !== undefined ? row : ''}`;
|
|
48
56
|
}
|
|
57
|
+
export function encodeRect(rect) {
|
|
58
|
+
return `${encodeAddress(rect.left, rect.top)}:${encodeAddress(rect.right, rect.bottom)}`;
|
|
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
|
+
}
|
|
49
65
|
function makeCellAddress(col, row) {
|
|
50
66
|
return { address: encodeCornerRef(col, row), col, row };
|
|
51
67
|
}
|
|
52
68
|
export function decodeAddress(reference) {
|
|
53
69
|
const match = SINGLE_REF.exec(reference);
|
|
54
70
|
if (!match) {
|
|
55
|
-
throw new SyntaxError(`invalid cell reference:
|
|
71
|
+
throw new SyntaxError(`invalid cell reference: ${quoted(reference)}`);
|
|
56
72
|
}
|
|
57
73
|
const letters = match[1] ?? '';
|
|
58
74
|
const digits = match[2] ?? '';
|
|
59
75
|
if (letters.length === 0 && digits.length === 0) {
|
|
60
|
-
throw new SyntaxError(`invalid cell reference:
|
|
76
|
+
throw new SyntaxError(`invalid cell reference: ${quoted(reference)}`);
|
|
61
77
|
}
|
|
62
78
|
const col = letters.length > 0 ? columnToNumber(letters) : undefined;
|
|
63
79
|
const row = digits.length > 0 ? Number.parseInt(digits, 10) : undefined;
|
|
80
|
+
if (row !== undefined)
|
|
81
|
+
assertRowInBounds(row);
|
|
64
82
|
return { address: `${letters}${digits}`, col, row };
|
|
65
83
|
}
|
|
66
84
|
export function decodeCellRef(reference) {
|
|
67
85
|
const { col, row } = decodeAddress(reference);
|
|
68
86
|
if (col === undefined || row === undefined) {
|
|
69
|
-
throw new SyntaxError(
|
|
87
|
+
throw new SyntaxError(`${quoted(reference)} is not a single-cell reference: it omits a column or row`);
|
|
70
88
|
}
|
|
71
89
|
return { col, row };
|
|
72
90
|
}
|
|
73
|
-
function rowCanExist(row) {
|
|
74
|
-
return row === undefined || (Number.isInteger(row) && row >= 1 && row <= MAX_ROW);
|
|
75
|
-
}
|
|
76
91
|
export function tryDecodeCellRef(reference) {
|
|
77
|
-
let position;
|
|
78
92
|
try {
|
|
79
|
-
|
|
93
|
+
return decodeCellRef(reference);
|
|
80
94
|
}
|
|
81
95
|
catch {
|
|
82
96
|
return undefined;
|
|
83
97
|
}
|
|
84
|
-
|
|
98
|
+
}
|
|
99
|
+
export function boundedRect(range) {
|
|
100
|
+
const { top, left, bottom, right } = range;
|
|
101
|
+
if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return { top, left, bottom, right };
|
|
85
105
|
}
|
|
86
106
|
export function tryDecodeRange(reference) {
|
|
87
|
-
let range;
|
|
88
107
|
try {
|
|
89
|
-
|
|
108
|
+
return decodeRange(reference);
|
|
90
109
|
}
|
|
91
110
|
catch {
|
|
92
111
|
return undefined;
|
|
93
112
|
}
|
|
94
|
-
|
|
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;
|
|
95
120
|
}
|
|
96
121
|
export function decodeRange(reference) {
|
|
97
122
|
const prefix = SHEET_PREFIX.exec(reference);
|
|
@@ -125,8 +150,37 @@ export function decodeRange(reference) {
|
|
|
125
150
|
};
|
|
126
151
|
}
|
|
127
152
|
export function encodeAddress(col, row) {
|
|
128
|
-
|
|
129
|
-
throw new RangeError(`row ${row} is out of bounds: rows start at 1`);
|
|
130
|
-
}
|
|
153
|
+
assertRowInBounds(row);
|
|
131
154
|
return `${numberToColumn(col)}${row}`;
|
|
132
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,3 +1,4 @@
|
|
|
1
|
+
import { type AxisSplice } from './grid-shift.ts';
|
|
1
2
|
/**
|
|
2
3
|
* A worksheet's autofilter: the filtered region plus any per-column criteria narrowing it. A bare
|
|
3
4
|
* range (no columns) is just the header-row dropdowns Excel draws; adding {@link FilterColumn}s
|
|
@@ -54,8 +55,8 @@ export declare const isCustomFilterOperator: (value: string) => value is CustomF
|
|
|
54
55
|
*/
|
|
55
56
|
export declare function canonicalizeAutoFilter(input: string | AutoFilter): AutoFilter;
|
|
56
57
|
/**
|
|
57
|
-
* Re-anchor a filter through a splice
|
|
58
|
-
*
|
|
58
|
+
* Re-anchor a filter through a splice, or drop it (`undefined`) when the splice deleted every line
|
|
59
|
+
* it covered.
|
|
59
60
|
*
|
|
60
61
|
* A row splice only moves the range. A column splice moves its left edge too, and a criterion is
|
|
61
62
|
* addressed by its offset from that edge rather than by an absolute column, so every offset is
|
|
@@ -63,4 +64,4 @@ export declare function canonicalizeAutoFilter(input: string | AutoFilter): Auto
|
|
|
63
64
|
* Left alone, those offsets would keep their old numbers and silently re-point each filter at a
|
|
64
65
|
* neighbouring column.
|
|
65
66
|
*/
|
|
66
|
-
export declare function shiftAutoFilter(filter: AutoFilter,
|
|
67
|
+
export declare function shiftAutoFilter(filter: AutoFilter, splice: AxisSplice): AutoFilter | undefined;
|