engine/sc/inc/SheetView.hxx | 11 ++
engine/sc/qa/unit/NamedSheetViewsImportExportTest.cxx | 60 +++++++++++++++
engine/sc/source/core/data/SheetViewManager.cxx | 6 +
engine/sc/source/filter/excel/export/NamedSheetViews.cxx | 11 +-
engine/sc/source/filter/oox/NamedSheetViewImporter.cxx | 5 +
5 files changed, 85 insertions(+), 8 deletions(-)
New commits:
commit bb58d472a9b1d0045c01ff9d344c81def3264978
Author: Tomaž Vajngerl <
tomaz.v...@collabora.co.uk>
AuthorDate: Thu Apr 23 13:50:26 2026 +0900
Commit: Miklos Vajna <
vmi...@collabora.com>
CommitDate: Fri Apr 24 07:57:05 2026 +0000
sc: Preserve SheetView GUID in OOXML round-trip, generate if new
Element namedSheetView attribute @id and nsvFilter attribute
@filterId were regenerated on every save, so each round trip
churned the view's identity. Store both on SheetView and create
fresh ones when a new sheet view is created.
Signed-off-by: Tomaž Vajngerl <
tomaz.v...@collabora.co.uk>
Change-Id: I64aeb233e1bed4abd6ec900cb1061dfebe73865c
Reviewed-on:
https://gerrit.collaboraoffice.com/c/online/+/1430
diff --git a/engine/sc/inc/SheetView.hxx b/engine/sc/inc/SheetView.hxx
index bf97c6dc59ee..9e84b98b59d1 100644
--- a/engine/sc/inc/SheetView.hxx
+++ b/engine/sc/inc/SheetView.hxx
@@ -11,6 +11,7 @@
#include "scdllapi.h"
#include "types.hxx"
+#include <rtl/string.hxx>
#include <rtl/ustring.hxx>
#include "SheetViewTypes.hxx"
#include "sortparam.hxx"
@@ -86,6 +87,10 @@ private:
OUString maName;
SheetViewID mnID;
+ // GUIDs
+ OString maGUID;
+ OString maFilterGUID;
+
/** Sort data - nullptr when no sort has been performed. */
std::shared_ptr<SheetViewSortData> mpSortData;
@@ -113,6 +118,12 @@ public:
OUString const& GetName() const { return maName; }
void SetName(OUString const& rName) { maName = rName; }
+ OString const& GetGUID() const { return maGUID; }
+ void SetGUID(OString const& rGUID) { maGUID = rGUID; }
+
+ OString const& GetFilterGUID() const { return maFilterGUID; }
+ void SetFilterGUID(OString const& rGUID) { maFilterGUID = rGUID; }
+
/** A sheet view is valid if the pointer to the table is set */
bool isValid() const;
diff --git a/engine/sc/qa/unit/NamedSheetViewsImportExportTest.cxx b/engine/sc/qa/unit/NamedSheetViewsImportExportTest.cxx
index a8fd68e3544e..9b830b3dd001 100644
--- a/engine/sc/qa/unit/NamedSheetViewsImportExportTest.cxx
+++ b/engine/sc/qa/unit/NamedSheetViewsImportExportTest.cxx
@@ -175,6 +175,66 @@ CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, testRoundtripModelState)
}
}
+CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, testRoundtripGUIDs)
+{
+ loadFromFile(u"xlsx/NamedSheetViews.xlsx");
+
+ OString aView1GUID;
+ OString aView1FilterGUID;
+ OString aView2GUID;
+ OString aView2FilterGUID;
+ {
+ ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocument* pDocument = pModelObj->GetDocument();
+ CPPUNIT_ASSERT(pDocument);
+
+ auto pManager = pDocument->GetSheetViewManager(SCTAB(0));
+ CPPUNIT_ASSERT(pManager);
+
+ for (auto& rSheetView : pManager->iterateValidSheetViews())
+ {
+ if (rSheetView.GetName() == u"View1")
+ {
+ aView1GUID = rSheetView.GetGUID();
+ aView1FilterGUID = rSheetView.GetFilterGUID();
+ }
+ else if (rSheetView.GetName() == u"View2")
+ {
+ aView2GUID = rSheetView.GetGUID();
+ aView2FilterGUID = rSheetView.GetFilterGUID();
+ }
+ }
+
+ // Imported GUIDs must not be empty.
+ CPPUNIT_ASSERT(!aView1GUID.isEmpty());
+ CPPUNIT_ASSERT(!aView1FilterGUID.isEmpty());
+ CPPUNIT_ASSERT(!aView2GUID.isEmpty());
+ CPPUNIT_ASSERT(!aView2FilterGUID.isEmpty());
+ }
+
+ save(TestFilter::XLSX);
+
+ xmlDocUniquePtr pNsv = parseExport(u"xl/namedSheetViews/namedSheetView1.xml"_ustr);
+ CPPUNIT_ASSERT(pNsv);
+
+ // Exported @id / @filterId must match the GUIDs held on the runtime SheetView
+ CPPUNIT_ASSERT_EQUAL(
+ aView1GUID,
+ getXPath(pNsv, "/xnsv:namedSheetViews/xnsv:namedSheetView[1]", "id").toUtf8());
+ CPPUNIT_ASSERT_EQUAL(
+ aView1FilterGUID,
+ getXPath(pNsv, "/xnsv:namedSheetViews/xnsv:namedSheetView[1]/xnsv:nsvFilter", "filterId")
+ .toUtf8());
+ CPPUNIT_ASSERT_EQUAL(
+ aView2GUID,
+ getXPath(pNsv, "/xnsv:namedSheetViews/xnsv:namedSheetView[2]", "id").toUtf8());
+ CPPUNIT_ASSERT_EQUAL(
+ aView2FilterGUID,
+ getXPath(pNsv, "/xnsv:namedSheetViews/xnsv:namedSheetView[2]/xnsv:nsvFilter", "filterId")
+ .toUtf8());
+}
+
CPPUNIT_TEST_FIXTURE(NamedSheetViewsImportExportTest, testMultiSheetRoundtripVisibility)
{
loadFromFile(u"xlsx/NamedSheetViews.xlsx");
diff --git a/engine/sc/source/core/data/SheetViewManager.cxx b/engine/sc/source/core/data/SheetViewManager.cxx
index 8193fea6adf0..b8b07cad313b 100644
--- a/engine/sc/source/core/data/SheetViewManager.cxx
+++ b/engine/sc/source/core/data/SheetViewManager.cxx
@@ -11,6 +11,7 @@
#include <table.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
+#include <comphelper/xmltools.hxx>
namespace sc
{
@@ -19,7 +20,10 @@ SheetViewManager::SheetViewManager() {}
SheetViewID SheetViewManager::create(ScTable* pSheetViewTable)
{
SheetViewID nID(maViews.size());
- maViews.emplace_back(std::make_shared<SheetView>(pSheetViewTable, generateName(), nID));
+ auto pView = std::make_shared<SheetView>(pSheetViewTable, generateName(), nID);
+ pView->SetGUID(comphelper::xml::generateGUIDString());
+ pView->SetFilterGUID(comphelper::xml::generateGUIDString());
+ maViews.emplace_back(std::move(pView));
mnSheetViewCount++;
return nID;
}
diff --git a/engine/sc/source/filter/excel/export/NamedSheetViews.cxx b/engine/sc/source/filter/excel/export/NamedSheetViews.cxx
index 9902e1e9b231..62b55085d4a1 100644
--- a/engine/sc/source/filter/excel/export/NamedSheetViews.cxx
+++ b/engine/sc/source/filter/excel/export/NamedSheetViews.cxx
@@ -281,10 +281,8 @@ void xcl::exp::NamedSheetViews::saveSortRules(const sax_fastparser::FSHelperPtr&
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);
+ rSheetView.GetGUID());
// Get the view tab to access its filter/sort data
SCTAB nViewTab = rSheetView.getTableNumber();
@@ -298,10 +296,9 @@ void xcl::exp::NamedSheetViews::saveSheetView(const sax_fastparser::FSHelperPtr&
aRange.aEnd.SetTab(nViewTab);
OString aRangeString = XclXmlUtils::ToOString(GetDoc(), aRange);
- OString aFilterGUID = comphelper::xml::generateGUIDString();
-
- // TODO: emit XML_tableId when the filter is bound to a real table, not an anonymous DB range.
- pStream->startElement(XML_nsvFilter, XML_filterId, aFilterGUID, XML_ref, aRangeString);
+ // TODO: emit XML_tableId when the filter is bound to a real table
+ pStream->startElement(XML_nsvFilter, XML_filterId, rSheetView.GetFilterGUID(), XML_ref,
+ aRangeString);
ScQueryParam aQueryParam;
pDBData->GetQueryParam(aQueryParam);
diff --git a/engine/sc/source/filter/oox/NamedSheetViewImporter.cxx b/engine/sc/source/filter/oox/NamedSheetViewImporter.cxx
index fba97611292d..cb2063efc785 100644
--- a/engine/sc/source/filter/oox/NamedSheetViewImporter.cxx
+++ b/engine/sc/source/filter/oox/NamedSheetViewImporter.cxx
@@ -57,7 +57,12 @@ void NamedSheetViewImporter::finalizeImport()
auto pSheetView = pSheetViewManager->get(nSheetViewID);
if (pSheetView)
+ {
pSheetView->SetName(rViewData.maName);
+ pSheetView->SetGUID(rViewData.maID.toUtf8());
+ if (!rViewData.maNsvFilters.empty())
+ pSheetView->SetFilterGUID(rViewData.maNsvFilters.front().maFilterID.toUtf8());
+ }
// Apply filters and sort for each nsvFilter
for (const auto& rNsvFilter : rViewData.maNsvFilters)