include/oox/export/drawingml.hxx         |    8 +--
 oox/source/drawingml/diagram/diagram.cxx |   22 ++++++--
 oox/source/drawingml/diagram/diagram.hxx |   10 +++
 oox/source/drawingml/shape.cxx           |   78 ++++++++++++++++++++++---------
 oox/source/export/drawingml.cxx          |   59 ++++++++++++++++++++---
 5 files changed, 137 insertions(+), 40 deletions(-)

New commits:
commit 8a049c8a1482260f0ffe85b6da8c5e8834c20652
Author:     Karthik Godha <[email protected]>
AuthorDate: Tue Dec 23 14:15:00 2025 +0530
Commit:     Michael Stahl <[email protected]>
CommitDate: Thu Jan 8 20:22:40 2026 +0100

    tdf#170094: PPTX->PPTX export SmartArt hyperlinks
    
    SmartArt hyperlinks are not preserved after roundtrip. This leads
    to XML having relationship IDs which are invalid.
    
    Change-Id: I3fa3d6f79b8eb8d086b1061d26c30568e3b5793f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196152
    Reviewed-by: Michael Stahl <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx
index 0cfe1cc34763..6f94bad3f883 100644
--- a/include/oox/export/drawingml.hxx
+++ b/include/oox/export/drawingml.hxx
@@ -545,9 +545,11 @@ public:
     OString WriteWdpPicture( const OUString& rFileId, const 
css::uno::Sequence< sal_Int8 >& rPictureData );
     OOX_DLLPUBLIC void WriteDiagram(const 
css::uno::Reference<css::drawing::XShape>& rXShape,
                                     sal_Int32 nDiagramId, sal_Int32 nShapeId = 
-1);
-    void writeDiagramRels(const 
css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& xRelSeq,
-                          const css::uno::Reference<css::io::XOutputStream>& 
xOutStream,
-                          std::u16string_view sGrabBagProperyName, int 
nDiagramId);
+    void writeDiagramImageRels(const 
css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& xRelSeq,
+                               const 
css::uno::Reference<css::io::XOutputStream>& xOutStream,
+                               std::u16string_view sGrabBagProperyName, int 
nDiagramId);
+    void writeDiagramHlinkRels(const 
css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& xRelSeq,
+                               const 
css::uno::Reference<css::io::XOutputStream>& xOutStream);
     static void WriteFromTo(const css::uno::Reference<css::drawing::XShape>& 
rXShape, const css::awt::Size& aPageSize,
                             const sax_fastparser::FSHelperPtr& pDrawing);
 
diff --git a/oox/source/drawingml/diagram/diagram.cxx 
b/oox/source/drawingml/diagram/diagram.cxx
index 3928e82767ed..e552e1c6e55b 100644
--- a/oox/source/drawingml/diagram/diagram.cxx
+++ b/oox/source/drawingml/diagram/diagram.cxx
@@ -152,7 +152,9 @@ uno::Sequence<beans::PropertyValue> 
Diagram::getDomsAsPropertyValues() const
 {
     sal_Int32 length = maMainDomMap.size();
 
-    if (maDataRelsMap.hasElements())
+    if (getDataImageRelsSeq().hasElements())
+        ++length;
+    if (getDataHlinkRelsSeq().hasElements())
         ++length;
 
     uno::Sequence<beans::PropertyValue> aValue(length);
@@ -164,10 +166,16 @@ uno::Sequence<beans::PropertyValue> 
Diagram::getDomsAsPropertyValues() const
         ++pValue;
     }
 
-    if (maDataRelsMap.hasElements())
+    if (getDataImageRelsSeq().hasElements())
+    {
+        pValue->Name = "OOXDiagramDataImageRels";
+        pValue->Value <<= getDataImageRelsSeq();
+        ++pValue;
+    }
+    if (getDataHlinkRelsSeq().hasElements())
     {
-        pValue->Name = "OOXDiagramDataRels";
-        pValue->Value <<= maDataRelsMap;
+        pValue->Name = "OOXDiagramDataHlinkRels";
+        pValue->Value <<= getDataHlinkRelsSeq();
         ++pValue;
     }
 
@@ -334,8 +342,10 @@ void loadDiagram( ShapePtr const & pShape,
                            pDiagram,
                            xRefDataModel);
 
-            pDiagram->getDataRelsMap() = 
pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter,
-                    xRefDataModel->getFragmentPath(), u"image" );
+            pDiagram->getDataImageRelsSeq() = 
pShape->resolveRelationshipsOfTypeFromOfficeDoc(
+                rFilter, xRefDataModel->getFragmentPath(), u"image");
+            pDiagram->getDataHlinkRelsSeq() = 
pShape->resolveRelationshipsOfTypeFromOfficeDoc(
+                rFilter, xRefDataModel->getFragmentPath(), u"hlink");
 
             // Pass the info to pShape
             for (auto const& extDrawing : pData->getExtDrawings())
diff --git a/oox/source/drawingml/diagram/diagram.hxx 
b/oox/source/drawingml/diagram/diagram.hxx
index 5eb82069b91b..cc5ab12b8024 100644
--- a/oox/source/drawingml/diagram/diagram.hxx
+++ b/oox/source/drawingml/diagram/diagram.hxx
@@ -143,7 +143,12 @@ public:
     DiagramColorMap& getColors() { return maColors; }
     const DiagramColorMap& getColors() const { return maColors; }
     DiagramDomMap & getDomMap() { return maMainDomMap; }
-    css::uno::Sequence< css::uno::Sequence< css::uno::Any > > & 
getDataRelsMap() { return maDataRelsMap; }
+
+    css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& 
getDataImageRelsSeq() { return maImageRelsSeq; }
+    css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& 
getDataHlinkRelsSeq() { return maHlinkRelsSeq; }
+    const css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& 
getDataImageRelsSeq() const { return maImageRelsSeq; }
+    const css::uno::Sequence<css::uno::Sequence<css::uno::Any>>& 
getDataHlinkRelsSeq() const { return maHlinkRelsSeq; }
+
     void addTo( const ShapePtr & pShape, bool bCreate );
 
     css::uno::Sequence<css::beans::PropertyValue> getDomsAsPropertyValues() 
const;
@@ -159,7 +164,8 @@ private:
     DiagramQStyleMap               maStyles;
     DiagramColorMap                maColors;
     DiagramDomMap                  maMainDomMap;
-    css::uno::Sequence< css::uno::Sequence< css::uno::Any > > maDataRelsMap;
+    css::uno::Sequence<css::uno::Sequence<css::uno::Any>> maImageRelsSeq;
+    css::uno::Sequence<css::uno::Sequence<css::uno::Any>> maHlinkRelsSeq;
 };
 
 typedef std::shared_ptr< Diagram > DiagramPtr;
diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx
index c05f7bf8d0e9..55f0eef8afe4 100644
--- a/oox/source/drawingml/shape.cxx
+++ b/oox/source/drawingml/shape.cxx
@@ -2882,36 +2882,72 @@ uno::Sequence< uno::Sequence< uno::Any > >  
Shape::resolveRelationshipsOfTypeFro
     core::RelationsRef xRels = rFilter.importRelations( sFragment );
     if ( xRels )
     {
-        core::RelationsRef xImageRels = 
xRels->getRelationsFromTypeFromOfficeDoc( sType );
-        if ( xImageRels )
+        if (sType == u"image")
         {
-            xRelListTemp.realloc( xImageRels->size() );
-            auto pxRelListTemp = xRelListTemp.getArray();
-            for (auto const& imageRel : *xImageRels)
+            core::RelationsRef xImageRels = 
xRels->getRelationsFromTypeFromOfficeDoc(sType);
+            if (xImageRels)
             {
-                uno::Sequence< uno::Any > diagramRelTuple (3);
-                auto pdiagramRelTuple = diagramRelTuple.getArray();
-                // [0] => RID, [1] => InputStream [2] => extension
-                OUString sRelId = imageRel.second.maId;
+                xRelListTemp.realloc(xImageRels->size());
+                auto pxRelListTemp = xRelListTemp.getArray();
+                for (auto const& imageRel : *xImageRels)
+                {
+                    uno::Sequence<uno::Any> diagramRelTuple(3);
+                    auto pdiagramRelTuple = diagramRelTuple.getArray();
+                    // [0] => RID, [1] => InputStream [2] => extension
+                    OUString sRelId = imageRel.second.maId;
+
+                    pdiagramRelTuple[0] <<= sRelId;
+                    OUString sTarget = 
xImageRels->getFragmentPathFromRelId(sRelId);
+
+                    uno::Reference<io::XInputStream> xImageInputStrm(
+                        rFilter.openInputStream(sTarget), uno::UNO_SET_THROW);
+                    StreamDataSequence dataSeq;
+                    if (rFilter.importBinaryData(dataSeq, sTarget))
+                    {
+                        pdiagramRelTuple[1] <<= dataSeq;
+                    }
 
-                pdiagramRelTuple[0] <<= sRelId;
-                OUString sTarget = xImageRels->getFragmentPathFromRelId( 
sRelId );
+                    pdiagramRelTuple[2] <<= 
sTarget.copy(sTarget.lastIndexOf("."));
 
-                uno::Reference< io::XInputStream > xImageInputStrm( 
rFilter.openInputStream( sTarget ), uno::UNO_SET_THROW );
-                StreamDataSequence dataSeq;
-                if ( rFilter.importBinaryData( dataSeq, sTarget ) )
-                {
-                    pdiagramRelTuple[1] <<= dataSeq;
+                    pxRelListTemp[counter] = std::move(diagramRelTuple);
+                    ++counter;
                 }
+            }
+        }
+        else if (sType == u"hlink")
+        {
+            // Hyperlink can be internal or external
+            core::RelationsRef xSlideRels = 
xRels->getRelationsFromTypeFromOfficeDoc(u"slide");
+            core::RelationsRef xHlinkRels = 
xRels->getRelationsFromTypeFromOfficeDoc(u"hyperlink");
 
-                pdiagramRelTuple[2] <<= sTarget.copy( sTarget.lastIndexOf(".") 
);
+            sal_Int32 totalSize
+                = (xSlideRels ? xSlideRels->size() : 0) + (xHlinkRels ? 
xHlinkRels->size() : 0);
+            xRelListTemp.realloc(totalSize);
+            auto pxRelListTemp = xRelListTemp.getArray();
 
-                pxRelListTemp[counter] = std::move(diagramRelTuple);
-                ++counter;
-            }
-            xRelListTemp.realloc(counter);
+            // Helper to create relation tuple
+            auto addRelation = [&](const auto& rel, const OUString& relType)
+            {
+                uno::Sequence<uno::Any> tuple(3);
+                auto pTuple = tuple.getArray();
+                pTuple[0] <<= rel.second.maId;
+                pTuple[1] <<= rel.second.maTarget;
+                pTuple[2] <<= relType;
+                pxRelListTemp[counter++] = std::move(tuple);
+            };
 
+            if (xSlideRels)
+            {
+                for (auto const& slideRel : *xSlideRels)
+                    addRelation(slideRel, "slide");
+            }
+            if (xHlinkRels)
+            {
+                for (auto const& hlinkRel : *xHlinkRels)
+                    addRelation(hlinkRel, "hyperlink");
+            }
         }
+        xRelListTemp.realloc(counter);
     }
     return xRelListTemp;
 }
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index 15497622cabf..8b7e18d2ec66 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -6723,7 +6723,8 @@ void DrawingML::WriteDiagram(const 
css::uno::Reference<css::drawing::XShape>& rX
     uno::Reference<xml::dom::XDocument> styleDom;
     uno::Reference<xml::dom::XDocument> colorDom;
     uno::Reference<xml::dom::XDocument> drawingDom;
-    uno::Sequence<uno::Sequence<uno::Any>> xDataRelSeq;
+    uno::Sequence<uno::Sequence<uno::Any>> xDataImageRelSeq;
+    uno::Sequence<uno::Sequence<uno::Any>> xDataHlinkRelSeq;
     uno::Sequence<uno::Any> diagramDrawing;
 
     // retrieve the doms from the GrabBag
@@ -6746,8 +6747,10 @@ void DrawingML::WriteDiagram(const 
css::uno::Reference<css::drawing::XShape>& rX
             diagramDrawing[0]
                 >>= drawingDom; // if there is OOXDrawing property then set 
drawingDom here only.
         }
-        else if (propName == "OOXDiagramDataRels")
-            rProp.Value >>= xDataRelSeq;
+        else if (propName == "OOXDiagramDataImageRels")
+            rProp.Value >>= xDataImageRelSeq;
+        else if (propName == "OOXDiagramDataHlinkRels")
+            rProp.Value >>= xDataHlinkRelSeq;
     }
 
     // check that we have the 4 mandatory XDocuments
@@ -6892,7 +6895,8 @@ void DrawingML::WriteDiagram(const 
css::uno::Reference<css::drawing::XShape>& rX
                           uno::Sequence<beans::StringPair>());
 
     // write the associated Images and rels for data file
-    writeDiagramRels(xDataRelSeq, xDataOutputStream, u"OOXDiagramDataRels", 
nDiagramId);
+    writeDiagramImageRels(xDataImageRelSeq, xDataOutputStream, 
u"OOXDiagramDataRels", nDiagramId);
+    writeDiagramHlinkRels(xDataHlinkRelSeq, xDataOutputStream);
 
     // write layout file
     serializer.set(layoutDom, uno::UNO_QUERY);
@@ -6933,12 +6937,12 @@ void DrawingML::WriteDiagram(const 
css::uno::Reference<css::drawing::XShape>& rX
     // write the associated Images and rels for drawing file
     uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq;
     diagramDrawing[1] >>= xDrawingRelSeq;
-    writeDiagramRels(xDrawingRelSeq, xDrawingOutputStream, 
u"OOXDiagramDrawingRels", nDiagramId);
+    writeDiagramImageRels(xDrawingRelSeq, xDrawingOutputStream, 
u"OOXDiagramDrawingRels", nDiagramId);
 }
 
-void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& 
xRelSeq,
-                                 const uno::Reference<io::XOutputStream>& 
xOutStream,
-                                 std::u16string_view sGrabBagProperyName, int 
nDiagramId)
+void DrawingML::writeDiagramImageRels(const 
uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq,
+                                      const uno::Reference<io::XOutputStream>& 
xOutStream,
+                                      std::u16string_view sGrabBagProperyName, 
int nDiagramId)
 {
     // add image relationships of OOXData, OOXDiagram
     OUString sType(oox::getRelationship(Relationship::IMAGE));
@@ -6996,6 +7000,45 @@ void DrawingML::writeDiagramRels(const 
uno::Sequence<uno::Sequence<uno::Any>>& x
     }
 }
 
+void DrawingML::writeDiagramHlinkRels(const 
uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq,
+                                      const uno::Reference<io::XOutputStream>& 
xOutStream)
+{
+    uno::Reference<xml::sax::XWriter> xWriter
+        = xml::sax::Writer::create(comphelper::getProcessComponentContext());
+    xWriter->setOutputStream(xOutStream);
+
+    // retrieve the relationships from Sequence
+    for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++)
+    {
+        // diagramDataRelTuple[0] => RID,
+        // diagramDataRelTuple[1] => Target
+        // diagramDataRelTuple[2] => Type
+        const uno::Sequence<uno::Any>& diagramDataRelTuple = xRelSeq[j];
+
+        OUString sRelId;
+        OUString sTarget;
+        OUString sType;
+        diagramDataRelTuple[0] >>= sRelId;
+        sRelId = sRelId.copy(3);
+        diagramDataRelTuple[1] >>= sTarget;
+        diagramDataRelTuple[2] >>= sType;
+
+        OUString sRelType;
+        bool bExtURL = true;
+        if (sType == u"slide")
+        {
+            sRelType = oox::getRelationship(Relationship::SLIDE);
+            bExtURL = false;
+        }
+        else
+            sRelType = oox::getRelationship(Relationship::HYPERLINK);
+
+        PropertySet aProps(xOutStream);
+        aProps.setAnyProperty(PROP_RelId, uno::Any(sRelId.toInt32()));
+        mpFB->addRelation(xOutStream, sRelType, sTarget, bExtURL);
+    }
+}
+
 void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& 
rXShape, const awt::Size& aPageSize,
                             const FSHelperPtr& pDrawing)
 {

Reply via email to