@niicojs/excel 0.2.2 → 0.2.4
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 +373 -5
- package/dist/index.cjs +507 -25
- package/dist/index.d.cts +172 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +172 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +504 -26
- package/package.json +1 -1
- package/src/index.ts +17 -1
- package/src/pivot-cache.ts +11 -1
- package/src/pivot-table.ts +122 -12
- package/src/range.ts +15 -2
- package/src/styles.ts +60 -1
- package/src/types.ts +62 -0
- package/src/utils/address.ts +4 -1
- package/src/utils/xml.ts +0 -7
- package/src/workbook.ts +18 -0
- package/src/worksheet.ts +343 -4
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,6 +8,18 @@ export { Styles } from './styles';
|
|
|
8
8
|
export { PivotTable } from './pivot-table';
|
|
9
9
|
export { PivotCache } from './pivot-cache';
|
|
10
10
|
|
|
11
|
+
// Address utilities
|
|
12
|
+
export {
|
|
13
|
+
parseAddress,
|
|
14
|
+
toAddress,
|
|
15
|
+
parseRange,
|
|
16
|
+
toRange,
|
|
17
|
+
colToLetter,
|
|
18
|
+
letterToCol,
|
|
19
|
+
normalizeRange,
|
|
20
|
+
isInRange,
|
|
21
|
+
} from './utils/address';
|
|
22
|
+
|
|
11
23
|
// Type exports
|
|
12
24
|
export type {
|
|
13
25
|
CellValue,
|
|
@@ -20,16 +32,20 @@ export type {
|
|
|
20
32
|
BorderStyle,
|
|
21
33
|
BorderType,
|
|
22
34
|
Alignment,
|
|
35
|
+
DateHandling,
|
|
23
36
|
// Pivot table types
|
|
24
37
|
PivotTableConfig,
|
|
25
38
|
PivotValueConfig,
|
|
26
39
|
AggregationType,
|
|
27
40
|
PivotFieldAxis,
|
|
41
|
+
PivotSortOrder,
|
|
42
|
+
PivotFieldFilter,
|
|
28
43
|
// Sheet from data types
|
|
29
44
|
SheetFromDataConfig,
|
|
30
45
|
ColumnConfig,
|
|
31
46
|
RichCellValue,
|
|
47
|
+
// Sheet to JSON types
|
|
48
|
+
SheetToJsonConfig,
|
|
32
49
|
} from './types';
|
|
33
50
|
|
|
34
51
|
// Utility exports
|
|
35
|
-
export { parseAddress, toAddress, parseRange, toRange } from './utils/address';
|
package/src/pivot-cache.ts
CHANGED
|
@@ -13,6 +13,7 @@ export class PivotCache {
|
|
|
13
13
|
private _records: CellValue[][] = [];
|
|
14
14
|
private _recordCount = 0;
|
|
15
15
|
private _refreshOnLoad = true; // Default to true
|
|
16
|
+
private _dateGrouping = false;
|
|
16
17
|
|
|
17
18
|
constructor(cacheId: number, sourceSheet: string, sourceRange: string) {
|
|
18
19
|
this._cacheId = cacheId;
|
|
@@ -126,6 +127,9 @@ export class PivotCache {
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
// Enable date grouping flag if any date field exists
|
|
131
|
+
this._dateGrouping = this._fields.some((field) => field.isDate);
|
|
132
|
+
|
|
129
133
|
// Store records
|
|
130
134
|
this._records = data;
|
|
131
135
|
}
|
|
@@ -160,6 +164,8 @@ export class PivotCache {
|
|
|
160
164
|
for (const item of field.sharedItems) {
|
|
161
165
|
sharedItemChildren.push(createElement('s', { v: item }, []));
|
|
162
166
|
}
|
|
167
|
+
} else if (field.isDate) {
|
|
168
|
+
sharedItemsAttrs.containsDate = '1';
|
|
163
169
|
} else if (field.isNumeric) {
|
|
164
170
|
// Numeric field - use "0"/"1" for boolean attributes as Excel expects
|
|
165
171
|
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
@@ -187,7 +193,11 @@ export class PivotCache {
|
|
|
187
193
|
{ ref: this._sourceRange, sheet: this._sourceSheet },
|
|
188
194
|
[],
|
|
189
195
|
);
|
|
190
|
-
const
|
|
196
|
+
const cacheSourceAttrs: Record<string, string> = { type: 'worksheet' };
|
|
197
|
+
if (this._dateGrouping) {
|
|
198
|
+
cacheSourceAttrs.grouping = '1';
|
|
199
|
+
}
|
|
200
|
+
const cacheSourceNode = createElement('cacheSource', cacheSourceAttrs, [worksheetSourceNode]);
|
|
191
201
|
|
|
192
202
|
// Build attributes - refreshOnLoad should come early per OOXML schema
|
|
193
203
|
const definitionAttrs: Record<string, string> = {
|
package/src/pivot-table.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AggregationType, PivotFieldAxis } from './types';
|
|
1
|
+
import type { AggregationType, PivotFieldAxis, PivotFieldFilter, PivotSortOrder } from './types';
|
|
2
2
|
import { PivotCache } from './pivot-cache';
|
|
3
3
|
import { createElement, stringifyXml, XmlNode } from './utils/xml';
|
|
4
4
|
|
|
@@ -11,6 +11,8 @@ interface FieldAssignment {
|
|
|
11
11
|
axis: PivotFieldAxis;
|
|
12
12
|
aggregation?: AggregationType;
|
|
13
13
|
displayName?: string;
|
|
14
|
+
sortOrder?: PivotSortOrder;
|
|
15
|
+
filter?: PivotFieldFilter;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -29,6 +31,8 @@ export class PivotTable {
|
|
|
29
31
|
private _valueFields: FieldAssignment[] = [];
|
|
30
32
|
private _filterFields: FieldAssignment[] = [];
|
|
31
33
|
|
|
34
|
+
private _fieldAssignments: Map<number, FieldAssignment> = new Map();
|
|
35
|
+
|
|
32
36
|
private _pivotTableIndex: number;
|
|
33
37
|
|
|
34
38
|
constructor(
|
|
@@ -94,11 +98,13 @@ export class PivotTable {
|
|
|
94
98
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
const assignment: FieldAssignment = {
|
|
98
102
|
fieldName,
|
|
99
103
|
fieldIndex,
|
|
100
104
|
axis: 'row',
|
|
101
|
-
}
|
|
105
|
+
};
|
|
106
|
+
this._rowFields.push(assignment);
|
|
107
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
102
108
|
|
|
103
109
|
return this;
|
|
104
110
|
}
|
|
@@ -113,11 +119,13 @@ export class PivotTable {
|
|
|
113
119
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
|
|
122
|
+
const assignment: FieldAssignment = {
|
|
117
123
|
fieldName,
|
|
118
124
|
fieldIndex,
|
|
119
125
|
axis: 'column',
|
|
120
|
-
}
|
|
126
|
+
};
|
|
127
|
+
this._columnFields.push(assignment);
|
|
128
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
121
129
|
|
|
122
130
|
return this;
|
|
123
131
|
}
|
|
@@ -136,13 +144,15 @@ export class PivotTable {
|
|
|
136
144
|
|
|
137
145
|
const defaultName = `${aggregation.charAt(0).toUpperCase() + aggregation.slice(1)} of ${fieldName}`;
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
const assignment: FieldAssignment = {
|
|
140
148
|
fieldName,
|
|
141
149
|
fieldIndex,
|
|
142
150
|
axis: 'value',
|
|
143
151
|
aggregation,
|
|
144
152
|
displayName: displayName || defaultName,
|
|
145
|
-
}
|
|
153
|
+
};
|
|
154
|
+
this._valueFields.push(assignment);
|
|
155
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
146
156
|
|
|
147
157
|
return this;
|
|
148
158
|
}
|
|
@@ -157,12 +167,54 @@ export class PivotTable {
|
|
|
157
167
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
158
168
|
}
|
|
159
169
|
|
|
160
|
-
|
|
170
|
+
const assignment: FieldAssignment = {
|
|
161
171
|
fieldName,
|
|
162
172
|
fieldIndex,
|
|
163
173
|
axis: 'filter',
|
|
164
|
-
}
|
|
174
|
+
};
|
|
175
|
+
this._filterFields.push(assignment);
|
|
176
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
177
|
+
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Set a sort order for a row/column field
|
|
183
|
+
*/
|
|
184
|
+
sortField(fieldName: string, order: PivotSortOrder): this {
|
|
185
|
+
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
186
|
+
if (fieldIndex < 0) {
|
|
187
|
+
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
191
|
+
if (!assignment || (assignment.axis !== 'row' && assignment.axis !== 'column')) {
|
|
192
|
+
throw new Error(`Field is not assigned to row or column axis: ${fieldName}`);
|
|
193
|
+
}
|
|
165
194
|
|
|
195
|
+
assignment.sortOrder = order;
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Filter items for a field (include or exclude list)
|
|
201
|
+
*/
|
|
202
|
+
filterField(fieldName: string, filter: PivotFieldFilter): this {
|
|
203
|
+
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
204
|
+
if (fieldIndex < 0) {
|
|
205
|
+
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
209
|
+
if (!assignment) {
|
|
210
|
+
throw new Error(`Field is not assigned to pivot table: ${fieldName}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (filter.include && filter.exclude) {
|
|
214
|
+
throw new Error('Pivot field filter cannot use both include and exclude');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
assignment.filter = filter;
|
|
166
218
|
return this;
|
|
167
219
|
}
|
|
168
220
|
|
|
@@ -328,15 +380,27 @@ export class PivotTable {
|
|
|
328
380
|
const filterField = this._filterFields.find((f) => f.fieldIndex === fieldIndex);
|
|
329
381
|
const valueField = this._valueFields.find((f) => f.fieldIndex === fieldIndex);
|
|
330
382
|
|
|
383
|
+
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
384
|
+
|
|
331
385
|
if (rowField) {
|
|
332
386
|
attrs.axis = 'axisRow';
|
|
333
387
|
attrs.showAll = '0';
|
|
388
|
+
if (assignment?.sortOrder) {
|
|
389
|
+
attrs.sortType = 'ascending';
|
|
390
|
+
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
391
|
+
}
|
|
334
392
|
// Add items for shared values
|
|
335
393
|
const cacheField = this._cache.fields[fieldIndex];
|
|
336
394
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
337
395
|
const itemNodes: XmlNode[] = [];
|
|
396
|
+
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
338
397
|
for (let i = 0; i < cacheField.sharedItems.length; i++) {
|
|
339
|
-
|
|
398
|
+
const shouldInclude = allowedIndexes.has(i);
|
|
399
|
+
const itemAttrs: Record<string, string> = { x: String(i) };
|
|
400
|
+
if (!shouldInclude) {
|
|
401
|
+
itemAttrs.h = '1';
|
|
402
|
+
}
|
|
403
|
+
itemNodes.push(createElement('item', itemAttrs, []));
|
|
340
404
|
}
|
|
341
405
|
// Add default subtotal item
|
|
342
406
|
itemNodes.push(createElement('item', { t: 'default' }, []));
|
|
@@ -345,11 +409,21 @@ export class PivotTable {
|
|
|
345
409
|
} else if (colField) {
|
|
346
410
|
attrs.axis = 'axisCol';
|
|
347
411
|
attrs.showAll = '0';
|
|
412
|
+
if (assignment?.sortOrder) {
|
|
413
|
+
attrs.sortType = 'ascending';
|
|
414
|
+
attrs.sortOrder = assignment.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
415
|
+
}
|
|
348
416
|
const cacheField = this._cache.fields[fieldIndex];
|
|
349
417
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
350
418
|
const itemNodes: XmlNode[] = [];
|
|
419
|
+
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
351
420
|
for (let i = 0; i < cacheField.sharedItems.length; i++) {
|
|
352
|
-
|
|
421
|
+
const shouldInclude = allowedIndexes.has(i);
|
|
422
|
+
const itemAttrs: Record<string, string> = { x: String(i) };
|
|
423
|
+
if (!shouldInclude) {
|
|
424
|
+
itemAttrs.h = '1';
|
|
425
|
+
}
|
|
426
|
+
itemNodes.push(createElement('item', itemAttrs, []));
|
|
353
427
|
}
|
|
354
428
|
itemNodes.push(createElement('item', { t: 'default' }, []));
|
|
355
429
|
children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
|
|
@@ -360,8 +434,14 @@ export class PivotTable {
|
|
|
360
434
|
const cacheField = this._cache.fields[fieldIndex];
|
|
361
435
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
362
436
|
const itemNodes: XmlNode[] = [];
|
|
437
|
+
const allowedIndexes = this._resolveItemFilter(cacheField.sharedItems, assignment?.filter);
|
|
363
438
|
for (let i = 0; i < cacheField.sharedItems.length; i++) {
|
|
364
|
-
|
|
439
|
+
const shouldInclude = allowedIndexes.has(i);
|
|
440
|
+
const itemAttrs: Record<string, string> = { x: String(i) };
|
|
441
|
+
if (!shouldInclude) {
|
|
442
|
+
itemAttrs.h = '1';
|
|
443
|
+
}
|
|
444
|
+
itemNodes.push(createElement('item', itemAttrs, []));
|
|
365
445
|
}
|
|
366
446
|
itemNodes.push(createElement('item', { t: 'default' }, []));
|
|
367
447
|
children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
|
|
@@ -376,6 +456,36 @@ export class PivotTable {
|
|
|
376
456
|
return createElement('pivotField', attrs, children);
|
|
377
457
|
}
|
|
378
458
|
|
|
459
|
+
private _resolveItemFilter(items: string[], filter?: PivotFieldFilter): Set<number> {
|
|
460
|
+
const allowed = new Set<number>();
|
|
461
|
+
|
|
462
|
+
if (!filter || (!filter.include && !filter.exclude)) {
|
|
463
|
+
for (let i = 0; i < items.length; i++) {
|
|
464
|
+
allowed.add(i);
|
|
465
|
+
}
|
|
466
|
+
return allowed;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (filter.include) {
|
|
470
|
+
for (let i = 0; i < items.length; i++) {
|
|
471
|
+
if (filter.include.includes(items[i])) {
|
|
472
|
+
allowed.add(i);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return allowed;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (filter.exclude) {
|
|
479
|
+
for (let i = 0; i < items.length; i++) {
|
|
480
|
+
if (!filter.exclude.includes(items[i])) {
|
|
481
|
+
allowed.add(i);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return allowed;
|
|
487
|
+
}
|
|
488
|
+
|
|
379
489
|
/**
|
|
380
490
|
* Build row items based on unique values in row fields
|
|
381
491
|
*/
|
package/src/range.ts
CHANGED
|
@@ -42,12 +42,25 @@ export class Range {
|
|
|
42
42
|
* Get all values in the range as a 2D array
|
|
43
43
|
*/
|
|
44
44
|
get values(): CellValue[][] {
|
|
45
|
+
return this.getValues();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get all values in the range as a 2D array with options
|
|
50
|
+
*/
|
|
51
|
+
getValues(options: { createMissing?: boolean } = {}): CellValue[][] {
|
|
52
|
+
const { createMissing = true } = options;
|
|
45
53
|
const result: CellValue[][] = [];
|
|
46
54
|
for (let r = this._range.start.row; r <= this._range.end.row; r++) {
|
|
47
55
|
const row: CellValue[] = [];
|
|
48
56
|
for (let c = this._range.start.col; c <= this._range.end.col; c++) {
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
if (createMissing) {
|
|
58
|
+
const cell = this._worksheet.cell(r, c);
|
|
59
|
+
row.push(cell.value);
|
|
60
|
+
} else {
|
|
61
|
+
const cell = this._worksheet.getCellIfExists(r, c);
|
|
62
|
+
row.push(cell?.value ?? null);
|
|
63
|
+
}
|
|
51
64
|
}
|
|
52
65
|
result.push(row);
|
|
53
66
|
}
|
package/src/styles.ts
CHANGED
|
@@ -35,6 +35,52 @@ export class Styles {
|
|
|
35
35
|
|
|
36
36
|
// Cache for style deduplication
|
|
37
37
|
private _styleCache: Map<string, number> = new Map();
|
|
38
|
+
private _styleObjectCache: Map<number, CellStyle> = new Map();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate a deterministic cache key for a style object.
|
|
42
|
+
* More efficient than JSON.stringify as it avoids the overhead of
|
|
43
|
+
* full JSON serialization and produces a consistent key regardless
|
|
44
|
+
* of property order.
|
|
45
|
+
*/
|
|
46
|
+
private _getStyleKey(style: CellStyle): string {
|
|
47
|
+
// Use a delimiter that won't appear in values
|
|
48
|
+
const SEP = '\x00';
|
|
49
|
+
|
|
50
|
+
// Build key from all style properties in a fixed order
|
|
51
|
+
const parts: string[] = [
|
|
52
|
+
style.bold ? '1' : '0',
|
|
53
|
+
style.italic ? '1' : '0',
|
|
54
|
+
style.underline === true ? '1' : style.underline === 'single' ? 's' : style.underline === 'double' ? 'd' : '0',
|
|
55
|
+
style.strike ? '1' : '0',
|
|
56
|
+
style.fontSize?.toString() ?? '',
|
|
57
|
+
style.fontName ?? '',
|
|
58
|
+
style.fontColor ?? '',
|
|
59
|
+
style.fill ?? '',
|
|
60
|
+
style.numberFormat ?? '',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// Border properties
|
|
64
|
+
if (style.border) {
|
|
65
|
+
parts.push(style.border.top ?? '', style.border.bottom ?? '', style.border.left ?? '', style.border.right ?? '');
|
|
66
|
+
} else {
|
|
67
|
+
parts.push('', '', '', '');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Alignment properties
|
|
71
|
+
if (style.alignment) {
|
|
72
|
+
parts.push(
|
|
73
|
+
style.alignment.horizontal ?? '',
|
|
74
|
+
style.alignment.vertical ?? '',
|
|
75
|
+
style.alignment.wrapText ? '1' : '0',
|
|
76
|
+
style.alignment.textRotation?.toString() ?? '',
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
parts.push('', '', '0', '');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return parts.join(SEP);
|
|
83
|
+
}
|
|
38
84
|
|
|
39
85
|
/**
|
|
40
86
|
* Parse styles from XML content
|
|
@@ -228,6 +274,9 @@ export class Styles {
|
|
|
228
274
|
* Get a style by index
|
|
229
275
|
*/
|
|
230
276
|
getStyle(index: number): CellStyle {
|
|
277
|
+
const cached = this._styleObjectCache.get(index);
|
|
278
|
+
if (cached) return { ...cached };
|
|
279
|
+
|
|
231
280
|
const xf = this._cellXfs[index];
|
|
232
281
|
if (!xf) return {};
|
|
233
282
|
|
|
@@ -276,6 +325,7 @@ export class Styles {
|
|
|
276
325
|
};
|
|
277
326
|
}
|
|
278
327
|
|
|
328
|
+
this._styleObjectCache.set(index, { ...style });
|
|
279
329
|
return style;
|
|
280
330
|
}
|
|
281
331
|
|
|
@@ -284,7 +334,7 @@ export class Styles {
|
|
|
284
334
|
* Uses caching to deduplicate identical styles
|
|
285
335
|
*/
|
|
286
336
|
createStyle(style: CellStyle): number {
|
|
287
|
-
const key =
|
|
337
|
+
const key = this._getStyleKey(style);
|
|
288
338
|
const cached = this._styleCache.get(key);
|
|
289
339
|
if (cached !== undefined) {
|
|
290
340
|
return cached;
|
|
@@ -324,10 +374,19 @@ export class Styles {
|
|
|
324
374
|
const index = this._cellXfs.length;
|
|
325
375
|
this._cellXfs.push(xf);
|
|
326
376
|
this._styleCache.set(key, index);
|
|
377
|
+
this._styleObjectCache.set(index, { ...style });
|
|
327
378
|
|
|
328
379
|
return index;
|
|
329
380
|
}
|
|
330
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Clone an existing style by index, optionally overriding fields.
|
|
384
|
+
*/
|
|
385
|
+
cloneStyle(index: number, overrides: Partial<CellStyle> = {}): number {
|
|
386
|
+
const baseStyle = this.getStyle(index);
|
|
387
|
+
return this.createStyle({ ...baseStyle, ...overrides });
|
|
388
|
+
}
|
|
389
|
+
|
|
331
390
|
private _findOrCreateFont(style: CellStyle): number {
|
|
332
391
|
const font: StyleFont = {
|
|
333
392
|
bold: style.bold || false,
|
package/src/types.ts
CHANGED
|
@@ -17,6 +17,11 @@ export type ErrorType = '#NULL!' | '#DIV/0!' | '#VALUE!' | '#REF!' | '#NAME?' |
|
|
|
17
17
|
*/
|
|
18
18
|
export type CellType = 'number' | 'string' | 'boolean' | 'date' | 'error' | 'empty';
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Date handling strategy when serializing cell values.
|
|
22
|
+
*/
|
|
23
|
+
export type DateHandling = 'jsDate' | 'excelSerial' | 'isoString';
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* Style definition for cells
|
|
22
27
|
*/
|
|
@@ -113,6 +118,19 @@ export interface Relationship {
|
|
|
113
118
|
*/
|
|
114
119
|
export type AggregationType = 'sum' | 'count' | 'average' | 'min' | 'max';
|
|
115
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Sort order for pivot fields.
|
|
123
|
+
*/
|
|
124
|
+
export type PivotSortOrder = 'asc' | 'desc';
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Filter configuration for pivot fields.
|
|
128
|
+
*/
|
|
129
|
+
export interface PivotFieldFilter {
|
|
130
|
+
include?: string[];
|
|
131
|
+
exclude?: string[];
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
/**
|
|
117
135
|
* Configuration for a value field in a pivot table
|
|
118
136
|
*/
|
|
@@ -204,3 +222,47 @@ export interface RichCellValue {
|
|
|
204
222
|
/** Cell style */
|
|
205
223
|
style?: CellStyle;
|
|
206
224
|
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Configuration for converting a sheet to JSON objects.
|
|
228
|
+
*/
|
|
229
|
+
export interface SheetToJsonConfig {
|
|
230
|
+
/**
|
|
231
|
+
* Field names to use for each column.
|
|
232
|
+
* If provided, the first row of data starts at row 1 (or startRow).
|
|
233
|
+
* If not provided, the first row is used as field names.
|
|
234
|
+
*/
|
|
235
|
+
fields?: string[];
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Starting row (0-based). Defaults to 0.
|
|
239
|
+
* If fields are not provided, this row contains the headers.
|
|
240
|
+
* If fields are provided, this is the first data row.
|
|
241
|
+
*/
|
|
242
|
+
startRow?: number;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Starting column (0-based). Defaults to 0.
|
|
246
|
+
*/
|
|
247
|
+
startCol?: number;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Ending row (0-based, inclusive). Defaults to the last row with data.
|
|
251
|
+
*/
|
|
252
|
+
endRow?: number;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Ending column (0-based, inclusive). Defaults to the last column with data.
|
|
256
|
+
*/
|
|
257
|
+
endCol?: number;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* If true, stop reading when an empty row is encountered. Defaults to true.
|
|
261
|
+
*/
|
|
262
|
+
stopOnEmptyRow?: boolean;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* How to serialize Date values. Defaults to 'jsDate'.
|
|
266
|
+
*/
|
|
267
|
+
dateHandling?: DateHandling;
|
|
268
|
+
}
|
package/src/utils/address.ts
CHANGED
|
@@ -41,8 +41,11 @@ export const parseAddress = (address: string): CellAddress => {
|
|
|
41
41
|
if (!match) {
|
|
42
42
|
throw new Error(`Invalid cell address: ${address}`);
|
|
43
43
|
}
|
|
44
|
+
const rowNumber = +match[2];
|
|
45
|
+
if (rowNumber <= 0) throw new Error(`Invalid cell address: ${address}`);
|
|
46
|
+
|
|
44
47
|
const col = letterToCol(match[1].toUpperCase());
|
|
45
|
-
const row =
|
|
48
|
+
const row = rowNumber - 1; // Convert to 0-based
|
|
46
49
|
return { row, col };
|
|
47
50
|
};
|
|
48
51
|
|
package/src/utils/xml.ts
CHANGED
|
@@ -138,10 +138,3 @@ export const createElement = (tagName: string, attrs?: Record<string, string>, c
|
|
|
138
138
|
export const createText = (text: string): XmlNode => {
|
|
139
139
|
return { '#text': text } as unknown as XmlNode;
|
|
140
140
|
};
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Adds XML declaration to the start of an XML string
|
|
144
|
-
*/
|
|
145
|
-
export const addXmlDeclaration = (xml: string): string => {
|
|
146
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n${xml}`;
|
|
147
|
-
};
|
package/src/workbook.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
SheetFromDataConfig,
|
|
8
8
|
ColumnConfig,
|
|
9
9
|
RichCellValue,
|
|
10
|
+
DateHandling,
|
|
10
11
|
} from './types';
|
|
11
12
|
import { Worksheet } from './worksheet';
|
|
12
13
|
import { SharedStrings } from './shared-strings';
|
|
@@ -34,6 +35,9 @@ export class Workbook {
|
|
|
34
35
|
private _pivotCaches: PivotCache[] = [];
|
|
35
36
|
private _nextCacheId = 0;
|
|
36
37
|
|
|
38
|
+
// Date serialization handling
|
|
39
|
+
private _dateHandling: DateHandling = 'jsDate';
|
|
40
|
+
|
|
37
41
|
private constructor() {
|
|
38
42
|
this._sharedStrings = new SharedStrings();
|
|
39
43
|
this._styles = Styles.createDefault();
|
|
@@ -119,6 +123,20 @@ export class Workbook {
|
|
|
119
123
|
return this._styles;
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Get the workbook date handling strategy.
|
|
128
|
+
*/
|
|
129
|
+
get dateHandling(): DateHandling {
|
|
130
|
+
return this._dateHandling;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Set the workbook date handling strategy.
|
|
135
|
+
*/
|
|
136
|
+
set dateHandling(value: DateHandling) {
|
|
137
|
+
this._dateHandling = value;
|
|
138
|
+
}
|
|
139
|
+
|
|
122
140
|
/**
|
|
123
141
|
* Get a worksheet by name or index
|
|
124
142
|
*/
|