From eb239f1a15f3bd8481ee5c5cf72bdb52c347efae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Tue, 9 Jun 2026 23:50:29 +0900 Subject: [PATCH] sc: round-trip dynamic-array masters through XLSX, strip @ on XLS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XLSX marks a dynamic-array master with cm="1" on the cell and a matching dynamicArrayProperties metadata block. Both import and export now travel through mbDynamicArrayMaster on the cell. XLSX import plumbs the flag through the three formula paths. XML_normal and XML_shared land in applyCellFormulas and applySharedFormulas through setCellFormula. XML_array lands in applyArrayFormulas through setCellArrayFormula. Each path sets mbDynamicArrayMaster on the imported cell or on the matrix master. XLSX export reads the flag in XclExpFormulaCell::SaveXml to drive cm="1" and the dynamicArrayProperties block. The legacy pair of a Ctrl+Shift+Enter (CSE) master that calls a dynamic-array function stays as a fallback while the importers and the UI entry path are being wired through. XLS has no @ encoding and no dynamic-array marker. Save and load mirror the ODF behaviour: - The exporter strips ocSingleValue (alongside ocSpaces and ocWhitespace) in GetNextToken so the BIFF stream stays valid. The saved formula evaluates to the upper-left value the way XLS always did. - The importer walks the just-built RPN of every plain non-array formula and prepends @ when the formula intends an array. The resolve runs before the formula cell is constructed, while the parse array is still mutable. The static helper compiles a missing RPN on demand so the array-intent walk has the post-fix order it needs. - XLS array records keep the legacy CSE behaviour. They have no dynamic-array encoding, so loading them as static arrays matches what the file format can express. Tests cover an XLSX import of Spill.xlsx, an XLSX round trip of the same file, a direct-XML XLSX export check that the spilled master at C2 keeps cm="1" and the dynamicArrayProperties metadata block, and an XLS round trip of =@TRANSPOSE(B1:B4) where the @ reappears after reload. Signed-off-by: Tomaž Vajngerl Change-Id: I30318bad4cdb50bbd92240fd71d70df0959a0eda Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4664 Reviewed-by: Miklos Vajna Tested-by: Tomaž Vajngerl (cherry picked from commit 79eda1a177abf9a348dbc67c211b26cabd0cb4c1) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207218 Tested-by: Xisco Fauli Reviewed-by: Xisco Fauli --- sc/inc/formulacell.hxx | 7 ++++ sc/qa/unit/subsequent_export_test2.cxx | 30 ++++++++++++++++ sc/qa/unit/subsequent_export_test4.cxx | 29 ++++++++++++++++ sc/qa/unit/subsequent_filters_test.cxx | 33 ++++++++++++++++++ sc/source/core/data/formulacell.cxx | 28 ++++++++++----- sc/source/filter/excel/excform.cxx | 12 ++++++- sc/source/filter/excel/impop.cxx | 4 +++ sc/source/filter/excel/xeformula.cxx | 11 ++++-- sc/source/filter/excel/xetable.cxx | 10 ++++-- sc/source/filter/inc/formulabuffer.hxx | 33 ++++++++++++++---- sc/source/filter/inc/worksheethelper.hxx | 10 ++++-- sc/source/filter/oox/formulabuffer.cxx | 42 ++++++++++++++++++----- sc/source/filter/oox/sheetdatacontext.cxx | 8 +++-- sc/source/filter/oox/worksheethelper.cxx | 17 +++++---- 14 files changed, 232 insertions(+), 42 deletions(-) diff --git a/sc/inc/formulacell.hxx b/sc/inc/formulacell.hxx index 6f582082e6de..7d6490eda762 100644 --- a/sc/inc/formulacell.hxx +++ b/sc/inc/formulacell.hxx @@ -361,6 +361,13 @@ public: bool IsDynamicArrayMaster() const { return mbDynamicArrayMaster; } SC_DLLPUBLIC void SetDynamicArrayMaster( bool bDynamic ); SC_DLLPUBLIC void ResolveImplicitIntersection(); + // Run this on the bare token array before it is wrapped in a cell. + // The cell constructor finalizes the parse array. Once finalized, + // appending a new token silently no-ops. The document and position + // arguments compile a missing RPN so the array-intent walk has the + // post-fix order it needs. + SC_DLLPUBLIC static void ResolveImplicitIntersection(ScTokenArray& rCode, ScDocument& rDoc, + const ScAddress& rPos); ScTokenArray* GetCode() { return pCode;} const ScTokenArray* GetCode() const { return pCode;} diff --git a/sc/qa/unit/subsequent_export_test2.cxx b/sc/qa/unit/subsequent_export_test2.cxx index 6b0cbfaa839e..0c1e46b8b9f8 100644 --- a/sc/qa/unit/subsequent_export_test2.cxx +++ b/sc/qa/unit/subsequent_export_test2.cxx @@ -1872,6 +1872,36 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, testArrayFormulaSpillRoundtripXLSX) CPPUNIT_ASSERT_EQUAL(40.0, pDocument->GetValue(ScAddress(0, 3, 0))); } +CPPUNIT_TEST_FIXTURE(ScExportTest2, testDynamicArraySpilledExportXLSX) +{ + // Spill.xlsx has C2 = UNIQUE(A2:A5) imported as a dynamic-array + // master that already spilled into C2:C5. Saving back to XLSX must + // write cm="1" on the master cell, t="array" with ref="C2:C5" on + // the formula, and the matching dynamic-array metadata block. + // Slave cells in the spilled range must not carry cm. + + createScDoc("xlsx/Spill.xlsx"); + + save(TestFilter::XLSX); + + xmlDocUniquePtr pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr); + CPPUNIT_ASSERT(pSheet); + + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[@r='C2']", "cm", u"1"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[@r='C2']/x:f", "t", u"array"); + assertXPath(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[@r='C2']/x:f", "ref", u"C2:C5"); + assertXPathNoAttribute(pSheet, "/x:worksheet/x:sheetData/x:row[3]/x:c[@r='C3']", "cm"); + assertXPathNoAttribute(pSheet, "/x:worksheet/x:sheetData/x:row[4]/x:c[@r='C4']", "cm"); + assertXPathNoAttribute(pSheet, "/x:worksheet/x:sheetData/x:row[5]/x:c[@r='C5']", "cm"); + + xmlDocUniquePtr pMetadata = parseExport(u"xl/metadata.xml"_ustr); + CPPUNIT_ASSERT_MESSAGE("xl/metadata.xml must be written", pMetadata); + assertXPath(pMetadata, + "/x:metadata/x:futureMetadata[@name='XLDAPR']/x:bk/x:extLst/x:ext" + "/xda:dynamicArrayProperties", + "fDynamic", u"1"); +} + CPPUNIT_TEST_FIXTURE(ScExportTest2, testArrayFormulaSpillRoundtripODS) { // Same scenario as testArrayFormulaSpillRoundtripXLSX but for ODS. diff --git a/sc/qa/unit/subsequent_export_test4.cxx b/sc/qa/unit/subsequent_export_test4.cxx index b23773df4848..97f049973852 100644 --- a/sc/qa/unit/subsequent_export_test4.cxx +++ b/sc/qa/unit/subsequent_export_test4.cxx @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include @@ -155,6 +157,33 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf120177) CPPUNIT_ASSERT_EQUAL(sGroupName1, sGroupName2); } +CPPUNIT_TEST_FIXTURE(ScExportTest4, testSingleValueDroppedOnXlsExport) +{ + // The @ implicit-intersection marker survives an XLS round trip + // even though the BIFF stream has no token for it. + + createScDoc(); + ScDocument* pDoc = getScDoc(); + + pDoc->SetValue(ScAddress(1, 0, 0), 10.0); + pDoc->SetValue(ScAddress(1, 1, 0), 20.0); + pDoc->SetValue(ScAddress(1, 2, 0), 30.0); + pDoc->SetValue(ScAddress(1, 3, 0), 40.0); + + pDoc->SetFormula(ScAddress(0, 0, 0), u"=@TRANSPOSE(B1:B4)"_ustr, + formula::FormulaGrammar::GRAM_NATIVE); + CPPUNIT_ASSERT_EQUAL(10.0, pDoc->GetValue(ScAddress(0, 0, 0))); + + saveAndReload(TestFilter::XLS); + + pDoc = getScDoc(); + ScFormulaCell* pCell = pDoc->GetFormulaCell(ScAddress(0, 0, 0)); + CPPUNIT_ASSERT(pCell); + CPPUNIT_ASSERT_EQUAL(sal_Int32(FormulaError::NONE), sal_Int32(pCell->GetErrCode())); + CPPUNIT_ASSERT_EQUAL(u"=@TRANSPOSE(B1:B4)"_ustr, pDoc->GetFormula(0, 0, 0)); + CPPUNIT_ASSERT_EQUAL(10.0, pDoc->GetValue(ScAddress(0, 0, 0))); +} + CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf85553) { createScDoc("ods/tdf85553.ods"); diff --git a/sc/qa/unit/subsequent_filters_test.cxx b/sc/qa/unit/subsequent_filters_test.cxx index f859a6d0a690..0d5f616abf47 100644 --- a/sc/qa/unit/subsequent_filters_test.cxx +++ b/sc/qa/unit/subsequent_filters_test.cxx @@ -1648,6 +1648,39 @@ CPPUNIT_TEST_FIXTURE(ScFiltersTest, testNonAsciiWithDotXLSX) CPPUNIT_ASSERT_EQUAL(5.0, aValue); } +CPPUNIT_TEST_FIXTURE(ScFiltersTest, testCmImportSetsDynamicArrayMaster) +{ + // The cm="1" attribute on an XLSX cell lands as a dynamic-array master. + + createScDoc("xlsx/Spill.xlsx"); + ScDocument* pDoc = getScDoc(); + + // C2 carries cm="1" with t="array" ref="C2:C5", the spilled master. + ScFormulaCell* pSpilled = pDoc->GetFormulaCell(ScAddress(2, 1, 0)); + CPPUNIT_ASSERT(pSpilled); + CPPUNIT_ASSERT(pSpilled->IsDynamicArrayMaster()); + + // A2 is a plain value cell, so the document holds no formula at A2. + ScFormulaCell* pValueCell = pDoc->GetFormulaCell(ScAddress(0, 1, 0)); + CPPUNIT_ASSERT(!pValueCell); +} + +CPPUNIT_TEST_FIXTURE(ScFiltersTest, testCmRoundTripXLSX) +{ + // The dynamic-array marker survives an XLSX save and reload through + // the cm="1" attribute on the master cell. + + createScDoc("xlsx/Spill.xlsx"); + + saveAndReload(TestFilter::XLSX); + + ScDocument* pDoc = getScDoc(); + + ScFormulaCell* pMaster = pDoc->GetFormulaCell(ScAddress(2, 1, 0)); + CPPUNIT_ASSERT(pMaster); + CPPUNIT_ASSERT(pMaster->IsDynamicArrayMaster()); +} + CPPUNIT_TEST_FIXTURE(ScFiltersTest, testArrayFormulaSpillXLSX) { // D2 holds =UNIQUE(A2:A5) imported in #SPILL! state with blocker text diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx index 7f241d2ebf53..6258f9b51bc5 100644 --- a/sc/source/core/data/formulacell.cxx +++ b/sc/source/core/data/formulacell.cxx @@ -2675,17 +2675,29 @@ void ScFormulaCell::SetDynamicArrayMaster(bool bDynamic) } } +void ScFormulaCell::ResolveImplicitIntersection(ScTokenArray& rCode, ScDocument& rDoc, + const ScAddress& rPos) +{ + if (rCode.IsHyperLink() || rCode.GetLen() == 0) + return; + if (rCode.GetCodeLen() == 0) + { + // Parse has tokens but RPN was not built yet. Build it so the + // walk has post-fix order to inspect. + ScCompiler aComp(rDoc, rPos, rCode, formula::FormulaGrammar::GRAM_DEFAULT, true, false); + aComp.CompileTokenArray(); + } + if (rpnTopIsImplicitIntersection(rCode)) + return; + if (rpnIntendsArrayResult(rCode)) + prependImplicitIntersection(rCode); +} + void ScFormulaCell::ResolveImplicitIntersection() { - if (!pCode - || cMatrixFlag != ScMatrixMode::NONE - || pCode->IsHyperLink() || mxGroup - || pCode->GetCodeLen() == 0) + if (!pCode || cMatrixFlag != ScMatrixMode::NONE || mxGroup) return; - if (rpnTopIsImplicitIntersection(*pCode)) - return; - if (rpnIntendsArrayResult(*pCode)) - prependImplicitIntersection(*pCode); + ResolveImplicitIntersection(*pCode, rDocument, aPos); } void ScFormulaCell::SetMatColsRows( SCCOL nCols, SCROW nRows ) diff --git a/sc/source/filter/excel/excform.cxx b/sc/source/filter/excel/excform.cxx index 1d007cc84307..6ab8327a39f2 100644 --- a/sc/source/filter/excel/excform.cxx +++ b/sc/source/filter/excel/excform.cxx @@ -119,7 +119,13 @@ void ImportExcel::Formula( if (pSharedCode) { ScFormulaCell* pCell; - pCell = new ScFormulaCell(rD, aScPos, pSharedCode->Clone()); + // XLS has no @ encoding. Bake an implicit @ into the + // RPN when the formula intends an array result so a + // TRANSPOSE-style call returns the upper-left value at + // the cell position. + std::unique_ptr pTokens = pSharedCode->Clone(); + ScFormulaCell::ResolveImplicitIntersection(*pTokens, rD, aScPos); + pCell = new ScFormulaCell(rD, aScPos, std::move(pTokens)); pCell->GetCode()->WrapReference(aScPos, EXC_MAXCOL8, EXC_MAXROW8); rDoc.getDoc().EnsureTable(aScPos.Tab()); rDoc.setFormulaCell(aScPos, pCell); @@ -147,6 +153,10 @@ void ImportExcel::Formula( if (pResult) { + // XLS has no @ encoding. Bake an implicit @ into the RPN when + // the formula intends an array result so a TRANSPOSE-style call + // returns the upper-left value at the cell position. + ScFormulaCell::ResolveImplicitIntersection(*pResult, rDoc.getDoc(), aScPos); pCell = new ScFormulaCell(rDoc.getDoc(), aScPos, std::move(pResult)); pCell->GetCode()->WrapReference(aScPos, EXC_MAXCOL8, EXC_MAXROW8); rDoc.getDoc().CheckLinkFormulaNeedingCheck( *pCell->GetCode()); diff --git a/sc/source/filter/excel/impop.cxx b/sc/source/filter/excel/impop.cxx index b4f8b64e5374..b39f5b2b0368 100644 --- a/sc/source/filter/excel/impop.cxx +++ b/sc/source/filter/excel/impop.cxx @@ -849,6 +849,10 @@ void ImportExcel::Shrfmla() ScDocumentImport& rDoc = GetDocImport(); + // XLS has no @ encoding. Bake an implicit @ into the RPN when the + // formula intends an array result so a TRANSPOSE-style call returns + // the upper-left value at the cell position. + ScFormulaCell::ResolveImplicitIntersection(*pResult, rD, aPos); ScFormulaCell* pCell = new ScFormulaCell(rD, aPos, std::move(pResult)); pCell->GetCode()->WrapReference(aPos, EXC_MAXCOL8, EXC_MAXROW8); rDoc.getDoc().CheckLinkFormulaNeedingCheck( *pCell->GetCode()); diff --git a/sc/source/filter/excel/xeformula.cxx b/sc/source/filter/excel/xeformula.cxx index 65aba6546372..ec0449b1a79e 100644 --- a/sc/source/filter/excel/xeformula.cxx +++ b/sc/source/filter/excel/xeformula.cxx @@ -844,9 +844,16 @@ bool XclExpFmlaCompImpl::GetNextToken( XclExpScToken& rTokData ) rTokData.mpScToken = GetNextRawToken(); rTokData.mnSpaces = 0; /* TODO: handle ocWhitespace characters? */ - while (rTokData.GetOpCode() == ocSpaces || rTokData.GetOpCode() == ocWhitespace) + // XLS predates the @ implicit-intersection operator. The BIFF token + // stream has no encoding for ocSingleValue, so drop it on the way out + // and let the operand flow through unchanged. XLS does not auto-spill + // anyway, so the saved formula evaluates to the same upper-left value + // it did before. + while (rTokData.GetOpCode() == ocSpaces || rTokData.GetOpCode() == ocWhitespace + || rTokData.GetOpCode() == ocSingleValue) { - rTokData.mnSpaces += static_cast(rTokData.mpScToken)->GetByte(); + if (rTokData.GetOpCode() != ocSingleValue) + rTokData.mnSpaces += static_cast(rTokData.mpScToken)->GetByte(); rTokData.mpScToken = GetNextRawToken(); } return rTokData.Is(); diff --git a/sc/source/filter/excel/xetable.cxx b/sc/source/filter/excel/xetable.cxx index 629b4c0214c9..f8efe366e1f7 100644 --- a/sc/source/filter/excel/xetable.cxx +++ b/sc/source/filter/excel/xetable.cxx @@ -949,10 +949,14 @@ void XclExpFormulaCell::SaveXml( XclExpXmlStream& rStrm ) // Mark dynamic array masters with cm="1" so it is recognised as a // dynamic array and reads the matching dynamicArrayProperties out of - // xl/metadata.xml. + // xl/metadata.xml. The cell flag is the primary source. The legacy + // pair of a Ctrl+Shift+Enter (CSE) master that calls a dynamic-array + // function stays as a fallback while the importers and the UI entry + // path are being wired through. const bool bDynamicArrayMaster - = mrScFmlaCell.GetMatrixFlag() == ScMatrixMode::Formula - && mrScFmlaCell.GetCode() && mrScFmlaCell.GetCode()->HasDynamicArrayFunction(); + = mrScFmlaCell.IsDynamicArrayMaster() + || (mrScFmlaCell.GetMatrixFlag() == ScMatrixMode::Formula + && mrScFmlaCell.GetCode() && mrScFmlaCell.GetCode()->HasDynamicArrayFunction()); if (bDynamicArrayMaster) rStrm.NoteDynamicArrayFormula(); diff --git a/sc/source/filter/inc/formulabuffer.hxx b/sc/source/filter/inc/formulabuffer.hxx index bb1aebf658ec..373c62411a2e 100644 --- a/sc/source/filter/inc/formulabuffer.hxx +++ b/sc/source/filter/inc/formulabuffer.hxx @@ -42,17 +42,28 @@ public: OUString maCellValue; sal_Int32 mnSharedId; sal_Int32 mnValueType; + /// True when the XLSX cell carried cm="1", marking it as a dynamic-array + /// master. + bool mbDynamicArrayMaster = false; SharedFormulaDesc( const ScAddress& rAddr, sal_Int32 nSharedId, - OUString aCellValue, sal_Int32 nValueType ); + OUString aCellValue, sal_Int32 nValueType, + bool bDynamicArrayMaster = false ); }; struct TokenAddressItem { OUString maTokenStr; ScAddress maAddress; - TokenAddressItem( OUString aTokenStr, const ScAddress& rAddress ) : maTokenStr(std::move( aTokenStr )), maAddress( rAddress ) {} + /// True when the XLSX cell carried cm="1", marking it as a dynamic-array + /// master. + bool mbDynamicArrayMaster = false; + TokenAddressItem( OUString aTokenStr, const ScAddress& rAddress, + bool bDynamicArrayMaster = false ) + : maTokenStr(std::move( aTokenStr )) + , maAddress( rAddress ) + , mbDynamicArrayMaster(bDynamicArrayMaster) {} }; struct TokenRangeAddressItem @@ -64,12 +75,17 @@ public: /// blocker preservation so reference cells aren't materialised over /// real user data sitting in the matrix range. bool mbCachedSpill; + /// True when the XLSX cell carried cm="1", marking it as a dynamic-array + /// master. + bool mbDynamicArrayMaster = false; TokenRangeAddressItem(TokenAddressItem aTokenAndAddress, const ScRange& rRange, - bool bCachedSpill = false) + bool bCachedSpill = false, + bool bDynamicArrayMaster = false) : maTokenAndAddress(std::move(aTokenAndAddress)) , maRange(rRange) - , mbCachedSpill(bCachedSpill) + , mbCachedSpill(bCachedSpill) + , mbDynamicArrayMaster(bDynamicArrayMaster) {} }; @@ -108,11 +124,13 @@ private: public: explicit FormulaBuffer( const WorkbookHelper& rHelper ); void finalizeImport(); - void setCellFormula( const ScAddress& rAddress, const OUString& ); + void setCellFormula( const ScAddress& rAddress, const OUString&, + bool bDynamicArrayMaster = false ); void setCellFormula( const ScAddress& rAddress, sal_Int32 nSharedId, - const OUString& rCellValue, sal_Int32 nValueType ); + const OUString& rCellValue, sal_Int32 nValueType, + bool bDynamicArrayMaster = false ); void setCellFormulaValue( const ScAddress& rAddress, const OUString& rValueStr, sal_Int32 nCellType ); @@ -120,7 +138,8 @@ public: void setCellArrayFormula(const ScRange& rRangeAddress, const ScAddress& rTokenAddress, const OUString&, - bool bCachedSpill = false); + bool bCachedSpill = false, + bool bDynamicArrayMaster = false); void createSharedFormulaMapEntry( const ScAddress& rAddress, sal_Int32 nSharedId, const OUString& rTokens ); diff --git a/sc/source/filter/inc/worksheethelper.hxx b/sc/source/filter/inc/worksheethelper.hxx index 9eb500d6da8f..67a1bc2c692e 100644 --- a/sc/source/filter/inc/worksheethelper.hxx +++ b/sc/source/filter/inc/worksheethelper.hxx @@ -284,13 +284,17 @@ public: /** Finalizes the import of named sheet views, by creating the sheet views from the data. */ void finalizeNamedSheetViews(); - void setCellFormula( const ScAddress& rTokenAddress, const OUString& ); + void setCellFormula( const ScAddress& rTokenAddress, const OUString&, + bool bDynamicArrayMaster = false ); void setCellFormula( const ScAddress& rAddr, sal_Int32 nSharedId, - const OUString& rCellValue, sal_Int32 nValueType ); + const OUString& rCellValue, sal_Int32 nValueType, + bool bDynamicArrayMaster = false ); - void setCellArrayFormula(const ScRange& rRangeAddress, const ScAddress& rTokenAddress, const OUString& rTokenStr, bool bCachedSpill = false); + void setCellArrayFormula(const ScRange& rRangeAddress, const ScAddress& rTokenAddress, + const OUString& rTokenStr, bool bCachedSpill = false, + bool bDynamicArrayMaster = false); void createSharedFormulaMapEntry( const ScAddress& rAddress, diff --git a/sc/source/filter/oox/formulabuffer.cxx b/sc/source/filter/oox/formulabuffer.cxx index fa8d40ea9a44..bdba0aab9ac1 100644 --- a/sc/source/filter/oox/formulabuffer.cxx +++ b/sc/source/filter/oox/formulabuffer.cxx @@ -154,6 +154,9 @@ void applySharedFormulas( else pCell = new ScFormulaCell(rDoc.getDoc(), aPos, *pArray); + if (rDesc.mbDynamicArrayMaster) + pCell->SetDynamicArrayMaster(true); + rDoc.setFormulaCell(aPos, pCell); const bool bNeedNumberFormat = ((rDoc.getDoc().GetNumberFormat( aPos.Col(), aPos.Row(), aPos.Tab()) % SV_COUNTRY_LANGUAGE_OFFSET) == 0); @@ -263,6 +266,9 @@ void applyCellFormulas( else pCell = new ScFormulaCell(rDoc.getDoc(), aPos, p->mpCell->GetCode()->Clone()); + if (rItem.mbDynamicArrayMaster) + pCell->SetDynamicArrayMaster(true); + rDoc.setFormulaCell(aPos, pCell); if (rDoc.getDoc().GetNumberFormat(aPos.Col(), aPos.Row(), aPos.Tab()) % SV_COUNTRY_LANGUAGE_OFFSET == 0) pCell->SetNeedNumberFormat(true); @@ -282,6 +288,8 @@ void applyCellFormulas( aCompiler.CompileTokenArray(); // Generate RPN tokens. ScFormulaCell* pCell = new ScFormulaCell(rDoc.getDoc(), aPos, std::move(pCode)); + if (rItem.mbDynamicArrayMaster) + pCell->SetDynamicArrayMaster(true); rDoc.setFormulaCell(aPos, pCell); if (rDoc.getDoc().GetNumberFormat(aPos.Col(), aPos.Row(), aPos.Tab()) % SV_COUNTRY_LANGUAGE_OFFSET == 0) pCell->SetNeedNumberFormat(true); @@ -306,6 +314,12 @@ void applyArrayFormulas( rDoc.setMatrixCells(rAddressItem.maRange, *pArray, formula::FormulaGrammar::GRAM_OOXML, rAddressItem.mbCachedSpill); + if (rAddressItem.mbDynamicArrayMaster) + { + ScFormulaCell* pMaster = rDoc.getDoc().GetFormulaCell(aPos); + if (pMaster) + pMaster->SetDynamicArrayMaster(true); + } } } } @@ -422,9 +436,14 @@ FormulaBuffer::SharedFormulaEntry::SharedFormulaEntry( maAddress(rAddr), maTokenStr(std::move(aTokenStr)), mnSharedId(nSharedId) {} FormulaBuffer::SharedFormulaDesc::SharedFormulaDesc( - const ScAddress& rAddr, sal_Int32 nSharedId, - OUString aCellValue, sal_Int32 nValueType ) : - maAddress(rAddr), maCellValue(std::move(aCellValue)), mnSharedId(nSharedId), mnValueType(nValueType) {} + const ScAddress& rAddr, sal_Int32 nSharedId, + OUString aCellValue, sal_Int32 nValueType, bool bDynamicArrayMaster) + : maAddress(rAddr) + , maCellValue(std::move(aCellValue)) + , mnSharedId(nSharedId) + , mnValueType(nValueType) + , mbDynamicArrayMaster(bDynamicArrayMaster) +{} FormulaBuffer::SheetItem::SheetItem() : mpCellFormulas(nullptr), @@ -513,25 +532,30 @@ void FormulaBuffer::createSharedFormulaMapEntry( rSharedFormulas.push_back( aEntry ); } -void FormulaBuffer::setCellFormula( const ScAddress& rAddress, const OUString& rTokenStr ) +void FormulaBuffer::setCellFormula(const ScAddress& rAddress, const OUString& rTokenStr, + bool bDynamicArrayMaster) { assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maCellFormulas.size() ); - maCellFormulas[ rAddress.Tab() ].emplace_back( rTokenStr, rAddress ); + maCellFormulas[rAddress.Tab()].emplace_back(rTokenStr, rAddress, bDynamicArrayMaster); } void FormulaBuffer::setCellFormula( - const ScAddress& rAddress, sal_Int32 nSharedId, const OUString& rCellValue, sal_Int32 nValueType ) + const ScAddress& rAddress, sal_Int32 nSharedId, const OUString& rCellValue, sal_Int32 nValueType, + bool bDynamicArrayMaster) { assert( rAddress.Tab() >= 0 && o3tl::make_unsigned(rAddress.Tab()) < maSharedFormulaIds.size() ); - maSharedFormulaIds[rAddress.Tab()].emplace_back(rAddress, nSharedId, rCellValue, nValueType); + maSharedFormulaIds[rAddress.Tab()].emplace_back(rAddress, nSharedId, rCellValue, nValueType, + bDynamicArrayMaster); } -void FormulaBuffer::setCellArrayFormula( const ScRange& rRangeAddress, const ScAddress& rTokenAddress, const OUString& rTokenStr, bool bCachedSpill) +void FormulaBuffer::setCellArrayFormula(const ScRange& rRangeAddress, const ScAddress& rTokenAddress, + const OUString& rTokenStr, bool bCachedSpill, + bool bDynamicArrayMaster) { TokenAddressItem tokenPair( rTokenStr, rTokenAddress ); assert( rRangeAddress.aStart.Tab() >= 0 && o3tl::make_unsigned(rRangeAddress.aStart.Tab()) < maCellArrayFormulas.size() ); - maCellArrayFormulas[rRangeAddress.aStart.Tab()].emplace_back(tokenPair, rRangeAddress, bCachedSpill); + maCellArrayFormulas[rRangeAddress.aStart.Tab()].emplace_back(tokenPair, rRangeAddress, bCachedSpill, bDynamicArrayMaster); } void FormulaBuffer::setCellFormulaValue( diff --git a/sc/source/filter/oox/sheetdatacontext.cxx b/sc/source/filter/oox/sheetdatacontext.cxx index 03f1a4f12819..56f9d4dbb37d 100644 --- a/sc/source/filter/oox/sheetdatacontext.cxx +++ b/sc/source/filter/oox/sheetdatacontext.cxx @@ -143,7 +143,7 @@ void SheetDataContext::onEndElement() // master will create the matrix cells via setMatrixCells. if (!maFormulaStr.isEmpty() || mbCalcAlways) { - setCellFormula(maCellData.maCellAddr, maFormulaStr); + setCellFormula(maCellData.maCellAddr, maFormulaStr, mbCellMetadata); mrSheetData.setCellFormat(maCellData); // If a number cell has some preloaded value, stick it into the buffer @@ -161,7 +161,8 @@ void SheetDataContext::onEndElement() if( mbValidRange && maFmlaData.isValidSharedRef( maCellData.maCellAddr ) ) createSharedFormulaMapEntry(maCellData.maCellAddr, maFmlaData.mnSharedId, maFormulaStr); - setCellFormula(maCellData.maCellAddr, maFmlaData.mnSharedId, maCellValue, maCellData.mnCellType); + setCellFormula(maCellData.maCellAddr, maFmlaData.mnSharedId, maCellValue, maCellData.mnCellType, + mbCellMetadata); mrSheetData.setCellFormat( maCellData ); } else @@ -181,7 +182,8 @@ void SheetDataContext::onEndElement() = maCellData.mnCellType == XML_e && mbCellMetadata && maCellValue == u"#VALUE!"; - setCellArrayFormula(maFmlaData.maFormulaRef, maCellData.maCellAddr, maFormulaStr, bCachedSpill); + setCellArrayFormula(maFmlaData.maFormulaRef, maCellData.maCellAddr, maFormulaStr, + bCachedSpill, mbCellMetadata); } // set cell formatting, but do not set result as cell value mrSheetData.setBlankCell( maCellData ); diff --git a/sc/source/filter/oox/worksheethelper.cxx b/sc/source/filter/oox/worksheethelper.cxx index fa93e7a2fe5b..1c8d24e2bbad 100644 --- a/sc/source/filter/oox/worksheethelper.cxx +++ b/sc/source/filter/oox/worksheethelper.cxx @@ -1644,21 +1644,26 @@ void WorksheetHelper::finalizeNamedSheetViews() mrSheetGlob.finalizeNamedSheetViews(); } -void WorksheetHelper::setCellFormula( const ScAddress& rTokenAddress, const OUString& rTokenStr ) +void WorksheetHelper::setCellFormula(const ScAddress& rTokenAddress, const OUString& rTokenStr, + bool bDynamicArrayMaster) { - getFormulaBuffer().setCellFormula( rTokenAddress, rTokenStr ); + getFormulaBuffer().setCellFormula(rTokenAddress, rTokenStr, bDynamicArrayMaster); } void WorksheetHelper::setCellFormula( const ScAddress& rAddr, sal_Int32 nSharedId, - const OUString& rCellValue, sal_Int32 nValueType ) + const OUString& rCellValue, sal_Int32 nValueType, + bool bDynamicArrayMaster) { - getFormulaBuffer().setCellFormula(rAddr, nSharedId, rCellValue, nValueType); + getFormulaBuffer().setCellFormula(rAddr, nSharedId, rCellValue, nValueType, bDynamicArrayMaster); } -void WorksheetHelper::setCellArrayFormula(const ScRange& rRangeAddress, const ScAddress& rTokenAddress, const OUString& rTokenStr, bool bCachedSpill) +void WorksheetHelper::setCellArrayFormula(const ScRange& rRangeAddress, const ScAddress& rTokenAddress, + const OUString& rTokenStr, bool bCachedSpill, + bool bDynamicArrayMaster) { - getFormulaBuffer().setCellArrayFormula(rRangeAddress, rTokenAddress, rTokenStr, bCachedSpill); + getFormulaBuffer().setCellArrayFormula(rRangeAddress, rTokenAddress, rTokenStr, bCachedSpill, + bDynamicArrayMaster); } void WorksheetHelper::createSharedFormulaMapEntry(