diff --git a/packages/core/src/sheets/typedef.ts b/packages/core/src/sheets/typedef.ts index 306291abaa..542c0d3b13 100644 --- a/packages/core/src/sheets/typedef.ts +++ b/packages/core/src/sheets/typedef.ts @@ -265,6 +265,12 @@ export interface ICellData { */ ref?: Nullable; + /** + * New Excel formulas need to add prefixes for differentiation, e.g., _xlfn._xlws. _xlfn. _xlws. _xludf. + * @ignore + */ + xf?: Nullable; + /** * Id of the formula. */ diff --git a/packages/engine-formula/src/basics/common.ts b/packages/engine-formula/src/basics/common.ts index 61c8b7a828..a13ad9e0c3 100644 --- a/packages/engine-formula/src/basics/common.ts +++ b/packages/engine-formula/src/basics/common.ts @@ -115,6 +115,10 @@ export interface IArrayFormulaRangeType { [unitId: string]: Nullable<{ [sheetId: string]: IObjectMatrixPrimitiveType }>; } +export interface IArrayFormulaEmbeddedMap { + [unitId: string]: Nullable<{ [sheetId: string]: IObjectMatrixPrimitiveType }>; +} + export interface IFeatureDirtyRangeType { [unitId: string]: Nullable<{ [sheetId: string]: IRange[] }>; } diff --git a/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts b/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts index 70f9507031..58d0f42e30 100644 --- a/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts +++ b/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts @@ -15,13 +15,14 @@ */ import type { IAccessor, IMutation } from '@univerjs/core'; -import { CommandType } from '@univerjs/core'; +import type { IArrayFormulaEmbeddedMap, IArrayFormulaRangeType, IArrayFormulaUnitCellType } from '../../basics/common'; -import type { IArrayFormulaRangeType, IArrayFormulaUnitCellType } from '../../basics/common'; +import { CommandType } from '@univerjs/core'; export interface ISetArrayFormulaDataMutationParams { arrayFormulaRange: IArrayFormulaRangeType; arrayFormulaCellData: IArrayFormulaUnitCellType; + arrayFormulaEmbedded: IArrayFormulaEmbeddedMap; } /** diff --git a/packages/engine-formula/src/commands/mutations/set-defined-name.mutation.ts b/packages/engine-formula/src/commands/mutations/set-defined-name.mutation.ts index c83f160ee7..7432753286 100644 --- a/packages/engine-formula/src/commands/mutations/set-defined-name.mutation.ts +++ b/packages/engine-formula/src/commands/mutations/set-defined-name.mutation.ts @@ -29,6 +29,7 @@ export interface ISetDefinedNameMutationParam extends ISetDefinedNameMutationSea comment?: string; localSheetId?: string; hidden?: boolean; + formulaOrRefStringWithPrefix?: string; // for excel } /** @@ -65,7 +66,7 @@ export const SetDefinedNameMutation: IMutation = { } const definedNamesService = accessor.get(IDefinedNamesService); - const { id, unitId, name, formulaOrRefString, comment, hidden, localSheetId } = params as ISetDefinedNameMutationParam; + const { id, unitId, name, formulaOrRefString, comment, hidden, localSheetId, formulaOrRefStringWithPrefix } = params as ISetDefinedNameMutationParam; definedNamesService.registerDefinedName(unitId, { id, name: name.trim(), @@ -73,6 +74,7 @@ export const SetDefinedNameMutation: IMutation = { comment: comment?.trim(), hidden, localSheetId, + formulaOrRefStringWithPrefix, }); return true; diff --git a/packages/engine-formula/src/controller/calculate.controller.ts b/packages/engine-formula/src/controller/calculate.controller.ts index e62fc7fb64..599ae0ad57 100644 --- a/packages/engine-formula/src/controller/calculate.controller.ts +++ b/packages/engine-formula/src/controller/calculate.controller.ts @@ -145,14 +145,14 @@ export class CalculateController extends Disposable { } private async _applyResult(data: IAllRuntimeData) { - const { unitData, unitOtherData, arrayFormulaRange, arrayFormulaCellData, clearArrayFormulaCellData } = data; + const { unitData, unitOtherData, arrayFormulaRange, arrayFormulaCellData, clearArrayFormulaCellData, arrayFormulaEmbedded } = data; if (!unitData) { console.error('No sheetData from Formula Engine!'); return; } - if (arrayFormulaRange) { + if (arrayFormulaRange || arrayFormulaEmbedded) { this._formulaDataModel.clearPreviousArrayFormulaCellData(clearArrayFormulaCellData); this._formulaDataModel.mergeArrayFormulaCellData(arrayFormulaCellData); this._formulaDataModel.mergeArrayFormulaRange(arrayFormulaRange); @@ -162,6 +162,7 @@ export class CalculateController extends Disposable { { arrayFormulaRange: this._formulaDataModel.getArrayFormulaRange(), arrayFormulaCellData: this._formulaDataModel.getArrayFormulaCellData(), + arrayFormulaEmbedded, }, { onlyLocal: true, diff --git a/packages/engine-formula/src/engine/analysis/__tests__/lexer-tree-builder.spec.ts b/packages/engine-formula/src/engine/analysis/__tests__/lexer-tree-builder.spec.ts index 13bc81112c..e1f8b8fa8a 100644 --- a/packages/engine-formula/src/engine/analysis/__tests__/lexer-tree-builder.spec.ts +++ b/packages/engine-formula/src/engine/analysis/__tests__/lexer-tree-builder.spec.ts @@ -15,7 +15,6 @@ */ import type { LexerNode } from '../lexer-node'; - import { AbsoluteRefType } from '@univerjs/core'; import { describe, expect, it } from 'vitest'; import { ErrorType } from '../../../basics/error-type'; diff --git a/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts b/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts index 4179b6199a..043929d87c 100644 --- a/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts +++ b/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts @@ -17,11 +17,12 @@ import type { IRange, Nullable } from '@univerjs/core'; import type { IDirtyUnitSheetDefinedNameMap } from '../../basics/common'; +import type { IFunctionNames } from '../../basics/function'; import type { IDefinedNamesServiceParam } from '../../services/defined-names.service'; import type { ISequenceArray, ISequenceNode } from '../utils/sequence'; import { AbsoluteRefType, Disposable, isValidRange, moveRangeByOffset, Tools } from '@univerjs/core'; -import { FormulaAstLRU } from '../../basics/cache-lru'; +import { FormulaAstLRU } from '../../basics/cache-lru'; import { ERROR_TYPE_COUNT_ARRAY, ERROR_TYPE_SET, ErrorType } from '../../basics/error-type'; import { isFormulaLexerToken, isTokenCannotBeAtEnd, isTokenCannotPrecedeSuffixToken } from '../../basics/match-token'; import { regexTestSingeRange } from '../../basics/regex'; @@ -1878,4 +1879,8 @@ export class LexerTreeBuilder extends Disposable { currentLexerNode: this._currentLexerNode, }); } + + getNewFormulaWithPrefix(formulaString: string, hasFunction: (functionToken: IFunctionNames) => boolean): string | null { + return null; + } } diff --git a/packages/engine-formula/src/engine/ast-node/function-node.ts b/packages/engine-formula/src/engine/ast-node/function-node.ts index bd403b4434..bf997be0a6 100644 --- a/packages/engine-formula/src/engine/ast-node/function-node.ts +++ b/packages/engine-formula/src/engine/ast-node/function-node.ts @@ -100,12 +100,15 @@ export class FunctionNode extends BaseAstNode { const resultVariant = await this._calculateAsync(variants); let result: FunctionVariantType; + if (resultVariant.isAsyncObject() || resultVariant.isAsyncArrayObject()) { result = await (resultVariant as AsyncObject | AsyncArrayObject).getValue(); } else { result = resultVariant as FunctionVariantType; } + this._setEmbeddedArrayFormulaToResult(result); + this._setRefData(result); this.setValue(result); @@ -143,6 +146,8 @@ export class FunctionNode extends BaseAstNode { const resultVariant = this._calculate(variants) as FunctionVariantType; + this._setEmbeddedArrayFormulaToResult(resultVariant); + this._setRefData(resultVariant); this.setValue(resultVariant as FunctionVariantType); @@ -152,6 +157,17 @@ export class FunctionNode extends BaseAstNode { return this._functionExecutor.isArgumentsIgnoreNumberPattern(); } + /** + * If it contains an array formula, set the current cell to the cache and send itself as a ref outward + */ + private _setEmbeddedArrayFormulaToResult(result: FunctionVariantType) { + if (!result.isArray()) { + return; + } + + this._runtimeService.setUnitArrayFormulaEmbeddedMap(); + } + /** * Compatibility handling for special functions. */ diff --git a/packages/engine-formula/src/functions/new-excel-functions.ts b/packages/engine-formula/src/functions/new-excel-functions.ts new file mode 100644 index 0000000000..aeff35988d --- /dev/null +++ b/packages/engine-formula/src/functions/new-excel-functions.ts @@ -0,0 +1,187 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IFunctionNames } from '../basics/function'; +import { FUNCTION_NAMES_DATE } from './date/function-names'; +import { FUNCTION_NAMES_ENGINEERING } from './engineering/function-names'; +import { FUNCTION_NAMES_FINANCIAL } from './financial/function-names'; +import { FUNCTION_NAMES_INFORMATION } from './information/function-names'; +import { FUNCTION_NAMES_LOGICAL } from './logical/function-names'; +import { FUNCTION_NAMES_LOOKUP } from './lookup/function-names'; +import { FUNCTION_NAMES_MATH } from './math/function-names'; +import { FUNCTION_NAMES_STATISTICAL } from './statistical/function-names'; +import { FUNCTION_NAMES_TEXT } from './text/function-names'; +import { FUNCTION_NAMES_WEB } from './web/function-names'; + +export const NEW_EXCEL_FUNCTIONS = new Set([ + // Trigonometric & Math extensions + FUNCTION_NAMES_MATH.ACOT, + FUNCTION_NAMES_MATH.ACOTH, + FUNCTION_NAMES_MATH.ARABIC, + FUNCTION_NAMES_MATH.BASE, + FUNCTION_NAMES_MATH.CEILING_MATH, + FUNCTION_NAMES_MATH.CEILING_PRECISE, + FUNCTION_NAMES_MATH.COMBINA, + FUNCTION_NAMES_MATH.COT, + FUNCTION_NAMES_MATH.COTH, + FUNCTION_NAMES_MATH.CSC, + FUNCTION_NAMES_MATH.CSCH, + FUNCTION_NAMES_MATH.DECIMAL, + FUNCTION_NAMES_MATH.FLOOR_MATH, + FUNCTION_NAMES_MATH.FLOOR_PRECISE, + FUNCTION_NAMES_MATH.MUNIT, + FUNCTION_NAMES_MATH.RANDARRAY, + FUNCTION_NAMES_MATH.SEC, + FUNCTION_NAMES_MATH.SECH, + FUNCTION_NAMES_MATH.SEQUENCE, + + // Dynamic array functions + FUNCTION_NAMES_LOOKUP.CHOOSECOLS, + FUNCTION_NAMES_LOOKUP.CHOOSEROWS, + FUNCTION_NAMES_LOOKUP.DROP, + FUNCTION_NAMES_LOOKUP.EXPAND, + FUNCTION_NAMES_LOOKUP.FILTER, + FUNCTION_NAMES_LOOKUP.FORMULATEXT, + FUNCTION_NAMES_LOOKUP.HSTACK, + FUNCTION_NAMES_LOOKUP.SORT, + FUNCTION_NAMES_LOOKUP.SORTBY, + FUNCTION_NAMES_LOOKUP.TAKE, + FUNCTION_NAMES_LOOKUP.TOCOL, + FUNCTION_NAMES_LOOKUP.TOROW, + FUNCTION_NAMES_LOOKUP.UNIQUE, + FUNCTION_NAMES_LOOKUP.VSTACK, + FUNCTION_NAMES_LOOKUP.WRAPCOLS, + FUNCTION_NAMES_LOOKUP.WRAPROWS, + FUNCTION_NAMES_LOOKUP.XLOOKUP, + FUNCTION_NAMES_LOOKUP.XMATCH, + + // Bitwise and complex math + FUNCTION_NAMES_ENGINEERING.BITAND, + FUNCTION_NAMES_ENGINEERING.BITLSHIFT, + FUNCTION_NAMES_ENGINEERING.BITOR, + FUNCTION_NAMES_ENGINEERING.BITRSHIFT, + FUNCTION_NAMES_ENGINEERING.BITXOR, + FUNCTION_NAMES_ENGINEERING.ERF_PRECISE, + FUNCTION_NAMES_ENGINEERING.ERFC_PRECISE, + FUNCTION_NAMES_ENGINEERING.IMCOSH, + FUNCTION_NAMES_ENGINEERING.IMCOT, + FUNCTION_NAMES_ENGINEERING.IMCSC, + FUNCTION_NAMES_ENGINEERING.IMCSCH, + FUNCTION_NAMES_ENGINEERING.IMSEC, + FUNCTION_NAMES_ENGINEERING.IMSECH, + FUNCTION_NAMES_ENGINEERING.IMSINH, + FUNCTION_NAMES_ENGINEERING.IMTAN, + + // Information functions + FUNCTION_NAMES_INFORMATION.ISFORMULA, + FUNCTION_NAMES_INFORMATION.SHEET, + FUNCTION_NAMES_INFORMATION.SHEETS, + + // Logical extensions + FUNCTION_NAMES_LOGICAL.IFNA, + FUNCTION_NAMES_LOGICAL.IFS, + FUNCTION_NAMES_LOGICAL.SWITCH, + FUNCTION_NAMES_LOGICAL.XOR, + + // Statistical and text extensions + FUNCTION_NAMES_STATISTICAL.BETA_DIST, + FUNCTION_NAMES_STATISTICAL.BETA_INV, + FUNCTION_NAMES_STATISTICAL.BINOM_DIST, + FUNCTION_NAMES_STATISTICAL.BINOM_DIST_RANGE, + FUNCTION_NAMES_STATISTICAL.BINOM_INV, + FUNCTION_NAMES_STATISTICAL.CHISQ_DIST, + FUNCTION_NAMES_STATISTICAL.CHISQ_DIST_RT, + FUNCTION_NAMES_STATISTICAL.CHISQ_INV, + FUNCTION_NAMES_STATISTICAL.CHISQ_INV_RT, + FUNCTION_NAMES_STATISTICAL.CHISQ_TEST, + FUNCTION_NAMES_STATISTICAL.CONFIDENCE_NORM, + FUNCTION_NAMES_STATISTICAL.CONFIDENCE_T, + FUNCTION_NAMES_STATISTICAL.COVARIANCE_P, + FUNCTION_NAMES_STATISTICAL.COVARIANCE_S, + FUNCTION_NAMES_STATISTICAL.EXPON_DIST, + FUNCTION_NAMES_STATISTICAL.F_DIST, + FUNCTION_NAMES_STATISTICAL.F_DIST_RT, + FUNCTION_NAMES_STATISTICAL.F_INV, + FUNCTION_NAMES_STATISTICAL.F_INV_RT, + FUNCTION_NAMES_STATISTICAL.F_TEST, + FUNCTION_NAMES_STATISTICAL.FORECAST_LINEAR, + FUNCTION_NAMES_STATISTICAL.GAMMA, + FUNCTION_NAMES_STATISTICAL.GAMMA_DIST, + FUNCTION_NAMES_STATISTICAL.GAMMA_INV, + FUNCTION_NAMES_STATISTICAL.GAMMALN_PRECISE, + FUNCTION_NAMES_STATISTICAL.GAUSS, + FUNCTION_NAMES_STATISTICAL.HYPGEOM_DIST, + FUNCTION_NAMES_STATISTICAL.LOGNORM_DIST, + FUNCTION_NAMES_STATISTICAL.LOGNORM_INV, + FUNCTION_NAMES_STATISTICAL.MAXIFS, + FUNCTION_NAMES_STATISTICAL.MINIFS, + FUNCTION_NAMES_STATISTICAL.MODE_MULT, + FUNCTION_NAMES_STATISTICAL.MODE_SNGL, + FUNCTION_NAMES_STATISTICAL.NEGBINOM_DIST, + FUNCTION_NAMES_STATISTICAL.NORM_DIST, + FUNCTION_NAMES_STATISTICAL.NORM_INV, + FUNCTION_NAMES_STATISTICAL.NORM_S_DIST, + FUNCTION_NAMES_STATISTICAL.NORM_S_INV, + FUNCTION_NAMES_STATISTICAL.PERCENTILE_EXC, + FUNCTION_NAMES_STATISTICAL.PERCENTILE_INC, + FUNCTION_NAMES_STATISTICAL.PERCENTRANK_EXC, + FUNCTION_NAMES_STATISTICAL.PERCENTRANK_INC, + FUNCTION_NAMES_STATISTICAL.PERMUTATIONA, + FUNCTION_NAMES_STATISTICAL.PHI, + FUNCTION_NAMES_STATISTICAL.POISSON_DIST, + FUNCTION_NAMES_STATISTICAL.QUARTILE_EXC, + FUNCTION_NAMES_STATISTICAL.QUARTILE_INC, + FUNCTION_NAMES_STATISTICAL.RANK_AVG, + FUNCTION_NAMES_STATISTICAL.RANK_EQ, + FUNCTION_NAMES_STATISTICAL.SKEW_P, + FUNCTION_NAMES_STATISTICAL.STDEV_P, + FUNCTION_NAMES_STATISTICAL.STDEV_S, + FUNCTION_NAMES_STATISTICAL.T_DIST, + FUNCTION_NAMES_STATISTICAL.T_DIST_2T, + FUNCTION_NAMES_STATISTICAL.T_DIST_RT, + FUNCTION_NAMES_STATISTICAL.T_INV, + FUNCTION_NAMES_STATISTICAL.T_INV_2T, + FUNCTION_NAMES_STATISTICAL.T_TEST, + FUNCTION_NAMES_STATISTICAL.VAR_P, + FUNCTION_NAMES_STATISTICAL.VAR_S, + FUNCTION_NAMES_STATISTICAL.WEIBULL_DIST, + FUNCTION_NAMES_STATISTICAL.Z_TEST, + + // Text and encoding + FUNCTION_NAMES_TEXT.ARRAYTOTEXT, + FUNCTION_NAMES_WEB.ENCODEURL, + FUNCTION_NAMES_TEXT.NUMBERVALUE, + FUNCTION_NAMES_TEXT.TEXTAFTER, + FUNCTION_NAMES_TEXT.TEXTBEFORE, + FUNCTION_NAMES_TEXT.TEXTJOIN, + FUNCTION_NAMES_TEXT.TEXTSPLIT, + FUNCTION_NAMES_TEXT.UNICHAR, + FUNCTION_NAMES_TEXT.UNICODE, + FUNCTION_NAMES_TEXT.VALUETOTEXT, + + // Date & time + FUNCTION_NAMES_DATE.DAYS, + FUNCTION_NAMES_DATE.ISOWEEKNUM, + FUNCTION_NAMES_FINANCIAL.PDURATION, + FUNCTION_NAMES_FINANCIAL.RRI, + + FUNCTION_NAMES_LOGICAL.BYCOL, + FUNCTION_NAMES_LOGICAL.BYROW, + FUNCTION_NAMES_LOGICAL.MAKEARRAY, + FUNCTION_NAMES_LOGICAL.MAP, + FUNCTION_NAMES_LOGICAL.REDUCE, + FUNCTION_NAMES_LOGICAL.SCAN, +]); diff --git a/packages/engine-formula/src/index.ts b/packages/engine-formula/src/index.ts index ae278c5173..1bcb9ada5e 100644 --- a/packages/engine-formula/src/index.ts +++ b/packages/engine-formula/src/index.ts @@ -15,6 +15,7 @@ */ export type { + IArrayFormulaEmbeddedMap, IArrayFormulaRangeType, IArrayFormulaUnitCellType, IDirtyUnitFeatureMap, @@ -42,7 +43,8 @@ export { includeFormulaLexerToken, isFormulaLexerToken, normalizeSheetName } fro export { matchRefDrawToken } from './basics/match-token'; export { isReferenceString } from './basics/regex'; export { convertUnitDataToRuntime } from './basics/runtime'; -export { compareToken, matchToken, operatorToken } from './basics/token'; +export { compareToken, matchToken, OPERATOR_TOKEN_SET, operatorToken, prefixToken, SUFFIX_TOKEN_SET } from './basics/token'; +export { DEFAULT_TOKEN_LAMBDA_FUNCTION_NAME, DEFAULT_TOKEN_LET_FUNCTION_NAME, DEFAULT_TOKEN_TYPE_LAMBDA_PARAMETER, DEFAULT_TOKEN_TYPE_PARAMETER, DEFAULT_TOKEN_TYPE_ROOT } from './basics/token-type'; export { RegisterFunctionMutation } from './commands/mutations/register-function.mutation'; export { type ISetArrayFormulaDataMutationParams, SetArrayFormulaDataMutation } from './commands/mutations/set-array-formula-data.mutation'; export { type ISetDefinedNameMutationParam, type ISetDefinedNameMutationSearchParam, RemoveDefinedNameMutation, SetDefinedNameMutation } from './commands/mutations/set-defined-name.mutation'; @@ -142,6 +144,7 @@ export { FUNCTION_NAMES_LOOKUP } from './functions/lookup/function-names'; export { functionMath } from './functions/math/function-map'; export { FUNCTION_NAMES_MATH } from './functions/math/function-names'; export { functionMeta } from './functions/meta/function-map'; +export { NEW_EXCEL_FUNCTIONS } from './functions/new-excel-functions'; export { functionStatistical } from './functions/statistical/function-map'; export { FUNCTION_NAMES_STATISTICAL } from './functions/statistical/function-names'; export { functionText } from './functions/text/function-map'; diff --git a/packages/engine-formula/src/plugin.ts b/packages/engine-formula/src/plugin.ts index 8aeebd2879..2a82060462 100644 --- a/packages/engine-formula/src/plugin.ts +++ b/packages/engine-formula/src/plugin.ts @@ -123,8 +123,6 @@ export class UniverFormulaEnginePlugin extends Plugin { [GlobalComputingStatusService], // Models [FormulaDataModel], - // Engine - [LexerTreeBuilder], //Controllers [FormulaController], [SetSuperTableController], @@ -165,6 +163,7 @@ export class UniverFormulaEnginePlugin extends Plugin { } protected _initializeWithOverride() { + this._injector.add([LexerTreeBuilder, { useClass: LexerTreeBuilder }]); if (!this._config?.notExecuteFormula) { // only worker const dependencies: Dependency[] = [ diff --git a/packages/engine-formula/src/services/defined-names.service.ts b/packages/engine-formula/src/services/defined-names.service.ts index 57576924c9..798c9c4c8e 100644 --- a/packages/engine-formula/src/services/defined-names.service.ts +++ b/packages/engine-formula/src/services/defined-names.service.ts @@ -27,7 +27,7 @@ export interface IDefinedNamesServiceParam { comment?: string; localSheetId?: string; hidden?: boolean; - + formulaOrRefStringWithPrefix?: string; // for excel } export interface IDefinedNamesServiceFocusParam extends IDefinedNamesServiceParam { @@ -75,6 +75,8 @@ export interface IDefinedNamesService { getWorksheetByRef(unitId: string, ref: string): Nullable; + getAllDefinedNames(): IDefinedNameMap; + } export class DefinedNamesService extends Disposable implements IDefinedNamesService { // 18.2.6 definedNames (Defined Names) @@ -217,6 +219,10 @@ export class DefinedNamesService extends Disposable implements IDefinedNamesServ return size !== 0; } + getAllDefinedNames() { + return this._definedNameMap; + } + private _update() { this._update$.next(null); } diff --git a/packages/engine-formula/src/services/runtime.service.ts b/packages/engine-formula/src/services/runtime.service.ts index 8efc10e299..3d544aaa3d 100644 --- a/packages/engine-formula/src/services/runtime.service.ts +++ b/packages/engine-formula/src/services/runtime.service.ts @@ -16,6 +16,7 @@ import type { ICellData, IRange, Nullable } from '@univerjs/core'; import type { + IArrayFormulaEmbeddedMap, IArrayFormulaRangeType, IFeatureDirtyRangeType, IRuntimeOtherUnitDataType, @@ -65,6 +66,7 @@ export enum FormulaExecutedStateType { export interface IAllRuntimeData { unitData: IRuntimeUnitDataType; arrayFormulaRange: IArrayFormulaRangeType; + arrayFormulaEmbedded: IArrayFormulaEmbeddedMap; unitOtherData: IRuntimeOtherUnitDataType; functionsExecutedState: FormulaExecutedStateType; arrayFormulaCellData: IRuntimeUnitDataType; @@ -177,6 +179,10 @@ export interface IFormulaRuntimeService { setRuntimeFeatureRange(featureId: string, featureRange: IFeatureDirtyRangeType): void; clearReferenceAndNumberformatCache(): void; + + getUnitArrayFormulaEmbeddedMap(): IArrayFormulaEmbeddedMap; + + setUnitArrayFormulaEmbeddedMap(): void; } export class FormulaRuntimeService extends Disposable implements IFormulaRuntimeService { @@ -199,6 +205,8 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime private _unitArrayFormulaRange: IArrayFormulaRangeType = {}; + private _unitArrayFormulaEmbeddedMap: IArrayFormulaEmbeddedMap = {}; + private _runtimeArrayFormulaCellData: IRuntimeUnitDataType = {}; private _runtimeClearArrayFormulaCellData: IRuntimeUnitDataType = {}; @@ -355,6 +363,7 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime this._runtimeData = {}; this._runtimeOtherData = {}; this._unitArrayFormulaRange = {}; + this._unitArrayFormulaEmbeddedMap = {}; this._runtimeArrayFormulaCellData = {}; this._runtimeClearArrayFormulaCellData = {}; @@ -661,6 +670,32 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime return this._unitArrayFormulaRange; } + getUnitArrayFormulaEmbeddedMap() { + return this._unitArrayFormulaEmbeddedMap; + } + + setUnitArrayFormulaEmbeddedMap() { + const unitId = this._currentUnitId; + const sheetId = this._currentSubUnitId; + const rowIndex = this._currentRow; + const columnIndex = this._currentColumn; + + const arrayFormulaEmbeddedMap = this._unitArrayFormulaEmbeddedMap; + if (arrayFormulaEmbeddedMap[unitId] == null) { + arrayFormulaEmbeddedMap[unitId] = {}; + } + + if (arrayFormulaEmbeddedMap[unitId][sheetId] == null) { + arrayFormulaEmbeddedMap[unitId][sheetId] = {}; + } + + if (arrayFormulaEmbeddedMap[unitId][sheetId][rowIndex] == null) { + arrayFormulaEmbeddedMap[unitId][sheetId][rowIndex] = {}; + } + + arrayFormulaEmbeddedMap[unitId][sheetId][rowIndex][columnIndex] = true; + } + getRuntimeOtherData() { return this._runtimeOtherData; } @@ -693,6 +728,7 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime return { unitData: this.getUnitData(), arrayFormulaRange: this.getUnitArrayFormula(), + arrayFormulaEmbedded: this.getUnitArrayFormulaEmbeddedMap(), unitOtherData: this.getRuntimeOtherData(), functionsExecutedState: this._functionsExecutedState, arrayFormulaCellData: this.getRuntimeArrayFormulaCellData(), diff --git a/packages/sheets-formula/src/controllers/array-formula-cell-interceptor.controller.ts b/packages/sheets-formula/src/controllers/array-formula-cell-interceptor.controller.ts index 2504bbc792..d439fadfca 100644 --- a/packages/sheets-formula/src/controllers/array-formula-cell-interceptor.controller.ts +++ b/packages/sheets-formula/src/controllers/array-formula-cell-interceptor.controller.ts @@ -15,11 +15,11 @@ */ import type { ICellData, ICommandInfo } from '@univerjs/core'; -import type { IArrayFormulaRangeType, IArrayFormulaUnitCellType, ISetArrayFormulaDataMutationParams } from '@univerjs/engine-formula'; +import type { IArrayFormulaEmbeddedMap, IArrayFormulaRangeType, IArrayFormulaUnitCellType, ISetArrayFormulaDataMutationParams, ISetDefinedNameMutationParam } from '@univerjs/engine-formula'; import type { ISetRangeValuesMutationParams } from '@univerjs/sheets'; import type { IUniverSheetsFormulaBaseConfig } from './config.schema'; import { CellValueType, Disposable, ICommandService, IConfigService, Inject, InterceptorEffectEnum, isRealNum, ObjectMatrix } from '@univerjs/core'; -import { FormulaDataModel, serializeRange, SetArrayFormulaDataMutation, stripErrorMargin } from '@univerjs/engine-formula'; +import { FormulaDataModel, IDefinedNamesService, IFunctionService, LexerTreeBuilder, serializeRange, SetArrayFormulaDataMutation, SetDefinedNameMutation, stripErrorMargin } from '@univerjs/engine-formula'; import { INTERCEPTOR_POINT, SetRangeValuesMutation, SheetInterceptorService } from '@univerjs/sheets'; import { PLUGIN_CONFIG_KEY_BASE } from './config.schema'; @@ -28,7 +28,10 @@ export class ArrayFormulaCellInterceptorController extends Disposable { @ICommandService private readonly _commandService: ICommandService, @IConfigService private readonly _configService: IConfigService, @Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService, - @Inject(FormulaDataModel) private readonly _formulaDataModel: FormulaDataModel + @Inject(FormulaDataModel) private readonly _formulaDataModel: FormulaDataModel, + @Inject(LexerTreeBuilder) private readonly _lexerTreeBuilder: LexerTreeBuilder, + @IFunctionService private readonly _functionService: IFunctionService, + @IDefinedNamesService private readonly _definedNamesService: IDefinedNamesService ) { super(); @@ -55,19 +58,78 @@ export class ArrayFormulaCellInterceptorController extends Disposable { return; } - const { arrayFormulaRange, arrayFormulaCellData } = params; + const { arrayFormulaRange, arrayFormulaCellData, arrayFormulaEmbedded } = params; this._formulaDataModel.setArrayFormulaRange(arrayFormulaRange); this._formulaDataModel.setArrayFormulaCellData(arrayFormulaCellData); // Note the logic should only be executed in exporting and hence shall be set once. if (this._configService.getConfig(PLUGIN_CONFIG_KEY_BASE)?.writeArrayFormulaToSnapshot) { - this._writeArrayFormulaToSnapshot(arrayFormulaRange, arrayFormulaCellData); + this._writeArrayFormulaToSnapshot(arrayFormulaRange, arrayFormulaCellData, arrayFormulaEmbedded); + this._addPrefixToFunctionSnapshot(); + this._addPreixToDefinedNamesFunctionSnapshot(); } }) ); } - private _writeArrayFormulaToSnapshot(arrayFormulaRange: IArrayFormulaRangeType, arrayFormulaCellData: IArrayFormulaUnitCellType) { + private _addPreixToDefinedNamesFunctionSnapshot() { + const allDefinedNames = this._definedNamesService.getAllDefinedNames(); + Object.entries(allDefinedNames).forEach(([unitId, definedNames]) => { + definedNames && Array.from(Object.entries(definedNames)).forEach(([_, definedName]) => { + const { formulaOrRefString } = definedName; + if (formulaOrRefString.substring(0, 1) === '=') { + const newFormula = this._lexerTreeBuilder.getNewFormulaWithPrefix(formulaOrRefString, this._functionService.hasExecutor.bind(this._functionService)); + if (newFormula) { + this._commandService.executeCommand(SetDefinedNameMutation.id, { + ...definedName, + unitId, + formulaOrRefStringWithPrefix: newFormula, + }, { + onlyLocal: true, + fromFormula: true, + }); + } + } + }); + }); + } + + private _addPrefixToFunctionSnapshot() { + const dataModel = this._formulaDataModel.getFormulaData(); + Object.entries(dataModel).forEach(([unitId, subUnitData]) => { + subUnitData && Array.from(Object.entries(subUnitData)).forEach(([subUnitId, formulaDataItem]) => { + // Convert from IObjectMatrixPrimitiveType to IObjectMatrixPrimitiveType + if (!formulaDataItem) { + return; + } + const cellValue = new ObjectMatrix(); + const matrix = new ObjectMatrix(formulaDataItem); + matrix.forValue((row, col, value) => { + const functionText = value?.f; + if (!functionText || functionText.length === 0) { + return; + } + + const newFormula = this._lexerTreeBuilder.getNewFormulaWithPrefix(functionText, this._functionService.hasExecutor.bind(this._functionService)); + if (newFormula) { + cellValue.setValue(row, col, { xf: newFormula }); + } + }); + + // Keep this local to avoid triggering re-calculate in the worker. + this._commandService.executeCommand(SetRangeValuesMutation.id, { + unitId, + subUnitId, + cellValue: cellValue.getMatrix(), + }, { + onlyLocal: true, + fromFormula: true, + }); + }); + }); + } + + private _writeArrayFormulaToSnapshot(arrayFormulaRange: IArrayFormulaRangeType, arrayFormulaCellData: IArrayFormulaUnitCellType, arrayFormulaEmbedded: IArrayFormulaEmbeddedMap) { // Write values to the `ref` property of the array formula range. Object.entries(arrayFormulaRange).forEach(([unitId, subUnitData]) => { subUnitData && Array.from(Object.entries(subUnitData)).forEach(([subUnitId, rangeData]) => { @@ -90,6 +152,38 @@ export class ArrayFormulaCellInterceptorController extends Disposable { }); }); + // SUM(A1:A7*B1:B7) , write ref for embedded array formula cells + Array.from(Object.entries(arrayFormulaEmbedded)).forEach(([unitId, subUnitData]) => { + subUnitData && Array.from(Object.entries(subUnitData)).forEach(([subUnitId, rangeData]) => { + const cellValue = new ObjectMatrix(); + const matrix = new ObjectMatrix(rangeData); + matrix.forValue((row, col) => { + const existingArrayRange = arrayFormulaRange?.[unitId]?.[subUnitId]?.[row]?.[col]; + if (existingArrayRange) { + return; // Skip if there is already an array formula range at this position + } + cellValue.setValue(row, col, { + ref: serializeRange({ + startRow: row, + endRow: row, + startColumn: col, + endColumn: col, + }), + }); // convert to ref string + }); + + // Keep this local to avoid triggering re-calculate in the worker. + this._commandService.executeCommand(SetRangeValuesMutation.id, { + unitId, + subUnitId, + cellValue: cellValue.getMatrix(), + }, { + onlyLocal: true, + fromFormula: true, + }); + }); + }); + // Write values to the 'v' property of the cell data. Array.from(Object.entries(arrayFormulaCellData)).forEach(([unitId, subUnitData]) => { subUnitData && Array.from(Object.entries(subUnitData)).forEach(([subUnitId, rowData]) => {