From a2cfdf3ebe934ad25996d7573b315d992dc69287 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Fri, 15 Mar 2024 21:06:03 +0800 Subject: [PATCH] feat(formual): add text function --- .../src/basics/object-class-type.ts | 4 + .../src/engine/ast-node/suffix-node.ts | 4 +- .../reference-object/base-reference-object.ts | 6 +- .../engine/value-object/primitive-object.ts | 14 +- .../src/functions/lookup/index/index.ts | 4 +- .../src/functions/text/function-map.ts | 6 +- .../text/text/__test__/index.spec.ts | 141 ++++++++++++++++++ .../src/functions/text/text/index.ts | 83 +++++++++++ .../src/locale/function-list/text/en-US.ts | 4 +- .../src/locale/function-list/text/ja-JP.ts | 4 +- .../src/locale/function-list/text/zh-CN.ts | 4 +- .../src/services/function-list/text.ts | 12 +- 12 files changed, 267 insertions(+), 19 deletions(-) create mode 100644 packages/engine-formula/src/functions/text/text/__test__/index.spec.ts create mode 100644 packages/engine-formula/src/functions/text/text/index.ts diff --git a/packages/engine-formula/src/basics/object-class-type.ts b/packages/engine-formula/src/basics/object-class-type.ts index 504538a8fc4f..b20d190e240b 100644 --- a/packages/engine-formula/src/basics/object-class-type.ts +++ b/packages/engine-formula/src/basics/object-class-type.ts @@ -23,6 +23,10 @@ export class ObjectClassType extends Disposable { return this.pattern; } + /** + * Only used in NumberValueObject + * @param pattern + */ setPattern(pattern: string) { this.pattern = pattern; } diff --git a/packages/engine-formula/src/engine/ast-node/suffix-node.ts b/packages/engine-formula/src/engine/ast-node/suffix-node.ts index a782086500ce..fc39539b9b85 100644 --- a/packages/engine-formula/src/engine/ast-node/suffix-node.ts +++ b/packages/engine-formula/src/engine/ast-node/suffix-node.ts @@ -65,7 +65,9 @@ export class SuffixNode extends BaseAstNode { ) as FunctionVariantType; // set number format - result.setPattern('0.00%'); + if ((result as NumberValueObject).isNumber()) { + result.setPattern('0.00%'); + } } else if (this._operatorString === suffixToken.POUND) { result = this._handlerPound(value); } else { diff --git a/packages/engine-formula/src/engine/reference-object/base-reference-object.ts b/packages/engine-formula/src/engine/reference-object/base-reference-object.ts index cd65febd7dfb..b5eef5674cea 100644 --- a/packages/engine-formula/src/engine/reference-object/base-reference-object.ts +++ b/packages/engine-formula/src/engine/reference-object/base-reference-object.ts @@ -161,9 +161,10 @@ export class BaseReferenceObject extends ObjectClassType { } const resultObjectValue = this.getCellValueObject(cell); + const isNumber = resultObjectValue.isNumber(); const pattern = this._numfmtItemData[unitId]?.[sheetId]?.[r]?.[c]; - pattern && resultObjectValue.setPattern(pattern); + pattern && isNumber && resultObjectValue.setPattern(pattern); result = callback(resultObjectValue, r, c); @@ -183,12 +184,13 @@ export class BaseReferenceObject extends ObjectClassType { } const cellValueObject = this.getCellValueObject(cell); + const isNumber = cellValueObject.isNumber(); // Set numfmt pattern const unitId = this._forcedUnitId || this._defaultUnitId; const sheetId = this._forcedSheetId || this._defaultSheetId; const numfmtItem = this._numfmtItemData[unitId]?.[sheetId]?.[startRow]?.[startColumn]; - numfmtItem && cellValueObject.setPattern(numfmtItem); + numfmtItem && isNumber && cellValueObject.setPattern(numfmtItem); return cellValueObject; } diff --git a/packages/engine-formula/src/engine/value-object/primitive-object.ts b/packages/engine-formula/src/engine/value-object/primitive-object.ts index 659224347cd5..7a309b797c67 100644 --- a/packages/engine-formula/src/engine/value-object/primitive-object.ts +++ b/packages/engine-formula/src/engine/value-object/primitive-object.ts @@ -442,8 +442,10 @@ export class NumberValueObject extends BaseValueObject { return (o as BaseValueObject).plus(this); } const object = this.minusBy(valueObject.getValue()); + + // = 1 - #NAME? gets #NAME?, = 1 - #VALUE! gets #VALUE! if (object.isError()) { - return this; + return object; } // Set number format @@ -458,6 +460,11 @@ export class NumberValueObject extends BaseValueObject { } const object = this.multiplyBy(valueObject.getValue()); + // = 1 * #NAME? gets #NAME?, = 1 * #VALUE! gets #VALUE! + if (object.isError()) { + return object; + } + // Set number format object.setPattern(this.getPattern() || valueObject.getPattern()); @@ -474,6 +481,11 @@ export class NumberValueObject extends BaseValueObject { } const object = this.dividedBy(valueObject.getValue()); + // = 1 / #NAME? gets #NAME?, = 1 / #VALUE! gets #VALUE! + if (object.isError()) { + return object; + } + // Set number format object.setPattern(this.getPattern() || valueObject.getPattern()); diff --git a/packages/engine-formula/src/functions/lookup/index/index.ts b/packages/engine-formula/src/functions/lookup/index/index.ts index ea4156f34a53..97057ca5bd62 100644 --- a/packages/engine-formula/src/functions/lookup/index/index.ts +++ b/packages/engine-formula/src/functions/lookup/index/index.ts @@ -23,7 +23,7 @@ import { BaseFunction } from '../../base-function'; export class Index extends BaseFunction { override calculate(reference: BaseValueObject, rowNum: BaseValueObject, columnNum?: BaseValueObject, areaNum?: BaseValueObject) { - if (reference == null) { + if (reference == null || rowNum == null) { return ErrorValueObject.create(ErrorType.NA); } @@ -31,7 +31,7 @@ export class Index extends BaseFunction { return reference; } - if (rowNum?.isError()) { + if (rowNum.isError()) { return rowNum; } diff --git a/packages/engine-formula/src/functions/text/function-map.ts b/packages/engine-formula/src/functions/text/function-map.ts index 5273ac82aff4..cf7f709363b2 100644 --- a/packages/engine-formula/src/functions/text/function-map.ts +++ b/packages/engine-formula/src/functions/text/function-map.ts @@ -16,5 +16,9 @@ import { Concatenate } from './concatenate'; import { FUNCTION_NAMES_TEXT } from './function-names'; +import { Text } from './text'; -export const functionText = [[Concatenate, FUNCTION_NAMES_TEXT.CONCATENATE]]; +export const functionText = [ + [Concatenate, FUNCTION_NAMES_TEXT.CONCATENATE], + [Text, FUNCTION_NAMES_TEXT.TEXT], +]; diff --git a/packages/engine-formula/src/functions/text/text/__test__/index.spec.ts b/packages/engine-formula/src/functions/text/text/__test__/index.spec.ts new file mode 100644 index 000000000000..197611879d7d --- /dev/null +++ b/packages/engine-formula/src/functions/text/text/__test__/index.spec.ts @@ -0,0 +1,141 @@ +/** + * 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 { describe, expect, it } from 'vitest'; + +import { FUNCTION_NAMES_TEXT } from '../../function-names'; +import { Text } from '..'; +import { NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object'; +import type { ArrayValueObject } from '../../../../engine/value-object/array-value-object'; +import { transformToValue } from '../../../../engine/value-object/array-value-object'; + +describe('Test text function', () => { + const textFunction = new Text(FUNCTION_NAMES_TEXT.TEXT); + + // function getArrayValueObjectPattern(arrayValue: ArrayValueObject) { + // return arrayValue.mapValue((value) => { + // return value.getPattern(); + // }) + // } + + describe('Text', () => { + it('Text is single cell, format text is single cell', () => { + const text1 = NumberValueObject.create(111); + const formatText = StringValueObject.create('$#,##0.00'); + const result = textFunction.calculate(text1, formatText); + const resultArray = result.getArrayValue(); + expect(transformToValue(resultArray)).toStrictEqual([[111]]); + expect((result as ArrayValueObject).getFirstCell().getPattern()).toStrictEqual('$#,##0.00'); + }); + + // it('Text1 is single cell, text2 is array', () => { + // const text1 = StringValueObject.create('a'); + // const text2 = ArrayValueObject.create({ + // calculateValueList: transformToValueObject([ + // [1, 2, 3], + // [2, 3, 4], + // [3, 4, 5], + // ]), + // rowCount: 3, + // columnCount: 3, + // unitId: '', + // sheetId: '', + // row: 0, + // column: 0, + // }); + // const result = textFunction.calculate(text1, text2); + // expect(transformToValue(result.getArrayValue())).toStrictEqual([['a1', 'a2', 'a3'], ['a2', 'a3', 'a4'], ['a3', 'a4', 'a5']]); + // }); + + // it('Text1 is array, text2 is single cell', () => { + // const text1 = ArrayValueObject.create({ + // calculateValueList: transformToValueObject([ + // [1, 2, 3], + // [2, 3, 4], + // [3, 4, 5], + // ]), + // rowCount: 3, + // columnCount: 3, + // unitId: '', + // sheetId: '', + // row: 0, + // column: 0, + // }); + // const text2 = StringValueObject.create('a'); + // const result = textFunction.calculate(text1, text2); + // expect(transformToValue(result.getArrayValue())).toStrictEqual([['1a', '2a', '3a'], ['2a', '3a', '4a'], ['3a', '4a', '5a']]); + // }); + + // it('Text1 is 3*1 array, text2 is 1*3 array', () => { + // const text1 = ArrayValueObject.create({ + // calculateValueList: transformToValueObject([ + // ['a'], + // ['b'], + // ['c'], + // ]), + // rowCount: 3, + // columnCount: 1, + // unitId: '', + // sheetId: '', + // row: 0, + // column: 0, + // }); + // const text2 = ArrayValueObject.create({ + // calculateValueList: transformToValueObject([ + // [1, 2, 3], + // ]), + // rowCount: 1, + // columnCount: 3, + // unitId: '', + // sheetId: '', + // row: 0, + // column: 0, + // }); + // const result = textFunction.calculate(text1, text2); + // expect(transformToValue(result.getArrayValue())).toStrictEqual([['a1', 'a2', 'a3'], ['b1', 'b2', 'b3'], ['c1', 'c2', 'c3']]); + // }); + + // it('Text1 is 2*2 array, text2 is 3*3 array', () => { + // const text1 = ArrayValueObject.create({ + // calculateValueList: transformToValueObject([ + // ['a', 'd'], + // [0, null], + // ]), + // rowCount: 2, + // columnCount: 2, + // unitId: '', + // sheetId: '', + // row: 0, + // column: 0, + // }); + // const text2 = ArrayValueObject.create({ + // calculateValueList: transformToValueObject([ + // [1, 2, 3], + // [0, null, 4], + // [3, 4, 5], + // ]), + // rowCount: 3, + // columnCount: 3, + // unitId: '', + // sheetId: '', + // row: 0, + // column: 0, + // }); + // const result = textFunction.calculate(text1, text2); + // expect(transformToValue(result.getArrayValue())).toStrictEqual([['a1', 'd2', '#N/A'], ['00', '', '#N/A'], ['#N/A', '#N/A', '#N/A']]); + // }); + }); +}); diff --git a/packages/engine-formula/src/functions/text/text/index.ts b/packages/engine-formula/src/functions/text/text/index.ts new file mode 100644 index 000000000000..504946c2bd07 --- /dev/null +++ b/packages/engine-formula/src/functions/text/text/index.ts @@ -0,0 +1,83 @@ +/** + * 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 { ErrorType } from '../../../basics/error-type'; +import { expandArrayValueObject } from '../../../engine/utils/array-object'; +import type { ArrayValueObject } from '../../../engine/value-object/array-value-object'; +import { type BaseValueObject, ErrorValueObject } from '../../../engine/value-object/base-value-object'; +import { NumberValueObject, StringValueObject } from '../../../engine/value-object/primitive-object'; +import { BaseFunction } from '../../base-function'; + +export class Text extends BaseFunction { + override calculate(text: BaseValueObject, formatText: BaseValueObject) { + if (text == null || formatText == null) { + return ErrorValueObject.create(ErrorType.NA); + } + + if (text.isError()) { + return text; + } + + if (formatText.isError()) { + return formatText; + } + + // get max row length + const maxRowLength = Math.max( + text.isArray() ? (text as ArrayValueObject).getRowCount() : 1, + formatText.isArray() ? (formatText as ArrayValueObject).getRowCount() : 1 + ); + + // get max column length + const maxColumnLength = Math.max( + text.isArray() ? (text as ArrayValueObject).getColumnCount() : 1, + formatText.isArray() ? (formatText as ArrayValueObject).getColumnCount() : 1 + ); + + const textArray = expandArrayValueObject(maxRowLength, maxColumnLength, text); + const formatTextArray = expandArrayValueObject(maxRowLength, maxColumnLength, formatText); + + return textArray.map((textValue, rowIndex, columnIndex) => { + if (textValue.isError() || textValue.isString() || textValue.isBoolean()) { + return textValue; + } + + let formatTextValue = formatTextArray.get(rowIndex, columnIndex); + + if (formatTextValue.isError()) { + return formatTextValue; + } + + if (formatTextValue.isBoolean()) { + return ErrorValueObject.create(ErrorType.VALUE); + } + + if (formatTextValue.isNull()) { + formatTextValue = StringValueObject.create(' '); + } + + const formatTextValueString = `${formatTextValue.getValue()}`; + + if (textValue.isNull()) { + textValue = NumberValueObject.create(0); + } + + const textValueNumber = textValue.getValue() as number; + + return NumberValueObject.create(textValueNumber, formatTextValueString); + }); + } +} diff --git a/packages/sheets-formula/src/locale/function-list/text/en-US.ts b/packages/sheets-formula/src/locale/function-list/text/en-US.ts index d4ce36a55365..3f8d3be0d968 100644 --- a/packages/sheets-formula/src/locale/function-list/text/en-US.ts +++ b/packages/sheets-formula/src/locale/function-list/text/en-US.ts @@ -487,8 +487,8 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + value: { name: 'value', detail: 'A numeric value that you want to be converted into text.' }, + formatText: { name: 'format_text', detail: 'A text string that defines the formatting that you want to be applied to the supplied value.' }, }, }, TEXTAFTER: { diff --git a/packages/sheets-formula/src/locale/function-list/text/ja-JP.ts b/packages/sheets-formula/src/locale/function-list/text/ja-JP.ts index 97695294c37a..fa12dc0eca61 100644 --- a/packages/sheets-formula/src/locale/function-list/text/ja-JP.ts +++ b/packages/sheets-formula/src/locale/function-list/text/ja-JP.ts @@ -488,8 +488,8 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + value: { name: '値', detail: 'テキストに変換する数値。' }, + formatText: { name: '数値形式', detail: '指定された値に適用する書式を定義するテキスト文字列。' }, }, }, TEXTAFTER: { diff --git a/packages/sheets-formula/src/locale/function-list/text/zh-CN.ts b/packages/sheets-formula/src/locale/function-list/text/zh-CN.ts index db356b9438f2..85999bba7d56 100644 --- a/packages/sheets-formula/src/locale/function-list/text/zh-CN.ts +++ b/packages/sheets-formula/src/locale/function-list/text/zh-CN.ts @@ -487,8 +487,8 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + value: { name: '值', detail: '要转换为文本的数值。' }, + formatText: { name: '数字格式', detail: '一个文本字符串,定义要应用于所提供值的格式。' }, }, }, TEXTAFTER: { diff --git a/packages/sheets-formula/src/services/function-list/text.ts b/packages/sheets-formula/src/services/function-list/text.ts index 3ffd99beb146..c14cb3de9255 100644 --- a/packages/sheets-formula/src/services/function-list/text.ts +++ b/packages/sheets-formula/src/services/function-list/text.ts @@ -750,16 +750,16 @@ export const FUNCTION_LIST_TEXT: IFunctionInfo[] = [ abstract: 'formula.functionList.TEXT.abstract', functionParameter: [ { - name: 'formula.functionList.TEXT.functionParameter.number1.name', - detail: 'formula.functionList.TEXT.functionParameter.number1.detail', - example: 'A1:A20', + name: 'formula.functionList.TEXT.functionParameter.value.name', + detail: 'formula.functionList.TEXT.functionParameter.value.detail', + example: '1.23', require: 1, repeat: 0, }, { - name: 'formula.functionList.TEXT.functionParameter.number2.name', - detail: 'formula.functionList.TEXT.functionParameter.number2.detail', - example: 'A1:A20', + name: 'formula.functionList.TEXT.functionParameter.formatText.name', + detail: 'formula.functionList.TEXT.functionParameter.formatText.detail', + example: '"$0.00"', require: 1, repeat: 0, },