exceljs
Version:
Excel Workbook Manager - Read and Write xlsx and csv Files.
423 lines (398 loc) • 12.4 kB
JavaScript
/**
* 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 _ = require('underscore');
var utils = require('../../../utils/utils');
var BaseXform = require('../base-xform');
var ColorXform = require('./color-xform');
// pattern fill: fgColor is pattern, gbColor is solid gb fill
//<fill>
// <patternFill patternType="solid">
// <fgColor theme="0" tint="-4.9989318521683403E-2"/>
// <bgColor indexed="64"/>
// </patternFill>
//</fill>
//
// angle gradient. degree=[0-180] 0=L->R, 1-359=rotate clockwise
// stop sequence specifies way points for the color to travel through
//<fill>
// <gradientFill degree="30">
// <stop position="0"><color theme="0"/></stop>
// <stop position="1"><color theme="4"/></stop>
// </gradientFill>
//</fill>
//
// "path" gradient. Outwards from a point (or rectangle) where left=right=x, top=bottom=y
//<fill>
// <gradientFill type="path" left="0.3" right="0.3" top="0.7" bottom="0.7">
// <stop position="0"><color rgb="FFFF0000"/></stop>
// <stop position="0.5"><color rgb="FF00FF00"/></stop>
// <stop position="1"><color rgb="FF0000FF"/></stop>
// </gradientFill>
//</fill>
// var modelSchema = {
// // fill will be one of these types
// type: ['pattern', 'gradient'],
//
// // ============================================================
// // pattern properties
//
// // pattern style, required
// pattern: ['solid', 'darkGray', 'mediumGray', 'lightGray', 'gray125', 'gray0625',
// 'darkHorizontal', 'darkVertical', 'darkDown', 'darkUp', 'darkGrid', 'darkTrellis',
// 'lightHorizontal', 'lightVertical', 'lightDown', 'lightUp', 'lightGrid', 'lightTrellis'],
//
// // foreground colour, default is auto (i.e. black)
// fgColor: new ColorXform('fgColor'),
//
// // background (solid fill) colour, default is auto (i.e. white)
// bgColor: new ColorXform('bgColor'),
//
// // ============================================================
// // gradient properties
// gradient: ['angle', 'path'],
// degree: [0,359], // for angle gradients 0=left to right, then clockwise from that
// center: { left:[0,1], top:[0,1] }, // for path gradients optional right, bottom for a square
// stops: [ // start at position 0 and end at position 1
// {position:0, color: {argb:''}},
// {position:(0,1), color: {argb:''}}, // intermediate steps optional
// {position:1, color: {argb:''}}
// ]
// };
var StopXform = function() {
this.map = {
color: new ColorXform()
};
};
utils.inherits(StopXform, BaseXform, {
get tag() { return 'stop'; },
render: function(xmlStream, model) {
xmlStream.openNode('stop');
xmlStream.addAttribute('position', model.position);
this.map.color.render(xmlStream, model.color);
xmlStream.closeNode();
},
parseOpen: function(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
} else {
switch (node.name) {
case 'stop':
this.model = {
position: parseFloat(node.attributes.position)
};
return true;
case 'color':
this.parser = this.map.color;
this.parser.parseOpen(node);
return true;
default:
return false;
}
}
},
parseText: function() {
},
parseClose: function(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.model.color = this.parser.model;
this.parser = undefined;
}
return true;
}
return false;
}
});
var PatternFillXform = function() {
this.map = {
fgColor: new ColorXform('fgColor'),
bgColor: new ColorXform('bgColor')
};
};
utils.inherits(PatternFillXform, BaseXform, {
get name() { return 'pattern'; },
get tag() { return 'patternFill'; },
render: function(xmlStream, model) {
xmlStream.openNode('patternFill');
xmlStream.addAttribute('patternType', model.pattern);
if (model.fgColor) {
this.map.fgColor.render(xmlStream, model.fgColor);
}
if (model.bgColor) {
this.map.bgColor.render(xmlStream, model.bgColor);
}
xmlStream.closeNode();
},
parseOpen: function(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
} else {
switch(node.name) {
case 'patternFill':
this.model = {
type: 'pattern',
pattern: node.attributes.patternType
};
return true;
default:
if ((this.parser = this.map[node.name])) {
this.parser.parseOpen(node);
return true;
} else {
return false;
}
}
}
},
parseText: function(text) {
if (this.parser) {
this.parser.parseText(text);
}
},
parseClose: function(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
if (this.parser.model) {
this.model[name] = this.parser.model;
}
this.parser = undefined;
}
return true;
} else {
return false;
}
}
});
var GradientFillXform = function() {
this.map = {
stop: new StopXform()
};
// if (model) {
// this.gradient = model.gradient;
// if (model.center) {
// this.center = model.center;
// }
// if (model.degree !== undefined) {
// this.degree = model.degree;
// }
// this.stops = model.stops.map(function(stop) { return new StopXform(stop); });
// } else {
// this.stops = [];
// }
};
utils.inherits(GradientFillXform, BaseXform, {
get name() { return 'gradient'; },
get tag() { return 'gradientFill'; },
render: function(xmlStream, model) {
xmlStream.openNode('gradientFill');
switch(model.gradient) {
case 'angle':
xmlStream.addAttribute('degree', model.degree);
break;
case 'path':
xmlStream.addAttribute('type', 'path');
if (model.center.left) {
xmlStream.addAttribute('left', model.center.left);
if (model.center.right === undefined) {
xmlStream.addAttribute('right', model.center.left);
}
}
if (model.center.right) {
xmlStream.addAttribute('right', model.center.right);
}
if (model.center.top) {
xmlStream.addAttribute('top', model.center.top);
if (model.center.bottom === undefined) {
xmlStream.addAttribute('bottom', model.center.top);
}
}
if (model.center.bottom) {
xmlStream.addAttribute('bottom', model.center.bottom);
}
break;
}
var stopXform = this.map.stop;
_.each(model.stops, function(stopModel) {
stopXform.render(xmlStream, stopModel);
});
xmlStream.closeNode();
},
parseOpen: function(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
} else {
switch (node.name) {
case 'gradientFill':
var model = this.model = {
stops: []
};
if (node.attributes.degree) {
model.gradient = 'angle';
model.degree = parseInt(node.attributes.degree);
} else if (node.attributes.type == 'path') {
model.gradient = 'path';
model.center = {
left: node.attributes.left ? parseFloat(node.attributes.left) : 0,
top: node.attributes.top ? parseFloat(node.attributes.top) : 0
};
if (node.attributes.right != node.attributes.left) {
model.center.right = node.attributes.right ? parseFloat(node.attributes.right) : 0;
}
if (node.attributes.bottom != node.attributes.top) {
model.center.bottom = node.attributes.bottom ? parseFloat(node.attributes.bottom) : 0;
}
}
return true;
case 'stop':
this.parser = this.map.stop;
this.parser.parseOpen(node);
return true;
break;
default:
return false;
}
}
},
parseText: function(text) {
if (this.parser) {
this.parser.parseText(text);
}
},
parseClose: function(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.model.stops.push(this.parser.model);
this.parser = undefined;
}
return true;
} else {
return false;
}
}
});
// Fill encapsulates translation from fill model to/from xlsx
var FillXform = module.exports = function() {
this.map = {
patternFill: new PatternFillXform(),
gradientFill: new GradientFillXform()
};
// if (model) {
// if (model.pattern) {
// this._model = new PatternFillXform(model);
// } else if (model.gradient) {
// this._model = new GradientFill(model);
// } else {
// throw new Error('Did not recognise this fill model: ' + JSON.stringify(model));
// }
// }
};
utils.inherits(FillXform, BaseXform, {
StopXform: StopXform,
PatternFillXform: PatternFillXform,
GradientFillXform: GradientFillXform
},{
get tag() { return 'fill'; },
render: function(xmlStream, model) {
xmlStream.addRollback();
xmlStream.openNode('fill');
switch(model.type) {
case 'pattern':
this.map.patternFill.render(xmlStream, model);
break;
case 'gradient':
this.map.gradientFill.render(xmlStream, model);
break;
default:
xmlStream.rollback();
return;
}
xmlStream.closeNode();
xmlStream.commit();
},
parseOpen: function(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
} else {
switch (node.name) {
case 'fill':
this.model = {};
return true;
default:
if ((this.parser = this.map[node.name])) {
this.parser.parseOpen(node);
return true;
} else {
return false;
}
}
}
},
parseText: function(text) {
if (this.parser) {
this.parser.parseText(text);
}
},
parseClose: function(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.model = this.parser.model;
this.model.type = this.parser.name;
this.parser = undefined;
}
return true;
} else {
return false;
}
},
validPatternValues: [
'none',
'solid',
'darkVertical',
'darkGray',
'mediumGray',
'lightGray',
'gray125',
'gray0625',
'darkHorizontal',
'darkVertical',
'darkDown',
'darkUp',
'darkGrid',
'darkTrellis',
'lightHorizontal',
'lightVertical',
'lightDown',
'lightUp',
'lightGrid',
'lightTrellis',
'lightGrid'
].reduce(function(p,v){p[v]=true; return p;}, {}),
validStyle: function(value) {
return this.validStyleValues[value];
}
});