Skip to content

Commit 94f59e6

Browse files
committed
[IMP] evaluation: formula results can be used as reference
Enabling the reuse of functions returning references as arguments in functions expecting references. Task: 5428577
1 parent f8677dc commit 94f59e6

File tree

12 files changed

+298
-103
lines changed

12 files changed

+298
-103
lines changed

packages/o-spreadsheet-engine/src/functions/helpers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ const expectNumberValueError = (value: string) =>
5757
value
5858
);
5959

60+
export const expectReferenceError = _t(
61+
"The function [[FUNCTION_NAME]] expects a reference to a cell or range."
62+
);
63+
6064
export const expectNumberRangeError = (lowerBound: number, upperBound: number, value: number) =>
6165
_t(
6266
"The function [[FUNCTION_NAME]] expects a number value between %s and %s inclusive, but receives %s.",

packages/o-spreadsheet-engine/src/functions/module_info.ts

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { getFullReference, setXcToFixedReferenceType, splitReference } from "../helpers/references";
1+
import { toXC } from "../helpers/coordinates";
22
import { _t } from "../translation";
3-
import { CellValueType } from "../types/cells";
43
import { CellErrorType, EvaluationError } from "../types/errors";
54
import { AddFunctionDescription } from "../types/functions";
6-
import { FunctionResultObject, Matrix, Maybe } from "../types/misc";
5+
import { Arg, FunctionResultObject, Maybe } from "../types/misc";
76
import { arg } from "./arguments";
8-
import { isEvaluationError, toString } from "./helpers";
7+
import { isEvaluationError, toMatrix, toString } from "./helpers";
98

109
// -----------------------------------------------------------------------------
1110
// CELL
@@ -35,45 +34,43 @@ export const CELL = {
3534
description: _t("Gets information about a cell."),
3635
args: [
3736
arg("info_type (string)", _t("The type of information requested."), CELL_INFO_TYPES),
38-
arg("reference (meta, range<meta>)", _t("The reference to the cell.")),
37+
arg("reference (any, range<any>)", _t("The reference to the cell.")),
3938
],
40-
compute: function (info: Maybe<FunctionResultObject>, reference: Matrix<{ value: string }>) {
39+
compute: function (info: Maybe<FunctionResultObject>, reference: Arg) {
4140
const _info = toString(info).toLowerCase();
4241
if (!CELL_INFO_TYPES.map((type) => type.value).includes(_info)) {
4342
return new EvaluationError(
4443
_t("The info_type should be one of %s.", CELL_INFO_TYPES.join(", "))
4544
);
4645
}
4746

48-
const sheetId = this.__originSheetId;
49-
const _reference = reference[0][0].value;
50-
let { sheetName, xc } = splitReference(_reference);
51-
// only put the sheet name if the referenced range is in another sheet than the cell the formula is on
52-
sheetName = sheetName === this.getters.getSheetName(sheetId) ? undefined : sheetName;
53-
const fixedRef = getFullReference(sheetName, setXcToFixedReferenceType(xc, "colrow"));
54-
const range = this.getters.getRangeFromSheetXC(sheetId, fixedRef);
47+
const firstReference = toMatrix(reference)[0][0];
48+
const position = firstReference.position;
49+
if (position === undefined) {
50+
return new EvaluationError(_t("The reference is invalid."));
51+
}
5552

5653
switch (_info) {
5754
case "address":
58-
return this.getters.getRangeString(range, sheetId);
55+
const sheetName =
56+
this.__originSheetId === position.sheetId
57+
? ""
58+
: this.getters.getSheetName(position.sheetId) + "!";
59+
return sheetName + toXC(position.col, position.row, { colFixed: true, rowFixed: true });
5960
case "col":
60-
return range.zone.left + 1;
61+
return position.col + 1;
6162
case "contents": {
62-
const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
63-
return this.getters.getEvaluatedCell(position).value;
63+
return firstReference.value;
6464
}
6565
case "format": {
66-
const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
67-
return this.getters.getEvaluatedCell(position).format || "";
66+
return firstReference.format || "";
6867
}
6968
case "row":
70-
return range.zone.top + 1;
69+
return position.row + 1;
7170
case "type": {
72-
const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
73-
const type = this.getters.getEvaluatedCell(position).type;
74-
if (type === CellValueType.empty) {
71+
if (firstReference.type === "empty") {
7572
return "b"; // blank
76-
} else if (type === CellValueType.text) {
73+
} else if (firstReference.type === "text") {
7774
return "l"; // label
7875
} else {
7976
return "v"; // value

packages/o-spreadsheet-engine/src/functions/module_lookup.ts

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { getPivotTooBigErrorMessage } from "../components/translations_terms";
22
import { PIVOT_MAX_NUMBER_OF_CELLS } from "../constants";
3-
import { getFullReference, splitReference } from "../helpers/";
4-
import { toCartesian, toXC } from "../helpers/coordinates";
3+
import { getFullReference } from "../helpers/";
4+
import { toXC } from "../helpers/coordinates";
55
import { isFormula, range } from "../helpers/misc";
66
import {
77
addAlignFormatToPivotHeader,
88
getPivotStyleFromFnArgs,
99
} from "../helpers/pivot/pivot_helpers";
10-
import { toZone } from "../helpers/zones";
1110
import { _t } from "../translation";
1211
import {
1312
CellErrorType,
@@ -16,7 +15,7 @@ import {
1615
NotAvailableError,
1716
} from "../types/errors";
1817
import { AddFunctionDescription } from "../types/functions";
19-
import { Arg, FunctionResultObject, Matrix, Maybe, UID, Zone } from "../types/misc";
18+
import { Arg, FunctionResultObject, Matrix, Maybe, Zone } from "../types/misc";
2019
import { arg } from "./arguments";
2120
import { expectNumberGreaterThanOrEqualToOne } from "./helper_assert";
2221
import {
@@ -28,8 +27,8 @@ import {
2827
import {
2928
dichotomicSearch,
3029
expectNumberRangeError,
30+
expectReferenceError,
3131
generateMatrix,
32-
isEvaluationError,
3332
linearSearch,
3433
LinearSearchMode,
3534
strictToInteger,
@@ -134,15 +133,15 @@ export const COLUMN = {
134133
description: _t("Column number of a specified cell."),
135134
args: [
136135
arg(
137-
"cell_reference (meta, range<meta>, default='this cell')",
136+
"cell_reference (any, range<any>, default='this cell')",
138137
_t(
139138
"The cell whose column number will be returned. Column A corresponds to 1. By default, the function use the cell in which the formula is entered."
140139
)
141140
),
142141
],
143-
compute: function (cellReference: Matrix<{ value: string }> | undefined) {
142+
compute: function (cellReference: Arg) {
144143
if (cellReference === undefined) {
145-
if (this.__originCellPosition?.col === undefined) {
144+
if (this.__originCellPosition === undefined) {
146145
return new EvaluationError(
147146
_t(
148147
"In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."
@@ -151,20 +150,20 @@ export const COLUMN = {
151150
}
152151
return this.__originCellPosition.col + 1;
153152
}
154-
if (cellReference[0][0] === undefined) {
153+
const _cellReference = toMatrix(cellReference);
154+
const firstCell = _cellReference[0][0];
155+
if (firstCell === undefined) {
156+
// to do: should work with ranges out of bounds
155157
return new EvaluationError(_t("The range is out of bounds."));
156158
}
157-
if (cellReference[0][0].value === CellErrorType.InvalidReference) {
158-
return cellReference[0][0]; // return the same error
159+
if (firstCell.position === undefined) {
160+
return new InvalidReferenceError(expectReferenceError);
159161
}
160-
const left = this.getters.getRangeFromSheetXC(
161-
this.getters.getActiveSheetId(),
162-
cellReference[0][0].value
163-
).zone.left;
164-
if (cellReference.length === 1) {
162+
const left = firstCell.position.col;
163+
if (_cellReference.length === 1) {
165164
return left + 1;
166165
}
167-
return generateMatrix(cellReference.length, 1, (col, row) => ({ value: left + col + 1 }));
166+
return generateMatrix(_cellReference.length, 1, (col) => ({ value: left + col + 1 }));
168167
},
169168
isExported: true,
170169
} satisfies AddFunctionDescription;
@@ -528,13 +527,13 @@ export const ROW = {
528527
description: _t("Row number of a specified cell."),
529528
args: [
530529
arg(
531-
"cell_reference (meta, range<meta>, default='this cell')",
530+
"cell_reference (any, range<any>, default='this cell')",
532531
_t(
533532
"The cell whose row number will be returned. By default, this function uses the cell in which the formula is entered."
534533
)
535534
),
536535
],
537-
compute: function (cellReference: Matrix<{ value: string }> | undefined) {
536+
compute: function (cellReference: Arg) {
538537
if (cellReference === undefined) {
539538
if (this.__originCellPosition?.row === undefined) {
540539
return new EvaluationError(
@@ -545,20 +544,20 @@ export const ROW = {
545544
}
546545
return this.__originCellPosition.row + 1;
547546
}
548-
if (cellReference[0][0] === undefined) {
547+
const _cellReference = toMatrix(cellReference);
548+
const firstCell = _cellReference[0][0];
549+
if (firstCell === undefined) {
550+
// to do: should work with ranges out of bounds
549551
return new EvaluationError(_t("The range is out of bounds."));
550552
}
551-
if (cellReference[0][0].value === CellErrorType.InvalidReference) {
552-
return cellReference[0][0]; // return the same error
553+
if (firstCell.position === undefined) {
554+
return new InvalidReferenceError(expectReferenceError);
553555
}
554-
const top = this.getters.getRangeFromSheetXC(
555-
this.getters.getActiveSheetId(),
556-
cellReference[0][0].value
557-
).zone.top;
558-
if (cellReference[0].length === 1) {
556+
const top = firstCell.position.row;
557+
if (_cellReference[0].length === 1) {
559558
return top + 1;
560559
}
561-
return generateMatrix(1, cellReference[0].length, (col, row) => ({ value: top + row + 1 }));
560+
return generateMatrix(1, _cellReference[0].length, (col, row) => ({ value: top + row + 1 }));
562561
},
563562
isExported: true,
564563
} satisfies AddFunctionDescription;
@@ -1006,7 +1005,7 @@ export const OFFSET = {
10061005
),
10071006
args: [
10081007
arg(
1009-
"cell_reference (meta, range<meta>)",
1008+
"cell_reference (any, range<any>)",
10101009
_t("The starting point from which to count the offset rows and columns.")
10111010
),
10121011
arg("offset_rows (number)", _t("The number of rows to offset by.")),
@@ -1021,26 +1020,29 @@ export const OFFSET = {
10211020
),
10221021
],
10231022
compute: function (
1024-
cellReference: Matrix<{ value: string }>,
1023+
cellReference: Arg,
10251024
offsetRows: Maybe<FunctionResultObject>,
10261025
offsetColumns: Maybe<FunctionResultObject>,
10271026
height: Maybe<FunctionResultObject>,
10281027
width: Maybe<FunctionResultObject>
10291028
) {
1030-
if (isEvaluationError(cellReference[0][0].value)) {
1031-
return cellReference[0][0];
1029+
if (cellReference === undefined) {
1030+
return new InvalidReferenceError(expectReferenceError);
10321031
}
1032+
const _cellReference = toMatrix(cellReference);
1033+
const firstCell = _cellReference[0][0];
10331034

1034-
const ref0 = cellReference[0][0].value;
1035-
if (!ref0) {
1036-
return new EvaluationError(
1037-
"In this context, the function OFFSET needs to have a cell or range in parameter."
1038-
);
1035+
if (firstCell === undefined) {
1036+
// to do: should work with ranges out of bounds
1037+
return new EvaluationError(_t("The range is out of bounds."));
10391038
}
1040-
const zone = toZone(ref0);
10411039

1042-
let offsetHeight = cellReference[0].length;
1043-
let offsetWidth = cellReference.length;
1040+
if (firstCell.position === undefined) {
1041+
return new InvalidReferenceError(expectReferenceError);
1042+
}
1043+
1044+
let offsetHeight = _cellReference[0].length;
1045+
let offsetWidth = _cellReference.length;
10441046

10451047
if (height) {
10461048
const _height = toNumber(height, this.locale);
@@ -1062,11 +1064,6 @@ export const OFFSET = {
10621064
offsetWidth = _width;
10631065
}
10641066

1065-
const { sheetName } = splitReference(ref0);
1066-
1067-
const sheetId =
1068-
(sheetName && this.getters.getSheetIdByName(sheetName)) || this.getters.getActiveSheetId();
1069-
10701067
const _offsetRows = toNumber(offsetRows, this.locale);
10711068
const _offsetColumns = toNumber(offsetColumns, this.locale);
10721069

@@ -1075,8 +1072,8 @@ export const OFFSET = {
10751072
this.updateDependencies?.(originPosition);
10761073
}
10771074

1078-
const startingCol = zone.left + _offsetColumns;
1079-
const startingRow = zone.top + _offsetRows;
1075+
const startingCol = firstCell.position.col + _offsetColumns;
1076+
const startingRow = firstCell.position.row + _offsetRows;
10801077

10811078
if (startingCol < 0 || startingRow < 0) {
10821079
return new InvalidReferenceError(_t("OFFSET evaluates to an out of bounds range."));
@@ -1089,6 +1086,7 @@ export const OFFSET = {
10891086
bottom: startingRow + offsetHeight - 1,
10901087
};
10911088

1089+
const sheetId = firstCell.position.sheetId;
10921090
const range = this.getters.getRangeFromZone(sheetId, dependencyZone);
10931091
if (range.invalidXc || range.invalidSheetName) {
10941092
return new InvalidReferenceError();
@@ -1246,13 +1244,12 @@ export const TAKE = {
12461244

12471245
export const FORMULATEXT = {
12481246
description: _t("Returns a formula as a string."),
1249-
args: [arg("cell_reference (meta)", _t("A reference to a cell."))],
1250-
compute: function (cellReference: { value: string }) {
1251-
const { sheetName, xc } = splitReference(cellReference.value);
1252-
const { col, row } = toCartesian(xc);
1253-
const sheetId: UID =
1254-
(sheetName && this.getters.getSheetIdByName(sheetName)) ?? this.__originSheetId;
1255-
const cell = this.getters.getCell({ sheetId, col, row });
1247+
args: [arg("cell_reference (any)", _t("A reference to a cell."))],
1248+
compute: function (cellReference: Maybe<FunctionResultObject>) {
1249+
if (cellReference?.position === undefined) {
1250+
return new InvalidReferenceError(expectReferenceError);
1251+
}
1252+
const cell = this.getters.getCell(cellReference.position);
12561253
if (cell && isFormula(cell.content)) {
12571254
return cell.content;
12581255
} else {

packages/o-spreadsheet-engine/src/functions/module_math.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { splitReference } from "../helpers";
2-
import { toZone } from "../helpers/zones";
31
import { isSubtotalCell } from "../plugins/ui_feature/subtotal_evaluation";
42
import { _t } from "../translation";
53
import { EvaluatedCell } from "../types/cells";
6-
import { DivisionByZeroError, EvaluationError } from "../types/errors";
4+
import { DivisionByZeroError, EvaluationError, InvalidReferenceError } from "../types/errors";
75
import { AddFunctionDescription } from "../types/functions";
86
import {
97
Arg,
@@ -18,6 +16,7 @@ import { assertNotZero } from "./helper_assert";
1816
import { countUnique, sum } from "./helper_math";
1917
import { getUnitMatrix } from "./helper_matrices";
2018
import {
19+
expectReferenceError,
2120
generateMatrix,
2221
inferFormat,
2322
isDataNonEmpty,
@@ -26,6 +25,7 @@ import {
2625
strictToNumber,
2726
toBoolean,
2827
toInteger,
28+
toMatrix,
2929
toNumber,
3030
toString,
3131
visitMatchingRanges,
@@ -1372,14 +1372,11 @@ export const SUBTOTAL = {
13721372
...subtotalFunctionOptionsExcludeHiddenRows,
13731373
]),
13741374
arg(
1375-
"ref (meta, range<meta>, repeating)",
1375+
"ref (any, range<any>, repeating)",
13761376
_t("Range or reference for which you want the subtotal.")
13771377
),
13781378
],
1379-
compute: function (
1380-
functionCode: Maybe<FunctionResultObject>,
1381-
...refs: Matrix<{ value: string }>[]
1382-
) {
1379+
compute: function (functionCode: Maybe<FunctionResultObject>, ...refs: Arg[]) {
13831380
let code = toInteger(functionCode, this.locale);
13841381
let acceptHiddenCells = true;
13851382
if (code > 100) {
@@ -1395,20 +1392,20 @@ export const SUBTOTAL = {
13951392
const evaluatedCellToKeep: EvaluatedCell[] = [];
13961393

13971394
for (const ref of refs) {
1398-
const ref0 = ref[0][0];
1399-
const sheetName = splitReference(ref0.value).sheetName;
1400-
const sheetId = sheetName ? this.getters.getSheetIdByName(sheetName) : this.__originSheetId;
1401-
1402-
if (!sheetId) continue;
1403-
const { top, left } = toZone(ref0.value);
1404-
const right = left + ref.length - 1;
1405-
const bottom = top + ref[0].length - 1;
1395+
const _ref = toMatrix(ref);
1396+
const firstPosition = _ref[0][0]?.position;
1397+
if (firstPosition === undefined) {
1398+
return new InvalidReferenceError(expectReferenceError);
1399+
}
1400+
const right = firstPosition.col + _ref.length - 1;
1401+
const bottom = firstPosition.row + _ref[0].length - 1;
1402+
const sheetId = firstPosition.sheetId;
14061403

1407-
for (let row = top; row <= bottom; row++) {
1404+
for (let row = firstPosition.row; row <= bottom; row++) {
14081405
if (this.getters.isRowFiltered(sheetId, row)) continue;
14091406
if (!acceptHiddenCells && this.getters.isRowHiddenByUser(sheetId, row)) continue;
14101407

1411-
for (let col = left; col <= right; col++) {
1408+
for (let col = firstPosition.col; col <= right; col++) {
14121409
const cell = this.getters.getCorrespondingFormulaCell({ sheetId, col, row });
14131410
if (!cell || !isSubtotalCell(cell)) {
14141411
evaluatedCellToKeep.push(this.getters.getEvaluatedCell({ sheetId, col, row }));

0 commit comments

Comments
 (0)