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 <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/202033
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
(cherry picked from commit 288cfdd1632e77037fcac466afa2c2f12a146f1d)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/205470
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Tomaž Vajngerl
2026-05-21 18:08:00 +02:00
committed by Xisco Fauli
parent 022c4ad8a9
commit b15e1dcaa8
12 changed files with 777 additions and 17 deletions
@@ -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:
+1
View File
@@ -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 \
+1
View File
@@ -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 \
@@ -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/unoapi_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 UnoApiTest
{
public:
NamedSheetViewsImportExportTest()
: UnoApiTest(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(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<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(TestFilter::XLSX);
// 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: */
Binary file not shown.
+3
View File
@@ -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>
@@ -645,6 +646,8 @@ void ExcTable::FillAsTableXml()
// <tableParts> after <drawing> and before <extLst>
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 ));
+3 -17
View File
@@ -18,6 +18,7 @@
*/
#include <excrecds.hxx>
#include <export/ExportTools.hxx>
#include <map>
#include <filter/msfilter/countryid.hxx>
@@ -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<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);
}
@@ -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(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<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: */
@@ -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"_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: */
@@ -12,12 +12,22 @@
#include <docmodel/color/ComplexColor.hxx>
#include <sax/fshelper.hxx>
#include <string_view>
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: */
@@ -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: */
@@ -13,7 +13,9 @@
#include <addressconverter.hxx>
#include <autofilterbuffer.hxx>
#include <datauno.hxx>
#include <dbdata.hxx>
#include <document.hxx>
#include <queryparam.hxx>
#include <SheetView.hxx>
#include <SheetViewManager.hxx>
#include <com/sun/star/sheet/XDatabaseRange.hpp>
@@ -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&)