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);

Reply via email to