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