diff --git a/sc/qa/unit/data/xlsx/IntersectionOperatorFixture.xlsx b/sc/qa/unit/data/xlsx/IntersectionOperatorFixture.xlsx new file mode 100644 index 000000000000..7458b80d5c67 Binary files /dev/null and b/sc/qa/unit/data/xlsx/IntersectionOperatorFixture.xlsx differ diff --git a/sc/qa/unit/subsequent_filters_test.cxx b/sc/qa/unit/subsequent_filters_test.cxx index 53b50d7ac149..95f5b0c8fafc 100644 --- a/sc/qa/unit/subsequent_filters_test.cxx +++ b/sc/qa/unit/subsequent_filters_test.cxx @@ -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) { diff --git a/sc/source/filter/oox/formulabuffer.cxx b/sc/source/filter/oox/formulabuffer.cxx index 0b820a5cc4c9..192327eeb65a 100644 --- a/sc/source/filter/oox/formulabuffer.cxx +++ b/sc/source/filter/oox/formulabuffer.cxx @@ -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);