Skip to content

Commit

Permalink
feat(formula): add LEFT function implementation dream-num#2572
Browse files Browse the repository at this point in the history
  • Loading branch information
sneha khaitan authored and sneha khaitan committed Jun 21, 2024
1 parent b3393ef commit 69d5f65
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 15 deletions.
2 changes: 2 additions & 0 deletions packages/engine-formula/src/functions/text/function-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { Len } from './len';
import { Lenb } from './lenb';
import { Text } from './text';
import { Lower } from './lower';
import { Left } from './left';

export const functionText = [
[Concatenate, FUNCTION_NAMES_TEXT.CONCATENATE],
[Len, FUNCTION_NAMES_TEXT.LEN],
[Lenb, FUNCTION_NAMES_TEXT.LENB],
[Text, FUNCTION_NAMES_TEXT.TEXT],
[Lower, FUNCTION_NAMES_TEXT.LOWER],
[Left, FUNCTION_NAMES_TEXT.LEFT],
];
170 changes: 170 additions & 0 deletions packages/engine-formula/src/functions/text/left/__test__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* 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 { Left } from '../index';
import { NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';
import { ArrayValueObject, transformToValue, transformToValueObject } from '../../../../engine/value-object/array-value-object';
import { ErrorType } from '../../../../basics/error-type';

describe('Test LEFT function', () => {
const leftFunction = new Left(FUNCTION_NAMES_TEXT.LEFT);

describe('Left', () => {
it('Extracts single character from single cell', () => {
const text = StringValueObject.create('Hello');
const numChars = NumberValueObject.create(1);
const result = leftFunction.calculate(text, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([['H']]);
});

it('Extracts multiple characters from single cell', () => {
const text = StringValueObject.create('Hello World');
const numChars = NumberValueObject.create(5);
const result = leftFunction.calculate(text, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([['Hello']]);
});

it('Handles no numChars passed, defaults to 1', () => {
const text = StringValueObject.create('Hello');
const result = leftFunction.calculate(text);
expect(transformToValue(result.getArrayValue())).toStrictEqual([['H']]);
});

it('Handles numChars larger than text length', () => {
const text = StringValueObject.create('Hi');
const numChars = NumberValueObject.create(10);
const result = leftFunction.calculate(text, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([['Hi']]);
});

it('Handles empty text', () => {
const text = StringValueObject.create('');
const numChars = NumberValueObject.create(1);
const result = leftFunction.calculate(text, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([['']]);
});

it('Handles negative numChars', () => {
const text = StringValueObject.create('Hello');
const numChars = NumberValueObject.create(-1);
const result = leftFunction.calculate(text, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([[ErrorType.VALUE]]);
});

it('Extracts characters from array by specifying single numChar', () => {
const textArray = new ArrayValueObject({
calculateValueList: transformToValueObject([
['Hello'],
['World'],
['New World'],
['Example'],
]),
rowCount: 4,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const numChars = NumberValueObject.create(3);
const result = leftFunction.calculate(textArray, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([
['Hel'], // First character of 'Hello'
['Wor'], // First two characters of 'World'
['New'], // First three characters of 'Test'
['Exa'], // First four characters of 'Example'
]);
});
it('Extracts characters from array', () => {
const textArray = new ArrayValueObject({
calculateValueList: transformToValueObject([
['Hello'],
['World'],
['Test'],
['Example'],
]),
rowCount: 4,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const numCharsArray = new ArrayValueObject({
calculateValueList: transformToValueObject([
[1],
[2],
[3],
[4],
]),
rowCount: 4,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = leftFunction.calculate(textArray, numCharsArray);
expect(transformToValue(result.getArrayValue())).toStrictEqual([
['H'], // First character of 'Hello'
['Wo'], // First two characters of 'World'
['Tes'], // First three characters of 'Test'
['Exam'], // First four characters of 'Example'
]);
});

it('Handles numChars not provided for array', () => {
const textArray = new ArrayValueObject({
calculateValueList: transformToValueObject([
['Hello'],
['World'],
['Test'],
['Example'],
]),
rowCount: 4,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = leftFunction.calculate(textArray);
expect(transformToValue(result.getArrayValue())).toStrictEqual([
['H'], // Default to first character of 'Hello'
['W'], // Default to first character of 'World'
['T'], // Default to first character of 'Test'
['E'], // Default to first character of 'Example'
]);
});

it('Handles extracting from text with emojis', () => {
const text = StringValueObject.create('Hello 😊 World');
const numChars = NumberValueObject.create(7);
const result = leftFunction.calculate(text, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([['Hello 😊']]);
});

it('Handles extracting with numChars as zero', () => {
const text = StringValueObject.create('Hello');
const numChars = NumberValueObject.create(0);
const result = leftFunction.calculate(text, numChars);
expect(transformToValue(result.getArrayValue())).toStrictEqual([['']]);
});
});
});
94 changes: 94 additions & 0 deletions packages/engine-formula/src/functions/text/left/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* 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 { 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 Left extends BaseFunction {
override minParams = 1;

override maxParams = 2;

override calculate(text: BaseValueObject, numChars?: BaseValueObject) {
if (text.isError()) {
return text;
}

if (numChars && numChars.isError()) {
return numChars;
}

if (text.isArray()) {
const maxRowLength = Math.max(
text.isArray() ? (text as ArrayValueObject).getRowCount() : 1,
numChars && numChars.isArray() ? (numChars as ArrayValueObject).getRowCount() : 1
);

const maxColumnLength = Math.max(
text.isArray() ? (text as ArrayValueObject).getColumnCount() : 1,
numChars && numChars.isArray() ? (numChars as ArrayValueObject).getColumnCount() : 1
);

const textArray = expandArrayValueObject(maxRowLength, maxColumnLength, text);
const numCharsArray = numChars ? expandArrayValueObject(maxRowLength, maxColumnLength, numChars) : null;

return textArray.map((textValue, rowIndex, columnIndex) => {
if (textValue.isError()) {
return textValue;
}

const numCharsValue = numCharsArray ? numCharsArray.get(rowIndex, columnIndex) : NumberValueObject.create(1);
if (numCharsValue?.isError()) {
return numCharsValue;
}

const numCharsValueNumber = numCharsValue?.getValue() as number;

if (typeof numCharsValueNumber !== 'number' || numCharsValueNumber < 0) {
return ErrorValueObject.create(ErrorType.VALUE);
}

const textValueString = `${textValue.getValue()}`;
return StringValueObject.create(Array.from(textValueString).slice(0, numCharsValueNumber).join(''));
});
}

return this._handleSingleText(text, numChars);
}

private _handleSingleText(text: BaseValueObject, numChars?: BaseValueObject) {
if (text.isError()) {
return text;
}

if (numChars && numChars.isError()) {
return numChars;
}

const textValueString = `${text.getValue()}`;
const numCharsValueNumber = numChars ? (numChars.getValue() as number) : 1;

if (typeof numCharsValueNumber !== 'number' || numCharsValueNumber < 0) {
return ArrayValueObject.createByArray([[ErrorType.VALUE]]);
}

return ArrayValueObject.createByArray([[Array.from(textValueString).slice(0, numCharsValueNumber).join('')]]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ export default {
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
text: { name: 'text', detail: 'The text string that contains the characters you want to extract.' },
numChars: { name: 'num_chars', detail: ' Optional. Specifies the number of characters you want LEFT to extract.' },
},
},
LEFTB: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ export default {
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
text: { name: 'text', detail: 'The text string that contains the characters you want to extract.' },
numChars: { name: 'num_chars', detail: ' Optional. Specifies the number of characters you want LEFT to extract.' },
},
},
LEFTB: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ export default {
},
],
functionParameter: {
number1: { name: 'number1', detail: 'первый' },
number2: { name: 'number2', detail: 'второй' },
text: { name: 'text', detail: 'The text string that contains the characters you want to extract.' },
numChars: { name: 'num_chars', detail: ' Optional. Specifies the number of characters you want LEFT to extract.' },
},
},
LEFTB: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ export default {
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
text: { name: 'text', detail: 'The text string that contains the characters to extract.' },
numChars: { name: 'num_chars', detail: ' Optional. Specifies the number of characters for LEFT to extract.' },
},
},
LEFTB: {
Expand Down
14 changes: 7 additions & 7 deletions packages/sheets-formula/src/services/function-list/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,17 +332,17 @@ export const FUNCTION_LIST_TEXT: IFunctionInfo[] = [
abstract: 'formula.functionList.LEFT.abstract',
functionParameter: [
{
name: 'formula.functionList.LEFT.functionParameter.number1.name',
detail: 'formula.functionList.LEFT.functionParameter.number1.detail',
example: 'A1:A20',
name: 'formula.functionList.LEFT.functionParameter.text.name',
detail: 'formula.functionList.LEFT.functionParameter.text.detail',
example: '"Hello Univer"',
require: 1,
repeat: 0,
},
{
name: 'formula.functionList.LEFT.functionParameter.number2.name',
detail: 'formula.functionList.LEFT.functionParameter.number2.detail',
example: 'A1:A20',
require: 1,
name: 'formula.functionList.LEFT.functionParameter.numChars.name',
detail: 'formula.functionList.LEFT.functionParameter.numChars.detail',
example: '1',
require: 0,
repeat: 0,
},
],
Expand Down

0 comments on commit 69d5f65

Please sign in to comment.