@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 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
@@ -2467,6 +2496,110 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2467
2496
  return copy;
2468
2497
  }
2469
2498
  /**
2499
+ * Create a new worksheet from an array of objects.
2500
+ *
2501
+ * The first row contains headers (object keys or custom column headers),
2502
+ * and subsequent rows contain the object values.
2503
+ *
2504
+ * @param config - Configuration for the sheet creation
2505
+ * @returns The created Worksheet
2506
+ *
2507
+ * @example
2508
+ * ```typescript
2509
+ * const data = [
2510
+ * { name: 'Alice', age: 30, city: 'Paris' },
2511
+ * { name: 'Bob', age: 25, city: 'London' },
2512
+ * { name: 'Charlie', age: 35, city: 'Berlin' },
2513
+ * ];
2514
+ *
2515
+ * // Simple usage - all object keys become columns
2516
+ * const sheet = wb.addSheetFromData({
2517
+ * name: 'People',
2518
+ * data: data,
2519
+ * });
2520
+ *
2521
+ * // With custom column configuration
2522
+ * const sheet2 = wb.addSheetFromData({
2523
+ * name: 'People Custom',
2524
+ * data: data,
2525
+ * columns: [
2526
+ * { key: 'name', header: 'Full Name' },
2527
+ * { key: 'age', header: 'Age (years)' },
2528
+ * ],
2529
+ * });
2530
+ * ```
2531
+ */ addSheetFromData(config) {
2532
+ const { name, data, columns, headerStyle = true, startCell = 'A1' } = config;
2533
+ if (data.length === 0) {
2534
+ // Create empty sheet if no data
2535
+ return this.addSheet(name);
2536
+ }
2537
+ // Create the new sheet
2538
+ const sheet = this.addSheet(name);
2539
+ // Parse start cell
2540
+ const startAddr = parseAddress(startCell);
2541
+ let startRow = startAddr.row;
2542
+ const startCol = startAddr.col;
2543
+ // Determine columns to use
2544
+ const columnConfigs = columns ?? this._inferColumns(data[0]);
2545
+ // Write header row
2546
+ for(let colIdx = 0; colIdx < columnConfigs.length; colIdx++){
2547
+ const colConfig = columnConfigs[colIdx];
2548
+ const headerText = colConfig.header ?? String(colConfig.key);
2549
+ const cell = sheet.cell(startRow, startCol + colIdx);
2550
+ cell.value = headerText;
2551
+ // Apply header style if enabled
2552
+ if (headerStyle) {
2553
+ cell.style = {
2554
+ bold: true
2555
+ };
2556
+ }
2557
+ }
2558
+ // Move to data rows
2559
+ startRow++;
2560
+ // Write data rows
2561
+ for(let rowIdx = 0; rowIdx < data.length; rowIdx++){
2562
+ const rowData = data[rowIdx];
2563
+ for(let colIdx = 0; colIdx < columnConfigs.length; colIdx++){
2564
+ const colConfig = columnConfigs[colIdx];
2565
+ const value = rowData[colConfig.key];
2566
+ const cell = sheet.cell(startRow + rowIdx, startCol + colIdx);
2567
+ // Convert value to CellValue
2568
+ cell.value = this._toCellValue(value);
2569
+ // Apply column style if defined
2570
+ if (colConfig.style) {
2571
+ cell.style = colConfig.style;
2572
+ }
2573
+ }
2574
+ }
2575
+ return sheet;
2576
+ }
2577
+ /**
2578
+ * Infer column configuration from the first data object
2579
+ */ _inferColumns(sample) {
2580
+ return Object.keys(sample).map((key)=>({
2581
+ key
2582
+ }));
2583
+ }
2584
+ /**
2585
+ * Convert an unknown value to a CellValue
2586
+ */ _toCellValue(value) {
2587
+ if (value === null || value === undefined) {
2588
+ return null;
2589
+ }
2590
+ if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') {
2591
+ return value;
2592
+ }
2593
+ if (value instanceof Date) {
2594
+ return value;
2595
+ }
2596
+ if (typeof value === 'object' && 'error' in value) {
2597
+ return value;
2598
+ }
2599
+ // Convert other types to string
2600
+ return String(value);
2601
+ }
2602
+ /**
2470
2603
  * Create a pivot table from source data.
2471
2604
  *
2472
2605
  * @param config - Pivot table configuration
@@ -2677,7 +2810,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2677
2810
  Type: rel.type,
2678
2811
  Target: rel.target
2679
2812
  }, []));
2680
- let nextRelId = this._relationships.length + 1;
2813
+ // Calculate next available relationship ID based on existing max ID
2814
+ let nextRelId = Math.max(0, ...this._relationships.map((r)=>parseInt(r.id.replace('rId', ''), 10) || 0)) + 1;
2681
2815
  // Add shared strings relationship if needed
2682
2816
  if (this._sharedStrings.count > 0) {
2683
2817
  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