excel-builder
Version:
An easy way of building Excel files with javascript
327 lines (289 loc) • 12.4 kB
JavaScript
"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;