@niicojs/excel 0.1.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.
@@ -0,0 +1,523 @@
1
+ import type { AggregationType, PivotFieldAxis } from './types';
2
+ import { PivotCache } from './pivot-cache';
3
+ import { createElement, stringifyXml, XmlNode } from './utils/xml';
4
+
5
+ /**
6
+ * Internal structure for tracking field assignments
7
+ */
8
+ interface FieldAssignment {
9
+ fieldName: string;
10
+ fieldIndex: number;
11
+ axis: PivotFieldAxis;
12
+ aggregation?: AggregationType;
13
+ displayName?: string;
14
+ }
15
+
16
+ /**
17
+ * Represents an Excel pivot table with a fluent API for configuration.
18
+ */
19
+ export class PivotTable {
20
+ private _name: string;
21
+ private _cache: PivotCache;
22
+ private _targetSheet: string;
23
+ private _targetCell: string;
24
+ private _targetRow: number;
25
+ private _targetCol: number;
26
+
27
+ private _rowFields: FieldAssignment[] = [];
28
+ private _columnFields: FieldAssignment[] = [];
29
+ private _valueFields: FieldAssignment[] = [];
30
+ private _filterFields: FieldAssignment[] = [];
31
+
32
+ private _pivotTableIndex: number;
33
+
34
+ constructor(
35
+ name: string,
36
+ cache: PivotCache,
37
+ targetSheet: string,
38
+ targetCell: string,
39
+ targetRow: number,
40
+ targetCol: number,
41
+ pivotTableIndex: number,
42
+ ) {
43
+ this._name = name;
44
+ this._cache = cache;
45
+ this._targetSheet = targetSheet;
46
+ this._targetCell = targetCell;
47
+ this._targetRow = targetRow;
48
+ this._targetCol = targetCol;
49
+ this._pivotTableIndex = pivotTableIndex;
50
+ }
51
+
52
+ /**
53
+ * Get the pivot table name
54
+ */
55
+ get name(): string {
56
+ return this._name;
57
+ }
58
+
59
+ /**
60
+ * Get the target sheet name
61
+ */
62
+ get targetSheet(): string {
63
+ return this._targetSheet;
64
+ }
65
+
66
+ /**
67
+ * Get the target cell address
68
+ */
69
+ get targetCell(): string {
70
+ return this._targetCell;
71
+ }
72
+
73
+ /**
74
+ * Get the pivot cache
75
+ */
76
+ get cache(): PivotCache {
77
+ return this._cache;
78
+ }
79
+
80
+ /**
81
+ * Get the pivot table index (for file naming)
82
+ */
83
+ get index(): number {
84
+ return this._pivotTableIndex;
85
+ }
86
+
87
+ /**
88
+ * Add a field to the row area
89
+ * @param fieldName - Name of the source field (column header)
90
+ */
91
+ addRowField(fieldName: string): this {
92
+ const fieldIndex = this._cache.getFieldIndex(fieldName);
93
+ if (fieldIndex < 0) {
94
+ throw new Error(`Field not found in source data: ${fieldName}`);
95
+ }
96
+
97
+ this._rowFields.push({
98
+ fieldName,
99
+ fieldIndex,
100
+ axis: 'row',
101
+ });
102
+
103
+ return this;
104
+ }
105
+
106
+ /**
107
+ * Add a field to the column area
108
+ * @param fieldName - Name of the source field (column header)
109
+ */
110
+ addColumnField(fieldName: string): this {
111
+ const fieldIndex = this._cache.getFieldIndex(fieldName);
112
+ if (fieldIndex < 0) {
113
+ throw new Error(`Field not found in source data: ${fieldName}`);
114
+ }
115
+
116
+ this._columnFields.push({
117
+ fieldName,
118
+ fieldIndex,
119
+ axis: 'column',
120
+ });
121
+
122
+ return this;
123
+ }
124
+
125
+ /**
126
+ * Add a field to the values area with aggregation
127
+ * @param fieldName - Name of the source field (column header)
128
+ * @param aggregation - Aggregation function (sum, count, average, min, max)
129
+ * @param displayName - Optional display name (defaults to "Sum of FieldName")
130
+ */
131
+ addValueField(fieldName: string, aggregation: AggregationType = 'sum', displayName?: string): this {
132
+ const fieldIndex = this._cache.getFieldIndex(fieldName);
133
+ if (fieldIndex < 0) {
134
+ throw new Error(`Field not found in source data: ${fieldName}`);
135
+ }
136
+
137
+ const defaultName = `${aggregation.charAt(0).toUpperCase() + aggregation.slice(1)} of ${fieldName}`;
138
+
139
+ this._valueFields.push({
140
+ fieldName,
141
+ fieldIndex,
142
+ axis: 'value',
143
+ aggregation,
144
+ displayName: displayName || defaultName,
145
+ });
146
+
147
+ return this;
148
+ }
149
+
150
+ /**
151
+ * Add a field to the filter (page) area
152
+ * @param fieldName - Name of the source field (column header)
153
+ */
154
+ addFilterField(fieldName: string): this {
155
+ const fieldIndex = this._cache.getFieldIndex(fieldName);
156
+ if (fieldIndex < 0) {
157
+ throw new Error(`Field not found in source data: ${fieldName}`);
158
+ }
159
+
160
+ this._filterFields.push({
161
+ fieldName,
162
+ fieldIndex,
163
+ axis: 'filter',
164
+ });
165
+
166
+ return this;
167
+ }
168
+
169
+ /**
170
+ * Generate the pivotTableDefinition XML
171
+ */
172
+ toXml(): string {
173
+ const children: XmlNode[] = [];
174
+
175
+ // Calculate location (estimate based on fields)
176
+ const locationRef = this._calculateLocationRef();
177
+
178
+ // Calculate first data row/col offsets (1-based, relative to pivot table)
179
+ // firstHeaderRow: row offset of column headers (usually 1)
180
+ // firstDataRow: row offset where data starts (after filters and column headers)
181
+ // firstDataCol: column offset where data starts (after row labels)
182
+ const filterRowCount = this._filterFields.length > 0 ? this._filterFields.length + 1 : 0;
183
+ const headerRows = this._columnFields.length > 0 ? 1 : 0;
184
+ const firstDataRow = filterRowCount + headerRows + 1;
185
+ const firstDataCol = this._rowFields.length > 0 ? this._rowFields.length : 1;
186
+
187
+ const locationNode = createElement(
188
+ 'location',
189
+ {
190
+ ref: locationRef,
191
+ firstHeaderRow: String(filterRowCount + 1),
192
+ firstDataRow: String(firstDataRow),
193
+ firstDataCol: String(firstDataCol),
194
+ },
195
+ [],
196
+ );
197
+ children.push(locationNode);
198
+
199
+ // Build pivotFields (one per source field)
200
+ const pivotFieldNodes: XmlNode[] = [];
201
+ for (const cacheField of this._cache.fields) {
202
+ const fieldNode = this._buildPivotFieldNode(cacheField.index);
203
+ pivotFieldNodes.push(fieldNode);
204
+ }
205
+ children.push(createElement('pivotFields', { count: String(pivotFieldNodes.length) }, pivotFieldNodes));
206
+
207
+ // Row fields
208
+ if (this._rowFields.length > 0) {
209
+ const rowFieldNodes = this._rowFields.map((f) => createElement('field', { x: String(f.fieldIndex) }, []));
210
+ children.push(createElement('rowFields', { count: String(rowFieldNodes.length) }, rowFieldNodes));
211
+
212
+ // Row items
213
+ const rowItemNodes = this._buildRowItems();
214
+ children.push(createElement('rowItems', { count: String(rowItemNodes.length) }, rowItemNodes));
215
+ }
216
+
217
+ // Column fields
218
+ if (this._columnFields.length > 0) {
219
+ const colFieldNodes = this._columnFields.map((f) => createElement('field', { x: String(f.fieldIndex) }, []));
220
+ // If we have multiple value fields, add -2 to indicate where "Values" header goes
221
+ if (this._valueFields.length > 1) {
222
+ colFieldNodes.push(createElement('field', { x: '-2' }, []));
223
+ }
224
+ children.push(createElement('colFields', { count: String(colFieldNodes.length) }, colFieldNodes));
225
+
226
+ // Column items - need to account for multiple value fields
227
+ const colItemNodes = this._buildColItems();
228
+ children.push(createElement('colItems', { count: String(colItemNodes.length) }, colItemNodes));
229
+ } else if (this._valueFields.length > 1) {
230
+ // If no column fields but we have multiple values, need colFields with -2 (data field indicator)
231
+ children.push(createElement('colFields', { count: '1' }, [createElement('field', { x: '-2' }, [])]));
232
+
233
+ // Column items for each value field
234
+ const colItemNodes: XmlNode[] = [];
235
+ for (let i = 0; i < this._valueFields.length; i++) {
236
+ colItemNodes.push(createElement('i', {}, [createElement('x', i === 0 ? {} : { v: String(i) }, [])]));
237
+ }
238
+ children.push(createElement('colItems', { count: String(colItemNodes.length) }, colItemNodes));
239
+ } else if (this._valueFields.length === 1) {
240
+ // Single value field - just add a single column item
241
+ children.push(createElement('colItems', { count: '1' }, [createElement('i', {}, [])]));
242
+ }
243
+
244
+ // Page (filter) fields
245
+ if (this._filterFields.length > 0) {
246
+ const pageFieldNodes = this._filterFields.map((f) =>
247
+ createElement('pageField', { fld: String(f.fieldIndex), hier: '-1' }, []),
248
+ );
249
+ children.push(createElement('pageFields', { count: String(pageFieldNodes.length) }, pageFieldNodes));
250
+ }
251
+
252
+ // Data fields (values)
253
+ if (this._valueFields.length > 0) {
254
+ const dataFieldNodes = this._valueFields.map((f) =>
255
+ createElement(
256
+ 'dataField',
257
+ {
258
+ name: f.displayName || f.fieldName,
259
+ fld: String(f.fieldIndex),
260
+ baseField: '0',
261
+ baseItem: '0',
262
+ subtotal: f.aggregation || 'sum',
263
+ },
264
+ [],
265
+ ),
266
+ );
267
+ children.push(createElement('dataFields', { count: String(dataFieldNodes.length) }, dataFieldNodes));
268
+ }
269
+
270
+ // Pivot table style
271
+ children.push(
272
+ createElement(
273
+ 'pivotTableStyleInfo',
274
+ {
275
+ name: 'PivotStyleMedium9',
276
+ showRowHeaders: '1',
277
+ showColHeaders: '1',
278
+ showRowStripes: '0',
279
+ showColStripes: '0',
280
+ showLastColumn: '1',
281
+ },
282
+ [],
283
+ ),
284
+ );
285
+
286
+ const pivotTableNode = createElement(
287
+ 'pivotTableDefinition',
288
+ {
289
+ xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
290
+ 'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
291
+ name: this._name,
292
+ cacheId: String(this._cache.cacheId),
293
+ applyNumberFormats: '0',
294
+ applyBorderFormats: '0',
295
+ applyFontFormats: '0',
296
+ applyPatternFormats: '0',
297
+ applyAlignmentFormats: '0',
298
+ applyWidthHeightFormats: '1',
299
+ dataCaption: 'Values',
300
+ updatedVersion: '8',
301
+ minRefreshableVersion: '3',
302
+ useAutoFormatting: '1',
303
+ rowGrandTotals: '1',
304
+ colGrandTotals: '1',
305
+ itemPrintTitles: '1',
306
+ createdVersion: '8',
307
+ indent: '0',
308
+ outline: '1',
309
+ outlineData: '1',
310
+ multipleFieldFilters: '0',
311
+ },
312
+ children,
313
+ );
314
+
315
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n${stringifyXml([pivotTableNode])}`;
316
+ }
317
+
318
+ /**
319
+ * Build a pivotField node for a given field index
320
+ */
321
+ private _buildPivotFieldNode(fieldIndex: number): XmlNode {
322
+ const attrs: Record<string, string> = {};
323
+ const children: XmlNode[] = [];
324
+
325
+ // Check if this field is assigned to an axis
326
+ const rowField = this._rowFields.find((f) => f.fieldIndex === fieldIndex);
327
+ const colField = this._columnFields.find((f) => f.fieldIndex === fieldIndex);
328
+ const filterField = this._filterFields.find((f) => f.fieldIndex === fieldIndex);
329
+ const valueField = this._valueFields.find((f) => f.fieldIndex === fieldIndex);
330
+
331
+ if (rowField) {
332
+ attrs.axis = 'axisRow';
333
+ attrs.showAll = '0';
334
+ // Add items for shared values
335
+ const cacheField = this._cache.fields[fieldIndex];
336
+ if (cacheField && cacheField.sharedItems.length > 0) {
337
+ const itemNodes: XmlNode[] = [];
338
+ for (let i = 0; i < cacheField.sharedItems.length; i++) {
339
+ itemNodes.push(createElement('item', { x: String(i) }, []));
340
+ }
341
+ // Add default subtotal item
342
+ itemNodes.push(createElement('item', { t: 'default' }, []));
343
+ children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
344
+ }
345
+ } else if (colField) {
346
+ attrs.axis = 'axisCol';
347
+ attrs.showAll = '0';
348
+ const cacheField = this._cache.fields[fieldIndex];
349
+ if (cacheField && cacheField.sharedItems.length > 0) {
350
+ const itemNodes: XmlNode[] = [];
351
+ for (let i = 0; i < cacheField.sharedItems.length; i++) {
352
+ itemNodes.push(createElement('item', { x: String(i) }, []));
353
+ }
354
+ itemNodes.push(createElement('item', { t: 'default' }, []));
355
+ children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
356
+ }
357
+ } else if (filterField) {
358
+ attrs.axis = 'axisPage';
359
+ attrs.showAll = '0';
360
+ const cacheField = this._cache.fields[fieldIndex];
361
+ if (cacheField && cacheField.sharedItems.length > 0) {
362
+ const itemNodes: XmlNode[] = [];
363
+ for (let i = 0; i < cacheField.sharedItems.length; i++) {
364
+ itemNodes.push(createElement('item', { x: String(i) }, []));
365
+ }
366
+ itemNodes.push(createElement('item', { t: 'default' }, []));
367
+ children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
368
+ }
369
+ } else if (valueField) {
370
+ attrs.dataField = '1';
371
+ attrs.showAll = '0';
372
+ } else {
373
+ attrs.showAll = '0';
374
+ }
375
+
376
+ return createElement('pivotField', attrs, children);
377
+ }
378
+
379
+ /**
380
+ * Build row items based on unique values in row fields
381
+ */
382
+ private _buildRowItems(): XmlNode[] {
383
+ const items: XmlNode[] = [];
384
+
385
+ if (this._rowFields.length === 0) return items;
386
+
387
+ // Get unique values from first row field
388
+ const firstRowField = this._rowFields[0];
389
+ const cacheField = this._cache.fields[firstRowField.fieldIndex];
390
+
391
+ if (cacheField && cacheField.sharedItems.length > 0) {
392
+ for (let i = 0; i < cacheField.sharedItems.length; i++) {
393
+ items.push(createElement('i', {}, [createElement('x', i === 0 ? {} : { v: String(i) }, [])]));
394
+ }
395
+ }
396
+
397
+ // Add grand total row
398
+ items.push(createElement('i', { t: 'grand' }, [createElement('x', {}, [])]));
399
+
400
+ return items;
401
+ }
402
+
403
+ /**
404
+ * Build column items based on unique values in column fields
405
+ */
406
+ private _buildColItems(): XmlNode[] {
407
+ const items: XmlNode[] = [];
408
+
409
+ if (this._columnFields.length === 0) return items;
410
+
411
+ // Get unique values from first column field
412
+ const firstColField = this._columnFields[0];
413
+ const cacheField = this._cache.fields[firstColField.fieldIndex];
414
+
415
+ if (cacheField && cacheField.sharedItems.length > 0) {
416
+ if (this._valueFields.length > 1) {
417
+ // Multiple value fields - need nested items for each column value + value field combination
418
+ for (let colIdx = 0; colIdx < cacheField.sharedItems.length; colIdx++) {
419
+ for (let valIdx = 0; valIdx < this._valueFields.length; valIdx++) {
420
+ const xNodes: XmlNode[] = [
421
+ createElement('x', colIdx === 0 ? {} : { v: String(colIdx) }, []),
422
+ createElement('x', valIdx === 0 ? {} : { v: String(valIdx) }, []),
423
+ ];
424
+ items.push(createElement('i', {}, xNodes));
425
+ }
426
+ }
427
+ } else {
428
+ // Single value field - simple column items
429
+ for (let i = 0; i < cacheField.sharedItems.length; i++) {
430
+ items.push(createElement('i', {}, [createElement('x', i === 0 ? {} : { v: String(i) }, [])]));
431
+ }
432
+ }
433
+ }
434
+
435
+ // Add grand total column(s)
436
+ if (this._valueFields.length > 1) {
437
+ // Grand total for each value field
438
+ for (let valIdx = 0; valIdx < this._valueFields.length; valIdx++) {
439
+ const xNodes: XmlNode[] = [
440
+ createElement('x', {}, []),
441
+ createElement('x', valIdx === 0 ? {} : { v: String(valIdx) }, []),
442
+ ];
443
+ items.push(createElement('i', { t: 'grand' }, xNodes));
444
+ }
445
+ } else {
446
+ items.push(createElement('i', { t: 'grand' }, [createElement('x', {}, [])]));
447
+ }
448
+
449
+ return items;
450
+ }
451
+
452
+ /**
453
+ * Calculate the location reference for the pivot table output
454
+ */
455
+ private _calculateLocationRef(): string {
456
+ // Estimate output size based on fields
457
+ const numRows = this._estimateRowCount();
458
+ const numCols = this._estimateColCount();
459
+
460
+ const startRow = this._targetRow;
461
+ const startCol = this._targetCol;
462
+ const endRow = startRow + numRows - 1;
463
+ const endCol = startCol + numCols - 1;
464
+
465
+ return `${this._colToLetter(startCol)}${startRow}:${this._colToLetter(endCol)}${endRow}`;
466
+ }
467
+
468
+ /**
469
+ * Estimate number of rows in pivot table output
470
+ */
471
+ private _estimateRowCount(): number {
472
+ let count = 1; // Header row
473
+
474
+ // Add filter area rows
475
+ count += this._filterFields.length;
476
+
477
+ // Add row labels (unique values in row fields)
478
+ if (this._rowFields.length > 0) {
479
+ const firstRowField = this._rowFields[0];
480
+ const cacheField = this._cache.fields[firstRowField.fieldIndex];
481
+ count += (cacheField?.sharedItems.length || 1) + 1; // +1 for grand total
482
+ } else {
483
+ count += 1; // At least one data row
484
+ }
485
+
486
+ return Math.max(count, 3);
487
+ }
488
+
489
+ /**
490
+ * Estimate number of columns in pivot table output
491
+ */
492
+ private _estimateColCount(): number {
493
+ let count = 0;
494
+
495
+ // Row label columns
496
+ count += Math.max(this._rowFields.length, 1);
497
+
498
+ // Column labels (unique values in column fields)
499
+ if (this._columnFields.length > 0) {
500
+ const firstColField = this._columnFields[0];
501
+ const cacheField = this._cache.fields[firstColField.fieldIndex];
502
+ count += (cacheField?.sharedItems.length || 1) + 1; // +1 for grand total
503
+ } else {
504
+ // Value columns
505
+ count += Math.max(this._valueFields.length, 1);
506
+ }
507
+
508
+ return Math.max(count, 2);
509
+ }
510
+
511
+ /**
512
+ * Convert 0-based column index to letter (A, B, ..., Z, AA, etc.)
513
+ */
514
+ private _colToLetter(col: number): string {
515
+ let result = '';
516
+ let n = col;
517
+ while (n >= 0) {
518
+ result = String.fromCharCode((n % 26) + 65) + result;
519
+ n = Math.floor(n / 26) - 1;
520
+ }
521
+ return result;
522
+ }
523
+ }
package/src/range.ts ADDED
@@ -0,0 +1,141 @@
1
+ import type { CellValue, CellStyle, RangeAddress } from './types';
2
+ import type { Worksheet } from './worksheet';
3
+ import { toAddress, normalizeRange } from './utils/address';
4
+
5
+ /**
6
+ * Represents a range of cells in a worksheet
7
+ */
8
+ export class Range {
9
+ private _worksheet: Worksheet;
10
+ private _range: RangeAddress;
11
+
12
+ constructor(worksheet: Worksheet, range: RangeAddress) {
13
+ this._worksheet = worksheet;
14
+ this._range = normalizeRange(range);
15
+ }
16
+
17
+ /**
18
+ * Get the range address as a string
19
+ */
20
+ get address(): string {
21
+ const start = toAddress(this._range.start.row, this._range.start.col);
22
+ const end = toAddress(this._range.end.row, this._range.end.col);
23
+ if (start === end) return start;
24
+ return `${start}:${end}`;
25
+ }
26
+
27
+ /**
28
+ * Get the number of rows in the range
29
+ */
30
+ get rowCount(): number {
31
+ return this._range.end.row - this._range.start.row + 1;
32
+ }
33
+
34
+ /**
35
+ * Get the number of columns in the range
36
+ */
37
+ get colCount(): number {
38
+ return this._range.end.col - this._range.start.col + 1;
39
+ }
40
+
41
+ /**
42
+ * Get all values in the range as a 2D array
43
+ */
44
+ get values(): CellValue[][] {
45
+ const result: CellValue[][] = [];
46
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
47
+ const row: CellValue[] = [];
48
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
49
+ const cell = this._worksheet.cell(r, c);
50
+ row.push(cell.value);
51
+ }
52
+ result.push(row);
53
+ }
54
+ return result;
55
+ }
56
+
57
+ /**
58
+ * Set values in the range from a 2D array
59
+ */
60
+ set values(data: CellValue[][]) {
61
+ for (let r = 0; r < data.length && r < this.rowCount; r++) {
62
+ const row = data[r];
63
+ for (let c = 0; c < row.length && c < this.colCount; c++) {
64
+ const cell = this._worksheet.cell(this._range.start.row + r, this._range.start.col + c);
65
+ cell.value = row[c];
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Get all formulas in the range as a 2D array
72
+ */
73
+ get formulas(): (string | undefined)[][] {
74
+ const result: (string | undefined)[][] = [];
75
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
76
+ const row: (string | undefined)[] = [];
77
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
78
+ const cell = this._worksheet.cell(r, c);
79
+ row.push(cell.formula);
80
+ }
81
+ result.push(row);
82
+ }
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ * Set formulas in the range from a 2D array
88
+ */
89
+ set formulas(data: (string | undefined)[][]) {
90
+ for (let r = 0; r < data.length && r < this.rowCount; r++) {
91
+ const row = data[r];
92
+ for (let c = 0; c < row.length && c < this.colCount; c++) {
93
+ const cell = this._worksheet.cell(this._range.start.row + r, this._range.start.col + c);
94
+ cell.formula = row[c];
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get the style of the top-left cell
101
+ */
102
+ get style(): CellStyle {
103
+ return this._worksheet.cell(this._range.start.row, this._range.start.col).style;
104
+ }
105
+
106
+ /**
107
+ * Set style for all cells in the range
108
+ */
109
+ set style(style: CellStyle) {
110
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
111
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
112
+ const cell = this._worksheet.cell(r, c);
113
+ cell.style = style;
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Iterate over all cells in the range
120
+ */
121
+ *[Symbol.iterator]() {
122
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
123
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
124
+ yield this._worksheet.cell(r, c);
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Iterate over cells row by row
131
+ */
132
+ *rows() {
133
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
134
+ const row = [];
135
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
136
+ row.push(this._worksheet.cell(r, c));
137
+ }
138
+ yield row;
139
+ }
140
+ }
141
+ }