UNPKG

exceljs

Version:

Excel Workbook Manager - Read and Write xlsx and csv Files.

259 lines (236 loc) 8.02 kB
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 parseBool(value) { switch (value) { case '1': case 'true': return true; default: return false; } } function assignBool(definedName, attributes, name, defaultValue) { const value = attributes[name]; if (value !== undefined) { definedName[name] = parseBool(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.decodeAddress(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;