first commit
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s

This commit is contained in:
John
2026-09-23 17:31:02 +05:30
commit 69b4e68baf
51657 changed files with 3864077 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
const Enums = require('../../../doc/enums');
const utils = require('../../../utils/utils');
const BaseXform = require('../base-xform');
const validation = {
horizontalValues: [
'left',
'center',
'right',
'fill',
'centerContinuous',
'distributed',
'justify',
].reduce((p, v) => {
p[v] = true;
return p;
}, {}),
horizontal(value) {
return this.horizontalValues[value] ? value : undefined;
},
verticalValues: ['top', 'middle', 'bottom', 'distributed', 'justify'].reduce((p, v) => {
p[v] = true;
return p;
}, {}),
vertical(value) {
if (value === 'middle') return 'center';
return this.verticalValues[value] ? value : undefined;
},
wrapText(value) {
return value ? true : undefined;
},
shrinkToFit(value) {
return value ? true : undefined;
},
textRotation(value) {
switch (value) {
case 'vertical':
return value;
default:
value = utils.validInt(value);
return value >= -90 && value <= 90 ? value : undefined;
}
},
indent(value) {
value = utils.validInt(value);
return Math.max(0, value);
},
readingOrder(value) {
switch (value) {
case 'ltr':
return Enums.ReadingOrder.LeftToRight;
case 'rtl':
return Enums.ReadingOrder.RightToLeft;
default:
return undefined;
}
},
};
const textRotationXform = {
toXml(textRotation) {
textRotation = validation.textRotation(textRotation);
if (textRotation) {
if (textRotation === 'vertical') {
return 255;
}
const tr = Math.round(textRotation);
if (tr >= 0 && tr <= 90) {
return tr;
}
if (tr < 0 && tr >= -90) {
return 90 - tr;
}
}
return undefined;
},
toModel(textRotation) {
const tr = utils.validInt(textRotation);
if (tr !== undefined) {
if (tr === 255) {
return 'vertical';
}
if (tr >= 0 && tr <= 90) {
return tr;
}
if (tr > 90 && tr <= 180) {
return 90 - tr;
}
}
return undefined;
},
};
// Alignment encapsulates translation from style.alignment model to/from xlsx
class AlignmentXform extends BaseXform {
get tag() {
return 'alignment';
}
render(xmlStream, model) {
xmlStream.addRollback();
xmlStream.openNode('alignment');
let isValid = false;
function add(name, value) {
if (value) {
xmlStream.addAttribute(name, value);
isValid = true;
}
}
add('horizontal', validation.horizontal(model.horizontal));
add('vertical', validation.vertical(model.vertical));
add('wrapText', validation.wrapText(model.wrapText) ? '1' : false);
add('shrinkToFit', validation.shrinkToFit(model.shrinkToFit) ? '1' : false);
add('indent', validation.indent(model.indent));
add('textRotation', textRotationXform.toXml(model.textRotation));
add('readingOrder', validation.readingOrder(model.readingOrder));
xmlStream.closeNode();
if (isValid) {
xmlStream.commit();
} else {
xmlStream.rollback();
}
}
parseOpen(node) {
const model = {};
let valid = false;
function add(truthy, name, value) {
if (truthy) {
model[name] = value;
valid = true;
}
}
add(node.attributes.horizontal, 'horizontal', node.attributes.horizontal);
add(
node.attributes.vertical,
'vertical',
node.attributes.vertical === 'center' ? 'middle' : node.attributes.vertical
);
add(node.attributes.wrapText, 'wrapText', utils.parseBoolean(node.attributes.wrapText));
add(node.attributes.shrinkToFit, 'shrinkToFit', utils.parseBoolean(node.attributes.shrinkToFit));
add(node.attributes.indent, 'indent', parseInt(node.attributes.indent, 10));
add(
node.attributes.textRotation,
'textRotation',
textRotationXform.toModel(node.attributes.textRotation)
);
add(
node.attributes.readingOrder,
'readingOrder',
node.attributes.readingOrder === '2' ? 'rtl' : 'ltr'
);
this.model = valid ? model : null;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = AlignmentXform;
+207
View File
@@ -0,0 +1,207 @@
/* eslint-disable max-classes-per-file */
const BaseXform = require('../base-xform');
const utils = require('../../../utils/utils');
const ColorXform = require('./color-xform');
class EdgeXform extends BaseXform {
constructor(name) {
super();
this.name = name;
this.map = {
color: new ColorXform(),
};
}
get tag() {
return this.name;
}
render(xmlStream, model, defaultColor) {
const color = (model && model.color) || defaultColor || this.defaultColor;
xmlStream.openNode(this.name);
if (model && model.style) {
xmlStream.addAttribute('style', model.style);
if (color) {
this.map.color.render(xmlStream, color);
}
}
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case this.name: {
const {style} = node.attributes;
if (style) {
this.model = {
style,
};
} else {
this.model = undefined;
}
return true;
}
case 'color':
this.parser = this.map.color;
this.parser.parseOpen(node);
return true;
default:
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.parser = undefined;
}
return true;
}
if (name === this.name) {
if (this.map.color.model) {
if (!this.model) {
this.model = {};
}
this.model.color = this.map.color.model;
}
}
return false;
}
validStyle(value) {
return EdgeXform.validStyleValues[value];
}
}
EdgeXform.validStyleValues = [
'thin',
'dashed',
'dotted',
'dashDot',
'hair',
'dashDotDot',
'slantDashDot',
'mediumDashed',
'mediumDashDotDot',
'mediumDashDot',
'medium',
'double',
'thick',
].reduce((p, v) => {
p[v] = true;
return p;
}, {});
// Border encapsulates translation from border model to/from xlsx
class BorderXform extends BaseXform {
constructor() {
super();
this.map = {
top: new EdgeXform('top'),
left: new EdgeXform('left'),
bottom: new EdgeXform('bottom'),
right: new EdgeXform('right'),
diagonal: new EdgeXform('diagonal'),
};
}
render(xmlStream, model) {
const {color} = model;
xmlStream.openNode('border');
if (model.diagonal && model.diagonal.style) {
if (model.diagonal.up) {
xmlStream.addAttribute('diagonalUp', '1');
}
if (model.diagonal.down) {
xmlStream.addAttribute('diagonalDown', '1');
}
}
function add(edgeModel, edgeXform) {
if (edgeModel && !edgeModel.color && model.color) {
// don't mess with incoming models
edgeModel = {
...edgeModel,
color: model.color,
};
}
edgeXform.render(xmlStream, edgeModel, color);
}
add(model.left, this.map.left);
add(model.right, this.map.right);
add(model.top, this.map.top);
add(model.bottom, this.map.bottom);
add(model.diagonal, this.map.diagonal);
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case 'border':
this.reset();
this.diagonalUp = utils.parseBoolean(node.attributes.diagonalUp);
this.diagonalDown = utils.parseBoolean(node.attributes.diagonalDown);
return true;
default:
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.parser = undefined;
}
return true;
}
if (name === 'border') {
const model = (this.model = {});
const add = function(key, edgeModel, extensions) {
if (edgeModel) {
if (extensions) {
Object.assign(edgeModel, extensions);
}
model[key] = edgeModel;
}
};
add('left', this.map.left.model);
add('right', this.map.right.model);
add('top', this.map.top.model);
add('bottom', this.map.bottom.model);
add('diagonal', this.map.diagonal.model, {up: this.diagonalUp, down: this.diagonalDown});
}
return false;
}
}
module.exports = BorderXform;
+63
View File
@@ -0,0 +1,63 @@
const BaseXform = require('../base-xform');
// Color encapsulates translation from color model to/from xlsx
class ColorXform extends BaseXform {
constructor(name) {
super();
// this.name controls the xm node name
this.name = name || 'color';
}
get tag() {
return this.name;
}
render(xmlStream, model) {
if (model) {
xmlStream.openNode(this.name);
if (model.argb) {
xmlStream.addAttribute('rgb', model.argb);
} else if (model.theme !== undefined) {
xmlStream.addAttribute('theme', model.theme);
if (model.tint !== undefined) {
xmlStream.addAttribute('tint', model.tint);
}
} else if (model.indexed !== undefined) {
xmlStream.addAttribute('indexed', model.indexed);
} else {
xmlStream.addAttribute('auto', '1');
}
xmlStream.closeNode();
return true;
}
return false;
}
parseOpen(node) {
if (node.name === this.name) {
if (node.attributes.rgb) {
this.model = {argb: node.attributes.rgb};
} else if (node.attributes.theme) {
this.model = {theme: parseInt(node.attributes.theme, 10)};
if (node.attributes.tint) {
this.model.tint = parseFloat(node.attributes.tint);
}
} else if (node.attributes.indexed) {
this.model = {indexed: parseInt(node.attributes.indexed, 10)};
} else {
this.model = undefined;
}
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = ColorXform;
+111
View File
@@ -0,0 +1,111 @@
const BaseXform = require('../base-xform');
const AlignmentXform = require('./alignment-xform');
const BorderXform = require('./border-xform');
const FillXform = require('./fill-xform');
const FontXform = require('./font-xform');
const NumFmtXform = require('./numfmt-xform');
const ProtectionXform = require('./protection-xform');
// <xf numFmtId="[numFmtId]" fontId="[fontId]" fillId="[fillId]" borderId="[xf.borderId]" xfId="[xfId]">
// Optional <alignment>
// Optional <protection>
// </xf>
// Style assists translation from style model to/from xlsx
class DxfXform extends BaseXform {
constructor() {
super();
this.map = {
alignment: new AlignmentXform(),
border: new BorderXform(),
fill: new FillXform(),
font: new FontXform(),
numFmt: new NumFmtXform(),
protection: new ProtectionXform(),
};
}
get tag() {
return 'dxf';
}
// how do we generate dxfid?
render(xmlStream, model) {
xmlStream.openNode(this.tag);
if (model.font) {
this.map.font.render(xmlStream, model.font);
}
if (model.numFmt && model.numFmtId) {
const numFmtModel = {id: model.numFmtId, formatCode: model.numFmt};
this.map.numFmt.render(xmlStream, numFmtModel);
}
if (model.fill) {
this.map.fill.render(xmlStream, model.fill);
}
if (model.alignment) {
this.map.alignment.render(xmlStream, model.alignment);
}
if (model.border) {
this.map.border.render(xmlStream, model.border);
}
if (model.protection) {
this.map.protection.render(xmlStream, model.protection);
}
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case this.tag:
// this node is often repeated. Need to reset children
this.reset();
return true;
default:
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
}
return true;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.parser = undefined;
}
return true;
}
if (name === this.tag) {
this.model = {
alignment: this.map.alignment.model,
border: this.map.border.model,
fill: this.map.fill.model,
font: this.map.font.model,
numFmt: this.map.numFmt.model,
protection: this.map.protection.model,
};
return false;
}
return true;
}
}
module.exports = DxfXform;
+364
View File
@@ -0,0 +1,364 @@
/* eslint-disable max-classes-per-file */
const BaseXform = require('../base-xform');
const ColorXform = require('./color-xform');
class StopXform extends BaseXform {
constructor() {
super();
this.map = {
color: new ColorXform(),
};
}
get tag() {
return 'stop';
}
render(xmlStream, model) {
xmlStream.openNode('stop');
xmlStream.addAttribute('position', model.position);
this.map.color.render(xmlStream, model.color);
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
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() {}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.model.color = this.parser.model;
this.parser = undefined;
}
return true;
}
return false;
}
}
class PatternFillXform extends BaseXform {
constructor() {
super();
this.map = {
fgColor: new ColorXform('fgColor'),
bgColor: new ColorXform('bgColor'),
};
}
get name() {
return 'pattern';
}
get tag() {
return 'patternFill';
}
render(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(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case 'patternFill':
this.model = {
type: 'pattern',
pattern: node.attributes.patternType,
};
return true;
default:
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
if (this.parser.model) {
this.model[name] = this.parser.model;
}
this.parser = undefined;
}
return true;
}
return false;
}
}
class GradientFillXform extends BaseXform {
constructor() {
super();
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 = [];
// }
}
get name() {
return 'gradient';
}
get tag() {
return 'gradientFill';
}
render(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;
default:
break;
}
const stopXform = this.map.stop;
model.stops.forEach(stopModel => {
stopXform.render(xmlStream, stopModel);
});
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case 'gradientFill': {
const model = (this.model = {
stops: [],
});
if (node.attributes.degree) {
model.gradient = 'angle';
model.degree = parseInt(node.attributes.degree, 10);
} 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;
default:
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.model.stops.push(this.parser.model);
this.parser = undefined;
}
return true;
}
return false;
}
}
// Fill encapsulates translation from fill model to/from xlsx
class FillXform extends BaseXform {
constructor() {
super();
this.map = {
patternFill: new PatternFillXform(),
gradientFill: new GradientFillXform(),
};
}
get tag() {
return 'fill';
}
render(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(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case 'fill':
this.model = {};
return true;
default:
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(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;
}
return false;
}
validStyle(value) {
return FillXform.validPatternValues[value];
}
}
FillXform.validPatternValues = [
'none',
'solid',
'darkVertical',
'darkGray',
'mediumGray',
'lightGray',
'gray125',
'gray0625',
'darkHorizontal',
'darkVertical',
'darkDown',
'darkUp',
'darkGrid',
'darkTrellis',
'lightHorizontal',
'lightVertical',
'lightDown',
'lightUp',
'lightGrid',
'lightTrellis',
'lightGrid',
].reduce((p, v) => {
p[v] = true;
return p;
}, {});
FillXform.StopXform = StopXform;
FillXform.PatternFillXform = PatternFillXform;
FillXform.GradientFillXform = GradientFillXform;
module.exports = FillXform;
+102
View File
@@ -0,0 +1,102 @@
'use strict';
const ColorXform = require('./color-xform');
const BooleanXform = require('../simple/boolean-xform');
const IntegerXform = require('../simple/integer-xform');
const StringXform = require('../simple/string-xform');
const UnderlineXform = require('./underline-xform');
const _ = require('../../../utils/under-dash');
const BaseXform = require('../base-xform');
// Font encapsulates translation from font model to xlsx
class FontXform extends BaseXform {
constructor(options) {
super();
this.options = options || FontXform.OPTIONS;
this.map = {
b: {prop: 'bold', xform: new BooleanXform({tag: 'b', attr: 'val'})},
i: {prop: 'italic', xform: new BooleanXform({tag: 'i', attr: 'val'})},
u: {prop: 'underline', xform: new UnderlineXform()},
charset: {prop: 'charset', xform: new IntegerXform({tag: 'charset', attr: 'val'})},
color: {prop: 'color', xform: new ColorXform()},
condense: {prop: 'condense', xform: new BooleanXform({tag: 'condense', attr: 'val'})},
extend: {prop: 'extend', xform: new BooleanXform({tag: 'extend', attr: 'val'})},
family: {prop: 'family', xform: new IntegerXform({tag: 'family', attr: 'val'})},
outline: {prop: 'outline', xform: new BooleanXform({tag: 'outline', attr: 'val'})},
vertAlign: {prop: 'vertAlign', xform: new StringXform({tag: 'vertAlign', attr: 'val'})},
scheme: {prop: 'scheme', xform: new StringXform({tag: 'scheme', attr: 'val'})},
shadow: {prop: 'shadow', xform: new BooleanXform({tag: 'shadow', attr: 'val'})},
strike: {prop: 'strike', xform: new BooleanXform({tag: 'strike', attr: 'val'})},
sz: {prop: 'size', xform: new IntegerXform({tag: 'sz', attr: 'val'})},
};
this.map[this.options.fontNameTag] = {
prop: 'name',
xform: new StringXform({tag: this.options.fontNameTag, attr: 'val'}),
};
}
get tag() {
return this.options.tagName;
}
render(xmlStream, model) {
const {map} = this;
xmlStream.openNode(this.options.tagName);
_.each(this.map, (defn, tag) => {
map[tag].xform.render(xmlStream, model[defn.prop]);
});
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
if (this.map[node.name]) {
this.parser = this.map[node.name].xform;
return this.parser.parseOpen(node);
}
switch (node.name) {
case this.options.tagName:
this.model = {};
return true;
default:
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser && !this.parser.parseClose(name)) {
const item = this.map[name];
if (this.parser.model) {
this.model[item.prop] = this.parser.model;
}
this.parser = undefined;
return true;
}
switch (name) {
case this.options.tagName:
return false;
default:
return true;
}
}
}
FontXform.OPTIONS = {
tagName: 'font',
fontNameTag: 'name',
};
module.exports = FontXform;
+63
View File
@@ -0,0 +1,63 @@
const _ = require('../../../utils/under-dash');
const defaultNumFormats = require('../../defaultnumformats');
const BaseXform = require('../base-xform');
function hashDefaultFormats() {
const hash = {};
_.each(defaultNumFormats, (dnf, id) => {
if (dnf.f) {
hash[dnf.f] = parseInt(id, 10);
}
// at some point, add the other cultures here...
});
return hash;
}
const defaultFmtHash = hashDefaultFormats();
// NumFmt encapsulates translation between number format and xlsx
class NumFmtXform extends BaseXform {
constructor(id, formatCode) {
super();
this.id = id;
this.formatCode = formatCode;
}
get tag() {
return 'numFmt';
}
render(xmlStream, model) {
xmlStream.leafNode('numFmt', {numFmtId: model.id, formatCode: model.formatCode});
}
parseOpen(node) {
switch (node.name) {
case 'numFmt':
this.model = {
id: parseInt(node.attributes.numFmtId, 10),
formatCode: node.attributes.formatCode.replace(/[\\](.)/g, '$1'),
};
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
NumFmtXform.getDefaultFmtId = function getDefaultFmtId(formatCode) {
return defaultFmtHash[formatCode];
};
NumFmtXform.getDefaultFmtCode = function getDefaultFmtCode(numFmtId) {
return defaultNumFormats[numFmtId] && defaultNumFormats[numFmtId].f;
};
module.exports = NumFmtXform;
+60
View File
@@ -0,0 +1,60 @@
const BaseXform = require('../base-xform');
const validation = {
boolean(value, dflt) {
if (value === undefined) {
return dflt;
}
return value;
},
};
// Protection encapsulates translation from style.protection model to/from xlsx
class ProtectionXform extends BaseXform {
get tag() {
return 'protection';
}
render(xmlStream, model) {
xmlStream.addRollback();
xmlStream.openNode('protection');
let isValid = false;
function add(name, value) {
if (value !== undefined) {
xmlStream.addAttribute(name, value);
isValid = true;
}
}
add('locked', validation.boolean(model.locked, true) ? undefined : '0');
add('hidden', validation.boolean(model.hidden, false) ? '1' : undefined);
xmlStream.closeNode();
if (isValid) {
xmlStream.commit();
} else {
xmlStream.rollback();
}
}
parseOpen(node) {
const model = {
locked: !(node.attributes.locked === '0'),
hidden: node.attributes.hidden === '1',
};
// only want to record models that differ from defaults
const isSignificant = !model.locked || model.hidden;
this.model = isSignificant ? model : null;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = ProtectionXform;
+125
View File
@@ -0,0 +1,125 @@
const BaseXform = require('../base-xform');
const AlignmentXform = require('./alignment-xform');
const ProtectionXform = require('./protection-xform');
// <xf numFmtId="[numFmtId]" fontId="[fontId]" fillId="[fillId]" borderId="[xf.borderId]" xfId="[xfId]">
// Optional <alignment>
// Optional <protection>
// </xf>
// Style assists translation from style model to/from xlsx
class StyleXform extends BaseXform {
constructor(options) {
super();
this.xfId = !!(options && options.xfId);
this.map = {
alignment: new AlignmentXform(),
protection: new ProtectionXform(),
};
}
get tag() {
return 'xf';
}
render(xmlStream, model) {
xmlStream.openNode('xf', {
numFmtId: model.numFmtId || 0,
fontId: model.fontId || 0,
fillId: model.fillId || 0,
borderId: model.borderId || 0,
});
if (this.xfId) {
xmlStream.addAttribute('xfId', model.xfId || 0);
}
if (model.numFmtId) {
xmlStream.addAttribute('applyNumberFormat', '1');
}
if (model.fontId) {
xmlStream.addAttribute('applyFont', '1');
}
if (model.fillId) {
xmlStream.addAttribute('applyFill', '1');
}
if (model.borderId) {
xmlStream.addAttribute('applyBorder', '1');
}
if (model.alignment) {
xmlStream.addAttribute('applyAlignment', '1');
}
if (model.protection) {
xmlStream.addAttribute('applyProtection', '1');
}
/**
* Rendering tags causes close of XML stream.
* Therefore adding attributes must be done before rendering tags.
*/
if (model.alignment) {
this.map.alignment.render(xmlStream, model.alignment);
}
if (model.protection) {
this.map.protection.render(xmlStream, model.protection);
}
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
// used during sax parsing of xml to build font object
switch (node.name) {
case 'xf':
this.model = {
numFmtId: parseInt(node.attributes.numFmtId, 10),
fontId: parseInt(node.attributes.fontId, 10),
fillId: parseInt(node.attributes.fillId, 10),
borderId: parseInt(node.attributes.borderId, 10),
};
if (this.xfId) {
this.model.xfId = parseInt(node.attributes.xfId, 10);
}
return true;
case 'alignment':
this.parser = this.map.alignment;
this.parser.parseOpen(node);
return true;
case 'protection':
this.parser = this.map.protection;
this.parser.parseOpen(node);
return true;
default:
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
if (this.map.protection === this.parser) {
this.model.protection = this.parser.model;
} else {
this.model.alignment = this.parser.model;
}
this.parser = undefined;
}
return true;
}
return name !== 'xf';
}
}
module.exports = StyleXform;
+527
View File
@@ -0,0 +1,527 @@
/* eslint-disable max-classes-per-file */
const Enums = require('../../../doc/enums');
const XmlStream = require('../../../utils/xml-stream');
const BaseXform = require('../base-xform');
const StaticXform = require('../static-xform');
const ListXform = require('../list-xform');
const FontXform = require('./font-xform');
const FillXform = require('./fill-xform');
const BorderXform = require('./border-xform');
const NumFmtXform = require('./numfmt-xform');
const StyleXform = require('./style-xform');
const DxfXform = require('./dxf-xform');
// custom numfmt ids start here
const NUMFMT_BASE = 164;
// =============================================================================
// StylesXform is used to generate and parse the styles.xml file
// it manages the collections of fonts, number formats, alignments, etc
class StylesXform extends BaseXform {
constructor(initialise) {
super();
this.map = {
numFmts: new ListXform({tag: 'numFmts', count: true, childXform: new NumFmtXform()}),
fonts: new ListXform({
tag: 'fonts',
count: true,
childXform: new FontXform(),
$: {'x14ac:knownFonts': 1},
}),
fills: new ListXform({tag: 'fills', count: true, childXform: new FillXform()}),
borders: new ListXform({tag: 'borders', count: true, childXform: new BorderXform()}),
cellStyleXfs: new ListXform({tag: 'cellStyleXfs', count: true, childXform: new StyleXform()}),
cellXfs: new ListXform({
tag: 'cellXfs',
count: true,
childXform: new StyleXform({xfId: true}),
}),
dxfs: new ListXform({tag: 'dxfs', always: true, count: true, childXform: new DxfXform()}),
// for style manager
numFmt: new NumFmtXform(),
font: new FontXform(),
fill: new FillXform(),
border: new BorderXform(),
style: new StyleXform({xfId: true}),
cellStyles: StylesXform.STATIC_XFORMS.cellStyles,
tableStyles: StylesXform.STATIC_XFORMS.tableStyles,
extLst: StylesXform.STATIC_XFORMS.extLst,
};
if (initialise) {
// StylesXform also acts as style manager and is used to build up styles-model during worksheet processing
this.init();
}
}
initIndex() {
this.index = {
style: {},
numFmt: {},
numFmtNextId: 164, // start custom format ids here
font: {},
border: {},
fill: {},
};
}
init() {
// Prepare for Style Manager role
this.model = {
styles: [],
numFmts: [],
fonts: [],
borders: [],
fills: [],
dxfs: [],
};
this.initIndex();
// default (zero) border
this._addBorder({});
// add default (all zero) style
this._addStyle({numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0});
// add default fills
this._addFill({type: 'pattern', pattern: 'none'});
this._addFill({type: 'pattern', pattern: 'gray125'});
this.weakMap = new WeakMap();
}
render(xmlStream, model) {
model = model || this.model;
//
// <fonts count="2" x14ac:knownFonts="1">
xmlStream.openXml(XmlStream.StdDocAttributes);
xmlStream.openNode('styleSheet', StylesXform.STYLESHEET_ATTRIBUTES);
if (this.index) {
// model has been built by style manager role (contains xml)
if (model.numFmts && model.numFmts.length) {
xmlStream.openNode('numFmts', {count: model.numFmts.length});
model.numFmts.forEach(numFmtXml => {
xmlStream.writeXml(numFmtXml);
});
xmlStream.closeNode();
}
if (!model.fonts.length) {
// default (zero) font
this._addFont({size: 11, color: {theme: 1}, name: 'Calibri', family: 2, scheme: 'minor'});
}
xmlStream.openNode('fonts', {count: model.fonts.length, 'x14ac:knownFonts': 1});
model.fonts.forEach(fontXml => {
xmlStream.writeXml(fontXml);
});
xmlStream.closeNode();
xmlStream.openNode('fills', {count: model.fills.length});
model.fills.forEach(fillXml => {
xmlStream.writeXml(fillXml);
});
xmlStream.closeNode();
xmlStream.openNode('borders', {count: model.borders.length});
model.borders.forEach(borderXml => {
xmlStream.writeXml(borderXml);
});
xmlStream.closeNode();
this.map.cellStyleXfs.render(xmlStream, [{numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0}]);
xmlStream.openNode('cellXfs', {count: model.styles.length});
model.styles.forEach(styleXml => {
xmlStream.writeXml(styleXml);
});
xmlStream.closeNode();
} else {
// model is plain JSON and needs to be xformed
this.map.numFmts.render(xmlStream, model.numFmts);
this.map.fonts.render(xmlStream, model.fonts);
this.map.fills.render(xmlStream, model.fills);
this.map.borders.render(xmlStream, model.borders);
this.map.cellStyleXfs.render(xmlStream, [{numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0}]);
this.map.cellXfs.render(xmlStream, model.styles);
}
StylesXform.STATIC_XFORMS.cellStyles.render(xmlStream);
this.map.dxfs.render(xmlStream, model.dxfs);
StylesXform.STATIC_XFORMS.tableStyles.render(xmlStream);
StylesXform.STATIC_XFORMS.extLst.render(xmlStream);
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case 'styleSheet':
this.initIndex();
return true;
default:
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
}
return true;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
}
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.parser = undefined;
}
return true;
}
switch (name) {
case 'styleSheet': {
this.model = {};
const add = (propName, xform) => {
if (xform.model && xform.model.length) {
this.model[propName] = xform.model;
}
};
add('numFmts', this.map.numFmts);
add('fonts', this.map.fonts);
add('fills', this.map.fills);
add('borders', this.map.borders);
add('styles', this.map.cellXfs);
add('dxfs', this.map.dxfs);
// index numFmts
this.index = {
model: [],
numFmt: [],
};
if (this.model.numFmts) {
const numFmtIndex = this.index.numFmt;
this.model.numFmts.forEach(numFmt => {
numFmtIndex[numFmt.id] = numFmt.formatCode;
});
}
return false;
}
default:
// not quite sure how we get here!
return true;
}
}
// add a cell's style model to the collection
// each style property is processed and cross-referenced, etc.
// the styleId is returned. Note: cellType is used when numFmt not defined
addStyleModel(model, cellType) {
if (!model) {
return 0;
}
// if we have no default font, add it here now
if (!this.model.fonts.length) {
// default (zero) font
this._addFont({size: 11, color: {theme: 1}, name: 'Calibri', family: 2, scheme: 'minor'});
}
// if we have seen this style object before, assume it has the same styleId
if (this.weakMap && this.weakMap.has(model)) {
return this.weakMap.get(model);
}
const style = {};
cellType = cellType || Enums.ValueType.Number;
if (model.numFmt) {
style.numFmtId = this._addNumFmtStr(model.numFmt);
} else {
switch (cellType) {
case Enums.ValueType.Number:
style.numFmtId = this._addNumFmtStr('General');
break;
case Enums.ValueType.Date:
style.numFmtId = this._addNumFmtStr('mm-dd-yy');
break;
default:
break;
}
}
if (model.font) {
style.fontId = this._addFont(model.font);
}
if (model.border) {
style.borderId = this._addBorder(model.border);
}
if (model.fill) {
style.fillId = this._addFill(model.fill);
}
if (model.alignment) {
style.alignment = model.alignment;
}
if (model.protection) {
style.protection = model.protection;
}
const styleId = this._addStyle(style);
if (this.weakMap) {
this.weakMap.set(model, styleId);
}
return styleId;
}
// given a styleId (i.e. s="n"), get the cell's style model
// objects are shared where possible.
getStyleModel(id) {
// if the style doesn't exist return null
const style = this.model.styles[id];
if (!style) return null;
// have we built this model before?
let model = this.index.model[id];
if (model) return model;
// build a new model
model = this.index.model[id] = {};
// -------------------------------------------------------
// number format
if (style.numFmtId) {
const numFmt = this.index.numFmt[style.numFmtId] || NumFmtXform.getDefaultFmtCode(style.numFmtId);
if (numFmt) {
model.numFmt = numFmt;
}
}
function addStyle(name, group, styleId) {
if (styleId || styleId === 0) {
const part = group[styleId];
if (part) {
model[name] = part;
}
}
}
addStyle('font', this.model.fonts, style.fontId);
addStyle('border', this.model.borders, style.borderId);
addStyle('fill', this.model.fills, style.fillId);
// -------------------------------------------------------
// alignment
if (style.alignment) {
model.alignment = style.alignment;
}
// -------------------------------------------------------
// protection
if (style.protection) {
model.protection = style.protection;
}
return model;
}
addDxfStyle(style) {
if (style.numFmt) {
// register numFmtId to use it during dxf-xform rendering
style.numFmtId = this._addNumFmtStr(style.numFmt);
}
this.model.dxfs.push(style);
return this.model.dxfs.length - 1;
}
getDxfStyle(id) {
return this.model.dxfs[id];
}
// =========================================================================
// Private Interface
_addStyle(style) {
const xml = this.map.style.toXml(style);
let index = this.index.style[xml];
if (index === undefined) {
index = this.index.style[xml] = this.model.styles.length;
this.model.styles.push(xml);
}
return index;
}
// =========================================================================
// Number Formats
_addNumFmtStr(formatCode) {
// check if default format
let index = NumFmtXform.getDefaultFmtId(formatCode);
if (index !== undefined) return index;
// check if already in
index = this.index.numFmt[formatCode];
if (index !== undefined) return index;
index = this.index.numFmt[formatCode] = NUMFMT_BASE + this.model.numFmts.length;
const xml = this.map.numFmt.toXml({id: index, formatCode});
this.model.numFmts.push(xml);
return index;
}
// =========================================================================
// Fonts
_addFont(font) {
const xml = this.map.font.toXml(font);
let index = this.index.font[xml];
if (index === undefined) {
index = this.index.font[xml] = this.model.fonts.length;
this.model.fonts.push(xml);
}
return index;
}
// =========================================================================
// Borders
_addBorder(border) {
const xml = this.map.border.toXml(border);
let index = this.index.border[xml];
if (index === undefined) {
index = this.index.border[xml] = this.model.borders.length;
this.model.borders.push(xml);
}
return index;
}
// =========================================================================
// Fills
_addFill(fill) {
const xml = this.map.fill.toXml(fill);
let index = this.index.fill[xml];
if (index === undefined) {
index = this.index.fill[xml] = this.model.fills.length;
this.model.fills.push(xml);
}
return index;
}
// =========================================================================
}
StylesXform.STYLESHEET_ATTRIBUTES = {
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
'mc:Ignorable': 'x14ac x16r2',
'xmlns:x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac',
'xmlns:x16r2': 'http://schemas.microsoft.com/office/spreadsheetml/2015/02/main',
};
StylesXform.STATIC_XFORMS = {
cellStyles: new StaticXform({
tag: 'cellStyles',
$: {count: 1},
c: [{tag: 'cellStyle', $: {name: 'Normal', xfId: 0, builtinId: 0}}],
}),
dxfs: new StaticXform({tag: 'dxfs', $: {count: 0}}),
tableStyles: new StaticXform({
tag: 'tableStyles',
$: {count: 0, defaultTableStyle: 'TableStyleMedium2', defaultPivotStyle: 'PivotStyleLight16'},
}),
extLst: new StaticXform({
tag: 'extLst',
c: [
{
tag: 'ext',
$: {
uri: '{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}',
'xmlns:x14': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main',
},
c: [{tag: 'x14:slicerStyles', $: {defaultSlicerStyle: 'SlicerStyleLight1'}}],
},
{
tag: 'ext',
$: {
uri: '{9260A510-F301-46a8-8635-F512D64BE5F5}',
'xmlns:x15': 'http://schemas.microsoft.com/office/spreadsheetml/2010/11/main',
},
c: [{tag: 'x15:timelineStyles', $: {defaultTimelineStyle: 'TimeSlicerStyleLight1'}}],
},
],
}),
};
// the stylemanager mock acts like StyleManager except that it always returns 0 or {}
class StylesXformMock extends StylesXform {
constructor() {
super();
this.model = {
styles: [{numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0}],
numFmts: [],
fonts: [{size: 11, color: {theme: 1}, name: 'Calibri', family: 2, scheme: 'minor'}],
borders: [{}],
fills: [
{type: 'pattern', pattern: 'none'},
{type: 'pattern', pattern: 'gray125'},
],
};
}
// =========================================================================
// Style Manager Interface
// override normal behaviour - consume and dispose
parseStream(stream) {
stream.autodrain();
return Promise.resolve();
}
// add a cell's style model to the collection
// each style property is processed and cross-referenced, etc.
// the styleId is returned. Note: cellType is used when numFmt not defined
addStyleModel(model, cellType) {
switch (cellType) {
case Enums.ValueType.Date:
return this.dateStyleId;
default:
return 0;
}
}
get dateStyleId() {
if (!this._dateStyleId) {
const dateStyle = {
numFmtId: NumFmtXform.getDefaultFmtId('mm-dd-yy'),
};
this._dateStyleId = this.model.styles.length;
this.model.styles.push(dateStyle);
}
return this._dateStyleId;
}
// given a styleId (i.e. s="n"), get the cell's style model
// objects are shared where possible.
getStyleModel(/* id */) {
return {};
}
}
StylesXform.Mock = StylesXformMock;
module.exports = StylesXform;
+47
View File
@@ -0,0 +1,47 @@
const BaseXform = require('../base-xform');
class UnderlineXform extends BaseXform {
constructor(model) {
super();
this.model = model;
}
get tag() {
return 'u';
}
render(xmlStream, model) {
model = model || this.model;
if (model === true) {
xmlStream.leafNode('u');
} else {
const attr = UnderlineXform.Attributes[model];
if (attr) {
xmlStream.leafNode('u', attr);
}
}
}
parseOpen(node) {
if (node.name === 'u') {
this.model = node.attributes.val || true;
}
}
parseText() {}
parseClose() {
return false;
}
}
UnderlineXform.Attributes = {
single: {},
double: {val: 'double'},
singleAccounting: {val: 'singleAccounting'},
doubleAccounting: {val: 'doubleAccounting'},
};
module.exports = UnderlineXform;