• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.27
/projects/igniteui-angular/src/lib/services/excel/excel-files.ts
1
import { IExcelFile } from './excel-interfaces';
2
import { ExcelStrings } from './excel-strings';
3
import { WorksheetData } from './worksheet-data';
4

5
import { strToU8 } from 'fflate';
6
import { yieldingLoop } from '../../core/utils';
7
import { ExportHeaderType, ExportRecordType, IExportRecord, IColumnList, IColumnInfo, GRID_ROOT_SUMMARY, GRID_PARENT, GRID_LEVEL_COL } from '../exporter-common/base-export-service';
8

9
/**
10
 * @hidden
11
 */
12
export class RootRelsFile implements IExcelFile {
13
    public writeElement(folder: Object) {
UNCOV
14
        folder['.rels'] = strToU8(ExcelStrings.getRels());
×
15
    }
16
}
17

18
/**
19
 * @hidden
20
 */
21
export class AppFile implements IExcelFile {
22
    public writeElement(folder: Object, worksheetData: WorksheetData) {
UNCOV
23
        folder['app.xml'] = strToU8(ExcelStrings.getApp(worksheetData.options.worksheetName));
×
24
    }
25
}
26

27
/**
28
 * @hidden
29
 */
30
export class CoreFile implements IExcelFile {
31
    public writeElement(folder: Object) {
UNCOV
32
        folder['core.xml'] = strToU8(ExcelStrings.getCore());
×
33
    }
34
}
35

36
/**
37
 * @hidden
38
 */
39
export class WorkbookRelsFile implements IExcelFile {
40
    public writeElement(folder: Object, worksheetData: WorksheetData) {
UNCOV
41
        const hasSharedStrings = !worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders;
×
UNCOV
42
        folder['workbook.xml.rels'] = strToU8(ExcelStrings.getWorkbookRels(hasSharedStrings));
×
43
    }
44
}
45

46
/**
47
 * @hidden
48
 */
49
export class ThemeFile implements IExcelFile {
50
    public writeElement(folder: Object) {
UNCOV
51
        folder['theme1.xml'] = strToU8(ExcelStrings.getTheme());
×
52
    }
53
}
54

55
interface Dimensions {
56
    startCoordinate: string
57
    endCoordinate: string
58
}
59

60
interface CurrencyInfo {
61
    styleXf: number
62
    symbol: string
63
}
64

65
/**
66
 * @hidden
67
 */
68
export class WorksheetFile implements IExcelFile {
69
    private static MIN_WIDTH = 8.43;
2✔
UNCOV
70
    private maxOutlineLevel = 0;
×
UNCOV
71
    private sheetData = '';
×
UNCOV
72
    private dimension = '';
×
UNCOV
73
    private freezePane = '';
×
UNCOV
74
    private rowHeight = '';
×
75

UNCOV
76
    private mergeCellStr = '';
×
UNCOV
77
    private mergeCellsCounter = 0;
×
UNCOV
78
    private rowIndex = 0;
×
UNCOV
79
    private pivotGridRowHeadersMap = new Map<number, string>();
×
80

UNCOV
81
    private dimensionMap: Map<string, Dimensions> = new Map<string, Dimensions>();
×
UNCOV
82
    private hierarchicalDimensionMap: Map<any,  Map<string, Dimensions>> = new Map<any,  Map<string, Dimensions>>();
×
UNCOV
83
    private currentSummaryOwner = '';
×
UNCOV
84
    private currentHierarchicalOwner = '';
×
UNCOV
85
    private firstColumn = Number.MAX_VALUE;
×
UNCOV
86
    private firstDataRow = Number.MAX_VALUE;
×
87
    private isValidGrid: boolean;
88
    private lastValidRow: string;
89

UNCOV
90
    private currencyStyleMap = new Map<string, CurrencyInfo>([
×
91
        ['USD', {styleXf: 5, symbol: '$'}],
92
        ['GBP', {styleXf: 6, symbol: '£'}],
93
        ['CNY', {styleXf: 7, symbol: '¥'}],
94
        ['EUR', {styleXf: 8, symbol: '€'}],
95
        ['JPY', {styleXf: 9, symbol: '¥'}],
96
    ]);
97

98
    public writeElement() {}
99

100
    public async writeElementAsync(folder: Object, worksheetData: WorksheetData) {
UNCOV
101
        return new Promise<void>(resolve => {
×
UNCOV
102
            this.prepareDataAsync(worksheetData, (cols, rows) => {
×
UNCOV
103
                const hasTable = (!worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders)
×
104
                    && worksheetData.options.exportAsTable;
105

UNCOV
106
                folder['sheet1.xml'] = strToU8(ExcelStrings.getSheetXML(
×
107
                    this.dimension, this.freezePane, cols, rows, hasTable, this.maxOutlineLevel, worksheetData.isHierarchical));
UNCOV
108
                resolve();
×
109
            });
110
        });
111
    }
112

113
    private prepareDataAsync(worksheetData: WorksheetData, done: (cols: string, sheetData: string) => void) {
UNCOV
114
        this.sheetData = '';
×
UNCOV
115
        let cols = '';
×
UNCOV
116
        const dictionary = worksheetData.dataDictionary;
×
UNCOV
117
        this.rowIndex = 0;
×
118

UNCOV
119
        if (worksheetData.isEmpty && (!worksheetData.options.alwaysExportHeaders || worksheetData.owner.columns.length === 0)) {
×
UNCOV
120
            this.sheetData += '<sheetData/>';
×
UNCOV
121
            this.dimension = 'A1';
×
UNCOV
122
            done('', this.sheetData);
×
123
        } else {
UNCOV
124
            const owner = worksheetData.owner;
×
UNCOV
125
            const isHierarchicalGrid = worksheetData.isHierarchical;
×
UNCOV
126
            const hasMultiColumnHeader = worksheetData.hasMultiColumnHeader;
×
UNCOV
127
            const hasMultiRowHeader = worksheetData.hasMultiRowHeader;
×
128

UNCOV
129
            const hasUserSetIndex = owner.columns.some(col => col.exportIndex !== undefined);
×
130

UNCOV
131
            const height =  worksheetData.options.rowHeight;
×
132

UNCOV
133
            this.isValidGrid = worksheetData.isHierarchical || worksheetData.isTreeGrid || worksheetData.isGroupedGrid;
×
UNCOV
134
            this.rowHeight = height ? ` ht="${height}" customHeight="1"` : '';
×
UNCOV
135
            this.sheetData += `<sheetData>`;
×
136

UNCOV
137
            let headersForLevel: IColumnInfo[] = [];
×
138

UNCOV
139
            for(let i = 0; i <= owner.maxRowLevel; i++) {
×
UNCOV
140
                headersForLevel =  owner.columns.filter(c => c.level === i && c.rowSpan > 0 && !c.skip)
×
141

UNCOV
142
                this.printHeaders(worksheetData, headersForLevel, i, true);
×
143

UNCOV
144
                this.rowIndex++;
×
145
            }
146

UNCOV
147
            this.rowIndex = 0;
×
148

UNCOV
149
            for (let i = 0; i <= owner.maxLevel; i++) {
×
UNCOV
150
                this.rowIndex++;
×
UNCOV
151
                const pivotGridColumns = this.pivotGridRowHeadersMap.get(this.rowIndex) ?? "";
×
UNCOV
152
                this.sheetData += `<row r="${this.rowIndex}"${this.rowHeight}>${pivotGridColumns}`;
×
153

UNCOV
154
                const allowedColumns = owner.columns.filter(c => c.headerType !== ExportHeaderType.RowHeader &&
×
155
                     c.headerType !== ExportHeaderType.MultiRowHeader &&
156
                     c.headerType !== ExportHeaderType.PivotRowHeader &&
157
                     c.headerType !== ExportHeaderType.PivotMergedHeader);
158

UNCOV
159
                headersForLevel = hasMultiColumnHeader ?
×
160
                    allowedColumns
UNCOV
161
                        .filter(c => (c.level < i &&
×
162
                            c.headerType !== ExportHeaderType.MultiColumnHeader || c.level === i) && c.columnSpan > 0 && !c.skip)
UNCOV
163
                        .sort((a, b) => a.startIndex - b.startIndex)
×
UNCOV
164
                        .sort((a, b) => a.pinnedIndex - b.pinnedIndex) :
×
165
                    hasUserSetIndex ?
×
UNCOV
166
                        allowedColumns.filter(c => !c.skip) :
×
UNCOV
167
                        allowedColumns.filter(c => !c.skip)
×
UNCOV
168
                            .sort((a, b) => a.startIndex - b.startIndex)
×
UNCOV
169
                            .sort((a, b) => a.pinnedIndex - b.pinnedIndex);
×
170

UNCOV
171
                this.printHeaders(worksheetData, headersForLevel, i, false);
×
172

UNCOV
173
                this.sheetData += `</row>`;
×
174
            }
175

UNCOV
176
            const multiColumnHeaderLevel = worksheetData.options.ignoreMultiColumnHeaders ? 0 : owner.maxLevel;
×
UNCOV
177
            const freezeHeaders = worksheetData.options.freezeHeaders ? 2 + multiColumnHeaderLevel : 1;
×
178

UNCOV
179
            if (!isHierarchicalGrid) {
×
UNCOV
180
                const col = worksheetData.hasSummaries ? worksheetData.columnCount + 1 : worksheetData.columnCount - 1
×
UNCOV
181
                this.dimension = 'A1:' + ExcelStrings.getExcelColumn(col) + (worksheetData.rowCount);
×
182

UNCOV
183
                cols += '<cols>';
×
184

UNCOV
185
                if (!hasMultiColumnHeader) {
×
UNCOV
186
                    for (let j = 0; j < worksheetData.columnCount; j++) {
×
UNCOV
187
                        const width = dictionary.columnWidths[j];
×
188
                        // Use the width provided in the options if it exists
UNCOV
189
                        let widthInTwips = worksheetData.options.columnWidth !== undefined ?
×
190
                                                worksheetData.options.columnWidth :
191
                                                Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH);
UNCOV
192
                        if (!(widthInTwips > 0)) {
×
UNCOV
193
                            widthInTwips = WorksheetFile.MIN_WIDTH;
×
194
                        }
195

UNCOV
196
                        cols += `<col min="${(j + 1)}" max="${(j + 1)}" width="${widthInTwips}" customWidth="1"/>`;
×
197
                    }
198
                } else {
UNCOV
199
                    cols += `<col min="1" max="${worksheetData.columnCount}" width="15" customWidth="1"/>`;
×
200
                }
201

UNCOV
202
                const indexOfLastPinnedColumn = worksheetData.indexOfLastPinnedColumn;
×
UNCOV
203
                const frozenColumnCount = indexOfLastPinnedColumn + 1;
×
UNCOV
204
                let firstCell = ExcelStrings.getExcelColumn(frozenColumnCount) + freezeHeaders;
×
UNCOV
205
                if (indexOfLastPinnedColumn !== undefined && indexOfLastPinnedColumn !== -1 &&
×
206
                    !worksheetData.options.ignorePinning &&
207
                    !worksheetData.options.ignoreColumnsOrder) {
UNCOV
208
                    this.freezePane =
×
209
                        `<pane xSplit="${frozenColumnCount}" ySplit="${freezeHeaders - 1}"
210
                         topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
UNCOV
211
                } else if (worksheetData.options.freezeHeaders) {
×
UNCOV
212
                    firstCell = ExcelStrings.getExcelColumn(0) + freezeHeaders;
×
UNCOV
213
                    this.freezePane =
×
214
                        `<pane xSplit="0" ySplit="${freezeHeaders - 1}"
215
                         topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
216
                }
217
            } else {
UNCOV
218
                const columnWidth = worksheetData.options.columnWidth ? worksheetData.options.columnWidth : 20;
×
UNCOV
219
                cols += `<cols><col min="1" max="${worksheetData.columnCount}" width="${columnWidth}" customWidth="1"/>`;
×
220

UNCOV
221
                if (worksheetData.options.freezeHeaders) {
×
UNCOV
222
                    const firstCell = ExcelStrings.getExcelColumn(0) + freezeHeaders;
×
UNCOV
223
                    this.freezePane =
×
224
                        `<pane xSplit="0" ySplit="${freezeHeaders - 1}"
225
                         topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
226
                }
227
            }
228

UNCOV
229
            if (worksheetData.hasSummaries) {
×
UNCOV
230
                cols += `<col min="${worksheetData.columnCount + 2}" max="${worksheetData.columnCount + 2}" hidden="1"/>`;
×
231
            }
232

UNCOV
233
            cols += '</cols>';
×
234

UNCOV
235
            this.processDataRecordsAsync(worksheetData, (rows) => {
×
UNCOV
236
                this.sheetData += rows;
×
UNCOV
237
                this.sheetData += '</sheetData>';
×
238

UNCOV
239
                if ((hasMultiColumnHeader || hasMultiRowHeader) && this.mergeCellsCounter > 0) {
×
UNCOV
240
                    this.sheetData += `<mergeCells count="${this.mergeCellsCounter}">${this.mergeCellStr}</mergeCells>`;
×
241
                }
242

UNCOV
243
                done(cols, this.sheetData);
×
244
            });
245
        }
246
    }
247

248
    private processDataRecordsAsync(worksheetData: WorksheetData, done: (rows: string) => void) {
UNCOV
249
        const rowDataArr = [];
×
UNCOV
250
        const height =  worksheetData.options.rowHeight;
×
UNCOV
251
        this.rowHeight = height ? ' ht="' + height + '" customHeight="1"' : '';
×
252

UNCOV
253
        const isHierarchicalGrid = worksheetData.isHierarchical;
×
UNCOV
254
        const hasUserSetIndex = worksheetData.owner.columns.some(c => c.exportIndex !== undefined);
×
255

UNCOV
256
        let recordHeaders = [];
×
257

UNCOV
258
        yieldingLoop(worksheetData.rowCount - worksheetData.multiColumnHeaderRows - 1, 1000,
×
259
            (i) => {
UNCOV
260
                if (!worksheetData.isEmpty){
×
UNCOV
261
                    if (!isHierarchicalGrid) {
×
UNCOV
262
                        if (hasUserSetIndex) {
×
UNCOV
263
                            recordHeaders = worksheetData.rootKeys;
×
264
                        } else {
UNCOV
265
                            recordHeaders = worksheetData.owner.columns
×
UNCOV
266
                                .filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
×
UNCOV
267
                                .sort((a, b) => a.startIndex-b.startIndex)
×
UNCOV
268
                                .sort((a, b) => a.pinnedIndex-b.pinnedIndex)
×
UNCOV
269
                                .map(c => c.field);
×
270
                        }
271
                    } else {
UNCOV
272
                        const record = worksheetData.data[i];
×
273

UNCOV
274
                        if (record.type === ExportRecordType.HeaderRecord) {
×
UNCOV
275
                            const recordOwner = worksheetData.owners.get(record.owner);
×
UNCOV
276
                            const hasMultiColumnHeaders = recordOwner.columns.some(c => !c.skip && c.headerType === ExportHeaderType.MultiColumnHeader);
×
277

UNCOV
278
                            if (hasMultiColumnHeaders) {
×
UNCOV
279
                                this.hGridPrintMultiColHeaders(worksheetData, rowDataArr, record, recordOwner);
×
280
                            }
281
                        }
282

UNCOV
283
                        recordHeaders = Object.keys(worksheetData.data[i].data);
×
284
                    }
285

UNCOV
286
                    rowDataArr.push(this.processRow(worksheetData, i, recordHeaders, isHierarchicalGrid));
×
287
                }
288
            },
289
            () => {
UNCOV
290
                done(rowDataArr.join(''));
×
291
        });
292
    }
293

294
    private hGridPrintMultiColHeaders(worksheetData: WorksheetData, rowDataArr: any[], record: IExportRecord,
295
        owner: IColumnList) {
UNCOV
296
        for (let j = 0; j < owner.maxLevel; j++) {
×
UNCOV
297
            const recordLevel = record.level;
×
UNCOV
298
            const outlineLevel = recordLevel > 0 ? ` outlineLevel="${recordLevel}"` : '';
×
UNCOV
299
            this.maxOutlineLevel = this.maxOutlineLevel < recordLevel ? recordLevel : this.maxOutlineLevel;
×
UNCOV
300
            const sHidden = record.hidden ? ` hidden="1"` : '';
×
301

UNCOV
302
            this.rowIndex++;
×
UNCOV
303
            let row = `<row r="${this.rowIndex}"${this.rowHeight}${outlineLevel}${sHidden}>`;
×
304

UNCOV
305
            const headersForLevel = owner.columns
×
UNCOV
306
                .filter(c => (c.level < j &&
×
307
                    c.headerType !== ExportHeaderType.MultiColumnHeader || c.level === j) && c.columnSpan > 0 && !c.skip)
UNCOV
308
                .sort((a, b) => a.startIndex - b.startIndex)
×
UNCOV
309
                .sort((a, b) => a.pinnedIndex - b.pinnedIndex);
×
310

UNCOV
311
            let startValue = 0 + record.level;
×
312

UNCOV
313
            for (const currentCol of headersForLevel) {
×
UNCOV
314
                if (currentCol.level === j) {
×
315
                    let columnCoordinate;
UNCOV
316
                    columnCoordinate =
×
317
                        ExcelStrings.getExcelColumn(startValue) + this.rowIndex;
318

UNCOV
319
                    const columnValue = worksheetData.dataDictionary.saveValue(currentCol.header, true);
×
UNCOV
320
                    row += `<c r="${columnCoordinate}" s="3" t="s"><v>${columnValue}</v></c>`;
×
321

UNCOV
322
                    if (j !== owner.maxLevel) {
×
UNCOV
323
                        this.mergeCellsCounter++;
×
UNCOV
324
                        this.mergeCellStr += ` <mergeCell ref="${columnCoordinate}:`;
×
325

UNCOV
326
                        if (currentCol.headerType === ExportHeaderType.ColumnHeader) {
×
UNCOV
327
                            columnCoordinate = ExcelStrings.getExcelColumn(startValue) +
×
328
                                (this.rowIndex + owner.maxLevel - currentCol.level);
329
                        } else {
UNCOV
330
                            for (let k = 1; k < currentCol.columnSpan; k++) {
×
UNCOV
331
                                columnCoordinate = ExcelStrings.getExcelColumn(startValue + k) + this.rowIndex;
×
UNCOV
332
                                row += `<c r="${columnCoordinate}" s="3" />`;
×
333
                            }
334
                        }
335

UNCOV
336
                        this.mergeCellStr += `${columnCoordinate}" />`;
×
337
                    }
338
                }
339

UNCOV
340
                startValue += currentCol.columnSpan;
×
341
            }
UNCOV
342
            row += `</row>`;
×
UNCOV
343
            rowDataArr.push(row);
×
344
        }
345
    }
346

347
    private processRow(worksheetData: WorksheetData, i: number, headersForLevel: any[], isHierarchicalGrid: boolean) {
UNCOV
348
        const record = worksheetData.data[i];
×
349

UNCOV
350
        const rowData = new Array(worksheetData.columnCount + 2);
×
351

UNCOV
352
        const rowLevel = record.level;
×
UNCOV
353
        const outlineLevel = rowLevel > 0 ? ` outlineLevel="${rowLevel}"` : '';
×
UNCOV
354
        this.maxOutlineLevel = this.maxOutlineLevel < rowLevel ? rowLevel : this.maxOutlineLevel;
×
355

UNCOV
356
        const sHidden = record.hidden ? ` hidden="1"` : '';
×
357

UNCOV
358
        this.rowIndex++;
×
UNCOV
359
        const pivotGridColumns = this.pivotGridRowHeadersMap.get(this.rowIndex) ?? "";
×
360

UNCOV
361
        rowData[0] = `<row r="${this.rowIndex}"${this.rowHeight}${outlineLevel}${sHidden}>${pivotGridColumns}`;
×
UNCOV
362
        const keys = worksheetData.isSpecialData ? [record.data] : headersForLevel;
×
UNCOV
363
        const isDataRecord = record.type === ExportRecordType.HierarchicalGridRecord
×
364
            || record.type === ExportRecordType.DataRecord
365
            || record.type === ExportRecordType.GroupedRecord
366
            || record.type === ExportRecordType.TreeGridRecord;
367

UNCOV
368
        const isValidRecordType = isDataRecord || record.type === ExportRecordType.SummaryRecord;
×
369

UNCOV
370
        if (isValidRecordType && worksheetData.hasSummaries) {
×
UNCOV
371
            this.resolveSummaryDimensions(record, isDataRecord, worksheetData.isGroupedGrid)
×
372
        }
373

UNCOV
374
        for (let j = 0; j < keys.length; j++) {
×
UNCOV
375
            const col = j + (isHierarchicalGrid ? rowLevel : worksheetData.isPivotGrid ? worksheetData.owner.maxRowLevel : 0);
×
376

UNCOV
377
            const cellData = this.getCellData(worksheetData, i, col, keys[j]);
×
378

UNCOV
379
            rowData[j + 1] = cellData;
×
380
        }
381

UNCOV
382
        rowData[keys.length + 1] = '</row>';
×
383

UNCOV
384
        return rowData.join('');
×
385
    }
386

387
    private getCellData(worksheetData: WorksheetData, row: number, column: number, key: string): string {
UNCOV
388
        const dictionary = worksheetData.dataDictionary;
×
UNCOV
389
        let columnName = ExcelStrings.getExcelColumn(column) + (this.rowIndex);
×
UNCOV
390
        const fullRow = worksheetData.data[row];
×
UNCOV
391
        const isHeaderRecord = fullRow.type === ExportRecordType.HeaderRecord;
×
UNCOV
392
        const isSummaryRecord = fullRow.type === ExportRecordType.SummaryRecord;
×
UNCOV
393
        const isValidRecordType = fullRow.type === ExportRecordType.GroupedRecord
×
394
            || fullRow.type === ExportRecordType.DataRecord
395
            || fullRow.type === ExportRecordType.HierarchicalGridRecord
396
            || fullRow.type === ExportRecordType.TreeGridRecord;
397

UNCOV
398
        this.firstDataRow = this.firstDataRow > this.rowIndex ? this.rowIndex : this.firstDataRow;
×
399

UNCOV
400
        const cellValue = worksheetData.isSpecialData ?
×
401
            fullRow.data :
402
            fullRow.data[key];
403

UNCOV
404
        if (cellValue === GRID_LEVEL_COL || key === GRID_LEVEL_COL) {
×
UNCOV
405
            columnName = ExcelStrings.getExcelColumn(worksheetData.columnCount + 1) + (this.rowIndex);
×
406
        }
407

UNCOV
408
        if (worksheetData.hasSummaries && (isValidRecordType || (worksheetData.isGroupedGrid && isSummaryRecord))) {
×
UNCOV
409
            this.setSummaryCoordinates(columnName, key, fullRow.hierarchicalOwner, worksheetData.isGroupedGrid && isSummaryRecord)
×
410
        }
411

UNCOV
412
        if (fullRow.summaryKey && fullRow.summaryKey === GRID_ROOT_SUMMARY && key !== GRID_LEVEL_COL && worksheetData.isGroupedGrid) {
×
UNCOV
413
            this.setRootSummaryStartCoordinate(column, key);
×
414

UNCOV
415
            if (this.firstColumn > column) {
×
UNCOV
416
                this.setRootSummaryStartCoordinate(worksheetData.columnCount + 1, GRID_LEVEL_COL);
×
UNCOV
417
                this.firstColumn = column;
×
418
            }
419
        }
420

UNCOV
421
        const targetColArr = Array.from(worksheetData.owners.values()).map(arr => arr.columns).find(product => product.some(item => item.field === key));
×
UNCOV
422
        const targetCol = targetColArr ? targetColArr.find(col => col.field === key) : undefined;
×
423

UNCOV
424
        if ((cellValue === undefined || cellValue === null) && !worksheetData.hasSummaries) {
×
UNCOV
425
            return `<c r="${columnName}" s="1"/>`;
×
UNCOV
426
        } else if ((worksheetData.hasSummaries && (isValidRecordType || isHeaderRecord)) || !worksheetData.hasSummaries) {
×
UNCOV
427
            const savedValue = dictionary.saveValue(cellValue, isHeaderRecord);
×
UNCOV
428
            const isSavedAsString = savedValue !== -1;
×
429

UNCOV
430
            const isSavedAsDate = !isSavedAsString && cellValue instanceof Date;
×
431

UNCOV
432
            let value = isSavedAsString ? savedValue : cellValue;
×
433

UNCOV
434
            if (isSavedAsDate) {
×
UNCOV
435
                const timeZoneOffset = value.getTimezoneOffset() * 60000;
×
UNCOV
436
                const isoString = (new Date(value - timeZoneOffset)).toISOString();
×
UNCOV
437
                value = isoString.substring(0, isoString.indexOf('.'));
×
438
            }
439

UNCOV
440
            const type = isSavedAsString ? ` t="s"` : isSavedAsDate ? ` t="d"` : '';
×
441

UNCOV
442
            const isTime = targetCol?.dataType === 'time';
×
UNCOV
443
            const isDateTime = targetCol?.dataType === 'dateTime';
×
UNCOV
444
            const isPercentage = targetCol?.dataType === 'percent';
×
UNCOV
445
            const isColumnCurrencyType = targetCol?.dataType === 'currency';
×
446

UNCOV
447
            const format = isPercentage ? ` s="12"` : isDateTime ? ` s="11"` : isTime ? ` s="10"` : isHeaderRecord ? ` s="3"` : isSavedAsString ? '' : isSavedAsDate ? ` s="2"` : isColumnCurrencyType ? ` s="${this.currencyStyleMap.get(targetCol.currencyCode)?.styleXf || 0}"` : ` s="1"`;
×
448

UNCOV
449
            return `<c r="${columnName}"${type}${format}><v>${value}</v></c>`;
×
450
        } else {
UNCOV
451
            let summaryFunc = `"${cellValue ?? ""}"`;
×
452

UNCOV
453
            if (isSummaryRecord && cellValue) {
×
UNCOV
454
                const dimensionMapKey = this.isValidGrid ? fullRow.hierarchicalOwner ?? GRID_PARENT : null;
×
UNCOV
455
                const level = worksheetData.isGroupedGrid ? worksheetData.maxLevel : fullRow.level;
×
456

UNCOV
457
                summaryFunc = this.getSummaryFunction(cellValue.label, key, dimensionMapKey, level, targetCol);
×
458

UNCOV
459
                if (!summaryFunc) {
×
460
                    let summaryValue;
UNCOV
461
                    const label = cellValue.label?.toString();
×
UNCOV
462
                    const value = cellValue.value?.toString();
×
463

UNCOV
464
                    if (label && value) {
×
465
                        summaryValue = `${cellValue.label}: ${cellValue.value}`;
×
UNCOV
466
                    } else if (label) {
×
UNCOV
467
                        summaryValue = cellValue.label;
×
UNCOV
468
                    } else if (value) {
×
UNCOV
469
                        summaryValue = cellValue.value;
×
470
                    }
471

UNCOV
472
                    const savedValue = dictionary.saveValue(summaryValue, false);
×
UNCOV
473
                    const isSavedAsString = savedValue !== -1;
×
UNCOV
474
                    const isSavedAsDate = !isSavedAsString && summaryValue instanceof Date;
×
475

UNCOV
476
                    if (isSavedAsDate) {
×
UNCOV
477
                        const timeZoneOffset = summaryValue.getTimezoneOffset() * 60000;
×
UNCOV
478
                        const isoString = (new Date(summaryValue - timeZoneOffset)).toISOString();
×
UNCOV
479
                        summaryValue = isoString.substring(0, isoString.indexOf('.'));
×
480
                    }
481

UNCOV
482
                    const resolvedValue = isSavedAsString ? savedValue : summaryValue;
×
UNCOV
483
                    const type = isSavedAsString ? `t="s"` : isSavedAsDate ? `t="d"` : '';
×
UNCOV
484
                    const style = isSavedAsDate ? `s="2"` : `s="1"`;
×
485

UNCOV
486
                    return `<c r="${columnName}" ${type} ${style}><v>${resolvedValue}</v></c>`;
×
487
                }
488

UNCOV
489
                return `<c r="${columnName}"><f t="array" ref="${columnName}">${summaryFunc}</f></c>`;
×
490
            }
491

UNCOV
492
            return `<c r="${columnName}" s="1"><f>${summaryFunc}</f></c>`;
×
493
        }
494
    }
495

496
    private resolveSummaryDimensions(record: IExportRecord, isDataRecord: boolean, isGroupedGrid: boolean) {
UNCOV
497
        if (this.isValidGrid &&
×
498
            this.currentHierarchicalOwner !== '' &&
499
            this.currentHierarchicalOwner !== record.owner &&
500
            !this.hierarchicalDimensionMap.get(this.currentHierarchicalOwner)) {
UNCOV
501
            this.hierarchicalDimensionMap.set(this.currentHierarchicalOwner, new Map(this.dimensionMap))
×
502
        }
503

UNCOV
504
        if (isDataRecord) {
×
UNCOV
505
            if (this.currentSummaryOwner !== record.summaryKey || this.currentHierarchicalOwner !== record.hierarchicalOwner) {
×
UNCOV
506
                this.dimensionMap.clear();
×
507
            }
508

UNCOV
509
            this.currentSummaryOwner = record.summaryKey;
×
510

511
            // For grouped grid we need to reset the parent map
512
            // so we can change the startCoordinate for each record
UNCOV
513
            if (isGroupedGrid && this.currentHierarchicalOwner !== '' && record.hierarchicalOwner === GRID_PARENT) {
×
UNCOV
514
                this.hierarchicalDimensionMap.delete(GRID_PARENT)
×
515
            }
516

UNCOV
517
            this.currentHierarchicalOwner = record.hierarchicalOwner;
×
518
        }
519
    }
520

521
    private setSummaryCoordinates(columnName: string, key: string, hierarchicalOwner: string, useLastValidEndCoordinate: boolean) {
UNCOV
522
        const targetDimensionMap = this.hierarchicalDimensionMap.get(hierarchicalOwner) ?? this.dimensionMap;
×
523

UNCOV
524
        if (!targetDimensionMap.get(key)) {
×
UNCOV
525
            const initialDimensions: Dimensions = {
×
526
                startCoordinate: columnName,
527
                endCoordinate: columnName
528
            };
529

UNCOV
530
            targetDimensionMap.set(key, initialDimensions)
×
531
        } else {
UNCOV
532
            if (useLastValidEndCoordinate) {
×
UNCOV
533
                this.setEndCoordinates(targetDimensionMap, true);
×
534
            } else {
UNCOV
535
                targetDimensionMap.get(key).endCoordinate = columnName;
×
UNCOV
536
                this.lastValidRow = targetDimensionMap.get(key).endCoordinate.match(/[a-z]+|[^a-z]+/gi)[1]
×
537
            }
538
        }
539

UNCOV
540
        if (this.isValidGrid && !useLastValidEndCoordinate && hierarchicalOwner !== GRID_PARENT) {
×
UNCOV
541
            const parentMap = this.hierarchicalDimensionMap.get(GRID_PARENT);
×
UNCOV
542
            this.setEndCoordinates(parentMap);
×
543
        }
544
    }
545

546
    private setEndCoordinates(map: Map<string, Dimensions>, useLastValidEndCoordinate = false) {
×
UNCOV
547
        for (const a of map.values()) {
×
UNCOV
548
            const colName = a.endCoordinate.match(/[a-z]+|[^a-z]+/gi)[0];
×
UNCOV
549
            a.endCoordinate = `${colName}${useLastValidEndCoordinate ? this.lastValidRow : this.rowIndex}`;
×
550
         }
551
    }
552

553
    private getSummaryFunction(type: string, key: string, dimensionMapKey: any, recordLevel: number, col: IColumnInfo): string {
UNCOV
554
        const dimensionMap = dimensionMapKey ? this.hierarchicalDimensionMap.get(dimensionMapKey) : this.dimensionMap;
×
UNCOV
555
        const dimensions = dimensionMap.get(key);
×
UNCOV
556
        const levelDimensions = dimensionMap.get(GRID_LEVEL_COL);
×
557

UNCOV
558
        let func = '';
×
UNCOV
559
        let funcType = '';
×
UNCOV
560
        let result = '';
×
UNCOV
561
        const currencyInfo = this.currencyStyleMap.get(col.currencyCode);
×
562

UNCOV
563
        switch(type?.toString().toLowerCase()) {
×
564
            case "count":
UNCOV
565
                return `"Count: "&amp;_xlfn.COUNTIF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}, ${recordLevel})`
×
566
            case "min":
UNCOV
567
                func = `_xlfn.MIN(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate}))`
×
UNCOV
568
                funcType = `"Min: "&amp;`;
×
569

UNCOV
570
                result = funcType + (col.dataType === 'currency' && currencyInfo
×
571
                    ? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
572
                    : `${func}`);
573

UNCOV
574
                return result
×
575
            case "max":
UNCOV
576
                func = `_xlfn.MAX(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate}))`
×
UNCOV
577
                funcType = `"Max: "&amp;`;
×
578

UNCOV
579
                result = funcType + (col.dataType === 'currency' && currencyInfo
×
580
                    ? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
581
                    : `${func}`);
582

UNCOV
583
                return result
×
584
            case "sum":
UNCOV
585
                func =  `_xlfn.SUMIF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}, ${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})`
×
UNCOV
586
                funcType = `"Sum: "&amp;`;
×
587

UNCOV
588
                result = funcType + (col.dataType === 'currency' && currencyInfo
×
589
                    ? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
590
                    : `${func}`);
591

UNCOV
592
                return result
×
593
            case "avg":
UNCOV
594
                func = `_xlfn.AVERAGEIF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}, ${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})`
×
UNCOV
595
                funcType = `"Avg: "&amp;`;
×
596

UNCOV
597
                result = funcType + (col.dataType === 'currency' && currencyInfo
×
598
                    ? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
599
                    : `${func}`);
600

UNCOV
601
                return result
×
602
            case "earliest":
603
                // TODO: get date format from locale
UNCOV
604
                return `"Earliest: "&amp;_xlfn.TEXT(_xlfn.MIN(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})), "m/d/yyyy")`
×
605
            case "latest":
606
                // TODO: get date format from locale
UNCOV
607
                return `"Latest: "&amp;_xlfn.TEXT(_xlfn.MAX(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})), "m/d/yyyy")`
×
608
        }
609
    }
610

611
    private setRootSummaryStartCoordinate(column: number, key: string) {
UNCOV
612
        const firstDataRecordColName = ExcelStrings.getExcelColumn(column) + (this.firstDataRow);
×
UNCOV
613
        const targetMap = this.hierarchicalDimensionMap.get(GRID_PARENT);
×
614

UNCOV
615
        if (targetMap.get(key).startCoordinate !== firstDataRecordColName) {
×
UNCOV
616
            targetMap.get(key).startCoordinate = firstDataRecordColName;
×
617
        }
618
    }
619

620
    private printHeaders(worksheetData: WorksheetData, headersForLevel: IColumnInfo[], i: number, isVertical: boolean) {
UNCOV
621
        let startValue = 0;
×
UNCOV
622
        let str = '';
×
623

UNCOV
624
        const isHierarchicalGrid = worksheetData.isHierarchical;
×
UNCOV
625
        let rowStyle = isHierarchicalGrid ? ' s="3"' : '';
×
UNCOV
626
        const dictionary = worksheetData.dataDictionary;
×
UNCOV
627
        const owner = worksheetData.owner;
×
UNCOV
628
        const maxLevel = isVertical
×
629
            ? owner.maxRowLevel
630
            : owner.maxLevel;
631

UNCOV
632
        for (const currentCol of headersForLevel) {
×
UNCOV
633
            const spanLength = isVertical ? currentCol.rowSpan : currentCol.columnSpan;
×
634

UNCOV
635
            if (currentCol.level === i && currentCol.headerType !== ExportHeaderType.PivotMergedHeader) {
×
636
                let columnCoordinate;
UNCOV
637
                const column = isVertical
×
638
                    ? this.rowIndex
639
                    : startValue + (owner.maxRowLevel ?? 0)
×
640

UNCOV
641
                let rowCoordinate = isVertical
×
642
                    ? startValue + owner.maxLevel + 2
643
                    : this.rowIndex
UNCOV
644
                if (currentCol.headerType === ExportHeaderType.PivotRowHeader) {
×
UNCOV
645
                    rowCoordinate = startValue + 1;
×
646
                }
UNCOV
647
                const columnValue = dictionary.saveValue(currentCol.header, true, false);
×
648

UNCOV
649
                columnCoordinate = (currentCol.field === GRID_LEVEL_COL
×
650
                    ? ExcelStrings.getExcelColumn(worksheetData.columnCount + 1)
651
                    : ExcelStrings.getExcelColumn(column)) + rowCoordinate;
652

UNCOV
653
                rowStyle = isVertical && currentCol.rowSpan > 1 ? ' s="4"' : rowStyle;
×
UNCOV
654
                str = `<c r="${columnCoordinate}"${rowStyle} t="s"><v>${columnValue}</v></c>`;
×
655

UNCOV
656
                if (isVertical) {
×
UNCOV
657
                    if (this.pivotGridRowHeadersMap.has(rowCoordinate)) {
×
UNCOV
658
                        this.pivotGridRowHeadersMap.set(rowCoordinate, this.pivotGridRowHeadersMap.get(rowCoordinate) + str)
×
659
                    } else {
UNCOV
660
                        this.pivotGridRowHeadersMap.set(rowCoordinate, str)
×
661
                    }
662
                } else {
UNCOV
663
                    this.sheetData += str;
×
664
                }
665

UNCOV
666
                if (i !== maxLevel) {
×
UNCOV
667
                    this.mergeCellsCounter++;
×
UNCOV
668
                    this.mergeCellStr += ` <mergeCell ref="${columnCoordinate}:`;
×
669

UNCOV
670
                    if (currentCol.headerType === ExportHeaderType.ColumnHeader) {
×
UNCOV
671
                        const col = isVertical
×
672
                            ? maxLevel
673
                            : startValue + (owner.maxRowLevel ?? 0);
×
674

UNCOV
675
                        const row = isVertical
×
676
                            ? rowCoordinate
677
                            : owner.maxLevel + 1;
678

UNCOV
679
                        columnCoordinate = ExcelStrings.getExcelColumn(col) + row;
×
680
                    } else {
UNCOV
681
                        for (let k = 1; k < spanLength; k++) {
×
UNCOV
682
                            const col = isVertical
×
683
                                ? column
684
                                : column + k;
685

UNCOV
686
                            const row = isVertical
×
687
                                ? rowCoordinate + k
688
                                : this.rowIndex;
689

UNCOV
690
                            columnCoordinate = ExcelStrings.getExcelColumn(col) + row;
×
UNCOV
691
                            str = `<c r="${columnCoordinate}"${rowStyle} />`;
×
692

UNCOV
693
                            isVertical
×
694
                                ? this.pivotGridRowHeadersMap.set(row, str)
695
                                : this.sheetData += str
696
                        }
697
                    }
UNCOV
698
                    if ((currentCol.headerType === ExportHeaderType.RowHeader || currentCol.headerType === ExportHeaderType.MultiRowHeader) &&
×
699
                        currentCol.columnSpan && currentCol.columnSpan > 1 ) {
700
                        columnCoordinate = ExcelStrings.getExcelColumn(column + currentCol.columnSpan - 1) + (rowCoordinate + spanLength - 1);
×
701
                    }
702

UNCOV
703
                    this.mergeCellStr += `${columnCoordinate}" />`;
×
704
                }
705
            }
UNCOV
706
            if (currentCol.headerType !== ExportHeaderType.PivotRowHeader) {
×
UNCOV
707
                startValue += spanLength;
×
708
            }
709
        }
710
    }
711
}
712

713
/**
714
 * @hidden
715
 */
716
export class StyleFile implements IExcelFile {
717
    public writeElement(folder: Object) {
UNCOV
718
        folder['styles.xml'] = strToU8(ExcelStrings.getStyles());
×
719
    }
720
}
721

722
/**
723
 * @hidden
724
 */
725
export class WorkbookFile implements IExcelFile {
726
    public writeElement(folder: Object, worksheetData: WorksheetData) {
UNCOV
727
        folder['workbook.xml'] = strToU8(ExcelStrings.getWorkbook(worksheetData.options.worksheetName));
×
728
    }
729
}
730

731
/**
732
 * @hidden
733
 */
734
export class ContentTypesFile implements IExcelFile {
735
    public writeElement(folder: Object, worksheetData: WorksheetData) {
UNCOV
736
        const hasSharedStrings = !worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders;
×
UNCOV
737
        folder['[Content_Types].xml'] = strToU8(ExcelStrings.getContentTypesXML(hasSharedStrings, worksheetData.options.exportAsTable));
×
738
    }
739
}
740

741
/**
742
 * @hidden
743
 */
744
export class SharedStringsFile implements IExcelFile {
745
    public writeElement(folder: Object, worksheetData: WorksheetData) {
UNCOV
746
        const dict = worksheetData.dataDictionary;
×
UNCOV
747
        const sortedValues = dict.getKeys();
×
UNCOV
748
        const sharedStrings = new Array<string>(sortedValues.length);
×
749

UNCOV
750
        for (const value of sortedValues) {
×
UNCOV
751
            sharedStrings[dict.getSanitizedValue(value)] = '<si><t>' + value + '</t></si>';
×
752
        }
753

UNCOV
754
        folder['sharedStrings.xml'] = strToU8(ExcelStrings.getSharedStringXML(
×
755
                        dict.stringsCount,
756
                        sortedValues.length,
757
                        sharedStrings.join(''))
758
                    );
759
    }
760
}
761

762
/**
763
 * @hidden
764
 */
765
export class TablesFile implements IExcelFile {
766
    public writeElement(folder: Object, worksheetData: WorksheetData) {
UNCOV
767
        const columnCount = worksheetData.columnCount;
×
UNCOV
768
        const lastColumn = ExcelStrings.getExcelColumn(columnCount - 1) + worksheetData.rowCount;
×
UNCOV
769
        const autoFilterDimension = 'A1:' + lastColumn;
×
UNCOV
770
        const tableDimension = worksheetData.isEmpty
×
771
            ? 'A1:' + ExcelStrings.getExcelColumn(columnCount - 1) + (worksheetData.rowCount + 1)
772
            : autoFilterDimension;
UNCOV
773
        const hasUserSetIndex = worksheetData.owner.columns.some(c => c.exportIndex !== undefined);
×
UNCOV
774
        const values = hasUserSetIndex
×
775
            ? worksheetData.rootKeys
776
            : worksheetData.owner.columns
UNCOV
777
                .filter(c => !c.skip)
×
UNCOV
778
                .sort((a, b) => a.startIndex - b.startIndex)
×
UNCOV
779
                .sort((a, b) => a.pinnedIndex - b.pinnedIndex)
×
UNCOV
780
                .map(c => c.header);
×
781

UNCOV
782
        let sortString = '';
×
783

UNCOV
784
        let tableColumns = '<tableColumns count="' + columnCount + '">';
×
UNCOV
785
        for (let i = 0; i < columnCount; i++) {
×
UNCOV
786
            const value =  values[i];
×
UNCOV
787
            tableColumns += '<tableColumn id="' + (i + 1) + '" name="' + value + '"/>';
×
788
        }
789

UNCOV
790
        tableColumns += '</tableColumns>';
×
791

UNCOV
792
        if (worksheetData.sort) {
×
793
            const sortingExpression = worksheetData.sort;
×
794
            const sc = ExcelStrings.getExcelColumn(values.indexOf(sortingExpression.fieldName));
×
795
            const dir = sortingExpression.dir - 1;
×
796
            sortString = `<sortState ref="A2:${lastColumn}"><sortCondition descending="${dir}" ref="${sc}1:${sc}15"/></sortState>`;
×
797
        }
798

UNCOV
799
        folder['table1.xml'] = strToU8(ExcelStrings.getTablesXML(autoFilterDimension, tableDimension, tableColumns, sortString));
×
800
    }
801
}
802

803
/**
804
 * @hidden
805
 */
806
export class WorksheetRelsFile implements IExcelFile {
807
    public writeElement(folder: Object) {
UNCOV
808
        folder['sheet1.xml.rels'] = strToU8(ExcelStrings.getWorksheetRels());
×
809
    }
810
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc