oox/source/core/xmlfilterbase.cxx | 3 - sw/qa/extras/ooxmlexport/data/tdf166511.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport11.cxx | 11 ++++- sw/qa/extras/ooxmlexport/ooxmlexport22.cxx | 58 +++++++++++++++++++++++++++ sw/source/filter/ww8/docxattributeoutput.cxx | 38 ++++++----------- sw/source/filter/ww8/docxattributeoutput.hxx | 1 sw/source/filter/ww8/docxsdrexport.cxx | 20 ++++----- sw/source/filter/ww8/docxsdrexport.hxx | 8 +-- 8 files changed, 98 insertions(+), 41 deletions(-)
New commits: commit aa3922bb78fed1458417972f23f3b94c3a277687 Author: Aron Budea <aron.bu...@collabora.com> AuthorDate: Mon May 12 14:54:11 2025 +0930 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Tue May 13 10:59:43 2025 +0200 tdf#166511 sw: unify export of DrawingML object IDs Both docPr and cNvPr have IDs referring to DrawingML objects, and are supposed to be unique within the document. These IDs were handled in different parts of the code during export. 7460e4f4a7b15cc7984adf65bc17e3d580413224 added a workaround, likely to keep them unique, but together with 4b0fa253a4540f5461397815d290586f9ddabe61 this caused a clash sometimes, and the result couldn't be opened in Word afterwards. Use the same counter for both IDs. It needs to start from 1, as 0 ID makes at least grouped shapes invalid. Change-Id: I5e7b9855bc88a61841dfcd3d41599a4ce444eb40 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185191 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> diff --git a/oox/source/core/xmlfilterbase.cxx b/oox/source/core/xmlfilterbase.cxx index daf4ce7cd281..d61cb679e05f 100644 --- a/oox/source/core/xmlfilterbase.cxx +++ b/oox/source/core/xmlfilterbase.cxx @@ -202,7 +202,8 @@ XmlFilterBase::XmlFilterBase( const Reference< XComponentContext >& rxContext ) FilterBase( rxContext ), mxImpl( new XmlFilterBaseImpl ), mnRelId( 1 ), - mnMaxDocId( 0 ), + // mnMaxDocId has to start from 1, Word doesn't accept id 0 for eg. grouped shapes + mnMaxDocId( 1 ), mbMSO2007(false), mbMSO(false), mbMissingExtDrawing(false) diff --git a/sw/qa/extras/ooxmlexport/data/tdf166511.docx b/sw/qa/extras/ooxmlexport/data/tdf166511.docx new file mode 100644 index 000000000000..f718e75b4903 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf166511.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx index f3e3676f2c07..e2f9e8efaa20 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx @@ -852,8 +852,9 @@ DECLARE_OOXMLEXPORT_TEST(testTdf149996, "lorem_hyperlink.fodt") // because the exported file was corrupted. } -DECLARE_OOXMLEXPORT_TEST(testGroupedShapeLink, "grouped_link.docx") +CPPUNIT_TEST_FIXTURE(Test, testGroupedShapeLink) { + loadAndSave("grouped_link.docx"); // tdf#145147 Hyperlink in grouped shape not imported // tdf#154469 Hyperlink in grouped shape not exported uno::Reference<drawing::XShapes> xGroupShape(getShape(1), uno::UNO_QUERY); @@ -861,6 +862,14 @@ DECLARE_OOXMLEXPORT_TEST(testGroupedShapeLink, "grouped_link.docx") getProperty<OUString>(xGroupShape->getByIndex(0), u"Hyperlink"_ustr)); CPPUNIT_ASSERT_EQUAL(u"https://www.documentfoundation.org"_ustr, getProperty<OUString>(xGroupShape->getByIndex(1), u"Hyperlink"_ustr)); + + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + // The id of the grouped shape's docPr must be > 0, otherwise Word fails to open the exported docx + sal_Int32 aId = getXPath(pXmlDoc, "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/wp:anchor/wp:docPr", + "id").toInt32(); + CPPUNIT_ASSERT(aId > 0); } DECLARE_OOXMLEXPORT_TEST(testTdf147810, "tdf147810.odt") diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx index a798e6dcb0d1..2d77c7155ed5 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx @@ -20,6 +20,8 @@ #include <unotxdoc.hxx> #include <docsh.hxx> +#include <set> + namespace { class Test : public SwModelTestBase @@ -223,6 +225,62 @@ DECLARE_OOXMLEXPORT_TEST(testTdf139418, "tdf139418.docx") CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nPorLen3); } +CPPUNIT_TEST_FIXTURE(Test, testTdf166511) +{ + loadAndSave("tdf166511.docx"); + + // The reproducer for tdf#166511 depends on result having footer2 and footer3.xml + // Original bugdoc only has footer1.xml, and the result only started having multiples + // after tdf#136472's fix + // This test uses a "good" roundtripped version of the bugdoc after the fix already + // having the extra footer XMLs, so it doesn't depend on the change coming from tdf#136472 + + // Count total docPr ids and collect unique docPr ids to be counted later from various parts + // of the doc, in the end both counts must be the same, as ids have to be unique + std::set<sal_Int32> aDiffIds; + size_t nIds = 0; + const OUString aFooters[] = { u"word/footer2.xml"_ustr, u"word/footer3.xml"_ustr }; + for (const OUString& footer : aFooters) + { + xmlDocUniquePtr pXmlDoc = parseExport(footer); + CPPUNIT_ASSERT(pXmlDoc); + + const int aDrawingNodes1 = countXPathNodes(pXmlDoc, "/w:ftr/w:p/w:r/mc:AlternateContent"); + for (int i = 1; i <= aDrawingNodes1; i++) + { + OUString aId = getXPath(pXmlDoc, + "/w:ftr/w:p/w:r/mc:AlternateContent[" + OString::number(i) + + "]/mc:Choice/w:drawing/wp:anchor/wp:docPr", + "id"); + aDiffIds.insert(aId.toInt32()); + nIds++; + const OString aFallbackXPath + = "/w:ftr/w:p/w:r/mc:AlternateContent[" + OString::number(i) + + "]/mc:Fallback/w:pict/v:rect/v:textbox/w:txbxContent/w:p/w:r/" + "w:drawing/wp:inline/wp:docPr"; + if (countXPathNodes(pXmlDoc, aFallbackXPath) > 0) + { + aId = getXPath(pXmlDoc, aFallbackXPath, "id"); + aDiffIds.insert(aId.toInt32()); + nIds++; + } + } + const int aDrawingNodes2 = countXPathNodes(pXmlDoc, "/w:ftr/w:p/w:r/w:drawing"); + for (int i = 1; i <= aDrawingNodes2; i++) + { + OUString aId = getXPath( + pXmlDoc, "/w:ftr/w:p/w:r/w:drawing[" + OString::number(i) + "]/wp:anchor/wp:docPr", + "id"); + aDiffIds.insert(aId.toInt32()); + nIds++; + } + } + // Without the accompanying fix in place, this test would have failed with: + // - Expected : 7 + // - Actual : 8 + CPPUNIT_ASSERT_EQUAL(aDiffIds.size(), nIds); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 5131deebdc3d..3498494aac8a 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -1178,7 +1178,7 @@ void DocxAttributeOutput::EndParagraph( const ww8::WW8TableNodeInfoInner::Pointe */ { DocxTableExportContext aDMLTableExportContext(*this); - m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++); + m_rExport.SdrExporter().writeDMLTextFrame(&aFrame); } m_pSerializer->endElementNS(XML_mc, XML_Choice); SetAlternateContentChoiceOpen( false ); @@ -3775,9 +3775,9 @@ void DocxAttributeOutput::WritePostponedGraphic() void DocxAttributeOutput::WritePostponedDiagram() { - for( const auto & rPostponedDiagram : *m_oPostponedDiagrams ) + for(const auto & rPostponedDiagram : *m_oPostponedDiagrams) m_rExport.SdrExporter().writeDiagram(rPostponedDiagram.object, - *rPostponedDiagram.frame, m_anchorId++); + *rPostponedDiagram.frame); m_oPostponedDiagrams.reset(); } @@ -5318,11 +5318,11 @@ uno::Reference<css::text::XTextFrame> DocxAttributeOutput::GetUnoTextFrame( } static rtl::Reference<::sax_fastparser::FastAttributeList> CreateDocPrAttrList( - DocxExport & rExport, int const nAnchorId, std::u16string_view const& rName, + DocxExport & rExport, std::u16string_view const& rName, std::u16string_view const& rTitle, std::u16string_view const& rDescription) { rtl::Reference<::sax_fastparser::FastAttributeList> const pAttrs(FastSerializerHelper::createAttrList()); - pAttrs->add(XML_id, OString::number(nAnchorId)); + pAttrs->add(XML_id, OString::number(rExport.GetFilter().GetUniqueId())); pAttrs->add(XML_name, rName); if (rExport.GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION) { @@ -5448,7 +5448,7 @@ void DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size OUString const descr(pGrfNode ? pGrfNode->GetDescription() : pOLEFrameFormat->GetObjDescription()); OUString const title(pGrfNode ? pGrfNode->GetTitle() : pOLEFrameFormat->GetObjTitle()); auto const docPrattrList(CreateDocPrAttrList( - GetExport(), m_anchorId++, pFrameFormat->GetName(), title, descr)); + GetExport(), pFrameFormat->GetName(), title, descr)); m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrattrList ); OUString sURL, sRelId; @@ -5659,7 +5659,7 @@ void DocxAttributeOutput::WritePostponedChart() docPr Id should be unique, ensuring the same here. */ auto const docPrattrList(CreateDocPrAttrList( - GetExport(), m_anchorId++, sName, title, descr)); + GetExport(), sName, title, descr)); m_pSerializer->singleElementNS(XML_wp, XML_docPr, docPrattrList); m_pSerializer->singleElementNS(XML_wp, XML_cNvGraphicFramePr); @@ -6347,12 +6347,10 @@ void DocxAttributeOutput::WritePostponedCustomShape() for( const auto & rPostponedDrawing : *m_oPostponedCustomShape) { - m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1); if ( IsAlternateContentChoiceOpen() ) - m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++); + m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame); else - m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++); - m_anchorId = m_rExport.GetFilter().GetMaxDocId(); + m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame); } m_oPostponedCustomShape.reset(); } @@ -6371,12 +6369,10 @@ void DocxAttributeOutput::WritePostponedDMLDrawing() for( const auto & rPostponedDrawing : *pPostponedDMLDrawings ) { // Avoid w:drawing within another w:drawing. - m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1); if ( IsAlternateContentChoiceOpen() && !( m_rExport.SdrExporter().IsDrawingOpen()) ) - m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++); + m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame); else - m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++); - m_anchorId = m_rExport.GetFilter().GetMaxDocId(); + m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame); } m_oPostponedOLEs = std::move(pPostponedOLEs); @@ -6420,7 +6416,7 @@ void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame) if ( !m_oPostponedDiagrams ) { m_bPostponedProcessingFly = false ; - m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++); + m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat()); } else // we are writing out attributes, but w:drawing should not be inside w:rPr, { // so write it out later @@ -6432,19 +6428,16 @@ void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame) { if (!m_oPostponedDMLDrawings) { - m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1); if ( IsAlternateContentChoiceOpen() ) { // Do not write w:drawing inside w:drawing. Instead Postpone the Inner Drawing. if( m_rExport.SdrExporter().IsDrawingOpen() ) m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat()))); else - m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat(), m_anchorId++); + m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat()); } else - m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++); - m_anchorId = m_rExport.GetFilter().GetMaxDocId(); - + m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat()); m_bPostponedProcessingFly = false ; } // IsAlternateContentChoiceOpen(): check is to ensure that only one object is getting added. Without this check, plus one object gets added @@ -6740,7 +6733,7 @@ void DocxAttributeOutput::WriteTextBox(uno::Reference<drawing::XShape> xShape) if (pAnchor) //pAnchor can be null, so that's why not assert here. { ww8::Frame aFrame(*pTextBox, *pAnchor); - m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++, /*bTextBoxOnly=*/true); + m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, /*bTextBoxOnly=*/true); if (bFlyAtPage) { delete pAnchor; @@ -10456,7 +10449,6 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr m_nChartCount(0), m_PendingPlaceholder( nullptr ), m_postitFieldsMaxId( 0 ), - m_anchorId( 1 ), m_nextFontId( 1 ), m_bIgnoreNextFill(false), m_pTableStyleExport(std::make_shared<DocxTableStyleExport>(rExport.m_rDoc, pSerializer)), diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index 3bd1544003a1..4cb81b0c71e4 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -1051,7 +1051,6 @@ private: std::vector<std::pair<const SwPostItField*, PostItDOCXData>> m_postitFields; /// Number of postit fields which already have a commentReference written. unsigned int m_postitFieldsMaxId; - int m_anchorId; int m_nextFontId; struct EmbeddedFontRef { diff --git a/sw/source/filter/ww8/docxsdrexport.cxx b/sw/source/filter/ww8/docxsdrexport.cxx index de0e3769a561..1936504bad32 100644 --- a/sw/source/filter/ww8/docxsdrexport.cxx +++ b/sw/source/filter/ww8/docxsdrexport.cxx @@ -1407,8 +1407,7 @@ void AddExtLst(sax_fastparser::FSHelperPtr const& pFS, DocxExport const& rExport } } -void DocxSdrExport::writeDMLDrawing(const SdrObject* pSdrObject, const SwFrameFormat* pFrameFormat, - int nAnchorId) +void DocxSdrExport::writeDMLDrawing(const SdrObject* pSdrObject, const SwFrameFormat* pFrameFormat) { uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pSdrObject)->getUnoShape()); if (!Impl::isSupportedDMLShape(xShape, pSdrObject)) @@ -1423,7 +1422,7 @@ void DocxSdrExport::writeDMLDrawing(const SdrObject* pSdrObject, const SwFrameFo rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); - pDocPrAttrList->add(XML_id, OString::number(nAnchorId)); + pDocPrAttrList->add(XML_id, OString::number(m_pImpl->getExport().GetFilter().GetUniqueId())); pDocPrAttrList->add(XML_name, pSdrObject->GetName()); if (!pSdrObject->GetTitle().isEmpty()) pDocPrAttrList->add(XML_title, pSdrObject->GetTitle()); @@ -1603,7 +1602,7 @@ bool DocxSdrExport::Impl::isSupportedDMLShape(const uno::Reference<drawing::XSha } void DocxSdrExport::writeDMLAndVMLDrawing(const SdrObject* sdrObj, - const SwFrameFormat& rFrameFormat, int nAnchorId) + const SwFrameFormat& rFrameFormat) { bool bDMLAndVMLDrawingOpen = m_pImpl->getDMLAndVMLDrawingOpen(); m_pImpl->setDMLAndVMLDrawingOpen(true); @@ -1626,7 +1625,7 @@ void DocxSdrExport::writeDMLAndVMLDrawing(const SdrObject* sdrObj, auto pObjGroup = dynamic_cast<const SdrObjGroup*>(sdrObj); m_pImpl->getSerializer()->startElementNS(XML_mc, XML_Choice, XML_Requires, (pObjGroup ? "wpg" : "wps")); - writeDMLDrawing(sdrObj, &rFrameFormat, nAnchorId); + writeDMLDrawing(sdrObj, &rFrameFormat); m_pImpl->getSerializer()->endElementNS(XML_mc, XML_Choice); m_pImpl->getSerializer()->startElementNS(XML_mc, XML_Fallback); @@ -1703,8 +1702,7 @@ void DocxSdrExport::writeDMLEffectLst(const SwFrameFormat& rFrameFormat) m_pImpl->getSerializer()->endElementNS(XML_a, XML_effectLst); } -void DocxSdrExport::writeDiagram(const SdrObject* sdrObject, const SwFrameFormat& rFrameFormat, - int nDiagramId) +void DocxSdrExport::writeDiagram(const SdrObject* sdrObject, const SwFrameFormat& rFrameFormat) { uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(sdrObject)->getUnoShape(), uno::UNO_QUERY); @@ -1714,7 +1712,7 @@ void DocxSdrExport::writeDiagram(const SdrObject* sdrObject, const SwFrameFormat startDMLAnchorInline(&rFrameFormat, aSize); m_pImpl->getDrawingML()->SetFS(m_pImpl->getSerializer()); - m_pImpl->getDrawingML()->WriteDiagram(xShape, nDiagramId); + m_pImpl->getDrawingML()->WriteDiagram(xShape, m_pImpl->getExport().GetFilter().GetUniqueId()); endDMLAnchorInline(&rFrameFormat); } @@ -1784,8 +1782,7 @@ void DocxSdrExport::writeBoxItemLine(const SvxBoxItem& rBox) pFS->endElementNS(XML_a, XML_ln); } -void DocxSdrExport::writeDMLTextFrame(ww8::Frame const* pParentFrame, int nAnchorId, - bool bTextBoxOnly) +void DocxSdrExport::writeDMLTextFrame(ww8::Frame const* pParentFrame, bool bTextBoxOnly) { bool bDMLAndVMLDrawingOpen = m_pImpl->getDMLAndVMLDrawingOpen(); m_pImpl->setDMLAndVMLDrawingOpen(IsAnchorTypeInsideParagraph(pParentFrame)); @@ -1828,7 +1825,8 @@ void DocxSdrExport::writeDMLTextFrame(ww8::Frame const* pParentFrame, int nAncho rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); - pDocPrAttrList->add(XML_id, OString::number(nAnchorId)); + pDocPrAttrList->add(XML_id, + OString::number(m_pImpl->getExport().GetFilter().GetUniqueId())); pDocPrAttrList->add(XML_name, rFrameFormat.GetName()); pFS->startElementNS(XML_wp, XML_docPr, pDocPrAttrList); diff --git a/sw/source/filter/ww8/docxsdrexport.hxx b/sw/source/filter/ww8/docxsdrexport.hxx index 5ad93d51ad8e..eaeac875f219 100644 --- a/sw/source/filter/ww8/docxsdrexport.hxx +++ b/sw/source/filter/ww8/docxsdrexport.hxx @@ -82,15 +82,15 @@ public: /// Writes a drawing as VML data. void writeVMLDrawing(const SdrObject* sdrObj, const SwFrameFormat& rFrameFormat); /// Writes a drawing as DML. - void writeDMLDrawing(const SdrObject* pSdrObject, const SwFrameFormat* pFrameFormat, int nAnchorId); + void writeDMLDrawing(const SdrObject* pSdrObject, const SwFrameFormat* pFrameFormat); /// Writes shape in both DML and VML format. - void writeDMLAndVMLDrawing(const SdrObject* sdrObj, const SwFrameFormat& rFrameFormat, int nAnchorId); + void writeDMLAndVMLDrawing(const SdrObject* sdrObj, const SwFrameFormat& rFrameFormat); /// Write <a:effectLst>, the effect list. void writeDMLEffectLst(const SwFrameFormat& rFrameFormat); /// Writes a diagram (smartart). - void writeDiagram(const SdrObject* sdrObject, const SwFrameFormat& rFrameFormat, int nDiagramId); + void writeDiagram(const SdrObject* sdrObject, const SwFrameFormat& rFrameFormat); /// Writes text frame in DML format. - void writeDMLTextFrame(ww8::Frame const* pParentFrame, int nAnchorId, bool bTextBoxOnly = false); + void writeDMLTextFrame(ww8::Frame const* pParentFrame, bool bTextBoxOnly = false); /// Writes text frame in VML format. void writeVMLTextFrame(ww8::Frame const* pParentFrame, bool bTextBoxOnly = false); /// Is this a standalone TextFrame, or used as a TextBox of a shape?