sc/inc/scextopt.hxx                       |    1 +
 sc/qa/unit/subsequent_export_test2.cxx    |   27 ++++++++++++++++++++++++++-
 sc/source/filter/excel/excdoc.cxx         |   17 +++++++++++++----
 sc/source/filter/inc/workbooksettings.hxx |    5 +++++
 sc/source/filter/oox/workbookfragment.cxx |    1 +
 sc/source/filter/oox/workbooksettings.cxx |   16 ++++++++++++++++
 6 files changed, 62 insertions(+), 5 deletions(-)

New commits:
commit 70ec8eca720e3cdc0f1b26b14c9984ce691659cd
Author:     Justin Luth <[email protected]>
AuthorDate: Wed Dec 10 16:31:34 2025 -0500
Commit:     Justin Luth <[email protected]>
CommitDate: Fri Dec 12 00:44:35 2025 +0100

    tdf#165180 xlsx export: lowestEdited=4 for ECMA 376 1st ed.
    
    If we don't provide lowestEdited=4
    when saving as an Excel 2007 spreadsheet,
    then LO (and Excel) will treat it as a 2010 document
    the next time it is loaded.
    
    make CppunitTest_sc_subsequent_export_test2 \
        CPPUNIT_TEST_NAME=testTdf114969XLSX
    
    Change-Id: I7146c083dbc4eb451ce26cf1e282f63e6859c1b7
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195180
    Tested-by: Jenkins
    Reviewed-by: Justin Luth <[email protected]>

diff --git a/sc/qa/unit/subsequent_export_test2.cxx 
b/sc/qa/unit/subsequent_export_test2.cxx
index b38b6eefaf6f..5eaa901bb05b 100644
--- a/sc/qa/unit/subsequent_export_test2.cxx
+++ b/sc/qa/unit/subsequent_export_test2.cxx
@@ -1124,6 +1124,12 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf114969XLSX)
     CPPUNIT_ASSERT(pDoc);
     assertXPath(pDoc, "/x:worksheet/x:hyperlinks/x:hyperlink[1]", "location", 
u"'1.1.1.1'!C1");
     assertXPath(pDoc, "/x:worksheet/x:hyperlinks/x:hyperlink[2]", "location", 
u"'1.1.1.1'!C2");
+
+    // tdf#165180: should be recognizable as a 2007 XLSX format when saved
+    save(TestFilter::XLSX_2007);
+    xmlDocUniquePtr pWorkbook = parseExport(u"xl/workbook.xml"_ustr);
+    // lowestEdited = 4 needs to be output in order to be detected as a 2007 
ECMA_376_1ST_EDITION
+    assertXPath(pWorkbook, "/x:workbook/x:fileVersion", "lowestEdited", u"4");
 }
 
 CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf115192XLSX)
diff --git a/sc/source/filter/excel/excdoc.cxx 
b/sc/source/filter/excel/excdoc.cxx
index fc794b00ab60..21e537baa28f 100644
--- a/sc/source/filter/excel/excdoc.cxx
+++ b/sc/source/filter/excel/excdoc.cxx
@@ -857,6 +857,11 @@ void ExcDocument::WriteXml( XclExpXmlStream& rStrm )
         = sax_fastparser::FastSerializerHelper::createAttrList();
     pAttrListFileVersion->add(XML_appName, "Calc");
     std::optional<sal_Int16> oLow = 
GetExtDocOptions().GetDocSettings().moLowestEdited;
+    if (rStrm.getVersion() == oox::core::ECMA_376_1ST_EDITION
+        && (!oLow.has_value() || oLow > 4))
+    {
+        oLow = 4; // when saving as MS Excel 2007, set the corresponding 
lowestEdited-by version
+    }
     if (oLow.has_value())
         pAttrListFileVersion->add(XML_lowestEdited, OString::number(*oLow));
             // OOXTODO: XML_codeName
commit 78db442150f7c729c6ab6b7dd7a23cedcace1ef8
Author:     Justin Luth <[email protected]>
AuthorDate: Sat Nov 29 17:08:59 2025 -0500
Commit:     Justin Luth <[email protected]>
CommitDate: Fri Dec 12 00:44:28 2025 +0100

    tdf#165180 xlsx UI: round-trip as 2007 Spreadsheet, not 2010-365
    
    Historically we have ALWAYS round-tripped with the 2007 filter.
    
    A recent change (in this patchset)
    started importing xlsx without a lowestEdited as 2010+ documents,
    and thus they will then also export as 2010+.
    
    With this patch, we round-trip the original lowestEdited,
    and thus we can again round-trip 2007 as 2007.
    
    make CppunitTest_sc_subsequent_export_test2 \
        CPPUNIT_TEST_NAME=testMatrixMultiplicationXLSX
    
    Change-Id: I85c4163f76902f2972193a2fdfe7ee9b4dd510c0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195035
    Tested-by: Jenkins
    Reviewed-by: Justin Luth <[email protected]>

diff --git a/sc/inc/scextopt.hxx b/sc/inc/scextopt.hxx
index 4a71fdd7dd30..1cc8b3f3bcd7 100644
--- a/sc/inc/scextopt.hxx
+++ b/sc/inc/scextopt.hxx
@@ -30,6 +30,7 @@ struct ScExtDocSettings
     double              mfTabBarWidth;      ///< Width of the tabbar, relative 
to frame window width (0.0 ... 1.0).
     sal_uInt32          mnLinkCnt;          ///< Recursive counter for loading 
external documents.
     SCTAB               mnDisplTab;         ///< Index of displayed sheet.
+    std::optional<sal_Int16> moLowestEdited; ///< Oldest Excel version that 
edited this document.
 
     explicit            ScExtDocSettings();
 };
diff --git a/sc/qa/unit/subsequent_export_test2.cxx 
b/sc/qa/unit/subsequent_export_test2.cxx
index 3753d531bbcf..b38b6eefaf6f 100644
--- a/sc/qa/unit/subsequent_export_test2.cxx
+++ b/sc/qa/unit/subsequent_export_test2.cxx
@@ -140,6 +140,13 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, 
testMatrixMultiplicationXLSX)
 {
     createScDoc("xlsx/matrix-multiplication.xlsx");
 
+    // tdf#165180: should be recognized as a 2007 XLSX format when loaded (and 
resaved...)
+    SfxMedium* pMedium = getScDocShell()->GetMedium();
+    SfxFilterMatcher aMatcher(u"com.sun.star.sheet.SpreadsheetDocument"_ustr);
+    std::shared_ptr<const SfxFilter> pFilter;
+    aMatcher.DetectFilter(*pMedium, pFilter);
+    CPPUNIT_ASSERT_EQUAL(u"Calc MS Excel 2007 XML"_ustr, 
pFilter->GetFilterName());
+
     save(TestFilter::XLSX);
 
     xmlDocUniquePtr pDoc = parseExport(u"xl/worksheets/sheet1.xml"_ustr);
@@ -154,6 +161,11 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, 
testMatrixMultiplicationXLSX)
 
     // make sure that the CellFormulaType is array.
     CPPUNIT_ASSERT_EQUAL(u"array"_ustr, CellFormulaType);
+
+    // tdf#165180: should be recognizable as a 2007 XLSX format when saved
+    xmlDocUniquePtr pWorkbook = parseExport(u"xl/workbook.xml"_ustr);
+    // lowestEdited = 4 needs to be round-tripped in order to detect as a 2007 
ECMA_376_1ST_EDITION
+    assertXPath(pWorkbook, "/x:workbook/x:fileVersion", "lowestEdited", u"4");
 }
 
 CPPUNIT_TEST_FIXTURE(ScExportTest2, testRefStringXLSX)
@@ -1431,7 +1443,7 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf123645XLSX)
 CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf125173XLSX)
 {
     createScDoc("ods/text_box_hyperlink.ods");
-    save(TestFilter::XLSX);
+    saveAndReload(TestFilter::XLSX);
 
     xmlDocUniquePtr pDoc = parseExport(u"xl/drawings/drawing1.xml"_ustr);
     CPPUNIT_ASSERT(pDoc);
@@ -1443,6 +1455,13 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf125173XLSX)
                 u"http://www.google.com/";);
     assertXPath(pXmlRels, "/rels:Relationships/rels:Relationship[@Id='rId1']", 
"TargetMode",
                 u"External");
+
+    // tdf#137883: should be recognized as a 2010+ XLSX format when reloaded 
(and resaved...)
+    SfxMedium* pMedium = getScDocShell()->GetMedium();
+    SfxFilterMatcher aMatcher(u"com.sun.star.sheet.SpreadsheetDocument"_ustr);
+    std::shared_ptr<const SfxFilter> pFilter;
+    aMatcher.DetectFilter(*pMedium, pFilter);
+    CPPUNIT_ASSERT_EQUAL(u"Calc Office Open XML"_ustr, 
pFilter->GetFilterName());
 }
 
 CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf79972XLSX)
diff --git a/sc/source/filter/excel/excdoc.cxx 
b/sc/source/filter/excel/excdoc.cxx
index 0d0c6655aaab..fc794b00ab60 100644
--- a/sc/source/filter/excel/excdoc.cxx
+++ b/sc/source/filter/excel/excdoc.cxx
@@ -852,13 +852,17 @@ void ExcDocument::WriteXml( XclExpXmlStream& rStrm )
             FSNS(XML_xmlns, XML_xr6), rStrm.getNamespaceURL(OOX_NS(xr6)),
             FSNS(XML_xmlns, XML_xr10), rStrm.getNamespaceURL(OOX_NS(xr10)),
             FSNS(XML_xmlns, XML_xr2), rStrm.getNamespaceURL(OOX_NS(xr2)) );
-    rWorkbook->singleElement( XML_fileVersion,
-            XML_appName, "Calc"
+
+    rtl::Reference<sax_fastparser::FastAttributeList> pAttrListFileVersion
+        = sax_fastparser::FastSerializerHelper::createAttrList();
+    pAttrListFileVersion->add(XML_appName, "Calc");
+    std::optional<sal_Int16> oLow = 
GetExtDocOptions().GetDocSettings().moLowestEdited;
+    if (oLow.has_value())
+        pAttrListFileVersion->add(XML_lowestEdited, OString::number(*oLow));
             // OOXTODO: XML_codeName
             // OOXTODO: XML_lastEdited
-            // OOXTODO: XML_lowestEdited
             // OOXTODO: XML_rupBuild
-    );
+    rWorkbook->singleElement(XML_fileVersion, pAttrListFileVersion);
 
     if (bHasPasswordHash)
         rWorkbook->singleElement(XML_fileSharing,
diff --git a/sc/source/filter/inc/workbooksettings.hxx 
b/sc/source/filter/inc/workbooksettings.hxx
index 1078b3e6c096..729daa754664 100644
--- a/sc/source/filter/inc/workbooksettings.hxx
+++ b/sc/source/filter/inc/workbooksettings.hxx
@@ -19,6 +19,7 @@
 
 #pragma once
 
+#include <scextopt.hxx>
 #include "workbookhelper.hxx"
 
 namespace oox { class AttributeList; }
@@ -81,9 +82,12 @@ class WorkbookSettings : public WorkbookHelper
 {
 public:
     explicit            WorkbookSettings( const WorkbookHelper& rHelper );
+    ~WorkbookSettings();
 
     /** Imports the fileSharing element containing write protection settings. 
*/
     void                importFileSharing( const AttributeList& rAttribs );
+    /** Imports the fileVersion element containing last-edited-by settings. */
+    void                importFileVersion( const AttributeList& rAttribs );
     /** Imports the workbookPr element containing global workbook settings. */
     void                importWorkbookPr( const AttributeList& rAttribs );
     /** Imports the calcPr element containing workbook calculation settings. */
@@ -112,6 +116,7 @@ private:
     FileSharingModel    maFileSharing;
     WorkbookSettingsModel maBookSettings;
     CalcSettingsModel   maCalcSettings;
+    ScExtDocOptions maExtDocOptions;
 };
 
 } // namespace oox::xls
diff --git a/sc/source/filter/oox/workbookfragment.cxx 
b/sc/source/filter/oox/workbookfragment.cxx
index 18eed29be335..14db1335349d 100644
--- a/sc/source/filter/oox/workbookfragment.cxx
+++ b/sc/source/filter/oox/workbookfragment.cxx
@@ -118,6 +118,7 @@ ContextHandlerRef WorkbookFragment::onCreateContext( 
sal_Int32 nElement, const A
                 case XLS_TOKEN( pivotCaches ):          return this;
 
                 case XLS_TOKEN( fileSharing ):          
getWorkbookSettings().importFileSharing( rAttribs );    break;
+                case XLS_TOKEN( fileVersion ):          
getWorkbookSettings().importFileVersion( rAttribs );    break;
                 case XLS_TOKEN( workbookPr ):           
getWorkbookSettings().importWorkbookPr( rAttribs );     break;
                 case XLS_TOKEN( calcPr ):               
getWorkbookSettings().importCalcPr( rAttribs );         break;
                 case XLS_TOKEN( oleSize ):              
getViewSettings().importOleSize( rAttribs );            break;
diff --git a/sc/source/filter/oox/workbooksettings.cxx 
b/sc/source/filter/oox/workbooksettings.cxx
index 0914934a344a..1bea1995d81f 100644
--- a/sc/source/filter/oox/workbooksettings.cxx
+++ b/sc/source/filter/oox/workbooksettings.cxx
@@ -38,7 +38,9 @@
 #include <oox/token/tokens.hxx>
 #include <unitconverter.hxx>
 #include <biffhelper.hxx>
+#include <document.hxx>
 #include <docuno.hxx>
+#include <scextopt.hxx>
 
 namespace oox::xls {
 
@@ -107,6 +109,13 @@ CalcSettingsModel::CalcSettingsModel() :
 WorkbookSettings::WorkbookSettings( const WorkbookHelper& rHelper ) :
     WorkbookHelper( rHelper )
 {
+    if (getScDocument().GetExtDocOptions())
+        maExtDocOptions = *getScDocument().GetExtDocOptions();
+}
+
+WorkbookSettings::~WorkbookSettings()
+{
+    
getScDocument().SetExtDocOptions(std::make_unique<ScExtDocOptions>(maExtDocOptions));
 }
 
 void WorkbookSettings::importFileSharing( const AttributeList& rAttribs )
@@ -120,6 +129,13 @@ void WorkbookSettings::importFileSharing( const 
AttributeList& rAttribs )
     maFileSharing.mbRecommendReadOnly = rAttribs.getBool( 
XML_readOnlyRecommended, false );
 }
 
+void WorkbookSettings::importFileVersion(const AttributeList& rAttribs)
+{
+    const sal_Int16 nLow = rAttribs.getInteger(XML_lowestEdited, -1);
+    if (nLow != -1)
+        maExtDocOptions.GetDocSettings().moLowestEdited = nLow;
+}
+
 void WorkbookSettings::importWorkbookPr( const AttributeList& rAttribs )
 {
     maBookSettings.maCodeName          = rAttribs.getString( XML_codeName, 
OUString() );

Reply via email to