@niicojs/excel 0.2.7 → 0.3.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.
- package/LICENSE +20 -20
- package/README.md +241 -8
- package/dist/index.cjs +1455 -152
- package/dist/index.d.cts +359 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +359 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1455 -153
- package/package.json +1 -1
- package/src/index.ts +9 -1
- package/src/pivot-cache.ts +10 -1
- package/src/pivot-table.ts +129 -31
- package/src/range.ts +15 -2
- package/src/shared-strings.ts +65 -16
- package/src/styles.ts +192 -21
- package/src/table.ts +386 -0
- package/src/types.ts +70 -0
- package/src/utils/address.ts +4 -1
- package/src/utils/xml.ts +0 -7
- package/src/workbook.ts +426 -41
- package/src/worksheet.ts +484 -27
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { SharedStrings } from './shared-strings';
|
|
|
7
7
|
export { Styles } from './styles';
|
|
8
8
|
export { PivotTable } from './pivot-table';
|
|
9
9
|
export { PivotCache } from './pivot-cache';
|
|
10
|
+
export { Table } from './table';
|
|
11
|
+
export { parseAddress, toAddress, parseRange, toRange } from './utils/address';
|
|
10
12
|
|
|
11
13
|
// Type exports
|
|
12
14
|
export type {
|
|
@@ -20,11 +22,18 @@ export type {
|
|
|
20
22
|
BorderStyle,
|
|
21
23
|
BorderType,
|
|
22
24
|
Alignment,
|
|
25
|
+
DateHandling,
|
|
23
26
|
// Pivot table types
|
|
24
27
|
PivotTableConfig,
|
|
25
28
|
PivotValueConfig,
|
|
26
29
|
AggregationType,
|
|
27
30
|
PivotFieldAxis,
|
|
31
|
+
PivotSortOrder,
|
|
32
|
+
PivotFieldFilter,
|
|
33
|
+
// Table types
|
|
34
|
+
TableConfig,
|
|
35
|
+
TableStyleConfig,
|
|
36
|
+
TableTotalFunction,
|
|
28
37
|
// Sheet from data types
|
|
29
38
|
SheetFromDataConfig,
|
|
30
39
|
ColumnConfig,
|
|
@@ -34,4 +43,3 @@ export type {
|
|
|
34
43
|
} from './types';
|
|
35
44
|
|
|
36
45
|
// Utility exports
|
|
37
|
-
export { parseAddress, toAddress, parseRange, toRange } from './utils/address';
|
package/src/pivot-cache.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { createElement, stringifyXml, XmlNode } from './utils/xml';
|
|
|
7
7
|
*/
|
|
8
8
|
export class PivotCache {
|
|
9
9
|
private _cacheId: number;
|
|
10
|
+
private _fileIndex: number;
|
|
10
11
|
private _sourceSheet: string;
|
|
11
12
|
private _sourceRange: string;
|
|
12
13
|
private _fields: PivotCacheField[] = [];
|
|
@@ -16,8 +17,9 @@ export class PivotCache {
|
|
|
16
17
|
// Optimized lookup: Map<fieldIndex, Map<stringValue, sharedItemsIndex>>
|
|
17
18
|
private _sharedItemsIndexMap: Map<number, Map<string, number>> = new Map();
|
|
18
19
|
|
|
19
|
-
constructor(cacheId: number, sourceSheet: string, sourceRange: string) {
|
|
20
|
+
constructor(cacheId: number, sourceSheet: string, sourceRange: string, fileIndex: number) {
|
|
20
21
|
this._cacheId = cacheId;
|
|
22
|
+
this._fileIndex = fileIndex;
|
|
21
23
|
this._sourceSheet = sourceSheet;
|
|
22
24
|
this._sourceRange = sourceRange;
|
|
23
25
|
}
|
|
@@ -29,6 +31,13 @@ export class PivotCache {
|
|
|
29
31
|
return this._cacheId;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Get the file index for this cache (used for file naming).
|
|
36
|
+
*/
|
|
37
|
+
get fileIndex(): number {
|
|
38
|
+
return this._fileIndex;
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
/**
|
|
33
42
|
* Set refreshOnLoad option
|
|
34
43
|
*/
|
package/src/pivot-table.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AggregationType, PivotFieldAxis, PivotValueConfig } from './types';
|
|
1
|
+
import type { AggregationType, PivotFieldAxis, PivotFieldFilter, PivotSortOrder, PivotValueConfig } from './types';
|
|
2
2
|
import type { Styles } from './styles';
|
|
3
3
|
import { PivotCache } from './pivot-cache';
|
|
4
4
|
import { createElement, stringifyXml, XmlNode } from './utils/xml';
|
|
@@ -13,6 +13,8 @@ interface FieldAssignment {
|
|
|
13
13
|
aggregation?: AggregationType;
|
|
14
14
|
displayName?: string;
|
|
15
15
|
numFmtId?: number;
|
|
16
|
+
sortOrder?: PivotSortOrder;
|
|
17
|
+
filter?: PivotFieldFilter;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -30,8 +32,10 @@ export class PivotTable {
|
|
|
30
32
|
private _columnFields: FieldAssignment[] = [];
|
|
31
33
|
private _valueFields: FieldAssignment[] = [];
|
|
32
34
|
private _filterFields: FieldAssignment[] = [];
|
|
35
|
+
private _fieldAssignments: Map<number, FieldAssignment> = new Map();
|
|
33
36
|
|
|
34
37
|
private _pivotTableIndex: number;
|
|
38
|
+
private _cacheFileIndex: number;
|
|
35
39
|
private _styles: Styles | null = null;
|
|
36
40
|
|
|
37
41
|
constructor(
|
|
@@ -42,6 +46,7 @@ export class PivotTable {
|
|
|
42
46
|
targetRow: number,
|
|
43
47
|
targetCol: number,
|
|
44
48
|
pivotTableIndex: number,
|
|
49
|
+
cacheFileIndex: number,
|
|
45
50
|
) {
|
|
46
51
|
this._name = name;
|
|
47
52
|
this._cache = cache;
|
|
@@ -50,6 +55,7 @@ export class PivotTable {
|
|
|
50
55
|
this._targetRow = targetRow;
|
|
51
56
|
this._targetCol = targetCol;
|
|
52
57
|
this._pivotTableIndex = pivotTableIndex;
|
|
58
|
+
this._cacheFileIndex = cacheFileIndex;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/**
|
|
@@ -87,6 +93,14 @@ export class PivotTable {
|
|
|
87
93
|
return this._pivotTableIndex;
|
|
88
94
|
}
|
|
89
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Get the pivot cache file index used for rels.
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
get cacheFileIndex(): number {
|
|
101
|
+
return this._cacheFileIndex;
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
/**
|
|
91
105
|
* Set the styles reference for number format resolution
|
|
92
106
|
* @internal
|
|
@@ -106,11 +120,13 @@ export class PivotTable {
|
|
|
106
120
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
107
121
|
}
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
const assignment: FieldAssignment = {
|
|
110
124
|
fieldName,
|
|
111
125
|
fieldIndex,
|
|
112
126
|
axis: 'row',
|
|
113
|
-
}
|
|
127
|
+
};
|
|
128
|
+
this._rowFields.push(assignment);
|
|
129
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
114
130
|
|
|
115
131
|
return this;
|
|
116
132
|
}
|
|
@@ -125,11 +141,13 @@ export class PivotTable {
|
|
|
125
141
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
|
|
144
|
+
const assignment: FieldAssignment = {
|
|
129
145
|
fieldName,
|
|
130
146
|
fieldIndex,
|
|
131
147
|
axis: 'column',
|
|
132
|
-
}
|
|
148
|
+
};
|
|
149
|
+
this._columnFields.push(assignment);
|
|
150
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
133
151
|
|
|
134
152
|
return this;
|
|
135
153
|
}
|
|
@@ -149,12 +167,7 @@ export class PivotTable {
|
|
|
149
167
|
* pivot.addValueField({ field: 'Sales', aggregation: 'sum', name: 'Total Sales', numberFormat: '$#,##0.00' });
|
|
150
168
|
*/
|
|
151
169
|
addValueField(config: PivotValueConfig): this;
|
|
152
|
-
addValueField(
|
|
153
|
-
fieldName: string,
|
|
154
|
-
aggregation?: AggregationType,
|
|
155
|
-
displayName?: string,
|
|
156
|
-
numberFormat?: string,
|
|
157
|
-
): this;
|
|
170
|
+
addValueField(fieldName: string, aggregation?: AggregationType, displayName?: string, numberFormat?: string): this;
|
|
158
171
|
addValueField(
|
|
159
172
|
fieldNameOrConfig: string | PivotValueConfig,
|
|
160
173
|
aggregation: AggregationType = 'sum',
|
|
@@ -192,14 +205,16 @@ export class PivotTable {
|
|
|
192
205
|
numFmtId = this._styles.getOrCreateNumFmtId(format);
|
|
193
206
|
}
|
|
194
207
|
|
|
195
|
-
|
|
208
|
+
const assignment: FieldAssignment = {
|
|
196
209
|
fieldName,
|
|
197
210
|
fieldIndex,
|
|
198
211
|
axis: 'value',
|
|
199
212
|
aggregation: agg,
|
|
200
213
|
displayName: name || defaultName,
|
|
201
214
|
numFmtId,
|
|
202
|
-
}
|
|
215
|
+
};
|
|
216
|
+
this._valueFields.push(assignment);
|
|
217
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
203
218
|
|
|
204
219
|
return this;
|
|
205
220
|
}
|
|
@@ -214,12 +229,61 @@ export class PivotTable {
|
|
|
214
229
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
215
230
|
}
|
|
216
231
|
|
|
217
|
-
|
|
232
|
+
const assignment: FieldAssignment = {
|
|
218
233
|
fieldName,
|
|
219
234
|
fieldIndex,
|
|
220
235
|
axis: 'filter',
|
|
221
|
-
}
|
|
236
|
+
};
|
|
237
|
+
this._filterFields.push(assignment);
|
|
238
|
+
this._fieldAssignments.set(fieldIndex, assignment);
|
|
239
|
+
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Set a sort order for a row or column field
|
|
245
|
+
* @param fieldName - Name of the field to sort
|
|
246
|
+
* @param order - Sort order ('asc' or 'desc')
|
|
247
|
+
*/
|
|
248
|
+
sortField(fieldName: string, order: PivotSortOrder): this {
|
|
249
|
+
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
250
|
+
if (fieldIndex < 0) {
|
|
251
|
+
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
255
|
+
if (!assignment) {
|
|
256
|
+
throw new Error(`Field is not assigned to pivot table: ${fieldName}`);
|
|
257
|
+
}
|
|
258
|
+
if (assignment.axis !== 'row' && assignment.axis !== 'column') {
|
|
259
|
+
throw new Error(`Sort is only supported for row or column fields: ${fieldName}`);
|
|
260
|
+
}
|
|
222
261
|
|
|
262
|
+
assignment.sortOrder = order;
|
|
263
|
+
return this;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Filter items for a row, column, or filter field
|
|
268
|
+
* @param fieldName - Name of the field to filter
|
|
269
|
+
* @param filter - Filter configuration with include or exclude list
|
|
270
|
+
*/
|
|
271
|
+
filterField(fieldName: string, filter: PivotFieldFilter): this {
|
|
272
|
+
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
273
|
+
if (fieldIndex < 0) {
|
|
274
|
+
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const assignment = this._fieldAssignments.get(fieldIndex);
|
|
278
|
+
if (!assignment) {
|
|
279
|
+
throw new Error(`Field is not assigned to pivot table: ${fieldName}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (filter.include && filter.exclude) {
|
|
283
|
+
throw new Error('Cannot use both include and exclude in the same filter');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
assignment.filter = filter;
|
|
223
287
|
return this;
|
|
224
288
|
}
|
|
225
289
|
|
|
@@ -391,30 +455,36 @@ export class PivotTable {
|
|
|
391
455
|
const filterField = this._filterFields.find((f) => f.fieldIndex === fieldIndex);
|
|
392
456
|
const valueField = this._valueFields.find((f) => f.fieldIndex === fieldIndex);
|
|
393
457
|
|
|
458
|
+
// Get the assignment to check for sort/filter options
|
|
459
|
+
const assignment = rowField || colField || filterField;
|
|
460
|
+
|
|
394
461
|
if (rowField) {
|
|
395
462
|
attrs.axis = 'axisRow';
|
|
396
463
|
attrs.showAll = '0';
|
|
464
|
+
|
|
465
|
+
// Add sort order if specified
|
|
466
|
+
if (rowField.sortOrder) {
|
|
467
|
+
attrs.sortType = rowField.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
468
|
+
}
|
|
469
|
+
|
|
397
470
|
// Add items for shared values
|
|
398
471
|
const cacheField = this._cache.fields[fieldIndex];
|
|
399
472
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
400
|
-
const itemNodes
|
|
401
|
-
for (let i = 0; i < cacheField.sharedItems.length; i++) {
|
|
402
|
-
itemNodes.push(createElement('item', { x: String(i) }, []));
|
|
403
|
-
}
|
|
404
|
-
// Add default subtotal item
|
|
405
|
-
itemNodes.push(createElement('item', { t: 'default' }, []));
|
|
473
|
+
const itemNodes = this._buildItemNodes(cacheField.sharedItems, assignment?.filter);
|
|
406
474
|
children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
|
|
407
475
|
}
|
|
408
476
|
} else if (colField) {
|
|
409
477
|
attrs.axis = 'axisCol';
|
|
410
478
|
attrs.showAll = '0';
|
|
479
|
+
|
|
480
|
+
// Add sort order if specified
|
|
481
|
+
if (colField.sortOrder) {
|
|
482
|
+
attrs.sortType = colField.sortOrder === 'asc' ? 'ascending' : 'descending';
|
|
483
|
+
}
|
|
484
|
+
|
|
411
485
|
const cacheField = this._cache.fields[fieldIndex];
|
|
412
486
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
413
|
-
const itemNodes
|
|
414
|
-
for (let i = 0; i < cacheField.sharedItems.length; i++) {
|
|
415
|
-
itemNodes.push(createElement('item', { x: String(i) }, []));
|
|
416
|
-
}
|
|
417
|
-
itemNodes.push(createElement('item', { t: 'default' }, []));
|
|
487
|
+
const itemNodes = this._buildItemNodes(cacheField.sharedItems, assignment?.filter);
|
|
418
488
|
children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
|
|
419
489
|
}
|
|
420
490
|
} else if (filterField) {
|
|
@@ -422,11 +492,7 @@ export class PivotTable {
|
|
|
422
492
|
attrs.showAll = '0';
|
|
423
493
|
const cacheField = this._cache.fields[fieldIndex];
|
|
424
494
|
if (cacheField && cacheField.sharedItems.length > 0) {
|
|
425
|
-
const itemNodes
|
|
426
|
-
for (let i = 0; i < cacheField.sharedItems.length; i++) {
|
|
427
|
-
itemNodes.push(createElement('item', { x: String(i) }, []));
|
|
428
|
-
}
|
|
429
|
-
itemNodes.push(createElement('item', { t: 'default' }, []));
|
|
495
|
+
const itemNodes = this._buildItemNodes(cacheField.sharedItems, assignment?.filter);
|
|
430
496
|
children.push(createElement('items', { count: String(itemNodes.length) }, itemNodes));
|
|
431
497
|
}
|
|
432
498
|
} else if (valueField) {
|
|
@@ -439,6 +505,38 @@ export class PivotTable {
|
|
|
439
505
|
return createElement('pivotField', attrs, children);
|
|
440
506
|
}
|
|
441
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Build item nodes for a pivot field, with optional filtering
|
|
510
|
+
*/
|
|
511
|
+
private _buildItemNodes(sharedItems: string[], filter?: PivotFieldFilter): XmlNode[] {
|
|
512
|
+
const itemNodes: XmlNode[] = [];
|
|
513
|
+
|
|
514
|
+
for (let i = 0; i < sharedItems.length; i++) {
|
|
515
|
+
const itemValue = sharedItems[i];
|
|
516
|
+
const itemAttrs: Record<string, string> = { x: String(i) };
|
|
517
|
+
|
|
518
|
+
// Check if this item should be hidden
|
|
519
|
+
if (filter) {
|
|
520
|
+
let hidden = false;
|
|
521
|
+
if (filter.exclude && filter.exclude.includes(itemValue)) {
|
|
522
|
+
hidden = true;
|
|
523
|
+
} else if (filter.include && !filter.include.includes(itemValue)) {
|
|
524
|
+
hidden = true;
|
|
525
|
+
}
|
|
526
|
+
if (hidden) {
|
|
527
|
+
itemAttrs.h = '1';
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
itemNodes.push(createElement('item', itemAttrs, []));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Add default subtotal item
|
|
535
|
+
itemNodes.push(createElement('item', { t: 'default' }, []));
|
|
536
|
+
|
|
537
|
+
return itemNodes;
|
|
538
|
+
}
|
|
539
|
+
|
|
442
540
|
/**
|
|
443
541
|
* Build row items based on unique values in row fields
|
|
444
542
|
*/
|
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/shared-strings.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
parseXml,
|
|
3
|
+
findElement,
|
|
4
|
+
getChildren,
|
|
5
|
+
getAttr,
|
|
6
|
+
XmlNode,
|
|
7
|
+
stringifyXml,
|
|
8
|
+
createElement,
|
|
9
|
+
createText,
|
|
10
|
+
} from './utils/xml';
|
|
2
11
|
|
|
3
12
|
/**
|
|
4
13
|
* Manages the shared strings table from xl/sharedStrings.xml
|
|
5
14
|
* Excel stores strings in a shared table to reduce file size
|
|
6
15
|
*/
|
|
7
16
|
export class SharedStrings {
|
|
8
|
-
private
|
|
17
|
+
private entries: SharedStringEntry[] = [];
|
|
9
18
|
private stringToIndex: Map<string, number> = new Map();
|
|
10
19
|
private _dirty = false;
|
|
20
|
+
private _totalCount = 0;
|
|
11
21
|
|
|
12
22
|
/**
|
|
13
23
|
* Parse shared strings from XML content
|
|
@@ -18,16 +28,28 @@ export class SharedStrings {
|
|
|
18
28
|
const sst = findElement(parsed, 'sst');
|
|
19
29
|
if (!sst) return ss;
|
|
20
30
|
|
|
31
|
+
const countAttr = getAttr(sst, 'count');
|
|
32
|
+
if (countAttr) {
|
|
33
|
+
const total = parseInt(countAttr, 10);
|
|
34
|
+
if (Number.isFinite(total) && total >= 0) {
|
|
35
|
+
ss._totalCount = total;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
const children = getChildren(sst, 'sst');
|
|
22
40
|
for (const child of children) {
|
|
23
41
|
if ('si' in child) {
|
|
24
42
|
const siChildren = getChildren(child, 'si');
|
|
25
43
|
const text = ss.extractText(siChildren);
|
|
26
|
-
ss.
|
|
27
|
-
ss.stringToIndex.set(text, ss.
|
|
44
|
+
ss.entries.push({ text, node: child });
|
|
45
|
+
ss.stringToIndex.set(text, ss.entries.length - 1);
|
|
28
46
|
}
|
|
29
47
|
}
|
|
30
48
|
|
|
49
|
+
if (ss._totalCount === 0 && ss.entries.length > 0) {
|
|
50
|
+
ss._totalCount = ss.entries.length;
|
|
51
|
+
}
|
|
52
|
+
|
|
31
53
|
return ss;
|
|
32
54
|
}
|
|
33
55
|
|
|
@@ -68,7 +90,7 @@ export class SharedStrings {
|
|
|
68
90
|
* Get a string by index
|
|
69
91
|
*/
|
|
70
92
|
getString(index: number): string | undefined {
|
|
71
|
-
return this.
|
|
93
|
+
return this.entries[index]?.text;
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
/**
|
|
@@ -78,11 +100,18 @@ export class SharedStrings {
|
|
|
78
100
|
addString(str: string): number {
|
|
79
101
|
const existing = this.stringToIndex.get(str);
|
|
80
102
|
if (existing !== undefined) {
|
|
103
|
+
this._totalCount++;
|
|
104
|
+
this._dirty = true;
|
|
81
105
|
return existing;
|
|
82
106
|
}
|
|
83
|
-
const index = this.
|
|
84
|
-
|
|
107
|
+
const index = this.entries.length;
|
|
108
|
+
const tElement = createElement('t', str.startsWith(' ') || str.endsWith(' ') ? { 'xml:space': 'preserve' } : {}, [
|
|
109
|
+
createText(str),
|
|
110
|
+
]);
|
|
111
|
+
const siElement = createElement('si', {}, [tElement]);
|
|
112
|
+
this.entries.push({ text: str, node: siElement });
|
|
85
113
|
this.stringToIndex.set(str, index);
|
|
114
|
+
this._totalCount++;
|
|
86
115
|
this._dirty = true;
|
|
87
116
|
return index;
|
|
88
117
|
}
|
|
@@ -98,7 +127,14 @@ export class SharedStrings {
|
|
|
98
127
|
* Get the count of strings
|
|
99
128
|
*/
|
|
100
129
|
get count(): number {
|
|
101
|
-
return this.
|
|
130
|
+
return this.entries.length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get total usage count of shared strings
|
|
135
|
+
*/
|
|
136
|
+
get totalCount(): number {
|
|
137
|
+
return Math.max(this._totalCount, this.entries.length);
|
|
102
138
|
}
|
|
103
139
|
|
|
104
140
|
/**
|
|
@@ -106,20 +142,28 @@ export class SharedStrings {
|
|
|
106
142
|
*/
|
|
107
143
|
toXml(): string {
|
|
108
144
|
const siElements: XmlNode[] = [];
|
|
109
|
-
for (const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
145
|
+
for (const entry of this.entries) {
|
|
146
|
+
if (entry.node) {
|
|
147
|
+
siElements.push(entry.node);
|
|
148
|
+
} else {
|
|
149
|
+
const str = entry.text;
|
|
150
|
+
const tElement = createElement(
|
|
151
|
+
't',
|
|
152
|
+
str.startsWith(' ') || str.endsWith(' ') ? { 'xml:space': 'preserve' } : {},
|
|
153
|
+
[createText(str)],
|
|
154
|
+
);
|
|
155
|
+
const siElement = createElement('si', {}, [tElement]);
|
|
156
|
+
siElements.push(siElement);
|
|
157
|
+
}
|
|
115
158
|
}
|
|
116
159
|
|
|
160
|
+
const totalCount = Math.max(this._totalCount, this.entries.length);
|
|
117
161
|
const sst = createElement(
|
|
118
162
|
'sst',
|
|
119
163
|
{
|
|
120
164
|
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
121
|
-
count: String(
|
|
122
|
-
uniqueCount: String(this.
|
|
165
|
+
count: String(totalCount),
|
|
166
|
+
uniqueCount: String(this.entries.length),
|
|
123
167
|
},
|
|
124
168
|
siElements,
|
|
125
169
|
);
|
|
@@ -127,3 +171,8 @@ export class SharedStrings {
|
|
|
127
171
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n${stringifyXml([sst])}`;
|
|
128
172
|
}
|
|
129
173
|
}
|
|
174
|
+
|
|
175
|
+
interface SharedStringEntry {
|
|
176
|
+
text: string;
|
|
177
|
+
node?: XmlNode;
|
|
178
|
+
}
|