Skip to content

Commit

Permalink
feat(formula): supplement sheet and sheets formula (#3859)
Browse files Browse the repository at this point in the history
Co-authored-by: wpxp123456 <Wpxp1223456>
  • Loading branch information
wpxp123456 authored Oct 25, 2024
1 parent ff9cf49 commit 5fcd97a
Show file tree
Hide file tree
Showing 14 changed files with 508 additions and 52 deletions.
8 changes: 8 additions & 0 deletions packages/engine-formula/src/engine/ast-node/function-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class FunctionNode extends BaseAstNode {
if (this._functionExecutor.needsLocale) {
this._setLocale();
}

if (this._functionExecutor.needsSheetsInfo) {
this._setSheetsInfo();
}
}

override get nodeType() {
Expand Down Expand Up @@ -275,6 +279,10 @@ export class FunctionNode extends BaseAstNode {
private _setLocale() {
this._functionExecutor.setLocale(this._currentConfigService.getLocale());
}

private _setSheetsInfo() {
this._functionExecutor.setSheetsInfo(this._currentConfigService.getSheetsInfo());
}
}

export class ErrorFunctionNode extends BaseAstNode {
Expand Down
25 changes: 25 additions & 0 deletions packages/engine-formula/src/functions/base-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export class BaseFunction {
private _column: number = -1;
private _definedNames: Nullable<IDefinedNameMapItem>;
private _locale: LocaleType;
private _sheetOrder: string[];
private _sheetNameMap: { [sheetId: string]: string };

/**
* Whether the function needs to expand the parameters
Expand All @@ -59,6 +61,11 @@ export class BaseFunction {
*/
needsLocale: boolean = false;

/**
* Whether the function needs sheets info
*/
needsSheetsInfo: boolean = false;

/**
* Minimum number of parameters
*/
Expand Down Expand Up @@ -125,6 +132,24 @@ export class BaseFunction {
this._locale = locale;
}

getSheetsInfo() {
return {
sheetOrder: this._sheetOrder,
sheetNameMap: this._sheetNameMap,
};
}

setSheetsInfo({
sheetOrder,
sheetNameMap,
}: {
sheetOrder: string[];
sheetNameMap: { [sheetId: string]: string };
}) {
this._sheetOrder = sheetOrder;
this._sheetNameMap = sheetNameMap;
}

isAsync() {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
* limitations under the License.
*/

import { FUNCTION_NAMES_INFORMATION } from './function-names';
import { Cell } from './cell';
import { ErrorType } from './error-type';
import { FUNCTION_NAMES_INFORMATION } from './function-names';
import { Isblank } from './isblank';
import { Iserr } from './iserr';
import { Iserror } from './iserror';
Expand All @@ -31,6 +31,8 @@ import { Isref } from './isref';
import { Istext } from './istext';
import { N } from './n';
import { Na } from './na';
import { Sheet } from './sheet';
import { Sheets } from './sheets';
import { Type } from './type';

export const functionInformation = [
Expand All @@ -50,5 +52,7 @@ export const functionInformation = [
[Istext, FUNCTION_NAMES_INFORMATION.ISTEXT],
[N, FUNCTION_NAMES_INFORMATION.N],
[Na, FUNCTION_NAMES_INFORMATION.NA],
[Sheet, FUNCTION_NAMES_INFORMATION.SHEET],
[Sheets, FUNCTION_NAMES_INFORMATION.SHEETS],
[Type, FUNCTION_NAMES_INFORMATION.TYPE],
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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 { Injector, IWorkbookData } from '@univerjs/core';
import type { LexerNode } from '../../../../engine/analysis/lexer-node';

import type { BaseAstNode } from '../../../../engine/ast-node/base-ast-node';
import { CellValueType, LocaleType } from '@univerjs/core';
import { beforeEach, describe, expect, it } from 'vitest';
import { ErrorType } from '../../../../basics/error-type';
import { Lexer } from '../../../../engine/analysis/lexer';
import { AstTreeBuilder } from '../../../../engine/analysis/parser';
import { Interpreter } from '../../../../engine/interpreter/interpreter';
import { generateExecuteAstNodeData } from '../../../../engine/utils/ast-node-tool';
import { IFormulaCurrentConfigService } from '../../../../services/current-data.service';
import { IFunctionService } from '../../../../services/function.service';
import { IFormulaRuntimeService } from '../../../../services/runtime.service';
import { createFunctionTestBed, getObjectValue } from '../../../__tests__/create-function-test-bed';
import { FUNCTION_NAMES_INFORMATION } from '../../function-names';
import { Sheet } from '../index';

const getTestWorkbookData = (): IWorkbookData => {
return {
id: 'test',
appVersion: '3.0.0-alpha',
sheets: {
sheet1: {
id: 'sheet1',
name: '工作表1',
cellData: {
0: {
0: {
v: 1,
t: CellValueType.NUMBER,
},
},
},
},
sheet2: {
id: 'sheet2',
name: '工作表2',
cellData: {
0: {
0: {
v: 2,
t: CellValueType.NUMBER,
},
},
},
},
},
locale: LocaleType.ZH_CN,
name: '',
sheetOrder: [],
styles: {},
};
};

describe('Test rank function', () => {
let get: Injector['get'];
let lexer: Lexer;
let astTreeBuilder: AstTreeBuilder;
let interpreter: Interpreter;
let calculate: (formula: string) => (string | number | boolean | null)[][] | string | number | boolean;

beforeEach(() => {
const testBed = createFunctionTestBed(getTestWorkbookData());

get = testBed.get;

lexer = get(Lexer);
astTreeBuilder = get(AstTreeBuilder);
interpreter = get(Interpreter);

const functionService = get(IFunctionService);

const formulaCurrentConfigService = get(IFormulaCurrentConfigService);

const formulaRuntimeService = get(IFormulaRuntimeService);

formulaCurrentConfigService.load({
formulaData: {},
arrayFormulaCellData: {},
arrayFormulaRange: {},
forceCalculate: false,
dirtyRanges: [],
dirtyNameMap: {},
dirtyDefinedNameMap: {},
dirtyUnitFeatureMap: {},
dirtyUnitOtherFormulaMap: {},
excludedCell: {},
allUnitData: {
[testBed.unitId]: testBed.sheetData,
},
});

const sheetItem = testBed.sheetData[testBed.sheetId];

formulaRuntimeService.setCurrent(
0,
0,
sheetItem.rowCount,
sheetItem.columnCount,
testBed.sheetId,
testBed.unitId
);

functionService.registerExecutors(
new Sheet(FUNCTION_NAMES_INFORMATION.SHEET)
);

calculate = (formula: string) => {
const lexerNode = lexer.treeBuilder(formula);

const astNode = astTreeBuilder.parse(lexerNode as LexerNode);

const result = interpreter.execute(generateExecuteAstNodeData(astNode as BaseAstNode));

return getObjectValue(result);
};
});

describe('Sheet', () => {
it('Value is normal', async () => {
const result = await calculate('=SHEET()');
expect(result).toBe(1);
});

it('Value is error', async () => {
const result = await calculate(`=SHEET(${ErrorType.NAME})`);
expect(result).toBe(ErrorType.NAME);
});

it('Value is reference', async () => {
const result = await calculate('=SHEET(A1)');
expect(result).toBe(1);

const result2 = await calculate(`=SHEET('工作表2'!A1)`);
expect(result2).toBe(2);
});

it('Value is array', async () => {
const result = await calculate('=SHEET({1,2,3})');
expect(result).toBe(ErrorType.NA);
});

it('Value is match sheet name', async () => {
const result = await calculate('=SHEET("工作表3")');
expect(result).toBe(ErrorType.NA);

const result2 = await calculate('=SHEET("工作表2")');
expect(result2).toBe(2);
});
});
});
77 changes: 77 additions & 0 deletions packages/engine-formula/src/functions/information/sheet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* 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 { BaseReferenceObject, FunctionVariantType } from '../../../engine/reference-object/base-reference-object';
import type { BaseValueObject } from '../../../engine/value-object/base-value-object';
import { ErrorType } from '../../../basics/error-type';
import { ErrorValueObject } from '../../../engine/value-object/base-value-object';
import { NumberValueObject } from '../../../engine/value-object/primitive-object';
import { BaseFunction } from '../../base-function';

export class Sheet extends BaseFunction {
override minParams = 0;

override maxParams = 1;

override needsReferenceObject = true;

override needsSheetsInfo = true;

override calculate(value?: FunctionVariantType): BaseValueObject {
if (value?.isError()) {
return value as ErrorValueObject;
}

const { sheetOrder, sheetNameMap } = this.getSheetsInfo();

if (!value) {
const sheetIndex = sheetOrder.findIndex((sheetId) => sheetId === this.subUnitId);

return NumberValueObject.create(sheetIndex + 1);
}

if (value.isReferenceObject()) {
const forcedSheetId = (value as BaseReferenceObject).getForcedSheetId();
const defaultSheetId = (value as BaseReferenceObject).getDefaultSheetId();

const sheetIndex = sheetOrder.findIndex((sheetId) => {
if (forcedSheetId) {
return sheetId === forcedSheetId;
} else {
return sheetId === defaultSheetId;
}
});

return NumberValueObject.create(sheetIndex + 1);
}

if (value.isArray()) {
return ErrorValueObject.create(ErrorType.NA);
}

const inputValue = `${(value as BaseValueObject).getValue()}`.toLocaleLowerCase();

const inputSheetId = Object.entries(sheetNameMap).find(([_, name]) => name.toLocaleLowerCase() === inputValue)?.[0];

if (!inputSheetId) {
return ErrorValueObject.create(ErrorType.NA);
}

const sheetIndex = sheetOrder.findIndex((sheetId) => sheetId === inputSheetId);

return NumberValueObject.create(sheetIndex + 1);
}
}
Loading

0 comments on commit 5fcd97a

Please sign in to comment.