feat(formula): adapt formula format for SSC service compatibility (#6109)

This commit is contained in:
Univer
2025-11-08 22:25:52 +08:00
committed by GitHub
parent d85ca63cd0
commit 708daa9c5c
14 changed files with 376 additions and 17 deletions
+6
View File
@@ -265,6 +265,12 @@ export interface ICellData {
*/
ref?: Nullable<string>;
/**
* New Excel formulas need to add prefixes for differentiation, e.g., _xlfn._xlws. _xlfn. _xlws. _xludf.
* @ignore
*/
xf?: Nullable<string>;
/**
* Id of the formula.
*/
@@ -115,6 +115,10 @@ export interface IArrayFormulaRangeType {
[unitId: string]: Nullable<{ [sheetId: string]: IObjectMatrixPrimitiveType<IRange> }>;
}
export interface IArrayFormulaEmbeddedMap {
[unitId: string]: Nullable<{ [sheetId: string]: IObjectMatrixPrimitiveType<boolean> }>;
}
export interface IFeatureDirtyRangeType {
[unitId: string]: Nullable<{ [sheetId: string]: IRange[] }>;
}
@@ -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;
}
/**
@@ -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<ISetDefinedNameMutationParam> = {
}
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<ISetDefinedNameMutationParam> = {
comment: comment?.trim(),
hidden,
localSheetId,
formulaOrRefStringWithPrefix,
});
return true;
@@ -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,
@@ -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';
@@ -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;
}
}
@@ -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.
*/
@@ -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<IFunctionNames>([
// 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,
]);
+4 -1
View File
@@ -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';
+1 -2
View File
@@ -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[] = [
@@ -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<Worksheet>;
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);
}
@@ -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(),
@@ -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<IUniverSheetsFormulaBaseConfig>(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<ISetDefinedNameMutationParam>(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<IRange> to IObjectMatrixPrimitiveType<ICellData>
if (!formulaDataItem) {
return;
}
const cellValue = new ObjectMatrix<ICellData>();
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<ISetRangeValuesMutationParams>(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 {
});
});
// <f t="array" ref="E7">SUM(A1:A7*B1:B7)</f> , 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<ICellData>();
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<ISetRangeValuesMutationParams>(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]) => {