sc/CppunitTest_sc_named_sheet_views_import_export_test.mk |   71 ++
 sc/Library_scfilt.mk                                      |    1 
 sc/Module_sc.mk                                           |    1 
 sc/qa/unit/NamedSheetViewsImportExportTest.cxx            |  232 +++++++++
 sc/qa/unit/data/xlsx/NamedSheetViews.xlsx                 |binary
 sc/source/filter/excel/excdoc.cxx                         |    3 
 sc/source/filter/excel/excrecds.cxx                       |   20 
 sc/source/filter/excel/export/ExportTools.cxx             |   39 +
 sc/source/filter/excel/export/NamedSheetViews.cxx         |  357 ++++++++++++++
 sc/source/filter/inc/export/ExportTools.hxx               |   10 
 sc/source/filter/inc/export/NamedSheetViews.hxx           |   47 +
 sc/source/filter/oox/NamedSheetViewImporter.cxx           |   13 
 12 files changed, 777 insertions(+), 17 deletions(-)

New commits:
commit 2191fabec57de4af13ae6f9c6646b7eeb3a14b87
Author:     Tomaž Vajngerl <[email protected]>
AuthorDate: Thu Mar 5 14:29:14 2026 +0900
Commit:     Miklos Vajna <[email protected]>
CommitDate: Thu Mar 5 15:07:35 2026 +0100

    sc: Add OOXML export of sheet views to named sheet view structure
    
    Writes named sheet views to the OOXML document, including filter
    and sort data if a sheet view uses an auto-filter.
    
    Additionally adds OOXML import/export/roundtrip tests for named
    sheet views and tests that those are properly added as sheet views
    to the document model.
    
    Change-Id: Id7d38e4c95522986daa232e554b544d35c2fc284
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200996
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/sc/CppunitTest_sc_named_sheet_views_import_export_test.mk 
b/sc/CppunitTest_sc_named_sheet_views_import_export_test.mk
new file mode 100644
index 000000000000..a7a7d27fee9e
--- /dev/null
+++ b/sc/CppunitTest_sc_named_sheet_views_import_export_test.mk
@@ -0,0 +1,71 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call 
gb_CppunitTest_CppunitTest,sc_named_sheet_views_import_export_test))
+
+$(eval $(call 
gb_CppunitTest_use_common_precompiled_header,sc_named_sheet_views_import_export_test))
+
+$(eval $(call 
gb_CppunitTest_add_exception_objects,sc_named_sheet_views_import_export_test, \
+    sc/qa/unit/NamedSheetViewsImportExportTest \
+))
+
+$(eval $(call 
gb_CppunitTest_use_externals,sc_named_sheet_views_import_export_test, \
+    boost_headers \
+    mdds_headers \
+    libxml2 \
+))
+
+$(eval $(call 
gb_CppunitTest_use_libraries,sc_named_sheet_views_import_export_test, \
+    basegfx \
+    comphelper \
+    cppu \
+    cppuhelper \
+    sal \
+    salhelper \
+    sax \
+    sc \
+    scqahelper \
+    sfx \
+    subsequenttest \
+    test \
+    tl \
+    unotest \
+    utl \
+    vcl \
+))
+
+$(eval $(call 
gb_CppunitTest_set_include,sc_named_sheet_views_import_export_test,\
+    -I$(SRCDIR)/sc/source/ui/inc \
+    -I$(SRCDIR)/sc/inc \
+    $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_api,sc_named_sheet_views_import_export_test,\
+    offapi \
+    udkapi \
+))
+
+$(eval $(call 
gb_CppunitTest_use_sdk_api,sc_named_sheet_views_import_export_test))
+
+$(eval $(call gb_CppunitTest_use_ure,sc_named_sheet_views_import_export_test))
+$(eval $(call gb_CppunitTest_use_vcl,sc_named_sheet_views_import_export_test))
+
+$(eval $(call 
gb_CppunitTest_use_rdb,sc_named_sheet_views_import_export_test,services))
+
+$(eval $(call 
gb_CppunitTest_use_components,sc_named_sheet_views_import_export_test))
+
+$(eval $(call 
gb_CppunitTest_use_configuration,sc_named_sheet_views_import_export_test))
+
+$(eval $(call 
gb_CppunitTest_add_arguments,sc_named_sheet_views_import_export_test, \
+    
-env:arg-env=$(gb_Helper_LIBRARY_PATH_VAR)"$$$${$(gb_Helper_LIBRARY_PATH_VAR)+=$$$$$(gb_Helper_LIBRARY_PATH_VAR)}"
 \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sc/Library_scfilt.mk b/sc/Library_scfilt.mk
index 459cf81676ae..69cdfa7f2604 100644
--- a/sc/Library_scfilt.mk
+++ b/sc/Library_scfilt.mk
@@ -132,6 +132,7 @@ $(eval $(call gb_Library_add_exception_objects,scfilt,\
        sc/source/filter/excel/xltools \
        sc/source/filter/excel/xltracer \
        sc/source/filter/excel/xlview \
+       sc/source/filter/excel/export/NamedSheetViews \
        sc/source/filter/excel/export/SparklineExt \
        sc/source/filter/excel/export/ExportTools \
        sc/source/filter/ftools/fapihelper \
diff --git a/sc/Module_sc.mk b/sc/Module_sc.mk
index 2d1abc9d6ae1..20be74043d87 100644
--- a/sc/Module_sc.mk
+++ b/sc/Module_sc.mk
@@ -90,6 +90,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sc, \
        CppunitTest_sc_pdf_export \
        CppunitTest_sc_pivottable_filters_test \
        CppunitTest_sc_pivottable_formats_import_export_test \
+       CppunitTest_sc_named_sheet_views_import_export_test \
        CppunitTest_sc_scriptforge_test \
        CppunitTest_sc_sparkline_test \
        CppunitTest_sc_subsequent_filters_test \
diff --git a/sc/qa/unit/NamedSheetViewsImportExportTest.cxx 
b/sc/qa/unit/NamedSheetViewsImportExportTest.cxx
new file mode 100644
index 000000000000..0796b03f58b4
--- /dev/null
+++ b/sc/qa/unit/NamedSheetViewsImportExportTest.cxx
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <test/unoapixml_test.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <dbdata.hxx>
+#include <document.hxx>
+#include <docuno.hxx>
+#include <queryparam.hxx>
+#include <sortparam.hxx>
+#include <SheetView.hxx>
+#include <SheetViewManager.hxx>
+
+class NamedSheetViewsImportExportTest : public UnoApiXmlTest
+{
+public:
+    NamedSheetViewsImportExportTest()
+        : UnoApiXmlTest(u"sc/qa/unit/data"_ustr)
+    {
+    }
+};
+
+CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, testImportAndCheckState)
+{
+    loadFromFile(u"xlsx/NamedSheetViews.xlsx");
+
+    ScModelObj* pModelObj = 
comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
+    CPPUNIT_ASSERT(pModelObj);
+    ScDocument* pDoc = pModelObj->GetDocument();
+    CPPUNIT_ASSERT(pDoc);
+
+    // Check sheet view manager
+    auto pManager = pDoc->GetSheetViewManager(SCTAB(0));
+    CPPUNIT_ASSERT(pManager);
+    // 2 sheet views for sheet 1
+    CPPUNIT_ASSERT_EQUAL(size_t(2), pManager->size());
+
+    // 3 sheets total: Sheet1 + 2 sheet view tabs
+    CPPUNIT_ASSERT_EQUAL(SCTAB(3), pDoc->GetTableCount());
+}
+
+CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, testRoundtripXLSX)
+{
+    loadFromFile(u"xlsx/NamedSheetViews.xlsx");
+
+    // Loading is verified in a separate test so here we can assume it's fine
+
+    save(u"Calc Office Open XML"_ustr);
+
+    // Verify the exported XML structure
+    xmlDocUniquePtr pNsv = 
parseExport(u"xl/namedSheetViews/namedSheetView1.xml"_ustr);
+    CPPUNIT_ASSERT(pNsv);
+
+    // Two named sheet views: "View1" and "View2"
+    assertXPath(pNsv, "/xnsv:namedSheetViews/xnsv:namedSheetView", 2);
+    assertXPath(pNsv, "/xnsv:namedSheetViews/xnsv:namedSheetView[1]", "name", 
u"View1");
+    assertXPath(pNsv, "/xnsv:namedSheetViews/xnsv:namedSheetView[2]", "name", 
u"View2");
+
+    // View1: nsvFilter sort rule on column 2, no column filters
+    OString sView1 = 
"/xnsv:namedSheetViews/xnsv:namedSheetView[1]/xnsv:nsvFilter"_ostr;
+    assertXPath(pNsv, sView1, 1);
+    assertXPath(pNsv, sView1 + "/xnsv:columnFilter", 0);
+    assertXPath(pNsv, sView1 + "/xnsv:sortRules/xnsv:sortRule", 1);
+    assertXPath(pNsv, sView1 + "/xnsv:sortRules/xnsv:sortRule", "colId", u"2");
+    assertXPath(pNsv, sView1 + 
"/xnsv:sortRules/xnsv:sortRule/xnsv:sortCondition", "ref", u"C1:C8");
+
+    // View2: nsvFilter with 2 column filters and sort rule on column 0
+    OString sView2 = 
"/xnsv:namedSheetViews/xnsv:namedSheetView[2]/xnsv:nsvFilter"_ostr;
+    assertXPath(pNsv, sView2, 1);
+    assertXPath(pNsv, sView2 + "/xnsv:columnFilter", 2);
+
+    // Column filter on ID 1
+    assertXPath(pNsv, sView2 + "/xnsv:columnFilter[1]", "colId", u"1");
+    assertXPath(pNsv, sView2 + 
"/xnsv:columnFilter[1]/xnsv:filter/x:filters/x:filter", 4);
+
+    // Column filter on ID 2
+    assertXPath(pNsv, sView2 + "/xnsv:columnFilter[2]", "colId", u"2");
+    assertXPath(pNsv, sView2 + 
"/xnsv:columnFilter[2]/xnsv:filter/x:filters/x:dateGroupItem", 3);
+
+    // Sort rule on column 0
+    assertXPath(pNsv, sView2 + "/xnsv:sortRules/xnsv:sortRule", 1);
+    assertXPath(pNsv, sView2 + "/xnsv:sortRules/xnsv:sortRule", "colId", u"0");
+    assertXPath(pNsv, sView2 + 
"/xnsv:sortRules/xnsv:sortRule/xnsv:sortCondition", "ref", u"A1:A8");
+}
+
+CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, testRoundtripModelState)
+{
+    loadFromFile(u"xlsx/NamedSheetViews.xlsx");
+
+    // Save and reload to test full round-trip
+    saveAndReload(u"Calc Office Open XML"_ustr);
+
+    ScModelObj* pModelObj = 
comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
+    CPPUNIT_ASSERT(pModelObj);
+    ScDocument* pDoc = pModelObj->GetDocument();
+    CPPUNIT_ASSERT(pDoc);
+
+    // Check after roundtrip
+
+    // 3 tabs should exist
+    SCTAB nTabCount = pDoc->GetTableCount();
+    CPPUNIT_ASSERT_EQUAL(SCTAB(3), nTabCount);
+
+    // Tab index 0 should be visible, the sheet view tabs should be hidden
+    CPPUNIT_ASSERT(pDoc->IsVisible(0));
+    CPPUNIT_ASSERT(!pDoc->IsVisible(1)); // sheet view 1
+    CPPUNIT_ASSERT(!pDoc->IsVisible(2)); // sheet view 2
+
+    // Sheet view tabs should be sheet view holders
+    CPPUNIT_ASSERT(!pDoc->IsSheetViewHolder(0));
+    CPPUNIT_ASSERT(pDoc->IsSheetViewHolder(1));
+    CPPUNIT_ASSERT(pDoc->IsSheetViewHolder(2));
+
+    // Sheet views should exist on tab index 0 - associated with 2 sheet views
+    auto pManager = pDoc->GetSheetViewManager(0);
+    CPPUNIT_ASSERT(pManager);
+    CPPUNIT_ASSERT_EQUAL(size_t(2), pManager->size());
+
+    // Find the tab index for each view by name
+    SCTAB nView1Tab = -1;
+    SCTAB nView2Tab = -1;
+    for (auto& rSheetView : pManager->iterateValidSheetViews())
+    {
+        if (rSheetView.GetName() == u"View1")
+            nView1Tab = rSheetView.getTableNumber();
+        else if (rSheetView.GetName() == u"View2")
+            nView2Tab = rSheetView.getTableNumber();
+    }
+    CPPUNIT_ASSERT(nView1Tab >= 0);
+    CPPUNIT_ASSERT(nView2Tab >= 0);
+
+    // Sheet View 1: has sort on column 2
+    {
+        ScDBData* pDBData = pDoc->GetAnonymousDBData(nView1Tab);
+        CPPUNIT_ASSERT(pDBData);
+
+        ScSortParam aSortParam;
+        pDBData->GetSortParam(aSortParam);
+        CPPUNIT_ASSERT(aSortParam.maKeyState[0].bDoSort);
+        CPPUNIT_ASSERT_EQUAL(SCCOLROW(2), aSortParam.maKeyState[0].nField);
+    }
+
+    // Sheet View 2: has column filters and sort on column 0, with rows 
filtered
+    {
+        ScDBData* pDBData = pDoc->GetAnonymousDBData(nView2Tab);
+        CPPUNIT_ASSERT(pDBData);
+
+        ScQueryParam aQueryParam;
+        pDBData->GetQueryParam(aQueryParam);
+        // Should have at least one active query entry
+        CPPUNIT_ASSERT(aQueryParam.GetEntry(0).bDoQuery);
+
+        ScSortParam aSortParam;
+        pDBData->GetSortParam(aSortParam);
+        CPPUNIT_ASSERT(aSortParam.maKeyState[0].bDoSort);
+        CPPUNIT_ASSERT_EQUAL(SCCOLROW(0), aSortParam.maKeyState[0].nField);
+
+        // At least one data row should be hidden by the filter
+        bool bHasHiddenRow = false;
+        for (SCROW nRow = 1; nRow <= 7; ++nRow)
+        {
+            if (pDoc->RowHidden(nRow, nView2Tab))
+            {
+                bHasHiddenRow = true;
+                break;
+            }
+        }
+        CPPUNIT_ASSERT_MESSAGE("Sheet View 2 should have at least one filtered 
row", bHasHiddenRow);
+    }
+}
+
+CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, 
testMultiSheetRoundtripVisibility)
+{
+    loadFromFile(u"xlsx/NamedSheetViews.xlsx");
+
+    // After loading, the document already has 3 tabs: Sheet1 + 2 view tabs.
+    // Insert Sheet2 at the end so that after round-trip it tests that
+    // view tab creation doesn't break visibility of subsequent sheets.
+    {
+        ScModelObj* pModelObj = 
comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
+        CPPUNIT_ASSERT(pModelObj);
+        ScDocument* pDoc = pModelObj->GetDocument();
+        CPPUNIT_ASSERT(pDoc);
+
+        // Add a new tab (Sheet2) at the end
+        pDoc->InsertTab(pDoc->GetTableCount(), u"Sheet2"_ustr);
+        CPPUNIT_ASSERT_EQUAL(SCTAB(4), pDoc->GetTableCount());
+    }
+
+    // Roundtrip it
+    saveAndReload(u"Calc Office Open XML"_ustr);
+
+    // Check the state after roundtrip
+    {
+        ScModelObj* pModelObj = 
comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
+        CPPUNIT_ASSERT(pModelObj);
+        ScDocument* pDoc = pModelObj->GetDocument();
+        CPPUNIT_ASSERT(pDoc);
+
+        // 2 Sheets, Sheet 1 has 2 sheet views
+        SCTAB nTabCount = pDoc->GetTableCount();
+        CPPUNIT_ASSERT_EQUAL(SCTAB(4), nTabCount);
+
+        // Sheet1 (index 0) should be visible
+        CPPUNIT_ASSERT(pDoc->IsVisible(0));
+
+        // Sheet view tabs (index 1, 2) should be hidden and should be sheet 
view holders
+        CPPUNIT_ASSERT(!pDoc->IsVisible(1));
+        CPPUNIT_ASSERT(!pDoc->IsVisible(2));
+        CPPUNIT_ASSERT(pDoc->IsSheetViewHolder(1));
+        CPPUNIT_ASSERT(pDoc->IsSheetViewHolder(2));
+
+        // Sheet2 (index 3) should be visible as it's a normal sheet
+        CPPUNIT_ASSERT(pDoc->IsVisible(3));
+        CPPUNIT_ASSERT(!pDoc->IsSheetViewHolder(3));
+
+        // Sheet1 (index 0) should be associated with 2 sheet views
+        auto pManager = pDoc->GetSheetViewManager(0);
+        CPPUNIT_ASSERT(pManager);
+        CPPUNIT_ASSERT_EQUAL(size_t(2), pManager->size());
+    }
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/qa/unit/data/xlsx/NamedSheetViews.xlsx 
b/sc/qa/unit/data/xlsx/NamedSheetViews.xlsx
new file mode 100644
index 000000000000..76abfca9a78c
Binary files /dev/null and b/sc/qa/unit/data/xlsx/NamedSheetViews.xlsx differ
diff --git a/sc/source/filter/excel/excdoc.cxx 
b/sc/source/filter/excel/excdoc.cxx
index f11c21b929fc..86e525ad690f 100644
--- a/sc/source/filter/excel/excdoc.cxx
+++ b/sc/source/filter/excel/excdoc.cxx
@@ -46,6 +46,7 @@
 #include <XclExpChangeTrack.hxx>
 #include <xepivotxml.hxx>
 #include <xedbdata.hxx>
+#include <export/NamedSheetViews.hxx>
 #include <xlcontent.hxx>
 #include <xlname.hxx>
 #include <xllink.hxx>
@@ -647,6 +648,8 @@ void ExcTable::FillAsTableXml()
     // <tableParts> after <drawing> and before <extLst>
     aRecList.AppendRecord( GetTablesManager().GetTablesBySheet( mnScTab));
 
+    aRecList.AppendNewRecord(new xcl::exp::NamedSheetViews(GetRoot(), 
mnScTab));
+
     aRecList.AppendRecord( xExtLst );
 }
 
diff --git a/sc/source/filter/excel/excrecds.cxx 
b/sc/source/filter/excel/excrecds.cxx
index 2d18970e176b..9bc5bbc918b6 100644
--- a/sc/source/filter/excel/excrecds.cxx
+++ b/sc/source/filter/excel/excrecds.cxx
@@ -18,6 +18,7 @@
  */
 
 #include <excrecds.hxx>
+#include <export/ExportTools.hxx>
 
 #include <map>
 #include <filter/msfilter/countryid.hxx>
@@ -894,23 +895,8 @@ void XclExpAutofilter::SaveXml( XclExpXmlStream& rStrm )
             // CT_DateGroupItems
             for (const auto& rDateValue : maDateValues)
             {
-                OString aStr = OUStringToOString(rDateValue, 
RTL_TEXTENCODING_UTF8);
-                rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = 
sax_fastparser::FastSerializerHelper::createAttrList();
-                sal_Int32 aDateGroup[3] = { XML_year, XML_month, XML_day };
-                sal_Int32 idx = 0;
-                for (size_t i = 0; idx >= 0 && i < 3; i++)
-                {
-                    OString kw = aStr.getToken(0, '-', idx);
-                    kw = kw.trim();
-                    if (!kw.isEmpty())
-                    {
-                        pAttrList->add(aDateGroup[i], kw);
-                    }
-                }
-                // TODO: date filter can only handle YYYY-MM-DD date formats, 
so XML_dateTimeGrouping value
-                // will be "day" as default, until date filter cannot handle 
HH:MM:SS.
-                pAttrList->add(XML_dateTimeGrouping, "day");
-                rWorksheet->singleElement(XML_dateGroupItem, pAttrList);
+                OString aDateString = OUStringToOString(rDateValue, 
RTL_TEXTENCODING_UTF8);
+                oox::xls::writeDateGroupItem(rWorksheet, XML_dateGroupItem, 
aDateString);
             }
             rWorksheet->endElement(XML_filters);
         }
diff --git a/sc/source/filter/excel/export/ExportTools.cxx 
b/sc/source/filter/excel/export/ExportTools.cxx
index b093da012581..ce6550bbb8b4 100644
--- a/sc/source/filter/excel/export/ExportTools.cxx
+++ b/sc/source/filter/excel/export/ExportTools.cxx
@@ -11,6 +11,7 @@
 #include <oox/export/ColorExportUtils.hxx>
 #include <oox/token/tokens.hxx>
 #include <oox/token/namespaces.hxx>
+#include <sax/fastattribs.hxx>
 #include <xestream.hxx>
 
 namespace oox::xls
@@ -40,6 +41,44 @@ void writeComplexColor(sax_fastparser::FSHelperPtr& pFS, 
sal_Int32 nElement,
         writeComplexColor(pFS, nElement, rComplexColor, 
rComplexColor.getFinalColor());
     }
 }
+
+void writeDateGroupItem(const sax_fastparser::FSHelperPtr& pStream, sal_Int32 
nElement,
+                        std::string_view aDateString)
+{
+    rtl::Reference<sax_fastparser::FastAttributeList> pAttrList
+        = sax_fastparser::FastSerializerHelper::createAttrList();
+
+    // Tokenize on '-' to extract year, month, day components.
+    // The date string may be partial: "YYYY", "YYYY-MM", or "YYYY-MM-DD".
+    static constexpr sal_Int32 aDateTokens[] = { XML_year, XML_month, XML_day 
};
+    const char* pGrouping = "year";
+
+    size_t nStart = 0;
+    for (size_t i = 0; i < 3 && nStart < aDateString.size(); ++i)
+    {
+        size_t nSep = aDateString.find('-', nStart);
+        std::string_view aPart;
+        if (nSep != std::string_view::npos)
+            aPart = aDateString.substr(nStart, nSep - nStart);
+        else
+            aPart = aDateString.substr(nStart);
+
+        if (!aPart.empty())
+            pAttrList->add(aDateTokens[i], aPart);
+
+        if (i == 1)
+            pGrouping = "month";
+        else if (i == 2)
+            pGrouping = "day";
+
+        if (nSep == std::string_view::npos)
+            break;
+        nStart = nSep + 1;
+    }
+
+    pAttrList->add(XML_dateTimeGrouping, pGrouping);
+    pStream->singleElement(nElement, pAttrList);
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/excel/export/NamedSheetViews.cxx 
b/sc/source/filter/excel/export/NamedSheetViews.cxx
new file mode 100644
index 000000000000..5fde98ff32e6
--- /dev/null
+++ b/sc/source/filter/excel/export/NamedSheetViews.cxx
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <export/NamedSheetViews.hxx>
+#include <export/ExportTools.hxx>
+
+#include <document.hxx>
+#include <dbdata.hxx>
+#include <queryentry.hxx>
+#include <queryparam.hxx>
+#include <sortparam.hxx>
+#include <SheetView.hxx>
+#include <SheetViewManager.hxx>
+
+#include <xestream.hxx>
+#include <xestyle.hxx>
+
+#include <oox/token/namespaces.hxx>
+#include <oox/token/tokens.hxx>
+#include <oox/export/utils.hxx>
+#include <comphelper/xmltools.hxx>
+
+using namespace oox;
+
+namespace
+{
+/** Writes filter XML elements for query entries on a single column. */
+class ColumnFilterWriter
+{
+public:
+    ColumnFilterWriter(const sax_fastparser::FSHelperPtr& pStream,
+                       const std::vector<const ScQueryEntry*>& rEntries, const 
XclExpDxfs& rDxfs)
+        : mpStream(pStream)
+        , mrEntries(rEntries)
+        , mrDxfs(rDxfs)
+    {
+    }
+
+    void write()
+    {
+        if (mrEntries.empty())
+            return;
+
+        const ScQueryEntry& rFirst = *mrEntries[0];
+
+        if (rFirst.eOp == SC_TOPVAL || rFirst.eOp == SC_BOTVAL || rFirst.eOp 
== SC_TOPPERC
+            || rFirst.eOp == SC_BOTPERC)
+            writeTop10(rFirst);
+        else if (rFirst.IsQueryByTextColor() || 
rFirst.IsQueryByBackgroundColor())
+            writeColor(rFirst);
+        else if (rFirst.eOp == SC_EQUAL)
+            writeDiscrete();
+        else
+            writeCustom();
+    }
+
+private:
+    static const char* getOperator(ScQueryOp eOp)
+    {
+        switch (eOp)
+        {
+            case SC_EQUAL:
+                return "equal";
+            case SC_LESS:
+                return "lessThan";
+            case SC_GREATER:
+                return "greaterThan";
+            case SC_LESS_EQUAL:
+                return "lessThanOrEqual";
+            case SC_GREATER_EQUAL:
+                return "greaterThanOrEqual";
+            case SC_NOT_EQUAL:
+                return "notEqual";
+            default:
+                return nullptr;
+        }
+    }
+
+    void writeTop10(const ScQueryEntry& rEntry)
+    {
+        const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+        if (rItems.empty())
+            return;
+
+        bool bTop = (rEntry.eOp == SC_TOPVAL || rEntry.eOp == SC_TOPPERC);
+        bool bPercent = (rEntry.eOp == SC_TOPPERC || rEntry.eOp == SC_BOTPERC);
+        mpStream->singleElement(FSNS(XML_x, XML_top10), XML_top, ToPsz(bTop), 
XML_percent,
+                                ToPsz(bPercent), XML_val,
+                                OString::number(sal_Int32(rItems[0].mfVal)));
+    }
+
+    void writeColor(const ScQueryEntry& rEntry)
+    {
+        const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+        if (rItems.empty())
+            return;
+
+        bool bCellColor = rEntry.IsQueryByBackgroundColor();
+        sal_Int32 nDxfId = mrDxfs.GetDxfByColor(rItems[0].maColor);
+        mpStream->singleElement(FSNS(XML_x, XML_colorFilter), XML_dxfId, 
OString::number(nDxfId),
+                                XML_cellColor, OString::number(bCellColor ? 1 
: 0));
+    }
+
+    void writeDiscrete()
+    {
+        const ScQueryEntry& rFirst = *mrEntries[0];
+
+        bool bHasBlank = false;
+        std::vector<OString> aValues;
+        std::vector<OString> aDateValues;
+
+        for (const auto* pEntry : mrEntries)
+        {
+            const ScQueryEntry::QueryItemsType& rItems = 
pEntry->GetQueryItems();
+            for (const auto& rItem : rItems)
+            {
+                if (rItem.maString.isEmpty())
+                    bHasBlank = true;
+                else if (rItem.meType == ScQueryEntry::ByDate)
+                    aDateValues.push_back(
+                        OUStringToOString(rItem.maString.getString(), 
RTL_TEXTENCODING_UTF8));
+                else
+                    aValues.push_back(
+                        OUStringToOString(rItem.maString.getString(), 
RTL_TEXTENCODING_UTF8));
+            }
+        }
+
+        if (rFirst.IsQueryByEmpty())
+            bHasBlank = true;
+
+        if (bHasBlank)
+            mpStream->startElement(FSNS(XML_x, XML_filters), XML_blank, "1");
+        else
+            mpStream->startElement(FSNS(XML_x, XML_filters));
+
+        for (const auto& rValue : aValues)
+        {
+            mpStream->singleElement(FSNS(XML_x, XML_filter), XML_val, rValue);
+        }
+
+        for (const auto& rDateValue : aDateValues)
+        {
+            oox::xls::writeDateGroupItem(mpStream, FSNS(XML_x, 
XML_dateGroupItem), rDateValue);
+        }
+
+        mpStream->endElement(FSNS(XML_x, XML_filters));
+    }
+
+    void writeCustom()
+    {
+        const char* pOperator = getOperator(mrEntries[0]->eOp);
+        if (!pOperator)
+            return;
+
+        bool bAnd = (mrEntries.size() > 1 && mrEntries[1]->eConnect == SC_AND);
+        mpStream->startElement(FSNS(XML_x, XML_customFilters), XML_and, 
ToPsz(bAnd));
+
+        for (const auto* pEntry : mrEntries)
+        {
+            const char* pEntryOperator = getOperator(pEntry->eOp);
+            if (!pEntryOperator)
+                continue;
+
+            const ScQueryEntry::QueryItemsType& rItems = 
pEntry->GetQueryItems();
+            OString aValue;
+            if (!rItems.empty())
+                aValue = OUStringToOString(rItems[0].maString.getString(), 
RTL_TEXTENCODING_UTF8);
+
+            mpStream->singleElement(FSNS(XML_x, XML_customFilter), 
XML_operator, pEntryOperator,
+                                    XML_val, aValue);
+        }
+
+        mpStream->endElement(FSNS(XML_x, XML_customFilters));
+    }
+
+    const sax_fastparser::FSHelperPtr& mpStream;
+    const std::vector<const ScQueryEntry*>& mrEntries;
+    const XclExpDxfs& mrDxfs;
+};
+
+} // anonymous namespace
+
+xcl::exp::NamedSheetViews::NamedSheetViews(const XclExpRoot& rRoot, SCTAB nTab)
+    : XclExpRoot(rRoot)
+    , mnTab(nTab)
+    , mbHasSheetViews(false)
+{
+    auto pManager = GetDoc().GetSheetViewManager(nTab);
+    if (pManager && !pManager->isEmpty())
+        mbHasSheetViews = true;
+}
+
+void xcl::exp::NamedSheetViews::saveColumnFilters(const 
sax_fastparser::FSHelperPtr& pStream,
+                                                  const ScQueryParam& 
rQueryParam)
+{
+    // Group query entries by column
+    std::map<SCCOLROW, std::vector<const ScQueryEntry*>> aColumnEntries;
+    bool bHasFilters = false;
+
+    for (SCSIZE i = 0; i < rQueryParam.GetEntryCount(); ++i)
+    {
+        const ScQueryEntry& rEntry = rQueryParam.GetEntry(i);
+        if (!rEntry.bDoQuery)
+            break;
+        aColumnEntries[rEntry.nField].push_back(&rEntry);
+        bHasFilters = true;
+    }
+
+    if (!bHasFilters)
+        return;
+
+    for (const auto & [ nCol, rEntries ] : aColumnEntries)
+    {
+        pStream->startElement(XML_columnFilter, XML_colId, 
OString::number(nCol));
+        pStream->startElement(XML_filter, XML_colId, OString::number(nCol));
+        ColumnFilterWriter(pStream, rEntries, GetDxfs()).write();
+        pStream->endElement(XML_filter);
+        pStream->endElement(XML_columnFilter);
+    }
+}
+
+void xcl::exp::NamedSheetViews::saveSortRules(const 
sax_fastparser::FSHelperPtr& pStream,
+                                              const ScRange& rRange, SCTAB 
nViewTab,
+                                              const ScSortParam& rSortParam)
+{
+    bool bHasSort = false;
+    for (const auto& rKey : rSortParam.maKeyState)
+    {
+        if (rKey.bDoSort)
+        {
+            bHasSort = true;
+            break;
+        }
+    }
+
+    if (!bHasSort)
+        return;
+
+    pStream->startElement(XML_sortRules, XML_caseSensitive, 
ToPsz(rSortParam.bCaseSens));
+
+    for (const auto& rKey : rSortParam.maKeyState)
+    {
+        if (!rKey.bDoSort)
+            continue;
+
+        OString aSortRuleGUID = comphelper::xml::generateGUIDString();
+
+        pStream->startElement(XML_sortRule, XML_colId, 
OString::number(rKey.nField), XML_id,
+                              aSortRuleGUID);
+
+        // Build the column range reference for sortCondition
+        ScRange aSortColRange;
+        aSortColRange.aStart.SetCol(SCCOL(rKey.nField));
+        aSortColRange.aStart.SetRow(rRange.aStart.Row());
+        aSortColRange.aStart.SetTab(nViewTab);
+        aSortColRange.aEnd.SetCol(SCCOL(rKey.nField));
+        aSortColRange.aEnd.SetRow(rRange.aEnd.Row());
+        aSortColRange.aEnd.SetTab(nViewTab);
+
+        OString aSortReference = XclXmlUtils::ToOString(GetDoc(), 
aSortColRange);
+
+        std::optional<OString> sDescending;
+        if (!rKey.bAscending)
+            sDescending = "1";
+
+        pStream->singleElement(XML_sortCondition, XML_ref, aSortReference, 
XML_descending,
+                               sDescending);
+
+        pStream->endElement(XML_sortRule);
+    }
+
+    pStream->endElement(XML_sortRules);
+}
+
+void xcl::exp::NamedSheetViews::saveSheetView(const 
sax_fastparser::FSHelperPtr& pStream,
+                                              const sc::SheetView& rSheetView)
+{
+    OString aGUID = comphelper::xml::generateGUIDString();
+
+    pStream->startElement(XML_namedSheetView, XML_name, 
rSheetView.GetName().toUtf8(), XML_id,
+                          aGUID);
+
+    // Get the view tab to access its filter/sort data
+    SCTAB nViewTab = rSheetView.getTableNumber();
+    ScDBData* pDBData = GetDoc().GetAnonymousDBData(nViewTab);
+
+    if (pDBData)
+    {
+        ScRange aRange;
+        pDBData->GetArea(aRange);
+        aRange.aStart.SetTab(nViewTab);
+        aRange.aEnd.SetTab(nViewTab);
+
+        OString aRangeString = XclXmlUtils::ToOString(GetDoc(), aRange);
+        OString aFilterGUID = comphelper::xml::generateGUIDString();
+
+        pStream->startElement(XML_nsvFilter, XML_filterId, aFilterGUID, 
XML_ref, aRangeString,
+                              XML_tableId, "0");
+
+        ScQueryParam aQueryParam;
+        pDBData->GetQueryParam(aQueryParam);
+        saveColumnFilters(pStream, aQueryParam);
+
+        ScSortParam aSortParam;
+        pDBData->GetSortParam(aSortParam);
+        saveSortRules(pStream, aRange, nViewTab, aSortParam);
+
+        pStream->endElement(XML_nsvFilter);
+    }
+
+    pStream->endElement(XML_namedSheetView);
+}
+
+void xcl::exp::NamedSheetViews::SaveXml(XclExpXmlStream& rStream)
+{
+    if (!mbHasSheetViews)
+        return;
+
+    auto pManager = GetDoc().GetSheetViewManager(mnTab);
+    if (!pManager || pManager->isEmpty())
+        return;
+
+    // Create output stream for the namedSheetView part
+    sax_fastparser::FSHelperPtr pWorksheetStream = rStream.GetCurrentStream();
+
+    OUString sFullPath
+        = XclXmlUtils::GetStreamName("xl/namedSheetViews/", "namedSheetView", 
mnTab + 1);
+    OUString sRelPath
+        = XclXmlUtils::GetStreamName("../namedSheetViews/", "namedSheetView", 
mnTab + 1);
+
+    sax_fastparser::FSHelperPtr pNamedSheetViewStream = 
rStream.CreateOutputStream(
+        sFullPath, sRelPath, pWorksheetStream->getOutputStream(),
+        "application/vnd.ms-excel.namedSheetViews+xml",
+        
u"http://schemas.microsoft.com/office/2019/04/relationships/namedSheetView";);
+
+    rStream.PushStream(pNamedSheetViewStream);
+
+    pNamedSheetViewStream->startElement(
+        XML_namedSheetViews, XML_xmlns, rStream.getNamespaceURL(OOX_NS(xnsv)),
+        FSNS(XML_xmlns, XML_x), rStream.getNamespaceURL(OOX_NS(xls)), 
FSNS(XML_xmlns, XML_x14),
+        rStream.getNamespaceURL(OOX_NS(xls14Lst)));
+
+    for (auto& rSheetView : pManager->iterateValidSheetViews())
+        saveSheetView(pNamedSheetViewStream, rSheetView);
+
+    pNamedSheetViewStream->endElement(XML_namedSheetViews);
+
+    rStream.PopStream();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/export/ExportTools.hxx 
b/sc/source/filter/inc/export/ExportTools.hxx
index 369728f06e10..d450dcc84cd1 100644
--- a/sc/source/filter/inc/export/ExportTools.hxx
+++ b/sc/source/filter/inc/export/ExportTools.hxx
@@ -12,12 +12,22 @@
 #include <docmodel/color/ComplexColor.hxx>
 #include <sax/fshelper.hxx>
 
+#include <string_view>
+
 namespace oox::xls
 {
 void writeComplexColor(sax_fastparser::FSHelperPtr& pFS, sal_Int32 nElement,
                        model::ComplexColor const& rComplexColor, Color const& 
rColor);
 void writeComplexColor(sax_fastparser::FSHelperPtr& pFS, sal_Int32 nElement,
                        model::ComplexColor const& rComplexColor);
+
+/** Writes an OOXML dateGroupItem element from a date string.
+    The date string may be partial: "YYYY", "YYYY-MM", or "YYYY-MM-DD".
+    The dateTimeGrouping attribute is determined by the number of date 
components.
+    nElement is the fully-qualified element token (e.g. XML_dateGroupItem or
+    FSNS(XML_x, XML_dateGroupItem) depending on the stream's namespace 
context). */
+void writeDateGroupItem(sax_fastparser::FSHelperPtr const& pStream, sal_Int32 
nElement,
+                        std::string_view aDateString);
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/export/NamedSheetViews.hxx 
b/sc/source/filter/inc/export/NamedSheetViews.hxx
new file mode 100644
index 000000000000..797266399ba1
--- /dev/null
+++ b/sc/source/filter/inc/export/NamedSheetViews.hxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <xerecord.hxx>
+#include <xeroot.hxx>
+#include <sax/fshelper.hxx>
+#include <types.hxx>
+
+struct ScQueryParam;
+struct ScSortParam;
+namespace sc
+{
+class SheetView;
+}
+
+namespace xcl::exp
+{
+/** Export of named sheet views for a single sheet. */
+class NamedSheetViews : public XclExpRecordBase, protected XclExpRoot
+{
+public:
+    explicit NamedSheetViews(const XclExpRoot& rRoot, SCTAB nTab);
+
+    virtual void SaveXml(XclExpXmlStream& rStream) override;
+
+private:
+    void saveSheetView(const sax_fastparser::FSHelperPtr& pStream, const 
sc::SheetView& rSheetView);
+    void saveColumnFilters(const sax_fastparser::FSHelperPtr& pStream,
+                           const ScQueryParam& rQueryParam);
+    void saveSortRules(const sax_fastparser::FSHelperPtr& pStream, const 
ScRange& rRange,
+                       SCTAB nViewTab, const ScSortParam& rSortParam);
+
+    SCTAB mnTab;
+    bool mbHasSheetViews;
+};
+
+} // end namespace xcl::exp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/oox/NamedSheetViewImporter.cxx 
b/sc/source/filter/oox/NamedSheetViewImporter.cxx
index 7494dd5970c7..60908f2f9948 100644
--- a/sc/source/filter/oox/NamedSheetViewImporter.cxx
+++ b/sc/source/filter/oox/NamedSheetViewImporter.cxx
@@ -13,7 +13,9 @@
 #include <oox/token/properties.hxx>
 #include <addressconverter.hxx>
 #include <autofilterbuffer.hxx>
+#include <dbdata.hxx>
 #include <document.hxx>
+#include <queryparam.hxx>
 #include <SheetView.hxx>
 #include <SheetViewManager.hxx>
 #include <com/sun/star/sheet/XDatabaseRange.hpp>
@@ -104,6 +106,17 @@ void NamedSheetViewImporter::finalizeImport()
                         PropertySet aRangeProps(xDatabaseRange);
                         aRangeProps.setProperty(PROP_AutoFilter, true);
                         aAutoFilter.finalizeImport(xDatabaseRange, nViewTab);
+
+                        // Execute the filter query to actually hide rows
+                        ScDBData* pDBData = rDoc.GetAnonymousDBData(nViewTab);
+                        ScDocShell* pFilterDocShell = rDoc.GetDocumentShell();
+                        if (pDBData && pFilterDocShell)
+                        {
+                            ScQueryParam aQueryParam;
+                            pDBData->GetQueryParam(aQueryParam);
+                            ScDBDocFunc aDBDocFunc(*pFilterDocShell);
+                            aDBDocFunc.Query(nViewTab, aQueryParam, nullptr, 
false, false);
+                        }
                     }
                 }
                 catch (const css::uno::Exception&)

Reply via email to