@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.
431 lines (400 loc) • 15.3 kB
JavaScript
"use strict";
const ArgHandler = require("./ArgHandler");
const addressConverter = require("./addressConverter");
/**
* A range of cells.
*/
class Range {
// /**
// * Creates a new instance of Range.
// * @param {Cell} startCell - The start cell.
// * @param {Cell} endCell - The end cell.
// */
constructor(startCell, endCell) {
this._startCell = startCell;
this._endCell = endCell;
this._findRangeExtent(startCell, endCell);
}
/**
* Get the address of the range.
* @param {{}} [opts] - Options
* @param {boolean} [opts.includeSheetName] - Include the sheet name in the address.
* @param {boolean} [opts.startRowAnchored] - Anchor the start row.
* @param {boolean} [opts.startColumnAnchored] - Anchor the start column.
* @param {boolean} [opts.endRowAnchored] - Anchor the end row.
* @param {boolean} [opts.endColumnAnchored] - Anchor the end column.
* @param {boolean} [opts.anchored] - Anchor all row and columns.
* @returns {string} The address.
*/
address(opts) {
return addressConverter.toAddress({
type: 'range',
startRowNumber: this.startCell().rowNumber(),
startRowAnchored: opts && (opts.startRowAnchored || opts.anchored),
startColumnName: this.startCell().columnName(),
startColumnAnchored: opts && (opts.startColumnAnchored || opts.anchored),
endRowNumber: this.endCell().rowNumber(),
endRowAnchored: opts && (opts.endRowAnchored || opts.anchored),
endColumnName: this.endCell().columnName(),
endColumnAnchored: opts && (opts.endColumnAnchored || opts.anchored),
sheetName: opts && opts.includeSheetName && this.sheet().name()
});
}
/**
* Gets a cell within the range.
* @param {number} ri - Row index relative to the top-left corner of the range (0-based).
* @param {number} ci - Column index relative to the top-left corner of the range (0-based).
* @returns {Cell} The cell.
*/
cell(ri, ci) {
return this.sheet().cell(this._minRowNumber + ri, this._minColumnNumber + ci);
}
/**
* Sets sheet autoFilter to this range.
* @returns {Range} This range.
*/
autoFilter() {
this.sheet().autoFilter(this);
return this;
}
/**
* Get the cells in the range as a 2D array.
* @returns {Array.<Array.<Cell>>} The cells.
*/
cells() {
return this.map(cell => cell);
}
/**
* Clear the contents of all the cells in the range.
* @returns {Range} The range.
*/
clear() {
return this.value(undefined);
}
/**
* Get the end cell of the range.
* @returns {Cell} The end cell.
*/
endCell() {
return this._endCell;
}
/**
* Callback used by forEach.
* @callback Range~forEachCallback
* @param {Cell} cell - The cell.
* @param {number} ri - The relative row index.
* @param {number} ci - The relative column index.
* @param {Range} range - The range.
* @returns {undefined}
*/
/**
* Call a function for each cell in the range. Goes by row then column.
* @param {Range~forEachCallback} callback - Function called for each cell in the range.
* @returns {Range} The range.
*/
forEach(callback) {
for (let ri = 0; ri < this._numRows; ri++) {
for (let ci = 0; ci < this._numColumns; ci++) {
callback(this.cell(ri, ci), ri, ci, this);
}
}
return this;
}
/**
* Gets the shared formula in the start cell (assuming it's the source of the shared formula).
* @returns {string|undefined} The shared formula.
*//**
* Sets the shared formula in the range. The formula will be translated for each cell.
* @param {string} formula - The formula to set.
* @returns {Range} The range.
*/
formula() {
return new ArgHandler("Range.formula")
.case(() => {
return this.startCell().getSharedRefFormula();
})
.case('string', formula => {
const sharedFormulaId = this.sheet().incrementMaxSharedFormulaId();
this.forEach((cell, ri, ci) => {
if (ri === 0 && ci === 0) {
cell.setSharedFormula(sharedFormulaId, formula, this.address());
} else {
cell.setSharedFormula(sharedFormulaId);
}
});
return this;
})
.handle(arguments);
}
/**
* Callback used by map.
* @callback Range~mapCallback
* @param {Cell} cell - The cell.
* @param {number} ri - The relative row index.
* @param {number} ci - The relative column index.
* @param {Range} range - The range.
* @returns {*} The value to map to.
*/
/**
* Creates a 2D array of values by running each cell through a callback.
* @param {Range~mapCallback} callback - Function called for each cell in the range.
* @returns {Array.<Array.<*>>} The 2D array of return values.
*/
map(callback) {
const result = [];
this.forEach((cell, ri, ci) => {
if (!result[ri]) result[ri] = [];
result[ri][ci] = callback(cell, ri, ci, this);
});
return result;
}
/**
* Gets a value indicating whether the cells in the range are merged.
* @returns {boolean} The value.
*//**
* Sets a value indicating whether the cells in the range should be merged.
* @param {boolean} merged - True to merge, false to unmerge.
* @returns {Range} The range.
*/
merged(merged) {
return new ArgHandler('Range.merged')
.case(() => {
return this.sheet().merged(this.address());
})
.case('*', merged => {
this.sheet().merged(this.address(), merged);
return this;
})
.handle(arguments);
}
/**
* Gets the data validation object attached to the Range.
* @returns {object|undefined} The data validation object or undefined if not set.
*//**
* Set or clear the data validation object of the entire range.
* @param {object|undefined} dataValidation - Object or null to clear.
* @returns {Range} The range.
*/
dataValidation() {
return new ArgHandler('Range.dataValidation')
.case(() => {
return this.sheet().dataValidation(this.address());
})
.case('boolean', obj => {
return this.sheet().dataValidation(this.address(), obj);
})
.case('*', obj => {
this.sheet().dataValidation(this.address(), obj);
return this;
})
.handle(arguments);
}
/**
* Callback used by reduce.
* @callback Range~reduceCallback
* @param {*} accumulator - The accumulated value.
* @param {Cell} cell - The cell.
* @param {number} ri - The relative row index.
* @param {number} ci - The relative column index.
* @param {Range} range - The range.
* @returns {*} The value to map to.
*/
/**
* Reduces the range to a single value accumulated from the result of a function called for each cell.
* @param {Range~reduceCallback} callback - Function called for each cell in the range.
* @param {*} [initialValue] - The initial value.
* @returns {*} The accumulated value.
*/
reduce(callback, initialValue) {
let accumulator = initialValue;
this.forEach((cell, ri, ci) => {
accumulator = callback(accumulator, cell, ri, ci, this);
});
return accumulator;
}
/**
* Gets the parent sheet of the range.
* @returns {Sheet} The parent sheet.
*/
sheet() {
return this.startCell().sheet();
}
/**
* Gets the start cell of the range.
* @returns {Cell} The start cell.
*/
startCell() {
return this._startCell;
}
/**
* Gets a single style for each cell.
* @param {string} name - The name of the style.
* @returns {Array.<Array.<*>>} 2D array of style values.
*//**
* Gets multiple styles for each cell.
* @param {Array.<string>} names - The names of the styles.
* @returns {Object.<string, Array.<Array.<*>>>} Object whose keys are style names and values are 2D arrays of style values.
*//**
* Set the style in each cell to the result of a function called for each.
* @param {string} name - The name of the style.
* @param {Range~mapCallback} - The callback to provide value for the cell.
* @returns {Range} The range.
*//**
* Sets the style in each cell to the corresponding value in the given 2D array of values.
* @param {string} name - The name of the style.
* @param {Array.<Array.<*>>} - The style values to set.
* @returns {Range} The range.
*//**
* Set the style of all cells in the range to a single style value.
* @param {string} name - The name of the style.
* @param {*} value - The value to set.
* @returns {Range} The range.
*//**
* Set multiple styles for the cells in the range.
* @param {object.<string,Range~mapCallback|Array.<Array.<*>>|*>} styles - Object whose keys are style names and values are either function callbacks, 2D arrays of style values, or a single value for all the cells.
* @returns {Range} The range.
*//**
* Sets to a specific style
* @param {Style} style - Style object given from stylesheet.createStyle
* @returns {Range} The range.
*/
style() {
return new ArgHandler("Range.style")
.case('string', name => {
// Get single value
return this.map(cell => cell.style(name));
})
.case('array', names => {
// Get list of values
const values = {};
names.forEach(name => {
values[name] = this.style(name);
});
return values;
})
.case(['string', 'function'], (name, callback) => {
// Set a single value for the cells to the result of a function
return this.forEach((cell, ri, ci) => {
cell.style(name, callback(cell, ri, ci, this));
});
})
.case(['string', 'array'], (name, values) => {
// Set a single value for the cells using an array of matching dimension
return this.forEach((cell, ri, ci) => {
if (values[ri] && values[ri][ci] !== undefined) {
cell.style(name, values[ri][ci]);
}
});
})
.case(['string', '*'], (name, value) => {
// Set a single value for all cells to a single value
return this.forEach(cell => cell.style(name, value));
})
.case('object', nameValues => {
// Object of key value pairs to set
for (const name in nameValues) {
if (!nameValues.hasOwnProperty(name)) continue;
const value = nameValues[name];
this.style(name, value);
}
return this;
})
.case('Style', style => {
this._style = style;
return this.forEach(cell => cell.style(style));
})
.handle(arguments);
}
/**
* Callback used by tap.
* @callback Range~tapCallback
* @param {Range} range - The range.
* @returns {undefined}
*/
/**
* Invoke a callback on the range and return the range. Useful for method chaining.
* @param {Range~tapCallback} callback - The callback function.
* @returns {Range} The range.
*/
tap(callback) {
callback(this);
return this;
}
/**
* Callback used by thru.
* @callback Range~thruCallback
* @param {Range} range - The range.
* @returns {*} The value to return from thru.
*/
/**
* Invoke a callback on the range and return the value provided by the callback. Useful for method chaining.
* @param {Range~thruCallback} callback - The callback function.
* @returns {*} The return value of the callback.
*/
thru(callback) {
return callback(this);
}
/**
* Get the values of each cell in the range as a 2D array.
* @returns {Array.<Array.<*>>} The values.
*//**
* Set the values in each cell to the result of a function called for each.
* @param {Range~mapCallback} callback - The callback to provide value for the cell.
* @returns {Range} The range.
*//**
* Sets the value in each cell to the corresponding value in the given 2D array of values.
* @param {Array.<Array.<*>>} values - The values to set.
* @returns {Range} The range.
*//**
* Set the value of all cells in the range to a single value.
* @param {*} value - The value to set.
* @returns {Range} The range.
*/
value() {
return new ArgHandler("Range.value")
.case(() => {
// Get values
return this.map(cell => cell.value());
})
.case('function', callback => {
// Set a value for the cells to the result of a function
return this.forEach((cell, ri, ci) => {
cell.value(callback(cell, ri, ci, this));
});
})
.case('array', values => {
// Set value for the cells using an array of matching dimension
return this.forEach((cell, ri, ci) => {
if (values[ri] && values[ri][ci] !== undefined) {
cell.value(values[ri][ci]);
}
});
})
.case('*', value => {
// Set the value for all cells to a single value
return this.forEach(cell => cell.value(value));
})
.handle(arguments);
}
/**
* Gets the parent workbook.
* @returns {Workbook} The parent workbook.
*/
workbook() {
return this.sheet().workbook();
}
/**
* Find the extent of the range.
* @returns {undefined}
* @private
*/
_findRangeExtent() {
this._minRowNumber = Math.min(this._startCell.rowNumber(), this._endCell.rowNumber());
this._maxRowNumber = Math.max(this._startCell.rowNumber(), this._endCell.rowNumber());
this._minColumnNumber = Math.min(this._startCell.columnNumber(), this._endCell.columnNumber());
this._maxColumnNumber = Math.max(this._startCell.columnNumber(), this._endCell.columnNumber());
this._numRows = this._maxRowNumber - this._minRowNumber + 1;
this._numColumns = this._maxColumnNumber - this._minColumnNumber + 1;
}
}
module.exports = Range;