@niicojs/excel 0.2.2 → 0.2.3

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 CHANGED
@@ -10,6 +10,8 @@ A TypeScript library for Excel/OpenXML manipulation with maximum format preserva
10
10
  - Cell styles (fonts, fills, borders, alignment)
11
11
  - Merged cells
12
12
  - Sheet operations (add, delete, rename, copy)
13
+ - Create sheets from arrays of objects (`addSheetFromData`)
14
+ - Convert sheets to JSON arrays (`toJson`)
13
15
  - Create new workbooks from scratch
14
16
  - TypeScript-first with full type definitions
15
17
 
@@ -28,6 +30,7 @@ import { Workbook } from '@niicojs/excel';
28
30
 
29
31
  // Create a new workbook
30
32
  const wb = Workbook.create();
33
+ wb.addSheet('Sheet1');
31
34
  const sheet = wb.sheet('Sheet1');
32
35
 
33
36
  // Write data
@@ -146,7 +149,7 @@ wb.addSheet('Data');
146
149
  wb.addSheet('Summary', 0); // Insert at index 0
147
150
 
148
151
  // Get sheet names
149
- console.log(wb.sheetNames); // ['Summary', 'Sheet1', 'Data']
152
+ console.log(wb.sheetNames); // ['Summary', 'Data']
150
153
 
151
154
  // Access sheets
152
155
  const sheet = wb.sheet('Data'); // By name
@@ -162,6 +165,115 @@ wb.copySheet('RawData', 'RawData_Backup');
162
165
  wb.deleteSheet('Summary');
163
166
  ```
164
167
 
168
+ ## Creating Sheets from Data
169
+
170
+ Create sheets directly from arrays of objects with `addSheetFromData`:
171
+
172
+ ```typescript
173
+ const wb = Workbook.create();
174
+
175
+ // Simple usage - object keys become column headers
176
+ const employees = [
177
+ { name: 'Alice', age: 30, city: 'Paris' },
178
+ { name: 'Bob', age: 25, city: 'London' },
179
+ ];
180
+
181
+ wb.addSheetFromData({
182
+ name: 'Employees',
183
+ data: employees,
184
+ });
185
+
186
+ // Custom column configuration
187
+ wb.addSheetFromData({
188
+ name: 'Custom',
189
+ data: employees,
190
+ columns: [
191
+ { key: 'name', header: 'Full Name' },
192
+ { key: 'age', header: 'Age (years)' },
193
+ { key: 'city', header: 'Location', style: { bold: true } },
194
+ ],
195
+ });
196
+
197
+ // With formulas and styles using RichCellValue
198
+ const orderLines = [
199
+ { product: 'Widget', price: 10, qty: 5, total: { formula: 'B2*C2', style: { bold: true } } },
200
+ { product: 'Gadget', price: 20, qty: 3, total: { formula: 'B3*C3', style: { bold: true } } },
201
+ ];
202
+
203
+ wb.addSheetFromData({
204
+ name: 'Orders',
205
+ data: orderLines,
206
+ });
207
+
208
+ // Other options
209
+ wb.addSheetFromData({
210
+ name: 'Options',
211
+ data: employees,
212
+ headerStyle: false, // Don't bold headers
213
+ startCell: 'B3', // Start at B3 instead of A1
214
+ });
215
+ ```
216
+
217
+ ## Converting Sheets to JSON
218
+
219
+ Convert sheet data back to arrays of objects with `toJson`:
220
+
221
+ ```typescript
222
+ const sheet = wb.sheet('Data');
223
+
224
+ // Using first row as headers
225
+ const data = sheet.toJson();
226
+ // [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
227
+
228
+ // Using custom field names (first row is data, not headers)
229
+ const data = sheet.toJson({
230
+ fields: ['name', 'age', 'city'],
231
+ });
232
+
233
+ // With TypeScript generics
234
+ interface Person {
235
+ name: string | null;
236
+ age: number | null;
237
+ }
238
+ const people = sheet.toJson<Person>();
239
+
240
+ // Starting from a specific position
241
+ const data = sheet.toJson({
242
+ startRow: 2, // Skip first 2 rows (0-based)
243
+ startCol: 1, // Start from column B
244
+ });
245
+
246
+ // Limiting the range
247
+ const data = sheet.toJson({
248
+ endRow: 10, // Stop at row 11 (0-based, inclusive)
249
+ endCol: 3, // Only read columns A-D
250
+ });
251
+
252
+ // Continue past empty rows
253
+ const data = sheet.toJson({
254
+ stopOnEmptyRow: false, // Default is true
255
+ });
256
+ ```
257
+
258
+ ### Roundtrip Example
259
+
260
+ ```typescript
261
+ // Create from objects
262
+ const originalData = [
263
+ { name: 'Alice', age: 30 },
264
+ { name: 'Bob', age: 25 },
265
+ ];
266
+
267
+ const sheet = wb.addSheetFromData({
268
+ name: 'People',
269
+ data: originalData,
270
+ });
271
+
272
+ // Read back as objects
273
+ const readData = sheet.toJson();
274
+ // readData equals originalData
275
+ ```
276
+
165
277
  ## Saving
166
278
 
167
279
  ```typescript
@@ -187,6 +299,38 @@ type CellType = 'number' | 'string' | 'boolean' | 'date' | 'error' | 'empty';
187
299
 
188
300
  // Border types
189
301
  type BorderType = 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
302
+
303
+ // Configuration for addSheetFromData
304
+ interface SheetFromDataConfig<T> {
305
+ name: string;
306
+ data: T[];
307
+ columns?: ColumnConfig<T>[];
308
+ headerStyle?: boolean; // Default: true
309
+ startCell?: string; // Default: 'A1'
310
+ }
311
+
312
+ interface ColumnConfig<T> {
313
+ key: keyof T;
314
+ header?: string;
315
+ style?: CellStyle;
316
+ }
317
+
318
+ // Rich cell value for formulas/styles in data
319
+ interface RichCellValue {
320
+ value?: CellValue;
321
+ formula?: string;
322
+ style?: CellStyle;
323
+ }
324
+
325
+ // Configuration for toJson
326
+ interface SheetToJsonConfig {
327
+ fields?: string[];
328
+ startRow?: number;
329
+ startCol?: number;
330
+ endRow?: number;
331
+ endCol?: number;
332
+ stopOnEmptyRow?: boolean; // Default: true
333
+ }
190
334
  ```
191
335
 
192
336
  ## Format Preservation
package/dist/index.cjs CHANGED
@@ -823,6 +823,102 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
823
823
  return this._cells;
824
824
  }
825
825
  /**
826
+ * Convert sheet data to an array of JSON objects.
827
+ *
828
+ * @param config - Configuration options
829
+ * @returns Array of objects where keys are field names and values are cell values
830
+ *
831
+ * @example
832
+ * ```typescript
833
+ * // Using first row as headers
834
+ * const data = sheet.toJson();
835
+ *
836
+ * // Using custom field names
837
+ * const data = sheet.toJson({ fields: ['name', 'age', 'city'] });
838
+ *
839
+ * // Starting from a specific row/column
840
+ * const data = sheet.toJson({ startRow: 2, startCol: 1 });
841
+ * ```
842
+ */ toJson(config = {}) {
843
+ const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true } = config;
844
+ // Get the bounds of data in the sheet
845
+ const bounds = this._getDataBounds();
846
+ if (!bounds) {
847
+ return [];
848
+ }
849
+ const effectiveEndRow = endRow ?? bounds.maxRow;
850
+ const effectiveEndCol = endCol ?? bounds.maxCol;
851
+ // Determine field names
852
+ let fieldNames;
853
+ let dataStartRow;
854
+ if (fields) {
855
+ // Use provided field names, data starts at startRow
856
+ fieldNames = fields;
857
+ dataStartRow = startRow;
858
+ } else {
859
+ // Use first row as headers
860
+ fieldNames = [];
861
+ for(let col = startCol; col <= effectiveEndCol; col++){
862
+ const cell = this._cells.get(toAddress(startRow, col));
863
+ const value = cell?.value;
864
+ fieldNames.push(value != null ? String(value) : `column${col}`);
865
+ }
866
+ dataStartRow = startRow + 1;
867
+ }
868
+ // Read data rows
869
+ const result = [];
870
+ for(let row = dataStartRow; row <= effectiveEndRow; row++){
871
+ const obj = {};
872
+ let hasData = false;
873
+ for(let colOffset = 0; colOffset < fieldNames.length; colOffset++){
874
+ const col = startCol + colOffset;
875
+ const cell = this._cells.get(toAddress(row, col));
876
+ const value = cell?.value ?? null;
877
+ if (value !== null) {
878
+ hasData = true;
879
+ }
880
+ const fieldName = fieldNames[colOffset];
881
+ if (fieldName) {
882
+ obj[fieldName] = value;
883
+ }
884
+ }
885
+ // Stop on empty row if configured
886
+ if (stopOnEmptyRow && !hasData) {
887
+ break;
888
+ }
889
+ result.push(obj);
890
+ }
891
+ return result;
892
+ }
893
+ /**
894
+ * Get the bounds of data in the sheet (min/max row and column with data)
895
+ */ _getDataBounds() {
896
+ if (this._cells.size === 0) {
897
+ return null;
898
+ }
899
+ let minRow = Infinity;
900
+ let maxRow = -Infinity;
901
+ let minCol = Infinity;
902
+ let maxCol = -Infinity;
903
+ for (const cell of this._cells.values()){
904
+ if (cell.value !== null) {
905
+ minRow = Math.min(minRow, cell.row);
906
+ maxRow = Math.max(maxRow, cell.row);
907
+ minCol = Math.min(minCol, cell.col);
908
+ maxCol = Math.max(maxCol, cell.col);
909
+ }
910
+ }
911
+ if (minRow === Infinity) {
912
+ return null;
913
+ }
914
+ return {
915
+ minRow,
916
+ maxRow,
917
+ minCol,
918
+ maxCol
919
+ };
920
+ }
921
+ /**
826
922
  * Generate XML for this worksheet
827
923
  */ toXml() {
828
924
  // Build sheetData from cells
package/dist/index.d.cts CHANGED
@@ -168,6 +168,39 @@ interface RichCellValue {
168
168
  /** Cell style */
169
169
  style?: CellStyle;
170
170
  }
171
+ /**
172
+ * Configuration for converting a sheet to JSON objects.
173
+ */
174
+ interface SheetToJsonConfig {
175
+ /**
176
+ * Field names to use for each column.
177
+ * If provided, the first row of data starts at row 1 (or startRow).
178
+ * If not provided, the first row is used as field names.
179
+ */
180
+ fields?: string[];
181
+ /**
182
+ * Starting row (0-based). Defaults to 0.
183
+ * If fields are not provided, this row contains the headers.
184
+ * If fields are provided, this is the first data row.
185
+ */
186
+ startRow?: number;
187
+ /**
188
+ * Starting column (0-based). Defaults to 0.
189
+ */
190
+ startCol?: number;
191
+ /**
192
+ * Ending row (0-based, inclusive). Defaults to the last row with data.
193
+ */
194
+ endRow?: number;
195
+ /**
196
+ * Ending column (0-based, inclusive). Defaults to the last column with data.
197
+ */
198
+ endCol?: number;
199
+ /**
200
+ * If true, stop reading when an empty row is encountered. Defaults to true.
201
+ */
202
+ stopOnEmptyRow?: boolean;
203
+ }
171
204
 
172
205
  /**
173
206
  * Represents a single cell in a worksheet
@@ -377,6 +410,29 @@ declare class Worksheet {
377
410
  * Get all cells in the worksheet
378
411
  */
379
412
  get cells(): Map<string, Cell>;
413
+ /**
414
+ * Convert sheet data to an array of JSON objects.
415
+ *
416
+ * @param config - Configuration options
417
+ * @returns Array of objects where keys are field names and values are cell values
418
+ *
419
+ * @example
420
+ * ```typescript
421
+ * // Using first row as headers
422
+ * const data = sheet.toJson();
423
+ *
424
+ * // Using custom field names
425
+ * const data = sheet.toJson({ fields: ['name', 'age', 'city'] });
426
+ *
427
+ * // Starting from a specific row/column
428
+ * const data = sheet.toJson({ startRow: 2, startCol: 1 });
429
+ * ```
430
+ */
431
+ toJson<T = Record<string, CellValue>>(config?: SheetToJsonConfig): T[];
432
+ /**
433
+ * Get the bounds of data in the sheet (min/max row and column with data)
434
+ */
435
+ private _getDataBounds;
380
436
  /**
381
437
  * Generate XML for this worksheet
382
438
  */
@@ -836,5 +892,5 @@ declare const parseRange: (range: string) => RangeAddress;
836
892
  declare const toRange: (range: RangeAddress) => string;
837
893
 
838
894
  export { Cell, PivotCache, PivotTable, Range, SharedStrings, Styles, Workbook, Worksheet, parseAddress, parseRange, toAddress, toRange };
839
- export type { AggregationType, Alignment, BorderStyle, BorderType, CellAddress, CellError, CellStyle, CellType, CellValue, ColumnConfig, ErrorType, PivotFieldAxis, PivotTableConfig, PivotValueConfig, RangeAddress, RichCellValue, SheetFromDataConfig };
895
+ export type { AggregationType, Alignment, BorderStyle, BorderType, CellAddress, CellError, CellStyle, CellType, CellValue, ColumnConfig, ErrorType, PivotFieldAxis, PivotTableConfig, PivotValueConfig, RangeAddress, RichCellValue, SheetFromDataConfig, SheetToJsonConfig };
840
896
  //# sourceMappingURL=index.d.cts.map