sc/qa/unit/data/dataprovider/csv/tdf169610_SortTransform.csv | 9 + sc/qa/unit/dataproviders_test.cxx | 79 +++++++++++ sc/source/filter/xml/xmlexprt.cxx | 10 - sc/source/filter/xml/xmltransformationi.cxx | 69 +++++++++ sc/source/filter/xml/xmltransformationi.hxx | 5 sc/source/ui/inc/datatransformation.hxx | 3 6 files changed, 160 insertions(+), 15 deletions(-)
New commits: commit 0721944ce91774373946228cfbbac2e62052038d Author: Regina Henschel <[email protected]> AuthorDate: Sat Dec 20 23:44:34 2025 +0100 Commit: Regina Henschel <[email protected]> CommitDate: Sun Dec 21 18:02:29 2025 +0100 tdf#169610 dataprovider save,load SortTransformation Before this patch, sorting could be applied in the dataprovider dialog, but it was lost when saving the document. This patch adds saving and loading of the SortTransformation. The transformations of a dataprovider are applied to the data in a clipboard document. Thus the range address in the SortParam starts always in 0|0. That is different to the sorting that belongs to a database range. The file markup uses the elements table:sort and table:sort-by. But a way different to a database-range is needed, because they are not child elements of a <table:database-range>. The associated database range is only indirectly known from the calcext:database-name attribute of the calcext:data-mapping element. Change-Id: I2425e4f7e342e2ab2895388c7ae08bd08afa6101 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196037 Reviewed-by: Regina Henschel <[email protected]> Tested-by: Jenkins diff --git a/sc/qa/unit/data/dataprovider/csv/tdf169610_SortTransform.csv b/sc/qa/unit/data/dataprovider/csv/tdf169610_SortTransform.csv new file mode 100644 index 000000000000..5e2c02cf5741 --- /dev/null +++ b/sc/qa/unit/data/dataprovider/csv/tdf169610_SortTransform.csv @@ -0,0 +1,9 @@ +ID,Name,Region,Sales +1,Anton,West,23 +2,Josef,West,12 +3,Tom,East,14 +4,Ali,East,9 +5,Doris,West,10 +6,Frieda,East,17 +7,John,West,4 +8,Mary,East,3 \ No newline at end of file diff --git a/sc/qa/unit/dataproviders_test.cxx b/sc/qa/unit/dataproviders_test.cxx index 4fe3b5ed0cf9..1f8616f6b287 100644 --- a/sc/qa/unit/dataproviders_test.cxx +++ b/sc/qa/unit/dataproviders_test.cxx @@ -15,6 +15,8 @@ #include <datamapper.hxx> #include <vcl/scheduler.hxx> #include <orcusxml.hxx> +#include <sortparam.hxx> +#include <datatransformation.hxx> #include <memory> @@ -31,6 +33,7 @@ public: void testXMLImport(); void testBaseImport(); void testTdf169541_TwoDataMapping(); + void testTdf169610_SortTransform(); CPPUNIT_TEST_SUITE(ScDataProvidersTest); CPPUNIT_TEST(testCSVImport); @@ -39,6 +42,7 @@ public: CPPUNIT_TEST(testXMLImport); CPPUNIT_TEST(testBaseImport); CPPUNIT_TEST(testTdf169541_TwoDataMapping); + CPPUNIT_TEST(testTdf169610_SortTransform); CPPUNIT_TEST_SUITE_END(); }; @@ -226,6 +230,81 @@ void ScDataProvidersTest::testTdf169541_TwoDataMapping() // not adapted to the test environment and thus no further tests about the content here. } +void ScDataProvidersTest::testTdf169610_SortTransform() +{ + //Create Document + createScDoc(); + + // Create database range. Size arbitrary, but larger as data, here range C4:K16. + // rName, nTab, nCol1, nRow1, nCol2, nRow2 + ScDBData* pDBData = new ScDBData(u"testDB"_ustr, 0, 2, 3, 10, 15); + pDBData->SetHeader(true); + ScDocument* pDoc = getScDoc(); + bool bInserted + = pDoc->GetDBCollection()->getNamedDBs().insert(std::unique_ptr<ScDBData>(pDBData)); + CPPUNIT_ASSERT(bInserted); + + // Create data mapping directly without dialog + OUString aFileURL = createFileURL(u"csv/tdf169610_SortTransform.csv"); + sc::ExternalDataSource aDataSource(aFileURL, u"org.libreoffice.calc.csv"_ustr, pDoc); + aDataSource.setDBData(pDBData->GetName()); + + // range address is relative in SortTransform of Data Provider + ScSortParam aSortParam; + // ctor includes Clear(). That sets alreay 0 for nCol1, nRow1 and nSourceTab + aSortParam.nCol2 = 8; + aSortParam.nRow2 = 12; + aSortParam.bHasHeader = true; + aSortParam.maKeyState[0].bDoSort = true; + aSortParam.maKeyState[0].nField = 1; + aSortParam.maKeyState[0].bAscending = true; + aDataSource.AddDataTransformation(std::make_shared<sc::SortTransformation>(aSortParam)); + + pDoc->GetExternalDataMapper().insertDataSource(aDataSource); + auto& rDataSources = pDoc->GetExternalDataMapper().getDataSources(); + CPPUNIT_ASSERT(!rDataSources.empty()); + + // actually import the data. The data will cover the range C4:F12. + rDataSources[0].refresh(pDoc, true); + Scheduler::ProcessEventsToIdle(); + + // Examine some data + // Header Row with ID, Name, Region, Sales + CPPUNIT_ASSERT_EQUAL(u"ID"_ustr, pDoc->GetString(2, 3, 0)); // col, row, tab + CPPUNIT_ASSERT_EQUAL(u"Sales"_ustr, pDoc->GetString(5, 3, 0)); + // First data row + CPPUNIT_ASSERT_EQUAL(u"Ali"_ustr, pDoc->GetString(3, 4, 0)); + CPPUNIT_ASSERT_EQUAL(9.0, pDoc->GetValue(5, 4, 0)); + // Last data row + CPPUNIT_ASSERT_EQUAL(u"Tom"_ustr, pDoc->GetString(3, 11, 0)); + CPPUNIT_ASSERT_EQUAL(14.0, pDoc->GetValue(5, 11, 0)); + + // Save document and examine markup. Without fix the sort transformation + // was not written to file + skipValidation(); // ToDo: tdf#169669 no schema yet for calcext:data-mappings + saveAndReload(TestFilter::ODS); + pDoc = getScDoc(); + + xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr); + static constexpr OString sDataMappingPath = "//calcext:data-mapping"_ostr; + assertXPath(pXmlDoc, sDataMappingPath, 1); + const OString sTransformationPath = sDataMappingPath + + "/calcext:data-transformations" + "/calcext:column-sort-transformation"; + assertXPath(pXmlDoc, sTransformationPath, 1); + assertXPath(pXmlDoc, sTransformationPath + "/table:sort", 1); + const OString sSortByPath = sTransformationPath + "/table:sort/table:sort-by"; + assertXPath(pXmlDoc, sSortByPath, 1); + assertXPath(pXmlDoc, sSortByPath + "[@table:field-number='1']"); + + // Examine data again in reloaded document. + // Without fix the data were unsorted after reload. + CPPUNIT_ASSERT_EQUAL(u"Ali"_ustr, pDoc->GetString(3, 4, 0)); + CPPUNIT_ASSERT_EQUAL(9.0, pDoc->GetValue(5, 4, 0)); + CPPUNIT_ASSERT_EQUAL(u"Tom"_ustr, pDoc->GetString(3, 11, 0)); + CPPUNIT_ASSERT_EQUAL(14.0, pDoc->GetValue(5, 11, 0)); +} + ScDataProvidersTest::ScDataProvidersTest() : ScModelTestBase(u"sc/qa/unit/data/dataprovider"_ustr) { diff --git a/sc/source/filter/xml/xmlexprt.cxx b/sc/source/filter/xml/xmlexprt.cxx index 6200754b01c9..e32c6b89ee36 100644 --- a/sc/source/filter/xml/xmlexprt.cxx +++ b/sc/source/filter/xml/xmlexprt.cxx @@ -4257,14 +4257,8 @@ void ScXMLExport::WriteExternalDataTransformations(ScDocument& rDoc, const std:: // Sort Transformation std::shared_ptr<sc::SortTransformation> aSortTransformation = std::dynamic_pointer_cast<sc::SortTransformation>(itr); ScSortParam aSortParam = aSortTransformation->getSortParam(); - const sc::DocumentLinkManager& rMgr = rDoc.GetDocLinkManager(); - const sc::DataStream* pStrm = rMgr.getDataStream(); - if (!pStrm) - // No data stream. - return; - - // Streamed range - ScRange aRange = pStrm->GetRange(); + ScRange aRange(aSortParam.nCol1, aSortParam.nRow1, aSortParam.nSourceTab, + aSortParam.nCol2, aSortParam.nRow2, aSortParam.nSourceTab); SvXMLElementExport aTransformation(*this, XML_NAMESPACE_CALC_EXT, XML_COLUMN_SORT_TRANSFORMATION, true, true); diff --git a/sc/source/filter/xml/xmltransformationi.cxx b/sc/source/filter/xml/xmltransformationi.cxx index 2359c034ae3f..174345526e56 100644 --- a/sc/source/filter/xml/xmltransformationi.cxx +++ b/sc/source/filter/xml/xmltransformationi.cxx @@ -15,6 +15,8 @@ #include <datamapper.hxx> #include <document.hxx> +#include <dbdata.hxx> +#include <datatransformation.hxx> using namespace com::sun::star; using namespace xmloff::token; @@ -233,16 +235,75 @@ ScXMLColumnSortContext::ScXMLColumnSortContext( { } -ScXMLColumnSortContext::~ScXMLColumnSortContext() {} +ScXMLColumnSortContext::~ScXMLColumnSortContext() +{ + ScDocument* pDoc = GetScImport().GetDocument(); + auto& rDataSources = pDoc->GetExternalDataMapper().getDataSources(); + if (!rDataSources.empty()) + { + SCCOL nEndCol = 30; // ersatz if database range not found + SCROW nEndRow = 10000; // ersatz if database range not found + SCCOL nStartCol = 0; + SCROW nStartRow = 0; + SCTAB nTab = 0; + ScDBCollection::NamedDBs& rLocalDBs = pDoc->GetDBCollection()->getNamedDBs(); + ScDBData* pDB = rLocalDBs.findByName(rDataSources.back().getDBName()); + if (pDB) + { + // address of the to be sorted area is relative in sort transformation + pDB->GetArea(nTab, nStartCol, nStartRow, nEndCol, nEndRow); + nEndCol = nEndCol - nStartCol; + nEndRow = nEndRow - nStartRow; + maSortParam.bHasHeader = pDB->HasHeader(); + } + maSortParam.nCol2 = nEndCol; + maSortParam.nRow2 = nEndRow; + // Since transformation is applied in a clipboard document before it is transferred to the + // actual destination, the default value of 0 for nCol1, nRow1 and nSourceTab is correct. + rDataSources.back().AddDataTransformation( + std::make_shared<sc::SortTransformation>(maSortParam)); + } +} -/* uno::Reference<xml::sax::XFastContextHandler> SAL_CALL ScXMLColumnSortContext::createFastChildContext( sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) { - + switch (nElement) + { + case XML_ELEMENT(TABLE, XML_SORT): + { + return this; // handle child directly here + } + break; + case XML_ELEMENT(TABLE, XML_SORT_BY): + { + ScSortKeyState aSortKeyState; + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + switch (aIter.getToken()) + { + case XML_ELEMENT(TABLE, XML_FIELD_NUMBER): + { + aSortKeyState.nField = aIter.toInt32(); + aSortKeyState.bDoSort = true; + } + break; + case XML_ELEMENT(TABLE, XML_ORDER): + { + aSortKeyState.bAscending = IsXMLToken(aIter.toString(), XML_ASCENDING); + } + break; + // case XML_ELEMENT(TABLE, XML_DATA_TYPE), not used, always 'automatic' + } + } + // Currently no multi-level sort in sort transformation, thus write to index 0. + maSortParam.maKeyState[0] = aSortKeyState; + } + break; + } + return new SvXMLImportContext(GetImport()); } -*/ ScXMLColumnTextContext::ScXMLColumnTextContext( ScXMLImport& rImport, const rtl::Reference<sax_fastparser::FastAttributeList>& rAttrList) diff --git a/sc/source/filter/xml/xmltransformationi.hxx b/sc/source/filter/xml/xmltransformationi.hxx index 9ee7066209bb..cf6e951fe857 100644 --- a/sc/source/filter/xml/xmltransformationi.hxx +++ b/sc/source/filter/xml/xmltransformationi.hxx @@ -72,16 +72,17 @@ public: class ScXMLColumnSortContext : public ScXMLImportContext { + ScSortParam maSortParam; + public: ScXMLColumnSortContext(ScXMLImport& rImport, const rtl::Reference<sax_fastparser::FastAttributeList>& rAttrList); virtual ~ScXMLColumnSortContext() override; - /* + virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 nElement, const css::uno::Reference<css::xml::sax::XFastAttributeList>& xAttrList) override; - */ }; class ScXMLColumnTextContext : public ScXMLImportContext diff --git a/sc/source/ui/inc/datatransformation.hxx b/sc/source/ui/inc/datatransformation.hxx index aa822340fa80..038c11fb03b5 100644 --- a/sc/source/ui/inc/datatransformation.hxx +++ b/sc/source/ui/inc/datatransformation.hxx @@ -103,9 +103,10 @@ public: const std::set<SCCOL> & getColumns() const; }; -class SortTransformation : public DataTransformation +class SC_DLLPUBLIC SortTransformation : public DataTransformation { ScSortParam maSortParam; + public: SortTransformation(const ScSortParam& rParam);
