@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/dist/index.js CHANGED
@@ -821,6 +821,102 @@ const builder = new XMLBuilder(builderOptions);
821
821
  return this._cells;
822
822
  }
823
823
  /**
824
+ * Convert sheet data to an array of JSON objects.
825
+ *
826
+ * @param config - Configuration options
827
+ * @returns Array of objects where keys are field names and values are cell values
828
+ *
829
+ * @example
830
+ * ```typescript
831
+ * // Using first row as headers
832
+ * const data = sheet.toJson();
833
+ *
834
+ * // Using custom field names
835
+ * const data = sheet.toJson({ fields: ['name', 'age', 'city'] });
836
+ *
837
+ * // Starting from a specific row/column
838
+ * const data = sheet.toJson({ startRow: 2, startCol: 1 });
839
+ * ```
840
+ */ toJson(config = {}) {
841
+ const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true } = config;
842
+ // Get the bounds of data in the sheet
843
+ const bounds = this._getDataBounds();
844
+ if (!bounds) {
845
+ return [];
846
+ }
847
+ const effectiveEndRow = endRow ?? bounds.maxRow;
848
+ const effectiveEndCol = endCol ?? bounds.maxCol;
849
+ // Determine field names
850
+ let fieldNames;
851
+ let dataStartRow;
852
+ if (fields) {
853
+ // Use provided field names, data starts at startRow
854
+ fieldNames = fields;
855
+ dataStartRow = startRow;
856
+ } else {
857
+ // Use first row as headers
858
+ fieldNames = [];
859
+ for(let col = startCol; col <= effectiveEndCol; col++){
860
+ const cell = this._cells.get(toAddress(startRow, col));
861
+ const value = cell?.value;
862
+ fieldNames.push(value != null ? String(value) : `column${col}`);
863
+ }
864
+ dataStartRow = startRow + 1;
865
+ }
866
+ // Read data rows
867
+ const result = [];
868
+ for(let row = dataStartRow; row <= effectiveEndRow; row++){
869
+ const obj = {};
870
+ let hasData = false;
871
+ for(let colOffset = 0; colOffset < fieldNames.length; colOffset++){
872
+ const col = startCol + colOffset;
873
+ const cell = this._cells.get(toAddress(row, col));
874
+ const value = cell?.value ?? null;
875
+ if (value !== null) {
876
+ hasData = true;
877
+ }
878
+ const fieldName = fieldNames[colOffset];
879
+ if (fieldName) {
880
+ obj[fieldName] = value;
881
+ }
882
+ }
883
+ // Stop on empty row if configured
884
+ if (stopOnEmptyRow && !hasData) {
885
+ break;
886
+ }
887
+ result.push(obj);
888
+ }
889
+ return result;
890
+ }
891
+ /**
892
+ * Get the bounds of data in the sheet (min/max row and column with data)
893
+ */ _getDataBounds() {
894
+ if (this._cells.size === 0) {
895
+ return null;
896
+ }
897
+ let minRow = Infinity;
898
+ let maxRow = -Infinity;
899
+ let minCol = Infinity;
900
+ let maxCol = -Infinity;
901
+ for (const cell of this._cells.values()){
902
+ if (cell.value !== null) {
903
+ minRow = Math.min(minRow, cell.row);
904
+ maxRow = Math.max(maxRow, cell.row);
905
+ minCol = Math.min(minCol, cell.col);
906
+ maxCol = Math.max(maxCol, cell.col);
907
+ }
908
+ }
909
+ if (minRow === Infinity) {
910
+ return null;
911
+ }
912
+ return {
913
+ minRow,
914
+ maxRow,
915
+ minCol,
916
+ maxCol
917
+ };
918
+ }
919
+ /**
824
920
  * Generate XML for this worksheet
825
921
  */ toXml() {
826
922
  // Build sheetData from cells
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niicojs/excel",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "typescript library to manipulate excel files",
5
5
  "homepage": "https://github.com/niicojs/excel#readme",
6
6
  "bugs": {
package/src/index.ts CHANGED
@@ -29,6 +29,8 @@ export type {
29
29
  SheetFromDataConfig,
30
30
  ColumnConfig,
31
31
  RichCellValue,
32
+ // Sheet to JSON types
33
+ SheetToJsonConfig,
32
34
  } from './types';
33
35
 
34
36
  // Utility exports
package/src/types.ts CHANGED
@@ -204,3 +204,42 @@ export interface RichCellValue {
204
204
  /** Cell style */
205
205
  style?: CellStyle;
206
206
  }
207
+
208
+ /**
209
+ * Configuration for converting a sheet to JSON objects.
210
+ */
211
+ export interface SheetToJsonConfig {
212
+ /**
213
+ * Field names to use for each column.
214
+ * If provided, the first row of data starts at row 1 (or startRow).
215
+ * If not provided, the first row is used as field names.
216
+ */
217
+ fields?: string[];
218
+
219
+ /**
220
+ * Starting row (0-based). Defaults to 0.
221
+ * If fields are not provided, this row contains the headers.
222
+ * If fields are provided, this is the first data row.
223
+ */
224
+ startRow?: number;
225
+
226
+ /**
227
+ * Starting column (0-based). Defaults to 0.
228
+ */
229
+ startCol?: number;
230
+
231
+ /**
232
+ * Ending row (0-based, inclusive). Defaults to the last row with data.
233
+ */
234
+ endRow?: number;
235
+
236
+ /**
237
+ * Ending column (0-based, inclusive). Defaults to the last column with data.
238
+ */
239
+ endCol?: number;
240
+
241
+ /**
242
+ * If true, stop reading when an empty row is encountered. Defaults to true.
243
+ */
244
+ stopOnEmptyRow?: boolean;
245
+ }
package/src/worksheet.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CellData, RangeAddress } from './types';
1
+ import type { CellData, RangeAddress, SheetToJsonConfig, CellValue } from './types';
2
2
  import type { Workbook } from './workbook';
3
3
  import { Cell, parseCellRef } from './cell';
4
4
  import { Range } from './range';
@@ -280,6 +280,117 @@ export class Worksheet {
280
280
  return this._cells;
281
281
  }
282
282
 
283
+ /**
284
+ * Convert sheet data to an array of JSON objects.
285
+ *
286
+ * @param config - Configuration options
287
+ * @returns Array of objects where keys are field names and values are cell values
288
+ *
289
+ * @example
290
+ * ```typescript
291
+ * // Using first row as headers
292
+ * const data = sheet.toJson();
293
+ *
294
+ * // Using custom field names
295
+ * const data = sheet.toJson({ fields: ['name', 'age', 'city'] });
296
+ *
297
+ * // Starting from a specific row/column
298
+ * const data = sheet.toJson({ startRow: 2, startCol: 1 });
299
+ * ```
300
+ */
301
+ toJson<T = Record<string, CellValue>>(config: SheetToJsonConfig = {}): T[] {
302
+ const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true } = config;
303
+
304
+ // Get the bounds of data in the sheet
305
+ const bounds = this._getDataBounds();
306
+ if (!bounds) {
307
+ return [];
308
+ }
309
+
310
+ const effectiveEndRow = endRow ?? bounds.maxRow;
311
+ const effectiveEndCol = endCol ?? bounds.maxCol;
312
+
313
+ // Determine field names
314
+ let fieldNames: string[];
315
+ let dataStartRow: number;
316
+
317
+ if (fields) {
318
+ // Use provided field names, data starts at startRow
319
+ fieldNames = fields;
320
+ dataStartRow = startRow;
321
+ } else {
322
+ // Use first row as headers
323
+ fieldNames = [];
324
+ for (let col = startCol; col <= effectiveEndCol; col++) {
325
+ const cell = this._cells.get(toAddress(startRow, col));
326
+ const value = cell?.value;
327
+ fieldNames.push(value != null ? String(value) : `column${col}`);
328
+ }
329
+ dataStartRow = startRow + 1;
330
+ }
331
+
332
+ // Read data rows
333
+ const result: T[] = [];
334
+
335
+ for (let row = dataStartRow; row <= effectiveEndRow; row++) {
336
+ const obj: Record<string, CellValue> = {};
337
+ let hasData = false;
338
+
339
+ for (let colOffset = 0; colOffset < fieldNames.length; colOffset++) {
340
+ const col = startCol + colOffset;
341
+ const cell = this._cells.get(toAddress(row, col));
342
+ const value = cell?.value ?? null;
343
+
344
+ if (value !== null) {
345
+ hasData = true;
346
+ }
347
+
348
+ const fieldName = fieldNames[colOffset];
349
+ if (fieldName) {
350
+ obj[fieldName] = value;
351
+ }
352
+ }
353
+
354
+ // Stop on empty row if configured
355
+ if (stopOnEmptyRow && !hasData) {
356
+ break;
357
+ }
358
+
359
+ result.push(obj as T);
360
+ }
361
+
362
+ return result;
363
+ }
364
+
365
+ /**
366
+ * Get the bounds of data in the sheet (min/max row and column with data)
367
+ */
368
+ private _getDataBounds(): { minRow: number; maxRow: number; minCol: number; maxCol: number } | null {
369
+ if (this._cells.size === 0) {
370
+ return null;
371
+ }
372
+
373
+ let minRow = Infinity;
374
+ let maxRow = -Infinity;
375
+ let minCol = Infinity;
376
+ let maxCol = -Infinity;
377
+
378
+ for (const cell of this._cells.values()) {
379
+ if (cell.value !== null) {
380
+ minRow = Math.min(minRow, cell.row);
381
+ maxRow = Math.max(maxRow, cell.row);
382
+ minCol = Math.min(minCol, cell.col);
383
+ maxCol = Math.max(maxCol, cell.col);
384
+ }
385
+ }
386
+
387
+ if (minRow === Infinity) {
388
+ return null;
389
+ }
390
+
391
+ return { minRow, maxRow, minCol, maxCol };
392
+ }
393
+
283
394
  /**
284
395
  * Generate XML for this worksheet
285
396
  */