include/oox/drawingml/diagram/diagramhelper_oox.hxx |    3 
 include/oox/export/drawingml.hxx                    |    7 ++
 include/svx/diagram/DiagramHelper_svx.hxx           |    3 
 oox/source/drawingml/diagram/datamodel_oox.cxx      |   62 +++++++++++++++++--
 oox/source/drawingml/diagram/datamodel_oox.hxx      |    5 +
 oox/source/drawingml/diagram/diagram.cxx            |   64 ++++++++++++++++----
 oox/source/drawingml/diagram/diagram.hxx            |    2 
 oox/source/drawingml/diagram/diagramhelper_oox.cxx  |    4 -
 oox/source/export/drawingml.cxx                     |   22 ++++++
 oox/source/export/shapes.cxx                        |    8 ++
 oox/source/token/tokens.txt                         |    1 
 11 files changed, 159 insertions(+), 22 deletions(-)

New commits:
commit f2d455d03fd980fa2c92607d66370de28d3d9e60
Author:     Armin Le Grand (collabora) <[email protected]>
AuthorDate: Fri Jan 30 15:11:36 2026 +0100
Commit:     Armin Le Grand <[email protected]>
CommitDate: Fri Jan 30 17:49:29 2026 +0100

    SmartArt: Added export of OOXDiagram
    
    This change adds writing the OOXDiagram SmartArt replacement
    graphic. This makes stuff working better, but still stuff
    missing. With this we have now two of the 4-7 (optional)
    DomTrees used for the Diagram data representation.
    Still deactivated, to use for experiments you will need to
    set ACTIVATE_RECREATE_DIAGRAM_DATADOMS.
    
    Change-Id: Ic6c1108d6cf0037302085b6f22433b199d40ee53
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198435
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <[email protected]>

diff --git a/include/oox/drawingml/diagram/diagramhelper_oox.hxx 
b/include/oox/drawingml/diagram/diagramhelper_oox.hxx
index f75a87752991..b3c9cb3f3cd2 100644
--- a/include/oox/drawingml/diagram/diagramhelper_oox.hxx
+++ b/include/oox/drawingml/diagram/diagramhelper_oox.hxx
@@ -28,6 +28,7 @@
 namespace oox::drawingml {
 
 class Diagram;
+class DrawingML;
 
 // Advanced DiagramHelper
 //
@@ -101,7 +102,7 @@ public:
 
     // check if mandatory DiagramDomS exist (or can be created)
     bool checkMinimalDataDoms() const;
-    void tryToCreateMissingDataDoms(oox::core::XmlFilterBase& rFB);
+    void tryToCreateMissingDataDoms(DrawingML& rOriginalDrawingML);
 };
 
 }
diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx
index 36378c9dbdb4..75a14e6fe8cd 100644
--- a/include/oox/export/drawingml.hxx
+++ b/include/oox/export/drawingml.hxx
@@ -316,6 +316,9 @@ protected:
     /// True when exporting presentation placeholder shape.
     bool mbPlaceholder;
 
+    /// True when DiagramReplacementVisualization is exported
+    bool mbDiagaramExport;
+
     bool mbEmbedFonts = false;
 
     bool GetProperty( const css::uno::Reference< css::beans::XPropertySet >& 
rXPropSet, const OUString& aName );
@@ -376,6 +379,10 @@ public:
     /// The application-specific text exporter callback, if there is one.
     DMLTextExport* GetTextExport() { return mpTextExport; }
 
+    /// get/set mbDiagaramExport
+    void setDiagaramExport(bool bNew) { mbDiagaramExport = bNew; }
+    bool isDiagaramExport() const { return mbDiagaramExport;}
+
     void SetBackgroundDark(bool bIsDark) { mbIsBackgroundDark = bIsDark; }
     /// If bRelPathToMedia is true add "../" to image folder path while adding 
the image relationship
     OOX_DLLPUBLIC OUString writeGraphicToStorage(const Graphic &rGraphic , 
bool bRelPathToMedia = false, GraphicExport::TypeHint eHint = 
GraphicExport::TypeHint::Detect);
diff --git a/include/svx/diagram/DiagramHelper_svx.hxx 
b/include/svx/diagram/DiagramHelper_svx.hxx
index f6591f598236..517bc77699d5 100644
--- a/include/svx/diagram/DiagramHelper_svx.hxx
+++ b/include/svx/diagram/DiagramHelper_svx.hxx
@@ -121,6 +121,9 @@ public:
 
     // access to PropertyValues
     virtual css::uno::Any getOOXDomValue(svx::diagram::DomMapFlag aDomMapFlag) 
const = 0;
+
+    // access to RootShape - the GroupObject used to host this Diagram
+    css::uno::Reference< css::drawing::XShape >& getRootShape() { return 
accessRootShape(); }
 };
 
 }} // end of namespace
diff --git a/oox/source/drawingml/diagram/datamodel_oox.cxx 
b/oox/source/drawingml/diagram/datamodel_oox.cxx
index 2dafea9ff620..1a72c837ce18 100644
--- a/oox/source/drawingml/diagram/datamodel_oox.cxx
+++ b/oox/source/drawingml/diagram/datamodel_oox.cxx
@@ -34,6 +34,7 @@
 #include <oox/token/namespaces.hxx>
 #include <oox/export/drawingml.hxx>
 #include <sax/fastattribs.hxx>
+#include <oox/export/shapes.hxx>
 
 #include <unordered_set>
 
@@ -59,14 +60,63 @@ Shape* DiagramData_oox::getOrCreateAssociatedShape(const 
svx::diagram::Point& rP
     return rShapePtr.get();
 }
 
-void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rFB, 
sax_fastparser::FSHelperPtr& rTarget)
+void DiagramData_oox::writeDiagramReplacement(DrawingML& rOriginalDrawingML, 
sax_fastparser::FSHelperPtr& rTarget)
+{
+    if (!rTarget)
+        return;
+
+    uno::Reference<drawing::XShapes> xShapes(accessRootShape(), 
uno::UNO_QUERY);
+    if (!xShapes.is())
+        return;
+
+    // create an own ShapeExport with the needed target and namespace, mark as 
DiagramExport
+    ::oox::core::XmlFilterBase* pOriginalFB(rOriginalDrawingML.GetFB());
+    ShapeExport aShapeExport(XML_dsp, rTarget, nullptr, pOriginalFB, 
rOriginalDrawingML.GetDocumentType(), rOriginalDrawingML.GetTextExport(), true);
+    aShapeExport.setDiagaramExport(true);
+    const sal_Int32 nCount(xShapes->getCount());
+
+    // write header infos
+    const OUString 
aNsDmlDiagram(pOriginalFB->getNamespaceURL(OOX_NS(dmlDiagram)));
+    const OUString aNsDsp(pOriginalFB->getNamespaceURL(OOX_NS(dsp)));
+    const OUString aNsDml(pOriginalFB->getNamespaceURL(OOX_NS(dml)));
+    rTarget->startElementNS(XML_dsp, XML_drawing,
+        FSNS(XML_xmlns, XML_dgm), aNsDmlDiagram,
+        FSNS(XML_xmlns, XML_dsp), aNsDsp,
+        FSNS(XML_xmlns, XML_a), aNsDml);
+    rTarget->startElementNS(XML_dsp, XML_spTree);
+
+    for ( sal_Int32 i = 0; i < nCount; ++i )
+    {
+        uno::Reference< drawing::XShape > xShape;
+        if ( xShapes->getByIndex( i ) >>= xShape )
+        {
+            if (xShape)
+            {
+                // do *not* write BackgroundShape. MSO has BG infos just as 
fill properties
+                // as part of the OOXData DomTree
+                SdrObject* pTarget(SdrObject::getSdrObjectFromXShape(xShape));
+                if (nullptr == pTarget || getBackgroundShapeModelID() == 
pTarget->getDiagramDataModelID())
+                    continue;
+
+                // write Shape & sub-shapes
+                aShapeExport.WriteShape(xShape);
+            }
+        }
+    }
+
+    rTarget->endElementNS(XML_dsp, XML_spTree);
+    rTarget->endElementNS(XML_dsp, XML_drawing);
+    rTarget->endDocument();
+}
+
+void DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, 
sax_fastparser::FSHelperPtr& rTarget)
 {
     if (!rTarget)
         return;
 
     // write header infos
-    const OUString aNsDmlDiagram(rFB.getNamespaceURL(OOX_NS(dmlDiagram)));
-    const OUString aNsDml(rFB.getNamespaceURL(OOX_NS(dml)));
+    const OUString 
aNsDmlDiagram(rOriginalFB.getNamespaceURL(OOX_NS(dmlDiagram)));
+    const OUString aNsDml(rOriginalFB.getNamespaceURL(OOX_NS(dml)));
     rTarget->startElementNS(XML_dgm, XML_dataModel,
         FSNS(XML_xmlns, XML_dgm), aNsDmlDiagram,
         FSNS(XML_xmlns, XML_a), aNsDml);
@@ -90,7 +140,7 @@ void 
DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rFB, sax_fastpa
         if (xMasterText)
         {
             rTarget->startElementNS(XML_dgm, XML_t);
-            DrawingML aTempML(rTarget, &rFB);
+            DrawingML aTempML(rTarget, &rOriginalFB);
             aTempML.WriteText(xMasterText, false, true, XML_a);
             rTarget->endElementNS(XML_dgm, XML_t);
 
@@ -145,7 +195,7 @@ void 
DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rFB, sax_fastpa
     {
         // if we have the BGShape as XShape, export using a temp DrawingML 
which uses
         // the target file combined with the XmlFilterBase representing the 
ongoing Diagram export
-        DrawingML aTempML(rTarget, &rFB);
+        DrawingML aTempML(rTarget, &rOriginalFB);
         uno::Reference<beans::XPropertySet> xProps(xBgShape, uno::UNO_QUERY);
         aTempML.WriteFill( xProps, xBgShape->getSize());
     }
@@ -159,7 +209,7 @@ void 
DiagramData_oox::writeDiagramData(oox::core::XmlFilterBase& rFB, sax_fastpa
     // for this case where the only relevant data is the 'relId' entry I will 
allow
     // to construct the XML statement by own string concatenation
     rTarget->startElementNS(XML_dgm, XML_extLst);
-    const OUString rNsDsp(rFB.getNamespaceURL(OOX_NS(dsp)));
+    const OUString rNsDsp(rOriginalFB.getNamespaceURL(OOX_NS(dsp)));
     rTarget->startElementNS(XML_a, XML_ext, XML_uri, rNsDsp);
     OUString aDspLine("<dsp:dataModelExt xmlns:dsp=\"" + rNsDsp + "\" ");
     if (!getExtDrawings().empty())
diff --git a/oox/source/drawingml/diagram/datamodel_oox.hxx 
b/oox/source/drawingml/diagram/datamodel_oox.hxx
index 7845ffd5bec2..b3f6eb85fe8a 100644
--- a/oox/source/drawingml/diagram/datamodel_oox.hxx
+++ b/oox/source/drawingml/diagram/datamodel_oox.hxx
@@ -34,6 +34,8 @@
 
 namespace oox::drawingml {
 
+class DrawingML;
+
 class DiagramData_oox : public svx::diagram::DiagramData_svx
 {
 public:
@@ -50,7 +52,8 @@ public:
 
     Shape* getOrCreateAssociatedShape(const svx::diagram::Point& rPoint, bool 
bCreateOnDemand = false) const;
 
-    void writeDiagramData(oox::core::XmlFilterBase& rFB, 
sax_fastparser::FSHelperPtr& rTarget);
+    void writeDiagramReplacement(DrawingML& rOriginalDrawingML, 
sax_fastparser::FSHelperPtr& rTarget);
+    void writeDiagramData(oox::core::XmlFilterBase& rOriginalFB, 
sax_fastparser::FSHelperPtr& rTarget);
 
 private:
     // The model definition, the parts *only* available in oox. Also look for 
already
diff --git a/oox/source/drawingml/diagram/diagram.cxx 
b/oox/source/drawingml/diagram/diagram.cxx
index 70eccc96c93c..704d0b030ed2 100644
--- a/oox/source/drawingml/diagram/diagram.cxx
+++ b/oox/source/drawingml/diagram/diagram.cxx
@@ -44,10 +44,11 @@
 #include "diagramfragmenthandler.hxx"
 #include <comphelper/processfactory.hxx>
 #include <com/sun/star/io/TempFile.hpp>
+#include <oox/export/drawingml.hxx>
 
 #ifdef DBG_UTIL
 #include <osl/file.hxx>
-#include <iostream>
+#include <o3tl/environment.hxx>
 #endif
 
 using namespace ::com::sun::star;
@@ -200,25 +201,23 @@ bool Diagram::checkMinimalDataDoms() const
     return true;
 }
 
-void Diagram::tryToCreateMissingDataDoms(oox::core::XmlFilterBase& rFB)
+void Diagram::tryToCreateMissingDataDoms(DrawingML& rOriginalDrawingML)
 {
     // internal testing: allow to force to always recreate
-    static bool bForceAlwaysReCreate(false);
+    static bool bForceAlwaysReCreate(nullptr != 
std::getenv("FORCE_RECREATE_DIAGRAM_DATADOMS"));
 
     // check if activated, return if not to stay compatible for now
     static bool bReCreateDiagramDataDoms(nullptr != 
std::getenv("ACTIVATE_RECREATE_DIAGRAM_DATADOMS"));
-#ifdef DBG_UTIL
-    std::cout << "DiagramReCreate: always==" << bForceAlwaysReCreate << 
",bReCreate==" << bReCreateDiagramDataDoms << std::endl;
-#endif
+    SAL_INFO("oox", "DiagramReCreate: always==" << bForceAlwaysReCreate << 
",bReCreate==" << bReCreateDiagramDataDoms);
     if (!bForceAlwaysReCreate && !bReCreateDiagramDataDoms)
         return;
 
+    oox::core::XmlFilterBase& rFB(*rOriginalDrawingML.GetFB());
+
     if (bForceAlwaysReCreate || maDiagramPRDomMap.end() == 
maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXData))
     {
         // re-create OOXData DomFile from model data
-#ifdef DBG_UTIL
-        std::cout << "DiagramReCreate: creating DomMapFlag::OOXData" << 
std::endl;
-#endif
+        SAL_INFO("oox", "DiagramReCreate: creating DomMapFlag::OOXData");
         uno::Reference< io::XTempFile > xTempFile = 
io::TempFile::create(comphelper::getProcessComponentContext());
         uno::Reference< io::XOutputStream > xOutput = 
xTempFile->getOutputStream();
 
@@ -243,7 +242,52 @@ void 
Diagram::tryToCreateMissingDataDoms(oox::core::XmlFilterBase& rFB)
             }
 
 #ifdef DBG_UTIL
-            osl::File::move(xTempFile->getUri(), 
"file:///home/alg/Downloads/tonne/test.xml");
+            const OUString 
env(o3tl::getEnvironment(u"DIAGRAM_DUMP_PATH"_ustr));
+            if(!env.isEmpty())
+            {
+                OUString url;
+                ::osl::FileBase::getFileURLFromSystemPath(env, url);
+                osl::File::move(xTempFile->getUri(), url + "data_T.xml");
+            }
+#endif
+        }
+    }
+
+    if (bForceAlwaysReCreate || maDiagramPRDomMap.end() == 
maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXDrawing))
+    {
+        // re-create OOXDrawing DomFile from model data
+        SAL_INFO("oox", "DiagramReCreate: creating DomMapFlag::OOXDrawing");
+        uno::Reference< io::XTempFile > xTempFile = 
io::TempFile::create(comphelper::getProcessComponentContext());
+        uno::Reference< io::XOutputStream > xOutput = 
xTempFile->getOutputStream();
+
+        if (xOutput)
+        {
+            sax_fastparser::FSHelperPtr aFS = 
std::make_shared<sax_fastparser::FastSerializerHelper>(xOutput, true);
+            getData()->writeDiagramReplacement(rOriginalDrawingML, aFS);
+            xOutput->flush();
+
+            // this call is *important*, without it xDocBuilder->parse below 
fails and some strange
+            // and wrong assertion gets thrown in ~FastSerializerHelper that  
shall get called
+            xOutput->closeOutput();
+
+            uno::Reference<xml::dom::XDocumentBuilder> 
xDocBuilder(xml::dom::DocumentBuilder::create(comphelper::getProcessComponentContext()));
+            if (xDocBuilder)
+            {
+                uno::Reference<xml::dom::XDocument> xInstance = 
xDocBuilder->parse(xTempFile->getInputStream());
+                if (xInstance)
+                {
+                    maDiagramPRDomMap[svx::diagram::DomMapFlag::OOXDrawing] 
<<= xInstance;
+                }
+            }
+
+#ifdef DBG_UTIL
+            const OUString 
env(o3tl::getEnvironment(u"DIAGRAM_DUMP_PATH"_ustr));
+            if(!env.isEmpty())
+            {
+                OUString url;
+                ::osl::FileBase::getFileURLFromSystemPath(env, url);
+                osl::File::move(xTempFile->getUri(), url + "drawing_T.xml");
+            }
 #endif
         }
     }
diff --git a/oox/source/drawingml/diagram/diagram.hxx 
b/oox/source/drawingml/diagram/diagram.hxx
index c97c6699693a..8f79c852932e 100644
--- a/oox/source/drawingml/diagram/diagram.hxx
+++ b/oox/source/drawingml/diagram/diagram.hxx
@@ -153,7 +153,7 @@ public:
 
     // check if mandatory DiagramDomS exist (or can be created)
     bool checkMinimalDataDoms() const;
-    void tryToCreateMissingDataDoms(oox::core::XmlFilterBase& rFB);
+    void tryToCreateMissingDataDoms(DrawingML& rOriginalDrawingML);
 
 private:
     // This contains groups of shapes: automatic font size is the same in each 
group.
diff --git a/oox/source/drawingml/diagram/diagramhelper_oox.cxx 
b/oox/source/drawingml/diagram/diagramhelper_oox.cxx
index 42d4af5a9359..593a06a96160 100644
--- a/oox/source/drawingml/diagram/diagramhelper_oox.cxx
+++ b/oox/source/drawingml/diagram/diagramhelper_oox.cxx
@@ -469,12 +469,12 @@ bool DiagramHelper_oox::checkMinimalDataDoms() const
     return mpDiagramPtr->checkMinimalDataDoms();
 }
 
-void DiagramHelper_oox::tryToCreateMissingDataDoms(oox::core::XmlFilterBase& 
rFB)
+void DiagramHelper_oox::tryToCreateMissingDataDoms(DrawingML& 
rOriginalDrawingML)
 {
     if (!mpDiagramPtr)
         return;
 
-    mpDiagramPtr->tryToCreateMissingDataDoms(rFB);
+    mpDiagramPtr->tryToCreateMissingDataDoms(rOriginalDrawingML);
 }
 }
 
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index 8f673cfd49b1..aa1ea00bb173 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -265,6 +265,7 @@ DrawingML::DrawingML(::sax_fastparser::FSHelperPtr pFS, 
::oox::core::XmlFilterBa
     , mpFB(pFB)
     , mbIsBackgroundDark(false)
     , mbPlaceholder(false)
+    , mbDiagaramExport(false)
 {
     uno::Reference<beans::XPropertySet> 
xSettings(pFB->getModelFactory()->createInstance(u"com.sun.star.document.Settings"_ustr),
 uno::UNO_QUERY);
     if (xSettings.is())
@@ -2327,6 +2328,25 @@ void DrawingML::WriteShapeTransformation( const 
Reference< XShape >& rXShape, sa
     bFlipH = bFlipH && !bFlippedBeforeRotation;
     bFlipV = bFlipV && !bFlippedBeforeRotation;
 
+    if (isDiagaramExport())
+    {
+        // need to offset from top-left of Diagram's GroupObject
+        SdrObject* pShape(SdrObject::getSdrObjectFromXShape(rXShape));
+
+        if (nullptr != pShape)
+        {
+            std::shared_ptr< svx::diagram::DiagramHelper_svx > 
pDiagramHelper(pShape->getDiagramHelperFromDiagramOrMember());
+
+            if (pDiagramHelper)
+            {
+                const Reference< XShape >& 
rRootShape(pDiagramHelper->getRootShape());
+                awt::Point aRootPos = rRootShape->getPosition();
+                aPos.X -= aRootPos.X;
+                aPos.Y -= aRootPos.Y;
+            }
+        }
+    }
+
     if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is())
     {
         awt::Point aParentPos = m_xParent->getPosition();
@@ -6793,7 +6813,7 @@ bool DrawingML::PrepareToWriteAsDiagram(const 
css::uno::Reference<css::drawing::
         return false;
 
     // try to re-create (if needed is decided there)
-    pAdvancedDiagramHelper->tryToCreateMissingDataDoms(*GetFB());
+    pAdvancedDiagramHelper->tryToCreateMissingDataDoms(*this);
 
     if (!pAdvancedDiagramHelper->checkMinimalDataDoms())
         return false;
diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx
index 29dba9826903..27947ec75e61 100644
--- a/oox/source/export/shapes.cxx
+++ b/oox/source/export/shapes.cxx
@@ -912,6 +912,14 @@ ShapeExport& ShapeExport::WriteCustomShape( const 
Reference< XShape >& xShape )
                 }
             }
 
+            if (isDiagaramExport())
+            {
+                // add evtl. used DiagramDataModelID if DiagramExport
+                SdrObject* pTarget(SdrObject::getSdrObjectFromXShape(xShape));
+                if (nullptr != pTarget && 
!pTarget->getDiagramDataModelID().isEmpty())
+                    pAttrListSp->add(XML_modelId, 
pTarget->getDiagramDataModelID());
+            }
+
             // export <sp> element (with a namespace prefix)
             mpFS->startElementNS(mnXmlNamespace, XML_sp, pAttrListSp);
         }
diff --git a/oox/source/token/tokens.txt b/oox/source/token/tokens.txt
index 1eb24e1b9a3b..3ffcbc9a3956 100644
--- a/oox/source/token/tokens.txt
+++ b/oox/source/token/tokens.txt
@@ -2024,6 +2024,7 @@ dropLine
 dropLines
 dropauto
 ds
+dsp
 dstNode
 dstrike
 dt

Reply via email to