import { IStoreState, ID, ICell, ICellNN } from '../../index.data'; import { ERROR_REF, ERROR_NUM, ERROR_VALUE, ERROR_NOT_AVALABLE } from '../../formula-parser/error'; import * as R from 'rambda'; import { getCellByij, getCell, getRow, getCellsByijs_immediate, getRowi, getCellij, isTreeRow, getRangeByIj, getMatrix, } from '../selectors'; import Parser from '../../formula-parser/parser'; import { ok } from '../../formula-parser/helper/result'; import { ParsedResult } from '../../formula-parser/data'; import { getAncestors, getTypedId, isNumberStr } from '../../utils'; import { isExternalCell, getExternalCellIdByLabel } from './externalCell'; import { RootNodeId } from 'valor-app-utils/dist/tree/interface'; import { getDecendantIndexes } from 'valor-app-utils/dist/treeArray'; import * as Rx from 'rambdax'; export interface IFormulaRef_RelativePosition { [cellId: string]: { di: number; dj: number }; } /** * A5 => true */ export function isCellLabel(s: string): boolean { return /^([^!]+!)*[A-Z]+[0-9]+$/.test(s); } /** * {{id123}} => true */ export function isCellIdBraced(s: string): boolean { return /^\{\{(id[0-9_a-zA-Z!]+)\}\}$/.test(s); } /** * A1 => {i:0, j:0} */ export function getIJByLabel(s: string): { i: number; j: number } { // 由于调用此方法 const [_, jStr, iStr] = s.match(/^([A-Z]+)([0-9]+)$/)!; // A => 1 // X => 26 // AA => 27 => 1*26^1 + 1*26^0 // AB => 28 => 1*26^1 + 2*26^0 // AX => 52 => 1*26^1 + 26*26^0 // BA => 53 => 2*26^1 + 1*26^0 // BB => 54 => 2*26^1 + 2*26^0 // 最后减1 const len = jStr.length; const j = jStr // AB .toLowerCase() // ab .split('') // [a, b] .map((jj, index) => { // [1,2] return jj.charCodeAt(0) - 'a'.charCodeAt(0) + 1; }) // [1 * 26^(2-0-1), 2*26^(2-1-1)] = [2,26] .map((jj, index) => jj * Math.pow(26, len - index - 1)) .reduce(R.add); return { i: parseInt(iStr) - 1, j: j - 1, }; } /** * A1 => cell */ export function getCellByLabel(state: IStoreState, s: string): ICell { const { i, j } = getIJByLabel(s); const cell = getCellByij(state, i, j); if (!cell) { throw new Error(ERROR_REF); } return cell!; } /** * {{id123}} => 123 */ export function extractIdFromBracedIdStr(s: string): ID { const [_, id] = s.match(/^\{\{id([0-9_!a-zA-Z]+)\}\}$/)!; return id; } /** * {{id123}} => cell */ export function getCellByBracedId(state: IStoreState, s: string): ICell { const id = extractIdFromBracedIdStr(s); return getCell(state, id); } /** * 通过cellId获取cell的值 * `parser` 主要用于测试 * 返回 {result, error, deps} */ export function evalCellValue(state: IStoreState, cell: ICell, parser: Parser) { if (!cell!.formula || cell!.formulaDisabled) { return ok(cell!.value); } return parser!.parseWithDeps(cell!.formula); } /** * 通过字符串获取单元格, 通常用于公式, 暂时未使用 * 例: * A1 => cell * {{id333}} => cell */ export function getCellByStr(state: IStoreState, sCell: string) { return isCellLabel(sCell) ? getCellByLabel(state, sCell) // A1 : isCellIdBraced(sCell) ? getCellByBracedId(state, sCell) // {{id123}} : null; } /** * 通过字符串获取单元格id, 性能高, 同上. * 例: * A1 => 'A1' * {{id333}} => 333 * {{id333!33}} => '333!33' */ export function getCellIdByStr( state: IStoreState, sCell: string, options?: { getSheetIdByCode: (code: string) => ID; getExternalCellIdByLabel: (sheetCode: string, cellLabel: string) => ID; }, ) { if (isCellLabel(sCell)) { if (isExternalCell(sCell)) { if (!options) throw new Error('getCellIdByStr出错: 指定了外部单元格, 则必须设置 options'); return getExternalCellIdByLabel(sCell, options); } else { const cell = getCellByLabel(state, sCell); return !cell ? null : getTypedId(cell.id + ''); } } else if (isCellIdBraced(sCell)) { // 无需区分是否外部单元格 const _id = extractIdFromBracedIdStr(sCell); return getTypedId(_id + ''); } else { return null; } } /** * 对外接口 * 使用场景: 一次性, 例如 用户输入某个单元格, 立即求值 * 基本假定: 依赖已全部计算完成 * 不适用: 全部重算场景 ( 依赖可能未计算 ) * 通过sCell获取cell的值 */ // export function getCellValue(state: IStoreState, sCell: string) { // const cell = getCellByStr(state, sCell); // if (cell === null) throw Error(ERROR_REF); // return { result: cell.value, deps: { cells: [cell.id] } }; // } /** * @param cellId 要计算的单元格 * @param skipLevel 上溯级别 * A * A1 * A11 * A12 * A2 * A21 * A22 * B * B1 * B11 * B12 * B2 * B21 * B22 * * 如上,对B21,sumByPosition(B21, 1), 表示 sum(B11) * 对于B21, sumByPosition(B21, 2), 表示 sum(B11, A21, A11) * @param getCellValue 如何获取依赖cell的值? 如果不传, 则就地取cell.value, 如果传此参数则执行之(重算) */ export function sumSamePosition( state: IStoreState, cellId: ID, skipLevel: number = 1, getCellValue?: (cellID: ID) => any, ) { const currentCell = state.cells[cellId]; const currentRowId = currentCell.rowId!; const currentRowIndex = state.rows.findIndex(it => it.id === currentRowId); const currentRow = state.rows[currentRowIndex]; const j = currentRow.cellIds!.findIndex(it => it === cellId); const rpath = getAncestors(currentRowId, state.treeContext).reverse(); // path = [-1 1 2 3 4], 当uplevel=1时, topCellId取3, uplevel=2时, 取2 // 注意rpath = path.reverse() const topRowId = rpath[Math.min(skipLevel, rpath.length - 1)]; /** 获取decendants, 注意如果rowIndex== -1, 则需要遍历一级节点 */ const topRowIndex = state.rows.findIndex(it => it.id === topRowId); let decendantIndexes: number[]; const rows = state.rows; if (topRowIndex >= 0) { decendantIndexes = getDecendantIndexes(rows, topRowIndex); } else { const childrenIds = state.treeContext[RootNodeId].childrenIds; decendantIndexes = childrenIds.reduce( (acc, rowId) => { const rowIndex = state.rows.findIndex(it => it.id === rowId); const decendant = getDecendantIndexes(rows, rowIndex); return acc.concat(decendant); }, [] as number[], ); } /** end decendants */ const allSamePositionRows = decendantIndexes .filter(idx => { const row = state.rows[idx]; const { level, index } = state.treeContext[row.id]; return ( level === currentRow.level && index === state.treeContext[currentRowId!].index && row.id !== currentRowId ); }) .map(idx => state.rows[idx]); return allSamePositionRows.reduce( (acc, row) => { const cellId = row.cellIds![j]; if (!cellId) return acc; const cell = getCell(state, cellId); if (cell!.error) return { result: 0, error: ERROR_VALUE, deps: Object.assign({}, { cells: acc.deps.cells.concat(cellId) }), }; const value = cellId ? (getCellValue ? getCellValue(cellId) : state.cells[cellId].value) : 0; return R.is(Number, value) || isNumberStr(value) ? { result: acc.result + value, error: acc.error, deps: { cells: acc.deps.cells.concat(cellId) }, } : { ...acc, // update: 即便不是数字, 也应记下依赖关系 error: ERROR_VALUE, deps: { cells: acc.deps.cells.concat(cellId) }, }; }, { result: 0, error: null as any, deps: { cells: [] as ID[] } as any, }, ); } /** * 对外接口 * 取 同列 + 同级 cell的和 * 例: 估算表最后一行的总计 * @param state * @param cellId 要计算的单元格 * @param getCellValue 如何获取依赖cell的值? 如果不传, 则就地取cell.value, 如果传此参数则执行之(重算) * 使用getCellValue的场景: 整体重算recalculate */ export function sumSiblings( state: IStoreState, cellId: ID, getCellValue?: (cellID: ID) => any, ): ParsedResult { const cell = getCell(state, cellId)!; const { j } = getCellij(state, cellId); const row = getRow(state, cell!.rowId!); if (!isTreeRow(row)) return { result: 0, error: ERROR_VALUE, deps: {}, }; // const context = state.treeContext[row.id]; /*已替代, 后续删除 const siblingRows1 = state.rows .filter(row => row.type === 'body') .filter(row => { const sContext = state.treeContext[row.id]; return ( sContext.parentId === context.parentId && sContext.level === context.level && row.id !== cell.rowId ); }); */ const sContext = state.treeContext[row.id]; const siblingRows = state.treeContext[sContext.parentId].childrenIds .filter(id => id !== row.id) .map(it => getRow(state, it)); const ijs = siblingRows.map(sibling => { const i = getRowi(state, sibling.id); return { i, j }; }); const depCells = getCellsByijs_immediate(state, ijs); const s = depCells.reduce((acc, siblingCell) => { let value = getCellValue ? getCellValue(siblingCell!.id) : siblingCell!.value; value = R.is(Number, value) || isNumberStr(value) ? parseFloat(value) : value; return value + acc; }, 0); if (!R.is(Number, s)) { return { result: 0, error: ERROR_VALUE, deps: {} }; } else { return { result: s, error: null, deps: { cells: depCells.map(it => it!.id) } }; } } /** * 对外接口 * 取下级cell之和 * 例: 估算表中, 工程费用的中父项 等于 子项之和 * @param state * @param cellId 要计算的单元格 * @param getCellValue 如何获取依赖cell的值? 如果不传, 则就地取cell.value, 如果传此参数则执行之(重算) * 使用getCellValue的场景: 整体重算recalculate */ export function sumChildren( state: IStoreState, cellId: ID, getCellValue?: (cellID: ID) => any, ): ParsedResult { const cell = getCell(state, cellId)!; const { j } = getCellij(state, cellId); const row = getRow(state, cell!.rowId!); /* 已替代, 后续删除 const childrenRows1 = state.rows.filter(row => { const sContext = state.treeContext[row.id]; return sContext.parentId === cell.rowId; }); */ if (!isTreeRow(row)) return { result: 0, error: ERROR_VALUE, deps: {}, }; const sContext = state.treeContext[cell.rowId!]; const childrenRows = (sContext.childrenIds || ([] as ID[])).map(id => getRow(state, id)); const ijs = childrenRows.map(row => { const i = getRowi(state, row.id); return { i, j }; }); const depCells = getCellsByijs_immediate(state, ijs); const s = depCells.reduce((acc, childCell) => { let value = getCellValue ? getCellValue(childCell!.id) : childCell!.value; value = R.is(Number, value) || isNumberStr(value) ? parseFloat(value) : value; return value + acc; }, 0); if (!R.is(Number, s)) { return { result: 0, error: ERROR_VALUE, deps: {} }; } else { return { result: s, error: null, deps: { cells: depCells.map(it => it!.id) } }; } } /** * 对外接口 * 取左侧cell之和 * 例: 估算表中, 合计 = 人+ 材+ 机+其它 * @param state * @param cellId 要计算的单元格 * @param num 向左起计算多少单元格, 若为null 或 undefined 则表示全部 * @param interval 间隔多少个(如当前为0, 计算右侧3,6,9, 则取3) * @param getCellValue 如何获取依赖cell的值? 如果不传, 则就地取cell.value, 如果传此参数则执行之(重算) * 使用getCellValue的场景: 整体重算recalculate */ export function sumLeft( state: IStoreState, cellId: ID, num: number | null | undefined, interval: number | undefined, getCellValue?: (cellID: ID) => any, ): ParsedResult { if (!R.isNil(num) && num <= 0) throw Error(ERROR_NOT_AVALABLE); // const cell = getCell(state, cellId)!; const { i: celli, j: cellj } = getCellij(state, cellId); if (!R.isNil(num) && cellj - num! < 0) throw Error(ERROR_NOT_AVALABLE); const ijs = R.range(R.isNil(num) ? 0 : cellj - num, cellj) .map(j => ({ i: celli, j })) .filter(({ i, j }) => (cellj - j) % (interval || 1) === 0); const depCells = getCellsByijs_immediate(state, ijs); return sumCells(depCells, getCellValue); } export function sumRight( state: IStoreState, cellId: ID, num: number | null | undefined, interval: number | undefined, getCellValue?: (cellID: ID) => any, ): ParsedResult { if (!R.isNil(num) && num <= 0) throw Error(ERROR_NOT_AVALABLE); // const cell = getCell(state, cellId)!; const { i: celli, j: cellj } = getCellij(state, cellId); const row = state.rows[celli]; if (!R.isNil(num) && cellj + num! > row.cellIds!.length - 1) throw Error(ERROR_NOT_AVALABLE); const ijs = R.range(cellj + 1, R.isNil(num) ? row.cellIds!.length : cellj + num + 1) .map(j => ({ i: celli, j, })) .filter(({ i, j }) => (j - cellj) % (interval || 1) === 0); const depCells = getCellsByijs_immediate(state, ijs); return sumCells(depCells, getCellValue); } export function sumCells(cells: ICell[], getCellValue?: (cellID: ID) => any) { const s = cells.reduce((acc, leftCell) => { let value = getCellValue ? getCellValue(leftCell!.id) : leftCell!.value; value = R.is(Number, value) || isNumberStr(value) ? parseFloat(value) : value; return acc + value; }, 0); if (!R.is(Number, s)) { return { result: 0, error: ERROR_VALUE, deps: {} }; } else { return { result: s, error: null, deps: { cells: cells.map(it => it!.id) } }; } } // 第3行第1列: 2,0 => A2 export function getLabelByIj(i: number, j: number): string { return getLabelByJ(j) + (i + 1); } // 0=>A, 1=>B export function getLabelByJ(j: number): string { const rest = j % 26; const restStr = String.fromCharCode('A'.charCodeAt(0) + rest); const result = (j / 26) | 0; const resultStr = result <= 0 ? '' : getLabelByJ((j - rest) / 26 - 1); return resultStr + restStr; } /** * delay: 可能暂时用不上!!!! * 对外接口 * 通过cellRange获取数组值 ( 二维数组 ) * a1:a2=>[[1,2]] * a1:b2=>[[1,2], [3,4]] * {{id_1}}:{{id_2}}=>[[1,2], [3,4]] */ export function getRangeValue( state: IStoreState, startCellId?: ID | null, endCellId?: ID | null, getCellValue?: (cellID: ID) => any, ): ParsedResult { if (!startCellId || !endCellId) return { result: [[]], error: ERROR_REF, deps: {}, }; const startij = getCellij(state, startCellId); const endij = getCellij(state, endCellId); const cellss = getRangeByIj(state, startij.i!, startij.j, endij.i!, endij.j); const valuess = R.map( cells => R.map(cell => (cell ? (getCellValue ? getCellValue(cell.id) : cell.value) : null), cells), cellss, ); return { result: valuess, error: null, deps: { cells: (R.flatten(cellss.filter(Boolean)) as ICell[]).filter(Boolean).map(it => it!.id), }, }; } /** * 获取当前cell公式里引用的单元格的相对位置 * 暂未考虑绝对引用 * 例: * A1 A2 A3 * 其中, A3.formula='A1'+'A2'+'Sheet!A1' * 调用此方法将得到: {A1: {x:-2, y:0}, A2: {x:-1, y:0}} * ( 忽略外部公式 ) * @param state: 老的state ( 插入/插列前 ) * @param cellId */ export function getCellRelativeRefs(state: IStoreState, cellId: ID): IFormulaRef_RelativePosition { const cell = state.cells[cellId]; if (!cell) throw new Error(ERROR_REF); const formula = cell.formula; if (!formula) return {}; const result = formula.match(/\{\{id[^\}]+\}\}/g); const refCellIds = result ? result.map(it => it.slice(4, -2)) : []; const { i, j } = getCellij(state, cellId); return refCellIds.reduce( (acc, ref) => { if (isExternalCell(ref)) return acc; if (!state.cells[ref]) return acc; const refij = getCellij(state, ref); return Object.assign({}, acc, { [ref]: { di: refij.i - i, dj: refij.j - j } }); }, {} as ReturnType, ); } export function getMultipleCellsRelativeRefs( state: IStoreState, cellIds: ID[], ): Record { return cellIds.reduce( (acc, id) => { return Object.assign({}, acc, { [id]: getCellRelativeRefs(state, id) }); }, {} as ReturnType, ); } /** * 通过上面计算得到的 {A1: {x:-2, y:0}, A2: {x:-1, y:0}} * 以及A3.formula='A1'+'A2'+'Sheet!A1' * 计算新的cellId对应的位置 * 比如, 复制出来的单元格为 B3, 左侧分别是B1和B2: * B1 B2 B3 * 则B3.formula将变成'B1'+'B2'+'Sheet!A1' * @param state: 已经插入行/列的state * @param cellId: 刚插入的cell ( 注意此时cell.formula还是旧的) * @param refMaps: 注意map里的cellId是公式里的cellId. cell.formula里引用的老的cellId, 其di/dj是多少, 利用di/dj找到新的cellId并替换老的 */ export function realizeFormulaByRelativeRefs( state: IStoreState, cellId: ID, refMaps: IFormulaRef_RelativePosition, // 对于批查找情形, 直接使用外界传递的matrix缓存, 不要自己每次算 _martrixCache?: ICell[][], ): string | undefined { const cell = state.cells[cellId]; const oldFormula = cell.formula; if (!oldFormula) return; if (R.isEmpty(refMaps)) return oldFormula; const { i, j } = getCellij(state, cellId); const matrix: ICell[][] = _martrixCache || getMatrix(state); return oldFormula.replace(/\{\{id[^\}]+\}\}/g, idStr => { const id = getTypedId(idStr.slice(4, -2)); if (isExternalCell(id)) return toFormulaId(id); const relativePos = refMaps[id]; if (!relativePos) return toFormulaId(id); const x = i + relativePos.di; const y = j + relativePos.dj; const ref = getCellByij(state, x, y, true, matrix); return ref ? toFormulaId(ref.id) : toFormulaId(id); }); } /** * 批量修改cells里的formula * 依据是 cells里, 每个cell在cellsRefMaps里, 能找到 每个cell引用的refCellId, 从而找到di/dj, 从而找到新refCellId * @param state: 插行/列后的新state * @param cellIds: 单元格 * @param cells2RefMaps: 上述每个cell, 对应的formula里的每个refCellId, 其di/dj是多少 * @param _martrixCache * @return cells: 已修改formula的新cell */ export function realizeFormulaByRElativeRefs_multipleCells( state: IStoreState, cellIds: ID[], cell2RefMaps: ReturnType, _martrixCache?: ICell[][], ) { const matrix: ICell[][] = _martrixCache || getMatrix(state); const result = cellIds.map(cellId => { const cell = getCell(state, cellId); const refMaps = cell2RefMaps[cellId]; if (!cell || !cell.formula || R.isNil(refMaps) || R.isEmpty(refMaps)) return cell; const formula = realizeFormulaByRelativeRefs(state, cellId, refMaps, matrix); return Object.assign({}, cell, { formula }); }); return result; } /** * 当发生插行/插列操作后, 新插入的行/列的单元格, 其公式引用与源单元格是相同的, 需要修正 * 调用本方法后, 公式将根据相对位置修正 * @param stateBefore: 发生插列/插行之前的state * @param stateAfter: 发生插列/插行之后的state * @param newCell2BaseCell baseCell是指被复制的源格, newCell是根据baseCell创建的新格 * @return 新的修改过formula的cells */ export function updateFormula( stateBefore: IStoreState, stateAfter: IStoreState, baseCell2newCell_: Record, ): ICellNN[] { const baseCell2newCell = R.filter((v, k) => !!v && !!k, baseCell2newCell_); const baseCells = Object.keys(baseCell2newCell); const newCells = Object.values(baseCell2newCell); const refMaps = getMultipleCellsRelativeRefs(stateBefore, baseCells); // 此时算出的refMap, 其cellId是baseId, 需要换成newId const newRefMaps: typeof refMaps = Rx.renameProps(baseCell2newCell, refMaps) as any; return realizeFormulaByRElativeRefs_multipleCells(stateAfter, newCells, newRefMaps).filter( it => it && it.formula, ) as ICellNN[]; } export function toFormulaId(id: any): string { return `{{id${id}}}`; } /////////////////////////// parser //////////////////////////////////////////// // 改用每次调用都初始化一个parser的方法解决, 不再维护全局parser // const parsers: Record = {}; // export function setParser(id: ID, getState: () => IStoreState) { // if (parsers[id]) return; // parsers[id] = new Parser({ // getCellValue: (s: string) => getCellValue(getState(), s), // }); // } // export function getParser(id: ID): Parser { // return parsers[id]; // } // export function removeParser(id: ID) { // delete parsers[id]; // }