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;
