@niicojs/excel 0.1.0 → 0.2.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/dist/index.cjs CHANGED
@@ -152,6 +152,10 @@ const ERROR_TYPES = new Set([
152
152
  }
153
153
  switch(t){
154
154
  case 'n':
155
+ // Check if this is actually a date stored as number
156
+ if (this._isDateFormat()) {
157
+ return 'date';
158
+ }
155
159
  return 'number';
156
160
  case 's':
157
161
  case 'str':
@@ -164,7 +168,12 @@ const ERROR_TYPES = new Set([
164
168
  return 'date';
165
169
  default:
166
170
  // If no type but has value, infer from value
167
- if (typeof this._data.v === 'number') return 'number';
171
+ if (typeof this._data.v === 'number') {
172
+ if (this._isDateFormat()) {
173
+ return 'date';
174
+ }
175
+ return 'number';
176
+ }
168
177
  if (typeof this._data.v === 'string') return 'string';
169
178
  if (typeof this._data.v === 'boolean') return 'boolean';
170
179
  return 'empty';
@@ -180,7 +189,14 @@ const ERROR_TYPES = new Set([
180
189
  }
181
190
  switch(t){
182
191
  case 'n':
183
- return typeof v === 'number' ? v : parseFloat(String(v));
192
+ {
193
+ const numVal = typeof v === 'number' ? v : parseFloat(String(v));
194
+ // Check if this is actually a date stored as number
195
+ if (this._isDateFormat()) {
196
+ return this._excelDateToJs(numVal);
197
+ }
198
+ return numVal;
199
+ }
184
200
  case 's':
185
201
  // Shared string reference
186
202
  if (typeof v === 'number') {
@@ -242,9 +258,15 @@ const ERROR_TYPES = new Set([
242
258
  this._data.v = val ? 1 : 0;
243
259
  this._data.t = 'b';
244
260
  } else if (val instanceof Date) {
245
- // Store as ISO date string with 'd' type
246
- this._data.v = val.toISOString();
247
- this._data.t = 'd';
261
+ // Store as Excel serial number with date format for maximum compatibility
262
+ this._data.v = this._jsDateToExcel(val);
263
+ this._data.t = 'n';
264
+ // Apply a default date format if no style is set
265
+ if (this._data.s === undefined) {
266
+ this._data.s = this._worksheet.workbook.styles.createStyle({
267
+ numberFormat: 'yyyy-mm-dd'
268
+ });
269
+ }
248
270
  } else if ('error' in val) {
249
271
  this._data.v = val.error;
250
272
  this._data.t = 'e';
@@ -334,9 +356,16 @@ const ERROR_TYPES = new Set([
334
356
  /**
335
357
  * Check if this cell has a date number format
336
358
  */ _isDateFormat() {
337
- // TODO: Check actual number format from styles
338
- // For now, return false - dates should be explicitly typed
339
- return false;
359
+ if (this._data.s === undefined) {
360
+ return false;
361
+ }
362
+ const style = this._worksheet.workbook.styles.getStyle(this._data.s);
363
+ if (!style.numberFormat) {
364
+ return false;
365
+ }
366
+ // Common date format patterns
367
+ const fmt = style.numberFormat.toLowerCase();
368
+ 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';
340
369
  }
341
370
  /**
342
371
  * Convert Excel serial date to JavaScript Date
@@ -2312,8 +2341,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2312
2341
  */ static create() {
2313
2342
  const workbook = new Workbook();
2314
2343
  workbook._dirty = true;
2315
- // Add default sheet
2316
- workbook.addSheet('Sheet1');
2317
2344
  return workbook;
2318
2345
  }
2319
2346
  /**
@@ -2467,6 +2494,110 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2467
2494
  return copy;
2468
2495
  }
2469
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
+ /**
2470
2601
  * Create a pivot table from source data.
2471
2602
  *
2472
2603
  * @param config - Pivot table configuration
@@ -2677,7 +2808,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2677
2808
  Type: rel.type,
2678
2809
  Target: rel.target
2679
2810
  }, []));
2680
- let nextRelId = this._relationships.length + 1;
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;
2681
2813
  // Add shared strings relationship if needed
2682
2814
  if (this._sharedStrings.count > 0) {
2683
2815
  const hasSharedStrings = this._relationships.some((r)=>r.type === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings');
package/dist/index.d.cts CHANGED
@@ -130,6 +130,32 @@ interface PivotCacheField {
130
130
  * Pivot field axis assignment
131
131
  */
132
132
  type PivotFieldAxis = 'row' | 'column' | 'filter' | 'value';
133
+ /**
134
+ * Configuration for creating a sheet from an array of objects
135
+ */
136
+ interface SheetFromDataConfig<T extends object = Record<string, unknown>> {
137
+ /** Name of the sheet to create */
138
+ name: string;
139
+ /** Array of objects with the same structure */
140
+ data: T[];
141
+ /** Column definitions (optional - defaults to all keys from first object) */
142
+ columns?: ColumnConfig<T>[];
143
+ /** Apply header styling (bold text) (default: true) */
144
+ headerStyle?: boolean;
145
+ /** Starting cell address (default: 'A1') */
146
+ startCell?: string;
147
+ }
148
+ /**
149
+ * Column configuration for sheet data
150
+ */
151
+ interface ColumnConfig<T = Record<string, unknown>> {
152
+ /** Key from the object to use for this column */
153
+ key: keyof T;
154
+ /** Header text (optional - defaults to key name) */
155
+ header?: string;
156
+ /** Cell style for data cells in this column */
157
+ style?: CellStyle;
158
+ }
133
159
 
134
160
  /**
135
161
  * Represents a single cell in a worksheet
@@ -665,6 +691,49 @@ declare class Workbook {
665
691
  * Copy a worksheet
666
692
  */
667
693
  copySheet(sourceName: string, newName: string): Worksheet;
694
+ /**
695
+ * Create a new worksheet from an array of objects.
696
+ *
697
+ * The first row contains headers (object keys or custom column headers),
698
+ * and subsequent rows contain the object values.
699
+ *
700
+ * @param config - Configuration for the sheet creation
701
+ * @returns The created Worksheet
702
+ *
703
+ * @example
704
+ * ```typescript
705
+ * const data = [
706
+ * { name: 'Alice', age: 30, city: 'Paris' },
707
+ * { name: 'Bob', age: 25, city: 'London' },
708
+ * { name: 'Charlie', age: 35, city: 'Berlin' },
709
+ * ];
710
+ *
711
+ * // Simple usage - all object keys become columns
712
+ * const sheet = wb.addSheetFromData({
713
+ * name: 'People',
714
+ * data: data,
715
+ * });
716
+ *
717
+ * // With custom column configuration
718
+ * const sheet2 = wb.addSheetFromData({
719
+ * name: 'People Custom',
720
+ * data: data,
721
+ * columns: [
722
+ * { key: 'name', header: 'Full Name' },
723
+ * { key: 'age', header: 'Age (years)' },
724
+ * ],
725
+ * });
726
+ * ```
727
+ */
728
+ addSheetFromData<T extends object>(config: SheetFromDataConfig<T>): Worksheet;
729
+ /**
730
+ * Infer column configuration from the first data object
731
+ */
732
+ private _inferColumns;
733
+ /**
734
+ * Convert an unknown value to a CellValue
735
+ */
736
+ private _toCellValue;
668
737
  /**
669
738
  * Create a pivot table from source data.
670
739
  *
@@ -741,5 +810,5 @@ declare const parseRange: (range: string) => RangeAddress;
741
810
  declare const toRange: (range: RangeAddress) => string;
742
811
 
743
812
  export { Cell, PivotCache, PivotTable, Range, SharedStrings, Styles, Workbook, Worksheet, parseAddress, parseRange, toAddress, toRange };
744
- export type { AggregationType, Alignment, BorderStyle, BorderType, CellAddress, CellError, CellStyle, CellType, CellValue, ErrorType, PivotFieldAxis, PivotTableConfig, PivotValueConfig, RangeAddress };
813
+ export type { AggregationType, Alignment, BorderStyle, BorderType, CellAddress, CellError, CellStyle, CellType, CellValue, ColumnConfig, ErrorType, PivotFieldAxis, PivotTableConfig, PivotValueConfig, RangeAddress, SheetFromDataConfig };
745
814
  //# sourceMappingURL=index.d.cts.map