igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
776 lines (627 loc) • 34.5 kB
text/typescript
import { IExcelFile } from './excel-interfaces';
import { ExcelStrings } from './excel-strings';
import { WorksheetData } from './worksheet-data';
import { strToU8 } from 'fflate';
import { yieldingLoop } from '../../core/utils';
import { ExportHeaderType, ExportRecordType, IExportRecord, IColumnList, IColumnInfo, GRID_ROOT_SUMMARY, GRID_PARENT, GRID_LEVEL_COL } from '../exporter-common/base-export-service';
/**
* @hidden
*/
export class RootRelsFile implements IExcelFile {
public writeElement(folder: Object) {
folder['.rels'] = strToU8(ExcelStrings.getRels());
}
}
/**
* @hidden
*/
export class AppFile implements IExcelFile {
public writeElement(folder: Object, worksheetData: WorksheetData) {
folder['app.xml'] = strToU8(ExcelStrings.getApp(worksheetData.options.worksheetName));
}
}
/**
* @hidden
*/
export class CoreFile implements IExcelFile {
public writeElement(folder: Object) {
folder['core.xml'] = strToU8(ExcelStrings.getCore());
}
}
/**
* @hidden
*/
export class WorkbookRelsFile implements IExcelFile {
public writeElement(folder: Object, worksheetData: WorksheetData) {
const hasSharedStrings = !worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders;
folder['workbook.xml.rels'] = strToU8(ExcelStrings.getWorkbookRels(hasSharedStrings));
}
}
/**
* @hidden
*/
export class ThemeFile implements IExcelFile {
public writeElement(folder: Object) {
folder['theme1.xml'] = strToU8(ExcelStrings.getTheme());
}
}
interface Dimensions {
startCoordinate: string
endCoordinate: string
}
interface CurrencyInfo {
styleXf: number
symbol: string
}
/**
* @hidden
*/
export class WorksheetFile implements IExcelFile {
private static MIN_WIDTH = 8.43;
private maxOutlineLevel = 0;
private sheetData = '';
private dimension = '';
private freezePane = '';
private rowHeight = '';
private mergeCellStr = '';
private mergeCellsCounter = 0;
private rowIndex = 0;
private pivotGridRowHeadersMap = new Map<number, string>();
private dimensionMap: Map<string, Dimensions> = new Map<string, Dimensions>();
private hierarchicalDimensionMap: Map<any, Map<string, Dimensions>> = new Map<any, Map<string, Dimensions>>();
private currentSummaryOwner = '';
private currentHierarchicalOwner = '';
private firstColumn = Number.MAX_VALUE;
private firstDataRow = Number.MAX_VALUE;
private isValidGrid: boolean;
private lastValidRow: string;
private currencyStyleMap = new Map<string, CurrencyInfo>([
['USD', {styleXf: 5, symbol: '$'}],
['GBP', {styleXf: 6, symbol: '£'}],
['CNY', {styleXf: 7, symbol: '¥'}],
['EUR', {styleXf: 8, symbol: '€'}],
['JPY', {styleXf: 9, symbol: '¥'}],
]);
public writeElement() {}
public async writeElementAsync(folder: Object, worksheetData: WorksheetData) {
return new Promise<void>(resolve => {
this.prepareDataAsync(worksheetData, (cols, rows) => {
const hasTable = (!worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders)
&& worksheetData.options.exportAsTable;
folder['sheet1.xml'] = strToU8(ExcelStrings.getSheetXML(
this.dimension, this.freezePane, cols, rows, hasTable, this.maxOutlineLevel, worksheetData.isHierarchical));
resolve();
});
});
}
private prepareDataAsync(worksheetData: WorksheetData, done: (cols: string, sheetData: string) => void) {
this.sheetData = '';
let cols = '';
const dictionary = worksheetData.dataDictionary;
this.rowIndex = 0;
if (worksheetData.isEmpty && (!worksheetData.options.alwaysExportHeaders || worksheetData.owner.columns.length === 0)) {
this.sheetData += '<sheetData/>';
this.dimension = 'A1';
done('', this.sheetData);
} else {
const owner = worksheetData.owner;
const isHierarchicalGrid = worksheetData.isHierarchical;
const hasMultiColumnHeader = worksheetData.hasMultiColumnHeader;
const hasMultiRowHeader = worksheetData.hasMultiRowHeader;
const hasUserSetIndex = owner.columns.some(col => col.exportIndex !== undefined);
const height = worksheetData.options.rowHeight;
this.isValidGrid = worksheetData.isHierarchical || worksheetData.isTreeGrid || worksheetData.isGroupedGrid;
this.rowHeight = height ? ` ht="${height}" customHeight="1"` : '';
this.sheetData += `<sheetData>`;
let headersForLevel: IColumnInfo[] = [];
for(let i = 0; i <= owner.maxRowLevel; i++) {
headersForLevel = owner.columns.filter(c => c.level === i && c.rowSpan > 0 && !c.skip)
this.printHeaders(worksheetData, headersForLevel, i, true);
this.rowIndex++;
}
this.rowIndex = 0;
for (let i = 0; i <= owner.maxLevel; i++) {
this.rowIndex++;
const pivotGridColumns = this.pivotGridRowHeadersMap.get(this.rowIndex) ?? "";
this.sheetData += `<row r="${this.rowIndex}"${this.rowHeight}>${pivotGridColumns}`;
const allowedColumns = owner.columns.filter(c => c.headerType !== ExportHeaderType.RowHeader && c.headerType !== ExportHeaderType.MultiRowHeader);
headersForLevel = hasMultiColumnHeader ?
allowedColumns
.filter(c => (c.level < i &&
c.headerType !== ExportHeaderType.MultiColumnHeader || c.level === i) && c.columnSpan > 0 && !c.skip)
.sort((a, b) => a.startIndex - b.startIndex)
.sort((a, b) => a.pinnedIndex - b.pinnedIndex) :
hasUserSetIndex ?
allowedColumns.filter(c => !c.skip) :
allowedColumns.filter(c => !c.skip)
.sort((a, b) => a.startIndex - b.startIndex)
.sort((a, b) => a.pinnedIndex - b.pinnedIndex);
this.printHeaders(worksheetData, headersForLevel, i, false);
this.sheetData += `</row>`;
}
const multiColumnHeaderLevel = worksheetData.options.ignoreMultiColumnHeaders ? 0 : owner.maxLevel;
const freezeHeaders = worksheetData.options.freezeHeaders ? 2 + multiColumnHeaderLevel : 1;
if (!isHierarchicalGrid) {
const col = worksheetData.hasSummaries ? worksheetData.columnCount + 1 : worksheetData.columnCount - 1
this.dimension = 'A1:' + ExcelStrings.getExcelColumn(col) + (worksheetData.rowCount);
cols += '<cols>';
if (!hasMultiColumnHeader) {
for (let j = 0; j < worksheetData.columnCount; j++) {
const width = dictionary.columnWidths[j];
// Use the width provided in the options if it exists
let widthInTwips = worksheetData.options.columnWidth !== undefined ?
worksheetData.options.columnWidth :
Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH);
if (!(widthInTwips > 0)) {
widthInTwips = WorksheetFile.MIN_WIDTH;
}
cols += `<col min="${(j + 1)}" max="${(j + 1)}" width="${widthInTwips}" customWidth="1"/>`;
}
} else {
cols += `<col min="1" max="${worksheetData.columnCount}" width="15" customWidth="1"/>`;
}
const indexOfLastPinnedColumn = worksheetData.indexOfLastPinnedColumn;
const frozenColumnCount = indexOfLastPinnedColumn + 1;
let firstCell = ExcelStrings.getExcelColumn(frozenColumnCount) + freezeHeaders;
if (indexOfLastPinnedColumn !== undefined && indexOfLastPinnedColumn !== -1 &&
!worksheetData.options.ignorePinning &&
!worksheetData.options.ignoreColumnsOrder) {
this.freezePane =
`<pane xSplit="${frozenColumnCount}" ySplit="${freezeHeaders - 1}"
topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
} else if (worksheetData.options.freezeHeaders) {
firstCell = ExcelStrings.getExcelColumn(0) + freezeHeaders;
this.freezePane =
`<pane xSplit="0" ySplit="${freezeHeaders - 1}"
topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
}
} else {
const columnWidth = worksheetData.options.columnWidth ? worksheetData.options.columnWidth : 20;
cols += `<cols><col min="1" max="${worksheetData.columnCount}" width="${columnWidth}" customWidth="1"/>`;
if (worksheetData.options.freezeHeaders) {
const firstCell = ExcelStrings.getExcelColumn(0) + freezeHeaders;
this.freezePane =
`<pane xSplit="0" ySplit="${freezeHeaders - 1}"
topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
}
}
if (worksheetData.hasSummaries) {
cols += `<col min="${worksheetData.columnCount + 2}" max="${worksheetData.columnCount + 2}" hidden="1"/>`;
}
cols += '</cols>';
this.processDataRecordsAsync(worksheetData, (rows) => {
this.sheetData += rows;
this.sheetData += '</sheetData>';
if ((hasMultiColumnHeader || hasMultiRowHeader) && this.mergeCellsCounter > 0) {
this.sheetData += `<mergeCells count="${this.mergeCellsCounter}">${this.mergeCellStr}</mergeCells>`;
}
done(cols, this.sheetData);
});
}
}
private processDataRecordsAsync(worksheetData: WorksheetData, done: (rows: string) => void) {
const rowDataArr = [];
const height = worksheetData.options.rowHeight;
this.rowHeight = height ? ' ht="' + height + '" customHeight="1"' : '';
const isHierarchicalGrid = worksheetData.isHierarchical;
const hasUserSetIndex = worksheetData.owner.columns.some(c => c.exportIndex !== undefined);
let recordHeaders = [];
yieldingLoop(worksheetData.rowCount - worksheetData.multiColumnHeaderRows - 1, 1000,
(i) => {
if (!worksheetData.isEmpty){
if (!isHierarchicalGrid) {
if (hasUserSetIndex) {
recordHeaders = worksheetData.rootKeys;
} else {
recordHeaders = worksheetData.owner.columns
.filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
.sort((a, b) => a.startIndex-b.startIndex)
.sort((a, b) => a.pinnedIndex-b.pinnedIndex)
.map(c => c.field);
}
} else {
const record = worksheetData.data[i];
if (record.type === ExportRecordType.HeaderRecord) {
const recordOwner = worksheetData.owners.get(record.owner);
const hasMultiColumnHeaders = recordOwner.columns.some(c => !c.skip && c.headerType === ExportHeaderType.MultiColumnHeader);
if (hasMultiColumnHeaders) {
this.hGridPrintMultiColHeaders(worksheetData, rowDataArr, record, recordOwner);
}
}
recordHeaders = Object.keys(worksheetData.data[i].data);
}
rowDataArr.push(this.processRow(worksheetData, i, recordHeaders, isHierarchicalGrid));
}
},
() => {
done(rowDataArr.join(''));
});
}
private hGridPrintMultiColHeaders(worksheetData: WorksheetData, rowDataArr: any[], record: IExportRecord,
owner: IColumnList) {
for (let j = 0; j < owner.maxLevel; j++) {
const recordLevel = record.level;
const outlineLevel = recordLevel > 0 ? ` outlineLevel="${recordLevel}"` : '';
this.maxOutlineLevel = this.maxOutlineLevel < recordLevel ? recordLevel : this.maxOutlineLevel;
const sHidden = record.hidden ? ` hidden="1"` : '';
this.rowIndex++;
let row = `<row r="${this.rowIndex}"${this.rowHeight}${outlineLevel}${sHidden}>`;
const headersForLevel = owner.columns
.filter(c => (c.level < j &&
c.headerType !== ExportHeaderType.MultiColumnHeader || c.level === j) && c.columnSpan > 0 && !c.skip)
.sort((a, b) => a.startIndex - b.startIndex)
.sort((a, b) => a.pinnedIndex - b.pinnedIndex);
let startValue = 0 + record.level;
for (const currentCol of headersForLevel) {
if (currentCol.level === j) {
let columnCoordinate;
columnCoordinate =
ExcelStrings.getExcelColumn(startValue) + this.rowIndex;
const columnValue = worksheetData.dataDictionary.saveValue(currentCol.header, true);
row += `<c r="${columnCoordinate}" s="3" t="s"><v>${columnValue}</v></c>`;
if (j !== owner.maxLevel) {
this.mergeCellsCounter++;
this.mergeCellStr += ` <mergeCell ref="${columnCoordinate}:`;
if (currentCol.headerType === ExportHeaderType.ColumnHeader) {
columnCoordinate = ExcelStrings.getExcelColumn(startValue) +
(this.rowIndex + owner.maxLevel - currentCol.level);
} else {
for (let k = 1; k < currentCol.columnSpan; k++) {
columnCoordinate = ExcelStrings.getExcelColumn(startValue + k) + this.rowIndex;
row += `<c r="${columnCoordinate}" s="3" />`;
}
}
this.mergeCellStr += `${columnCoordinate}" />`;
}
}
startValue += currentCol.columnSpan;
}
row += `</row>`;
rowDataArr.push(row);
}
}
private processRow(worksheetData: WorksheetData, i: number, headersForLevel: any[], isHierarchicalGrid: boolean) {
const record = worksheetData.data[i];
const rowData = new Array(worksheetData.columnCount + 2);
const rowLevel = record.level;
const outlineLevel = rowLevel > 0 ? ` outlineLevel="${rowLevel}"` : '';
this.maxOutlineLevel = this.maxOutlineLevel < rowLevel ? rowLevel : this.maxOutlineLevel;
const sHidden = record.hidden ? ` hidden="1"` : '';
this.rowIndex++;
const pivotGridColumns = this.pivotGridRowHeadersMap.get(this.rowIndex) ?? "";
rowData[0] = `<row r="${this.rowIndex}"${this.rowHeight}${outlineLevel}${sHidden}>${pivotGridColumns}`;
const keys = worksheetData.isSpecialData ? [record.data] : headersForLevel;
const isDataRecord = record.type === ExportRecordType.HierarchicalGridRecord
|| record.type === ExportRecordType.DataRecord
|| record.type === ExportRecordType.GroupedRecord
|| record.type === ExportRecordType.TreeGridRecord;
const isValidRecordType = isDataRecord || record.type === ExportRecordType.SummaryRecord;
if (isValidRecordType && worksheetData.hasSummaries) {
this.resolveSummaryDimensions(record, isDataRecord, worksheetData.isGroupedGrid)
}
for (let j = 0; j < keys.length; j++) {
const col = j + (isHierarchicalGrid ? rowLevel : worksheetData.isPivotGrid ? worksheetData.owner.maxRowLevel : 0);
const cellData = this.getCellData(worksheetData, i, col, keys[j]);
rowData[j + 1] = cellData;
}
rowData[keys.length + 1] = '</row>';
return rowData.join('');
}
private getCellData(worksheetData: WorksheetData, row: number, column: number, key: string): string {
const dictionary = worksheetData.dataDictionary;
let columnName = ExcelStrings.getExcelColumn(column) + (this.rowIndex);
const fullRow = worksheetData.data[row];
const isHeaderRecord = fullRow.type === ExportRecordType.HeaderRecord;
const isSummaryRecord = fullRow.type === ExportRecordType.SummaryRecord;
const isValidRecordType = fullRow.type === ExportRecordType.GroupedRecord
|| fullRow.type === ExportRecordType.DataRecord
|| fullRow.type === ExportRecordType.HierarchicalGridRecord
|| fullRow.type === ExportRecordType.TreeGridRecord;
this.firstDataRow = this.firstDataRow > this.rowIndex ? this.rowIndex : this.firstDataRow;
const cellValue = worksheetData.isSpecialData ?
fullRow.data :
fullRow.data[key];
if (cellValue === GRID_LEVEL_COL || key === GRID_LEVEL_COL) {
columnName = ExcelStrings.getExcelColumn(worksheetData.columnCount + 1) + (this.rowIndex);
}
if (worksheetData.hasSummaries && (isValidRecordType || (worksheetData.isGroupedGrid && isSummaryRecord))) {
this.setSummaryCoordinates(columnName, key, fullRow.hierarchicalOwner, worksheetData.isGroupedGrid && isSummaryRecord)
}
if (fullRow.summaryKey && fullRow.summaryKey === GRID_ROOT_SUMMARY && key !== GRID_LEVEL_COL && !this.isValidGrid) {
this.setRootSummaryStartCoordinate(column, key);
if (this.firstColumn > column) {
this.setRootSummaryStartCoordinate(worksheetData.columnCount + 1, GRID_LEVEL_COL);
this.firstColumn = column;
}
}
const targetColArr = Array.from(worksheetData.owners.values()).map(arr => arr.columns).find(product => product.some(item => item.field === key));
const targetCol = targetColArr ? targetColArr.find(col => col.field === key) : undefined;
if ((cellValue === undefined || cellValue === null) && !worksheetData.hasSummaries) {
return `<c r="${columnName}" s="1"/>`;
} else if ((worksheetData.hasSummaries && (isValidRecordType || isHeaderRecord)) || !worksheetData.hasSummaries) {
const savedValue = dictionary.saveValue(cellValue, isHeaderRecord);
const isSavedAsString = savedValue !== -1;
const isSavedAsDate = !isSavedAsString && cellValue instanceof Date;
let value = isSavedAsString ? savedValue : cellValue;
if (isSavedAsDate) {
const timeZoneOffset = value.getTimezoneOffset() * 60000;
const isoString = (new Date(value - timeZoneOffset)).toISOString();
value = isoString.substring(0, isoString.indexOf('.'));
}
const type = isSavedAsString ? ` t="s"` : isSavedAsDate ? ` t="d"` : '';
const isTime = targetCol?.dataType === 'time';
const isDateTime = targetCol?.dataType === 'dateTime';
const isPercentage = targetCol?.dataType === 'percent';
const isColumnCurrencyType = targetCol?.dataType === 'currency';
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"`;
return `<c r="${columnName}"${type}${format}><v>${value}</v></c>`;
} else {
let summaryFunc = `"${cellValue ?? ""}"`;
if (isSummaryRecord && cellValue) {
const dimensionMapKey = this.isValidGrid ? fullRow.hierarchicalOwner ?? GRID_PARENT : null;
const level = worksheetData.isGroupedGrid ? worksheetData.maxLevel : fullRow.level;
summaryFunc = this.getSummaryFunction(cellValue.label, key, dimensionMapKey, level, targetCol);
if (!summaryFunc) {
const cellStr = `${cellValue.label}: ${cellValue.value}`;
const savedValue = dictionary.saveValue(cellStr, false);
return `<c r="${columnName}" t="s" s="1"><v>${savedValue}</v></c>`;
}
return `<c r="${columnName}" t="str"><f t="array" ref="${columnName}">${summaryFunc}</f></c>`;
}
return `<c r="${columnName}" t="s" s="1"><f>${summaryFunc}</f></c>`;
}
}
private resolveSummaryDimensions(record: IExportRecord, isDataRecord: boolean, isGroupedGrid: boolean) {
if (this.isValidGrid &&
this.currentHierarchicalOwner !== '' &&
this.currentHierarchicalOwner !== record.owner &&
!this.hierarchicalDimensionMap.get(this.currentHierarchicalOwner)) {
this.hierarchicalDimensionMap.set(this.currentHierarchicalOwner, new Map(this.dimensionMap))
}
if (isDataRecord) {
if (this.currentSummaryOwner !== record.summaryKey || this.currentHierarchicalOwner !== record.hierarchicalOwner) {
this.dimensionMap.clear();
}
this.currentSummaryOwner = record.summaryKey;
// For grouped grid we need to reset the parent map
// so we can change the startCoordinate for each record
if (isGroupedGrid && this.currentHierarchicalOwner !== '' && record.hierarchicalOwner === GRID_PARENT) {
this.hierarchicalDimensionMap.delete(GRID_PARENT)
}
this.currentHierarchicalOwner = record.hierarchicalOwner;
}
}
private setSummaryCoordinates(columnName: string, key: string, hierarchicalOwner: string, useLastValidEndCoordinate: boolean) {
const targetDimensionMap = this.hierarchicalDimensionMap.get(hierarchicalOwner) ?? this.dimensionMap;
if (!targetDimensionMap.get(key)) {
const initialDimensions: Dimensions = {
startCoordinate: columnName,
endCoordinate: columnName
};
targetDimensionMap.set(key, initialDimensions)
} else {
if (useLastValidEndCoordinate) {
this.setEndCoordinates(targetDimensionMap, true);
} else {
targetDimensionMap.get(key).endCoordinate = columnName;
this.lastValidRow = targetDimensionMap.get(key).endCoordinate.match(/[a-z]+|[^a-z]+/gi)[1]
}
}
if (this.isValidGrid && !useLastValidEndCoordinate && hierarchicalOwner !== GRID_PARENT) {
const parentMap = this.hierarchicalDimensionMap.get(GRID_PARENT);
this.setEndCoordinates(parentMap);
}
}
private setEndCoordinates(map: Map<string, Dimensions>, useLastValidEndCoordinate = false) {
for (const a of map.values()) {
const colName = a.endCoordinate.match(/[a-z]+|[^a-z]+/gi)[0];
a.endCoordinate = `${colName}${useLastValidEndCoordinate ? this.lastValidRow : this.rowIndex}`;
}
}
private getSummaryFunction(type: string, key: string, dimensionMapKey: any, recordLevel: number, col: IColumnInfo): string {
const dimensionMap = dimensionMapKey ? this.hierarchicalDimensionMap.get(dimensionMapKey) : this.dimensionMap;
const dimensions = dimensionMap.get(key);
const levelDimensions = dimensionMap.get(GRID_LEVEL_COL);
let func = '';
let funcType = '';
let result = '';
const currencyInfo = this.currencyStyleMap.get(col.currencyCode);
switch(type.toLowerCase()) {
case "count":
return `"Count: "&_xlfn.COUNTIF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}, ${recordLevel})`
case "min":
func = `_xlfn.MIN(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate}))`
funcType = `"Min: "&`;
result = funcType + (col.dataType === 'currency' && currencyInfo
? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
: `${func}`);
return result
case "max":
func = `_xlfn.MAX(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate}))`
funcType = `"Max: "&`;
result = funcType + (col.dataType === 'currency' && currencyInfo
? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
: `${func}`);
return result
case "sum":
func = `_xlfn.SUMIF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}, ${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})`
funcType = `"Sum: "&`;
result = funcType + (col.dataType === 'currency' && currencyInfo
? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
: `${func}`);
return result
case "avg":
func = `_xlfn.AVERAGEIF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}, ${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})`
funcType = `"Avg: "&`;
result = funcType + (col.dataType === 'currency' && currencyInfo
? `_xlfn.TEXT(${func}, "${currencyInfo.symbol}#,##0.00")`
: `${func}`);
return result
case "earliest":
// TODO: get date format from locale
return `"Earliest: "&_xlfn.TEXT(_xlfn.MIN(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})), "m/d/yyyy")`
case "latest":
// TODO: get date format from locale
return `"Latest: "&_xlfn.TEXT(_xlfn.MAX(_xlfn.IF(${levelDimensions.startCoordinate}:${levelDimensions.endCoordinate}=${recordLevel}, ${dimensions.startCoordinate}:${dimensions.endCoordinate})), "m/d/yyyy")`
}
}
private setRootSummaryStartCoordinate(column: number, key: string) {
const firstDataRecordColName = ExcelStrings.getExcelColumn(column) + (this.firstDataRow);
if (this.dimensionMap.get(key).startCoordinate !== firstDataRecordColName) {
this.dimensionMap.get(key).startCoordinate = firstDataRecordColName;
}
}
private printHeaders(worksheetData: WorksheetData, headersForLevel: IColumnInfo[], i: number, isVertical: boolean) {
let startValue = 0;
let str = '';
const isHierarchicalGrid = worksheetData.isHierarchical;
let rowStyle = isHierarchicalGrid ? ' s="3"' : '';
const dictionary = worksheetData.dataDictionary;
const owner = worksheetData.owner;
const maxLevel = isVertical
? owner.maxRowLevel
: owner.maxLevel;
for (const currentCol of headersForLevel) {
const spanLength = isVertical ? currentCol.rowSpan : currentCol.columnSpan;
if (currentCol.level === i) {
let columnCoordinate;
const column = isVertical
? this.rowIndex
: startValue + (owner.maxRowLevel ?? 0)
const rowCoordinate = isVertical
? startValue + owner.maxLevel + 2
: this.rowIndex
const columnValue = dictionary.saveValue(currentCol.header, true, false);
columnCoordinate = (currentCol.field === GRID_LEVEL_COL
? ExcelStrings.getExcelColumn(worksheetData.columnCount + 1)
: ExcelStrings.getExcelColumn(column)) + rowCoordinate;
rowStyle = isVertical && currentCol.rowSpan > 1 ? ' s="4"' : rowStyle;
str = `<c r="${columnCoordinate}"${rowStyle} t="s"><v>${columnValue}</v></c>`;
if (isVertical) {
if (this.pivotGridRowHeadersMap.has(rowCoordinate)) {
this.pivotGridRowHeadersMap.set(rowCoordinate, this.pivotGridRowHeadersMap.get(rowCoordinate) + str)
} else {
this.pivotGridRowHeadersMap.set(rowCoordinate, str)
}
} else {
this.sheetData += str;
}
if (i !== maxLevel) {
this.mergeCellsCounter++;
this.mergeCellStr += ` <mergeCell ref="${columnCoordinate}:`;
if (currentCol.headerType === ExportHeaderType.ColumnHeader) {
const col = isVertical
? maxLevel
: startValue + (owner.maxRowLevel ?? 0);
const row = isVertical
? rowCoordinate
: owner.maxLevel + 1;
columnCoordinate = ExcelStrings.getExcelColumn(col) + row;
} else {
for (let k = 1; k < spanLength; k++) {
const col = isVertical
? column
: column + k;
const row = isVertical
? rowCoordinate + k
: this.rowIndex;
columnCoordinate = ExcelStrings.getExcelColumn(col) + row;
str = `<c r="${columnCoordinate}"${rowStyle} />`;
isVertical
? this.pivotGridRowHeadersMap.set(row, str)
: this.sheetData += str
}
}
this.mergeCellStr += `${columnCoordinate}" />`;
}
}
startValue += spanLength;
}
}
}
/**
* @hidden
*/
export class StyleFile implements IExcelFile {
public writeElement(folder: Object) {
folder['styles.xml'] = strToU8(ExcelStrings.getStyles());
}
}
/**
* @hidden
*/
export class WorkbookFile implements IExcelFile {
public writeElement(folder: Object, worksheetData: WorksheetData) {
folder['workbook.xml'] = strToU8(ExcelStrings.getWorkbook(worksheetData.options.worksheetName));
}
}
/**
* @hidden
*/
export class ContentTypesFile implements IExcelFile {
public writeElement(folder: Object, worksheetData: WorksheetData) {
const hasSharedStrings = !worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders;
folder['[Content_Types].xml'] = strToU8(ExcelStrings.getContentTypesXML(hasSharedStrings, worksheetData.options.exportAsTable));
}
}
/**
* @hidden
*/
export class SharedStringsFile implements IExcelFile {
public writeElement(folder: Object, worksheetData: WorksheetData) {
const dict = worksheetData.dataDictionary;
const sortedValues = dict.getKeys();
const sharedStrings = new Array<string>(sortedValues.length);
for (const value of sortedValues) {
sharedStrings[dict.getSanitizedValue(value)] = '<si><t>' + value + '</t></si>';
}
folder['sharedStrings.xml'] = strToU8(ExcelStrings.getSharedStringXML(
dict.stringsCount,
sortedValues.length,
sharedStrings.join(''))
);
}
}
/**
* @hidden
*/
export class TablesFile implements IExcelFile {
public writeElement(folder: Object, worksheetData: WorksheetData) {
const columnCount = worksheetData.columnCount;
const lastColumn = ExcelStrings.getExcelColumn(columnCount - 1) + worksheetData.rowCount;
const autoFilterDimension = 'A1:' + lastColumn;
const tableDimension = worksheetData.isEmpty
? 'A1:' + ExcelStrings.getExcelColumn(columnCount - 1) + (worksheetData.rowCount + 1)
: autoFilterDimension;
const hasUserSetIndex = worksheetData.owner.columns.some(c => c.exportIndex !== undefined);
const values = hasUserSetIndex
? worksheetData.rootKeys
: worksheetData.owner.columns
.filter(c => !c.skip)
.sort((a, b) => a.startIndex - b.startIndex)
.sort((a, b) => a.pinnedIndex - b.pinnedIndex)
.map(c => c.header);
let sortString = '';
let tableColumns = '<tableColumns count="' + columnCount + '">';
for (let i = 0; i < columnCount; i++) {
const value = values[i];
tableColumns += '<tableColumn id="' + (i + 1) + '" name="' + value + '"/>';
}
tableColumns += '</tableColumns>';
if (worksheetData.sort) {
const sortingExpression = worksheetData.sort;
const sc = ExcelStrings.getExcelColumn(values.indexOf(sortingExpression.fieldName));
const dir = sortingExpression.dir - 1;
sortString = `<sortState ref="A2:${lastColumn}"><sortCondition descending="${dir}" ref="${sc}1:${sc}15"/></sortState>`;
}
folder['table1.xml'] = strToU8(ExcelStrings.getTablesXML(autoFilterDimension, tableDimension, tableColumns, sortString));
}
}
/**
* @hidden
*/
export class WorksheetRelsFile implements IExcelFile {
public writeElement(folder: Object) {
folder['sheet1.xml.rels'] = strToU8(ExcelStrings.getWorksheetRels());
}
}