sc/CppunitTest_sc_uicalc2.mk | 1 sc/qa/unit/uicalc/data/biff12.clipboard.xlsb |binary sc/qa/unit/uicalc/uicalc2.cxx | 81 +++++++++++++++++++++++++++ sc/source/filter/xml/xmlexprt.cxx | 7 ++ sc/source/ui/view/viewfun5.cxx | 7 ++ 5 files changed, 94 insertions(+), 2 deletions(-)
New commits: commit 17fb6ff6d163c7037e1c66d28c229473ab77717e Author: Mike Kaganski <[email protected]> AuthorDate: Tue Feb 3 18:53:38 2026 +0500 Commit: Mike Kaganski <[email protected]> CommitDate: Tue Feb 3 18:56:03 2026 +0100 tdf#170567: Avoid resetting destination document's data When clipboard document is set up in Biff12 paste code, a call to ScDocument::ResetClip is required. In nearby code, the destination document is used for that, which was why I did the same. But it turned out, that following call to SfxObjectShell::DoLoad called ScDocShell::InitNew, and there some shared pooled items got reset, including an ScPatternAttr used for default attributes. It included clearing style pattern's, and that change affected target document. Then, in export, the document's formatting ranges were collected in ScXMLExport::AddStyleFromCells, which checked direct properties of each range. The document's default ScPatternAttr was missing style, and ScCellRangesBase::GetOnePropertyState returned PropertyState_AMBIGUOUS_VALUE, and ScXMLExport::AddStyleFromCells got empty aPropStates, without style name (which is documented to not have a default, so it must always be present). In the end, the default-formatted document ranges didn't create respective entries in ScRowFormatRanges::aRowFormatRanges. Then in ScXMLExport::ExportFormatRanges, trying to export formats for the first (default-formatted) rows, calls to GetFormatRanges left pRowFormatRanges empty, meaning that nMaxRows were 0, and no advance happened in the loop -> hang. (Maybe a similar situation caused commit b36a8a532b3ee6521d80277cb32a6936c0b09320 - busy loop seen on ods export attempt, 2023-12-07.) This change avoids unintended modification of the target document, by creating a fake "source" document to use in ResetClip. Also, this changes OSL_ENSURE to an assert, and fails export with XMLERROR_CANCEL | XMLERROR_FLAG_SEVERE error, when nMaxRows is 0 in ScXMLExport::ExportFormatRanges - same as what the mentioned commit b36a8a532b3ee6521d80277cb32a6936c0b09320 did in an adjacent branch. Change-Id: I3839ef0d2cb42d33cfc0b36b329658e159022586 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198609 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/sc/CppunitTest_sc_uicalc2.mk b/sc/CppunitTest_sc_uicalc2.mk index fbc467622f40..ec9ebbafb23a 100644 --- a/sc/CppunitTest_sc_uicalc2.mk +++ b/sc/CppunitTest_sc_uicalc2.mk @@ -29,6 +29,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sc_uicalc2, \ sc \ scqahelper \ sfx \ + sot \ subsequenttest \ svl \ svl \ diff --git a/sc/qa/unit/uicalc/data/biff12.clipboard.xlsb b/sc/qa/unit/uicalc/data/biff12.clipboard.xlsb new file mode 100644 index 000000000000..a013537d1cfc Binary files /dev/null and b/sc/qa/unit/uicalc/data/biff12.clipboard.xlsb differ diff --git a/sc/qa/unit/uicalc/uicalc2.cxx b/sc/qa/unit/uicalc/uicalc2.cxx index 047abf75c1a0..7bc542920cbf 100644 --- a/sc/qa/unit/uicalc/uicalc2.cxx +++ b/sc/qa/unit/uicalc/uicalc2.cxx @@ -8,9 +8,13 @@ */ #include "../helper/qahelper.hxx" + +#include <comphelper/compbase.hxx> #include <editeng/brushitem.hxx> #include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <sot/exchange.hxx> #include <svx/svdpage.hxx> +#include <tools/stream.hxx> #include <vcl/keycodes.hxx> #include <vcl/scheduler.hxx> #include <stlsheet.hxx> @@ -20,6 +24,9 @@ #include <comphelper/propertyvalue.hxx> #include <comphelper/servicehelper.hxx> #include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/DataFlavor.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> #include <com/sun/star/sheet/GlobalSheetSettings.hpp> #include <com/sun/star/text/XTextRange.hpp> #include <dbdata.hxx> @@ -1738,6 +1745,80 @@ CPPUNIT_TEST_FIXTURE(ScUiCalcTest2, testcommand_SetOptimalColumnWidth) CPPUNIT_ASSERT_EQUAL(nWidthB, pDoc->GetColWidth(1, 0)); } +CPPUNIT_TEST_FIXTURE(ScUiCalcTest2, testTdf170567_paste_Biff12_and_save_ODS) +{ + // A simple XTransferable implementation, that can provide a Biff12 content from file + class Biff12Transferable : public comphelper::WeakImplHelper<datatransfer::XTransferable> + { + public: + Biff12Transferable(const OUString& url) + : m_aFileURL(url) + { + } + + // XTransferable + uno::Any SAL_CALL getTransferData(const datatransfer::DataFlavor& aFlavor) override + { + if (!isDataFlavorSupported(aFlavor)) + return {}; + SvFileStream aStream(m_aFileURL, StreamMode::READ); + uno::Sequence<sal_Int8> bytes(aStream.remainingSize()); + aStream.ReadBytes(bytes.getArray(), aStream.remainingSize()); + return uno::Any(bytes); + } + uno::Sequence<datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override + { + return { getBiff12Flavor() }; + } + sal_Bool SAL_CALL isDataFlavorSupported(const datatransfer::DataFlavor& aFlavor) override + { + return aFlavor.MimeType.equalsIgnoreAsciiCase(getBiff12Flavor().MimeType); + } + + private: + OUString m_aFileURL; + + static datatransfer::DataFlavor getBiff12Flavor() + { + datatransfer::DataFlavor ret; + CPPUNIT_ASSERT(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BIFF_12, ret)); + return ret; + } + }; + + // Put a Biff12 format data into system clipboard: + auto xContext(comphelper::getProcessComponentContext()); + auto xClipboard = xContext->getServiceManager() + ->createInstanceWithContext( + u"com.sun.star.datatransfer.clipboard.SystemClipboard"_ustr, xContext) + .queryThrow<datatransfer::clipboard::XClipboard>(); + xClipboard->setContents(new Biff12Transferable(createFileURL(u"biff12.clipboard.xlsb")), {}); + + // Create document, and paste into C4 (important that first row is empty): + createScDoc(); + goToCell(u"C4"_ustr); + dispatchCommand(mxComponent, u".uno:Paste"_ustr, {}); + + ScDocument* pDoc = getScDoc(); + CPPUNIT_ASSERT_EQUAL(u"1"_ustr, pDoc->GetString(ScAddress(2, 3, 0))); + CPPUNIT_ASSERT_EQUAL(u"2"_ustr, pDoc->GetString(ScAddress(2, 4, 0))); + CPPUNIT_ASSERT_EQUAL(u"3"_ustr, pDoc->GetString(ScAddress(3, 3, 0))); + CPPUNIT_ASSERT_EQUAL(u"4"_ustr, pDoc->GetString(ScAddress(3, 4, 0))); + + // Save to ODS and realod. Without the fix in place, this test used to hang (and now there is + // an assertion that would fail): + saveAndReload(TestFilter::ODS); + + pDoc = getScDoc(); + CPPUNIT_ASSERT_EQUAL(u"1"_ustr, pDoc->GetString(ScAddress(2, 3, 0))); + CPPUNIT_ASSERT_EQUAL(u"2"_ustr, pDoc->GetString(ScAddress(2, 4, 0))); + CPPUNIT_ASSERT_EQUAL(u"3"_ustr, pDoc->GetString(ScAddress(3, 3, 0))); + CPPUNIT_ASSERT_EQUAL(u"4"_ustr, pDoc->GetString(ScAddress(3, 4, 0))); + + // No need to keep clipboard content after the test + xClipboard->setContents({}, {}); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/xml/xmlexprt.cxx b/sc/source/filter/xml/xmlexprt.cxx index 982c3579e917..5e6bd34fb5fd 100644 --- a/sc/source/filter/xml/xmlexprt.cxx +++ b/sc/source/filter/xml/xmlexprt.cxx @@ -1589,7 +1589,12 @@ void ScXMLExport::ExportFormatRanges(ScDocument& rDoc, const sal_Int32 nStartCol { pCellStyles->GetFormatRanges(0, pSharedData->GetLastColumn(nSheet), nStartRow + nRows, nSheet, pRowFormatRanges.get()); sal_Int32 nMaxRows = pRowFormatRanges->GetMaxRows(); - OSL_ENSURE(nMaxRows, "something went wrong"); + assert(nMaxRows && "ScXMLExport::ExportFormatRanges cannot make progress with zero rows, something went wrong"); + if (!nMaxRows) + { + SetError(XMLERROR_CANCEL | XMLERROR_FLAG_SEVERE, {}); + break; + } if (nMaxRows >= nTotalRows - nRows) { OpenRow(nSheet, nStartRow + nRows, nTotalRows - nRows, aRowAttr); diff --git a/sc/source/ui/view/viewfun5.cxx b/sc/source/ui/view/viewfun5.cxx index d294979cb389..5a401d20e9ba 100644 --- a/sc/source/ui/view/viewfun5.cxx +++ b/sc/source/ui/view/viewfun5.cxx @@ -340,9 +340,13 @@ bool ScViewFunc::PasteDataFormat( SotClipboardFormatId nFormatId, SfxFilterMatcher aMatcher(ScDocShell::Factory().GetFilterContainer()->GetName()); if (auto pFilter = aMatcher.GetFilter4ClipBoardId(SotClipboardFormatId::BIFF_12)) { + // Do not use rDoc as "source" in the call to ResetClip: DoLoad would call InitNew, + // which resets many pooled items, shared between source and clipboard documents, + // which modifies the source. We don't want that, so use a temporary document. + ScDocument aTmpClipSrc(SCDOCMODE_DOCUMENT); ScDocShellRef pClipShell(new ScDocShell(SfxModelFlags::NONE, SCDOCMODE_CLIP)); SCTAB nSrcTab = 0; - pClipShell->GetDocument().ResetClip(&rDoc, nSrcTab); + pClipShell->GetDocument().ResetClip(&aTmpClipSrc, nSrcTab); auto pMed = std::make_unique<SfxMedium>(); pMed->GetItemSet().Put(SfxUnoAnyItem(SID_INPUTSTREAM, uno::Any(xStm))); pMed->SetFilter(pFilter); @@ -353,6 +357,7 @@ bool ScViewFunc::PasteDataFormat( SotClipboardFormatId nFormatId, bAllowDialogs); bRet = true; } + pClipShell->DoClose(); } } }
