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 61b5cd7c4a..408f6c75a0 100644 --- a/packages/engine-formula/src/engine/ast-node/function-node.ts +++ b/packages/engine-formula/src/engine/ast-node/function-node.ts @@ -33,6 +33,7 @@ import type { import { ArrayValueObject, transformToValueObject, ValueObjectFactory } from '../value-object/array-value-object'; import type { BaseValueObject } from '../value-object/base-value-object'; import { prefixHandler } from '../utils/prefixHandler'; +import { IDefinedNamesService } from '../../services/defined-names.service'; import { BaseAstNode, ErrorNode } from './base-ast-node'; import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory'; import { NODE_ORDER_MAP, NodeType } from './node-type'; @@ -42,7 +43,8 @@ export class FunctionNode extends BaseAstNode { token: string, private _functionExecutor: BaseFunction, private _currentConfigService: IFormulaCurrentConfigService, - private _runtimeService: IFormulaRuntimeService + private _runtimeService: IFormulaRuntimeService, + private _definedNamesService: IDefinedNamesService ) { super(token); @@ -206,12 +208,34 @@ export class FunctionNode extends BaseAstNode { }); } } else { + /** + * In Excel, to inject a defined name into a function that has positioning capabilities, + * such as using the INDIRECT function to reference a named range, + * you can write it as follows: + * =INDIRECT("DefinedName1") + */ + if (this._functionExecutor.isAddress()) { + this._setDefinedNamesForFunction(); + } resultVariant = this._functionExecutor.calculate(...variants); } return resultVariant; } + private _setDefinedNamesForFunction() { + const editorUnitId = this._currentConfigService.getExecuteUnitId(); + if (editorUnitId == null) { + return; + } + const definedNames = this._definedNamesService.getDefinedNameMap(editorUnitId); + if (definedNames == null) { + return; + } + + this._functionExecutor.setDefinedNames(definedNames); + } + private _setRefInfo() { const { currentUnitId, currentSubUnitId, currentRow, currentColumn } = this._runtimeService; @@ -243,6 +267,7 @@ export class FunctionNodeFactory extends BaseAstNodeFactory { @IFunctionService private readonly _functionService: IFunctionService, @IFormulaCurrentConfigService private readonly _currentConfigService: IFormulaCurrentConfigService, @IFormulaRuntimeService private readonly _runtimeService: IFormulaRuntimeService, + @IDefinedNamesService private readonly _definedNamesService: IDefinedNamesService, @Inject(Injector) private readonly _injector: Injector ) { super(); @@ -259,7 +284,7 @@ export class FunctionNodeFactory extends BaseAstNodeFactory { return ErrorNode.create(ErrorType.NAME); } - return new FunctionNode(token, functionExecutor, this._currentConfigService, this._runtimeService); + return new FunctionNode(token, functionExecutor, this._currentConfigService, this._runtimeService, this._definedNamesService); } override checkAndCreateNodeType(param: LexerNode | string) { diff --git a/packages/engine-formula/src/functions/base-function.ts b/packages/engine-formula/src/functions/base-function.ts index 08084cc08f..29c5098b87 100644 --- a/packages/engine-formula/src/functions/base-function.ts +++ b/packages/engine-formula/src/functions/base-function.ts @@ -34,12 +34,14 @@ import { CellReferenceObject } from '../engine/reference-object/cell-reference-o import { RowReferenceObject } from '../engine/reference-object/row-reference-object'; import { ColumnReferenceObject } from '../engine/reference-object/column-reference-object'; import { RangeReferenceObject } from '../engine/reference-object/range-reference-object'; +import type { IDefinedNameMapItem } from '../services/defined-names.service'; export class BaseFunction extends Disposable { private _unitId: Nullable; private _subUnitId: Nullable; private _row: number = -1; private _column: number = -1; + private _definedNames: Nullable; /** * Whether the function needs to expand the parameters @@ -75,6 +77,26 @@ export class BaseFunction extends Disposable { return this._column; } + /** + * In Excel, to inject a defined name into a function that has positioning capabilities, + * such as using the INDIRECT function to reference a named range, + * you can write it as follows: + * =INDIRECT("DefinedName1") + */ + getDefinedName(name: string) { + const nameMap = this._definedNames; + if (nameMap == null) { + return null; + } + return Array.from(Object.values(nameMap)).filter((value) => { + return value.name === name; + })?.[0]; + } + + setDefinedNames(definedNames: IDefinedNameMapItem) { + this._definedNames = definedNames; + } + isAsync() { return false; } diff --git a/packages/engine-formula/src/functions/lookup/indirect/index.ts b/packages/engine-formula/src/functions/lookup/indirect/index.ts index c42e1b034b..c7e59837bf 100644 --- a/packages/engine-formula/src/functions/lookup/indirect/index.ts +++ b/packages/engine-formula/src/functions/lookup/indirect/index.ts @@ -16,6 +16,7 @@ import { ErrorType } from '../../../basics/error-type'; import { REFERENCE_REGEX_COLUMN, REFERENCE_REGEX_ROW, REFERENCE_SINGLE_RANGE_REGEX } from '../../../basics/regex'; +import { operatorToken } from '../../../basics/token'; import type { BaseReferenceObject } from '../../../engine/reference-object/base-reference-object'; import { CellReferenceObject } from '../../../engine/reference-object/cell-reference-object'; import { ColumnReferenceObject } from '../../../engine/reference-object/column-reference-object'; @@ -28,6 +29,10 @@ import { type BaseValueObject, ErrorValueObject } from '../../../engine/value-ob import { BaseFunction } from '../../base-function'; export class Indirect extends BaseFunction { + override isAddress() { + return true; + } + override calculate(refText: BaseValueObject, a1?: BaseValueObject) { if (refText == null) { return ErrorValueObject.create(ErrorType.NA); @@ -58,7 +63,7 @@ export class Indirect extends BaseFunction { return ErrorValueObject.create(ErrorType.REF); } - const refTextV = refText.getValue() as string; + const refTextV = this._convertToDefinedName(refText.getValue() as string); if (a1Value === 0) { const gridRange = deserializeRangeForR1C1(refTextV); @@ -104,4 +109,29 @@ export class Indirect extends BaseFunction { object.setDefaultSheetId(this.subUnitId); return object; } + + /** + * In Excel, to inject a defined name into a function that has positioning capabilities, + * such as using the INDIRECT function to reference a named range, + * you can write it as follows: + * =INDIRECT("DefinedName1") + */ + private _convertToDefinedName(refText: string) { + const definedName = this.getDefinedName(refText); + if (definedName == null) { + return refText; + } + + const formulaOrRefString = definedName.formulaOrRefString; + + if (formulaOrRefString == null) { + return refText; + } + + if (formulaOrRefString.startsWith(operatorToken.EQUALS)) { + return formulaOrRefString.slice(1); + } + + return formulaOrRefString; + } }