@cj-tech-master/excelts 1.0.0 → 1.4.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/dist/browser/excelts.iife.js +2568 -1188
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +21 -19
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/stream/xlsx/workbook-reader.js +2 -2
- package/dist/cjs/stream/xlsx/workbook-writer.js +8 -4
- package/dist/cjs/utils/cell-format.js +815 -0
- package/dist/cjs/utils/cell-matrix.js +37 -2
- package/dist/cjs/utils/parse-sax.js +2 -2
- package/dist/cjs/utils/sheet-utils.js +615 -0
- package/dist/cjs/utils/stream-buf.js +15 -4
- package/dist/cjs/utils/unzip/buffer-stream.js +27 -0
- package/dist/cjs/utils/unzip/index.js +23 -0
- package/dist/cjs/utils/unzip/noop-stream.js +20 -0
- package/dist/cjs/utils/unzip/parse-buffer.js +60 -0
- package/dist/cjs/utils/unzip/parse-datetime.js +23 -0
- package/dist/cjs/utils/unzip/parse-extra-field.js +52 -0
- package/dist/cjs/utils/unzip/parse.js +340 -0
- package/dist/cjs/utils/unzip/pull-stream.js +145 -0
- package/dist/cjs/utils/utils.js +13 -17
- package/dist/cjs/utils/zip-stream.js +29 -33
- package/dist/cjs/xlsx/xlsx.js +1 -2
- package/dist/esm/index.browser.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/stream/xlsx/workbook-reader.js +2 -2
- package/dist/esm/stream/xlsx/workbook-writer.js +9 -5
- package/dist/esm/utils/cell-format.js +810 -0
- package/dist/esm/utils/cell-matrix.js +37 -2
- package/dist/esm/utils/parse-sax.js +1 -1
- package/dist/esm/utils/sheet-utils.js +595 -0
- package/dist/esm/utils/stream-buf.js +15 -4
- package/dist/esm/utils/unzip/buffer-stream.js +24 -0
- package/dist/esm/utils/unzip/index.js +12 -0
- package/dist/esm/utils/unzip/noop-stream.js +16 -0
- package/dist/esm/utils/unzip/parse-buffer.js +57 -0
- package/dist/esm/utils/unzip/parse-datetime.js +20 -0
- package/dist/esm/utils/unzip/parse-extra-field.js +49 -0
- package/dist/esm/utils/unzip/parse.js +332 -0
- package/dist/esm/utils/unzip/pull-stream.js +141 -0
- package/dist/esm/utils/utils.js +12 -16
- package/dist/esm/utils/zip-stream.js +30 -34
- package/dist/esm/xlsx/xlsx.js +1 -2
- package/dist/types/doc/column.d.ts +1 -1
- package/dist/types/doc/worksheet.d.ts +2 -2
- package/dist/types/index.browser.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/stream/xlsx/workbook-writer.d.ts +1 -0
- package/dist/types/utils/cell-format.d.ts +32 -0
- package/dist/types/utils/sheet-utils.d.ts +203 -0
- package/dist/types/utils/unzip/buffer-stream.d.ts +9 -0
- package/dist/types/utils/unzip/index.d.ts +12 -0
- package/dist/types/utils/unzip/noop-stream.d.ts +13 -0
- package/dist/types/utils/unzip/parse-buffer.d.ts +24 -0
- package/dist/types/utils/unzip/parse-datetime.d.ts +12 -0
- package/dist/types/utils/unzip/parse-extra-field.d.ts +18 -0
- package/dist/types/utils/unzip/parse.d.ts +70 -0
- package/dist/types/utils/unzip/pull-stream.d.ts +24 -0
- package/dist/types/utils/utils.d.ts +5 -2
- package/dist/types/utils/zip-stream.d.ts +5 -1
- package/package.json +35 -32
- package/dist/cjs/utils/browser-buffer-decode.js +0 -13
- package/dist/cjs/utils/browser-buffer-encode.js +0 -13
- package/dist/cjs/utils/browser.js +0 -6
- package/dist/esm/utils/browser-buffer-decode.js +0 -11
- package/dist/esm/utils/browser-buffer-encode.js +0 -11
- package/dist/esm/utils/browser.js +0 -3
- package/dist/types/utils/browser-buffer-decode.d.ts +0 -2
- package/dist/types/utils/browser-buffer-encode.d.ts +0 -2
- package/dist/types/utils/browser.d.ts +0 -1
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CellMatrix = void 0;
|
|
4
4
|
const col_cache_js_1 = require("./col-cache");
|
|
5
|
+
// Helper to check for prototype pollution
|
|
6
|
+
function isSafeKey(key) {
|
|
7
|
+
if (typeof key === "number") {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return key !== "__proto__" && key !== "constructor" && key !== "prototype";
|
|
11
|
+
}
|
|
12
|
+
// Safe deep clone that filters out prototype pollution keys
|
|
13
|
+
function safeDeepClone(obj) {
|
|
14
|
+
if (obj === null || typeof obj !== "object") {
|
|
15
|
+
return obj;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(obj)) {
|
|
18
|
+
return obj.map(item => safeDeepClone(item));
|
|
19
|
+
}
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const key of Object.keys(obj)) {
|
|
22
|
+
if (isSafeKey(key)) {
|
|
23
|
+
result[key] = safeDeepClone(obj[key]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
5
28
|
class CellMatrix {
|
|
6
29
|
constructor(template) {
|
|
7
30
|
this.template = template;
|
|
@@ -42,6 +65,9 @@ class CellMatrix {
|
|
|
42
65
|
return this.findRowCell(row, address, create);
|
|
43
66
|
}
|
|
44
67
|
getCellAt(sheetName, rowNumber, colNumber) {
|
|
68
|
+
if (!isSafeKey(sheetName)) {
|
|
69
|
+
throw new Error(`Invalid sheet name: ${sheetName}`);
|
|
70
|
+
}
|
|
45
71
|
const sheet = this.sheets[sheetName] || (this.sheets[sheetName] = []);
|
|
46
72
|
const row = sheet[rowNumber] || (sheet[rowNumber] = []);
|
|
47
73
|
const cell = row[colNumber] ||
|
|
@@ -92,7 +118,10 @@ class CellMatrix {
|
|
|
92
118
|
}
|
|
93
119
|
findSheet(address, create) {
|
|
94
120
|
const name = address.sheetName;
|
|
95
|
-
if (
|
|
121
|
+
if (!isSafeKey(name)) {
|
|
122
|
+
throw new Error(`Invalid sheet name: ${name}`);
|
|
123
|
+
}
|
|
124
|
+
if (Object.prototype.hasOwnProperty.call(this.sheets, name)) {
|
|
96
125
|
return this.sheets[name];
|
|
97
126
|
}
|
|
98
127
|
if (create) {
|
|
@@ -102,6 +131,9 @@ class CellMatrix {
|
|
|
102
131
|
}
|
|
103
132
|
findSheetRow(sheet, address, create) {
|
|
104
133
|
const { row } = address;
|
|
134
|
+
if (!isSafeKey(row)) {
|
|
135
|
+
throw new Error(`Invalid row: ${row}`);
|
|
136
|
+
}
|
|
105
137
|
if (sheet && sheet[row]) {
|
|
106
138
|
return sheet[row];
|
|
107
139
|
}
|
|
@@ -112,12 +144,15 @@ class CellMatrix {
|
|
|
112
144
|
}
|
|
113
145
|
findRowCell(row, address, create) {
|
|
114
146
|
const { col } = address;
|
|
147
|
+
if (!isSafeKey(col)) {
|
|
148
|
+
throw new Error(`Invalid column: ${col}`);
|
|
149
|
+
}
|
|
115
150
|
if (row && row[col]) {
|
|
116
151
|
return row[col];
|
|
117
152
|
}
|
|
118
153
|
if (create) {
|
|
119
154
|
return (row[col] = this.template
|
|
120
|
-
?
|
|
155
|
+
? { ...address, ...safeDeepClone(this.template) }
|
|
121
156
|
: address);
|
|
122
157
|
}
|
|
123
158
|
return undefined;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseSax = parseSax;
|
|
4
4
|
const saxes_1 = require("saxes");
|
|
5
|
-
const
|
|
5
|
+
const utils_js_1 = require("./utils");
|
|
6
6
|
async function* parseSax(iterable) {
|
|
7
7
|
const saxesParser = new saxes_1.SaxesParser({
|
|
8
8
|
xmlns: false,
|
|
@@ -17,7 +17,7 @@ async function* parseSax(iterable) {
|
|
|
17
17
|
saxesParser.on("text", (value) => events.push({ eventType: "text", value }));
|
|
18
18
|
saxesParser.on("closetag", (value) => events.push({ eventType: "closetag", value }));
|
|
19
19
|
for await (const chunk of iterable) {
|
|
20
|
-
saxesParser.write((0,
|
|
20
|
+
saxesParser.write((0, utils_js_1.bufferToString)(chunk));
|
|
21
21
|
// saxesParser.write and saxesParser.on() are synchronous,
|
|
22
22
|
// so we can only reach the below line once all events have been emitted
|
|
23
23
|
if (error) {
|
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utility functions for ExcelTS
|
|
4
|
+
* Provides convenient helper functions for common spreadsheet operations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.utils = void 0;
|
|
8
|
+
exports.decodeCol = decodeCol;
|
|
9
|
+
exports.encodeCol = encodeCol;
|
|
10
|
+
exports.decodeRow = decodeRow;
|
|
11
|
+
exports.encodeRow = encodeRow;
|
|
12
|
+
exports.decodeCell = decodeCell;
|
|
13
|
+
exports.encodeCell = encodeCell;
|
|
14
|
+
exports.decodeRange = decodeRange;
|
|
15
|
+
exports.encodeRange = encodeRange;
|
|
16
|
+
exports.jsonToSheet = jsonToSheet;
|
|
17
|
+
exports.sheetAddJson = sheetAddJson;
|
|
18
|
+
exports.sheetToJson = sheetToJson;
|
|
19
|
+
exports.sheetToCsv = sheetToCsv;
|
|
20
|
+
exports.bookNew = bookNew;
|
|
21
|
+
exports.bookAppendSheet = bookAppendSheet;
|
|
22
|
+
exports.aoaToSheet = aoaToSheet;
|
|
23
|
+
exports.sheetAddAoa = sheetAddAoa;
|
|
24
|
+
exports.sheetToAoa = sheetToAoa;
|
|
25
|
+
const workbook_js_1 = require("../doc/workbook");
|
|
26
|
+
const col_cache_js_1 = require("./col-cache");
|
|
27
|
+
const utils_js_1 = require("./utils");
|
|
28
|
+
const cell_format_js_1 = require("./cell-format");
|
|
29
|
+
/**
|
|
30
|
+
* Get formatted display text for a cell value
|
|
31
|
+
* Returns the value formatted according to the cell's numFmt
|
|
32
|
+
* This matches Excel's display exactly
|
|
33
|
+
*/
|
|
34
|
+
function getCellDisplayText(cell) {
|
|
35
|
+
const value = cell.value;
|
|
36
|
+
const fmt = cell.numFmt || "General";
|
|
37
|
+
// Null/undefined
|
|
38
|
+
if (value == null) {
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
// Date object - convert to Excel serial number
|
|
42
|
+
if (value instanceof Date) {
|
|
43
|
+
const serial = (0, utils_js_1.dateToExcel)(value, false);
|
|
44
|
+
return (0, cell_format_js_1.format)(fmt, serial);
|
|
45
|
+
}
|
|
46
|
+
// Number/Boolean/String - let cellFormat handle it
|
|
47
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
48
|
+
return (0, cell_format_js_1.format)(fmt, value);
|
|
49
|
+
}
|
|
50
|
+
// Fallback to cell.text for other types (rich text, hyperlink, error, formula, etc.)
|
|
51
|
+
return cell.text;
|
|
52
|
+
}
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Cell Address Encoding/Decoding
|
|
55
|
+
// =============================================================================
|
|
56
|
+
/**
|
|
57
|
+
* Decode column string to 0-indexed number
|
|
58
|
+
* @example decodeCol("A") => 0, decodeCol("Z") => 25, decodeCol("AA") => 26
|
|
59
|
+
*/
|
|
60
|
+
function decodeCol(colstr) {
|
|
61
|
+
return col_cache_js_1.colCache.l2n(colstr.toUpperCase()) - 1;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Encode 0-indexed column number to string
|
|
65
|
+
* @example encodeCol(0) => "A", encodeCol(25) => "Z", encodeCol(26) => "AA"
|
|
66
|
+
*/
|
|
67
|
+
function encodeCol(col) {
|
|
68
|
+
return col_cache_js_1.colCache.n2l(col + 1);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Decode row string to 0-indexed number
|
|
72
|
+
* @example decodeRow("1") => 0, decodeRow("10") => 9
|
|
73
|
+
*/
|
|
74
|
+
function decodeRow(rowstr) {
|
|
75
|
+
return parseInt(rowstr, 10) - 1;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Encode 0-indexed row number to string
|
|
79
|
+
* @example encodeRow(0) => "1", encodeRow(9) => "10"
|
|
80
|
+
*/
|
|
81
|
+
function encodeRow(row) {
|
|
82
|
+
return String(row + 1);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Decode cell address string to CellAddress object
|
|
86
|
+
* @example decodeCell("A1") => {c: 0, r: 0}, decodeCell("B2") => {c: 1, r: 1}
|
|
87
|
+
*/
|
|
88
|
+
function decodeCell(cstr) {
|
|
89
|
+
const addr = col_cache_js_1.colCache.decodeAddress(cstr.toUpperCase());
|
|
90
|
+
return { c: addr.col - 1, r: addr.row - 1 };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Encode CellAddress object to cell address string
|
|
94
|
+
* @example encodeCell({c: 0, r: 0}) => "A1", encodeCell({c: 1, r: 1}) => "B2"
|
|
95
|
+
*/
|
|
96
|
+
function encodeCell(cell) {
|
|
97
|
+
return col_cache_js_1.colCache.encodeAddress(cell.r + 1, cell.c + 1);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Decode range string to Range object
|
|
101
|
+
* @example decodeRange("A1:B2") => {s: {c: 0, r: 0}, e: {c: 1, r: 1}}
|
|
102
|
+
*/
|
|
103
|
+
function decodeRange(range) {
|
|
104
|
+
const idx = range.indexOf(":");
|
|
105
|
+
if (idx === -1) {
|
|
106
|
+
const cell = decodeCell(range);
|
|
107
|
+
return { s: cell, e: { ...cell } };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
s: decodeCell(range.slice(0, idx)),
|
|
111
|
+
e: decodeCell(range.slice(idx + 1))
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function encodeRange(startOrRange, end) {
|
|
115
|
+
if (end === undefined) {
|
|
116
|
+
const range = startOrRange;
|
|
117
|
+
return encodeRange(range.s, range.e);
|
|
118
|
+
}
|
|
119
|
+
const start = startOrRange;
|
|
120
|
+
const startStr = encodeCell(start);
|
|
121
|
+
const endStr = encodeCell(end);
|
|
122
|
+
return startStr === endStr ? startStr : `${startStr}:${endStr}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Create a worksheet from an array of objects (xlsx compatible)
|
|
126
|
+
* @example
|
|
127
|
+
* const ws = jsonToSheet([{name: "Alice", age: 30}, {name: "Bob", age: 25}])
|
|
128
|
+
*/
|
|
129
|
+
function jsonToSheet(data, opts) {
|
|
130
|
+
const o = opts || {};
|
|
131
|
+
// Create a temporary workbook to get a worksheet
|
|
132
|
+
const tempWb = new workbook_js_1.Workbook();
|
|
133
|
+
const worksheet = tempWb.addWorksheet("Sheet1");
|
|
134
|
+
if (data.length === 0) {
|
|
135
|
+
return worksheet;
|
|
136
|
+
}
|
|
137
|
+
// Determine headers - use provided header or Object.keys from first object
|
|
138
|
+
const allKeys = new Set();
|
|
139
|
+
data.forEach(row => Object.keys(row).forEach(k => allKeys.add(k)));
|
|
140
|
+
const headers = o.header ? [...o.header] : [...allKeys];
|
|
141
|
+
// Add any missing keys from data that weren't in header
|
|
142
|
+
if (o.header) {
|
|
143
|
+
allKeys.forEach(k => {
|
|
144
|
+
if (!headers.includes(k)) {
|
|
145
|
+
headers.push(k);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
let rowNum = 1;
|
|
150
|
+
// Write header row
|
|
151
|
+
if (!o.skipHeader) {
|
|
152
|
+
headers.forEach((h, colIdx) => {
|
|
153
|
+
worksheet.getCell(rowNum, colIdx + 1).value = h;
|
|
154
|
+
});
|
|
155
|
+
rowNum++;
|
|
156
|
+
}
|
|
157
|
+
// Write data rows
|
|
158
|
+
for (const row of data) {
|
|
159
|
+
headers.forEach((key, colIdx) => {
|
|
160
|
+
const val = row[key];
|
|
161
|
+
if (val === null && o.nullError) {
|
|
162
|
+
worksheet.getCell(rowNum, colIdx + 1).value = { error: "#NULL!" };
|
|
163
|
+
}
|
|
164
|
+
else if (val !== undefined && val !== null) {
|
|
165
|
+
worksheet.getCell(rowNum, colIdx + 1).value = val;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
rowNum++;
|
|
169
|
+
}
|
|
170
|
+
return worksheet;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Add data from an array of objects to an existing worksheet (xlsx compatible)
|
|
174
|
+
*/
|
|
175
|
+
function sheetAddJson(worksheet, data, opts) {
|
|
176
|
+
const o = opts || {};
|
|
177
|
+
if (data.length === 0) {
|
|
178
|
+
return worksheet;
|
|
179
|
+
}
|
|
180
|
+
// Determine starting position
|
|
181
|
+
let startRow = 1;
|
|
182
|
+
let startCol = 1;
|
|
183
|
+
if (o.origin !== undefined) {
|
|
184
|
+
if (typeof o.origin === "string") {
|
|
185
|
+
const addr = decodeCell(o.origin);
|
|
186
|
+
startRow = addr.r + 1;
|
|
187
|
+
startCol = addr.c + 1;
|
|
188
|
+
}
|
|
189
|
+
else if (typeof o.origin === "number") {
|
|
190
|
+
if (o.origin === -1) {
|
|
191
|
+
// Append to bottom
|
|
192
|
+
startRow = worksheet.rowCount + 1;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
startRow = o.origin + 1; // 0-indexed row
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
startRow = o.origin.r + 1;
|
|
200
|
+
startCol = o.origin.c + 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Determine headers
|
|
204
|
+
const allKeys = new Set();
|
|
205
|
+
data.forEach(row => Object.keys(row).forEach(k => allKeys.add(k)));
|
|
206
|
+
const headers = o.header ? [...o.header] : [...allKeys];
|
|
207
|
+
if (o.header) {
|
|
208
|
+
allKeys.forEach(k => {
|
|
209
|
+
if (!headers.includes(k)) {
|
|
210
|
+
headers.push(k);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
let rowNum = startRow;
|
|
215
|
+
// Write header row
|
|
216
|
+
if (!o.skipHeader) {
|
|
217
|
+
headers.forEach((h, colIdx) => {
|
|
218
|
+
worksheet.getCell(rowNum, startCol + colIdx).value = h;
|
|
219
|
+
});
|
|
220
|
+
rowNum++;
|
|
221
|
+
}
|
|
222
|
+
// Write data rows
|
|
223
|
+
for (const row of data) {
|
|
224
|
+
headers.forEach((key, colIdx) => {
|
|
225
|
+
const val = row[key];
|
|
226
|
+
if (val === null && o.nullError) {
|
|
227
|
+
worksheet.getCell(rowNum, startCol + colIdx).value = { error: "#NULL!" };
|
|
228
|
+
}
|
|
229
|
+
else if (val !== undefined && val !== null) {
|
|
230
|
+
worksheet.getCell(rowNum, startCol + colIdx).value = val;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
rowNum++;
|
|
234
|
+
}
|
|
235
|
+
return worksheet;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Convert worksheet to JSON array (xlsx compatible)
|
|
239
|
+
* @example
|
|
240
|
+
* // Default: array of objects with first row as headers
|
|
241
|
+
* const data = sheetToJson(worksheet)
|
|
242
|
+
* // => [{name: "Alice", age: 30}, {name: "Bob", age: 25}]
|
|
243
|
+
*
|
|
244
|
+
* // Array of arrays
|
|
245
|
+
* const aoa = sheetToJson(worksheet, { header: 1 })
|
|
246
|
+
* // => [["name", "age"], ["Alice", 30], ["Bob", 25]]
|
|
247
|
+
*
|
|
248
|
+
* // Column letters as keys
|
|
249
|
+
* const cols = sheetToJson(worksheet, { header: "A" })
|
|
250
|
+
* // => [{A: "name", B: "age"}, {A: "Alice", B: 30}]
|
|
251
|
+
*/
|
|
252
|
+
function sheetToJson(worksheet, opts) {
|
|
253
|
+
const o = opts || {};
|
|
254
|
+
// Determine range
|
|
255
|
+
let startRow = 1;
|
|
256
|
+
let endRow = worksheet.rowCount;
|
|
257
|
+
let startCol = 1;
|
|
258
|
+
let endCol = worksheet.columnCount;
|
|
259
|
+
if (o.range !== undefined) {
|
|
260
|
+
if (typeof o.range === "number") {
|
|
261
|
+
startRow = o.range + 1; // 0-indexed to 1-indexed
|
|
262
|
+
}
|
|
263
|
+
else if (typeof o.range === "string") {
|
|
264
|
+
const r = decodeRange(o.range);
|
|
265
|
+
startRow = r.s.r + 1;
|
|
266
|
+
endRow = r.e.r + 1;
|
|
267
|
+
startCol = r.s.c + 1;
|
|
268
|
+
endCol = r.e.c + 1;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (endRow < startRow || endCol < startCol) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
// Handle header option
|
|
275
|
+
const headerOpt = o.header;
|
|
276
|
+
// header: 1 - return array of arrays
|
|
277
|
+
if (headerOpt === 1) {
|
|
278
|
+
const result = [];
|
|
279
|
+
// Default for header:1 is to include blank rows
|
|
280
|
+
const includeBlank = o.blankrows !== false;
|
|
281
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
282
|
+
const rowData = [];
|
|
283
|
+
let isEmpty = true;
|
|
284
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
285
|
+
const cell = worksheet.getCell(row, col);
|
|
286
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
287
|
+
if (val != null && val !== "") {
|
|
288
|
+
rowData[col - startCol] = val;
|
|
289
|
+
isEmpty = false;
|
|
290
|
+
}
|
|
291
|
+
else if (o.defval !== undefined) {
|
|
292
|
+
rowData[col - startCol] = o.defval;
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
rowData[col - startCol] = null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (!isEmpty || includeBlank) {
|
|
299
|
+
result.push(rowData);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
// header: "A" - use column letters as keys
|
|
305
|
+
if (headerOpt === "A") {
|
|
306
|
+
const result = [];
|
|
307
|
+
// Default for header:"A" is to skip blank rows
|
|
308
|
+
const includeBlank = o.blankrows === true;
|
|
309
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
310
|
+
const rowData = {};
|
|
311
|
+
let isEmpty = true;
|
|
312
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
313
|
+
const cell = worksheet.getCell(row, col);
|
|
314
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
315
|
+
const key = encodeCol(col - 1); // 0-indexed for encodeCol
|
|
316
|
+
if (val != null && val !== "") {
|
|
317
|
+
rowData[key] = val;
|
|
318
|
+
isEmpty = false;
|
|
319
|
+
}
|
|
320
|
+
else if (o.defval !== undefined) {
|
|
321
|
+
rowData[key] = o.defval;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (!isEmpty || includeBlank) {
|
|
325
|
+
result.push(rowData);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
// header: string[] - use provided array as keys
|
|
331
|
+
if (Array.isArray(headerOpt)) {
|
|
332
|
+
const result = [];
|
|
333
|
+
const includeBlank = o.blankrows === true;
|
|
334
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
335
|
+
const rowData = {};
|
|
336
|
+
let isEmpty = true;
|
|
337
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
338
|
+
const colIdx = col - startCol;
|
|
339
|
+
const key = headerOpt[colIdx] ?? `__EMPTY_${colIdx}`;
|
|
340
|
+
const cell = worksheet.getCell(row, col);
|
|
341
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
342
|
+
if (val != null && val !== "") {
|
|
343
|
+
rowData[key] = val;
|
|
344
|
+
isEmpty = false;
|
|
345
|
+
}
|
|
346
|
+
else if (o.defval !== undefined) {
|
|
347
|
+
rowData[key] = o.defval;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (!isEmpty || includeBlank) {
|
|
351
|
+
result.push(rowData);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
// Default: first row as header, disambiguate duplicates
|
|
357
|
+
const headers = [];
|
|
358
|
+
const headerCounts = {};
|
|
359
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
360
|
+
const cell = worksheet.getCell(startRow, col);
|
|
361
|
+
const val = cell.value;
|
|
362
|
+
let header = val != null ? String(val) : `__EMPTY_${col - startCol}`;
|
|
363
|
+
// Disambiguate duplicate headers
|
|
364
|
+
if (headerCounts[header] !== undefined) {
|
|
365
|
+
headerCounts[header]++;
|
|
366
|
+
header = `${header}_${headerCounts[header]}`;
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
headerCounts[header] = 0;
|
|
370
|
+
}
|
|
371
|
+
headers.push(header);
|
|
372
|
+
}
|
|
373
|
+
// Read data rows (skip header row)
|
|
374
|
+
const result = [];
|
|
375
|
+
const dataStartRow = startRow + 1;
|
|
376
|
+
// Default for objects is to skip blank rows
|
|
377
|
+
const includeBlank = o.blankrows === true;
|
|
378
|
+
for (let row = dataStartRow; row <= endRow; row++) {
|
|
379
|
+
const rowData = {};
|
|
380
|
+
let isEmpty = true;
|
|
381
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
382
|
+
const cell = worksheet.getCell(row, col);
|
|
383
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
384
|
+
const key = headers[col - startCol];
|
|
385
|
+
if (val != null && val !== "") {
|
|
386
|
+
rowData[key] = val;
|
|
387
|
+
isEmpty = false;
|
|
388
|
+
}
|
|
389
|
+
else if (o.defval !== undefined) {
|
|
390
|
+
rowData[key] = o.defval;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (!isEmpty || includeBlank) {
|
|
394
|
+
result.push(rowData);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Convert worksheet to CSV string
|
|
401
|
+
*/
|
|
402
|
+
function sheetToCsv(worksheet, opts) {
|
|
403
|
+
const o = opts || {};
|
|
404
|
+
const FS = o.FS ?? ",";
|
|
405
|
+
const RS = o.RS ?? "\n";
|
|
406
|
+
const rows = [];
|
|
407
|
+
worksheet.eachRow({ includeEmpty: o.blankrows !== false }, (row, rowNumber) => {
|
|
408
|
+
const cells = [];
|
|
409
|
+
let isEmpty = true;
|
|
410
|
+
row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
411
|
+
let val = "";
|
|
412
|
+
if (cell.value != null) {
|
|
413
|
+
if (cell.value instanceof Date) {
|
|
414
|
+
val = cell.value.toISOString();
|
|
415
|
+
}
|
|
416
|
+
else if (typeof cell.value === "object") {
|
|
417
|
+
// Handle rich text, formula results, etc.
|
|
418
|
+
if ("result" in cell.value) {
|
|
419
|
+
val = String(cell.value.result ?? "");
|
|
420
|
+
}
|
|
421
|
+
else if ("text" in cell.value) {
|
|
422
|
+
val = String(cell.value.text ?? "");
|
|
423
|
+
}
|
|
424
|
+
else if ("richText" in cell.value) {
|
|
425
|
+
val = cell.value.richText.map(r => r.text).join("");
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
val = String(cell.value);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
val = String(cell.value);
|
|
433
|
+
}
|
|
434
|
+
isEmpty = false;
|
|
435
|
+
}
|
|
436
|
+
// Quote if necessary
|
|
437
|
+
const needsQuote = o.forceQuotes ||
|
|
438
|
+
val.includes(FS) ||
|
|
439
|
+
val.includes('"') ||
|
|
440
|
+
val.includes("\n") ||
|
|
441
|
+
val.includes("\r");
|
|
442
|
+
if (needsQuote) {
|
|
443
|
+
val = `"${val.replace(/"/g, '""')}"`;
|
|
444
|
+
}
|
|
445
|
+
cells.push(val);
|
|
446
|
+
});
|
|
447
|
+
// Pad cells to match column count
|
|
448
|
+
while (cells.length < worksheet.columnCount) {
|
|
449
|
+
cells.push("");
|
|
450
|
+
}
|
|
451
|
+
if (!isEmpty || o.blankrows !== false) {
|
|
452
|
+
rows.push(cells.join(FS));
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
return rows.join(RS);
|
|
456
|
+
}
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// Workbook Functions
|
|
459
|
+
// =============================================================================
|
|
460
|
+
/**
|
|
461
|
+
* Create a new workbook
|
|
462
|
+
*/
|
|
463
|
+
function bookNew() {
|
|
464
|
+
return new workbook_js_1.Workbook();
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Append worksheet to workbook (xlsx compatible)
|
|
468
|
+
* @example
|
|
469
|
+
* const wb = bookNew();
|
|
470
|
+
* const ws = jsonToSheet([{a: 1, b: 2}]);
|
|
471
|
+
* bookAppendSheet(wb, ws, "Sheet1");
|
|
472
|
+
*/
|
|
473
|
+
function bookAppendSheet(workbook, worksheet, name) {
|
|
474
|
+
// Copy the worksheet data to a new sheet in the workbook
|
|
475
|
+
const newWs = workbook.addWorksheet(name);
|
|
476
|
+
// Copy all cells from source worksheet
|
|
477
|
+
worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => {
|
|
478
|
+
row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
479
|
+
const newCell = newWs.getCell(rowNumber, colNumber);
|
|
480
|
+
newCell.value = cell.value;
|
|
481
|
+
if (cell.style) {
|
|
482
|
+
newCell.style = cell.style;
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
// Copy column properties
|
|
487
|
+
worksheet.columns?.forEach((col, idx) => {
|
|
488
|
+
if (col && newWs.columns[idx]) {
|
|
489
|
+
if (col.width) {
|
|
490
|
+
newWs.getColumn(idx + 1).width = col.width;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Create a worksheet from an array of arrays (xlsx compatible)
|
|
497
|
+
* @example
|
|
498
|
+
* const ws = aoaToSheet([["Name", "Age"], ["Alice", 30], ["Bob", 25]])
|
|
499
|
+
*/
|
|
500
|
+
function aoaToSheet(data, opts) {
|
|
501
|
+
const tempWb = new workbook_js_1.Workbook();
|
|
502
|
+
const worksheet = tempWb.addWorksheet("Sheet1");
|
|
503
|
+
if (data.length === 0) {
|
|
504
|
+
return worksheet;
|
|
505
|
+
}
|
|
506
|
+
// Determine starting position
|
|
507
|
+
let startRow = 1;
|
|
508
|
+
let startCol = 1;
|
|
509
|
+
if (opts?.origin !== undefined) {
|
|
510
|
+
if (typeof opts.origin === "string") {
|
|
511
|
+
const addr = decodeCell(opts.origin);
|
|
512
|
+
startRow = addr.r + 1;
|
|
513
|
+
startCol = addr.c + 1;
|
|
514
|
+
}
|
|
515
|
+
else if (typeof opts.origin === "number") {
|
|
516
|
+
startRow = opts.origin + 1; // 0-indexed row
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
startRow = opts.origin.r + 1;
|
|
520
|
+
startCol = opts.origin.c + 1;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
data.forEach((row, rowIdx) => {
|
|
524
|
+
if (!row) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
row.forEach((val, colIdx) => {
|
|
528
|
+
if (val !== undefined && val !== null) {
|
|
529
|
+
worksheet.getCell(startRow + rowIdx, startCol + colIdx).value = val;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
return worksheet;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Add data from an array of arrays to an existing worksheet (xlsx compatible)
|
|
537
|
+
*/
|
|
538
|
+
function sheetAddAoa(worksheet, data, opts) {
|
|
539
|
+
if (data.length === 0) {
|
|
540
|
+
return worksheet;
|
|
541
|
+
}
|
|
542
|
+
// Determine starting position
|
|
543
|
+
let startRow = 1;
|
|
544
|
+
let startCol = 1;
|
|
545
|
+
if (opts?.origin !== undefined) {
|
|
546
|
+
if (typeof opts.origin === "string") {
|
|
547
|
+
const addr = decodeCell(opts.origin);
|
|
548
|
+
startRow = addr.r + 1;
|
|
549
|
+
startCol = addr.c + 1;
|
|
550
|
+
}
|
|
551
|
+
else if (typeof opts.origin === "number") {
|
|
552
|
+
if (opts.origin === -1) {
|
|
553
|
+
// Append to bottom
|
|
554
|
+
startRow = worksheet.rowCount + 1;
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
startRow = opts.origin + 1; // 0-indexed row
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
startRow = opts.origin.r + 1;
|
|
562
|
+
startCol = opts.origin.c + 1;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
data.forEach((row, rowIdx) => {
|
|
566
|
+
if (!row) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
row.forEach((val, colIdx) => {
|
|
570
|
+
if (val !== undefined && val !== null) {
|
|
571
|
+
worksheet.getCell(startRow + rowIdx, startCol + colIdx).value = val;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
return worksheet;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Convert worksheet to array of arrays
|
|
579
|
+
*/
|
|
580
|
+
function sheetToAoa(worksheet) {
|
|
581
|
+
const result = [];
|
|
582
|
+
worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => {
|
|
583
|
+
const rowData = [];
|
|
584
|
+
row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
585
|
+
rowData[colNumber - 1] = cell.value;
|
|
586
|
+
});
|
|
587
|
+
result[rowNumber - 1] = rowData;
|
|
588
|
+
});
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
// =============================================================================
|
|
592
|
+
// Export utils object
|
|
593
|
+
// =============================================================================
|
|
594
|
+
exports.utils = {
|
|
595
|
+
// Cell encoding/decoding (camelCase)
|
|
596
|
+
decodeCol,
|
|
597
|
+
encodeCol,
|
|
598
|
+
decodeRow,
|
|
599
|
+
encodeRow,
|
|
600
|
+
decodeCell,
|
|
601
|
+
encodeCell,
|
|
602
|
+
decodeRange,
|
|
603
|
+
encodeRange,
|
|
604
|
+
// Sheet/JSON conversion (camelCase)
|
|
605
|
+
jsonToSheet,
|
|
606
|
+
sheetAddJson,
|
|
607
|
+
sheetToJson,
|
|
608
|
+
sheetToCsv,
|
|
609
|
+
aoaToSheet,
|
|
610
|
+
sheetAddAoa,
|
|
611
|
+
sheetToAoa,
|
|
612
|
+
// Workbook functions (camelCase)
|
|
613
|
+
bookNew,
|
|
614
|
+
bookAppendSheet
|
|
615
|
+
};
|