@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 +145 -1
- package/dist/index.cjs +96 -0
- package/dist/index.d.cts +57 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +57 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/types.ts +39 -0
- package/src/worksheet.ts +112 -1
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
package/src/index.ts
CHANGED
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
|
*/
|