include/oox/export/chartexport.hxx                              |    6 
 oox/source/export/chartexport.cxx                               |   48 ++++---
 sw/qa/extras/ooxmlexport/data/tdf143269_missingEmbeddings.odt   |binary
 sw/qa/extras/ooxmlexport/data/tdf143269_zeroSizeEmbeddings.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport10.cxx                      |   22 +++
 sw/source/filter/ww8/docxexport.cxx                             |   63 
++++++++--
 6 files changed, 106 insertions(+), 33 deletions(-)

New commits:
commit 746f59ac958f7d1d04b6f0fcd407de99011a87af
Author:     Justin Luth <[email protected]>
AuthorDate: Wed Mar 4 13:41:44 2026 -0500
Commit:     Justin Luth <[email protected]>
CommitDate: Fri Mar 6 13:48:42 2026 +0100

    tdf#143269 docx export: no LinkToExternalData if invalid embeddings
    
    MS Word reports a document as corrupt
    if there is a relationship to a non-existing embeddings/file.
    
    It also complains if the referenced embeddings file is size 0.
    
    make CppunitTest_sw_ooxmlexport10 \
        CPPUNIT_TEST_NAME=testTdf143269_missingEmbeddings
    
    make CppunitTest_sw_ooxmlexport10 \
        CPPUNIT_TEST_NAME=testTdf143269_zeroSizeEmbeddings
    
    Change-Id: I40ad7f9d470db88c56c613a1a8da9f3d86ac9e81
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201058
    Tested-by: Jenkins
    Reviewed-by: Justin Luth <[email protected]>

diff --git a/include/oox/export/chartexport.hxx 
b/include/oox/export/chartexport.hxx
index 6fd71cf87da1..b049a92aadba 100644
--- a/include/oox/export/chartexport.hxx
+++ b/include/oox/export/chartexport.hxx
@@ -183,9 +183,7 @@ private:
                           bool bIsChartex);
     void exportData_chartex( const css::uno::Reference<
                               css::chart::XChartDocument >& rChartDoc);
-    void exportExternalData( const css::uno::Reference<
-                              css::chart::XChartDocument >& rChartDoc,
-                              bool bIsChartex);
+    void exportExternalData(bool bIsChartex);
     void exportLegend( const css::uno::Reference<
                           css::chart::XChartDocument >& rChartDoc,
                           bool bIsChartex);
@@ -332,6 +330,8 @@ public:
     void InitRangeSegmentationProperties(
         const css::uno::Reference<
             css::chart2::XChartDocument > & xChartDoc );
+
+    OOX_DLLPUBLIC OUString GetExternalDataPath() const;
 };
 
 }
diff --git a/oox/source/export/chartexport.cxx 
b/oox/source/export/chartexport.cxx
index bf9486950c3d..584860981c3a 100644
--- a/oox/source/export/chartexport.cxx
+++ b/oox/source/export/chartexport.cxx
@@ -85,6 +85,8 @@
 #include <com/sun/star/drawing/FillStyle.hpp>
 #include <com/sun/star/drawing/LineStyle.hpp>
 #include <com/sun/star/awt/XBitmap.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XStreamListener.hpp>
 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
 #include <com/sun/star/lang/XServiceName.hpp>
 
@@ -1464,7 +1466,7 @@ void ChartExport::exportChartSpace( const Reference< 
css::chart::XChartDocument
         // chartData
         pFS->startElement(FSNS(XML_cx, XML_chartData));
 
-        exportExternalData(xChartDoc, true);
+        exportExternalData(true);
         exportData_chartex(xChartDoc);
 
         pFS->endElement(FSNS(XML_cx, XML_chartData));
@@ -1499,7 +1501,7 @@ void ChartExport::exportChartSpace( const Reference< 
css::chart::XChartDocument
     // TODO for chartex
     if (!bIsChartex) {
         //XML_externalData
-        exportExternalData(xChartDoc, false);
+        exportExternalData(false);
     }
 
     // export additional shapes in chart
@@ -1815,8 +1817,31 @@ void ChartExport::exportData_chartex( [[maybe_unused]] 
const Reference< css::cha
     }
 }
 
-void ChartExport::exportExternalData( const Reference< 
css::chart::XChartDocument >& xChartDoc,
-        bool bIsChartex)
+OUString ChartExport::GetExternalDataPath() const
+{
+    OUString sRet;
+
+    const Reference<css::chart::XChartDocument> xChartDoc(getModel(), 
uno::UNO_QUERY);
+    if (!xChartDoc.is())
+        return sRet;
+
+    const Reference<beans::XPropertySet> xDocPropSet(xChartDoc->getDiagram(), 
uno::UNO_QUERY);
+    if (!xDocPropSet.is())
+        return sRet;
+
+    try
+    {
+        Any aAny(xDocPropSet->getPropertyValue(u"ExternalData"_ustr));
+        aAny >>= sRet;
+    }
+    catch(beans::UnknownPropertyException&)
+    {
+    }
+
+    return sRet;
+}
+
+void ChartExport::exportExternalData(bool bIsChartex)
 {
     if (bIsChartex) return; // TODO!!
     // Embedded external data is grab bagged for docx file hence adding export 
part of
@@ -1824,20 +1849,7 @@ void ChartExport::exportExternalData( const Reference< 
css::chart::XChartDocumen
     if(!mbLinkToExternalData || GetDocumentType() != DOCUMENT_DOCX)
         return;
 
-    OUString externalDataPath;
-    Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), 
uno::UNO_QUERY );
-    if( xDocPropSet.is())
-    {
-        try
-        {
-            Any aAny( xDocPropSet->getPropertyValue( u"ExternalData"_ustr ));
-            aAny >>= externalDataPath;
-        }
-        catch( beans::UnknownPropertyException & )
-        {
-            SAL_WARN("oox", "Required property not found in ChartDocument");
-        }
-    }
+    const OUString externalDataPath = GetExternalDataPath();
     if(externalDataPath.isEmpty())
         return;
 
diff --git a/sw/qa/extras/ooxmlexport/data/tdf143269_missingEmbeddings.odt 
b/sw/qa/extras/ooxmlexport/data/tdf143269_missingEmbeddings.odt
new file mode 100644
index 000000000000..34d973d4d1a8
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf143269_missingEmbeddings.odt differ
diff --git a/sw/qa/extras/ooxmlexport/data/tdf143269_zeroSizeEmbeddings.docx 
b/sw/qa/extras/ooxmlexport/data/tdf143269_zeroSizeEmbeddings.docx
new file mode 100644
index 000000000000..cc29d5151a72
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf143269_zeroSizeEmbeddings.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx
index 35ca217fdf1f..773e5948e39f 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx
@@ -752,6 +752,28 @@ DECLARE_OOXMLEXPORT_TEST(testMsoBrightnessContrast, 
"msobrightnesscontrast.docx"
     CPPUNIT_ASSERT_EQUAL(Color( 0xce, 0xce, 0xce ), aColor);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf143269_zeroSizeEmbeddings)
+{
+    // Given a (broken) document that contains a word/embeddings xlsx file 
that is size 0
+    createSwDoc("tdf143269_zeroSizeEmbeddings.docx");
+    save(TestFilter::DOCX);
+
+    // MS Word reports corrupt if embeddings/file referred is zero-sized
+    xmlDocUniquePtr pXmlChart1 = parseExport(u"word/charts/chart1.xml"_ustr);
+    assertXPath(pXmlChart1, "//c:externalData", 0);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf143269_missingEmbeddings)
+{
+    // Given a XLSX->ODT document (I presume) that has lost the 
word/embeddings xlsx file
+    createSwDoc("tdf143269_missingEmbeddings.odt");
+    save(TestFilter::DOCX);
+
+    // MS Word reports corrupt if embeddings/file referred to does not exist
+    xmlDocUniquePtr pXmlChart1 = parseExport(u"word/charts/chart1.xml"_ustr);
+    assertXPath(pXmlChart1, "//c:externalData", 0);
+}
+
 DECLARE_OOXMLEXPORT_TEST(testChartSize, "chart-size.docx")
 {
     // When chart was in a TextFrame, its size was too large.
diff --git a/sw/source/filter/ww8/docxexport.cxx 
b/sw/source/filter/ww8/docxexport.cxx
index 9f88fc5309bc..dc1c54bcf77e 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -115,6 +115,31 @@ using oox::vml::VMLExport;
 
 using sw::mark::MarkBase;
 
+namespace
+{
+
+uno::Sequence<beans::PropertyValue>
+lcl_getEmbeddingsList(const rtl::Reference<SwXTextDocument>& xTextDoc)
+{
+    uno::Sequence<beans::PropertyValue> aRet;
+
+    if 
(!xTextDoc->getPropertySetInfo()->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG))
+        return aRet;
+
+    uno::Sequence<beans::PropertyValue> xPropertyList;
+    xTextDoc->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= 
xPropertyList;
+    auto pProp = std::find_if(
+        std::cbegin(xPropertyList), std::cend(xPropertyList),
+        [](const beans::PropertyValue& rProp) { return rProp.Name == 
"OOXEmbeddings"; });
+
+    if (pProp != std::cend(xPropertyList))
+        pProp->Value >>= aRet;
+
+    return aRet;
+}
+
+} // end anonymous namespace
+
 AttributeOutputBase& DocxExport::AttrOutput() const
 {
     return *m_pAttrOutput;
@@ -424,6 +449,31 @@ OString DocxExport::OutputChart( uno::Reference< 
frame::XModel > const & xModel,
         break;
     }
 
+    // verify that the to-be-embedded-file being linked to is available and 
valid.
+    if (aChartExport.mbLinkToExternalData)
+    {
+        // missing or zero-sized embedded files are reported as corrupt by MS 
Word
+        aChartExport.mbLinkToExternalData = false; // assume something wrong 
until proven valid
+        const OUString rExternalDataPath = aChartExport.GetExternalDataPath();
+        if (!rExternalDataPath.isEmpty())
+        {
+            for (const auto& rEmbedding : lcl_getEmbeddingsList(m_xTextDoc))
+            {
+                if (rEmbedding.Name == rExternalDataPath)
+                {
+                    uno::Reference<io::XInputStream> embeddingsStream;
+                    rEmbedding.Value >>= embeddingsStream;
+                    if (embeddingsStream)
+                    {
+                        uno::Reference<io::XSeekable> 
xSeekable(embeddingsStream, uno::UNO_QUERY);
+                        if (xSeekable && xSeekable->getLength())
+                            aChartExport.mbLinkToExternalData = true;
+                    }
+                }
+            }
+        }
+    }
+
     aChartExport.ExportContent();
     m_aExportedCharts.push_back(xModel);
     if (!bOldModified && xModifiable && xModifiable->isModified())
@@ -1923,18 +1973,7 @@ void DocxExport::WriteEmbeddings()
     if (!pShell)
         return;
 
-    uno::Reference< beans::XPropertySetInfo > xPropSetInfo = 
m_xTextDoc->getPropertySetInfo();
-    OUString aName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG;
-    if ( !xPropSetInfo->hasPropertyByName( aName ) )
-        return;
-
-    uno::Sequence< beans::PropertyValue > embeddingsList;
-    uno::Sequence< beans::PropertyValue > propList;
-    m_xTextDoc->getPropertyValue( aName ) >>= propList;
-    auto pProp = std::find_if(std::cbegin(propList), std::cend(propList),
-        [](const beans::PropertyValue& rProp) { return rProp.Name == 
"OOXEmbeddings"; });
-    if (pProp != std::cend(propList))
-        pProp->Value >>= embeddingsList;
+    uno::Sequence<beans::PropertyValue> embeddingsList = 
lcl_getEmbeddingsList(m_xTextDoc);
     for (const auto& rEmbedding : embeddingsList)
     {
         OUString embeddingPath = rEmbedding.Name;

Reply via email to