| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- 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;
|