formula/source/core/api/token.cxx | 35 ++++++++++++++++++++-- include/formula/tokenarray.hxx | 1 sc/qa/unit/data/ods/tdf170565_empty_functions.ods |binary sc/qa/unit/subsequent_export_test4.cxx | 24 +++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-)
New commits: commit 3cbf4957d33c7414b5cd02481136679b5c6a834e Author: Aron Budea <[email protected]> AuthorDate: Mon Feb 2 22:46:23 2026 +1030 Commit: Aron Budea <[email protected]> CommitDate: Thu Feb 5 07:22:05 2026 +0100 tdf#170565 save empty SUM/MAX/MIN functions with parameters to XLSX Excel won't allow them with no parameters. In Calc: - SUM() is 0, - MAX()/MIN() gives Err:511. Save them to XLSX as SUM(0), MAX(#VALUE!) and MIN(#VALUE!). Change-Id: I0f0d05290b67684df294611bd0612090abf1a672 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198543 Reviewed-by: Aron Budea <[email protected]> Tested-by: Jenkins diff --git a/formula/source/core/api/token.cxx b/formula/source/core/api/token.cxx index dffcb1b3ff26..9c41f2986c9f 100644 --- a/formula/source/core/api/token.cxx +++ b/formula/source/core/api/token.cxx @@ -937,6 +937,11 @@ FormulaToken* FormulaTokenArray::AddStringXML( const OUString& rStr ) return Add( new FormulaStringOpToken( ocStringXML, svl::SharedString( rStr ) ) ); // string not interned } +FormulaToken* FormulaTokenArray::AddError( FormulaError nErr ) +{ + return Add( new FormulaErrorToken( nErr ) ); +} + FormulaToken* FormulaTokenArray::AddStringName( const OUString& rStr ) { return Add( new FormulaStringOpToken( ocStringName, svl::SharedString( rStr ) ) ); // string not interned @@ -969,7 +974,6 @@ void FormulaTokenArray::AddRecalcMode( ScRecalcMode nBits ) SetCombinedBitsRecalcMode( nBits ); } - bool FormulaTokenArray::HasMatrixDoubleRefOps() const { if ( !pRPN || !nRPN ) @@ -1074,6 +1078,9 @@ inline bool MissingConventionOOXML::isRewriteNeeded( OpCode eOp ) switch (eOp) { case ocIf: + case ocMax: + case ocMin: + case ocSum: case ocExternal: case ocEuroConvert: @@ -1112,8 +1119,9 @@ class FormulaMissingContext public: const FormulaToken* mpFunc; int mnCurArg; + bool mbEmpty; // no parameters: () - void Clear() { mpFunc = nullptr; mnCurArg = 0; } + void Clear() { mpFunc = nullptr; mnCurArg = 0; mbEmpty = false; } inline bool AddDefaultArg( FormulaTokenArray* pNewArr, int nArg, double f ) const; bool AddMissingExternal( FormulaTokenArray* pNewArr ) const; bool AddMissing( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const; @@ -1195,6 +1203,24 @@ void FormulaMissingContext::AddMoreArgs( FormulaTokenArray *pNewArr, const Missi } break; + case ocMax: + case ocMin: + if ( mnCurArg == 0 && mbEmpty ) + { + // Excel needs at least one parameter in MAX/MIN functions + // In Calc empty MAX/MIN are Err:511 + pNewArr->AddError( FormulaError::NoValue ); + } + break; + + case ocSum: + if ( mnCurArg == 0 && mbEmpty ) + { + // Excel needs at least one parameter in SUM function + pNewArr->AddDouble( 0.0 ); // SUM() = 0 + } + break; + case ocEuroConvert: if ( mnCurArg == 2 ) { @@ -1477,6 +1503,11 @@ FormulaTokenArray * FormulaTokenArray::RewriteMissing( const MissingConvention & ++nFn; // all following operations on _that_ function pCtx[ nFn ].mpFunc = aIter.PeekPrevNoSpaces(); pCtx[ nFn ].mnCurArg = 0; + FormulaToken* pNext = aIter.PeekNextNoSpaces(); + if (pNext != nullptr && pNext->GetOpCode() == ocClose) + pCtx[ nFn ].mbEmpty = true; + else + pCtx[ nFn ].mbEmpty = false; if (rConv.isPODF() && pCtx[ nFn ].mpFunc && pCtx[ nFn ].mpFunc->GetOpCode() == ocAddress) pOcas[ nOcas++ ] = nFn; // entering ADDRESS() if PODF else if ((rConv.isODFF() || rConv.isOOXML()) && pCtx[ nFn ].mpFunc) diff --git a/include/formula/tokenarray.hxx b/include/formula/tokenarray.hxx index a623059d7288..e4e5f8d6d182 100644 --- a/include/formula/tokenarray.hxx +++ b/include/formula/tokenarray.hxx @@ -488,6 +488,7 @@ public: FormulaToken* AddBad( const OUString& rStr ); /// ocBad with OUString FormulaToken* AddStringXML( const OUString& rStr ); /// ocStringXML with OUString, temporary during import FormulaToken* AddStringName( const OUString& rStr ); /// ocStringName with OUString - Lambda functions + FormulaToken* AddError( FormulaError nErr ); virtual FormulaToken* MergeArray( ); diff --git a/sc/qa/unit/data/ods/tdf170565_empty_functions.ods b/sc/qa/unit/data/ods/tdf170565_empty_functions.ods new file mode 100644 index 000000000000..2297a76f9e62 Binary files /dev/null and b/sc/qa/unit/data/ods/tdf170565_empty_functions.ods differ diff --git a/sc/qa/unit/subsequent_export_test4.cxx b/sc/qa/unit/subsequent_export_test4.cxx index cc3d419d09c4..14e368e0255c 100644 --- a/sc/qa/unit/subsequent_export_test4.cxx +++ b/sc/qa/unit/subsequent_export_test4.cxx @@ -2476,6 +2476,30 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf165733_leap_day_BCE) } } +CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf170565_empty_functions) +{ + createScDoc("ods/tdf170565_empty_functions.ods"); + + save(TestFilter::XLSX); + xmlDocUniquePtr pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr); + CPPUNIT_ASSERT(pSheet); + + // Without the fix these would've been exported as SUM(), MIN(), MAX(), which aren't valid in Excel + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[1]/x:c[1]/x:f", u"SUM(0)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[1]/x:f", u"MIN(#VALUE!)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[3]/x:c[1]/x:f", u"MAX(#VALUE!)"); + // Further checks to ensure there are no regressions + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[1]/x:c[2]/x:f", u"SUM(,)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[1]/x:c[3]/x:f", u"SUM(100)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[1]/x:c[4]/x:f", u"SUM(C1:C3)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[2]/x:f", u"MIN(,)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[3]/x:f", u"MIN(200)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[4]/x:f", u"MIN(C1:C3)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[3]/x:c[2]/x:f", u"MAX(,)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[3]/x:c[3]/x:f", u"MAX(-100)"); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[3]/x:c[4]/x:f", u"MAX(C1:C3)"); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
