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:
committed by
Xisco Fauli
parent
022c4ad8a9
commit
b15e1dcaa8
@@ -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:
|
||||
@@ -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 \
|
||||
|
||||
@@ -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.
@@ -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 ));
|
||||
|
||||
|
||||
@@ -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&)
|
||||
|
||||
Reference in New Issue
Block a user