UNPKG

excel-builder

Version:

An easy way of building Excel files with javascript

327 lines (289 loc) 12.4 kB
"use strict"; var Q = require('q'); var _ = require('lodash'); var util = require('./util'); var StyleSheet = require('./StyleSheet'); var Worksheet = require('./Worksheet'); var SharedStrings = require('./SharedStrings'); var RelationshipManager = require('./RelationshipManager'); var Paths = require('./Paths'); var XMLDOM = require('./XMLDOM'); /** * @module Excel/Workbook */ /* globals console: true */ var Workbook = function (config) { this.worksheets = []; this.tables = []; this.drawings = []; this.media = {}; this.initialize(config); }; _.extend(Workbook.prototype, { initialize: function () { this.id = _.uniqueId('Workbook'); this.styleSheet = new StyleSheet(); this.sharedStrings = new SharedStrings(); this.relations = new RelationshipManager(); this.relations.addRelation(this.styleSheet, 'stylesheet'); this.relations.addRelation(this.sharedStrings, 'sharedStrings'); }, createWorksheet: function (config) { config = config || {}; _.defaults(config, { name: 'Sheet '.concat(this.worksheets.length + 1) }); return new Worksheet(config); }, getStyleSheet: function () { return this.styleSheet; }, addTable: function (table) { this.tables.push(table); }, addDrawings: function (drawings) { this.drawings.push(drawings); }, /** * Set number of rows to repeat for this sheet. * * @param {String} sheet name * @param {int} number of rows to repeat from the top * @returns {undefined} */ setPrintTitleTop: function (inSheet, inRowCount) { if (this.printTitles == null) { this.printTitles = {}; } if (this.printTitles[inSheet] == null) { this.printTitles[inSheet] = {}; } this.printTitles[inSheet].top = inRowCount; }, /** * Set number of rows to repeat for this sheet. * * @param {String} sheet name * @param {int} number of columns to repeat from the left * @returns {undefined} */ setPrintTitleLeft: function (inSheet, inColumn) { if (this.printTitles == null) { this.printTitles = {}; } if (this.printTitles[inSheet] == null) { this.printTitles[inSheet] = {}; } //WARN: this does not handle AA, AB, etc. this.printTitles[inSheet].left = String.fromCharCode(64 + inRowCount); }, addMedia: function (type, fileName, fileData, contentType) { var fileNamePieces = fileName.split('.'); var extension = fileNamePieces[fileNamePieces.length - 1]; if(!contentType) { switch(extension.toLowerCase()) { case 'jpeg': case 'jpg': contentType = "image/jpeg"; break; case 'png': contentType = "image/png"; break; case 'gif': contentType = "image/gif"; break; default: contentType = null; break; } } if(!this.media[fileName]) { this.media[fileName] = { id: fileName, data: fileData, fileName: fileName, contentType: contentType, extension: extension }; } return this.media[fileName]; }, addWorksheet: function (worksheet) { this.relations.addRelation(worksheet, 'worksheet'); worksheet.setSharedStringCollection(this.sharedStrings); this.worksheets.push(worksheet); }, createContentTypes: function () { var doc = util.createXmlDoc(util.schemas.contentTypes, 'Types'); var types = doc.documentElement; var i, l; types.appendChild(util.createElement(doc, 'Default', [ ['Extension', "rels"], ['ContentType', "application/vnd.openxmlformats-package.relationships+xml"] ])); types.appendChild(util.createElement(doc, 'Default', [ ['Extension', "xml"], ['ContentType', "application/xml"] ])); var extensions = {}; for(var filename in this.media) { if(this.media.hasOwnProperty(filename)) { extensions[this.media[filename].extension] = this.media[filename].contentType; } } for(var extension in extensions) { if(extensions.hasOwnProperty(extension)) { types.appendChild(util.createElement(doc, 'Default', [ ['Extension', extension], ['ContentType', extensions[extension]] ])); } } types.appendChild(util.createElement(doc, 'Override', [ ['PartName', "/xl/workbook.xml"], ['ContentType', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"] ])); types.appendChild(util.createElement(doc, 'Override', [ ['PartName', "/xl/sharedStrings.xml"], ['ContentType', "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"] ])); types.appendChild(util.createElement(doc, 'Override', [ ['PartName', "/xl/styles.xml"], ['ContentType', "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"] ])); for(i = 0, l = this.worksheets.length; i < l; i++) { types.appendChild(util.createElement(doc, 'Override', [ ['PartName', "/xl/worksheets/sheet" + (i + 1) + ".xml"], ['ContentType', "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"] ])); } for(i = 0, l = this.tables.length; i < l; i++) { types.appendChild(util.createElement(doc, 'Override', [ ['PartName', "/xl/tables/table" + (i + 1) + ".xml"], ['ContentType', "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"] ])); } for(i = 0, l = this.drawings.length; i < l; i++) { types.appendChild(util.createElement(doc, 'Override', [ ['PartName', '/xl/drawings/drawing' + (i + 1) + '.xml'], ['ContentType', 'application/vnd.openxmlformats-officedocument.drawing+xml'] ])); } return doc; }, toXML: function () { var doc = util.createXmlDoc(util.schemas.spreadsheetml, 'workbook'); var wb = doc.documentElement; wb.setAttribute('xmlns:r', util.schemas.relationships); var maxWorksheetNameLength = 31; var sheets = util.createElement(doc, 'sheets'); for(var i = 0, l = this.worksheets.length; i < l; i++) { var sheet = doc.createElement('sheet'); // Microsoft Excel (2007, 2013) do not allow worksheet names longer than 31 characters // if the worksheet name is longer, Excel displays an "Excel found unreadable content..." popup when opening the file if(typeof console !== "undefined" && this.worksheets[i].name.length > maxWorksheetNameLength) { console.log('Microsoft Excel requires work sheet names to be less than ' + (maxWorksheetNameLength+1) + ' characters long, work sheet name "' + this.worksheets[i].name + '" is ' + this.worksheets[i].name.length + ' characters long'); } sheet.setAttribute('name', this.worksheets[i].name); sheet.setAttribute('sheetId', i + 1); sheet.setAttribute('r:id', this.relations.getRelationshipId(this.worksheets[i])); sheets.appendChild(sheet); } wb.appendChild(sheets); //now to add repeating rows var definedNames = util.createElement(doc, "definedNames"); var ctr = 0; for (var name in this.printTitles) { if (!this.printTitles.hasOwnProperty(name)) { continue; } var entry = this.printTitles[name]; var definedName = doc.createElement('definedName'); definedName.setAttribute("name", "_xlnm.Print_Titles"); definedName.setAttribute("localSheetId", ctr++); var value = ""; if (entry.top) { value += name + "!$1:$" + entry.top; if (entry.left) { value += "," } } if (entry.left) { value += name + "!$A:$" + entry.left; } definedName.appendChild(doc.createTextNode(value)); definedNames.appendChild(definedName); } wb.appendChild(definedNames); return doc; }, createWorkbookRelationship: function () { var doc = util.createXmlDoc(util.schemas.relationshipPackage, 'Relationships'); var relationships = doc.documentElement; relationships.appendChild(util.createElement(doc, 'Relationship', [ ['Id', 'rId1'], ['Type', util.schemas.officeDocument], ['Target', 'xl/workbook.xml'] ])); return doc; }, _generateCorePaths: function (files) { var i, l; Paths[this.styleSheet.id] = 'styles.xml'; Paths[this.sharedStrings.id] = 'sharedStrings.xml'; Paths[this.id] = '/xl/workbook.xml'; for(i = 0, l = this.tables.length; i < l; i++) { files['/xl/tables/table' + (i + 1) + '.xml'] = this.tables[i].toXML(); Paths[this.tables[i].id] = '/xl/tables/table' + (i + 1) + '.xml'; } for(var fileName in this.media) { if(this.media.hasOwnProperty(fileName)) { var media = this.media[fileName]; files['/xl/media/' + fileName] = media.data; Paths[fileName] = '/xl/media/' + fileName; } } for(i = 0, l = this.drawings.length; i < l; i++) { files['/xl/drawings/drawing' + (i + 1) + '.xml'] = this.drawings[i].toXML(); Paths[this.drawings[i].id] = '/xl/drawings/drawing' + (i + 1) + '.xml'; files['/xl/drawings/_rels/drawing' + (i + 1) + '.xml.rels'] = this.drawings[i].relations.toXML(); } }, _prepareFilesForPackaging: function (files) { _.extend(files, { '/[Content_Types].xml': this.createContentTypes(), '/_rels/.rels': this.createWorkbookRelationship(), '/xl/styles.xml': this.styleSheet.toXML(), '/xl/workbook.xml': this.toXML(), '/xl/sharedStrings.xml': this.sharedStrings.toXML(), '/xl/_rels/workbook.xml.rels': this.relations.toXML() }); _.each(files, function (value, key) { if(key.indexOf('.xml') !== -1 || key.indexOf('.rels') !== -1) { if (value instanceof XMLDOM){ files[key] = value.toString(); } else { files[key] = value.xml || new window.XMLSerializer().serializeToString(value); } var content = files[key].replace(/xmlns=""/g, ''); content = content.replace(/NS[\d]+:/g, ''); content = content.replace(/xmlns:NS[\d]+=""/g, ''); files[key] = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + "\n" + content; } }); }, generateFiles: function () { var files = {}; this._generateCorePaths(files); for(var i = 0, l = this.worksheets.length; i < l; i++) { files['/xl/worksheets/sheet' + (i + 1) + '.xml'] = this.worksheets[i].toXML(); Paths[this.worksheets[i].id] = 'worksheets/sheet' + (i + 1) + '.xml'; files['/xl/worksheets/_rels/sheet' + (i + 1) + '.xml.rels'] = this.worksheets[i].relations.toXML(); } this._prepareFilesForPackaging(files); return Q.resolve(files); } }); module.exports = Workbook;