From b15e1dcaa8d233717569662834f8c4351edf0e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Thu, 5 Mar 2026 14:29:14 +0900 Subject: [PATCH] sc: Add OOXML export of sheet views to named sheet view structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Miklos Vajna Reviewed-on: https://gerrit.libreoffice.org/c/core/+/202033 Reviewed-by: Tomaž Vajngerl (cherry picked from commit 288cfdd1632e77037fcac466afa2c2f12a146f1d) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/205470 Tested-by: Jenkins Reviewed-by: Xisco Fauli --- ...sc_named_sheet_views_import_export_test.mk | 71 ++++ sc/Library_scfilt.mk | 1 + sc/Module_sc.mk | 1 + .../unit/NamedSheetViewsImportExportTest.cxx | 232 ++++++++++++ sc/qa/unit/data/xlsx/NamedSheetViews.xlsx | Bin 0 -> 9494 bytes sc/source/filter/excel/excdoc.cxx | 3 + sc/source/filter/excel/excrecds.cxx | 20 +- sc/source/filter/excel/export/ExportTools.cxx | 39 ++ .../filter/excel/export/NamedSheetViews.cxx | 357 ++++++++++++++++++ sc/source/filter/inc/export/ExportTools.hxx | 10 + .../filter/inc/export/NamedSheetViews.hxx | 47 +++ .../filter/oox/NamedSheetViewImporter.cxx | 13 + 12 files changed, 777 insertions(+), 17 deletions(-) create mode 100644 sc/CppunitTest_sc_named_sheet_views_import_export_test.mk create mode 100644 sc/qa/unit/NamedSheetViewsImportExportTest.cxx create mode 100644 sc/qa/unit/data/xlsx/NamedSheetViews.xlsx create mode 100644 sc/source/filter/excel/export/NamedSheetViews.cxx create mode 100644 sc/source/filter/inc/export/NamedSheetViews.hxx 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 3e91cb1c04bc..d5cd1bdb0238 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 96a750ba02b0..2a58bca9e8ea 100644 --- a/sc/Module_sc.mk +++ b/sc/Module_sc.mk @@ -91,6 +91,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..a336de04abb8 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +class NamedSheetViewsImportExportTest : public UnoApiTest +{ +public: + NamedSheetViewsImportExportTest() + : UnoApiTest(u"sc/qa/unit/data"_ustr) + { + } +}; + +CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, testImportAndCheckState) +{ + loadFromFile(u"xlsx/NamedSheetViews.xlsx"); + + ScModelObj* pModelObj = comphelper::getFromUnoTunnel(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(TestFilter::XLSX); + + // 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(TestFilter::XLSX); + + ScModelObj* pModelObj = comphelper::getFromUnoTunnel(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(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(TestFilter::XLSX); + + // Check the state after roundtrip + { + ScModelObj* pModelObj = comphelper::getFromUnoTunnel(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 0000000000000000000000000000000000000000..76abfca9a78c967640891f788892476f835b57ef GIT binary patch literal 9494 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 V@Q_6W1%(Rv7DGx7Wzt_){|`eo&A9*o literal 0 HcmV?d00001 diff --git a/sc/source/filter/excel/excdoc.cxx b/sc/source/filter/excel/excdoc.cxx index be7c9284fae3..e1d70e22cf0f 100644 --- a/sc/source/filter/excel/excdoc.cxx +++ b/sc/source/filter/excel/excdoc.cxx @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -645,6 +646,8 @@ void ExcTable::FillAsTableXml() // after and before aRecList.AppendRecord( GetTablesManager().GetTablesBySheet( mnScTab)); + aRecList.AppendNewRecord(new xcl::exp::NamedSheetViews(GetRoot(), mnScTab)); + // queryTable parts (no wrapper element in sheet XML, only .rels entries) aRecList.AppendRecord( GetQueryTablesManager().GetQueryTablesBySheet( mnScTab )); diff --git a/sc/source/filter/excel/excrecds.cxx b/sc/source/filter/excel/excrecds.cxx index ca63d474fea1..adbed9cd76eb 100644 --- a/sc/source/filter/excel/excrecds.cxx +++ b/sc/source/filter/excel/excrecds.cxx @@ -18,6 +18,7 @@ */ #include +#include #include #include @@ -893,23 +894,8 @@ void XclExpAutofilter::SaveXml( XclExpXmlStream& rStrm ) // CT_DateGroupItems for (const auto& rDateValue : maDateValues) { - OString aStr = OUStringToOString(rDateValue, RTL_TEXTENCODING_UTF8); - rtl::Reference 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 dacd4e469a4a..5d0fca61ce0a 100644 --- a/sc/source/filter/excel/export/ExportTools.cxx +++ b/sc/source/filter/excel/export/ExportTools.cxx @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace oox::xls @@ -40,6 +41,44 @@ void writeComplexColor(const sax_fastparser::FSHelperPtr& pFS, sal_Int32 nElemen writeComplexColor(pFS, nElement, rComplexColor, rComplexColor.getFinalColor()); } } + +void writeDateGroupItem(const sax_fastparser::FSHelperPtr& pStream, sal_Int32 nElement, + std::string_view aDateString) +{ + rtl::Reference 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..d4cf64540d1f --- /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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +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& 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 aValues; + std::vector 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& 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> 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 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"_ustr); + + 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 dc08c33846b1..66166593a4c4 100644 --- a/sc/source/filter/inc/export/ExportTools.hxx +++ b/sc/source/filter/inc/export/ExportTools.hxx @@ -12,12 +12,22 @@ #include #include +#include + namespace oox::xls { void writeComplexColor(const sax_fastparser::FSHelperPtr& pFS, sal_Int32 nElement, model::ComplexColor const& rComplexColor, Color const& rColor); void writeComplexColor(const 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 +#include +#include +#include + +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 bd5462c7d322..8d2539a5728b 100644 --- a/sc/source/filter/oox/NamedSheetViewImporter.cxx +++ b/sc/source/filter/oox/NamedSheetViewImporter.cxx @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -103,6 +105,17 @@ void NamedSheetViewImporter::finalizeImport() { xDatabaseRange->setPropertyValue(u"AutoFilter"_ustr, css::uno::Any(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&)