Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sheet): indirect support defined name #1899

Merged
merged 1 commit into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions packages/engine-formula/src/engine/ast-node/function-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand Down
22 changes: 22 additions & 0 deletions packages/engine-formula/src/functions/base-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
private _subUnitId: Nullable<string>;
private _row: number = -1;
private _column: number = -1;
private _definedNames: Nullable<IDefinedNameMapItem>;

/**
* Whether the function needs to expand the parameters
Expand Down Expand Up @@ -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;
}
Expand Down
32 changes: 31 additions & 1 deletion packages/engine-formula/src/functions/lookup/indirect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
Loading