UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

554 lines (459 loc) • 24.3 kB
"use strict"; var Class = require("../core/class"), window = require("../core/utils/window").getWindow(), typeUtils = require("../core/utils/type"), extend = require("../core/utils/extend").extend, inArray = require("../core/utils/array").inArray, errors = require("../ui/widget/ui.errors"), stringUtils = require("../core/utils/string"), JSZip = require("jszip"), fileSaver = require("./file_saver"), excelFormatConverter = require("./excel_format_converter"), XML_TAG = "<?xml version=\"1.0\" encoding=\"utf-8\"?>", GROUP_SHEET_PR_XML = "<sheetPr><outlinePr summaryBelow=\"0\"/></sheetPr>", SINGLE_SHEET_PR_XML = "<sheetPr/>", BASE_STYLE_XML = "<fonts count=\"2\"><font><sz val=\"11\"/><color theme=\"1\"/><name val=\"Calibri\"/><family val=\"2\"/>" + "<scheme val=\"minor\"/></font><font><b/><sz val=\"11\"/><color theme=\"1\"/><name val=\"Calibri\"/>" + "<family val=\"2\"/><scheme val=\"minor\"/></font></fonts><fills count=\"1\"><fill><patternFill patternType=\"none\"/>" + "</fill></fills><borders count=\"1\"><border><left style=\"thin\"><color rgb=\"FFD3D3D3\"/></left><right style=\"thin\">" + "<color rgb=\"FFD3D3D3\"/></right><top style=\"thin\"><color rgb=\"FFD3D3D3\"/></top><bottom style=\"thin\"><color rgb=\"FFD3D3D3\"/>" + "</bottom></border></borders><cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>", OPEN_XML_FORMAT_URL = "http://schemas.openxmlformats.org", RELATIONSHIP_PART_NAME = "rels", XL_FOLDER_NAME = "xl", WORKBOOK_FILE_NAME = "workbook.xml", CONTENTTYPES_FILE_NAME = "[Content_Types].xml", SHAREDSTRING_FILE_NAME = "sharedStrings.xml", STYLE_FILE_NAME = "styles.xml", WORKSHEETS_FOLDER = "worksheets", WORKSHEET_FILE_NAME = "sheet1.xml", VALID_TYPES = { "boolean": "b", "date": "d", "number": "n", "string": "s" }, EXCEL_START_TIME = Date.UTC(1899, 11, 30), DAYS_COUNT_BEFORE_29_FEB_1900 = 60, BOLD_STYLES_COUNT = 4, MAX_DIGIT_WIDTH_IN_PIXELS = 7, // Calibri font with 11pt size CUSTOM_FORMAT_START_INDEX = 165; exports.ExcelCreator = Class.inherit({ _getXMLTag: function _getXMLTag(tagName, attributes, content) { var result = "<" + tagName, i, length = attributes.length, attr; for (i = 0; i < length; i++) { attr = attributes[i]; result = result + " " + attr.name + "=\"" + attr.value + "\""; } return typeUtils.isDefined(content) ? result + ">" + content + "</" + tagName + ">" : result + " />"; }, _getCellIndex: function _getCellIndex(rowIndex, cellIndex) { var sheetIndex = '', max = 26, charCode; if (this._maxIndex[0] < Number(rowIndex)) { this._maxIndex[0] = Number(rowIndex); } if (this._maxIndex[1] < Number(cellIndex)) { this._maxIndex[1] = Number(cellIndex); } while (true) { charCode = 65 + (cellIndex >= max ? cellIndex % max : Math.ceil(cellIndex)); sheetIndex = String.fromCharCode(charCode) + sheetIndex; if (cellIndex >= max) { cellIndex = Math.floor(cellIndex / max) - 1; } else { break; } } return sheetIndex + rowIndex; }, _getDataType: function _getDataType(dataType) { return VALID_TYPES[dataType] || "s"; }, _formatObjectConverter: function _formatObjectConverter(format, precision, dataType) { var result = { format: format, precision: precision, dataType: dataType }; if (typeUtils.isObject(format)) { return extend(result, format, { format: format.formatter || format.type, currency: format.currency }); } return result; }, _appendFormat: function _appendFormat(format, precision, dataType) { var currency, newFormat = this._formatObjectConverter(format, precision, dataType); format = newFormat.format; precision = newFormat.precision; currency = newFormat.currency; dataType = newFormat.dataType; format = excelFormatConverter.convertFormat(format, precision, dataType, currency); if (format) { if (inArray(format, this._styleFormat) === -1) { this._styleFormat.push(format); } return inArray(format, this._styleFormat) + 1; } }, _appendString: function _appendString(value) { if (typeUtils.isDefined(value)) { value = String(value); if (value.length) { value = stringUtils.encodeHtml(value); if (this._stringHash[value] === undefined) { this._stringHash[value] = this._stringArray.length; this._stringArray.push(value); } return this._stringHash[value]; } } }, _getExcelDateValue: function _getExcelDateValue(date) { var days, totalTime; if (typeUtils.isDate(date)) { days = Math.floor((Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) - EXCEL_START_TIME) / (1000 * 60 * 60 * 24)); if (days < DAYS_COUNT_BEFORE_29_FEB_1900) { days--; } totalTime = (date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds()) / (24 * 3600); return days + totalTime; } }, _prepareValue: function _prepareValue(rowIndex, cellIndex) { var dataProvider = this._dataProvider, value = dataProvider.getCellValue(rowIndex, cellIndex), type = this._getDataType(dataProvider.getCellType(rowIndex, cellIndex)), formatID = this._styleArray[this._dataProvider.getStyleId(rowIndex, cellIndex)].formatID, format = typeUtils.isNumeric(formatID) ? this._styleFormat[formatID - 1] : null; if (type === "d" && !typeUtils.isDate(value)) { type = "s"; } switch (type) { case "s": value = this._appendString(value); break; case "d": value = this._getExcelDateValue(value, format); type = "n"; break; } return { value: value, type: type }; }, _getDataArray: function _getDataArray() { var that = this, rowIndex, cellIndex, cellsArray, cellData, result = [], dataProvider = that._dataProvider, rowsLength = dataProvider.getRowsCount(), columns = dataProvider.getColumns(), cellsLength; for (rowIndex = 0; rowIndex < rowsLength; rowIndex++) { cellsArray = []; cellsLength = columns.length; for (cellIndex = 0; cellIndex !== cellsLength; cellIndex++) { cellData = that._prepareValue(rowIndex, cellIndex); cellsArray.push({ style: dataProvider.getStyleId(rowIndex, cellIndex), value: cellData.value, type: cellData.type }); } if (!that._needSheetPr && dataProvider.getGroupLevel(rowIndex) > 0) { that._needSheetPr = true; } result.push(cellsArray); } return result; }, _getBoldStyleID: function _getBoldStyleID(alignment) { for (var i = 0; i < BOLD_STYLES_COUNT - 1; i++) { if (this._styleArray[i].alignment === alignment) { return i; } } }, _calculateWidth: function _calculateWidth(pixelsWidth) { pixelsWidth = parseInt(pixelsWidth, 10); if (!pixelsWidth || pixelsWidth < 5) pixelsWidth = 100; return Math.min(255, Math.floor((pixelsWidth - 5) / MAX_DIGIT_WIDTH_IN_PIXELS * 100 + 0.5) / 100); }, _prepareStyleData: function _prepareStyleData() { var that = this, styles = that._dataProvider.getStyles(); that._dataProvider.getColumns().forEach(function (column) { that._colsArray.push(that._calculateWidth(column.width)); }); styles.forEach(function (style) { that._styleArray.push({ bold: !!style.bold, alignment: style.alignment || "left", formatID: that._appendFormat(style.format, style.precision, style.dataType), wrapText: style.wrapText }); }); }, _prepareCellData: function _prepareCellData() { this._cellsArray = this._getDataArray(); }, _createXMLRelationships: function _createXMLRelationships(xmlRelationships) { return this._getXMLTag("Relationships", [{ name: "xmlns", value: OPEN_XML_FORMAT_URL + "/package/2006/relationships" }], xmlRelationships); }, _createXMLRelationship: function _createXMLRelationship(id, type, target) { return this._getXMLTag("Relationship", [{ name: "Id", value: "rId" + id }, { name: "Type", value: OPEN_XML_FORMAT_URL + "/officeDocument/2006/relationships/" + type }, { name: "Target", value: target }]); }, _getWorkbookContent: function _getWorkbookContent() { var content = "<bookViews><workbookView xWindow=\"0\" yWindow=\"0\" windowWidth=\"0\" windowHeight=\"0\"/>" + "</bookViews><sheets><sheet name=\"Sheet\" sheetId=\"1\" r:id=\"rId1\" /></sheets><definedNames>" + "<definedName name=\"_xlnm.Print_Titles\" localSheetId=\"0\">Sheet!$1:$1</definedName>" + "<definedName name=\"_xlnm._FilterDatabase\" hidden=\"0\" localSheetId=\"0\">Sheet!$A$1:$F$6332</definedName></definedNames>"; return XML_TAG + this._getXMLTag("workbook", [{ name: "xmlns:r", value: OPEN_XML_FORMAT_URL + "/officeDocument/2006/relationships" }, { name: "xmlns", value: OPEN_XML_FORMAT_URL + "/spreadsheetml/2006/main" }], content); }, _getContentTypesContent: function _getContentTypesContent() { return XML_TAG + "<Types xmlns=\"" + OPEN_XML_FORMAT_URL + "/package/2006/content-types\"><Default Extension=\"rels\" " + "ContentType=\"application/vnd.openxmlformats-package.relationships+xml\" /><Default Extension=\"xml\" " + "ContentType=\"application/xml\" /><Override PartName=\"/xl/worksheets/sheet1.xml\" " + "ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\" />" + "<Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\" />" + "<Override PartName=\"/xl/sharedStrings.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\" />" + "<Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\" /></Types>"; }, _generateStylesXML: function _generateStylesXML() { var that = this, folder = that._zip.folder(XL_FOLDER_NAME), xmlStyles = [], formatIndex, XML = ""; for (formatIndex = 0; formatIndex < that._styleFormat.length; formatIndex++) { that._styleFormat[formatIndex] = that._getXMLTag("numFmt", [{ name: "numFmtId", value: Number(formatIndex) + CUSTOM_FORMAT_START_INDEX }, { name: "formatCode", value: that._styleFormat[formatIndex] }]); } XML = XML + that._getXMLTag("numFmts", [{ name: "count", value: that._styleFormat.length }], that._styleFormat.join("")) + BASE_STYLE_XML; this._styleArray.forEach(function (style) { xmlStyles.push(that._getXMLTag("xf", [{ name: "xfId", value: 0 }, { name: "applyAlignment", value: 1 }, { name: "fontId", value: Number(!!style.bold) }, { name: "applyNumberFormat", value: typeUtils.isDefined(style.formatID) ? 1 : 0 }, { name: "numFmtId", value: typeUtils.isDefined(style.formatID) ? Number(style.formatID) + CUSTOM_FORMAT_START_INDEX - 1 : 0 }], that._getXMLTag("alignment", [{ name: "vertical", value: "top" }, { name: "wrapText", value: Number(!!style.wrapText) }, { name: "horizontal", value: style.alignment }]))); }); XML = XML + that._getXMLTag("cellXfs", [{ name: "count", value: xmlStyles.length }], xmlStyles.join("")); XML = XML + that._getXMLTag("cellStyles", [{ name: "count", value: 1 }], that._getXMLTag("cellStyle", [{ name: "name", value: "Normal" }, { name: "xfId", value: 0 }, { name: "builtinId", value: 0 }])); XML = XML_TAG + that._getXMLTag("styleSheet", [{ name: "xmlns", value: OPEN_XML_FORMAT_URL + "/spreadsheetml/2006/main" }], XML); folder.file(STYLE_FILE_NAME, XML); that._styleArray = []; }, _generateStringsXML: function _generateStringsXML() { var folder = this._zip.folder(XL_FOLDER_NAME), stringIndex, stringsLength = this._stringArray.length, sharedStringXml = XML_TAG; for (stringIndex = 0; stringIndex < stringsLength; stringIndex++) { this._stringArray[stringIndex] = this._getXMLTag("si", [], this._getXMLTag("t", [], this._stringArray[stringIndex])); } sharedStringXml = sharedStringXml + this._getXMLTag("sst", [{ name: "xmlns", value: OPEN_XML_FORMAT_URL + "/spreadsheetml/2006/main" }, { name: "count", value: this._stringArray.length }, { name: "uniqueCount", value: this._stringArray.length }], this._stringArray.join("")); folder.file(SHAREDSTRING_FILE_NAME, sharedStringXml); this._stringArray = []; }, _getPaneXML: function _getPaneXML() { var attributes = [{ name: "activePane", value: "bottomLeft" }, { name: "state", value: "frozen" }], frozenArea = this._dataProvider.getFrozenArea(); if (!(frozenArea.x || frozenArea.y)) return ""; if (frozenArea.x) { attributes.push({ name: "xSplit", value: frozenArea.x }); } if (frozenArea.y) { attributes.push({ name: "ySplit", value: frozenArea.y }); } attributes.push({ name: "topLeftCell", value: this._getCellIndex(frozenArea.y + 1, frozenArea.x) }); return this._getXMLTag("pane", attributes); }, _getAutoFilterXML: function _getAutoFilterXML(maxCellIndex) { if (this._options.autoFilterEnabled) { return "<autoFilter ref=\"A" + this._dataProvider.getHeaderRowCount() + ":" + maxCellIndex + "\" />"; } return ""; }, _getIgnoredErrorsXML: function _getIgnoredErrorsXML(maxCellIndex) { if (this._options.ignoreErrors) { return "<ignoredErrors><ignoredError sqref=\"A1:" + maxCellIndex + "\" numberStoredAsText=\"1\" /></ignoredErrors>"; } return ""; }, _generateWorksheetXML: function _generateWorksheetXML() { var colIndex, rowIndex, cellData, xmlCells, maxCellIndex, counter = 0, xmlRows = [], rowsLength = this._cellsArray.length, cellsLength, colsLength = this._colsArray.length, rSpans = "1:" + colsLength, headerRowCount = this._dataProvider.getHeaderRowCount ? this._dataProvider.getHeaderRowCount() : 1, xmlResult = [["<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" " + "xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" " + "mc:Ignorable=\"x14ac\" xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\">", this._needSheetPr ? GROUP_SHEET_PR_XML : SINGLE_SHEET_PR_XML, "<dimension ref=\"A1:", this._getCellIndex(this._maxIndex[0], this._maxIndex[1]) + "\"/><sheetViews><sheetView " + (this._rtlEnabled ? "rightToLeft=\"1\" " : "") + "tabSelected=\"1\" workbookViewId=\"0\">" + this._getPaneXML() + "</sheetView></sheetViews><sheetFormatPr defaultRowHeight=\"15\" outlineLevelRow=\"", this._dataProvider.getRowsCount() > 0 ? this._dataProvider.getGroupLevel(0) : 0, "\" x14ac:dyDescent=\"0.25\"/>"].join("")]; for (colIndex = 0; colIndex < colsLength; colIndex++) { this._colsArray[colIndex] = this._getXMLTag("col", [{ name: "width", value: this._colsArray[colIndex] }, { name: "min", value: Number(colIndex) + 1 }, { name: "max", value: Number(colIndex) + 1 }]); } xmlResult.push(this._getXMLTag("cols", [], this._colsArray.join("")) + "<sheetData>"); for (rowIndex = 0; rowIndex < rowsLength; rowIndex++) { xmlCells = []; cellsLength = this._cellsArray[rowIndex].length; for (colIndex = 0; colIndex < cellsLength; colIndex++) { rowIndex = Number(rowIndex); cellData = this._cellsArray[rowIndex][colIndex]; xmlCells.push(this._getXMLTag("c", [{ name: "r", value: this._getCellIndex(rowIndex + 1, colIndex) }, { name: "s", value: cellData.style }, { name: "t", value: cellData.type }], typeUtils.isDefined(cellData.value) ? this._getXMLTag("v", [], cellData.value) : null)); } xmlRows.push(this._getXMLTag("row", [{ name: "r", value: Number(rowIndex) + 1 }, { name: "spans", value: rSpans }, { name: "outlineLevel", value: rowIndex >= headerRowCount ? this._dataProvider.getGroupLevel(rowIndex) : 0 }, { name: "x14ac:dyDescent", value: "0.25" }], xmlCells.join(""))); this._cellsArray[rowIndex] = null; if (counter++ > 10000) { xmlResult.push(xmlRows.join("")); xmlRows = []; counter = 0; } } xmlResult.push(xmlRows.join("")); xmlRows = []; maxCellIndex = this._getCellIndex(this._maxIndex[0], this._maxIndex[1]); xmlResult.push("</sheetData>" + this._getAutoFilterXML(maxCellIndex) + this._generateMergingXML() + this._getIgnoredErrorsXML(maxCellIndex) + "</worksheet>"); this._zip.folder(XL_FOLDER_NAME).folder(WORKSHEETS_FOLDER).file(WORKSHEET_FILE_NAME, xmlResult.join("")); this._colsArray = []; this._cellsArray = []; xmlResult = []; }, _generateMergingXML: function _generateMergingXML() { var k, l, cellIndex, rowIndex, rowsLength = typeUtils.isDefined(this._dataProvider.getHeaderRowCount) ? this._dataProvider.getHeaderRowCount() : this._dataProvider.getRowsCount(), columnsLength = this._dataProvider.getColumns().length, usedArea = [], mergeArray = [], mergeArrayLength, mergeIndex, mergeXML = ''; for (rowIndex = 0; rowIndex < rowsLength; rowIndex++) { for (cellIndex = 0; cellIndex !== columnsLength; cellIndex++) { if (!typeUtils.isDefined(usedArea[rowIndex]) || !typeUtils.isDefined(usedArea[rowIndex][cellIndex])) { var cellMerge = this._dataProvider.getCellMerging(rowIndex, cellIndex); if (cellMerge.colspan || cellMerge.rowspan) { mergeArray.push({ start: this._getCellIndex(rowIndex + 1, cellIndex), end: this._getCellIndex(rowIndex + 1 + (cellMerge.rowspan || 0), cellIndex + (cellMerge.colspan || 0)) }); for (k = rowIndex; k <= rowIndex + cellMerge.rowspan || 0; k++) { for (l = cellIndex; l <= cellIndex + cellMerge.colspan || 0; l++) { if (!typeUtils.isDefined(usedArea[k])) { usedArea[k] = []; } usedArea[k][l] = true; } } } } } } mergeArrayLength = mergeArray.length; for (mergeIndex = 0; mergeIndex < mergeArrayLength; mergeIndex++) { mergeXML = mergeXML + this._getXMLTag("mergeCell", [{ name: "ref", value: mergeArray[mergeIndex].start + ":" + mergeArray[mergeIndex].end }]); } return mergeXML.length ? this._getXMLTag("mergeCells", [{ name: "count", value: mergeArrayLength }], mergeXML) : ""; }, _generateCommonXML: function _generateCommonXML() { var relsFileContent = XML_TAG + this._createXMLRelationships(this._createXMLRelationship(1, "officeDocument", "xl/" + WORKBOOK_FILE_NAME)), xmlRelationships, folder = this._zip.folder(XL_FOLDER_NAME), relsXML = XML_TAG; this._zip.folder("_" + RELATIONSHIP_PART_NAME).file("." + RELATIONSHIP_PART_NAME, relsFileContent); xmlRelationships = this._createXMLRelationship(1, "worksheet", "worksheets/" + WORKSHEET_FILE_NAME) + this._createXMLRelationship(2, "styles", STYLE_FILE_NAME) + this._createXMLRelationship(3, "sharedStrings", SHAREDSTRING_FILE_NAME); relsXML = relsXML + this._createXMLRelationships(xmlRelationships); folder.folder("_" + RELATIONSHIP_PART_NAME).file(WORKBOOK_FILE_NAME + ".rels", relsXML); folder.file(WORKBOOK_FILE_NAME, this._getWorkbookContent()); this._zip.file(CONTENTTYPES_FILE_NAME, this._getContentTypesContent()); }, _generateContent: function _generateContent() { this._prepareStyleData(); this._prepareCellData(); this._generateWorkXML(); this._generateCommonXML(); }, _generateWorkXML: function _generateWorkXML() { this._generateStylesXML(); this._generateStringsXML(); this._generateWorksheetXML(); }, ctor: function ctor(dataProvider, options) { this._rtlEnabled = options && !!options.rtlEnabled; this._options = options; this._maxIndex = [1, 2]; this._stringArray = []; this._stringHash = {}; this._styleArray = []; this._colsArray = []; this._cellsArray = []; this._styleFormat = []; this._needSheetPr = false; this._dataProvider = dataProvider; if (typeUtils.isDefined(JSZip)) { this._zip = new JSZip(); } else { this._zip = null; } }, _checkZipState: function _checkZipState() { if (!this._zip) { throw errors.Error("E1041"); } }, ready: function ready() { return this._dataProvider.ready(); }, getData: function getData(isBlob) { var options = { type: isBlob ? "blob" : "base64", compression: "DEFLATE", mimeType: fileSaver.MIME_TYPES["EXCEL"] }; this._checkZipState(); this._generateContent(); return this._zip.generateAsync ? this._zip.generateAsync(options) : this._zip.generate(options); } }); exports.getData = function (data, options, callback) { // TODO: Looks like there is no need to export ExcelCreator any more? var excelCreator = new exports.ExcelCreator(data, options); excelCreator._checkZipState(); excelCreator.ready().done(function () { if (excelCreator._zip.generateAsync) { excelCreator.getData(typeUtils.isFunction(window.Blob)).then(callback); } else { callback(excelCreator.getData(typeUtils.isFunction(window.Blob))); } }); }; ///#DEBUG exports.__internals = { CONTENTTYPES_FILE_NAME: CONTENTTYPES_FILE_NAME, RELATIONSHIP_PART_NAME: RELATIONSHIP_PART_NAME, XL_FOLDER_NAME: XL_FOLDER_NAME, WORKBOOK_FILE_NAME: WORKBOOK_FILE_NAME, STYLE_FILE_NAME: STYLE_FILE_NAME, WORKSHEET_FILE_NAME: WORKSHEET_FILE_NAME, WORKSHEETS_FOLDER: WORKSHEETS_FOLDER, SHAREDSTRING_FILE_NAME: SHAREDSTRING_FILE_NAME, GROUP_SHEET_PR_XML: GROUP_SHEET_PR_XML, SINGLE_SHEET_PR_XML: SINGLE_SHEET_PR_XML }; ///#ENDDEBUG