@ricsam/excel-to-formula-engine 0.0.0 → 0.1.1
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 +179 -45
- package/dist/index.cjs +1611 -0
- package/dist/index.cjs.map +19 -0
- package/dist/index.mjs +1571 -0
- package/dist/index.mjs.map +19 -0
- package/dist/types/index.d.ts +36 -0
- package/dist/types/src/a1.d.ts +29 -0
- package/dist/types/src/convert.d.ts +23 -0
- package/dist/types/src/translate/conditional-format.d.ts +33 -0
- package/dist/types/src/translate/dates.d.ts +9 -0
- package/dist/types/src/translate/formula.d.ts +51 -0
- package/dist/types/src/types.d.ts +119 -0
- package/dist/types/src/xlsx/package.d.ts +26 -0
- package/dist/types/src/xlsx/styles.d.ts +22 -0
- package/dist/types/src/xlsx/workbook.d.ts +42 -0
- package/dist/types/src/xlsx/worksheet.d.ts +72 -0
- package/package.json +58 -6
package/README.md
CHANGED
|
@@ -1,45 +1,179 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
# excel-to-formula-engine
|
|
2
|
+
|
|
3
|
+
Convert an Excel `.xlsx` file into [`@ricsam/formula-engine`](https://github.com/ricsam/formula-engine)
|
|
4
|
+
`WorkbookData`, in the browser.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { excelToFormulaEngine } from "@ricsam/excel-to-formula-engine";
|
|
8
|
+
|
|
9
|
+
const data = await excelToFormulaEngine(blob);
|
|
10
|
+
engine.addWorkbook({ workbookName: "Budget", data });
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Pure TypeScript — no WASM, no native toolchain, no server round trip. An `.xlsx`
|
|
14
|
+
is a ZIP of XML, so it unzips with [fflate](https://github.com/101arrowz/fflate)
|
|
15
|
+
and parses with [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser).
|
|
16
|
+
Ships as ESM and CJS with type declarations; ~48 kB per bundle, dependencies
|
|
17
|
+
external.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun add @ricsam/excel-to-formula-engine @ricsam/formula-engine
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
`excelToFormulaEngine` accepts a `Blob` or `File` straight from a file input, or
|
|
28
|
+
raw bytes as an `ArrayBuffer` / `Uint8Array`.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { FormulaEngine } from "@ricsam/formula-engine";
|
|
32
|
+
import { excelToFormulaEngine } from "@ricsam/excel-to-formula-engine";
|
|
33
|
+
|
|
34
|
+
input.addEventListener("change", async () => {
|
|
35
|
+
const file = input.files?.[0];
|
|
36
|
+
if (!file) return;
|
|
37
|
+
|
|
38
|
+
const data = await excelToFormulaEngine(file);
|
|
39
|
+
|
|
40
|
+
const engine = FormulaEngine.buildEmpty();
|
|
41
|
+
engine.addWorkbook({ workbookName: file.name, data });
|
|
42
|
+
|
|
43
|
+
// Formulas are live — the engine recalculates them.
|
|
44
|
+
engine.getCellValue({
|
|
45
|
+
workbookName: file.name,
|
|
46
|
+
sheetName: "Data",
|
|
47
|
+
rowIndex: 4,
|
|
48
|
+
colIndex: 3,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The whole workbook loads as a single undo step, so one `Ctrl-Z` reverts an
|
|
54
|
+
import.
|
|
55
|
+
|
|
56
|
+
### Diagnostics
|
|
57
|
+
|
|
58
|
+
Anything that could not be translated is reported rather than silently dropped.
|
|
59
|
+
`excelToFormulaEngineDetailed` returns the diagnostics beside the data; they are
|
|
60
|
+
also stored in `data.workbookMetadata.diagnostics`.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const { data, diagnostics } = await excelToFormulaEngineDetailed(file);
|
|
64
|
+
|
|
65
|
+
for (const diagnostic of diagnostics) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`[${diagnostic.code}] ${diagnostic.sheetName}!${diagnostic.cellReference}`,
|
|
68
|
+
diagnostic.message
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Options
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
await excelToFormulaEngine(file, {
|
|
77
|
+
sheets: ["Summary"], // import only these sheets
|
|
78
|
+
styles: false, // skip fills, fonts, borders
|
|
79
|
+
conditionalFormatting: false, // skip conditional formatting
|
|
80
|
+
tables: false, // skip Excel tables
|
|
81
|
+
definedNames: false, // skip defined names
|
|
82
|
+
cellMetadata: false, // skip number formats and hyperlinks
|
|
83
|
+
convertDates: false, // keep date serials as raw numbers
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## What gets translated
|
|
88
|
+
|
|
89
|
+
| Excel | Engine | Notes |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| Cell values | Cell content | Strings, numbers, booleans and error values keep their type. |
|
|
92
|
+
| Formulas | Formulas | Kept as formulas, not as Excel's cached results — the engine recalculates. |
|
|
93
|
+
| Shared formulas | Expanded formulas | Excel stores one formula per group; each member gets its references offset. |
|
|
94
|
+
| Sheet order | Sheet order | Array order becomes tab order. |
|
|
95
|
+
| Cross-sheet references | Unchanged | `Data!D5` parses identically. |
|
|
96
|
+
| Excel tables | Tables | Structured references (`Sales[Amount]`) work. Tables without a header row are skipped. |
|
|
97
|
+
| Defined names | Named expressions | Sheet-scoped names stay sheet-scoped. Built-ins like `_xlnm.Print_Area` are skipped. |
|
|
98
|
+
| Fills, fonts, borders | `CellStyle` | Theme and indexed colours are resolved, including tints. |
|
|
99
|
+
| Conditional formatting | Conditional styles | `cellIs`, `expression`, text rules and colour scales. See below. |
|
|
100
|
+
| Dates | ISO strings | The engine has no date type, so `45306` becomes `"2024-01-15"`. |
|
|
101
|
+
| Number formats, hyperlinks | Cell metadata | Stored for your UI; the engine does not interpret them. |
|
|
102
|
+
| Merges, frozen panes, column widths, hidden sheets | Sheet metadata | Same — stored for your UI to apply. |
|
|
103
|
+
|
|
104
|
+
### Formulas the engine cannot evaluate
|
|
105
|
+
|
|
106
|
+
The engine implements around 40 functions. A formula using anything else — an
|
|
107
|
+
Excel file will often reach for `VLOOKUP`, `TEXT`, `ROUND` or `SUMPRODUCT` — is
|
|
108
|
+
**imported unchanged** and evaluates to `#NAME?`, with a warning diagnostic
|
|
109
|
+
naming the function and the cell.
|
|
110
|
+
|
|
111
|
+
This keeps the author's intent visible and editable in the cell, rather than
|
|
112
|
+
replacing it with a frozen copy of whatever Excel last calculated. Everything
|
|
113
|
+
needed to do the opposite is in the diagnostics if you prefer that trade.
|
|
114
|
+
|
|
115
|
+
### Conditional formatting
|
|
116
|
+
|
|
117
|
+
Excel's rule catalogue is much larger than the engine's, which has a formula
|
|
118
|
+
rule and a two-stop gradient. These map across:
|
|
119
|
+
|
|
120
|
+
- `cellIs` with every operator, including `between` / `notBetween`
|
|
121
|
+
- `expression` (custom formula rules)
|
|
122
|
+
- `containsText`, `notContainsText`, `beginsWith`, `endsWith`
|
|
123
|
+
- `containsBlanks`, `notContainsBlanks`
|
|
124
|
+
- `colorScale` → gradient (a three-colour scale loses its midpoint, with an
|
|
125
|
+
`info` diagnostic)
|
|
126
|
+
|
|
127
|
+
Data bars, icon sets, `top10`, `aboveAverage` and duplicate rules are reported
|
|
128
|
+
as `unsupported-conditional-format` and skipped.
|
|
129
|
+
|
|
130
|
+
### Not imported
|
|
131
|
+
|
|
132
|
+
Charts, images, shapes, pivot tables, macros, data validation, sparklines,
|
|
133
|
+
comments, and cell protection. The engine has no representation for them.
|
|
134
|
+
Password-protected and legacy `.xls` files are rejected with a clear error.
|
|
135
|
+
|
|
136
|
+
## Demo
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
bun run demo:serve
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Opens a page at <http://localhost:4321> where you can drop in a workbook and see
|
|
143
|
+
it recalculated live. `demo/sample.xlsx` is there to try.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
bun install
|
|
149
|
+
bun run fixtures # generate .xlsx test fixtures with ExcelJS
|
|
150
|
+
bun run test # regenerates fixtures, then runs the suite
|
|
151
|
+
bun run typecheck
|
|
152
|
+
bun run build # emit dist/ (ESM, CJS and .d.ts)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Fixtures are written by ExcelJS rather than hand-rolled XML, so the tests run
|
|
156
|
+
against real OOXML — shared strings, shared formulas, style indirection, table
|
|
157
|
+
parts and conditional formatting blocks as a spreadsheet application emits them.
|
|
158
|
+
They are generated rather than committed, so `bun run test` builds them first.
|
|
159
|
+
|
|
160
|
+
## Releasing
|
|
161
|
+
|
|
162
|
+
Releases go out from `main` through [changesets](https://github.com/changesets/changesets)
|
|
163
|
+
and npm [trusted publishing](https://docs.npmjs.com/trusted-publishers), so no
|
|
164
|
+
npm token is stored in the repository — the publish job mints a short-lived
|
|
165
|
+
OIDC token instead.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
bunx changeset # describe the change; pick patch / minor / major
|
|
169
|
+
bunx changeset version # fold pending changesets into package.json + CHANGELOG
|
|
170
|
+
git commit -am "Release x.y.z"
|
|
171
|
+
git push origin main # .github/workflows/publish.yml builds, tests, publishes
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`changeset publish` only publishes when the version in `package.json` is not
|
|
175
|
+
already on npm, so pushing unrelated commits to `main` is safe.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
ISC
|