@niicojs/excel 0.1.0 → 0.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/dist/index.cjs +143 -9
- package/dist/index.d.cts +70 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +70 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +143 -9
- package/package.json +7 -7
- package/src/cell.ts +44 -9
- package/src/index.ts +3 -0
- package/src/types.ts +28 -0
- package/src/workbook.ts +130 -2
package/dist/index.js
CHANGED
|
@@ -150,6 +150,10 @@ const ERROR_TYPES = new Set([
|
|
|
150
150
|
}
|
|
151
151
|
switch(t){
|
|
152
152
|
case 'n':
|
|
153
|
+
// Check if this is actually a date stored as number
|
|
154
|
+
if (this._isDateFormat()) {
|
|
155
|
+
return 'date';
|
|
156
|
+
}
|
|
153
157
|
return 'number';
|
|
154
158
|
case 's':
|
|
155
159
|
case 'str':
|
|
@@ -162,7 +166,12 @@ const ERROR_TYPES = new Set([
|
|
|
162
166
|
return 'date';
|
|
163
167
|
default:
|
|
164
168
|
// If no type but has value, infer from value
|
|
165
|
-
if (typeof this._data.v === 'number')
|
|
169
|
+
if (typeof this._data.v === 'number') {
|
|
170
|
+
if (this._isDateFormat()) {
|
|
171
|
+
return 'date';
|
|
172
|
+
}
|
|
173
|
+
return 'number';
|
|
174
|
+
}
|
|
166
175
|
if (typeof this._data.v === 'string') return 'string';
|
|
167
176
|
if (typeof this._data.v === 'boolean') return 'boolean';
|
|
168
177
|
return 'empty';
|
|
@@ -178,7 +187,14 @@ const ERROR_TYPES = new Set([
|
|
|
178
187
|
}
|
|
179
188
|
switch(t){
|
|
180
189
|
case 'n':
|
|
181
|
-
|
|
190
|
+
{
|
|
191
|
+
const numVal = typeof v === 'number' ? v : parseFloat(String(v));
|
|
192
|
+
// Check if this is actually a date stored as number
|
|
193
|
+
if (this._isDateFormat()) {
|
|
194
|
+
return this._excelDateToJs(numVal);
|
|
195
|
+
}
|
|
196
|
+
return numVal;
|
|
197
|
+
}
|
|
182
198
|
case 's':
|
|
183
199
|
// Shared string reference
|
|
184
200
|
if (typeof v === 'number') {
|
|
@@ -240,9 +256,15 @@ const ERROR_TYPES = new Set([
|
|
|
240
256
|
this._data.v = val ? 1 : 0;
|
|
241
257
|
this._data.t = 'b';
|
|
242
258
|
} else if (val instanceof Date) {
|
|
243
|
-
// Store as
|
|
244
|
-
this._data.v =
|
|
245
|
-
this._data.t = '
|
|
259
|
+
// Store as Excel serial number with date format for maximum compatibility
|
|
260
|
+
this._data.v = this._jsDateToExcel(val);
|
|
261
|
+
this._data.t = 'n';
|
|
262
|
+
// Apply a default date format if no style is set
|
|
263
|
+
if (this._data.s === undefined) {
|
|
264
|
+
this._data.s = this._worksheet.workbook.styles.createStyle({
|
|
265
|
+
numberFormat: 'yyyy-mm-dd'
|
|
266
|
+
});
|
|
267
|
+
}
|
|
246
268
|
} else if ('error' in val) {
|
|
247
269
|
this._data.v = val.error;
|
|
248
270
|
this._data.t = 'e';
|
|
@@ -332,9 +354,16 @@ const ERROR_TYPES = new Set([
|
|
|
332
354
|
/**
|
|
333
355
|
* Check if this cell has a date number format
|
|
334
356
|
*/ _isDateFormat() {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
357
|
+
if (this._data.s === undefined) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const style = this._worksheet.workbook.styles.getStyle(this._data.s);
|
|
361
|
+
if (!style.numberFormat) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
// Common date format patterns
|
|
365
|
+
const fmt = style.numberFormat.toLowerCase();
|
|
366
|
+
return fmt.includes('y') || fmt.includes('m') || fmt.includes('d') || fmt.includes('h') || fmt.includes('s') || fmt === 'general date' || fmt === 'short date' || fmt === 'long date';
|
|
338
367
|
}
|
|
339
368
|
/**
|
|
340
369
|
* Convert Excel serial date to JavaScript Date
|
|
@@ -2465,6 +2494,110 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2465
2494
|
return copy;
|
|
2466
2495
|
}
|
|
2467
2496
|
/**
|
|
2497
|
+
* Create a new worksheet from an array of objects.
|
|
2498
|
+
*
|
|
2499
|
+
* The first row contains headers (object keys or custom column headers),
|
|
2500
|
+
* and subsequent rows contain the object values.
|
|
2501
|
+
*
|
|
2502
|
+
* @param config - Configuration for the sheet creation
|
|
2503
|
+
* @returns The created Worksheet
|
|
2504
|
+
*
|
|
2505
|
+
* @example
|
|
2506
|
+
* ```typescript
|
|
2507
|
+
* const data = [
|
|
2508
|
+
* { name: 'Alice', age: 30, city: 'Paris' },
|
|
2509
|
+
* { name: 'Bob', age: 25, city: 'London' },
|
|
2510
|
+
* { name: 'Charlie', age: 35, city: 'Berlin' },
|
|
2511
|
+
* ];
|
|
2512
|
+
*
|
|
2513
|
+
* // Simple usage - all object keys become columns
|
|
2514
|
+
* const sheet = wb.addSheetFromData({
|
|
2515
|
+
* name: 'People',
|
|
2516
|
+
* data: data,
|
|
2517
|
+
* });
|
|
2518
|
+
*
|
|
2519
|
+
* // With custom column configuration
|
|
2520
|
+
* const sheet2 = wb.addSheetFromData({
|
|
2521
|
+
* name: 'People Custom',
|
|
2522
|
+
* data: data,
|
|
2523
|
+
* columns: [
|
|
2524
|
+
* { key: 'name', header: 'Full Name' },
|
|
2525
|
+
* { key: 'age', header: 'Age (years)' },
|
|
2526
|
+
* ],
|
|
2527
|
+
* });
|
|
2528
|
+
* ```
|
|
2529
|
+
*/ addSheetFromData(config) {
|
|
2530
|
+
const { name, data, columns, headerStyle = true, startCell = 'A1' } = config;
|
|
2531
|
+
if (data.length === 0) {
|
|
2532
|
+
// Create empty sheet if no data
|
|
2533
|
+
return this.addSheet(name);
|
|
2534
|
+
}
|
|
2535
|
+
// Create the new sheet
|
|
2536
|
+
const sheet = this.addSheet(name);
|
|
2537
|
+
// Parse start cell
|
|
2538
|
+
const startAddr = parseAddress(startCell);
|
|
2539
|
+
let startRow = startAddr.row;
|
|
2540
|
+
const startCol = startAddr.col;
|
|
2541
|
+
// Determine columns to use
|
|
2542
|
+
const columnConfigs = columns ?? this._inferColumns(data[0]);
|
|
2543
|
+
// Write header row
|
|
2544
|
+
for(let colIdx = 0; colIdx < columnConfigs.length; colIdx++){
|
|
2545
|
+
const colConfig = columnConfigs[colIdx];
|
|
2546
|
+
const headerText = colConfig.header ?? String(colConfig.key);
|
|
2547
|
+
const cell = sheet.cell(startRow, startCol + colIdx);
|
|
2548
|
+
cell.value = headerText;
|
|
2549
|
+
// Apply header style if enabled
|
|
2550
|
+
if (headerStyle) {
|
|
2551
|
+
cell.style = {
|
|
2552
|
+
bold: true
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
// Move to data rows
|
|
2557
|
+
startRow++;
|
|
2558
|
+
// Write data rows
|
|
2559
|
+
for(let rowIdx = 0; rowIdx < data.length; rowIdx++){
|
|
2560
|
+
const rowData = data[rowIdx];
|
|
2561
|
+
for(let colIdx = 0; colIdx < columnConfigs.length; colIdx++){
|
|
2562
|
+
const colConfig = columnConfigs[colIdx];
|
|
2563
|
+
const value = rowData[colConfig.key];
|
|
2564
|
+
const cell = sheet.cell(startRow + rowIdx, startCol + colIdx);
|
|
2565
|
+
// Convert value to CellValue
|
|
2566
|
+
cell.value = this._toCellValue(value);
|
|
2567
|
+
// Apply column style if defined
|
|
2568
|
+
if (colConfig.style) {
|
|
2569
|
+
cell.style = colConfig.style;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
return sheet;
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Infer column configuration from the first data object
|
|
2577
|
+
*/ _inferColumns(sample) {
|
|
2578
|
+
return Object.keys(sample).map((key)=>({
|
|
2579
|
+
key
|
|
2580
|
+
}));
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Convert an unknown value to a CellValue
|
|
2584
|
+
*/ _toCellValue(value) {
|
|
2585
|
+
if (value === null || value === undefined) {
|
|
2586
|
+
return null;
|
|
2587
|
+
}
|
|
2588
|
+
if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') {
|
|
2589
|
+
return value;
|
|
2590
|
+
}
|
|
2591
|
+
if (value instanceof Date) {
|
|
2592
|
+
return value;
|
|
2593
|
+
}
|
|
2594
|
+
if (typeof value === 'object' && 'error' in value) {
|
|
2595
|
+
return value;
|
|
2596
|
+
}
|
|
2597
|
+
// Convert other types to string
|
|
2598
|
+
return String(value);
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2468
2601
|
* Create a pivot table from source data.
|
|
2469
2602
|
*
|
|
2470
2603
|
* @param config - Pivot table configuration
|
|
@@ -2675,7 +2808,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2675
2808
|
Type: rel.type,
|
|
2676
2809
|
Target: rel.target
|
|
2677
2810
|
}, []));
|
|
2678
|
-
|
|
2811
|
+
// Calculate next available relationship ID based on existing max ID
|
|
2812
|
+
let nextRelId = Math.max(0, ...this._relationships.map((r)=>parseInt(r.id.replace('rId', ''), 10) || 0)) + 1;
|
|
2679
2813
|
// Add shared strings relationship if needed
|
|
2680
2814
|
if (this._sharedStrings.count > 0) {
|
|
2681
2815
|
const hasSharedStrings = this._relationships.some((r)=>r.type === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings');
|
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@niicojs/excel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "typescript library to manipulate excel files",
|
|
5
5
|
"homepage": "https://github.com/niicojs/excel#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/niicojs/excel/issues"
|
|
8
8
|
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "niico",
|
|
9
11
|
"repository": {
|
|
10
12
|
"type": "git",
|
|
11
13
|
"url": "git+https://github.com/niicojs/excel.git"
|
|
12
14
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
15
19
|
"type": "module",
|
|
16
20
|
"main": "./dist/index.cjs",
|
|
17
21
|
"module": "./dist/index.js",
|
|
@@ -29,10 +33,6 @@
|
|
|
29
33
|
},
|
|
30
34
|
"./package.json": "./package.json"
|
|
31
35
|
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist",
|
|
34
|
-
"src"
|
|
35
|
-
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"dev": "tsdx dev",
|
|
38
38
|
"build": "tsdx build",
|
package/src/cell.ts
CHANGED
|
@@ -66,6 +66,10 @@ export class Cell {
|
|
|
66
66
|
}
|
|
67
67
|
switch (t) {
|
|
68
68
|
case 'n':
|
|
69
|
+
// Check if this is actually a date stored as number
|
|
70
|
+
if (this._isDateFormat()) {
|
|
71
|
+
return 'date';
|
|
72
|
+
}
|
|
69
73
|
return 'number';
|
|
70
74
|
case 's':
|
|
71
75
|
case 'str':
|
|
@@ -78,7 +82,12 @@ export class Cell {
|
|
|
78
82
|
return 'date';
|
|
79
83
|
default:
|
|
80
84
|
// If no type but has value, infer from value
|
|
81
|
-
if (typeof this._data.v === 'number')
|
|
85
|
+
if (typeof this._data.v === 'number') {
|
|
86
|
+
if (this._isDateFormat()) {
|
|
87
|
+
return 'date';
|
|
88
|
+
}
|
|
89
|
+
return 'number';
|
|
90
|
+
}
|
|
82
91
|
if (typeof this._data.v === 'string') return 'string';
|
|
83
92
|
if (typeof this._data.v === 'boolean') return 'boolean';
|
|
84
93
|
return 'empty';
|
|
@@ -97,8 +106,14 @@ export class Cell {
|
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
switch (t) {
|
|
100
|
-
case 'n':
|
|
101
|
-
|
|
109
|
+
case 'n': {
|
|
110
|
+
const numVal = typeof v === 'number' ? v : parseFloat(String(v));
|
|
111
|
+
// Check if this is actually a date stored as number
|
|
112
|
+
if (this._isDateFormat()) {
|
|
113
|
+
return this._excelDateToJs(numVal);
|
|
114
|
+
}
|
|
115
|
+
return numVal;
|
|
116
|
+
}
|
|
102
117
|
case 's':
|
|
103
118
|
// Shared string reference
|
|
104
119
|
if (typeof v === 'number') {
|
|
@@ -160,9 +175,13 @@ export class Cell {
|
|
|
160
175
|
this._data.v = val ? 1 : 0;
|
|
161
176
|
this._data.t = 'b';
|
|
162
177
|
} else if (val instanceof Date) {
|
|
163
|
-
// Store as
|
|
164
|
-
this._data.v =
|
|
165
|
-
this._data.t = '
|
|
178
|
+
// Store as Excel serial number with date format for maximum compatibility
|
|
179
|
+
this._data.v = this._jsDateToExcel(val);
|
|
180
|
+
this._data.t = 'n';
|
|
181
|
+
// Apply a default date format if no style is set
|
|
182
|
+
if (this._data.s === undefined) {
|
|
183
|
+
this._data.s = this._worksheet.workbook.styles.createStyle({ numberFormat: 'yyyy-mm-dd' });
|
|
184
|
+
}
|
|
166
185
|
} else if ('error' in val) {
|
|
167
186
|
this._data.v = val.error;
|
|
168
187
|
this._data.t = 'e';
|
|
@@ -272,9 +291,25 @@ export class Cell {
|
|
|
272
291
|
* Check if this cell has a date number format
|
|
273
292
|
*/
|
|
274
293
|
private _isDateFormat(): boolean {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
294
|
+
if (this._data.s === undefined) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const style = this._worksheet.workbook.styles.getStyle(this._data.s);
|
|
298
|
+
if (!style.numberFormat) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
// Common date format patterns
|
|
302
|
+
const fmt = style.numberFormat.toLowerCase();
|
|
303
|
+
return (
|
|
304
|
+
fmt.includes('y') ||
|
|
305
|
+
fmt.includes('m') ||
|
|
306
|
+
fmt.includes('d') ||
|
|
307
|
+
fmt.includes('h') ||
|
|
308
|
+
fmt.includes('s') ||
|
|
309
|
+
fmt === 'general date' ||
|
|
310
|
+
fmt === 'short date' ||
|
|
311
|
+
fmt === 'long date'
|
|
312
|
+
);
|
|
278
313
|
}
|
|
279
314
|
|
|
280
315
|
/**
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -163,3 +163,31 @@ export interface PivotCacheField {
|
|
|
163
163
|
* Pivot field axis assignment
|
|
164
164
|
*/
|
|
165
165
|
export type PivotFieldAxis = 'row' | 'column' | 'filter' | 'value';
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Configuration for creating a sheet from an array of objects
|
|
169
|
+
*/
|
|
170
|
+
export interface SheetFromDataConfig<T extends object = Record<string, unknown>> {
|
|
171
|
+
/** Name of the sheet to create */
|
|
172
|
+
name: string;
|
|
173
|
+
/** Array of objects with the same structure */
|
|
174
|
+
data: T[];
|
|
175
|
+
/** Column definitions (optional - defaults to all keys from first object) */
|
|
176
|
+
columns?: ColumnConfig<T>[];
|
|
177
|
+
/** Apply header styling (bold text) (default: true) */
|
|
178
|
+
headerStyle?: boolean;
|
|
179
|
+
/** Starting cell address (default: 'A1') */
|
|
180
|
+
startCell?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Column configuration for sheet data
|
|
185
|
+
*/
|
|
186
|
+
export interface ColumnConfig<T = Record<string, unknown>> {
|
|
187
|
+
/** Key from the object to use for this column */
|
|
188
|
+
key: keyof T;
|
|
189
|
+
/** Header text (optional - defaults to key name) */
|
|
190
|
+
header?: string;
|
|
191
|
+
/** Cell style for data cells in this column */
|
|
192
|
+
style?: CellStyle;
|
|
193
|
+
}
|
package/src/workbook.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
SheetDefinition,
|
|
4
|
+
Relationship,
|
|
5
|
+
PivotTableConfig,
|
|
6
|
+
CellValue,
|
|
7
|
+
SheetFromDataConfig,
|
|
8
|
+
ColumnConfig,
|
|
9
|
+
} from './types';
|
|
3
10
|
import { Worksheet } from './worksheet';
|
|
4
11
|
import { SharedStrings } from './shared-strings';
|
|
5
12
|
import { Styles } from './styles';
|
|
@@ -276,6 +283,126 @@ export class Workbook {
|
|
|
276
283
|
return copy;
|
|
277
284
|
}
|
|
278
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Create a new worksheet from an array of objects.
|
|
288
|
+
*
|
|
289
|
+
* The first row contains headers (object keys or custom column headers),
|
|
290
|
+
* and subsequent rows contain the object values.
|
|
291
|
+
*
|
|
292
|
+
* @param config - Configuration for the sheet creation
|
|
293
|
+
* @returns The created Worksheet
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* const data = [
|
|
298
|
+
* { name: 'Alice', age: 30, city: 'Paris' },
|
|
299
|
+
* { name: 'Bob', age: 25, city: 'London' },
|
|
300
|
+
* { name: 'Charlie', age: 35, city: 'Berlin' },
|
|
301
|
+
* ];
|
|
302
|
+
*
|
|
303
|
+
* // Simple usage - all object keys become columns
|
|
304
|
+
* const sheet = wb.addSheetFromData({
|
|
305
|
+
* name: 'People',
|
|
306
|
+
* data: data,
|
|
307
|
+
* });
|
|
308
|
+
*
|
|
309
|
+
* // With custom column configuration
|
|
310
|
+
* const sheet2 = wb.addSheetFromData({
|
|
311
|
+
* name: 'People Custom',
|
|
312
|
+
* data: data,
|
|
313
|
+
* columns: [
|
|
314
|
+
* { key: 'name', header: 'Full Name' },
|
|
315
|
+
* { key: 'age', header: 'Age (years)' },
|
|
316
|
+
* ],
|
|
317
|
+
* });
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
addSheetFromData<T extends object>(config: SheetFromDataConfig<T>): Worksheet {
|
|
321
|
+
const { name, data, columns, headerStyle = true, startCell = 'A1' } = config;
|
|
322
|
+
|
|
323
|
+
if (data.length === 0) {
|
|
324
|
+
// Create empty sheet if no data
|
|
325
|
+
return this.addSheet(name);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Create the new sheet
|
|
329
|
+
const sheet = this.addSheet(name);
|
|
330
|
+
|
|
331
|
+
// Parse start cell
|
|
332
|
+
const startAddr = parseAddress(startCell);
|
|
333
|
+
let startRow = startAddr.row;
|
|
334
|
+
const startCol = startAddr.col;
|
|
335
|
+
|
|
336
|
+
// Determine columns to use
|
|
337
|
+
const columnConfigs: ColumnConfig<T>[] = columns ?? this._inferColumns(data[0]);
|
|
338
|
+
|
|
339
|
+
// Write header row
|
|
340
|
+
for (let colIdx = 0; colIdx < columnConfigs.length; colIdx++) {
|
|
341
|
+
const colConfig = columnConfigs[colIdx];
|
|
342
|
+
const headerText = colConfig.header ?? String(colConfig.key);
|
|
343
|
+
const cell = sheet.cell(startRow, startCol + colIdx);
|
|
344
|
+
cell.value = headerText;
|
|
345
|
+
|
|
346
|
+
// Apply header style if enabled
|
|
347
|
+
if (headerStyle) {
|
|
348
|
+
cell.style = { bold: true };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Move to data rows
|
|
353
|
+
startRow++;
|
|
354
|
+
|
|
355
|
+
// Write data rows
|
|
356
|
+
for (let rowIdx = 0; rowIdx < data.length; rowIdx++) {
|
|
357
|
+
const rowData = data[rowIdx];
|
|
358
|
+
|
|
359
|
+
for (let colIdx = 0; colIdx < columnConfigs.length; colIdx++) {
|
|
360
|
+
const colConfig = columnConfigs[colIdx];
|
|
361
|
+
const value = rowData[colConfig.key];
|
|
362
|
+
const cell = sheet.cell(startRow + rowIdx, startCol + colIdx);
|
|
363
|
+
|
|
364
|
+
// Convert value to CellValue
|
|
365
|
+
cell.value = this._toCellValue(value);
|
|
366
|
+
|
|
367
|
+
// Apply column style if defined
|
|
368
|
+
if (colConfig.style) {
|
|
369
|
+
cell.style = colConfig.style;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return sheet;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Infer column configuration from the first data object
|
|
379
|
+
*/
|
|
380
|
+
private _inferColumns<T extends object>(sample: T): ColumnConfig<T>[] {
|
|
381
|
+
return (Object.keys(sample) as (keyof T)[]).map((key) => ({
|
|
382
|
+
key,
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Convert an unknown value to a CellValue
|
|
388
|
+
*/
|
|
389
|
+
private _toCellValue(value: unknown): CellValue {
|
|
390
|
+
if (value === null || value === undefined) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') {
|
|
394
|
+
return value;
|
|
395
|
+
}
|
|
396
|
+
if (value instanceof Date) {
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
if (typeof value === 'object' && 'error' in value) {
|
|
400
|
+
return value as CellValue;
|
|
401
|
+
}
|
|
402
|
+
// Convert other types to string
|
|
403
|
+
return String(value);
|
|
404
|
+
}
|
|
405
|
+
|
|
279
406
|
/**
|
|
280
407
|
* Create a pivot table from source data.
|
|
281
408
|
*
|
|
@@ -521,7 +648,8 @@ export class Workbook {
|
|
|
521
648
|
createElement('Relationship', { Id: rel.id, Type: rel.type, Target: rel.target }, []),
|
|
522
649
|
);
|
|
523
650
|
|
|
524
|
-
|
|
651
|
+
// Calculate next available relationship ID based on existing max ID
|
|
652
|
+
let nextRelId = Math.max(0, ...this._relationships.map((r) => parseInt(r.id.replace('rId', ''), 10) || 0)) + 1;
|
|
525
653
|
|
|
526
654
|
// Add shared strings relationship if needed
|
|
527
655
|
if (this._sharedStrings.count > 0) {
|