UNPKG

exceljs

Version:

Excel Workbook Manager - Read and Write xlsx and csv Files.

321 lines (298 loc) 10.2 kB
/** * Copyright (c) 2015 Guyon Roche * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the 'Software'), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ 'use strict'; var fs = require('fs'); var Bluebird = require('bluebird'); var _ = require('underscore'); var Archiver = require('archiver'); var utils = require('../../utils/utils'); var StreamBuf = require('../../utils/stream-buf'); var RelType = require('../../xlsx/rel-type'); var StylesXform = require('../../xlsx/xform/style/styles-xform'); var SharedStrings = require('../../utils/shared-strings'); var DefinedNames = require('../../doc/defined-names'); var CoreXform = require('../../xlsx/xform/core/core-xform'); var RelationshipsXform = require('../../xlsx/xform/core/relationships-xform'); var ContentTypesXform = require('../../xlsx/xform/core/content-types-xform'); var AppXform = require('../../xlsx/xform/core/app-xform'); var WorkbookXform = require('../../xlsx/xform/book/workbook-xform'); var SharedStringsXform = require('../../xlsx/xform/strings/shared-strings-xform'); var WorksheetWriter = require('./worksheet-writer'); var WorkbookWriter = module.exports = function(options) { options = options || {}; //console.log(JSON.stringify(options, null, ' ')) this.created = options.created || new Date(); this.modified = options.modified || this.created; this.creator = options.creator || 'ExcelJS'; this.lastModifiedBy = options.lastModifiedBy || 'ExcelJS'; // using shared strings creates a smaller xlsx file but may use more memory this.useSharedStrings = options.useSharedStrings || false; this.sharedStrings = new SharedStrings(); // style manager this.styles = options.useStyles ? new StylesXform(true) : new StylesXform.Mock(true); // defined names this._definedNames = new DefinedNames(); this._worksheets = []; this.views = []; this.zip = Archiver('zip'); if (options.stream) { this.stream = options.stream; } else if (options.filename) { this.stream = fs.createWriteStream(options.filename); } else { this.stream = new StreamBuf(); } this.zip.pipe(this.stream); // these bits can be added right now this.promise = Bluebird.all([ this.addThemes(), this.addOfficeRels() ]); }; WorkbookWriter.prototype = { get definedNames() { return this._definedNames; }, _openStream: function(path) { var self = this; var stream = new StreamBuf({bufSize: 65536, batch: true}); self.zip.append(stream, { name: path }); stream.on('end', function() { stream.emit('zipped'); }); return stream; }, _commitWorksheets: function() { var commitWorksheet = function(worksheet) { if (!worksheet.committed) { return new Bluebird(function(resolve) { worksheet.stream.on('zipped', function() { resolve(); }); worksheet.commit(); }); } else { return Bluebird.resolve(); } }; // if there are any uncommitted worksheets, commit them now and wait var promises = this._worksheets.map(commitWorksheet); if (promises.length) { return Bluebird.all(promises); } else { return Bluebird.resolve(); } }, commit: function() { var self = this; // commit all worksheets, then add suplimentary files return this.promise.then(function() { return self._commitWorksheets(); }) .then(function() { return Bluebird.all([ self.addContentTypes(), self.addApp(), self.addCore(), self.addSharedStrings(), self.addStyles(), self.addWorkbookRels() ]); }) .then(function() { return self.addWorkbook(); }) .then(function(){ return self._finalize(); }); }, get nextId() { // find the next unique spot to add worksheet var i; for (i = 1; i < this._worksheets.length; i++) { if (!this._worksheets[i]) { return i; } } return this._worksheets.length || 1; }, addWorksheet: function(name, options) { // it's possible to add a worksheet with different than default // shared string handling // in fact, it's even possible to switch it mid-sheet options = options || {}; var useSharedStrings = options.useSharedStrings !== undefined ? options.useSharedStrings : this.useSharedStrings; var id = this.nextId; name = name || 'sheet' + id; var worksheet = new WorksheetWriter({ id: id, name: name, workbook: this, useSharedStrings: useSharedStrings, properties: options.properties, pageSetup: options.pageSetup }); this._worksheets[id] = worksheet; return worksheet; }, getWorksheet: function(id) { if (id === undefined) { return _.find(this._worksheets, function() { return true; }); } else if (typeof(id) === 'number') { return this._worksheets[id]; } else if (typeof id === 'string') { return _.find(this._worksheets, function(worksheet) { return worksheet.name == id; }); } else { return undefined; } }, addStyles: function() { var self = this; return new Bluebird(function(resolve) { self.zip.append(self.styles.xml, {name: 'xl/styles.xml'}); resolve(); }); }, addThemes: function() { var self = this; return utils.readModuleFile(require.resolve('../../xlsx/xml/theme1.xml')) .then(function(data){ self.zip.append(data, { name: 'xl/theme/theme1.xml' }); }); }, addOfficeRels: function() { var self = this; return new Bluebird(function(resolve) { var xform = new RelationshipsXform(); var xml = xform.toXml([ {rId: 'rId1', type: RelType.OfficeDocument, target: 'xl/workbook.xml'} ]); self.zip.append(xml, {name: '/_rels/.rels'}); resolve(); }); }, addContentTypes: function() { var self = this; return new Bluebird(function(resolve) { var model = { worksheets: self._worksheets }; var xform = new ContentTypesXform(); var xml = xform.toXml(model); self.zip.append(xml, {name: '[Content_Types].xml'}); resolve(); }); }, addApp: function() { var self = this; return new Bluebird(function(resolve) { var model = { worksheets: self._worksheets }; var xform = new AppXform(); var xml = xform.toXml(model); self.zip.append(xml, {name: 'docProps/app.xml'}); resolve(); }); }, addCore: function() { var self = this; return new Bluebird(function(resolve) { var coreXform = new CoreXform(); var xml = coreXform.toXml(self); self.zip.append(xml, {name: 'docProps/core.xml'}); resolve(); }); }, addSharedStrings: function() { var self = this; if (this.sharedStrings.count) { return new Bluebird(function(resolve) { var sharedStringsXform = new SharedStringsXform(); var xml = sharedStringsXform.toXml(self.sharedStrings); self.zip.append(xml, {name: '/xl/sharedStrings.xml'}); resolve(); }); } else { return Bluebird.resolve(); } }, addWorkbookRels: function() { var self = this; var count = 1; var relationships = [ {rId: 'rId' + (count++), type: RelType.Styles, target: 'styles.xml'}, {rId: 'rId' + (count++), type: RelType.Theme, target: 'theme/theme1.xml'} ]; if (this.sharedStrings.count) { relationships.push( {rId: 'rId' + (count++), type: RelType.SharedStrings, target: 'sharedStrings.xml'} ); } _.each(this._worksheets, function (worksheet) { worksheet.rId = 'rId' + (count++); relationships.push( {rId: worksheet.rId, type: RelType.Worksheet, target: 'worksheets/sheet' + worksheet.id + '.xml'} ); }); return new Bluebird(function(resolve) { var xform = new RelationshipsXform(); var xml = xform.toXml(relationships); self.zip.append(xml, {name: '/xl/_rels/workbook.xml.rels'}); resolve(); }); }, addWorkbook: function() { var zip = this.zip; var model = { worksheets: this._worksheets, definedNames: this._definedNames.model, views: this.views }; return new Bluebird(function(resolve) { var xform = new WorkbookXform(); xform.prepare(model); zip.append(xform.toXml(model), {name: '/xl/workbook.xml'}); resolve(); }); }, _finalize: function() { var self = this; return new Bluebird(function(resolve, reject) { self.stream.on('error', function(error){ reject(error); }); self.stream.on('finish', function(){ resolve(self); }); self.zip.on('error', function(error){ reject(error); }); self.zip.finalize(); }); } };