UNPKG

xlsx-datafill

Version:

Scalable, template based data population for Excel XLSX spreadsheets.

323 lines (289 loc) 11.3 kB
"use strict"; const _ = require('lodash'); // const allStyles = [ // "bold", // "italic", // "underline", // "strikethrough", // "subscript", // "superscript", // "fontSize", // "fontFamily", // "fontGenericFamily", // "fontScheme", // "fontColor", // "horizontalAlignment", // "justifyLastLine", // "indent", // "verticalAlignment", // "wrapText", // "shrinkToFit", // "textDirection", // "textRotation", // "angleTextCounterclockwise", // "angleTextClockwise", // "rotateTextUp", // "rotateTextDown", // "verticalText", // "fill", // "border", // "borderColor", // "borderStyle", // "leftBorder", "rightBorder", "topBorder", "bottomBorder", "diagonalBorder", // "leftBorderColor", "rightBorderColor", "topBorderColor", "bottomBorderColor", "diagonalBorderColor", // "leftBorderStyle", "rightBorderStyle", "topBorderStyle", "bottomBorderStyle", "diagonalBorderStyle", // "diagonalBorderDirection", // "numberFormat" // ]; let _RichText = null; /** * `xslx-populate` library based accessor to a given Excel workbook. All these methods are internally used by {@link XlsxDataFill}, * but can be used as a reference for implementing custom spreadsheet accessors. */ class XlsxPopulateAccess { /** * Constructs a new instance of XlsxSmartTemplate with given options. * @param {Workbook} workbook - The workbook to be accessed. * @param {XlsxPopulate} XlsxPopulate - The actual xlsx-populate library object. * @description The `XlsxPopulate` object need to be passed in order to extract * certain information from it, _without_ referring the whole library, thus * avoiding making the `xlsx-datafill` package a dependency. */ constructor(workbook, XlsxPopulate) { this._workbook = workbook; this._rowSizes = {}; this._colSizes = {}; _RichText = XlsxPopulate.RichText; } /** * Returns the configured workbook for direct XlsxPopulate manipulation. * @returns {Workbook} The workbook involved. */ workbook() { return this._workbook; } /** * Gets the textual representation of the cell value. * @param {Cell} cell - The cell to retrieve the value from. * @returns {string} The textual representation of cell's contents. */ cellValue(cell) { const theValue = cell.value(); return theValue instanceof _RichText ? theValue.text() : theValue; } /** * Sets the cell value. * @param {Cell} cell - The cell to retrieve the value from. * @param {*} value - The requested value for setting. * @returns {XlsxPopulateAccess} Either the requested value or chainable this. */ setCellValue(cell, value) { cell.value(value); return this; } /** * Gets the textual representation of the cell value. * @param {Cell} cell - The cell to retrieve the value from. * @returns {string} The type of the cell - 'formula', 'richtext', * 'text', 'number', 'date', 'hyperlink', or 'unknown'; */ cellType(cell) { if (cell.formula()) return 'formula'; else if (cell.hyperlink()) return 'hyperlink'; const theValue = cell.value(); if (theValue instanceof _RichText) return 'richtext'; else if (theValue instanceof Date) return 'date'; else return typeof theValue; } /** * Sets the formula in the cell * @param {Cell} cell - The cell to retrieve the value from. * @param {string} formula - the text of the formula to be set. * @returns {XlsxPopulateAccess} For chaining. */ setCellFormula(cell, formula) { cell.formula(_.trimStart(formula, ' =')); return this; } /** * Measures the distance, as a vector between two given cells. * @param {Cell} from The first cell. * @param {Cell} to The second cell. * @returns {Array.<Number>} An array with two values [<rows>, <cols>], representing the distance between the two cells. */ cellDistance(from, to) { return [ to.rowNumber() - from.rowNumber(), to.columnNumber() - from.columnNumber() ]; } /** * Determines the size of cell, taking into account if it is part of a merged range. * @param {Cell} cell The cell to be investigated. * @returns {Array.<Number>} An array with two values [<rows>, <cols>], representing the occupied size. */ cellSize(cell) { const cellAddr = cell.address(); let theSize = [1, 1]; _.forEach(cell.sheet()._mergeCells, range => { const rangeAddr = range.attributes.ref.split(":"); if (rangeAddr[0] == cellAddr) { theSize = this.cellDistance(cell, cell.sheet().cell(rangeAddr[1])); ++theSize[0]; ++theSize[1]; return false; } }); return theSize; } /** * Sets a named style of a given cell. * @param {Cell} cell The cell to be operated. * @param {string} name The name of the style property to be set. * @param {string|object} value The value for this property to be set. * @returns {XlsxPopulateAccess} For invocation chaining. */ setCellStyle(cell, name, value) { cell.style(name, value); return this; } /** * Creates a reference Id for a given cell, based on its sheet and address. * @param {Cell} cell The cell to create a reference Id to. * @param {boolean} withSheet Whether to include the sheet name in the reference. Defaults to true. * @returns {string} The id to be used as a reference for this cell. */ cellRef(cell, withSheet) { if (withSheet == null) withSheet = true; return cell.address({ includeSheetName: withSheet }); } /** * Build a reference string for a cell identified by @param adr, from the @param cell. * @param {Cell} cell A cell that is a base of the reference. * @param {string} adr The address of the target cell, as mentioned in @param cell. * @param {boolean} withSheet Whether to include the sheet name in the reference. Defaults to true. * @returns {string} A reference string identifying the target cell uniquely. */ buildRef(cell, adr, withSheet) { if (withSheet == null) withSheet = true; return adr ? cell.sheet().cell(adr).address({ includeSheetName: withSheet }) : null; } /** * Retrieves a given cell from a given sheet (or an active one). * @param {string|object|array} address The cell adress to be used * @param {string|idx} sheetId The id/name of the sheet to retrieve the cell from. Defaults to an active one. * @returns {Cell} A reference to the required cell. */ getCell(address, sheetId) { const theSheet = sheetId == null ? this._workbook.activeSheet() : this._workbook.sheet(sheetId); return theSheet.cell(address); } /** * Duplicates a cell across a given range. * @param {Cell} cell Cell, which needs duplicating. * @param {Range} range The range, as returned from {@link getCellRange} * @returns {XlsxPopulateAccess} For chain invokes. */ duplicateCell(cell, range) { range.value(cell.value()); return this; } /** * Constructs and returns the range starting from the given cell and spawning given rows and cells. * @param {Cell} cell The starting cell of the range. * @param {Number} rowOffset Number of rows away from the starting cell. 0 means same row. * @param {Number} colOffset Number of columns away from the starting cell. 0 means same column. * @returns {Range} The constructed range. */ getCellRange(cell, rowOffset, colOffset) { return cell.rangeTo(cell.relativeCell(rowOffset, colOffset)); } /** * Gets the cell at a certain offset from a given one. * @param {Cell} cell The reference cell to make the offset from. * @param {int} rows Number of rows to offset. * @param {int} cols Number of columns to offset. * @returns {Cell} The resulting cell. */ offsetCell(cell, rows, cols) { return cell.relativeCell(rows, cols); } /** * Merge or split range of cells. * @param {Range} range The range, as returned from {@link getCellRange} * @param {boolean} status The merged status to be set. * @returns {XlsxPopulateAccess} For chain invokes. */ rangeMerged(range, status) { if (status === undefined) return range.merged(); else { range.merged(status); return this; } } /** * Sets a formula for the whole range. If it contains only one - it is set directly. * @param {Range} range The range, as returned from {@link getCellRange} * @param {String} formula The formula to be set. * @returns {XlsxPopulateAccess} For chain invokes. */ setRangeFormula(range, formula) { range.formula(_.trimStart(formula, ' =')); return this; } /** * Return the string representation of a given range. * @param {Range} range The range which address we're interested in. * @param {boolean} withSheet Whether to include sheet name in the address. * @return {String} The string, representing the given range. */ rangeRef(range, withSheet) { if (withSheet == null) withSheet = true; return range.address({ includeSheetName: withSheet }); } /** * Iterate over all used cells of the given workbook. * @param {function} cb The callback to be invoked with `cell` argument for each used cell. * @returns {XlsxPopulateAccess} For chain invokes. */ forAllCells(cb) { this._workbook.sheets().forEach(sheet => { const theRange = sheet.usedRange(); if (theRange) theRange.forEach(cb); }); return this; } /** * Copies the styles from `src` cell to the `dest`-ination one. * @param {Cell} dest Destination cell. * @param {Cell} src Source cell. * @returns {XlsxPopulateAccess} For invocation chaining. */ copyStyle(dest, src) { if (!src || !dest) throw new Error("Crash! Null 'src' or 'dest' for copyStyle()!"); if (src == dest) return this; if (src._style !== undefined) dest.style(src._style); else if (src._styleId > 0) dest._styleId = src._styleId; const destSheetId = dest.sheet().name(), rowId = `'${destSheetId}':${dest.rowNumber()}`, colId = `'${destSheetId}':${dest.columnNumber()}`; if (this._rowSizes[rowId] === undefined) dest.row().height(this._rowSizes[rowId] = src.row().height()); if (this._colSizes[colId] === undefined) dest.column().width(this._colSizes[colId] = src.column().width()); return this; } } module.exports = XlsxPopulateAccess;