excel-builder
Version:
An easy way of building Excel files with javascript
694 lines (645 loc) • 23.8 kB
JavaScript
/**
* @module Excel/StyleSheet
*/
"use strict";
var _ = require('lodash');
var util = require('./util');
var StyleSheet = function () {
this.id = _.uniqueId('StyleSheet');
this.cellStyles = [{
name:"Normal",
xfId:"0",
builtinId:"0"
}];
this.defaultTableStyle = false;
this.differentialStyles = [{}];
this.masterCellFormats = [{
numFmtId: 0,
fontId: 0,
fillId: 0,
borderId: 0,
xfid: 0
}];
this.masterCellStyles = [{
numFmtId: 0,
fontId: 0,
fillId: 0,
borderId: 0
}];
this.fonts = [{}];
this.numberFormatters = [];
this.fills = [{}, {
type: 'pattern',
patternType: 'gray125',
fgColor: 'FF333333',
bgColor: 'FF333333'
}];
this.borders = [{
top: {},
left: {},
right: {},
bottom: {},
diagonal: {}
}];
this.tableStyles = [];
};
_.extend(StyleSheet.prototype, {
createSimpleFormatter: function (type) {
var sid = this.masterCellFormats.length;
var style = {
id: sid
};
switch(type) {
case 'date':
style.numFmtId = 14;
break;
}
this.masterCellFormats.push(style);
return style;
},
createFill: function (fillInstructions) {
var id = this.fills.length;
var fill = fillInstructions;
fill.id = id;
this.fills.push(fill);
return fill;
},
createNumberFormatter: function (formatInstructions) {
var id = this.numberFormatters.length + 100;
var format = {
id: id,
formatCode: formatInstructions
};
this.numberFormatters.push(format);
return format;
},
/**
* alignment: {
* horizontal: http://www.schemacentral.com/sc/ooxml/t-ssml_ST_HorizontalAlignment.html
* vertical: http://www.schemacentral.com/sc/ooxml/t-ssml_ST_VerticalAlignment.html
* @param {Object} styleInstructions
*/
createFormat: function (styleInstructions) {
var sid = this.masterCellFormats.length;
var style = {
id: sid
};
if (styleInstructions.protection) {
style.protection = styleInstructions.protection;
}
if(styleInstructions.font && _.isObject(styleInstructions.font)) {
style.fontId = this.createFontStyle(styleInstructions.font).id;
} else if(styleInstructions.font) {
if(_.isNaN(parseInt(styleInstructions.font, 10))) {
throw "Passing a non-numeric font id is not supported";
}
style.fontId = styleInstructions.font;
}
if (styleInstructions.format && _.isString(styleInstructions.format)) {
style.numFmtId = this.createNumberFormatter(styleInstructions.format).id;
} else if(styleInstructions.format) {
if(_.isNaN(parseInt(styleInstructions.format, 10))) {
throw "Invalid number formatter id";
}
style.numFmtId = styleInstructions.format;
}
if (styleInstructions.border && _.isObject(styleInstructions.border)) {
style.borderId = this.createBorderFormatter(styleInstructions.border).id;
} else if (styleInstructions.border) {
if(_.isNaN(parseInt(styleInstructions.border, 10))) {
throw "Passing a non-numeric border id is not supported";
}
style.borderId = styleInstructions.border;
}
if (styleInstructions.fill && _.isObject(styleInstructions.fill)) {
style.fillId = this.createFill(styleInstructions.fill).id;
} else if (styleInstructions.fill) {
if(_.isNaN(parseInt(styleInstructions.fill, 10))) {
throw "Passing a non-numeric fill id is not supported";
}
style.fillId = styleInstructions.fill;
}
if (styleInstructions.alignment && _.isObject(styleInstructions.alignment)) {
style.alignment = _.pick(
styleInstructions.alignment,
'horizontal',
'justifyLastLine',
'readingOrder',
'relativeIndent',
'shrinkToFit',
'textRotation',
'vertical',
'wrapText'
);
}
this.masterCellFormats.push(style);
return style;
},
createDifferentialStyle: function (styleInstructions) {
var id = this.differentialStyles.length;
var style = {
id: id
};
if(styleInstructions.font && _.isObject(styleInstructions.font)) {
style.font = styleInstructions.font;
}
if (styleInstructions.border && _.isObject(styleInstructions.border)) {
style.border = _.defaults(styleInstructions.border, {
top: {},
left: {},
right: {},
bottom: {},
diagonal: {}
});
}
if (styleInstructions.fill && _.isObject(styleInstructions.fill)) {
style.fill = styleInstructions.fill;
}
if (styleInstructions.alignment && _.isObject(styleInstructions.alignment)) {
style.alignment = styleInstructions.alignment;
}
if (styleInstructions.format && _.isString(styleInstructions.format)) {
style.numFmt = styleInstructions.format;
}
this.differentialStyles[id] = style;
return style;
},
/**
* Should be an object containing keys that match with one of the keys from this list:
* http://www.schemacentral.com/sc/ooxml/t-ssml_ST_TableStyleType.html
*
* The value should be a reference to a differential format (dxf)
* @param {Object} instructions
*/
createTableStyle: function (instructions) {
this.tableStyles.push(instructions);
},
/**
* All params optional
* Expects: {
* top: {},
* left: {},
* right: {},
* bottom: {},
* diagonal: {},
* outline: boolean,
* diagonalUp: boolean,
* diagonalDown: boolean
* }
* Each border should follow:
* {
* style: styleString, http://www.schemacentral.com/sc/ooxml/t-ssml_ST_BorderStyle.html
* color: ARBG color (requires the A, so for example FF006666)
* }
* @param {Object} border
*/
createBorderFormatter: function (border) {
_.defaults(border, {
top: {},
left: {},
right: {},
bottom: {},
diagonal: {},
id: this.borders.length
});
this.borders.push(border);
return border;
},
/**
* Supported font styles:
* bold
* italic
* underline (single, double, singleAccounting, doubleAccounting)
* size
* color
* fontName
* strike (strikethrough)
* outline (does this actually do anything?)
* shadow (does this actually do anything?)
* superscript
* subscript
*
* Color is a future goal - at the moment it's looking a bit complicated
* @param {Object} instructions
*/
createFontStyle: function (instructions) {
var fontId = this.fonts.length;
var fontStyle = {
id: fontId
};
if(instructions.bold) {
fontStyle.bold = true;
}
if(instructions.italic) {
fontStyle.italic = true;
}
if(instructions.superscript) {
fontStyle.vertAlign = 'superscript';
}
if(instructions.subscript) {
fontStyle.vertAlign = 'subscript';
}
if(instructions.underline) {
if(_.indexOf([
'double',
'singleAccounting',
'doubleAccounting'
], instructions.underline) !== -1) {
fontStyle.underline = instructions.underline;
} else {
fontStyle.underline = true;
}
}
if(instructions.strike) {
fontStyle.strike = true;
}
if(instructions.outline) {
fontStyle.outline = true;
}
if(instructions.shadow) {
fontStyle.shadow = true;
}
if(instructions.size) {
fontStyle.size = instructions.size;
}
if(instructions.color) {
fontStyle.color = instructions.color;
}
if(instructions.fontName) {
fontStyle.fontName = instructions.fontName;
}
this.fonts.push(fontStyle);
return fontStyle;
},
exportBorders: function (doc) {
var borders = doc.createElement('borders');
borders.setAttribute('count', this.borders.length);
for(var i = 0, l = this.borders.length; i < l; i++) {
borders.appendChild(this.exportBorder(doc, this.borders[i]));
}
return borders;
},
exportBorder: function (doc, data) {
var border = doc.createElement('border');
var self = this;
var borderGenerator = function (name) {
var b = doc.createElement(name);
if(data[name].style) {
b.setAttribute('style', data[name].style);
}
if(data[name].color) {
b.appendChild(self.exportColor(doc, data[name].color));
}
return b;
};
border.appendChild(borderGenerator('left'));
border.appendChild(borderGenerator('right'));
border.appendChild(borderGenerator('top'));
border.appendChild(borderGenerator('bottom'));
border.appendChild(borderGenerator('diagonal'));
return border;
},
exportColor: function (doc, color) {
var colorEl = doc.createElement('color');
if(_.isString(color)) {
colorEl.setAttribute('rgb', color);
return colorEl;
}
if (!_.isUndefined(color.tint)) {
colorEl.setAttribute('tint', color.tint);
}
if (!_.isUndefined(color.auto)) {
colorEl.setAttribute('auto', !!color.auto);
}
if (!_.isUndefined(color.theme)) {
colorEl.setAttribute('theme', color.theme);
}
return colorEl;
},
exportMasterCellFormats: function (doc) {
var cellFormats = util.createElement(doc, 'cellXfs', [
['count', this.masterCellFormats.length]
]);
for(var i = 0, l = this.masterCellFormats.length; i < l; i++) {
var mformat = this.masterCellFormats[i];
cellFormats.appendChild(this.exportCellFormatElement(doc, mformat));
}
return cellFormats;
},
exportMasterCellStyles: function (doc) {
var records = util.createElement(doc, 'cellStyleXfs', [
['count', this.masterCellStyles.length]
]);
for(var i = 0, l = this.masterCellStyles.length; i < l; i++) {
var mstyle = this.masterCellStyles[i];
records.appendChild(this.exportCellFormatElement(doc, mstyle));
}
return records;
},
exportCellFormatElement: function (doc, styleInstructions) {
var xf = doc.createElement('xf');
var allowed = ['applyAlignment', 'applyBorder', 'applyFill', 'applyFont', 'applyNumberFormat',
'applyProtection', 'borderId', 'fillId', 'fontId', 'numFmtId', 'pivotButton', 'quotePrefix', 'xfId'];
var attributes = _.filter(_.keys(styleInstructions), function (key) {
if(_.indexOf(allowed, key) !== -1) {
return true;
}
});
if(styleInstructions.alignment) {
var alignmentData = styleInstructions.alignment;
xf.appendChild(this.exportAlignment(doc, alignmentData));
}
if (styleInstructions.protection) {
xf.appendChild(this.exportProtection(doc, styleInstructions.protection));
xf.setAttribute('applyProtection', '1');
}
var a = attributes.length;
while(a--) {
xf.setAttribute(attributes[a], styleInstructions[attributes[a]]);
}
if (styleInstructions.fillId) {
xf.setAttribute('applyFill', '1');
}
if (styleInstructions.fontId) {
xf.setAttribute('applyFont', '1');
}
if (styleInstructions.borderId) {
xf.setAttribute('applyBorder', '1');
}
if (styleInstructions.alignment) {
xf.setAttribute('applyAlignment', '1');
}
if (styleInstructions.numFmtId) {
xf.setAttribute('applyNumberFormat', '1');
}
if((styleInstructions.numFmtId !== undefined) && (styleInstructions.xfId === undefined)) {
xf.setAttribute('xfId', '0');
}
return xf;
},
exportAlignment: function (doc, alignmentData) {
var alignment = doc.createElement('alignment');
var keys = _.keys(alignmentData);
for(var i = 0, l = keys.length; i < l; i++) {
alignment.setAttribute(keys[i], alignmentData[keys[i]]);
}
return alignment;
},
exportFonts: function (doc) {
var fonts = doc.createElement('fonts');
fonts.setAttribute('count', this.fonts.length);
for(var i = 0, l = this.fonts.length; i < l; i++) {
var fd = this.fonts[i];
fonts.appendChild(this.exportFont(doc, fd));
}
return fonts;
},
exportFont: function (doc, fd) {
var font = doc.createElement('font');
if(fd.size) {
var size = doc.createElement('sz');
size.setAttribute('val', fd.size);
font.appendChild(size);
}
if(fd.fontName) {
var fontName = doc.createElement('name');
fontName.setAttribute('val', fd.fontName);
font.appendChild(fontName);
}
if(fd.bold) {
font.appendChild(doc.createElement('b'));
}
if(fd.italic) {
font.appendChild(doc.createElement('i'));
}
if(fd.vertAlign) {
var vertAlign = doc.createElement('vertAlign');
vertAlign.setAttribute('val', fd.vertAlign);
font.appendChild(vertAlign);
}
if(fd.underline) {
var u = doc.createElement('u');
if(fd.underline !== true) {
u.setAttribute('val', fd.underline);
}
font.appendChild(u);
}
if(fd.strike) {
font.appendChild(doc.createElement('strike'));
}
if(fd.shadow) {
font.appendChild(doc.createElement('shadow'));
}
if(fd.outline) {
font.appendChild(doc.createElement('outline'));
}
if(fd.color) {
font.appendChild(this.exportColor(doc, fd.color));
}
return font;
},
exportFills: function (doc) {
var fills = doc.createElement('fills');
fills.setAttribute('count', this.fills.length);
for(var i = 0, l = this.fills.length; i < l; i++) {
var fd = this.fills[i];
fills.appendChild(this.exportFill(doc, fd));
}
return fills;
},
exportFill: function (doc, fd) {
var fillDef;
var fill = doc.createElement('fill');
if (fd.type === 'pattern') {
fillDef = this.exportPatternFill(doc, fd);
fill.appendChild(fillDef);
} else if (fd.type === 'gradient') {
fillDef = this.exportGradientFill(doc, fd);
fill.appendChild(fillDef);
}
return fill;
},
exportGradientFill: function (doc, data) {
var fillDef = doc.createElement('gradientFill');
if(data.degree) {
fillDef.setAttribute('degree', data.degree);
} else if (data.left) {
fillDef.setAttribute('left', data.left);
fillDef.setAttribute('right', data.right);
fillDef.setAttribute('top', data.top);
fillDef.setAttribute('bottom', data.bottom);
}
var start = doc.createElement('stop');
start.setAttribute('position', data.start.pureAt || 0);
var startColor = doc.createElement('color');
if (typeof data.start === 'string' || data.start.color) {
startColor.setAttribute('rgb', data.start.color || data.start);
} else if (typeof data.start.theme) {
startColor.setAttribute('theme', data.start.theme);
}
var end = doc.createElement('stop');
var endColor = doc.createElement('color');
end.setAttribute('position', data.end.pureAt || 1);
if (typeof data.start === 'string' || data.end.color) {
endColor.setAttribute('rgb', data.end.color || data.end);
} else if (typeof data.end.theme) {
endColor.setAttribute('theme', data.end.theme);
}
start.appendChild(startColor);
end.appendChild(endColor);
fillDef.appendChild(start);
fillDef.appendChild(end);
return fillDef;
},
/**
* Pattern types: http://www.schemacentral.com/sc/ooxml/t-ssml_ST_PatternType.html
* @param {XMLDoc} doc
* @param {Object} data
*/
exportPatternFill: function (doc, data) {
var fillDef = util.createElement(doc, 'patternFill', [
['patternType', data.patternType]
]);
if(!data.bgColor) {
data.bgColor = 'FFFFFFFF';
}
if(!data.fgColor) {
data.fgColor = 'FFFFFFFF';
}
var bgColor = doc.createElement('bgColor');
if(_.isString(data.bgColor)) {
bgColor.setAttribute('rgb', data.bgColor);
} else {
if(data.bgColor.theme) {
bgColor.setAttribute('theme', data.bgColor.theme);
} else {
bgColor.setAttribute('rgb', data.bgColor.rbg);
}
}
var fgColor = doc.createElement('fgColor');
if(_.isString(data.fgColor)) {
fgColor.setAttribute('rgb', data.fgColor);
} else {
if(data.fgColor.theme) {
fgColor.setAttribute('theme', data.fgColor.theme);
} else {
fgColor.setAttribute('rgb', data.fgColor.rbg);
}
}
fillDef.appendChild(fgColor);
fillDef.appendChild(bgColor);
return fillDef;
},
exportNumberFormatters: function (doc) {
var formatters = doc.createElement('numFmts');
formatters.setAttribute('count', this.numberFormatters.length);
for(var i = 0, l = this.numberFormatters.length; i < l; i++) {
var fd = this.numberFormatters[i];
formatters.appendChild(this.exportNumberFormatter(doc, fd));
}
return formatters;
},
exportNumberFormatter: function (doc, fd) {
var numFmt = doc.createElement('numFmt');
numFmt.setAttribute('numFmtId', fd.id);
numFmt.setAttribute('formatCode', fd.formatCode);
return numFmt;
},
exportCellStyles: function (doc) {
var cellStyles = doc.createElement('cellStyles');
cellStyles.setAttribute('count', this.cellStyles.length);
for(var i = 0, l = this.cellStyles.length; i < l; i++) {
var style = this.cellStyles[i];
delete style.id; //Remove internal id
var record = util.createElement(doc, 'cellStyle');
cellStyles.appendChild(record);
var attributes = _.keys(style);
var a = attributes.length;
while(a--) {
record.setAttribute(attributes[a], style[attributes[a]]);
}
}
return cellStyles;
},
exportDifferentialStyles: function (doc) {
var dxfs = doc.createElement('dxfs');
dxfs.setAttribute('count', this.differentialStyles.length);
for(var i = 0, l = this.differentialStyles.length; i < l; i++) {
var style = this.differentialStyles[i];
dxfs.appendChild(this.exportDFX(doc, style));
}
return dxfs;
},
exportDFX: function (doc, style) {
var dxf = doc.createElement('dxf');
if(style.font) {
dxf.appendChild(this.exportFont(doc, style.font));
}
if(style.fill) {
dxf.appendChild(this.exportFill(doc, style.fill));
}
if(style.border) {
dxf.appendChild(this.exportBorder(doc, style.border));
}
if(style.numFmt) {
dxf.appendChild(this.exportNumberFormatter(doc, style.numFmt));
}
if(style.alignment) {
dxf.appendChild(this.exportAlignment(doc, style.alignment));
}
return dxf;
},
exportTableStyles: function (doc) {
var tableStyles = doc.createElement('tableStyles');
tableStyles.setAttribute('count', this.tableStyles.length);
if(this.defaultTableStyle) {
tableStyles.setAttribute('defaultTableStyle', this.defaultTableStyle);
}
for(var i = 0, l = this.tableStyles.length; i < l; i++) {
tableStyles.appendChild(this.exportTableStyle(doc, this.tableStyles[i]));
}
return tableStyles;
},
exportTableStyle: function (doc, style) {
var tableStyle = doc.createElement('tableStyle');
tableStyle.setAttribute('name', style.name);
tableStyle.setAttribute('pivot', 0);
var i = 0;
_.each(style, function (value, key) {
if(key === 'name') {return;}
i++;
var styleEl = doc.createElement('tableStyleElement');
styleEl.setAttribute('type', key);
styleEl.setAttribute('dxfId', value);
tableStyle.appendChild(styleEl);
});
tableStyle.setAttribute('count', i);
return tableStyle;
},
exportProtection: function (doc, protectionData) {
var node = doc.createElement('protection');
for (var k in protectionData) {
if(protectionData.hasOwnProperty(k)) {
node.setAttribute(k, protectionData[k]);
}
}
return node;
},
toXML: function () {
var doc = util.createXmlDoc(util.schemas.spreadsheetml, 'styleSheet');
var styleSheet = doc.documentElement;
styleSheet.appendChild(this.exportNumberFormatters(doc));
styleSheet.appendChild(this.exportFonts(doc));
styleSheet.appendChild(this.exportFills(doc));
styleSheet.appendChild(this.exportBorders(doc));
styleSheet.appendChild(this.exportMasterCellStyles(doc));
styleSheet.appendChild(this.exportMasterCellFormats(doc));
styleSheet.appendChild(this.exportCellStyles(doc));
styleSheet.appendChild(this.exportDifferentialStyles(doc));
if(this.tableStyles.length) {
styleSheet.appendChild(this.exportTableStyles(doc));
}
return doc;
}
});
module.exports = StyleSheet;