From ecdc7a5ff91227b734d1a0bb181208c59f4ff347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Rahir=20=28rar=29?= Date: Tue, 27 Aug 2024 15:35:23 +0000 Subject: [PATCH] [FIX] XLSX: Export values along the formula string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While it seems redundant to export the evaluated final value of a formula in the xlsx file, that information is acutally relevant for parsers that want to process "raw" data from the xlsx file. Among those parsers lie Odoo's import process... closes odoo/o-spreadsheet#4943 Task: 4141855 X-original-commit: 3b2956e43b1fbb895dcb751f38b2167a90919e4d Signed-off-by: Lucas Lefèvre (lul) Signed-off-by: Rémi Rahir (rar) --- src/xlsx/functions/cells.ts | 20 +- src/xlsx/helpers/content_helpers.ts | 13 + .../__snapshots__/xlsx_export.test.ts.snap | 1720 +++++++++++++---- tests/xlsx/xlsx_export.test.ts | 40 +- 4 files changed, 1435 insertions(+), 358 deletions(-) diff --git a/src/xlsx/functions/cells.ts b/src/xlsx/functions/cells.ts index 4930d5b80a..d3717fee20 100644 --- a/src/xlsx/functions/cells.ts +++ b/src/xlsx/functions/cells.ts @@ -10,10 +10,9 @@ import { functionRegistry } from "../../functions"; import { formatValue, isNumber } from "../../helpers"; import { mdyDateRegexp, parseDateTime, timeRegexp, ymdDateRegexp } from "../../helpers/dates"; import { ExcelCellData, Format } from "../../types"; -import { CellErrorType } from "../../types/errors"; import { XMLAttributes, XMLString } from "../../types/xlsx"; import { FORCE_DEFAULT_ARGS_FUNCTIONS, NON_RETROCOMPATIBLE_FUNCTIONS } from "../constants"; -import { pushElement } from "../helpers/content_helpers"; +import { getCellType, pushElement } from "../helpers/content_helpers"; import { escapeXml } from "../helpers/xml_helpers"; import { DEFAULT_LOCALE } from "./../../types/locale"; @@ -26,18 +25,15 @@ export function addFormula(cell: ExcelCellData): { return { attrs: [], node: escapeXml`` }; } - const attrs: XMLAttributes = []; - let node = escapeXml``; + const type = getCellType(cell.value); + if (type === undefined) { + return { attrs: [], node: escapeXml`` }; + } - let cycle = escapeXml``; + const attrs: XMLAttributes = [["t", type]]; const XlsxFormula = adaptFormulaToExcel(formula); - // hack for cycles : if we don't set a value (be it 0 or #VALUE!), it will appear as invisible on excel, - // Making it very hard for the client to find where the recursion is. - if (cell.value === CellErrorType.CircularDependency) { - attrs.push(["t", "str"]); - cycle = escapeXml/*xml*/ `${cell.value}`; - } - node = escapeXml/*xml*/ `${XlsxFormula}${cycle}`; + + const node = escapeXml/*xml*/ `${XlsxFormula}${cell.value}`; return { attrs, node }; } diff --git a/src/xlsx/helpers/content_helpers.ts b/src/xlsx/helpers/content_helpers.ts index 74ffe3c274..970e2ef770 100644 --- a/src/xlsx/helpers/content_helpers.ts +++ b/src/xlsx/helpers/content_helpers.ts @@ -53,6 +53,19 @@ export function convertOperator(operator: ConditionalFormattingOperatorValues): // WORKSHEET HELPERS // ------------------------------------- +export function getCellType(value: number | string | boolean | null): string | undefined { + switch (typeof value) { + case "boolean": + return "b"; + case "string": + return "str"; + case "number": + return "n"; + default: + return undefined; + } +} + export function convertHeightToExcel(height: number): number { return Math.round(HEIGHT_FACTOR * height * 100) / 100; } diff --git a/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap b/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap index b4550c123c..34255ac654 100644 --- a/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap +++ b/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap @@ -9681,10 +9681,11 @@ exports[`Test XLSX export Export data filters Export data filters snapshot 1`] = 5 - + "" + @@ -10785,17 +10786,23 @@ exports[`Test XLSX export Generic sheets (style, hidden, size, cf) Simple model - + "this is a quote: """ + + this is a quote: \\" + - + '<Sheet2>'!B2 + + 42 + @@ -10809,10 +10816,13 @@ exports[`Test XLSX export Generic sheets (style, hidden, size, cf) Simple model - + (1+2)/3 + + 1 + @@ -13861,10 +13871,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + ABS(-5.5) + + 5.5 + @@ -13898,10 +13911,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + ACOS(1) + + 0 + @@ -13935,10 +13951,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + ACOSH(2) + + 1.3169578969248166 + @@ -13972,15 +13991,21 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + _xlfn.ACOT(1) + + 0.7853981633974483 + - + _xlfn.ACOT(_xlfn.ACOT(1)) + + 0.9050225767665427 + @@ -14014,10 +14039,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + _xlfn.ACOTH(2) + + 0.5493061443340548 + @@ -14051,10 +14079,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + AND(TRUE,TRUE) + + true + @@ -14088,10 +14119,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + ASIN(0.5) + + 0.5235987755982989 + @@ -14125,10 +14159,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + ASINH(2) + + 1.4436354751788103 + @@ -14157,17 +14194,23 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + ATAN(1) + + 0.7853981633974483 + - + ATAN2(-1,0) + + 3.141592653589793 + @@ -14176,10 +14219,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + ATANH(0.7) + + 0.8673005276940531 + @@ -14208,10 +14254,13 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + AVEDEV(I2:I9) + + 2959.1624999999995 + @@ -14240,1088 +14289,1553 @@ exports[`Test XLSX export formulas All exportable formulas 1`] = ` - + AVERAGE(H2:H9) + + 26.25 + - + AVERAGEA(G2:H9) + + 13.125 + - + AVERAGEIF(J2:J9,">150000") + + 222797 + - + AVERAGEIFS(I2:I9,H2:H9,">=30",K2:K9,"<10") + + 8376.65 + - + CEILING(20.4,1) + + 21 + - + _xlfn.CEILING.MATH(-5.5,1,0) + + -5 + - + _xlfn.CEILING.PRECISE(230,100) + + 300 + - + CHAR(74) + + J + - + COLUMN(C4) + + 3 + - + COLUMNS(A5:D12) + + 4 + - + _xlfn.CONCAT(1,23) + + 123 + - + CONCATENATE("BUT, ","MICHEL") + + BUT, MICHEL + - + COS(PI()/3) + + 0.5000000000000001 + - + COSH(2) + + 3.7621956910836314 + - + _xlfn.COT(PI()/6) + + 1.7320508075688774 + - + _xlfn.COTH(0.5) + + 2.163953413738653 + - + COUNT(1,"a","5","2021-03-14") + + 3 + - + COUNTA(1,"a","5","2021-03-14") + + 4 + - + COUNTBLANK("","1",3,FALSE) + + 1 + - + COUNTIF(H2:H9,">30") + + 2 + - + COUNTIFS(H2:H9,">25",K2:K9,"<4") + + 3 + - + COVAR(H2:H9,K2:K9) + + -2119.25 + - + _xlfn.COVARIANCE.P(K2:K9,H2:H9) + + -2119.25 + - + _xlfn.COVARIANCE.P(I2:I9,J2:J9) + + 237217364.71640626 + - + _xlfn.CSC(PI()/4) + + 1.4142135623730951 + - + _xlfn.CSCH(PI()/3) + + 0.8004052928885931 + - + DATE(2020,5,25) + + 43976 + - + DATEVALUE("1969-08-15") + + 25430 + - + DAVERAGE(G1:K9,"Tot. Score",J12:J13) + + 151434.625 + - + DAY("2020-03-17") + + 17 + - + _xlfn.DAYS("2022-03-17","2021-03-17") + + 365 + - + DCOUNT(G1:K9,"Name",H12:H13) + + 0 + - + DCOUNTA(G1:K9,"Name",H12:H13) + + 3 + - + _xlfn.DECIMAL(20,16) + + 32 + - + DEGREES(PI()/4) + + 45 + - + DGET(G1:K9,"Hours Played",G12:G13) + + 252.4 + - + DMAX(G1:K9,"Tot. Score",I12:I13) + + 189576 + - + DMIN(G1:K9,"Tot. Score",H12:H13) + + 5000 + - + DPRODUCT(G1:K9,"Age",K12:K13) + + 333 + - + DSTDEV(G1:K9,"Age",H12:H13) + + 6.027713773341708 + - + DSTDEVP(G1:K9,"Age",H12:H13) + + 4.921607686744467 + - + DSUM(G1:K9,"Age",I12:I13) + + 101 + - + DVAR(G1:K9,"Hours Played",H12:H13) + + 17560207.923333332 + - + DVARP(G1:K9,"Hours Played",H12:H13) + + 11706805.28222222 + - + EDATE("1969-07-22",-2) + + 25345 + - + EOMONTH("2020-07-21",1) + + 44074 + - + EXACT("AbsSdf%","AbsSdf%") + + true + - + EXP(4) + + 54.598150033144236 + - + FIND("A","qbdahbaazo A") + + 12 + - + FLOOR(5.5,2) + + 4 + - + _xlfn.FLOOR.MATH(-5.55,2,1) + + -4 + - + _xlfn.FLOOR.PRECISE(199,100) + + 100 + - + HLOOKUP("Tot. Score",H1:K9,4,FALSE) + + 110120.5 + - + HOUR("02:14:56") + + 2 + - + IF(TRUE,"TABOURET","JAMBON") + + TABOURET + - + IFERROR(0/0,"no diving by zero.") + + no diving by zero. + - + _xlfn.IFS($H2>$H3,"first player is older",$H3>$H2,"second player is older") + + first player is older + - + ISERROR(0/0) + + true + - + ISEVEN(3) + + false + - + ISLOGICAL("TRUE") + + false + - + ISNONTEXT(TRUE) + + true + - + ISNUMBER(1231.5) + + true + - + ISO.CEILING(-7.89) + + -7 + - + ISODD(4) + + false + - + _xlfn.ISOWEEKNUM("2016-01-03") + + 53 + - + ISTEXT("123") - + + true + + - + LARGE(H2:H9,3) + + 30 + - + LEFT("Mich",4) + + Mich + - + LEN("anticonstitutionnellement") + + 25 + - + ROUND(LN(2),5) + + 0.69315 + - + LOOKUP(42,H2:J9) + + 50024 + - + LOWER("オAドB") + + オaドb + - + MATCH(42,H2:H9,0) + + 4 + - + MAX(N1:N8) + + 0.6 + - + MAXA(N1:N8) + + 1 + - + _xlfn.MAXIFS(H2:H9,K2:K9,"<20",K2:K9,"<>4") + + 30 + - + MEDIAN(-1,6,7,234,163845) + + 7 + - + MIN(N1:N8) + + 0.1 + - + MINA(N1:N8) + + 0 + - + _xlfn.MINIFS(J2:J9,H2:H9,">20") + + 5000 + - + MINUTE(0.126) + + 1 + - + MOD(42,12) + + 6 + - + MONTH("1954-05-02") + + 5 + - + NETWORKDAYS("2013-01-01","2013-02-01") + + 24 + - + NETWORKDAYS.INTL("2013-01-01","2013-02-01","0000111") + + 19 + - + NOT(FALSE) + + true + - + NOW() + + 1 + - + ODD(4) + + 5 + - + OR("true",FALSE) + + true + - + PERCENTILE(N1:N5,1) + + 0.6 + - + _xlfn.PERCENTILE.EXC(N1:N5,0.5) + + 0.4 + - + _xlfn.PERCENTILE.INC(N1:N5,0) + + 0.1 + - + PI() + + 3.141592653589793 + - + POWER(42,2) + + 1764 + - + PRODUCT(1,2,3) + + 6 + - + QUARTILE(N1:N5,0) + + 0.1 + - + _xlfn.QUARTILE.EXC(N1:N5,1) + + 0.15000000000000002 + - + _xlfn.QUARTILE.INC(N1:N5,4) + + 0.6 + - + RAND() + + 1 + - + RANDBETWEEN(1.1,2) + + 1 + - + REPLACE("ABZ",2,1,"Y") + + AYZ + - + RIGHT("kikou",2) + + ou + - + ROUND(49.9,1) + + 49.9 + - + ROUNDDOWN(42,-1) + + 40 + - + ROUNDUP(-1.6,0) + + -2 + - + ROW(A234) + + 234 + - + ROWS(B3:C40) + + 38 + - + SEARCH("C","ABCD") + + 3 + - + _xlfn.SEC(PI()/3) + + 1.9999999999999996 + - + _xlfn.SECH(1) + + 0.6480542736638855 + - + SECOND("00:21:42") + + 42 + - + SIN(PI()/6) + + 0.49999999999999994 + - + SINH(1) + + 1.1752011936438014 + - + SMALL(H2:H9,3) + + 26 + - + SQRT(4) + + 2 + - + STDEV(-2,0,2) + + 2 + - + _xlfn.STDEV.P(2,4) + + 1 + - + _xlfn.STDEV.S(2,4,6) + + 2 + - + STDEVA(TRUE,3,5) + + 2 + - + STDEVP(2,5,8) + + 2.449489742783178 + - + STDEVPA(TRUE,4,7) + + 2.449489742783178 + - + SUBSTITUTE("SAP is best","SAP","Odoo") + + Odoo is best + - + SUM(1,2,3,4,5) + + 15 + - + SUMIF(K2:K9,"<100") + + 52 + - + SUMIFS(H2:H9,K2:K9,"<100") + + 201 + - + TAN(PI()/4) + + 0.9999999999999999 + - + TANH(1) + + 0.7615941559557649 + - + _xlfn.TEXTJOIN("-",TRUE,"","1","A","%") + + 1-A-% + - + TIME(9,11,31) + + 0.3829976851851852 + - + TIMEVALUE("18:00:00") + + 0.75 + - + TODAY() + + 1 + - + TRIM(" Jean Ticonstitutionnalise ") + + Jean Ticonstitutionnalise + - + TRUNC(42.42,1) + + 42.4 + - + UPPER("grrrr !") + + GRRRR ! + - + VAR(K1:K5) + + 2.9166666666666665 + - + _xlfn.VAR.P(K1:K5) + + 2.1875 + - + _xlfn.VAR.S(2,5,8) + + 9 + - + VARA(K1:K5) + + 6.7 + - + VARP(K1:K5) + + 2.1875 + - + VARPA(K1:K5) + + 5.36 + - + VLOOKUP("NotACheater",G1:K9,3,FALSE) + + 252.4 + - + WEEKDAY("2021-06-12") + + 7 + - + WEEKNUM("2021-06-29") + + 27 + - + WORKDAY("2021-03-15",6) + + 44278 + - + WORKDAY.INTL("2021-03-15",6,"0111111") + + 44312 + - + _xlfn.XOR(FALSE,TRUE,FALSE,FALSE) + + true + - + YEAR("2012-03-12") + + 2012 + - + DELTA(1,1) + + 1 + - + NA() + + #N/A + - + ISNA(A162) + + true + - + ISERR(A162) + + false + - + HYPERLINK("https://www.odoo.com","Odoo") + + Odoo + - + ADDRESS(27,53,4,FALSE,"sheet!") + + 'sheet!'!R[27]C[53] + - + DATEDIF("2002-01-01","2002-01-02","D") + + 1 + - + _xlfn.RANDARRAY(2,2) + + 1 + @@ -15780,17 +16294,23 @@ exports[`Test XLSX export formulas All non-exportable formulas 1`] = ` - + SUM(A3:Z3) + + #NAME? + - + SUM(A3:A100) + + #NAME? + @@ -16474,10 +16994,13 @@ exports[`Test XLSX export formulas Multi-Sheet export functions with cross refer - + SUM(Sheet2!A1) + + 5 + @@ -16719,10 +17242,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + ABS(-5.5) + + 5.5 + @@ -16756,10 +17282,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + ACOS(1) + + 0 + @@ -16793,10 +17322,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + ACOSH(2) + + 1.3169578969248166 + @@ -16830,15 +17362,21 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + _xlfn.ACOT(1) + + 0.7853981633974483 + - + _xlfn.ACOT(_xlfn.ACOT(1)) + + 0.9050225767665427 + @@ -16872,10 +17410,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + _xlfn.ACOTH(2) + + 0.5493061443340548 + @@ -16909,10 +17450,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + AND(TRUE,TRUE) + + true + @@ -16946,10 +17490,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + ASIN(0.5) + + 0.5235987755982989 + @@ -16983,10 +17530,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + ASINH(2) + + 1.4436354751788103 + @@ -17015,17 +17565,23 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + ATAN(1) + + 0.7853981633974483 + - + ATAN2(-1,0) + + 3.141592653589793 + @@ -17034,10 +17590,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + ATANH(0.7) + + 0.8673005276940531 + @@ -17066,10 +17625,13 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + AVEDEV(I2:I9) + + 2959.1624999999995 + @@ -17098,1088 +17660,1553 @@ exports[`Test XLSX export formulas Multi-Sheets exportable functions 1`] = ` - + AVERAGE(H2:H9) + + 26.25 + - + AVERAGEA(G2:H9) + + 13.125 + - + AVERAGEIF(J2:J9,">150000") + + 222797 + - + AVERAGEIFS(I2:I9,H2:H9,">=30",K2:K9,"<10") + + 8376.65 + - + CEILING(20.4,1) + + 21 + - + _xlfn.CEILING.MATH(-5.5,1,0) + + -5 + - + _xlfn.CEILING.PRECISE(230,100) + + 300 + - + CHAR(74) + + J + - + COLUMN(C4) + + 3 + - + COLUMNS(A5:D12) + + 4 + - + _xlfn.CONCAT(1,23) + + 123 + - + CONCATENATE("BUT, ","MICHEL") + + BUT, MICHEL + - + COS(PI()/3) + + 0.5000000000000001 + - + COSH(2) + + 3.7621956910836314 + - + _xlfn.COT(PI()/6) + + 1.7320508075688774 + - + _xlfn.COTH(0.5) + + 2.163953413738653 + - + COUNT(1,"a","5","2021-03-14") + + 3 + - + COUNTA(1,"a","5","2021-03-14") + + 4 + - + COUNTBLANK("","1",3,FALSE) + + 1 + - + COUNTIF(H2:H9,">30") + + 2 + - + COUNTIFS(H2:H9,">25",K2:K9,"<4") + + 3 + - + COVAR(H2:H9,K2:K9) + + -2119.25 + - + _xlfn.COVARIANCE.P(K2:K9,H2:H9) + + -2119.25 + - + _xlfn.COVARIANCE.P(I2:I9,J2:J9) + + 237217364.71640626 + - + _xlfn.CSC(PI()/4) + + 1.4142135623730951 + - + _xlfn.CSCH(PI()/3) + + 0.8004052928885931 + - + DATE(2020,5,25) + + 43976 + - + DATEVALUE("1969-08-15") + + 25430 + - + DAVERAGE(G1:K9,"Tot. Score",J12:J13) + + 151434.625 + - + DAY("2020-03-17") + + 17 + - + _xlfn.DAYS("2022-03-17","2021-03-17") + + 365 + - + DCOUNT(G1:K9,"Name",H12:H13) + + 0 + - + DCOUNTA(G1:K9,"Name",H12:H13) + + 3 + - + _xlfn.DECIMAL(20,16) + + 32 + - + DEGREES(PI()/4) + + 45 + - + DGET(G1:K9,"Hours Played",G12:G13) + + 252.4 + - + DMAX(G1:K9,"Tot. Score",I12:I13) + + 189576 + - + DMIN(G1:K9,"Tot. Score",H12:H13) + + 5000 + - + DPRODUCT(G1:K9,"Age",K12:K13) + + 333 + - + DSTDEV(G1:K9,"Age",H12:H13) + + 6.027713773341708 + - + DSTDEVP(G1:K9,"Age",H12:H13) + + 4.921607686744467 + - + DSUM(G1:K9,"Age",I12:I13) + + 101 + - + DVAR(G1:K9,"Hours Played",H12:H13) + + 17560207.923333332 + - + DVARP(G1:K9,"Hours Played",H12:H13) + + 11706805.28222222 + - + EDATE("1969-07-22",-2) + + 25345 + - + EOMONTH("2020-07-21",1) + + 44074 + - + EXACT("AbsSdf%","AbsSdf%") + + true + - + EXP(4) + + 54.598150033144236 + - + FIND("A","qbdahbaazo A") + + 12 + - + FLOOR(5.5,2) + + 4 + - + _xlfn.FLOOR.MATH(-5.55,2,1) + + -4 + - + _xlfn.FLOOR.PRECISE(199,100) + + 100 + - + HLOOKUP("Tot. Score",H1:K9,4,FALSE) + + 110120.5 + - + HOUR("02:14:56") + + 2 + - + IF(TRUE,"TABOURET","JAMBON") + + TABOURET + - + IFERROR(0/0,"no diving by zero.") + + no diving by zero. + - + _xlfn.IFS($H2>$H3,"first player is older",$H3>$H2,"second player is older") + + first player is older + - + ISERROR(0/0) + + true + - + ISEVEN(3) + + false + - + ISLOGICAL("TRUE") + + false + - + ISNONTEXT(TRUE) + + true + - + ISNUMBER(1231.5) + + true + - + ISO.CEILING(-7.89) + + -7 + - + ISODD(4) + + false + - + _xlfn.ISOWEEKNUM("2016-01-03") + + 53 + - + ISTEXT("123") + + true + - + LARGE(H2:H9,3) + + 30 + - + LEFT("Mich",4) + + Mich + - + LEN("anticonstitutionnellement") + + 25 + - + ROUND(LN(2),5) + + 0.69315 + - + LOOKUP(42,H2:J9) + + 50024 + - + LOWER("オAドB") + + オaドb + - + MATCH(42,H2:H9,0) + + 4 + - + MAX(N1:N8) + + 0.6 + - + MAXA(N1:N8) + + 1 + - + _xlfn.MAXIFS(H2:H9,K2:K9,"<20",K2:K9,"<>4") + + 30 + - + MEDIAN(-1,6,7,234,163845) + + 7 + - + MIN(N1:N8) + + 0.1 + - + MINA(N1:N8) + + 0 + - + _xlfn.MINIFS(J2:J9,H2:H9,">20") + + 5000 + - + MINUTE(0.126) + + 1 + - + MOD(42,12) + + 6 + - + MONTH("1954-05-02") + + 5 + - + NETWORKDAYS("2013-01-01","2013-02-01") + + 24 + - + NETWORKDAYS.INTL("2013-01-01","2013-02-01","0000111") + + 19 + - + NOT(FALSE) + + true + - + NOW() + + 1 + - + ODD(4) + + 5 + - + OR("true",FALSE) + + true + - + PERCENTILE(N1:N5,1) + + 0.6 + - + _xlfn.PERCENTILE.EXC(N1:N5,0.5) + + 0.4 + - + _xlfn.PERCENTILE.INC(N1:N5,0) + + 0.1 + - + PI() + + 3.141592653589793 + - + POWER(42,2) + + 1764 + - + PRODUCT(1,2,3) + + 6 + - + QUARTILE(N1:N5,0) + + 0.1 + - + _xlfn.QUARTILE.EXC(N1:N5,1) + + 0.15000000000000002 + - + _xlfn.QUARTILE.INC(N1:N5,4) + + 0.6 + - + RAND() + + 1 + - + RANDBETWEEN(1.1,2) + + 1 + - + REPLACE("ABZ",2,1,"Y") + + AYZ + - + RIGHT("kikou",2) + + ou + - + ROUND(49.9,1) + + 49.9 + - + ROUNDDOWN(42,-1) + + 40 + - + ROUNDUP(-1.6,0) + + -2 + - + ROW(A234) + + 234 + - + ROWS(B3:C40) + + 38 + - + SEARCH("C","ABCD") + + 3 + - + _xlfn.SEC(PI()/3) + + 1.9999999999999996 + - + _xlfn.SECH(1) + + 0.6480542736638855 + - + SECOND("00:21:42") + + 42 + - + SIN(PI()/6) + + 0.49999999999999994 + - + SINH(1) + + 1.1752011936438014 + - + SMALL(H2:H9,3) + + 26 + - + SQRT(4) + + 2 + - + STDEV(-2,0,2) + + 2 + - + _xlfn.STDEV.P(2,4) + + 1 + - + _xlfn.STDEV.S(2,4,6) + + 2 + - + STDEVA(TRUE,3,5) + + 2 + - + STDEVP(2,5,8) + + 2.449489742783178 + - + STDEVPA(TRUE,4,7) + + 2.449489742783178 + - + SUBSTITUTE("SAP is best","SAP","Odoo") + + Odoo is best + - + SUM(1,2,3,4,5) + + 15 + - + SUMIF(K2:K9,"<100") + + 52 + - + SUMIFS(H2:H9,K2:K9,"<100") + + 201 + - + TAN(PI()/4) + + 0.9999999999999999 + - + TANH(1) + + 0.7615941559557649 + - + _xlfn.TEXTJOIN("-",TRUE,"","1","A","%") + + 1-A-% + - + TIME(9,11,31) + + 0.3829976851851852 + - + TIMEVALUE("18:00:00") + + 0.75 + - + TODAY() + + 1 + - + TRIM(" Jean Ticonstitutionnalise ") + + Jean Ticonstitutionnalise + - + TRUNC(42.42,1) + + 42.4 + - + UPPER("grrrr !") + + GRRRR ! + - + VAR(K1:K5) + + 2.9166666666666665 + - + _xlfn.VAR.P(K1:K5) + + 2.1875 + - + _xlfn.VAR.S(2,5,8) + + 9 + - + VARA(K1:K5) + + 6.7 + - + VARP(K1:K5) + + 2.1875 + - + VARPA(K1:K5) + + 5.36 + - + VLOOKUP("NotACheater",G1:K9,3,FALSE) + + 252.4 + - + WEEKDAY("2021-06-12") + + 7 + - + WEEKNUM("2021-06-29") + + 27 + - + WORKDAY("2021-03-15",6) + + 44278 + - + WORKDAY.INTL("2021-03-15",6,"0111111") + + 44312 + - + _xlfn.XOR(FALSE,TRUE,FALSE,FALSE) + + true + - + YEAR("2012-03-12") + + 2012 + - + DELTA(1,1) + + 1 + - + NA() + + #N/A + - + ISNA(A162) + + true + - + ISERR(A162) + + false + - + HYPERLINK("https://www.odoo.com","Odoo") + + Odoo + - + ADDRESS(27,53,4,FALSE,"sheet!") + + 'sheet!'!R[27]C[53] + - + DATEDIF("2002-01-01","2002-01-02","D") + + 1 + - + _xlfn.RANDARRAY(2,2) + + 1 + @@ -22112,10 +23139,13 @@ exports[`Test XLSX export references with headers should be converted to referen - + SUM(B3:B5) + + 0 + diff --git a/tests/xlsx/xlsx_export.test.ts b/tests/xlsx/xlsx_export.test.ts index 59d611b182..ab2e9e1f69 100644 --- a/tests/xlsx/xlsx_export.test.ts +++ b/tests/xlsx/xlsx_export.test.ts @@ -1,4 +1,6 @@ import { arg, functionRegistry } from "../../src/functions"; +import { NOW, TODAY } from "../../src/functions/module_date"; +import { RAND, RANDARRAY, RANDBETWEEN } from "../../src/functions/module_math"; import { buildSheetLink, toXC } from "../../src/helpers"; import { createEmptyExcelWorkbookData } from "../../src/migrations/data"; import { Model } from "../../src/model"; @@ -7,6 +9,7 @@ import { Dimension, ExcelWorkbookData, PLAIN_TEXT_FORMAT } from "../../src/types import { XLSXExportXMLFile } from "../../src/types/xlsx"; import { adaptFormulaToExcel } from "../../src/xlsx/functions/cells"; import { escapeXml, parseXML } from "../../src/xlsx/helpers/xml_helpers"; + import { createChart, createGaugeChart, @@ -23,7 +26,12 @@ import { } from "../test_helpers/commands_helpers"; import { TEST_CHART_DATA } from "../test_helpers/constants"; import { getCellContent } from "../test_helpers/getters_helpers"; -import { exportPrettifiedXlsx, mockChart, toRangesData } from "../test_helpers/helpers"; +import { + exportPrettifiedXlsx, + mockChart, + restoreDefaultFunctions, + toRangesData, +} from "../test_helpers/helpers"; function getExportedExcelData(model: Model): ExcelWorkbookData { model.dispatch("EVALUATE_CELLS"); @@ -722,6 +730,36 @@ describe("Test XLSX export", () => { }); describe("formulas", () => { + beforeAll(() => { + functionRegistry.add("NOW", { + ...NOW, + compute: () => 1, + }); + functionRegistry.add("RAND", { + ...RAND, + compute: () => 1, + }); + functionRegistry.add("TODAY", { + ...TODAY, + compute: () => 1, + }); + functionRegistry.add("RANDARRAY", { + ...RANDARRAY, + compute: () => [ + [1, 1], + [1, 1], + ], + }); + // @ts-ignore + functionRegistry.add("RANDBETWEEN", { + ...RANDBETWEEN, + compute: () => 1, + }); + }); + + afterAll(() => { + restoreDefaultFunctions(); + }); test("All exportable formulas", async () => { const model = new Model(allExportableFormulasData); expect(await exportPrettifiedXlsx(model)).toMatchSnapshot();