sc: import @-wrapped XLSX cells as scalar formulas, not CSE arrays

XLSX tags a cell holding =@(expr) with t="array" ref=<self>,
because the OOXML writer evaluates the @ in array context. The
import path honoured the tag and routed such cells through
setMatrixCells, so GetFormula wrapped the imported text in {}
braces. A user-typed =@(F3:F5+0) came back as {=@(F3:F5+0)},
reading as a CSE array even though @ keeps the cell scalar.

When the t="array" range is a single cell and the parse array
starts with @, construct a plain ScFormulaCell instead of
calling setMatrixCells. The @ collapses the operand, so there
is nothing to spill.

testIntersectionOperatorXlsxRoundTrip loads the fixture and
asserts the @-wrapped cells read back as plain formulas, both
on import and after a round trip.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I05020fa2810a8f2d4f0c9374a9a8afc39e2d9e5e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5536
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 75eda52e374810561261485ed038e70d9e2edab1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207869
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Tomaž Vajngerl
2026-07-09 17:09:01 +02:00
committed by Xisco Fauli
parent 14d723ea36
commit 49e9da81fa
3 changed files with 50 additions and 0 deletions
+31
View File
@@ -1680,6 +1680,37 @@ CPPUNIT_TEST_FIXTURE(ScFiltersTest, testCmRoundTripXLSX)
CPPUNIT_ASSERT(pMaster);
CPPUNIT_ASSERT(pMaster->IsDynamicArrayMaster());
}
CPPUNIT_TEST_FIXTURE(ScFiltersTest, testIntersectionOperatorXlsxRoundTrip)
{
// The fixture has A1:A4 = {42, 69, 13, 666} and three cells
// that read back the upper-left value 42 through implicit
// intersection. All three import as plain formulas, so
// GetFormula returns them without {} wrapping. The same shape
// survives an XLSX round trip.
auto assertFormulas = [](ScDocument* pDoc) {
// C1: plain =A1:A4 range reference. No t="array" tag, so
// the cell collapses through implicit intersection at row 1.
CPPUNIT_ASSERT_EQUAL(u"=A1:A4"_ustr, pDoc->GetFormula(2, 0, 0));
CPPUNIT_ASSERT_EQUAL(42.0, pDoc->GetValue(ScAddress(2, 0, 0)));
// D1: t="array" wrapper around (A1:A4+0). The outer paren
// pair the writer adds is stripped on import.
CPPUNIT_ASSERT_EQUAL(u"=@(A1:A4+0)"_ustr, pDoc->GetFormula(3, 0, 0));
CPPUNIT_ASSERT_EQUAL(42.0, pDoc->GetValue(ScAddress(3, 0, 0)));
// E1: t="array" wrapper around TRANSPOSE(A1:A4). The
// argument is a function call, so no outer paren strip.
CPPUNIT_ASSERT_EQUAL(u"=@(TRANSPOSE(A1:A4))"_ustr, pDoc->GetFormula(4, 0, 0));
CPPUNIT_ASSERT_EQUAL(42.0, pDoc->GetValue(ScAddress(4, 0, 0)));
};
createScDoc("xlsx/IntersectionOperatorFixture.xlsx");
assertFormulas(getScDoc());
saveAndReload(TestFilter::XLSX);
assertFormulas(getScDoc());
}
CPPUNIT_TEST_FIXTURE(ScFiltersTest, testArrayFormulaSpillXLSX)
{
+19
View File
@@ -381,6 +381,25 @@ void applyArrayFormulas(
if (pArray)
{
lcl_stripRedundantParentheses(*pArray, {ocSingleValue});
// A single-cell t="array" that starts with @ imports as
// a plain ScFormulaCell, not a CSE array. The @ collapses
// the operand to a scalar, so the cell reads back as
// =@(expr) instead of {=@(expr)}.
const sal_uInt16 nParseLength = pArray->GetLen();
const formula::FormulaToken* pFirst = nParseLength ? pArray->GetArray()[0] : nullptr;
const bool bSingleCellRange
= rAddressItem.maRange.aStart == rAddressItem.maRange.aEnd;
if (bSingleCellRange && pFirst
&& pFirst->GetOpCode() == ocSingleValue)
{
aComp.CompileTokenArray();
ScFormulaCell* pCell = new ScFormulaCell(rDoc.getDoc(), aPos,
std::move(pArray));
rDoc.setFormulaCell(aPos, pCell);
continue;
}
rDoc.setMatrixCells(rAddressItem.maRange, *pArray,
formula::FormulaGrammar::GRAM_OOXML,
rAddressItem.mbCachedSpill);