xlsx-formula-recalc 0.29.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/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) bilig contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# xlsx-formula-recalc
|
|
2
|
+
|
|
3
|
+
Recalculate XLSX formulas in Node.js without opening Excel, LibreOffice, or a browser.
|
|
4
|
+
|
|
5
|
+
This package is a narrow wrapper around Bilig WorkPaper for the high-friction Node XLSX workflow:
|
|
6
|
+
|
|
7
|
+
1. import an XLSX workbook,
|
|
8
|
+
2. edit input cells,
|
|
9
|
+
3. recalculate formulas,
|
|
10
|
+
4. read proof values,
|
|
11
|
+
5. export an updated XLSX.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install xlsx-formula-recalc
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## CLI
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npx xlsx-formula-recalc pricing.xlsx \
|
|
23
|
+
--set Inputs!B2=48 \
|
|
24
|
+
--set Inputs!B3=1500 \
|
|
25
|
+
--read Summary!B7 \
|
|
26
|
+
--out pricing.recalculated.xlsx \
|
|
27
|
+
--json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The CLI writes a recalculated workbook and prints readback values. Cell targets must be sheet-qualified A1 references such as `Inputs!B2` or `'Pricing Model'!F12`.
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { recalculateXlsx } from "xlsx-formula-recalc";
|
|
36
|
+
|
|
37
|
+
const result = recalculateXlsx(await fs.promises.readFile("pricing.xlsx"), {
|
|
38
|
+
edits: [
|
|
39
|
+
{ target: "Inputs!B2", value: 48 },
|
|
40
|
+
{ target: "Inputs!B3", value: 1500 },
|
|
41
|
+
],
|
|
42
|
+
reads: ["Summary!B7"],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await fs.promises.writeFile("pricing.recalculated.xlsx", result.xlsx);
|
|
46
|
+
console.log(result.reads["Summary!B7"]);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For the full workbook API, import `WorkPaper`, `importXlsx`, and `exportXlsx` from this package.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
Use this when a Node service needs deterministic formula readback after it changes XLSX inputs. It is not a full Excel clone: unsupported Excel functions, external workbook links, macros, and volatile functions may need review. Import warnings are returned in `result.warnings`.
|
|
54
|
+
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, extname, join, basename } from "node:path";
|
|
4
|
+
import { recalculateXlsx } from "./index.js";
|
|
5
|
+
try {
|
|
6
|
+
const options = parseCliArgs(process.argv.slice(2));
|
|
7
|
+
const result = recalculateXlsx(readFileSync(options.inputPath), {
|
|
8
|
+
fileName: basename(options.inputPath),
|
|
9
|
+
edits: options.edits,
|
|
10
|
+
reads: options.reads,
|
|
11
|
+
});
|
|
12
|
+
writeFileSync(options.outputPath, result.xlsx);
|
|
13
|
+
const summary = {
|
|
14
|
+
input: options.inputPath,
|
|
15
|
+
output: options.outputPath,
|
|
16
|
+
edits: options.edits.length,
|
|
17
|
+
reads: result.reads,
|
|
18
|
+
warnings: result.warnings,
|
|
19
|
+
verified: true,
|
|
20
|
+
};
|
|
21
|
+
if (options.json) {
|
|
22
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.log(`Recalculated ${options.inputPath} -> ${options.outputPath}`);
|
|
26
|
+
for (const [target, value] of Object.entries(result.reads)) {
|
|
27
|
+
console.log(`${target}: ${JSON.stringify(value)}`);
|
|
28
|
+
}
|
|
29
|
+
if (result.warnings.length > 0) {
|
|
30
|
+
console.log(`Warnings: ${result.warnings.length.toString()}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
37
|
+
}
|
|
38
|
+
function parseCliArgs(args) {
|
|
39
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
40
|
+
printHelp();
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
const inputPath = args[0];
|
|
44
|
+
if (!inputPath || inputPath.startsWith("-")) {
|
|
45
|
+
throw new Error("Expected input XLSX path");
|
|
46
|
+
}
|
|
47
|
+
const edits = [];
|
|
48
|
+
const reads = [];
|
|
49
|
+
let outputPath;
|
|
50
|
+
let json = false;
|
|
51
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
52
|
+
const arg = args[index];
|
|
53
|
+
switch (arg) {
|
|
54
|
+
case "--set":
|
|
55
|
+
edits.push(parseEdit(requireNextArg(args, index, "--set")));
|
|
56
|
+
index += 1;
|
|
57
|
+
break;
|
|
58
|
+
case "--read":
|
|
59
|
+
reads.push(requireNextArg(args, index, "--read"));
|
|
60
|
+
index += 1;
|
|
61
|
+
break;
|
|
62
|
+
case "--out":
|
|
63
|
+
case "-o":
|
|
64
|
+
outputPath = requireNextArg(args, index, arg);
|
|
65
|
+
index += 1;
|
|
66
|
+
break;
|
|
67
|
+
case "--json":
|
|
68
|
+
json = true;
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
throw new Error(`Unknown xlsx-recalc option: ${arg}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
inputPath,
|
|
76
|
+
outputPath: outputPath ?? defaultOutputPath(inputPath),
|
|
77
|
+
edits,
|
|
78
|
+
reads,
|
|
79
|
+
json,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function parseEdit(raw) {
|
|
83
|
+
const separator = raw.indexOf("=");
|
|
84
|
+
if (separator <= 0) {
|
|
85
|
+
throw new Error(`Expected --set value in Target=Value form, received: ${raw}`);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
target: raw.slice(0, separator),
|
|
89
|
+
value: parseRawCellContent(raw.slice(separator + 1)),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function parseRawCellContent(raw) {
|
|
93
|
+
if (raw === "null") {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (raw === "true") {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (raw === "false") {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (/^-?(?:\d+|\d*\.\d+)(?:e[+-]?\d+)?$/iu.test(raw)) {
|
|
103
|
+
return Number(raw);
|
|
104
|
+
}
|
|
105
|
+
return raw;
|
|
106
|
+
}
|
|
107
|
+
function requireNextArg(args, index, option) {
|
|
108
|
+
const value = args[index + 1];
|
|
109
|
+
if (!value || value.startsWith("--")) {
|
|
110
|
+
throw new Error(`Expected value after ${option}`);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function defaultOutputPath(inputPath) {
|
|
115
|
+
const extension = extname(inputPath);
|
|
116
|
+
const base = extension.length > 0 ? basename(inputPath, extension) : basename(inputPath);
|
|
117
|
+
return join(dirname(inputPath), `${base}.recalculated${extension || ".xlsx"}`);
|
|
118
|
+
}
|
|
119
|
+
function printHelp() {
|
|
120
|
+
console.log(`Usage: xlsx-recalc <input.xlsx> [options]
|
|
121
|
+
|
|
122
|
+
Options:
|
|
123
|
+
--set <Sheet!A1=value> Edit an input cell before recalculation. Repeatable.
|
|
124
|
+
--read <Sheet!A1> Read a recalculated cell after edits. Repeatable.
|
|
125
|
+
--out, -o <path> Output XLSX path. Defaults to <input>.recalculated.xlsx.
|
|
126
|
+
--json Print a JSON summary.
|
|
127
|
+
--help, -h Show this help.
|
|
128
|
+
`);
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,eAAe,EAA8B,MAAM,YAAY,CAAC;AAUzE,IAAI,CAAC;IACH,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,eAAe,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;QAC9D,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;QACrC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAC;IACH,aAAa,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG;QACd,KAAK,EAAE,OAAO,CAAC,SAAS;QACxB,MAAM,EAAE,OAAO,CAAC,UAAU;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM;QAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,IAAI;KACf,CAAC;IAEF,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,gBAAgB,OAAO,CAAC,SAAS,OAAO,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QAC1E,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3D,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;AACH,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACrB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,YAAY,CAAC,IAAuB;IAC3C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxE,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,UAA8B,CAAC;IACnC,IAAI,IAAI,GAAG,KAAK,CAAC;IAEjB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,OAAO;gBACV,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC5D,KAAK,IAAI,CAAC,CAAC;gBACX,MAAM;YACR,KAAK,QAAQ;gBACX,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAClD,KAAK,IAAI,CAAC,CAAC;gBACX,MAAM;YACR,KAAK,OAAO,CAAC;YACb,KAAK,IAAI;gBACP,UAAU,GAAG,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC9C,KAAK,IAAI,CAAC,CAAC;gBACX,MAAM;YACR,KAAK,QAAQ;gBACX,IAAI,GAAG,IAAI,CAAC;gBACZ,MAAM;YACR;gBACE,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO;QACL,SAAS;QACT,UAAU,EAAE,UAAU,IAAI,iBAAiB,CAAC,SAAS,CAAC;QACtD,KAAK;QACL,KAAK;QACL,IAAI;KACL,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,wDAAwD,GAAG,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;QAC/B,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW;IACtC,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,sCAAsC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,cAAc,CAAC,IAAuB,EAAE,KAAa,EAAE,MAAc;IAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,SAAiB;IAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACzF,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,gBAAgB,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;CAQb,CAAC,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { WorkPaper, type RawCellContent, type WorkPaperCellAddress, type WorkPaperChange, type WorkPaperConfig } from "@bilig/headless";
|
|
2
|
+
export { WorkPaper } from "@bilig/headless";
|
|
3
|
+
export { exportXlsx, importXlsx } from "@bilig/headless/xlsx";
|
|
4
|
+
type WorkPaperInstance = ReturnType<typeof WorkPaper.buildFromSnapshot>;
|
|
5
|
+
export type XlsxFormulaRecalcCellValue = ReturnType<WorkPaperInstance["getCellValue"]>;
|
|
6
|
+
export interface XlsxFormulaRecalcEdit {
|
|
7
|
+
readonly target: string;
|
|
8
|
+
readonly value: RawCellContent;
|
|
9
|
+
}
|
|
10
|
+
export interface XlsxFormulaRecalcOptions {
|
|
11
|
+
readonly fileName?: string;
|
|
12
|
+
readonly edits?: readonly XlsxFormulaRecalcEdit[];
|
|
13
|
+
readonly reads?: readonly string[];
|
|
14
|
+
readonly config?: WorkPaperConfig;
|
|
15
|
+
}
|
|
16
|
+
export interface XlsxFormulaRecalcResult {
|
|
17
|
+
readonly xlsx: Uint8Array;
|
|
18
|
+
readonly warnings: readonly string[];
|
|
19
|
+
readonly sheetNames: readonly string[];
|
|
20
|
+
readonly reads: Readonly<Record<string, XlsxFormulaRecalcCellValue>>;
|
|
21
|
+
readonly changes: readonly WorkPaperChange[];
|
|
22
|
+
}
|
|
23
|
+
export declare function recalculateXlsx(input: Uint8Array | ArrayBuffer | Buffer, options?: XlsxFormulaRecalcOptions): XlsxFormulaRecalcResult;
|
|
24
|
+
export declare function parseQualifiedCellTarget(workbook: WorkPaperInstance, target: string): WorkPaperCellAddress;
|
|
25
|
+
export declare function parseQualifiedA1(target: string): {
|
|
26
|
+
sheetName: string;
|
|
27
|
+
row: number;
|
|
28
|
+
col: number;
|
|
29
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { WorkPaper } from "@bilig/headless";
|
|
2
|
+
import { exportXlsx, importXlsx } from "@bilig/headless/xlsx";
|
|
3
|
+
export { WorkPaper } from "@bilig/headless";
|
|
4
|
+
export { exportXlsx, importXlsx } from "@bilig/headless/xlsx";
|
|
5
|
+
export function recalculateXlsx(input, options = {}) {
|
|
6
|
+
const imported = importXlsx(toUint8Array(input), options.fileName ?? "workbook.xlsx");
|
|
7
|
+
const workbook = WorkPaper.buildFromSnapshot(imported.snapshot, {
|
|
8
|
+
evaluationTimeoutMs: 30_000,
|
|
9
|
+
useColumnIndex: true,
|
|
10
|
+
...options.config,
|
|
11
|
+
});
|
|
12
|
+
try {
|
|
13
|
+
const changes = [];
|
|
14
|
+
for (const edit of options.edits ?? []) {
|
|
15
|
+
changes.push(...workbook.setCellContents(parseQualifiedCellTarget(workbook, edit.target), edit.value));
|
|
16
|
+
}
|
|
17
|
+
const reads = {};
|
|
18
|
+
for (const target of options.reads ?? []) {
|
|
19
|
+
reads[target] = workbook.getCellValue(parseQualifiedCellTarget(workbook, target));
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
xlsx: toUint8Array(exportXlsx(workbook.exportSnapshot())),
|
|
23
|
+
warnings: imported.warnings,
|
|
24
|
+
sheetNames: imported.sheetNames,
|
|
25
|
+
reads,
|
|
26
|
+
changes,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
workbook.dispose();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function parseQualifiedCellTarget(workbook, target) {
|
|
34
|
+
const parsed = parseQualifiedA1(target);
|
|
35
|
+
const sheet = workbook.getSheetId(parsed.sheetName);
|
|
36
|
+
if (sheet === undefined) {
|
|
37
|
+
throw new Error(`Unknown sheet in XLSX formula recalculation target: ${parsed.sheetName}`);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
sheet,
|
|
41
|
+
row: parsed.row,
|
|
42
|
+
col: parsed.col,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function parseQualifiedA1(target) {
|
|
46
|
+
const trimmed = target.trim();
|
|
47
|
+
const separator = findSheetSeparator(trimmed);
|
|
48
|
+
if (separator <= 0 || separator >= trimmed.length - 1) {
|
|
49
|
+
throw new Error(`Expected a sheet-qualified A1 target such as Inputs!B2, received: ${target}`);
|
|
50
|
+
}
|
|
51
|
+
const sheetName = unquoteSheetName(trimmed.slice(0, separator));
|
|
52
|
+
const a1 = trimmed.slice(separator + 1).replace(/\$/gu, "").toUpperCase();
|
|
53
|
+
const match = /^(?<col>[A-Z]+)(?<row>[1-9][0-9]*)$/u.exec(a1);
|
|
54
|
+
if (!match?.groups) {
|
|
55
|
+
throw new Error(`Expected a single A1 cell reference in target ${target}`);
|
|
56
|
+
}
|
|
57
|
+
const row = match.groups["row"];
|
|
58
|
+
const col = match.groups["col"];
|
|
59
|
+
if (!row || !col) {
|
|
60
|
+
throw new Error(`Expected a single A1 cell reference in target ${target}`);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
sheetName,
|
|
64
|
+
row: Number.parseInt(row, 10) - 1,
|
|
65
|
+
col: columnLettersToIndex(col),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function findSheetSeparator(target) {
|
|
69
|
+
let inQuote = false;
|
|
70
|
+
for (let index = 0; index < target.length; index += 1) {
|
|
71
|
+
const char = target[index];
|
|
72
|
+
if (char === "'") {
|
|
73
|
+
if (inQuote && target[index + 1] === "'") {
|
|
74
|
+
index += 1;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
inQuote = !inQuote;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === "!" && !inQuote) {
|
|
82
|
+
return index;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return -1;
|
|
86
|
+
}
|
|
87
|
+
function unquoteSheetName(rawSheetName) {
|
|
88
|
+
const trimmed = rawSheetName.trim();
|
|
89
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
90
|
+
return trimmed.slice(1, -1).replace(/''/gu, "'");
|
|
91
|
+
}
|
|
92
|
+
return trimmed;
|
|
93
|
+
}
|
|
94
|
+
function columnLettersToIndex(letters) {
|
|
95
|
+
let index = 0;
|
|
96
|
+
for (const char of letters) {
|
|
97
|
+
index = index * 26 + (char.charCodeAt(0) - 64);
|
|
98
|
+
}
|
|
99
|
+
return index - 1;
|
|
100
|
+
}
|
|
101
|
+
function toUint8Array(input) {
|
|
102
|
+
if (input instanceof Uint8Array) {
|
|
103
|
+
return input;
|
|
104
|
+
}
|
|
105
|
+
return new Uint8Array(input);
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xlsx-formula-recalc",
|
|
3
|
+
"version": "0.29.0",
|
|
4
|
+
"description": "Recalculate XLSX formulas in Node.js without Excel, LibreOffice, or browser automation.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"excel",
|
|
7
|
+
"excel-formulas",
|
|
8
|
+
"exceljs",
|
|
9
|
+
"formula-engine",
|
|
10
|
+
"formula-recalculation",
|
|
11
|
+
"node",
|
|
12
|
+
"sheetjs",
|
|
13
|
+
"spreadsheet",
|
|
14
|
+
"spreadsheet-formulas",
|
|
15
|
+
"workbook",
|
|
16
|
+
"workpaper",
|
|
17
|
+
"xlsx",
|
|
18
|
+
"xlsx-calc",
|
|
19
|
+
"xlsx-formula",
|
|
20
|
+
"xlsx-formula-recalculation",
|
|
21
|
+
"xlsx-populate",
|
|
22
|
+
"xlsx-recalc",
|
|
23
|
+
"xlsx-recalculation"
|
|
24
|
+
],
|
|
25
|
+
"homepage": "https://proompteng.github.io/bilig/xlsx-recalculation-proof.html",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/proompteng/bilig/issues"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/proompteng/bilig.git",
|
|
33
|
+
"directory": "packages/xlsx-formula-recalc"
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"xlsx-recalc": "./dist/cli.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"type": "module",
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"main": "./dist/index.js",
|
|
46
|
+
"types": "./dist/index.d.ts",
|
|
47
|
+
"exports": {
|
|
48
|
+
".": {
|
|
49
|
+
"types": "./dist/index.d.ts",
|
|
50
|
+
"import": "./dist/index.js"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@bilig/headless": "0.29.0"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22.0.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "pnpm --dir ../.. --filter @bilig/headless build && rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json",
|
|
64
|
+
"smoke": "node ./dist/cli.js --help"
|
|
65
|
+
}
|
|
66
|
+
}
|