Skip to content

Commit

Permalink
fix(formula): add OR/TEXT/LEN functions (#1593)
Browse files Browse the repository at this point in the history
* fix(formula): date default format

* feat(formula): add or function

* fix(formula): create StringValueObject

* feat(formual): add text function

* feat(formula): add len,lenb functions

* fix(formula): index row number
  • Loading branch information
Dushusir committed Mar 19, 2024
1 parent d884ee0 commit c26eed1
Show file tree
Hide file tree
Showing 27 changed files with 739 additions and 82 deletions.
2 changes: 1 addition & 1 deletion packages/engine-formula/src/basics/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

export const DEFFAULT_DATE_FORMAT = 'yyyy-mm-dd;@';
export const DEFAULT_DATE_FORMAT = 'yyyy/mm/dd;@';

/**
* Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,447 days after January 1, 1900.
Expand Down
4 changes: 4 additions & 0 deletions packages/engine-formula/src/basics/object-class-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class ObjectClassType extends Disposable {
return this.pattern;
}

/**
* Only used in NumberValueObject
* @param pattern
*/
setPattern(pattern: string) {
this.pattern = pattern;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/engine-formula/src/engine/ast-node/suffix-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,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);

Expand All @@ -194,12 +195,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;
}
Expand Down
44 changes: 44 additions & 0 deletions packages/engine-formula/src/engine/utils/char-kit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* 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.
*/

/**
* Korean in Excel does not count as two characters. Here we calculate all Chinese, Japanese and Korean characters as two characters.
*
* ā -> 1
* ー -> 2
*
* @param str
* @returns
*/
export function charLenByte(str: string) {
let byteCount = 0;

for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);

if (
(charCode >= 0x3040 && charCode <= 0x30FF) || // Japanese hiragana and katakana
(charCode >= 0x4E00 && charCode <= 0x9FFF) || // Chinese (simplified and traditional)
(charCode >= 0xAC00 && charCode <= 0xD7AF) // Korean language
) {
byteCount += 2;
} else {
byteCount += 1;
}
}

return byteCount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -459,8 +459,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
Expand All @@ -475,6 +477,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());

Expand All @@ -491,6 +498,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());

Expand Down
4 changes: 2 additions & 2 deletions packages/engine-formula/src/functions/date/date/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { DEFFAULT_DATE_FORMAT, excelDateSerial } from '../../../basics/date';
import { DEFAULT_DATE_FORMAT, excelDateSerial } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
import { expandArrayValueObject } from '../../../engine/utils/array-object';
import type { ArrayValueObject } from '../../../engine/value-object/array-value-object';
Expand Down Expand Up @@ -100,7 +100,7 @@ export class DateFunction extends BaseFunction {
}

const valueObject = NumberValueObject.create(currentSerial);
valueObject.setPattern(DEFFAULT_DATE_FORMAT);
valueObject.setPattern(DEFAULT_DATE_FORMAT);

return valueObject;
});
Expand Down
4 changes: 2 additions & 2 deletions packages/engine-formula/src/functions/date/edate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { DEFFAULT_DATE_FORMAT, excelDateSerial, excelSerialToDate } from '../../../basics/date';
import { DEFAULT_DATE_FORMAT, excelDateSerial, excelSerialToDate } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
import { expandArrayValueObject } from '../../../engine/utils/array-object';
import type { ArrayValueObject } from '../../../engine/value-object/array-value-object';
Expand Down Expand Up @@ -88,7 +88,7 @@ export class Edate extends BaseFunction {
const currentSerial = excelDateSerial(resultDate);

const valueObject = NumberValueObject.create(currentSerial);
valueObject.setPattern(DEFFAULT_DATE_FORMAT);
valueObject.setPattern(DEFAULT_DATE_FORMAT);

return valueObject;
});
Expand Down
4 changes: 2 additions & 2 deletions packages/engine-formula/src/functions/date/today/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { DEFFAULT_DATE_FORMAT, excelDateSerial } from '../../../basics/date';
import { DEFAULT_DATE_FORMAT, excelDateSerial } from '../../../basics/date';
import { ErrorType } from '../../../basics/error-type';
import type { BaseValueObject } from '../../../engine/value-object/base-value-object';
import { ErrorValueObject } from '../../../engine/value-object/base-value-object';
Expand All @@ -29,7 +29,7 @@ export class Today extends BaseFunction {

const currentSerial = excelDateSerial(new Date());
const valueObject = NumberValueObject.create(currentSerial);
valueObject.setPattern(DEFFAULT_DATE_FORMAT);
valueObject.setPattern(DEFAULT_DATE_FORMAT);
return valueObject;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { If } from './if';
import { Iferror } from './iferror';
import { Lambda } from './lambda';
import { Makearray } from './makearray';
import { Or } from './or';

export const functionLogical = [
[And, FUNCTION_NAMES_LOGICAL.AND],
[If, FUNCTION_NAMES_LOGICAL.IF],
[Lambda, FUNCTION_NAMES_LOGICAL.LAMBDA],
[Makearray, FUNCTION_NAMES_LOGICAL.MAKEARRAY],
[If, FUNCTION_NAMES_LOGICAL.IF],
[Or, FUNCTION_NAMES_LOGICAL.OR],
[Iferror, FUNCTION_NAMES_LOGICAL.IFERROR],
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* 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_LOGICAL } from '../../function-names';
import { Or } from '..';
import { ArrayValueObject, transformToValueObject } from '../../../../engine/value-object/array-value-object';
import { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';
import { ErrorType } from '../../../..';

describe('Test or function', () => {
const textFunction = new Or(FUNCTION_NAMES_LOGICAL.OR);

describe('Or', () => {
it('logical1 string', () => {
const logical1 = StringValueObject.create('a1');
const result = textFunction.calculate(logical1);
expect(result.getValue()).toBe(ErrorType.VALUE);
});
it('logical1 number 1', () => {
const logical1 = NumberValueObject.create(1);
const result = textFunction.calculate(logical1);
expect(result.getValue()).toBe(true);
});

it('logical1 number 0', () => {
const logical1 = NumberValueObject.create(0);
const result = textFunction.calculate(logical1);
expect(result.getValue()).toBe(false);
});

it('logical1 null', () => {
const logical1 = NullValueObject.create();
const result = textFunction.calculate(logical1);
expect(result.getValue()).toBe(ErrorType.VALUE);
});

it('logical1 true', () => {
const logical1 = BooleanValueObject.create(true);
const result = textFunction.calculate(logical1);
expect(result.getValue()).toBe(true);
});

it('logical2 false', () => {
const logical1 = BooleanValueObject.create(true);
const logical2 = BooleanValueObject.create(false);
const result = textFunction.calculate(logical1, logical2);
expect(result.getValue()).toBe(true);
});

it('logical1 is array, no logical value', () => {
const logical1 = ArrayValueObject.create({
calculateValueList: transformToValueObject([
['a1'],
['a2'],
]),
rowCount: 2,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = textFunction.calculate(logical1);
expect(result.getValue()).toBe(ErrorType.VALUE);
});

it('logical1 is array and logical2 is array', () => {
const logical1 = ArrayValueObject.create({
calculateValueList: transformToValueObject([
['a1'],
['a2'],
]),
rowCount: 2,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const logical2 = ArrayValueObject.create({
calculateValueList: transformToValueObject([
[true],
['a4'],
]),
rowCount: 2,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = textFunction.calculate(logical1, logical2);
expect(result.getValue()).toBe(true);
});

it('logical1 is array and logical2 is array, error value', () => {
const logical1 = ArrayValueObject.create({
calculateValueList: transformToValueObject([
['a1'],
['a2'],
]),
rowCount: 2,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const logical2 = ArrayValueObject.create({
calculateValueList: transformToValueObject([
[false],
['#NAME?'],
]),
rowCount: 2,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = textFunction.calculate(logical1, logical2);
expect(result.getValue()).toBe('#NAME?');
});
});
});
Loading

0 comments on commit c26eed1

Please sign in to comment.