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
+38
View File
@@ -0,0 +1,38 @@
const colCache = require('../../../utils/col-cache');
const BaseXform = require('../base-xform');
class AutoFilterXform extends BaseXform {
get tag() {
return 'autoFilter';
}
render(xmlStream, model) {
if (model) {
if (typeof model === 'string') {
// assume range
xmlStream.leafNode('autoFilter', {ref: model});
} else {
const getAddress = function(addr) {
if (typeof addr === 'string') {
return addr;
}
return colCache.getAddress(addr.row, addr.column).address;
};
const firstAddress = getAddress(model.from);
const secondAddress = getAddress(model.to);
if (firstAddress && secondAddress) {
xmlStream.leafNode('autoFilter', {ref: `${firstAddress}:${secondAddress}`});
}
}
}
}
parseOpen(node) {
if (node.name === 'autoFilter') {
this.model = node.attributes.ref;
}
}
}
module.exports = AutoFilterXform;
+498
View File
@@ -0,0 +1,498 @@
const utils = require('../../../utils/utils');
const BaseXform = require('../base-xform');
const Range = require('../../../doc/range');
const Enums = require('../../../doc/enums');
const RichTextXform = require('../strings/rich-text-xform');
function getValueType(v) {
if (v === null || v === undefined) {
return Enums.ValueType.Null;
}
if (v instanceof String || typeof v === 'string') {
return Enums.ValueType.String;
}
if (typeof v === 'number') {
return Enums.ValueType.Number;
}
if (typeof v === 'boolean') {
return Enums.ValueType.Boolean;
}
if (v instanceof Date) {
return Enums.ValueType.Date;
}
if (v.text && v.hyperlink) {
return Enums.ValueType.Hyperlink;
}
if (v.formula) {
return Enums.ValueType.Formula;
}
if (v.error) {
return Enums.ValueType.Error;
}
throw new Error('I could not understand type of value');
}
function getEffectiveCellType(cell) {
switch (cell.type) {
case Enums.ValueType.Formula:
return getValueType(cell.result);
default:
return cell.type;
}
}
class CellXform extends BaseXform {
constructor() {
super();
this.richTextXForm = new RichTextXform();
}
get tag() {
return 'c';
}
prepare(model, options) {
const styleId = options.styles.addStyleModel(model.style || {}, getEffectiveCellType(model));
if (styleId) {
model.styleId = styleId;
}
if (model.comment) {
options.comments.push({...model.comment, ref: model.address});
}
switch (model.type) {
case Enums.ValueType.String:
case Enums.ValueType.RichText:
if (options.sharedStrings) {
model.ssId = options.sharedStrings.add(model.value);
}
break;
case Enums.ValueType.Date:
if (options.date1904) {
model.date1904 = true;
}
break;
case Enums.ValueType.Hyperlink:
if (options.sharedStrings && model.text !== undefined && model.text !== null) {
model.ssId = options.sharedStrings.add(model.text);
}
options.hyperlinks.push({
address: model.address,
target: model.hyperlink,
tooltip: model.tooltip,
});
break;
case Enums.ValueType.Merge:
options.merges.add(model);
break;
case Enums.ValueType.Formula:
if (options.date1904) {
// in case valueType is date
model.date1904 = true;
}
if (model.shareType === 'shared') {
model.si = options.siFormulae++;
}
if (model.formula) {
options.formulae[model.address] = model;
} else if (model.sharedFormula) {
const master = options.formulae[model.sharedFormula];
if (!master) {
throw new Error(
`Shared Formula master must exist above and or left of clone for cell ${model.address}`
);
}
if (master.si === undefined) {
master.shareType = 'shared';
master.si = options.siFormulae++;
master.range = new Range(master.address, model.address);
} else if (master.range) {
master.range.expandToAddress(model.address);
}
model.si = master.si;
}
break;
default:
break;
}
}
renderFormula(xmlStream, model) {
let attrs = null;
switch (model.shareType) {
case 'shared':
attrs = {
t: 'shared',
ref: model.ref || model.range.range,
si: model.si,
};
break;
case 'array':
attrs = {
t: 'array',
ref: model.ref,
};
break;
default:
if (model.si !== undefined) {
attrs = {
t: 'shared',
si: model.si,
};
}
break;
}
switch (getValueType(model.result)) {
case Enums.ValueType.Null: // ?
xmlStream.leafNode('f', attrs, model.formula);
break;
case Enums.ValueType.String:
// oddly, formula results don't ever use shared strings
xmlStream.addAttribute('t', 'str');
xmlStream.leafNode('f', attrs, model.formula);
xmlStream.leafNode('v', null, model.result);
break;
case Enums.ValueType.Number:
xmlStream.leafNode('f', attrs, model.formula);
xmlStream.leafNode('v', null, model.result);
break;
case Enums.ValueType.Boolean:
xmlStream.addAttribute('t', 'b');
xmlStream.leafNode('f', attrs, model.formula);
xmlStream.leafNode('v', null, model.result ? 1 : 0);
break;
case Enums.ValueType.Error:
xmlStream.addAttribute('t', 'e');
xmlStream.leafNode('f', attrs, model.formula);
xmlStream.leafNode('v', null, model.result.error);
break;
case Enums.ValueType.Date:
xmlStream.leafNode('f', attrs, model.formula);
xmlStream.leafNode('v', null, utils.dateToExcel(model.result, model.date1904));
break;
// case Enums.ValueType.Hyperlink: // ??
// case Enums.ValueType.Formula:
default:
throw new Error('I could not understand type of value');
}
}
render(xmlStream, model) {
if (model.type === Enums.ValueType.Null && !model.styleId) {
// if null and no style, exit
return;
}
xmlStream.openNode('c');
xmlStream.addAttribute('r', model.address);
if (model.styleId) {
xmlStream.addAttribute('s', model.styleId);
}
switch (model.type) {
case Enums.ValueType.Null:
break;
case Enums.ValueType.Number:
xmlStream.leafNode('v', null, model.value);
break;
case Enums.ValueType.Boolean:
xmlStream.addAttribute('t', 'b');
xmlStream.leafNode('v', null, model.value ? '1' : '0');
break;
case Enums.ValueType.Error:
xmlStream.addAttribute('t', 'e');
xmlStream.leafNode('v', null, model.value.error);
break;
case Enums.ValueType.String:
case Enums.ValueType.RichText:
if (model.ssId !== undefined) {
xmlStream.addAttribute('t', 's');
xmlStream.leafNode('v', null, model.ssId);
} else if (model.value && model.value.richText) {
xmlStream.addAttribute('t', 'inlineStr');
xmlStream.openNode('is');
model.value.richText.forEach(text => {
this.richTextXForm.render(xmlStream, text);
});
xmlStream.closeNode('is');
} else {
xmlStream.addAttribute('t', 'str');
xmlStream.leafNode('v', null, model.value);
}
break;
case Enums.ValueType.Date:
xmlStream.leafNode('v', null, utils.dateToExcel(model.value, model.date1904));
break;
case Enums.ValueType.Hyperlink:
if (model.ssId !== undefined) {
xmlStream.addAttribute('t', 's');
xmlStream.leafNode('v', null, model.ssId);
} else {
xmlStream.addAttribute('t', 'str');
xmlStream.leafNode('v', null, model.text);
}
break;
case Enums.ValueType.Formula:
this.renderFormula(xmlStream, model);
break;
case Enums.ValueType.Merge:
// nothing to add
break;
default:
break;
}
xmlStream.closeNode(); // </c>
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case 'c':
// const address = colCache.decodeAddress(node.attributes.r);
this.model = {
address: node.attributes.r,
};
this.t = node.attributes.t;
if (node.attributes.s) {
this.model.styleId = parseInt(node.attributes.s, 10);
}
return true;
case 'f':
this.currentNode = 'f';
this.model.si = node.attributes.si;
this.model.shareType = node.attributes.t;
this.model.ref = node.attributes.ref;
return true;
case 'v':
this.currentNode = 'v';
return true;
case 't':
this.currentNode = 't';
return true;
case 'r':
this.parser = this.richTextXForm;
this.parser.parseOpen(node);
return true;
default:
return false;
}
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
return;
}
switch (this.currentNode) {
case 'f':
this.model.formula = this.model.formula ? this.model.formula + text : text;
break;
case 'v':
case 't':
if (this.model.value && this.model.value.richText) {
this.model.value.richText.text = this.model.value.richText.text
? this.model.value.richText.text + text
: text;
} else {
this.model.value = this.model.value ? this.model.value + text : text;
}
break;
default:
break;
}
}
parseClose(name) {
switch (name) {
case 'c': {
const {model} = this;
// first guess on cell type
if (model.formula || model.shareType) {
model.type = Enums.ValueType.Formula;
if (model.value) {
if (this.t === 'str') {
model.result = utils.xmlDecode(model.value);
} else if (this.t === 'b') {
model.result = parseInt(model.value, 10) !== 0;
} else if (this.t === 'e') {
model.result = {error: model.value};
} else {
model.result = parseFloat(model.value);
}
model.value = undefined;
}
} else if (model.value !== undefined) {
switch (this.t) {
case 's':
model.type = Enums.ValueType.String;
model.value = parseInt(model.value, 10);
break;
case 'str':
model.type = Enums.ValueType.String;
model.value = utils.xmlDecode(model.value);
break;
case 'inlineStr':
model.type = Enums.ValueType.String;
break;
case 'b':
model.type = Enums.ValueType.Boolean;
model.value = parseInt(model.value, 10) !== 0;
break;
case 'e':
model.type = Enums.ValueType.Error;
model.value = {error: model.value};
break;
default:
model.type = Enums.ValueType.Number;
model.value = parseFloat(model.value);
break;
}
} else if (model.styleId) {
model.type = Enums.ValueType.Null;
} else {
model.type = Enums.ValueType.Merge;
}
return false;
}
case 'f':
case 'v':
case 'is':
this.currentNode = undefined;
return true;
case 't':
if (this.parser) {
this.parser.parseClose(name);
return true;
}
this.currentNode = undefined;
return true;
case 'r':
this.model.value = this.model.value || {};
this.model.value.richText = this.model.value.richText || [];
this.model.value.richText.push(this.parser.model);
this.parser = undefined;
this.currentNode = undefined;
return true;
default:
if (this.parser) {
this.parser.parseClose(name);
return true;
}
return false;
}
}
reconcile(model, options) {
const style = model.styleId && options.styles && options.styles.getStyleModel(model.styleId);
if (style) {
model.style = style;
}
if (model.styleId !== undefined) {
model.styleId = undefined;
}
switch (model.type) {
case Enums.ValueType.String:
if (typeof model.value === 'number') {
if (options.sharedStrings) {
model.value = options.sharedStrings.getString(model.value);
}
}
if (model.value.richText) {
model.type = Enums.ValueType.RichText;
}
break;
case Enums.ValueType.Number:
if (style && utils.isDateFmt(style.numFmt)) {
model.type = Enums.ValueType.Date;
model.value = utils.excelToDate(model.value, options.date1904);
}
break;
case Enums.ValueType.Formula:
if (model.result !== undefined && style && utils.isDateFmt(style.numFmt)) {
model.result = utils.excelToDate(model.result, options.date1904);
}
if (model.shareType === 'shared') {
if (model.ref) {
// master
options.formulae[model.si] = model.address;
} else {
// slave
model.sharedFormula = options.formulae[model.si];
delete model.shareType;
}
delete model.si;
}
break;
default:
break;
}
// look for hyperlink
const hyperlink = options.hyperlinkMap[model.address];
if (hyperlink) {
if (model.type === Enums.ValueType.Formula) {
model.text = model.result;
model.result = undefined;
} else {
model.text = model.value;
model.value = undefined;
}
model.type = Enums.ValueType.Hyperlink;
model.hyperlink = hyperlink;
}
const comment = options.commentsMap && options.commentsMap[model.address];
if (comment) {
model.comment = comment;
}
}
}
module.exports = CellXform;
+27
View File
@@ -0,0 +1,27 @@
const BaseXform = require('../../base-xform');
class CfIconExtXform extends BaseXform {
get tag() {
return 'x14:cfIcon';
}
render(xmlStream, model) {
xmlStream.leafNode(this.tag, {
iconSet: model.iconSet,
iconId: model.iconId,
});
}
parseOpen({attributes}) {
this.model = {
iconSet: attributes.iconSet,
iconId: BaseXform.toIntValue(attributes.iconId),
};
}
parseClose(name) {
return name !== this.tag;
}
}
module.exports = CfIconExtXform;
+98
View File
@@ -0,0 +1,98 @@
const {v4: uuidv4} = require('uuid');
const BaseXform = require('../../base-xform');
const CompositeXform = require('../../composite-xform');
const DatabarExtXform = require('./databar-ext-xform');
const IconSetExtXform = require('./icon-set-ext-xform');
const extIcons = {
'3Triangles': true,
'3Stars': true,
'5Boxes': true,
};
class CfRuleExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'x14:dataBar': (this.databarXform = new DatabarExtXform()),
'x14:iconSet': (this.iconSetXform = new IconSetExtXform()),
};
}
get tag() {
return 'x14:cfRule';
}
static isExt(rule) {
// is this rule primitive?
if (rule.type === 'dataBar') {
return DatabarExtXform.isExt(rule);
}
if (rule.type === 'iconSet') {
if (rule.custom || extIcons[rule.iconSet]) {
return true;
}
}
return false;
}
prepare(model) {
if (CfRuleExtXform.isExt(model)) {
model.x14Id = `{${uuidv4()}}`.toUpperCase();
}
}
render(xmlStream, model) {
if (!CfRuleExtXform.isExt(model)) {
return;
}
switch (model.type) {
case 'dataBar':
this.renderDataBar(xmlStream, model);
break;
case 'iconSet':
this.renderIconSet(xmlStream, model);
break;
}
}
renderDataBar(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: 'dataBar',
id: model.x14Id,
});
this.databarXform.render(xmlStream, model);
xmlStream.closeNode();
}
renderIconSet(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: 'iconSet',
priority: model.priority,
id: model.x14Id || `{${uuidv4()}}`,
});
this.iconSetXform.render(xmlStream, model);
xmlStream.closeNode();
}
createNewModel({attributes}) {
return {
type: attributes.type,
x14Id: attributes.id,
priority: BaseXform.toIntValue(attributes.priority),
};
}
onParserClose(name, parser) {
Object.assign(this.model, parser.model);
}
}
module.exports = CfRuleExtXform;
+43
View File
@@ -0,0 +1,43 @@
const CompositeXform = require('../../composite-xform');
const FExtXform = require('./f-ext-xform');
class CfvoExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'xm:f': (this.fExtXform = new FExtXform()),
};
}
get tag() {
return 'x14:cfvo';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: model.type,
});
if (model.value !== undefined) {
this.fExtXform.render(xmlStream, model.value);
}
xmlStream.closeNode();
}
createNewModel(node) {
return {
type: node.attributes.type,
};
}
onParserClose(name, parser) {
switch (name) {
case 'xm:f':
this.model.value = parser.model ? parseFloat(parser.model) : 0;
break;
}
}
}
module.exports = CfvoExtXform;
@@ -0,0 +1,62 @@
const CompositeXform = require('../../composite-xform');
const SqRefExtXform = require('./sqref-ext-xform');
const CfRuleExtXform = require('./cf-rule-ext-xform');
class ConditionalFormattingExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'xm:sqref': (this.sqRef = new SqRefExtXform()),
'x14:cfRule': (this.cfRule = new CfRuleExtXform()),
};
}
get tag() {
return 'x14:conditionalFormatting';
}
prepare(model, options) {
model.rules.forEach(rule => {
this.cfRule.prepare(rule, options);
});
}
render(xmlStream, model) {
if (!model.rules.some(CfRuleExtXform.isExt)) {
return;
}
xmlStream.openNode(this.tag, {
'xmlns:xm': 'http://schemas.microsoft.com/office/excel/2006/main',
});
model.rules.filter(CfRuleExtXform.isExt).forEach(rule => this.cfRule.render(xmlStream, rule));
// for some odd reason, Excel needs the <xm:sqref> node to be after the rules
this.sqRef.render(xmlStream, model.ref);
xmlStream.closeNode();
}
createNewModel() {
return {
rules: [],
};
}
onParserClose(name, parser) {
switch (name) {
case 'xm:sqref':
this.model.ref = parser.model;
break;
case 'x14:cfRule':
this.model.rules.push(parser.model);
break;
}
}
}
module.exports = ConditionalFormattingExtXform;
@@ -0,0 +1,50 @@
const CompositeXform = require('../../composite-xform');
const CfRuleExtXform = require('./cf-rule-ext-xform');
const ConditionalFormattingExtXform = require('./conditional-formatting-ext-xform');
class ConditionalFormattingsExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'x14:conditionalFormatting': (this.cfXform = new ConditionalFormattingExtXform()),
};
}
get tag() {
return 'x14:conditionalFormattings';
}
hasContent(model) {
if (model.hasExtContent === undefined) {
model.hasExtContent = model.some(cf => cf.rules.some(CfRuleExtXform.isExt));
}
return model.hasExtContent;
}
prepare(model, options) {
model.forEach(cf => {
this.cfXform.prepare(cf, options);
});
}
render(xmlStream, model) {
if (this.hasContent(model)) {
xmlStream.openNode(this.tag);
model.forEach(cf => this.cfXform.render(xmlStream, cf));
xmlStream.closeNode();
}
}
createNewModel() {
return [];
}
onParserClose(name, parser) {
// model is array of conditional formatting objects
this.model.push(parser.model);
}
}
module.exports = ConditionalFormattingsExtXform;
+98
View File
@@ -0,0 +1,98 @@
const BaseXform = require('../../base-xform');
const CompositeXform = require('../../composite-xform');
const ColorXform = require('../../style/color-xform');
const CfvoExtXform = require('./cfvo-ext-xform');
class DatabarExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'x14:cfvo': (this.cfvoXform = new CfvoExtXform()),
'x14:borderColor': (this.borderColorXform = new ColorXform('x14:borderColor')),
'x14:negativeBorderColor': (this.negativeBorderColorXform = new ColorXform(
'x14:negativeBorderColor'
)),
'x14:negativeFillColor': (this.negativeFillColorXform = new ColorXform(
'x14:negativeFillColor'
)),
'x14:axisColor': (this.axisColorXform = new ColorXform('x14:axisColor')),
};
}
static isExt(rule) {
// not all databars need ext
// TODO: refine this
return !rule.gradient;
}
get tag() {
return 'x14:dataBar';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag, {
minLength: BaseXform.toIntAttribute(model.minLength, 0, true),
maxLength: BaseXform.toIntAttribute(model.maxLength, 100, true),
border: BaseXform.toBoolAttribute(model.border, false),
gradient: BaseXform.toBoolAttribute(model.gradient, true),
negativeBarColorSameAsPositive: BaseXform.toBoolAttribute(
model.negativeBarColorSameAsPositive,
true
),
negativeBarBorderColorSameAsPositive: BaseXform.toBoolAttribute(
model.negativeBarBorderColorSameAsPositive,
true
),
axisPosition: BaseXform.toAttribute(model.axisPosition, 'auto'),
direction: BaseXform.toAttribute(model.direction, 'leftToRight'),
});
model.cfvo.forEach(cfvo => {
this.cfvoXform.render(xmlStream, cfvo);
});
this.borderColorXform.render(xmlStream, model.borderColor);
this.negativeBorderColorXform.render(xmlStream, model.negativeBorderColor);
this.negativeFillColorXform.render(xmlStream, model.negativeFillColor);
this.axisColorXform.render(xmlStream, model.axisColor);
xmlStream.closeNode();
}
createNewModel({attributes}) {
return {
cfvo: [],
minLength: BaseXform.toIntValue(attributes.minLength, 0),
maxLength: BaseXform.toIntValue(attributes.maxLength, 100),
border: BaseXform.toBoolValue(attributes.border, false),
gradient: BaseXform.toBoolValue(attributes.gradient, true),
negativeBarColorSameAsPositive: BaseXform.toBoolValue(
attributes.negativeBarColorSameAsPositive,
true
),
negativeBarBorderColorSameAsPositive: BaseXform.toBoolValue(
attributes.negativeBarBorderColorSameAsPositive,
true
),
axisPosition: BaseXform.toStringValue(attributes.axisPosition, 'auto'),
direction: BaseXform.toStringValue(attributes.direction, 'leftToRight'),
};
}
onParserClose(name, parser) {
const [, prop] = name.split(':');
switch (prop) {
case 'cfvo':
this.model.cfvo.push(parser.model);
break;
default:
this.model[prop] = parser.model;
break;
}
}
}
module.exports = DatabarExtXform;
+25
View File
@@ -0,0 +1,25 @@
const BaseXform = require('../../base-xform');
class FExtXform extends BaseXform {
get tag() {
return 'xm:f';
}
render(xmlStream, model) {
xmlStream.leafNode(this.tag, null, model);
}
parseOpen() {
this.model = '';
}
parseText(text) {
this.model += text;
}
parseClose(name) {
return name !== this.tag;
}
}
module.exports = FExtXform;
+73
View File
@@ -0,0 +1,73 @@
const BaseXform = require('../../base-xform');
const CompositeXform = require('../../composite-xform');
const CfvoExtXform = require('./cfvo-ext-xform');
const CfIconExtXform = require('./cf-icon-ext-xform');
class IconSetExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'x14:cfvo': (this.cfvoXform = new CfvoExtXform()),
'x14:cfIcon': (this.cfIconXform = new CfIconExtXform()),
};
}
get tag() {
return 'x14:iconSet';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag, {
iconSet: BaseXform.toStringAttribute(model.iconSet),
reverse: BaseXform.toBoolAttribute(model.reverse, false),
showValue: BaseXform.toBoolAttribute(model.showValue, true),
custom: BaseXform.toBoolAttribute(model.icons, false),
});
model.cfvo.forEach(cfvo => {
this.cfvoXform.render(xmlStream, cfvo);
});
if (model.icons) {
model.icons.forEach((icon, i) => {
icon.iconId = i;
this.cfIconXform.render(xmlStream, icon);
});
}
xmlStream.closeNode();
}
createNewModel({attributes}) {
return {
cfvo: [],
iconSet: BaseXform.toStringValue(attributes.iconSet, '3TrafficLights'),
reverse: BaseXform.toBoolValue(attributes.reverse, false),
showValue: BaseXform.toBoolValue(attributes.showValue, true),
};
}
onParserClose(name, parser) {
const [, prop] = name.split(':');
switch (prop) {
case 'cfvo':
this.model.cfvo.push(parser.model);
break;
case 'cfIcon':
if (!this.model.icons) {
this.model.icons = [];
}
this.model.icons.push(parser.model);
break;
default:
this.model[prop] = parser.model;
break;
}
}
}
module.exports = IconSetExtXform;
+25
View File
@@ -0,0 +1,25 @@
const BaseXform = require('../../base-xform');
class SqrefExtXform extends BaseXform {
get tag() {
return 'xm:sqref';
}
render(xmlStream, model) {
xmlStream.leafNode(this.tag, null, model);
}
parseOpen() {
this.model = '';
}
parseText(text) {
this.model += text;
}
parseClose(name) {
return name !== this.tag;
}
}
module.exports = SqrefExtXform;
+301
View File
@@ -0,0 +1,301 @@
const BaseXform = require('../../base-xform');
const CompositeXform = require('../../composite-xform');
const Range = require('../../../../doc/range');
const DatabarXform = require('./databar-xform');
const ExtLstRefXform = require('./ext-lst-ref-xform');
const FormulaXform = require('./formula-xform');
const ColorScaleXform = require('./color-scale-xform');
const IconSetXform = require('./icon-set-xform');
const extIcons = {
'3Triangles': true,
'3Stars': true,
'5Boxes': true,
};
const getTextFormula = model => {
if (model.formulae && model.formulae[0]) {
return model.formulae[0];
}
const range = new Range(model.ref);
const {tl} = range;
switch (model.operator) {
case 'containsText':
return `NOT(ISERROR(SEARCH("${model.text}",${tl})))`;
case 'containsBlanks':
return `LEN(TRIM(${tl}))=0`;
case 'notContainsBlanks':
return `LEN(TRIM(${tl}))>0`;
case 'containsErrors':
return `ISERROR(${tl})`;
case 'notContainsErrors':
return `NOT(ISERROR(${tl}))`;
default:
return undefined;
}
};
const getTimePeriodFormula = model => {
if (model.formulae && model.formulae[0]) {
return model.formulae[0];
}
const range = new Range(model.ref);
const {tl} = range;
switch (model.timePeriod) {
case 'thisWeek':
return `AND(TODAY()-ROUNDDOWN(${tl},0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(${tl},0)-TODAY()<=7-WEEKDAY(TODAY()))`;
case 'lastWeek':
return `AND(TODAY()-ROUNDDOWN(${tl},0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(${tl},0)<(WEEKDAY(TODAY())+7))`;
case 'nextWeek':
return `AND(ROUNDDOWN(${tl},0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(${tl},0)-TODAY()<(15-WEEKDAY(TODAY())))`;
case 'yesterday':
return `FLOOR(${tl},1)=TODAY()-1`;
case 'today':
return `FLOOR(${tl},1)=TODAY()`;
case 'tomorrow':
return `FLOOR(${tl},1)=TODAY()+1`;
case 'last7Days':
return `AND(TODAY()-FLOOR(${tl},1)<=6,FLOOR(${tl},1)<=TODAY())`;
case 'lastMonth':
return `AND(MONTH(${tl})=MONTH(EDATE(TODAY(),0-1)),YEAR(${tl})=YEAR(EDATE(TODAY(),0-1)))`;
case 'thisMonth':
return `AND(MONTH(${tl})=MONTH(TODAY()),YEAR(${tl})=YEAR(TODAY()))`;
case 'nextMonth':
return `AND(MONTH(${tl})=MONTH(EDATE(TODAY(),0+1)),YEAR(${tl})=YEAR(EDATE(TODAY(),0+1)))`;
default:
return undefined;
}
};
const opType = attributes => {
const {type, operator} = attributes;
switch (type) {
case 'containsText':
case 'containsBlanks':
case 'notContainsBlanks':
case 'containsErrors':
case 'notContainsErrors':
return {
type: 'containsText',
operator: type,
};
default:
return {type, operator};
}
};
class CfRuleXform extends CompositeXform {
constructor() {
super();
this.map = {
dataBar: (this.databarXform = new DatabarXform()),
extLst: (this.extLstRefXform = new ExtLstRefXform()),
formula: (this.formulaXform = new FormulaXform()),
colorScale: (this.colorScaleXform = new ColorScaleXform()),
iconSet: (this.iconSetXform = new IconSetXform()),
};
}
get tag() {
return 'cfRule';
}
static isPrimitive(rule) {
// is this rule primitive?
if (rule.type === 'iconSet') {
if (rule.custom || extIcons[rule.iconSet]) {
return false;
}
}
return true;
}
render(xmlStream, model) {
switch (model.type) {
case 'expression':
this.renderExpression(xmlStream, model);
break;
case 'cellIs':
this.renderCellIs(xmlStream, model);
break;
case 'top10':
this.renderTop10(xmlStream, model);
break;
case 'aboveAverage':
this.renderAboveAverage(xmlStream, model);
break;
case 'dataBar':
this.renderDataBar(xmlStream, model);
break;
case 'colorScale':
this.renderColorScale(xmlStream, model);
break;
case 'iconSet':
this.renderIconSet(xmlStream, model);
break;
case 'containsText':
this.renderText(xmlStream, model);
break;
case 'timePeriod':
this.renderTimePeriod(xmlStream, model);
break;
}
}
renderExpression(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: 'expression',
dxfId: model.dxfId,
priority: model.priority,
});
this.formulaXform.render(xmlStream, model.formulae[0]);
xmlStream.closeNode();
}
renderCellIs(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: 'cellIs',
dxfId: model.dxfId,
priority: model.priority,
operator: model.operator,
});
model.formulae.forEach(formula => {
this.formulaXform.render(xmlStream, formula);
});
xmlStream.closeNode();
}
renderTop10(xmlStream, model) {
xmlStream.leafNode(this.tag, {
type: 'top10',
dxfId: model.dxfId,
priority: model.priority,
percent: BaseXform.toBoolAttribute(model.percent, false),
bottom: BaseXform.toBoolAttribute(model.bottom, false),
rank: BaseXform.toIntValue(model.rank, 10, true),
});
}
renderAboveAverage(xmlStream, model) {
xmlStream.leafNode(this.tag, {
type: 'aboveAverage',
dxfId: model.dxfId,
priority: model.priority,
aboveAverage: BaseXform.toBoolAttribute(model.aboveAverage, true),
});
}
renderDataBar(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: 'dataBar',
priority: model.priority,
});
this.databarXform.render(xmlStream, model);
this.extLstRefXform.render(xmlStream, model);
xmlStream.closeNode();
}
renderColorScale(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: 'colorScale',
priority: model.priority,
});
this.colorScaleXform.render(xmlStream, model);
xmlStream.closeNode();
}
renderIconSet(xmlStream, model) {
// iconset is all primitive or all extLst
if (!CfRuleXform.isPrimitive(model)) {
return;
}
xmlStream.openNode(this.tag, {
type: 'iconSet',
priority: model.priority,
});
this.iconSetXform.render(xmlStream, model);
xmlStream.closeNode();
}
renderText(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: model.operator,
dxfId: model.dxfId,
priority: model.priority,
operator: BaseXform.toStringAttribute(model.operator, 'containsText'),
});
const formula = getTextFormula(model);
if (formula) {
this.formulaXform.render(xmlStream, formula);
}
xmlStream.closeNode();
}
renderTimePeriod(xmlStream, model) {
xmlStream.openNode(this.tag, {
type: 'timePeriod',
dxfId: model.dxfId,
priority: model.priority,
timePeriod: model.timePeriod,
});
const formula = getTimePeriodFormula(model);
if (formula) {
this.formulaXform.render(xmlStream, formula);
}
xmlStream.closeNode();
}
createNewModel({attributes}) {
return {
...opType(attributes),
dxfId: BaseXform.toIntValue(attributes.dxfId),
priority: BaseXform.toIntValue(attributes.priority),
timePeriod: attributes.timePeriod,
percent: BaseXform.toBoolValue(attributes.percent),
bottom: BaseXform.toBoolValue(attributes.bottom),
rank: BaseXform.toIntValue(attributes.rank),
aboveAverage: BaseXform.toBoolValue(attributes.aboveAverage),
};
}
onParserClose(name, parser) {
switch (name) {
case 'dataBar':
case 'extLst':
case 'colorScale':
case 'iconSet':
// merge parser model with ours
Object.assign(this.model, parser.model);
break;
case 'formula':
// except - formula is a string and appends to formulae
this.model.formulae = this.model.formulae || [];
this.model.formulae.push(parser.model);
break;
}
}
}
module.exports = CfRuleXform;
+27
View File
@@ -0,0 +1,27 @@
const BaseXform = require('../../base-xform');
class CfvoXform extends BaseXform {
get tag() {
return 'cfvo';
}
render(xmlStream, model) {
xmlStream.leafNode(this.tag, {
type: model.type,
val: model.value,
});
}
parseOpen(node) {
this.model = {
type: node.attributes.type,
value: BaseXform.toFloatValue(node.attributes.val),
};
}
parseClose(name) {
return name !== this.tag;
}
}
module.exports = CfvoXform;
+45
View File
@@ -0,0 +1,45 @@
const CompositeXform = require('../../composite-xform');
const ColorXform = require('../../style/color-xform');
const CfvoXform = require('./cfvo-xform');
class ColorScaleXform extends CompositeXform {
constructor() {
super();
this.map = {
cfvo: (this.cfvoXform = new CfvoXform()),
color: (this.colorXform = new ColorXform()),
};
}
get tag() {
return 'colorScale';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag);
model.cfvo.forEach(cfvo => {
this.cfvoXform.render(xmlStream, cfvo);
});
model.color.forEach(color => {
this.colorXform.render(xmlStream, color);
});
xmlStream.closeNode();
}
createNewModel(node) {
return {
cfvo: [],
color: [],
};
}
onParserClose(name, parser) {
this.model[name].push(parser.model);
}
}
module.exports = ColorScaleXform;
@@ -0,0 +1,48 @@
const CompositeXform = require('../../composite-xform');
const CfRuleXform = require('./cf-rule-xform');
class ConditionalFormattingXform extends CompositeXform {
constructor() {
super();
this.map = {
cfRule: new CfRuleXform(),
};
}
get tag() {
return 'conditionalFormatting';
}
render(xmlStream, model) {
// if there are no primitive rules, exit now
if (!model.rules.some(CfRuleXform.isPrimitive)) {
return;
}
xmlStream.openNode(this.tag, {sqref: model.ref});
model.rules.forEach(rule => {
if (CfRuleXform.isPrimitive(rule)) {
rule.ref = model.ref;
this.map.cfRule.render(xmlStream, rule);
}
});
xmlStream.closeNode();
}
createNewModel({attributes}) {
return {
ref: attributes.sqref,
rules: [],
};
}
onParserClose(name, parser) {
this.model.rules.push(parser.model);
}
}
module.exports = ConditionalFormattingXform;
@@ -0,0 +1,92 @@
const BaseXform = require('../../base-xform');
const ConditionalFormattingXform = require('./conditional-formatting-xform');
class ConditionalFormattingsXform extends BaseXform {
constructor() {
super();
this.cfXform = new ConditionalFormattingXform();
}
get tag() {
return 'conditionalFormatting';
}
reset() {
this.model = [];
}
prepare(model, options) {
// ensure each rule has a priority value
let nextPriority = model.reduce(
(p, cf) => Math.max(p, ...cf.rules.map(rule => rule.priority || 0)),
1
);
model.forEach(cf => {
cf.rules.forEach(rule => {
if (!rule.priority) {
rule.priority = nextPriority++;
}
if (rule.style) {
rule.dxfId = options.styles.addDxfStyle(rule.style);
}
});
});
}
render(xmlStream, model) {
model.forEach(cf => {
this.cfXform.render(xmlStream, cf);
});
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
switch (node.name) {
case 'conditionalFormatting':
this.parser = this.cfXform;
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.push(this.parser.model);
this.parser = undefined;
return false;
}
return true;
}
return false;
}
reconcile(model, options) {
model.forEach(cf => {
cf.rules.forEach(rule => {
if (rule.dxfId !== undefined) {
rule.style = options.styles.getDxfStyle(rule.dxfId);
delete rule.dxfId;
}
});
});
}
}
module.exports = ConditionalFormattingsXform;
+49
View File
@@ -0,0 +1,49 @@
const CompositeXform = require('../../composite-xform');
const ColorXform = require('../../style/color-xform');
const CfvoXform = require('./cfvo-xform');
class DatabarXform extends CompositeXform {
constructor() {
super();
this.map = {
cfvo: (this.cfvoXform = new CfvoXform()),
color: (this.colorXform = new ColorXform()),
};
}
get tag() {
return 'dataBar';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag);
model.cfvo.forEach(cfvo => {
this.cfvoXform.render(xmlStream, cfvo);
});
this.colorXform.render(xmlStream, model.color);
xmlStream.closeNode();
}
createNewModel() {
return {
cfvo: [],
};
}
onParserClose(name, parser) {
switch (name) {
case 'cfvo':
this.model.cfvo.push(parser.model);
break;
case 'color':
this.model.color = parser.model;
break;
}
}
}
module.exports = DatabarXform;
+87
View File
@@ -0,0 +1,87 @@
/* eslint-disable max-classes-per-file */
const BaseXform = require('../../base-xform');
const CompositeXform = require('../../composite-xform');
class X14IdXform extends BaseXform {
get tag() {
return 'x14:id';
}
render(xmlStream, model) {
xmlStream.leafNode(this.tag, null, model);
}
parseOpen() {
this.model = '';
}
parseText(text) {
this.model += text;
}
parseClose(name) {
return name !== this.tag;
}
}
class ExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'x14:id': (this.idXform = new X14IdXform()),
};
}
get tag() {
return 'ext';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag, {
uri: '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}',
'xmlns:x14': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main',
});
this.idXform.render(xmlStream, model.x14Id);
xmlStream.closeNode();
}
createNewModel() {
return {};
}
onParserClose(name, parser) {
this.model.x14Id = parser.model;
}
}
class ExtLstRefXform extends CompositeXform {
constructor() {
super();
this.map = {
ext: new ExtXform(),
};
}
get tag() {
return 'extLst';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag);
this.map.ext.render(xmlStream, model);
xmlStream.closeNode();
}
createNewModel() {
return {};
}
onParserClose(name, parser) {
Object.assign(this.model, parser.model);
}
}
module.exports = ExtLstRefXform;
+25
View File
@@ -0,0 +1,25 @@
const BaseXform = require('../../base-xform');
class FormulaXform extends BaseXform {
get tag() {
return 'formula';
}
render(xmlStream, model) {
xmlStream.leafNode(this.tag, null, model);
}
parseOpen() {
this.model = '';
}
parseText(text) {
this.model += text;
}
parseClose(name) {
return name !== this.tag;
}
}
module.exports = FormulaXform;
+47
View File
@@ -0,0 +1,47 @@
const BaseXform = require('../../base-xform');
const CompositeXform = require('../../composite-xform');
const CfvoXform = require('./cfvo-xform');
class IconSetXform extends CompositeXform {
constructor() {
super();
this.map = {
cfvo: (this.cfvoXform = new CfvoXform()),
};
}
get tag() {
return 'iconSet';
}
render(xmlStream, model) {
xmlStream.openNode(this.tag, {
iconSet: BaseXform.toStringAttribute(model.iconSet, '3TrafficLights'),
reverse: BaseXform.toBoolAttribute(model.reverse, false),
showValue: BaseXform.toBoolAttribute(model.showValue, true),
});
model.cfvo.forEach(cfvo => {
this.cfvoXform.render(xmlStream, cfvo);
});
xmlStream.closeNode();
}
createNewModel({attributes}) {
return {
iconSet: BaseXform.toStringValue(attributes.iconSet, '3TrafficLights'),
reverse: BaseXform.toBoolValue(attributes.reverse),
showValue: BaseXform.toBoolValue(attributes.showValue),
cfvo: [],
};
}
onParserClose(name, parser) {
this.model[name].push(parser.model);
}
}
module.exports = IconSetXform;
+86
View File
@@ -0,0 +1,86 @@
const utils = require('../../../utils/utils');
const BaseXform = require('../base-xform');
class ColXform extends BaseXform {
get tag() {
return 'col';
}
prepare(model, options) {
const styleId = options.styles.addStyleModel(model.style || {});
if (styleId) {
model.styleId = styleId;
}
}
render(xmlStream, model) {
xmlStream.openNode('col');
xmlStream.addAttribute('min', model.min);
xmlStream.addAttribute('max', model.max);
if (model.width) {
xmlStream.addAttribute('width', model.width);
}
if (model.styleId) {
xmlStream.addAttribute('style', model.styleId);
}
if (model.hidden) {
xmlStream.addAttribute('hidden', '1');
}
if (model.bestFit) {
xmlStream.addAttribute('bestFit', '1');
}
if (model.outlineLevel) {
xmlStream.addAttribute('outlineLevel', model.outlineLevel);
}
if (model.collapsed) {
xmlStream.addAttribute('collapsed', '1');
}
xmlStream.addAttribute('customWidth', '1');
xmlStream.closeNode();
}
parseOpen(node) {
if (node.name === 'col') {
const model = (this.model = {
min: parseInt(node.attributes.min || '0', 10),
max: parseInt(node.attributes.max || '0', 10),
width:
node.attributes.width === undefined
? undefined
: parseFloat(node.attributes.width || '0'),
});
if (node.attributes.style) {
model.styleId = parseInt(node.attributes.style, 10);
}
if (utils.parseBoolean(node.attributes.hidden)) {
model.hidden = true;
}
if (utils.parseBoolean(node.attributes.bestFit)) {
model.bestFit = true;
}
if (node.attributes.outlineLevel) {
model.outlineLevel = parseInt(node.attributes.outlineLevel, 10);
}
if (utils.parseBoolean(node.attributes.collapsed)) {
model.collapsed = true;
}
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
reconcile(model, options) {
// reconcile column styles
if (model.styleId) {
model.style = options.styles.getStyleModel(model.styleId);
}
}
}
module.exports = ColXform;
+257
View File
@@ -0,0 +1,257 @@
const _ = require('../../../utils/under-dash');
const utils = require('../../../utils/utils');
const colCache = require('../../../utils/col-cache');
const BaseXform = require('../base-xform');
const Range = require('../../../doc/range');
function assign(definedName, attributes, name, defaultValue) {
const value = attributes[name];
if (value !== undefined) {
definedName[name] = value;
} else if (defaultValue !== undefined) {
definedName[name] = defaultValue;
}
}
function assignBool(definedName, attributes, name, defaultValue) {
const value = attributes[name];
if (value !== undefined) {
definedName[name] = utils.parseBoolean(value);
} else if (defaultValue !== undefined) {
definedName[name] = defaultValue;
}
}
function optimiseDataValidations(model) {
// Squeeze alike data validations together into rectangular ranges
// to reduce file size and speed up Excel load time
const dvList = _.map(model, (dataValidation, address) => ({
address,
dataValidation,
marked: false,
})).sort((a, b) => _.strcmp(a.address, b.address));
const dvMap = _.keyBy(dvList, 'address');
const matchCol = (addr, height, col) => {
for (let i = 0; i < height; i++) {
const otherAddress = colCache.encodeAddress(addr.row + i, col);
if (!model[otherAddress] || !_.isEqual(model[addr.address], model[otherAddress])) {
return false;
}
}
return true;
};
return dvList
.map(dv => {
if (!dv.marked) {
const addr = colCache.decodeEx(dv.address);
if (addr.dimensions) {
dvMap[addr.dimensions].marked = true;
return {
...dv.dataValidation,
sqref: dv.address,
};
}
// iterate downwards - finding matching cells
let height = 1;
let otherAddress = colCache.encodeAddress(addr.row + height, addr.col);
while (model[otherAddress] && _.isEqual(dv.dataValidation, model[otherAddress])) {
height++;
otherAddress = colCache.encodeAddress(addr.row + height, addr.col);
}
// iterate rightwards...
let width = 1;
while (matchCol(addr, height, addr.col + width)) {
width++;
}
// mark all included addresses
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
otherAddress = colCache.encodeAddress(addr.row + i, addr.col + j);
dvMap[otherAddress].marked = true;
}
}
if (height > 1 || width > 1) {
const bottom = addr.row + (height - 1);
const right = addr.col + (width - 1);
return {
...dv.dataValidation,
sqref: `${dv.address}:${colCache.encodeAddress(bottom, right)}`,
};
}
return {
...dv.dataValidation,
sqref: dv.address,
};
}
return null;
})
.filter(Boolean);
}
class DataValidationsXform extends BaseXform {
get tag() {
return 'dataValidations';
}
render(xmlStream, model) {
const optimizedModel = optimiseDataValidations(model);
if (optimizedModel.length) {
xmlStream.openNode('dataValidations', {count: optimizedModel.length});
optimizedModel.forEach(value => {
xmlStream.openNode('dataValidation');
if (value.type !== 'any') {
xmlStream.addAttribute('type', value.type);
if (value.operator && value.type !== 'list' && value.operator !== 'between') {
xmlStream.addAttribute('operator', value.operator);
}
if (value.allowBlank) {
xmlStream.addAttribute('allowBlank', '1');
}
}
if (value.showInputMessage) {
xmlStream.addAttribute('showInputMessage', '1');
}
if (value.promptTitle) {
xmlStream.addAttribute('promptTitle', value.promptTitle);
}
if (value.prompt) {
xmlStream.addAttribute('prompt', value.prompt);
}
if (value.showErrorMessage) {
xmlStream.addAttribute('showErrorMessage', '1');
}
if (value.errorStyle) {
xmlStream.addAttribute('errorStyle', value.errorStyle);
}
if (value.errorTitle) {
xmlStream.addAttribute('errorTitle', value.errorTitle);
}
if (value.error) {
xmlStream.addAttribute('error', value.error);
}
xmlStream.addAttribute('sqref', value.sqref);
(value.formulae || []).forEach((formula, index) => {
xmlStream.openNode(`formula${index + 1}`);
if (value.type === 'date') {
xmlStream.writeText(utils.dateToExcel(new Date(formula)));
} else {
xmlStream.writeText(formula);
}
xmlStream.closeNode();
});
xmlStream.closeNode();
});
xmlStream.closeNode();
}
}
parseOpen(node) {
switch (node.name) {
case 'dataValidations':
this.model = {};
return true;
case 'dataValidation': {
this._address = node.attributes.sqref;
const dataValidation = {type: node.attributes.type || 'any', formulae: []};
if (node.attributes.type) {
assignBool(dataValidation, node.attributes, 'allowBlank');
}
assignBool(dataValidation, node.attributes, 'showInputMessage');
assignBool(dataValidation, node.attributes, 'showErrorMessage');
switch (dataValidation.type) {
case 'any':
case 'list':
case 'custom':
break;
default:
assign(dataValidation, node.attributes, 'operator', 'between');
break;
}
assign(dataValidation, node.attributes, 'promptTitle');
assign(dataValidation, node.attributes, 'prompt');
assign(dataValidation, node.attributes, 'errorStyle');
assign(dataValidation, node.attributes, 'errorTitle');
assign(dataValidation, node.attributes, 'error');
this._dataValidation = dataValidation;
return true;
}
case 'formula1':
case 'formula2':
this._formula = [];
return true;
default:
return false;
}
}
parseText(text) {
if (this._formula) {
this._formula.push(text);
}
}
parseClose(name) {
switch (name) {
case 'dataValidations':
return false;
case 'dataValidation': {
if (!this._dataValidation.formulae || !this._dataValidation.formulae.length) {
delete this._dataValidation.formulae;
delete this._dataValidation.operator;
}
// The four known cases: 1. E4:L9 N4:U9 2.E4 L9 3. N4:U9 4. E4
const list = this._address.split(/\s+/g) || [];
list.forEach(addr => {
if (addr.includes(':')) {
const range = new Range(addr);
range.forEachAddress(address => {
this.model[address] = this._dataValidation;
});
} else {
this.model[addr] = this._dataValidation;
}
});
return true;
}
case 'formula1':
case 'formula2': {
let formula = this._formula.join('');
switch (this._dataValidation.type) {
case 'whole':
case 'textLength':
formula = parseInt(formula, 10);
break;
case 'decimal':
formula = parseFloat(formula);
break;
case 'date':
formula = utils.excelToDate(parseFloat(formula));
break;
default:
break;
}
this._dataValidation.formulae.push(formula);
this._formula = undefined;
return true;
}
default:
return true;
}
}
}
module.exports = DataValidationsXform;
+29
View File
@@ -0,0 +1,29 @@
const BaseXform = require('../base-xform');
class DimensionXform extends BaseXform {
get tag() {
return 'dimension';
}
render(xmlStream, model) {
if (model) {
xmlStream.leafNode('dimension', {ref: model});
}
}
parseOpen(node) {
if (node.name === 'dimension') {
this.model = node.attributes.ref;
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = DimensionXform;
+33
View File
@@ -0,0 +1,33 @@
const BaseXform = require('../base-xform');
class DrawingXform extends BaseXform {
get tag() {
return 'drawing';
}
render(xmlStream, model) {
if (model) {
xmlStream.leafNode(this.tag, {'r:id': model.rId});
}
}
parseOpen(node) {
switch (node.name) {
case this.tag:
this.model = {
rId: node.attributes['r:id'],
};
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = DrawingXform;
+86
View File
@@ -0,0 +1,86 @@
/* eslint-disable max-classes-per-file */
const CompositeXform = require('../composite-xform');
const ConditionalFormattingsExt = require('./cf-ext/conditional-formattings-ext-xform');
class ExtXform extends CompositeXform {
constructor() {
super();
this.map = {
'x14:conditionalFormattings': (this.conditionalFormattings = new ConditionalFormattingsExt()),
};
}
get tag() {
return 'ext';
}
hasContent(model) {
return this.conditionalFormattings.hasContent(model.conditionalFormattings);
}
prepare(model, options) {
this.conditionalFormattings.prepare(model.conditionalFormattings, options);
}
render(xmlStream, model) {
xmlStream.openNode('ext', {
uri: '{78C0D931-6437-407d-A8EE-F0AAD7539E65}',
'xmlns:x14': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main',
});
this.conditionalFormattings.render(xmlStream, model.conditionalFormattings);
xmlStream.closeNode();
}
createNewModel() {
return {};
}
onParserClose(name, parser) {
this.model[name] = parser.model;
}
}
class ExtLstXform extends CompositeXform {
constructor() {
super();
this.map = {
ext: (this.ext = new ExtXform()),
};
}
get tag() {
return 'extLst';
}
prepare(model, options) {
this.ext.prepare(model, options);
}
hasContent(model) {
return this.ext.hasContent(model);
}
render(xmlStream, model) {
if (!this.hasContent(model)) {
return;
}
xmlStream.openNode('extLst');
this.ext.render(xmlStream, model);
xmlStream.closeNode();
}
createNewModel() {
return {};
}
onParserClose(name, parser) {
Object.assign(this.model, parser.model);
}
}
module.exports = ExtLstXform;
+146
View File
@@ -0,0 +1,146 @@
const BaseXform = require('../base-xform');
class HeaderFooterXform extends BaseXform {
get tag() {
return 'headerFooter';
}
render(xmlStream, model) {
if (model) {
xmlStream.addRollback();
let createTag = false;
xmlStream.openNode('headerFooter');
if (model.differentFirst) {
xmlStream.addAttribute('differentFirst', '1');
createTag = true;
}
if (model.differentOddEven) {
xmlStream.addAttribute('differentOddEven', '1');
createTag = true;
}
if (model.oddHeader && typeof model.oddHeader === 'string') {
xmlStream.leafNode('oddHeader', null, model.oddHeader);
createTag = true;
}
if (model.oddFooter && typeof model.oddFooter === 'string') {
xmlStream.leafNode('oddFooter', null, model.oddFooter);
createTag = true;
}
if (model.evenHeader && typeof model.evenHeader === 'string') {
xmlStream.leafNode('evenHeader', null, model.evenHeader);
createTag = true;
}
if (model.evenFooter && typeof model.evenFooter === 'string') {
xmlStream.leafNode('evenFooter', null, model.evenFooter);
createTag = true;
}
if (model.firstHeader && typeof model.firstHeader === 'string') {
xmlStream.leafNode('firstHeader', null, model.firstHeader);
createTag = true;
}
if (model.firstFooter && typeof model.firstFooter === 'string') {
xmlStream.leafNode('firstFooter', null, model.firstFooter);
createTag = true;
}
if (createTag) {
xmlStream.closeNode();
xmlStream.commit();
} else {
xmlStream.rollback();
}
}
}
parseOpen(node) {
switch (node.name) {
case 'headerFooter':
this.model = {};
if (node.attributes.differentFirst) {
this.model.differentFirst = parseInt(node.attributes.differentFirst, 0) === 1;
}
if (node.attributes.differentOddEven) {
this.model.differentOddEven = parseInt(node.attributes.differentOddEven, 0) === 1;
}
return true;
case 'oddHeader':
this.currentNode = 'oddHeader';
return true;
case 'oddFooter':
this.currentNode = 'oddFooter';
return true;
case 'evenHeader':
this.currentNode = 'evenHeader';
return true;
case 'evenFooter':
this.currentNode = 'evenFooter';
return true;
case 'firstHeader':
this.currentNode = 'firstHeader';
return true;
case 'firstFooter':
this.currentNode = 'firstFooter';
return true;
default:
return false;
}
}
parseText(text) {
switch (this.currentNode) {
case 'oddHeader':
this.model.oddHeader = text;
break;
case 'oddFooter':
this.model.oddFooter = text;
break;
case 'evenHeader':
this.model.evenHeader = text;
break;
case 'evenFooter':
this.model.evenFooter = text;
break;
case 'firstHeader':
this.model.firstHeader = text;
break;
case 'firstFooter':
this.model.firstFooter = text;
break;
default:
break;
}
}
parseClose() {
switch (this.currentNode) {
case 'oddHeader':
case 'oddFooter':
case 'evenHeader':
case 'evenFooter':
case 'firstHeader':
case 'firstFooter':
this.currentNode = undefined;
return true;
default:
return false;
}
}
}
module.exports = HeaderFooterXform;
+54
View File
@@ -0,0 +1,54 @@
const BaseXform = require('../base-xform');
class HyperlinkXform extends BaseXform {
get tag() {
return 'hyperlink';
}
render(xmlStream, model) {
if (this.isInternalLink(model)) {
xmlStream.leafNode('hyperlink', {
ref: model.address,
'r:id': model.rId,
tooltip: model.tooltip,
location: model.target,
});
} else {
xmlStream.leafNode('hyperlink', {
ref: model.address,
'r:id': model.rId,
tooltip: model.tooltip,
});
}
}
parseOpen(node) {
if (node.name === 'hyperlink') {
this.model = {
address: node.attributes.ref,
rId: node.attributes['r:id'],
tooltip: node.attributes.tooltip,
};
// This is an internal link
if (node.attributes.location) {
this.model.target = node.attributes.location;
}
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
isInternalLink(model) {
// @example: Sheet2!D3, return true
return model.target && /^[^!]+![a-zA-Z]+[\d]+$/.test(model.target);
}
}
module.exports = HyperlinkXform;
+27
View File
@@ -0,0 +1,27 @@
const BaseXform = require('../base-xform');
class MergeCellXform extends BaseXform {
get tag() {
return 'mergeCell';
}
render(xmlStream, model) {
xmlStream.leafNode('mergeCell', {ref: model});
}
parseOpen(node) {
if (node.name === 'mergeCell') {
this.model = node.attributes.ref;
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = MergeCellXform;
+56
View File
@@ -0,0 +1,56 @@
const _ = require('../../../utils/under-dash');
const Range = require('../../../doc/range');
const colCache = require('../../../utils/col-cache');
const Enums = require('../../../doc/enums');
class Merges {
constructor() {
// optional mergeCells is array of ranges (like the xml)
this.merges = {};
}
add(merge) {
// merge is {address, master}
if (this.merges[merge.master]) {
this.merges[merge.master].expandToAddress(merge.address);
} else {
const range = `${merge.master}:${merge.address}`;
this.merges[merge.master] = new Range(range);
}
}
get mergeCells() {
return _.map(this.merges, merge => merge.range);
}
reconcile(mergeCells, rows) {
// reconcile merge list with merge cells
_.each(mergeCells, merge => {
const dimensions = colCache.decode(merge);
for (let i = dimensions.top; i <= dimensions.bottom; i++) {
const row = rows[i - 1];
for (let j = dimensions.left; j <= dimensions.right; j++) {
const cell = row.cells[j - 1];
if (!cell) {
// nulls are not included in document - so if master cell has no value - add a null one here
row.cells[j] = {
type: Enums.ValueType.Null,
address: colCache.encodeAddress(i, j),
};
} else if (cell.type === Enums.ValueType.Merge) {
cell.master = dimensions.tl;
}
}
}
});
}
getMasterAddress(address) {
// if address has been merged, return its master's address. Assumes reconcile has been called
const range = this.hash[address];
return range && range.tl;
}
}
module.exports = Merges;
+43
View File
@@ -0,0 +1,43 @@
const BaseXform = require('../base-xform');
const isDefined = attr => typeof attr !== 'undefined';
class OutlinePropertiesXform extends BaseXform {
get tag() {
return 'outlinePr';
}
render(xmlStream, model) {
if (model && (isDefined(model.summaryBelow) || isDefined(model.summaryRight))) {
xmlStream.leafNode(this.tag, {
summaryBelow: isDefined(model.summaryBelow) ? Number(model.summaryBelow) : undefined,
summaryRight: isDefined(model.summaryRight) ? Number(model.summaryRight) : undefined,
});
return true;
}
return false;
}
parseOpen(node) {
if (node.name === this.tag) {
this.model = {
summaryBelow: isDefined(node.attributes.summaryBelow)
? Boolean(Number(node.attributes.summaryBelow))
: undefined,
summaryRight: isDefined(node.attributes.summaryRight)
? Boolean(Number(node.attributes.summaryRight))
: undefined,
};
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = OutlinePropertiesXform;
+27
View File
@@ -0,0 +1,27 @@
const BaseXform = require('../base-xform');
class PageBreaksXform extends BaseXform {
get tag() {
return 'brk';
}
render(xmlStream, model) {
xmlStream.leafNode('brk', model);
}
parseOpen(node) {
if (node.name === 'brk') {
this.model = node.attributes.ref;
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = PageBreaksXform;
+49
View File
@@ -0,0 +1,49 @@
const _ = require('../../../utils/under-dash');
const BaseXform = require('../base-xform');
class PageMarginsXform extends BaseXform {
get tag() {
return 'pageMargins';
}
render(xmlStream, model) {
if (model) {
const attributes = {
left: model.left,
right: model.right,
top: model.top,
bottom: model.bottom,
header: model.header,
footer: model.footer,
};
if (_.some(attributes, value => value !== undefined)) {
xmlStream.leafNode(this.tag, attributes);
}
}
}
parseOpen(node) {
switch (node.name) {
case this.tag:
this.model = {
left: parseFloat(node.attributes.left || 0.7),
right: parseFloat(node.attributes.right || 0.7),
top: parseFloat(node.attributes.top || 0.75),
bottom: parseFloat(node.attributes.bottom || 0.75),
header: parseFloat(node.attributes.header || 0.3),
footer: parseFloat(node.attributes.footer || 0.3),
};
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = PageMarginsXform;
@@ -0,0 +1,35 @@
const BaseXform = require('../base-xform');
class PageSetupPropertiesXform extends BaseXform {
get tag() {
return 'pageSetUpPr';
}
render(xmlStream, model) {
if (model && model.fitToPage) {
xmlStream.leafNode(this.tag, {
fitToPage: model.fitToPage ? '1' : undefined,
});
return true;
}
return false;
}
parseOpen(node) {
if (node.name === this.tag) {
this.model = {
fitToPage: node.attributes.fitToPage === '1',
};
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = PageSetupPropertiesXform;
+103
View File
@@ -0,0 +1,103 @@
const _ = require('../../../utils/under-dash');
const BaseXform = require('../base-xform');
function booleanToXml(model) {
return model ? '1' : undefined;
}
function pageOrderToXml(model) {
switch (model) {
case 'overThenDown':
return model;
default:
return undefined;
}
}
function cellCommentsToXml(model) {
switch (model) {
case 'atEnd':
case 'asDisplyed':
return model;
default:
return undefined;
}
}
function errorsToXml(model) {
switch (model) {
case 'dash':
case 'blank':
case 'NA':
return model;
default:
return undefined;
}
}
function pageSizeToModel(value) {
return value !== undefined ? parseInt(value, 10) : undefined;
}
class PageSetupXform extends BaseXform {
get tag() {
return 'pageSetup';
}
render(xmlStream, model) {
if (model) {
const attributes = {
paperSize: model.paperSize,
orientation: model.orientation,
horizontalDpi: model.horizontalDpi,
verticalDpi: model.verticalDpi,
pageOrder: pageOrderToXml(model.pageOrder),
blackAndWhite: booleanToXml(model.blackAndWhite),
draft: booleanToXml(model.draft),
cellComments: cellCommentsToXml(model.cellComments),
errors: errorsToXml(model.errors),
scale: model.scale,
fitToWidth: model.fitToWidth,
fitToHeight: model.fitToHeight,
firstPageNumber: model.firstPageNumber,
useFirstPageNumber: booleanToXml(model.firstPageNumber),
usePrinterDefaults: booleanToXml(model.usePrinterDefaults),
copies: model.copies,
};
if (_.some(attributes, value => value !== undefined)) {
xmlStream.leafNode(this.tag, attributes);
}
}
}
parseOpen(node) {
switch (node.name) {
case this.tag:
this.model = {
paperSize: pageSizeToModel(node.attributes.paperSize),
orientation: node.attributes.orientation || 'portrait',
horizontalDpi: parseInt(node.attributes.horizontalDpi || '4294967295', 10),
verticalDpi: parseInt(node.attributes.verticalDpi || '4294967295', 10),
pageOrder: node.attributes.pageOrder || 'downThenOver',
blackAndWhite: node.attributes.blackAndWhite === '1',
draft: node.attributes.draft === '1',
cellComments: node.attributes.cellComments || 'None',
errors: node.attributes.errors || 'displayed',
scale: parseInt(node.attributes.scale || '100', 10),
fitToWidth: parseInt(node.attributes.fitToWidth || '1', 10),
fitToHeight: parseInt(node.attributes.fitToHeight || '1', 10),
firstPageNumber: parseInt(node.attributes.firstPageNumber || '1', 10),
useFirstPageNumber: node.attributes.useFirstPageNumber === '1',
usePrinterDefaults: node.attributes.usePrinterDefaults === '1',
copies: parseInt(node.attributes.copies || '1', 10),
};
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = PageSetupXform;
+33
View File
@@ -0,0 +1,33 @@
const BaseXform = require('../base-xform');
class PictureXform extends BaseXform {
get tag() {
return 'picture';
}
render(xmlStream, model) {
if (model) {
xmlStream.leafNode(this.tag, {'r:id': model.rId});
}
}
parseOpen(node) {
switch (node.name) {
case this.tag:
this.model = {
rId: node.attributes['r:id'],
};
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = PictureXform;
+49
View File
@@ -0,0 +1,49 @@
const _ = require('../../../utils/under-dash');
const BaseXform = require('../base-xform');
function booleanToXml(model) {
return model ? '1' : undefined;
}
class PrintOptionsXform extends BaseXform {
get tag() {
return 'printOptions';
}
render(xmlStream, model) {
if (model) {
const attributes = {
headings: booleanToXml(model.showRowColHeaders),
gridLines: booleanToXml(model.showGridLines),
horizontalCentered: booleanToXml(model.horizontalCentered),
verticalCentered: booleanToXml(model.verticalCentered),
};
if (_.some(attributes, value => value !== undefined)) {
xmlStream.leafNode(this.tag, attributes);
}
}
}
parseOpen(node) {
switch (node.name) {
case this.tag:
this.model = {
showRowColHeaders: node.attributes.headings === '1',
showGridLines: node.attributes.gridLines === '1',
horizontalCentered: node.attributes.horizontalCentered === '1',
verticalCentered: node.attributes.verticalCentered === '1',
};
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = PrintOptionsXform;
+39
View File
@@ -0,0 +1,39 @@
'use strict';
const PageBreaksXform = require('./page-breaks-xform');
const ListXform = require('../list-xform');
class RowBreaksXform extends ListXform {
constructor() {
const options = {
tag: 'rowBreaks',
count: true,
childXform: new PageBreaksXform(),
};
super(options);
}
// get tag() { return 'rowBreaks'; }
render(xmlStream, model) {
if (model && model.length) {
xmlStream.openNode(this.tag, this.$);
if (this.count) {
xmlStream.addAttribute(this.$count, model.length);
xmlStream.addAttribute('manualBreakCount', model.length);
}
const {childXform} = this;
model.forEach(childModel => {
childXform.render(xmlStream, childModel);
});
xmlStream.closeNode();
} else if (this.empty) {
xmlStream.leafNode(this.tag);
}
}
}
module.exports = RowBreaksXform;
+142
View File
@@ -0,0 +1,142 @@
const BaseXform = require('../base-xform');
const utils = require('../../../utils/utils');
const CellXform = require('./cell-xform');
class RowXform extends BaseXform {
constructor(options) {
super();
this.maxItems = options && options.maxItems;
this.map = {
c: new CellXform(),
};
}
get tag() {
return 'row';
}
prepare(model, options) {
const styleId = options.styles.addStyleModel(model.style);
if (styleId) {
model.styleId = styleId;
}
const cellXform = this.map.c;
model.cells.forEach(cellModel => {
cellXform.prepare(cellModel, options);
});
}
render(xmlStream, model, options) {
xmlStream.openNode('row');
xmlStream.addAttribute('r', model.number);
if (model.height) {
xmlStream.addAttribute('ht', model.height);
xmlStream.addAttribute('customHeight', '1');
}
if (model.hidden) {
xmlStream.addAttribute('hidden', '1');
}
if (model.min > 0 && model.max > 0 && model.min <= model.max) {
xmlStream.addAttribute('spans', `${model.min}:${model.max}`);
}
if (model.styleId) {
xmlStream.addAttribute('s', model.styleId);
xmlStream.addAttribute('customFormat', '1');
}
xmlStream.addAttribute('x14ac:dyDescent', '0.25');
if (model.outlineLevel) {
xmlStream.addAttribute('outlineLevel', model.outlineLevel);
}
if (model.collapsed) {
xmlStream.addAttribute('collapsed', '1');
}
const cellXform = this.map.c;
model.cells.forEach(cellModel => {
cellXform.render(xmlStream, cellModel, options);
});
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
if (node.name === 'row') {
this.numRowsSeen += 1;
const spans = node.attributes.spans
? node.attributes.spans.split(':').map(span => parseInt(span, 10))
: [undefined, undefined];
const model = (this.model = {
number: parseInt(node.attributes.r, 10),
min: spans[0],
max: spans[1],
cells: [],
});
if (node.attributes.s) {
model.styleId = parseInt(node.attributes.s, 10);
}
if (utils.parseBoolean(node.attributes.hidden)) {
model.hidden = true;
}
if (utils.parseBoolean(node.attributes.bestFit)) {
model.bestFit = true;
}
if (node.attributes.ht) {
model.height = parseFloat(node.attributes.ht);
}
if (node.attributes.outlineLevel) {
model.outlineLevel = parseInt(node.attributes.outlineLevel, 10);
}
if (utils.parseBoolean(node.attributes.collapsed)) {
model.collapsed = true;
}
return true;
}
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.cells.push(this.parser.model);
if (this.maxItems && this.model.cells.length > this.maxItems) {
throw new Error(`Max column count (${this.maxItems}) exceeded`);
}
this.parser = undefined;
}
return true;
}
return false;
}
reconcile(model, options) {
model.style = model.styleId ? options.styles.getStyleModel(model.styleId) : {};
if (model.styleId !== undefined) {
model.styleId = undefined;
}
const cellXform = this.map.c;
model.cells.forEach(cellModel => {
cellXform.reconcile(cellModel, options);
});
}
}
module.exports = RowXform;
@@ -0,0 +1,55 @@
const _ = require('../../../utils/under-dash');
const BaseXform = require('../base-xform');
class SheetFormatPropertiesXform extends BaseXform {
get tag() {
return 'sheetFormatPr';
}
render(xmlStream, model) {
if (model) {
const attributes = {
defaultRowHeight: model.defaultRowHeight,
outlineLevelRow: model.outlineLevelRow,
outlineLevelCol: model.outlineLevelCol,
'x14ac:dyDescent': model.dyDescent,
};
if (model.defaultColWidth) {
attributes.defaultColWidth = model.defaultColWidth;
}
// default value for 'defaultRowHeight' is 15, this should not be 'custom'
if (!model.defaultRowHeight || model.defaultRowHeight !== 15) {
attributes.customHeight = '1';
}
if (_.some(attributes, value => value !== undefined)) {
xmlStream.leafNode('sheetFormatPr', attributes);
}
}
}
parseOpen(node) {
if (node.name === 'sheetFormatPr') {
this.model = {
defaultRowHeight: parseFloat(node.attributes.defaultRowHeight || '0'),
dyDescent: parseFloat(node.attributes['x14ac:dyDescent'] || '0'),
outlineLevelRow: parseInt(node.attributes.outlineLevelRow || '0', 10),
outlineLevelCol: parseInt(node.attributes.outlineLevelCol || '0', 10),
};
if (node.attributes.defaultColWidth) {
this.model.defaultColWidth = parseFloat(node.attributes.defaultColWidth);
}
return true;
}
return false;
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = SheetFormatPropertiesXform;
+90
View File
@@ -0,0 +1,90 @@
const BaseXform = require('../base-xform');
const ColorXform = require('../style/color-xform');
const PageSetupPropertiesXform = require('./page-setup-properties-xform');
const OutlinePropertiesXform = require('./outline-properties-xform');
class SheetPropertiesXform extends BaseXform {
constructor() {
super();
this.map = {
tabColor: new ColorXform('tabColor'),
pageSetUpPr: new PageSetupPropertiesXform(),
outlinePr: new OutlinePropertiesXform(),
};
}
get tag() {
return 'sheetPr';
}
render(xmlStream, model) {
if (model) {
xmlStream.addRollback();
xmlStream.openNode('sheetPr');
let inner = false;
inner = this.map.tabColor.render(xmlStream, model.tabColor) || inner;
inner = this.map.pageSetUpPr.render(xmlStream, model.pageSetup) || inner;
inner = this.map.outlinePr.render(xmlStream, model.outlineProperties) || inner;
if (inner) {
xmlStream.closeNode();
xmlStream.commit();
} else {
xmlStream.rollback();
}
}
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
if (node.name === this.tag) {
this.reset();
return true;
}
if (this.map[node.name]) {
this.parser = this.map[node.name];
this.parser.parseOpen(node);
return true;
}
return false;
}
parseText(text) {
if (this.parser) {
this.parser.parseText(text);
return true;
}
return false;
}
parseClose(name) {
if (this.parser) {
if (!this.parser.parseClose(name)) {
this.parser = undefined;
}
return true;
}
if (this.map.tabColor.model || this.map.pageSetUpPr.model || this.map.outlinePr.model) {
this.model = {};
if (this.map.tabColor.model) {
this.model.tabColor = this.map.tabColor.model;
}
if (this.map.pageSetUpPr.model) {
this.model.pageSetup = this.map.pageSetUpPr.model;
}
if (this.map.outlinePr.model) {
this.model.outlineProperties = this.map.outlinePr.model;
}
} else {
this.model = null;
}
return false;
}
}
module.exports = SheetPropertiesXform;
+89
View File
@@ -0,0 +1,89 @@
const _ = require('../../../utils/under-dash');
const BaseXform = require('../base-xform');
function booleanToXml(model, value) {
return model ? value : undefined;
}
function xmlToBoolean(value, equals) {
return value === equals ? true : undefined;
}
class SheetProtectionXform extends BaseXform {
get tag() {
return 'sheetProtection';
}
render(xmlStream, model) {
if (model) {
const attributes = {
sheet: booleanToXml(model.sheet, '1'),
selectLockedCells: model.selectLockedCells === false ? '1' : undefined,
selectUnlockedCells: model.selectUnlockedCells === false ? '1' : undefined,
formatCells: booleanToXml(model.formatCells, '0'),
formatColumns: booleanToXml(model.formatColumns, '0'),
formatRows: booleanToXml(model.formatRows, '0'),
insertColumns: booleanToXml(model.insertColumns, '0'),
insertRows: booleanToXml(model.insertRows, '0'),
insertHyperlinks: booleanToXml(model.insertHyperlinks, '0'),
deleteColumns: booleanToXml(model.deleteColumns, '0'),
deleteRows: booleanToXml(model.deleteRows, '0'),
sort: booleanToXml(model.sort, '0'),
autoFilter: booleanToXml(model.autoFilter, '0'),
pivotTables: booleanToXml(model.pivotTables, '0'),
};
if (model.sheet) {
attributes.algorithmName = model.algorithmName;
attributes.hashValue = model.hashValue;
attributes.saltValue = model.saltValue;
attributes.spinCount = model.spinCount;
attributes.objects = booleanToXml(model.objects === false, '1');
attributes.scenarios = booleanToXml(model.scenarios === false, '1');
}
if (_.some(attributes, value => value !== undefined)) {
xmlStream.leafNode(this.tag, attributes);
}
}
}
parseOpen(node) {
switch (node.name) {
case this.tag:
this.model = {
sheet: xmlToBoolean(node.attributes.sheet, '1'),
objects: node.attributes.objects === '1' ? false : undefined,
scenarios: node.attributes.scenarios === '1' ? false : undefined,
selectLockedCells: node.attributes.selectLockedCells === '1' ? false : undefined,
selectUnlockedCells: node.attributes.selectUnlockedCells === '1' ? false : undefined,
formatCells: xmlToBoolean(node.attributes.formatCells, '0'),
formatColumns: xmlToBoolean(node.attributes.formatColumns, '0'),
formatRows: xmlToBoolean(node.attributes.formatRows, '0'),
insertColumns: xmlToBoolean(node.attributes.insertColumns, '0'),
insertRows: xmlToBoolean(node.attributes.insertRows, '0'),
insertHyperlinks: xmlToBoolean(node.attributes.insertHyperlinks, '0'),
deleteColumns: xmlToBoolean(node.attributes.deleteColumns, '0'),
deleteRows: xmlToBoolean(node.attributes.deleteRows, '0'),
sort: xmlToBoolean(node.attributes.sort, '0'),
autoFilter: xmlToBoolean(node.attributes.autoFilter, '0'),
pivotTables: xmlToBoolean(node.attributes.pivotTables, '0'),
};
if (node.attributes.algorithmName) {
this.model.algorithmName = node.attributes.algorithmName;
this.model.hashValue = node.attributes.hashValue;
this.model.saltValue = node.attributes.saltValue;
this.model.spinCount = parseInt(node.attributes.spinCount, 10);
}
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = SheetProtectionXform;
+202
View File
@@ -0,0 +1,202 @@
const colCache = require('../../../utils/col-cache');
const BaseXform = require('../base-xform');
const VIEW_STATES = {
frozen: 'frozen',
frozenSplit: 'frozen',
split: 'split',
};
class SheetViewXform extends BaseXform {
get tag() {
return 'sheetView';
}
prepare(model) {
switch (model.state) {
case 'frozen':
case 'split':
break;
default:
model.state = 'normal';
break;
}
}
render(xmlStream, model) {
xmlStream.openNode('sheetView', {
workbookViewId: model.workbookViewId || 0,
});
const add = function(name, value, included) {
if (included) {
xmlStream.addAttribute(name, value);
}
};
add('rightToLeft', '1', model.rightToLeft === true);
add('tabSelected', '1', model.tabSelected);
add('showRuler', '0', model.showRuler === false);
add('showRowColHeaders', '0', model.showRowColHeaders === false);
add('showGridLines', '0', model.showGridLines === false);
add('zoomScale', model.zoomScale, model.zoomScale);
add('zoomScaleNormal', model.zoomScaleNormal, model.zoomScaleNormal);
add('view', model.style, model.style);
let topLeftCell;
let xSplit;
let ySplit;
let activePane;
switch (model.state) {
case 'frozen':
xSplit = model.xSplit || 0;
ySplit = model.ySplit || 0;
topLeftCell = model.topLeftCell || colCache.getAddress(ySplit + 1, xSplit + 1).address;
activePane =
(model.xSplit && model.ySplit && 'bottomRight') ||
(model.xSplit && 'topRight') ||
'bottomLeft';
xmlStream.leafNode('pane', {
xSplit: model.xSplit || undefined,
ySplit: model.ySplit || undefined,
topLeftCell,
activePane,
state: 'frozen',
});
xmlStream.leafNode('selection', {
pane: activePane,
activeCell: model.activeCell,
sqref: model.activeCell,
});
break;
case 'split':
if (model.activePane === 'topLeft') {
model.activePane = undefined;
}
xmlStream.leafNode('pane', {
xSplit: model.xSplit || undefined,
ySplit: model.ySplit || undefined,
topLeftCell: model.topLeftCell,
activePane: model.activePane,
});
xmlStream.leafNode('selection', {
pane: model.activePane,
activeCell: model.activeCell,
sqref: model.activeCell,
});
break;
case 'normal':
if (model.activeCell) {
xmlStream.leafNode('selection', {
activeCell: model.activeCell,
sqref: model.activeCell,
});
}
break;
default:
break;
}
xmlStream.closeNode();
}
parseOpen(node) {
switch (node.name) {
case 'sheetView':
this.sheetView = {
workbookViewId: parseInt(node.attributes.workbookViewId, 10),
rightToLeft: node.attributes.rightToLeft === '1',
tabSelected: node.attributes.tabSelected === '1',
showRuler: !(node.attributes.showRuler === '0'),
showRowColHeaders: !(node.attributes.showRowColHeaders === '0'),
showGridLines: !(node.attributes.showGridLines === '0'),
zoomScale: parseInt(node.attributes.zoomScale || '100', 10),
zoomScaleNormal: parseInt(node.attributes.zoomScaleNormal || '100', 10),
style: node.attributes.view,
};
this.pane = undefined;
this.selections = {};
return true;
case 'pane':
this.pane = {
xSplit: parseInt(node.attributes.xSplit || '0', 10),
ySplit: parseInt(node.attributes.ySplit || '0', 10),
topLeftCell: node.attributes.topLeftCell,
activePane: node.attributes.activePane || 'topLeft',
state: node.attributes.state,
};
return true;
case 'selection': {
const name = node.attributes.pane || 'topLeft';
this.selections[name] = {
pane: name,
activeCell: node.attributes.activeCell,
};
return true;
}
default:
return false;
}
}
parseText() {}
parseClose(name) {
let model;
let selection;
switch (name) {
case 'sheetView':
if (this.sheetView && this.pane) {
model = this.model = {
workbookViewId: this.sheetView.workbookViewId,
rightToLeft: this.sheetView.rightToLeft,
state: VIEW_STATES[this.pane.state] || 'split', // split is default
xSplit: this.pane.xSplit,
ySplit: this.pane.ySplit,
topLeftCell: this.pane.topLeftCell,
showRuler: this.sheetView.showRuler,
showRowColHeaders: this.sheetView.showRowColHeaders,
showGridLines: this.sheetView.showGridLines,
zoomScale: this.sheetView.zoomScale,
zoomScaleNormal: this.sheetView.zoomScaleNormal,
};
if (this.model.state === 'split') {
model.activePane = this.pane.activePane;
}
selection = this.selections[this.pane.activePane];
if (selection && selection.activeCell) {
model.activeCell = selection.activeCell;
}
if (this.sheetView.style) {
model.style = this.sheetView.style;
}
} else {
model = this.model = {
workbookViewId: this.sheetView.workbookViewId,
rightToLeft: this.sheetView.rightToLeft,
state: 'normal',
showRuler: this.sheetView.showRuler,
showRowColHeaders: this.sheetView.showRowColHeaders,
showGridLines: this.sheetView.showGridLines,
zoomScale: this.sheetView.zoomScale,
zoomScaleNormal: this.sheetView.zoomScaleNormal,
};
selection = this.selections.topLeft;
if (selection && selection.activeCell) {
model.activeCell = selection.activeCell;
}
if (this.sheetView.style) {
model.style = this.sheetView.style;
}
}
return false;
default:
return true;
}
}
reconcile() {}
}
module.exports = SheetViewXform;
+33
View File
@@ -0,0 +1,33 @@
const BaseXform = require('../base-xform');
class TablePartXform extends BaseXform {
get tag() {
return 'tablePart';
}
render(xmlStream, model) {
if (model) {
xmlStream.leafNode(this.tag, {'r:id': model.rId});
}
}
parseOpen(node) {
switch (node.name) {
case this.tag:
this.model = {
rId: node.attributes['r:id'],
};
return true;
default:
return false;
}
}
parseText() {}
parseClose() {
return false;
}
}
module.exports = TablePartXform;
+539
View File
@@ -0,0 +1,539 @@
const _ = require('../../../utils/under-dash');
const colCache = require('../../../utils/col-cache');
const XmlStream = require('../../../utils/xml-stream');
const RelType = require('../../rel-type');
const Merges = require('./merges');
const BaseXform = require('../base-xform');
const ListXform = require('../list-xform');
const RowXform = require('./row-xform');
const ColXform = require('./col-xform');
const DimensionXform = require('./dimension-xform');
const HyperlinkXform = require('./hyperlink-xform');
const MergeCellXform = require('./merge-cell-xform');
const DataValidationsXform = require('./data-validations-xform');
const SheetPropertiesXform = require('./sheet-properties-xform');
const SheetFormatPropertiesXform = require('./sheet-format-properties-xform');
const SheetViewXform = require('./sheet-view-xform');
const SheetProtectionXform = require('./sheet-protection-xform');
const PageMarginsXform = require('./page-margins-xform');
const PageSetupXform = require('./page-setup-xform');
const PrintOptionsXform = require('./print-options-xform');
const AutoFilterXform = require('./auto-filter-xform');
const PictureXform = require('./picture-xform');
const DrawingXform = require('./drawing-xform');
const TablePartXform = require('./table-part-xform');
const RowBreaksXform = require('./row-breaks-xform');
const HeaderFooterXform = require('./header-footer-xform');
const ConditionalFormattingsXform = require('./cf/conditional-formattings-xform');
const ExtListXform = require('./ext-lst-xform');
const mergeRule = (rule, extRule) => {
Object.keys(extRule).forEach(key => {
const value = rule[key];
const extValue = extRule[key];
if (value === undefined && extValue !== undefined) {
rule[key] = extValue;
}
});
};
const mergeConditionalFormattings = (model, extModel) => {
// conditional formattings are rendered in worksheet.conditionalFormatting and also in
// worksheet.extLst.ext.x14:conditionalFormattings
// some (e.g. dataBar) are even spread across both!
if (!extModel || !extModel.length) {
return model;
}
if (!model || !model.length) {
return extModel;
}
// index model rules by x14Id
const cfMap = {};
const ruleMap = {};
model.forEach(cf => {
cfMap[cf.ref] = cf;
cf.rules.forEach(rule => {
const {x14Id} = rule;
if (x14Id) {
ruleMap[x14Id] = rule;
}
});
});
extModel.forEach(extCf => {
extCf.rules.forEach(extRule => {
const rule = ruleMap[extRule.x14Id];
if (rule) {
// merge with matching rule
mergeRule(rule, extRule);
} else if (cfMap[extCf.ref]) {
// reuse existing cf ref
cfMap[extCf.ref].rules.push(extRule);
} else {
// create new cf
model.push({
ref: extCf.ref,
rules: [extRule],
});
}
});
});
// need to cope with rules in extModel that don't exist in model
return model;
};
class WorkSheetXform extends BaseXform {
constructor(options) {
super();
const {maxRows, maxCols, ignoreNodes} = options || {};
this.ignoreNodes = ignoreNodes || [];
this.map = {
sheetPr: new SheetPropertiesXform(),
dimension: new DimensionXform(),
sheetViews: new ListXform({
tag: 'sheetViews',
count: false,
childXform: new SheetViewXform(),
}),
sheetFormatPr: new SheetFormatPropertiesXform(),
cols: new ListXform({tag: 'cols', count: false, childXform: new ColXform()}),
sheetData: new ListXform({
tag: 'sheetData',
count: false,
empty: true,
childXform: new RowXform({maxItems: maxCols}),
maxItems: maxRows,
}),
autoFilter: new AutoFilterXform(),
mergeCells: new ListXform({tag: 'mergeCells', count: true, childXform: new MergeCellXform()}),
rowBreaks: new RowBreaksXform(),
hyperlinks: new ListXform({
tag: 'hyperlinks',
count: false,
childXform: new HyperlinkXform(),
}),
pageMargins: new PageMarginsXform(),
dataValidations: new DataValidationsXform(),
pageSetup: new PageSetupXform(),
headerFooter: new HeaderFooterXform(),
printOptions: new PrintOptionsXform(),
picture: new PictureXform(),
drawing: new DrawingXform(),
sheetProtection: new SheetProtectionXform(),
tableParts: new ListXform({tag: 'tableParts', count: true, childXform: new TablePartXform()}),
conditionalFormatting: new ConditionalFormattingsXform(),
extLst: new ExtListXform(),
};
}
prepare(model, options) {
options.merges = new Merges();
model.hyperlinks = options.hyperlinks = [];
model.comments = options.comments = [];
options.formulae = {};
options.siFormulae = 0;
this.map.cols.prepare(model.cols, options);
this.map.sheetData.prepare(model.rows, options);
this.map.conditionalFormatting.prepare(model.conditionalFormattings, options);
model.mergeCells = options.merges.mergeCells;
// prepare relationships
const rels = (model.rels = []);
function nextRid(r) {
return `rId${r.length + 1}`;
}
model.hyperlinks.forEach(hyperlink => {
const rId = nextRid(rels);
hyperlink.rId = rId;
rels.push({
Id: rId,
Type: RelType.Hyperlink,
Target: hyperlink.target,
TargetMode: 'External',
});
});
// prepare comment relationships
if (model.comments.length > 0) {
const comment = {
Id: nextRid(rels),
Type: RelType.Comments,
Target: `../comments${model.id}.xml`,
};
rels.push(comment);
const vmlDrawing = {
Id: nextRid(rels),
Type: RelType.VmlDrawing,
Target: `../drawings/vmlDrawing${model.id}.vml`,
};
rels.push(vmlDrawing);
model.comments.forEach(item => {
item.refAddress = colCache.decodeAddress(item.ref);
});
options.commentRefs.push({
commentName: `comments${model.id}`,
vmlDrawing: `vmlDrawing${model.id}`,
});
}
const drawingRelsHash = [];
let bookImage;
model.media.forEach(medium => {
if (medium.type === 'background') {
const rId = nextRid(rels);
bookImage = options.media[medium.imageId];
rels.push({
Id: rId,
Type: RelType.Image,
Target: `../media/${bookImage.name}.${bookImage.extension}`,
});
model.background = {
rId,
};
model.image = options.media[medium.imageId];
} else if (medium.type === 'image') {
let {drawing} = model;
bookImage = options.media[medium.imageId];
if (!drawing) {
drawing = model.drawing = {
rId: nextRid(rels),
name: `drawing${++options.drawingsCount}`,
anchors: [],
rels: [],
};
options.drawings.push(drawing);
rels.push({
Id: drawing.rId,
Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
Target: `../drawings/${drawing.name}.xml`,
});
}
let rIdImage =
this.preImageId === medium.imageId ? drawingRelsHash[medium.imageId] : drawingRelsHash[drawing.rels.length];
if (!rIdImage) {
rIdImage = nextRid(drawing.rels);
drawingRelsHash[drawing.rels.length] = rIdImage;
drawing.rels.push({
Id: rIdImage,
Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
Target: `../media/${bookImage.name}.${bookImage.extension}`,
});
}
const anchor = {
picture: {
rId: rIdImage,
},
range: medium.range,
};
if (medium.hyperlinks && medium.hyperlinks.hyperlink) {
const rIdHyperLink = nextRid(drawing.rels);
drawingRelsHash[drawing.rels.length] = rIdHyperLink;
anchor.picture.hyperlinks = {
tooltip: medium.hyperlinks.tooltip,
rId: rIdHyperLink,
};
drawing.rels.push({
Id: rIdHyperLink,
Type: RelType.Hyperlink,
Target: medium.hyperlinks.hyperlink,
TargetMode: 'External',
});
}
this.preImageId = medium.imageId;
drawing.anchors.push(anchor);
}
});
// prepare tables
model.tables.forEach(table => {
// relationships
const rId = nextRid(rels);
table.rId = rId;
rels.push({
Id: rId,
Type: RelType.Table,
Target: `../tables/${table.target}`,
});
// dynamic styles
table.columns.forEach(column => {
const {style} = column;
if (style) {
column.dxfId = options.styles.addDxfStyle(style);
}
});
});
// prepare ext items
this.map.extLst.prepare(model, options);
}
render(xmlStream, model) {
xmlStream.openXml(XmlStream.StdDocAttributes);
xmlStream.openNode('worksheet', WorkSheetXform.WORKSHEET_ATTRIBUTES);
const sheetFormatPropertiesModel = model.properties
? {
defaultRowHeight: model.properties.defaultRowHeight,
dyDescent: model.properties.dyDescent,
outlineLevelCol: model.properties.outlineLevelCol,
outlineLevelRow: model.properties.outlineLevelRow,
}
: undefined;
if (model.properties && model.properties.defaultColWidth) {
sheetFormatPropertiesModel.defaultColWidth = model.properties.defaultColWidth;
}
const sheetPropertiesModel = {
outlineProperties: model.properties && model.properties.outlineProperties,
tabColor: model.properties && model.properties.tabColor,
pageSetup:
model.pageSetup && model.pageSetup.fitToPage
? {
fitToPage: model.pageSetup.fitToPage,
}
: undefined,
};
const pageMarginsModel = model.pageSetup && model.pageSetup.margins;
const printOptionsModel = {
showRowColHeaders: model.pageSetup && model.pageSetup.showRowColHeaders,
showGridLines: model.pageSetup && model.pageSetup.showGridLines,
horizontalCentered: model.pageSetup && model.pageSetup.horizontalCentered,
verticalCentered: model.pageSetup && model.pageSetup.verticalCentered,
};
const sheetProtectionModel = model.sheetProtection;
this.map.sheetPr.render(xmlStream, sheetPropertiesModel);
this.map.dimension.render(xmlStream, model.dimensions);
this.map.sheetViews.render(xmlStream, model.views);
this.map.sheetFormatPr.render(xmlStream, sheetFormatPropertiesModel);
this.map.cols.render(xmlStream, model.cols);
this.map.sheetData.render(xmlStream, model.rows);
this.map.sheetProtection.render(xmlStream, sheetProtectionModel); // Note: must be after sheetData and before autoFilter
this.map.autoFilter.render(xmlStream, model.autoFilter);
this.map.mergeCells.render(xmlStream, model.mergeCells);
this.map.conditionalFormatting.render(xmlStream, model.conditionalFormattings); // Note: must be before dataValidations
this.map.dataValidations.render(xmlStream, model.dataValidations);
// For some reason hyperlinks have to be after the data validations
this.map.hyperlinks.render(xmlStream, model.hyperlinks);
this.map.printOptions.render(xmlStream, printOptionsModel); // Note: must be before pageMargins
this.map.pageMargins.render(xmlStream, pageMarginsModel);
this.map.pageSetup.render(xmlStream, model.pageSetup);
this.map.headerFooter.render(xmlStream, model.headerFooter);
this.map.rowBreaks.render(xmlStream, model.rowBreaks);
this.map.drawing.render(xmlStream, model.drawing); // Note: must be after rowBreaks
this.map.picture.render(xmlStream, model.background); // Note: must be after drawing
this.map.tableParts.render(xmlStream, model.tables);
this.map.extLst.render(xmlStream, model);
if (model.rels) {
// add a <legacyDrawing /> node for each comment
model.rels.forEach(rel => {
if (rel.Type === RelType.VmlDrawing) {
xmlStream.leafNode('legacyDrawing', {'r:id': rel.Id});
}
});
}
xmlStream.closeNode();
}
parseOpen(node) {
if (this.parser) {
this.parser.parseOpen(node);
return true;
}
if (node.name === 'worksheet') {
_.each(this.map, xform => {
xform.reset();
});
return true;
}
if (this.map[node.name] && !this.ignoreNodes.includes(node.name)) {
this.parser = this.map[node.name];
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 'worksheet': {
const properties = this.map.sheetFormatPr.model || {};
if (this.map.sheetPr.model && this.map.sheetPr.model.tabColor) {
properties.tabColor = this.map.sheetPr.model.tabColor;
}
if (this.map.sheetPr.model && this.map.sheetPr.model.outlineProperties) {
properties.outlineProperties = this.map.sheetPr.model.outlineProperties;
}
const sheetProperties = {
fitToPage:
(this.map.sheetPr.model &&
this.map.sheetPr.model.pageSetup &&
this.map.sheetPr.model.pageSetup.fitToPage) ||
false,
margins: this.map.pageMargins.model,
};
const pageSetup = Object.assign(sheetProperties, this.map.pageSetup.model, this.map.printOptions.model);
const conditionalFormattings = mergeConditionalFormattings(
this.map.conditionalFormatting.model,
this.map.extLst.model && this.map.extLst.model['x14:conditionalFormattings']
);
this.model = {
dimensions: this.map.dimension.model,
cols: this.map.cols.model,
rows: this.map.sheetData.model,
mergeCells: this.map.mergeCells.model,
hyperlinks: this.map.hyperlinks.model,
dataValidations: this.map.dataValidations.model,
properties,
views: this.map.sheetViews.model,
pageSetup,
headerFooter: this.map.headerFooter.model,
background: this.map.picture.model,
drawing: this.map.drawing.model,
tables: this.map.tableParts.model,
conditionalFormattings,
};
if (this.map.autoFilter.model) {
this.model.autoFilter = this.map.autoFilter.model;
}
if (this.map.sheetProtection.model) {
this.model.sheetProtection = this.map.sheetProtection.model;
}
return false;
}
default:
// not quite sure how we get here!
return true;
}
}
reconcile(model, options) {
// options.merges = new Merges();
// options.merges.reconcile(model.mergeCells, model.rows);
const rels = (model.relationships || []).reduce((h, rel) => {
h[rel.Id] = rel;
if (rel.Type === RelType.Comments) {
model.comments = options.comments[rel.Target].comments;
}
if (rel.Type === RelType.VmlDrawing && model.comments && model.comments.length) {
const vmlComment = options.vmlDrawings[rel.Target].comments;
model.comments.forEach((comment, index) => {
comment.note = Object.assign({}, comment.note, vmlComment[index]);
});
}
return h;
}, {});
options.commentsMap = (model.comments || []).reduce((h, comment) => {
if (comment.ref) {
h[comment.ref] = comment;
}
return h;
}, {});
options.hyperlinkMap = (model.hyperlinks || []).reduce((h, hyperlink) => {
if (hyperlink.rId) {
h[hyperlink.address] = rels[hyperlink.rId].Target;
}
return h;
}, {});
options.formulae = {};
// compact the rows and cells
model.rows = (model.rows && model.rows.filter(Boolean)) || [];
model.rows.forEach(row => {
row.cells = (row.cells && row.cells.filter(Boolean)) || [];
});
this.map.cols.reconcile(model.cols, options);
this.map.sheetData.reconcile(model.rows, options);
this.map.conditionalFormatting.reconcile(model.conditionalFormattings, options);
model.media = [];
if (model.drawing) {
const drawingRel = rels[model.drawing.rId];
const match = drawingRel.Target.match(/\/drawings\/([a-zA-Z0-9]+)[.][a-zA-Z]{3,4}$/);
if (match) {
const drawingName = match[1];
const drawing = options.drawings[drawingName];
drawing.anchors.forEach(anchor => {
if (anchor.medium) {
const image = {
type: 'image',
imageId: anchor.medium.index,
range: anchor.range,
hyperlinks: anchor.picture.hyperlinks,
};
model.media.push(image);
}
});
}
}
const backgroundRel = model.background && rels[model.background.rId];
if (backgroundRel) {
const target = backgroundRel.Target.split('/media/')[1];
const imageId = options.mediaIndex && options.mediaIndex[target];
if (imageId !== undefined) {
model.media.push({
type: 'background',
imageId,
});
}
}
model.tables = (model.tables || []).map(tablePart => {
const rel = rels[tablePart.rId];
return options.tables[rel.Target];
});
delete model.relationships;
delete model.hyperlinks;
delete model.comments;
}
}
WorkSheetXform.WORKSHEET_ATTRIBUTES = {
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
'mc:Ignorable': 'x14ac',
'xmlns:x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac',
};
module.exports = WorkSheetXform;