UNPKG

@entestat/formula

Version:

fast excel formula parser

162 lines (147 loc) 4.81 kB
const FormulaError = require('../../formulas/error'); const {FormulaHelpers} = require('../../formulas/helpers'); const {Parser} = require('../parsing'); const lexer = require('../lexing'); const Utils = require('./utils'); const {formatChevrotainError} = require('../utils'); class DepParser { /** * * @param {{onVariable: Function}} [config] */ constructor(config) { this.data = []; this.utils = new Utils(this); config = Object.assign({ onVariable: () => null, }, config); this.utils = new Utils(this); this.onVariable = config.onVariable; this.functions = {} this.parser = new Parser(this, this.utils); } /** * Get value from the cell reference * @param ref * @return {*} */ getCell(ref) { // console.log('get cell', JSON.stringify(ref)); if (ref.row != null) { if (ref.sheet == null) ref.sheet = this.position ? this.position.sheet : undefined; const idx = this.data.findIndex(element => { return (element.from && element.from.row <= ref.row && element.to.row >= ref.row && element.from.col <= ref.col && element.to.col >= ref.col) || (element.row === ref.row && element.col === ref.col && element.sheet === ref.sheet) }); if (idx === -1) this.data.push(ref); } return 0; } /** * Get values from the range reference. * @param ref * @return {*} */ getRange(ref) { // console.log('get range', JSON.stringify(ref)); if (ref.from.row != null) { if (ref.sheet == null) ref.sheet = this.position ? this.position.sheet : undefined; const idx = this.data.findIndex(element => { return element.from && element.from.row === ref.from.row && element.from.col === ref.from.col && element.to.row === ref.to.row && element.to.col === ref.to.col; }); if (idx === -1) this.data.push(ref); } return [[0]] } /** * TODO: * Get references or values from a user defined variable. * @param name * @return {*} */ getVariable(name) { // console.log('get variable', name); const res = {ref: this.onVariable(name, this.position.sheet)}; if (res.ref == null) return FormulaError.NAME; if (FormulaHelpers.isCellRef(res)) this.getCell(res.ref); else { this.getRange(res.ref); } return 0; } /** * Retrieve values from the given reference. * @param valueOrRef * @return {*} */ retrieveRef(valueOrRef) { if (FormulaHelpers.isRangeRef(valueOrRef)) { return this.getRange(valueOrRef.ref); } if (FormulaHelpers.isCellRef(valueOrRef)) { return this.getCell(valueOrRef.ref) } return valueOrRef; } /** * Call an excel function. * @param name - Function name. * @param args - Arguments that pass to the function. * @return {*} */ callFunction(name, args) { args.forEach(arg => { if (arg == null) return; this.retrieveRef(arg); }); return {value: 0, ref: {}}; } /** * Check and return the appropriate formula result. * @param result * @return {*} */ checkFormulaResult(result) { this.retrieveRef(result); } /** * Parse an excel formula and return the dependencies * @param {string} inputText * @param {{row: number, col: number, sheet: string}} position * @param {boolean} [ignoreError=false] if true, throw FormulaError when error occurred. * if false, the parser will return partial dependencies. * @returns {Array.<{}>} */ parse(inputText, position, ignoreError = false) { if (inputText.length === 0) throw Error('Input must not be empty.'); this.data = []; this.position = position; const lexResult = lexer.lex(inputText); this.parser.input = lexResult.tokens; try { const res = this.parser.formulaWithBinaryOp(); this.checkFormulaResult(res); } catch (e) { if (!ignoreError) { throw FormulaError.ERROR(e.message, e); } } if (this.parser.errors.length > 0 && !ignoreError) { const error = this.parser.errors[0]; throw formatChevrotainError(error, inputText); } return this.data; } } module.exports = { DepParser, };