@niicojs/excel 0.2.2 → 0.2.4
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 +373 -5
- package/dist/index.cjs +507 -25
- package/dist/index.d.cts +172 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +172 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +504 -26
- package/package.json +1 -1
- package/src/index.ts +17 -1
- package/src/pivot-cache.ts +11 -1
- package/src/pivot-table.ts +122 -12
- package/src/range.ts +15 -2
- package/src/styles.ts +60 -1
- package/src/types.ts +62 -0
- package/src/utils/address.ts +4 -1
- package/src/utils/xml.ts +0 -7
- package/src/workbook.ts +18 -0
- package/src/worksheet.ts +343 -4
package/README.md
CHANGED
|
@@ -9,7 +9,12 @@ A TypeScript library for Excel/OpenXML manipulation with maximum format preserva
|
|
|
9
9
|
- Full formula support (read/write/preserve)
|
|
10
10
|
- Cell styles (fonts, fills, borders, alignment)
|
|
11
11
|
- Merged cells
|
|
12
|
+
- Column widths and row heights
|
|
13
|
+
- Freeze panes
|
|
14
|
+
- Pivot tables with fluent API
|
|
12
15
|
- Sheet operations (add, delete, rename, copy)
|
|
16
|
+
- Create sheets from arrays of objects (`addSheetFromData`)
|
|
17
|
+
- Convert sheets to JSON arrays (`toJson`)
|
|
13
18
|
- Create new workbooks from scratch
|
|
14
19
|
- TypeScript-first with full type definitions
|
|
15
20
|
|
|
@@ -28,6 +33,7 @@ import { Workbook } from '@niicojs/excel';
|
|
|
28
33
|
|
|
29
34
|
// Create a new workbook
|
|
30
35
|
const wb = Workbook.create();
|
|
36
|
+
wb.addSheet('Sheet1');
|
|
31
37
|
const sheet = wb.sheet('Sheet1');
|
|
32
38
|
|
|
33
39
|
// Write data
|
|
@@ -136,6 +142,47 @@ sheet.unmergeCells('A1:C1');
|
|
|
136
142
|
console.log(sheet.mergedCells); // ['A1:C3', 'D5:E10', ...]
|
|
137
143
|
```
|
|
138
144
|
|
|
145
|
+
## Column Widths and Row Heights
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const sheet = wb.sheet(0);
|
|
149
|
+
|
|
150
|
+
// Set column width (by letter or 0-based index)
|
|
151
|
+
sheet.setColumnWidth('A', 20);
|
|
152
|
+
sheet.setColumnWidth(1, 15); // Column B
|
|
153
|
+
|
|
154
|
+
// Get column width
|
|
155
|
+
const width = sheet.getColumnWidth('A'); // 20 or undefined
|
|
156
|
+
|
|
157
|
+
// Set row height (0-based index)
|
|
158
|
+
sheet.setRowHeight(0, 30); // First row
|
|
159
|
+
|
|
160
|
+
// Get row height
|
|
161
|
+
const height = sheet.getRowHeight(0); // 30 or undefined
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Freeze Panes
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const sheet = wb.sheet(0);
|
|
168
|
+
|
|
169
|
+
// Freeze first row (header)
|
|
170
|
+
sheet.freezePane(1, 0);
|
|
171
|
+
|
|
172
|
+
// Freeze first column
|
|
173
|
+
sheet.freezePane(0, 1);
|
|
174
|
+
|
|
175
|
+
// Freeze first row and first column
|
|
176
|
+
sheet.freezePane(1, 1);
|
|
177
|
+
|
|
178
|
+
// Unfreeze
|
|
179
|
+
sheet.freezePane(0, 0);
|
|
180
|
+
|
|
181
|
+
// Get current freeze pane configuration
|
|
182
|
+
const frozen = sheet.getFrozenPane();
|
|
183
|
+
// { row: 1, col: 0 } or null
|
|
184
|
+
```
|
|
185
|
+
|
|
139
186
|
## Sheet Operations
|
|
140
187
|
|
|
141
188
|
```typescript
|
|
@@ -146,7 +193,7 @@ wb.addSheet('Data');
|
|
|
146
193
|
wb.addSheet('Summary', 0); // Insert at index 0
|
|
147
194
|
|
|
148
195
|
// Get sheet names
|
|
149
|
-
console.log(wb.sheetNames); // ['Summary', '
|
|
196
|
+
console.log(wb.sheetNames); // ['Summary', 'Data']
|
|
150
197
|
|
|
151
198
|
// Access sheets
|
|
152
199
|
const sheet = wb.sheet('Data'); // By name
|
|
@@ -162,14 +209,191 @@ wb.copySheet('RawData', 'RawData_Backup');
|
|
|
162
209
|
wb.deleteSheet('Summary');
|
|
163
210
|
```
|
|
164
211
|
|
|
212
|
+
## Pivot Tables
|
|
213
|
+
|
|
214
|
+
Create pivot tables with a fluent API:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const wb = await Workbook.fromFile('sales-data.xlsx');
|
|
218
|
+
|
|
219
|
+
// Create a pivot table from source data
|
|
220
|
+
const pivot = wb.createPivotTable({
|
|
221
|
+
name: 'SalesPivot',
|
|
222
|
+
source: 'Data!A1:E100', // Source range with headers
|
|
223
|
+
target: 'Summary!A3', // Where to place the pivot table
|
|
224
|
+
refreshOnLoad: true, // Refresh when file opens (default: true)
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Configure fields using fluent API
|
|
228
|
+
pivot
|
|
229
|
+
.addRowField('Region') // Group by region
|
|
230
|
+
.addRowField('Product') // Then by product
|
|
231
|
+
.addColumnField('Year') // Columns by year
|
|
232
|
+
.addValueField('Sales', 'sum', 'Total Sales') // Sum of sales
|
|
233
|
+
.addValueField('Quantity', 'count', 'Order Count') // Count of orders
|
|
234
|
+
.addFilterField('Category'); // Page filter
|
|
235
|
+
|
|
236
|
+
// Sort and filter fields
|
|
237
|
+
pivot
|
|
238
|
+
.sortField('Region', 'asc') // Sort ascending
|
|
239
|
+
.filterField('Product', { include: ['Widget', 'Gadget'] }); // Include only these
|
|
240
|
+
|
|
241
|
+
await wb.toFile('report.xlsx');
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Pivot Table API
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// Add fields to different areas
|
|
248
|
+
pivot.addRowField(fieldName: string): PivotTable
|
|
249
|
+
pivot.addColumnField(fieldName: string): PivotTable
|
|
250
|
+
pivot.addValueField(fieldName: string, aggregation?: AggregationType, displayName?: string): PivotTable
|
|
251
|
+
pivot.addFilterField(fieldName: string): PivotTable
|
|
252
|
+
|
|
253
|
+
// Aggregation types: 'sum' | 'count' | 'average' | 'min' | 'max'
|
|
254
|
+
|
|
255
|
+
// Sort a row or column field
|
|
256
|
+
pivot.sortField(fieldName: string, order: 'asc' | 'desc'): PivotTable
|
|
257
|
+
|
|
258
|
+
// Filter field values
|
|
259
|
+
pivot.filterField(fieldName: string, filter: { include?: string[] } | { exclude?: string[] }): PivotTable
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Creating Sheets from Data
|
|
263
|
+
|
|
264
|
+
Create sheets directly from arrays of objects with `addSheetFromData`:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const wb = Workbook.create();
|
|
268
|
+
|
|
269
|
+
// Simple usage - object keys become column headers
|
|
270
|
+
const employees = [
|
|
271
|
+
{ name: 'Alice', age: 30, city: 'Paris' },
|
|
272
|
+
{ name: 'Bob', age: 25, city: 'London' },
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
wb.addSheetFromData({
|
|
276
|
+
name: 'Employees',
|
|
277
|
+
data: employees,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Custom column configuration
|
|
281
|
+
wb.addSheetFromData({
|
|
282
|
+
name: 'Custom',
|
|
283
|
+
data: employees,
|
|
284
|
+
columns: [
|
|
285
|
+
{ key: 'name', header: 'Full Name' },
|
|
286
|
+
{ key: 'age', header: 'Age (years)' },
|
|
287
|
+
{ key: 'city', header: 'Location', style: { bold: true } },
|
|
288
|
+
],
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// With formulas and styles using RichCellValue
|
|
292
|
+
const orderLines = [
|
|
293
|
+
{ product: 'Widget', price: 10, qty: 5, total: { formula: 'B2*C2', style: { bold: true } } },
|
|
294
|
+
{ product: 'Gadget', price: 20, qty: 3, total: { formula: 'B3*C3', style: { bold: true } } },
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
wb.addSheetFromData({
|
|
298
|
+
name: 'Orders',
|
|
299
|
+
data: orderLines,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Other options
|
|
303
|
+
wb.addSheetFromData({
|
|
304
|
+
name: 'Options',
|
|
305
|
+
data: employees,
|
|
306
|
+
headerStyle: false, // Don't bold headers
|
|
307
|
+
startCell: 'B3', // Start at B3 instead of A1
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Converting Sheets to JSON
|
|
312
|
+
|
|
313
|
+
Convert sheet data back to arrays of objects with `toJson`:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const sheet = wb.sheet('Data');
|
|
317
|
+
|
|
318
|
+
// Using first row as headers
|
|
319
|
+
const data = sheet.toJson();
|
|
320
|
+
// [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
|
|
321
|
+
|
|
322
|
+
// Using custom field names (first row is data, not headers)
|
|
323
|
+
const data2 = sheet.toJson({
|
|
324
|
+
fields: ['name', 'age', 'city'],
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// With TypeScript generics
|
|
328
|
+
interface Person {
|
|
329
|
+
name: string | null;
|
|
330
|
+
age: number | null;
|
|
331
|
+
}
|
|
332
|
+
const people = sheet.toJson<Person>();
|
|
333
|
+
|
|
334
|
+
// Starting from a specific position
|
|
335
|
+
const data3 = sheet.toJson({
|
|
336
|
+
startRow: 2, // Skip first 2 rows (0-based)
|
|
337
|
+
startCol: 1, // Start from column B
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Limiting the range
|
|
341
|
+
const data4 = sheet.toJson({
|
|
342
|
+
endRow: 10, // Stop at row 11 (0-based, inclusive)
|
|
343
|
+
endCol: 3, // Only read columns A-D
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Continue past empty rows
|
|
347
|
+
const data5 = sheet.toJson({
|
|
348
|
+
stopOnEmptyRow: false, // Default is true
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Control how dates are serialized
|
|
352
|
+
const data6 = sheet.toJson({
|
|
353
|
+
dateHandling: 'isoString', // 'jsDate' | 'excelSerial' | 'isoString'
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Roundtrip Example
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
// Create from objects
|
|
361
|
+
const originalData = [
|
|
362
|
+
{ name: 'Alice', age: 30 },
|
|
363
|
+
{ name: 'Bob', age: 25 },
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
const sheet = wb.addSheetFromData({
|
|
367
|
+
name: 'People',
|
|
368
|
+
data: originalData,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Read back as objects
|
|
372
|
+
const readData = sheet.toJson();
|
|
373
|
+
// readData equals originalData
|
|
374
|
+
```
|
|
375
|
+
|
|
165
376
|
## Saving
|
|
166
377
|
|
|
167
378
|
```typescript
|
|
168
|
-
//
|
|
169
|
-
await
|
|
379
|
+
// Load from file
|
|
380
|
+
const wb = await Workbook.fromFile('template.xlsx');
|
|
170
381
|
|
|
171
|
-
//
|
|
172
|
-
const buffer = await
|
|
382
|
+
// Or load from buffer
|
|
383
|
+
const buffer = await fetch('https://example.com/file.xlsx').then((r) => r.arrayBuffer());
|
|
384
|
+
const wb2 = await Workbook.fromBuffer(new Uint8Array(buffer));
|
|
385
|
+
|
|
386
|
+
// Read data
|
|
387
|
+
const sheet = wb.sheet('Sheet1');
|
|
388
|
+
console.log(sheet.cell('A1').value); // The cell value
|
|
389
|
+
console.log(sheet.cell('A1').formula); // The formula (if any)
|
|
390
|
+
console.log(sheet.cell('A1').type); // 'string' | 'number' | 'boolean' | 'date' | 'error' | 'empty'
|
|
391
|
+
|
|
392
|
+
// Check if a cell exists without creating it
|
|
393
|
+
const existingCell = sheet.getCellIfExists('A1');
|
|
394
|
+
if (existingCell) {
|
|
395
|
+
console.log(existingCell.value);
|
|
396
|
+
}
|
|
173
397
|
```
|
|
174
398
|
|
|
175
399
|
## Type Definitions
|
|
@@ -187,8 +411,152 @@ type CellType = 'number' | 'string' | 'boolean' | 'date' | 'error' | 'empty';
|
|
|
187
411
|
|
|
188
412
|
// Border types
|
|
189
413
|
type BorderType = 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
|
|
414
|
+
|
|
415
|
+
// Configuration for addSheetFromData
|
|
416
|
+
interface SheetFromDataConfig<T> {
|
|
417
|
+
name: string;
|
|
418
|
+
data: T[];
|
|
419
|
+
columns?: ColumnConfig<T>[];
|
|
420
|
+
headerStyle?: boolean; // Default: true
|
|
421
|
+
startCell?: string; // Default: 'A1'
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
interface ColumnConfig<T> {
|
|
425
|
+
key: keyof T;
|
|
426
|
+
header?: string;
|
|
427
|
+
style?: CellStyle;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Rich cell value for formulas/styles in data
|
|
431
|
+
interface RichCellValue {
|
|
432
|
+
value?: CellValue;
|
|
433
|
+
formula?: string;
|
|
434
|
+
style?: CellStyle;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Configuration for toJson
|
|
438
|
+
interface SheetToJsonConfig {
|
|
439
|
+
fields?: string[];
|
|
440
|
+
startRow?: number;
|
|
441
|
+
startCol?: number;
|
|
442
|
+
endRow?: number;
|
|
443
|
+
endCol?: number;
|
|
444
|
+
stopOnEmptyRow?: boolean; // Default: true
|
|
445
|
+
dateHandling?: 'jsDate' | 'excelSerial' | 'isoString'; // Default: 'jsDate'
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Pivot table configuration
|
|
449
|
+
interface PivotTableConfig {
|
|
450
|
+
name: string;
|
|
451
|
+
source: string; // e.g., "Sheet1!A1:D100"
|
|
452
|
+
target: string; // e.g., "Sheet2!A3"
|
|
453
|
+
refreshOnLoad?: boolean; // Default: true
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
type AggregationType = 'sum' | 'count' | 'average' | 'min' | 'max';
|
|
457
|
+
type PivotSortOrder = 'asc' | 'desc';
|
|
458
|
+
interface PivotFieldFilter {
|
|
459
|
+
include?: string[];
|
|
460
|
+
exclude?: string[];
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Address Utilities
|
|
465
|
+
|
|
466
|
+
Helper functions for working with Excel cell addresses:
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import {
|
|
470
|
+
colToLetter,
|
|
471
|
+
letterToCol,
|
|
472
|
+
parseAddress,
|
|
473
|
+
toAddress,
|
|
474
|
+
parseRange,
|
|
475
|
+
toRange,
|
|
476
|
+
normalizeRange,
|
|
477
|
+
isInRange,
|
|
478
|
+
} from '@niicojs/excel';
|
|
479
|
+
|
|
480
|
+
// Convert column index to letter (0-based)
|
|
481
|
+
colToLetter(0); // 'A'
|
|
482
|
+
colToLetter(25); // 'Z'
|
|
483
|
+
colToLetter(26); // 'AA'
|
|
484
|
+
|
|
485
|
+
// Convert column letter to index (0-based)
|
|
486
|
+
letterToCol('A'); // 0
|
|
487
|
+
letterToCol('Z'); // 25
|
|
488
|
+
letterToCol('AA'); // 26
|
|
489
|
+
|
|
490
|
+
// Parse cell address to row/col
|
|
491
|
+
parseAddress('B3'); // { row: 2, col: 1 }
|
|
492
|
+
parseAddress('$A$1'); // { row: 0, col: 0 } (absolute refs supported)
|
|
493
|
+
|
|
494
|
+
// Convert row/col to address
|
|
495
|
+
toAddress(0, 0); // 'A1'
|
|
496
|
+
toAddress(9, 2); // 'C10'
|
|
497
|
+
|
|
498
|
+
// Parse range string
|
|
499
|
+
parseRange('A1:C10'); // { start: { row: 0, col: 0 }, end: { row: 9, col: 2 } }
|
|
500
|
+
|
|
501
|
+
// Convert range object to string
|
|
502
|
+
toRange({ start: { row: 0, col: 0 }, end: { row: 9, col: 2 } }); // 'A1:C10'
|
|
503
|
+
|
|
504
|
+
// Normalize range (ensure start is top-left, end is bottom-right)
|
|
505
|
+
normalizeRange({ start: { row: 5, col: 3 }, end: { row: 0, col: 0 } });
|
|
506
|
+
// { start: { row: 0, col: 0 }, end: { row: 5, col: 3 } }
|
|
507
|
+
|
|
508
|
+
// Check if address is within a range
|
|
509
|
+
isInRange({ row: 2, col: 1 }, { start: { row: 0, col: 0 }, end: { row: 5, col: 5 } }); // true
|
|
510
|
+
isInRange({ row: 10, col: 0 }, { start: { row: 0, col: 0 }, end: { row: 5, col: 5 } }); // false
|
|
190
511
|
```
|
|
191
512
|
|
|
513
|
+
## Style Schema
|
|
514
|
+
|
|
515
|
+
Supported style properties (CellStyle):
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
interface CellStyle {
|
|
519
|
+
bold?: boolean;
|
|
520
|
+
italic?: boolean;
|
|
521
|
+
underline?: boolean | 'single' | 'double';
|
|
522
|
+
strike?: boolean;
|
|
523
|
+
fontSize?: number;
|
|
524
|
+
fontName?: string;
|
|
525
|
+
fontColor?: string; // Hex (RGB/RRGGBB/AARRGGBB)
|
|
526
|
+
fill?: string; // Hex (RGB/RRGGBB/AARRGGBB)
|
|
527
|
+
border?: {
|
|
528
|
+
top?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
|
|
529
|
+
bottom?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
|
|
530
|
+
left?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
|
|
531
|
+
right?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
|
|
532
|
+
};
|
|
533
|
+
alignment?: {
|
|
534
|
+
horizontal?: 'left' | 'center' | 'right' | 'justify';
|
|
535
|
+
vertical?: 'top' | 'middle' | 'bottom';
|
|
536
|
+
wrapText?: boolean;
|
|
537
|
+
textRotation?: number;
|
|
538
|
+
};
|
|
539
|
+
numberFormat?: string; // Excel format code
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## Performance and Large Files
|
|
544
|
+
|
|
545
|
+
Tips for large sheets and high-volume writes:
|
|
546
|
+
|
|
547
|
+
- Prefer `addSheetFromData` for bulk writes when possible.
|
|
548
|
+
- Use `range.getValues({ createMissing: false })` to avoid creating empty cells during reads.
|
|
549
|
+
- Keep shared strings small: prefer numbers/booleans where applicable.
|
|
550
|
+
- Avoid frequent `toBuffer()` calls in loops; batch writes and serialize once.
|
|
551
|
+
|
|
552
|
+
## Limitations
|
|
553
|
+
|
|
554
|
+
The library focuses on preserving existing structure and editing common parts of workbooks.
|
|
555
|
+
|
|
556
|
+
- Chart editing is not supported (charts are preserved only).
|
|
557
|
+
- Conditional formatting and data validation are preserved but not editable yet.
|
|
558
|
+
- Some advanced Excel features (sparklines, slicers, macros) are preserved only.
|
|
559
|
+
|
|
192
560
|
## Format Preservation
|
|
193
561
|
|
|
194
562
|
When modifying existing Excel files, this library preserves:
|