sc/source/core/tool/dbdata.cxx      |   20 ++++++++++++++++++++
 sc/source/filter/excel/xedbdata.cxx |   14 ++++++++++++++
 sc/source/filter/excel/xetable.cxx  |   31 +++++++++++++++++++++++++++----
 sc/source/filter/inc/xedbdata.hxx   |    1 +
 4 files changed, 62 insertions(+), 4 deletions(-)

New commits:
commit b16dd6621e1eeeb15272f2bfb64e2e73c8ffa4c5
Author:     Szymon Kłos <[email protected]>
AuthorDate: Thu Feb 19 12:57:19 2026 +0000
Commit:     Tomaž Vajngerl <[email protected]>
CommitDate: Fri Feb 20 11:08:39 2026 +0100

    xlsx export: numeric table headers use shared strings
    
    When a structured table header cell contains a number (e.g. "1" or
    "2025"), the xlsx export was writing it as a numeric cell without the
    t="s" type attribute. This caused a mismatch between the table column
    name in table1.xml and the cell value in the sheet XML.
    
    - RefreshTableColumnNames() now converts numeric header cells to their
      formatted string representation, so table.xml gets the actual value
      (e.g. "1") instead of auto-generating "Column1".
    
    - In XclExpCellTable, numeric cells in table header rows are now
      exported as XclExpLabelCell (shared string with t="s") instead of
      XclExpNumberCell, using the already-initialized XclExpTablesManager
      to detect header positions.
    
    - Removed the explicit t="n" type attribute from XclExpNumberCell and
      XclExpRkCell, as "n" is the default cell type
    
    This prevents showing an error when file is opened in MSO.
    
    Change-Id: I24d68cc3c802fede8cfc029b4a829ac9748da227
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199791
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Tomaž Vajngerl <[email protected]>

diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx
index 6d4ee3e2e518..f1acb9c241fe 100644
--- a/sc/source/core/tool/dbdata.cxx
+++ b/sc/source/core/tool/dbdata.cxx
@@ -1509,6 +1509,26 @@ void ScDBData::RefreshTableColumnNames( ScDocument* pDoc 
)
                 }
                 nLastColFilled = nCol;
             }
+            else if (pCell->getType() == CELLTYPE_VALUE)
+            {
+                // Numeric header: convert to string so the column name
+                // in table.xml matches the shared string exported for
+                // this cell.
+                double fValue = pCell->getDouble();
+                sal_uInt32 nFormat = pDoc->GetNumberFormat(nCol, nRow, nTable);
+                OUString aStr;
+                const Color* pColor = nullptr;
+                pDoc->GetFormatTable()->GetOutputString(fValue, nFormat, aStr, 
&pColor);
+                if (aStr.isEmpty())
+                    bHaveEmpty = true;
+                else
+                {
+                    SetTableColumnName( aNewNames, nCol-nStartCol, aStr, 0);
+                    if (nLastColFilled < nCol-1)
+                        bHaveEmpty = true;
+                }
+                nLastColFilled = nCol;
+            }
             else
                 bHaveEmpty = true;
         }
diff --git a/sc/source/filter/excel/xedbdata.cxx 
b/sc/source/filter/excel/xedbdata.cxx
index 522b5ea19747..9bc3e7f2bdef 100644
--- a/sc/source/filter/excel/xedbdata.cxx
+++ b/sc/source/filter/excel/xedbdata.cxx
@@ -176,6 +176,20 @@ void XclExpTables::AppendTable( const ScDBData* pData, 
sal_Int32 nTableId )
     maTables.emplace_back( pData, nTableId);
 }
 
+void XclExpTables::GetHeaderRows( ::std::vector<ScRange>& rRanges ) const
+{
+    for (const auto& rEntry : maTables)
+    {
+        if (rEntry.mpData->HasHeader())
+        {
+            ScRange aArea;
+            rEntry.mpData->GetArea(aArea);
+            aArea.aEnd.SetRow(aArea.aStart.Row());
+            rRanges.push_back(aArea);
+        }
+    }
+}
+
 void XclExpTables::SaveTableXml( XclExpXmlStream& rStrm, const Entry& rEntry )
 {
     const ScDBData& rData = *rEntry.mpData;
diff --git a/sc/source/filter/excel/xetable.cxx 
b/sc/source/filter/excel/xetable.cxx
index 5e8bc8a3ffda..eeccbe215c22 100644
--- a/sc/source/filter/excel/xetable.cxx
+++ b/sc/source/filter/excel/xetable.cxx
@@ -34,6 +34,7 @@
 #include <formulacell.hxx>
 #include <patattr.hxx>
 #include <attrib.hxx>
+#include <xedbdata.hxx>
 #include <xehelper.hxx>
 #include <xecontent.hxx>
 #include <xeescher.hxx>
@@ -646,8 +647,7 @@ void XclExpNumberCell::SaveXml( XclExpXmlStream& rStrm )
     sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
     rWorksheet->startElement( XML_c,
             XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), 
GetXclPos()).getStr(),
-            XML_s, lcl_GetStyleId(rStrm, *this),
-            XML_t, "n"
+            XML_s, lcl_GetStyleId(rStrm, *this)
             // OOXTODO: XML_cm, XML_vm, XML_ph
     );
     rWorksheet->startElement(XML_v);
@@ -1427,8 +1427,7 @@ void XclExpRkCell::WriteXmlContents( XclExpXmlStream& 
rStrm, const XclAddress& r
     sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
     rWorksheet->startElement( XML_c,
             XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), 
rAddress).getStr(),
-            XML_s, lcl_GetStyleId(rStrm, nXFId),
-            XML_t, "n"
+            XML_s, lcl_GetStyleId(rStrm, nXFId)
             // OOXTODO: XML_cm, XML_vm, XML_ph
     );
     rWorksheet->startElement( XML_v );
@@ -2626,6 +2625,16 @@ XclExpCellTable::XclExpCellTable( const XclExpRoot& 
rRoot ) :
     SCROW nLastIterScRow = ulimit_cast< SCROW >( nLastUsedScRow, nMaxScRow );
     ScUsedAreaIterator aIt( rDoc, nScTab, 0, 0, nLastIterScCol, nLastIterScRow 
);
 
+    // Collect table header ranges for the current sheet so that numeric
+    // cells in table headers are exported as shared strings (not numbers).
+    std::vector<ScRange> aTableHeaderRanges;
+    if (GetOutput() == EXC_OUTPUT_XML_2007)
+    {
+        rtl::Reference<XclExpTables> xTables = 
GetTablesManager().GetTablesBySheet(nScTab);
+        if (xTables)
+            xTables->GetHeaderRows(aTableHeaderRanges);
+    }
+
     // activate the correct segment and sub segment at the progress bar
     GetProgressBar().ActivateCreateRowsSegment();
 
@@ -2668,6 +2677,20 @@ XclExpCellTable::XclExpCellTable( const XclExpRoot& 
rRoot ) :
             {
                 double fValue = rScCell.getDouble();
 
+                // If the cell is in a table header row, force export as 
shared string
+                if (std::any_of(aTableHeaderRanges.begin(), 
aTableHeaderRanges.end(),
+                        [&aScPos](const ScRange& rRange) { return 
rRange.Contains(aScPos); }))
+                {
+                    OUString aStr;
+                    const Color* pColor = nullptr;
+                    sal_uInt32 nScNumFmt = pPattern
+                        ? pPattern->GetItem(ATTR_VALUE_FORMAT).GetValue() : 0;
+                    rFormatter.GetOutputString(fValue, nScNumFmt, aStr, 
&pColor);
+                    xCell = new XclExpLabelCell(
+                        GetRoot(), aXclPos, pPattern, nMergeBaseXFId, aStr);
+                    break;
+                }
+
                 if (pPattern)
                 {
                     OUString aUrl = 
pPattern->GetItem(ATTR_HYPERLINK).GetValue();
diff --git a/sc/source/filter/inc/xedbdata.hxx 
b/sc/source/filter/inc/xedbdata.hxx
index a7fddfe04eaa..24df0fffcb67 100644
--- a/sc/source/filter/inc/xedbdata.hxx
+++ b/sc/source/filter/inc/xedbdata.hxx
@@ -31,6 +31,7 @@ public:
     virtual             ~XclExpTables() override;
 
     void                AppendTable( const ScDBData* pData, sal_Int32 nTableId 
);
+    void                GetHeaderRows( ::std::vector<ScRange>& rRanges ) const;
 
 protected:
     struct Entry

Reply via email to