@elbstack/xlsx-populate
Version:
Excel XLSX parser/generator written in JavaScript with Node.js and browser support, jQuery/d3-style method chaining, and a focus on keeping existing workbook features and styles in tact.
195 lines (178 loc) • 6.99 kB
JavaScript
;
// V8 doesn't support optimization for compound assignment of let variables.
// These methods get called a lot so disable the rule to allow V8 opmtimization.
/* eslint-disable operator-assignment */
const _ = require("lodash");
const ADDRESS_REGEX = /^(?:'?(.+?)'?!)?(?:(\$)?([A-Z]+)(\$)?(\d+)(?::(\$)?([A-Z]+)(\$)?(\d+))?|(\$)?([A-Z]+):(\$)?([A-Z]+)|(\$)?(\d+):(\$)?(\d+))$/;
/**
* Address converter.
* @private
*/
module.exports = {
/**
* Convert a column name to a number.
* @param {string} name - The column name.
* @returns {number} The number.
*/
columnNameToNumber(name) {
if (!name || typeof name !== "string") return;
name = name.toUpperCase();
let sum = 0;
for (let i = 0; i < name.length; i++) {
sum = sum * 26;
sum = sum + (name[i].charCodeAt(0) - 'A'.charCodeAt(0) + 1);
}
return sum;
},
/**
* Convert a column number to a name.
* @param {number} number - The column number.
* @returns {string} The name.
*/
columnNumberToName(number) {
let dividend = number;
let name = '';
let modulo = 0;
while (dividend > 0) {
modulo = (dividend - 1) % 26;
name = String.fromCharCode('A'.charCodeAt(0) + modulo) + name;
dividend = Math.floor((dividend - modulo) / 26);
}
return name;
},
/**
* Convert an address to a reference object.
* @param {string} address - The address.
* @returns {{}} The reference object.
*/
fromAddress(address) {
const match = address.match(ADDRESS_REGEX);
if (!match) return;
const ref = {};
if (match[1]) ref.sheetName = match[1].replace(/''/g, "'");
if (match[3] && match[7]) {
ref.type = 'range';
ref.startColumnAnchored = !!match[2];
ref.startColumnName = match[3];
ref.startColumnNumber = this.columnNameToNumber(ref.startColumnName);
ref.startRowAnchored = !!match[4];
ref.startRowNumber = parseInt(match[5]);
ref.endColumnAnchored = !!match[6];
ref.endColumnName = match[7];
ref.endColumnNumber = this.columnNameToNumber(ref.endColumnName);
ref.endRowAnchored = !!match[8];
ref.endRowNumber = parseInt(match[9]);
} else if (match[3]) {
ref.type = 'cell';
ref.columnAnchored = !!match[2];
ref.columnName = match[3];
ref.columnNumber = this.columnNameToNumber(ref.columnName);
ref.rowAnchored = !!match[4];
ref.rowNumber = parseInt(match[5]);
} else if (match[11] && match[11] !== match[13]) {
ref.type = 'columnRange';
ref.startColumnAnchored = !!match[10];
ref.startColumnName = match[11];
ref.startColumnNumber = this.columnNameToNumber(ref.startColumnName);
ref.endColumnAnchored = !!match[12];
ref.endColumnName = match[13];
ref.endColumnNumber = this.columnNameToNumber(ref.endColumnName);
} else if (match[11]) {
ref.type = 'column';
ref.columnAnchored = !!match[10];
ref.columnName = match[11];
ref.columnNumber = this.columnNameToNumber(ref.columnName);
} else if (match[15] && match[15] !== match[17]) {
ref.type = 'rowRange';
ref.startRowAnchored = !!match[14];
ref.startRowNumber = parseInt(match[15]);
ref.endRowAnchored = !!match[16];
ref.endRowNumber = parseInt(match[17]);
} else if (match[15]) {
ref.type = 'row';
ref.rowAnchored = !!match[14];
ref.rowNumber = parseInt(match[15]);
}
return ref;
},
/**
* Convert a reference object to an address.
* @param {{}} ref - The reference object.
* @returns {string} The address.
*/
toAddress(ref) {
let a, b;
const sheetName = ref.sheetName;
if (ref.type === 'cell') {
a = {
columnName: ref.columnName,
columnNumber: ref.columnNumber,
columnAnchored: ref.columnAnchored,
rowNumber: ref.rowNumber,
rowAnchored: ref.rowAnchored
};
} else if (ref.type === 'range') {
a = {
columnName: ref.startColumnName,
columnNumber: ref.startColumnNumber,
columnAnchored: ref.startColumnAnchored,
rowNumber: ref.startRowNumber,
rowAnchored: ref.startRowAnchored
};
b = {
columnName: ref.endColumnName,
columnNumber: ref.endColumnNumber,
columnAnchored: ref.endColumnAnchored,
rowNumber: ref.endRowNumber,
rowAnchored: ref.endRowAnchored
};
} else if (ref.type === 'column') {
a = b = {
columnName: ref.columnName,
columnNumber: ref.columnNumber,
columnAnchored: ref.columnAnchored
};
} else if (ref.type === 'row') {
a = b = {
rowNumber: ref.rowNumber,
rowAnchored: ref.rowAnchored
};
} else if (ref.type === 'columnRange') {
a = {
columnName: ref.startColumnName,
columnNumber: ref.startColumnNumber,
columnAnchored: ref.startColumnAnchored
};
b = {
columnName: ref.endColumnName,
columnNumber: ref.endColumnNumber,
columnAnchored: ref.endColumnAnchored
};
} else if (ref.type === 'rowRange') {
a = {
rowNumber: ref.startRowNumber,
rowAnchored: ref.startRowAnchored
};
b = {
rowNumber: ref.endRowNumber,
rowAnchored: ref.endRowAnchored
};
}
let address = '';
if (sheetName) address = `${address}'${sheetName.replace(/'/g, "''")}'!`;
if (a.columnAnchored) address = `${address}$`;
if (a.columnName) address = address + a.columnName;
else if (a.columnNumber) address = address + this.columnNumberToName(a.columnNumber);
if (a.rowAnchored) address = `${address}$`;
if (a.rowNumber) address = address + a.rowNumber;
if (b) {
address = `${address}:`;
if (b.columnAnchored) address = `${address}$`;
if (b.columnName) address = address + b.columnName;
else if (b.columnNumber) address = address + this.columnNumberToName(b.columnNumber);
if (b.rowAnchored) address = `${address}$`;
if (b.rowNumber) address = address + b.rowNumber;
}
return address;
}
};