From a074008d5804a5a46ca5e855aed369d25fa35554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Sun, 30 Aug 2026 06:34:34 +0000 Subject: [PATCH] formula: don't take the blank before an operator into the wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operand of an @ or of a union list ends where an operator further out ends it, and the whitespace in front of that operator was still inside it. So the blank was written inside the call parentheses, and since a range ending in whitespace is no longer one parenthesised group, the operand got a second pair of parentheses on top of the ones it had - @(A1) + 1 came out as _xlfn.SINGLE((A1) )+ 1. The wrapper now ends in front of those tokens. A blank before the # stays with the operand, that one is the operator being rewritten. Signed-off-by: Tomaž Vajngerl Change-Id: Ie6fe2df4e3e05b0ef845ebdb8a1aedb54b3f2a66 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10851 Tested-by: Tomaž Vajngerl Reviewed-by: Miklos Vajna (cherry picked from commit fd39c3a52d1cab7c0429bb98c4749bbfed088e6a) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210478 Tested-by: Jenkins Reviewed-by: Xisco Fauli --- formula/source/core/api/OOXMLRewriter.cxx | 18 +++++++++++++++--- sc/qa/unit/ucalc_formula2.cxx | 5 +++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/formula/source/core/api/OOXMLRewriter.cxx b/formula/source/core/api/OOXMLRewriter.cxx index 83b19ea452b6..db448b8a8331 100644 --- a/formula/source/core/api/OOXMLRewriter.cxx +++ b/formula/source/core/api/OOXMLRewriter.cxx @@ -120,6 +120,7 @@ private: }; OpCode opCodeAt(sal_uInt16 nPosition) const; + sal_uInt16 endWithoutTrailingWhitespace(sal_uInt16 nBegin, sal_uInt16 nEnd) const; sal_uInt16 literalOffsetArgument(sal_uInt16 nPosition) const; bool isCallBeforeArguments(OpCode eOp, sal_uInt16 nPosition) const; bool isOneParenthesizedGroup(sal_uInt16 nBegin, sal_uInt16 nEnd) const; @@ -143,6 +144,14 @@ OpCode RewriteCollector::opCodeAt(sal_uInt16 nPosition) const return mrTokens.TokenAt(nPosition)->GetOpCode(); } +// An operand stops before the whitespace in front of the operator that ends it. +sal_uInt16 RewriteCollector::endWithoutTrailingWhitespace(sal_uInt16 nBegin, sal_uInt16 nEnd) const +{ + while (nEnd > nBegin && isWhitespaceOpCode(opCodeAt(sal_uInt16(nEnd - 1)))) + --nEnd; + return nEnd; +} + // Position of the first argument if the token at nPosition opens an OFFSET() call with a // literal string or number there, NO_POSITION otherwise. OFFSET() needs a reference, so a // literal cannot be written out in a way any reader accepts. @@ -260,8 +269,9 @@ void RewriteCollector::closeSingleValues(size_t nScope, sal_uInt16 nEnd) // An empty operand gets no wrapper. if (rSingle.mbHasOperand) { - mrWrappers.push_back({ rSingle.mnBegin, nEnd, ocSingleValue, - !isOneParenthesizedGroup(rSingle.mnBegin, nEnd) }); + const sal_uInt16 nOperandEnd = endWithoutTrailingWhitespace(rSingle.mnBegin, nEnd); + mrWrappers.push_back({ rSingle.mnBegin, nOperandEnd, ocSingleValue, + !isOneParenthesizedGroup(rSingle.mnBegin, nOperandEnd) }); // The wrapper now encloses the expression, so the expression starts at the // wrapper. Whitespace the scope start had skipped is inside the wrapper too, // so pull that start back as well. @@ -291,7 +301,9 @@ void RewriteCollector::closeUnionList(sal_uInt16 nEnd, bool bMayTakeScopeParenth if (!(bMayTakeScopeParenthesis && rScope.mbGroupParenthesis && rScope.mnListStart == rScope.mnContentStart)) { - mrWrappers.push_back({ rScope.mnListStart, nEnd, ocNone, true }); + mrWrappers.push_back({ rScope.mnListStart, + endWithoutTrailingWhitespace(rScope.mnListStart, nEnd), ocNone, + true }); } rScope.mnListStart = NO_POSITION; } diff --git a/sc/qa/unit/ucalc_formula2.cxx b/sc/qa/unit/ucalc_formula2.cxx index b2a460747a3c..89ce0ebbdb3f 100644 --- a/sc/qa/unit/ucalc_formula2.cxx +++ b/sc/qa/unit/ucalc_formula2.cxx @@ -7169,6 +7169,11 @@ CPPUNIT_TEST_FIXTURE(TestFormula2, testSpilledRangeBindsTighterThanSignAndMarker CPPUNIT_ASSERT_EQUAL(u"SUM( _xlfn.SINGLE(_xlfn.ANCHORARRAY(A1)))"_ustr, compileAndPrintAsOoxml(*m_pDoc, u"SUM( @A1#)"_ustr)); + // Whitespace before the operator that ends the operand stays outside the wrapper as well, + // so the operand is still one parenthesised group and that pair serves as the call's. + CPPUNIT_ASSERT_EQUAL(u"_xlfn.SINGLE(A1) + 1"_ustr, + compileAndPrintAsOoxml(*m_pDoc, u"@(A1) + 1"_ustr)); + // A # on an argument inside a wrapped call wraps just that argument. CPPUNIT_ASSERT_EQUAL(u"_xlfn.SINGLE(SUM(_xlfn.ANCHORARRAY(A1)))"_ustr, compileAndPrintAsOoxml(*m_pDoc, u"@SUM(A1#)"_ustr));